C++类继承基础3——访问控制与继承

私有继承

在C++中,私有继承是一种继承方式,它定义了一个私有派生类,也称为派生类。私有继承意味着派生类继承了基类的所有成员,但这些成员在派生类中是私有的,对外部不可见。

要进行私有继承请使用private关键字,或者不使用任何关键字(因为private是默认值,因此省略访问限定符也将导致私有继承)

格式如下

class 派生类名:private 基类名
{
}或者class 派生类名:基类名
{
}

 使用私有继承时,只能在派生类的方法里使用基类的方法

私有继承允许使用类名和作用域解析运算符来调用基类的方法

我们看个例子

#include<iostream>
using namespace std;
class AA
{
private:int a_;
public:AA(int a):a_(a){}void A(){cout << a_ << endl;} 
};
class BB :AA
{
private:int b_;
public:BB(int a,int b):AA(a),b_(b){}void B(){A();//可以AA::A();//可以}
};int main()
{BB r(2, 3);r.B();}

 我们怎么通过派生类访问基类对象呢?当然是用强制类型转换

我们看个例子

#include<iostream>
using namespace std;
class AA
{
private:int a_;
public:AA(int a):a_(a){}void A(){cout << a_ << endl;} 
};
class BB :AA
{
private:int b_;
public:BB(int a,int b):AA(a),b_(b){}AA& B(){return (AA&)*this;//派生类强制转换为基类}
};int main()
{BB r(2, 3);AA t=r.B();t.A();}

在私有继承中,未进行显式类型转换的派生类引用或指针,无法赋值给基类的引用或指针

#include<iostream>
using namespace std;
class AA
{
private:int a_;
public:AA(int a):a_(a){}void A(){cout << a_ << endl;} 
};
class BB :AA
{
private:int b_;
public:BB(int a,int b):AA(a),b_(b){}};int main()
{BB r(2, 3);AA& t = r;//这是不行的AA* y = &r;//这是不行的
}

保护成员

class A
{
protected:
int a;
}
  1.  和私有成员类似,受保护的成员对于类的用户来说是不可访问的
  2. 和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的。
  3. 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于个基类对象中的受保护成员没有任何访问特权。

 为了理解最后一条规则,请考虑如下的例子:

class Base 
{
protected:
int prot_mem; // protected成员
};
class Sneaky:public Base{
friend void clobber(Sneaky&); // 能访问 Sneaky::prot_mem
friend void clobber(Base&); // 不能访问Base::prot mem
int j; // j默认是private
};
//正确:clobber能访问Sneaky对象的private和protected成员
void clobber(Sneaky &s) 
{s.i =s.prot_mem =0;} 
// 错误:clobber不能访问Base的protected成员
void clobber(Base &b) 
{b.prot_mem=0;}

如果派生类(及其友元)能访问基类对象的受保护成员,则上面的第二个clobber(接受一个Base&)将是合法的。该函数不是Base的友元,但是它仍然能够改变一个Base对象的内容。

如果按照这样的思路,则我们只要定义一个形如 Sneaky 的新类就能非常简单地规避掉protected提供的访问保护了。

要想阻止以上的用法,我们就要做出如下规定,即派生类的成员和友元只能访问派生类对象中的基类部分的受保护成员;对于普通的基类对象中的成员不具有特殊的访问权限。

 保护继承

保护继承是私有继承的变体。保护继承在列出基类时使用关键字protected:

class 派生类名:protected 基类名
{
}

使用保护继承时,基类的公有成员和保护成员都成为派生类的保护成员。

和私有继承一样,基类的接口在派生类中也是可用的,但是在继承层次结构之外是不可用的。当从派生类派生出另一个类时,私有继承和保护继承的区别就体现出来了。

使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的公有方法在派生类中变成私有方法;使用保护继承时,基类的公有方法在第二代中变成受保护的,因此第三代派生类可以使用它们。 

三种继承方式的比较

公有继承(public)继承、私有继承(private)、保护继承(protected)是常用的三种继承方式。

1.公有继承(public)

  公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的       状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。

2.私有继承(private)

  私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这      个派生类的子类所访问。

3.保护继承

  保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能         被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。

三种不同继承方式的基类特性和派生类特性

公有继承保护继承私有继承
公有成员变为派生类的公有成员派生类的保护成员派生类的私有成员
保护成员变成派生类的保护成员派生类的保护成员派生类的私有成员
私有成员变为只能通过基类接口访问只能通过基类接口访问

只能通过基类接口访问

能否隐式向上转换是(但只能在基类里)

