【C++】继承

举报
YIN_尹 发表于 2023/12/13 22:14:03 2023/12/13
【摘要】 @[TOC] 这篇文章开始,我们来学习一下C++中的继承。1. 继承的概念及语法相信大家对于继承这个词应该都不陌生,所以在这篇文章的学习之前,大家可以先联想一下现实生活中的继承是怎么样的。 C++里面呢也有继承的概念,那C++的继承我们该如何去理解呢?1.1 继承的概念其实不仅在C++这门语言里有继承的概念,大多数面向对象的语言都支持继承。<font color = blue>继承(inhe...

@[TOC] 这篇文章开始,我们来学习一下C++中的继承。

1. 继承的概念及语法

相信大家对于继承这个词应该都不陌生,所以在这篇文章的学习之前,大家可以先联想一下现实生活中的继承是怎么样的。 在这里插入图片描述

C++里面呢也有继承的概念,那C++的继承我们该如何去理解呢?

1.1 继承的概念

其实不仅在C++这门语言里有继承的概念,大多数面向对象的语言都支持继承。

<font color = blue>继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类(基类)特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。 那这个过程其实就叫做继承,这里被继承的类(即原有类)叫做基类或者父类,新产生的类叫做派生类或者子类 继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。 继承是类设计层次的复用。

举个栗子:

<font color = black>比如,现在有一个描述人的类,可能有名字、年龄、电话、住址这些成员变量,有吃饭、睡觉、喝水这些成员方法/函数。 然后呢,我还想定义一个学生类,那此时我还需要重新从0开始定义一个类吗? 当然也是可以的。但是像这种情况,使用继承就会非常香。

为什么这种情况可以使用继承呢?

<font color = black>大家想,要定义学生类,学生是人吗? 当然是人,这是毫无疑问的。 那它具备人的属性吗?当然具备。人有名字、年龄这些属性;那学生也是一个人,他当然也具备这些属性。 那当具备人的方法吗?当然也具备。人会吃饭喝水,学生也是人,他也会。 那我们定义一个学生类的时候再去给它定义名字年龄这些属性以及吃饭喝水这些方法,就显得有点麻烦了。如果后续我再定义一个老师类呢?再定义一个辅导员类呢? 都要一个个再去给他们增添这些人类都共有的属性吗? 不需要,使用继承就很方便的搞定了这些问题。

我们上面说了,继承可以保持原有类的特性(即新的类继承了原有类,就直接拥有了原有类的各种属性和方法),我们可以在他的基础上增添新的属性和方法(比如学生还有学号的属性和考试的方法),然后产生一个新符合需求的类。

相信说到这里,大家对于C++中继承的概念已经有了一定的理解了,那继承的语法是怎样的呢?我们如何去定义一个继承类呢?

1.2 继承语法

上面给大家举了一个例子,下面我就来把它实现成代码,带大家熟悉一下继承的语法。

现在呢,我这里已经写好了一个人的类

class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; // 姓名
    int _age = 18; // 年龄
};

然后我再定义一个学生类,这个学生类我想去继承人这个类,怎么做呢?

class Student : public Person
{
protected:
    int _stuid; // 学号
};

在这里插入图片描述 这样写就🆗了。

1.2.1 语法格式

在这里插入图片描述

<font color = black>Person是父类,也称作基类。Student是子类,也称作派生类。 ps:这里的继承方式是公有继承,继承方式有三种,我们待会会讲。

我们的Student类看起来好像啥也没有,就一个学号的属性(成员变量)。 但是事实是这样吗?

<font color = blue>🆗,student继承后person后,父类Person的成员(成员函数+成员变量)也会变成子类的一部分,即子类会直接拥有从父类那里继承下来的成员。 但要注意这并不意味着它们共享同一个成员,它们是相互独立的,大家可以自己调式观察:子类继承下来的父类成员,它们在子类对象和父类对象中的地址是不一样的。

我们来验证一下:

