C++ Syntax: More Information about Variables

Scope rules for variables

We mentioned in the previous lectures that variables could be defined in different places in programs. Now we are ready to discuss what difference it makes to define a variable inside a function, outside of all functions, or inside an instruction block in a function. To start this subject, we introduce the idea of scope of a variable. The scope of a variable is the area where this variable is visible or accessible by instructions. For example, if we have two functions: then variable a is accessible everywhere inside the first function, but not in the second one, while the variable b is visible inside fun_two, but not it fun_one.

We did not talk about it, but we also can define new variables inside any instruction block; that is any set of instructions bounded with curly brackets { and }. For example, we can define a new variable inside a loop or an if statement:

Now we would like to discuss two questions about of variable's scopes. Namely:

  • where the existence of a variable ends
  • where a variable can be used.
    The answer to the first question is a variable ends its existence when the block where it is defined ends. Once a block is over, the variables defined in it no longer exist. For example, in the following code variable i no longer exists after line 8 because the block where it is defined (lines 4--8) is over.

    The following scope rules answer the second question.

    1 If we have two blocks, one within another, then a variable declared in the outer block can be accessed in both the outer and the inner blocks. Variables declared inside the inner block cannot be accessed in the outer block.
    2 If we have two nested blocks and an identifier is declared as a variable in both of them, then these are two different variables with the same name. The one defined in the inner block is accessible only in this block and cannot be accessed from outside the block. The other variable exists only in the outer block and cannot be accessed in the inner block. Since these variables are distinct the changes made to one of them do not affect the other.
    3 The whole program is to be considered as the most outer block. That is the variables declared in the program (outside of any function) can be accessed everywhere in the program. Such variables are called global variables. A variable declared in a block is called a local variable for the block.

    Let us illustrate these rules with the following example: In the program we defined two functions, one is the main function, and the other is the printVars function. Each function has an inner block in it. We defined some variables in each function and their nested blocks. We also declared a couple of global variables. All variables are initialized and their values supposed to stay unchanged during the execution of the program. Now let's try to analyze the output if the program:

    The main function variables:
    a = 3 (defined here)
    b = 2
    c = 4 (defined here)
    Variables in the nested block in the main function:
    a = 3
    b = 2
    c = 4
    d = 5 (defined here)
    The printVars function variables:
    a = 1
    b = 6 (defined here)
    e = 7 (defined here)
    Variables in the nested block in the printVars function:
    a = 1
    b = 9 (defined here)
    e = 7
    f = 8 (defined here)
    
    Once the main function starts working, it declares two integer variables (a and c), then it immediately prints the values of all visible variables: local variables a and c and global variable b. Notice that although there is a global variable a equals 1, the value of a printed by the program is 3 because the main function cannot see the global variable a due to the fact that local a is defined in the block. We hope the following table explains the values printed by the program.
    Variable scopes for example
    Global variables:
    a = 1
    b = 2
      main function:
    a = 3 (local)
    b = 2 (global defined for the whole program)
    c = 4 (local)
     
      Nested block inside main function:
    a = 3 (external for this block defined in main)
    b = 2 (global defined for the whole program)
    c = 4 (external for this block defined in main)
    d = 5 (local variable defined inside this block)
     
     
     
    printVars function:
    a = 1 (global variable defined for the whole program)
    b = 6 (local variable defined here)
    e = 7 (local variable defined here)
      Nested block inside printVars function:
    a = 1 (global variable defined for the whole program)
    b = 9 (local variable defined in this block)
    e = 7 (external variable defined in the function printvars)
    f = 8 (local variable defined in this block)
     
     
     

    One-dimensional arrays

    Let us imagine that we need to create a program, which reads 4 integer numbers from the user, computes their average and dispersion. Dispersion is a square root of the sum of the square differences between each number, and the average divided by the quantity of the numbers. For example, if our numbers are a1, a2, a3, and a4, then the average will be

    avg = (a1 + a2 + a3 + a4) / 4;
    and the dispersion
    disp = sqrt( ( (avg-a1)2 + (avg-a2)2 + (avg-a3)2 + (avg-a4)2 ) / 4 )
    From the first glance, it seems we have written a program very similar to this one, but this program has one big difference, namely, we need to remember all the numbers the user entered because we are not able to start computing the dispersion until we know the average and we don't know the average until we read all the numbers from the user. Since it's very impolite to ask the user to enter the same numbers again, we need to memorize them in the program. So, our first version can look like this: This program works just fine for four numbers, but what should we do if we need to do the same thing with 5, or 10, or 200 numbers? Of course, for 5 or even for 10 numbers we can modify lines 12 and 15-17 and add more variables, but this method is practically impossible for big number. So, the question is, how can we define and deal with a big amount of variables without declaring each variable separately. Idea of arrays gives us the answer. Instead of defining ten variables a1, ..., a10, we can define an array a containing ten elements. The general syntax for defining an array is:
    variable_type variable_name[number_of_elements_in_array];
    For example, to declare an array a containing 10 integer numbers we need to write:

    How to deal with arrays and their elements

    Notation: can be considered as equivalent to But in the array case, we do not need to list all our elements and we can also access each element not only with constant index like: but also using an integer variable like in this example: Please notice that the very first element of the array has index 0, not 1. So, the very last element of 10-element array will have index 9. Using array we can easily modify example and make it usable for any number of integers:

    It is useful to understand what happens when you declare an array in your program. If you define a 10-element integer array in your program, compiler allocates 10 times as many bytes as needed to keep one integer value and remembers the address of the very first element. All these allocated bytes are placed one after another in the memory so the compiler does not have to remember the location of each element. If you need to access the third element of the array (a[2]), then the compiler just computes the offset from the first element (it needs to take the address of the first element, which it remembers, and add two times the number of bytes for one element). For example, the program line int a[5] will correspond the following picture: There is one very important moment in this scheme: the compiler uses the specified size of the array only when it is allocating memory for it and forgets the size after that moment. That is, when the program runs you can actually use illegal indexes. For example, for the example above we can try to run Index 6 corresponds to the 7th element of the array but our array a - has only 5 elements in it. Nevertheless, the program will run this instruction using the algorithm described above. It will take the address of the first element then adds 6 times the number of bytes for one int (which is usually 4) and will put the new value at the computed location. Thus, we can accidentally change values of other variables without even knowing it. This program shows how this can be done. In particular, this means that it is your responsibility to take care of the indexes and check if they are inside legal ranges.

    Initialization. Arrays as function arguments

    We can initialize an array at the moment when we declare it, just the way we initialized usual variables: Notice that the number of the constants inside the curly brackets must match the size of the array. When the size of the array is relatively big, it can very annoying compute all these numbers on the right side of the assignment operator. To avoid this we can use a shorter form of array declaration. We don't have to specify the size of the array if we immediately assign values: This notation is equivalent to the one above. The compiler counts the numbers of constants on the right and allocates the required amount of memory automatically. Don't forget that this method can be used only when we initialize the array in the declaration instruction. We cannot write just and hope that compiler will guess the desired size of the array.

    However, sometimes we can see the notation like this int a[] without size. This structure is used when we need to pass an array as an argument of a function. Let's consider an example of function that searches for the biggest element in an array: We can use the construction without array size here because we do not define an array here we just tell the compiler that the first argument of the function is not a usual variable but an array of integers. Here is a main that calls this function:

    Pointers. Operators & and *

    As we saw above, the compiler allocates memory for array. That is, we must specify the size of each array before compiling our program. Unfortunately, we not always know how big should our array be to satisfy users. It would be good to be able to allocate memory for arrays later when the program is already running and can figure these sizes out (ask the users, for example). C++ allows us to create so called dynamic arrays. But before explaining how to use them we need to explain the idea of pointer.

    Assume, we declared the following variables: ,then the picture of the memory will look something like this where the addresses on the left are the numbers of the memory cells where these variables are stored. So , we can tell that variable a takes cells with numbers 10DC--10E0, variable ch takes cell number 10DB, etc. It's an old tradition use hexadecimal notation for memory addresses. In usual decimal notation the numbers will be:

  • a: 4317--4320
  • ch: 4316
  • s: 4308--4315
  • array: 4296--4307
    Using C++ operator & we can get to know the address of any declared variable when the program is running: There is another operator which is a pair to the operator & this is operator *. This operator allows us to take an address and see what is stored in the memory cell(s) with this number. Thus, the star operator * is the opposite of taking the address operator &. That is, *(&a) is exactly the same as a itself. We first took the address of variable a and then looked at what located at this address, which is exactly the value of a.

    Thus, if a variable addr contains an address of a particular memory cell, then the expression *addr gives us the value stored at this address. However, the big question is, how does the compiler know what value we are interested in, or in other words, at how many bytes it should look? Here we come to the idea of pointer. Pointer is an address that also contains information of what type of variable is located at this address. Pointer says, "I point to an integer value", or "a double value". To declare a pointer variable, we need to use the star operator: You can read this notation as "if an operator star is applied to an address contained in variable int_ptr, then the value is integer". That is, int_ptr is a pointer to an integer. The following code illustrates how we can access variable know its address: In this example, we define two integer variables a and b and a pointer to an integer ptr. At line 4, we initialize the pointer and from now on it points at the location of variable b. Line number 5 is the most important and confusing line of the code. Let's read it step by step.

  • First of all, we add 1 to the value of pointer ptr. By doing this, we tell the compiler that we are interested in the next integer value allocated after the place where b is stored. If you print values of ptr and ptr+1 you'll see that actual difference between these values is equal to ... 4, not 1. It happens because if b is stored in memory cells with numbers 1230--1233, then the next value after it will be stored in cells 1234--1237. See the picture below.
  • Second, since compilers allocate space for variables starting from higher addresses address of a is bigger than address of b, and if prt points to b, then ptr+1 points at a.
  • When we use operator star applied to the pointer ptr+1, we command the program to reach the memory cells where ptr+1 points at.
  • And finally, we command the program put new value 100 at this location.
    Thus, line number 5 is equivalent to a = 100. You can get the complete text of the program and play with it to understand ho wit actually works.
  • All these things about pointers, addresses, and star operator may look very confusing for the first time. And honestly, you don't need to actually know them for this course. If you keep programming on C++ you won't avoid it anyway. For the course, though, you can take all the above as long explanation of the following:

    1 The general syntax to describe a pointer to a value of a specific type is
    variable_type *pointer;
    2 Incrementing a pointer by one makes the pointer to point to the next value of the type the pointer points at. For example, if ptr is a pointer to double, then ptr+1 points 8 bytes away from the ptr because a value of the type double takes exactly 8 bytes. If ptr points to a char value, then ptr+3 points 3 bytes away from the byte ptr points at because a value of the type char takes exactly one byte.

    Dynamically allocated arrays. Operators new and delete.

    Now we are ready to talk about dynamically allocated arrays. Dynamic arrays unlike static arrays, which are to be declared with specified size before the program compiled, can be allocated during the execution of the program. There is a special C++ operator new that reserves a required amount of memory and returns a pointer to the reserved memory. The general syntax of the operator is:

    pointer = new variable_type[number_of_elements];
    So, to create a dynamic array we first need to define a pointer where we will store the location returned by the operator new (of course, type of pointer should correspond to the type of the array), and then use the operator new to allocated amount of memory you need. Here is an example where we dynamically create an array to store 15 double numbers:

    Once we created a dynamic array, we need to use it. The next question is, how to access its elements? Since we have a pointer to the memory reserved for the array we can use

  • *dyn_ptr to access the first element;
  • *(dyn_ptr+1) to access the second element;
  • *(dyn_ptr+N) to access the (N+1)th element of the array.
    Yes we can do that, but this is not always convenient. It turns out, though, that we still can use the usual array notation
  • a[0] for the first element;
  • a[1] for the second element;
  • etc.
    That is, in terms of usage dynamic arrays do not differ from static ones.

    Dynamic arrays also have another advantage over static arrays. The memory reserved for keeping their elements can be de-allocated during the program execution and can be used by other programs or other arrays in the same program. To free the memory of a no longer needed dynamic array, use operator delete. The general syntax for the delete operator is:

    delete pointer;
    This operator will free the previously reserved memory, which the variable pointer is pointing at. Now we can modify example and make it work with any number of users want to enter. To do so, we will use a dynamic array to store numbers and ask user at the very beginning how many numbers he/she want to enter: Strictly speaking, line 23 is not necessary. This line manually release the memory we allocated in line 13, but all reserved memory will be allocated automatically once the program stops. However, later we'll use combination of operators new and delete to change the size of an array "on fly".

    Combining different data types together. Defining new types.

    Sometimes it's very convenient to treat a set of different variables together as one item. For example, if we need to remember a date, then we need to have a variable for day, variable for month, and a variable for year. Another example, as you saw above for every array we need to remember its size to check that our indexes are inside the legal range; that is, it's very convenient to keep an array (or a pointer for dynamic arrays) and its size as a single item. This convenience becomes necessity if we have a lot of similar data to work with, for example, a bunch of different dates, of a set of arrays.

    C++ has a special structure that allows us to combine several variables together and treat them as one bigger variable. This structure is called struct. The general syntax is this:

    struct VariableName {
       var_type var_name_1;
       var_type var_name_2;
       ...
       var_type var_name_N;
    };
    
    Here are two examples: These lines above defined two new types. One is called Date and the other DynamicDblArray. When we defined a new type we can declare variables of that new type just like we declare variables of basic types: New variables birthday and today are structures containing Day, Month, and Year in them and variable numbers is a structure that contains a pointer to a double and an integer. We can also initialize these variables at the same moment we declare them:

    Now we do know how to define a new type that contains variables of several different types in it and how to declare variables of that type and initialize them. The only thing we need to know is how to extract information out of a structure, or, in other words, how to access every element inside a structure variable. A special operator . (dot) allows us to that. The general form is:

    structure_variable_name.element_name
    For example, to print just year of the variable birthday and to assign a new month we need to write

    The following example will illustrate several things about structures. Please notice while you reading:

  • how to pass a structure to a function;
  • how to make it changeable by the function;
  • how to reach every element of a structure;
  • case sensitivity of C++ (Date and date are different things).
    You can find a complete program that works with structured types here.

    Exercises.

    1. Create a new structured type IntArray for a dynamic array of integer numbers. This type should include a pointer to an integer and integer for size of the array.
    2. Write a function printIntArray that takes a variable of IntArray type as an argument and print this array.
    3. Write a function getIntArray that takes a variable of IntArray type as a changeable argument and does the following:
      1. asks the user how many number there will be in the array;
      2. checks the user's input for correctness;
      3. reserves the needed amount of memory (use operator new);
      4. reads all numbers from the user and puts them into the array.
    4. Write a function maxInIntArray that takes an IntArray variable as its argument and returns the biggest element in the array.
    5. Write a function minInIntArray that takes an IntArray variable as its argument and returns the smallest element in the array.
    6. Write a program that combines all the previous parts together and reads an array from the user, prints it back, and also prints the max and min element of the array. Comment each function in your program, the new type, and the whole program itself.

    References

    Book Chapter/Chapter.Section
     5.1--5.3, 6.1, 10.1--10.2
     9.Arrays, 11 all, 14 all but Classes.
     4.1--4.8, 5.1--5.3, 6.1--6.4, 15.3, 16.1--16.4
     6.1, 9.1--9.3, 11.1--11.2
    Keywords: operator &, operator *, operator ., new, delete, struct, pointer, array, scope, dynamic array, static array