== Access Modifiers == * What are the access modifiers used in C++? - public - protected - private * When should we use any of them? * Example: can you implement 2D Points again? #ifndef POINT_2D #define POINT_2D class Point2D { public: Point2D(double x, double y): _x(x), _y(y) {} double getX() { return _x; } double getY() { return _y; } std::string to_string() const { return "2D(" + std::to_string(_x) + ", " + std::to_string(_y) + ")"; } private: double _x; double _y; }; * When should a property be public or private? * Why is it good to encapsulate some data? * Can you write a program to test the class Point2D above? #include #include "Point2D.h" int main() { Point2D p0(2.71, 3.14); std::cout << p0.to_string() << std::endl; } * Now, can you write a class Point3D, that extends Point2D? #ifndef POINT_3D #define POINT_3D class Point3D: public Point2D { public: Point3D(double x, double y, double z): Point2D(x, y), _z(z) {} std::string to_string() const { return "3D(" + std::to_string(_x) + ", " + std::to_string(_y) + ", " + std::to_string(_z) + ")"; } private: double _z; }; #endif * Why doesn't it compile? * How can you make it compile? #ifndef POINT_2D #define POINT_2D class Point2D { public: Point2D(double x, double y): _x(x), _y(y) {} double getX() { return _x; } double getY() { return _y; } std::string to_string() const { return "(" + std::to_string(_x) + ", " + std::to_string(_y) + ")"; } protected: double _x; double _y; }; #endif * Can you write code to test this class? #include #include "Point2D.h" #include "Point3D.h" int main() { Point2D *p0 = new Point2D(2.71, 3.14); Point3D *p1 = new Point3D(2.71, 3.14, 1.68); std::cout << p0->to_string() << std::endl; std::cout << p1->to_string() << std::endl; } * What is the program below going to print? #include #include "Point2D.h" #include "Point3D.h" int main() { Point2D *p1 = new Point3D(2.71, 3.14, 1.68); std::cout << p1->to_string() << std::endl; } * How can you modify the program, so that it prints the 3D point, instead of the 2D point? #ifndef POINT_2D #define POINT_2D class Point2D { public: Point2D(double x, double y): _x(x), _y(y) {} double getX() { return _x; } double getY() { return _y; } // // The virtual keyword makes all the difference! virtual std::string to_string() const { return "(" + std::to_string(_x) + ", " + std::to_string(_y) + ")"; } protected: double _x; double _y; }; #endif * Write a program that reads a text file containing points per line. Each line contains either two doubles, or three. Lines with two doubles should be converted into Point2D's, and lines containing three doubles should be converted into Point3D's. To test your program, you can add all the points to a vector, and then print the vector. * How can we model the flow of this problem? int main(int argc, char** argv) { // open the file // read the file // print the results } * How can we open the file and check that the file is indeed open? #include int main(int argc, char** argv) { // open the file if (argc != 2) { std::cerr << "Syntax: cmd \n"; return 1; } else { std::ifstream infile(argv[1]); if (!infile.is_open()) { std::cerr << "Failed to open file\n"; return 2; } else { // read the file // print the results } } return 0; } * And how can we read the file, line per line? // Read the file: std::vector points; std::string line; while (std::getline(infile, line)) { // Convert the line into a point, and push it onto the vector: points.push_back(readNextPoint(line)); } * Let's add a place holder to readPoint, and then we implement it later: Point2D* readNextPoint(std::string line) { return new Point2D(0, 0); } * And let's add the code to print the results: // Print the results: for (Point2D* p: points) { std::cout << p->to_string() << std::endl; } infile.close(); * At this point, we can check results. See what you get with the file below: 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 10.10 11.11 12.12 * To get something useful, we need to implement readNextPoint. Can you do it? #include Point2D* readNextPoint(std::string line) { std::istringstream ss(line); std::string token; std::getline(ss, token, ' '); double x = stod(token); std::getline(ss, token, ' '); double y = stod(token); if (std::getline(ss, token, ' ')) { double z = stod(token); return new Point3D(x, y, z); } else { return new Point2D(x, y); } } * Test the whole program, and see what you get. * Important: why we always print the point in the right format, even though the vector is declared as a vector of Point2D's? * Remove the 'virtual' keyword from Point2D::to_string, and see what happens. * The method readNextPoint actually implements a design pattern called "Factory Method". Can you imagine what is this pattern? * Important: how can we know that a Point2D points to a Point3D? * Can you implement a function "filter" that filters out 3D points from a vector of 2D points? void filter(std::vector* points3D, std::vector points2D) { for (Point2D* p2d: points2D) { Point3D* p3d = dynamic_cast(p2d); if (p3d) { points3D->push_back(p3d); } } } * The construction "dynamic_cast(p2d)" is called a dynamic cast. What's a dynamic cast? * Can you modify your test file to check that the function works? // Filter out the 3D points: std::vector points3D; filter(&points3D, points); std::cout << "Printing the 3D Points only:\n"; for (Point3D* p: points3D) { std::cout << p->to_string() << std::endl; } * Implement a Line2D class. This class has two constructors. - receives slop and y-intersection - receives two points It also has a 'contains' method, which receives a point, and tells if the point belongs into the line. * Let's implement a simple stub first, and then we improve it: #ifndef LINE_2D_H #define LINE_2D_H class Point2D; class Line2D { public: Line2D(double a, double b): _a(a), _b(b) {} // TODO: finish the constructor: Line2D(Point2D *p1, Point2D *p2) { _a = 0.0; _b = 0.0; } std::string to_string() const { return "y = " + std::to_string(_a) + "x + " + std::to_string(_b); } private: double _a; double _b; }; #endif * Before we finish the constructor, can you implement a test for this class? #include #include "Point2D.h" #include "Line2D.h" int main() { Line2D l0(-0.5, 1.0); Point2D p1(0.0, 0.0); Point2D p2(2.0, 1.0); Line2D l1(&p1, &p2); std::cout << l0.to_string() << std::endl; std::cout << l1.to_string() << std::endl; } * Ok, now we need to implement the constructor that receives points. * Do you remember the formulae of slope and intersect? _b = (x2*y1 - x1*y2)/(x2 - x1) _a = (y2 - y1)/(x2 - x1) * Can you implement the constructor using these formulae? Line2D(Point2D *p1, Point2D *p2) { // _b = (x2*y1 - x1*y2)/(x2 - x1) // _a = (y2 - y1)/(x2 - x1) double den = p2->_x - p1->_x; _a = (p2->_y - p1->_y)/den; _b = (p2->_x*p1->_y - p1->_x*p2->_y)/den; } * But the program will not compile. There will be many errors like: "error: '_x' is a protected member of 'Point2D' " How can we fix this issue? class Point2D { public: Point2D(double x, double y): _x(x), _y(y) {} double getX() { return _x; } double getY() { return _y; } virtual std::string to_string() const { return "2D(" + std::to_string(_x) + ", " + std::to_string(_y) + ")"; } protected: double _x; double _y; // This declaration gives Line2D access to _x and _y: friend class Line2D; }; * Can we use Point3D to create Line2D? #include #include "Point2D.h" #include "Point3D.h" #include "Line2D.h" int main() { Point2D *p1 = new Point3D(0.0, 0.0, 3.14); Point2D *p2 = new Point3D(2.0, 1.0, 2.71); Line2D l1(p1, p2); std::cout << l1.to_string() << std::endl; delete p1; delete p2; } * Add a method contains to Line2D, which tells if a point belongs into the line: bool contains(Point2D *p) const { return p->_y == (_a*p->_x + _b); } * Can you create a harness to test it? #include #include "Point2D.h" #include "Point3D.h" #include "Line2D.h" int main() { Point2D *p1 = new Point2D(0.0, 0.0); Point2D *p2 = new Point2D(2.0, 1.0); Line2D l1(p1, p2); std::cout << l1.to_string() << std::endl; Point2D *p3 = new Point2D(4.0, 2.0); std::cout << "L1 contains P3? " << l1.contains(p3) << std::endl; delete p1; delete p2; delete p3; } * Can you tell me if this code works? #include #include "Point2D.h" #include "Point3D.h" #include "Line2D.h" int main() { Point2D *p1 = new Point2D(0.0, 0.0); Point2D *p2 = new Point2D(2.0, 1.0); Line2D l1(p1, p2); std::cout << l1.to_string() << std::endl; Point2D *p3 = new Point3D(4.0, 2.0, 3.14); std::cout << "L1 contains P3? " << l1.contains(p3) << std::endl; Point3D *p4 = new Point3D(4.0, 2.0, 3.14); std::cout << "L1 contains P3? " << l1.contains(p3) << std::endl; std::cout << "L1 contains P4? " << l1.contains(p4) << std::endl; delete p1; delete p2; delete p3; delete p4; } * There is a problem with the program above. The point (4.0, 2.0, 3.14) is not really in the line y = 0.5x. Would it be possible to avoid this kind of situation? #ifndef LINE_2D_H #define LINE_2D_H #include #include class Point2D; class Line2D { public: Line2D(double a, double b): _a(a), _b(b) {} Line2D(Point2D *p1, Point2D *p2) { ... } std::string to_string() const { ... } bool contains(Point2D *p) const { assert(typeid(Point2D).name() == typeid(*p).name()); return p->_y == (_a*p->_x + _b); } private: double _a; double _b; }; #endif * Can you write a routine to check if there are three points in the input file that are collinear? void listCollinearPoints(std::vector points) { for (Point2D *p1: points) { for (Point2D *p2: points) { if (p1 != p2) { Line2D l1(p1, p2); for (Point2D *p3: points) { if (p3 != p1 && p3 != p2) { if (l1.contains(p3)) { std::cout << "Collinear:\n"; std::cout << " - " << p1->to_string() << std::endl; std::cout << " - " << p2->to_string() << std::endl; std::cout << " - " << p3->to_string() << std::endl; } } } } } } } * How can you test it? int main(int argc, char** argv) { // Open the file: if (argc != 2) { std::cerr << "Syntax: cmd \n"; return 1; } else { std::ifstream infile(argv[1]); if (!infile.is_open()) { std::cerr << "Failed to open file\n"; return 2; } else { // Read the file: std::vector points; std::string line; while (std::getline(infile, line)) { points.push_back(readNextPoint(line)); } infile.close(); // Check which points are collinear: listCollinearPoints(points); } } return 0; } * This code is a bit annoying, because it prints all the permutations of collinear points. Can we avoid these redundancies? void listCollinearPoints_2(std::vector points) { for (int i = 0; i < points.size() - 2; i++) { Point2D* p1 = points[i]; for (int j = i+1; j < points.size() - 1; j++) { Point2D* p2 = points[j]; if (p1 != p2) { Line2D l1(p1, p2); for (int k = j+1; k < points.size(); k++) { Point2D* p3 = points[k]; if (p3 != p1 && p3 != p2) { if (l1.contains(p3)) { std::cout << "Collinear:\n"; std::cout << " - " << p1->to_string() << std::endl; std::cout << " - " << p2->to_string() << std::endl; std::cout << " - " << p3->to_string() << std::endl; } } } } } } }