Chapter preview

Chapter 4

Learning C++

C++ is a programming language that is especially well suited for computationally intensive programs and for interfacing with hardware or the operating system. In this chapter, we describe C++ starting with low-level features such as variable types, operators, pointers, arrays, I/O, and control flow, and concluding with object-oriented programming and the standard template library. We consider the latest version of C++ at the time of writing: C++17.

Note: The examples below are abridged; the book contains more details.

  1. Compilation
  2. Variables
  3. Scope
  4. Operators
  5. Type Conversions
  6. References
  7. Pointers
  8. Arrays
  9. Preprocessor and Namespaces - Part I
  10. Preprocessor and Namespaces - Part II
  11. Strings - Part I
  12. Strings - Part II
  13. Input - Part I
  14. Input - Part II
  15. Input - Part III
  16. Output
  17. If-Else Clauses
  18. While-Loops
  19. For-Loops - Part I
  20. For-Loops - Part II
  21. For-Loops - Part III
  22. Functions
  23. Return Value
  24. Function Parameters
  25. Function Definition and Function Declaration
  26. Scope of Function Variables
  27. Pointer Parameters
  28. Reference Parameters
  29. Recursion - Part I
  30. Recursion - Part II
  31. Recursion - Part III
  32. Recursion - Part IV
  33. Passing Arguments to Main
  34. Overloading Functions
  35. Structs - Part I
  36. Structs - Part II
  37. Structs - Part III
  38. Structs - Part IV
  39. Structs - Part V
  40. Classes
  41. Constructors - Part I
  42. Constructors - Part II
  43. The Destructor
  44. The Copy Constructor
  45. The Converting Constructor
  46. The Implicit Converting Constructor
  47. The Explicit Converting Constructor
  48. Operator Overloading
  49. Friend Functions and Classes
  50. Inheritance
  51. Polymorphism
  52. Static Binding
  53. Virtual Functions
  54. Static Variables and Functions - Part I
  55. Static Variables and Functions - Part II
  56. Dynamic Memory Allocation - Part I
  57. Dynamic Memory Allocation - Part II
  58. Dynamic Memory Allocation - Part III
  59. Smart Pointers - Part I
  60. Smart Pointers - Part II
  61. Smart Pointers - Part III
  62. Template Functions - Part I
  63. Template Functions - Part II
  64. Template Classes
  65. Vectors
  66. Arrays
  67. Sets
  68. Maps - Part I
  69. Maps - Part II
  70. Unordered Maps

Compilation

foo.cpp

// this is a comment.
/* this is another comment
   that spans multiple lines. */

int main() {
  return 0;  // return a value 0 to the operating system
}

compilation.sh

#!/bin/bash
cd `dirname "$0"`

# compile and link foo.cpp into an executable file named foo
g++ foo.cpp -o foo

# compile (but do not link) the C++ code in foo.cpp 
# into the object file foo.o
g++ -c foo.cpp -o foo.o
# link the object file foo.o and create an executable file foo
g++ foo.o -o foo

# compile multiple C++ files into a single executable, using the C++11 standard,
# optimized compilation, and linking to the standard mathematical library
g++ -o foo -lm -O3 -std=c++0x foo.cpp
# compile and link to create an executable file foo
g++ -o foo foo.cpp
# run the executable file foo (in the current directory)
./foo
# display value returned by the compiled program
echo $?

Variables

// Example 1
int age; // age is a variable of type int (integer)
// two double variables
double height;
double weight;

// Example 2
int age = 32; // integer variable holding 32
double height; // unassigned variable
float pi = 3.14; // new float variable
float pi_squared = pi * pi; // new float variable

height = (5 * 30.48) + (10 * 2.54); // assigs a value to height

// Example 3
int a = 2;
const int b = 3; // more readable
int const c = 5; // alternative form

a = 6;  // ok
b = 6;  // error (b cannot be modified)

// Example 4
double height = 58.0;
double weight = 155.2;
auto bmi = weight / (height * height); // inferred type (double)

Scope

// Example 1
{
  int a = 2;
  a = a + 1; // ok, a is recognized
  {
    int b = a; // ok, a is still in scope
  }
  a = b; // error, b is out of scope and is undefined
}
a = a + 1; // error, a is out of scope

// Example 2
int a = 2;

{
  int a = 3;  // inner a (a=3) masks the outer a (a=2)
  int b = a;  // b is assigned the value 3
}

// inner a is out of scope and outer a is no longer masked
int c = a; // c is assigned the value of the outer a (a=2) 
int d = b; // error: b is no longer in scope

// Example 3
int a = 42; // global variable

int main() {
  int a = 13; // local variable
  int b = a + 1; // 14
  int c = ::a + 1; // 43

  return 0;
}

Operators

bool result;

result = (3 == 3); // result equals true
result = (3 > 3); // result equals false

int a = 3;
int b;

b = ++a; // b and a both equal 4
result = ((4 > 3) && (4 < 5)); // result equals true
result = !(1 > 2); // result equals true
a = 10 % 3; // a equals 1 (remainder after dividing 10 by 3)
b = (a = 5); // assign 5 to a, and then assign that value to b