<font color = black>在这里插入图片描述 我们看到,Student类并没有name和age属性,也没有Print方法,但是Student类的对象却可以调用Print函数并打印name和age。 原因就在于他继承了父类Person的属性(成员变量)和方法(成员函数)。 我们通过调式也可以观察到: 在这里插入图片描述

那这就体现了复用:

<font color = black>子类Student复用了父类Person的成员(包括成员变量和成员函数),如果没有继承,那这些成员就需要我们自己去定义。

1.2.2 继承关系和访问限定符

我们之前学习类和对象的时候学了类的三种访问修饰限定符public、protected、private 在这里插入图片描述

那在继承这里,它们还可以用做继承方式/关系。 在这里插入图片描述

那不同的继承方式有什么不一样呢?

1.2.3 继承后成员访问方式的变化

我们来看一张表格 在这里插入图片描述

private成员的继承

那对于这张表格其实我们总结一下会发现:

<font color = blue>基类的私有private成员在子类都是不可见的(无论以什么方式被继承)。 基类的protected 或public成员在子类中的访问方式 == Min(成员在基类中的访问限定符,继承方式),public > protected > private。

那这里的不可见是什么意思呢?

<font color = blue>这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

我们来看一下是不是不可见:

<font color = black>现在我将Person的成员变量都变成私有的 在这里插入图片描述 然后我在Student里面再加一个成员函数 在这里插入图片描述 此时程序是可以正常运行的 在这里插入图片描述 但是我如果想访问继承下来的Person的成员变量 在这里插入图片描述 在这里插入图片描述 对不起,不行。因为是在子类中不可见,虽然被继承到了子类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。 他跟私有不一样,如果是私有,至少在类里面还可以访问。

所以什么时候我可以把成员定义成私有啊,除了我不想在类外被访问到:

<font color = black>🆗,是不是如果我当前类的某些成员不想被子类使用和访问,也可以把它定义成私有啊。 就可以理解成好比是你爸爸的私房钱,你看不到,也不能花,不论你是什么继承。 所以,如果一个类的成员是私有的,除了在类外不能被直接访问,它的子类虽然可以继承,但也不能直接访问(不可见的)。 当然Person的Print函数我们没把他修饰成private,所以借助Print其实还是可以间接访问到的 在这里插入图片描述

protected成员的继承

那如果我想让一个类的成员在类外不能被直接访问,但在子类中是可见的(在子类里面可以直接访问),可以怎么做呢?

<font color = blue>那此时我们就可以用protected来修饰基类的成员。 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 此时s就可以通过func访问了。

那在这里我们也可以得出一个结论:

<font color = blue>即保护成员限定符是因继承才出现的。 对于一个类来说,类里面使用private和protected修饰成员是没区别的,都是在类外不能被直接访问。 <font color = black>这个其实我们在类和对象的学习中也提过 在这里插入图片描述

默认继承方式

那和我们之前学的类的访问修饰限定符一样,继承方式呢,我们也可以选择不写,那可以不写就意味着也存在默认的继承方式:

<font color = blue>使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显式的写出继承方式。 <font color = black>演示一下 在这里插入图片描述 那此时我们用的是class,所以默认的是私有继承 在这里插入图片描述 在这里插入图片描述 那我们的基类中的成员是pubic和protected修饰的,而这里是private继承,所以在子类中都是private的 在这里插入图片描述 所以我们在类外是无法访问继承下来的成员的。 当然如果我们改成struct就可以 在这里插入图片描述 因为struct默认是共有继承。

实际中常用的继承方式

那我们上面讲了这么多,但是呢?

<font color = blue>在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。 实际运用中基类的成员一般都是public/protected,子类继承的方式一般都是public继承。

即实际中最常用的继承关系其实是这一块 在这里插入图片描述

所以后面如果我们没有特别说明,讨论的就是公有继承下的情况。

2. 基类和派生类对象的赋值转换

首先我们来看这样一个问题:

<font color = black>在这里插入图片描述 大家看这里y能赋给x吗? 当然可以的,这个问题其实我们之前的文章里讲过。 这里会发生一个隐式类型转换嘛,会产生一个临时变量,临时变量的值就是y转换为int类型的值,然后再把这个临时变量赋给x。 另外临时变量具有常性,如果你赋给引用的话还得加const嘛。

