Android C++系列:C++最佳实践3继承与访问控制

比特幻翼使
• 阅读 147

1. 背景

Java中有四种访问控制:public、protected、default、private,它们的使用范围可以用下面一张表概括:

类内部本包子类外部包
public
protected
default
private

整个结构还是比较简单的,从类内部到本包到子类到外部包权限越来越小,比较好理解也比较好记忆。但是在C++中访问控制要复杂很多,因为不仅有属性和方法的访问控制,还有继承时的派生列表访问说明符。今天我们着重了解访问控制。

2. 受保护的成员protected

在Java中我们一般默认子类可以访问父类的protected,但是C++中要更复杂些。它有三个特征:

  1. 受保护的成员对于类的用户来说是不可访问的;
  2. 受保护的成员对于派生类的成员和友元来说是可访问的;
  3. 派生类的成员或友元只能通过派生类对象来访问积累的受保护成员,派生类对于一个基类对象中的受保护成员没有任何访问特权。

怎么理解第三点呢?举个例子:

class Base{
protected:
    int num;
}
class Sub:public Base{
    friend void set(Sub&);
    friend void set(Base&);
    int i;
}
void set(Sub& sub){
    sub.i = sub.num = 0;//正确,可以访问基类和派生类的成员
}
void set(Base &base){
    base.num = 0;//错误,不能访问基类的成员
}

为什么要这样设计呢?如果派生类的友元可以直接访问基类的受保护成员,那么我们对任何类,只要设计它的子类,在子类的友元函数中就可以访问基类受保护的成员了,破坏了设计访问控制的目的,这一点比Java要严谨一些,任何访问控制都会被反射虐的体无完肤。

所以这里的结论是:派生类的成员和友元只能访问派生类对象中的基类部分的受保护成员,对于普通的基类对象中的成员不具有特殊的访问权限。

3. 派生访问说明符

派生访问说明符的目的是控制抱愧派生类的派生类在内的派生类用户对于基类成员的访问权限:

  1. 如果继承是公有的,则成员将遵循其原有的访问说明符;
  2. 如果继承是私有的,派生类中基类的公有成员也会变成私有。

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

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

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

5. 改变个别成员的可访问性

我们经常在代码中看到using Base::size这样的语句,很蒙圈不知道是干什么。这里是为了改变派生类继承的某个名字的访问级别。比如我们大部分内容想要私有继承,那么派生类列表访问类型使用私有就行。但是如果私有继承时某个或者某几个字段想要公开就可以使用using语句。举个例子:

class Base{
public:
    int size() const{
        return n;
    }
protected:
    int n;
};
class Sub:private Base{
public:
    //保持size函数的可访问性
    using Base::size;
protected:
    using Base::n;
} 

6. 默认的继承保护级别

有时候我们很困惑strcut和class关键字的区别,其实它们唯一的区别就是具有不同的默认访问说明符和默认派生运算符。但是我们规范书写代码的话都尽量不适用默认访问权限,所以很多时候struct和class区别不大。

7. 名字冲突和继承

派生类的成员将隐藏同名的基类成员,因为编译器查找类属性时,会先从自己的作用域内找,如果找不到再查找直接基类,如果还是查不到再查找直接基类的基类...。

class Base{
public:
    Base():num(0){}
protected:
    int num;
}
class Sub:public Base{
public:
    Sub(int i):num(i){}
    int getNum(){
        return num;
    }
protected:
    int num;
}

void main(){
    Sub s(20);
    cout << s.getNum <<end;//打印结果为20,而不是基类0
}

我们可以通过作用域运算符来使用一个被隐藏的基类成员。

int Sub::getNum(){
    return Base::num;
}
void main(){
    Sub s(20);
    cout << s.getNum <<end;//打印结果就变成基类的0了
}

最佳实践:除了覆盖基类继承而来的虚函数之外,子类最好不要重用其他定义在基类中的名字。

注意:如果子类的成员和基类的成员同名,则子类将在它作用域内隐藏这个基类的成员。即使子类成员和基类成员的形参列表不一致也会被隐藏。因为类中的名字查找是先找子类再找基类,当编译器在子类找到了该名字的成员就不会再向父类去找。

8. 总结

文本介绍了Java和C++访问控制权限的区别,以及C++派生访问说明符、派生类向基类转换的可访问性、改变个别成员的可访问性、默认的继承保护级别的内容。

点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
thymeleaf在工作中遇到的问题及解决办法(四)
1、关于字符串拼接的问题       字符串拼接可以使用如下方式。<ahref""th:text"第${StartNo}页''共${countPage}页"       还有一种更优雅的方式,使用“||”减少了字符串的拼接,代码如下。<ahref""th:
Wesley13 Wesley13
3年前
Uiautomator向jar包传多个参数
转:http://www.th7.cn/Program/java/201512/740518.shtmlUiautomator向jar包传多个参数,有需要的朋友可以参考下。先看uiautomator的命令解析,随便输了个uiautomatorhelp,让其显示用法,打印出来的信息如下:————————————————————————
Stella981 Stella981
3年前
Jira 使用手册
<tablestyle"width:100%;margin:200px0300px0;"<tr<thDate</th<thRevisionversion</th<thDescription</th<thauthor</th</tr<tr<td20180614</td<tdV1.0.0</td
Wesley13 Wesley13
3年前
ThinkPHP 控制器调用模板的流程和项目模板部署步骤
现在主流的MVC框架网站中,控制器接收到页面请求后,通常会调用相应的模板,模板经过渲染之后,内容返回给前台页面,如下面ThinkPHP的一个控制器:shop/home/controller/UserController.class.php<?php namespace Home\Controller;use Th
Wesley13 Wesley13
3年前
2020软件工程作业03
<styletable{width:100%;/\表格宽度\/margin:auto;/\外边距\/emptycells:show;/\单元格无内容依旧绘制边框\/fontsize:18px;}table,th,td{border:2pxsolidpink;}li{fontsize:1
Stella981 Stella981
3年前
AQS (AbstractQueuedSynchronizer)源码导读:锁的获得与释放
AQS是什么?AbstractQueuedSynchronizer简称AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。Providesaframeworkforimplementingblockinglocksandrelatedsynchronizers(semaphores,events,etc)th
Wesley13 Wesley13
3年前
Java 日期与时间
Java的日期Java没有内置的日期类,但可以导入java.time包,这个包中包含了许多类,可用于处理日期和时间。例如:<table<tbody<tr<thstyle"width:25%"Java类</th<thstyle"width:75%"描述</th</tr<tr<td<code
Easter79 Easter79
3年前
Thrift
安装thriftmacbrewinstallthrift安装完成检查thriftversion新建maven项目pom.xml<dependencies<dependency<groupIdorg.apache.th
Wesley13 Wesley13
3年前
C# 线程基础
1 线程是进程中的一个执行流 2线程是一个可以单独操作的活动3线程创建和常用方法 a 创建    Thread thnewThread(Method); b常见方法 th.start()//启动线程 th.Abort()//终止线程 Thread.Sleep(n)//休眠线程(停止n毫秒后继续执
Stella981 Stella981
3年前
99th Packers and Movers Services
99th.co.inistheleadingsearchdirectoryforIndia.Hereonecanfindtheverifiedcompaniesinanyindustryliketransport,logistics,packers&moversservice,BusinessService
吴押狱 吴押狱
1年前
测试用
Inrecentyears,theOscarshavebeenwidelycriticized.Frombeingtoopoliticallycorrecttolackinginnovation,andwithplummetingviewership,th
比特幻翼使
比特幻翼使
Lv1
老至居人下,春归在客先。
文章
3
粉丝
0
获赞
0