Type Conversions

// Example 1
int i;
double d = 58.3;

i = (int) d; // converts 58.3 to int

// Example 2
int a = 3.2;  // 3.2 is converted to the integer 3
int b;

b = 3.2;  // same casting as above
b = 3.0 / 2.0; // 1.5 is converted to the integer 1 

// Example 3
int a = 1;
int b = 2;
float f = 2.0f; // without the suffix f, 2.0 is a double

// Below, in the division a/f, the integer a is converted
// to a float resulting in a division of two floats: 
// 1.0f/2.0f (which equals 0.5)
float g = a / f;  // g equals 0.5

// Below, there is no type conversion for the division a/b.
// The division of an integer 1 by another integer 2 gives 
// the integer 0, which is then converted to a float 0.0
// that is assigned to h
float h = a / b;  // h equals 0.0

References

int a = 2;
int c = 3;
int &refA = a; // refA is a reference to the variable a
int b = refA; // has same effect as b = a

refA = 5; // has same effect as a = 5 

int &refC1 = c;
int &refC2 = c;  // both refC1 and refC2 refer to c

Pointers

// Example 1
int *pa;
int *pb;  // pa and pb are pointers to int variables
float *fx;
float *fy;  // fx and fy are pointers to float variables

// Example 2
int a = 2;
int *b; // uninitialized pointer - may contain unexpected address
int *c = &a; // address of variable a is assigned to pointer c
int d = *c; // value at address c is assigned to d (d = 2)
// define a new pointer e that points to the same memory as c
int *e = c; 
float f = 3.0f;
float *fp = &f;  // ok
int *a = &f;  // problem: a is of type int* but points to float

// Example 3
int a = 2;
int *b = &a; // pointer b points to a
int **c = &b; // double pointer c points to b
// c now holds the address of b, which holds the address of a
int *d = *c; // contents of b (address of a) is assigned to d
int e = *(*c); // contents of a (2) is assigned to e

Arrays

// Example 1
int a[10]; // define an array of 10 integers

a[0] = 1;  // assign 1 to the first element
a[3] = 2;  // assign 2 to the fourth element

int b = a[3]; // assign to b the contents of the fourth element

// Example 2
// using an array as a pointer
int a[10];  

*a = 3; // assign a value of 3 to the first element 
*(a + 2) = 4; // assign a value of 4 to the third element

// using a pointer as an array
int *pA = a + 1; // pA points to the second element of a

pA[1] = 5;  // assign a value of 5 to the third element of a

// Example 3
// define an array of size 10 containing the integers 0, 1,..., 9
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// array size may be omitted in this case
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// define an array of size 10 initialized to default
// values (0 for int)
int a[10] = {};
// define an array of size 10 initialized to 1, 0,..., 0
int a[10] = {1};

// Example 4
const int N = 3;
double a[N];  // ok
int m = 3;
double b[m];  // error

// Example 5
int a[10] = {};
a[13] = 3; // dangerous bug - possible erratic behavior

// Example 6
// a is 3 by 4 table of integers initialized to default values
// (0 in the case of int variables)
int a[3][4] = {};
a[0][0] = 2; // assign 2 to first row, first column element
a[0][1] = 3; // assign 3 to first row, second column element
a[1][2] = 4; // assign 4 to second row, third column element
// b is 3 by 4 by 5
int b[3][4][5] = {};
b[0][0][0] = 2; // first row, first column, first layer element
b[0][1][2] = 2; // first row, second column, third layer element

Preprocessor and Namespaces - Part I

// include the C++ input and output header file 
#include <iostream>

// the statement below prints 3 (cout is an output stream defined 
// in the header file iostream)
int main() {
  std::cout << 3;
  
  return 0;
}

Preprocessor and Namespaces - Part II

// Example 1
// include a header file written by the programmer
#include "my_header_file.h"

// call my_function, which is a function defined in
// the header file my_header_file.h
int main() {
  my_function();
  
  return 0;
}

// Example 2
#define WIDTH 80

int page_width = WIDTH;
int two_page_width = 2 * WIDTH;

// This code below is similar but harder to comprehend 
// and maintain
int three_page_width = 3 * 80;
int four_page_width = 4 * 80;

// Example 3
#include <iostream>
#define arraysize(array) (sizeof(array) / sizeof(*array))

int main() {
  int a[10];
  
  std::cout << arraysize(a); // prints the size of the array (10) 

  return 0;
}

// Example 4
#include "my_header_file1.h"
#include "my_header_file2.h"
// constants or functions in my_header_file3 are defined twice

// Example 5
#ifndef _MY_HEADER_3_GUARD
#define _MY_HEADER_3_GUARD

// copy original contents of my_header_file3.h here

#endif

// Example 6
// option 1: Prefix cout by the namespace name and ::
std::cout << 3;  // print 3 (cout is defined std namespace)

// option 2: Using namespace std allows dropping the std:: prefix
using namespace std;
cout << 3;