2.1 派生类对象赋值给基类对象

那大家看这样可以吗?

<font color = black>在这里插入图片描述 还是我们上面写的两个类,Student继承了Person,那子类的对象可以赋值给父类的对象吗? 在这里插入图片描述 是可以的。 <font color = blue>不过要告诉大家此时我们是在公有继承的情况下,其它继承是不行的,不适用与我们接下来要讲的这个赋值转换的。只有公有继承才可以。 在这里插入图片描述 在这里插入图片描述

那这个过程有发生隐式类型转换吗?

<font color = blue>那要告诉大家这个过程其实是没有隐式类型转换的,公有继承是天然支持这个的。 在这里插入图片描述

那为什么天然支持呢,这个过程是怎样的呢?

<font color = black>因为他这里其实可以认为子类对象就是一个特殊的父类对象(在公有继承下面)。 就好比这里学生类继承了人这个类,那当然可以认为学生就是一个人啊(人这个类具有的成员学生类都有)。 这没毛病,所以可以认为它是一个天然支持的。 <font color = blue>所以,在公有继承中派生类对象可以赋值给父(基)类的对象,这个过程有一个形象的说法叫做切割/切片。 在这里插入图片描述 <font color = black>你可以认为这个过程就是把子类中父类的那一部分切割下来,依次赋给父类(当然这只是一个形象的说法,帮助大家理解)。

2.2 派生类对象赋值给基类对象的引用

那除此之外呢,还可以这样搞:

<font color = black>在这里插入图片描述 <font color = blue>还可以赋给Person类对象的引用,即子类对象可以赋值给基类对象的引用。 <font color = black>不加const也没报错,这也证明了这里没有产生临时变量(因为临时变量具有常性)。 那这个我们可以怎么理解? 我们说引用其实就是起别名嘛,那这里rp就可以认为是子类对象s中属于父类Person的那一部分的别名。 在这里插入图片描述

2.3 派生类对象的地址赋值给基类对象的指针

然后呢还支持这样搞:

<font color = black>在这里插入图片描述 <font color = blue>即子类对象的地址可以赋值给父类对象的指针。 <font color = black>那对它解引用就相当于拿到一个父类的对象,这个父类对象可以认为是从子类对象中切出来的属于父类的那一部分。 在这里插入图片描述

2.4 基类对象不能赋值给派生类对象

我们刚才是把派生类对象赋值给基类,那反过来可以吗?基类可以赋值给派生类吗?

<font color = blue>是不行的,基类对象不能赋值给派生类对象 <font color = black>大家可以还按我们上面说的那种切片的方式去理解 在这里插入图片描述 显然是不行啊,因为子类的成员有可能比父类多啊,你把父类赋给子类,是不是有可能不够用啊。

3. 继承中的作用域

3.1 基类和派生类作用域相互独立

首先大家要知道:

<font color = blue>在继承体系中基类和派生类都有各自独立的作用域。

那大家思考这样一个问题:既然子类和父类的类域或者说作用域是相互独立的,那在子类和父类中可不可以有同名的成员

<font color = black>之前我们说过,在一个工程里面可以有同名的变量或函数等,只要它们不在同一个域就可以,因为在同一个域的话就会出现命名冲突的问题。 那在子类和父类中可以吗? 那我们来试一下: 在这里插入图片描述 现在我们的子类和父类是这样的,父类Person里面有一个成员变量_num,子类Student里面也有一个。 在这里插入图片描述 没什么问题,程序是可以正常运行的。

那如果我们现在调用Student的成员函数打印_num,打印的是哪一个呢?

<font color = black>在这里插入图片描述 我们看到打印的是子类中的_num 那这样很正常嘛,跟我们正常想的是一样的,可以认为还是局部优先嘛。 因为我子类所在的这个局部域本来就有一个_num,就算不继承的话我也本来就有,所以我还是优先访问我自己的。 就好比你和你爸爸有一双一样的鞋子,连尺码都一样,那你穿鞋的时候肯定正常情况下还是先穿自己的鞋子嘛。

