Lecture Notes. Introductory Day

Hello!

This introductory lecture contains a few rather simple programs. I wanted to select them as mostly typical for C/C++.

Following the traditions, let me start with a sort of "hello world!" code:

  1. // hello.cpp

  2. #include <iostream.h>

  3. main()

  4. {

  5.     char name[80];

  6.     cout << "Please enter a name:" << endl;

  7.     // type anything, e.g., AER, IT, personal name, etc.

  8.     cin >> name;

  9.     cout << "Hello, " << name << " << endl;

  10.     return 0;

  11. }

If you name this file as in a comment of the top line, the regular way to compile it and execute might be:

>g++ -c hello.cpp
>g++ -o hello hello.o
>hello


Although nothing special is in this code, it certainly contains a few elements, which could create a little discomfort for C programmers, and which are mostly unfamiliar for the Fortran world. The 1st line calls the standard library file, which is responsible for input/output (C counterpart: stdio.h). The main function is of the integer type, so, you need to return something. It does not take any argument here, and simply outputs a greeting on the display. The program invites you to enter a name from your keyboard (lines 6/8). Note, that we declare the name of the character type in line 5. It is declared as the char-type array. You may substitute the declaration of line 5 by char *name; with the same result.
For the C people: if you are conservative and prefer to use printf and scanf instead of cout and cin, just do it! Note: // means a line from a current position to its end is commented. A C-type comment /*...*/ is also available, and commonly used, if a comment involves a few sequential lines. And do not forget to put semicolumns at the ends of lines!

A general information about control statements.  C++ provides three types of loop controls: while, do/while, and for. The for control is as popular, as do/end do, or, do/continue in FORTRAN. Three selection controls include a single if structure,  a double selection if/else structure,  and multiple-selection switch structure.  No problems should arise from the first two. As for the last one, below please find modification of the "Hello" code, for which the switch structure is employed.

  1. #include <iostream.h>

  2. main()

  3. {

  4.     char *newName;

  5.     int name;

  6.     cout << "The name of person whom you would like "

  7.          << "to forward your greetings." << endl

  8.          << "Type the first character of the name." << endl

  9.          << "For terminating greetings, type Ctrl D." << endl;

  10.     while ((name = cin.get()) != EOF)

  11.     {

  12.         switch (name)

  13.         {

  14.             case 'A': case 'a':

  15.                 cout << "Hello, Adam!" << endl;

  16.                 break;

  17.             case 'E': case 'e':

  18.                 cout << "Hello, Ernesto!" << endl;

  19.                 break;

  20.             case 'B': case 'b':

  21.                 cout << "Hello, Bob!" << endl;

  22.                 break;

  23.             case 'J': case 'j':

  24.                 cout << "Hello, John!" << endl;

  25.                 break;

  26.             case '\n':

  27.                 break;

  28.             default: // catch all other possibilities

  29.                 cout << "Sorry, I did not find any candidate," << endl

  30.                      << "type the first name with no abbreviations" << endl;

  31.                 cin >> newName;

  32.                 cout << "Hello, " << newName << "!" << endl;

  33.                 break;

  34.         }

  35.         cout << "More names?" << endl;

  36.         name = cin.get();

  37.     }

  38.     return 0;

  39. }
The output of your dialog may look like this:

   The name of person whom you would like to forward your greetings.
   Type the first character of the name.
   For terminating greetings, type Ctrl D.
      a
   Hello, Adam!
   More names?
      B
   Hello, Bob!
   More names?
      E
   Hello, Ernesto!
   More names?
      h
   Sorry, I cannot find any candidate,
   type the name with no abbreviations:
      Helene
   Hello, Helene!
   More names?
     

What is interesting in this program? First, you meet with a new definition of the char type: char *newName. More about strings will be given later. A brief comment: char type is an array of single characters. In C/C++ you can refer to the array by pointing at its first elementl, like this: char *newName. The pointer in this example gets an alias name, coinciding with the name of array newName.

