C++ 派生类成员的标识与访问——作用域分辨符

news2024/9/28 1:15:54

在派生类中,成员可以按访问属性分为以下四种:
(1)不可访问成员。这是从基类私有成员继承下来的,派生类或是建立派生类对象的模块都无法访问到它们,如果从派生类继续派生新类,也是无法访问的。
(2)私有成员。包括从基类继承过来成员以及新增的成员,在派生类内部可以访问,但是建立派生类对象的模块无法访问,继续派生,就变成了新的派生类中的不可访问成员。
(3)保护成员。可能是新增也可能是从基类继承过来的,派生类内部成员可以访问,建立派生类对象的模块无法访问,进一步派生,在新的派生类中可能成为私有成员或者保护成员。
(4)公有成员。派生类、建立派生类对象的模块都可以访问,继续派生,可能是新派生类中的私有或者保护成员。

在对派生类的访问中,我们只能访问一个能够唯一标识的可见成员。如果通过某一个表达式能引用的成员不只一个,称为有二义性。

1.作用域分辨符

作用域分辨符就是我们常见的“::”,它可以用来限定要访问的成员所在的类的名称。一般的使用形式为:

类名::成员名//数据成员
类名::成员名(参数表)//函数成员

2.作用域分辨符在类族层次结构中唯一标识成员

对于在不同的作用域声明的标识符,可见性原则是:如果存在两个或多个具有包含关系的作用域,外层声明了一个标识符,而内层没有再次声明同名标识符,那么外层标识符在内层仍然可见;如果在内层声明了同名标识符,则外层标识符在内层不可见,这时称为内层标识符隐藏了外层同名标识符,这种现象叫做隐藏规则

在类的派生层次结构中,基类的成员和派生类新增的成员都具有作用域。二者的作用范围不同,是相互包含的两个层,派生类在内层。这时,如果派生类声明了一个和某个基类成员同名的新成员,派生类的新成员隐藏了外层基类中的同名成员,直接使用成员名只能访问到派生类的成员。如果派生类中声明了与基类成员函数同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式也都会被隐藏。。如果要访问隐藏的成员,就需要使用作用域分辨符和基类名来限定。

对于多继承情况,首先考虑各个基类之间有没有继承关系,同时也没有共同基类的情况。最经典的情况就是所有基类都没有上级基类。如果某派生类的多个基类拥有同名成员,同时,派生类又新增这样的同名成员,在这种情况下,派生类成员将隐藏所有基类的同名成员。 这时,使用“对象名.成员名”或者“对象指针->成员名”的方式可以唯一标识和访问派生类的新增成员,基类的同名成员也可以使用基类名和作用域分辨符访问。但是,如果派生类没有声明同名成员,使用“对象名.成员名”或者“对象指针->成员名”的方式就无法唯一标识成员。这时,从不同基类继承过来的成员具有相同的名称,同时具有相同的作用域,这时就必须通过基类名和作用域分辨符来标识成员。

【例】定义基类B1,B2,由基类B1,B2共同公有派生产生新类D。两个基类中都声明了数据成员v和函数fun,在派生类中新增同名的两个成员。这时的D类中共含有6个成员,而这6个成员只有两个名字。

#include<iostream>
using namespace std;

class B1//定义基类B1
{
public:
	int v;
	void fun()
	{
		cout << "基类B1的成员" << endl;
	}
};

class B2//定义基类B2
{
public:
	int v;
	void fun()
	{
		cout << "基类B2的成员" << endl;
	}
};

class D :public B1, public B2//定义派生类D
{
public:
	int v;//同名数据成员
	void fun()//同名函数成员
	{
		cout << "派生类D的成员" << endl;
	}
};

int main()
{
	D d;
	D* p = &d;

	d.v = 1;//对象名.成员名标识
	d.fun();//D类对象d访问D类成员函数fun

	d.B1::v = 2;//作用域分辨符标识
	d.B1::fun();//D类对象d访问B1类成员函数fun

	p->B2::v = 3;//作用域分辨符标识
	p->B2::fun();//D类对象d访问B2类成员函数fun

	return 0;
}

