39 类的继承与派生
https://www.cctry.com/thread-290039-1-1.html
继承
#include "Student.h"
class CXiaoStudent : public CStudent
{
public:
int yuwen_score;
int shuxue_score;
int english_score;
};
class CZhongStudent : public CXiaoStudent
{
public:
int wuli_score;
int huaxue_score;
};
继承的方式:
①、public公有继承:父类的公有成员和受保护成员在子类中保持原有的访问属性,其私有成员仍为父类私有,在子类中是访问不了的,即使通过子类的共有成员函数也访问不了;
②、private私有继承: 父类的公有成员和受保护的成员在子类中变成了私有成员,其私有成员仍为父类私有,在子类中是访问不了的,即使通过子类的共有成员函数也访问不了;
③、protected受保护继承: 父类的公有成员和受保护的成员在子类中变成了受保护成员,其私有成员仍为父类私有, 在子类中是访问不了的,即使通过子类的共有成员函数也访问不了;
(PS:基类对象不能访问基类的protected成员,派生类中可以访问基类的protected成员。
“派生类对象如果要访问基类protected成员只有通过派生类对象,派生类不能访问基类对象的protected成员。”——意思是:只有在派生类中才可以通过派生类对象访问基类的protected成员。)
小作业:
按照继承的规则,既然父类中的private私有成员不能在子类中直接使用,那么有没有什么办法能解决这个问题呢?让子类可以直接或者间接的使用父类中的private私有成员呢?
方法1:将需要访问父类私有成员的子类成员函数声明为友元,子类可直接访问父类的私有成员
方法2:在父类里面增加公有的setxx和getxx方法,在子类里面通过这些getxx和setxx方法间接访问父类的私有成员变量
#include <iostream>
#include <string>
using namespace std;
class CStudent
{
public:
char* p_name;
char sex;
int num;
int age;
int getflag()
{
return flag_private2;
}
private:
int flag_private2;
};
class CXiaoStudent : public CStudent //小学生类
{
friend class CZhongStudent; //把CZhongStudent声明为友元类
public:
int yuwen_score;
int shuxue_score;
int english_score;
int f;
void getprivate()
{
f = getflag(); //正确,通过父类的public方法访问父类的private成员
}
private:
int flag_private;
protected:
int flag_protected;
};
class CZhongStudent : public CXiaoStudent //中学生类;;
{
public:
int wuli_score;
int huaxue_score;
public:
int get_flag_1()
{
return flag_protected; //没有错,派生类中可以访问基类的protected成员
}
int get_flag_2(CZhongStudent zhong)
{
zhong.flag_protected = 1; //正确,只有在派生类中才可以通过派生类对象访问基类的protected成员
return zhong.flag_protected;
}
int get_flag_3()
{
return flag_private; //正确,因为基类已经CXiaoStudent把派生类CZhongStudent声明成友元类,所以CZhongStudent可以访问CXiaoStudent的私有成员
}
};
int main()
{
CXiaoStudent xiao;
CZhongStudent zhong;
//xiao.flag_protected = 1; //错误,基类对象不能访问基类的protected成员
//zhong.flag_protected = 1; //错误,只有在派生类中才可以通过派生类对象访问基类的protected成员。
return 0;
}
40 子类的构造函数与析构函数
https://www.cctry.com/thread-290040-1-1.html
子类不能继承父类的构造函数
父类构造函数的调用规则: ①. 如果子类没有定义构造函数,则调用父类的无参数的构造函数;
②. 如果子类定义了构造函数,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造函数,然后执行自己的构造函数;
总结:如果子类没有显示地调用父类的构造函数,那么默认会调用父类无参的构造函数!!!
如果父类只提供了有参数的构造函数,那么子类在默认情况下调用父类的无参构造函数时就会报错!
⑥. 如果子类调用父类带参数的构造函数,需要用初始化父类成员对象的方式
CXiaoStudent() : CStudent("zhangsan", 'm', 1001, 20)
{
yuwen_score = 2;
shuxue_score = 0;
english_score = 0;
flag_private = 0;
flag_protected = 0;
}
子类的析构函数:
子类也一样不能继承父类的析构函数,也需要通过派生子类的析构函数去调用父类的析构函数。
对于析构函数不需要过多操作,一般都是默认调用
小作业:
用一个函数来实现一个功能,分别统计全市在校学生的平均年龄。学生包括小学生、中学生、高中生、大学生 等。用一个函数来实现!
41 父类对象与子类对象之间的相互转换
https://www.cctry.com/forum.php?mod=viewthread&tid=290043
1、有父子关系的两个类的对象之间能否进行互相转换呢?
答案:由子类对象给父类对象赋值是可以的,俗称大材小用。在赋值的时候会舍弃子类的新增成员
2、父子类对象转换的实际用途
一般是使用指针来操作。
解答上节题目:
只能写一个函数,这个函数既要区分各种学生的类型,还要统计数据,像这种情况,把类型定义在基类
如果遇到这种用某个函数去统计多种不同类型的对象的值,就要使所有要统计的类型都从一个基类派生过来,这个基类里面可以没有任何成员,但是只要用它来派生就可以,然后你用来统计的这个函数的参数就传递基类的指针。传递过来之后,在函数的内部再动态地区分到底是哪一个子类(把区分的标志放在基类当中(可以使用枚举))
#include <iostream>
#include <string>
using namespace std;
enum EStudentType
{
EStudentType_Error = 0,
EStudentType_Xiao,
EStudentType_Zhong,
EStudentType_Da
};
class CStudent
{
public:
char* p_name;
char sex;
int num;
int age;
CStudent();
CStudent(char* t_name, char t_sex, int t_num, int t_age);
~CStudent();
EStudentType type; //这样所有从基类继承的派生类就都可以加类型了
};
CStudent::CStudent() //基类的无参构造函数
{
type = EStudentType_Error;
p_name = NULL;
}
CStudent::CStudent(char* t_name, char t_sex, int t_num, int t_age) :sex(t_sex), num(t_num), age(t_age) //基类的有参构造函数
{
type = EStudentType_Error;
int n_len = strlen(t_name) + 1;
p_name = new char[n_len];
memset(p_name, 0, n_len);
strcpy_s(p_name, n_len, t_name);
}
CStudent::~CStudent() //基类的析构函数
{
if (p_name)
{
delete[] p_name;
p_name = NULL;
}
cout << "CStudent::~CStudent()" << endl;
}
class CXiaoStudent : public CStudent //小学生类
{
public:
int shuxue_score;
CXiaoStudent() {}
CXiaoStudent(int shuxue)
{
type = EStudentType_Xiao;
shuxue_score = shuxue;
}
~CXiaoStudent()
{
cout << "~CXiaoStudent()" << endl;
}
private:
int music_score;
protected:
int yuwen_score;
};
class CZhongStudent : public CXiaoStudent //中学生类;
{
public:
int wuli_score;
int huaxue_score;
CZhongStudent():CXiaoStudent(88) //子类调用父类带参数的构造函数,使用初始化父类成员对象的方式
{
type = EStudentType_Zhong;
wuli_score = 10;
huaxue_score = 20;
}
CZhongStudent(int wuli, int huaxue) :CXiaoStudent(88)
{
type = EStudentType_Zhong;
wuli_score = wuli;
huaxue_score = huaxue;
}
~CZhongStudent()
{
cout << "~CZhongStudent()" << endl;
}
int get_yuwen_score()
{
return yuwen_score;
}
};
class CDaStudent : public CZhongStudent
{
public:
int jiuye_score;
CDaStudent() :CZhongStudent(90, 100)
{
type = EStudentType_Da;
}
CDaStudent(int jiuye) :CZhongStudent(90, 100)
{
type = EStudentType_Da;
jiuye_score = jiuye;
}
~CDaStudent()
{
cout << "~CDaStudent()" << endl;
}
};
int average_age(CStudent* p_arr_stud, int n_size) //参数传进来的可能是小、中、大学生,但他们都是学生类型,所以就定一个基类CStudent类型
{
//有很多学生,所以肯定是要一个数组
//不管是哪个级别的学生,都是派生自基类学生类,所以要传递基类的指针
//先检测一些基本错误
if (!p_arr_stud || n_size <= 0)
{
return 0;
}
int age_sum = 0;
//先判断类型
EStudentType stud_type = p_arr_stud[0].type;
for (int i = 0; i < n_size; i++)
{
switch (stud_type)
{
case EStudentType_Xiao:
age_sum += ((CXiaoStudent*)p_arr_stud)[i].age;
break;
case EStudentType_Zhong:
age_sum += ((CZhongStudent*)p_arr_stud)[i].age;
break;
case EStudentType_Da:
age_sum += ((CDaStudent*)p_arr_stud)[i].age;
break;
default:
break;
}
}
int age_av = age_sum / n_size;
switch (stud_type)
{
case EStudentType_Xiao:
cout << "当前的小学生的平均年龄是:" << age_av << endl;
break;
case EStudentType_Zhong:
cout << "当前的中学生的平均年龄是:" << age_av << endl;
break;
case EStudentType_Da:
cout << "当前的大学生的平均年龄是:" << age_av << endl;
break;
default:
break;
}
return age_av;
}
int main()
{
CDaStudent arr_stud[3];
arr_stud[0].age = 21;
arr_stud[1].age = 20;
arr_stud[2].age = 23;
average_age(arr_stud, 3);
return 0;
}
在该函数内部可以动态区分传进来的参数是哪个类型的学生,区分的方式是通过基类的type,不同的子类初始化type为不同的值(因为函数参数传入的是基类的指针,所以type一定要定义在基类)
这个是常用到的,要记住
小作业,刚刚的 average_age 函数改成以下这样行不行?
(下面这个就是先算了平均数再分类输出)
void average_age(CStudent* p_arr_stud, int n_size)
{
int total_age = 0;
if (!p_arr_stud || n_size <= 0) return;
for (int idx = 0; idx < n_size; ++idx)
{
total_age += p_arr_stud[idx].age;
}
EStudentType type = p_arr_stud[0].type;
int aver_age = total_age / n_size;
switch (type)
{
case EStudentType_Xiao:
cout << "小学生的平均年龄是:" << aver_age << endl;
break;
case EStudentType_Zhong:
cout << "中学生的平均年龄是:" << aver_age << endl;
break;
default:
break;
}
}
答(个人理解,不是标准答案):
调试过了,不行。原因应该是,在主函数中定义的是一个子类的对象,所以作为函数参数传过去的指针,指向的是子类的内存。虽然在函数参数中改成了基类类型,但实际上指的还是子类的内存,所以最开始的答案那里,把它从基类强制转为子类也并没有报错,因为从始至终都是指的子类的内存。(本来按规定,是不能把父类对象转换成子类对象的,很危险,但在这里是特殊情况)
但是以上的方法,在函数参数那里把指针转成父类了,没有转回子类就开始以父类类型进行计算(感觉出错点是在这里,不知道对不对。。。)
反正上面那个方法肯定是错的,所以遇到这种问题,要先转回子类再取数据进行计算