With using a request of C-function get() by the input object cin, i.e., name = cin.get(), one character from the keyboard is read and stored in integer variable name. EOF is a symbolic integer constant defined in iostream.h header file. On UNIX systems, EOF is entered by typing ctrl-d.

The most interesting in this program is how the switch structure works. It is enclosed within the while statement. I want to draw your attention to the fact, that all cases should be identified by integer numbers. In "Hello_1" the characters in, let say, line 14 are overloaded in integers. You can easily check this by inserting a new line between lines 15 and 16: cout << name << endl;. For "a" you will arrive at 97, for "A", at 65.

Roll a Die

The next example "Roll-a-die" is also predominantly addressed to the Fortran world. It will show us, how the random number generator works. The loop structure for, strictly speaking, two for statements, will be a help, as well as the array of integer numbers frequency.

  1. // roll_a_die.cpp

  2. #include <iostream.h>

  3. #include <stdlib.h>


  4. main()

  5. {

  6.     int frequency[7] = {0}; /* number of the array elements 7, they are indexed

  7.                              from 0 to 6. all 7 elements are initialized to zero. */



  8.     cout << "Face" << " " << "Frequency" << endl; // header for the output

  9.     // Roll a 6-sided die 1000 times...

  10.     for (int attempt = 1; attempt <= 1000; attempt++)

  11.     {

  12.         int face = 1 + rand() % 6;

  13.         ++frequency[face];

  14.     }

  15.     // output

  16.     for (face = 1; face <= 6; face++)

  17.         cout << " " << face << " " << frequency[face] << endl;


  18.     return 0;

  19. }

The output on your machine might be of the following kind:

Face     Frequency
   1           157
   2           177
   3           157
   4           161
   5           165
   6           183

If you execute this program repeatedly the output will be the same. So, it's possible to conclude that the random number generator creates pseudorandom numbers. That's right. The reason for this is that rand() function generates an integer between 0 and RAND_MAX, a symbolic int constant defined in stdlib.h. For two-byte integers this constant is at least 32767, for four-byte integers, 2147483647. So, that was a reason why for having the integers randomly distrubuted between 1 and 6, we used the modulus division, as in line 15. However, rand() always selects the same random number series. If you wish to get rid of such a restriction, use function srand(unsigned int). You will observe a little improvement, if you insert the following content at the lines 9 and 10:

   9. unsigned seed = 2001; // or, any other unsigned int number...
  10. srand(seed);

However, that's still far from being perfect. This time you get a sequence of random numbers different from that due to rand(). However, it's still deterministic: executing your program again you will arraive at the same series of random numbers. Note: srand(1) produces the output identical to rand(). One of the ways for escaping from such a "determinism" is to use the system function time(NULL). It causes the computer to read its clock to obtain the insigned int seed automatically. Its easy to make such a modification. You do not need line 9 anymore. Change line 10 like this

  10. srand(time(NULL));


and fill line 4 with the include statement, which is for activating time(NULL):

   4. #include <time.h>;

In "Roll-a-die" you meet with post- & pre-incrementing. In both cases, the integer number is incremented by 1, a difference can be briefly described as follows:
Let us suppose that currentNmb = 5. Then, currentNmb is, certainly, 6 after applying currentNmb++, and/or, ++currentNmb. If we consider someFunction(++currentNmb), it is someFunction(6), but someFunction(currentNmb++) is still someFunction(5).
Note, that the starting index of an array is 0.

Towards Pointers

Arrays and strings, which we briefly discussed above, are closely related to pointers. So, they should be introducted as well: A pointer is a variable that holds a memory address. Try this simple program:

  1. // very_simple_pointer.cpp

  2. #include <iostream.h>

  3. main()

  4. {

  5.     int * ptrValue = 0;

  6.     int value;

  7.     value = 5;

  8.     ptrValue = & value;

  9.     cout << *ptrValue << endl;

  10. return 0;

  11. }

