Sams Teach Yourself C++ in 24 Hours

Sams Teach Yourself C++ in 24 Hours



A copy of programs from the book’s website at http://cplusplus.cadenhead.org.

HOUR 1: Writing Your First Program
HOUR 2: Organizing the Parts of a Program
HOUR 3: Creating Variables and Constants
HOUR 4: Using Expressions, Statements, and Operators
HOUR 5: Calling Functions
HOUR 6: Controlling the Flow of a Program
HOUR 7: Storing Information in Arrays and Strings
HOUR 8: Creating Basic Classes
HOUR 9: Moving into Advanced Classes
HOUR 10: Creating Pointers
HOUR 11: Developing Advanced Pointers
HOUR 12: Creating References
HOUR 13: Developing Advanced References and Pointers
HOUR 14: Calling Advanced Functions
HOUR 15: Using Operator Overloading
HOUR 16: Extending Classes with Inheritance
HOUR 17: Using Polymorphism and Derived Classes
HOUR 18: Making Use of Advanced Polymorphism
HOUR 19: Storing Information in Linked Lists
HOUR 20: Using Special Classes, Functions, and Pointers
HOUR 21: Using New Features of C++14
HOUR 22: Employing Object-Oriented Analysis and Design
HOUR 23: Creating Templates
HOUR 24: Dealing with Exceptions and Error Handling

HOUR 1: Writing Your First Program


New features that are introduced in C++14, which has 14 as part of its name because it was released in 2014.
Microsoft Visual Studio also supports C++ programming, and a free version called Visual Studio Community is available from the website www.visualstudio.com.

hello.cpp:

#include <iostream>

int main()
{
std::cout << "Solidum petit in profundis!\n";
return 0;
}
To compile and link the source file:

$ g++ hello.cpp -o hello


c++ actually links you to GNU’s classic g++ compiler, thus, c++ is a symbolic link to g++.

HOUR 2: Organizing the Parts of a Program


An interpreter-based language translates a program as it reads each line, acting on each instruction.
A compiler-based language translates a program into what is called object code through a process called compiling. This code is stored in an object file. Next, a linker transforms the object file into an executable program that can be run on an operating system.

The essence of object-oriented programming is to treat data and the procedures that act upon the data as a single object.

The C++ language fully supports object-oriented programming, including three concepts:

  • encapsulation
  • C++ supports the properties of encapsulation through the creation of user-defined types called classes.
  • inheritance
  • A new type can be declared that is an extension of an existing type. This new subclass is said to derive from the existing type
  • polymorphism
  • C++ supports this idea that different objects do the right thing through a language feature called function polymorphism and class polymorphism.

A C++ compiler’s first action is to call another tool called the preprocessor that examines the source code. This happens automatically each time the compiler runs: the # symbol, which indicates that the line is a command to be handled by the preprocessor.
The preprocessor serves as an editor of code right before it is compiled.
  • The #include directive tells the preprocessor to include the entire contents of a designated filename at that spot in a program.
  • The < and > brackets around the filename iostream tell the preprocessor to look in a standard set of locations: the folder that holds header files for the compiler.
  • Header files traditionally ended with the filename extension .h.
    Modern compilers don’t require that extension, but if you refer to files using it, the directive might still work for compatibility reasons.
<< is called the output redirection operator.

