C++: The Basics - eps1.11_Operators
Hello and welcome. Name the three O’s of an expression! Already forgotten? All right, for once I’ll give you a quick summary.
An expression consists of O perands, which are connected with O perators. Operators are the symbolic description of a O peration. Operands are variables or literals of all possible data types. And for each data type, there is also a set of operators.
To put it briefly, with operators you can process data, modify it, or make decisions based on it.
What operators are there?
To pursue this question with the right “feel”, I recommend the song Smooth Operator by the wonderful Sade Adu for the acoustic background of this post.
There are a number of categories into which operators are divided. Last time we learned about the assignment operator =
in addition to expressions and statements. And this time we’ll look at some more:
- Arithmetic operators
- Relational operators
- Logical operators
- Bit operators
- Compound operators
Arithmetic operations
As a branch of mathematics, arithmetic deals with the basic arithmetic operations. These include
- Addition, adding up values
- subtraction, subtracting values from others
- Multiplication, taking the multiple of a value
- Division, taking a part of a value
Of course, arithmetic also includes laws of arithmetic. “Dot before dash” and what they’re all called. Maybe you still remember your school lessons.
In the following table you have all arithmetic operators at a glance.
operator | operation |
---|---|
+ |
addition |
- |
Subtraction |
* |
Multiplication |
/ |
division |
% |
modulo division |
There is not much new to say about arithmetic operations compared to mathematics. They work analogously.
#include <iostream>
int main() {
int operand1 = 5;
int operand2 = 2;
std::cout << operand1 << " + " << operand2 << " = " << operand1 + operand2 << std::endl; // add
std::cout << operand1 << " - " << operand2 << " = " << operand1 - operand2 << std::endl; // subtract
std::cout << operand1 << " * " << operand2 << " = " << operand1 * operand2 << std::endl; // multiply
std::cout << operand1 << " / " << operand2 << " = " << operand1 / operand2 << std::endl; // divide, division of integer causes lost of remainder
std::cout << operand1 << " % " << operand2 << " = " << operand1 % operand2 << std::endl; // modulo
return 0;
}
// listing1: arithmetic operators
Attention, when dividing the integer data type you have to be careful. Integers have no decimal places. If you divide an integer by another integer, the division is not always smooth. There is a remainder that cannot be divided.
This remainder is not displayed and not considered by the program. Therefore, data loss can easily occur here.
But apart from the four basic arithmetic operations, information technology also knows the modulo ‘%’ operator for integers. Its function will surely be familiar to you. Basically it does the same operation as the division operator /
, only with the difference that with the modulo operator you get the integer remainder as result.
Increment and decrement
You’re bound to run into a situation at some point where you want to count something. How many laps have been made? How many times did a certain word come up? How many messages have been received? When the event occurs, a counter variable is incremented.
Instead of constantly adding the value 1
to the variable, you can simply use the Increment operator ++
. If you put the operator as a prefix in front of the identifier of your variable, its value will be incremented by 1
.
operator | operation |
---|---|
++ |
Increment |
-- |
Decrement |
For the opposite case, that you want to count down from a value, there is the Decrement operator --
. In listing2 you can see an example of how to use the two operators..
#include <iostream>
int main() {
int operand1 = 5;
int operand2 = 2;
std::cout << "x = " << operand1 << " y = " << operand2 << std::endl;
std::cout << "++x = " << ++operand1 << " --y = " << --operand2 << std::endl;
return 0;
}
// listing2: increment and decrement
The two operators do not provide you with any fundamentally new functionality, but you can use them to increase the readability of your code and also save yourself some typing.
At first glance, this doesn’t look like a big deal. At the latest when we deal with loops, you will notice the raison d’être of ++
and --
.
Conditions and comparison operators
The next set of operators helps you to compare operands and to formulate conditions. Often you will design your code to change or adapt its behavior when a certain value is given.
operator | operation |
---|---|
== |
equal |
!= |
not equal |
> |
greater than |
< |
less than |
>= |
greater than or equal to |
<= |
less than or equal to |
An unpleasant example is the radar control of traffic. If a vehicle drives faster than >
the allowed speed, the camera triggers, the driver is flashed and gets mail some time later.
So a relational operator compares the l-value operand with the r-value operand. It tells you whether the relation it stands for is satisfied or not.
If two operands are related with the equality operator ==
and actually have the same value, then the expression returns the boolean value true
. If the checked relation of the operator is not true, the expression has the boolean false
.
Try listing3 in your development environment. Then you will see how relational operators behave. Don’t get confused. The boolean true
will be displayed in the console as 1
and the boolean false
as 0
. From the compiler’s point of view these values are equivalent. You can use them as you like or compare them true == 1
.
#include <iostream>
int main() {
int operand1 = 5;
int operand2 = 2;
std::cout << operand1 << " is equal to " << operand2 << " : " << (operand1 == operand2) << std::endl;
std::cout << operand1 << " is not equal to " << operand2 << " : " << (operand1 != operand2) << std::endl;
std::cout << operand1 << " is greater than " << operand2 << " : " << (operand1 > operand2) << std::endl;
std::cout << operand1 << " is less than " << operand2 << " : " << (operand1 < operand2) << std::endl;
std::cout << operand1 << " is greater than or equal to " << operand2 << " : " << (operand1 >= operand2) << std::endl;
std::cout << operand1 << " is less than or equal to " << operand2 << " : " << (operand1 <= operand2) << std::endl << std::endl;
std::cout << " true == 1 -> " << (true == 1) << std::endl;
std::cout << " false == 0 -> " << (false == 0) << std::endl;
return 0;
}
// listing3: comparison and relational operators
Bei einem Detail musst du sehr gut aufpassen! Verwechsle den Zuweisungsoperator =
nicht mit dem Gleichheitsoperator ==
. Im besten Fall meldet sich dann der Compiler mit einer Fehlermeldung. Andernfalls crasht dein Programm und du kannst auf Fehlersuche gehen.
Wir werden relationale Operatoren noch häufig nutzen, da wir mit ihnen Entscheidungen während der Laufzeit treffen lassen und so verschiedene Wege des Programmablaufs einschlagen können.
Logical operators
Ever heard of Boolish Algebra? Sounds like math, it is! George Bool was the first to develop an “algebra of logic”, which is still the basis for the design of digital electronics.
There, there are only the two states true and false. In a circuit they stand for current flows or current does not flow.
Boolean algebra therefore only knows the set (0,1). For this the three operations conjunction, disjunction and negation are defined. Modern programming languages like C++ offer operators for these logical operations.
operator | operation | |
---|---|---|
& |
Conjuction, logical and | |
| |
disjunction, logical or | |
`! | negation, logical not |
The conjunction is also called and-connection. It yields 1
if both operands have the value 1
. Every other case is 0
.
For the disjunction also the term OR-connection is used. But you must not understand this as “either-or”, but as inclusive OR. If the first or the second operand is 1
, the operation results in 1
. Only if both operands are 0
, the result of the OR operation is 0
.
The third operation is the negation. It reverses the state. With the NOT operator a 1
becomes 0
and a 0
becomes 1
.
I hope the truth table in listing4 helps you to understand logical operators better. With more mathematics and theory about boolean algebra I don’t want to *Choose a verb that suits you: excite, annoy, torment.
#include <iostream>
int main() {
bool operand1 = true;
bool operand2 = false;
std::cout << "| Table of truth |" << std::endl;
std::cout << "| ---------------- |" << std::endl;
std::cout << "| a | b | and | or |" << std::endl;
std::cout << "| ---------------- |" << std::endl;
std::cout << "| " << operand1 << " | "<< operand1 << " | "<< (operand1 && operand1) << " | "<< (operand1 || operand1) << " |"<< std::endl;
std::cout << "| " << operand1 << " | "<< operand2 << " | "<< (operand1 && operand2) << " | "<< (operand1 || operand2) << " |"<< std::endl;
std::cout << "| " << operand2 << " | "<< operand1 << " | "<< (operand2 && operand1) << " | "<< (operand2 || operand1) << " |"<< std::endl;
std::cout << "| " << operand2 << " | "<< operand2 << " | "<< (operand2 && operand2) << " | "<< (operand2 || operand2) << " |"<< std::endl;
std::cout << std::endl;
std::cout << "| a = " << operand1 << " | not a = " << !operand1 << " |"<< std::endl;
std::cout << "| b = " << operand2 << " | not b = " << !operand2 << " |"<< std::endl;
return 0;
}
// listing4: logical operators
Bit Operationen
Bit operations
The next set of operators looks very similar to the logical operators at first sight. They also have partly the same names. But the operations are fundamentally different.
While logical operators look at the values of variables and answer with boolean truth values, bit operators actually work on bit level. Thus, they do not simply manipulate the entire value of a variable, but change individual bits.
The bit-level representation of data corresponds to the processor’s internal low-level representation. Today’s digital technology is built on the binary system. There, any value is formed from a different sequence of 0
and 1
. One digit in this sequence of numbers is a bit.
In the following table you find the bit operators of C++.
operator | operation |
---|---|
~ |
bitwise complementary |
& |
bitwise AND |
| |
bitwise OR |
^ |
bitwise exclusive OR |
< |
bitwise left |
»` | bitwise right |
The complementary operator ~
reverses every single bit. It works like a negation on bit level.
The bitwise AND operator &
establishes an AND-connection between the corresponding bits of two operands. In the same way the bitwise OR operator |
establishes an OR-connection.
However, the bitwise exclusive OR operator is new. This performs an XOR operation on all bits. Unlike the OR operation, one of the two compared bits may be 1
to yield true
. Any other combination results in 0
.
To help you understand how the operators work and to better see the changes, a binary representation of the values in listing5 is chosen. For this we need the std::bitset
function from the C++ standard library. We insert this with the preprocessor statement #include <bitset>
.
The length of the bitset depends on the size of the datatype. For this reason we take the shortest integer type short
with 16 bits. In the tip brackets of the std::bitset<size_t N>
template function you can specify the length of the bit sequence. In this example we restrict ourselves to the first 8
, so that the sequence does not become too long.
Listing5 shows you the decimal and binary representation of the values before and after the corresponding operations.
#include <iostream>
#include <bitset>
int main() {
short operand1 = 7;
short operand2 = 4;
std::cout << "a decimal: "<< operand1 << " binary: " << std::bitset<8>(operand1) << std::endl;
std::cout << "b decimal: "<< operand2 << " binary: " << std::bitset<8>(operand2) << std::endl << std::endl;
unsigned short operand3 = ~operand1;
short operand4 = ~operand2;
std::cout << "~a decimal: "<< (operand3) << " binary: " << std::bitset<8>(operand3) << std::endl;
std::cout << "~b decimal: "<< (operand4) << " binary: " << std::bitset<8>(operand4) << std::endl << std::endl;
std::cout << "a & b decimal: "<< (operand1 & operand2) << " binary: " << std::bitset<8>(operand1 & operand2) << std::endl;
std::cout << "a | b decimal: "<< (operand1 | operand2) << " binary: " << std::bitset<8>(operand1 | operand2) << std::endl;
std::cout << "a ^ b decimal: "<< (operand1 ^ operand2) << " binary: " << std::bitset<8>(operand1 ^ operand2) << std::endl << std::endl;
return 0;
}
// listing5: bitwise operators
For the complementary operator ~
there is a special feature. Here it is very important whether the first bit is interpreted as a sign or not. To see both cases an additional variable is initialized with the unsigned short
.
The bitshift operator shifts the bits by the desired number of places y
. Either to the left or to the right. Did you notice that shifting a binary sequence by y
places is equivalent to multiplying or dividing by 2^y
?
That would be pretty good, because who sees general relationships at first glance. But a concrete case may strike you. For example, the bit shift to the left by one place has the same effect on the numerical value as a multiplication by 2
.
#include <iostream>
#include <bitset>
int main() {
short operand1 = 7;
short operand2 = 4;
std::cout << "a decimal: " << operand1 << " binary: " << std::bitset<8>(operand1) << std::endl;
// operand1<<2
std::cout << "a<<2 decimal: "<< (operand1<<2) << " binary: " << std::bitset<8>(operand1<<2) << std::endl;
// operand1>>1
std::cout << "a>>1 decimal: "<< (operand1>>1) << " binary: " << std::bitset<8>(operand1>>1) << std::endl << std::endl;
std::cout << "b decimal: " << operand2 << " binary: " << std::bitset<8>(operand2) << std::endl;
// operand2<<2
std::cout << "b<<2 decimal: "<< (operand2<<1) << " binary: " << std::bitset<8>(operand2<<1) << std::endl;
// operand2>>1
std::cout << "b>>1 decimal: "<< (operand2>>1) << " binary: " << std::bitset<8>(operand2>>1) << std::endl;
return 0;
}
// listing6: bitshift
That sticks
There are quite a few operators. The Arithmetic Operators let you apply mathematical operations of Arithmetic, like Addition, Subtraction, Multiplication or Division to your variables. For variables of the datatype Integer this set gives you after the Modulo operator. With it the remainder of a gaz number division can be determined. In addition, the Increment and Decrement operators give you a more convenient way to count up or down.
With Relational Operators you can compare variables and formulate conditions.
The Logical Operators work with the Boolean data type and bring you the Conjunction, Disjunction, and Negation operations from Boolean Algebra.
Bit operators do not change the values themselves, but look down to the bit level of the data. There they apply their operations directly to the individual bits. Bits have only the two values 0
and 1
. This binary system is the basis for today’s digital technology. So you can get the same result with a bitshift as with a multiplication or division.
Actually, I promised you more operators at the beginning. But up to this point, we’ve learned so much new stuff that a break to let it sink in is in order.
Next time there will be the Coherent Operators and conclude the topic of operators with supplementary information.
I wish you maximum success!
Sources
- [1] B. Stroustrup, A Tour of C++. Pearson Education, 2. Auflage, 29. Juni 2018.
- [2] B. Stroustrup, Programming: Principles and Practice Using C++. Addison Wesley, 2. Auflage, 15. Mai 2014.
- [3] U. Breymann, Der C++ Programmierer. C++ lernen – professionell anwenden – Lösungen nutzen. Aktuell zu C++17. München: Carl Hanser Verlag GmbH & Co. KG; 5. Auflage, 6. November 2017