实用C++学习笔记6

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;
    }
}

答(个人理解,不是标准答案):
调试过了,不行。原因应该是,在主函数中定义的是一个子类的对象,所以作为函数参数传过去的指针,指向的是子类的内存。虽然在函数参数中改成了基类类型,但实际上指的还是子类的内存,所以最开始的答案那里,把它从基类强制转为子类也并没有报错,因为从始至终都是指的子类的内存。(本来按规定,是不能把父类对象转换成子类对象的,很危险,但在这里是特殊情况)
但是以上的方法,在函数参数那里把指针转成父类了,没有转回子类就开始以父类类型进行计算(感觉出错点是在这里,不知道对不对。。。)
反正上面那个方法肯定是错的,所以遇到这种问题,要先转回子类再取数据进行计算

猜你喜欢

转载自blog.csdn.net/weixin_45550460/article/details/106444411