This is a practical course that will provide you with a skill that you can use in may other courses. The purpose of the course is to have you be able to write programs in the C++ language. C++ is a large and complex language. It took many people many years to develop the language and define the language as an international standard. ISO/IEC 14882:1998 This course will teach C++ according to the standard because eventually the various C++ compilers will accept the standard language rather than the various experimental versions of C++. See the course WEB page. See the course syllabus. See the course homework assignments. See chaotic state of C++ compilers. See Compact Summary of C++
Technically there is a C++ language and there are C++ libraries. The ISO/IEC 14882:1998 C++ standard includes the formal definition of both the language and the libraries. A list of the library names is in the C++ summary
The simplest Input/Output is from the keyboard/display. To use this Input/Output a program must include the two lines: #include <iostream> using namespace std; The "#" line is a preprocessor directive to read the standard library file named iostream . The "using namespace std;" is necessary to make the operators >> and << visible. The statement cout << "Hello." << endl; would write Hello. to the display. The statements int amount; cin >> amount; would read an integer from the keyboard and place the value in 'amount' A skeleton program to read and print data is shown below. change.cpp // change.cpp #include <iostream> // basic console input/output #include <cctype> // handy C functions e.g. isdigit using namespace std; // bring standard include files in scope int main() { int amount; // value read by cin while(!cin.eof()) // a loop that ends when a end of file is reached { try // be ready to catch any exceptions and stay in the loop { if(!isdigit(cin.peek())) throw "not a number"; cin >> amount; //read something, hopefully an integer cout << "Input: " << amount ; // start the output line // more code here that computes and outputs using cout cout << endl; // this ends the output line } catch(...){ cout << " no change possible " << endl; } cin.ignore(80, '\n'); // get rid of any junk after the number } // end of while loop cout << "end of change run" << endl; return 0; } // end main For disk file I/O see test_file_io.cpp
The structure of exceptions is: try { ... statements throw "ooopse"; ... statements } catch( ) { ... statements } catch(...) { ... statements } Several example programs and their output are: try_try2.cc // try_try2.cc demonstrate how the type of 'catch' get various 'throw' #include <iostream> using namespace std; int main() { typedef int my_int; my_int k; for(int i=0; i<5; i++) { cout << i << '\n'; try { cout << "in try block, i= " << i << '\n'; if(i==0) throw "first exception"; cout << "in try block after first if \n"; if(i==1) throw i; cout << "in try block after second if \n"; if(i==2) throw (float)i; cout << "in try block after third if \n"; if(i==3) { k=i; throw k;} cout << "in try block after third if \n"; throw 12.5; // a double cout << "should not get here \n"; // compiler warning also } catch(const char * my_string){ cout << "my_string " << my_string << '\n'; } catch(const int j){ cout << "caught an integer = " << j << '\n'; } catch(const double j){ cout << "caught a double = " << j << '\n'; } catch(...){ cout << "caught something \n" << '\n' << '\n'; } cout << "outside the try block \n"; } cout << "outside the loop, normal return \n"; return 0; } // end main 0 in try block, i= 0 my_string first exception outside the try block 1 in try block, i= 1 in try block after first if caught an integer = 1 outside the try block 2 in try block, i= 2 in try block after first if in try block after second if caught something outside the try block 3 in try block, i= 3 in try block after first if in try block after second if in try block after third if caught an integer = 3 outside the try block 4 in try block, i= 4 in try block after first if in try block after second if in try block after third if in try block after third if caught a double = 12.5 outside the try block outside the loop, normal return And, now you can see how an exception thrown from a nested (possibly deeply nested) function comes up the call stack to the first 'catch' that can handle the type of the thrown exception. try_nest.cpp // try_nest.cpp 'throw' from nested function call #include <iostream> using namespace std; void f1(void); void f2(void); int main(void) { cout << "Staring try_nest.cpp \n"; try { f1(); cout << "should not get here in main \n"; } catch(const int j){ cout << "caught an integer = " << j << '\n'; } cout << "outside the try block \n"; return 0; } // end main void f1(void) { cout << "in f1, calling f2 \n"; f2(); cout << "in f1, returned from f2 \n"; } void f2(void) { cout << "in f2, about to throw an exception \n"; throw 7; } Staring try_nest.cpp in f1, calling f2 in f2, about to throw an exception caught an integer = 7 outside the try block
A class in C++ is a data structure (often called an abstract data type) and all the functions needed to operate on that data structure (called member functions). This is a very simple example of a circle class. A class is a definition and the class defines a new type. A typical setup is shown below: The class Circle is defined in a header file circle.h The class is implemented in a .cpp .cc .C file circle.cpp because users of the class just need the header. The test program test_circle.cpp must do a #include "circle.h" but definitely does NOT include circle.cpp The test program instantiates the class Circle to create the object 'c1' The test program uses the constructor Circle to initialize the object. // circle.h #ifndef CIRCLE_H // be sure file only included once per compilation #define CIRCLE_H // // Define a class that can be used to get objects of type Circle. // A class defines a data structure and the member functions // that operate on the data structure. // The name of the class becomes a type name. class Circle // the 'public' part should be first, the user interface // the 'private' part should be last, the safe data { public: Circle(double X, double Y, double R); // a constructor void Show(); // a member function void Set(double R); // change the radius void Move(double X, double Y); // move the circle private: double Xcenter; double Ycenter; double radius; }; #endif // CIRCLE_H nothing should be added after this line // circle.cpp // implement the member functions of the class Circle #include <iostream> #include "circle.h" using namespace std; Circle::Circle(double X, double Y, double R) { Xcenter = X; Ycenter = Y; radius = R; } void Circle::Show() { cout << "X, Y, R " << Xcenter << " " << Ycenter << " " << radius << endl; } void Circle::Set(double R) { radius = R; } void Circle::Move(double X, double Y) { Xcenter = X; Ycenter = Y; } // test_circle.cpp #include <iostream> #include "circle.h" using namespace std; int main() { Circle c1(1.0, 2.0, 0.5); // construct an object named 'c1' of type 'Circle' Circle circle2(2.5, 3.0, 0.1); // another object named 'circle2' c1.Show(); // tell the object c1 to execute the member function Show circle2.Show(); // circle2 runs its member function Show c1.Move(1.1, 2.1); // move center c1.Show(); circle2.Set(0.2); // set a new radius circle2.Show(); return 0; } The files above can be saved to your directory and compiled then executed with: on gl SGI CC -n32 -Iinclude -o test_circle test_circle.C circle.C test_circle on Unix g++ -o test_circle test_circle.cc circle.cc test_circle on PC VC++ cl /GX /ML test_circle.cpp circle.cpp test_circle The result of the execution is: X, Y, R 1 2 0.5 X, Y, R 2.5 3 0.1 X, Y, R 1.1 2.1 0.5 X, Y, R 2.5 3 0.2
A very similar program to Lecture 4 can be created using class inheritance. A useful memory aid to determine when inheritance should be used is to say "inheriting class" is a "base class" The "is a" applies here: a Circle is a Shape The example below defines a base class (class that does not inherit) Shape2 and defines a class Circle2 that inherits the base class. The word "inherits" is to be taken literally. The class Circle2 really has member functions Move, Xc and Yc . The class Circle2 really has three double precision numbers in its data structure (in the private part) Xcenter, Ycenter and radius. The following code show five files: shape2.h shape2.cpp circle2.h circle2.cpp test_shape.cpp // shape2.h #ifndef SHAPE2_H #define SHAPE2_H // // Demonstrate simple class inheritance and its test program all in one file // the Shape2 class provides the center for specific shapes that inherit it class Shape2 // demonstrate a base class { public: void Move(double X, double Y); // move center of shape to new X,Y double Xc(); // return Xcenter double Yc(); // return Ycenter private: double Xcenter; double Ycenter; }; // end Shape2 #endif // SHAPE2_H // shape2.cpp // implement the member functions of the class Shape2 #include "shape2.h" void Shape2::Move(double X, double Y) { Xcenter = X; Ycenter = Y; } double Shape2::Xc() { return Xcenter; } double Shape2::Yc() { return Ycenter; } // end shape2.cpp // circle2.h #ifndef CIRCLE2_H #define CIRCLE2_H // // Define a class that can be used to get objects of type Circle. // A circle is a shape, so it makes sense for Circle2 to inherit Shape2. // A class defines a data structure and the member functions // that operate on the data structure. // The name of the class becomes a type name. #include "shape2.h" class Circle2 : public Shape2 // <-- the colon ":" means "inherit" // the 'public' part should be first, the user interface // the 'private' part should be last, the safe data { public: Circle2(double X, double Y, double R); // a constructor void Show(); // a member function void Set(double R); // set a new radius // by inheritance Move is here private: double radius; // by inheritance Xcenter and Ycenter are here }; // end Circle2 #endif // CIRCLE2_H // circle2.cpp // implement the member functions of the class Circle2 #include <iostream> #include "circle2.h" using namespace std; Circle2::Circle2(double X, double Y, double R) { Move(X, Y); // puts values into Xcenter, Ycenter in Shape2 radius = R; } void Circle2::Set(double R) { radius = R; } void Circle2::Show() { cout << "X, Y, R " << Xc() << " " << Yc() << " " << radius << endl; } // end circle2.cpp // test_shape.cpp #include <iostream> #include "circle2.h" using namespace std; int main() { Circle2 c1(1.0, 2.0, 0.5); // construct an object named 'c1' of type 'Circle' Circle2 circle1(2.5, 3.0, 0.1); // another object named 'circle2' c1.Show(); // tell the object c1 to execute the member function Show circle1.Show(); // circle2 runs its member function Show c1.Move(1.1, 2.1); c1.Show(); circle1.Set(0.2); circle1.Show(); return 0; } // end test_shape and the output from executing: X, Y, R 1 2 0.5 X, Y, R 2.5 3 0.1 X, Y, R 1.1 2.1 0.5 X, Y, R 2.5 3 0.2
Before moving on to multiple inheritance, we need to understand the use of the three possible sections of a class with the following visibility: public: member functions and objects defined in this section of a class are visible to any class that inherits this class as 'public' and visible in any object of this class. protected: member functions and objects defined in this section of a class are visible to any class that inherits this class as 'public' or 'protected' and not visible anywhere else. private: member functions and objects defined in this section of a class are not visible anywhere. Then, the way a class is inherited may restrict the visibility further: inheriting class A using : public A; keeps all visibility as above. inheriting class B using : protected B; makes the public section of B become restricted to protected visibility. inheriting class C using : private C; makes all of C become restricted to private visibility. The following example, inherit.cpp demonstrates the three restrictions of inheritance with the three sections. (All defaults are private.) // inherit.cpp public: protected: private: // // define three classes A, B, C with variables 1, 2, 3 in // public: protected: and private: respectively // Then class D inherits public A, protected B and private C class A { public: int a1; protected: int a2; private: int a3; }; class B { public: int b1; protected: int b2; private: int b3; }; class C { public: int c1; protected: int c2; private: int c3; }; class D: public A, protected B, private C // various restricted inheritance { public: int d1; // also here a1 int test(); protected: int d2; // also here a2 b1 b2 private: int d3; // also here a3 b3 c1 c2 c3 }; int D::test() { return d1 + a1 + d2 + a2 + b1 + b2 + d3; // all visible inside D // not visible a3 b3 c1 c2 c3 inside D } int main() { D object_d; // object_d has 12 values of type int in memory return object_d.d1 + object_d.a1; // only these are visible outside D // not visible object_d. a2 a3 b1 b2 b3 c1 c2 c3 d2 d3 } Now, Multiple Inheritance Pictorially, multiple inheritance can be shown by representing classes by boxes and inheritance by lines. The class at the top, class A, is a base class. +---------+ | | | class A | a base class defining two int's | | | int p,q | | | +---------+ / \ / \ / \ +---------+ +---------+ | | | | C now has two int's inherited | class B | | class C | from A in addition to the | | | | two int's C defines | int q,r | | int q,s | | | | | +---------+ +---------+ \ / \ / \ / +---------+ The programmer has a design decision with | | multiple inheritance: Does the programmer | class D | want (or need) two copies of A in class D? | | Without 'virtual' you get two copies. | int q,t | Using 'virtual' you get one copy. | | +---------+ The following program multinh.cc shows the design getting two copies of class A and thus two sets of A's int p,q; (Further down is virtinh.cc that gets one copy of class A) // multinh.cc demonstrate multiple inheritance // design requirement is that B and C have their own A // compare this to virtinh.cc class A // base class for B and C { // indirect class for D public: int p, q; }; class B : public A // single inheritance { public: int q, r; // now have A::p A::q B::q B::r void f(int a){ A::q = a;} // because A::q won't be visible int g(){ return A::q;} // define functions to set and get }; class C : public A // another single inheritance { public: int q, s; // now have A::p A::q C::q C::s void f(int a){ A::q = a;} // four integers int g(){ return A::q;} }; class D : public B, public C // multiple inheritance { public: int q, t; // now have AB::p AB::q B::q B::r }; // AC::p AC::q C::q C::s // D::q D::t // ten integers #include <iostream> using namespace std; int main() { D stuff; // ten (10) integers stuff.B::p = 1; // really in A, ambiguous stuff.C::p = 2; // really in A, picked p from A in C // stuff.B::A::q = 3; // can not get to this one as object stuff.B::f(3); // set via a function in B // stuff.C::A::q = 4; // can not get to this one as object stuff.C::f(4); // set via a function in C stuff.B::q = 5; // the local q in B stuff.C::q = 6; // the local q in C stuff.D::q = 7; // the local q in D stuff::q also works stuff.r = 8; // from B unambiguous stuff.s = 9; // from C unambiguous stuff.t = 10; // from D unambiguous cout << stuff.B::p << stuff.C::p << stuff.B::g() << stuff.C::g() << stuff.B::q << stuff.C::q << stuff.D::q << stuff.r << stuff.s << stuff.t << endl; return 0; // prints 12345678910 to show ten variables stored } // // functions are needed to get/set some variables // this is due to lack of syntax in present C++ stuff.B::A::q should work Now, with a different design, the programmer may want only one copy of class A to be inherited into D. This design technique is implemented by inheriting using the key word 'virtual'. The rule is that any duplicate inheritances of a class will not occur if that class is inherited as virtual. If all inheritances are virtual, exactly one copy of the class will be inherited. This is demonstrated by the file virtinh.cc and should be compared to the file above to see the differences. // virtinh.cc demonstrate multiple inheritance with 'virtual' // design requirement needs only one copy of A in D // compare this to multinh.cc class A // base class for B and C { // indirect class for D public: int p, q; }; class B : virtual public A // single inheritance, virtual { public: int q, r; // now have A::p A::q B::q B::r // no special set/get functions needed }; class C : virtual public A // another single inheritance, virtual { // now have four integers public: int q, s; // now have A::p A::q C::q C::s }; class D : public B, public C // multiple inheritance, only one A { // class inherited because B, C virtuals public: int q, t; // now have A::p A::q B::q B::r }; // C::q C::s D::q D::t // now have eight integers #include <iostream> using namespace std; int main() { D stuff; // eight (8) integers stuff.p = 1; // the local p in A stuff.A::q = 2; // the local q in A note four (4) q's stuff.B::q = 3; // the local q in B stuff.C::q = 4; // the local q in C stuff.q = 5; // the local q in D stuff.r = 6; // from B unambiguous stuff.s = 7; // from C unambiguous stuff.t = 8; // from D unambiguous cout << stuff.p << stuff.A::q << stuff.B::q << stuff.C::q << stuff.D::q << stuff.r << stuff.s << stuff.t << '\n'; return 0; // prints 12345678 to show eight variables stored }
The programmer of a class has a design technique to cause member functions to be invisible when inherited by any class that uses the same function prototype as the member function. This technique is know as defining a virtual function. A class inheriting a virtual function gets an actual function as long as it is not over ridden by a local definition. An example of a virtual member function is: virtual double Compute(double X); Given a class hierarchy, a set of classes using inheritance, the user of the classes can achieve what is called 'polymorphism'. Polymorphism is the result of having pointers to classes in a hierarchy and having a given pointer point to different classes at different times during the execution of a program. This can result in run time polymorphism where the decision of what function to call is made at execution time rather than at compilation time. The cases where polymorphism occurs are indicated in the comments in the examples below. The first example below demonstrates many cases for virtual member functions. The second example below demonstrates some cases of pure virtual member functions and some abstract classes. // test_vir.cc // show how a virtual function in a base class gets called // demonstrate polymorphism // compare this with testpvir.cc using a pure function to make an abstract class class Base // terminology: a base class is any class that { // does not inherit another class public: void f(void); virtual void v(void); }; class D1 : public Base // note both f and v inherited { // then defined again public: void f(void); virtual void v(void); }; class D2 : public Base // note both f and v inherited { // only v defined again public: virtual void v(void); }; class D3 : public Base // note both f and v inherited { // only f defined again public: void f(void); }; class DD1 : public D1 // a class derived from the a derived class { // now have potentially three f's and three v's public: void f(void); virtual void v(void); }; int main(void) { Base b, *bp; // declare an object and a pointer for each class D1 d1, *d1p; D2 d2, *d2p; D3 d3, *d3p; DD1 dd1, *dd1p; b.f(); // calls Base::f() b.v(); // Base::v() bp=&b; bp->f(); // Base::f() bp->v(); // Base::v() no difference with virtual yet d1.f(); // D1::f() d1.v(); // D1::v() d1p=&d1; d1p->f(); // D1::f() d1p->v(); // D1::v() no difference with virtual yet bp=&d1; // now, make a pointer to the base type, point to an // object of a derived type of the base type bp->f(); // Base::f() choose function belonging to pointer type bp->v(); // D1::v() now difference with virtual! // run time polymorphism d2.f(); // Base::f() d2.v(); // D2::v() d2p=&d2; d2p->f(); // Base::f() D2 has no f(), get from base type d2p->v(); // D2::v() no difference with virtual yet bp=&d2; // now, make a pointer to the base type, point to an // object of a derived type of the base type bp->f(); // Base::f() choose function belonging to pointer type bp->v(); // D2::v() now difference with virtual! // run time polymorphism d3.f(); // D3::f() d3.v(); // Base::v() D3 has no v(), get from base type d3p=&d3; d3p->f(); // D3::f() d3p->v(); // Base::v() D3 has no v(), get from base type bp=&d3; // now, make a pointer to the base type, point to an // object of a derived type of the base type bp->f(); // Base::f() choose function belonging to pointer type bp->v(); // Base::v() no local function, choose from pointer type dd1.f(); // DD1::f() dd1.v(); // DD1::v() dd1p=&dd1; dd1p->f(); // DD1::f() dd1p->v(); // DD1::v() no difference with virtual yet bp=&dd1; // now, make a pointer to the base type, point to an // object of a derived derived type of the base type bp->f(); // Base::f() choose function belonging to pointer type bp->v(); // DD1::v() now difference with virtual! // this is run time polymorphism return 0; } #include <iostream> using namespace std; void Base::f(void) // implementation of each function { // each function just outputs its name cout << "Base::f()" << endl; } void Base::v(void) { cout << "Base::v()" << endl; } void D1::f(void) { cout << "D1::f()" << endl; } void D1::v(void) { cout << "D1::v()" << endl; } void D2::v(void) { cout << "D2::v()" << endl; } void D3::f(void) { cout << "D3::f()" << endl; } void DD1::f(void) { cout << "DD1::f()" << endl; } void DD1::v(void) { cout << "DD1::v()" << endl; } /* result of running above file: Base::f() Base::v() Base::f() Base::v() D1::f() D1::v() D1::f() D1::v() Base::f() D1::v() Base::f() D2::v() Base::f() D2::v() Base::f() D2::v() D3::f() Base::v() D3::f() Base::v() Base::f() Base::v() DD1::f() DD1::v() DD1::f() DD1::v() Base::f() DD1::v() */
The programmer of a class has a design technique to force any class inheriting class to define a member function with a specific function prototype. An example of such a function is: virtual void Draw(void) = 0; The combination of 'virtual' and the syntax = 0; causes this class to be an abstract class and causes any class that inherits this class to define a function void Draw(void) if that class is to be other than an abstract class. An abstract class may define a pointer object but may not define an object. Now we use virtual ... = 0; to make a class an abstract class. No objects can be created from an abstract class. // testpvir.cc // show how a virtual function in a base class gets called // demonstrate polymorphism // compare this file to test_vir.cc that does not use 'virtual' class Base // a base class { public: void f(void); virtual void v(void) = 0; // makes v a pure virtual function }; // makes Base an abstract class class D1 : public Base // a derived class { // inherits both f and v public: // then defines both void f(void); virtual void v(void); }; class D2 : public Base // inherits f and v { // defines only v public: virtual void v(void); }; class D3 : public Base { public: void f(void); // no v defined, thus D3 abstract also }; class DD1 : public D1 // a class derived from the a derived class { public: void f(void); virtual void v(void); }; class D4 : public D3 // now make non virtual so we can get instance (object) { public: void v(void); }; int main(void) { Base *bp; // no object but a pointer for each pure virtual class D1 d1, *d1p; D2 d2, *d2p; D3 *d3p; // no object because v() not defined DD1 dd1, *dd1p; D4 d4; // now v() defined and can get object // b.f(); // no longer can get an object for the pure virtual Base class // b.v(); // bp=&b; // can get pointer, but can not use until later class instance // bp->f(); // bp->v(); d1.f(); // D1::f() d1.v(); // D1::v() d1p=&d1; d1p->f(); // D1::f() d1p->v(); // D1::v() no difference with virtual yet bp=&d1; // now, make a pointer to the base type, point to an // object of a derived type of the base type bp->f(); // Base::f() choose function belonging to pointer type bp->v(); // D1::v() now difference with virtual! // run time polymorphism d2.f(); // Base::f() d2.v(); // D2::v() d2p=&d2; d2p->f(); // Base::f() d2p->v(); // D2::v() no difference with virtual yet bp=&d2; // now, make a pointer to the base type, point to an // object of a derived type of the base type bp->f(); // Base::f() choose function belonging to pointer type bp->v(); // D2::v() now difference with virtual! // run time polymorphism // d3.f(); // D3::f() // not with pure virtual // d3.v(); // D3::v() d3p=&d4; d3p->f(); // D3::f() d3p->v(); // D4::v() only choice bp=&d4; // now, make a pointer to the base type, point to an // object of a derived type of the base type bp->f(); // Base::f() choose function belonging to pointer type bp->v(); // D4::v() only choice dd1.f(); // DD1::f() dd1.v(); // DD1::v() dd1p=&dd1; dd1p->f(); // DD1::f() dd1p->v(); // DD1::v() no difference with virtual yet bp=&dd1; // now, make a pointer to the base type, point to an // object of a derived derived type of the base type bp->f(); // Base::f() choose function belonging to pointer type bp->v(); // DD1::v() now difference with virtual! // run time polymorphism return 0; } #include <iostream> using namespace std; void Base::f(void) // code for all the function prototypes above { // each function just prints its full name cout << "Base::f()" << endl; } void Base::v(void) { cout << "Base::v()" << endl; } void D1::f(void) { cout << "D1::f()" << endl; } void D1::v(void) { cout << "D1::v()" << endl; } void D2::v(void) { cout << "D2::v()" << endl; } void D3::f(void) { cout << "D3::f()" << endl; } void DD1::f(void) { cout << "DD1::f()" << endl; } void DD1::v(void) { cout << "DD1::v()" << endl; } void D4::v(void) { cout << "D4::v()" << endl; } /* results of running above program: D1::f() D1::v() D1::f() D1::v() Base::f() D1::v() Base::f() D2::v() Base::f() D2::v() Base::f() D2::v() D3::f() D4::v() Base::f() D4::v() DD1::f() DD1::v() DD1::f() DD1::v() Base::f() DD1::v() */
see homework assignment for details
see homework assignment for details
The key word 'friend' is used to allow visibility to the private part of a class. In the following example, class A1 declares that class B1 is a friend. Thus, allowing class B1 to have access to the private part of class A1. The 'friend' declaration is used most often when a pair of classes are being defined that have close connection and much interaction. // friends.cpp #include <iostream> using namespace std; class A1; // incomplete type definition needed because B1 refers to A1 // and A1 refers to B1 class B1 { public: B1(int pub, int pri){b1pub=pub; b1pri=pri;} void B1_out(A1 aa); int b1pub; private: int b1pri; }; class A1 { friend B1; // says B1 can access this classes private part public: A1(int pub, int pri){a1pub=pub; a1pri=pri;} void A1_out(B1 bb); int a1pub; private: int a1pri; }; void A1::A1_out(B1 bb) { cout << "A1_out " << a1pub << a1pri << bb.b1pub /* << bb.b1pri */ << '\n'; // B1 public OK ----- ----- no private } void B1::B1_out(A1 aa) { cout << "B1_out " << b1pub << b1pri << aa.a1pub << aa.a1pri << '\n'; // both public and private OK because of 'friend' } int main(void) { A1 a(1,2); B1 b(3,4); cout << "friends.cpp \n"; a.A1_out(b); b.B1_out(a); return 0; } // results: friends.cpp // A1_out 123 // B1_out 3412 There is a special case when a operator needs to be defined for another class and variables in the private part of a class need to be used by that operator. The most common case is when the cout operator << needs to be defined for a class. The operator is not a member function of the class being defined but need to access private variables. Thus a special use of 'friend' allows only the operator to have access to the private part. friend ostream &operator << (ostream &stream, A_class x); says that the operator << defined for reference to class ostream is allowed visibility to A_class private section. In general, any C++ predefined operator can be given a definition for a class. This example also shows the comparison operator < being defined. Note the function return type 'bool' is typically used for comparison operators. The first object to compare is this object, denoted by the key work 'this'. The second object used in the comparison is a reference to another object, 'y'. A simple comparison was shown here yet the class may define whatever makes sense for 'greater than' for the operator '<'. The precedence of operators can not be changed. A new form of constructor initialization is also demonstrated. The syntax : variable(constant or argument) initializes the variable in the object to the constant or argument. Actually any valid expression based on arguments and constants may be used for the initialization expression in the parenthesis. // cout_friend.cpp define the cout operator << for a class // define operator < for a class // demonstrate constructor initialization :var(arg) // a_class.h should be in a file by itself #include <iostream> // basic console input/output using namespace std; // bring standard include files in scope class A_class { public: A_class(int Akey, double Aval): key(Akey), val(Aval) {} friend ostream &operator << (ostream &stream, A_class x); bool operator < (A_class &y) {return this->key < y.key;} private: // bug in VC++ means this has to be public in VC++ only int key; double val; }; // #include "a_class.h" // would be needed if lines above were in a_class.h int main() { A_class a(5,7.5); A_class b(6,2.4); cout << "a= " << a << " b= " << b << endl; // using A_class << if( a < b) // using A_class < { cout << "OK a < b because 5 < 6" << endl; } else { cout << "BAD compare on a < b fix operator < definition" << endl; } return 0; } // end main // The lines below should be in a file a_class.cpp .cc .C // #include "a_class.h" // The 'friend' statement in A_class makes key and val visible here ostream &operator<< (ostream &stream, A_class x) { stream << x.key << " "; stream << " $" << x.val << " "; // just demonstrating added output return stream; } // output of execution is: // a= 5 $7.5 b= 6 $2.4 // OK a < b because 5 < 6 The next topic is the four uses of the key word 'static'. The following file static.cc is well commented to show the uses. Note the execution results that show the single location B1 is shared by all objects of class Base. // static.cc test various cases note //! is about 'static' FOUR USES !! // 1 a static member function // 2 a static member variable (shared by all objects of this class // 3 local to this file // 4 static (not on stack) variable in function - holds value // between calls to the function #include <iostream> using namespace std; static int funct(int stuff); //! local to this file, not seen by linker 3 class Base { public: Base(void) { B2=2; B3=3;} static void BF1(void); //! definition can not go here 1 void BF2(void) { cout << B1 << " " << B2 << " BF2 in Base \n"; B2=5; } private: static int B1; //! no =1; here (shared by all objects of this class) 2 int B2; int B3; }; int Base::B1 = 1; //! has to be at file scope. Not in 'main' or in class 2 //! only one per total program, not in .h file void Base::BF1(void) //! can not use word static in front of this { cout << B1 << " BF1 static in Base \n"; //! can not use B2 or B3 in here B1=4; } static int j=3; //! means local to this file, not visible to linker 3 static Base c; int main(void) { static int i; //! means not on stack, value saved from call to call 4 Base a, b; a.BF2(); a.BF1(); //! object can be used, any object, same result a.BF2(); b.BF2(); Base::BF1(); //! no object needed, but do need Base:: syntax b.BF2(); return funct(-2); //! call to function, local to this file } static int funct(int stuff) //! local to this file, not seen by linker 3 { static int i=4; // hold value between calls to funct 4 j=i; i++; return stuff+3; } // results, note B1 is changed in object 'a' and B1 gets changed also in 'b' // while B2 is changed in object 'a' and 'b' unaffected // 1 2 BF2 in Base B1==1, B2==2, B2=5 in 'a' // 1 BF1 static in Base B1==1, B1=4 global // 4 5 BF2 in Base B1==4, B2==5, B2=5 in 'a' // 4 2 BF2 in Base B1==4, B2==2, B2=5 in 'b' // 4 BF1 static in Base B1==4, B1=4 global // 4 5 BF2 in Base B1==4, B2==5, B2=5 in 'b'
The English language terminology "is a" A is a B would usually mean class A would inherit class B. Shown below: a vehicle is a transporter a car is a vehicle The English language terminology "has a" A has a B would usually mean class A is composed of one or more objects of type B. This is called assembly, construction or composition in various books on object oriented programming. Shown below: a car has a engine a car has a hood a car has wheels a car has doors It does not make sense to say: a car is a door or a door is a car. It does not make sense to inherit a door class twice to give a car two doors. An example is shown below: // composition.cc // we have covered inheritance, now we add composition // once students learn about inheritance, they tend to over use it. // it must be remembered that objects may be composed of other objects // composition has a different meaning and use than inheritance class door { // for a car door each usually a header file, e.g. door.h public: private: float width, height, mass; // etc. }; class engine { // for a car engine public: private: float horse_power, torque, mass; // etc. }; class hood { // for a car hood public: private: float width, height, mass; // etc. }; class wheel { // for a car wheel public: private: float diameter, width, mass; // etc. }; class transporter { // for a physical transporter public: float get_mass(transporter object); private: float mass; }; // should be abstract // #include "transporter" // all one file, so #include commented out class vehicle : public transporter { // for a physical vehicle // inherits "is a" transporter public: private: float new_variable; // and more } vehicle_1, vehicle_2; // can have objects //#include "vehicle.h" // all in this file //#include "door.h" //#include "engine.h" //#include "hood.h" //#include "wheel.h" class car : public vehicle { // for physical car // inherits "is a" vehicle public: private: door left_door; // composition, car "has a" door door right_door; engine v8; // composition, car "has a" engine hood my_hood; // composition, car "has a" hood wheel lf,lr,rf,rr; // composition, car has wheels // multiple inheritance will not work to get four wheels! // vehicle a_vehicle; WRONG! by inheritance, car "is a" vehicle }; int main() { car c1, c2; // c1 and c2 are two objects, instances of class car }
Templates allow the developer to leave one or more types to be defined by the user of the template. Functions take values as parameters, but templates take types as parameters. The first example shows the syntax and provides many comments about the definition and use of a template of a class: // test1_template.cc basic test of templates #include <iostream> #include <string> // this is definitely NOT!! using namespace std; template <class T> class foo; // a declaration that foo is a template class template <class T> // declaration or specification of foo class foo // the user gets to choose a type for 'T' { public: foo(); void put(T value); T get(int &j); private: int mine(T &a_value); T a; int i; }; // remember the ending semicolon template <class T> // implementation or body of foo foo<T>::foo() { i = 1; // can not initialize 'a' here because we do not know what type 'T' is } template <class T> void foo<T>:: put(T value) { a = value; // since 'a' and 'value' are both type 'T' this is OK } template <class T> T foo<T>::get(int &j) { j = ++i; // 'j' was passed by reference and gets incremented 'i' return a; // return 'a' whatever type it is } int main(int argc, char *argv[]) { foo<double> x; // in object 'x', the type 'T' is double double y; int j; x.put(1.5); // put the value 1.5 into 'a' in 'x' y = x.get(j); // increment 'i' in 'x' and return 'a' cout << "j= " << j << " y= " << y << endl; foo<string> glob; // in object 'glob' , the type 'T' is string::string string y_string; glob.put("abc"); // put the string "abc" int 'a' in 'glob' y_string = glob.get(j); // increment 'i' in 'glob' and return 'a' cout << "j= " << j << " y_string= " << y_string << endl; return 0; } Now, we define a template function where the type will be chosen by the user. The test case uses numeric types because + - * / have to be defined for the type 'typ' Notice the difference in using a template function, there is no < > needed because the types of the function parameters select (or determine) the type the template will be instantiated with. Note the differences in the results for the different user types: // test_template.cpp define and use a function template // define a template template <class typ > // user chooses a type for 'typ' typ funct( typ x, typ y, typ z) // the function takes 3 parameters { typ a, b; a = x + y; // our template limits the users b = y - z; // choice for 'typ' to a type return a * a / b; // that has +, -, *, and / defined } #include <iostream> // 'typ' must also have operator << defined using namespace std; int main() { int i = funct(3, 5, 7); // integer for typ long int j = funct(3L, 5L, 7L); // long int for typ unsigned int k = funct(3U, 5U, 7U); // unsigned int for typ float x = funct(3.1F, 5.2F, 7.3F); // float for typ double y = funct(3.1, 5.2, 7.3); // double for typ cout << i << " int, " << j << " long, " << k << " unsigned, " << x << " float, " << y << " double.\n"; return 0; } // end main // output is // -32 int, -32 long, 0 unsigned, -32.8047 float, -32.8048 double. // note the very different answer for the unsigned type // remember a small negative number becomes a very large positive number Now a more complicated template that takes two types are parameters. This example has a no parameter constructor for the template class and a two parameter constructor. The comments indicate which is being used. // template_class.cc a template that needs two classes to instantiate // this should be in a file my_class.h template $lt;class C1, class C2> // user needs two class types for 'C1' and 'C2' class My_class { public: My_class(void){} My_class(C1 Adata1, C2 Adata2) { data1 = Adata1; /*data2 = Adata2;*/} // other functions using types C1 and C2 // using statements such as below put requirements on C1 and C2 int Get_mode(void) { return data1.i; } int Get_mode(C2 Adata3) { return Adata3.mode; } // ... protected: C1 data1; // C2 data2(7); // initialization not allowed here // ... }; // in users main program file // #include <my_class.h> class A_class // users class that will be OK for template type 'C1' { public: A_class(void) { i = 3; } int i; }; class B_class // users class that will be OK for template type 'C2' { public: B_class(int Amode) { mode = Amode; stuff = 0.0; } int mode; private: double stuff; }; #include <iostream> using namespace std; int main(void) { A_class AA; // we need an object of type A_class B_class B1(7); // we need an object of type B_class B_class B2(15); // another object My_class<A_class, B_class> stuff; // 'stuff' is the instantiated template cout << "stuff.Get_mode(B1) " << stuff.Get_mode(B1) << endl; // now instantiate the template again to get the object 'things' My_class<A_class, B_class> things(AA, B2); // using the constructor to // pass objects 'AA' and 'B2" cout << "things.Get_mode() " << things.Get_mode() << endl; return 0; } // output from execution is: // stuff.Get_mode(B1) 7 // things.Get_mode() 3
test_vector.cpp test_vector.out
A demonstration of using some functions from STL algorithm (Note: These are template functions and not template classes that define data structures. Thus, the user must create data objects for the algorithms to work on.) test_algorithm1.cc Special case that works in VC++ (user template function) test_algorithms.cpp Modified case that works in g++ test_algorithms.cc
Unfortunately three compilers differ on handling the type 'string' This is one of the most complex STL header files. The typedef for string had to instantiate basic_string<...> which in turn has to instantiate char_traits<...>. Then there are template functions that must be instantiated. Visual C++ works most like the standard but may still have bugs: test_string.cpp g++ may be doing 'swap' correctly but does not have all the 'compare' test_string.cc On UMBC node 'retriever' SGI CC outputs a bunch of ^E^E^E ... and a bunch of line feeds on some commands. .capacity() did not work, possibly a problem with header files. Seems much better on node 'gl'. test_string.C Be sure you test whatever compiler you use. It is better to not use a feature that is non standard. Work around problems with as simple code as possible. Now, consider that the type 'string' is a STL container. Thus, many algorithms from 'algorithm' will work, for example: test_string2.cc This works in both g++ and CC on gl machines.
Sample programs: test_list.cpp test_set.cpp
test_priority_queue.cpp file io example
see project description
Go over HW5 - HW8 (fix it if you did not get it right) For the STL library <vector> <algorithm> <string> <list> <set> Know how to instantiate the template to get an object. Know how to get initial values into the object. Know how to add more values to the object. Know how to delete or remove or erase values from the object. Know how to print the object. Know how to look up member functions and algorithm template functions to be able to call the functions. test_print_template.cpp shows how to instantiate, initialize and print various STL templates. // test_print_template.cpp a general template function to print a STL container #include <iostream> #include <algorithm> #include <functional> #include <string> #include <vector> #include <list> #include <set> #include <queue> #include <stack> using namespace std; // a template for a general STL container print function template <class forward_iterator> void print(forward_iterator first, forward_iterator last, const char* title) { cout << title << endl; while (first != last) cout << *first++ << " "; cout << endl << endl; } int main() { int i; int n; // size of container int data[4] = {3, 7, 5, 4}; print(data, data+4, "normal integer array"); vector<int> v1(4); v1[0] = 3; v1[1] = 7; v1[2] = 5; v1[3] = 4; print(v1.begin(), v1.end(), "vector<int> data"); list<string> l1; l1.push_back("3"); l1.push_back("7"); l1.push_back("5"); l1.push_back("4"); print(l1.begin(), l1.end(), "list<string> data"); set<int> s1; s1.insert(3); s1.insert(7); s1.insert(5); s1.insert(4); print(s1.begin(), s1.end(), "set<int> data - note sorted"); multiset<int> ms; ms.insert(3); ms.insert(7); ms.insert(5); ms.insert(4); print(ms.begin(), ms.end(), "multiset<int> data - note sorted"); queue<int> q1; q1.push(3); q1.push(7); q1.push(5); q1.push(4); // print(q1.begin(), q1.end(), "queue<int> data"); // won't work cout << "special print loop for queue<int>, destroys queue" << endl; n = q1.size(); // can not be in 'for' statement for(i=0; i<n; i++) {cout << q1.front() << " "; q1.pop();} cout << endl << endl; priority_queue<int> pq; pq.push(4); pq.push(5); pq.push(7); pq.push(3); // print(pq.begin(), pq.end(), "priority_queue<int> data"); // won't work cout << "special print loop for priority_queue<int> sorted, destroys priority_queue" << endl; n = pq.size(); // can not be in 'for' statement for(i=0; i<n; i++) {cout << pq.top() << " "; pq.pop();} cout << endl << endl; deque<int> dq; dq.push_front(4); dq.push_front(5); dq.push_front(7); dq.push_front(3); // print(dq.begin(), dq.end(), "deque<int> data"); // won't work cout << "special print loop for deque<int>, destroys deque" << endl; n = dq.size(); // can not be in 'for' statement for(i=0; i<n; i++) {cout << dq.front() << " "; dq.pop_front();} cout << endl << endl; stack<int> st; st.push(4); st.push(5); st.push(7); st.push(3); // print(st.begin(), st.end(), "stack<int> data"); won't work cout << "special print loop for stack<int>, destroys stack" << endl; n = st.size(); // can not be in 'for' statement for(i=0; i<n; i++) {cout << st.top() << " "; st.pop();} cout << endl << endl; return 0; } // output from execution is: // normal integer array // 3 7 5 4 // // vector<int> data // 3 7 5 4 // // list<string> data // 3 7 5 4 // // set<int> data - note sorted // 3 4 5 7 // // multiset<int> data - note sorted // 3 4 5 7 // // special print loop for queue<int>, destroys queue // 3 7 5 4 // // special print loop for priority_queue<int> sorted, destroys priority_queue // 7 5 4 3 // // special print loop for deque<int>, destroys deque // 3 7 5 4 // // special print loop for stack<int>, destroys stack // 3 7 5 4 //
see homework assignment page for details
All about const in declaring types of pointers: Note what works, what is required and what is not allowed: // test_const.cpp pointer and value constant and non constant int main() { int i1 = 1; int i2 = 2; int i3 = 3; int i5 = 5; const int ic = 9; // variable 'ic' unchangeable // naming is pointer_object // con for constant // var for variable (not const) // four cases are possible: int * var_var; int * const con_var = &i1; // requires initialization const int * var_con; const int * const con_con = &i5; // requires initialization // ^ ^-- refers to the pointer // L__ refers to the value (dereferenced pointer) var_var = &i2; // con_var = &i2; // not allowed var_con = &i3; // con_con = &i2; // not allowed *var_var = 7; *con_var = 8; // *var_con = 7; // not allowed // *con_con = 7; // not allowed // var_var = ⁣ // not allowed // con_var = ⁣ // not allowed var_con = ⁣ // con_con = ⁣ // not allowed return 0; }
/* test_swap.c older style of test_swap.cpp */ void swap(int *v1, int *v2) /* pass by pointer */ { int tmp = *v2; *v2 = *v1; *v1 = tmp; } /* end swap */ #include <stdio.h> int main() /* C version of test swap */ { int i=10; int j=20; printf("before swap i= %d j= %d \n", i, j); swap(&i,&j); /* note users call requires & */ printf("after swap i= %d j= %d \n", i, j); return 0; } /* end main */
// test_swap.cpp demonstrate pass parameter by reference void swap(int &v1, int &v2) // pass parameter by reference { int tmp = v2; v2 = v1; v1 = tmp; } // end swap #include <iostream> using namespace std; int main() // C++ test swap { int i=10; int j=20; cout << "before swap i= " << i << " j= " << j << endl; swap(i,j); // note: just use object names, no '&' cout << "after swap i= " << i << " j= " << j << endl; return 0; } // end main
/* test_swap_str.c older style of test_swap_str.cpp */ void swap(char**v1, char**v2) /* pass by pointer */ { /* note has to be pointer to pointer */ char* tmp = *v2; /* in order to swap pointers */ *v2 = *v1; *v1 = tmp; } /* end swap */ #include <stdio.h> int main() /* C version of test swap str */ { char* i="abcdef"; char* j="ghi"; printf("before swap i= %s j= %s \n", i, j); swap(&i, &j); /* note users call needs & */ printf("after swap i= %s j= %s \n", i, j); return 0; } /* end main */
// test_swap_str.cpp demonstrate pass parameter by reference void swap(char* &v1, char* &v2) { char* tmp = v2; v2 = v1; v1 = tmp; } // end swap #include <iostream> using namespace std; int main() // C++ test swap { char* i="abcdef"; char* j="ghi"; cout << "before swap i= " << i << " j= " << j << endl; swap(i,j); cout << "after swap i= " << i << " j= " << j << endl; return 0; } // end main
// test_swap_template.cpp demonstrate pass parameter by reference template <class some_type> void swap_any(some_type &v1, some_type &v2) { some_type tmp = v2; v2 = v1; v1 = tmp; } // end swap_any #include <iostream> #include <string> using namespace std; int main() // C++ test swap { int i=10; int j=20; cout << "before swap i= " << i << " j= " << j << endl; swap_any(i, j); cout << "after swap i= " << i << " j= " << j << endl; string s10("string 10"); string s20("string 20, longer OK"); cout << "before swap s10= " << s10 << " s20= " << s20 << endl; swap_any(s10, s20); cout << "after swap s10= " << s10 << " s20= " << s20 << endl; return 0; } // end main Then, for using the various cases of 'const' in parameter passing, see test_parameters.cpp As a side issue, here is a C++ program that uses a dynamic matrix built on the vector template class, that needs no pointers, no malloc or new, and can use conventional matrix subscripting. // test_matrix.cpp dynamic matrix, just vector of vectors #include <vector> #include <iostream> using namespace std; int main() { typedef vector<double> d_vec; typedef vector<d_vec> d_mat; d_mat mat; // dynamic size matrix, initially 0 by 0 d_vec row; cout << "test_matrix.cpp" << endl; row.push_back(1.0); row.push_back(2.0); row.push_back(3.0); // build the first row mat.push_back(row); row[0]=4.0; mat.push_back(row); row[1]=5.0; mat.push_back(row); // now have 3 by 3 1.0 2.0 3.0 // 4.0 2.0 3.0 // 4.0 5.0 3.0 cout << "mat[2][2]=" << mat[2][2] << endl; mat[1][1]=9.0; mat[0].push_back(4.0); mat[1].push_back(6.0); mat[2].push_back(7.0); row.push_back(8.0); mat.push_back(row); // now have 4 by 4 1.0 2.0 3.0 4.0 // 4.0 9.0 3.0 6.0 // 4.0 5.0 3.0 7.0 // 4.0 5.0 3.0 8.0 cout << "mat[2][3]=" << mat[2][3] << endl; mat[2][3]=7.0001; // obviously do not use mat[4][4], it does not exists for(int i=0; i<mat.size(); i++) for(int j=0; j<mat[i].size(); j++) cout << "mat[" << i << "][" << j << "]= " << mat[i][j] << endl; return 0; }
Sometimes it is convenient to have a member function call itself. This is called a recursive member function. There must be a way for the function to return without calling itself or else there is an infinite loop. First, look a the simplest recursive function, n! called n factorial. 0! is defined as 1 1! is defined as 1 * 0! = 1 2! is defined as 2 * 1! = 2 3! is defined as 3 * 2! = 6 Note that the definition is 'recursive' because n! is defined as n * (n-1)! Each bigger factorial is defined using the next smaller factorial. Because n! grows fast with increasing n, integer overflow will occur. Where the overflow (a number bigger than a C++ type can hold) occurs depends on what computer and possibly what compiler you are using. Beware! Overflow is not easily detected as can be seen in the output below. Note how the function 'factorial' is coded from the definition of factorial. // test_factorial.cpp the simplest example of a recursive function // a recursive function is a function that calls itself static int factorial(int n) // n! is n factorial = 1*2*3*...*(n-1)*n { if( n <= 1 ) return 1; // must have a way to stop recursion return n * factorial(n-1); // factorial calls factorial with n-1 } // n * (n-1) * (n-2) * ... * (1) #include <iostream> using namespace std; int main() { cout << " 0!=" << factorial(0) << endl; // Yes, 0! is one cout << " 1!=" << factorial(1) << endl; cout << " 2!=" << factorial(2) << endl; cout << " 3!=" << factorial(3) << endl; cout << " 4!=" << factorial(4) << endl; cout << " 5!=" << factorial(5) << endl; cout << " 6!=" << factorial(6) << endl; cout << " 7!=" << factorial(7) << endl; cout << " 8!=" << factorial(8) << endl; cout << " 9!=" << factorial(9) << endl; cout << "10!=" << factorial(10) << endl; cout << "11!=" << factorial(11) << endl; cout << "12!=" << factorial(12) << endl; cout << "13!=" << factorial(13) << endl; cout << "14!=" << factorial(14) << endl; cout << "15!=" << factorial(15) << endl; // expect a problem with cout << "16!=" << factorial(16) << endl; // integer overflow cout << "17!=" << factorial(17) << endl; // uncaught in C++ (Bad!) cout << "18!=" << factorial(18) << endl; return 0; } // output of execution is: // 0!=1 // 1!=1 // 2!=2 // 3!=6 // 4!=24 // 5!=120 // 6!=720 // 7!=5040 // 8!=40320 // 9!=362880 // 10!=3628800 // 11!=39916800 // 12!=479001600 // 13!=1932053504 // wrong! 13! = 13 * 12!, must end in two zeros // 14!=1278945280 // wrong! and no indication! // 15!=2004310016 // wrong! // 16!=2004189184 // wrong! // 17!=-288522240 // wrong and obvious if you check your results // 18!=-898433024 // Only sometimes does integer overflow go negative Now, look at a program to solve the eight queens problem. The problem is defined as having an 8 by 8 chess board and you must find a way to place eight queens on the chess board such that no queen can capture any other queen. Queens can capture horizontally, vertically and diagonally. Rather obviously, no two queens can be in the same column or same row. The data structure is based on an object, a queen, and a simple linked list of such objects. The execution model is to recursively traverse the linked list. The author of this program went a little overboard with recursion but it still serves as an example. The student must be very careful in analyzing the program to keep tract of which object a function is working on during each recursive call. // eight_queens.cpp demonstrate recursive use of objects // place eight queens on a chess board so they do not attack each other #include <iostream> using namespace std; class Queen // in main() LastQueen is head of the list { // of queen objects. public: Queen(int C, Queen *Ngh); int First(void); void Print(void); private: int CanAttack(int R, int C); // private member functions int TestOrAdvance(void); // just used internally int Next(void); // try next Row int Row; // Row for this Queen int Column; // Column for this Queen Queen *Neighbor; // linked list of Queens }; Queen::Queen(int C, Queen *Ngh) // normal constructor { Column = C; // Column will not change Row = 0; // Row will be computed Neighbor = Ngh; // linked list of Queen objects } int Queen::First(void) { Row = 1; if (Neighbor && Neighbor -> First()) // order is important { // note recursion return TestOrAdvance(); } return 1; } void Queen::Print(void) { if(Neighbor) { Neighbor -> Print(); // recursively print data inside all Queens } cout << "column " << Column << " row " << Row << endl; } int Queen::TestOrAdvance(void) { if(Neighbor && Neighbor -> CanAttack(Row, Column)) { return Next(); // the member function 'next' does the 'advance' } return 1; } int Queen::CanAttack(int R, int C) // determine if this queen attacked { int Cd; if (Row == R) return 1; // a set of rules that detect in same Cd = C - Column; // row or column or diagonal if((Row+Cd == R) || (Row-Cd == R)) return 1; if(Neighbor) { return Neighbor -> CanAttack(R, C); // recursive } return 0; } int Queen::Next(void) // advance Row but protect against bugs { if (Row == 8) { if (!(Neighbor && Neighbor->Next())) return 0; Row = 0; } Row = Row + 1; return TestOrAdvance(); } int main() { Queen *LastQueen = 0; for ( int i=1; i <= 8; i++) // initialize { LastQueen = new Queen(i, LastQueen); } if (LastQueen -> First()) // solve problem { LastQueen -> Print(); // print solution } return 0; // finished } Output of execution of eight_queens column 1 row 1 column 2 row 5 column 3 row 8 column 4 row 6 column 5 row 3 column 6 row 7 column 7 row 2 column 8 row 4
When a class has only objects in it, that is no pointers that are set by the 'new' operator, a class is reasonably safe. But, when using the 'new' operator in a class: Use all three of these C++ constructs to build a safe class: Use the keyword 'explicit' on all constructors to prevent use of "=" Use a 'copy constructor' that really makes new space and copies an existing object into the new space. Use 'operator=', in other words, code your own '=' operator for a = b; This uses the same type of code as the copy constructor with the addition of a statement 'return *this' The links show test_safe1.cpp a bad example. Then test_safe2.cpp safe but ugly. Finally test_safev.cpp neat using STL. Note: The STL version provides a dynamic vector, yet it is an object, thus the 'explicit', copy constructor, and operator= are not needed. The above set works with Microsoft Visual C++, below with g++ The links show test_safe1.cc a bad example. Then test_safe2.cc safe but ugly. Finally test_safev.cc neat using STL. Note: The STL version provides a dynamic vector, yet it is an object, thus the 'explicit', copy constructor, and operator= are not needed.
Suppose you want to make some C code so that it works in both C and C++. Well, the technique is to use a specific form of a header file for your C code. The commented example below shows the header file: /* c_header.h header file that works for both C and C++ */ /* note that this works in both C and C++ because: 1) only C style comments are used 2) name mangling is turned off using extern "C" { ... } in #ifdef __cplusplus <-- a special name 3) good practice is used to be sure included just once */ #ifndef C_HEADER_H_ /* to only include once */ #define C_HEADER_H_ /* to only include once */ #ifdef __cplusplus /* make it C if in C++ compiler */ extern "C" { /* never get here in a C compiler */ #endif #include <time.h> /* other C header files */ void some_f(float seconds); /* types and function prototypes */ /* keep strictly C, not C++ */ #ifdef __cplusplus /* just closing } for C in C++ compiler */ } #endif #endif /* C_HEADER_H_ to only include once */ An example C program to be sure above header file works /* test_c_header.c */ #include "c_header.h" int main() { return 0; } And, an example C++ program to be sure above header file works // test_c_header.cpp #include "c_header.h" int main() { return 0; } Now, demonstrate calling a C compiled function from a C++ compiled function and calling a C++ compiled function from a C compiled function. The C compiled function is compiled gcc -c call_c.c to produce an object file call_c.o /* call_c.c goes with call_cc.cpp */ /* no main() in this file */ char call_cc(int x, float y); // function prototype // function defined in C++ #include <stdio.h> char call_c(int x, float y) // simple C function { char c; /* x and y passed through */ printf("in c: about to call a C++ function\n"); c = call_cc(x, y); printf("in c: returned from call_cc with %c \n", c); printf("in c: x = %d, y = %g \n", x, y); return 'a'; // return something to C++ } In order to call the above C function (or any C library) from C++ the essential statement is to place the C related statements in a block extern "C" { } Any code or files in the block is treated as C code. The function names are not mangled as they are in C++. The calling and return code is for C rather than for C++. It turns out, you can use C++ statements inside the extern "C" { } but it is not recommended. Now, compile the main() and link in the call_c.o with the command g++ -o call_cc call_cc.cpp call_c.o // call_cc.cpp calls a C program which calls back to this C++ program // must be compiled with call_cc.c or at least linked with // the object file from the C program extern "C" { // function prototype for a C function char call_c(int x, float y); } #include <iostream> using namespace std; int main() { char c; int x=5; float y = 1.5; cout << "in C++ about to call a c: function, call_c" << endl; c = call_c(x, y); cout << "in C++: returned from call_c with " << c << endl; cout << "in C++: x = " << x << " y = " << y << endl; return 0; } extern "C" { // a C++ function callable by a C function #include <stdio.h> // probably should use C++ rather than C I/O char call_cc(int x, float y) { try // can use C++ in C callable function { // but must load C++ and C libraries cout << "in C++ extern \"C\" test that C++ allowed" << endl; } catch(...) {printf("some exception thrown in C++\n");} printf("in C++ called from c: x = %d, y = %g \n", x, y); return 'q'; } } // output of execution is: // in C++ about to call a c: function, call_c // in c: about to call a C++ function // in C++ extern "C" test that C++ allowed // in C++ called from c: x = 5, y = 1.5 // in c: returned from call_cc with q // in c: x = 5, y = 1.5 // in C++: returned from call_c with a // in C++: x = 5 y = 1.5 The above program demonstrated a main C++ function calling a C function 'call_c' then the C function 'call_c' called a function 'call_cc' compiled in C++ yet having extern "C" { } The C++ compiled function returned to the C function which finally returned to main(). One additional point: A class member function can only be called from a C function when the class member function is declared "static" See static.cc The X Windows library, for example, can be used with a C++ program, keeping in mind that the X Windows call-backs must be to C++ code enclosed in extern "C" { } blocks. (And be static member functions if the call-back is to a class member function.)
The STL <complex> defines a template class complex that provides complex arithmetic and a few complex math functions. A sample use is test_complex.cpp A simple way to extend a standard C++ library class is to just define functions that work on that classes type. An extension is defined in complexf.h and has the corresponding code in complexf.cpp A test of the fuller definition of the complex<double> class is shown in test_complexf.cpp which uses the "complexf.h" header file. Compile with g++ -o test_complexf test_complexf.cpp complexf.cpp Execute test_complexf A more general extension would provide template functions rather than functions for a specific type. An example of "operator" functions that can not be member functions using a non template, very small, implementation of a complex class: // my_complex.cc a partial, non template, complex class // demonstrate some operator+ must be non-member functions // operator<< and operator >> must be non-member functions #include <iostream> using namespace std; class my_complex { public: my_complex() { re=0.0; im=0.0; } // three typical constructors my_complex(double x) { re=x; im=0.0; } my_complex(double x, double y):re(x),im(y) {} // { re=x; im=y; } my_complex operator+(my_complex & z) // for c = a + b { return my_complex(re+z.real(), im+z.imag()); } my_complex operator+(double x) // for c = a + 3.0 { return my_complex(re+x, im); } // many other operators would typically be defined double real(void) { return re; } double imag(void) { return im; } friend ostream &operator<< (ostream &stream, my_complex &z); private: double re; // the data values for this class double im; }; // non member functions my_complex operator+(double x, my_complex z) // for c = 3.0 + a { return z+x; } ostream &operator<< (ostream &stream, my_complex &z) { stream << "(" << z.real() << "," << z.imag() << ")"; return stream; } int main() { my_complex a(2.0, 3.0); cout << a << " =a\n"; my_complex b(4.0); cout << b << " =b\n"; my_complex c = my_complex(5.0, 6.0); cout << c << " =c\n"; my_complex d; cout << d << " =d\n"; c = a + c; cout << c << " c=a+c\n"; c = 3.0 + a; // not legal without non member function operator+ cout << c << " c=3.0+a\n"; c = a + 3.0; // not legal without second operator+ on double cout << c << " c=a+3.0\n"; c = a + 3; // not legal without second operator+ on double // uses standard "C" C++ conversion int -> float -> double cout << c << " c=a+3\n"; d = c.imag(); cout << d << " d=c.imag()\n"; return 0; } // Output from running my_complex.cc // (2,3) =a // (4,0) =b // (5,6) =c // (0,0) =d // (7,9) c=a+c // (5,3) c=3.0+a // (5,3) c=a+3.0 // (5,3) c=a+3 // (3,0) d=c.imag()
We will use a very crude screen plot output to show physically how objects can be created and manipulated. The very crude drawing is output by a very old C program, header file vt100_plot.h and code file vt100_plot.c. We added the #ifdef __cplusplus to the C header file per the previous lecture in order to use the old C code with C and with object oriented C++. For demonstration purposes, all the C++ header files and code files are shown in one physical file. Each file name is shown as comments and should be a separate file. (Uncommenting the //#include lines.) Note the public, protected and private parts of class Shape. Note how classes Circle and Rectangle inherit class Shape. A sample main() shows just a few usage examples. The program may be compiled and executed using: gcc -c vt100_plot.c g++ -o test_shape3 test_shape3.cpp vt100_plot.o test_shape3 // test_shape3.cpp all in one file, should really be split up #include "vt100_plot.h" // shape3.h struct Point{float X; float Y;}; class Shape // modified for shape made up of lists of shapes { public: Shape(); // constructor ~Shape(); // destructor, mainly deletes linked list void SetCenter(Point ACenter); Point Center(); void Move(float dx, float dy); // may also want MoveTo(Point XY) void AddComponent(Shape *Ashape); void AddSibling(Shape *Ashape); virtual void Draw(); protected: Point TheCenter; private: Shape *list_of_component_shapes; Shape *list_of_sibling_shapes; }; // shape3.cpp //#include "shape3.h" Shape::Shape() // body for constructor, default initialization { TheCenter.X = 0; // make it legal the first time, keep it legal TheCenter.Y = 0; list_of_component_shapes = 0; list_of_sibling_shapes = 0; } Shape::~Shape() // body for destructor { // could use 'delete' in here on list_of_component_shapes // and list_of_sibling_shapes } void Shape::SetCenter(Point ACenter) // body for function SetCemter { TheCenter = ACenter; } Point Shape::Center() { return TheCenter; } void Shape::Move(float dx, float dy) { TheCenter.X += dx; TheCenter.Y += dy; } void Shape::AddComponent(Shape *Ashape) { Ashape->list_of_component_shapes=list_of_component_shapes; list_of_component_shapes=Ashape; } void Shape::AddSibling(Shape *Ashape) { Ashape->list_of_sibling_shapes=list_of_component_shapes; list_of_sibling_shapes=Ashape; } void Shape::Draw() // draws linked list of sub-shapes { Shape *local_list = list_of_component_shapes; while(local_list){ local_list->Draw(); local_list=local_list->list_of_component_shapes; } } // circle3.h //#include "shape3.h" class Circle : public Shape { public: Circle(float X, float Y, float R); void SetRadius(float Aradius); void Draw(); float Radius(); private: float TheRadius; }; // circle3.cpp //#include "circle3.h" Circle::Circle(float X, float Y, float R) { Point p={X,Y}; SetCenter(p); TheRadius = R; } void Circle::SetRadius(float Aradius) { TheRadius = Aradius; } void Circle::Draw() { DRAW_CIRCLE(TheCenter.X, TheCenter.Y, TheRadius); } float Circle::Radius() { return TheRadius; } // rectangle3.h //#include "shape3.h" class Rectangle : public Shape { public: Rectangle(float X, float Y, float W, float H); void SetSize(float Awidth, float Aheight); void Draw(); float Width(); float Height(); private: float TheWidth; float TheHeight; }; // rectangle3.cpp //#include "rectangle3.h" Rectangle::Rectangle(float X, float Y, float W, float H) { TheCenter.X = X; TheCenter.Y = Y; TheWidth = W; TheHeight = H; } void Rectangle::SetSize(float Awidth, float Aheight) { TheWidth = Awidth; TheHeight = Aheight; } void Rectangle::Draw() { DRAW_RECTANGLE(TheCenter.X, TheCenter.Y, TheWidth, TheHeight); } float Rectangle::Height() { return TheHeight; } float Rectangle::Width() { return TheWidth; } // test_shape3.cpp //#include "circle3.h" //#include "rectangle3.h" int main() // just a few tests { Circle a_circle(1.0F, 2.0F, 3.0F); Circle *circle_ptr = new Circle(7.0F, 0.0F, 3.0F); Point a_point = {3,4}; Rectangle a_rect(-8.0, 2.0, 4.0, 6.0); Shape a_shape; INITIALIZE(); a_circle.SetCenter(a_point); a_circle.SetRadius(2); a_circle.Draw(); a_circle.Move(12.5, 5.0); a_circle.Draw(); circle_ptr->Draw(); a_rect.Draw(); a_shape.SetCenter(a_point); a_shape.AddComponent(new Circle(-12,5,4)); a_shape.AddComponent(new Rectangle(-18,-6,4,4)); a_shape.Draw(); PRINT(); return 0; } The very crude output would look something like: **** * * ***** * * ** ** * * ** ** **** * * *** * ***** ** ** * * * * * * ** *** * ** ****** ** ** * **** ** ***** * * * ***** * * ** ** ***** ***** * * * * * * *****
Object Oriented Design, analysis problem. Proposed problem: Develop a simulation of traffic flow that can be used to determine travel times under varying traffic conditions. Some requirements and desires: 1) Have a graphical view of the traffic situation in order to help identify problems and possibly identify solutions. (X Windows on Unix, MS Windows in Visual C++) 2) Have more than one type of vehicle, e.g. at least passenger car and truck 3) Have intersections and traffic signals. The traffic signals need to have setable timers and sensors. 4) Have the ability to insert vehicles into the area of study based on specific requirements or based on mean and standard deviation of time of entry. ( For each type of vehicle.) 5) Have the ability to define the road pattern and intersection type. ( Intersections can be no traffic control, stop signs or traffic signal.) 6) The vehicles must be able to have a goal or driving instructions (a route). The default goal is to drive at the speed limit whenever possible while obeying all traffic rules. Analysis of objects: 1) It seems a class 'Vehicle' is needed. Initialization with characteristics like weight, length, maximum acceleration, maximum deceleration and route instructions would provide the ability to get all of the required objects. Route instructions could be a list of travel and turn commands like 'go N blocks, turn W' where N counts intersections and W is left or right. 2) It seems a class 'Road' may be needed. A road object may have any number of vehicles on it, may have any number of intersections. A road object should be able to be initialized with a speed limit. A road object may have to accept vehicles from other road objects and hand off vehicles to other road objects. But, further thought indicates... 3) A better building block may be a 'Lane'. A lane handles any number of vehicles going in one direction. A lane starts at an intersection ( the entrance) and stops at an intersection ( the exit). A lane has a length and a speed limit ( and possibly a road condition that can affect acceleration.) A lane has an X,Y grid coordinate for its entrance and exit. 4) It seems a class 'Intersection' is needed to join lanes or roads. It may be that an intersection needs to be composed of smaller classes. 5) A 'simulation' class or object is needed for general stuff outside any other classes. 6) A display class or object or function or package is needed to provide for the graphical display. 7) A setup program is needed to initialize the road system and connectivity. This program would control over all timing and sequencing Miscellaneous notes: Vehicles will follow a policy of staying at least 16 feet apart for each 10 feet per second of speed. Vehicles can accelerate at from 1/32 G to 1/3 G depending on type. ( 1/3 G is about 11 feet per second per second, 0 to 88 feet per second (60 MPH) in 8 seconds.) Loaded trucks accelerate much slower. Because simulation uses discrete movement, the modeling is not as easy as it might be. The lead vehicle in a lane must move forward before the vehicle behind it. The ripple effect then moves the later vehicles. In C++ the best data structure for an unknown number of objects that must be controlled is a linked list. An agent method will traverse the list and cause the objects to perform the "move" method in the right order and at the right time. Implementation: It was a design decision to allow many vehicles and to allow long running times. This decision led to having the vehicle class store needed data locally rather than "reach" outside the class for data. e.g. When a vehicle is placed on a lane, it acquires the speed limit and other lane data into its local private variables. In this way each vehicle object is autonomous and can move and draw itself. Each step of the simulation, the main program operates each intersection in a linked list of intersections. Each intersection, in turn, operates each outgoing lane. Each lane, in turn, moves each vehicle in the linked list of vehicles. Timing is controlled by the main program and there is a delay if all moving is finished before the time of the next discrete time step. // vehicle.h for traffic simulation // includes class route for vehicle #ifndef VEHICLE_H_ #define VEHICLE_H_ #include "simulation.h" class route { public: route():intersection_count(0), moving(reset), next(0){} // default last move route(int Aroute_count, direction Amoving, route * route_list); void add_route( int Aroute_count, direction Amoving); direction get_moving(void); int get_count(void); route * get_next(void); private: int intersection_count; direction moving; route *next; }; // vehicle.h the vehicle class for autonomous vehicle class vehicle { public: vehicle( float Alength, float Aweight, float Aaccel, float Adecell, float Aposition, float Aspeed, float Aspeed_limit, float Ax_base, float Ay_base, direction Amoving, float Asignal_pos, signal_state Asignal, vehicle *Anext_ptr); void move(void); vehicle *next(void); void new_next( vehicle *a_next ); bool exiting( void ); void set_new_lane_data(float Aspeed_limit, float Ax_base, float Ay_base, direction Amoving, float Asignal_pos, signal_state Asignal); void set_signal(signal_state Asignal); void draw(bool erase_only=false); private: int id; float length; float weight; float accel; float decell; float position; float position_last; float speed; float speed_limit; float time_last_move; float x_base; float y_base; direction moving; signal_state signal; float signal_pos; route * route_list; route * route_now; int route_count; vehicle *next_ptr; }; // end vehicle.h #endif // VEHICLE_H_ // lane.h for traffic simulation #ifndef LANE_H_ #define LANE_H_ #include "simulation.h" #include "vehicle.h" class lane { public: lane(void): id(0), position_x(0.0), position_y(0.0), speed_limit(0.0), moving(north), length(0.0), signal(none), vehicle_list(0) {}; lane(float Aposition_x, float Aposition_y, float Aspeed_limit, direction Amoving, float Alength); void operate(void); void vehicle_start(float Alength, float Aweight, float Aaccel, float Adecell); bool vehicle_exiting( void ); vehicle * vehicle_remove( void ); void vehicle_add( vehicle *a_vehicle ); void lane_end( float & x_lane_end, float & y_lane_end); void set_signal(signal_state Asignal); void draw(void); private: int id; float position_x; float position_y; float speed_limit; direction moving; float length; signal_state signal; vehicle *vehicle_list; }; // end lane.h #endif // LANE_H_ // intersection.h for traffic simulation #ifndef INTERSECTION_H_ #define INTERSECTION_H_ #include "simulation.h" #include "vehicle.h" #include "lane.h" class intersection { public: intersection(float Ax_location, float Ay_location, intersection * Aintersection_list); void set_signal( intersection * Aintersection_list, signal_state Anorth_south, signal_state Aeast_west); void operate(intersection * Aintersection_list); void creator( float Alength, float Aweight, float Aaccel, float Adecell, direction Amoving); void connect_from( intersection * Aintersection_list); intersection * next(void); void draw(intersection * Aintersection_list); private: float x_location; float y_location; intersection * next_intersection; lane * lane_north; // fed by lane * lane_from_south; lane * lane_east; // fed by lane * lane_from_west; lane * lane_south; // fed by lane * lane_from_north; lane * lane_west; // fed by lane * lane_from_east; signal_state north_south; signal_state east_west; }; // end intersection.h #endif // INTERSECTION_H_ A somewhat working version of the complete program on Unix using X Windows is provided by these additional files: Makefile_traffic_sim traffic_sim.cc simulation.h simulation.cc vehicle.cc lane.cc intersection.cc x_plot_d.h x_plot_d.cc delay.h delay.c Some strange code may be due to this being designed for multiple platforms where the plot routine and delay are different. Other strange code is just due to the author.
see Syllabus and see homework assignment page for exam details
see homework assignment page for exam details
Last updated 5/1/01