Search

6.2 — Arrays (Part II)

This lesson continues the discussion of arrays that began in lesson 6.1 -- Arrays (part I)

Initializing fixed arrays

Array elements are treated just like normal variables, and as such, they are not initialized when created.

One way to “initialize” an array is to do it element by element:

However, this is a pain, especially as the array gets larger. Furthermore, it’s not initialization, but assignment. Assignments don’t work if the array is const.

Fortunately, C++ provides a more convenient way to initialize entire arrays via use of an initializer list. The following example initializes the array with the same values as the one above:

If there are more initializers in the list than the array can hold, the compiler will generate an error.

However, if there are less initializers in the list than the array can hold, the remaining elements are initialized to 0 (or whatever value 0 converts to for a non-integral fundamental type -- e.g. 0.0 for double). This is called zero initialization.

The following example shows this in action:

This prints:

7
4
5
0
0

Consequently, to initialize all the elements of an array to 0, you can do this:

If the initializer list is omitted, the elements are uninitialized, unless they are a class-type.

Best practice

Explicitly initialize arrays, even if they would be initialized without an initializer list.

Omitted length

If you are initializing a fixed array of elements using an initializer list, the compiler can figure out the length of the array for you, and you can omit explicitly declaring the length of the array.

The following two lines are equivalent:

This not only saves typing, it also means you don’t have to update the array length if you add or remove elements later.

Arrays and enums

One of the big documentation problems with arrays is that integer indices do not provide any information to the programmer about the meaning of the index. Consider a class of 5 students:

Who is represented by testScores[2]? It’s not clear.

This can be solved by setting up an enumeration where one enumerator maps to each of the possible array indices:

In this way, it’s much clearer what each of the array elements represents. Note that an extra enumerator named max_students has been added. This enumerator is used during the array declaration to ensure the array has the proper length (as the array length should be one greater than the largest index). This is useful both for documentation purposes, and because the array will automatically be resized if another enumerator is added:

Note that this “trick” only works if you do not change the enumerator values manually!

Arrays and enum classes

Enum classes don’t have an implicit conversion to integer, so if you try the following:

You’ll get a compiler error. This can be addressed by using a static_cast to convert the enumerator to an integer:

However, doing this is somewhat of a pain, so it might be better to use a standard enum inside of a namespace:

Passing arrays to functions

Although passing an array to a function at first glance looks just like passing a normal variable, underneath the hood, C++ treats arrays differently.

When a normal variable is passed by value, C++ copies the value of the argument into the function parameter. Because the parameter is a copy, changing the value of the parameter does not change the value of the original argument.

However, because copying large arrays can be very expensive, C++ does not copy an array when an array is passed into a function. Instead, the actual array is passed. This has the side effect of allowing functions to directly change the value of array elements!

The following example illustrates this concept:

before passValue: 1
after passValue: 1
before passArray: 2 3 5 7 11
after passArray: 11 7 5 3 2

In the above example, value is not changed in main() because the parameter value in function passValue() was a copy of variable value in function main(), not the actual variable. However, because the parameter array in function passArray() is the actual array, passArray() is able to directly change the value of the elements!

Why this happens is related to the way arrays are implemented in C++, a topic we’ll revisit once we’ve covered pointers. For now, you can consider this as a quirk of the language.

As a side note, if you want to ensure a function does not modify the array elements passed into it, you can make the array const:

Determining the length of an array

The std::size() function from the <iterator> header can be used to determine the length of arrays.

Here’s an example:

This prints:

The array has: 8 elements

Note that due to the way C++ passes arrays to functions, this will not work for arrays that have been passed to functions!

std::size() will work with other kinds of objects (such as std::array and std::vector), and it will cause a compiler error if you try to use it on a fixed array that has been passed to a function! Note that std::size returns an unsigned value. If you need a signed value, you can either cast the result or, since C++20, use std::ssize() (Stands for signed size).

std::size() was added in C++17. If you’re still using an old compiler, you have to use the sizeof operator instead. sizeof isn’t as easy to use as std::size() and there are a few things you have to watch out for. If you’re using a C++17-capable compiler, you can skip to section “Indexing an array out of range”.

The sizeof operator can be used on arrays, and it will return the total size of the array (array length multiplied by element size).

On a machine with 4 byte integers and 8 byte pointers, this printed:

32
4

(You may get a different result if the size of your types are different).

One neat trick: we can determine the length of a fixed array by dividing the size of the entire array by the size of an array element:

This printed

The array has: 8 elements

How does this work? First, note that the size of the entire array is equal to the array’s length multiplied by the size of an element. Put more compactly: array size = array length * element size.

Using algebra, we can rearrange this equation: array length = array size / element size. sizeof(array) is the array size, and sizeof(array[0]) is the element size, so our equation becomes array length = sizeof(array) / sizeof(array[0]). We typically use array element 0 for the array element, since it’s the only element guaranteed to exist no matter what the array length is.

Note that this will only work if the array is a fixed-length array, and you’re doing this trick in the same function that array is declared in (we’ll talk more about why this restriction exists in a future lesson in this chapter).

When sizeof is used on an array that has been passed to a function, it doesn’t error out like std::size() does. Instead, it returns the size of a pointer.

Again assuming 8 byte pointers and 4 byte integers, this prints

8
2

Author's note

A properly configured compiler should print a warning if you try to use sizeof() on an array that was passed to a function.

The calculation in main() was correct, but the sizeof() in printSize() returned 8 (This size of a pointer), and 8 divided by 4 is 2.

