文章目录
一、继承访问权限测试
1.1 设计类A具有public, protected, private等不同属性的成员函数或变量;
class A{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
1.2 类B通过public, protected, private等不同方式继承A,在类B的成员函数中测试访问A的成员函数或变量;
//公共继承
class B1:public A{
public:
void fun1(){
m_A;//可以访问public 权限为public
m_B;//可以访问protected 权限为protected
//m_C;// 不能访问父类的private
}
};
//保护继承
class B2:protected A{
public:
void fun2(){
m_A;//可以访问public 权限为protected
m_B;//可以访问protected 权限为protected
//m_C;//不能访问父类的private
}
};
//私有继承
class B3:private A{
public:
void fun3(){
m_A;//可以访问public 权限为private
m_B;//可以访问protected 权限为private
//m_C;//不能访问父类的private
}
};
综上,公共继承的属性权限不变;保护继承的属性权限到子类只有public变为protected,其他两类不变;私有继承的属性权限到子类全为private。注意一点:父类的私有属性不论被这三种任意一种方式继承,还是私有,故它的继承类是无法访问父类的私有属性的。
1.3 在类B中添加public, protected, private等不同属性的成员函数或变量,在外部测试访问B的各个成员函数或变量;
//新建B类,定义三种成员变量
class B{
public:
int m_BA;
protected:
int m_BB;
private:
int m_BC;
};
//在外部访问B类这三种变量
void test01(){
B b;
b.m_BA;//在外部可以访问B类的public成员变量
//b.m_BB;//在外部不能访问B类的protected成员变量
//b.m_BC; //在外部不能访问B类的private成员变量
}
通过这段测试代码可以得出结论:在外部访问类内属性,只有public作用域下的才能被外部访问;其他两种protected以及private不允许被外部访问。
1.4 B以private方式继承A,尝试把A中的部分public成员提升为public。
//B private继承 A,把A中的public提升为public
class B4:private A{
public:
using A::m_A;
};
//在外部访问B4
void test02(){
B4 b4;
//在B4类中,通过私有继承,把A中的所有成员变量都变成了private
//但是在该类下的public作用域,通过 using A::m_A
//使得继承下来原本是私有属性的m_A,变成了public
b4.m_A;//可以在外部访问,因此它的属性是public
}
提升权限的方法,在该作用域下(如:public),使用 using A::m_A 这句关键代码即可。
二、友元类继承测试
2.1 设计类A含有私有变量a,在类A中友元给类C;
class C;//告诉编译器有这么个类,待会儿写
class A{
friend class C;//C是A的友元,可以访问到A中的私有成员
private:
int m_A;
};
2.2 设计类B继承A,添加私有变量b;在类C中测试访问类B的成员变量a, b;
class B:public A{
private:
int m_B;
};
class C{
public:
void fun1(){
A a;
a.m_A;//可以访问A的私有成员
B b;
b.m_A;//C可以访问B从A继承下来的私有成员变量a
//b.m_B;//C不能访问B自己的私有成员b ,即不能访问C的朋友A的儿子的私有成员
}
};
得出结论:类作友元可以访问其私有成员,即便它被继承,也可以在子类访问父类的私有;但是友元权限不能被继承,如在这个案例中,B的父亲A是C的友元,然而C是不能访问A的儿子的私有成员的。
2.3 设计类D继承C,在D的成员函数中测试访问类A的成员变量a,类B的成员变量a, b。
class D:public C{
public:
void fun2(){
A a;
//a.m_A; //D访问不到A的私有成员a,即使它的父亲C能访问到
B b;
//b.m_A; //D访问不到B从A继承下来的私有成员a
//b.m_B; //D访问不到B自己本身的私有成员b
}
};
综上,类作友元的时候,无法访问对方子类的私有成员,但是可以访问友元自己的私有成员,因为它们之间的关系是friend。
同时,它的儿子也继承不到友元的这一权限 ,即:它的父亲能访问A的私有成员,但是作为父亲的继承,这个子类是无法访问父类的友人A的私有的。
得出结论:友元权限无法被继承,也不能访问别人的子类。
三、多态性综合运用
首先,介绍一下两种多态:
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
动态多态: 派生类和虚函数实现运行时多态
而他们两者之间的区别是:
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
另外,多态要满足以下条件:
1、有继承关系
2、子类重写父类中的虚函数
多态使用:
父类指针或引用指向子类对象
3.1 一般多态性函数:输入输出参数完全一样,在父类中添加virtual;
实际上,这是一种子类对父类的函数进行重写,即:函数返回值类型 ,函数名,参数列表完全一致。
创建父类:动物
创建它的子类:骆驼和羊
class Animal {
public:
int m_Age=0;
public:
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Sheep :public Animal{
public:
void speak() {
cout << "羊在说话" << endl;
}
};
class Camel :public Animal{
public:
void speak() {
cout << "骆驼在说话" << endl;
}
};
使用代码进行测试
void doWork(Animal &a) {
a.speak();
}
int main() {
Sheep s;
doWork(s);
cout << "----------动物分割线------------" << endl;
Camel c;
doWork(c);
return 0;
}
输出如下:
这是因为父类的函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
3.2 特殊多态性函数:输入或输出参数在子类中是父类的指针或基类的引用,在子类中对于的是子类的指针或子类的引用;
先在Animal的一个子类Sheep类加个函数
void doSpeak(Animal& a) {
a.speak();
}
对此进行测试
void test01() {
Sheep s;
Animal a;
Sheep s1;
cout<<"子类函数的参数实现多态:" << endl;
s.doSpeak(a);
cout << "---------动物分割线--------" << endl;
s.doSpeak(s1);
}
输出结果如下:
可以看到,无论是在类内还是类外,多态都是能实现的。
3.3 析构函数的多态性;
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。解决方式:将父类中的析构函数改为虚析构或者纯虚析构。在此我只以虚析构进行举例来分析其多态性。
虚析构:可以解决父类指针释放子类对象以及需要有具体的函数实现。
class Animal {
public:
int m_Age;
public:
virtual void speak() {
cout << "动物在说话" << endl;
}
Animal()
{
cout << "Animal 构造函数调用!" << endl;
}
virtual ~Animal()//加上virtual 变成虚析构,这样子类可以对析构函数重写
{
cout << "Animal 析造函数调用!" << endl;
}
};
class Cat :public Animal {
public:
string *m_Name;
public:
Cat(string name) {
cout << "Cat 构造函数调用!" << endl;
m_Name = new string(name);
}
~Cat() {
cout << "Cat析构函数调用!" << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
void speak() {
cout <<*m_Name<< "猫在说话" << endl;
}
};
进行测试
void test02() {
Animal* animal = new Cat("Tom");
animal->speak();
delete animal;
}
输出结果如下:
通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏。解决方案:给基类增加一个虚析构函数,这样就能通过父类指针释放子类对象。
3.4 多继承,注意什么情况需要虚继承
关于多继承:语法: class 子类 :继承方式 父类1 , 继承方式 父类2…
多继承可能会引发父类中有同名成员出现,需要加作用域区分
关于虚继承:
虚继承应在菱形继承(两个派生类继承同一个基类、又有某个类同时继承者两个派生类,而菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义)的情况下使用
虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 Animal就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
class Animal{
public:
int m_Age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Camel : virtual public Animal {};
class Alpaca : public Sheep, public Camel {};
void test03() {
Alpaca al;
cout << "修改Animal的年龄:" << endl;
al.Animal::m_Age = 150;
cout << "al.Animal::m_Age = "<< al.Animal::m_Age <<endl;
cout << "al.Sheep::m_Age = " << al.Sheep::m_Age << endl;
cout << "al.Camel::m_Age = " << al.Camel::m_Age << endl;
al.Sheep::m_Age = 100;
cout << "修改Sheep的年龄:" << endl;
cout << "al.Animal::m_Age = " << al.Animal::m_Age << endl;
cout << "al.Sheep::m_Age = " << al.Sheep::m_Age << endl;
cout << "al.Camel::m_Age = " << al.Camel::m_Age << endl;
al.Camel::m_Age = 50;
cout << "修改Camel的年龄:" << endl;
cout << "al.Animal::m_Age = " << al.Animal::m_Age << endl;
cout << "al.Sheep::m_Age = " << al.Sheep::m_Age << endl;
cout << "al.Camel::m_Age = " << al.Camel::m_Age << endl;
}
通过测试可知确实只有一份数据。
3.5 设计矢量图,运用多继承设计组合图形,要求具备创建不同类型矢量图、选择图形、移动图形、用不同颜色显示图形(表示选中与否),用vector或数组管理图形
这里暂且先使用多继承的方式简单设计图形(重点还是为了深入理解多继承而做案例说明)
//默认正三角形
class Triangle {
public:
int m_di;
int m_gao;
int m_color;
};
class Square {
public:
int m_chang;
int m_kuan;
int m_color;
};
class Mix :public Triangle, public Square {
public:
//组合图形继承了三角形以及矩形的属性
Mix(int chang, int kuan,int c1, int di, int gao,int c2){
m_chang = chang;
m_kuan = kuan;
Square::m_color = c1;//父类属性成员同名记得加作用域
m_di = di;
m_gao = gao;
Triangle::m_color = c2;
}
};
有两个父类:三角形和矩形。它们都被子类Mix继承,即子类是这两个图形的联合体。这样通过输出,就能获得它们不同颜色的联合体了。