在这里插入图片描述
在主函数中,创建了一个派生类D的对象d,根据隐藏规则,如果通过成员名称来访问该类的成员,就只能访问到派生类新增的两个成员,从基类继承过来的成员由于外层作用域被隐藏。这时,就必须使用类名和作用域分辨符来访问从基类继承来的成员。

主函数中后面两组语句:

	d.B1::v = 2;//作用域分辨符标识
	d.B1::fun();//D类对象d访问B1类成员函数fun

	p->B2::v = 3;//作用域分辨符标识
	p->B2::fun();//D类对象d访问B2类成员函数fun

就是分别访问由基类B1、B2继承来的成员。通过作用域分辨符,明确地唯一标识了派生类中由基类所继承来的成员,达到了访问的目的,解决了成员被隐藏的问题。

如果在上例中,派生类没有声明与基类同名的成员,那么采用“对象名.成员名”就无法访问到任何成员,来自B1、B2 类的同名成员具有相同的作用域,系统根本无法进行唯一标识,这时就需要使用作用域分辨符。将上例中的派生类改为如下形式:

class D :public B1, public B2//定义派生类D
{};

程序其余部分不改变,主函数中“对象名.成员名”的访问方式就会出错:
在这里插入图片描述
如果希望 d.v = 1;d.fun();的用法不产生二义性,可以使用using关键字加以澄清。例如:

class D :public B1, public B2//定义派生类D
{
public:
	using B1::v;
	using B1::fun;
};

这样,主函数中的 d.v = 1;d.fun();都可以明确表示对B1中的相关成员的引用了。

using的一般功能是将一个作用域中的名字引入到另一个作用域中,它还有一个非常有用的用法:将using用于基类中的函数名,这样派生类中如果定义同名但参数不同的函数,基类的函数不会被隐藏,两个重载函数将会并存在派生类的作用域中。例如:

#include<iostream>
using namespace std;

class B1//定义基类B1
{
public:
	int v;
	void fun()
	{
		cout << "基类B1的成员" << endl;
	}
};

class D2 :public B1
{
public:
	using B1::fun;
	void fun(int i)
	{
		cout << i << endl;
	}
};

int main()
{
	D2 dd;
	dd.fun();
	dd.fun(5);

	return 0;
}

运行结果:

在这里插入图片描述
这时使用D2的对象,既可以直接调用基类B1中的无参数的fun,又可以直接调用派生类D2中带int型参数的fun函数。

如果某个派生类的部分或全部直接基类是从另一个共同的基类派生而来的,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,因此派生类中也就会产生同名的现象,对这种类型的同名成员也要使用作用域分辨符来唯一标识,而且必须用直接基类来进行限定。

【例】有一个基类B0,声明了数据成员v0和函数成员fun0,由B0公有派生了类B1和类B2,在以B1,B2作为基类共同公有派生了新类D。在派生类中不再添加新的同名成员,这时的D类,就含有通过B1,B2继承来的基类B0中的同名成员v0和fun0。

class B0
{
public:
	int v0;
	void fun0()
	{
		cout << "基类B0的成员" << endl;
	}
};
class B1 :public B0
{
public:
	int v1;
 };
class B2 :public B0
{
public:
	int v2;
};
class D :public B1, public B2
{
public:
	int v;
	void fun()
	{
		cout << "基类D的成员" << endl;
	}
};

int main()
{
	D d;
	d.B1::v0 = 2;
	d.B1::fun0();
	d.B2::v0 = 3;
	d.B2::fun0();
	return 0;
}

运行结果:
在这里插入图片描述
分析:
在主函数中,创建了派生类D的对象d,如果只通过成员名来访问该类的成员v0和fun0,系统无法唯一确定要引用的成员。这时,必须采用作用域分辨符,通过直接基类名来确定要访问的从基类继承来的成员。

这种情况下,派生类的对象在内存中就同时拥有成员v0的两份同名副本。如下图所示:
在这里插入图片描述

