C programming language provides several operators to perform different kind to operations. There are operators for assignment, arithmetic functions, logical functions and many more. These operators generally work on many types of variables or constants, though some are restricted to work on certain types. Most operators are binary, meaning they take two operands. A few are unary and only take one operand.
Operators can be classified based on the type of operations they perform.
C Arithmetic Operators
Arithmetic operators include the familiar addition (+), subtraction (-), multiplication (*) and division (/) operations. In addition there is the modulus operator (%) which gives the remainder left over from a division operation. Let us look at some examples:
#include <stdio.h>
void main()
{
int a = 100;
int b = 3;
int c;
c = a + b;
printf( "a + b = %dn", c );
c = a - b;
printf( "a - b = %dn", c );
/* multiplication performed before call to printf() */
printf( "a * b = %dn", a * b );
c = a / b;
printf( "a / b = %dn", c );
c = 100 % 3;
printf( "a % b = %dn", c );
}
Note – Though the usual definition of the main() function is int main(), few compilers allow the main() function to return void. The C standard allows for implementation defined versions of main() that do not return int. If your compiler does not allow that, change line 3 to int main() and put a return statement return 0; at the end just before line 23.
This program gives the following output:
a + b = 103
a – b = 97
a * b = 300
a / b = 33
a % b = 1
In this example you can see on line 16 that it is not always necessary to assign the result of an operation to a variable. The multiplication is performed first, then the result is passed to the printf() function. On line 21 we see an example of using an operator on constants, with the 100 and 3 replacing the variables a and b.
If the modulus operator on line 21 is new to you, it is the remainder left over when one integral number is divided by another. For example 45 % 6 is 3 since 6 * 7 is 42 and the remainder is 3. The modulus operator works only with integral types (char, short, int, etc.). If you try to use it with other types such as a float or double the compiler will give an error.
So far we have only used integers in our examples. When you use an operator on
mixed types they will be implicitly converted into a common type before the
operation is performed. The result of the operation will also be in the common
type. The common type is derived using a few rules, but generally, a “smaller”
operand is converted to the “larger” operand’s type. If the result of the
operation is assigned to another variable, the result is converted from the
common type to the type of that variable. Let us look at some examples:
#include <stdio.h>
void main()
{
int a = 65000;
char c = 120;
int iresult;
char cresult;
iresult = a + c;
printf( "a + c = %dn", iresult );
cresult = a + c;
printf( "a + c = %dn", cresult );
}
The output is:
a + c = 65120
a + c = 96
On the system where this example was run, an int type is a signed 32 bit number, and a char type is a signed 8 bit number. When the a + c operation is performed, c is converted to an int and then added to a, resulting in an another int.
On line 10 this integer is assigned to iresult, which is also an int so no conversion is necessary.
On line 13 the result is assigned to a char, which means the int result must be converted into a char. The binary representation of 65120 is 1111111001100000. Truncating this to just the first 8 bits (since a char is 8 bits on this machine) gives 01100000 which is 96 in decimal.
Another example:
#include <stdio.h>
void main()
{
int a = 4;
float b = 3.14159;
int iresult;
float fresult;
fresult = a * b;
printf( "a * b = %fn", fresult );
iresult = a * b;
printf( "a * b = %dn", iresult );
}
The output is:
a * b = 12.566360
a * b = 12
A similar process happens in this example. The integer a is converted to a float since a float is capable of holding integers but not vice-versa. The result of the multiplication is a float, which must be converted back to an int on line 13. This is done by truncating the fractional part of the float.
This conversion process happens for constants too.
#include <stdio.h>
void main()
{
double d = 15 / 6;
printf( "15 / 6 is %fn", d );
d = 15.0 / 6;
printf( "15 / 6 is %fn", d );
}
The output is:
15 / 6 is 2.000000
15 / 6 is 2.500000
On line 5 both the constants are integers, so an integer division is performed and then the result converted to a double. On line 8 the 15.0 is a double constant which forces the division to be done using double precision floating point numbers, and then the result assigned to d.
C Relational Operators
Relational operators compare operands and return 1 for true or 0 for false. The following relational operators are available:
Symbol Meaning
< Less than
> Greater than
<= Less than or equal to
>= Greater than or equal to
== Equal to
!= Not equal to
Here is an example of how to use these operators. This program is a simple number guessing game:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void main()
{
int mynum;
int guess = -1;
char buffer[64];
srand( time(0) );
mynum = rand() % 100 + 1;
printf( "I have picked a number between 1-100.n" );
while( guess != mynum )
{
printf("Enter your guess: ");
if ( fgets( buffer, 64, stdin ) == NULL )
break;
guess = atoi( buffer );
if ( guess >= 101 || guess <= 0 )
{
printf("Your guess is out of bounds.n");
continue;
}
if ( mynum > guess )
printf("Your guess is too low.n");
else if ( mynum < guess )
printf("Your guess is too high.n");
}
if ( guess == mynum )
printf( "You guessed correctly.n" );
}
The output from a typical run is shown here (user entered values in red):
I have picked a number between 1-100.
Enter your guess: 300
Your guess is out of bounds.
Enter your guess: 50
Your guess is too high.
Enter your guess: 25
Your guess is too low.
Enter your guess: 37
Your guess is too high.
Enter your guess: 31
Your guess is too high.
Enter your guess: 28
Your guess is too high.
Enter your guess: 26
You guessed correctly.
When the program is first run, the condition in the while loop on line 16 is satisfied because guess was initialized to -1 on line 8 and lines 11-12 initialize mynum with a random number between 1 and 100. Lines 18-21 tell the user to enter their guess and read the input from the user.
The if statement on line 23 uses the or operator || which will be covered later – it basically says if the user enters a number greater than or equal to 101 or less than or equal to 0 then tell the user the guess is invalid. The program will keep looping until the user guesses the right value or there is an error reading input from the user (the break statement on line 20 breaks out of the loop in that case).
You can compare values of different types. Just like with arithmetic operations, the operands are converted to a common type and then compared.
#include <stdio.h>
void main()
{
double dnum = 5.1;
if ( ( dnum > 5 ) == 1 )
printf( "%lf > 5n", dnum );
else
printf( "%lf <= 5n", dnum );
}
The output of this program is:
5.100000 > 5
The integer constant 5 on line 7 is converted to a double and compared with dnum. The comparison returns the integer 1 for true, which is then compared for equality with the integer 1. This is a convoluted example just to show comparison of different types and that the relational operators return an integer result. Line 7 could have been written simply as if ( dnum > 5 ).
C Logical Operators
The logical operators are || for a logical or and && for a logical and and ! for logical not. The and and or operate on two boolean values of true or false. In C, an expression that evaluates to 0 is false and an expression that evaluates to non-zero is true. These operators return 1 for true or 0 for false.
The following table shows the results of applying these operators to different boolean values
Left Operation Right Result
True && True True
False && True False
True && False False
False && False False
True || True True
False || True True
True || False True
False || False False
As you can see from the table, an and operation is true only when both its operands are true. An or operation is true if either one of its operands is true.
The table also shows that these operators can do what is called “lazy evaluation” or “short circuit evaluation”. The operands are evaluated from left to right. For an and operation, if the left operand is false, the right operand is not even evaluated because the result is known to be false. For an or operation, if the left operand is true the right operand is not evaluated because the result will be true.
Here is a simple example to show the basic use of these operators:
#include <stdio.h>
#include <stdlib.h>
void main()
{
char buffer[64];
int a;
printf("Enter an integer from 1 to 10: ");
fgets( buffer, 64, stdin );
a = atoi( buffer );
if ( a > 4 && a < 8 )
{
printf( "The number is 5, 6 or 7n" );
}
else if ( a <= 2 || a >= 9 )
{
printf( "The number is 1, 2, 9 or 10n" );
}
else
{
printf( "The number is 3, 4 or 8n" );
}
}
The output from a couple of runs of this program follow:
Enter an integer from 1 to 10: 7
The number is 5, 6 or 7
Enter an integer from 1 to 10: 9
The number is 1, 2, 9 or 10
On the first run a is 7. The relational operators on line 13 then become 7 > 4 && 7 < 8 which reduces to true and true, so the whole statement evaluates to true and the printf on line 15 is run.
On the second run a is 9. The first condition on line 13 a > 4 is true, but the second condition a < 8 is false, which makes the statement false.
Execution continues on line 17 with the first condition a <= 2 being false but the second condition a >= 9 is true. Since line 17 is an or operation, the whole statement is true and the printf on line 19 is run.
It is possible to nest operations arbitrarily deep. For example you could have some code like this:
if ((( a == 3 ) || ( b >= c )) && ( d < a ))
The a == 3 would be evaluated first. If that is false then b >= c would be evaluated. If either of those comparisons are true then d < a is evaluated.
Here is an example demonstrating short circuit evaluation:
#include <stdio.h>
int func( const char * op )
{
printf("Running func() for %s operationn", op);
return 1; /* returning true */
}
void main()
{
int input;
printf("Enter t for true or f for false: ");
input = getchar();
input == 't' && func("AND");
input == 't' || func("OR");
}
The output from a couple of runs of this program:
Enter t for true or f for false: t
Running func() for AND operation
Enter t for true or f for false: f
Running func() for OR operation
Lines 17 and 19 in this program are a little unusual. They perform logical operations but the results of the operations are discarded. The func() function just prints out a line and returns true.
On the first run input is ‘t’. On line 17 the first condition input == ‘t’ is true so the func() function is called. This is necessary because for an and operation both left and right operands must be true for the statement to be true.
On line 19 the first condition is again true. But since line 19 is an or operation and the left operand is true the statement evaluates to true without having to call func().
On the second run input is ‘f’. On line 17 the first condition is false and therefore the whole and statement evaluates to false without calling func(). On line 19 the first condition is false, but the or statement still could evaluate to true if the second condition is true, so func() is called.
The logical not operator is a unary operator, it takes one operand after the ! symbol. It inverts the logical value of the operand – a true value becomes false, and a false value becomes true. Remember that in C a non-zero value is true and zero is false.
Therefore, these lines:
int a = -97;
int b = !a;
will set b to 0 because -97 is non-zero which is true, and a logical not of true is false which is represented as 0. You can invert the logical values of other comparisons as well like this:
if ( ! ( a < 0 && b > k ))
{
/* do something… */
}
If you know boolean logic you know this is the equivalent of saying if ( a >= 0 || b <= k ).
{mospagebreak title=C Assignment Operators}
C Assignment Operators
C has several assignment operators available – one simple assignment operator and several convenience assignment operators that combine arithmetic or bitwise operations with assignment. The operators are as follows:
Symbol Meaning
= Assignment
*= Multiply and assign
/= Divide and assign
%= Modulo and assign
+= Add and assign
-= Subtract and assign
<<= Bitwise left shift and assign
>>= Bitwise right shift and assign
&= Bitwise AND and assign
^= Bitwise XOR and assign
|= Bitwise OR and assign
The variable to be assigned to is the left operand. The right side is evaluated and its type converted to the type of the variable on the left and stored in the variable. The assignment operators return the value that was stored in the variable, making it possible to assign multiple variables on a single line.
The convenience assignment operations let you abbreviate a statement like a = a * b to a *= b. Here are some examples:
#include <stdio.h>
void main()
{
int a = 8.3;
float b = 1.343f;
int c;
printf( "a = %d, b = %fn", a, b );
a += 2;
b *= a;
printf( "a = %d, b = %fn", a, b );
a %= 4;
b -= 0.43;
printf( "a = %d, b = %fn", a, b );
a = c = 5;
printf( "a = %dn", a );
a <<= c - 3;
printf( "a = %dn", a );
a &= c;
printf( "a = %dn", a );
}
The output of this program is:
a = 8, b = 1.343000
a = 10, b = 13.430000
a = 2, b = 13.000000
a = 5
a = 20
a = 4
The double 8.3 constant on line 5 is converted to an int and then stored in a. On line 15 a is assigned to the remainder left over when a is divided by 4. Line 19 shows multiple assignments on a single line. The c = 5 is done first, then the value of c (which is now 5) is assigned to a.
Bitwise operations (lines 22 and 25) will be covered later but here is a brief explanation. At line 22 the c – 3 operation gives 2, so the statement becomes a <<= 2. The a variable at that point is 5 (101 in binary), which when left shifted by 2 becomes 20 (10100 in binary). The bitwise and operation on line 25 keeps only the 1 bits that are common to both numbers. So a ( 20 decimal, 10100 binary ) bitwise and with c ( 5 decimal, 101 binary ) results in 4 ( 100 binary ), which is then stored in a.
C Increment and Decrement Operators
The increment (++) and decrement (–) operators simply add 1 or subtract 1 from a variable and return the value of that variable. These are unary operators meaning they work on only one operand, which is the variable to modify.
There are two ways to use these operators – prefix or postfix. That means you can put the operator in front of the variable like ++a or behind the variable like b–. However, they have slightly different behaviour. In prefix notation the variable is incremented or decremented first, then the new value is returned. In postfix notation the variable’s original value is returned, and the addition or subtraction happens later.
Let us see some examples:
#include <stdio.h>
void main()
{
int a = 7;
int b;
++a; /* a is now 8 */
a++; /* a is now 9 */
b = a++;
printf( "a = %d, b = %dn", a, b );
b = ++a;
printf( "a = %d, b = %dn", a, b );
float f = 4.05;
float pi = --f + 0.09159;
printf( "f = %f, pi = %.5fn", f, pi );
}
Here is the output:
a = 10, b = 9
a = 11, b = 11
f = 3.050000, pi = 3.14159
Lines 8 and 9 show using prefix and postfix notation, and since these lines consist only of the increment operations, there is no difference between them. On line 10, using the postfix increment operator, b is assigned the value of a before a is incremented. The increment happens at the end of the statement – you can think of it as happening at the ‘;’ mark.
More precisely, C has places called “sequence points” at which side effects of operations like the postfix ++ and – are processed, and the end of a statement is one of those places. In the first printed line you can see that b has the value of a before it was incremented.
On line 13 the prefix increment operator causes a to be incremented first, then that value assigned to b. The second printed line shows that both a and b have the same value.
Lines 16-17 show that you can use these operators with real number variables as well, and you can combine these operators with others. Be careful writing complicated statements with postfix operators, they might not behave as you expect.
{mospagebreak title=C Conditional Operator}
C Conditional Operator
The conditional operator is unusual in that it takes three operands. The syntax of this operator is like this:
Condition ? Expression1 : Expression2;
You can think of the conditional operator as if it were a function that works like this:
if ( Condition )
return Expression1;
else
return Expression2;
The Condition expression must evaluate to true or false. If it is true Expression1 is evaluated and its value is returned. If Condition is false Expression2 is evaluated and its value is returned. Both Expression1 and Expression2 must be the same type (or convertible to the same type).
Here is an example:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void main()
{
int rndnum;
srand( time(0) );
rndnum = rand();
double d = ( rndnum > RAND_MAX/2 ) ? 12.23554: -34.339;
printf( "rndnum = %d, RAND_MAX/2 = %d, d = %fn", rndnum, RAND_MAX/2, d );
char * evenodd = ( rndnum % 2 == 0 ) ? "even" : "odd";
printf( "random number %d is an %s numbern", rndnum, evenodd );
}
The output of a couple of runs of this program:
rndnum = 1814423962, RAND_MAX/2 = 1073741823, d = 12.235540
random number 1814423962 is an even number
rndnum = 461441507, RAND_MAX/2 = 1073741823, d = -34.339000
random number 461441507 is an odd number
Lines 9-10 generate a random number. Line 12 sets d to 12.23554 if rndnum is
in the upper half of the range of the rand() function. Otherwise d is set to
-34.339. Line 16 sets the evenodd string pointer to “even” or “odd” depending on
rndnum’s value.
Just like if statements, it is possible to nest conditional operators. You could
have statements like the following:
x = ( y > 3 ) ? ( x < y ) ? y : x + 10 : ( y < 0 ) ? -y : y + 10;
This is equivalent to assigning x to a function that works like this:
if ( y > 3 )
{
if ( x < y )
return y;
else
return x + 10;
}
else
{
if ( y < 0 )
return -y;
else
return y + 10;
}
Though it is possible to nest these operations, generally it should be avoided as it makes the code harder to understand which can lead to bugs.
{mospagebreak title=C Bitwise Operator}
C Bitwise Operators
C’s bitwise operators work on the bits stored in integral types. They work similar to the logical operators except that instead of working on true and false values they work with ones and zeroes. There are several bitwise operators available:
Symbol Meaning
~ Complement
& And
| Or
^ Exclusive-Or
<< Left shift
>> Right shift
The complement operator is a unary operator. The other bitwise operators are binary, taking two arguments. Here is a quick reminder of how binary operations works:
Left Operation Right Result
N/A ~ 1 0
N/A ~ 0 1
1 & 1 1
1 & 0 0
0 & 1 0
0 & 0 0
1 | 1 1
1 | 0 1
0 | 1 1
0 | 0 0
1 ^ 1 0
1 ^ 0 1
0 ^ 1 1
0 ^ 0 0
Let us look at some examples:
#include <stdio.h>
void main()
{
int a = 0xFFFF;
char b = 0xAA;
unsigned char c = 0xAA;
int d;
printf("~b = 0x%Xn", ~b );
printf("a & b = 0x%Xn", a & b );
printf("a & c = 0x%Xn", a & c );
a = 0xA0;
b = 0x83;
printf("a | b = 0x%Xn", a | b );
a = 0xFFFF;
d = 0x18CF;
printf("a ^ d = 0x%Xn", a ^ d );
a = 20;
printf("d << %d = 0x%Xn", a, d << a );
d = 0x800018CF;
a = 5;
printf("d >> %d = 0x%Xn", a, d >> a );
}
The output from this program is:
~b = 0x55
a & b = 0xFFAA
a & c = 0xAA
a | b = 0xFFFFFFA3
a ^ d = 0xE730
d << 20 = 0x8CF00000
d >> 5 = 0xFC0000C6
This example uses hexadecimal numbers which are easier to work with when doing bitwise operations because every 4 bits make up one hexadecimal digit.
Line 10 performs a complement operation on 0xAA (binary 10101010) which basically flips the bits leaving 0x55 (binary 01010101).
Lines 12-13 perform a bitwise and operation. Like other arithmetic operations, since a, b and c are two different types the smaller type is converted to the larger type. On this system a char is a signed 8 bit value and an int is a signed 32 bit value. Since b is a signed value that is being converted to larger value it is “sign extended”, meaning the highest bit is copied to fill the 32 bits. This does not happen for c because it is an unsigned char. On line 12 the operation looks like this:
00000000000000001111111111111111 (a = 0xFFFF)
& 11111111111111111111111110101010 (sign extended b = 0xFFFFFFAA)
———————————-
00000000000000001111111110101010
This gives the value 0xFFAA seen in the second output line. The operation on line 13 looks like this:
00000000000000001111111111111111 (a = 0xFFFF)
& 00000000000000000000000010101010 (c converted to int = 0xAA)
———————————-
00000000000000000000000010101010 (0xAA)
Lines 15-17 perform a bitwise or operation. A similar thing happens here with b being sign extended to 32 bits. In bits, the or operation is:
00000000000000000000000010100000 (a = 0xA0)
| 11111111111111111111111110000011 (sign extended b = 0xFFFFFF83)
———————————-
11111111111111111111111110100011 (0xFFFFFFA3)
Lines 19-21 perform a bitwise exclusive-or operation. Here both operands have the same type so no conversion is necessary. The operation in bits:
1111111111111111 (a = 0xFFFF)
^ 0001100011001111 (d = 0x18CF)
——————
1110011100110000 (0xE730)
Lines 23-24 show a left shift operation. This basically shifts the bit pattern to the left by adding the given number of zeroes on the right. Since an int can only hold 32 bits, any bits shifted over the 32nd position are lost. In bits, this operation looks like this:
00000000000000000001100011001111 (d = 0x18CF)
10001100111100000000000000000000 (d << 20 = 0x8CF00000)
As you can see some bits at the top have been lost.
Lines 26-27 show a right shift operation, where the bit pattern is shifted to
the right by adding zeroes or ones on the left. For signed numbers, if the most
significant bit is 1 then the left is filled with 1s otherwise the left is
filled with 0s. For unsigned numbers the left is padded with 0s. Like the left
shift, any bits shifted “off the end” are lost. In bits the operation on line 27
would be:
10000000000000000001100011001111 (d = 0x800018CF)
11111100000000000000000011000110 (d >> 5 = 0xFC0000C6)
{mospagebreak title=C Special Operators}
C Special Operators
There are a few other operators that are used in C. The [] operator is used to access elements of an array. The syntax is like this:
int a[5] = { 1, 2, 3, 4, 5 };
int b = a[3];
On the second line the a[3] accesses the fourth element of the array and
assigns b to the value 4. Arrays will be covered in another tutorial.
The unary * operator is used to dereference pointers. For example:
int a = 1;
int *b = &a;
*b = *b + 1;
In this example b points to a. Using *b accesses the value stored in a. After
these statements are run a will have the value 2. Pointers will be covered in
another tutorial.
The ‘.’ and ‘->’ operators are used to access members of a structure or union.
The ‘.’ is used when the structure is accessible directly and the ‘->’ is used
when the structure is accessible through a pointer.
#include <stdio.h>
#include <stdio.h>
void main()
{
struct person
{
char *firstname;
char *lastname;
int age;
};
struct person a = { "Bob", "Smith", 30 };
struct person * b = &a;
printf( "a.firstname = %sn", a.firstname );
printf( "b->firstname = %sn", b->firstname );
}
a.firstname = Bob
b->firstname = Bob
Structures and unions will be covered in another tutorial.
Another little used operator is the ‘,’ (comma) which takes two operands. The
left side is evaluated first, then there is a sequence point then the right side
is evaluated. The return value and type of the ‘,’ operator is the value and
type of the right side of the comma.
#include <stdio.h>
void main()
{
int a = 1;
float b;
b = ( a++, a * 3.1 );
printf( "a = %d, b = %fn", a, b );
}
a = 2, b = 6.200000
On line 8, a is postfix incremented and because of the sequence point the updated value is stored back in a. Then the right side is evaluated and assigned to b.
The sizeof operator returns the size in bytes of that its operand takes up. The operand can be any expression that is not a function or bitfield, which means it can be an expression like 20 + 1.34 or a type name or a variable name or a constant. The size is returned as an int. Here are some examples:
#include <stdio.h>
void main()
{
unsigned int a = 3;
struct record
{
char string[30];
long num1;
short num2;
} arec;
struct record * recptr = &arec;
printf( "a char takes %d bytesn", sizeof( char ));
printf( "an int takes %d bytesn", sizeof( a ));
printf( "a double takes %d bytesn", sizeof( 3.14159 ));
printf( "a float takes %d bytesn", sizeof( 3.14159f ));
printf( "a record structure takes %d bytesn", sizeof( arec ));
printf( "a pointer takes %d bytesn", sizeof( recptr ));
}
The output is:
a char takes 1 bytes
an int takes 4 bytes
a double takes 8 bytes
a float takes 4 bytes
a record structure takes 40 bytes
a pointer takes 4 bytes
Line 14 uses sizeof with a type name. Line 15 uses a variable and line 16-17 use a constant. Line 18 shows that sizeof works on user defined types like structures and unions too.