There are two types of comments in C++:
  • A single-line comment begins with two slash marks ( // ) and causes the compiler to ignore everything that follows the slashes on the same line.
  • A multiple-line comment begins with the slash and asterisk characters ( /* ) and ends with the same characters reversed ( */ ).

Arguments are data sent to the function that control what it does.
These arguments are received by the function as parameters.

calculator.cpp:

#include <iostream>

int add(int x, int y)
{
  // add the numbers x and y together and return the sum
  std::cout << "Running calculator ...\n";

  return (x+y);
}

int main()
{
  /* this program calls an add() function to add two different
  sets of numbers together and display the results. The
  add() function doesn't do anything unless it is called by
  a line in the main() function. */
  std::cout << "What is 867 + 5309?\n";
  std::cout << "The sum is " << add(867, 5309) << "\n\n";
  std::cout << "What is 777 + 9311?\n";
  std::cout << "The sum is " << add(777, 9311) << "\n";

  return 0;
}

The result:

$ ./hello
What is 867 + 5309?
The sum is Running calculator ...
6176

What is 777 + 9311?
The sum is Running calculator ...
10088


HOUR 3: Creating Variables and Constants


A variable is a label on a memory space so that it can be accessed without knowing the actual memory address.
When you create a variable in C++, you must tell the compiler the variable’s name and what kind of information it will hold. The type tells the compiler how much room to set aside in memory to hold the variable’s value.
  • A short integer is usually 2 bytes
  • A long integer is 4 bytes
  • A long long integer is 8 bytes
The real size of each type relies on the sizeof() function to report the sizes of common C++ types on your computer.

A shortcut for an existing type can be created with the keyword typedef , which stands for type definition.


typedef unsigned short USHORT
This statement creates a type definition named USHORT that can be used anywhere in a program in place of unsigned short .

The auto keyword in C++ enables a type to be inferred based on the value that’s initially assigned to it.
Because the auto keyword was added in a recent C++ version, you may need a special command-line option "-std=c++14" to compile the program.
Note, older versions of C++ had an auto keyword that was supposed to be used to indicate that a variable was local to the part of the program it was defined in (local variable).

HOUR 4: Using Expressions, Statements, and Operators


Statements, which are commands that end with a semicolon.
In the source code of a C++ program, any spaces, tabs, and newline characters are called whitespace.
The compiler generally ignores whitespace, which serves the purpose of making the code more readable to programmers.

Every operator has a precedence value in C++.
When in doubt, use parentheses to make an expression’s meaning clear. They do not affect a program’s performance, so there’s no harm in using them even in cases where they wouldn’t be needed.

HOUR 5: Calling Functions


If the function prototype declares a default value for a parameter, the function can be called without that parameter. The default value is used whenever the parameter is omitted.

In C++, more than one function can have the same name as long as there are differences in their arguments, a practice called function overloading.

int store(int, int);
int store(long, long);
int store(long);
The parameters the function is called will determine which function will be called. Function overloading also is called function polymorphism.

Some performance overhead is required to jump in and out of functions.
When a function consists of a small number of statements, you can gain some efficiency if the program avoids making the jumps.
The program runs faster if the function call can be avoided.

If a C++ function is declared with the keyword inline, the compiler does not create a real function. Instead, it copies the code from the inline function directly into the place where the function was called. It is just as if you had written the statements of the function right there.

One of the features added to C++ with version C++14 is the automatic deduction of a function’s return type with the auto keyword.
To make a function’s return type determined by deduction, use auto where the return type is specified.


auto add(int x, int y){
    return (x+y);
}

HOUR 6: Controlling the Flow of a Program


Loops:
  • while
  • continue; break;
  • do {} while ();
  • for
switch:

switch ( var )
{
  case var'value:
      actions;
      break;
  default:
      default actions;
}

HOUR 7: Storing Information in Arrays and Strings


When you declare an array, you tell the compiler exactly how many elements you expect to store in it.

C++ uses a convenient abstraction called streams to perform input and output operations in sequential media.
A stream is an entity where a program can either insert or extract characters to/from.
The standard library defines a handful of stream objects:
  • cin
  • standard input stream
  • cout
  • standard output stream
  • cerr
  • standard error (output) stream
  • clog
  • standard logging (output) stream
The extraction operation on cin uses the type of the variable after the >> operator to determine how it interprets the characters read from the input.
In C++, a string is an array of characters ending with a null character, a special character coded as '\0' .
You can call a function of the cin object called getline() with two arguments:
  • The buffer to fill
  • The maximum number of characters to get

char quest[80];
std::cin.getline(quest, 79);

C++ inherits from C a library of functions for dealing with strings. This library can be incorporated in a program by including the header file string.h .

A recent addition to C++ is a new for loop that can loop through every element in an array.
This foreach loop uses the keyword for , just like other for loops.
Unlike other for loops, the foreach has two sections separated by a colon ( : ) instead of three separated by semicolons:

  • The first section is a variable that will hold an element of the array.
  • The second is the name of the array.

int production[] = { 10500, 16000, 5800, 4500, 13900 };

for (int year : production){
  std::cout << "Output: " << year << std::endl;
}

HOUR 8: Creating Basic Classes


You’ve worked with built-in types such as int , long , and float numbers.
In C++, you define your own types to model a problem you are trying to solve.
The mechanism for declaring a new type is to create a class. A class is a definition of a new type.

A C++ class is a template used to create objects.
An object is an instance of a class, and may be called a class instance or class object; instantiation is then also known as construction.

A class is a collection of related variables and functions bundled together. Bundling these together is called encapsulation.

  • member variables
  • The variables can be of any other type, including other classes. Variables make up the data in the class, and functions perform tasks using that data.The variables of the class are called its member variables.
  • member functions
  • The functions in the class use and modify the member variables. They are called the member functions (or methods) of the class.

Declaring a Class



class class_name
{
    /* private */
        

    public: /* public */
        type1 pub_data1;
        type2 pub_data2;
        pub_func1();
        pub_func2();
        ...

    private: /* private again */
        type1 pri_data1;
        type2 pri_data2;
        pri_func1();
        pri_func2();
        ...

};
        
Declaration does not allocate memory.

Defining an Object


An object is created from a class by specifying its class and a variable name, just as you’ve done with built-in types.

class_name object1;
The object will have all member variables and member functions defined for the class. An object is just an individual instance of a class. When you create an object, you are said to “instantiate” it from the class.

Accessing Class Members


After you create an object, you use the dot operator ( . ) to access the member functions and variables of that object.
All member variables and functions are private by default.
Private members can be accessed only within functions of the class itself.
For ex., the following code will have the compiling error:

#include <iostream>
using namespace std;

class Employee {
  private:
      int salary;
};

int main() {
  Employee myObj;

  cout << myObj.salary ;
  return 0;
}

prog.cpp: In function ‘int main()’:
prog.cpp:12:17: error: ‘int Employee::salary’ is private within this context
   12 |   cout << myObj.salary ;
      |                 ^~~~~~
prog.cpp:6:11: note: declared private here
    6 |       int salary;
      |           ^~~~~~
Public members can be accessed everywhere else. Other classes and programs can use a object's public members and functions.
When the public keyword appears in a class definition, all member variables and functions after the keyword are public.

There’s also a private keyword to make all subsequent member variables and functions private.
Each use of public or private changes access control from that point on to the end of the class or until the next access control keyword.

Although member variables can be public, it’s a good idea to keep them all private and make them available only via functions.

A function used to set or get the value of a private member variable is called an accessor. Other classes must call the accessor instead of working directly with the variable.
For ex.,


#include <iostream>
using namespace std;

class Employee {
  private:
    int salary;

  public:
    void setSalary(int s) {
      salary = s;
    }
    int getSalary() {
      return salary;
    }
};

int main() {
  Employee myObj;
  myObj.setSalary(50000);
  cout << myObj.getSalary();
  return 0;
}
Accessors enable you to separate the details of how the data is stored from how it is used.
If you later change how the data is stored, you don’t need to rewrite functions that use the data.

Implementing Member Functions


Every class member function defined in the class declaration must be defined.
A member function definition begins with the name of the class followed by the scope resolution operator ( :: ) and the name of the function.

void class_name::function_name()
{
    std::cout << "This is a member function\n";
}

Creating and Deleting Objects



Initialization combines the definition of the variable with its initial assignment.
As the built-in type int is considered, there are 2 ways to do it:
  • 
    int weight;
    weight = 7;
    
  • 
    int weight = 7;
    

Classes have a special member function called a constructor that is called when an object of the class is instantiated.
  • The job of the constructor is to create a valid object of the class, which often includes initializing its member data.
  • The constructor is a function with the same name as the class but no return value.
  • Constructors may or may not have parameters, just like any other function of the class.
  • If you create an object without specifying parameters, this calls the default constructor of the class, which is a constructor with no parameters.
If you have not declared a constructor in the class, the compiler provides a constructor which takes no action; it is as if you declared a constructor with no parameters whose body was empty.

When you declare a constructor, you also should declare a destructor.

  • Destructor function is automatically invoked when the objects are destroyed.
  • Destructors clean up objects and free any memory that was allocated for them.
  • A destructor always has the name of the class preceded by a tilde ( ~ ).
  • 
        ~constructor-name();
        
  • Destructors take no parameters and have no return value.
If you have not declared a destructor, the compiler also provides one which also has an empty body and does nothing.
The compiler automatically calls constructors when defining class objects and calls destructors when class objects go out of scope.

A test example code:


#include <iostream>

class Bicycle
{
    public:
        Bicycle(int initialSpeed);
        ~Bicycle();
        int getSpeed();
        void setSpeed(int speed);
        void pedal();
        void brake();
        
    private:
        int speed;
};

// constructor for the object
Bicycle::Bicycle(int initialSpeed)
{
    setSpeed(initialSpeed);
}

// destructor for the object
Bicycle::~Bicycle()
    {
    // do nothing
    }
    
// get the trike's speed
int Bicycle::getSpeed()
{
    return speed;
}

// set the trike's speed
void Bicycle::setSpeed(int newSpeed)
{
    if (newSpeed >= 0)
    {
        speed = newSpeed;
    }
}

// pedal the trike
void Bicycle::pedal()
{
    setSpeed(speed + 1);
    std::cout << "\nPedaling; bicycle speed " << getSpeed() << " mph\n";
}

// apply the brake on the trike
void Bicycle::brake()
{
    setSpeed(speed - 1);
    std::cout << "\nBraking; bicycle speed " << getSpeed() << " mph\n";
}

// create a trike and ride it
int main()
{
    Bicycle bike(5);
    bike.pedal();
    bike.pedal();
    bike.brake();
    bike.brake();
    bike.brake();
    return 0;
}

The result:

$ ./test
Pedaling; bicycle speed 6 mph
Pedaling; bicycle speed 7 mph
Braking; bicycle speed 6 mph
Braking; bicycle speed 5 mph
Braking; bicycle speed 4 mph

HOUR 9: Moving into Advanced Classes


const Member Functions

If you declare a member function to be constant with the const keyword, it indicates that the function won’t change the value of any members of the class.
To declare a function as const, put the keyword const after the parentheses, as in this example:

void displayPage() const;

Accessors used to retrieve a variable’s value, which also are called getter functions, often are const functions.
If you declare a function to be const and the implementation of that function changes the object by changing the value of any of its members, the compiler will flag it as an error.

It is good programming practice to declare as many functions to be const as possible. When you do, this enables the compiler to catch unintended changes to member variables, instead of letting these errors show up when your program is running.

Organizing Class Declarations and Function Definitions


Class definitions often are kept separate from their implementations in the source code of C++ programs.
Although you can put the declaration in the source code file .cpp, a convention that most programmers adopt is putting the declaration in a header file with the same name ending with the file extension .hpp (or less commonly .h or .hp ).
The reason to separate them is because clients of a class don’t care about the implementation specifics. Everything they need to know is in the header file.

inline Implementation


You can make member functions inline by the keyword inline.
Putting the definition of a function in the declaration of the class makes that function inline automatically.

bicycle.hbicycle.cpp


class Bicycle
{
    public:
        Bicycle(int initialSpeed);
        ~Bicycle();
        int getSpeed() const 
        { 
            return speed; 
        }
        void setSpeed(int speed);
        void pedal();
        void brake();
        
    private:
        int speed;
};


#include <iostream>

#include "bicycle.h"



// constructor for the object
Bicycle::Bicycle(int initialSpeed)
{
    setSpeed(initialSpeed);
}

// destructor for the object
Bicycle::~Bicycle()
{
    // do nothing
}


// set the trike's speed
void Bicycle::setSpeed(int newSpeed)
{
    if (newSpeed >= 0)
    {
        speed = newSpeed;
    }
}

// pedal the trike
inline void Bicycle::pedal()
{
    setSpeed(speed + 1);
    std::cout << "\nPedaling; bicycle speed " << getSpeed() << " mph\n";
}

// apply the brake on the trike
void Bicycle::brake()
{
    setSpeed(speed - 1);
    std::cout << "\nBraking; bicycle speed " << getSpeed() << " mph\n";
}

// create a trike and ride it
int main()
{
    Bicycle bike(5);
    bike.pedal();
    bike.pedal();
    bike.brake();
    bike.brake();
    bike.brake();
    return 0;
}

Classes with Other Classes as Member Data


A complex class may declare simpler classes(constructed using the default construtor) and include them in the declaration of this complicate class.

#include <iostream>

class Point
{
    // use default constructor
public:
    void setX(int newX) { x = newX; }
    void setY(int newY) { y = newY; }
    int getX() const { return x; }
    int getY() const { return y; }
private:
    int x;
    int y;
};

class Line
{
public:
    Line(Point start, Point end);
    ~Line();
    void printLine() const;
private:
    Point start;
    Point end;
};

Line::Line(Point p1, Point p2){
    start.setX( p1.getX());
    start.setY( p1.getY());
    end.setX( p2.getX());
    end.setY( p2.getY());
}

Line::~Line(){

}

void Line::printLine() const{
    std::cout << start.getX() << start.getY() << end.getX() << end.getY() << "\n";
}

int main()
{
    Point p1, p2;

    p1.setX(1);
    p1.setY(2);
    p2.setX(3);
    p2.setY(4);
    Line l(p1, p2);
    l.printLine();

    return 0;
}

HOUR 10: Creating Pointers


Understanding Pointers and Their Usage


A pointer is a variable that holds a memory address.
You can use the address-of operator & to get the address of a variable.

How does the compiler know how much memory each variable needs? You tell the compiler how much memory to allow for your variables by declaring the variable’s type.

Storing the Address in a Pointer


Every variable has an address, you can store that address in a pointer. A pointer is just a special type of variable that holds the address of an object in memory.
When you declare a pointer variable with a type, it is set up to hold an address of the declared type.

All pointers, when they are created, should be initialized to something. If you don’t know what you want to assign to the pointer, assign nullptr which points to nothing in C++. nullptr is an address constant equivalent to NULL .
If you are using a C++ compiler that does not support C++14 or C++11, use NULL instead of 0 or nullptr.

Accessing a variable by using the pointer is called indirection access. The indirection operator * also is called the dereference operator. When a pointer is dereferenced, the value at the address stored by the pointer is retrieved.
The indirection operator * in front of the variable means “Take the value stored at the address in the pointer.

Why Use Pointers?


Pointers are employed most often for three tasks:
  • Managing data on the heap
  • The heap is a memory used by programming languages to store global variables.
    The advantage to the heap is that the memory you reserve remains available until you explicitly free it. If you reserve memory on the heap while in a function, the memory is still available when the function returns.
    A stack is a special area of computer's memory which stores temporary variables created by a function. The stack is cleaned automatically when a function returns. All the local variables go out of scope, and they are removed from the stack.
  • Accessing class member data and functions
  • Passing variables by reference to functions

Using the new Keyword


You allocate memory on the heap in C++ by using the new keyword followed by the type of the object that you want to allocate.
The return value from new is a memory address. It must be assigned to a pointer.

unsigned short int *p = new (unsigned short int);
Line *pl = new Line(p1, p2);

#include <iostream>

using namespace std;

struct MyClass {
  int data[100];
};


int main(){

        MyClass* p1 = new MyClass;
        p1->data[0] = 'a';
        cout << p1->data[0] << endl;
        MyClass& p2 = *new MyClass;  // reference must be initialized with an object, not an address.
        // so we use * to de-reference the address
        p2.data[1] = 'b';
        cout << p2.data[1] << endl;
    return 0;
}

If new cannot create memory on the heap , it throws an exception.
Some older compilers return a pointer that equals NULL. If you have an older compiler, check your pointer for NULL each time you request new memory.
All modern compilers can be counted on to throw an exception.

A pointer to a C++ class is done exactly the same way as a pointer to a structure and to access members of a pointer to a class you use the member access operator -&ft; operator, just as you do with pointers to structures.


class Box {
   public:
      // Constructor definition
      Box(double l = 2.0, double b = 2.0, double h = 2.0) {
         length = l;
         breadth = b;
         height = h;
      }
      double Volume() {
         return length * breadth * height;
      }      
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a bo
};

int main(void) {
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box *ptrBox;                // Declare pointer to a class.

   // Save the address of first object
   ptrBox = &Box1;
   
   cout << "Volume of Box1: " << ptrBox->Volume() << endl;
}

Using the delete Keyword


The memory allocated with the new operator is not freed automatically.
If the memory is not freed, this situation is called a memory leak because that memory can’t be recovered until the program ends.
When you have finished with your area of memory, you must call delete on the pointer, which returns the memory to the heap.

delete pPointer;
When you call delete on a pointer, the memory it points to is freed. Calling delete on that pointer again will crash your program!

Avoiding Memory Leaks


For every time in your program that you call new, there should be a call to delete. It is important to keep track of which pointer owns an area of memory and to ensure that the memory is returned to the heap when you are done with it.

Null Pointer Constant


NULL is:
  • typically defined as (void *)0 and
  • conversion of NULL to integral types is allowed
Setting NULL pointers worked well in almost all circumstances, but it created an ambiguity(0, or NULL pointer?) when a class relies on function overloading.

// function with integer argument 
int fun(int N)   { cout << "fun(int)"; } 
  
// Overloaded function with char pointer argument 
int fun(char* s)  { cout << "fun(char *)"; } 
  
int main()  
{ 
    // Ideally, it should have called fun(char *), 
    // but it causes compiler error. 
    fun(NULL);   
} 
What is the problem with above program?
NULL is typically defined as (void *)0 and conversion of NULL to integral types is allowed. So the function call fun(NULL) becomes ambiguous.
nullptr is a keyword that can be used at all places where NULL is expected, nullptr is implicitly convertible and comparable to any pointer type.
nullptr is a new addition to C++, the program using it must be compiled with the --std=c++14 flag.

HOUR 11: Developing Advanced Pointers


Creating / Deleting Objects on the Heap


Just as you can create a pointer to an integer, you can create a pointer to any object.

Class_name *p = new Class_name;
A constructor is called whenever an object is created on the stack or on the heap by new.
When you call delete on a pointer to an object on the heap, the object’s destructor is called before the memory is released.
The default destructor works fine unless we have dynamically allocated memory or pointer in class.
When a class contains a pointer to memory allocated in class, we should write a destructor to release memory before the class instance is destroyed. This must be done to avoid memory leak.

Accessing Data Members Using Pointers


Data members and functions are accessed by using the dot operator ( . ) for objects.

(*ptr).func()

To use a pointer of object, the points-to operator -> is used to access the member data and functions.

ptr->func()

Member Data on the Heap


One or more of the data members of a class can be a pointer to an object on the heap. The memory can be allocated in the class constructor or in one of its functions, and it can be deleted in its destructor.

The this Pointer


Every class member function has a hidden parameter: the this pointer, which points to the individual object in which the function is running.
this stores the memory address of an object.

Stray or Dangling Pointers


To be safe, after you delete a pointer, set it to nullptr .

const Pointers


You can use the keyword const for pointers
  • before the type
  • 
    (const int) *pOne;
    
    pOne is a pointer to a constant integer.
  • after the type
  • 
    (int *) const pTwo;
    
    pTwo is a constant pointer to an integer. pTwo can’t point to anything else.
  • in both places
  • 
    const (int *) const pThree;
    
    pThree is a constant pointer to a constant integer.
An object declared as const cannot be modified and hence, can invoke only const member functions as these functions ensure not to modify the object.
That is, if you declare a pointer to a const object, the only functions that you can call with that pointer are const functions.

HOUR 12: Creating References


C++ introduced the so-called reference variables (or references in short).

What is a Reference?

A reference is an alias. The reference acts as an alternative name for the target, and anything you do to the reference is really done to the target.
Pointers are variables that hold the address of another object.
References are aliases to an object
.

Creating a Reference

You create a reference by:

the_type_od_target_object &the_name_of_created_reference) = the_target_object;
References must be declared and initialized at the same time.
The operator & is used for the reference operator when it is used in the declaration statement.

