C++ Course

 

Note:
11111This lecture notes have been prepared by Michael A. Bodkin (to whom we offer our thanks)
11111and has been slightly modified or re-arranged by Professor V.


A Short History of C and C++

C language evolved from BCPL and B.  Martin Richards developed BCPL to make the writing of operating systems and compilers easier (1967).  Ken Thompson  borrowed many of the concepts of BCPL to create his language B. Thompson used B to develop the first UNIX operation system (1970) for DEC PDP-7 computer.  Both of these languages suffered from being a type less. In other words it's up to the programmer to insure the behavior of their data types.

Dennis Ritchie at Bell Laboratories took B a step farther by adding data typing (1970) and calling this language C.  By the late 1970's a de facto standard developed based on the "The C Programming Language" book by Brian W. Kernighan and Dennis M. Ritchie. This version of C is referred to as K&R C. Due to the variety of hardware platforms that C was ported to. Each implementation would vary from one another.

It became clear to the American National Standards Committee on computers and Information Processing (ANSI) that one standard was needed for C. They began work on the standard during 1983 and completed it in 1990.  ANSI cooperated with the  International Standards Organization (ISO) to jointly publish the ANSI/ISO 9899: 1990 commonly referred to as ANSI C 1988.

During this time period Bjarne Stroustrup of Bell Labs was developing a version of C called C++ to support the new Object Oriented (OO) methodology for solving. This methodology is based on entities in the problem domain and combining the entity's identity (uniqueness), behavior (functions) and  it's state (characteristics).  C++ was made standard in 1998 by ANSI committee.
 

Overview of C and C++

