The C++ Programming Language

Part 5: Strings & Arrays

Arrays

In C++, an array is a data structure used to hold multiple items of the same type in a contiguous memory.

One-dimensional arrays

For example, we could use an array to hold the 10 high scores in a game. The declaration of an array is like this:

int hiScores[10];

This will allocate a 10-integer space on the stack. As we have seen this space will be automatically released when the function ends. We do not have to call new to allocate it or delete to release it. This memory is not dynamically managed.

We access each element in the array using the indexing operation:

for (int i = 0; i < 10; i++)
    hiScores[i] = 0;

Array indexing starts at zero (0) and goes as high as number of elements-1.

Here are the key characteristics of arrays.

Multi-dimensional arrays

Arrays can have more than one dimensions. In the case of two-dimensional arrays, we can visualize them as grouping made up of rows and columns:

int md_hiScores[5][10];

This is a high-score table for 5 games and 10 players for each game.

Column 1Column 2Column 3...
Row 1md_hiScores[0][0]md_hiScores[0][1]md_hiScores[0][2]...
Row 2md_hiScores[1][0]md_hiScores[1][1]md_hiScores[1][2]...
...............

Here is how to iterate over an array with two dimensions:

int md_hiScores[5][10];
for (int game = 0; game < 5; game++) {
    for (int player = 0; player < 10; player++) {
        md_hiScores[game][player] = 0;
    }
}

Three dimensions is like having multiple sheets and each of them having a two-dimensional table:

So now we have:

int hd_hiScores[3][5][10];
Sheet 0Column 1Column 2Column 3...
Row 1hd_hiScores[0][0][0]hd_hiScores[0][0][1]hd_hiScores[0][0][2]...
Row 2hd_hiScores[0][1][0]hd_hiScores[0][1][1]hd_hiScores[0][1][2]...
...............
Sheet 1Column 1Column 2Column 3...
Row 1hd_hiScores[1][0][0]hd_hiScores[1][0][1]hd_hiScores[1][0][2] ...
Row 2hd_hiScores[1][1][0]hd_hiScores[1][1][1]hd_hiScores[1][1][2] ...
...............
Sheet 2Column 1Column 2Column 3...
Row 1hd_hiScores[2][0][0]hd_hiScores[2][0][1]hd_hiScores[2][0][2]...
Row 2hd_hiScores[2][1][0]hd_hiScores[2][1][1]hd_hiScores[2][1][2]...
...............

Here is how we can use a three-dimensional array:

int hd_hiScores[3][5][10];
for (int system = 0; system < 3; system++) {
    for (int game = 0; game < 5; game++) {
        for (int player = 0; player < 10; player++) {
            hd_hiScores[system][game][player] = 0;
        }
    }
}

Declaration and initialization of arrays

Here is how we declare and initialize arrays.

For one-dimensional arrays we can use these syntaxes:

variable_type array_name[array_size]  or
variable_type array_name[array_size] {initial_values }
variable_type array_name[] {initial_values }

Here is an example:

double coords[3];  or
double coords[3] {1, 2, 3};
double coords[] {1, 2, 3};

For two-dimensional arrays we have:

variable_type array_name[rows][columns]  or
variable_type array_name[rows][columns] { initial_values } or
variable_type array_name[rows][columns] { {row[0] values}, {row[1] values} ... }

And in actual C++ code:

int img[3][4];
int img[3][4] { 1,2,3,4, 5,6,7,8, 9,10,11,12 };
int img[3][4] { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };

Finally for three-dimensional arrays we have:

variable_type array_name[arrays][rows][columns]  or
variable_type array_name[arrays][rows][columns] { initial_values } or
variable_type array_name[arrays][rows][columns] { { {row[0] values} ... }, ...} 

And in code:

int anim[2][3][4];
int anim[2][3][4]{ 1,2,3,4, 5,6,7,8, 9,10,11,12, 1,2,3,4, 5,6,7,8, 9,10,11,12 };
int anim[2][3][4]{ { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }, { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} } };

Arrays as arguments to functions

In C++ we can use arrays as arguments to functions. Here are the main methods to do it:

Passing as sized array

We can pass an array with a specified size. However, we also need to pass the size of the array as a separate parameter because arrays decay to pointers when passed to functions.

void displayArray(int arr[5]) {
    for(int i = 0; i < 5; i++)
        std::cout << "v1 " << arr[i] << "\n";
}

