Namespaces
Variants
Actions

Difference between revisions of "intro/types"

From cppreference.com
(Fix of copy-paste bug)
m (title/warning)
 
Line 1: Line 1:
 +
{{title|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.
 
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.
  

Latest revision as of 09:03, 6 July 2020

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.

Reference teaser

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 ??).