目录
继承
继承的作用:代码复用以及扩展类的功能
私有权限:类内可访问,类外实例化、子类都不可访问
保护权限:类内可访问,子类可访问,类外实例化不可访问
公有权限:类内可访问,子类可访问,类外实例化可访问
查看对象模型
开始—VS—Developer Command Prompt for VS 2019—cd 到要查看的.cpp文件的目录—输入:cl/d1 reportSingleClassLayout类名 文件名全称
继承中构造和析构的调用顺序
有如下代码:
class A
{
public:
A()
{
cout << "A的构造函数" << endl;
}
~A()
{
cout << "A的析构函数" << endl;
}
public:
Son s;
};
class B
{
public:
B()
{
cout << "B的构造函数" << endl;
}
~B()
{
cout << "B的析构函数" << endl;
}
};
class C
{
public:
C()
{
cout << "C的构造函数" << endl;
}
~C()
{
cout << "C的析构函数" << endl;
}
};
class D:public A
{
public:
D()
{
cout << "D的构造函数" << endl;
}
~D()
{
cout << "D的析构函数" << endl;
}
public:
B b;
C c;
};
void test02()
{
D d;
}
其顺序是:父类的构造函数—成员对象的构造函数—自身的构造函数—(函数结束)—自身的析构函数—成员对象的析构函数—父类的析构函数
构造是从祖类开始,再父类,然后对象,然后自身,析构则是反过来,类似于栈,先把父类的构造和成员对象的构造压入栈中,再依次弹出。
继承中同名成员的处理方法
1.当子类和父类有同名成员时,子类的同名成员会隐藏(不是覆盖)父类的同名成员
class Father
{
public:
Father()
{
a = 10;
}
public:
int a;
};
class son :public Father
{
public:
son()
{
a = 20;
}
public:
int a;
};
void test()
{
son s;
cout << s.a << endl;
cout << sizeof(son) << endl;//大小是8
//通过父类名作用域来访问
cout<<s.Father::a << endl;
}
2.当子类和父类有同名函数时,父类的所有函数重载也都会被隐藏
且同样可以通过作用域来访问
s.Father::func(10,20);
继承中的静态成员特性
//1.静态成员可以被继承
class Father
{
public:
static int mA;
static void func()
{
cout << "Father func()" << endl;
}
static void func(int a)
{
cout << "Father func(int a)" << endl;
}
static void func(int a, int b)
{
cout << "Father func(int a,int b)" << endl;
}
};
int Father::mA = 10;
class Son :public Father
{
public:
static int mA;
//static void func()
//{
// cout << "Son func()" << endl;
//}
//static int func()
//{
// cout << "int func()" << endl;
// return 0;
//}
};
int Son::mA = 20;
void test()
{
//2.继承中的静态成员变量一样会被同名的子类成员变量隐藏
Son s;
cout << s.mA << endl;
//cout << s.mB << endl;
//3.当子类和父类有同名静态函数时,父类的所有函数重载都会被隐藏
s.Father::func(10);
//4.(与普通成员函数不一样)继承中,如果在子类中改变基类继承过来的静态函数中的某个特性,无论是返回值,还是参数,都会隐藏基类重载的函数
s.func(10);
//5.(与普通成员函数不一样)静态成员函数不能是虚函数
//6.父类继承过来的静态成员变量就是父类的静态成员变量,是同一个
}
1.静态成员可以被继承。
2.继承中的静态成员变量一样会被同名的子类静态成员变量隐藏,若是不同名的静态成员变量那就是父类的静态成员变量,是同一个。
3.当子类和父类有同名静态函数时,父类的所有函数重载都会被隐藏。
4.(与普通成员函数不一样)继承中,如果在子类中改变基类继承过来的静态函数中的某个特性,无论是返回值,还是参数,都会隐藏基类重载的函数。
5.(与普通成员函数不一样)静态成员函数不能是虚函数。
多继承
一个类有两个以上的父类,当父类中有同名成员时子类会产生二义性,不知道调用哪个成员,不建议使用,但是菱形继承中的虚继承的方法可以解决这个问题。
菱形继承
菱形继承不也会有二义性吗?怎么解决呢?
用虚继承来解决
虚继承
虚继承需要使用virtual关键字,被virtual关键字修饰的继承叫做虚继承,被虚继承的基类叫做虚基类。
class Sheep : virtual public Animal
{
......
};
虚继承的解决二义性的原理
虚继承的派生类,编译器会给它添加一个指针,指针指向类似于表的组织,该表记录了该指针距离变量的偏移量(这个偏移量的意思是,类地址的首地址距离变量的偏移量),而如果你想要查看这个表,可以通过查看对象模型来看,比如代码:
class Animal
{
public:
Animal()
{
mA = 100;
}
public:
int mA;
};
class Sheep :virtual public Animal
{
};
class Camel :virtual public Animal
{
};
class SheepCamel :public Sheep, public Camel
{
};
则Sheep的对象模型:
以及SheepCamel的对象模型
由图可得,虚继承可以解决二义性的问题,被virtual修饰的类,编译器生成了一个vbptr指针,这个指针指向了类与mA的偏移距离,注意这个mA是放在一个公共区域里的,这样,当SheepCamel子类同时继承了Sheep和Camel两个父类时,并没有同时继承两个父类的mA变量,而是继承了两个父类的vbptr指针,唯一继承的那个mA变量是来自祖类的,所以,这样就不会产生二义性的问题了,你访问的mA就是祖类的mA。
虚继承中通过指针来访问变量
参照上图,可以通过指针来获取变量
Sheep s;
s.mA;
//1.&s;//类的首地址
//2.(int*)&s;//强转为int*类型,int*类型就是数组类型,每个所占字节为4
//3.*(int*)&s;
获取指针中的地址,此时如果你+1,因为是int*类型,是以4为单位的数组,
即把内存以字节4均分,然后以下标0,1,2,3...划分,所以你+1实质上是走了4个字节,
通过你观察对象模型,也能取到mA的值,即*((int*)(*(int*)(&s)) + 1)
//4.(int *)*(int*)&s;//指向表的首地址
//5.(int *)*(int*)&s+1;//指向了表存储偏移量的地址
//6.*((int *)*(int*)&s+1);//这就是偏移量
cout << *((int*)*(int*)&s + 1) << endl;
//1.&s;
//2.(char*)&s;
char *p;*p为字符串,所占字节单位为1,p为指针,所占字节单位为4
这里是将&s转化为char*类型,就是字符串类型,所以所占字节单位为1
//3.(char*)&s+*((int*)*(int*)&s + 1)//所占字节为1,去的是首地址即0,加上偏移量就是4,这个4就是mA的地址。
//4.把类型转换为Animal指针类型
cout << ((Animal*)((char*)&s + *((int*)*(int*)&s + 1)))->mA << endl;
输出分别为4和100,4是偏移量,100是mA变量的值
注意(32位,vs):
1.Int*,char*,double*.....指针类型在内存中所占大小一律为4字节
2.Int占4字节,char占1字节,double占8字节,float占4字节
3.int a[n]所占字节为4*n