types
Computers represent values in sequence of bits, grouped into bytes. Every bit can be one or zero. One byte is 8 bits. Byte is the smallest addressable area of the main memory.
Fundamental types in C++:
- Boolean (logical): bool
- Integer numbers: char, int, uint16_t, size_t, ...
- Floating point numbers: float, double, long double
Compound types in C++
- Pointers: Variables capable to hold the address of other variables.
- References: They are similar to pointers with some (useful) restrictions.
- Arrays: Continuous sequence of elements in memory of the same type.
- Structures: A grouping of named data members.
Other compound types in C++ which we will not detail in this introductory section:
- Classes: An abstract data type with the capability to protect its own type invariant properties. (1)
- Functions, lambdas (C++11): Functions also have types and their pointer can be passed around.
- Template structures, classes and functions: Incomplete types which become particular types with specific compile time parameters where needed.
(1) Technically structures and classes are the same, not counting the default visibility of their members. The above definitions rather refer to their common role of usage.
Contents |
Fundamental types
Boolean
The type bool is designed to hold a logical value. One bit would be enough to store it, but it takes more space because of efficient moving and copying. Its possible values are true and false. Comparisons are low precedence operators which are evaluated to bool, like in this snippet:
if(x==42){ /* do something */ }
You can assign the value of a comparison to a boolean:
bool b = (x < 43);
Common operators for the bool type are logical and (&&), logical or (||) and logical not (!). Logical exclusive or can be expressed with the not-equals comparison like a != b.
Integers
Concept
Integer numbers are represented using the binary numeral system, where every bit is used to store one base-2 digit. There are signed and unsigned types. For signed types one bit is used to store the sign of the number.
We have basic operations like addition +, subtraction -, multiplication *, division / and modulo %. We can also use binary operations like binary and &, or |, exclusive-or ^ and not ~. In explanatory text I will use the ^ sign for exponentiation and ^ sign (gray background) for binary exclusive-or.
Note that if you want to store the computational result in the left hand operand you can simply write x += 3 instead of x = x + 3.
Integer types can be prefixed with the signed or the unsigned keyword. Generally the signed version is the default, but it can be compiler (or compilation option) dependent. Minimum and maximum values for unsigned types are 0 and 2^b-1, for signed types they are -2^(b-1) and 2^(b-1)-1 where b is the number of bits used to represent the number.
char: At least one byte long. Most of the time it is exactly one byte, but you should not rely on that. Can store numbers from -128 to 127 (or from 0 to 255). It is also used to store characters according to the ASCII table, where the numerical value 65 means capital 'A'.
short [int], int, long [int], long long [int] (since c++11): Larger integer types. At least they are 2, 2, 4 and 8 bytes long. Usually they are 2, 4, 4 and 8 bytes. On 64 bit systems long is often 8 bytes.
The int keyword can be used after short, long and long long where it has no additional meaning, or it can stand alone when it means at least 16 bits. All of the above can be prefixed with unsigned or signed.
Fixed size integer types
Fixed size integer types can be found in the cstdint header (or stdint.h before C++11 or in C). These types are uint8_t, int8_t, ..., and int64_t.
Other integer types
size_t: A type which is used to represent container sizes of arrays or standard template containers. You can get detailed information on that in chapters about arrays and standard template containers.
ptrdiff_t: Type for the difference of two pointers. You can get detailed description about that in chapters about pointers.
And there are some other similarly abstractly named integer types. We will get to now them as we meet them.
Example program with booleans and integers
#include <iostream> // For standard input, output and error (std::cin, std::cout and std::cerr) int main() { long a,b; // We create 'a' and 'b' without initialization. Their value is undefined yet. std::cout << "Operations between integer numbers" << "\n"; // std::endl is end of line std::cout << "Operand 'a': "; std::cin >> a; // Read into 'a' std::cout << "Operand 'b': "; std::cin >> b; // Read into 'b' std::cout << "a + b = " << (a+b) << "\n"; // addition std::cout << "a - b = " << (a-b) << "\n"; // subtraction std::cout << "a * b = " << (a*b) << "\n"; // multiplication if(b!=0) // divisor is not zero: { std::cout << "a / b = " << (a/b) << "\n"; // division std::cout << "a % b = " << (a%b) << "\n"; // modulo } else // divisor is zero: { std::cout << "a / b is mathematically undefined." << "\n"; std::cout << "a % b is mathematically undefined." << "\n"; } bool aParity = (a % 2); // Parity of 'a' as logical value bool bParity = (b % 2); // Parity of 'b' as logical value std::cout << a << " is " << (aParity?"odd":"even") << "\n"; // See trinary operator '?:' std::cout << b << " is " << (bParity?"odd":"even") << "\n"; // See trinary operator '?:' std::cout << "Bytes used to hold a boolean value in this environment: " << sizeof(bool) << "\n"; std::cout << "Size of int in this environment: " << sizeof(int) << "\n"; return 0; // For the shell zero is success }
In bool aParity = (a % 2); we used casting (converting) from long to bool as (a % 2)'s type is long. When we cast integers to boolean everything that is non-zero counts as true, and zero counts as false.
The sizeof() operator (not function, as it is a notation which is calculated at compile time) can tell you the storage size of a type in bytes. You can use any expression as parameter, like sizeof(a).
Floating point numbers
float: Single precision floating point number with 32 bits as specified in IEEE-754
double: Double precision floating point number with 64 bits as specified in IEEE-754.
long double: Extended precision floating point number. Usually 80 bits long.
Example program with floating point numbers:
#include <iostream> // For standard input, output and error (std::cin, std::cout and std::cerr) #include <cmath> // Mathematical utilities. Wee need it for square root: sqrt() int main() { std::cout << "We will calculate the perimeter and the area of a triangle." << "\n"; // std::endl is end of line double a,b,c; // Variables to hold the sides of the triangle. Their value is undefined yet. std::cout << "a = "; std::cin >> a; // Read into 'a' std::cout << "b = "; std::cin >> b; // Read into 'b' std::cout << "c = "; std::cin >> c; // Read into 'c' double P = a + b + c; // Perimeter double s = P / 2; // Half of perimeter. // No side can be larger than that. Also needed later. if(a>s || b>s || c>s || a<0 || b<0 || c<0) // No side can be negative either { cerr << "Invalid triangle." << "\n"; return -1; // For the shell non-zero is error } double A = sqrt(s*(s-a)*(s-b)*(s-c)); // Heron's formula. std::cout << "Perimeter is " << P << "\n"; // Display perimeter std::cout << "Area is " << A << "\n"; // Display area return 0; // For the shell zero is success }
Compound types
Pointer
Pointers are special values which hold memory addresses. When you dereference a pointer you can access a value. Pointers can be set as addresses of already existing values or can be set to newly allocated values.
They can also be null (numerical 0 or nullptr since C++11). If you use a pointer as boolean it evaluates to true if it is not null, to false otherwise. Null used to represent the state when the pointer is not set to any particular value.
int x = 17; // Integer typed value with the name 'x' and initial value 17 int *p1; // 'int *' is a type: pointer to integer. The name of the variable is p1 p1 = nullptr; // Or '0' before C++11 or in C. int *p2 = &x; // The starting value if 'p2' is the address of 'x'. *p2 += 10; // In this figure the '*' is an unary operator (not a part of a type expression). // It means dereferencing. If you dereference the 'p' pointer you get to // the x value. Then we increment this value with 10 using the '+=' operator. std::cout << x << std::endl; // Prints out 27
Let's see another program where a function takes a pointer as argument.
#include <iostream> void increment_v1(int v) // This function expects an integer value as parameter, returns nothing { v += 10; // This will not change the value in the caller context as parameters count as // function parameters counts as local variables inside the functions. } void increment_v2(int *pV) // This function expects a pointer to integer, returns nothing. { *pV += 10; // This will change something that is pointed by 'pV'. } int main() { int a = 150, b = 200; increment_v1(a); // 'a' remains unchanged std::cout << a << "\n"; // Outputs '150' increment_v2(&a); // This passes the address of our 'a' to the other function, so it std::cout << a << "\n"; // can change its value. Outputs '160' increment_v2(&b); // We can use the same function with a different pointer. std::cout << b << "\n"; // Outputs '210' return 0; // OK }
Pointers also have arithmetic possibilities, but these have meaning only in the context of arrays. We will talk about it a little bit later.
Dangers of pointers
You must never dereference a null pointer. That will cause undefined behavior, in most cases causes the program to crash with Access violation (Windows) or Segmentation fault (Linux), etc... We can expect similar results if we try do dereference an uninitialized pointer.
Reference
As a first approach we can say that references are pointers with the following differences:
- A reference must be initialized when it is created.
- A reference cannot be changed.
- The dereferencing of it is automatic, there is no notation for it. Explicit dereferencing is error.
- We take the address of the pointed implicitly when it is initialized. No & needed. (In fact it is an error if you place it.)
Example and explanation
int x = 17; // OK int &r1; // Error. A reference must be initialized. r1 = nullptr; // Error: A reference cannot be changed int &r2 = &x; // Error: We don't use '&' of pointed value when initializing int &r3 = x; // OK. The 'r3' now points to x; *r3 += 10; // Error: Explicit dereferencing is error r3 += 10; // OK! The 'x' is increased by 10. std::cout << x << std::endl; // Prints out 27 std::cout << r3 << std::endl; // Prints out 27. Dereferencing is automatic again.
References give smoother outfit to the code once when understood. If you await a reference in a parameter list then you formally express that that parameter is absolutely needed for the operation. While it is easily possible to give null pointer as argument where a pointer is expected.
Let's see how it cleans up some clutter in our code with the incrementing functions. (We omit increment_v1 as it has no explanatory value for us anymore.)
#include <iostream> void increment_v2(int &v) // Take argument by reference { v += 10; // Dereferencing is automatic. We increase the pointed value by 10. } int main() { int a = 150, b = 200; increment_v2(a); // We don't take address explicitly... std::cout << a << "\n"; // Outputs 160. increment_v2(b); // ..., it is taken implicitly. std::cout << b << "\n"; // Outputs 210. return 0; }
Sometimes references do not manifest in reality as a pointer like phenomenon, they just appear as aliases. If we have an int a; and an int &b = a; then it is possible that the generated machine code will be the same where you mention 'a' and 'b'. That does not lead to any difference in the computational result of the program but may save some memory and CPU time.
Array
An array is a sequence of same type values in memory. Arrays can be defined with fixed size, or allocated at runtime. We expect the reader to know about loops (for, while, etc...). Arrays can be used to store characters, numbers, strings, structures or any other type of values including arrays.
Single dimensional arrays are useful for creating sequences of chess moves, characters, records.
If you have array typed values in an array then you have a so called multi dimensional array which can be used for storing board-like information like an elevation map, a chess board and so on.
Basic features of arrays include:
- Defining a fixed size array: int a[10];
- Defining array with runtime size (C++11, C99): int n; std::cin >> n; int a2[n];
- Creating an array dynamically on heap int n; std::cin >> n; int *a3 = new int[n];
- Accessing array members: a[2] = a[0] + a[1];
- Deleting a dynamically created array delete[] a3;
Array elements are indexed from zero to size minus one.
#include <iostream> int main() { std::cout << "Number sequence reverser tool" << "\n"; // Banner for program size_t n; // To store the number of elements std::cout << "Number of elements: "; std::cin >> n; // Read into 'n' int *numbers = new int[n]; // Allocate array dynamically with size 'n' for(size_t i=0; i<n; ++i) // Loop from 0 to n-1 using i { std::cout << "Number #" << (i+1) << ": "; // Print human indexing when asking for number std::cin >> numbers[i]; // Reads into 'numbers[i]' } // This will reverse the numbers in the array for(size_t i=0; i<n/2; ++i) // Loop to half. Limit has been chosen carefully. { int tmp = numbers[i]; // This is a three step swap of numbers[i] = numbers[n-1-i]; // numbers[i] and numbers[n-1-i] numbers[n-i-1] = tmp; // using a temporary variable tmp. } std::cout << "Numbers in reverse order:" << "\n"; // Banner for result for(size_t i=0; i<n; ++i) // Loop from 0 to n-1 using i { if(i>0) // We don't need comma before the first element, std::cout << ", "; // but after that we do. std::cout << numbers[i]; // Prints 'numbers[i]' } std::cout << "\n"; // Prints line feed after last number. delete[] numbers; // Deallocates array. Does not make 'numbers' to be 0 (or nullptr) // Dereferencing it after this line is error. return 0; }
(To be continued... Example for multi dimensional array)
Arrays and pointers
(In progess) Pointers, arrays, structs, (classes, template classes ??).