Strings - Part I

#include <string>
using namespace std;

int main() {
  string s1;  // define an empty string
  string s2 = "hello world"; // define a string with content
  int sz = s2.size(); // assign size of string s2 to variable
  char a = s2[1];  // access second character of s2 ('e') 
  string s3 = s2 + s2; // concatenate s2 with itself
  bool b = s2.empty();  // true if s2 is an empty string
  
  return 0;
}

Strings - Part II

#include <string>
using namespace std;

int main() {
  int a = 123;
  string s = to_string(a);  // assign "123" to s
  int i = stoi(s);  // assign integer 123 to i
  double d = stod(s); // assign floating point 123.0 to d
  
  return 0;
}

Input - Part I

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

int main() {
  string s1, s2;
  
  // read a string delimited by white space from standard
  // input into s1
  cin >> s1; 

  // read two strings separated by white spaces (first into s1
  // and then into s2)
  cin >> s1 >> s2;
  
  return 0;
}

Input - Part II

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

int main() {
  int c, d;
  
  // read buffered data from the standard input stream, 
  // convert the first two inputs into integers, and 
  // store them in c and d
  cin >> c >> d;
  
  return 0;
}

Input - Part III

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

int main() {
  string s;
  
  // read a line (possibly containing white spaces)
  // from standard input and assign it to s
  getline(cin, s);
  
  return 0;
}

Output

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

int main() {
  string s = "hello world";
  // display hello world followed by an end of line
  cout << s << endl; 
  // display hello world followed by ! and end of line
  cout << s << "!" << endl;
  
  return 0;
}

If-Else Clauses

// Example 1
int a;
int abs_a;

cin >> a; // read a from terminal
// replace a by its absolute value and store it in abs_a
if (a < 0) {
  abs_a = -a;  // if a is negative, use -a
} else {
  abs_a = a;  // if a is non-negative, use a
}

// Example 2
int a;
int abs_a;

cin >> a;
abs_a = a;
// negate abs_a if it is negative
if (abs_a < 0) {
  abs_a = -abs_a;
}

// Example 3
int a;

cin >> a;

int abs_a = a < 0 ? -a : a;

While-Loops

// Example 1
// Assigns 4! = 4 * 3 * 2 = 24 to the variable fac_val
// 3 loop iterations will be executed (on fourth iteration the
// condition 1 > 1 fails).
int val = 4;
int fac_val = 1;

while (val > 1) {
  fac_val = fac_val * val;
  val = val - 1;
}

// Example 2
// similar to previous example with break statement
int val = 4;
int fac_val = 1;

while (1) { 
  if (val <= 1) {
    break;
  }
  
  fac_val = fac_val * val;
  val = val - 1;
}

// Example 3
// computes = 4 * 2 (iteration that multiplies by 3 is skipped)
int val = 4;
int fac_val_mod = 1;

while (1) {
  if (val == 3) { 
    val = val - 1;
    continue;
  }
  
  if (val <= 1) {
    break;
  }
  
  fac_val_mod = fac_val_mod * val;
  val = val - 1;
}

For-Loops - Part I

#include <iostream>
using namespace std;

int main() {
  int an_array[10];
  int i;
  
  // initialize an_array to hold 0, 1, ..., 9
  // 10 iterations are executed, corresponding to
  // i = 0, 1, ..., 9 
  for (i = 0; i < 10; ++i) {
    an_array[i] = i;
  }
  
  // print the values of the array on a single line
  for (i = 0; i < 10; ++i) {
    cout << an_array[i] << ' ';
  }
  cout << endl;
  
  return 0;
}

For-Loops - Part II

#include <iostream>
using namespace std;

int main() {
  int an_array[10];
  int i = 0;
  
  for (auto &element : an_array) {
    element = i;
    ++i;
  }
  
  for (auto &element : an_array) {
    cout << element << ' ';
  }
  cout << endl;
  
  return 0;
}

For-Loops - Part III

#include <iostream>
using namespace std;

int main() {
  int i;
  int j;
  const int N = 10;
  int a_table[N][N];  // NxN 2D array
  
  // initialize table to hold the values 0, 1, ..., 99
  for (i = 0; i < N; ++i) {
    for (j = 0; j < N; ++j) {
      a_table[i][j] = i * N + j;
    }
  }
  
  // print the table
  for (i = 0; i < N; ++i) {
    for (j = 0; j < N; ++j) {
      cout << a_table[i][j] << ' ';
    }
    cout << endl;
  }
  
  return 0;
}

Functions

# include <iostream>
using namespace std;

// function print_two() prints 2 to standard output
void print_two() {
  cout << 2 << endl;
}

int main() {
  cout << 1 << endl;
  print_two();  // execute the function print_two
  cout << 3 << endl;
  
  return 0;
}

Return Value

# include <iostream>
using namespace std;

// function return_two returns an int variable that equals 2
int return_two() {
  return 2;
}

int main() {
  cout << 1 << endl;
  cout << return_two() << endl;  // print returned value
  cout << 3 << endl;
  
  return 0;
}

