Pages

Wednesday, 16 May 2018

Chapter 9 // Drill 1, 2, 3, 4, 5 - Principles & Practice Using C++

n this exercise I am using Visual Studio Community 2017 and the header file "std_lib_facilities.h" which can be found here:

http://www.stroustrup.com/Programming/PPP2code/std_lib_facilities.h


EDIT 24/03/2021
Please note the code below is for historical purposes (it's quite wrong). Also, thanks to a comment below I noticed two issues in add_day and add_month. The changes can be found here:
I haven't changed the other drills. Also, as the drill says the check for a valid date can be very simple, I didn't check for invalid dates like certain months only having 30 days or 31. It basically acts as though every month is 31 days long.

EDIT 31/10/2019
My lord, what a mess these examples are. I have now updated these drills on my GitHub as I work my way through the book again. Even though I only did this a year ago, I can't believe how much my skills have improved (as well as my ability to use c style casts...I use them too much now). Seeing the whole bool last year, end year if not last year = ...eurgh.

Chapter 9 // Drill



This drill simply involves getting the sequence of versions of Date to work. For each version define a Date called today initialised to June 25, 1978. Then, define a Date called tomorrow and give is a value by copying today into it and increasing its day by one using add_day(). Finally output today and tomorrow using a << defined as in section 9.8.

Your check for a valid date may be very simple. Feel free to ignore leap years. However, don't accept a month that is not in the (1,12) range or day of the month that is not in the (1,31) range. Test each version with at least one invalid date (e.g., 2004, 13, -5).

Drill 1
The version from section 9.4.1:

#include "stdafx.h"
#include "std_lib_facilities.h"

//9.4.1 - simple Date
struct Date
{
 int y;  //year
 int m;  //month
 int d;  //day
};

Date today;
Date tomorrow;

//initialise the day
void init_day(Date& dd, int y, int m, int d)
{
 //check that y m d is a valid date
 if (y < 1900 || y > 2018)
  cout << "Error, invalid year." << endl;
 else if (m < 1 || m > 12)
  cout << "Error, invalid month." << endl;
 else if (d < 1 || d > 31)
  cout << "Error, invalid day." << endl;
 else
 {
  //if date is valid, initalise the date
  dd.y = y;
  dd.m = m;
  dd.d = d;
 }

 return;
}

//increment date by n days
void add_day(Date dd, Date& dd_copy, int n)
{
 bool lastDay = false;
 bool endYear = false;

 //if day go above 31, increase month, set day to 1
 //if month goes above 12, increase year, set month to 1
 for (int i = 0; i < n; ++i)
 {
  //wrap days
  if (dd.d == 31)
   lastDay = true;
  dd.d = (dd.d == 31) ? 1 : ++dd.d;

  if (lastDay)
  {
   //wrap month
   lastDay = false;
   dd.m = (dd.m == 12) ? 1 : ++dd.m;
   if (dd.m == 12)
    endYear = true;

   if (endYear)
   {
    //just increase year by one
    endYear = false;
    ++dd.y;
   }

  }
  
 }

 //assign tomorrow with copy of modified today
 dd_copy = dd;
}

//print to screen
ostream& operator<<(ostream& os, const Date& d)
{
 return os << d.d << ", " << d.m << ", " << d.y << endl;
}

int main()
{
 //initialise today with June 25 1978
 init_day(today, 1978, 6, 25);

 //assign by copying and increase by 1 day
 add_day(today, tomorrow, 1);

 //print out results
 cout << "Today: " << today << endl;
 cout << "Tomorrow: " << tomorrow << endl;

 keep_window_open();

 return 0;
}

The most challenging part of this was getting the days and months to wrap correctly. I know he only wanted us to increase by 1 day but I feel like he's going to ask us to do something similar at some point. The days only wrap at 31 though so it's not completely accurate however it does work as expected.

Drill 2

The version from section 9.4.2:


#include "stdafx.h"
#include "std_lib_facilities.h"

//9.4.2 - simple Date
//guarantee initialisation with constructor
//provide some notational convenience
struct Date
{
 int y, m, d;   //year, month, day
 Date(int y, int m, int d); //check for valid date and initialise
 void add_day(int n);  //increase the Date by N days
};

//intialise dates
Date today(1978, 6, 25);
Date tomorrow(today);

//Date Constructor - initialise the day
Date::Date(int y, int m, int d)
{
 //check that y m d is a valid date
 if (y < 1900 || y > 2018)
  cout << "Error, invalid year." << endl;
 else if (m < 1 || m > 12)
  cout << "Error, invalid month." << endl;
 else if (d < 1 || d > 31)
  cout << "Error, invalid day." << endl;
 else
 {
  //if date is valid, initalise the date
  Date::y = y;
  Date::m = m;
  Date::d = d;
 }

 return;
}

//increment date by n days
void Date::add_day(int n)
{
 bool lastDay = false;
 bool endYear = false;

 //if day goes above 31, increase month, set day to 1
 //if month goes above 12, increase year, set month to 1
 for (int i = 0; i < n; ++i)
 {
  //wrap days
  if (Date::d == 31)
   lastDay = true;
  Date::d = (Date::d == 31) ? 1 : ++Date::d;  //if day is equal to 31, make it 1, otherwise ++

  if (lastDay)
  {
   //wrap month
   lastDay = false;
   Date::m = (Date::m == 12) ? 1 : ++Date::m; //if month is equal to 12, make it 1, otherwise ++
   if (Date::m == 12)
    endYear = true;

   if (endYear)
   {
    //just increase year by one
    endYear = false;
    ++Date::y;
   }

  }
  
 }
}

//print to screen
ostream& operator<<(ostream& os, const Date& d)
{
 return os << d.d << ", " << d.m << ", " << d.y << endl;
}

int main()
{
 //increase day by one
 tomorrow.add_day(1);

 //print out results
 cout << "Today: " << today << endl;
 cout << "Tomorrow: " << tomorrow << endl;

 keep_window_open();

 return 0;
}
For this version, the two functions were changed a little to become part of the Date struct and used the global operator to assign straight to the variables in the struct instead of taking in variables. Tomorrow is also intialised with today and then increased in main() using the add_day() function.
Drill 3
The version from section 9.4.3:
#include "stdafx.h"
#include "std_lib_facilities.h"

#include "stdafx.h"
#include "std_lib_facilities.h"

//9.4.3 - simple Date (control access)
class Date
{
private:
 int y, m, d;
public:
 Date(int y, int m, int d);
 void add_day(int n);

 int month() { return m; }
 int day() { return d; }
 int year() { return y; }
};

//intialise dates
Date today(1978, 6, 25);
Date tomorrow(today);

//Date Constructor - initialise the day
Date::Date(int y, int m, int d)
{
 //check that y m d is a valid date
 if (y < 1900 || y > 2018)
  cout << "Error, invalid year." << endl;
 else if (m < 1 || m > 12)
  cout << "Error, invalid month." << endl;
 else if (d < 1 || d > 31)
  cout << "Error, invalid day." << endl;
 else
 {
  //if date is valid, initalise the date
  Date::y = y;
  Date::m = m;
  Date::d = d;
 }

 return;
}

//increment date by n days
void Date::add_day(int n)
{
 bool lastDay = false;
 bool endYear = false;

 //if day goes above 31, increase month, set day to 1
 //if month goes above 12, increase year, set month to 1
 for (int i = 0; i < n; ++i)
 {
  //wrap days
  if (Date::d == 31)
   lastDay = true;
  Date::d = (Date::d == 31) ? 1 : ++Date::d;  //if day is equal to 31, make it 1, otherwise ++

  if (lastDay)
  {
   //wrap month
   lastDay = false;
   Date::m = (Date::m == 12) ? 1 : ++Date::m; //if month is equal to 12, make it 1, otherwise ++
   if (Date::m == 12)
    endYear = true;

   if (endYear)
   {
    //just increase year by one
    endYear = false;
    ++Date::y;
   }

  }
  
 }
}

//print to screen
ostream& operator<<(ostream& os, Date& d)
{
 return os << d.day() << ", " << d.month() << ", " << d.year() << endl;
}

int main()
{
 //increase day by one
 tomorrow.add_day(1);

 //print out results
 cout << "Today: " << today << endl;
 cout << "Tomorrow: " << tomorrow << endl;

 keep_window_open();

 return 0;
}
Not much changed on this version, just the operator overload which could no longer access the variables within the class directly. It had to be changed to non-const though as it kept returning an error which I found strange considering I wasn't trying to change anything; just print it to the screen.

Drill 4
The version from section 9.7.1:
#include "stdafx.h"
#include "std_lib_facilities.h"

enum class Month
{
 jan = 1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
};

//forward declaration
Month returnMonth(int month);

//9.4.3 - simple Date (use Month type)
class Date
{
public:
 Date(int y, Month m, int d);
 void add_day(int n);
 int year() { return y; }
 Month month() { return m; }
 int day() { return d; }
private:
 int y;
 Month m;
 int d;
};

//intialise dates
Date today(1978, Month::jun, 25);
Date tomorrow(today);

//Date Constructor - initialise the day
Date::Date(int y, Month m, int d)
{
 //check that y m d is a valid date
 if (y < 1900 || y > 2018)
  cout << "Error, invalid year." << endl;
 else if (static_cast<int>(m) < 1 || static_cast<int>(m) > 12)
  cout << "Error, invalid month." << endl;
 else if (d < 1 || d > 31)
  cout << "Error, invalid day." << endl;
 else
 {
  //if date is valid, initalise the date
  Date::y = y;
  Date::m = m;
  Date::d = d;
 }

 return;
}

//increment date by n days
void Date::add_day(int n)
{
 bool lastDay = false;
 bool endYear = false;

 //if day goes above 31, increase month, set day to 1
 //if month goes above 12, increase year, set month to 1
 for (int i = 0; i < n; ++i)
 {
  //wrap days
  if (Date::d == 31)
   lastDay = true;
  Date::d = (Date::d == 31) ? 1 : ++Date::d;  //if day is equal to 31, make it 1, otherwise ++

  if (lastDay)
  {
   //wrap month
   lastDay = false;
   int mon = (static_cast<int>(Date::m) == 12) ? 1 : (static_cast<int>(Date::m) + 1); //if month is equal to 12, make it 1, otherwise ++
   Date::m = returnMonth(mon);
   if (static_cast<int>(Date::m) == 12)
    endYear = true;

   if (endYear)
   {
    //just increase year by one
    endYear = false;
    ++Date::y;
   }

  }
  
 }
}

//switch to return correct type of Month
Month returnMonth(int month)
{
 switch (month)
 {
 case 1:
  return Month::jan;
  break;
 case 2:
  return Month::feb;
  break;
 case 3:
  return Month::mar;
  break;
 case 4:
  return Month::apr;
  break;
 case 5:
  return Month::may;
  break;
 case 6:
  return Month::jun;
  break;
 case 7:
  return Month::jul;
  break;
 case 8:
  return Month::aug;
  break;
 case 9:
  return Month::sep;
  break;
 case 10:
  return Month::oct;
  break;
 case 11:
  return Month::nov;
  break;
 case 12:
  return Month::dec;
  break;
 default:
  cout << "Bad month" << endl;
 }
}

//print to screen
ostream& operator<<(ostream& os, Date& d)
{
 return os << d.day() << ", " << static_cast<int>(d.month()) << ", " << d.year() << endl;
}

int main()
{
 //increase day by one
 tomorrow.add_day(10);

 //print out results
 cout << "Today: " << today << endl;
 cout << "Tomorrow: " << tomorrow << endl;

 keep_window_open();

 return 0;
}
There is actually 2 versions of Date in this section and he doesn't specify which one to use so however the second one includes a second class inside of it called Invalid{} which he hasn't hinted at anywhere so I went with the first. I'm guessing it was an error check but why not just make it a function? The fact that it was a class denotes a few functions in there so I'm not going to guess what they may be.

Anyway, because of enums being special I had to create a quick function which could assign the correct month in the add_day() function as you can't assign an integer to an enum. Well, you can, but some very strange things happen and even then the enum doesn't know what that integer means. For example say we wanted to assign June to our month variable, you can't just "=6" as the enum doesn't know that 6 is June. So the function uses a switch statement to compare the enum against an integer and then return the correct value.


Drill 5
The version from section 9.7.4:
#include "stdafx.h"
#include "std_lib_facilities.h"

enum class Month
{
 jan = 1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
};

//forward declaration
Month returnMonth(int month);

//9.4.3 - simple Date (use Month type)
class Date
{
public:
 Date(int y, Month m, int d);
 int day() const { return d; };  //const member: can't modify
 Month month() const { return m; }
 int year() const { return y; }

 void add_day(int n);
 void add_month(int n);
 void add_year(int n);

 bool lastDay = false;
 bool endYear = false;
private:
 int y;
 Month m;
 int d;
};

//intialise dates
Date today(1978, Month::jun, 25);
Date tomorrow(today);

//Date Constructor - initialise the day
Date::Date(int y, Month m, int d)
{
 //check that y m d is a valid date
 if (y < 1900 || y > 2018)
  cout << "Error, invalid year." << endl;
 else if (static_cast<int>(m) < 1 || static_cast<int>(m) > 12)
  cout << "Error, invalid month." << endl;
 else if (d < 1 || d > 31)
  cout << "Error, invalid day." << endl;
 else
 {
  //if date is valid, initalise the date
  Date::y = y;
  Date::m = m;
  Date::d = d;
 }

 return;
}

//increment date by n days
void Date::add_day(int n)
{
 //if day goes above 31, increase month, set day to 1
 //if month goes above 12, increase year, set month to 1
 for (int i = 0; i < n; ++i)
 {
  //wrap days
  if (Date::d == 31)
   lastDay = true;
  Date::d = (Date::d == 31) ? 1 : ++Date::d;  //if day is equal to 31, make it 1, otherwise ++

  if (lastDay)
  {
   //wrap month
   lastDay = false;
   int mon = (static_cast<int>(Date::m) == 12) ? 1 : (static_cast<int>(Date::m) + 1); //if month is equal to 12, make it 1, otherwise ++
   Date::m = returnMonth(mon);
   if (static_cast<int>(Date::m) == 12)
    endYear = true;

   if (endYear)
   {
    //just increase year by one
    endYear = false;
    ++Date::y;
   }

  }
  
 }
}

void Date::add_month(int n)
{
 for (int i = 0; i < n; ++i)
 {
  //if month is equal to 12, make it 1, otherwise ++
  int mon = (static_cast<int>(Date::m) == 12) ? 1 : (static_cast<int>(Date::m) + 1);
  Date::m = returnMonth(mon);
  if (static_cast<int>(Date::m) == 12)
   endYear = true;

  if (endYear)
  {
   //just increase year by one
   endYear = false;
   ++Date::y;
  }
 }
}

void Date::add_year(int n)
{
 for (int i = 0; i < n; ++i)
  ++Date::y;
}

//switch to return correct type of Month
Month returnMonth(int month)
{
 switch (month)
 {
 case 1:
  return Month::jan;
  break;
 case 2:
  return Month::feb;
  break;
 case 3:
  return Month::mar;
  break;
 case 4:
  return Month::apr;
  break;
 case 5:
  return Month::may;
  break;
 case 6:
  return Month::jun;
  break;
 case 7:
  return Month::jul;
  break;
 case 8:
  return Month::aug;
  break;
 case 9:
  return Month::sep;
  break;
 case 10:
  return Month::oct;
  break;
 case 11:
  return Month::nov;
  break;
 case 12:
  return Month::dec;
  break;
 default:
  cout << "Bad month" << endl;
 }
}

//print to screen
ostream& operator<<(ostream& os, Date& d)
{
 return os << d.day() << ", " << static_cast<int>(d.month()) << ", " << d.year() << endl;
}

int main()
{
 //increase day by one
 tomorrow.add_day(1);
 tomorrow.add_month(2);
 tomorrow.add_year(5);

 //print out results
 cout << "Today: " << today << endl;
 cout << "Tomorrow: " << tomorrow << endl;

 keep_window_open();

 return 0;
}

For this one I just separated parts of add_day() into their own functions with a few tweaks.

2 comments:

  1. Hi, I think you add_month will have problem. For example if it is Jan 30, your add_month will lead to Feb 30? It is probably more complicated. I coded like below:

    // Increase date by n months (31 days per month)
    void Date::add_month(int n) {
    for (int i = 0; i(Date::m) + 1;
    Date::m = returnMonth(mon);
    Date::d += 1;
    }
    // If Mar, May, Aug, Oct and before day 31, Month +1
    else if ((Date::m==Month::mar || Date::m==Month::may || Date::m==Month::aug || Date::m==Month::oct) && Date::d<31) {
    int mon = static_cast(Date::m) + 1;
    Date::m = returnMonth(mon);
    }
    // If Mar, May, Aug, Oct and on day 31, Month +2, Day = 1
    else if ((Date::m==Month::mar || Date::m==Month::may || Date::m==Month::aug || Date::m==Month::oct) && Date::d==31) {
    int mon = static_cast(Date::m) + 2;
    Date::m = returnMonth(mon);
    Date::d = 1;
    }
    // If Jul, Month +1
    else if (Date::m==Month::jul) {
    int mon = static_cast(Date::m) + 1;
    Date::m = returnMonth(mon);
    }
    // If Feb, Month +1, Day +3
    else if (Date::m==Month::feb) {
    int mon = static_cast(Date::m) + 1;
    Date::m = returnMonth(mon);
    Date::d += 3;
    }
    // If Jan and before 29, Month +1
    else if (Date::m==Month::jan && Date::d<29) {
    int mon = static_cast(Date::m) + 1;
    Date::m = returnMonth(mon);
    }
    // If Jan and on and after 29, Month +2, Day - 28
    else {
    int mon = static_cast(Date::m) + 2;
    Date::m = returnMonth(mon);
    Date::d -= 28;
    }
    }
    }

    ReplyDelete
    Replies
    1. Ah there was an error in add_day and add_month! I've changed the code and it can be found at the git hub link in the post. Basically, I should've wrapped d+=n in brackets in add_day and moved nextM out of the if statement and added some brackets in add_month. As for the the days, yes, you can have February 30/31 in this code as the exercise says your check for a valid date can be very simple so I didn't bother checking for inaccurate days in february.

      Delete