C is a general-purpose programming language which features modern control flow, data structures, and a rich set of operators. Only 38 reserve words are defined by  ANSI C 1988 standard making it a  small language.  Because of this smallness and its absence of restrictions C is not specialized to particular problem domain such as COBOL and FORTRAN.  C is considered a mid-level language as it has features of assembly (low-level language) as well as features of high-level languages. C is not considered a high level language due to its memory management scheme. (It's up to the developer to properly manage a C programs memory.)

C++ is an extension of C that supports the Object Oriented (OO) Programming paradigm. (74 reserve words)  ANSI C++ 1998 supports the OO paradigm with capabilities of: object creation, inheritance,  parametric polymorphism (Templates), and ad hoc polymorphism (Function Overloading, and Type Coercion).  C++ extends the OO paradigm by providing for exception handling, name spaces,  and Standard Template Library (STL).  The STL is a library of container classes (vector, string, set, map, ...) provided for developers use.

The designers of C/C++ have taken the view point that  developers know what they are doing. The benefits of view is there are very fell limitations on how a developer solves a problem using C++. The drawback is it also easy to write code that contains memory leaks or can cash the operating system.
 

Types

C++ is a typed language. That means C++ allows data to be assigned a name and  to belong to particular class of data that allows a set of operations to be  applied to the name.  The name is referred to as the identifier. For example if we declare the identifier i of type integer,  i can be acted upon by the operators -, +,  %,  /,  *, and  =.  If we declare the identifier str of type string, str can be acted upon by the operators + and = but not -, /, %, *. (Note a identifier can be used only once pre scope)

Declarations (type identifier; ):
    int i;           // i is an integer
    string str;   // str is a string

Expressions and Assignments( (identifier | expression) operator (identifier | expression)[;] ):
   i = 0;        // the data named by i now contains the value 0
   i = i - 1;     // the data named by i now contains the value -1;
   str = "Hello ";    // the data named by str now contains the characters Hello \0;
   str = str + "World";   // the data name by str now contains the character Hello World\0;

Note:
Variables will be called "Identifiers" in the context of this course.
111111  

C++ supports fundamental types and user defined types. The fundamental types supported by C++ are:

    Boolean Type (bool)
    Character Type (char)
    Integer Type (int)
    Floating point Type (double)
    Void Type (void)
    Pointer Type (example int*)
    Array Type (char [])
    Reference Type (double&)         // Discussion tabled to later.
 

The user defined types are defined using:

    Enumeration Declaration (enum)
    Structure Declaration (struct)       // Discussion tabled to later.
    Class Declaration (class)            // Discussion tabled to later.
 

Boolean Type can have one of the two values true or false. A Boolean is used to express the  results of logical operations. For example:

   int i = 0;
   int j = 1;
   bool result = (i == j);   // the data named by result will contain the value false
                                      // == is equality, = is assignment
    j = i;
   result = (i==j);             // the data named by result will contain the value true

Character Type can contain one character of the implementation's character set. (A good percentage of the time it is the ACSII Character set). Character Type has two types char and wchar_t.  For Example

    char ch = 'm';       // the data name by ch will contains the lower case letter m.  (Note C++ is case sensitive.)
                                //  'm' is refereed to as a literal

    wchar_t wch  = L'B';   // the data name by wch contains the wide character upper case b which is part wide character set usually Unicode.
 
char and wchar_t  types require special functions such as sprintf to convert from one to another and vice versa. For Example the following expression is illegal.

    ch = wch;  // illegal assignment

Both types come in three forms signed char, char, unsigned char which can be converted from one to another without special functions or casting. For Example
 
   unsigned char uch = 'u';
   char ch = uch;
   signed char sch = ch;

   ch = sch;
   sch = uch;
   uch = ch;
   uch = sch;
 

Integer Type has three forms: int, signed int, and unsigned int and come in three sizes: short int, int, and long int.  The forms and sizes can be combined and abbreviated. For example signed for signed int, unsigned for unsigned int, short for short int, long  for long int. Unlike like the plain char the plain int is signed. Signed int is just an explicit version of int.  The compiler will automatically convert between the different signs and sizes, truncating where appropriate. (Note most compilers do not notify the user of this truncation)

   long l = -1;
   int i = l;             // truncation possible
   short s = i;        // truncation possible

   s = l;                 // truncation possible
   i = s;
   l = i;
   l = s;

Floating Point Type represent floating-point numbers.  Just as with integers floating point type has three sizes: float (single-precision), double (double-precisiont), and long double (extedned-precision). The floating point type are implementation dependent.  Choosing the right precision for a problem where the choice matters requires significant understanding of  floating-point computation. If you don't have that understanding, get advice, take time to learn, or use double and hope for the best.

   double small_num = 1.2345E-25;
   long double ld = -1.1;
   double d = ld;   // truncation possible
   float f = d;        // truncation possible

   f = ld;              // truncation possible
   d = f;
   ld = d;
   ld = f;

Void Type represents nothing and used only to define return types for functions, denote functions with no parameters and to point address locations of  undetermined types.

void foo() { ; }   // the function foo will return nothing

Pointer Type defines a name that refers to an address location in memory. For example

int i = 0;
int j;

int* pi = &i;   // pi is said to point to i the value in pi is the address location of  i.
j = *pi;          // j is assigned the value located in the memory referenced by pi.
                     // this is called dereferencing the pointer.

A void pointer is a special pointer that can point to any type.

void *vp = pi;
char ch = 'm';
vp = &ch;
vp = &j;

std::cout << "j contains " << *((int*)vp) << endl;   // the following will be printed to standard output.
 

Array Type defines a name that can hold a given number (size) of elements of that type. Arrays are index from 0 to size - 1.  For example

char str[10];       // defines an array of ten characters
int    i_arr[20];    // the data named by i_arr contains 20 integers
int *p_iarr = i_arr;    // the pointer p_iarr contains the address location of the first integer of i_arr.
int i = i_arr[0];         // i contains the value index by the first position of the array i_arr

for (int j =0; j < 20; j++) i_arr[j] = 0;    // this command initializes the array i_arr to all zeros
 

Rules for Identifiers:

  1. An identifier consists of a sequence of letters and digits.
  2. An identifier  must start with a letter.
  3. The underscore character _ is considered a letter.
  4. An identifier cannot be a reserve word
  5. An identifier must not already be defined within that scope
  6. The compiler does not impose a limit on the length of an identifier (Some times the a linker or debugger will).
Examples of valid names:
    CORE_UTILS        Timer      CommandFactor    __
    _WINNT                i               DisplayMessage01

Examples of invalid names:
    123                         &foo        an integer value
    $sys                        no-way    dot.notation

Enumerations
 
Enumerations provide the developer a way to introduce a new type into program.  An enumeration is type that can hold set of values specified by the user.  These values behave as constant integers. For example:

    enum ATE_INST_STATUS { ATE_GOOD, ATE_BAD, ATE_MISSING };
 
    ATE_INST_STATUS health = ATE_GOOD;

    switch (health)
   {
        case ATE_GOOD  :
                // do something
                break;
        case ATE_BAD :
                // do something
               break;
        case ATE_MISSING :
               // do something
                break;
        default :
               // you should never reach this point
    }

 Enumerations default to starting with zero.  For example the statement (ATE_GOOD == 0) will equate to true.  You can change that by specifying which integer to start with. For example:

 enum MONTHS { JAN=1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };

It is also legal for you to specify each value. For example:

enum VOLTAGE_RANGE { MIN = -10, MAX = 210 };
enum ARITHMATIC_OPERATORS { PLUS='+', MINUS='-', ASSIGN='=', MULT='*', POWER='^', DIV='/', MOD='%', LP='(', RP=')' };

Remember the elements of an enumeration set behave as constant integers. The following expression is illegal:

MIN = -210;    // This will not compile.

An enumeration is a user-defined type, so user can define their own operations, such as ++ and << for and enumerations.

ostream& operator<<(ostream& in, VOLTAGE_RANGE);

int operator++(VOLTAGE_RANGE& in_range);
 

Operators

The arithmetic operators. 

    +  // plus, both unary and binary root2++; ++root2; and root2 = 2.1 + 2;
    -  // minus, both unary and binary root2--; --root2; and root2 = 2.1 - 2;
    *  // multiply, root2 = 2.1 * 2;
    /  // divide, int i = 7/3;  i equals 2.
    %  // remainder, int i = 7/3; i equals 1.

The comparison operators

    == // equal
    != // not equal
    <  // less than
    >  // greater than
    <= // less than or equal to
    >= // greater than or equal to

Resolution Modifiers

    class_name::member_name;    // scope resolution modifier
    namespace_name::member_name; // scope resolution modifier
    ::member_name                             // default or no namespace or global scope resolution modifier
 
Member Selection

    object.member_name;     // access a data member or invoke a member function for an object defined off of the stack
    object->member_name;  // access a data member or invoke a member function for an object defined from the Free Store.

Bit wise Operators

    expr << expr;      // shift left
    expr >> expr;      // shift right
    expr & expr;       // AND
    expr | expr;         //  inclusive OR
    expr ^ expr;        // exclusive OR
 
Logical Operators

    (expr1 && expr2) ;    // logical AND returns true or false
    (expr1 || expr2)          // logical inclusive OR return true or false

Note: logical operators use short circuit logic so if expr1 of expr1 && expr2 is false expr2 is not evaluated. If expr1 in expr1 || expr2 is not evaluated.
 

Functions

 The usual way of getting work done in C++ is to call a function to do it.  To call a function that function must be declared. For example:

int Square(int value);  // This is referred to as a function prototype

void main()
{
    int square_of_four = Square(4);
}

The declaration consists of the return type, identifier name, and parameter list. The return type and parameter list can be comprised of any types.
For this program to run the function Square. For example:

int Square(int value);  // This is referred to as a function prototype

void main()
{
    int square_of_four = Square(4);
}

int Square(int value)   // This is referred to as the function definition
{
    int return_value = value * value;
    return return_value;
}

All function parameters are defined off of the stack.  To pass in a parameter to be modified your must pass in the address location of that parameter. For example

#include <iostream>
#include <math>
bool SquareRoot(int value, int square_root);

 void main()
{
    int value = 4;
    SquareRoot(value, value);
    std::cout << "value = " << value << std::endl;
}
bool SquareRoot(int value, int square_root)
{

    if (value < 0)
        return false;

    square_root = sqrt(value);

     return true;
}

This program will print out the following string: value = 4

But is we modify  SquareRoot:
#include <iostream>
bool SquareRoot(int value, int& square_root);

 void main()
{
    int value = 4;
    SquareRoot(value, value);
    std::cout << "value = " << value << std::endl;
}

bool SquareRoot(int value, int& square_root)
{
    if (value < 0)
        return false;

    square_root = sqrt(value);

     return true;
}
 

This program will print out the following string: value = 2

In C++ a function is allowed to call it's self for instance take the factorial operation. This is know as recursion and the import thing to remember is to always have a terminating point.

long factorial(long value)
{
    if (value > 1)
      return ( factorial( value - 1) * value);
    else
      return 1;  // termination point
}

All recursive functions can be converted to a looping construct and a local variable for efficiently.