3.3.4继承与组合混搭情况下,构造和析构调用原则
原则: 先构造父类,再构造成员变量、最后构造自己
先析构自己,在析构成员变量、最后析构父类
//先构造的对象,后释放
//demo_07继承和组合混搭下的构造与析构.cpp
#include <iostream>
using namespace std;
class Object
{
public:
Object(int a, int b)
{
this->a = a;
this->b = b;
cout << "object构造函数 执行 " << "a" << a << " b " << b << endl;
}
~Object()
{
cout << "object析构函数 \n";
}
protected:
int a;
int b;
};
class Parent : public Object
{
public:
Parent(char *p) : Object(1, 2)
{
this->p = p;
cout << "父类构造函数..." << p << endl;
}
~Parent()
{
cout << "父类的析构函数..." << p << endl;
}
protected:
char *p;
};
class child : public Parent
{
public:
child(char *p) : Parent(p), obj1(3, 4), obj2(5, 6)
{
this->myp = p;
cout << "子类的构造函数" << myp << endl;
}
~child()
{
cout << "子类的析构" << myp << endl;
}
void printC()
{
cout << "我是儿子" << endl;
}
protected:
char *myp;
Object obj1;
Object obj2;
};
void objplay()
{
child c1("继承测试");
}
void main()
{
objplay();
cout << "hello..." << endl;
system("pause");
return;
}
/*
object构造函数 执行 a1 b 2 --先调用父类的父类-(祖宗类)的构造函数(父类里面调用了他的父类)
父类构造函数...继承测试 --调用父类的父类后,调用父类自己的构造函数
object构造函数 执行 a3 b 4 -- 父类调用结束,调用子类的里面组合对象的构造函数,有两个,每个都需要调用相应的构造函数
object构造函数 执行 a5 b 6
子类的构造函数继承测试 -- 调用子类自己的构造函数
子类的析构继承测试 - 析构的时候就反过来了
object析构函数
object析构函数
父类的析构函数...继承测试
object析构函数
*/
3.3.5继承中的同名成员变量处理方法
1、当子类成员变量与父类成员变量同名时
2、子类依然从父类继承同名成员
3、在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
4、同名成员存储在内存中的不同位置

总结:同名成员变量和成员函数通过作用域分辨符进行区分
//demo_08继承中的同名成员变量和函数.cpp
#include <iostream>
using namespace std;
class Parent
{
public:
int a;
int b;
public:
void get()
{
cout << b << endl;
}
void print()
{
cout << "AAAA " << endl;
}
private:
};
class Child:public Parent
{
public:
int b;
int c;
public:
void get_Child()
{
cout << b << endl;
}
void print()
{
cout << "CCCC " << endl;
}
private:
};
//总结:同名成员变量和成员函数通过作用域分辨符进行区分
//同名成员函数
void main()
{
Child c1;
c1.print();//默认派生类屏蔽基类的同名成员函数调用自己的成员函数
c1.Parent::print();//利用作用域分辨符取父类的成员函数
c1.Child::print();//利用作用域分辨符取子类的成员函数
}
//同名成员变量
void main0801()
{
Child c1;
c1.b = 1;//默认情况是子类的
c1.get_Child();
c1.Parent::b = 2;//修改父类的b
c1.get();
c1.Child::b = 3;//修改子类的b,默认是子类的
c1.get_Child();
cout << "hello..." << endl;
system("pause");
return;
}
3.3.6派生类中的static关键字
继承和static关键字在一起会产生什么现象哪?
理论知识
Ø 基类定义的静态成员,将被所有派生类共享
Ø 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质(遵守派生类的访问控制)
Ø 派生类中访问静态成员,用以下形式显式说明:
类名 :: 成员
或通过对象访问 对象名 . 成员