int main() {
    int arr[]{ 1,2,3,4,5 };
    displayArray(arr);
}

Passing as unsized array

We can pass an array without specifying its size in the function parameter. The size is passed separately. This method is more flexible than the previous.

void displayArray(int arr[], int many) {
    for (int i = 0; i < many; i++)
        std::cout << "v2 " << arr[i] << "\n";
}

int main() {
    int arr[]{ 1,2,3,4,5 };
    displayArray(arr, 5);
}

Passing as a pointer:

We can pass the array as a pointer, which is essentially what happens when we pass an array to a function.

void displayArray_ptr(int* arr, int many) {
    for (int i = 0; i < many; i++)
        std::cout << "v3 "  << arr[i] << "\n";
}

int main() {
    int arr[]{ 1,2,3,4,5 };
    displayArray_ptr(arr, 5);
}

Passing multidimensional arrays

We can also pass multidimensional arrays to functions. However, we need to specify the size of all dimensions except the first, which restricts things a little.

void displayArray(int arr[][10], int rows) {
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < 10; c++) {
            std::cout << arr[r][c] << " ";
        }
        std::cout << "\n";
    }
}

int main() {
    int md_hiScores[5][10];
    for (int game = 0; game < 5; game++) {
        for (int player = 0; player < 10; player++) {
            md_hiScores[game][player] = game * 10 + player;
        }
    }
    displayArray(md_hiScores, 5, 10);
}

Arrays using the C++ Standard Library

In Part 4we were introduced to std::vector, the C++ standard library to dynamically handle arrays. Now we are going to see how we can overcome the limitations of arrays using vectors.

Declaration of vector

Vectors are one-dimensional. But they can contain any kind of entity as elements. This feature enables us to create multidimensional vectors by specifying vectors containing vectors.

Here is the general syntax of declaring a vector:

std::vector<element_type> vec;
std::vector<element_type> vec = { initializer_list };

We specify we want to declare a vector that will contain elements of element_type. We will explain the odd syntax later when we talk about templates. For now, we just accept as it is.

Here is a vector of integers, and how to initialize if we need:

std::vector<int> vec;
std::vector<int> vec = { 1,3,5 };

Using a vector

Vector elements are accessed like ordinary array elements and are indexed the same way too, starting from 0:

std::vector<int> vec = { 1,3,5 };
std::cout << vec[1] << "\n",

A vector has built in dynamic memory management and can grow or shrink. We can add elements at the back or insert them anywhere inside. We can remove any element we want too. The only efficient operations though are adding and removing at the end it. The rest require a lot of memory move and copy operations to keep the internal array in a contiguous memory that make them inefficient.

Here is a list of the most common operations we can do on a vector:

reserveReserve initial space
push_backAppend element at the end of the vector
pop_backErase last element
clearClear all contents
sizeReturns the number of elements in the vector

Vectors as function arguments

We can pass a vector as an argument to a function. To make our function efficient we make it accept a reference to a vector and not a copy of it.

Here is a complete example of a vector:

#include <iostream>
#include <vector>

void printVec(std::vector<int>& vec) {
    for (int i = 0; i < vec.size(); ++i)
        std::cout << i << ". " << vec[i] << "\n";
}

int main() {
    std::vector<int> vec = { 1,3,5 };
    std::cout << vec[1] << "\n";
    vec.push_back(7);
    vec.push_back(9);

    printVec(vec);
}

There is a lot of weird syntax when using a vector. We will explain it Part 6 where we will talk about structures and classes.

Create multidimensional array using vectors

We can create a multidimensional array if we create a vector that has vectors as elements:

void printVec(std::vector<std::vector<int>> v2d) {
    for (int r = 0; r < v2d.size(); r++) { // over rows
        std::vector<int>& t = v2d[r]; // reference to the row, not copy
        for (int c = 0; c < t.size(); c++) { // over columns
            std::cout << t[c] << " ";
        }
        std::cout << "\n";
    }
}

int main() {
    std::vector<std::vector<int>> v2d;
    for (int r = 0; r < 5; r++) { // create the rows...
        std::vector<int> t; // create a new row
        v2d.push_back(t);  // add it to the 'array'
        for (int c = 0; c < 10; c++) { 
            v2d[r].push_back(r * 10 + c); // add elements to the row
        }
    }
    printVec(v2d);
}

printVec can go through the rows and columns using the sizeof the vectors. Using vectors, we are not restricted to having the same number of elements in each row.