那如果我想访问父类的_num,能做到吗?

<font color = black>当然可以,我们指定作用域就行了嘛。 在这里插入图片描述

3.2 隐藏/重定义

那在继承体系中,如果出现这种情况,即子类和父类中有同名成员,它有一个专属的称谓

<font color = blue>子类和父类中有同名成员,子类成员将屏蔽对父类同名成员的直接访问(默认访问到的是子类的那一个),这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)。

我们继续,刚才是子类和父类中出现同名的成员变量,那如果是出现同名的成员函数呢?

比如像这样:

<font color = black>在这里插入图片描述 有两个类,B继承A,B和A中都有一个名为fun的函数。

那么请问大家,这两个同名函数是什么关系?

<font color = black>那我们上面不是刚说嘛,子类和父类有同名成员,构成隐藏嘛。 但是仔细观察我们发现,这两个函数函数名确实相同,但是参数不同啊,那这不是函数重载的一种情况嘛。 那这里到底是函数重载还是隐藏啊? 但是啊,大家要记好了。函数重载是有前提的,函数重载要求在同一个作用域里面。

所以这里不是函数重载,还是隐藏。

<font color = blue>是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。 在这里插入图片描述 <font color = black>我们可以直接调用B里面的fun函数,但是A里面的不行,因为它被隐藏了 在这里插入图片描述 如果想调,需要指定作用域 在这里插入图片描述

但是呢:

<font color = blue>注意在实际中在继承体系里面最好不要定义同名的成员

4. 派生类的默认成员函数

在之前类和对象的学习中,我们学过类里面有6个默认成员函数 在这里插入图片描述 即我们不写,编译器可以自动生成,那在派生类中,这6个默认成员函数是如何生成的呢?

4.1 构造函数

下面我就来分析一下。 先来看构造函数:

class Person
{
public:
    Person(const char* name = "peter")
        : _name(name)
    {
        cout << "Person()" << endl;
    }
​
    Person(const Person& p)
        : _name(p._name)
    {
        cout << "Person(const Person& p)" << endl;
    }
​
    Person& operator=(const Person& p)
    {
        cout << "Person operator=(const Person& p)" << endl;
        if (this != &p)
            _name = p._name;
​
        return *this;
    }
​
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name; // 姓名
};

这是我们熟悉的人这个类,这里面我们自己实现了构造、拷贝构造、赋值重载和析构这些函数。

然后还是我们的Student类来继承它:

class Student : public Person
{
protected:
    int _num; //学号
};

然后我们在main函数中创建一个Student 类的对象并运行程序,子类我们没有实现拷贝构造,我们看看默认生成的是是什么行为?

<font color = black>在这里插入图片描述 而我们的Student类里面只定义了一个成员变量_num,其它啥也没写。 但我们看到s自动去调用了它父类的构造和析构。

那如果我子类对象自己显示实现构造函数呢?

<font color = black>在这里插入图片描述 我们发现这里我们自己初始化继承下来的_name成员但是报错了。

为什么不行呢?那这里要告诉大家的是:

<font color = blue>派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。 <font color = black>所以,我们要这样写 在这里插入图片描述 在这里插入图片描述 所以,这里我们就可以认为必须分开处理,子类的构造函数即使我们不写,默认生成构造函数的在初始化从父类继承下来的成员时也会自动去调父类的构造函数;如果我们自己实现了子类的构造函数,那要求我们必须调用用基类的构造函数初始化基类的那一部分成员。

那如果我们写了构造,但不去处理从父类继承下来的成员呢?

<font color = black>在这里插入图片描述 我们不手动去调,在初始化列表还是会自动去调父类的默认构造。 在这里插入图片描述

那如果父类没有默认构造呢?

<font color = blue>如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用 在这里插入图片描述 在这里插入图片描述