Function Parameters

#include <iostream>
using namespace std;

int add_two(int a, int b) {
  return a + b;
}

int main() {
  // print the value 3 returned by add_two(1, 2)
  cout << add_two(1, 2) << endl;
  
  return 0;
}

Function Definition and Function Declaration

// Example 1
int add_two(int x, int y) { // function definition
  return x + y;
}

int add_two(int x, int y);  // function declaration

// Example 2
#include "factorial.h"

int main() {
  cout << my_factorial(3) << endl;
  
  return 0;
}

Scope of Function Variables

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

// return a * b
int multiply_two(int a, int b) {
  int c = a * b;
  
  return c;
}

int main() {
  cout << multiply_two(2, 3) << endl; // prints 6
  
  int b = 4;
  int c = 5;
  
  cout << multiply_two(b, c) << endl; // prints 20
  cout << c << endl;  // print value of local c (5)
  
  return 0;
}

Pointer Parameters

#include <iostream>
using namespace std;

// The function below accepts an address and 
// an integer n and communicates back by writing
// the first n non-negative even integers in 
// the specified address
void write_seq(int *a, int n) {
  for (int i = 0; i < n; ++i) {
    a[i] = i * 2;
  }
}

// The function below accepts an address and 
// an integer n and negates the n integers
// that are stored in the specified address
void negate_seq(int *a, int n) {
  for (int i = 0; i < n; ++i) {
    a[i] = -a[i];
  }
}

int main() {
  int a[10] = {};
  
  write_seq(a, 10);  // assign even numbers to the array 
  negate_seq(a, 10);  // negate values of a
  
  for (const auto &element : a) {
    cout << element << " ";
  }
  cout << endl;
  
  return 0;
}

Reference Parameters

#include <iostream>
using namespace std;

// The function modifies variables in the calling environment by
// receiving parameters of type reference, and then modifying 
// the variables that are referred to.
void set(int &a, int &b, int &c) {
  a = 1;
  b = 2;
  c = 3;
}

int main() {
  int a = 0;
  int b = 0;
  int c = 0;
  
  // print initial values
  cout << a << ' ' << b << ' ' << c << endl;
  // modify the local a, b, c by passing references to them.
  set(a, b, c);
  // print values of a, b, c
  cout << a << ' ' << b << ' ' << c << endl;
  
  return 0;
}

Recursion - Part I

So far, we have seen examples of main calling other functions. It’s possible for any function to call another; for example:

#include<iostream>
using namespace std;

void bar() {
  cout << "bar" << endl;
}

void foo() {
  cout << "foo" << ' ';
  bar();
}

int main() {
  // enter foo and print "foo", then enter bar and print "bar"
  foo();
  
  return 0;
}

Recursion - Part II

In fact, it’s possible for a function to call itself:

// recurse() will keep calling itself, going into deeper
// and deeper recursive calls 
// (until the program crashes due to call stack overflow)

void recurse() {
  recurse();
}

int main() {
  recurse();
  
  return 0; // this statement will never be reached
}

Recursion - Part III

In the example below, the recursive function contains a stopping condition (n == 1), which stops the recursion when met:

#include <iostream>
using namespace std;

int factorial(int n) {
  if (n == 1) {
    return 1;
  }
  
  return n * factorial(n - 1);
}

int main() {
  cout << factorial(3) << endl; // prints 6
  
  return 0;
}

Recursion - Part IV

#include <iostream>
using namespace std;

int factorial(int n) {
  cout << "entering factorial(" << n << ')' << endl;
  
  if (n == 1) {
    cout << "leaving factorial(1) with return value 1" << endl;
    
    return 1;
  }
  
  int result = n * factorial(n - 1);
  cout << "leaving factorial(" << n <<
    ") with return value " << result << endl;
  
  return result;
}

int main() {
  cout << factorial(3) << endl;
  
  return 0;
}

Passing Arguments to Main

// running the program ./my_prog from the Linux terminal
// calls the appropriate main function with parameters a, 1, b, 2
// ./my_prog a 1 b 2

#include <iostream>
using namespace std;

int main(int argc, char **argv) {
  cout << "argc is: " << argc << endl;
  
  for (int i = 0; i < argc; ++i) {
    cout << "argv[" << i << "] is: " << argv[i] << endl;
  }
  
  return 0;
}

Overloading Functions

#include <iostream>
using namespace std;

void print_argument(int a) {
  cout << "One int argument passed: " << a << endl;
}

void print_argument(double a) {
  cout << "One double argument passed: " << a << endl;
}

void print_argument() {
  cout << "No argument passed" << endl;
}

int main() {
  print_argument(); // matches print_argument()
  print_argument(1); // matches print_argument(int)
  print_argument(1.2); // matches print_argument(double)
  
  return 0;
}

Structs - Part I

struct Point {
  double x;
  double y;
};

int main() {
    Point p1; // instantiation of object p1
    Point p2; // instantiation of object p2

    p1.x = 3;
    p1.y = 0;
}

Structs - Part II

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

