“One man’s constant is another man’s variable”
-Alan Perlis, Epigrams on Programming, 1982
To kick off our C series, I figured we should go over one of the most common pieces of language design there is: variables. Variables initially got their start in Mathematics as many pieces of language design did. At their core, variables are not complicated. They’re essentially giving a name to some kind of value like A = 5, B = “hello” or C = 1/2. However, with programming in general this can become more complex. In time we will get to more abstract usage of variables, but for now we need to cover the basic types in C.
In C, C++, Java, and many other explicit languages the programmer will have to specify what data type for a variable. This means that they must specify if what they’re feeding the variable is a number, a letter, a piece of memory, and more. C has a number of different data types with their own purpose:
|int||A number of some kind: …, -50, -1, 0, 1, 50, …||Takes up 2-4 bytes of memory, 16 (2*8) to 32 (4*8) bits.Ranges from -32,768 to 32,767 or −2,147,483,648 to 2,147,483,647|
|char||A character of some kind: ‘a’, ‘b’, ‘A’, ‘B’||Takes up 1 byte of memory, 8 (1*8) bits.Ranges from 0 to 255.|
|float||A decimal number: 1.000000, 2.123456, 3.999999||Takes up 4 bytes of memory, 32 (4*8) bits.Ranges from 1.2E-38 to 3.4E+38Allows up to 6 decimal places of accuracy.|
|double||A decimal number: 1.000000000000000, 2.123456789123456, 3.999999999999999||Takes up 8 bytes of memory, 64 (8*8) bits.Ranges from 2.3E-308 to 1.7E+308Allows up to 15 decimal places of accuracy.|
|void||Typeless data, can refer to memory location in certain circumstance (explained later).||N/A, 1 byte, 8 (1*8) bits with some flags.void does not have an actual size.|
|short||A number of some kind: …, -50, -1, 0, 1, 50, …||Takes up 2 bytes of memory, 16 (2*8) bits.Ranges from -32,768 to 32,767.|
|long||A number of some kind: …, -50, -1, 0, 1, 50, …||Takes up 4 bytes of memory, 32 (4*8) bits.Ranges from −2,147,483,648 to 2,147,483,647.|
Each of these variables have their own unique uses which we will see through the course of this tutorial. The byte specifics are not important at the moment, but they will be very useful when we begin to cover memory management. However, this is not the end of the variable types. Variable modifiers also exist within C which can cause the above variables to change properties. First, we need a quick crash course in binary before we can move on to the variable modifiers.
Binary Crash Course
For an integer (int), there are anywhere from 2 to 4 bytes depending on the compiler that the code is compiled with and what system the code is run on. Assuming an integer has 4 bytes, that means we have 32 (4*8) bits.
32 bit integer:
With binary, we count from right to left and we add powers of two as we go. For example:
0000 = 0
0001 = 2^0 = 1.
0010 = 2^1 = 2.
0100 = 2^2 = 4.
1000 = 2^3 = 8.
1111 = 2^3 + 2^2 + 2^1 + 2^0 = 8 + 4 + 2 + 1 = 15.
Not all bits contribute the same way to the final number when it comes to computing. Here we introduced the concept of signed and unsigned values. To tell if a number is negative the most significant bit (farthest left bit) will be 1 (negative) or 0 (positive). If the most significant bit is 1, then we count all 0s as if they were 1s and vice versa and subtract 1 from the total value. This means if our example above was signed it would be the following:
0000 = 0
0001 = 2^0 = 1.
0010 = 2^1 = 2.
0100 = 2^2 = 4.
1000 = -1 * (2^2 + 2^1 + 2^0) – 1 = -1 * (4 + 2 + 1) – 1 = -8.
1111 = -1 * 0 – 1 = -1.
With this we’re now able to express negative numbers, but we run into a big issue: our range has been cut down from 0 – 15 to -8 – 7. Our original form where our range was from 0 to 15 is referred to as unsigned and our new form where the range was from -8 to 7 is referred to as signed. In C, integers, shorts, and longs are all signed unless specified otherwise.
Applying these steps to our 4 byte integer, we can show that the maximum value is:
01111111111111111111111111111111 = 2,147,483,647
and the minimum value is:
10000000000000000000000000000000 = −2,147,483,648
Now that we know this, we can move on to variable modifiers.
Back to modifiers
In C there are 4 type modifiers: unsigned, signed, long, and short. While long and short can be used as their own data types shown above, they can also be used to be explicit about the format of some data (long long, short short) or they can modify another data type (long int, short int). Note that not all combinations of these modifiers and the above data types exist such as long/short void.
|unsigned X||Do not allow negative numbers, use full binary range.|
|signed X||Allow negative numbers, cut down binary range.|
|long X||Extend to 64 bits (8 bytes)|
|short X||Shrink to 16 bits (2 bytes)|
These are all of the major data types and type modifiers, but with these types a programmer can create more abstract types such as structs, unions, and classes (C++) which we will discuss later. For now, let’s take a look at an example of some of these variables in action. Shown below is an example written in C to demonstrate assigning values to variables of these datatypes and measuring the size of them in bytes. We’ll talk more about the printf function and its syntax in the next part.
We get the values that we assigned like a = 5, b = ‘b’, and so on. Also, since we printed the size of each variable, we were able to figure out the size in bytes according to GCC. Variables are a key tool in any programming language and they will only get more complex from here. In future parts we will discuss operators on variables like * and & which can drastically change how variables act. For now we’ll move on to operators and demonstrate the different major operators.
C has a large amount of operators that are usually divided into few categories: Arithmetic, Bitwise, Logical, Memory, Comparison, Assignment, and Incremental.
|+||Arithmetic||Adds two values|
|–||Arithmetic||Subtracts two values|
|*||Arithmetic||Multiplies two values|
|/||Arithmetic||Divides two values|
|%||Arithmetic||Gets the remainder of two values|
|^||Bitwise||XORs two valuesFor every bit in each value, the new value will have a 1 if one of the bits is 1, but not both (exclusive OR).A = 1011, B = 1110, A ^ B = 0101|
|&||Bitwise||ANDs two valuesFor every bit in each value, the new value will have a 1 if both bits are a 1.|
||||Bitwise||ORs two valuesFor every bit in each value, the new value will have a 1 if one of the bits is 1.|
|~||Bitwise||NOTs a valueFor every bit in the value, flip the bit to 0 if it is 1 and vice versa.|
|>>||Bitwise||Right shifts a valueFor every bit in the value, shift it right that many places.A = 1000, A >> 2 = 0010.|
|<<||Bitwise||Left shifts a valueFor every bit in the value, shift it left that many places.A = 0001, A << 2 = 0100.|
|!||Logical||Negation operatorInverses a particular value such as a 0 –> 1 or 1 –> 0.|
|&&||Logical||Logical ANDEvaluates if two variables are both 1 (true).|
|||||Logical||Logical OREvaluates if one variable is 1 (true).|
|&||Memory||Reference operatorGet the address of a variable.|
|*||Memory||Star / Pointer operatorPoint to a location in memory.|
|>=||Comparison||Greater than or equal|
|<||Comparison||Less than or equal|
|?||Comparison||Conditional / Ternary operatorIf a condition is met, assign one of two values.A = (B>C ? B : C)A = B if B>C is true and A = C is B>C is false.|
|=||Assignment||Assign a value|
|+=||Assignment||Add and assign a valueA = 1, B = 2, A += B, A == 3|
|-=||Assignment||Substract and assign a valueA = 2, B = 1, A -= B, A == 1|
|*=||Assignment||Multiply and assign a valueA = 1, B = 2, A *= B, A == 2|
|/=||Assignment||Divide and assign a valueA = 4, B = 2, A /= B, A == 2|
||=||Assignment||OR and assign a valueA = 4, B = 2, A |= B, A == 6|
|&=||Assignment||AND and assign a valueA = 4, B = 2, A &= B, A == 0|
|^=||Assignment||XOR and assign a valueA = 4, B = 2, A ^= B, A == 6|
|%=||Assignment||Modulo and assign a valueA = 4, B = 2, A %= B, A == 0|
|++||Incremental||Increments a value.A = 5, A = A++, A == 6|
|−−||Incremental||Decrements a value.A = 5, A = A–, A == 4.|
We’ll be able to see all of these in use later over some of the example projects. Although, just because C offers this vast amount of operators, it is not uncommon to find C programmers who have not used every one of these operators.