那其实这里感觉就有点像子类里面有一个父类的自定义类型的成员一样。当然有的地方可能还是有差异的,比如自定义类型成员如果没有默认构造,必须在初始列列表初始化(C++11之后可以给缺省值),但是父类没有默认构造的话,就只能在初始化列表显示调父类的构造。

4.2 拷贝构造

那还是以上面那个继承体系为例,我们来讨论一下子类的拷贝构造是怎么样的。

首先我们父类Person是显示实现了拷贝构造的,那如果子类不实现拷贝构造,默认生成的是什么行为呢?

<font color = black>在这里插入图片描述 那我们看到其实和构造一样,也是去自动调了父类的拷贝构造,完成父类部分的拷贝。

那我们自己实现拷贝构造呢?

<font color = black>在这里插入图片描述 我们发现对于父类成员也不能像正常的那样写。而是: <font color = blue>派生类的拷贝构造函数必须调用基类的拷贝构造完成基类部分成员的拷贝初始化 <font color = black>在这里插入图片描述 但是Person的拷贝构造要传Person类的对象啊,这里传s可以吗? 在这里插入图片描述 那这是不是我们上面讲过的知识啊,子类的对象是可以赋值给父类对象引用的,它支持天然的赋值转换。 在这里插入图片描述 这样就可以了。

所以其实我们上面赋值转换那里讲的子类对象赋值给父类对象Person p = s其实就是调用父类的拷贝构造。 在这里插入图片描述

4.3 赋值重载

那赋值重载呢其实也是一样,我们这里就不再那么仔细的分析了

<font color = blue>派生类的operator=必须要调用基类的operator=完成基类部分的赋值。

演示一下:

<font color = black>在这里插入图片描述 在这里插入图片描述 大家看这种情况一般就是程序有问题挂了之类的。 我们来调试一下: 在这里插入图片描述 栈溢出了,为什么会栈溢出呢? 我们可以观察一下调用堆栈 在这里插入图片描述 在这里插入图片描述 我们看到应该是operator=这里死递归了。

为什么会这样呢?

<font color = black>🆗,因为子类的operator=和父类的operator=发生了隐藏,这也是我们上面刚讲过的。 所以我们默认访问到的是子类的,因此这里就死递归了 指定一下作用域就行了 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 就可以了。

那上面这三个,我们可以让子类的也打印一下信息,观察一下它们调用的顺序:

<font color = black>在这里插入图片描述 我们发现都是一个先父后子的顺序。

4.4 析构函数

那析构函数呢?按照上面的规律,都是先调用父类的处理父类成员,然后再处理自己的,那我们可以这样写:

<font color = black>在这里插入图片描述 但我我们发现这里调不了父类的析构。

那为什么调不了父类的析构呢?

<font color = black>我们后面马上会学习多态,由于后面多态里面的一些原因(这个我们后面讲到会说),编译器会对析构函数名进行特殊处理,都会被处理成destrutor(),所以这里子类和父类的析构就会构成隐藏。 因此我们调不了父类的析构。

那隐藏的话我们可以指定类域去调:

<font color = black>在这里插入图片描述 那现在调是调到了,但是我们发现不对劲啊! 父类的析构是不是被调用了两次啊,多调一次。

那如果我们自己不显式调用呢?

<font color = black>在这里插入图片描述 这下就好了,所以父类的析构他会自动调,但是前面那几个都是先父后子,这个析构怎么先调用子类的,再是父类啊。 🆗,因为父类的部分是先构造的,子类自己的成员后构造,所以析构的时候要先析构子类,后析构父类。 这不是跟我们之前说的先定义的对象后析构一个道理嘛。 所以这里才不需要我们自己去显式调父类的,而是它自己调,其实就是为了保证这个顺序。

所以对于析构函数:

<font color = blue>派生类(子类)的析构函数会在调用完成后自动调用基类(父类)的析构函数清理基类成员。因为这样才能保证先清理子类成员再清理父类成员的顺序。 在这里插入图片描述 派生类对象初始化先调用基类构造再调派生类构造。 派生类对象析构清理先调用派生类析构再调基类的析构。

