C++(十二)类的继承与派生、运算符重载

一、类的继承与派生

继承与派生是C++编程过程中经常用到的一个非常重要的功能。可以复用以前开发好的功能,站在巨人的肩膀上,节省时间,提高开发效率。不用重复造轮子了。这就是C++面向对象编程中的继承与派生。

例如:定义的学生类 CStudent,里面包含了一些普通学生的信息:姓名、学号、性别、年龄。那么接下来呢,我要定义一个小学生的类,又要定义一个中学生的类,还要定义一个大学生的类。不管是小学生、中学生还是大学生,肯定也包含学生的姓名、学号、性别、年龄等基本信息,如果都分别新定义一个类的话,跟之前的学生类 CStudent 貌似有点重复,同样的代码写了很多次。所以,我们可以通过C++中的继承的机制来解决这个问题。

1.1 举例:继承

CStudent 学生类的信息可以作为所有学生的基础信息,任何一个学生都包含姓名、学号、性别、年龄等基本信息。那么我们在定义小学生、中学生或者大学生的类的时候就可以用 CStudent 作为父类进行派生子类:

#include "Student.h"                        //基类(public CStudent)声明在此文件中
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 是指的继承方式,即:父类中的成员在子类中的继承方式,一般也包含三种:public公有继承、private私有继承、protedted受保护继承。
通过子类可以访问基(父)类的成员,如下:

CZhongStudent zhong_1;
zhong_1.wuli_score = 90;    //调用本类的成员变量
zhong_1.yuwen_score = 100;  //调用父类的成员变量
zhong_1.age = 15;           //调用爷爷类的成员变量
1.2 继承的方式

①、public公有继承:
父类的公有成员和受保护成员在子类中保持原有的访问属性,其私有成员仍为父类私有,在子类中是访问不了的,即使通过子类的共有成员函数也访问不了;
②、private私有继承:
父类的公有成员和受保护的成员在子类中变成了私有成员,其私有成员仍为父类私有, 在子类中是访问不了的,即使通过子类的共有成员函数也访问不了;
③、protected受保护继承:
父类的公有成员和受保护的成员在子类中变成了受保护成员,其私有成员仍为父类私有, 在子类中是访问不了的,即使通过子类的共有成员函数也访问不了;

#include "Student.h"
class CXiaoStudent : public CStudent
{
public:
    int yuwen_score;
    int shuxue_score;
    int english_score;

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_private;
        return flag_protected;
    }
};

继承访问关系表:

基类数据成员继承方式基类数据成员在派生类中的访问属性
privatepublic不可直接访问
privateprotected不可直接访问
privateprivate不可直接访问
protectedpublicprotected
protectedprotectedprotected
protectedprivateprivate
publicpublicpublic
publicprotectedprotected
publicprivateprivate

总而言之(基类数据private除外):基类数据成员在派生类中的访问属性基类数据成员继承方式 的交集。取他们之间数据限制最严格的那一部分。

1.3 子类的构造函数与析构函数
构造函数

子类可以继承父类所有的成员变量和成员函数,但不能继承父类的构造函数。因此,在创建子类对象的时候,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造函数。

父类构造函数的调用规则:
①. 如果子类没有定义构造函数,则调用父类的无参数的构造函数;
②. 如果子类定义了构造函数,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造函数,然后执行自己的构造函数;
③. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的无参构造函数(优先使用自定义无参构造函数,否则使用默认无参构造函数);
④. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造函数,则子类必须显示调用此带参构造函数);
⑥. 如果子类调用父类带参数的构造函数,需要用初始化父类成员对象的方式
简而言之:
如果子类没有显示的调用父类的构造函数,那么默认会调用父类无参的构造函数。
如果父类只提供了有参数的构造函数,那么子类在默认情况下调用父类的无参构造函数时就会报错!
class CXiaoStudent : public CStudent
{
public:
    int yuwen_score;
    int shuxue_score;
    int english_score;

    CXiaoStudent() : CStudent("zhangsan", 'm', 1001, 20)
    {
        yuwen_score = 2;
        shuxue_score = 0;
        english_score = 0;

        flag_private = 0;
        flag_protected = 0;
    }

private:
    int flag_private;

protected:
    int flag_protected;
};
析构函数

跟父类的构造函数一样,子类也一样不能继承父类的析构函数,也需要通过派生子类的析构函数去调用父类的析构函数。在执行子类的析构函数时,系统会自动调用父类的析构函数和子对象的析构函数,对父类和子对象进行清理工作。
调用的顺序跟构造函数正好相反:先执行子类自己的析构函数,对派生类新增加的成员进行清理,之后调用子对象的析构函数,对子对象进行清理,最后调用父类的的析构函数,对基类进行清理。

