1.124J Foundations of Software Engineering

Recitation 1


Recitation: Friday 9/8/00, 11-12:00 (1-390)
TA: Petros Komodromos, 1-245, petros@mit.edu
 
 

a/a
Topics
1
Course goals & content, references, recitations & office hours
2
Problem set 0
3
Compilation
4
Debugging
5
Makefiles
6
Concurrent Versions System (CVS) 
7
Electronic Turnin
8
Introduction to C++ 
9
Data Types
10
Variable Declarations and Definitions
11
Operators
12
Expressions and Statements
13
Input/Output Operators
14
Preprocessor directives
15
Header files
16
Control Structures


1. Course Goals and Content

  •  C++:

  •  Procedural and Object Oriented Programming
     Data structures
     Software design and implementation
     
  • Algorithms:

  •  sorting (insertion, selection, mergesort, quicksort, shellsort, hashing)
     searching (linear search, binary search, binary search trees)
     
  • Java:

  •  Object Oriented Programming (OOP) using Java, Java application and applets,
     Graphical User Interfaces, Graphics using Java
     
  • Advanced  topics:

  •  Geometric algorithms, Java3D, Database design using  JDBC, etc.
     
  • Term project

  •  

    Textbooks - References:

  • C++
  • Algorithms
  • Java
  • "The Java Tutorial", Mary Campione and Kathy Walrath, (required)
  • "Core Java", Gary Cornell and Cay Horstmann, 2nd edition
  • "The Java programming language", Ken Arnold and James Gosling, 2nd edition
  • "Java: How to program", Deitel & Deitel, 2nd edition

  • Java 2 Platform API Specification
    Office Hours:
    The office hours of the course will be as follows:

     Prof Amaratunga: Tuesdays and Thursdays, 4-5:00 p.m., Room 1-274
     Petros: Fridays 12-2:00 pm, Room 5-332
     Eric: Mondays, 3:00-5:00 p.m, Room 5-332
     

    Problem Sets

    You need to follow the instructions that are provided with each problem set, concerning what you must submit. In all problem sets you must both electronically turnin the source code files, and submit hardcopies of all completed, or modified, source code files. Sometimes you may also need to provide screen dumps of the window with the output results from the execution of your programs.

    The problem statement and the provided source code files can be obtained using CVS, which is a version control system (covered later in this recitation). The problem set statement can also be obtained through the class homepage (http://command.mit.edu/1.124_F00/home.nsf)
    Whenever source code files are provided, the following naming convention is used:
     For each question there is a ps<number_of_problem_set>_<number_of_question>.C which you may need to use, e.g. ps3_2.C for question 2 of problem set 3.
     In some cases a makefile (named make<number_of_problem_set>) is provided, which you may use to compile and link your code

    Please comment your code to make it more readable whenever you think it would be helpful for someone else to understand what and how you do it (i.e. for the graders). Comments may be incorporated in your code by either enclosing them between /* and */, or, by putting them on the right side of two division symbols //.
    It is also very useful for both yourselves, and the graders, to indent your code in order to emphasize loops and different parts of your code. You should indent your code so that different blocks start in different columns making it less obscure and difficult to understand.
    Please, do not make any other changes to the provided code, except those that you are asked to make.

    You can print a file in a compact form (saving some paper) using the following command so as to have the name of the file and the time and date printed on the hardcopy.
            athena% enscript   -2Gr    -P<printer name>   <filename>
     Whenever necessary, you can dump an X window directly to a printer using the following command and clicking on the window you want to print.
            athena% xdpr  -P<printer name>

    Homework that is turned in late will be penalized as follows:
     


    Please, always staple your hardcopies together and write clearly on the first page your name and username. Also, type within a comment at the top of each file you submit the following information:


    2. Problem Set # 0

    The statement for problem set # 0 is available through the course homepage. However, to get both the problem set statement and the provided source code files and makefiles you need to use CVS. The purpose of this problem set is mainly to provide you with information related to the compiling and debugging process, the use of makefiles and header files, the electronic turnin of your homework solutions and the use of CVS.

    This problem set will be checked (its electronic turnin, in particular), but not graded. It is in your interest to spend some time getting familiar with Athena and the introductory topics covered in this problem set. Although for this problem set you don't have to submit any hardcopies (printouts), for all following problem sets you have to submit hardcopies of your source code files, in addition to the electronic turnin.

    The provided source code files are in the source repository directory /mit/1.124/src from where you can check them out to your directory using CVS, and then, make the necessary additions and/or modifications.

    To use CVS in order to check out a problem set (or any other provided files) from the 1.124 you should set the environment variable CVSROOT as below: (you can also put it in your .environment dotfile to avoid repetition)
         % setenv CVSROOT /afs/athena.mit.edu/course/1/1.124/src
    then you can use either of the following commands:
        % cvs co Problems/PS0
        % cvs co OOP_PS0                          (alias defined in 1.124/src/CVSROOT/modules)


    3. Compilation

    After writing the source code of a program, e.g. using an editor such as emacs, you must compile it, which translates the source code into machine instructions. For the C++ programs you need to use the GNU g++ compiler. To be able to use this compiler, you need to add its locker using the following command (on the Athena prompt):
           % add -f gnu

    You can customize your account, using a dotfile, so that it will automatically add the gnu locker at start-up. In the file .environment you need to add the following line:
           add -f gnu
    The add command attaches the specified locker to your workstation and adds it to your path. Dotfiles, such as .environment and .cshrc.mine, can be used to set environment variables, shell aliases and attach lockers in order to get the desired working environment. In the .environment dotfile you may also put the following lines to avoid typing them every time you log in:
           add infoagents
           add  1.124
           setenv CVSROOT /afs/athena.mit.edu/course/1/1.124/src

    You can check if you properly use the GNU compiler by giving the following commands:
        % which g++                   which should give you something like:
          /mit/gnu/arch/sun4x_55/bin/g++     or     /mit/gnu/arch/sgi_53/bin/g++

    Then, you can use the GNU compiler to compile a C++ source code file. For example, to compile and link the source code file ps0_1.C, which is provided in PS0, you can use the following command:
         % g++  ps0_1 .C

    Then, you can run the generated executable file, which is by default named a.out, by typing its name at the Athena prompt.
    To give a specific name to the generated executable the -o option must be used:
         % g++  ps0_1 .C -o  ps0_1
    Then, the generated executable file is named ps0_1
    Sometimes, you may need to include an external library, e.g. the math library, using the -l option as follows, and the name of the external library (below the math library is included using m after -l) you want to include (in addition to including its header file in your files):
          %   g++    ps0_1 .C       -o  ps0_1     -lm

    In some cases that you only need to compile a file without linking, i.e. to generate only the corresponding object file (machine language versions of the source code) and not the executable, you need to use the -c option. (In the following example a ps0_1.o will be generated)
         % g++  -c   ps0_1 .C

    You also need to use the flags -ansi -pedantic to enforce the rules of ANSI Standard C++. In addition, it is useful to use the -Wall option to get all warnings, e.g.:
         % g++  -ansi -pedantic -Wall    ps0_1 .C     -o  ps0_1    -lm
    It is more convenient to set an alias (e.g. c++), instead of typing all this every time. In particular you can add in your .cshrc.mine dotfile the following:
         alias   "c++"  "g++  -ansi   -pedantic   -Wall   -lm"
    and then simply use the following command to compile and link a program:
         % c++   ps0_1 .C    -o  ps0_1

    In addition, you can use makefiles to help you automate the compilation and linking of your programs. The following command creates the target_filename according to the instructions provided in the makefile make0a.
        % gmake -f makePS0a ps0_1
    Eventually you must learn to use makefiles since they are extremely useful for the development of large programs with several different source code files.

    Although you may work at any machine and using any compiler you want, you need to make sure that your code compiles properly using the GNU compiler on an Athena workstations. The graders will be using Athena workstations and the GNU compiler to check your solutions.


    4. Debugging

           Using a debugger can help you find logical and difficult to detect errors in your code much faster and easier. A debugger can be used to step through the program, line by line and examine the program variables while executing it. Therefore, you need to first correct all syntactical errors, using the messages from the compiler and then execute the program using the debugger to detect potential logical errors. Typically, you compile the program, identify errors using a debugger, correct it in emacs, compile and debug again, and repeat as often as necessary.

         The debugger allows you to examine in detail what is happening during a program execution, or when it crashes due to a run-time error. In order to be able to use a debugger, such as gdb and ddd, you must first compile and link your code with the flag -g. e.g.:

           % g++   -g    ps0_1 .C    -o  ps0_1
     

  • gdb debugger:

  •        The  g++   -g    ps0_1 .C    -o  ps0_1 command generates an executable filename that can be checked with gdb, e.g. using the following command for the program compiled above:

             % gdb   ps0_1

    You can find more information about GDB at the following URL:

  • ddd debugger:

  •     A user friendlier debugger, available on athena, is the Data Display Debugger (ddd), which uses the gdb for its operations. The ddd program is available in the outland locker. Since we also need the g++ compiler from the gnu locker for the compilation you need to type:

    % add outland
    % add gnu
     To invoke the ddd debugger with your executable program, e.g. for ps0_1, please, type:
    % ddd ps0_1 &
    You can see that three windows pop up:
    1. The main window, which has three main parts.
    2. Debugger command tool window, titled  ddd
    3. A tip window, which often provides useful debugging tips. You can close this window.
          In general, the debugging cycle involves stepping through your program carefully once it compiles and seems to run. In particular, step into each function that you wrote; step over, e.g. using the ’Next’ button, system functions. At each step, ’Print’ the variable value(s) computed and  check them for reasonableness. Keep going until you find logical errors. Then, make the proper correction, using the editor, recompile and do this again.

        The execution of the program can be controlled from the ddd command tool which has the Run, Interrupt, Step, Next etc. buttons.
     

  • To start debugging:
  •  To step through the program:
  • You can step through the program using the buttons on the ddd window. A green arrow will indicate the current program statement being executed in the source code panel.
  • Looking at variable values:
  • To examine arrays:
  • To display a variable:
  • You can continuously see the values stored in a variable, by displaying it instead of printing it. The displayed variable will be shown in a new panel which will pop up above the source code panel. As you step through the program, any changes to the variable’s value will be shown there. You can display a variable by:
  • To display values of all local variables in the current function:
  • To display the arguments passed to the current function:
  • choose "Display function arguments"  from the ’Data’ tab in the menu bar of the main window
  • To input and output:
  • Input and output is done through the debug console.
  • Resources:

  • You can learn more about ddd from the ddd web-page at

    http://www.gnu.org/software/ddd/ddd.html
    You can find the ddd manual at the following URL:
    http://www.gnu.org/manual/ddd

    5. Use of makefiles

    In some cases a makefile (named make<number_of_problem>) will be provided, and you may use it to compile and link your code. Makefiles are used to automate the compilation and linking of programs. To compile and link a specific program, assuming that a proper makefile is available, the following command is used:
            % gmake -f make_file_name   <program_name>
      You do not have to use the provided makefiles, but you can use instead your own makefiles, or any of the makefiles you have seen in the lectures or anywhere else. You need to turnin the makefile that you use to compile your files on athena (either the ones you got using CVS or your own) Learning to use makefiles will help you when you start writing and compiling larger programs with several files, which makes the use of makefiles necessary.

    In problem set # 0, a simple makefile is provided for you, called makePS0a, which you may use to compile and link your code. There is also a more advanced makefile named makePS0b which you can use.
    e.g.           athena%  gmake   -f makePS0a    ps0_1
    Executing the above command creates the target filename ps0_1, according to the instructions provided in the makefile makePS0a.

    You can find more information related to developing makefiles at the following URL
    http://www.lns.cornell.edu/public/COMP/info/make/make_toc.html


    6. Concurrent Version Control (CVS)

    For the development of large software packages and programs it is useful to use a control system for modifications and revisions. Although it may not seem very useful for the development of small simple programs (like your first homework problems) it would be very useful for your project, and you will benefit from getting used to using it. Therefore, it would be beneficiary for you to get used to using such a revision control system as CVS (Concurrent Versions System). You may obtain more information on CVS from the man command (% man cvs) and from the following URLs: The provided source code files are in the directory /mit/1.124/Problems/<Problem set number>   from where you can copy them using CVS to your directory, and, make the necessary additions and/or modifications. To use CVS to check out the problem sets for the 1.124 you should first set the environment variable CVSROOT as below: (you can also put it in your .environment dotfile)
         % setenv CVSROOT /afs/athena.mit.edu/course/1/1.124/src
    then you can use the command:
         % cvs co Problems/PS<Problem set number>
    or, using the alias defined in 1.124/src/CVSROOT/modules
         % cvs co OOP_PS<Problem set number>


    7. Electronic Turnin

    For all problem sets you must turnin electronically all the source code files you have modified or written. You also need to turnin the makefile that you use to compile your files (i.e. either the one you obtained using CVS, or your own). To electronically turnin a file use the following command:
         %   turnin   -c 1.124   problem_set_number   filename

    e.g. to submit the ps0_2.C file for problem set #0 you should use the following command:
         %  turnin   -c  1.124   0   ps0_2.C

    Please, do not turnin any executable files electronically.

    Problems set source code files must be turned in electronically before the beginning of the lecture (i.e. before 2:30 p.m.) on the due date of the problem set, unless permission has been granted from either of the professors prior to the due date. Any files turned in later than that time will be considered late and will be penalized.


    8. Introduction to C++

    C++ is both a procedural oriented programming language and an object oriented programming (OOP) language, since it allows you to organize your program not only around functions (procedures), but also and most commonly used, around data.

    A procedural language is based on a list of instructions (statements) organized in procedures (functions) and emphasizing the computations to be performed. Languages such as Fortran and C are procedural languages. C++ which is based on C, has many additional features that enable object oriented programming, where emphasis is given on the data and their behavior. Java is a pure object oriented language.

    The additional features and advantages of C++ are the following:
    Classes allow the programmer to create her/his own data types extending the capabilities of the language according to the physical world problems. Classes are similar with the data structures in C, which are also available in C++. However, within a class both data variables and member functions that are used to work with the data variables can be provided.

      class Complex
        {
          public:
             double real;
            double imaginary;
       };

      main()
          {
             Complex x;
             x.real =15.5;
             x.imaginary = 2.5;
         }

    There are many additional features, like inheritance and virtual functions, added to C++ which are related with the classes and provide simple ways to handle objects and develop programs in an object oriented programming style.

    C++ allows reusability since a class which has been written, debugged and checked can be distributed to other programers and with minimal effort be incorporated in several programming packages.

    C++ supports function overloading which allows the use of functions with the same name, as long as they have different signature. The signature of a function is considered its name and the number and type of its arguments. e.g.:
         int min(int x, int y) {      }
         double min(double x,  double y) {      }

    In addition, virtual functions and polymorphism allows the dynamic binding on functions during run-time instead of static binding during compilation.

    C++ allows operator overloading, i.e. to use operators with user defined data types according to a specified function associated with the specific operator. For example, we are able to add two complex numbers which are a user defined data type, as long as we provide the necessary functions for operator overloading.
       main()
             {
                Complex x,y,z;
                 ........
                 z = x + y ;
             }

    In C++, there are two ways to comment, // (which comments everything until the end of line), and /*  */ (which comments everything between /* and */.

    Two latest features of C++ are the templates and the exception handling. The templates allow us to parameterize the types within a function or a class providing a general definition that can be used for many different purposes. The exception handling mechanism provides a mechanism to respond to and handle run time errors (like division by zero, exhaustion of memory, etc.).


    9. Data Types

  • Boolean: bool
  • Character and very small integers: char
  • Integers: short int, int, long int    (short, int, long)
  • Floating (single, double and extended precision): float, double, long double

  •      The bool data type can be assigned the values true and false, which correspond to 1 and 0, respectively.
         A char can be used both as a character and as an integer, depending on how it is used. Therefore, char, short, int, and long are all called integral data types.
         There are also unsigned versions of integer types, which allow the increase of the range of the larger number that can be stored with them, by not using any bit for the sign: unsigned char, unsigned short int (unsigned short), unsigned int, unsigned long int (unsigned long).

         The reason of using different data types is mainly for memory efficiency, since we can use the data type which correspond to our needs avoiding useless waste of memory. The proper data type must be selected and used based on the expected requirements during the program’s execution.

         To refer to particular variables that correspond to a particular chunk of memory in C++ (and in any other programming language) we have to use identifiers (variable names). The variable names in C++ are case sensitive as they are in C, must begin with a letter or an underscore (_), and should not be a reserved keyword. You should use reasonable variable names that provide some meaning to the reader of your code.

        Using the above keywords we can declare the data type of a variable giving to the compiler information about the necessary amount of memory required to store (i.e. the memory that is required to be allocated and reserved for) the variable and which is machine dependent, e.g.:

    The const type modifier (or qualifier) defines a variable as a symbolic constant and does not allow any change of its value. Therefore, a const variable must be initialized when defined, since any attempt to change its value results in a compile-time error.
           const int i=10;
    Note the different meaning of the following declarations:
           const int *p: Pointer to a constant integer
           int *const p: Constant pointer to an integer
           const int *const p: Constant pointer to a constant integer


    10. Variable Declarations and Definitions

     Before using any variable we have to declare it, i.e. inform the C++ compiler what is the data type of the variable. Every variable has a certain data type that determines the storage requirements and the operations that can be performed on it. In C++, as in C, we can combine several separate variable declarations into one declaration, as long as each variable is of the same data type, e.g.:

               <data_type1>  <variable1_name>;
               <data_type2>  <variable2_name> = <initial_value>,  <variable3_name>;

    int a, b, c;
    double x,y ;
    float z ;
    The above declarations are also definitions. A declaration simply informs the compiler that the variable exists, its data type and that it is defined somewhere else in the program. C++ allows to have many declarations of the same variable as long as they are consistent. The definition of a variable defines the variable’s name, its data type and may also initialize the variable. Defining a variable informs the compiler about the variable's data type so as to reserve the proper amount of memory to store values for that variable. The difference is that the definition reserves memory for the variable. Therefore, there must be only one definition of a variable in a program. A declaration is also a definition if it also sets aside memory at compile time.

    For example, the following statement is a declaration because it informs the compiler that an external global variable will be used, but no memory is allocated for that variable. The memory is allocated at the definition of the variable.
            extern int x_limit ;                       // declaration

    We can also initialize a variables in its definition, assigning an initial value and this is called initialization. When a value is assigned to an already defined variable this is called assignment:
          int a, b, c;                        // definitions
          double x = 3.4, y(4.);      // definitions and  initializations
          float  z = 9.9;                  // definition and initialization
          c = 10;                            // assignment


    11. Operators

    You will often need to use operators which perform a specified action on their operands. Most operators are binary, i.e. they have two operands, e.g. a+b. The operators in C++ are the same as the ones used in C.
    These are:
  •  the arithmetic operators of C++ are the +, -, *, / and %
  •  the assignment operator is the =
  •  the shorthand (abbreviated) assignment operators: += ,  -= , *= and /=
  •  the (unary) postfix and prefix increment/decrement operators ++ and --
  •  the relational operators are: > , < , >= , and <=  (used to compare two expressions)
  •  the equality operators are: == and !=   (used to check two expressions for equality)
  •  the logical operators are the: && , || , and !

  • An assignment expression has the value that is assigned to the variable on the LHS, and, therefore, several variables can be assigned the same value in one statement,
    e.g.:    x = y = z = 100;

    The RHS of logical operators is executed only if its necessary for the decision that must be taken. e.g. if the LHS of a ‘logical and’ (&&) is false, there is no reason to examine its RHS.

    In addition, in C++ it is allowed to create new definitions for operators applied to user defined data types.
      main()
        {
            Point x,y,z;
            ........
           z = x + y ;
       }


    12. Expressions and Statements

    Each C++ program must contain a function named main(), as in C. The execution of a C++ program begins from the first statement of main and finishes when the last statement of main is executed (e.g. when the last curly brace of main is reached).

    An action in C++ is referred as an expression, while an expression terminated by a semicolon is called a statement. More than one statements enclosed in a pair of curly braces is called a compound statement.

    Precedence and associativity: The sequence (order) with which individual components of an expression in C++ are executed is based as in C on the order of precedence. When the operators have the same precedence the associativity defines the order of execution. A table with the precedence and associativity of the C++ operators is provided. Similar tables you can find in any C++ textbook.

    e.g.         a  =  b   +=    5    +      3      /      2     -     5     +     17    /     2    /     3
    (order):     (8)      (7)        (4)           (1)          (5)         (6)          (2)       (3)

    The following table provides the precedence and associativity of the C++ operators with the highest precedence being the operator :: having precedence level equal to 1.
     

    Precedence 
    Associativity
    Operator 
    Function 
    right
    :: 
    global scope (unary) 
    left
    :: 
    class scope (binary) 
    left
    -> , .
    member selectors 
    left
    [] 
    array index 
    left
    () 
    function call 
    left
    () 
    type construction 
    right
    sizeof 
    size in bytes 
    3
    right
    ++ , -- 
    increment, decrement 
    right
    bitwise NOT 
    right
    logical NOT 
    right
    + , - 
    uniary minus, plus 
    right
    * , & 
    dereference, address-of 
    right
    () 
    type conversion (cast) 
    right
    new , delete 
    free store management 
    left
    ->* , .* 
    member pointer selectors 
    left
    * , / , % 
    multiplicative operators 
    6
    left
    + , - 
    arithmetic operators 
    left
    << , >> 
    bitwise shift 
    left
    < , <= , > , >= 
    relational operators 
    left
    == , != 
    equality, inequality 
    10 
    left
    bitwise AND 
    11
    left
    bitwise XOR 
    12
    left
    bitwise OR 
    13
    left
    && 
    logical AND 
    14
    left
    || 
    logical OR 
    15 
    left
    ?: 
    arithmetic if 
    16
    right
    = , *= , /= , %= , += , -= 
    <<= , >>= , &= , |= , ^= 
    assignment operators 
    17
    left
    comma operator 

    Conversions: C++ defines a set of standard conversions, implicit type conversions, that are used in arithmetic conversions, assignments using different data types, and passing arguments to a function of different data types than the function parameters. In particular, when we have such mixed expressions the compiler makes some standard conversions, e.g. in binary operations the lower data type is promoted to the higher one (which dominates), so as to avoid losing information.

    The following order is used:
               bool, char, short int < int < long int < float < double < long double
    bool, char and short int are always converted to int whenever they appear in any expression, i.e. before performing any operation on them. A bool is promoted to int, getting the value 1 or 0, depending on its value (true or false, respectively). When a number is converted to a type bool all values other than zero are converted to true and a zero value is converted to false. Integer constants (e.g. 17) are considered int, and, floating point constants (e.g. 4.53) are considered double. We can use L after an integer and a floating point constant to define that should be considered as a long int, and, a long double, respectively.

    In C++, we can also define rules of conversions to be used with operators applied on user-defined data types.

    We can also explicitly define type conversions using casting to force the explicit conversion from one data type to another.

    (dataType) variableOrExpression ;  and   dataType (variableOrExpression) ;

    Another way do an explicit conversion, i.e. to cast a data type constant or variable to another data type, is using the keyword static_cast followed by a data type name surrounded by angle brackets and the certain variable or constant to cast within parentheses, e.g.
    static_cast <dataType> (variableOrExpression)
    static_cast <float> (5) / 3                    //  gives  1.66667

    /* Example: Mixed Expressions - Precedence - Associativity - Casting */

    #include <iostream.h>
    main()
    {
      int i=4 ;
      float f = 2.5 ;
      double d = 3;

      cout << "\n  i  / 5 * f = "            // Mixed Expressions
           <<  i  / 5 * f  << endl ;

      cout << "\n 'a'  = "    <<  'a' << endl ;

      cout << "\n 'a' - 1 = "                // Mixed Expressions
           <<  'a' - 1 << endl ;
      cout << "\n 'f' - 'd' = "              // Mixed Expressions
           <<  'f' - 'd' << endl ;

      cout << "\n  f + 5 * d = "                 // Precedence
           <<  f + 5 * d  << endl ;

      cout << "\n  f * 2 * 2.5 = "               // Associativity
           <<  f * 2 * 2.5  << endl ;

      cout << "\n (float) i  / 5 * f = "         // Casting
           <<  (float) i  / 5 * f  << endl ;
      cout << "\n float i  / 5 * f = "         // Casting
           <<  float (i)  / 5 * f  << endl ;
      cout << "\n static_cast <float> (5) / 3 = "     // Casting
           << static_cast <float> (5) / 3 << endl;
    }


    Results:

      i  / 5 * f = 0               float 0.0

     'a'  = a                      character

     'a' - 1 = 96                  int 96

     'f' - 'd' = 2                  int 2

      f + 5 * d = 17.5        double 17.5

      f * 2 * 2.5 = 12.5      double  12.5

     (float) i  / 5 * f = 2      float 2
      float(i)  / 5 * f = 2      float 2
     static_cast <float> (5) / 3 = 1.66667


    13. Input/Output Operators

    In C++ the predefined objects cin, cout and cerr are available for input and output operations. The predefined object cin refers to the standard input (which is by default the keyboard), cout and cerr refer to the standard output and the standard error, respectively (which are both by default the display). These defaults can be changed using redirection while executing the program.

    The output operator (<<) (known as insertion operator) directs (display) output information on your standard output (screen), e.g.
          cout << "\n  x = " << x << endl ;
    "\n" represents a new line character, while endl inserts a new line and flushes the output buffer. The operating system buffers the output to the display characters and prints them out in a batch to minimize I/O overhead. This may lead to wrong indications of where the error may be if we consider the printed out information without flushing the buffer.
    We may have several output operators in the same output statement

    Similarly, you can use the input operator (>>) to obtain (read) input values from the standard input (keyboard). e.g.:
         cout << "\n x = "  ;
        cin >> x ;    cin >> y >> z;

    The standard input-output library (iostream.h) must be included using a preprocessor directive, in order to be able to use the input and output operators, as well as the manipulators without arguments such as endl, flush, hex, oct, etc.

    Certain options may be specified when using the output stream operator to select the way that the output should look. The precision can be set using a iostream manipulator, the setprecision(number_of_digits), while the setiosflags(options separated by |) can be used e.g. to specify whether the decimal point or tailing zeros should be shown. The setw() specifies the field width in which the next value should be printed out. It is the only manipulator that does not apply to all subsequent input or output, but becomes zero as soon as something is printed. The setfill(c) makes c the fill character. To use these parameterized stream manipulators (i.e. with arguments) we need to include the iomanip.h header file. e.g.:
         cout << setprecision(2) << setiosflags(ios::fixed | ios::showpoint)
                 << "\n\n 3. = " << 3. << "\t 0.333333 = " << 0.333333 << endl;
    (will give: 3.=3.00    0.333333 = 0.33)

    We can also redirect the input from the keyboard to a file and the output to another file:
          athena%  executable_file_name  <  input_file_name      >  output_file_name

    Finally, there is a standard error stream cerr which is used to display error messages
          cerr << "\n Not proper values were provided!"  ;

    The following member functions can be invoked by the input stream, cin: cin.good() returns true if everything is ok;, cin.eof() return true if EOF is reached; cin.fail() returns true if a format error has occurred.

    The C input/output functions scanf()/printf() can also be used, since C is a subset of C++. In that case the stdio.h header file (which contains their prototypes) must be included. Then, the buffer can explicitly be flushed using "fflush(stdout);".

    However, when both C input/output functions and C++ input and output operators are used, you need to provide the following function call before doing any input or output, to avoid problems:
         ios::sync_with_stdio();


    14. Preprocessor directives

    The preprocessing takes place prior to the actual compilation. The preprocessor searches all files that are to be compiled and takes action according to the preprocessor directives. The preprocessor directives are the lines which begin with # (usually placed at the top of the source code file).

    An include preprocessor directive results in the substitution of it, with the contents of the indicated file. i.e. the following include preprocessor directive:
           #include <file.h>
    is equivalent to typing the contents of the included file at that point.

    There are two variations of the include preprocessor directive:
           #include <file.h>     or      #include "file.h"
    The difference is that in the first case the preprocessor searches for the included file in the standard include directory, while in the second case it searches in the current directory. The latter case is usually used for the user written functions.

    Another preprocessor directive is the #define which it can be used to associate a token string with an identifier, e.g.
           #define PI 3.1415926

    The preprocessor will replace PI wherever it appears in a file with the provided token. After the preprocessing finishes the compilation starts, in which the token is treated as a floating point constant.

    Other preprocessor directives are the following: #ifdef, #ifndef, and, #endif  They can be used for conditional compilation.
    e.g. the ...... statements will be skipped if  _MY_HEADER_H has already been defined.
        #ifndef _MY_HEADER_H
        #define _MY_HEADER_H
        ........
        #endif

    Also, while compiling a program we can define a preprocessor constant on the command line using the -D option followed by the name of the preprocessor constant and therefore certain parts of the code can be selectively excluded.
    e.g. compiling the file with -DDEBUG_MODE option will consider the cout statement:
         #ifdef DEBUG_MODE
           cout << "\n testing debug mode \n" << endl;
         #endif

    The define directive can also be used to specify macro substitutions with variable parameters. e.g. having defined the following macro using:              #define mult(x,y)  (x*y)
    then, the following statement:                   product = 267.2 + mult(25.7, 33.6)
    will be replaced during preprocessing by:     product = 267.2 + (25.7 * 33.6)


    15. Header files

    To be able to use the input and output operators you must first include the standard input-output library (iostream.h) using a preprocessor directive:
         #include <iostream.h>

    When the C input/output functions scanf()/printf() are used the stdio.h header file (which contains their prototypes) must be included instead.

     Similarly to be able to use any other standard library function you need to include its header file which contains all necessary declarations.
    e.g. to be able to use the math function, such as sqrt() you need to include the math.h header file using the following command:
         #include <math.h>

    When the header file that you include is in the current directory, e.g. a header file that you wrote, then you should use double quotes, instead of brackets, e.g.:
         #include "myheader.h"

    According to the new ANSI/ISO standard, which however is not followed by all available compilers yet, the iostream header file can be included using:
          #include <iostream>
          using namespace std;
          int main()
          {
              std::cout << "\n testing:
              pi = " << 3.1415 << endl;
          }

    Header files are very useful to provide declarations (e.g. for global variables and functions) in order to avoid incompatible declarations which may happen when multiple declarations are provided in several source-code files. In addition, any changes to a declaration would require only a single local modification instead of having to update all appearances of the declarations. A header file should never contain definitions, (unless its a definition of an inline function).


    16. Control Structures

    Control statements are used to control the flow of our programs which is normally sequential. i.e. statements are executed one after the other in order. Changing this sequential execution in a controlled way is called transfer of control and is achieved using control structures.

    The C++ control structures are identical with those of C. The relational operators ( > , < , >= ,  <= ), equality operators ( ==  ,  != ), and the logical operators (&& , || ,  !) are used in logical tests which produce either true or false (0).

    Typically, the following operators are used to form a logical test for the control structures which determines what action should be taken:

    The result of the above operators is of type bool, either true (i.e. 1), or false (i.e. 0). You should never compare floating-point values for equality or inequality, since the floating-point numbers can, in general, only be approximated since only a small number of digits are used for computer representation of values.

    In all control structures, a simple (i.e. single), or a compound, i.e. a sequence of statements enclosed in curly braces, statement is either conditionally or repeatedly executed, based on a logical test.

    The if and if-else, as well as the switch control structures are used to make certain selections
    The simplest selection control structure is the if, where if the logical test is true (i.e. non zero), then the following statement (or statements in curly braces) is (are) executed. Otherwise the statement (or statements) is (are) skipped and the statement after the if control structure is executed.
               if (logical test)
                   statement ;

    if-else if-...-else control structure provides several alternative actions. (It is more efficient to put the most probable selection first to reduce the chances of multiple checks)
        if (logical test)             (if-else if -else provides alternative actions)
              {
                 statements                      (executed if (logical test) is true)
              }
        else if  (another logical test)  (checked  if (logical test) is false)
            {
                statements
            }
        else                       (executed if not any (logical test) is true)
            {
               statements
            }

    The switch() control structure is useful when there are many different selections. It consists of multiple selection cases and an optional default case. Only constant integral expressions can be checked in switch() cases. The controlling expression in the parentheses determines which of the cases should be executed and starts executing statements in that case continuing until the closing brace of the switch control structure, or until a break is reached. The break causes the program to exit the switch structure and execute the next statement after it.
        switch (x)
        {
            case 1:
                statements
                break;
            case 2: case ’b’: case ’B’:
                statements
                break;
            case 3:
            case ’c’:
                statements
                break;
            ........
            default:
            .........
         }
     

    while, do/while, and for are the repetition control structures of C++, i.e. are used when iterations are required.

    The statements of the while control structure are executed repeatedly as long as the logical test is true, and, at each iteration when the closing brace is reached, control is passed back at the beginning of the while loop. The while loop is continuously repeated until the logical test becomes false (i.e. equal to zero)
        while(logical test)
            {
                  statements         // statements executed repeatedly as
                                           // long as the logical test is true
            }

    The do/while is similar to while with the only difference that its body is executed at least once since the check is done at the end.
        do
            {
               statements                 // executed repeatedly as long as the logical test
            } while(logical test);             // is true but always executed at least once

    The for control structure is used for repetitions, when we have a regular incrementing. First, expr1 is evaluated, which is usually used to initialize the loop variables. Then, the logical test (which is a loop continuation condition) is evaluated, and if it is true (i.e. nonzero), the following statements, within the curly braces, are executed. Finally, expr3 is executed (usually providing an increment or decrement of the control variable), and then the procedure from evaluation of the logical test is repeated, as long as it is true (i.e. non zero.) expr1 and expr2 can be comma separated lists of expressions, which are executed from left to right. All three expressions are optional, although the two semicolon are always required.
        for (expr1 ;  logical test ; expr3)
            {
                 statements in body of for loop
            }

    Finally, the following control structure is the conditional operator which produces a value based on the logical test. Therefore, it can be placed inside another expression. The first expression after the question mark is executed if the logical test is true. Otherwise, the expression after the colon is executed.
        ( logical test)  ?  when_true_statement : when_false_statement ;

    e.g.:      max = (x>y) ? x : y ;
                (i%2) ? cout << i << " is an odd integer" : cout << i << " is an even integer" ;

    The break statement is typically used to skip the remainder of the switch statement. It is also used to exit repetition control structures (i.e. while, do/while and for). In all these cases execution continues with the first statement after the terminated control structure. The break statement exits the innermost loop, or switch statement.

    The continue statement is used to skip the current iteration of a repetition control structure and continue with the next iteration, if there is one, i.e. the current iteration only is terminated and execution continues with the evaluation of the logical test of the next iteration. It goes to the next iteration of the innermost loop.



    For any comments or/and questions, please, contact petros@mit.edu
    Notes prepared by Petros Komodromos.