#include <iostream>

int main()
{
    int intOne;
    int &rSomeRef = intOne;

    intOne = 5;
    std::cout << "intOne: " << intOne << std::endl;
    std::cout << "rSomeRef: " << rSomeRef << std::endl;

    rSomeRef = 7;
    std::cout << "intOne: " << intOne << std::endl;
    std::cout << "rSomeRef: " << rSomeRef << std::endl;

    std::cout << "&intOne=" << &intOne << "; &rSomeRef=" << &rSomeRef;

    return 0;
}

$ ./test
intOne: 5
rSomeRef: 5
intOne: 7
rSomeRef: 7
&intOne=0x7ffc4be1a46c; &rSomeRef=0x7ffc4be1a46c

The addresses of the reference and its target object are identical.
C++ gives you no way to access the address of the reference.

What Can Be Referenced?

Any object can be referenced, including user-defined objects.
References to objects are used just like the object itself.
Member data and functions are accessed using the normal class member access operator ( . ).
As with the built-in types, the reference acts as an alias to the object.

Null Pointers and Null References

A reference cannot be null.

Passing Function Arguments by Reference

Functions have two limitations: Arguments are passed by value and the return statement only can return one value.
In C++, passing by reference is accomplished in two ways: using pointers and using references.
The main use of references is acting as function formal parameters to support pass-by-reference.
If an reference variable is passed into a function, the function works on the original copy (instead of a clone copy in pass-by-value). Changes inside the function are reflected outside the function.

#include <iostream>

class Man{

public:
    int age;
    void set_age(Man &p,int age);
};

void set_age(Man &p, int age){
    p.age = 52;
}

int main()
{
    Man jerry;

    set_age(jerry, 52);

    std::cout << jerry.age ;

    return 0;
}

Understanding Function Headers and Prototypes