5. 继承与友元

友元关系不能继承,也就是说父类的友元不是子类的友元,不能访问子类私有和保护成员。

比如:

class Student;
class Person
{
public:
    friend void Display(const Person& p, const Student& s);
protected:
    string _name; // 姓名
};
class Student : public Person
{
protected:
    int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
    cout << p._name << endl;
    cout << s._stuNum << endl;
}

在这里插入图片描述

6. 继承与静态成员

首先我们来回顾一下什么是静态成员: 在这里插入图片描述

静态成员不属于某个具体的对象,存放在静态区,被所有类对象所共有。所以:

<font color = blue>基类定义了static静态成员,则整个继承体系里面只有一个这样的成员,静态成员被所有类对象包括起子类和子类的子类共享。 无论派生出多少个子类,都只有一个static成员实例 。

我们可以来看一下:

class Person
{
public:
    Person() { ++_count; }
protected:
    string _name; // 姓名
public:
    static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
    int _stuNum; // 学号
};
class Graduate : public Student
{
protected:
    string _seminarCourse; // 研究科目
};

有三个类,父类Person,里面有一个静态成员_count,Student继承Person,Graduate继承Student。

那我先来问大家一个问题:

<font color = black>在这里插入图片描述 我先来把这个Person里面的name换成public修饰。 然后我问大家,Student继承了Person,那他们的name成员是一个吗? 这肯定不是的。我继承了你,我也有独属于自己的_name属性。 在这里插入图片描述 我们看到它们地址也是不一样的。

那对于静态成员来说呢?

<font color = black>应该是一样的,我们上面说了继承体系只有一个静态成员 在这里插入图片描述 它们的地址是一样的。

那来看这样一个问题:

<font color = black>在这里插入图片描述 我在main函数中定义了一些对象,现在我想统计一些总共的个数。 那这时候我们其实就可以通过静态成员去解决这个问题。 Person不是有一个静态成员变量count吗,只需在Person的构造函数里面加一句++count就行了 在这里插入图片描述 在这里插入图片描述 为什么这样就可以呢? 因为该继承体系里面每个类创建对象都会调Person的构造函数,而count又是整个继承体系共享的,只有一份,那每创建一个对象,count就++一次,最后对象的个数就是_count的值。

7. 如何实现一个不能被继承的类

那学到这里,大家来思考一个问题,如何去实现一个不能被继承的类?

我们可以将它的构造函数或析构函数设置成私有的。 <font color = black>为什么这样就可以呢? 因为子类的构造和析构必须去调用父类的,而如果我们设置成私有的话,子类继承下来在子类中是不可见的,就没办法调用,那就连构造都构造不了 。 在这里插入图片描述 在这里插入图片描述 <font color = blue>当然C++11有一个final关键字也可以解决这个问题。 <font color = black>那我们后面学到再说。

不过呢,第一种方法大家可能会有这样的疑问:

<font color = black>你把A的构造搞成私有的,那A在类外面不是也调不到了嘛,那A也没法创建对象了啊? 🆗,那在类外确实调不了,但在类里面可以啊,所以我们可以提供再一个函数去作为创建对象的方法 在这里插入图片描述 但是非静态成员函数的通过对象去调用啊,可是现在没对象怎么调? 那我们就可以把它搞成静态的。 在这里插入图片描述 这样就可以了。 当然后面学了final之后用final就很方便,这个大家了解一下。

8. 用到的代码

