第7节课:9.7:继承
文章目录
一、继承和派生
1.是什么?
1.继承是创建类的一个新的方式,新的类可以继承一个或者多个类
2.继承和派生,从不同的角度来区别:父类派生出子类,子类继承父类
3.新建的类为派生类****或者是子类***,原来的被继承的那个类称为基类或者父类
2.怎么定义一个派生类?
在新建的类的定义后面加上 :继承方式(分为三种) 要继承的那个类的类 :public CA
class CA
{
public:
int a;
}
class CB : public class CA
{
public:
int b;
}
3.使用:
1.继承的方式有三种:
私有,公有,保护
1.基类中的私成员,无论哪种继承方式都不可以继承类中的新成员和对象都不可以访问
2.基类中的成员属性,继承的方式,哪个更严格,使用哪种属性
#include <stdio.h>
class CA
{
public:
int a;
private:
int b;
protected:
int c;
public:
int GetCA();
};
int CA::GetCA()
{
return b;
}
class CB : public CA
{
public:
int GetCATest();
};
int CB::GetCATest()
{
return c;//保护属性的成员可以被访问
//return a;//公有的成员也可以
//return b;//私有的不行这时只能够通过调用CA中的方法来访问b
}
int main()
{
CB obj;
//obj.b;//私有
//obj.c;//保护
obj.GetCA();//通过接口拿到基类私有数据
return 0;
}
2.继承类的类中成员:
1.继承类中的成员:有一个父类拷贝过来的无名类(也就是说包含了父类的数据成员和函数成员)+自己新的数据成员和函数成员
继承过来的函数成员除了基类的构造函数和析构函数,其他都会继承过来???
3.派生类的内存分配:
(1)基类的无名拷贝类内存在继承类的内存之前(可以打断点调试,查看内存)
#include <stdio.h>
class CA
{
public:
int a;
};
class CB : public CA
{
public:
int b;
};
int main()
{
CA obj1;
CB obj2;
obj1.a = 30;
obj2.a = 10;
obj2.b = 20;
CA * a1 = &obj1;
CB * a2 = &obj2;
int c = 0;
return 0;
}
(2)回顾结构体那部分的内存,做一个比较:
#萝卜老师这里的内存分配有点问题,
(我的理解应该是,把类CA整个作为整体拷贝过来也就是24字节,然后字节对齐(最大成员内存的最小公倍数)int b 就要占8个字节,所以一共是32个字节
结构体那里的实例也是一样的计算方法)这种理解是对的
#include <stdio.h>
class CA
{
public:
int a;
double b;
char c;
};
class CB : public CA//如果按照展开的方式:CB应该是24,但是结果是32,
{
public:
int b;
};
struct a//24
{
int a1;
double b;
char c;
};
struct b//24?是错的,原来可达老师说是把A展开得到注释的部分8+8+8 = 24,但是结果却是32??
{
int d;
struct a A;
/*int a1;
double b;
char c;*/
};
int main()
{
printf("类:%d %d ",sizeof(CA),sizeof(CB));//24 32
printf("结构体:%d %d ", sizeof(a), sizeof(b));//24 32
return 0;
}
4.派生类和基类有同名成员
#include <stdio.h>
class CA
{
public:
int a;
private:
int b;
protected:
int c;
public:
//int GetCA();
};
//int CA::GetCA()
//{
// return b;
//}
class CB : public CA
{
public:
int a;
};
int main()
{
CB obj;
obj.a = 10;
obj.CA::a = 20;
CB *p = &obj;//内存之中,赋值情况是:CB中的无名拷贝类中的a没有赋值,而是CB类区域有a被赋值了
int c = 0;
return 0;
}
前面12个字节分别是拷贝过来的a,b,c,之后的四个字节才是CB的a值为10;
5.基类与派生类之间的关系:
(1)基类对象不是派生类对象
(2)派生类对象是基类对象
二、构造函数的调用顺序以及多继承以及派生类构造函数怎么定义
1.不带参构造
2.带参构造,用成员初始化列表,要调用父类构造函数传参
#include <stdio.h>
class CA
{
public:
int a;
CA(int i);
~CA();
};
CA::CA(int i)
{
printf("调用构造CA\n");
}
CA::~CA()
{
printf("调用析构CA\n");
}
class CB
{
public:
int a;
CB();
~CB();
};
CB::CB()
{
printf("调用构造CB\n");
}
CB::~CB()
{
printf("调用析构CB\n");
}
class CC : public CB,public CA//12字节
{
public:
int a;
CC(int i);
~CC();
};
CC::CC(int i) :CA(i), CB()
{
printf("调用构造CC\n");
}
CC::~CC()
{
printf("调用析构CC\n");
}
int main()
{
CC obj(1);//如果父类都不是带参构造时,则CC的构造函数会自动调用父类构造函数,不用自己定义
//调用顺序,如果多个基类,则按照定义类是的继承的声明顺序:先调用CB,在CA最后调用自己
//析构函数则是相反,栈先进后出
return 0;
}
三、菱形继承(虚基类的引出)虚基类virtual关键字
1.B和D中都有一份名字相同的A的拷贝过来的成员,
所以E中有两份数据a,访问会出问题,只能够通过如下方式:
#include <stdio.h>
class CA//虚基类
{
public:
int a;
CA(int i);
~CA();
};
CA::CA(int i)
{
a = i;
printf("构造CA\n");
}
CA::~CA()
{
printf("析构CA\n");
}
class CB : public CA
{
public:
int b;
CB();
~CB();
};
CB::CB() :CA(10)
{
b = 2;
printf("构造CB\n");
}
CB::~CB()
{
printf("析构CB\n");
}
class CD : public CA
{
public:
int d;
CD();
~CD();
};
CD::~CD()
{
printf("析构CD\n");
}
CD::CD() :CA(11)
{
d = 3;
printf("构造CD\n");
}
class CE : public CB, public CD
{
public:
int e;
CE();
~CE();
};
CE::CE() :CB(), CD()
{
e = 4;
printf("构造CE\n");
}
CE::~CE()
{
printf("析构CE\n");
}
int main()
{
CE obj;//CA,CB,CA,CD,CE
CE* p = &obj;//20字节
int c = 0;
//printf("%d",obj.CA::a);
printf("%d\n", obj.CD::CA::a);//虽然有波浪线但是可以编译成功
return 0;
}
2.只让最上层的基类独占一份:
四、虚基类virtual关键字
1.特点:
(1).调用构造函数的顺序发生了一点点小变化:虚基类,基类,子对象,最后自己、
(2).可以优化内存
虽然此时多了四个字节:CB四个字节的指针,四个字节的数据;CD四个字节的指针,四个字节的数据;CE四个字节的数据,和四个字节的唯一一个CA数据a
一共24
但是如果a是8个字节,那么内存就会有优化了
(3).注意内存的内容的变化:
出现了不认识的四个字节:该四个字节是指针,用来询问CA中的a是否被构造了
(4)当派生类加了virtual关键字之后,派生类的构造函数首先看有没有其他地方调用了基类的析构,如果调用了,就不用再调用了。
但是定义派生类析构函数的时候任然要加上声明调用基类构造,仅仅是在程序执行的时候不会调用而已,声明还是需要的
#include <stdio.h>
class CA
{
public:
int a;
CA(int i);
~CA();
};
CA::CA(int i)
{
a = i;
printf("构造CA\n");
}
CA::~CA()
{
printf("析构CA\n");
}
class CB : virtual public CA//有了这个关键字以后,调用该本类的构造函数的时候,先看看基类有没有被构造,有就不调用基类的构造函数
{
public:
int b;
CB();
~CB();
};
CB::CB(): CA(10) //虽然有virtual关键字,不用调用CA构造函数,但是如果定义了CB对象,则还是要调用CA的构造函数,所以在这里也要定义
{
b = 2;
printf("构造CB\n");
}
CB::~CB()
{
printf("析构CB\n");
}
class CD : virtual public CA//有了这个关键字以后,调用该本类的构造函数的时候,先看看基类有没有被构造,有就不调用基类的构造函数
{
public:
int d;
CD();
~CD();
};
CD::~CD()
{
printf("析构CD\n");
}
CD::CD() :CA(11)
{
d = 3;
printf("构造CD\n");
}
class CE: public CB,public CD
{
public:
int e;
CE();
~CE();
};
CE::CE() :CA(100)
{
e = 4;
printf("构造CE\n");
}
CE::~CE()
{
printf("析构CE\n");
}
int main()
{
//CE obj;//CA,CB,CA,CD,CE
//obj.CD::CA::a;
//CE* p = &obj;//20字节
printf("%d", obj.CA::a);
//int c = 0;
CE obj;
CE * p = &obj;
int c = 0;
return 0;
}