In C++, users of classes rely on the header file to tell all that is needed to use the class.
For ex., the values passed into class's methods are passed by references or values.

Returning Multiple Values

This can be done with passing objects by reference that enables a function to change the original objects.

HOUR 13: Developing Advanced References and Pointers



Passing by Reference for Efficiency

Each time you pass an object into a function by value, a copy of the object is made.
Each time you return an object from a function by value, another copy is made.

The copy constructor is called each time a temporary copy of the object is put on the stack.
In the C++ programming language, a copy constructor is a special constructor for creating a new object as a copy of an existing object.
The following cases may result in a call to a copy constructor:

  • When an object is returned by value
  • When an object is passed (to a function) by value as an argument
  • When an object is thrown
  • When an object is caught
  • When an object is placed in a brace-enclosed initializer list

Passing a big massive structure by copying it onto the stack can be expensive in terms of performance and memory consumption.


#include <iostream>

/*** class ***/
class Man
{
public:
    int age;
    Man();           // constructor
    Man(Man &p); // copy constructor
    ~Man();          // destructor
};

Man::Man()
{
    this->age = 50;
    std::cout << "  Man's Constructor()..." << this << "\n";
}

Man::Man(Man &p)
{
    this->age = 60;
    std::cout << "  Man's Copy Constructor() ..." << this << "\n";
}

Man::~Man()
{
    this->age = 0;
    std::cout << "  Man's Destructor() ..." << this << "\n";
}

/*** functions ***/
// passes by value
Man func_val(Man p)
{
    std::cout << "  func_val() returns an object " << &p << ",age=" << p.age << "\n";
    return p;
}

// functionTwo, passes by reference
Man *func_ref(Man *p)
{
    std::cout << "  func_ref() returns a pointer " << p << ",age=" << p->age << "\n";
    return p;
}

int main()
{
    std::cout << "Making a jerry ..." << std::endl;
    Man jerry;
    std::cout << "jerry's age=" << jerry.age << "\n"; 
    std::cout << "Making a person ..." << std::endl;
    Man person;
    std::cout << "person's age=" << person.age << "\n"; 
    Man *man_ptr=nullptr;
    std::cout << "Calling func_val(jerry) ..." << std::endl;
    person = func_val(jerry);
    std::cout << "person's age=" << person.age << "\n"; 
    std::cout << "Calling func_ref(&jerry) ..." << std::endl;
    man_ptr = func_ref(&jerry);
    std::cout << "man_ptr's age=" << man_ptr->age << "\n"; 
    std::cout << "End\n";
    return 0;
}

$ ./test
Making a jerry ...
  Man's Constructor()...0x7ffde1e1d2d0
jerry's age=50
Making a person ...
  Man's Constructor()...0x7ffde1e1d2d4
person's age=50
Calling func_val(jerry) ...
  Man's Copy Constructor() ...0x7ffde1e1d2d8
  func_val() returns an object copied from0x7ffde1e1d2d8,age=60
  Man's Copy Constructor() ...0x7ffde1e1d2dc
  Man's Destructor() ...0x7ffde1e1d2dc
  Man's Destructor() ...0x7ffde1e1d2d8
person's age=60
Calling func_ref(&jerry) ...
  func_ref() returns a pointer 0x7ffde1e1d2d0,age=50
man_ptr's age=50
End
  Man's Destructor() ...0x7ffde1e1d2d4
  Man's Destructor() ...0x7ffde1e1d2d0
If the input parameter of a function is an object, the input object type's copy constructor is called to create the object on the function's stack.
The object's descructor will be called before the function returned.

Passing a const Pointer

Passing the const pointer of a object to functions prevents calling any non- const member function on this object , and thus protects the object from change. A function becomes const when the const keyword is used in the function’s declaration.
The idea of const functions is not to allow them to modify the object on which they are called.

#include <iostream> 

using namespace std; 
  
class Test { 
    int value; 
public: 
    Test(int v = 0) {value = v;} 
      
    int getValue() const {
    	// value = 100;"  // compiler error
    	return value; 
    }   
    void touchTest(const Test *test_p){
        // test_p->value = 10; // compiler error
        cout << test_p->value << "\n";
    }    
}; 
  
int main() { 
    Test t(20); 
    cout << t.getValue(); 
    t.touchTest(&t);
    return 0; 
} 

References as an Alternative to Pointers

To take and return a reference to a constant object.

#include <iostream> 

using namespace std; 
  
class Man { 
    int age; 
public: 
    Man(int v = 0) {age = v;} 
      
    // We get compiler error if we add a line like "value = 100;" 
    // in this function. 
    int getAge() const { return age; }   

    void setAge(int age){
        this->age = age;
        cout << this->age << "\n";
    }
}; 
  
const Man & getRef (const Man & person)
{
    cout << "Returning..." << endl;
    cout << "The man is now " << person.getAge() << " years old" << endl;
    //person.setAge(8); // compile error
    return person;
}

int main() { 
    Man m(20); 
    cout << m.getAge() << "\n"; 
    getRef(m);
    return 0; 
} 

$ ./test
20
Returning...
The man is now 20 years old

When to Use References and When to Use Pointers

  • References cannot be reassigned
  • If you need to point first to one object and then to another, you must use a pointer.
  • References cannot be NULL
  • If there is any chance that the object in question might be, you must use a pointer rather than a reference.
  • If you want to allocate dynamic memory from the heap, you have to use pointers

References to Objects Not in Scope

A reference always is an alias that refers to some other object, make sure the referenced object exist whenever the reference is used. The following function declares a local object and initializes it, then, returns that local object by reference.

Man &simp()
{
    Man sman(10);
    return sman;
}
Since the local variable will be destroyed, the compiler will detect that and warn you:
warning: reference to local variable ‘sman’ returned

Returning a Reference to an Object on the Heap

The following resolved the variable scope issue but introduced a memory leak issue,

Man & getRef ()
{
    Man *p = new Man(15);

    return *p;
}
The returned memory referenced can't be delete()ed.
A better solution is to declare the object in the calling function and then pass it to the processing function by reference.

Pointer, Pointer, Who Has the Pointer?

When your program allocates memory on the heap, a pointer is returned. It is imperative that you keep a pointer to that memory, because after the pointer is lost, the memory cannot be deleted and becomes a memory leak.
It is dangerous for one function to create space in memory and another to free it,

HOUR 14: Calling Advanced Functions


Overloaded Member Functions

To implement function overloading by writing multiple functions with the same name but different parameters.
The compiler decides which function to call based on the number and type of parameters entered.

#include <iostream>

class Rectangle
{
public:
    Rectangle(int width, int height);
    ~Rectangle() {}
    void drawShape() const;
    void drawShape(int width, int height) const;
private:
    int width;
    int height;
};

Rectangle::Rectangle(int newWidth, int newHeight)
{
    width = newWidth;
    height = newHeight;
}

void Rectangle::drawShape() const
{
    drawShape(width, height);
}

void Rectangle::drawShape(int width, int height) const
{
    for (int i = 0; i < height; i++){
        for (int j = 0; j < width; j++)
            std::cout << "*";
        std::cout << std::endl;
    }
}

int main()
{
    Rectangle box(30, 5);
    std::cout << "drawShape():" << std::endl;
    box.drawShape();
    std::cout << "\ndrawShape(40, 2):" << std::endl;
    box.drawShape(40, 2);
    return 0;
}

$ ./test
drawShape():
******************************
******************************
******************************
******************************
******************************

drawShape(40, 2):
****************************************
****************************************

Using Default Values for Parameters

In the declaration of the function to assign the default parameter value:

void drawShape( int aWidth, int aHeight, bool useCurrentValue = false) const;

Initializing Objects

The compiler chooses the right constructor based on the number and type of the parameters.
You can overload constructors, but you can’t overload destructors. Destructors always have the same signature: the name of the class prepended by a tilde (~) and no parameters.

A member variable can be set :

  • during the constructor's initialization
  • by assigning it a value in the body of the constructor.
To initialize member variables during the initialization:

#include <iostream>

struct S {
    int x;
    int y;
    S(int); // constructor declaration
    S() : x(7), y(8) {} // constructor definition.
                  // ": x(7), y(8)" is the initializer list
                  // "{}" is the function body
};

S::S(int n) : x(n), y(n) // constructor definition. ": x(n), y(n)" is the initializer list
{ 
	y += x; 
} 

int main()
{
    S s; // calls S::S()
    std::cout << s.x << s.y << "\n";
    S s2(9); // calls S::S(int)
    std::cout << s2.x << s2.y << "\n";
}

$ ./test
78
918

The Copy Constructor

The compiler provides a default copy constructor.
The copy constructor is called every time a copy of an object is made.
When you pass an object by value, either into a function or as a function’s return value, a temporary copy of that object is made.
All copy constructors take one parameter: a reference to an object of the same class. It is a good idea to make it a constant reference, because the constructor will not have to alter the object passed in.

