Create a class of objects

Digression: get the current local date and time in C++

We will need to know how to do this when we make our first C++ class, class date.

  1. theTime.C, theTime.txt.
    (tm_sec might be a leap second.)
    Unofficially, the return value of the time function is the number of seconds that have elapsed since the ever-memorable night they invented Unix (the original version of Linux), midnight on January 1, 1970. That’s about 55 and a half years ago.
    bc -l         (minus lowercase L for the math library of the "binary calculator")
    1749005228 / (60 * 60 * 24 * 365.25)
    55.42263125205972570791
    control-d
    
    The function localtime fills up a structure with information, and returns a pointer to this structure. (tm_sec might be a leap second.)

Create a class of objects. Evolve it gradually.

Pass some information (the ints year, month, day) down to the functions (print, next, etc.) that do all the work.

  1. 3ints.C, 3ints.txt.
    Hold the information about a date in three ints.
  2. struct.C, struct.txt.
    Hold the information about a date in one struct.
  3. Hold the information about a date in one object.
    1. obj1.C, obj1.txt.
      An object with data members and (non-static) member functions
    2. obj2.C, obj2.txt.
      1. Add two constructors, taking different argument lists. We’ve already seen that a class can have two member functions with the same name, providing that they have different lists of arguments (the three-argument date::next and the four-argument (date::next). The constructor whose argument is a const date& is called the copy constructor.
      2. Let the data members be private members of the class. The name of a private member can be mentioned only within the member functions of the class.
      3. Let the array be a static data member of the class. No matter how many objects we create, or even if we never create any objects at all, there is always exactly one copy of a static data member.
    3. Exercise.
      Now that we have a constructor that throws exceptions, what would happen if the main function tried to create a
      	date d {2, 30, 2025};   //Bad date: there is no February 30
      
      Is the exception caught in the main function of obj2.C? See new3.C for a previous example of catching an exception.
    4. Exercise.
      Remove the declaration and definition of the copy constructor from the class date in obj2.C. Even if we don’t write this constructor, the computer will still behave as if we had.
    5. Exercise.
      Write another constructor for class date. This constructor will have no explicit arguments (i.e., arguments you can see), and will initialize the newborn date object to today’s date. A constructor with no expicit arguments is called a default constructor. The default constructor for class date will have to call the functions time and localtime, so obj2.C will have to include the header file ctime. Test the default constructor by changing the declaration for today in the main function of obj2.C to
      	const date today;   //Call the default constructor.
      
    6. Exercise.
      Make another pair of non-const member functions for the class date in obj2.C named prev and prev. They will be just like the existing member functions next and next, except that they will move the date object into the past instead of the future. Test the new member functions by calling them in main.
    7. Initialization vs. assignment, first with a plain old int:
      	//Bad.
      
      	int i;      //Initialize i to unpredictable garbage.
      	i = 10;     //Assign a value to i, replacing the garbage.
      
      	//Better.
      
      	int i {10}; //Initialize i to 10.
      
      Now with data members in a constructor:
      //Bad.
      
      //This constructor for class date initializes the three data members of the
      //newborn object to unpredictable garbage.  Then it assigns values to the
      //data members, replacing the garbage.
      //Error checking omitted for brevity.
      
      date::date(int init_month, int init_day, int init_year)
      {
      	year = init_year;
      	month = init_month;
      	day = init_day;
      }
      
      //Better.
      
      //This constructor for class date initializes the three data members of the
      //newborn object to the values supplied by the user, init_year, init_month,
      //init_day.
      //Error checking omitted for brevity.
      
      date::date(int init_month, int init_day, int init_year)
      	: year {init_year}, month {init_month}, day {init_day}
      {
      	//These curly braces are empty.
      	//Other than initializing the three data members, this constructor has
      	//no work to do (because we have omitted error checking).
      }
      
      obj3.C, obj3.txt.
      The three-argument constructor for class date initializes the data members of the newborn date object, instead of filling them with unpredictable garbage and then assigning to them.
    8. Create separate header and implementation files, but put them in the same directory. Use mkdir to make the directory. (This new directory will have to be a sub-directory, or a sub-sub-directory, or a sub-sub-sub-directory, etc., of your home directory on storm.cis.fordham.edu.)
      • #include with <angle brackets> looks in the directory /usr/include/c++/15 for the header file (on our machine storm.cis.fordham.edu).
      • #include with "double quotes" looks in your current directory for the header file.
      This program consists of the following three files:
      1. date.h is the header file for class date. It contains the blueprint for the class. See the explanation for the # lines at the start and end of this file.
      2. date.C is the implementation file for class date. It contains the definitions for the static data members, member functions, and friend functions of the class. It includes date.h.
      3. main.C, main.txt contains the rest of the C++ program that uses objects of class date. It includes date.h.
      Compile and run this three-file program with
      c++ main.C date.C
      ls -l a.out
      ./a.out
      
    9. Here’s another three-file C++ program, consisting of
      1. date.h, the same header file as above
      2. date.C, the same implementation file as above
      3. main2.C, main2.txt, a different main file
      This program has an array of objects. Compile this program with
      c++ main2.C date.C
      ls -l a.out
      ./a.out
      

Re-implement class date with fewer data members

The data members are private, so no one other than the member functions of class date needs to know that the data members have changed. Do not change the three arguments of the constructor, or the output of the print member function, even though the class no longer has three data members.

  1. Re-implement class date with two data members, year and day.
    1. date.h
    2. date.C
    3. main.C, main.txt
  2. Re-implement class date with one data member, day.
    1. date.h
    2. date.C
    3. main.C, main.txt
  3. Two ways of giving a distance function to the one-data-member class date.
    1. date.h: implement distance as member function vs. friend function.
    2. date.C
    3. main.C main.txt: exactly the same output for member function vs. friend function.

Class point

Like a date object, we can think of a point object primarily as a structure that holds data members.

  1. Compile and run this three-file C++ program with
    c++ main.C point.C
    ls -l a.out
    ./a.out
    
    1. point.h
    2. point.C
    3. main.C, main.txt
  2. Exercise.
    Give class point a static data member named origin of type const point. (See date.h and date.C for the declaration and definition of a static data member.) Then change the body of the r member function to
    	//return sqrt(x * x + y * y);
    	return distance(*this, origin); //distance from this point to the origin
    

Class announcer, a class with a constructor and a destructor

  1. Unlike classes date and point, an object of class announcer holds very little data: just a name for itself. The purpose of an announcer object is not to hold data. Its purpose is to output a birth announcement and a death announcement at the start and end of its life.

    New features:

    1. Class announcer has a static member function named howMany, that receives no invisible pointer. A static member function is similar to a “friend function”; we’ll have to talk about their difference later.
    2. I hade to write a copy constructor for class announcer because the copy constructor I’d get by default would do no more than initialize the non-static data member:
      announcer::announcer(const announcer& another)   //"copy constructor"
      	: name {another.name}
      {
      }
      
    Compile this three-file program with
    c++ main.C announcer.C
    ls -l a.out
    ./a.out
    
    1. announcer.h
    2. announcer.C
    3. main.C, main.txt

    Exercise.
    Have the constructors and destructor for class announcer indent their output by the current value (or twice the current value) of announcer::count spaces. In the above program, for example, Ann’s birth and death announcements would be indented 7 spaces. See main2.txt.

Events often happen in pairs

  1. When a program runs, events often come in pairs:
    1. open and close a file
    2. lock and unlock a record in a database
    3. allocate and deallocate a resource
    4. new and delete[] a block of memory in C++ (a typical resource)
    5. Make something appear and disappear on the screen
    6. compress and decompress a file
    7. encrypt and decrypt a file
    8. connect and disconnect a network connection
    You can think of many more examples!

    To make these events happen in pairs in C++, we trigger constructors and destructors.
    Warning: error checking omitted for simplicity.
    1. constructor.c. An old fashioned C program with a pair of explicit function calls, to fopen and fclose. It would be bad if you forgot one of these calls, or called them in the wrong order, or called one of them twice.
      Compile the program with cc instead of c++.
      cc constructor.c      (Should create a file named a.out)
      ls -l a.out
      -rwxr-xr-x. 1 jsmith students 25048 Jun  1 09:18 a.out
      
      ./a.out               (Should create a file named outfile.txt)
      echo $?               (See the exit status producted by a.out)
      0
      
      ls -l outfile.txt
      -rw-r--r--. 1 jsmith students 16 Jun  1 09:26 outfile.txt
      
      cat outfile.txt       (See what's in the file outfile.txt)
      Hello.
      Goodbye.
      
      rm outfile.txt
      
    2. constructor.C. A C++ program that constructs and destructs an object, instead of making a pair of explicit function calls. This program outputs the same outfile.txt file as the above program.

Events often happen in nested pairs

  1. A series of jobs often needs to be undone in the opposite order. For example,
    1. Create a window.
      Create icons in the window.
      Do work.
      Destroy the icons.
      Destroy the window.
    2. Create a directory (or folder).
      Create files in the directory.
      Do work.
      Destroy the files.
      Destroy the directory.

      To undo the jobs in the opposite order, exploit the order in which C++ objects are automatically destructed:
      #include <iostream>
      #include <cstdlib>
      using namespace std;
      
      int main()
      {
      	window w;      //Construct window w.
      	icon i0 {&w};  //Construct icon i0 and put it in the window.
      	icon i1 {&w};  //Construct icon i1 and put it in the window.
      	icon i2 {&w};  //Construct icon i2 and put it in the window.
      
      	doWork();
      
      	return EXIT_SUCCESS; //Destruct i2, i1, i0, w in that order.
      }
      
    3. Generate a file in HTML format, with the pairs of tags nested. This program outputs six pairs of tags by constructing and destructing six objects of class element.
      1. element.h
      2. element.C
      3. main.C, main.txt
    4. The last to go will see the first three go before her.
  2. Construct each object in its own block of dynamically allocated memory to escape from the tyranny of the {curly braces}. We saw a dynamically allocated block that holds an array here.
    1. announcer.h
    2. announcer.C
    3. main.C, main.txt
    c++ main.C announcer.C
    ls -l a.out
    ./a.out
    

Nested objects

  1. Below is a blueprint for a big object (an interval) containing two smaller objects (dates) as its data members. The constructor for class interval begins by automatically making two detours to the constructor for class date. After these detours, the {body} of the constructor for class interval is executed.

    Similarly, the destructor for class interval automatically ends by calling the destructors for the two date objects inside of the interval object that is dying.

    class date {
    	int year;
    	int month;
    	int day;
    public:
    	date(int m, int d, int y);
    };
    
    class interval {
    	date begin;
    	date end;
    public:
    	interval(int m1, int d1, int y1, int m2, int d2, int y2);  //6 arguments
    };
    
    //Definition of the constructor for class interval.
    
    interval::interval(int m1, int d1, int y1, int m2, int d2, int y2)
    	: begin {m1, d1, y1}, end {m2, d2, y2}
    {
    	//the body of the constructor for class interval
    }
    
    //Definition of the constructor for class date.
    
    date::date(int m, int d, int y)
    	: year {y}, month {m}, day {d}
    {
    	//the body of the constructor for class date
    }
    

Two more example of classes:
class stack

  1. This stack object is hardwired to hold a stack of ints. A stack is what an accountant would call a LIFO list: “last in, first out”.
    1. stack.h
    2. stack.C
    3. main.C, main.txt
    Compile and run this three-file program (consisting of stack.h, stack.C, and main.C) with
    c++ main.C stack.C
    ls -l a.out
    ./a.out
    
  2. stack.C, stack.txt.
    Don’t build your own class stack. The C++ Standard Library already has a template class stack, like the template class vector we saw here. Write the name of the data type of your choice in the <angle brackets>.

A digression on signed vs. unsigned integers

On our machine storm.cis.fordham.edu, an int occupies 4 bytes = 32 bits. Therefore an int can hold any one of 232 = 4,294,967,296 different values. We let these values represent integers in the range from –2,147,483,648 to 2,147,483,647 inclusive.

The leftmost bit of an int is called the sign bit. It is 1 for negative numbers, 0 for non-negative numbers.

bits int
01111111 11111111 11111111 11111111 2,147,483,647
01111111 11111111 11111111 11111110 2,147,483,646
01111111 11111111 11111111 11111101 2,147,483,645
00000000 00000000 00000000 00000010 2
00000000 00000000 00000000 00000001 1
00000000 00000000 00000000 00000000 0
11111111 11111111 11111111 11111111 –1
11111111 11111111 11111111 11111110 –2
11111111 11111111 11111111 11111101 –3
10000000 00000000 00000000 00000010 –2,147,483,646
10000000 00000000 00000000 00000001 –2,147,483,647
10000000 00000000 00000000 00000000 –2,147,483,648

On our machine storm.cis.fordham.edu, an unsigned int occupies 4 bytes = 32 bits. Therefore an unsigned int can hold any one of 232 = 4,294,967,296 different values. We let these values represent integers in the range from 0 to 4,294,967,295 inclusive.

bits unsigned int
11111111 11111111 11111111 11111111 4,294,967,295
11111111 11111111 11111111 11111110 4,294,967,294
11111111 11111111 11111111 11111101 4,294,967,293
00000000 00000000 00000000 00000010 2
00000000 00000000 00000000 00000001 1
00000000 00000000 00000000 00000000 0

Class myrandom

The member function myrandom::rand in the file myrandom.C scrambles the value of the data member myrandom::next with a multiplication and an addition. For example, the first time we call myrandom::random, it changes the value of next from 1 to 1,103,527,590. Mathematicians have determined that the most random part of the resulting value consists of the 15 bits in positions 16 through 30 inclusive. Here is 1,103,527,590 written in binary with these bits in yellow:

  01000001110001100111111010100110   (this is 1,103,527,590)
  00000000000000000100000111000110   (this is 1,103,527,590 shifted 16 places to the right)
& 00000000000000000111111111111111   (this mask is 0x7FFF)
  00000000000000000100000111000110   (this is 16,838)

The result of the “bitwise and” is
100000111000110
which is 16,838 in decimal.

  1. A myrandom object does only one thing for us. In other words, it has only one member function, not counting the constructor.
    1. myrandom.h
    2. myrandom.C
    3. main.C, main.txt

Students’ homework

weatherreport: an example of a class. Etc.