struct Person {
    string name;
    double age;
};

void by_address_of_const(const Person *p) {
    Person x;

    p = &x;
    cout << "modified copy inside function: " << p << endl;
}

int main() {
  Person person;
  Person *pointer = &person;
  
  cout << "before function call: " << pointer << endl;
  by_address_of_const(pointer);
  cout << "after function call: " << pointer << endl;
  
  return 0;
}

Structs - Part III

#include <iostream>
using namespace std;

struct Point {
  double x;
  double y;
};

// pass by reference - the function is modifying p
void scale_point(Point &p, double scaling_factor) {
  p.x = p.x * scaling_factor; 
  p.y = p.y * scaling_factor; 
}

// pass by const reference - the function is not modifying p
void print_point(const string &prefix, const Point &p) {
  cout << prefix << '(' << p.x << ',' << p.y << ')' << endl; 
}

// pass by const reference - the function is not modifying p1, p2
Point subtract_points(const Point &p1, const Point &p2) {
  Point delta;
  
  delta.x = p1.x - p2.x;
  delta.y = p1.y - p2.y;
  
  return delta;
}

int main() {
  Point p1;
  Point p2;
  
  p1.x = 1; 
  p1.y = 2;
  p2.x = 3;
  p2.y = 3;
  
  print_point("first point: ", p1);
  print_point("second point: ", p2);
  print_point("delta: ", subtract_points(p1, p2));
  
  // scale second point by a factor of 2
  cout << endl << "scaling the 2nd point to 2x" << endl;
  scale_point(p2, 2);
  
  print_point("first point: ", p1);
  print_point("second point: ", p2);
  print_point("delta:", subtract_points(p1, p2));
  
  return 0;
}

Structs - Part IV

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

struct Point {
  double x;
  double y;
};

struct Vector {
  Point start;
  Point end;
};

// pass by const reference; the function doesn't modify p1 or p2
Point subtract_points(const Point &p1, const Point &p2) {
  Point delta;
  
  delta.x = p1.x - p2.x;
  delta.y = p1.y - p2.y;
  
  return delta;
}

double length(const Vector &v) {
  Point diff = subtract_points(v.start, v.end);
  
  return sqrt(diff.x * diff.x + diff.y * diff.y);
}

int main() {
  Vector v;
  
  v.start.x = 1;
  v.start.y = 0;
  v.end.x = 2;
  v.end.y = 1;
  
  cout << "length of the vector is: " << length(v) << endl;
}

Structs - Part V

#include <iostream>
using namespace std;

struct Point {
  double x;
  double y;
};

int main() {
  // create an array of two points
  Point points[2];
  
  // initialize the array to (0,1) and (2,3)
  points[0].x = 0;
  points[0].y = 1;
  points[1].x = 2;
  points[1].y = 3;
  
  // points is a pointer that refers to the start of the array
  // print points[0] using -> syntax
  cout << points->x << ' ' << points->y << endl; 
  
  // print points[1] using . syntax
  cout << points[1].x << ' ' << points[1].y << endl;
}

Classes

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  void set_x(double a_x) {
    this->x = a_x; // set the member x to the value a_x
  }
  
  void set_y(double a_y) {
   this->y = a_y;
  }
  
  // get value of x, const implies that the method does not 
  // modify the object
  double get_x() const {
    return x;
  }
  
  // get value of y, const implies that the method does not 
  // modify the object
  double get_y() const {
    return y;
  }
  
  // negate x and y (method implemented outside class body)
  void reflect();
};

void Point::reflect() {
  x = -x; // alternatively, this->x = - this->x
  y = -y; // alternatively, this->y = - this->y
}

int main() {
  Point p;
  
  p.set_x(1);
  p.set_y(2);
  cout << '(' << p.get_x() << ',' << p.get_y() << ')' << endl;
  
  p.reflect();
  cout << '(' << p.get_x() << ',' << p.get_y() << ')' << endl;
  
  return 0;
}

Constructors - Part I

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  // empty constructor, called by the statement: Point p;
  Point() {}
  
  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point (double a_x, double a_y) {
    this->x = a_x;
    this->y = a_y;
  }

  // get value of x, const implies that the method does not 
  // modify the object
  double get_x() const {
    return x;
  }
  
  // get value of y, const implies that the method does not 
  // modify the object
  double get_y() const {
    return y;
  }
};

int main() {
  Point p(2,-2);  // constructor with two argument is called
  
  cout << '(' << p.get_x() << ',' << p.get_y() << ')' << endl;
  
  return 0;
}

Constructors - Part II

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  // empty constructor, called by the statement: Point p;
  Point() {}
  
  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point(double a_x, double a_y) : x(a_x), y(a_y) {}

  // get value of x, const implies that the method does not 
  // modify the object
  double get_x() const {
    return x;
  }
  
  // get value of y, const implies that the method does not 
  // modify the object
  double get_y() const {
    return y;
  }
};

int main() {
  Point p(2,-2);  // constructor with two argument is called
  
  cout << '(' << p.get_x() << ',' << p.get_y() << ')' << endl;
  
  return 0;
}

