继续进行一些博客的水经验帖。
Lec.7 组合与继承
对象组合:”has a”
组合:“对象套对象”
1、两种访问成员对象的接口:
2、子对象构造时若需要参数,则应在当前类的构造函数的初始化列表中进行。若使用默认构造函数来构造子对象,则不用做任何处理。(会自动调用子对象的默认构造函数!)
e.g.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class C3 { int num; S1 sub_obj1; S2 sub_obj2; public: C3() : num(0), sub_obj1(123) { cout << "C3()" << endl; } C3(int n) : num(n), sub_obj1(123) { cout << "C3(int)" << endl; } C3(int n, int k) : num(n), sub_obj1(k) { cout << "C3(int, int)" << endl; } ~C3() { cout << "~C3()" << endl; } };
int main() { C3 a, b(1), c(2), d(3, 4); return 0; }
|
3、对象构造与析构函数的次序
•先完成子对象构造,再完成当前对象构造
•子对象构造的次序仅由在类中声明的次序所决定
•析构函数的次序与构造函数相反
4、隐式定义的拷贝构造与拷贝赋值
•如果调用拷贝构造函数且没有给类显式定义拷贝构造函数,编译器将提供“隐式定义的拷贝构造函数”。该函数的功能为:
•递归调用所有子对象的拷贝构造函数——具体取决于子对象的拷贝构造函数的定义方式与功能
•对于基础类型,采用位拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <iostream> using namespace std;
class C1{ public: int i; C1(int n):i(n){} C1(const C1 &other) {i=other.i; cout << "C1(const C1 &other)" << endl;} };
class C2{ public: int j; C2(int n):j(n){} C2& operator= (const C2& right){ if(this != &right){ j = right.j; cout << "operator=(const C2&)" << endl; } return *this; } }; class C3{ public: C1 c1; C2 c2; C3():c1(0), c2(0){} C3(int i, int j):c1(i), c2(j){} void print(){cout << "c1.i = " << c1.i << " c2.j = " << c2.j << endl;} }; int main(){ C3 a(1, 2); C3 b(a); cout << "b: "; b.print(); cout << endl;
C3 c; cout << "c: "; c.print(); c = a; cout << "c: "; c.print(); return 0; }
|
对象继承:”is-a”
一般——特殊;父——子;特殊类
•如果类A具有类B全部的属性和服务,而且具有自己特有的某些属性或服务,则称A为B的特殊类,B为A的一般类。
•如果类A的全部对象都是类B的对象,而且类B中存在不属于类A的对象,则A是B的特殊类,B是A的一般类。
1、定义与分类
被继承的已有类,被称为基类(base class),也称“父类”。
通过继承得到的新类,被为派生类(derived class,也称“子类”、“扩展类”。
继承的内容:数据成员,函数成员
常见的继承方式:public, private——继承关键字
- class Derived : [private] Base { .. }; 缺省继承方式为private继承。
- class Derived : public Base { … };
- protected 继承很少被使用
class Derived : protected Base { … };
2、什么没法被继承?
•构造函数:创建派生类对象时,必须调用派生类的构造函数,派生类构造函数调用基类的构造函数,以创建派生对象的基类部分。C++11新增了继承构造函数的机制(使用using),但默认不继承
•析构函数:释放对象时,先调用派生类析构函数,再调用基类析构函数
•赋值运算符:编译器不会继承基类的赋值运算符(因为其参数为基类)
•但会自动合成隐式定义的赋值运算符(参数为派生类),其功能为调用基类的赋值运算符。
•友元函数:不是类成员
e.g.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> using namespace std;
class Base{ public: int k = 0; void f(){cout << "Base::f()" << endl;} Base & operator= (const Base &right){ if(this != &right){ k = right.k; cout << "operator= (const Base &right)" << endl; } return *this; } }; class Derive: public Base{}; int main(){ Derive d, d2; cout << d.k << endl; d.f(); Base e; d = d2; return 0; }
|
派生类的构造与析构
1、
- 基类中的数据成员,通过继承成为派生类对象的一部分,需要在构造派生类对象的过程中调用基类构造函数来正确初始化。
若没有显式调用,则编译器会自动调用基类的默认构造函数。
若想要显式调用,则只能在派生类构造函数的初始化成员列表中进行,既可以调用基类中不带参数的默认构造函数,也可以调用合适的带参数的其他构造函数。
- 先执行基类的构造函数来初始化继承来的数据,再执行派生类的构造函数。
- 对象析构时,先执行派生类析构函数,再执行由编译器自动调用的基类的析构函数。
例子见ppt
2、继承构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Base { int data; public: Base(int i) : data(i) { cout << "Base::Base(" << i << ")\n"; } Base(int i, int j) { cout << "Base::Base(" << i << “," << j << ")\n";} }; class Derive : public Base { public: using Base::Base; ///相当于 Derive(int i):Base(i){}; ///加上 Derive(int i, int j):Base(i,j){}; }; int main() { Derive obj1(356); Derive obj2(356, 789); return 0; } // g++ 1.cpp –o 1.out -std=c++11
|
- 如果基类的某个构造函数被声明为私有成员函数,则不能在派生类中声明继承该构造函数。
- 如果派生类使用了继承构造函数,编译器就不会再为派生类生成隐式定义的默认构造函数。(如上例,Derive obj;无法通过)
前面的例子,不能用Derive dr;定义对象
3、几种继承方式的比较
public继承
基类的公有成员,保护成员,私有成员作为派生类的成员时,都保持原有的状态。
private继承
基类的公有成员,保护成员,私有成员作为派生类的成员时,都作为私有成员。
protected继承
基类的公有成员,保护成员作为派生类的成员时,都成为保护成员,基类的私有成员仍然是私有的。
私有继承中的访问权限距离
”照此实现“:基类接口(公有成员函数)在派生类的成员函数中可以调用,故可以通过基类接口实现派生类功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> using namespace std; class Base { public: void baseFunc() { cout << "in Base::baseFunc()..." << endl; } };
class Derive2: private Base { public: void deriveFunc() { cout << “in Derive2::deriveFunc(), calling Base::baseFunc()..." << endl; baseFunc(); } };
int main() { Derive2 obj2; cout << "calling obj2.deriveFunc()..." << endl; obj2.deriveFunc(); return 0; }
|
基类中的私有成员在派生类中无法访问!!!!!!“基类私有”的概念
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Base{ private: int a{0}; protected: int b{0}; };
class Derive : private Base{ public: void getA(){cout<<a<<endl;} void getB(){cout<<b<<endl;} };
int main() { Derive d; d.getB(); return 0; }
|
”打开权限“后可以访问私有继承的基类部分函数
1 2 3 4
| class Base {private: int data{0};public: int getData(){ return data;} void setData(int i){ data=i;}}; class Derive1 : private Base {public: using Base::getData;}; int main() { Derive1 d1; cout<<d1.getData(); return 0;}
|
Summary:”取交集“
解释:
派生类成员函数只有一行no:基类私有一定要彻底私有,无法通过任何方式访问
派生类对象只有一个yes:public∩public=public,可以访问;其他都不行
保护成员:无法通过派生类对象访问,但派生类成员函数可以访问
组合与继承的作用
组合与继承的优点:支持增量开发。
允许引入新代码而不影响已有代码正确性。
- 相似:
实现代码重用。
将子对象引入新类。
使用构造函数的初始化成员列表初始化。
- 不同:
组合:
嵌入一个对象以实现新类的功能。
has-a 关系。
继承:
沿用已存在的类提供的接口。
public 继承:is-a。
private 继承:is-implementing-in-terms-of。
重写隐藏
重载(overload):
目的:提供同名函数的不同实现,属于静态多态。
函数名必须相同,函数参数必须不同,作用域相同(如位于同一个类中;或同名全局函数)。
重写隐藏(redefining):
- 目的:在派生类中重新定义基类函数,实现派生类的特殊功能。
- 屏蔽了基类的所有其它同名函数。
- 函数名必须相同,函数参数可以不同
1、重写隐藏发生时,基类中该成员函数的其他重载函数都将被屏蔽掉,不能提供给派生类对象使用
2、可以在派生类中通过using 类名::成员函数名; 在派生类中“恢复”指定的基类成员函数(即去掉屏蔽),使之重新可用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream> using namespace std; class T {}; class Base { public: void f() { cout << "B::f()\n"; } void f(int i) { cout << "Base::f(" << i << ")\n"; } void f(double d) { cout << "Base::f(" << d << ")\n"; } void f(T) { cout << "Base::f(T)\n"; } }; class Derive : public Base { public: void f(int i) { cout << "Derive::f(" << i << ")\n"; } }; int main() { Derive d; d.f(10); d.f(4.9); return 0; }
|
3、补充:using关键字的功能
多重继承