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:
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.
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.
|
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
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:
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.
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:
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:
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
Yes we can do that, but this is not always convenient. It turns out, though, that we still can use the usual array notation
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:
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:
The following example will illustrate several things about structures. Please notice while you reading:
You can find a complete program that works with structured types here.
Exercises.
- 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.
- Write a function printIntArray that takes a variable of IntArray type as an argument and print this array.
- Write a function getIntArray that takes a variable of IntArray type as
a changeable argument and does the following:
- asks the user how many number there will be in the array;
- checks the user's input for correctness;
- reserves the needed amount of memory (use operator new);
- reads all numbers from the user and puts them into the array.
- Write a function maxInIntArray that takes an IntArray variable as its argument and returns the biggest element in the array.
- Write a function minInIntArray that takes an IntArray variable as its argument and returns the smallest element in the array.
- 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 |