//class Person
//{
//public:
//  void Print()
//  {
//      cout << "name:" << _name << endl;
//      cout << "age:" << _age << endl;
//  }
////private:子类不可见
//protected: //子类可见
//  string _name = "peter"; // 姓名
//  int _age = 18; // 年龄
//};
//
//class Student : public Person
//{
//public:
//  void func()
//  {
//      cout << _name << endl;
//      cout << "void func()" << endl;
//  }
//protected:
//  int _stuid; // 学号
//};
//
//int main()
//{
//  /*Student s;
//  s.Print();*/
//
//  /*double y = 5.5;
//  int x = y;*/
//
//  Student s;
//  Person p = s;
//  Person& rp = s;
//  Person* pp = &s;
//  //pp->Print();
//  return 0;
//}
​
//class Person
//{
//protected:
//  string _name = "小李子"; // 姓名
//  int _num = 111;  // 身份证号
//};
//class Student : public Person
//{
//public:
//  void Print()
//  {
//      cout << _name << endl;
//      cout << Person::_num << endl;
//      cout << _num << endl;
//  }
//protected:
//  int _num = 999; // 学号
//};
//
//int main()
//{
//  Student s;
//  s.Print();
//  return 0;
//}
​
//class A
//{
//public:
//  void fun()
//  {
//      cout << "func()" << endl;
//  }
//};
//class B : public A
//{
//public:
//  void fun(int i)
//  {
//      cout << "func(int i)->" << i << endl;
//  }
//};
//int main()
//{
//  B b;
//  b.A::fun();
//  return 0;
//}
​
//class Person
//{
//public:
//  Person(const char* name = "peter")
//      : _name(name)
//  {
//      cout << "Person()" << endl;
//  }
//
//  Person(const Person& p)
//      : _name(p._name)
//  {
//      cout << "Person(const Person& p)" << endl;
//  }
//
//  Person& operator=(const Person& p)
//  {
//      cout << "Person operator=(const Person& p)" << endl;
//      if (this != &p)
//          _name = p._name;
//
//      return *this;
//  }
//
//  ~Person()
//  {
//      cout << "~Person()" << endl;
//  }
//protected:
//  string _name; // 姓名
//};
//class Student : public Person
//{
//public:
//  Student(const char* name, int num)
//      :Person(name)
//      ,_num(num)
//  {
//      cout<<"Student()" << endl;
//  }
//  Student(const Student& s)
//      :Person(s)
//      ,_num(s._num)
//  {
//      cout << "Student(const Student& s)" << endl;
//  }
//  Student& operator=(const Student& s)
//  {
//      if (this != &s)
//      {
//          Person::operator=(s);
//          _name = s._name;
//      }
//      cout << "Student& operator=(const Student& s)" << endl;
//      return *this;
//  }
//  ~Student()
//  {
//      //Person::~Person();
//      cout << "~Student()" << endl;
//  }
//protected:
//  int _num; //学号
//};
//
//int main()
//{
//  Student s1("zhangsan", 2023);
//  return 0;
//}
​
​
//class Student;
//class Person
//{
//public:
//  friend void Display(const Person& p, const Student& s);
//protected:
//  string _name; // 姓名
//};
//class Student : public Person
//{
//protected:
//  int _stuNum; // 学号
//};
//void Display(const Person& p, const Student& s)
//{
//  cout << p._name << endl;
//  //cout << s._stuNum << endl;
//}
//int main()
//{
//  Person p;
//  Student s;
//  Display(p, s);
//  return 0;
//}
​
//class Person
//{
//public:
//  Person() { ++_count; }
//public:
//  string _name; // 姓名
//public:
//  static int _count; // 统计人的个数。
//};
//int Person::_count = 0;
//class Student : public Person
//{
//protected:
//  int _stuNum; // 学号
//};
//class Graduate : public Student
//{
//protected:
//  string _seminarCourse; // 研究科目
//};
//int main()
//{
//  Person p;
//  Student s1;
//  Student s2;
//  Student s3;
//  Graduate s4;
//  cout << " 人数 :" << Person::_count << endl;
//  Student::_count = 0;
//  cout << " 人数 :" << Person::_count << endl;
//
//  return 0;
//}
​
//class A
//{
//public:
//  static A CreateObj()
//  {
//      return A();
//  }
//private:
//  A()
//  {}
//};
//class B :public A
//{
//};
//int main()
//{
//  //B b;
//  A::CreateObj();
//  return 0;
//}

下一篇文章,我们要讲一下C++中复杂的菱形继承及菱形虚拟继承,敬请期待...

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。