- 组合:
新的类是已有类的对象组合而成。//Point类的定义及成员函数的实现 class Point { friend istream &operator>>(istream &is, Point &obj) { is >> obj.x >> obj.y; return is;} friend ostream &operator<<(ostream &os, const Point &obj) { os << "( " << obj.x << ", " << obj.y << " )"; return os;} private: double x,y; public: Point(double a = 0, double b = 0) {x = a; y = b;} double getx() const {return x;} double gety() const {return y;} };
//Segment类的定义及成员函数的实现 class Segment { friend istream &operator>>(istream &is, Segment &obj); friend ostream &operator<<(ostream &os, const Segment &obj); private: Point start; Point end; public: Segment(double x1=0,double y1=0, double x2=0, double y2=0):start(x1,y1), end(x2,y2) {} Segment(Point p1, Point p2) { start = p1; end = p2; } double getLength() const; Point getMid() const; Point getStart() const { return start; } Point getEnd() const { return end; } }; istream &operator>>(istream &is, Segment &obj) { cout << "请输入起点坐标:"; is >> obj.start; cout << "请输入终点坐标:"; is >> obj.end; return is; } ostream &operator<<(ostream &os, const Segment &obj) { os << obj.start << " - " << obj.end ; return os; } double Segment::getLength() const { double x1 = start.getx(), x2 = end.getx(), y1 = start.gety(), y2 = end.gety(); return sqrt((x2-x1 )*(x2-x1) + (y2-y1)*(y2-y1)); } Point Segment::getMid() const { return Point((start.getx() + end.getx())/2, (start.gety() + end.gety())/2); } //---------------------------------------------------------------- //Segment的使用 int main() { Point p1(1,1), p2(3,3);
Segment s1, s2(p1, p2); cout << s1 << '\n' << s2 << endl; cin >> s1; cout << s1.getStart() << s1.getEnd() << s1.getMid() << endl; return 0; }
//triangle类的定义及成员函数的实现 class triangle { Point p1; Point p2; Point p3; public: triangle(double x1=0,double y1=0, double x2=0, double y2=0, double x3=0, double y3=0):p1(x1,y1),p2(x2,y2),p3(x3,y3) {} triangle(Point pt1, Point pt2, Point pt3):p1(pt1), p2(pt2), p3(pt3) {} double area() const; double circum() const; void sideLen(double &len1,double &len2,double &len3) const; }; void triangle::sideLen(double &len1,double &len2,double &len3) const { len1 = Segment(p1, p2).getLength(); len2 = Segment(p1, p3).getLength(); len3 = Segment(p3, p2).getLength(); } double triangle::circum() const { double s1, s2, s3; sideLen(s1,s2,s3); return s1 + s2 + s3; } double triangle::area() const { double len1, len2, len3, p; sideLen(len1,len2,len3); p = (len1 + len2 + len3) / 2; return sqrt(p *(p-len1)*(p-len2)*(p-len3)); } //--------------------------------------------------- //triangle类的使用 int main() { Point p1(1,1), p2(3,1), p3(2,2); triangle t1(0,0,0,1,1,0), t2(p1,p2,p3); cout << t1.area() << " " << t1.circum() << '\n' << t2.area() << " " << t2.circum() << endl; return 0; }
- 继承:
在已有的类的基础上,对它进行扩展,形成一个新类。
用继承方式创建新类时,需要指明这个新类是在哪个已有类的基础上扩展的。
已有的类成为基类或者父类,继承实现的新类称为派生类或子类,派生类本身也可能会成为未来派生类的基类,派生出功能更强的类。
继承的作用:①支持软件重用。②对事物进行分类,使对象之间关系更加清晰。③支持软件的增量开发。
- 派生类:
定义形式:
继承方式:public、private(默认)、protectedclass 派生类名:继承方式 基类名 { 新增的成员声明; };
类中的访问方式:
继承方式及访问特性:public公有成员:能够被程序中的所有函数访问。 private私有成员:只能被自己的成员函数和友元访问。 protected被保护成员:是特殊的私有成员,不可以被全局函数或其他类的成员函数访问, 但能被派生类的成员函数和友元函数访问。没有继承关系时等同于private。
派生类初始化:一、基类public派生类时,基类的public成员会成为派生类的public成员, 基类的protected成员会成为派生类的protected成员。 二、基类protected派生类时,基类的public成员和protected成员都会成为派生类的protected成员。 三、基类private派生类时,基类的public成员和protected成员成为派生类的private成员。 通常的继承方式是public,它可以在派生类中保持基类的访问特性。
C++规定,派生类对象的初始化由基类和派生类共同完成,派生类的构造函数体只负责初始化新增加的数据成员,派生类在初始化列表中调用基类的构造函数初始化基类的数据成员。
构造函数形式:
基类构造函数中的参数值通常来源于派生类构造函数的参数表,也可以用常值。派生类构造函数名(参数表):基类构造函数名(参数表){ ... }
先执行基类的构造函数,再执行派生类的构造函数。
如果派生类中的数据成员有对象成员,创建对象时,先执行基类的构造函数,再执行对象成员的构造函数,最后执行自己的构造函数。
如果基类是通过默认构造函数初始化,派生类构造函数的初始化列表可以不出现基类构造函数调用,隐式调用。
析构过程:派生类的析构函数会自动调用基类的析构函数,先执行派生类的析构函数,再执行基类的析构函数。
重定义基类的函数:class pool { double area; double depth; public: pool(double a = 200, double d = 2):area(a),depth(d) {} double getArea() const { return area; } double getDepth() const {return depth;} }; class swimmingPool : public pool { char time[15]; double price; public: swimmingPool(double a, double d, char *t, double p):pool(a, d) { strcpy(time, t); price = p; } swimmingPool() { time[0] = '\0'; price = 0; } void setTime(char *t) { strcpy(time, t); } void setPrice(double p) { price = p; } const char *getTime() const { return time; } double getPrice() const { return price; } }; class fishPond : public pool { char type[15]; double quantity; public: fishPond(double a, double d, char *t, double p):pool(a, d) { strcpy(type, t); quantity = p; } fishPond() { type[0] = '\0'; quantity = 0; } void setType(char *t) { strcpy(type, t); } void setQuantity(double p) { quantity = p; } const char *getType() const { return type; } double getQuantity() const { return quantity; } };
由于派生类的成员函数不能访问基类的私有成员,必须通过基类的公有成员函数实现,调用基类的公有函数,可以在函数名前加上基类名的限定。void display()const { car::display(); cout<<'\t'<<seat<<'\t'<<price<<endl; }
- 派生类赋值运算符重载:
派生类不能继承基类的构造函数,但可以调用基类的赋值运算符重载函数。如果派生类没有定义赋值运算符重载,系统会为它提供默认赋值运算符重载,派生类的基类对象调用基类的赋值运算符重载函数赋值。
如果默认赋值运算符不能满足派生类要求,可以在派生类中重载赋值运算符,需要显式的调用基类的赋值运算符函数实现基类成员的赋值。// 为People和Student类的重载赋值运算符,注意派生类的赋值运算符重载函数中对基类对象的复制 People &operator=(const People &other) { if (this == &other) return *this; delete name; name = new char[strlen(other.name) + 1]; strcpy(name, other.name); age = other.age; return *this; } Student &operator=(const Student &other) { if (this == &other) return *this; s_no = other.s_no; delete class_no; class_no = new char[strlen(other.class_no) + 1]; strcpy(class_no, other.class_no); People::operator=(other); return *this; }
派生类作为基类:
//派生类作为基类实例 class Base{ int x; public: Base(int xx) {x = xx; cout << "constructing base\n";} ~Base() { cout << "destructint base\n";} }; class Derive1:public Base{ int y; public: Derive1(int xx, int yy): Base(xx) { y = yy; cout << "constructing derive1\n";} ~Derive1() { cout << "destructing derive1\n";} }; class Derive2:public Derive1{ int z; public: Derive2(int xx, int yy, int zz):Derive1(xx, yy) { z = zz; cout << "constructing derive2\n";} ~Derive2() { cout << "destructing derive2\n";} };
派生类对象与基类对象的转换:
C++规定派生类对象可以自动转换成基类对象,而不必定义类型转换函数。
将派生类对象赋给基类对象、将基类指针指向派生类对象,以及定义一个引用派生类对像的基类对象时,会执行自动类型转换。
将派生类中的基类部分赋给此基类对象,派生类新增加的成员就会被丢弃,赋值后,基类对象和派生类对象再无任何关系。
当一个基类指针指向派生类对象时,由于它本身是一个基类指针,只能解释基类成员,而不能解释派生类新增的成员,因此,指向派生类的基类指针只能访问派生类中基类部分,尽管这个派生类中有与基类相同的成员函数,但是基类调用基类的成员函数(未实现多态性)。
引用是一种隐式指针,当用一个基类对象引用派生类对象时,相当于给派生类中的基类部分取了别名,对基类对象引用的修改就是对派生类中基类部分的修改
派生类的对象可以隐式的转换成基类的对象,但是基类对象无法隐式转换成派生类对象,因为无法解释派生类新增加的成员,除非在基类定义一个想派生类转换的类型转换函数,才能将基类对象转换成派生类对象。Derived d(1,2); Base &br = d;
同样不能将基类对象的地址赋给派生类的指针,或将一个基类指针赋给一个派生类的指针,即使该基类指针指向的就是一个派生类的对象。Derived d, *dp; Base *bp = &d; dp = bp; //编译器报错 dp = reinterpret_cast<Derived *>bp; //强制转换
- 多态性:
多态性,相当于对象有主观能动性。
编译时的多态性(静态绑定)
运行时的多态性(动态绑定):通过虚函数和基类指针指向不同的派生类的对象来实现
基类指针或基类引用可以访问派生类对象的基类部分,而不能访问派生类新增的成员,但如果基类的成员函数定义为虚函数时,表示该函数在派生类中可能有不同的实现,基类指针调用该虚函数时,首先会到派生类检查函数是否被重新定义,如果派生类重新定义了这个函数,则执行派生类中的函数,否则执行基类的函数。
每个派生类都可以重新定义虚函数,当用基类的指针指向不同的派生类的对象时,会调用不同的函数,实现运行时的多态性。
虚函数:
定义形式:函数原型声明前面加上关键字virtual
派生类重新定义时,它的函数原型(返回值类型、函数名、参数个数和参数类型)必须与基类中的虚函数完全相同,否则会被认为是两个重载函数。
可以 定义一个指向基类的指针数组,让它的每个元素指向基类或不同派生类的对象// 虚函数的定义,其中的area和display函数都是虚函数 class Shape{ protected: double x, y; // x、y是图形的位置 public: Shape(double xx, double yy) {x = xx; y = yy;} virtual double area() const {return 0.0;} virtual void display() const { cout << "This is a shape. The position is (" << x << ", " << y << ")\n";} }; class Rectangle:public Shape { protected: double w, h; // w、h是矩形的宽和高 public: Rectangle(double xx, double yy, double ww, double hh): Shape(xx,yy),w(ww),h(hh){} double area() const {return w * h;} //重定义虚函数area void display() const //重定义虚函数display { cout << "This is a rectangle. The position is (" << x << ", " << y << ")\t"; cout << "The width is " << w << ". The height is " << h << endl; } }; class Circle:public Shape { protected: double r; // r是圆的半径 public: Circle(double xx, double yy, double rr): Shape(xx,yy),r(rr){} double area() const {return 3.14 * r * r;} void display() const { cout << "This is a Circle. The position is (" << x << ", " << y << ")\t"; cout << "The radius is " << r << endl; } };
注意:①派生类重新定义虚函数时,原型必须于基类中的虚函数完全相同,否则编译器会认为是重载函数。Shape *sp[3] = {&s, &rect, &c};
②派生类对基类的虚函数重定义时,关键字virtual可以写也可以不写,但最好是在重定义时写上virtual。 - 虚析构函数:
构造函数不能是虚函数,但析构函数可以是虚函数,而且最好是虚函数
如果派生类新增加的数据成员中含有指针,指向动态申请的内存,那么派生类必须定义析构函数释放这部分空间,如果单纯delete基类指针指向的派生类对象时,会造成内存泄漏。
将基类的析构函数定义为虚函数,当基类指针指向的对象析构时,通过基类指针会找到派生类的析构函数,执行派生类的析构函数,派生类的析构函数在执行时会自动调用基类的析构函数,因此基类和派生类的析构函数都被执行,这样就把派生类的对象完全析构掉。 - 纯虚函数:
基类往往只表示一种抽象的意志,而不与具体事物相联系。
纯虚函数是一个在基类中声明的虚函数,在基类中没有定义,但要求派生类定义自己的版本。
声明形式:virtual 返回类型 函数名(参数表)=0; virtual double area()const = 0;
- 抽象类:
如果一个类至少含有一个纯虚函数,被成为抽象类。
如果抽象类的派生类没有重新定义此纯虚函数,只是继承了基类的纯虚函数,那么派生类仍然是一个抽象类。
因为抽象类有未定义全的函数,所以无法定义抽象类的对象,因为一旦对此对象调用纯虚函数,该函数无法执行。
但可以定义指向抽象类的指针,作用是指向派生类对象,以实现多态性。
抽象类的作用是保证进入继承层次的每个类都具有纯虚函数所要求的行为,保证围绕这个继承层次所建立的类都具有抽象类规定的行为,保证软件系统的正常运行,避免这个继承层次中的用户由于偶尔失误(忘了所建立的派生类提供继承层次所要求的行为)影响系统的正常运行。
- 小结:
组合是将一个或一组已定义类的对象作为当前类的数据成员,从而得到一个更强的类,用组合方式定义类时,对象成员的初始化一般是通过在构造函数的初始化
列表中调用对象成员的构造函数实现的
继承是在已有类的基础上加以扩展,形成一个新类,成为派生类,派生类的定义需要指定基类,以及在基类的基础上扩展哪些数据成员和成员函数。构造派生类对象时,由派生类的构造函数为新增的数据成员赋初值,而基类部分的初始化是调用基类的构造函数完成的。
运行时的多态性是通过虚函数和基类指针指向派生类对象实现的。