The Destructor

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  // empty constructor, called by the statement: Point p;
  Point() {}

  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point(double a_x, double a_y) : x(a_x), y(a_y) {}

  ~Point() {} // empty destructor

  // get value of x, const implies that the method does not 
  // modify the object
  double get_x() const {
    return x;
  }
  
  // get value of y, const implies that the method does not 
  // modify the object
  double get_y() const {
    return y;
  }
};

int main() {
  Point p(2,-2);  // constructor with two argument is called
  
  cout << '(' << p.get_x() << ',' << p.get_y() << ')' << endl;
  
  return 0;
}

The Copy Constructor

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point(double a_x, double a_y) : x(a_x), y(a_y) {}

  // copy constructor
  Point(const Point& p) {
    x = p.x;
    y = p.y;
  }
};

int main() {
  Point p1(1, 2); // new object using a constructor
  Point p2(p1);  // new object using the copy constructor
  
  return 0;
}

The Converting Constructor

#include <iostream>
using namespace std;

class Element {
private:
  int atomic_number;

public:
  Element(int an_atomic_number) :
    atomic_number(an_atomic_number) {}
  
  int get_atomic_number() const {
    return atomic_number;
  }
};

int main() {
  Element na(11);
  Element mg = 12; // alternative form
  
  cout << mg.get_atomic_number() << endl; // prints 12

  return 0;
}

The Implicit Converting Constructor

#include <iostream>
using namespace std;

class Element {
private:
  int atomic_number;

public:
  Element(int an_atomic_number) :
    atomic_number(an_atomic_number) {}
  
  int get_atomic_number() const {
    return atomic_number;
  }
};

// calling this function with an int parameter causes
// the converting constructor to be called
void print_element(Element x) {
  cout << x.get_atomic_number() << endl;
}

int main() {
  print_element(12); // prints 12
  
  return 0;
}

The Explicit Converting Constructor

#include <iostream>
using namespace std;

class Element {
private:
  int atomic_number;

public:
  // note the use of the explicit keyword below
  explicit Element(int an_atomic_number) :
    atomic_number(an_atomic_number) {}
  
  int get_atomic_number() const {
    return atomic_number;
  }
};

int main() {
  Element na(11);
  Element mg = 12; // error
  
  cout << na.get_atomic_number() << endl; // prints 11

  return 0;
}

Operator Overloading

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  Point() {}

  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point(double a_x, double a_y) : x(a_x), y(a_y) {}

  // get value of x, const implies that the method does not 
  // modify the object
  double get_x() const {
    return x;
  }
  
  // get value of y, const implies that the method does not 
  // modify the object
  double get_y() const {
    return y;
  }

  // overload addition operator (+)
  Point operator+(const Point &p) const {
    Point result;
    
    result.x = this->x + p.x;
    result.y = this->y + p.y;
    
    return result;
  }
};

ostream& operator<<(ostream& os, const Point &p) {
  os << '(' << p.get_x() << ',' << p.get_y() << ')';
  
  return os; // to support operator chaining
}

int main() {
  Point p1(1,2);
  Point p2(2,3);
  Point p3 = p1 + p2;  // calls the overloaded + operator
  
  // calls the overloaded << operator thrice for points
  // and thrice for spaces and end-of-line delimiters
  cout << p1 << ' ' << p2 << ' ' << p3 << endl;
  
  return 0;
}

Friend Functions and Classes

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point(double a_x, double a_y) : x(a_x), y(a_y) {}

  // mark the non-member function overloading << as a friend
  friend ostream& operator<<(ostream &os, const Point &p);
};

ostream& operator<<(ostream &os, const Point &p) {
  // access private fields since << is a friend of Point
  os << '(' << p.x << ',' << p.y << ')';
  
  return os;
}

int main() {
  Point p(1,2);

  cout << p << endl;
  
  return 0;
}

Inheritance

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  // empty constructor, called by the statement: Point p;
  Point() {}

  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point(double a_x, double a_y) : x(a_x), y(a_y) {}

  // get value of x, const implies that the method does not 
  // modify the object
  double get_x() const {
    return x;
  }
  
  // get value of y, const implies that the method does not 
  // modify the object
  double get_y() const {
    return y;
  }

  void set_x(double a_x) {
    this->x = a_x; // set the member x to the value a_x
  }
  
  void set_y(double a_y) {
   this->y = a_y;
  }
};

class NamedPoint : public Point {
private:
  string name;

public:
  // empty constructor calls point constructor
  NamedPoint() : Point() {}
  
  // constructor calls point constructor then initializes name
  NamedPoint(double x, double y, string name) :
    Point(x, y), name(name) {}
  
  string get_name() {
    return name;
  }
  
  void set_name(string a_name) {
    this->name = a_name;
  }
};

int main() {
  NamedPoint p(0, 0, "origin");
  
  p.set_name("FIRST POINT");
  p.set_x(2);  // calls a base class method
  cout << p.get_name() << ": " << '(' << p.get_x() << ',' <<
    p.get_y() << ')' << endl;

  
  return 0;
}

