目录
(1)继承概念与语法
(2)派生类的访问控制
(3)继承中的构造和析构
1.类型兼容性原则
2.继承中的构造析构调用原则
3.继承与组合混搭下构造和析构调用原则
(4)同名成员(函数)的继承
(5)static成员的继承
(6)多继承与虚继承
1.多继承的语法
2.多继承的二义性
3.用虚继承解决二义性
4.虚继承的实现原理
5.虚继承解决二义性的作用也有限
(1)继承概念与语法
类之间的关系:has-A,uses-A 和 is-A
- has-A 包含关系,⽤以描述⼀个类由多个“部件类”构成。实现has-A关系⽤类成员表示, 即⼀个类中的数据成员是另⼀种已经定义的类。
- uses-A ⼀个类部分地使⽤另⼀个类。通过类之间成员函数的相互联系,定义友员或对象参 数传递实现。
- is-A 机制称为“继承”。关系具有传递性,不具有对称性。
继承是类之间定义的一种重要关系。一个B类继承A类,或称从类A派生类B。类A称为基类(父类),类B称为派生类(子类)。
继承的重要作用就在于随着业务的不断变化,提出了新的业务需求,需要开发新的类,但又仍需要父类的某些功能,因此有了继承这个概念,子类继承自父类,可以继续使用父类的某些属性或者功能,同时又能有专属自己的功能。
类继承的语法:
// 单继承
class 子类名: 访问控制关键字 父类名
{数据成员和成员函数声明
};// 多继承
class 子类名: 访问控制关键字 父类名1, 访问控制关键字 父类名2, ...
{数据成员和成员函数声明
};
访问控制关键字表示子类对父类的继承方式:
public 公有继承
private 私有继承
protected 保护继承
继承重要说明:
- ⼦类拥有⽗类的所有成员变量和成员函数
- ⼦类可以拥有⽗类没有的⽅法和属性
- ⼦类就是⼀种特殊的⽗类
- ⼦类对象可以当作⽗类对象使用(比如函数参数要求传递父类对象,传递子类对象也可)
如何访问继承的成员或者函数:假设a是父类Parent的成员,child为Child的实例化对象,则可以通过这两种方式访问:
- child.Parent::a
- child.a
关于继承方式:采用不同的继承方式会影响从父类手中接盘资产的访问属性,这个将在下一节讲解,这里以public继承为例,子类将原封不动地接盘父类的全部资产,且资产属性不变。
示例代码
#include <iostream>class Parent
{
public:int b;
public:void print(){a = 0;b = 0;std::cout << "a = " << a << std::endl;std::cout << "b = " << b << std::endl;}private:int a;
protected:
};// 子类继承父类
// class Child : private Parent
// class Child : protected Parent
class Child : public Parent
{
public:
private:int c;
protected:
};int main()
{Child c1;c1.print();std::cout << "c1.b = " << c1.b << std::endl;// 也可以这样访问继承的成员std::cout << "c1.b = " << c1.Parent::b << std::endl; return 0;
}
运行结果
a = 0
b = 0
c1.b = 0
c1.b = 0
(2)派生类的访问控制
1.理解成员属性
- public: 修饰的成员变量⽅法 ,在类的内部和类的外部都能使⽤
- protected: 修饰的成员变量⽅法,在父类和子类的内部可⽤ ,在外部不能被使⽤
- private: 修饰的成员变量⽅法,只能在类的内部使⽤,不能在类的外部,不可继承
2.不同的继承方式会改变继承成员的访问属性
派⽣类继承了基类的全部成员变量和成员方法(除了构造和析构之外的成员方法),但是这些成 员的访问属性,在派生过程中是可以调整的。
- public继承:⽗类成员在⼦类中保持原有访问级别
- private继承:⽗类成员在⼦类中变为private成员
- protected继承:
- 父类中public成员会变成protected
- ⽗类中protected成员仍然为protected
- ⽗类中private成员仍然为private
注意:private成员在⼦类中依然存在,但是却⽆法访问到。不论种⽅式继承基类,派⽣类都不能直接 使⽤基类的私有成员。
项目开发中 一般情况下 是 class B : public A。
示例代码
#include <iostream>
#include <string>class Parent
{
public:Parent(){name = "parent";password = 123;emotion_friend = 0;}
public:std::string name; //老爹的名字
protected:int password; //老爹的银行密码
private:int emotion_friend; //老爹的情人 // 私有的无论怎样继承,儿子都没法访问
};// 公有继承
class Child1 : public Parent
{
public:void useVar(){std::cout << name << std::endl; // okstd::cout << password << std::endl; // ok// std::cout << emotion_friend << std::endl; // errorreturn;}
};// 私有继承
class Child2 : private Parent
{
public:void useVar(){std::cout << name << std::endl; // okstd::cout << password << std::endl; // ok// std::cout << emotion_friend << std::endl; // errorreturn;}
};// 保护继承
class Child3 : protected Parent
{
public:void useVar(){std::cout << name << std::endl; // okstd::cout << password << std::endl; // ok// std::cout << emotion_friend << std::endl; // errorreturn;}
};int main()
{// 1.公有继承std::cout << "----------------" << std::endl;Child1 c1;c1.name = "child1"; // ok// c1.password = 456; // err,因为protected不能外部访问// c1.emotion_friend = 1; // err,因为private不能外部访问c1.useVar();// 2.私有继承std::cout << "----------------" << std::endl;Child2 c2;// c2.name = "child1"; // err,因为private不能外部访问// c2.password = 456; // err,因为private不能外部访问// c1.emotion_friend = 1; // err,因为private不能外部访问c2.useVar();// 3.保护继承std::cout << "----------------" << std::endl;Child3 c3;// c3.name = "child1"; // err,因为protected不能外部访问// c3.password = 456; // err,因为protected不能外部访问// c3.emotion_friend = 1; // err,因为private不能外部访问c3.useVar();return 0;
}
运行结果
----------------
child1
123
----------------
parent
123
----------------
parent
123
(3)继承中的构造和析构
1.类型兼容性原则
类型兼容规则是指在需要基类对象的任何地⽅,都可以使⽤公有派⽣类的对象来替代。通过公有 继承,派⽣类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具 备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。
类型兼容规则中所指的替代包括以下情况:
- 子类对象可以当作父类对象使用
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象
- 父类引⽤可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使⽤,但是只能使用从基类继承的成员。
总结:子类就是特殊的⽗类 (base *p = &child;)
#include <iostream>
#include <string>class Parent
{
public:void printP(){std::cout << "I'm Parent" << std::endl;}
public:Parent(){std::cout << "Parent Normal Generate Func" << std::endl;}Parent(Parent &obj){std::cout << "Parent Copy Generate Func" << std::endl;}
private:int a;
};class Child : public Parent
{
public:void printC(){std::cout << "I'm Child" << std::endl;}
protected:
private:int c;
};void PrintParent(Parent *p)
{p->printP();
}
void PrintParent(Parent &base)
{base.printP();
}int main()
{Parent parent1; // Parent Normal Generate Funcparent1.printP(); // I'm Parent// 使用继承来的成员Child child1; // Parent Normal Generate Funcchild1.printC(); // I'm Childchild1.printP(); // I'm Parent//父类指针指向子类对象Parent *p = NULL;p = &child1;p->printP(); // I'm Parent//指针做函数参数PrintParent(&parent1); //I'm ParentPrintParent(p); //I'm Parent//引用做函数PrintParent(child1); //I'm ParentPrintParent(parent1); //I'm Parent//子类对象 初始化 父类对象Parent parent2 = child1; // Parent Copy Generate Funcparent2.printP(); //I'm Parentreturn 0;
}
2.继承中的构造析构调用原则
问题:如何初始化父类成员?父类与子类的构造函数有什么关系
- 在子类对象构造时,需要调用父类构造函数对其继承得来的成员进⾏初始化
- 子类对象在创建时会首先调⽤父类的构造函数
- 父类构造函数执行结束后,执行子类的构造函数
- 当父类的构造函数有参数时,需要在子类的初始化列表中显示调⽤
- 在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理
- 析构函数调⽤的先后顺序与构造函数相反
示例代码
#include <iostream>class Parent
{
public:Parent(int a = 0, int b = 0){this->a = a;this->b = b;std::cout << "Parent Normal Generate Func" << std::endl;}Parent(const Parent &obj){std::cout << "Parent Copy Generate Func" << std::endl;}~Parent(){std::cout << "Parent Destroyed Func" << std::endl;}
private:int a;int b;
};class Child : public Parent
{
public:Child(int a, int b, int c):Parent(a, b){this->c = c;std::cout << "Child Normal Generate Func" << std::endl;}~Child(){std::cout << "Child Destroyed Func" << std::endl;}
protected:
private:int c;
};int main()
{Child c1(1, 2, 5);return 0;
}
运行结果
Parent Normal Generate Func
Child Normal Generate Func
Child Destroyed Func
Parent Destroyed Func
3.继承与组合混搭下构造和析构调用原则
构造顺序:先祖宗类构造 → 父类构造 → 成员变量构造 → 再自己构造
析构顺序:先自己析构 → 再成员变量析构 → 再父类析构 → 再祖宗类析构
示例代码
#include <iostream>// 祖宗类
class GrandParent
{
public:GrandParent(int a, int b){this->a = a;this->b = b;std::cout << "GrandParent Normal Generate Func: " << a << "," << b << std::endl;}~GrandParent(){std::cout << "GrandParent Destroyed Func" << std::endl;}
private:int a;int b;
};// 父类,继承于祖宗类
class Parent: public GrandParent
{
public:Parent(char *p): GrandParent(1, 2){this->p = p;std::cout << "Parent Normal Generate Func: " << p << std::endl;}~Parent(){std::cout << "Parent Destroyed Func" << std::endl;}
private:char *p;
};// 子类,继承于父类
class Child : public Parent
{
public:Child(char *p):Parent(p), gp1(3, 4), gp2(5, 6){this->cp = p;std::cout << "Child Normal Generate Func: " << p << std::endl;}~Child(){std::cout << "Child Destroyed Func" << std::endl;}
public:
protected:char *cp;GrandParent gp1;GrandParent gp2;
};int main()
{char data[] = "test";std::cout << "-----------Generate-----------" << std::endl;Child c1(data);std::cout << "-----------Destroy-----------" << std::endl;return 0;
}
运行结果
-----------Generate-----------
GrandParent Normal Generate Func: 1,2
Parent Normal Generate Func: test
GrandParent Normal Generate Func: 3,4
GrandParent Normal Generate Func: 5,6
Child Normal Generate Func: test
-----------Destroy-----------
Child Destroyed Func
GrandParent Destroyed Func
GrandParent Destroyed Func
Parent Destroyed Func
GrandParent Destroyed Func
(4)同名成员(函数)的继承
当子类成员变量与父类成员变量同名时
- 子类依然从⽗类继承同名成员
- 在⼦类中通过作⽤域分辨符 :: 进⾏同名成员区分。在⼦类外部⽤ childObj.Parent::name 和 childObj.Child::name,在⼦类内部⽤Parent::name和Child::name。
- 同名成员变量和成员函数通过作⽤域分辨符进⾏区分。调⽤name默认是childObj.Child::name和 Child::name。
- 同名成员存储在内存中的不同位置
示例代码
#include <iostream>class Parent
{
public:Parent(int a = 0, int b = 0){this->a = a;this->b = b;}
public:void print(){ std::cout << "print parent" << std::endl;}void get(){ std::cout << "a=" << a << ",b=" << b << std::endl;}
public:int a;int b;
};class Child : public Parent
{
public:Child(int a=0, int b=0, int c=0):Parent(a, b){this->b = b;this->c = c;}
public:void print(){ std::cout << "print child" << std::endl;}void get(){ std::cout << "a=" << a << ",b=" << b << ",c=" << c << std::endl;}
public:int b;int c;};int main()
{Child c1(1, 2, 3);// 1.同名变量std::cout << c1.b << std::endl; // 2std::cout << c1.Child::b << std::endl; // 2std::cout << c1.Parent::b << std::endl; // 2//修改父类的bc1.Parent::b = 100;std::cout << c1.Parent::b << std::endl; // 100std::cout << c1.Child::b << std::endl; // 2std::cout << c1.b << std::endl; // 2// 2.同名函数c1.print(); // print childc1.Child::print(); // print child //默认情况c1.Parent::print(); // print parentc1.get(); // a=1,b=2,c=3c1.Child::get(); // a=1,b=2,c=3c1.Parent::get(); // a=1,b=100return 0;
}
(5)static成员的继承
- 基类定义的静态成员,将被所有派生类共享
- 根据静态成员自身的访问特性和派生类的继承⽅式,在类层次体系中具有不同的访问性质 (遵守派生类的访问控制)
- 在派生类的内部访问静态成员,直接变量名即可:
- 在派生类中的外部访问静态成员,⽤以下形式:
- 类名 :: 成员
- 对象名 . 成员
示例代码
#include <iostream>class Parent
{
public:Parent(){std::cout << "Parent generate func" << std::endl;}
public:void printa(){std::cout << "class Parent call: static a=" << a << std::endl;}
public:static int a;int b;
protected:
private:
};// 静态变量的初始化
int Parent::a = 100; //这句话 不是简单的变量赋值
// 更重要的是 要告诉C++编译器 你要给我分配内存
// 我在继承类中 用到了a 不然会报错..class Child : private Parent
{
public:Child(){std::cout << "Child generate func" << std::endl;}
public:void printa(){std::cout << "class Child call: static a=" << a << std::endl;}
public:int b;int c;
protected:
private:
};// 1 static关键字 遵守 派生类的访问控制规则
// 2 不是简单的变量赋值 更重要的是 要告诉C++编译器
// 你要给我分配内存 ,我再继承类中 用到了a 不然会报错..void objplay()
{Parent parent1; parent1.printa(); Child child1; child1.printa();// child1.a = 200; //非法,因为是私有继承,只能在类的内部共享std::cout << Parent::a << std::endl; // 访问同一个静态成员std::cout << parent1.a << std::endl; // 访问同一个静态成员// std::cout << child1.a << std::endl; // 将会error,因为私有继承
}int main()
{objplay();return 0;
}
运行结果
Parent generate func
class Parent call: static a=100
Parent generate func
Child generate func
class Child call: static a=100
100
100
(6)多继承与虚继承
1.多继承的语法
- 多个基类的派生类构造函数可以用初始化列表调用基类构造函数初始化数据成员
- 执⾏顺序与单继承构造函数情况类似。多个直接基类构造函数执⾏顺序取决于定义派⽣类时 指定的各个继承基类的顺序。
- ⼀个派⽣类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现⼆义性。如果不 同的基类有同名成员,派⽣类对象访问时应该加以识别。
#include <iostream>class Base1
{
public:Base1(int b1){std::cout << "Base1 generate func" << std::endl;this->b1 = b1;}~Base1(){std::cout << "Base1 destroy func" << std::endl;}void printB1(){std::cout << "b1:" << b1 << std::endl;;}
protected:
private:int b1;
};class Base2
{
public:Base2(int b2){std::cout << "Base2 generate func" << std::endl;this->b2 = b2;}~Base2(){std::cout << "Base2 destroy func" << std::endl;}void printB2(){std::cout << "b2:" << b2 << std::endl;;}
protected:
private:int b2;
};class Child : public Base1, public Base2
{
public:Child(int b1, int b2, int c) : Base2(b2), Base1(b1){std::cout << "Child generate func" << std::endl;this->c = c;}~Child(){std::cout << "Child destroy func" << std::endl;}void printC(){std::cout << "c:" << c << std::endl;;}
protected:
private:int c;
};int main()
{Child c1(1, 2, 3);c1.printC();c1.printB1();c1.printB2();return 0;
}
运行结果
Base1 generate func
Base2 generate func
Child generate func
c:3
b1:1
b2:2
Child destroy func
Base2 destroy func
Base1 destroy func
2.多继承的二义性
示例代码
#include <iostream>class B
{
public:int b;
};class B1 : public B
{
public:int b1;
};class B2 : public B
{
public:int b2;
};class C : public B1, public B2
{
public:int c;
};int main()
{C c1;c1.b1 = 100;c1.b2 = 200;c1.c = 300;// c1.b = 500; //这句会报错,因为二义性,// 到底是B1的的b,还是B2的b分不清return 0;
}
3.用虚继承解决二义性
- 如果⼀个派生类从多个基类派⽣,⽽这些基类⼜有⼀个共同的基类,则在对该基类中声明的 名字进⾏访问时,可能产⽣⼆义性
- 如果在多条继承路径上有⼀个公共的基类,那么在继承路径的某处汇合点,这个公共基类就 会在派生类的对象中产生多个基类子对象
- 要 使这个公共基类在派生类中只产生⼀个子对象,必须对这个基类声明为虚继承,使这个基 类成为虚基类
- 虚继承声明使用关键字:virtual
像下面这样
示例代码
#include <iostream>class B
{
public:int b;
};class B1 : virtual public B
{
public:int b1;
};class B2 : virtual public B
{
public:int b2;
};class C : public B1, public B2
{
public:int c;
};int main()
{C c1;c1.b1 = 100;c1.b2 = 200;c1.c = 300;c1.b = 500; //因为virtual继承,这里将不会再报错std::cout << c1.B1::b << std::endl; // 500c1.B1::b = 600;std::cout << c1.b << std::endl; // 600c1.B2::b = 700;std::cout << c1.b << std::endl; // 700c1.B::b = 1000;std::cout << c1.b << std::endl; // 1000return 0;
}
4.虚继承的实现原理
如果继承的多个基类有⼀个共同的基类,则⽤虚继承时只会把⽼祖宗的构造函数只只执⾏⼀次, 而不是执行多次使得产生⼆义性。
示例代码
#include <iostream>class B
{
public:B(){std::cout << "B copy generate func" << std::endl;}int b;
};class B1 : virtual public B
{
public:int b1;
};class B2 : virtual public B
{
public:int b2;
};class B3 : virtual public B
{
public:int b3;
};class B4 : virtual public B
{
public:int b4;
};class C1 : public B1, public B2
{
public:int c1;
};class C2 : public B3, public B4
{
public:int c2;
};int main()
{//加上virtual以后 , C++编译器会在给变量偷偷增加属性std::cout << "----------------------" << std::endl;std::cout << sizeof(B) << std::endl;std::cout << sizeof(B1) << std::endl;std::cout << sizeof(B2) << std::endl;std::cout << sizeof(B3) << std::endl;std::cout << sizeof(B4) << std::endl;std::cout << "----------------------" << std::endl;C1 c1;c1.b1 = 100;c1.b2 = 200;std::cout << "----------------------" << std::endl;C2 c2;c2.b3 = 500;c2.b4 = 600;c2.b = 700;c2.B3::b3 = 1000;c2.B::b = 99;return 0;
}
运行结果
----------------------
4
16
16
16
16
----------------------
B copy generate func
----------------------
B copy generate func
5.虚继承解决二义性的作用也有限
对于下图右边这种多继承,虚继承也无法解决⼆义性。
只能通过作用域符号来明确,c.B1::k,c.B2::k
end