Other types of variables operations and operators in C
Other types of variable operations
Hexadecimals and octals
You may assign integer data types hexadecimal or octal values. To work with hexadecimals you must start the number starting with the value 0x, and to work with octals, all you have to do is start the number with a 0 beforehand:
int hex = 0x222;
int oc = 0532;
printf("hex to decimal: %d \n", hex);
printf("octal to decimal: %d \n", oc);
Its possible to change your decimal integer representation also, by simply changing the output format of your printf() command:
Operators
If you are coming from another programming language background, you probably be at home working with C operators. You may use all the traditional arithmethic operators:
// + operator will add two operands
int a = 3 + 4;
// - operator will subtract two operands
int b = 10 - 4;
// / operator divides two operands
int c = 15 / 3;
// * operator multiplies two operands
int d = 7 * 3;
// % operand will return you the remainder of an integer division
int e = 16 % 4;
printf("%d %d %d %d %d \n", a, b, c, d, e);
You may very well use these operators with your variables instead of loose values:
int mult = a * b;
printf("mult: %d \n", mult);
Shift operations
Shift operations will get the binary representation of your declared value, and shift the bits to left or right, by the amount you determine. You probably gonna need this operation very often to read or understand assembly code. We will get into deeper details about binary when learning about assembly debuging but here is an small example of how binary numbers works:
0 1 = 1 - means 2 to the 0
1 0 = 2 - means 2 to the 1
1 0 0 = 4 - means 2 to the 2
1 0 1 = 5 - means 2 to the 2 + 2 to the 0
1 0 0 0 = 8 - means 2 to the 3
You may have a little bit of a hard time if this is the first time you are taking a look at binary. However in fact, all your computer see is binary! It converts these binary operations to values you and me can understand. Here is an example of how you might see a multiply operation or power of operation:
int x = 2; //binary representation is: 1 0
printf("x: %d \n", x);
int y = x << 2; //shifting 1 0 to the left by 2, we get the value 1 0 0 0
printf("x << 2: %d \n", y);
But you may use these operations to divide your numbers by a power of 2 for example:
int z = y >> 1;
printf("y >> 1: %d \n", z);
Thats what you will most likely see in many multiply or divisions operations while debugging using your favorite assembly debugger or even taking a deeper look into your own code trough visual studio debugger.
Unary operators
Unary operators are operators which take only one operand. Our unary operators are:
! operator will produce a negation of its operand.If your operand is true(greater than 0) then ! is false (0), and if it is false, its result will be true.
~ operator will change the value of each bit of the binary value of the operand, and invert. Let me give a little explanation so you can comprehend a little better.
In binary, as you have seen in shift operations, you have one bit representing one power of 2. So lets take the number 4, it will have its binary representation as 1 0 0, since our representation of 2 to 0 is 0, 2 to 1 is 0 and 2 to 2 has the bit set to 1(true). However, lets say you are working with signed values. You will have then one positive and one negative value, and there must be a representation for each in binary also. Saying you are working with a 32 bit system then, your representation of 4 will be 00000100. Where the sign? All the left bits beyond the most significant value of the power of 2 represent the sign! 0 will be positive and 1 negative in 2 complement. So the value of -4 will be 11111100.
Now, back to our operator ~, lets suppose we continue with number 4, which has its binary representation 00000100. If we apply your ~ operator, its value will become 11111011. How to interpret this value? the first 1111 means the signal of the most significant value, which are then added with the least significant values. The most significant bit is signaled as 8 (1000) in its negative form(1111) plus 2 to 0, plus 2 to 1. -8 + 3 = -5! Confused a little? Lets take a look at the values -6 and -7. To -6, first we set the signal bits (1111), then the value of the the most signficant bit to 8 (1000 which is 2 to the 3), next we set the bit of the arithmetic to be performed (0010 since -8 +2 = -6). Now lets try the representation of -7. First the sign 1111, now the most significant bit 1000 and finally the arithmetic 0001, concluding with -8 + 1 = -7. Confusing isnt it? Well to this operator actually, all you must know is, it will invert the bytes of its operand in fact, and we will get deeper into these hidden details while taking a look at binary to understand debugging.
-
operator will create a negative result of its operand
+
operator will do nothing, even if it is assigned to a negative value, if you use it as unary operator(one operand), being provided only as means of simmetry.
& operator, as we have seen before, will create the memory adress of its operand,for situation where you have to work with pointers for example.
*
in unary is the operator of indirection, means you are dereferencing something. Not to be confused with the the multiply operator, in unary you apply it to the operand, lets say a point, like in *x, and it will go the memory adress stored in x and get whatever is inside there.
sizeof operator will return the value of your operand in bytes. It is very often used to determine the size whenm allocating memory in operations like malloc, instead of writing the raw value in bytes and doing the math yourself for example. Its optional to use this operator in an datatype itself:
char x = -128;
char y = sizeof(x);
char z = sizeof(char);
printf("%d \n", y);
printf("%d \n", z);
(type) operator is called casting, which will convert one datataype to another, like converting a char to an integer. Be careful, in general you will have no problem casting one smaller datatype, to a larger datatype like a char to an integer:
char x = 12;
int y = (int)x;
However, if you are doing the opposite, converting one larger type to a smaller, you may truncate the value, and get weird values:
int x = 1222;
char y = (char)x;
printf("%d \n", y);
In such situations, your compiler will get the binary value and try to fit into the smaller datatype, which is responsible for the result you would be getting.
++ is an increment operator. It comes in 2 flavours, prefix and postfix:
int x = 45;
++x; //prefix
x++; //postfix
Relational Operators
As the name says, relational operators will test relations between their operands. Those are:
>
greater than
>
= greater equal than
< lower than
<= lower equal than
!= not equal
== equal
You must be very careful while evaluating an expression to not use = operator to assign a value instead of check if its equal than anoter value. Its easy to get confused in operations like this:
if (x = 5) do something;
In this case it will always evaluated the expression to true, it will in fact assign 5 to the variable x, and since 5 is greater than 0, which is false, in reality you must evaluate like this:
if (x == 5) do something;
Now if your x variable is different (!=) than 5, it will return 0 and not proceed with the evaluation.
Logical operators
C logical operators are && and ||, which mean and and or respectively. The result of these are 1(if true) or 0(if false). && operators return true if both its statments are true. || return true if only 1 of its statments is true.
The logical operators have lower precedence that the > or < operators, so you may very well declare your expression like this:
int a = 5 > 4 && 3 < 7;
and obtain the same results.
Bitwise operators
Bitwise operators may look somewhat like logical operators, being abble to perform operations on the bits of the operands. The bitwise operators are &, | and ^, which means in order, AND, OR and XOR.
The &(and) operator will set the result bit to 1(true), only if both bits of the operands are also set to 1. Lets take the example of number 2 and 1, which have the bit representation of 1 0 and 0 1:
1 0
&
0 1
-----
0 0
Using some code as sample:
int x = 2;
int y = 1;
int and = x & y;
printf("and: %d \n", and);
The |(or) operator will set the result bit to 1, if any of the operands is also set to 1. Using the same example as before, this is what happens when doing a | bitwise operation:
1 0
|
0 1
-----
1 1
Now, using some code:
int x = 2;
int y = 1;
int or = x | y;
printf("or: %d \n", or);
The ^(xor) operator will set our result bit to 1 only if the bits operands are different, and will produce a result of 0 if both of them are 1 or 0. Lets take as example of the operands, the values 2 ^ 2:
1 0
^
1 0
-----
0 0
and finally, with some code demonstration:
int x = 2;
int xor = x ^ x;
printf("xor: %d \n", xor);
What we get from our 3 samples:
You may be wondering, in which ways you must use such operations? Actually you would be intrigued by the many creative ways programmers and and computers use them. Lets say you are debugging assembly code, you will very often work with registers (for the sake of visualization, just imagine a register is a variable for now, altough they have fixed names), and this same assembly code will not just move a numeric value 0 when it wants to remove any value contained within. Instead of doing mov eax, 0(eax = 0) instruction, it will do instead a xor eax, eax, like our previous example above with the XOR operator, as means to optimize your code, it will xor itself!
Compound assignments
Compound assignments work performing the operation with your operand. however you can save code by using them, instead of typing:
x = x + 1;
You may just type:
x += 1;
which you can read, "add 1 to x". Back in time, those used to give a little better performance to your code, however today, chances are your compiler will optimize the underneath code for you. The compound assignments operators are:
I left them to the end, so you already familiar with the other operands. They will perform the same as the compound add will, except the operation will change, according to the compound assignment operator you choose.
Altough it doenst make a dramatic difference to compilers today, it may help a lot the programmer, lets say in complex operations like:
x [5*(m + 10/y(5))] = x [5*(m + 10/y(5))] + 5;
You may just create more compact code by:
x [5*(m + 10/y(5))] += 5;
Aint it helpful? :D