CS211: Operators
Table of Contents
Introduction
Up to this point, we've discussed data, how that data is stored in variables, and how those variables must all have types. This gives us everything we need in order to understand how we can write C code to make these typed variables interact with one another. These interactions can be effaced through the use of operators in C, i.e., special symbols which have semantic meaning for computations involving data.
Infix Notation
When language designers begin the long, exciting process of building out a language, they have to make a choice on how exactly that language is going to look. One of the first considerations is what sort of notation will be used for parsing operations: prefix, infix, or postfix. A quick demonstration will show what I mean:
- Prefix →
+ 2 1 - Infix →
2 + 1 - Postfix →
2 1 +
All of the above are ways of representing the semantic action of "add together the constants two and one". For the programmer, the difference is purely visual. For the language designer, or, more accurately, the compiler developer, the difference will manifest itself in how the language is parsed1. (We'll come back to this when we discuss stacks and queues.) For now, all we need to know is that C uses infix notation for all of its binary operators.
Operators in C
Here, I will enumerate all of the operators in C and provide examples for how each one operates.
Assignment
Assignment operators are used to capture values and place them into variables.
=
= is the basic assignment operator. The left operand must be a lvalue while the right operand must have a rvalue. For the following examples, assume int x and int y have already been declared"
x = 1→1is now stored in the memory location tied toxx = y→ The value tied to the memory location ofyat the time of evaluation is now stored in the memory location tied tox
Without getting into too much theory, lvalues are locations in memory (variables, or pointer dereferences) whereas rvalues are data (which can include memory locations). The variable x returns an l or r value based on its surrounding context.
Arithmetic
Arithmetic operators are only capable of being used with arithmetic types (char, int, float, double, and their variants2).
+
+ is the addition operator. It is used to add two numeric values.
4 + 2→6
-
- is the subtraction operator. It is used to subtract two numeric values.
4 - 2→2
- is also a unary operator to indicate a negative number. For example, -2 + 4 will also return 2.
*
* is the multiplication operator. It is used to multiply two numeric values.
4 * 2→8
/
/ is the division operator. It is used to divide two numeric values.
4 / 2→2
When two integers are involved integer division is performed. Functionally, that means the fractional result of the operation is removed. 2 / 4 equals 0.
%
% is the modulo operator. It is used to get the remainder after the left operand is divided by the right operand.
4 % 2→0(2goes into4two times with0remainder)2 % 4→2(4goes into20 times with2remainder)
Implicit Conversion, Again
Something that often trips students up is the fact that implicit conversion kicks in during the use of binary arithmetic operators. For example,
1 + 1.5→double1 + 1.5f→float1 / 2.0→double
And so on. In all of the above cases 1 is converted to the wider type.
Relational
Relational operators, like arithmetic operators, are only capable of being used with arithmetic types. They are used to perform comparisons between two values. All relational operations return either a 1 for true or a 0 for false.
>
> is the "greater than" operator. It is used to check if the left operand is greather than the right operand.
2 > 4→0
<
< is the "less than" operator. It is used to check if the left operand is less than the right operand.
2 < 4→1
>=
>= is the "greater than or equal to" operator. It is used to check if the left operand is greater than or equal to the right operand.
2 >= 4→04 >= 4→1
<=
<= is the "less than or equal to" operator. It is used to check if the left operand is less than or equal to the right operand.
2 <= 4→14 <= 4→1
Equality
Equality operators too are only capable of being used with arithmetic types. They are used to check equality between two values. All equality operations return either a 1 for true or a 0 for false.
==
== is the equality operator. It is used to check if the left operand is equal to the right operand.
2 == 4→02 == 2→1
Strict equality is dangerous when comparing floating point values! Relational operators should be used instead when dealing with floating points.
!=
!= is the inequality operator. It is used to check if the left operand is not equal to the right operand.
2 != 4→12 != 2→0
Logical
Logical operators can only take scalar values as input. All reals will be implicitly converted to scalars.
&&
&& is the logical AND operator. It is used for boolean conjunction where any non-zero value is considered to be true.
1 && 0→02 && -1→1
&& is guaranteed to be evaluated from left to right. This means that if the left operand is 0, the right operand will not be evaluated. This is often called "short circuiting".
||
|| is the logical OR operator. It is used for boolean disjunction where any non-zero value is considered to be true. Much like &&, || will short circuit if the left operand resolves to a true.
0 || 0→02 && 0→1
Bitwise
Bitwise operators require an integer type (this includes char, long int, and short int). They are used to perform "bit-twiddling", i.e. setting/unsetting bits in integer values.
&
& is the bitwise AND operator. Each bit in the resulting value is set if and only if each of the corresponding bits in the converted operands is set.
0xff & 0xf0→0xf00xfc & 0xf3→0xf00xff & 0x0f→0x0f
|
| is the bitwise OR operator. Each bit in the resulting value is set if one of the corresponding bits in the converted operands is set.
0xff | 0xf0→0xff0xfc | 0xf3→0xff0xff | 0x0f→0xff
^
^ is the bitwise XOR operator. Each bit in the resulting value is set if and only if exactly one of the corresponding bits in the converted operands is set.
0xff ^ 0xf0→0x0f0xfc ^ 0xf3→0x0f0xff ^ 0x0f→0xf0
~
~ is the bitwise NOT operator. Each bit in the resulting value is set if and only if the corresponding bits in the operand is not set.
~((char)0xff)→0x00~((char)0xfc)→0x03~((char)0x0f)→0xf0
If I don't explicitly cast the hexadecimal constant to a char, the C compiler will keep it as an int and flip more bits than I wanted to show.
<<
<< is the left bitshift operator. From the standard: "The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 × 2^E2, wrapped around. If E1 has a signed type and nonnegative value, and E1 × 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined."
0xf << 4→0xf00x80000000 << 1→0x00
>>
>> is the right bitshift operator. From the standard: "The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1/2^E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined."
0xf0 >> 4→0x0f0xf >> 4→0x000x80000000 >> 1→??
There are certain edge cases in the C programming language which produce undefined behavior. We'll learn a bit more about it when we get to pointers. For now, think of it as something that is impossible for progammers to know without reading compiler documentation.
Operators in Other Programming Languages
It just so happens that C-style syntax is rather popular and many of the symbols chosen by C are used in other programming languages. For example, almost all of the operators and their symbols in C are identical in Java. Even if they weren't, many programming languages share the exact same logic and abstractions that C does (at least in the general sense). Both in Python and in C, 2 * 3 represents the multiplication of the integers 2 and 3. Knowing the ins and outs of one programming language prepares you for learning another!
Exercises
- Predict the result of each of the following expressions before writing a C program to verify them. Pay close attention to integer division:
7 / 27 / 2.07.0 / 2(float) 7 / 2(float) (7 / 2)
- Write a C program that declares two
intvariablesa = 15andb = 4and prints the result of all five arithmetic operators applied to them. What type is the result of each operation? - The lecture notes that strict equality is dangerous with floating point values. Write a C program that computes
0.1 + 0.2and checks if it equals0.3using==. Print the result. Then print the raw value of0.1 + 0.2usingprintf("%.20f\n", 0.1 + 0.2). Explain what you observe. - Write a C program that prints the results of all six relational operators (
>,<,>=,<=,==,!=) comparinga = 5andb = 5. Predict each result before running. - The lecture states that
&&and||short circuit. Write a C program that demonstrates short circuiting by placing a division by zero on the right side of a&&where the left side is0, and on the right side of a||where the left side is1. Does the program crash? Explain why or why not. Using only bitwise operators, write a C program that:
- Sets the 4th bit of
0x00to 1 using| - Clears the 4th bit of
0xffusing& - Flips the 4th bit of
0xffusing^
Print each result in hexadecimal using
printf("%x\n", result).- Sets the 4th bit of
- Write a C program that demonstrates left and right bitshifting on the value
0x01. Left shift it 4 times, printing the result each time. Then right shift the final result 4 times back, printing each time. What do you observe? Express each result in both hex and decimal. - The lecture states that
1 + 1.5produces adouble. Write a C program that usessizeofto verify the type widths and then performs the following mixed-type operations, printing each result with an appropriate format specifier:int + floatint + doublefloat + double
- Evaluate the following expressions by hand using the bitwise operator rules, then verify in C using
printf("%x\n", result):0xAB & 0x0F0xAB | 0x0F0xAB ^ 0x0F~((char)0xAB)
- The lecture introduces three different notations for writing operations: prefix, infix, and postfix. Rewrite the following C expressions (which use infix notation) in both prefix and postfix notation:
2 + 310 - 43 * 58 / 2
Footnotes:
Most popular languages nowadays default to infix notation; however, there are still some languages which do not. For example, Lisp, a relatively popular functional programming language, and all of its various dialects use prefix notation. To my knowledge, Forth is the only programming language that anyone uses which sticks to postfix notation.