Man(const Man &person);
The copy constructor will do the shallow (or member-wise) copy.
If there is any member is a pointer variable, you need to new() the variable in the constructor then delete() it in the descructor.

Compile-Time Constant Expressions

constexpr is a feature added in C++ 11.
The main idea is performance improvement of programs by doing computations at compile time rather than run time.
constexpr specifies that the value of an object or a function can be evaluated at compile time and the expression can be used in other constant expressions.
Consider the following C++ program:

#include <iostream> 

using namespace std; 
  
constexpr int product(int x, int y) 
{ 
    return (x * y); 
} 
  
int main() 
{ 
    const int x = product(10, 20); 
    cout << x; 
    return 0; 
}

HOUR 15: Using Operator Overloading


Operator Overloading

An overloaded operator is called an operator function.
You declare an operator function with the keyword operator preceding the operator.

C++ allows you to define your own meanings for the standard C++ operators when they are applied to class types.
Operator overloading defines what happens when a specific operator is used with an object of a class. When you implement an operator for a class, you are said to be overloading that operator.
The most common way to overload an operator in a class is to implement a member function as this form:

returnType operatorsymbol(parameter list)
{
// body of overloaded member function
}

The name of the operator's overloading function is the key word operator followed by the operator's symbol being defined, such as + or ++ .

A simple and complete example:


#include <iostream> 
using namespace std; 
  
class Complex { 
private: 
    int real, imag; 
public: 
    Complex(int r = 0, int i =0)  {real = r;   imag = i;} 
      
    // This is automatically called when '+' is used with 
    // between two Complex objects 
    Complex operator + (Complex const &obj) { 
         Complex res; 
         cout << real << " + i" << imag << " + " << obj.real << " + i" << obj.imag << endl;; 
         res.real = real + obj.real; 
         res.imag = imag + obj.imag; 
         return res; 
    } 
    void print() { cout << real << " + i" << imag << endl; } 
}; 
  
int main() 
{ 
    Complex c1(10, 5), c2(2, 4); 
    Complex c3 = c1 + c2; // An example call to "operator +" with an object
    c3.print(); 
} 

$ ./test
10 + i5 + 2 + i4
12 + i9
  

Overloading the << Operator for Your Own Classes

In C++,
  • stream insertion operator<<” is used for output and extraction operator>>” is used for input.
  • cout is an object of ostream class and cin is an object of istream class
  • These operators must be overloaded as a global function. And if we want to allow them to access private data members of the class, we must make them friendly to that class.

// overload_date.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;

class Date
{
    int mo, da, yr;
public:
    Date(int m, int d, int y)
    {
        mo = m; da = d; yr = y;
    }
    friend ostream& operator<< (ostream& os, const Date& dt);
};

ostream& operator<< (ostream& os, const Date& dt)
{
    os << dt.mo << '/' << dt.da << '/' << dt.yr;
    return os;
}

int main()
{
    Date dt(5, 6, 92);
    cout << dt;
}
$ ./test
5/6/92

Limitations on Operator Overloading

  • Almost all operators can be overloaded except few.
  • Following is the list of operators that cannot be overloaded.
    • . (dot)
    • ::
    • ?:
    • sizeof
  • Operators for built-in types such as int cannot be overloaded.
  • operator=
  • The C++ compiler provides each class with a default constructor, destructor, copy constructor, and the assignment operator.
    The overloading of the assignment operator is to avoid memory leaks when working with dynamically allocated member data in a class.
    The dynamically allocated member data must be delete()ed then new()ed during the assignment.

Type Conversion Operators

A simple and complete example:

#include <iostream> 
#include <cmath> 
  
using namespace std; 
  
class Complex 
{ 
private: 
    double real; 
    double imag; 
  
public: 
    // Default constructor 
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) 
    {} 
  
    // magnitude : usual function style 
    double mag() 
    { 
        return getMag(); 
    } 
  
    // magnitude : conversion operator 
    operator double () 
    { 
        return getMag(); 
    } 
  
private: 
    // class helper to get magnitude 
    double getMag() 
    { 
        return sqrt(real * real + imag * imag); 
    } 
}; 
  
int main() 
{ 
    // a Complex object 
    Complex com(3.0, 4.0); 
  
    // print magnitude 
    cout << com.mag() << endl; 
    // same can be done like this 
    cout << com << endl; 
}
  
The usage of such smart (over smart ?) techniques are discouraged.

HOUR 16: Extending Classes with Inheritance


What Is Inheritance?

The capability of a class to derive properties and characteristics from another class is called Inheritance.
Inheritance is one of the most important feature of Object Oriented Programming.
  • Sub Class
  • The class that inherits properties from another class is called Sub class or Derived Class.
  • Super Class
  • The class whose properties are inherited by sub class is called Base Class or Super class.

The Syntax of Derivation

To create a class that inherits from another class in C++, in the class declaration,
  • put a colon after the class name
  • specify the access level of the class ( public , protected , or private )
  • the base class from which it derives

class subclass_name : access_level base_class_name

Types of Inheritance

  • Public
  • If we derive a sub class from a public base class. Then the public member of the base class will become public in the derived class and protected members of the base class will become protected in derived class.
  • Protected
  • If we derive a sub class from a Protected base class. Then both public member and protected members of the base class will become protected in derived class.
  • Private
  • If we derive a sub class from a Private base class. Then both public member and protected members of the base class will become Private in derived class.

Private Versus Protected Access

Private members are not available to derived classes.
Protected data members and functions are fully visible to derived classes, but are otherwise private.



// C++ Implementation to show that a derived class 
// doesn’t inherit access to private data members.  
// However, it does inherit a full parent object 
class A  
{ 
public: 
    int x; 
protected: 
    int y; 
private: 
    int z; 
}; 
  
class B : public A 
{ 
    // x is public 
    // y is protected 
    // z is not accessible from B 
}; 
  
class C : protected A 
{ 
    // x is protected 
    // y is protected 
    // z is not accessible from C 
}; 
  
class D : private A    // 'private' is default for classes 
{ 
    // x is private 
    // y is private 
    // z is not accessible from D 
}; 

Constructors and Destructors

More than one constructor is called when an object of a derived class is created.
Default constructor is present in all the classes.
In the below example we will see when and why Base class's and Derived class's constructors are called.

#include <iostream> 
using namespace std; 

class Base
{ 
    int x;
    public:
    // default constructor
    Base() 
    { 
        cout << "Base()\n"; 
    }
};

class Derived : public Base
{ 
    int y;
    public:
    // default constructor
    Derived() 
    { 
        cout << "Derived()\n"; 
    }
    // parameterized constructor
    Derived(int i) 
    { 
        cout << "Derived(i)\n"; 
    }
};

int main()
{
    cout << "init b\n";
    Base b;      
    cout << "init d1\n";  
    Derived d1;    
    cout << "init d2\n";
    Derived d2(10);
}

$ ./test
init b
Base()
init d1
Base()
Derived()
init d2
Base()
Derived(i)

Passing Arguments to the Base Class' Constructors

Base class initialization can be performed during its subclass initialization by writing the base class name followed by the parameters expected by the base class.
A Derived class constructor has access only to its own class members, but a derived class object also have inherited property of base class, and only base class constructor can properly initialize base class members.

#include <iostream>
using namespace std; 

class Base
{ 
public:
    int x;
    // parameterized constructor
    Base(){
        cout << "Base():\n";
    }
    Base(int i)
    { 
        x = i;
        cout << "Base(i):" << x << "\n";
    }
};

class Derived : public Base
{ 
public:
    int y;
    Derived()
    {
        cout << "Derived():\n";
    }
    // parameterized constructor
    Derived(int i):Base(i)
    { 
        y = i + x;
        cout << "Derived(i):" << y << "\n";
    }
};

int main()
{
    Derived n;
    Derived d(10) ;
}

$ ./test
Base():
Derived():
Base(i):10
Derived(i):20

Overriding Functions

When a derived class creates a member function with the same return type and signature as a member function in the base class, but with a new implementation, it is said to be overriding that function.

Overloading Versus Overriding

In C++,
  • When you overload a member function, you create more than one function with the same name but with different signatures.
  • Overloaded functions must differ in function signature. (achieved at compile time)
  • When you override a member function, you create a function in a derived class with the same name as a function in the base class and with the same signature.
  • (achieved at run time)

Calling the Base Member Function

If you have overridden a base member function, it is still possible to call it by fully qualifying the name of the function.
You do this by:
	
base_class:base_method()

HOUR 17: Using Polymorphism and Derived Classes


Polymorphism means "many forms".
多型(polymorphism), 在derived class中重新定義一個function(使用相同的function名稱),使其可處理一特定type的資料。
故在物件導向程式設計語言, 同一function可以依處理object的type執行其相對應的運算