No doubts, that your output will be 5. Line 5 is a pointer declaration. Be careful: with a usual variable, like int value, you may declare it, and then, initialize, as in lines 6 and 7. A pointer declaration should be accompanied with initialization. If you have some doubts about which address should be held by the pointer, assign zero to it, like in line 5. You certainly may leave that line empty, and make line 8 responsible for declaration, like this:

   8.     int * ptrValue = & value;

The pointer operator (*) is always used in a pointer declaration, and for dereferencing a pointer, as it is done in line 9. This last operation returns a value whose address the pointer holds.

The pointer with this sort of a definition is not const, in the code below the pointer holds, first, the address of value1, then, of value2.

  1. // also_simple_pointer.cpp

  2. #include <iostream.h>

  3. main()

  4. {

  5.     int value1;

  6.     value1 = 5;

  7.     int * ptrValue = & value1;

  8.     cout << *ptrValue << endl;

  9.     int value2;

  10.     value2 = 6;

  11.     ptrValue = & value2;

  12.     cout << *ptrValue << endl;


  13. return 0;

  14. }

The situation changes "dramatically", if pointer will be declared as const. Try this in line 7:

   7.     int * const ptrValue = & value1;

Compiler returns an error in this case: Holding the address of value1, ptrValue cannot be changed to point to anything else. The value can be modified through ptrValue, but ptrValue always points to the same memory location. However, if your change looks like this

   7.     const int * ptrValue = & value1;

the program works properly. Now ptrValue points to const value, but can be changed itself. However, trying to modify the value, which pointer holds, like this

        *ptrValue = 100;

you arrive at a compile error.
The least privilege is granted by a constant pointer to constant data. Syntax is now evident:

        const int * const ptrValue = & value;

One of the most important pointer's task is to manage data on the free store. In C++ you allocate memory on the free store by using the new keyword. After finishing with managing data, you must call delete on the pointer. And remember that the free store is free, but its resources are limited, so, do not forget to check whether memory is available. In C a similar management is performed via the key words malloc and free.

  1. // pointer_heap.cpp

  2. #include <iostream.h>


  3. main()

  4. {

  5.     int * ptrValue = new int;

  6.     if (ptrValue == NULL)

  7.     {

  8.         cout << "Error! No memory for ptrValue!";

  9.         return 0;

  10.     }

  11.     // look at the address, which pointer points to

  12.     cout << ptrValue << endl;

  13.     *ptrValue = 6;

  14.     cout << *ptrValue << endl;

  15.     delete ptrValue;

  16.     return 0;

  17. }

In lines 6-10 an elementary memory control is imposed. More sofisticated way for the memory control is to use assertion. In this case you should include the standard file

   3. #include <assert.h>

Then the conditional statement is simply replaced by

   7.     assert (ptrValue != 0);

After checking that no memory problems arise, the address will be displayed due to line 12. In my desktop computer it was 0x00780DA0. In line 13 the dereferenced pointer is used to store data. Line 15 is our farewell to the pointer. After deleting it, be careful: do not use it again in the program.

Towards References

A reference is an alias: creating a reference, you initialize it with target's name. From that moment on, the reference acts as an alternative name for the target, and anything you do to the reference is done to the target.

  1. // simple_references.cpp

  2. #include <iostream.h>


  3. main()

  4. {

  5.     int value;

  6.     int & rValue1 = value;

  7.     int & rValue2 = value;

  8.     value = 5;

  9.     cout << value << ' ' << rValue1 << ' ' << rValue2 << endl;

  10.     rValue1 = 6;

  11.     cout << value << ' ' << rValue1 << ' ' << rValue2 << endl;

  12.     rValue2 = 7;

  13.     cout << value << ' ' << rValue1 << ' ' << rValue2 << endl;

  14.     return 0;

  15. }

The output is easy to anticipate:

    5   5   5
    6   6   6
    7   7   7

An easy rule for dealing with references: if you declare them, but do not initialize, a compile-time error comes.
The reference operator & is the same as that one used for the address, but they are different, although they are related.
The address of a reference is identical to that one of a target.
And do not re-assign a reference!