二、运算符重载

一般的变量我们可以直接赋值等运算,但是对于复杂变量,或者对象,就不能简单的进行赋值或运算,比如:向量的加减等。
复制构造函数中,我们会对对象进行复制初始化,我们需要对不同的成员变量进行不同的操作,这就涉及到重载。

运算符的重载实际上就是函数的重载,即,定义一个重载运算符的函数。使指定的运算符不仅能实现原有的功能,而且在函数中还能实现新的自定义的功能。

CStudent& CStudent::operator=(const CStudent& stud)
{
    if (p_name)
    {
        delete[] p_name;
        p_name = NULL;
    }

    int name_len = strlen(stud.p_name) + 1;
    p_name = new char[name_len];
    memset(p_name, 0, name_len);

    strcpy(p_name, stud.p_name);
    sex = stud.sex;
    num = stud.num;
    age = stud.age;
    return *this;
}
2.1 重载运算符的规则

①、C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载:
例如,在python中**运算符,例如:3**5,表示3的5次方,如果你想在C++中创建**运算符并实现重载,那是不行的。

可重载的运算符列表:

名称详情
双目算术运算符+(加),-(减),*(乘),/(除),% (取模)
关系运算符==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于)
逻辑运算符||(逻辑或),&&(逻辑与),!(逻辑非)
单目运算符+ (正),-(负),*(指针),&(取地址)
自增自减运算符++(自增),–(自减)
位运算符| (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移)
赋值运算符=, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=
空间申请与释放new, delete, new[ ] , delete[]
其他运算符()(函数调用),->(成员访问),,(逗号),[](下标)
下面是不可重载的运算符列表:
. 成员访问运算符
.*, ->* 成员指针访问运算符
:: 域运算符
sizeof 长度运算符
?: 条件运算符
# 预处理符号

②、重载运算符不能改变运算符操作对象的个数:例如 > 和 < 运算符,本身是比较用的双目运算符,也就是说要有2个操作数,如果你重载完了之后变成不是2了,那肯定不行;
③、重载运算符不能改变运算符的优先级别,乘除 */ 运算符的优先级别大于±,如果你重载完了之后导致优先级改变了,那肯定也不行;
④、重载运算符不能改变运算符的结合性,例如,赋值运算符=是从右向左开始结合的,如果你重载完了之后导致从左往右了,那肯定也不行;
⑤、重载运算符的函数不能有默认的参数:不然就改变了运算符参数的个数,与第③点矛盾了;
⑥、重载运算符的函数参数至少有一个是本类的对象或引用,不能全部都是C++的基本类型,例如:CStudent& CStudent::operator+(int a, int b) 这种也是不行的;
⑦、重载运算符应该遵循运算符原有的含义,不能把+重载之后变成-的功能,虽然语法上没有错误,但是违背了重载运算符的初衷!

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++中,运算符重载和虚函数是两个重要的概念。运算符重载可以让我们自定义运算符的行为,虚函数则可以实现多态性,使派生能够覆盖基的同名函数。下面是对它们的实验小结: 1. 运算符重载 运算符重载C++中的一个重要特性,可以让我们自定义运算符的操作。通过运算符重载,我们可以让自己定义的型支持各种运算符操作,比如加、减、乘、除等。 在实验中,我们定义了一个Rational,实现了加、减、乘、除四种运算符的重载。重载运算符的实现方式是在中定义相关的成员函数,函数名以“operator”开头,后跟要重载的运算符。 2. 虚函数 虚函数是C++中实现多态性的重要手段。通过虚函数,我们可以让派生覆盖基的同名函数,从而实现多态性。在实验中,我们定义了一个Shape基和两个派生,分别是Rectangle和Circle。它们都实现了一个名为area()的函数。 在Shape中,我们将area()函数定义为虚函数。这样,在Rectangle和Circle中,我们可以覆盖基的area()函数,实现自己的计算面积的方法。同时,我们还定义了一个指向Shape对象的指针数组,通过该数组,我们可以对不同的对象调用同名的虚函数,从而实现多态性。 通过本次实验,我们深入了解了运算符重载和虚函数的实现方式和使用方法。这些概念在C++中非常重要,掌握它们可以让我们更好地理解和使用C++语言。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SongpingWang

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值