A single operation can be executed in different ways according to the type of operated objects.


#include <iostream>
#include <string>
using namespace std;

// Base class
class Animal {
  public:
    void animalSound() {
      cout << "The animal makes a sound \n" ;
    }
};

// Derived class
class Pig : public Animal {
  public:
    void animalSound() {
      cout << "The pig says: wee wee \n" ;
    }
};

// Derived class
class Dog : public Animal {
  public:
    void animalSound() {
      cout << "The dog says: bow wow \n" ;
    }
};

int main() {
  Animal myAnimal;
  Pig myPig;
  Dog myDog;

  myAnimal.animalSound();
  myPig.animalSound();
  myDog.animalSound();
  return 0;
}  

$ ./test
The animal makes a sound 
The pig says: wee wee 
The dog says: bow wow 

  
In C++ polymorphism is mainly divided into two types:
  • Compile time Polymorphism
  • This type of polymorphism is achieved by function overloading or operator overloading.
    Functions can be overloaded by different signatures.
  • Runtime Polymorphism
  • This type of polymorphism is achieved by Function Overriding.
    When a derived class has a definition for one of the member functions of the base class. That base function is said to be overridden.

Polymorphism Implemented with Virtual Member Functions

A virtual function is a member function of the base class, that is overridden in derived class.
The classes that have virtual functions are called polymorphic classes.
The compiler binds virtual function at runtime, hence called runtime polymorphism. Use of virtual function allows the program to decide at runtime which function is to be called based on the type of the object pointed by the pointer. A virtual member function is declared with the virtual keyword.

Class class_name
 {
   public:
     virtual returnType func_name( args.. )
      {
        //function definition
      }
 }

Consider the following simple program as an example of runtime polymorphism.
The main thing to note about the program is that the derived class’s function is called using a base class pointer.

#include <iostream>
using namespace std; 


class Human
{
 public:
 void label()
 {
 cout << "\nI am a human.";
 }
};

class Man : public Human
{
 public:
 void label()
 {
 cout << "\nI am a man.";
 }
};
class Woman : public Human
{
 public:
 void label()
 {
 cout << "\nI am a woman.";
 }
};

int main()
{
 Human *obj = new Human;
 Man *obj2 = new Man;
 Woman *obj3 = new Woman;

 obj->label();
 obj2->label();
 obj3->label();

 obj = obj2;
 obj->label();

 return 0;
}

$ ./test

I am a human.
I am a man.
I am a woman.
I am a human.
If we define the base class' method as a virtual function:

   virtual void label()
The results will be:

$ ./test

I am a human.
I am a man.
I am a woman.
I am a man.
The virtual functions are resolved at runtime.
This is called late binding or runtime binding.

What is the usage of the virtual function ?

Virtual functions allow us to create a base class pointers and call methods of any of the derived classes without even knowing kind of derived class object.
For example, consider an employee management software for an organization.
  • a simple base class Employee , the class contains virtual functions like raiseSalary(), transfer(), promote(), etc.
  • Different types of employees like Manager, Engineer, etc. may have their own implementations of the virtual functions present in base class Employee.
  • we just need to pass a list of employees everywhere and call appropriate functions without even knowing the type of employee.

How Virtual Member Functions Work

If a class contains a virtual function, the compiler will augment some of your code at compile time, the class object will be augmented by a pointer called _vptr which points to the virtual table.
virtual table is an array of a function pointer.
Each virtual function has a fixed index in the virtual table.

Virtual Destructors

Deleting a derived class object using a pointer to a base class that has a virtual function may result in undefined behavior.

  derived *d = new derived();   
  base *b = d; 
  delete b; 

If any of the functions in your class are virtual, the destructor also should be virtual.
Making base class destructor virtual guarantees that the object of derived class is destructed properly, i.e., both base class and derived class destructors are called.
Deleting a derived class object using a pointer of base class type,

// A program with virtual destructor
#include <iostream>

using namespace std;

class base {
public:
	base()	
	{ cout << "Constructing base\n"; }
	virtual ~base()
	{ cout << "Destructing base\n"; }	
};

class derived : public base {
public:
	derived()	
	{ cout << "Constructing derived\n"; }
	virtual ~derived()
	{ cout << "Destructing derived\n"; }
};

int main()
{
derived *d = new derived();
base *b = d;
delete b;
return 0;
}
Output:
 
Constructing base
Constructing derived
Destructing derived
Destructing base

Virtual Copy Constructors

The Cost of Virtual Member Functions

Because objects with virtual member functions must maintain a v-table, some overhead is required to employ them.

HOUR 18: Making Use of Advanced Polymorphism


Problems with Single Inheritance

Generally, if you have a pointer to a base class that is assigned to a derived class object, it is because you intend to use that object polymorphically, and in this case, you ought not even try to access functions that are specific to the derived class which are not declared in the base class. Because the base case can't find that function in its v-table.
If you must use the base class A do some non-polymorphic work on some derived classes B and C, then write like this:

class A { public: virtual ~A(){} };

class B: public A
{ public: void work4B(){} };

class C: public A
{ public: void work4C(){} };

void non_polymorphic_work(A* ap)
{
  if (B* bp = dynamic_cast<B*>(ap))
    bp->work4B(); 
  if (C* cp = dynamic_cast<C*>(ap))
    cp->work4C(); 
}

Syntax

dynamic_cast < new-type > ( expression )		

Abstract Data Types

Often, you will create a hierarchy of classes together.
Each of the derived classes overrides the member function defined in its parents.
The class exists only to provide an interface for its derived classes is called an abstract data type, or ADT.
In C++, an ADT is always the base class to other classes, and it is not valid to make an instance of an ADT.

Pure Virtual Functions

A pure virtual function is a virtual function that must be overridden in the derived class.
Pure refers to the lack of any implementation detail. That is, a pure virtual function is a signature without a definition.
A virtual function is made pure by initializing it with 0:

virtual void draw() = 0;
Any class with one or more pure virtual functions is an ADT, and it is illegal to instantiate an object of a class that is an ADT.
Any class that derives from an ADT inherits the pure virtual function as pure, so it must override every pure virtual function if it wants to instantiate objects.

#include <iostream>
using namespace std;

class Base
{
int x;
public:
	virtual void fun() = 0;
	int getX() { return x; }
};

// This class inherits from Base and implements fun()
class Derived: public Base
{
	int y;
public:
	void fun() { cout << "fun() called"; }
};

int main(void)
{
	Derived d;
	d.fun();
	return 0;
}

Implementing Pure Virtual Functions

Typically, the pure virtual functions in an abstract base class are never implemented.
It is possible, however, to provide an implementation to a pure virtual function. The function then can be called by objects derived from the ADT, perhaps to provide common functionality to all the overridden functions.

Complex Hierarchies of Abstraction

At times, you will want the derived class is an ADT from other ADTs.
It might be that you want to make some of the derived pure virtual functions non-pure and leave others pure to be implemented by further derived classes.

HOUR 19: Storing Information in Linked Lists


Linked Lists and Other Structures

Array's containers are in a fixed size.
A linked list is a data structure that consists of necessary containers that connect together.
These linked containers are called nodes. The first node in the list is called the head, and the last node in the list is called the tail.
Linked list has 3 forms:
  • Singly linked
  • Doubly linked
  • Trees

Linked List Case Study

We examine a linked list in detail as a case study.

Delegation of Responsibility

Each class sticks to its own assigned work, but together they create a functioning program.

Component Parts

The Data is a class you defined to be used in the list.

class Data
{
public:
    Data(int newVal):value(newVal) {}
    ~Data() {}
    int compare(const Data&);
    void show() { std::cout << value << "\n"; }
private:
    int value;
};

// decide the order of inserted data to the current data object
int Data::compare(const Data &otherData)
{
    if (value < otherData.value)
        return kIsSmaller;
    if (value > otherData.value)
        return kIsLarger;
    else
        return kIsSame;
}
The Node class itself will be abstract for 3 derived classes:

class Node
{
public:
    Node() {}
    virtual ~Node() {}
    virtual Node* insert(Data* data) = 0;
    virtual void show() = 0;
private:
};
  • HeadNode
  • 
    /*** HeadNode ***/
    // The head node, which holds no data but instead points
    // to the beginning of the list.
    class HeadNode : public Node
    {
    public:
        HeadNode();
        ~HeadNode() { delete next; }
        Node* insert(Data* data); // A node object created 
        void show() { next->show(); }
    private:
        Node *next;
    };
    
    // The first node in the list, which creates the tail
    HeadNode::HeadNode()
    {
        next = new TailNode;
        std::cout << "Created a TailNode=" << next << "\n";
    }
    
    // Since nothing comes before the head, just pass
    // the data on to the next node
    Node* HeadNode::insert(Data* data)
    {
        std::cout << "HeadNode:insert ";
        if ( data )
            data->show();
        next = next->insert(data);
        if ( next ) {
            std::cout << ", HeadNode:->next=" << next << "\n";
        }    
        return this;
    }    
        
  • TailNode
  • 
    class TailNode : public Node
    {
    public:
        TailNode() {}
        ~TailNode() {}
        Node* insert(Data* data);
        void show() {}
    private:
    };
    
    // If data comes to me, it must be inserted before me
    // since nothing goes after the tail
    Node* TailNode::insert(Data* data)
    {
        InternalNode* dataNode = new InternalNode(data, this); // data, next
        std::cout << " insert a data node=" << dataNode << " before the tail=" << this << "\n";
        return dataNode;
    }
    
    
  • InternalNode
  • This classe holds Data.
    
    // A node to hold objects of type Data.
    class InternalNode : public Node
    {
    public:
        InternalNode(Data* data, Node* next);
        ~InternalNode() { delete next; delete data; }
        Node* insert(Data* data);
        void show()
            { data->show(); next->show(); } // delegate!
    private:
        Data* data;// the data itself
        Node* next;// points to next node in the linked list
    };
    
    InternalNode::InternalNode(Data* newData, Node* newNext):
    data(newData), next(newNext)
    {
    }
    
    // A function to store a new object in the list.
    // The object is passed to the node which figures out
    // where it goes and inserts it into the list.
    Node* InternalNode::insert(Data* otherData)
    {
        std::cout << "InternalNode::insert(): comapre ";
        this->data->show();
        std::cout << "\n";
    
        int result = data->compare(*otherData);
        switch (result)
        {
            case kIsSame:    // the same content
            // fall through
            case kIsLarger:  // new data's content is small
            	{
                InternalNode* dataNode = new InternalNode(otherData, this);
                return dataNode;
                }
            case kIsSmaller:  // new data's content is bigger
                next = next->insert(otherData);
                return this;
        }
        return this; // appease the compiler
    }    
        
The LinkedList class connect all Nodes:

/*** LinkedList ***/
class LinkedList
{
public:
    LinkedList();
    ~LinkedList() { delete head; }
    void insert(Data* data);
    void showAll() { head->show(); }
private:
    HeadNode *head; 
};

// create the head node, which creates
// the tail node.
LinkedList::LinkedList()
{
    head = new HeadNode;// creat a tail pointed to next
}

// Delegate to a HeadNode node
void LinkedList::insert(Data* pData)
{
    head->insert(pData);
}

/*** main ***/

int main()
{
    Data *pData;
    int val;
    LinkedList ll;
    // store user values in a linked list
    while (true)
    {
        std::cout << "What value (0 to stop)? ";
        std::cin >> val;
        if (!val)
            break;
        pData = new Data(val);
        ll.insert(pData);
    }
    // display the list
    ll.showAll();
    return 0;
}

/*

init:

      HeadNode->next = TailNode

Insert the 1st value: TailNode->insert() is called

    InternalNode->next = TailNode
    HeadNode->next = InternalNode

    
Insert other values: InternalNode->insert() is called

    if ( new value > HeadNode->next )
        next = next->insert()   // traverse the linked node 
    if ( new value <= current InternalNode )
        new_InternalNode->next = InternalNode
        

*/

$ ./test
Created a TailNode=0x55a57ff44ed0
What value (0 to stop)? 5
HeadNode:insert 5
 insert a data node=0x55a57ff45730 before the tail=0x55a57ff44ed0
, HeadNode:->next=0x55a57ff45730
What value (0 to stop)? 1
HeadNode:insert 1
InternalNode::insert(): comapre 5

, HeadNode:->next=0x55a57ff45770
What value (0 to stop)? 3
HeadNode:insert 3
InternalNode::insert(): comapre 1

InternalNode::insert(): comapre 5

, HeadNode:->next=0x55a57ff45770
What value (0 to stop)? 7
HeadNode:insert 7
InternalNode::insert(): comapre 1

InternalNode::insert(): comapre 3

InternalNode::insert(): comapre 5

 insert a data node=0x55a57ff457f0 before the tail=0x55a57ff44ed0
, HeadNode:->next=0x55a57ff45770
What value (0 to stop)? 0
1
3
5
7

HOUR 20: Using Special Classes, Functions, and Pointers


The static members exist not in an object but in the scope of the class.

Static Member Data

Static member variables are shared among all instances of a class.
You can think of a static member as belonging to the class rather than to the object. Normal member data is one per object, but static members are one per class.
For ex., for counting the objects created, a static member variable increases with each construction and decreases with each destruction.

#include <iostream>
class Robot
{
public:
    Robot(int newAge = 1):age(newAge){ howManyRobots++; }
    virtual ~Robot() { howManyRobots--; }
    virtual int getAge() { return age; }
    virtual void setAge(int newAge) { age = newAge; }
    static int howManyRobots;
private:
    int age;
};

Static Member Functions

Static member functions are like static member variables: They exist not in an object but in the scope of the class. Therefore, they can be called without having an object of that class.
Note,
  • static member functions do not have a this pointer
  • static member functions cannot access any non-static member variables

Containment of Classes

Friend Classes and Functions

If you want to expose your private member data or functions to another class, you must declare that class to be a friend.
For example, a LinkedList class may be allowed to access private members of Node.

class Node { 
private: 
    int key; 
    Node* next; 
    /* Other members of Node Class */
  
    // Now class  LinkedList can 
    // access private members of Node 
    friend class LinkedList; 
}; 
Similarly, a friend function can be given special grant to access private and protected members.
A friend function can be:
  • A method of another class
  • A global function

class Node { 
private: 
    int key; 
    Node* next; 
  
    /* Other members of Node Class */
    friend int LinkedList::search(); 
    // Only search() of linkedList can access internal members 
};
This friendship cannot be transferred and is not inherited.
It also is not mutual. If class A is a friend of B, then B doesn’t become a friend of A automatically.

Pointers to Functions

The declaration of a function pointer always includes the return type and parentheses indicating the type of the parameters, if any.

	return_type (*function_ptr)(...);
For ex.,

void square(int &rX, int &rY)
{
    rX *= rX;
    rY *= rY;
}


int main()
{
    void (*pFunc)(int&, int&);

    pFunc = square;
    pFunc( 2, 4)
}

Arrays of Pointers to Functions

You can declare an array of pointers to functions.

Passing Pointers to Functions to Other Functions

The pointers to functions (and arrays of pointers to functions, for that matter) can be passed to other functions that may take action and then call the right function using the pointer.

Using typedef with Pointers to Functions

The declaration of a function using a pointer to function as its parameter:

  void func( void (*pFunc)() )
  

Pointers to Member Functions

Arrays of Pointers to Member Functions

HOUR 21: Using New Features of C++14


The Newest Version of C++

C++ is a general-purpose programming language created by Bjarne Stroustrup as an extension of the C programming language, or "C with Classes".
The language has expanded significantly over time, and modern C++ now has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.

C++ is standardized by the International Organization for Standardization (ISO): the latest version is informally known as C++17.
The C++ programming language was initially standardized in 1998 as ISO/IEC 14882:1998, which was then amended by the C++03, C++11 and C++14 standards.

Using auto in Function Return Types

In C++11, the auto keyword could be used in a variable declaration to let the compiler infer the data type.
C++ extends this type inference capability by making it possible to use auto as the return type of a function.

auto getArea(int w, int l){
	return (w * l);
}

auto w = 20;
auto l = 30;

auto area = getArea(w, l);

Improved Numeric Literals

Integer literals now can be expressed as binary values and can contain digit separators.
  • To express an integer in binary
  • Precede it with the characters “0b” or “0B”.
    
      int num = 0b11111111; // 255
      
  • The new digit separator in C++ is the single quote character ().
  • The separator is ignored by the C++ compiler and has no effect on the actual value.
    
      long long largeNum = 19'241'686'248'628; // 19241686248628
      

The constexpr Keyword

The keyword constexpr tells the compiler that a certain function is intended to be evaluated at compile time if all the conditions allowing for compile-time evaluation are fulfilled. Otherwise, it will execute at runtime, like a regular function.
The main idea is performance improvement of programs by doing computations at compile time rather than run time.

A constexpr function has a few restrictions; it is not allowed to do the following:

  • Allocate memory on the heap
  • Throw exceptions
  • Handle local static variables
  • Handle thread_local variables
  • Call any function, which, in itself, is not a constexpr.
A literal type is a data type whose memory size can be determined at compile time.
  • A constexpr variable or function must return a literal type.
  • A constexpr variable or function can have one or more parameters, each of which must be a literal type
  • A constexpr variable or function must return a literal type.


