C++三大特性—继承“名字搜索与默认成员函数”

news/2024/12/2 16:34:17/

继承中的类的作用域

每个类定义自己的作用域,在这个作用域中定义自己的成员。当存在继承关系时,派生类的作用域嵌套在基类的作用域之中。如果一个名字在派生类的作用域中无法解析,那么编译器将继续在外层的基类中寻找该名字的定义。

继承关系如下:在这里插入图片描述
我们看下面代码:

	Child obj;obj.fun();

   我们通过Child类型的obj去调用fun函数,所以我们首先在Child类域中查找,如果没找到。
   因为Child继承于Teacher,Child是Teacher的派生类,所以接下来我们继续在Teacher的类域中查找fun函数,如果没找到。
   因为Teacher继承于Person,Teacher是Person的派生类,所以接下来我们继续在Person的类域中查找。一直找到最终的基类。

编译时的名字搜索

一个对象、引用、指针的静态类型决定了该对象的哪个成员是可见的,即使它的静态类型与动态类型可能不一致,但是它能使用哪个类型依然是由静态类型决定的

举个例子:

class Person
{
protected:string _name; // 姓名string _sex;  // 性别int _age;     // 年龄
};
class Teacher : public Person
{
public:int _No; // 学号void fun() const{cout << _No;}
};

我们只能通过Teacher及其派生类的对象、指针、引用去访问fun函数

	Teacher obj;              Teacher* obj_Tea = &obj;  //静态类型与动态类型一致Person* obj_Per = &obj;   //静态类型与动态类型不一致obj_Tea->fun();           //正确、类型为Teacherobj_Per->fun();           //错误、类型为Person

   虽然obj之中确实是有一个名字为fun的函数,但是这个成员对于obj_Per是不可见的。obj_Per的类型是一个Person类型,那么就意味着对于fun的的搜索是从Person开始的,显然Person类中没有fun函数,所以我们无法通过Person的对象、指针、引用去调用fun。

名字冲突与继承

  • 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
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; // 学号
};
void Test()
{Student s1;s1.Print();
};

运行结果:在这里插入图片描述
   子类成员中有_num,父类成员中也有_num,所以正常在子类里面访问_num,会隐藏父类继承来的_num,而访问子类本身有的成员,如果要访问这个隐藏的成员,需要在前面加上Person::

  • 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){A::fun();//调用父类的cout << "func(int i)->" << i << endl;}
};
void Test()
{B b;b.fun(10);
};

运行结果:在这里插入图片描述
B中的fun和A中的fun不是构成重载,因为不是在同一作用域
B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。

如果有隐藏默认调用自身所在类的,如果需要调用父类的,就加上 父类::(A::)

这种方式叫:使用作用域运算符来使用隐藏的基类成员
作用域运算符将覆盖掉原有的查找规则,并指示编译器从指定类的作用域开始查找成员

派生类的成员将隐藏同名的基类成员
除了覆盖继承而来的虚函数之外,派生类最好不要重用其他定义在基类中的名字。

理解函数调用的解析过程对于理解C++继承至关重要:
如果我们需要调用一个函数:obj.fun();
1.我们首先要确定obj的静态类型,因为我们调用的是一个成员,所以该类型必然是类类型
2.在obj静态类型对应的类中查找fun,如果找不到,则依次在直接基类中不断查找直到到达继承链的顶端,如果找完了还找不到,编译器则会报错
3.一旦找到了fun,先进行常规的类型检查,以确定找到的fun合法
4.调用合法,编译器将根据调用的是否是虚函数而产生不同的代码

  • 如果fun是虚函数且是通过指针或引用,则编译器产生的代码将在运行时确定到底运行该虚函数的哪个版本,依据的是对象的动态类型
  • 如果不是虚函数,是通过对象(而非引用或指针)进行调用,编译器将产生一个常规函数调用

名字查找优先类型检查

   声明在内层作用域的函数并不会重载声明在外层作用域的函数、因此,定义在派生类中的函数也不会重载其基类中的成员
   如果派生类(即内层作用域)的成员与基类(即外层作用域)的某个成员同名,那么派生类将在其作用域内隐藏该基类成员,即使该派生类成员与基类成员的形参列表不一致,基类成员仍然会被隐藏。


派生类的默认成员函数

派生类的构造函数

   派生类对象中含有从基类继承过来的成员,但是派生类并不能直接初始化这些成员,派生类必须使用基类的构造函数初始化它的基类部分。

   派生类对象的基类部分与它自己的数据成员都是在构造函数的初始化阶段执行初始化操作的。派生类构造函数同样是通过构造函数初始化列表来讲实参传递给基类的构造函数。
且看下面分析:

class Person
{
public:Person(const string name,const string sex,int age):_name(name),_sex(sex),_age(age){}
protected:string _name; // 姓名string _sex;  // 性别int _age;     // 年龄
};
class Teacher : public Person
{
public:Teacher(const string name, const string sex, int age,int No):Person(name,sex,age),_No(No){}int _No; // 学号
};

   Teacher自己的构造函数,将前三个参数传递给Person的构造函数,Person的构造函数负责初始化Teacher的基类部分,接下来初始化派生类自己定义的成员,最后运行Teacher空的函数体。
   除非我们特别指出,否则派生类对象的基类部分会像数据成员一样执行默认初始化,如果你想使用其他基类的构造函数,我们需要以类名加圆括号内的实参列表的形式来为构造函数提供初始值。这些实参将告诉编译器到底使用哪一个构造函数来初始化派生类的基类部分。