Passing Arguments in Functions by Value

What should we know about functions is that
1) the return statement can return only one value
2) functions should be declared in accordance with their types.
3) function's arguments are not confined to values only: arguments may be passed by reference (using pointers, and/or, references) as well.
4) among other function's types there are analogs of procedures, or, subroutines in other languages: that is the type void.
5) if necessary, a function contains no arguments.

Let us consider now the swap function which performs a permutation of arguments by value. I draw your attention to the fact that the function should be declared and defined, like we did this with variables.

  1. // swap_by_value.cpp

  2. #include <iostream.h>


  3. void swap(int, int);


  4. main()

  5. {

  6.     int x = 5, y = 7;

  7.     cout << "In Main: Before swap: x = " << x << ", y = " << y << endl;

  8.     swap(x, y);

  9.     cout << "In Main: After swap: x = " << x << ", y = " << y << endl;


  10.     return 0;

  11. }


  12. void swap (int v, int w)

  13. {

  14.     int tmpr;

  15.     cout << "In Swap: Before swap: x = " << v << ", y = " << w << endl;

  16.     tmpr = v;

  17.     v = w;

  18.     w = tmpr;

  19.     cout << "In Swap: After swap: x = " << v << ", y = " << w << endl;

  20.     // void: no return

  21. }

You will get the output, which looks like this:

    In Main: Before swap: x = 5, y = 7
    In Swap: Before swap: x = 5, y = 7
    In Swap: After swap:   x = 7, y = 5
    In Main: After swap:   x = 5, y = 7

In spite of our efforts to swap the variables, they remained unchanged. That is because local copies were made in the swap function. Let us look, what happens, if we pass the arguments by reference.

Passing Arguments in Functions by Reference Using Reference

  1. // swap_by_reference.cpp

  2. #include <iostream.h>


  3. void swap(int &, int &); // arguments: references


  4. main()

  5. {

  6.     int x = 5, y = 7;

  7.     cout << "In Main: Before swap: x = " << x << ", y = " << y << endl;

  8.     swap(x, y);

  9.     cout << "In Main: After swap: x = " << x << ", y = " << y << endl;


  10.     return 0;

  11. }


  12. void swap (int &rX, int &rY)

  13. {

  14.     int tmpr;

  15.     cout << "In Swap: Before swap: rX = " << rX << ", rY = " << rY << endl;

  16.     tmpr = rX;

  17.     rX = rY;

  18.     rY = tmpr;

  19.     cout << "In Swap: After swap: rX = " << rX << ", rY = " << rY << endl;

  20.     // void: no return

  21. }

Now you get the following output:

    In Main: Before swap: x = 5, y = 7
    In Swap: Before swap: rX = 5, rY = 7
    In Swap: After swap:   rX = 7, rY = 5
    In Main: After swap:   x = 7, y = 5

We reached our goal. The variables exchanged by their values. And this happened, because we manipulated with references. Remember, that references are aliases of corresponding variables? So, that was just our case: Manipulating with refrences, we did this with the original variables as well.

Passing Arguments in Functions by Reference Using Pointers

  1. // swap_by_pointer.cpp

  2. #include <iostream.h>


  3. void swap(int *, int *) // arguments: pointers


  4. main()

  5. {

  6.     int x = 5, y = 7;

  7.     cout << "In Main: Before swap: x = " << x << ", y = " << y << endl;

  8.     swap(&x, &y); // arguments: variable's addresses

  9.     cout << "In Main: After swap: x = " << x << ", y = " << y << endl;


  10.     return 0;

  11. }

The program is not finished. So, it will be your exercize to complete it.

Interlude

The final part of this Introductory Day involves a brief info about Recursive functions, Arrays, and Strings.

The Fibonacci Series in Recursion

There exist many modifications of this Series. The original set was proposed long time ago by italian mathematician Fibonacci. Now you can meet with the Fibonacci numbers in different sorts of applications, e.g., in biology, quantum chaos, etc.

