We are the heirs of the past and should always be aware of it. Let’s take the chance not to repeat mistakes and apply acquired knowledge. We don’t need to reinvent the wheel, but knowing its origin definitely doesn’t hurt.

Why this appeal? Firstly, its statement applies generally and comprehensively to all areas of our lives as well as society. Adopt the good and improve the inappropriate. On the other hand we want to learn the programming language C++, which is an extension of the programming language C and therefore also builds on a history.

Therefore we often meet “relics” of this origin. This time it is the arrays.

What and what for?

Arrays are called fields in German and form a unit of a fixed number of elements of the same data type. In this sentence you have all three characteristics of an array:

  • unit of elements
  • fixed number
  • same data type

Okay, but what does that mean exactly? The array reserves space in memory to store your data elements there in orderly sequence. It is the raw memory and not an object in the real sense.

The declaration consists of the data type of the elements, the name of the array and then the number of elements in the index operator [] (see listing1).

datatype arrayName [numberOfElements];

// listing1: syntax of an array

And what benefit do you get from this construct? Imagine you have a huge record collection and all of them are nicely lined up next to each other on the shelf. Now you would like to have a digital listing of your stock and create a small database for your records.

One approach is to create an object of the structure vinyl for every single record.

#include <iostream>

struct vinyl
{
    // elements
};

int main()
{
    vinyl lp1;  // declaring variable of type vinyl
    vinyl lp2;
    vinyl lp3;
    vinyl lp4;
    vinyl lp5;

    return 0;
}

// listing2: declaration of some structs

That works. You can do it that way. You can do the same for 100 or 1000 records. But don’t you find that very cumbersome? Here you can use an array for the collection. Instead of 100 objects you just need to create an array of 100 objects.

#include <iostream>

struct vinyl
{
    // elements
};

int main()
{
    const int numberOfVinyls {5};       // initializing integer
    vinyl lpCollection[numberOfVinyls]; // declaring array

    return 0;
}

// listing3: array of some structs

When you declare an array, you must at the same time specify the fixed size in the form of the number of elements. After that you are not able to change the size and add more disks during the runtime of the program. On the other hand, the same amount of memory is always used, even if you use only half of it. That’s why you can find the name static arrays.

Fill the array

Alright, now the compiler reserves enough memory for 5 elements. Now we have to fill the space with data. The index operator [] is not only used to define the size of the array at declaration. It gives you access to every single element in the array.

The elements have a fixed order and are numbered. But be careful, the first element has the index 0. This kind of numbering is called zero-based and is used very often in information processing systems. This is due to binary logic. There it is about states of the single bits, which can be either zero or one. The state where all bits are set to 0 can be treated like any other. Do not forget it.

We now want to fill our record array and start at the element with index 0. To do this, enter the index into the index operator of the field and the compiler already knows in which area of the memory you want to store the data.

#include <iostream>

struct vinyl
{
    // elements
};

int main()
{
    vinyl lp1;  // declaring variable of type vinyl
    vinyl lp2;
    vinyl lp3;
    vinyl lp4;
    vinyl lp5;

    const int numberOfVinyls {5};
    vinyl lpCollection[numberOfVinyls];

    lpCollection[0] = lp1;  // assigning element at index 0
    lpCollection[1] = lp2;
    lpCollection[2] = lp3;
    lpCollection[3] = lp4;
    lpCollection[4] = lp5;

    return 0;
}

// listing4: assigning data to an array

What’s all in there?

The reverse case works analogously. If you want to access the data in the array you only need to specify the desired index.

#include <iostream>

struct vinyl
{
    // elements
};

int main()
{
    vinyl lp1;  // declaring variable of type vinyl
    vinyl lp2;
    vinyl lp3;
    vinyl lp4;
    vinyl lp5;

    const int numberOfVinyls {5};
    vinyl lpCollection[numberOfVinyls];

    lpCollection[0] = lp1;  // assigning element at index 0
    lpCollection[1] = lp2;
    lpCollection[2] = lp3;
    lpCollection[3] = lp4;
    lpCollection[4] = lp5;

    vinyl actualElement = lpCollection[3];  // accessing element at index 3

    return 0;
}

// listing5: accessing data of an array

And what happens if I choose an index that is outside my array? Well, this is exactly where the danger lies. Because you are not told about it. Also, the compiler doesn’t throw an error. It will simply take the data that is at the address in memory that the index would point to if the array had that size.

Figure 1: Memory of Arrays

Figure 1: Memory of Arrays

So you have to take care yourself to choose an existing index. This mistake happens quickly if you forget that the numbering starts at 0. Remember: N is the number of elements in the array. The boundaries of the array are at 0 and at N-1;

If N is the number of elements,

then the boundaries of the array reach from 0 to N-1.

By the way, if you don’t initialize an array, it will be filled with some junk values.

How much is the …

If you are interested in the number of bytes our array costs you in memory, the function sizeof() will help you once again. But be careful! In this case the function behaves a little bit different. If you apply sizeof() directly to the array, the result is not the amount of bytes, but the number of elements.