 注:隐式向上转换:意味着无需进行显式类型转换,就可以将基类指针或引用指向派生类对象

对于公有继承方式

  1.基类成员对其对象的可见性:公有成员可见,其他不可见。这里保护成员同于私有成员。

  2.基类成员对派生类的可见性:公有成员和保护成员可见,而私有成员不可见。这里保护成员同于公有成员。

  3.基类成员对派生类对象的可见性:公有成员可见,其他成员不可见。

  所以:在公有继承时,派生类的对象可以访问基类中的公有成员;派生类的成员函数可以访问基类中的公有成员和保护成员。这里,一定要区分清楚派生类的对  象和派生类中的成员函数对基类的访问是不同的。

对于私有继承方式

  1.基类成员对其对象的可见性:公有成员可见,其他成员不可见

  2.基类成员函数对派生类的可见性:公有成员和保护成员是可见的,而私有成员是不可见的。

  3.基类成员对派生类对象的可见性:所有成员都是不可见的。

  所以:在私有继承时,基类的成员只能由直接派生类访问,而无法向下继承。

对于保护继承方式

这种继承方式与私有继承方式的情况相同。两者的区别仅在于对派生类的成员而言,而对于基类成员有不同的可见性。(可见性也就是可访问性)。

关于可访问性还有另外一种说法。这种规则中,称派生类的对象对基类访问为水平访问,称派生类的派生类对基类的访问为垂直访问。

1 #include<iostream>2 class A{3 private:4     int privatedataA;5 protected:6     int protecteddataA;7 public:8     int publicdataA;9 };
10 //基类A的派生类B(公有继承)
11 class B :public A{
12 
13 public:
14     void funcA()
15     {
16         int b;
17         b = privatedataA;
18         //错误:基类中的私有成员在派生类中不可见
19         b = protecteddataA;
20         //正确:基类的保护成员在派生类中是保护成员
21         b = publicdataA;
22         //正确:基类的公共成员在派生类是公共成员
23     }
24 };
25 //基类A的派生类C 私有继承
26 class C :private A{
27 
28 public:
29     void funcA()
30     {
31         int c;
32         c = privatedataA;
33         //错误:基类中的私有成员在派生类中不可见
34         c = protecteddataA;
35         //正确:基类的保护成员在派生类中是私有成员
36         c = publicdataA;
37         //正确:基类的公共成员在派生类是私有成员
38     }
39 };
40 //基类A的派生类D 保护继承
41 class D :protected A{
42 public:
43     void funcA()
44     {
45         int d;
46         d = privatedataA;
47         //错误:基类中的私有成员在派生类中不可见
48         d = protecteddataA;
49         //正确:基类的保护成员在派生类中是保护成员
50         d = publicdataA;
51         //正确:基类的公共成员在派生类是保护成员
52     }
53 };
54 void main()
55 {
56     int value;
57     B objB;
58     value = objB.privatedataA;//错误:基类的私有成员在派生类不可见,对对象不可见
59     value = objB.protecteddataA;//错误:基类的保护成员在派生类中是保护成员,对对象不可见
60     value = objB.publicdataA;//错误:基类的公共成员在派生类中是公共成员,对对象可见
61 
62     C objC;
63     value = objC.privatedataA;//错误:基类的私有成员在派生类不可见,对对象不可见
64     value = objC.protecteddataA;//错误:基类的保护成员在派生类中是私有成员,对对象不可见
65     value = objC.publicdataA;//错误:基类的公共成员在派生类中是私有成员,对对象不可见
66 
67     D objD;
68     value = objD.privatedataA;//错误:基类的私有成员在派生类不可见,对对象不可见
69     value = objD.protecteddataA;//错误:基类的保护成员在派生类中是保护成员,对对象不可见
70     value = objD.publicdataA;//错误:基类的公共成员在派生类中是保护成员,对对象不可见
71     system("pause");
72 }

公有,私有和受保护继承

某个类对其继承而来的成员的访问权限受到两个因素影响:一是在基类中该成员的访问说明符,二是在派生类的派生列表中的访问说明符。

举个例子,考虑如下的继承关系:

class Base {
public:
void pub mem();
protected: 
int prot_mem; 
private:
char priv_mem;};
struct Pub_Derv : public Base {
//正确:派生类能访问protected成员
int f() { return prot mem; }
//错误:private成员对于派生类来说是不可访问的
char g() { return priv_mem; }
};
struct Priv_Derv : private Base {
// private不影响派生类的访问权限
int f1() const{ return prot_mem;}

派生访问说明符对于派生类的成员(及友元)能否访问其直接基类的成员没什么影响,对
基类成员的访问权限只与基类中的访问说明符有关。

Pub_Dery和Priv_Derv都能须问613 受保护的成员prot_mem,同时它们都不能访问私有成员priv_mem。

派生访问说明符的目的是控制派生类用户(包括派生类的派生类在内)对于基类成前的访问权限:

Pub_Dery d1; // 继承自Base的成员是public的
Priv_Derv d2;//继承自Base的成员是private的
dl.pub_mem();// 正确:pub_mem在派生类中是public的
d2.pub_mem();//错误:pub_mem在派生类中是private的


Pub_Derv和Priv_Derv都继承了pub_mem函数。

如果继承是公有的, 则成员将遵循其原有的访问说明符,此时d1可以调用pub_mem。

在Priv_Derv中,Base 的成员是私有的,因此类的用户不能调用pub_mem。

派生访问说明符还可以控制继承自派生类的新类的访问权限:

struct Derived_from_Public : public Pub_Derv{//正确:Base::prot mem在Pub_Derv中仍然是protected的
int use_base  
{return prot mem;} 
};struct Derived_from_Private: pubiic Priv_Derv {
// 错误:Base::prot_mem在Priv_Derv中是private的
int use_base()  
{return prot_mem;} 
};


Pub_Derv 的派生类之所以能访问Base的prot_mem成员是因为该成员在Pub Dery中仍然是受保护的。相反,Priv Derv 的派生类无法执行类的访问,对于它们来说,Priv_Derv继承自Base的所有成员都是私有的。

假设我们之前还定义了一个名为Prot_Derv的类,它采用受保护继承,则Base的所有公有成员在新定义的类中都是受保护的。Prot_Derv的用户不能访问pub mem,但是Prot_Derv的成员和友元可以访问那些继承而来的成员。

派生类向基类转换的可访问性

派生类向基类的转换是否可访问由使用该转换的代码决定,同时派生类的派生访问说明符也会有影响。假定D继承自B:

  1. 只有当D公有地继承B时,用户代码才能使用派生类向基类的转换;如果D继承B的方式是受保护的或者私有的,则用户代码不能使用该转换。
  2. 不论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对于派生类的成员和友元来说永远是可访问的。
  3. 如果D继承B的方式是公有的或者受保护的,则D的派生类的成员和友元可以使用D向B的类型转换;反之,如果D继承B的方式是私有的,则不能使用

对于代码中的某个给定节点来说,如果基类的公有成员是可访问的,则派生类Tp 向基类的类型转换也是可访问的;反之则不行。

访问控制与继承

不考虑继承的话,我们可以认为一个类有两种不同的用户:普通用户和类的实现者
其中,普通用户编写的代码使用类的对象,这部分代码只能访问类的公有(接口)成员:实现者则负责编写类的成员和友元的代码,成员和友元既能访问类的公有部分,也能访问类的私有(实现)部分。

如果进一步考虑继承的话就会出现第三种用户,即派生类。基类把它希望派生类能够使用的部分声明成受保护的。普通用户不能访问受保护的成员,而派生类及其友元仍旧不能访问私有成员。

和其他类一样,基类应该将其接口成员声明为公有的;同时将属于其实现的部分分成两组:一组可供派生类访问,另一组只能由基类及基类的友元访问。对于前者应该声明为受保护的,这样派生类就能在实现自己的功能时使用基类的这些操作和数据;对于后者应该声明为私有的。

友元与继承

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

class Base 
{
protected:
int prot_mem; // protected成员
// 添加friend声明,其他成员与之前的版本一致
friend class Pal; // Pal在访问Base的派生类时不具有特殊性
};
class Sneaky:public Base{
friend void clobber(Sneaky&); // 能访问 Sneaky::prot_mem
friend void clobber(Base&); // 不能访问Base::prot mem
int j; // j默认是private
};class Pal {
public: 
int f(Base b) { return b.prot_mem; } //正确:Pal是Base的友元int f2(Sneaky s) { return s.j;} //错误:Pal不是Sneaky的友元
// 对基类的访问权限由基类本身控制,即使对于派生类的基类部分也是如此
int f3(Sneaky s) { return s.prot mem;} // 正确:Pal是Base的友元
};


如前所述,每个类负责控制自己的成员的访问权限,因此尽管看起来有点儿奇怪,但f3 615确实是正确的。Pal是Base的友元,所以Pal能够访问Base对象的成员,这种可访问性包括了Base对象内嵌在其派生类对象中的情况。

当一个类将另一个类声明为友元时,这种友元关系只对做出声明的类有效。对于原来那个类来说,其友元的基类或者派生类不具有特殊的访问能力:

// D2对Base的protected和private成员不具有特殊的访问能力
class D2 : public Pal {
public:
int mem(Base b)
{ return b.prot_mem; } //错误:友元关系不能继承


不能继承友元关系;每个类负责控制各自成员的访问权限。

改变个别成员的可访问性

有时我们需要改变派生类继承的某个名字的访问级别,通过使用using声明可以达到这一目的:
 

class Base 
{
public:
std::size_t size() const { return n;}
protected: 
std::size_t n;// 注意:private继承
class Derived : private Base {public://保持对象尺寸相关的成员的访问级别
using Base::size;protected:
using Base::n;
};


因为Derived 使用了私有继承,所以继承而来的成员size和n(在默认情况下)是Derived的私有成员。

然而,我们使用using声明语句改变了这些成员的可访问性。改变之后,Derived的用户将可以使用size成员,而Derived的派生类将能使用通过在类的内部使用using 声明语句,我们可以将该类的直接或间接基类中的任备可访问成员(例如,非私有成员)标记出来。

using声明语句中名字的访问权限由该声明语句之前的访问说明符来决定。