For this reason, be careful about using sizeof() on arrays!

Note: In common usage, the terms “array size” and “array length” are both most often used to refer to the array’s length (the size of the array isn’t useful in most cases, outside of the trick we’ve shown you above).

Indexing an array out of range

Remember that an array of length N has array elements 0 through N-1. So what happens if you try to access an array with a subscript outside of that range?

Consider the following program:

In this program, our array is of length 5, but we’re trying to write a prime number into the 6th element (index 5).

C++ does not do any checking to make sure that your indices are valid for the length of your array. So in the above example, the value of 13 will be inserted into memory where the 6th element would have been had it existed. When this happens, you will get undefined behavior -- For example, this could overwrite the value of another variable, or cause your program to crash.

Although it happens less often, C++ will also let you use a negative index, with similarly undesirable results.

Rule

When using arrays, ensure that your indices are valid for the range of your array!

Quiz

1) Declare an array to hold the high temperature (to the nearest tenth of a degree) for each day of a year (assume 365 days in a year). Initialize the array with a value of 0.0 for each day.

2) Set up an enum with the names of the following animals: chicken, dog, cat, elephant, duck, and snake. Put the enum in a namespace. Define an array with an element for each of these animals, and use an initializer list to initialize each element to hold the number of legs that animal has.

Write a main function that prints the number of legs an elephant has, using the enumerator.

Quiz answers

1) Show Solution

2) Show Solution


6.3 -- Arrays and loops
Index
6.1 -- Arrays (Part I)

332 comments to 6.2 — Arrays (Part II)

  • 1: double temperature[365] { };

    2:#include <iostream>

    namespace Animals
    {
        enum Animals // The name of this enum could be omitted since it isn't used anywhere
        {
            chicken,
            dog,
            cat,
            elephant,
            duck,
            snake,
            max_animals
        };
    }

    int main()
    {
        int legs[Animals::max_animals]{ 2, 4, 4, 4, 2, 0 };

        std::cout << "An elephant has " << legs[Animals::elephant] << " legs.\n";

        return 0;

  • Tony

    My solution to quiz 2 :p

  • sims7564

    On a machine with 4 byte integers and 8 byte pointers, this printed:

    64
    4

    Assuming 4 byte integers, this should print:

    32
    4

  • Jimmy

    I read somewhere that if i use static_cast on enum class then maybe I shouldn't use enum classes in the first place. Is this true? if it is what are some alternative solution to this ?

    • nascardriver

      You don't need to wrap `Animal_Type` in a `namespace`. `enum class` only allows scoped access, making the namespace redundant.
      If you're using an `enum class` to index an array, you'll have to use a `static_cast` every time you use an enumerator. In that case, a regular `enum` inside a `namespace` can be a better choice, because it is implicitly convertible to `int`.

  • ammawn

    "sizeof isn’t as easy to use and std::size()"
    should be
    "sizeof isn’t as easy to use as std::size()"
    i think

  • Arvand

    Hello,

    the output of my code:
    max_animals: -858993460
    A duck has 2 legs

    can someone please explain to me why the max_animals holds that arbitrary (or not arbitrary) value?

  • samira

    Does my solution look good for question #2?

    • nascardriver

      In your switch-statement, you're switching an enum, so your cases should check for enumerators, not integers. If you use integers and you modify the enum, your code breaks (Because the integers are no longer correct).
      Note that `Animals::NUMBER_of_ANIMALS` in line 49 has no effect. You can keep it for documentation purposes, but practically it's the same as

      • samira

        Hello,
        Thank you so much for your tips.

        I modified it.

        Q1: I got the warning "enum type Animal::Animal is unscoped, prefer 'enum class' over 'enum'. Although we use namespace for 'enum', I was wondering why we still get this warning?!

        Q2: what is the wrong with having switch cases integers instead of enumerators as behind the scenes they are converted to their integer equivalents ?

        • nascardriver

          Q1: Even when nested in a namespace, an `enum` can still be misused, eg. by using integers in a switch. An `enum class` wouldn't have allowed you to do this.

          Q2: Magic numbers are bad. No one except you in this moment knows what they mean.
          If you update `Animals`, you code breaks. For example, if you remove `CAT`, `getAnimalName` returns wrong results. If you add an animal and don't add it to the end of the enum, your code breaks. Just because 2 things have the same value doesn't mean you can interchange them.

  • ZAbih

    > Note that this “trick” only works if you do not change the enumerator values manually!
    Is that OK if we change the order of enumerators and add enumerators from the top, like the following ? Then what happened to this code below?

    • nascardriver

      The code continues to work as expected, as long as you're using the enum consistently. If you're using a manual index somewhere, you'll break your code.

  • Eric

    Quiz 1:
    Did I misinterpret? I thought it was suggested to use:

    "As of C++11, the uniform initialization syntax should be used instead:

    but the quiz answer is

    I wrote a function to loop through and initialise all elements to 0.0.

    Thanks for a confirmation!

    • nascardriver

      The quiz is using list (uniform) initialization, it's just that the list is empty. This causes all elements to be initialized to 0.

  • Taras

    Hi dear teachers! I tried to solve task 1 but has faced the wrong output. I expect to see 0.0 but it outputs only 0.  Can't figure out what I have missed.

    • nascardriver

      You need to add `std::fixed`

      That way the decimal places are printed even if they're trailing zeroes.

  • Gabe

    Quiz 1

    Quiz 2

Leave a Comment

Put all code inside code tags: [code]your code here[/code]