对于数据成员来讲,虽然两个v0可以分别通过B1和B2调用B0的构造函数进行初始化,可以存放不同的数值,也可以使用作用域分辨符通过直接基类名限定来分别进行访问,但是很多情况下,我们只需要一个数据副本。同一成员的多份副本增加了内存的开销。C++中提供了虚基类技术解决这一问题。

【注意】上例中,其实B0类的函数成员fun0()的代码始终只有一个副本,之所以调用fun0函数时仍然需要用基类名B1和B2加以限定,是因为调用非静态成员函数总是针对特定的对象,执行函数时需要将指向该类的一个对象的指针作为隐含的参数传递给被调函数来初始化this指针。上例中,D类的对象中存在两个B0类的子对象,因此调用fun0函数时,需要使用B1和B2加以限定,这样才能明确针对哪个B0对象调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/853571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenLayers入门,OpenLayers视图飞行动画,OpenLayers飞行到指定经纬度位置

专栏目录: OpenLayers入门教程汇总目录 前言 本章实现OpenLayers视图飞行动画,根据经纬度和动画持续时长,飞行到指定地图位置。 上一章中可以直接通过修改中心点和层级跳转到指定位置:《Openlayers入门,Openlayers调整中心点坐标、Openlayers调整缩放级别、Openlayers调…

第八章:Linux信号

系列文章目录 文章目录 系列文章目录前言linux中的信号进程对信号的处理信号的释义 信号的捕捉信号的捕捉signal()信号的捕捉sigaction() 信号的产生通过终端按键产生信号前台进程与后台进程 kill()用户调用kill向操作系统发送信号raise()进程自己给自己发任意信号&#xff08;…

Qt事件过滤器

1 介绍 事件过滤器是一种机制&#xff0c;当某个QObject没有所需要的事件功能时&#xff0c;可将其委托给其它QObject&#xff0c;通过eventFilter成员函数来过滤实现功能。 2 主要构成 委托&#xff1a; ui->QObject1->installEventFilter(QObject2); eventFilter声明 …

【C++精华铺】4.C++类和对象(上)面向对象、类、this指针

目录 1. 面向过程和面向对象 2. 类的引入 3. 类的定义 4. 类的访问限定符和封装 4.1 类的访问限定符 4.2 封装 5. 类的作用域 6. 类的实例化 7. 类对象模型 7.1 类对象的存储方式 7.2 类的大小 7.2.1 空类的大小 7.2.2 结构体内存对齐规则 8. this关键字深入讲解 8.1…

如何选择适合自己的考试培训系统

随着考试的逐渐增多和竞争的加剧&#xff0c;许多人开始关注考试培训系统&#xff0c;以提高他们的考试成绩。然而&#xff0c;选择适合自己的考试培训系统并不容易&#xff0c;因为市场上有许多不同的培训系统可供选择。 1. 确定目标 在选择培训系统之前&#xff0c;首先要明…

Java并发编程(一)多线程基础概念

概述 多线程技术&#xff1a;基于软件或者硬件实现多个线程并发执行的技术 线程可以理解为轻量级进程&#xff0c;切换开销远远小于进程 在多核CPU的计算机下&#xff0c;使用多线程可以更好的利用计算机资源从而提高计算机利用率和效率来应对现如今的高并发网络环境 并发编程…

无涯教程-Perl - getpriority函数

描述 此函数返回进程(PRIO_PROCESS),进程组(PRIO_PGRP)或用户(PRIO_USER)的当前优先级。 参数WHICH指定要为PRIO_PROCESS,PRIO_PGRP或PRIO_USER之一设置优先级的实体,WHO是要设置的进程ID或用户ID。 WHO的值为0定义了当前流程,流程组或用户。这会在不支持系统getpriority()函…

C++STL简介:提升编程效率与可维护性的利器

目录 引言 一、STL基本概念 二、具体介绍 2.1 容器 2.2 算法 2.2.1 排序&#xff08;Sort&#xff09; 2.2.2 查找&#xff08;Find&#xff09; 2.3.2 复制&#xff08;Copy&#xff09; 2.3 迭代器 三、分析STL 优势&#xff1a; 缺点&#xff1a; 如何学习&#…