Polymorphism

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point(double a_x, double a_y) : x(a_x), y(a_y) {}

  void print() {  // Point::print method defined in base class
    cout << '(' << x << ',' << y << ')' << endl;
  }
};

class NamedPoint : public Point {
private:
  string name;

public:  
  // constructor calls point constructor then initializes name
  NamedPoint(double x, double y, string name) :
    Point(x, y), name(name) {}
  
  // NamedPoint::print method defined in derived class
  void print() {
    cout << name << ' ';
    Point::print();
  }
};

int main() {
  Point p(2,3);
  NamedPoint np(1, 2, "first point");
  
  p.print();  // Point::print is called
  np.print();  // NamedPoint::print is called
  
  return 0;
}

Static Binding

The following code demonstrates static binding, which is the default in C++:

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point(double a_x, double a_y) : x(a_x), y(a_y) {}

  void print() {  // Point::print method defined in base class
    cout << '(' << x << ',' << y << ')' << endl;
  }
};

class NamedPoint : public Point {
private:
  string name;

public:  
  // constructor calls point constructor then initializes name
  NamedPoint(double x, double y, string name) :
    Point(x, y), name(name) {}
  
  // NamedPoint::print method defined in derived class
  void print() {
    cout << name << ' ';
    Point::print();
  }
};

int main() {
  NamedPoint p(0, 0, "origin");
  Point &refP = p;
  Point *pp = &p;
  
  refP.print(); // static binding calls Point::print()
  pp->print(); // ditto
  
  return 0;
}

Virtual Functions

#include <iostream>
using namespace std;

class Point {
private:
  // fields are accessible only to methods belonging to point
  double x;
  double y;

public:
  // constructor accepting two arguments, called by a 
  // statement such as: Point p(1, 2);
  Point(double a_x, double a_y) : x(a_x), y(a_y) {}

  // Point::print method defined in base class
  // note the use of the virtual keyword below
  virtual void print() {  
    cout << '(' << x << ',' << y << ')' << endl;
  }
};

class NamedPoint : public Point {
private:
  string name;

public:  
  // constructor calls point constructor then initializes name
  NamedPoint(double x, double y, string name) :
    Point(x, y), name(name) {}
  
  // NamedPoint::print method defined in derived class
  // note the use of the virtual keyword below
  virtual void print() {  
    cout << name << ' ';
    Point::print();
  }
};

int main() {
  NamedPoint p(0, 0, "origin");
  Point &refP = p;
  Point *pp = &p;
  
  refP.print(); // dynamic binding calls NamedPoint::print()
  pp->print(); // ditto
  
  return 0;
}

Static Variables and Functions - Part I

#include <iostream>
using namespace std;

class Point {
private:
  double x;
  double y;
  static int num_objects;
  
public:
  Point() {
    // increments counter when a new object is created
    ++num_objects;
  }
  
  // even though this method does not modify any members,
  // static member functions cannot be qualified as const
  static int get_num_points() {
    return num_objects;
  }
};

// defines the static field num_objects and initializes it to 0
int Point::num_objects = 0; 

int main() {
  Point p1;
  Point p2;
  Point p3;
  
  cout << "number of point objects: " 
       << Point::get_num_points() << endl;
  
  return 0;
}

Static Variables and Functions - Part II

#include<iostream>
using namespace std;

int count_calls() {
  // initialization occurs only once when program is loaded
  static int counter = 0;
  
  return ++counter; // pre-increments then returns
}

int main() {
  count_calls();
  count_calls(); 
  cout << "number of calls: "<< count_calls() << endl;
  
  return 0;
}

Dynamic Memory Allocation - Part I

int main() {
  // pd points to a dynamically allocated double variable
  double *pd = new double;
  
  // free the allocated memory
  delete pd;

  return 0;
}

Dynamic Memory Allocation - Part II

int main() {
  Point *pp1 = new Point; // the default constructor
  Point *pp2 = new Point(1, 2); // an overloaded constructor

  delete pp1;
  delete pp2;
  
  return 0;
}

Dynamic Memory Allocation - Part III

int main() {
  int n = 2;
  // allocates memory for an array of n objects of type point
  Point *pp = new Point[n];

  delete[] pp; // frees up a dynamically-allocated array
  
  return 0;
}

Smart Pointers - Part I

#include<memory>
using namespace std;

int main() {
  // creates smart pointers to double variables,
  // each holds a value of 4.2
  shared_ptr<double> pd1 = make_shared<double>(4.2);  
  shared_ptr<double> pd2(new double(4.2)); // alternatively
  
  // pd2 is reassigned to a new memory location,
  // older memory content is no longer pointed to
  // and is therefore freed up automatically
  pd2 = make_shared<double>(8.4);

  // several shared_ptr objects may point to the same memory
  shared_ptr<double> pd3 = pd2;
  
  return 0;
}

Smart Pointers - Part II

#include <memory>
using namespace std;

unique_ptr<int> create_unique_pointer() {
  return unique_ptr<int>(new int(4));
}