//demo_09继承中的static关键字.cpp
#include <iostream>
using namespace std;
class A
{
public:
static int a;
int b;
//单例场景 .... UML 在单例中会需要把构造函数写出私有的
public:
A()//class 不写访问修饰符,默认是private,struct默认是public
{
cout << "A的构造函数" << endl;
}
void get()
{
cout << "b " << b << endl;
}
void print()
{
cout << "AAAAA " << endl;
}
protected:
private:
};
int A::a = 100; //这句话 不是简单的变量赋值 更重要的是 要告诉C++编译器 你要给我分配内存 ,我再继承类中 用到了a 不然会报错..
class B : public A
{
public:
int b;
int c;
public:
void get_child()
{
cout << "b " << b << endl;
cout << a << endl;
}
void print()
{
cout << "BBBB " << endl;
}
protected:
private:
};
//1 static关键字 遵守 派生类的访问控制规则
//2 不是简单的变量赋值 更重要的是 要告诉C++编译器 你要给我分配内存 ,我再继承类中 用到了a 不然会报错..
//3 A类中添加构造函数
//A类的构造函数中 A的构造函数是私有的构造函数 ...
//被别的类继承要小心....
//单例场景 .... UML
void main()
{
A a1;
B b1;
b1.print();
b1.A::print();
b1.B::print(); //默认情况
system("pause");
}
//同名成员变量
void main71()
{
B b1;
b1.b = 1; //
b1.get_child();
b1.A::b = 100; //修改父类的b
b1.B::b = 200; //修改子类的b 默认情况是B
b1.get();
cout << "hello..." << endl;
system("pause");
return;
}
3.4.1多继承的应用
多继承概念
Ø 一个类有多个直接基类的继承关系称为多继承
Ø 多继承声明语法
class 派生类名 : 访问控制基类名1 , 访问控制基类名2 , … ,访问控制基类名n
{
数据成员和成员函数声明
};
Ø 类C 可以根据访问控制同时继承类A和类B 的成员,并添加
自己的成员
多继承的派生类构造和访问
Ø 多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员
Ø 执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
Ø 一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
多继承简单应用
//demo_10多继承语法.cpp
#include <iostream>
using namespace std;
class Base1
{
public:
Base1(int b1)
{
this->b1 = b1;
}
void printB1()
{
cout << b1 << endl;
}
private:
int b1;
};
class Base2
{
public:
Base2(int b2)
{
this->b2 = b2;
}
void printB2()
{
cout << b2 << endl;
}
private:
int b2;
};
class Child:public Base1,public Base2
{
public:
//当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
Child(int b1, int b2, int c) :Base1(b1), Base2(b2)
{
this->c = c;
}
void printC()
{
cout << c << endl;
}
private:
int c;
};
void main()
{
Child c1(1,2,3);
c1.printB1();
c1.printB2();
c1.printC();
system("pause");
}
3.4.2虚继承
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
虚继承中出现二义性的示意图
总结:
Ø 如果一个派生类从多个基类派生,而这些基类又有一个共同
的基类,则在对该基类中声明的名字进行访问时,可能产生
二义性
Ø 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处
汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象
Ø 要使这个公共基类在派生类中只产生一个子对象,必须对这个基类
声明为虚继承,使这个基类成为虚基类。
Ø 虚继承声明使用关键字 virtual
实验:注意增加virtual关键字后,构造函数调用的次数。
3.5继承总结
Ø 继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有基类的基础上定义新的派生类。
Ø 单继承的派生类只有一个基类。多继承的派生类有多个基类。
Ø 派生类对基类成员的访问由继承方式和成员性质决定。
Ø 创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员。调用析构函数的次序和调用构造函数的次序相反。
Ø C++提供虚继承机制,防止类继承关系中成员访问的二义性。
Ø 多继承提供了软件重用的强大功能,也增加了程序的复杂性。
#include <iostream>
using namespace std;
class AA
{
public:
int b;
private:
};
class Base1 :virtual public AA
{
public:
int b1;
private:
};
class Base2 :virtual public AA
{
public:
int b2;
private:
};
class Child:public Base1,public Base2
{
public:
int c;
private:
};
void main()
{
Child c1;
c1.b1 = 1;
c1.b2 = 2;
c1.c = 3;
/*
要使这个公共基类在派生类中只产生一个子对象,必须对这个基类
声明为虚继承,使这个基类成为虚基类
虚继承声明使用关键字 virtual
*/
c1.b = 2;//err 从哪里继承过来的
//继承的二义性 和 虚继承解决方案
c1.Base1::b = 2;//ok 知道从Base1继承过来的
c1.Base2::b = 3;//ok 知道从Base2继承过来的
system("pause");
}
#include <iostream>
using namespace std;
class AA
{
public:
AA()
{
cout << "祖宗类构造函数" << endl;
}
int b;
private:
};
class Base1 :virtual public AA
{
public:
int b1;
private:
};
class Base2 :virtual public AA
{
public:
int b2;
private:
};
class Child :public Base1, public Base2
{
public:
int c;
private:
};
/*
1.加上virtual关键字 发现只会调用一次 祖宗类构造函数
不加virtual关键字 发现会调用两次 祖宗类构造函数
祖宗类构造函数
这也是导致二义性原因所在
2.
*/
void main1202()
{
cout << sizeof(AA) << endl; //4
cout << sizeof(Base1) << endl; //12 //加上virtual以后 , C++编译器会在给变量偷偷增加属性
cout << sizeof(Base2) << endl; //8 不加virtual关键字
system("pause");
}
void main1201()
{
Child c1;
c1.b1 = 1;
c1.b2 = 2;
c1.c = 3;
/*
要使这个公共基类在派生类中只产生一个子对象,必须对这个基类
声明为虚继承,使这个基类成为虚基类
虚继承声明使用关键字 virtual
*/
c1.b = 2;//err 从哪里继承过来的
//继承的二义性 和 虚继承解决方案
c1.Base1::b = 2;//ok 知道从Base1继承过来的
c1.Base2::b = 3;//ok 知道从Base2继承过来的
system("pause");
}
class D1
{
public:
int k;
};
class D2
{
public:
int k;
};
class E:virtual public D1,virtual public D2
{
public:
private:
};
//
void main()
{
E e1;
//e1.k;//这样就会出现二义性,无法知道这个从两条路径继承过来的
//解决方案还是手动加作用符
e1.D1::k;
e1.D2::k;
}