《合成孔径雷达成像算法与实现》Figure3.7

代码复现如下&#xff1a; clc clear all close all%参数设置 TBP 100; %时间带宽积 T 10e-6; %脉冲持续时间%参数计算 B TBP/T; …

做BI领域的ChatGPT,思迈特升级一站式ABI平台

8月8日&#xff0c;以「指标驱动 智能决策」为主题&#xff0c;2023 Smartbi V11系列新品发布会在广州丽思卡尔顿酒店开幕。 ​ 后疫情时代&#xff0c;BI发展趋势的观察与应对 在发布会上&#xff0c;思迈特CEO吴华夫在开场致辞中表示&#xff0c;当前大环境背景下&#xf…

行业追踪,2023-08-09

自动复盘 2023-08-09 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

讯飞星火大模型无需代码连接飞书即时消息的方法

1 使用场景 用户联络需求的升级&#xff0c;使智能客服与智能营销迎来飞速增长&#xff0c;客户服务也成为企业经营中非常重要的一环&#xff0c;如何提高客户服务质量和响应速度&#xff0c;一直是企业需要解决的难题。人工智能产品&#xff0c;也正在成为越来越多企业提升业务…

flutter3.0项目集成高得地图

文章目录 1.创建应用:注册高得开发平台 安卓端的设置flutter的集成ios端的设置 先看一下代码运行结果 安卓端真机 ios端真机屏幕录屏 本篇文章demo下载地址 下载后请用xcode修改自己的ios证书真机运行.flutter代码在PG1.dart里面修改 String androidKey “b3392bb7fe532b0eb…

Mac终端利器:Homebrew + iTerm2 + Oh My Zsh 教程

引言 前段时间调整了一下 iTerm2 的环境&#xff0c;感觉比以前好看多了&#xff0c;并且更加高效&#xff0c;这里做一个记录&#xff0c;希望能给大家一些启发。 工具介绍 brew&#xff1a;Mac OS 下强大的包管理工具。iTerm2&#xff1a;iTerm2是 Mac OS 终端的替代品&am…

实现vuex数据持久化处理

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 前言&#xff1a; 方案一 localStorage 介绍 值 示例 JSON.stringify() 介绍 语法 参数 返…

深入JVM - JIT分层编译技术与日志详解

深入JVM - JIT分层编译技术与日志详解 文章目录 深入JVM - JIT分层编译技术与日志详解1. 背景简介2. JIT 编译器2.1. 客户端版本的编译器: C12.2. 服务端版本的编译器: C22.3. Graal JIT 编译器 3. 分层编译技术(Tiered Compilation)3.1. 汇聚两种编译器的优点3.2. 精准优化(Ac…

人工智能、BIM技术、机器学习在智慧工地的应用

人工智能、BIM技术、机器学习在智慧工地的应用 智慧工地云平台是专为建筑施工领域所打造的一体化信息管理平台。通过大数据、云计算、人工智能、BIM、物联网和移动互联网等高科技技术手段&#xff0c;将施工区域各系统数据汇总&#xff0c;建立可视化数字工地。同时&#xff0…

⌈算法进阶⌋图论::并查集——快速理解到熟练运用

目录 一、原理 1. 初始化Init 2. 查询 find 3. 合并 union 二、代码模板 三、练习 1、 990.等式方程的可满足性 &#xff08;中等&#xff09; 2、 1061. 按字典序排列最小的等效字符串 &#xff08;中等&#xff09; 3、账户合并 &#xff08;较难&am…

QFontDialog

QFontDialog QFont字体类使用QFont类QFontDialog类的静态API简单的使用 QFontDialog类是QDialog的子类, 通过这个类我们可以得到一个进行字体属性设置的对话框窗口, 和前边介绍的对话框类一样, 我们只需要调用这个类的静态成员函数就可以得到想要的窗口了。 QFont字体类 关于…