int main() {
  unique_ptr<int> pi1(new int(3));
  // error: two unique_ptr pointers pointing to the same memory
  unique_ptr<int> pi2 = pi1; 
  // ok: new unique_ptr takes over as old unique_ptr is destroyed
  unique_ptr<int> pi3 = create_unique_pointer();
  
  return 0;
}

Smart Pointers - Part III

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

int main() {
  int n = 10;
  // smart pointer to an array of int variables
  unique_ptr<int[]> array(new int[n]);

  for (int i=0; i<n; ++i) {
    array[i] = i;
  }

  for (int i=0; i<n; ++i) {
    cout << array[i] << ' ';
  }
  cout << endl;

  return 0;
}

Template Functions - Part I

#include <iostream>
using namespace std;

class Element {
private:
  int atomic_number;

public:
  Element(int atomic_number) :
    atomic_number(atomic_number) {}
  
  int get_atomic_number() const {
    return atomic_number;
  }

  bool operator<(const Element &x) const {
    return this->atomic_number < x.atomic_number;
  }
};

// note the use of the template keyword below:
template <class T>
// note the use of const T& to avoid copying the return value
const T& get_min(const T &a, const T &b) {
  return a < b ? a : b;
}

int main() {
  // calls the template function, replacing T with int
  cout << get_min<int>(1, 2) << endl;
  // calls the template function, replacing T with double
  cout << get_min<double>(9.1, 2.5) << endl;
  // calls the template function with an inferred type (int)
  cout << get_min(13, 42) << endl;

  Element na = 11;
  Element mg = 12;

  // calls the template function, replacing T with Element
  // note that the replacement type, Element, is inferred
  cout << get_min(na, mg).get_atomic_number() << endl;
  
  return 0;
}

Template Functions - Part II

#include <iostream>
using namespace std;

class Element {
private:
  int atomic_number;

public:
  Element(int atomic_number) :
    atomic_number(atomic_number) {}
  
  int get_atomic_number() const {
    return atomic_number;
  }

  bool operator<(const Element &x) const {
    return this->atomic_number < x.atomic_number;
  }
};

template <class T, class S>
bool precedes(const T &a, const S &b) {
  return a < b;
}

int main() {
  // instructs cout to print true and false instead of 1 and 0
  cout.setf(ios::boolalpha);
  
  cout << precedes<int, double>(1, 42) << endl;
  cout << precedes(9.1, 3) << endl;
  cout << precedes(1.5f, 1.3) << endl;

  Element na = 11;
  Element mg = 12;

  cout << precedes(na, mg) << endl;
  
  return 0;
}

Template Classes

#include <iostream>
using namespace std;

template<class T>
class Point {
private:
  T x;
  T y;

public:
  void set_x(const T &x) {
    this->x = x;
  }

  void set_y(const T &y) {
    this->y = y;
  }

  T get_x() const {
    return x;
  }

  T get_y() const {
    return y;  
  }

  void reflect() {
    x = -x;
    y = -y; 
  }
};

int main() {
  Point<int> p;

  p.set_x(1);
  p.set_y(2);
  cout << '(' << p.get_x() << ',' << p.get_y() << ')' << endl;
  
  p.reflect();
  cout << '(' << p.get_x() << ',' << p.get_y() << ')' << endl;
  
  return 0;
}

Vectors

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

int main() {
  vector<int> v;

  for (int i = 0; i < 10; ++i) { // store 1..10
    v.push_back(i);
  }

  for (const auto &x : v) { // print all elements
    cout << x << ' ';
  }
  cout << endl;
  
  return 0;
}

Arrays

array<int, 10> array; // defines an array of 10 integers

// initializes values of all array elements
for (int i; i < array.size(); ++i) {
  array[i] = i;
}

Sets

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

int main() {
  set<string> s;

  s.insert({"one"});
  s.insert({"two"});
  s.insert({"three"});
  s.insert({"one"});

  for (const auto &x : s) {
    cout << x << ' ';
  }
  cout << endl;

  return 0;
}

Maps - Part I

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

int main() {
  multimap<string,int> names;

  // note the use of curly braces to create a pair
  names.insert({"John", 3});
  names.insert({"Peter", 2});
  names.insert({"Jane", 6});
  names.insert({"Jane", 2});

  for (const auto &x : names) {
    cout << x.first << ": " << x.second << endl;
  }
  
  return 0;
}

Maps - Part II

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

int main() {
  map<string,int> names;

  names["John"] = 3;
  names["Jane"] = 6;

  cout << "names[\"John\"]: " << names["John"] << endl;
  cout << "names[\"Jane\"]: " << names["Jane"] << endl;

  return 0;
}

Unordered Maps

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

int main() {
  unordered_multimap<string,int> names;

  names.insert({"John", 3});
  names.insert({"Peter", 2});
  names.insert({"Jane", 6});
  names.insert({"Jane", 2});

  for (const auto &x : names) {
    cout << x.first << ": " << x.second << endl;
  }

  return 0;
}