// A C++ program to demonstrate the use of constexpr
#include <iostream>
using namespace std;

constexpr long int fib(int n)
{
	return (n <= 1)? n : fib(n-1) + fib(n-2);
}

int main ()
{
	// value of res is computed at compile time.
	const long int res = fib(30);
	cout << res;
	return 0;
}

Lambda Expressions

The lambda expression allowa us write an inline function which can be used for short snippets of code that are not going to be reuse and not worth naming.(anonymous functions)

It is a function that we can write inline in our code in order to pass in to another function.
It is convenient because we can define it locally where we want to call it or pass it to a function as an argument.
In other words, it's just syntactic sugar.

The lambda expression is stored in a variable and called like a function.
The parts of a lambda expression include a capture clause, an optional parameter list, an optional return type, and the function’s body (although some parts are not required). lambda function syntax is defined as:


[ capture clause ] (parameters) -> returnType
{   
   body of function 
} 
  • capture clause
  • A lambda expression normally does not have access to the variables defined in the scope that defines the expression.
    To make one or more variables from the enclosing scope available in the lambda expression:
    • Nothing should be captured.
    • =
    • Specify that all variables used in the function’s definition should be captured by value.
      For ex.,
      
      #include <iostream>
      
      int main()
      {
              int value = 6;
              auto myFunction = [=] (int a) {return value * a * 4;};
              std::cout << myFunction(4) << "\n";
      }
      
      $ ./test
      96
      6
      
    • &
    • Specify that all variables used in the function’s definition should be captured by reference.
      For ex.,
      
      #include <iostream>
      
      int main()
      {
              int value = 6;
              auto myFunction = [&] (int a) {value = 10; return value * a * 4;};
              std::cout << myFunction(4) << "\n";
              std::cout << value << "\n";
      }
      
      $ ./test
      160
      10
      		
  • parameters
  • The parameter list of a lambda is just like the parameter list of any other method
  • returnType
  • Generally return-type in lambda expression are evaluated by compiler itself and we don’t need to specify that explicitly and return-type part can be ignored.

To specify variables from the enclosing scope available in the lambda expression, list them inside the [ capture clause ] separated by commas.


        int x=6, y=3;
        auto myFunction = [x, y] (int a) { return a * x * y;};
        std::cout << myFunction(4) << "\n";

HOUR 22: Employing Object-Oriented Analysis and Design


The Development Cycle

Simulating an Alarm System

You have been asked to simulate the alarm system for a house.
  • The house has four bedrooms, a finished basement, and an under-the-house garage.
  • windows
  • The downstairs has the following windows: three in the kitchen, four in the dining room, one in the half-bathroom, two each in the living room and the family room, and two small windows next to the front door. All four bedrooms are upstairs; each bedroom has two windows except for the master bedroom, which has four. There are two baths, each with one window. Finally, there are four half-windows in the basement and one window in the garage.
  • doors
  • Normal access to the house is through the front door. In addition, the kitchen has a sliding glass door, and the garage has two doors for the cars and one door for easy access to the basement. There also is a cellar door in the backyard.
  • alarms
  • All the windows and doors have alarms , and there is a panic button on each phone and one next to the bed in the master bedroom. The grounds have alarms, as well, although these alarms are carefully calibrated so that they are not set off by small animals or birds.
  • warning
  • A central alarm system in the basement sounds a warning chirp when the alarm has been tripped. If the alarm is not disabled within a set amount of time, the police are called. If a panic button is pushed, the police are called immediately.
  • sensors
  • The alarm also is wired into the fire and smoke detectors and the sprinkler system. The alarm system itself is fault tolerant, has its own internal backup power supply, and is encased in a fireproof box.

PostMaster: A Case Study

HOUR 23: Creating Templates


模板是一個切餅乾的工具,用於指定如何切割看起來幾乎相同的餅乾(儘管餅乾可以由各種麵團製成,但它們都具有相同的基本形狀)。

What Are Templates?

The simple idea of the template is to pass data type as a parameter so that we don’t need to write the same code for different data types.
Templates enable you to create a general class and pass types as parameters to the template to build specific instances of the parameterized type.

Instances of the Template

A class template is for a description of how to build a family of classes that all look basically the same, and a function template describes how to build a family of similar looking functions. The act of creating an object from a class or a specific type from a template is called instantiation, and the individual classes are called instances of the template.

Template Definition

The keyword template is used at the beginning of every declaration and definition of a template class/function.

template <typename T> // declare the template and the parameter

The template’s parameters T will change with each instance.

C++ adds two new keywords to support templates: template and typename. The second keyword can always be replaced by keyword class.
Templates are expanded at compiler time. This is like macros. The difference is, compiler does type checking before template expansion.

  • Function Templates
  • 
    #include <iostream>
    using namespace std; 
    
    // One function works for all data types. 
    template <typename T>
    T myMax(T x, T y) 
    { 
    	return (x > y)? x: y; 
    } 
    
    int main() 
    { 
    cout << myMax<int>(3, 7) << endl; // Call myMax for int 
    cout << myMax<double>(3.0, 7.0) << endl; // call myMax for double 
    cout << myMax<char>('g', 'e') << endl; // call myMax for char 
    
    return 0; 
    }
      
    Output:
    
    7
    7
    g
    
  • Class Templates
  • 
    #include <iostream> 
    using namespace std; 
      
    template <typename T> 
    class Array { 
    private: 
        T *ptr; 
        int size; 
    public: 
        Array(T arr[], int s); 
        void print(); 
    }; 
      
    template <typename T> 
    Array<T>::Array(T arr[], int s) { 
        ptr = new T[s]; 
        size = s; 
        for(int i = 0; i < size; i++) 
            ptr[i] = arr[i]; 
    } 
      
    template <typename T> 
    void Array<T>::print() { 
        for (int i = 0; i < size; i++) 
            cout<<" "<<*(ptr + i); 
        cout<<endl; 
    } 
      
    int main() { 
        int arr[5] = {1, 2, 3, 4, 5}; 
        Array<int> a(arr, 5); 
        a.print(); 
        return 0; 
    }   
      

Using Template Items

HOUR 24: Dealing with Exceptions and Error Handling


Bugs, Errors, Mistakes, and Code Rot

Handling the Unexpected

Programs only can respond when the exception occurs, using one of these approaches:
  • Crash the program.
  • Inform the user and exit gracefully.
  • Inform the user and allow the user to try to recover and continue.
  • Take corrective action and continue without alerting the user.

Exceptions

In C++, an exception is an object that is passed from the area of code where a problem occurs to the part of the code that is going to handle the problem.
  • When an exception occurs it is said to be thrown.
  • When an exception is handled, it is said to be caught.

How Exceptions are Used

  • A try block is created to surround areas of code that might have a problem and throw an exception.
  • The keyword throw is used to throw an exception.
  • A catch block is the block immediately following a try block in which exceptions are handled.
For example:

#include <iostream> 
using namespace std; 
  
int main() 
{ 
   int x = -1; 
  
   // Some code 
   cout << "Before try \n"; 
   try { 
      cout << "Inside try \n"; 
      if (x < 0) 
      { 
         throw x; 
         cout << "After throw (Never executed) \n"; 
      } 
   } 
   catch (int x ) { 
      cout << "Exception Caught \n"; 
   } 
  
   cout << "After catch (Will be executed) \n"; 
   return 0; 
}
Output:

Before try
Inside try
Exception Caught
After catch (Will be executed)
When an exception is thrown (or raised), control transfers to the catch block immediately following the current try block.

Using try and catch Blocks

When trying to determine try block locations, look for where you allocate memory or use resources. Other things to look for are out-of-bounds errors and illegal input.

Catching Exceptions

When an exception is thrown, the call stack is examined.
The exception is passed up the call stack to each enclosing block. As the stack is unwound, the destructors for local objects on the stack are invoked and the objects are destroyed.
After the exception is handled, the program continues after the try block of the catch statement that handled the exception.
If the exception reaches all the way to the beginning of the program ( the main() method) and still is not caught, the function terminate() is called, which in turn calls abort() to abort the program.

More Than One Catch

Catch statements can be lined up one after another.
The catch statement takes a parameter which is a type/class variable which the try block may throw.
If you do not know the throw type used in the try block, you can use the "three dots" syntax (...) inside the catch block, which will handle any type of exception.

    try  { 
       throw 123; 
    } 
    catch (int x)  { 
        cout << "Caught " << x; 
    } 
    catch (...)  { 
        cout << "Default Exception\n"; 
    } 

Catching by Reference and Polymorphism

By passing the exception by reference, you can use the inheritance hierarchy to take the appropriate action based on the runtime type of the exception.

Writing Professional-Quality Code( Coding Style )

Appendixes

留言

熱門文章