Learn C++ with Qt, Part 008: Initialization

After talking about the definition of variables and constants in the previous parts of this tutorial, I’d like to add a word or two about the initialization of variables.

If you define a constant, it is obvious that the constant has to be assigned an explicit value in order to be useful. Variables do not always require an initial value before they are used, depending on where and how they are used.

You might think that this is a slightly boring topic – but believe me, it’s an important one. I’ve seen a lot of strange behavior from programs where variables were used without proper initialization. This can lead to some strange errors. So my advice is that you read the following paragraphs and try to keep in mind that you need to initialize variables before using them for the first time.

Default values

For the standard data types in C and C++, there are default values which are used for variables of this type when they have not been assigned a specific value yet. These default values are usually set automatically by the compiler.

Numeric types like "int", "long", "float", "double" and their variations have a default value of zero ("0" or "0.0", with possible suffixes depending on the data type you use for a specific variable).

Character-based types like "char" and "string" use the "null" character ("\0") as default value. This is essentially a single character with all bits set to zero.

The “null” character is also used to mark the end of a string inside the computer’s memory. With a default, empty string, the “null” character is the first and only character in the string.

For Boolean variables which use the type "bool", there is no fixed default value. If you start the same program several times, the same "bool" variable can have an initial value of "true" or "false" – this is purely random. So whenever you are using Boolean variables, make sure that they are given an explicit initial value before you use them within your program.

Null pointers (type “nullptr_t”) are a special case – by default, they always point to a “NULL” value. A pointer variable (more on that later) can be assigned a null pointer, but their default value is undetermined.

Initializing variables (as part of their declaration)

If you want different default values for your variables before using them for the first time, you need to assign initial values to them. This is called initialization of variables.

Within a program, the initialization of simple variables which use one of the standard data types can be done in the same step as the declaration of the variable.

Alternatively you can also initialize variables in a central block of code, a function or a method that is called at the start of the main program. This kind of centralized initialization is also the default way to initialize variables that use more complex data types.

For the direct initialization of variables during their declaration there are three different methods right now in C++:

  • the c-like initialization
  • the constructor initialization
  • the uniform initialization

All of these options are equal in C++, so you can use all of them (at least with a modern C++ compiler). The result is always the declaration of a variable and the direct assignment of a specified initial value.

C-like initialization

The first one, the C-like initialization, uses a simple, direct assignment of the initial value to the newly defined variable by using an equal sign, just like in a mathematical equation:

type variable_name = initial_value;

This is called "C-like" initialization because this is the way to initialize variables in the language "C" which "C++" was derived from. With a single line of code like

int radius = 1;

we can define a variable with the name “radius” and type “int” that is given an initial value of one.

This is also the oldest and most compatible method for initializing variables that works with any C++ compiler.

Constructor initialization

Setting initial values for variables by using a constructor initialization was introduced as part of the "C++" programming language which builds up on the "C" programming language.

The syntax is slightly different from the C-like initialization. Here the initial value for the variable you define is placed between parentheses:

type variable_name (initial_value);

This form is similar to calling constructor methods for objects, hence the name. Constructor methods also initialize values, but instead of doing this just for a single variable, they do it for all variables that are part of the defined object (the object’s properties – more on that later).

Here is a short example for a constructor initialization:

int radius (4);

Uniform initialization

The third option to initialize a newly defined variable is the uniform initialization. This kind of initialization is similar to the constructor initialization and was introduced as part of the revision of the C++ standard in the year 2011.

Instead of the normal parentheses from the constructor initialization, the uniform initialization uses curly braces:

type variable_name {initial_value};

This change is connected to some additions and changes in the C++ standard.

The example from above looks like this if we use uniform initialization:

int radius {4};

All three currently available ways to initialize variables – the C-like initialization, the constructor initialization and the uniform initialization – are valid for modern C++ compilers. They are also treated equally – the result is always a newly defined variable with an assigned initial value.

Initialization after declaration

Apart from the different ways mentioned above which allow you to set initial values for your variables at the same time that you define them, there is also the option of initializing variables later, in a central code block right at the beginning of your program.

This means that you first define all your variables without initial values, and then add several lines to your program to set the initial values afterwards. These initializations can be put inside a function or procedure; you just need to make sure that the program executes these instructions right at the start, before the variables are used.

This kind of "late" initialization looks like this:

#include <QCoreApplication> #include <iostream> #include <string> using namespace std; int main(int argc, char *argv[]) { int apples; double price; double special; QCoreApplication a(argc, argv); apples = 0; price = 0.50; special = 0.2; cout << "How many apples do you want?"; cin >> apples; cout << apples << " apples cost " << ( apples * price ) << endl; cout << "But today the special price is " << ( special * apples * price ) << endl; return a.exec(); }

Bundling all of the necessary initializations in a central place like this – normally inside a special initialization procedure or function – has one advantage: you can combine these simple variable initializations with more complex ones like the initialization of arrays, structures, objects etc. and keep all of them in one place. If you want to change any initial value afterwards, you just need to remember this one place in your program, rather than several different places.

Consequences of missing initializations

It’s difficult to state the possible consequences of using variables without previous initialization as it really depends on where and how you use them in your program, but I’ll give it a try…

Numerical variables without initial values are only problematic when you use them as the divider in a calculation. This would lead to a division by zero, which mathematically is equal to infinity. As the computer can only handle finite values within a certain maximum range, this will lead to an error in the program. In such cases, you should add a short value check before the actual calculation.

In other cases a numerical value of zero will not produce an error in a calculation, but it might lead to wrong or confusing output (e.g. as part of a calculated value using multiplication) or equally confusing program behavior (e.g. when the value is used in the calculation for a control structure).

Empty characters and strings are no problem if they are just part of some text output, but can be difficult if they are used in certain control structures. It makes sense to include some check or branching option for empty characters or strings. Most of the time though, non-initialized character and string variables will not lead to the crash of a program.

Boolean variables without defined initial values can lead to unexpected behavior of a program. Due to the random nature of the initial value of a non-initialized "bool" variable, the first usage of the variable leads to inconsistent results when the program is run again and again. While this seldom breaks a program, you effectively lose control over the program’s behavior, so it’s better to initialize Boolean variables.

Pointer variables are usually set to an initial value of a "null" pointer automatically by most C++ compilers. However, it makes sense to add a proper initialization, even if the pointer is assigned a null pointer value. If the initial value isn’t set automatically somehow, the pointer will likely contain a random address value. Once the program uses the pointer to access the computer’s memory and possibly change values at the given memory address, this can easily crash the program and – if things go bad – even the whole operating system. So a proper initialization of pointers is absolutely necessary.

Final words / Code download

Proper initialization can avoid several logical problems in a program. If your program crashes or shows unexpected behavior, it can be caused by a missing initialization.

The code from above can be found in the "Variables-Initialization" subdirectory inside the complete tutorial code repository on GitHub. If you have git or GitHub Desktop installed on your computer, you can clone or sync the git repository, otherwise you can download everything as a ZIP file.

I hope you come back for the next part of this tutorial…