CS105: Introduction to Computer Programming: C++

References, arrays and const

The default mode of passing variables to functions in C++ is by value.

class Date {
public:
  int day;
  int month;
  int year;
};

void printAs1999( Date d ) {
  d.year = 1999;
  cout << d.month << "/" << d.day << "/" << d.year << endl;
}

int main() {
  Date originalDate;
  originalDate.month = 7;
  originalDate.day = 20;
  originalDate.year = 2010;

  printAs1999( originalDate );

  cout << "originalDate.year is " << originalDate.year << endl;
}

In this example, we create an instance of the Date class called originalDate and initialize originalDate.year to be 2010. When we call the printAs1999 function, however, a copy of originalDate is made and called d. The function modifies d, but since it's just a copy, the originalDate variable remains unchanged.

The actual work of copying the Date class is done by the copy constructor of the Date class. If we don't define a copy constructor for a class, the compiler defines one for us that does a naive copy of the data in the class. We could define a custom copy constructor for the Date class like this:

class Date {
public:
...
  Date( const Date& d );
...
};

Date::Date( const Date& d ) {
  month = d.month;
  day = d.day;
  year = d.year;
}

References

There are often situations where we don't want to make a copy of a variable when we pass it to a function (i.e. when we want any changes made to the variable in the function to stick, or when copying the variable would take a lot of memory or time). To avoid copying a variable, we can pass it by reference instead of by value, by simply include an ampersand (&) before the variable name in the function header:

void printAs1999( Date& d ) {   // the & means pass by reference
  d.year = 1999;
  cout << d.month << "/" << d.day << "/" << d.year << endl;
}

int main() {
  Date originalDate;
  originalDate.month = 7;
  originalDate.day = 20;
  originalDate.year = 2010;

  printAs1999( originalDate );

  cout << "originalDate.year is " << originalDate.year << endl;
}

In this example, after printAs1999 has been called, the value of originalDate.year has been changed to 1999.

Passing by reference is more efficient than passing by value, since you don't have to make a copy of the variable. It's a little more dangerous, too - you've increased the number of functions that could concievably corrupt your data!

In order to get the best of both worlds - the efficiency of pass-by-reference and the safety of pass-by-value, many programmers use a const reference:

void printAs1999( const Date& d ) {   // safe and efficient
  cout << d.month << "/" << d.day << "/" << 1999 << endl;
}

int main() {
  Date originalDate;
  originalDate.month = 7;
  originalDate.day = 20;
  originalDate.year = 2010;

  printAs1999( originalDate );

  cout << "originalDate.year is " << originalDate.year << endl;
}

The const keyword tells the compiler that the associated variable should not be altered. const can also be used to describe member functions of a class that don't alter any data of that class. The following definition of a Date class uses const everywhere that it would be appropriate, a strategy known as const correctness:

#ifndef DATE_H
#define DATE_H

#include <string>
using std::string;

/**
 * A nifty class for holding a date in time.
 */
class Date {
public:

  /**
   * Constructors
   */
  Date();
  Date( const string& s );
  Date( int m, int d, int y );
  Date( const Date& d );   // copy constructor
  ~Date();

  /**
   * Display the date to standard out
   */
  void display() const;    // displaying doesn't modify the date, so it's const

  /**
   * Getters/setters
   */ 
  void setDay( int d );
  void setMonth( int m );
  void setYear( int y );
  int getDay() const;      // these getters are const, because they don't change anything
  int getMonth() const;
  int getYear() const;

private:
  int day;
  int month;
  int year;
};

#endif
#include "Date.h"

#include <iostream>
using std::cout;
using std::endl;


Date::Date() {
  // initialize data
}


Date::Date( const string& s ) {
  // convert s to m/d/y
}


Date::Date( int m, int d, int y ) {
  day = d;
  month = m;
  year = y;
}  


Date::Date( const Date& d ) {
  day = d.getDay();
  month = d.getMonth();
  year = d.getYear();

  cout << "Calling copy constructor" << endl;
}


Date::~Date() {
  cout << "Destructing Date" << endl;
}


void Date::display() const {
  cout << getMonth() << "/" << getDay() << "/" << getYear();
}  


/**
 * Getters/setters
 */ 
void Date::setDay( int d ) { 
  if( d < 0 || d > 31 ) {
    cout << "Error! So you had a bad day: " << d << endl;
    day = 1;
  } else {
    day = d;
  } 
}
void Date::setMonth( int m ) { 
  month = m; 
}
void Date::setYear( int y ) { 
  year = y; 
}
int Date::getDay() const { 
  return day; 
}
int Date::getMonth() const { 
  return month; 
}
int Date::getYear() const { 
  return year; 
}

Arrays

Arrays in C++ are fairly simple structures:

int int_array1[10];
int int_array2[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int int_array3[10] = {0, 1, 2, 3, 4};  // remaining elements set to 0

cout << int_array1[0];  // displays uninitialized value
cout << int_array2[0];  // displays 0

char char_array1[5];
char char_array2[] = "Hello";  // size is 5 chars + 1 null char
char char_array3[] = {'H', 'e', 'l', 'l', 'o', '\0'}; // same thing 

cout << char_array1[0]; // displays uninitialized value
cout << char_array2[0]; // displays H

In C, strings were represented as null-terminated char arrays. In C++, we have the string class, which is a bit more useful.