  1. 如果一条using 声明语句出现在类的private部分,则该名字只能被类的成员和友元访问;
  2. 如果using声明语句位于public部分,则类的所有用户都能访问它;
  3. 如果 using 声明语句位于protected 部分,则该名字对于成员、友元和派生类是可访问的。

派生类只能为那些它可以访问的名字提供using声明。

默认的继承保护级别

我们曾经介绍过使用struct和class关键字定义的类具有不同的默认访问说明符。

类似的,默认派生运算符也由定义派生类所用的关键字来决定。

默认情况下,使用class关键字定义的派生类是私有继承的;而使用 struct 关键字定义的派生类是公有继承的:

class Base { /*...*/};
struct D1: Base{ /*...*/}; // 默认 public继承
class D2 : Base{/*..,*/} // 默认 private 继承


人们常常有一种错觉,认为在使用struct关键字和class关键字定义的类之间还有更深层次的差别。事实上,唯一的差别就是默认成员访问说明符及默认派生访问说明符;除此之外,再无其他不同之处。

一个私有派生的类最好显式地将private声明出来,而不要仅仅依赖于默认的设置。显式声明的好处是可以令私有继承关系清晰明了,不至于产生误会。

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

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

相关文章

蓝桥杯十四届JavaB组省赛ABCD

A阶乘求和 从1&#xff01;一直加到202320232023&#xff01;&#xff0c;如果一个个算阶乘的后九位再相加&#xff0c;算法可以实现&#xff0c;但是运算量很大&#xff0c;需要一段时间。用计算器算了一下100&#xff01;阶乘发现后几位都是0&#xff0c;因此加到20232023202…

centos7.5 安装gitlab-ce (Omnibus)

一、安装前置依赖 # 安装基础依赖 $ sudo yum -y install policycoreutils openssh-server openssh-clients postfix# 启动 ssh 服务 & 设置为开机启动 $ sudo systemctl enable sshd & sudo systemctl start sshd二、安装gitlab rpm包 # 下载并执行社区版脚本 curl …

安装redis任意版本详解(包含yum安装和编译安装)

根据不同需求需要安装的redis版本不同&#xff0c;在此有编译安装和yum安装详细操作。&#xff08;3.x 5.x 6.x 版本安装都有写到&#xff0c;可以根据需要进行部署参考&#xff09; Yum安装redis yum install -y epel-release yum install -y redis 下载的是3.2.12版本 v…

AI如何影响装饰器模式与组合模式的选择与应用

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#xff1a;设计模式深度解析&#xff1a;AI如何影响…

【Java初阶(七)】接口

❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; 目录 1.前言2.接口2.1语法规则2.2接口使用2.3接口特性2.4实现多个接口2.5接口使用实例2.6Clonable接口和深拷贝 3.Object类3.1对象比较equals方法3.2hashcod…

vue系列——v-on

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>v-on指令</title> </head> <body>…

算法系列--递归,回溯,剪枝的综合应用(2)

&#x1f495;"对相爱的人来说&#xff0c;对方的心意&#xff0c;才是最好的房子。"&#x1f495; 作者&#xff1a;Lvzi 文章主要内容&#xff1a;算法系列–递归,回溯,剪枝的综合应用(2) 大家好,今天为大家带来的是算法系列--递归,回溯,剪枝的综合应用(2) 一.括号…

Java EE:Thread类中run和start的区别

目录 1、run 2、start 总结&#xff1a; Java 的线程是通过 java.lang.Thread 类来实现的。JVM 启动时会有一个由主方法所定义的线程&#xff08;main线程&#xff09;。可以通过创建 Thread 的实例来创建新的线程&#xff0c;从而实现多线程。 每个线程都是通过某个特定的 …

Memcached 教程之 Memcached set 命令(五)

Memcached set 命令 Memcached set 命令用于将 value(数据值) 存储在指定的 key(键) 中。 如果set的key已经存在&#xff0c;该命令可以更新该key所对应的原来的数据&#xff0c;也就是实现更新的作用。 语法&#xff1a; set 命令的基本语法格式如下&#xff1a; set key…

【MySQL】DQL-排序查询-语法&注意事项&可cv例题语句

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

【运维】Elsatic Search学习笔记

基本使用 Elasticsearch(简称ES): 是一个开源的高扩展的分布式全文搜索引擎 Docker安装Elasticsearch1 version: "3.1" services:elasticsearch:image: elasticsearch:7.13.3container_name: elasticsearchprivileged: trueenvironment:- "cluster.nameelast…

Redis经典面试笔试题整理汇总20题-指令举例-代码演示

五、Redis经典面试笔试题 Redis经典面试笔试题和大厂面试笔试题涉及的内容相当广泛&#xff0c;主要围绕Redis的基本概念、特性、数据结构、使用场景以及性能优化等方面。以下是一些常见的Redis面试题目及其解答&#xff1a; 题目1&#xff1a;Redis是什么&#xff1f;简述它…

数字孪生关键技术及体系架构

摘要&#xff1a; 数字孪生以各领域日益庞大的数据为基本要素&#xff0c;借助发展迅速的建模仿真、人工智能、虚拟现实等先进技术&#xff0c;构建物理实体在虚拟空间中的数字孪生体&#xff0c;实现对物理实体的数字化管控与优化&#xff0c;开拓了企业数字化转型的可行思路…

vue3+vite+cesium自定义材料处理

目录 存在问题 问题原因 解决思路 存在问题 在cesium1.99版本及以上,采用老的材料规格写法,基本上会出现如下问题,这个XXX重构的对象 报错add property XXXXX, object is not extensible 有些文章采用require,会报错require是undefined 问题原因 vue2采用Object.defi…

初始Java篇(JavaSE基础语法)(5)(类和对象(上))

个人主页&#xff08;找往期文章包括但不限于本期文章中不懂的知识点&#xff09;&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 目录 面向对象的初步认知 面向对象与面向过程的区别 类的定义和使用 类的定义格式 类的实例化 this引用 什么是this引用&#xff1f; this引用…

JS如何区分数组

在JavaScript中&#xff0c;你可以使用几种不同的方法来检测一个对象是否是数组。以下是一些常用的方法&#xff1a; 1.使用 Array.isArray() 方法 Array.isArray() 是一个静态方法&#xff0c;用于检测一个对象是否为数组。它返回一个布尔值。 let obj [1, 2, 3]; conso…

开源博客项目Blog .NET Core源码学习(13:App.Hosting项目结构分析-1)

开源博客项目Blog的App.Hosting项目为MVC架构的&#xff0c;主要定义或保存博客网站前台内容显示页面及后台数据管理页面相关的控制器类、页面、js/css/images文件&#xff0c;页面使用基于layui的Razor页面&#xff08;最早学习本项目就是想学习layui的用法&#xff0c;不过最…

《Retrieval-Augmented Generation for Large Language Models: A Survey》 AI 解读

论文链接&#xff1a;Retrieval-Augmented Generation for Large Language Models: A Survey 论文标题&#xff1a;《Retrieval-Augmented Generation for Large Language Models: A Survey》 一译中文版地址&#xff1a; https://yiyibooks.cn/arxiv/2312.10997v5/index.htm…

【LeetCode】热题100 刷题笔记

T1 两数之和 题目 链接&#xff1a; https://leetcode.cn/problems/two-sum/submissions/517876748/?envTypestudy-plan-v2&envIdtop-100-liked 【刷题感悟】这道题用两层for循环也能做出来&#xff0c;但我们还是要挑战一下时间复杂度小于 O ( n 2 ) O(n^2) O(n2)的解…

麒麟系统安装GDAL

一. 配置环境 最新版容易出问题&#xff0c;日常使用的话&#xff0c;gdal3.3.3就可以了。如果你需要最新版的&#xff0c;可能要去别的地方找找了。 1. gdal-3.3.3 https://github.com/OSGeo/gdal/releases/download/v3.3.3/gdal-3.3.3.tar.gz 2. proj-6.2.1 https://downl…