We are not left wondering, but are clever. If the array consists of a fixed number of elements of the same data type and represents only the pure memory without any overhead, then we can determine size in bytes from it ourselves.

Using sizeof() we get the number of elements. This multiplied by the size of an element (you remember determining the size of a datatype) gives the total amount of bytes of the array.

int numberOfElements = sizeof(anArray);
int sizeOfArray = sizeof(elementType) * numberOfElements; // in bytes

// listing6: size of an array

For our record collection this means: int bytesOfCollection = sizeof(lpCollection) * sizeof(vinyl);

Multidimensional

So far we have looked at one-dimensional arrays. With this, we have mapped a shelf of records standing next to each other. But we can also map a shelf of multiple boards in a multidimensional array. The structure is comparable to rows and columns of a matrix. Sometimes we also speak of an “array of arrays”.

Figure 2: Bidimensional Array

Figure 2: Bidimensional Array

The syntax changes only minimally. For the added dimension we only need another index operator. This way the element has a unique coordinate in the array.

For a two-dimensional array, the declaration would look like this:

datatype arrayName [numberOfRows][numberOfColumns];

// listing7: declaration of a bidimensional array

As the prefix multi suggests, multidimensional arrays are not limited to two dimensions. You can append as many index operators as you like. Keep in mind that not only the number of elements increases exponentially. The required memory grows in parallel.

Actually multidimensional arrays are only an abstraction for you as a programmer. You can achieve the same result by multiplying the indices of a one-dimensional array. But for an overview or a better idea of your data this is not very helpful.

Special case: Char arrays

In [eps1.4Datatatypes](/posts/c++/datatypes-en/) we got to know the datatype _char. With it you can store characters. Its size of one byte is sufficient for the representation of the ASCII character set. Now there is not so much you can do with one character. So how about we create an array of chars and combine whole sentences in it as strings?

The idea is of course not new and from me. Strings are called Strings and such objects already exist in the C programming language. S-strings are a special case of arrays of chars.

They are not completely unknown to you. In our examples they already appeared frequently in the form of a literals. In Listing6 you see the output of the string literal "Hello World" and this is followed by the equivalent initialization of the string as a char array.

#include <iostream>

int main()
{
    std::cout << "Hello World" << std::endl; // string literal

    char sayHello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0'}; // initialize char array
    std::cout << sayHello << std::endl;

    return 0;
}

// listing8: char array

Each letter and also the space are an element of the array. You can access each character individually or read the array as a stream.

If you want to read in a char stream and thus enable user input, you must first specify the size of the string. The size of the array must be specified in the declaration and cannot be changed during runtime. So the user is only allowed to enter a certain number of characters.

But he is not stopped to enter more. The further characters are then outside the array and do not belong to this unit. Therefore, always keep in mind the static behavior of char arrays when developing your code.

#include <iostream>

int main()
{
    char sayHello[] = {'H','e','l','l','o',' ','W','o','r','l','d','\0'};
    std::cout << sayHello << std::endl;
    std::cout << "Size of array: " << sizeof(sayHello) << std::endl;

    std::cout << "Replacing space with null" << std::endl;
    sayHello[5] = '\0';
    std::cout << sayHello << std::endl;
    std::cout << "Size of array: " << sizeof(sayHello) << std::endl;
    return 0;
}

// listing9: escape code "\0" in char arrays

Surely you noticed the “\0” character in the last element of the array. This is called String-Terminating Character because it shows the compiler the end of the string. Every char array must end with the NULL terminator. In the case of the string literal, the compiler itself adds “\0” to the end.

If you write “\0” in the middle of a string, it will be truncated there and the rest will not be considered further by the compiler. The size of the string in memory does not change. Only not all information is processed further, but also nothing is deleted.

The Backslash \ announces a special instruction for the compiler. In our example it should insert a zero and thus terminate the string. If you just write 0 the character will be interpreted as ASCII coded.

We have discussed such a compiler statement inside a string before. With \n we discussed an alternative to std::endl. There the backslash is used to show the compiler that we want to continue the string in a new line at this point.

If you forget to end the char array in listing7 with \0, more characters will be printed after Hello World. This is because the function std::cout expects the explicit termination and, without regard to the bounds of the array, simply tries to represent the data that follows it in memory. Until a zero occurs.

So you see, using char arrays is cumbersome. Also, it runs the risk of forgetting the null terminator and causing your program to crash.

That sticks

That was our little “history lesson” on C-style arrays. You’ll need them if you’re programming in C. In modern C++, they are hardly ever used in the manner presented. But it is important that you understand the concept behind arrays.

You now know the static arrays from C and the char array as a special case. You know that they exist. You can also use them. But C++ has evolution in store for you in the form of dynamic arrays in the C++ Standard Library. These are called vectors and you will love them in direct comparison! We’ll also take a look at another object in the STL. With C++ Strings strings can be handled much more comfortably than with Char Arrays. More about that next time.

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