Let us define the Fibonacci series recursively:

    fibonacci(0)=0
    fibonacci(1)=1
    fibonacci(n)=fibonacci(n-1)+fibonacci(n-2)

The recursive program looks very compact, although a rich content is hidden in a recursion procedure:

  1. // fibonacci_with_recursion.cpp

  2. #include <iostream.h>


  3. unsigned long fibonacci(unsigned long); // type: int modification


  4. main()

  5. {

  6.     unsigned long number;

  7.     cout << "Enter an integer: ";

  8.     cin >> number;

  9.     cout << "Fibonacci(" << number << ") = "

  10.             << fibonacci(number) << endl;

  11.     return 0;

  12. }


  13. unsigned long fibonacci(unsigned long n)

  14. {

  15.     if (n == 0 || n == 1) // operator ||: or

  16.         return n;

  17.     else

  18.         return fibonacci(n-1) + fibonacci(n-2);

  19. }

An exercise for you again: try to right your own program, using the C++ syntax, which would determine the Fibonacci numbers iteratively.

A word of caution about recursive programs: each level of recursion in the example from above results in doubling of the number of variable calls. Although recursive procedures are very readable and well-organized, the number of calls, when executing, grows exponentially.

Arrays

For this moment let me confine to the syntax of arrays. Remember, when you declare the array like this
arrayType arr[arraySize];

the number of the array's elements is arraySize, but, they are enumerated from 0 to arraySize - 1. Does not matter, whether you initialize it at the step of declaration, or, later. If there are fewer initializers than elements in array, the remaining elements will be initialized to zero. If you need to initialized an array to zero, you may do it like this

    float r[100]={0.0};