首先初始化基类部分,然后按照声明的顺序依次初始化派生类的成员

在这里插入图片描述
1、子类析构函数和父类析构函数构成隐藏关系。(由于多态关系需求,所有析构函数都会特殊处理成destructor函数名)
2、子类先析构,父类再析构。子类析构函数不需要显示调用父类析构,子类析构后会自动调用父类析构

默认成员函数规则总结:
1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造
6. 派生类对象析构清理先调用派生类析构再调基类的析构
7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系


派生类的声明

  派生类的声明与其他类差别不大,声明包含类名但是不包含它的派生列表

class Student : Person; //错误:派生列表不能出现在声明
class Student;          //正确声明方式

一条声明语句的目的是令程序知晓某个名字的存在,以及该名字表示一个什么样的实体。派生列表以及定义有关的细节必须与类的主体一起出现。

用作基类的类

如果我们想将某个类用作基类,那么这个类必须已经定义而非只声明

一个类是基类,同时它也可以是一个派生类,但是一个类不能派生它自己。

class Person{...};
class Teacher : private Person{...};
class Child : public Teacher{...};

  在这个继承关系中,Person是Teacher的直接基类,同时也是Child的间接基类。
  每个类都会继承直接基类的所有成员。最终的派生类将包含它的直接基类的子对象以及每个间接基类的子对象。

继承与友元

   就像友元关系不能传递一样,友元关系同样不能继承。基类的友元在访问派生类的成员时不具有特殊性,类似的,派生类的友元也不能随意访问基类的成员

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;
}
void main()
{Person p;Student s;Display(p, s);
}

Display是基类Person的友元,cout << s._stuNum << endl;这条语句想要访问Students的受保护成员_stuNum,显然是不可以的。基类友元不能访问派生类私有和保护成员。
不能继承友元关系,每个类负责控制各自成员的访问权限


继承与静态成员

  如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义,不论从基类中派生多少个派生类出来,对于每个静态成员来说都只存在唯一实例。

class Person
{
public:static string _age;
};

静态成员遵循通用的访问规则,如果基类中成员是private,那么派生类无权访问它。假设某静态成员是可访问的,那么我们既可以通过基类也能通过派生类使用它。


如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀


http://www.ppmy.cn/news/58325.html

相关文章

运维——ssh无法登录云服务器

0x00 概述 一般来讲&#xff0c;无法登录ssh的原因挺多&#xff0c;如果无法登录云服务器&#xff0c;则除了要检查ssh端口是否放行&#xff0c;防火墙状态外&#xff0c;还需要检查云服务器web控制台入站规则是否开放了对应端口。如果你前面检查都是正常&#xff0c;那么还需…

你的个人AI助理Pi来了

还记得之前的文章《不要老盯着ChatGPT&#xff0c;这几家公司的产品同样不容小觑》提到的Inflection AI公司吗&#xff1f;通过其官方推文了解到&#xff0c;前期我们关注的个人AI助理有了新的进展&#xff0c;Pi开始对外发布。 Pi是什么 Pi 是一种 AI&#xff0c;一种旨在提供…

数据库之约束、索引和事务

一、约束 约束,顾名思义就是数据库对数据库中的数据所给出的一组检验规则.负责判断元素是否符合数据库要求.其目的就是为了提高效率以及准确性. 1.not null - > 数据元素非空 表示如果插入数据,则当前数据不能为空. //创建一张学生表,其班级id和年级id不为空 create …

项目集的定义及管理

一、什么是项目集 项目集是相互关联且被协调管理的项目、子项目集和项目集活动&#xff0c;以便获得分别管理所无法获 得的效益。 以项目集的形式管理项目、子项目集及项目集活动能确保项目集组件的战略和工作计划根据各组 件的成果做出相应调整&#xff0c;或者按照发起组织的…

Hive3面试基础

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、基本知识Hive31.表的类型和表的存储格式a)b)c)创建表i&#xff09;ii&#xff09; 2.表 二、使用步骤1.引入库2.读入数据 总结 前言 面试准备之Hive 回顾…

postgresdb备份脚本

以下是一个简单的postgresdb备份脚本示例&#xff1a; 复制 #!/bin/bash # 设置备份目录和文件名 BACKUP_DIR/path/to/backup BACKUP_FILEdb_backup_$(date %F_%H-%M-%S).sql # 设置数据库连接参数 DB_HOSTlocalhost DB_PORT5432 DB_NAMEmydatabase DB_USERmyusername DB_PA…

Probabilistic and Geometric Depth: Detecting Objects in Perspective 论文学习

论文地址&#xff1a;Probabilistic and Geometric Depth: Detecting Objects in Perspective Github 地址&#xff1a;Probabilistic and Geometric Depth: Detecting Objects in Perspective 1. 解决了什么问题&#xff1f; 3D 目标检测在许多应用中发挥着重要作用&#xf…

2023年全国最新二级建造师精选真题及答案63

百分百题库提供二级建造师考试试题、二建考试预测题、二级建造师考试真题、二建证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 81.关于留置的说法&#xff0c;正确的是&#xff08;&#xff09;。 A.留置权属于债权 B.债…