From vectors to pointers

Vectors keep their data in a contiguous area. This is the same as an array on some memory we allocate with new. Its is easy to obtain a pointer to the vector data and interface with old code we might have, or with code written in the C programming language. Many external libraries expect data to be passed with pointers. One library we use in games and computer simulations is OpenGL for 3D graphics.

A pointer to the first element of a vector is the pointer to the vector data:

void displayArray_ptr(int* arr, int many) {
    for (int i = 0; i < many; i++)
        std::cout << "v3 " << arr[i] << "\n";
}

int main() {
    std::vector<int> vec = { 1,3,5 };
    std::cout << vec[1] << "\n";
    vec.push_back(7);
    vec.push_back(9);

    displayArray_ptr(&vec[0], vec.size());
}

We can use the displayArray_ptr with the vector.

Strings

String is an array of characters. C++ has two ways of handling them. The first comes from C and is called C-sttyle string handling. The other way comes from the standard library and is the most efficient and safe way to handle them.

C-Strings

This is how strings are handled in C programming. In C strings are arrays of characters. This is supported in C++ as well with the functionality available in C. These arrays are of type char and have to be terminated with a null (\0) character. In computer terms this is 0.

Since it is an array it has some space allocated for the characters. So, we need allocate one more character than the text we want to store for the terminating zero. We can allocate more space than we actually need.

Declaring a C-string

Here are some string declarations:

char str_1[] = "C++";
char str_2[4] = "C++";
char str_3[] = { 'C','+','+','\0' };
char str_4[4] = { 'C','+','+','\0' };
char str_5[100] = "C++";
const char* str_6 = "C++";

The first five declarations allocate array space and copy the constant strings in that space. The first two allocate space for the terminating 0 although we do not see it. The next two clearly point the array nature of the string. In the fifth we allocate more space than we actually need. Finally, we have a pointer to a string. In this we do not create a copy of the string, but because we have a constant string that will be in the initialized data segment we saw in Part 4, it is declared as const. Any variable declared as const maintains its contents until the program terminates.

Using a C-string

We can use a C-string to read/manipulate and print text in our programs. In the example we will create a function that counts the digits we have typed when prompted.

int count_numerals(char* str) {
    int count = 0;
    while (*str != '') {
        if (*str >= '0' && *str <= '9')
            count++;
        str++;
    }
    return count;
}

int main() {
    char txt[100];
    std::cout << "type some text:";
    std::cin >> txt;
    int n = count_numerals(txt);
    std::cout << txt << "," << n << "\n";
}

Using C-strings is quite dangerous. They are pointers to the memory and we can allocate/deallocate storage space with new and delete with all the risks it may have. We must be very carefull when copying one string to another and make sure there is always enough space for the job.

C++ strings

The internal representation of the string may be the same as in C, but the tools for handling them are very safe, robust and easy to use. They were designed to overcome the difficulties generated by the C-style strings.

Like the vectors we saw earlier they are handled by the C++ std::string class. In Part 6 we will cover classes and object-oriented programming, but now we are going to see the basics of strings and appreciate the advantages C++ gives us.

Declaring strings

To use the std::string class we need to include the <string> header. It all starts with variable declaration:

std::string str = "C++ is easy";
std::string another_str("C++ is easy");

These declare and initialize strings.

Using std::strings

It is easy to assign a new string to a C++ string, add two strings together, check if two strings are equal, and so many more. The string manipulation library is very rich and most of all intuitive to use.

std::string str = "C++ is easy";
std::string another_str("C++ is easy");

std::cout << str << "\n";
another_str = "C++ can handle strings";
str = str + " and " + another_str;
std::cout << str << "\n";
if (str == another_str)
    std::cout << "strings are equal\n";
else
    std::cout << "strings are not equal\n";

And all this functionality comes with the most efficient memory management.

From std::string to char* for C compatibility

The two key features that set C++ strings apart from C-strings are Memory Management and Functionality. Like vectors we face situations where we need to pass an old-fashioned char* to library functions. The standard library offers this as well. It provides a function to access the underlying pointer:

const char* chr_ptr = str.c_str();

We should note here that c_str returns a constant pointer. This gives us read-only access to the characters in the string, protecting us from modifying it to avoid any possible errors.

Summary

In this part we were introduced to some very useful concepts we will need to start organizing data in our programs:

The C++ Programming Language