Multiple-dimensional arrays I would like to illustrate in two dimensions. The array int z[2][3] may be interpreted as two-dimensional matrix where the second subscript is related to columns (from 0 to 2), and the first subscript to rows (from 0 to 1). Notice that the function definition specifies the parameters of the mentioned above array as int z[][3], for example, if function void printArray calls it, the syntax should be as follows:

    void printArray(int z[] [3])
   { ...function's body...}

A possible initialization of the array

    int z[2] [3] = { {1, 2, 3}, {4, 5, 6} };

might be re-written as follows

    int z[2] [3] = { 1, 2, 3, 4, 5, 6 };

Pointers & Arrays: Relationship

the array name without a subscript is a pointer to the first element of the array.
For instance, if we declare pointer float * rPtr, we can set it to the address of the first element of array r with the statement

    rPtr = r;

This is equivalent to

    rPtr = &r[0];

The pointer arithmetic definition is based on this analogy, for example, you may dereference the tenth element of the array with the pointer expression,

    *(rPtr + 9);

or, even like this

    *(r + 9);

String as a Character Array

We met already with strings as character arrays, however, with no accompanying exlanations. Recall, in hello.cpp we declared string name as

    char name[80];

This means that with the statement

    cin >> name;

we may store from a keyboard a character array capable of storing up to 79 characters and a terminating null. If I decide to store my name into that string, the structure of the character array will be as follows:

    name = {'G','e','n','n','a','d','y','\0', .... }

The meaningless part of the array is denoted by dots. Try a program echoo.cpp. It and its modifications are for visualizing a character array structure.

  1. // echoo.cpp

  2. #include <iostream.h>

  3. #include <string.h>

  4. main()

  5. {

  6.     int stringLength = 20;

  7.     char name[stringLength];

  8.     cin >> name;

  9.     int i = 0;

  10.     while (name[i] !='\0')

  11. //  while (i < stringLength)

  12.     {

  13.         cout << name[i] << endl;

  14.         i++;

  15.     }

  16.     return 0;

  17. }

This program returns you a string, that you stored, as a vertical echo. Comment line 10 and uncomment line 11. The structure of a complete array, its meaning and meaningless parts, will be visualized.
    A popular mistake that people do with the strings: the number of stored characters coincides with stringLength. This does not leave any place for a terminating null.

Character arrays can be initialized by using the following feature of arrays: If the array size is omitted from a declaration with an initialized list, the number of elements in the array will be the number of elements in the initialized list. Let me illustrate this with my name again:

    char name[] = {'G','e','n','n','a','d','y','\0'};

The array size is evidently 8. This kind of initialization can be done in a much easier manner:

    char name[] = "Gennady";

Although the number of literal characters in name is 7, the length of name is 8 because of a terminating null.

Strings via Pointers

Initialization of my name can be also done with a variable of type char *:

    char *namePtr = "Gennady";

This last declaration creates pointer variable namePtr that points to the string Gennady somewhere in memory.

Manipulating Strings

Here I collected an elementary information on how to manipulate strings with string library functions. First two are strcpy and strncpy. How they work is shown in copy.cpp:

  1. // copy.cpp

  2. #include <iostream.h>

  3. #include <string.h> // for manipulating strings

  4. main()

  5. {

  6.     const char *name2 = "Four legs better, than two";

  7.     unsigned int n = 16;

  8.     char name1[80];

  9.     strcpy(name1,name2);    // name2 is copied in name1:

  10.     cout << name1 << endl;  // terminating null included

  11.     strncpy(name1,name2,n); // 16 characters of name2 are copied

  12.     name1[n] = '\0';        // include terminating null

  13.     cout << name1 << endl;

  14.     return 0;

  15. }

Functions strcat and strncat append the second argument, a string, to its first argument, a character array. See the example below.

  1. // append.cpp

  2. #include <iostream.h>

  3. #include <string.h> // for manipulating strings

  4. main()

  5. {

  6.     const char *name2 = "better, than two";

  7.     unsigned int n = 6;

  8.     char name1[40] = "Four legs ";

  9.     char name3[40] = "Four legs ";

  10.     strcat(name1,name2);    // just append name2 to name1

  11.     cout << name1 << endl;  // terminating null included

  12.     strncat(name3,name2,n); // 6 characters of name2 are appended

  13.     cout << name3 << endl;  // terminating null included

  14.     return 0;

  15. }

A couple of notes in connection with append.cpp: first, unsigned int and unsigned long types may be substituted by the size_t type, which is defined in the standard C library. Second, appending with strcat and strncat is accompanied by replacing a terminating null of the first argument by the first character of the second argument. A terminating null is appended to the result.

strlen is a function of int type. It returns the number of characters within a string: terminating null is not included.

Exersizes

1. Find errors, if they exist, in the following fragments:

(a)     char name[80]

(b)     int frequency[] = {1, 2, 3, 4, 5};

(c)     int a[6] = {0};
        int i = 1;
        while (i < 6)
            a[i++] = i;
        cout << a << endl;

(d) #include <iostream.h>;
    main()
    {
        cout << srand(time(NULL)) << endl;
        return 0;
    }

(e)     int *ptrValue = 0;
        int &rValue;

(f)     int value = 5;
        int &rValue = value;
        int *ptrValue = rValue;

(g) int swap (int v, int w)
    {
        int tmpr;
        tmpr = v;
        v = w;
        w = tmpr;
    }

(h)     char name[80];
        cin >> name << endl;
        while (name !='0')
        {
            case '1':
                cout << "Hello everybody!" << endl
                break;
            default:
                cout << "Enter 0, or, 1" << endl
                break;
        }
        cout << "Goodbye everybody!";

2. Write a program, which will allow you to calculate factorial recursively.

3. What is a difference between two outputs? Make necessary corrections in the case of errors.


(a)     int a[6] = {0};
        int i = 1;
        while (i < 6)
            a[i++] = i;

(b)     int a[6] = {0};
        int i = 1;
        while (i < 6)
            a[++i] = i;

4. Define void swap(int *,int*) in swap_by_pointer.cpp.

5. Write a program, which will return the Fibonacci numbers iteratively.

6. Write a program, which will tokenize a string: "The introductory day of the C++ course is over".

1