.. highlight:: c++ Chapter 10 Notes ================ Please read chapter 10.1 of the textbook. You do not need to read sections 10.2 and onwards (at least for this course --- classes will be a topic in later courses). Structures ---------- in C/C++, a ``struct`` is used to define a collection of (often different) values for example :: struct Person { string name; int age; }; // note the semi-colon: it's an error to leave it out! we use it to create variables of type ``Person`` :: Person p{"Bill", 61}; cout << p.name << "\n" // Bill << p.age << "\n"; // 61 p.name = "William"; p.age++; cout << p.name << "\n" // William << p.age << "\n"; // 62 we use the dot-notation to access the members of ``p`` Structures ---------- it's usually best to use the {}-notation to provide initial values to a structure as we have been doing however, the {}-notation is new in C++11, and doesn't work in C or older versions of C++ in that case you can do something like this :: Person p; // p.name and p.age have unknown values p.name = "Bill"; p.age = 61; cout << p.name << "\n" // Bill << p.age << "\n"; // 61 in C++11, it is almost always best to use the {}-notation to ensure your structures have sensible initial values unknown initial values can be the source of subtle errors! Passing a Structure to a Function --------------------------------- you can pass structures to functions :: void print1(Person p) { // p is passed by value; cout << p.name << "\n" // i.e. a copy of p is made << p.age << "\n"; } void print2(Person& p) { // p is passed by reference; cout << p.name << "\n" // i.e. no copy of p is made << p.age << "\n"; } void print3(const Person& p) { // p is passed by constant cout << p.name << "\n" // reference; i.e. no copy is made << p.age << "\n"; // and the compiler ensures p is not } // modified for ``print``, the best way to pass ``p`` is by constant reference here to avoid making a copy of the data in ``p`` this avoids making a copy and tells any other programmer that use ``print`` that they don't need to worry about it modifying the ``Person`` struct passed to it Examples of Structures: dates ----------------------------- times and dates are a surprisingly complex topic here lets look at a simple representation of a date as a struct of integers :: struct Date { int day; // starts at 1 int month; // starts at 1 int year; }; // don't forget the semi-colon! a ``Date`` struct is essentially a triple of 3 integers (8, 11, 2014) is November 8th, 2014 (16, 7, 1969) is July 16th, 1969 not all triples of numbers are valid dates (31, 4, 2011) is an invalid date: there is no April 31st (29, 2, 1900) is an invalid date: there was no February 29th in 1900 recognizing *all* valid dates is tricky! Examples of Structures: dates ----------------------------- the following programs shows some simple code for processing dates it includes an incomplete date validity checker (it doesn't handle leap years) it prints dates in only two different formats (there are many different date formats!) :: // dates.cpp #include "cmpt_error.h" #include #include using namespace std; struct Date { int day; int month; int year; }; // don't forget the semi-colon! const vector months = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; // Returns true if d holds a valid date, and false otherwise. // Numbering starts at 1 for both days and months. // Does *not* handle leap years! bool is_valid(const Date& d) { // first check that basic ranges are satisfied if (d.day < 1 || d.day > 31) return false; if (d.month < 1 || d.month > 12) return false; if (d.year < 0) return false; // different months have different numbers of days // 28 days: feb (leap years are not handled!) // 30 days: apr, jun, sep, nov // 31 days: all other months switch (d.month) { case 2: return d.day <= 28; case 4: case 6: case 9: case 11: return d.day <= 30; default: return true; } } string get_month(const Date& d) { if (!is_valid(d)) { cmpt::error("invalid date"); } return months[d.month - 1]; } string get_day_ordinal(const Date& d) { if (!is_valid(d)) { cmpt::error("invalid date"); } if (d.day == 11 || d.day == 12 || d.day == 13) { return "th"; } int last_digit = d.day % 10; switch (last_digit) { case 1: return "st"; case 2: return "nd"; case 3: return "rd"; default: return "th"; } } void print(const Date& d) { cout << get_month(d) << " " << d.day << get_day_ordinal(d) << ", " << d.year; } void println(const Date& d) { print(d); cout << "\n"; } void print_raw(const Date& d) { cout << "(" << d.day << ", " << d.month << ", " << d.year << ")"; } void println_raw(const Date& d) { print_raw(d); cout << "\n"; } int main() { for(int i = 1; i < 31; ++i) { Date d{i, 11, 2014}; print(d); cout << " "; println_raw(d); } } Structures within Structures ---------------------------- you can put structures within structures when it makes sense :: struct Point { double x; double y; }; // don't forget the semi-colon! struct Circle { Point center; double radius; }; // don't forget the semi-colon! struct Rectangle { Point corner; double width; double height; }; // don't forget the semi-colon! both circles and rectangles have constraints on the values they can take on so lets write some checking functions to test that they are valid :: bool valid_circle(const Circle& c) { return c.radius > 0.0; } bool valid_rectangle(const Rectangle& r) { return r.width > 0.0 && r.height > 0.0; } next, lets write functions to calculate perimeters and areas :: // Pre-condition: // c.radius > 0 // Post-condition: // returns the area of c double perimeter(const Circle& c) { if (!valid_circle(c)) cmpt::error("invalid circle"); return 2.0 * 3.1415926 * c.radius; } // Pre-condition: // c.radius > 0 // Post-condition: // returns the area of c double area(const Circle& c) { if (!valid_circle(c)) cmpt::error("invalid circle"); return 3.1415926 * c.radius * c.radius; } // Pre-condition: // c.radius > 0 // Post-condition: // returns the area of c double perimeter(const Rectangle& r) { if (!valid_rectangle(r)) cmpt::error("invalid rectangle"); return 2.0 * (r.width + r.height); } // Pre-condition: // c.radius > 0 // Post-condition: // returns the area of c double area(const Rectangle& r) { if (!valid_rectangle(r)) cmpt::error("invalid rectangle"); return r.width * r.height; } these functions have been written pretty carefully notice that the documentation (pre-condition and post-condition) and error- checking is longer than the function itself! the if-statement at the start of each function does decrease the performance of the functions so it's possible in some applications this might cause the functions to run too slowly so you could remove the error checks, and run the risk of not catching errors or, you could re-think how this code is designed and make it object-oriented we won't do any object-oriented programming in this course, but it is worth mentioning that, in this example, an object-oriented design could improve the performance and keep the safety Sorting with Structs -------------------- the standard C++ ``sort`` is efficient and flexible if you want to sort, say, a vector of structs, then you need to write a comparison function for example :: struct Date { int day; // 1 <= day <= 31 int month; // 1 <= month <= 12 int year; // 0 <= year }; // Pre-condition: // a and b are valid dates // Post-condition: // returns true if a comes before b // Note: // This is not necessarily the fastest way // to compare two dates. The intention is for the // code to be clear an explicit, so that it can // be seen to be correct more easily. bool less_than(const Date& a, const Date& b) { if (a.year < b.year) return true; if (a.year > b.year) return false; // at this point we know a.year == b.year if (a.month < b.month) return true; if (a.month > b.month) return false; // at this point we know a.month == b.month if (a.day < b.day) return true; if (a.day > b.day) return false; // at this point we know a.day == b.day return false; } int main() { vector dates = { Date{1, 2, 3}, Date{25, 12, 2014}, Date{25, 12, 2016}, Date{2, 9, 2014} }; sort(dates.begin(), dates.end(), less_than); for(Date d : dates) { println(d); } } the ``println`` function used here is defined above if you want the dates sorted from most recent to earliest, use ``reverse`` :: sort(dates.begin(), dates.end(), less_than); reverse(dates.begin(), dates.end()); Structs and Pointers -------------------- :: struct Assignment { string name; // e.g "Assignment 1" string short_name; // e.g. "a1" int out_of; // > 0 }; we could use ``Assignment`` with pointers like this :: Assignment* a1 = new Assignment; // a1 points to an Assignment (*a1).name = "Assignment 1"; (*a1).short_name = "a1"; (*a1).out_of = 10; cout << (*a1).name << "\n" << (*a1).short_name << "\n" << (*a1).out_of << "\n"; delete a1; here we create an ``Assignment`` object dynamically ``a1`` has the type "pointer to ``Assignment``" ``*a1`` has the type ``Assignment`` so to access the members of ``*a``, we use dot-notation, e.g. ``(*a).name`` the expression ``*a1.name`` is incorrect because in C++ the ``.`` operator has higher precedence than the ``*`` operator, and so C++ evaluates ``a1.name`` first but since ``a1`` is a pointer, it does not have a member called ``name`` so ``*a1.name`` causes an error, and we must use brackets to force ``*a1`` to be evaluated first Structs and Pointers -------------------- pointers to structs are very common in C/C++ programs the notation ``(*a1).name`` is awkward and hard to read so C/C++ provide a special ``->`` operator for this case ``a1->name`` is the same as ``(*a1).name`` :: Assignment* a1 = new Assignment; // a1 points to an Assignment a1->name = "Assignment 1"; a1->short_name = "a1"; a1->out_of = 10; cout << a1->name << "\n" << a1->short_name << "\n" << a1->out_of << "\n"; delete a1; once you get used to it, the ``->`` is quite readable