专栏首页HappenLee的技术杂谈C++雾中风景1:友元类与面向对象

C++雾中风景1:友元类与面向对象

因为后续准备入职的公司都希望能转C++,所以最近也是按部就班的开始进行C++的学习。然后这个系列的文章打算探究C++的语言特性,也比较一下不同语言(如Java,Scala,Python,Go)之间的设计哲学,同时也抛砖引玉的希望能有大拿们的指点。最近在学习过程之中接触了友元函数与友元类的概念,第一篇文章我们就聊聊友元的概念。

1.友元函数:

开篇先简单介绍一下友元这个概念吧。 在C++之中,类的友元函数是定义在类外部,但它有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。

直接上个代码,我们看看友元函数与友元类具体是怎么样使用的:

#include <iostream>
using namespace std;

class Box {
public:
    Box(double l, double b, double h) {
        length = l;
        breadth = b;
        height = h;
    }
    friend class A;
    friend void boxPrintBox(Box &box);
private:
    double length;
    double breadth;
    double height;
};

//友元函数,友元函数并非成员函数
void boxPrintBox(Box &b) {
    cout << b.height << " " << b.length << " " << b.breadth << endl;
}

//友元类
class A {
public:
    void printBox(Box &b) {
        cout << b.height << " " << b.length << " " << b.breadth << endl;
    }
};


int main() {
    Box box(1,2,3);
    
    //友元函数,可以访问Box类的private的变量
    boxPrintBox(box);
    //友元类,同样可以访问Box类的private的变量
    A a;
    a.printBox(box);
        
    return 0;
}

上面的代码可以看出,友元函数和友元类都可以直接访问到对象的私有变量。接下来我们来分析一下友元函数的特点。

  • 1、为什么要引入友元函数: 在实现类之间数据共享时,减少系统开销,提高效率。具体来说:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。能够提高效率,表达简单、清晰
  • 2、什么时候使用友元函数: 1)运算符重载的某些场合需要使用友元。 2)两个类要共享数据的时候
  • 3、友元方式的缺点: 1)友元函数破环了封装机制,除非不得已的情况下才使用友元函数。

2.友元关系与面向对象:

接下来我们聊聊怎么从面向对象的角度去理解友元关系。(以下内容皆为个人理解,有不准确之处望能斧正

  • 1)友元函数  友元函数是不从属与类的函数,除了能够访问类的私有变量之外,与其他实现在类外部的函数无异。从面向对象的角度看,函数是不应该独立实现于类之外的。显然独立与类之外的友元函数,从面向对象的角度来思考,是不优雅的解决方式。  这和C++本身兼容C语法有关,如操作符<<的重载利用的就是友元函数。<<的函数重载如下,这个函数是独立与类之外实现的方法:
friend ostream &operator<<(ostream &output, const object &o)

显然,这个<<的函数方法更应该从属在ostream这个类之中,做为一个可重载的方法实现。如下定义

friend ostream &operator<<(const object &o) //作为ostream的类函数

如Java,Scala,Python都不支持独立与类存在定义的函数了。在面向对象的角度,后续的语言实现的更加纯粹了。所以如果本身代码风格趋近与面向对象的风格,就应该尽量理由友元类来实现需要的功能,而不是使用友元函数。 有朋友私信我说:Python之中明明直接def就可以定义函数了,也不需要类啊,这是不是也不符合面向对象的逻辑思维?这里简单解释一下,Python之中的每个函数,都会被包装成一个function对象,所以Python之中是一切皆对象的,不会存在独立与类存在的函数。而如同Java与Scala之中的lambda表达式,也是包装为匿名类存在的。

  • 2)友元类 通过友元类包装之中,友元类摇身一变,类之中所有的方法都成为友元函数了。看起来并不会破坏上文提到的面向对象的逻辑了,但是涉及到继承又存在一些不大不小的坑,我们一起来捋一捋: 友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。   好复杂,我们直接上代码:
#include <iostream>
using namespace std;

class A {
private:
    int x;
friend class C;
};

class B:public A{
private:
    int y;
};

class C {
    void printA(A& a) {
        cout << a.x << endl;
    }
    void printB(B& b) {
        cout << b.x << endl; //C类依然可以利用友元关系访问从B类从A类继承来的私有变量
        //cout << b.y << endl; C类不可以访问B类的私有变量,友元关系不继承,该语句不合法。
    }
};

显然,类C与A的友元关系止步于继承处,类C没法访问类B新定义的私有变量。(这里留一个小问题给大家,如果类B覆盖了类A的私有变量x,类C之中的printB是否仍然可以通过编译呢?)

我们再看一段不同的代码:

#include <iostream>
using namespace std;

class A
{
    int x;
public:
    friend class B;
};
class B
{
public:
    void fun(A& a){ cout << a.x << endl;}
};

class C:public B
{
public:
    //void fun2(A& a){ cout <<a.a <<endl;}   //派生类新加的函数却不能访问A,此句会报错
};

void main()
{
    A a;
    C c;
    c.fun(a); //C是B的派生类,只能基类B的函数fun来访问A的对象
}

类C虽然继承了类B,但是也不在具有了和A的友元关系,只能"拼爹"。依赖从类B之中继承的友元函数来访问类A。(这里同样留一个小问题给大家,如果类B之中的fun函数是protected或private的,那上述代码还能够正常编译吗?)

在这里做一个简单的小结:友元关系在友元类之中没有继承性,只能依赖基类的友元关系。

3.非C++语言是怎么解决友元关系的:

  • Java JAVA修饰符类型(public,protected,private)
    • public的类、类属变量及方法,包内及包外的任何类均可以访问;
    • protected的类、类属变量及方法,包内的任何类,及包外的那些继承了此类的子类才能访问;
    • private的类、类属变量及方法,包内包外的任何类均不能访问;
    • 如果一个类、类属变量及方法不以这三种修饰符来修饰。那么包内的任何类都可以访问它,而包外的任何类都不能访问它(包括包外继承了此类的子类)。所以这种类型有时也称为friendly类型,现在知道这个名字的出处了吧,大家对同一个package之中要放哪些类有木有新的认识了呢?
  • Scala 在Scala之中,private和protected可以指定额外的参数。可以使用private[AccessQualifier],AccessQualifier可以是this,也可以是其它的类名或包名。这样就可以这么理解:这个成员对所有类都是private,除了自己和AccessQualifier所表示范围内的类。这个概念也是可以递推的,也就是说,如果AccessQualifier是一个类,那么private成员对于AccessQualifier的AccessQualifier也是可见的。 (好优雅的方式啊,我爱Scala。
  • Python 老子他喵的没有访问控制,全靠自觉。
  • Golang 比较粗暴,就靠首字母的大小写区分访问控制,没法做到更加细粒度的控制了。不过看起来也不影响绝大多数场景的工程实现。所以这样化繁为简的设计哲学,是否也是一种优雅的解决方式呢?

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ClickHouse源码笔记1:聚合函数的实现

    聚合函数: 顾名思义就是对一组数据执行聚合计算并返回结果的函数。 这类函数在数据库之中很常见,如:count, max, min, sum等等。

    HappenLee
  • C++雾中风景3:const用法的小结

    const关键字,翻译成中文是常量,常数的意思。所以在绝大多数场合之中,const是来定义常量的,定义常量也是好的编程习惯。在C类语言之中,定义常量通常会使用宏...

    HappenLee
  • OLAP与数据仓库------《Designing Data-Intensive Applications》读书笔记4

    联机事务处理过程(On-Line Transaction Processing)也就是我们通常称之的OLTP。 联机分析处理过程(On-Line Analys...

    HappenLee
  • APT34 Glimpse&PoisonFrog 项目分析

    近期在Lab Dookhtegan Telegram Chanel中泄露的关于APT34的攻击工具项目、攻击成果记录及部分组织成员信息的事件,引发业界威胁情报及...

    风流
  • ESP8266学习笔记5:ESP8266接入yeelink

    搞定了SmartConfig,前头也用cURL玩过了yeelink,今天就编写代码,让ESP8266接入yeelink。

    twowinter
  • 马来西亚强制IPv6认证新规! 最实用出口认证攻略来了

    马来西亚通讯与多媒体委员会(MCMC)于2019年7月发布规定《MCMC MTSFB TC T013:2019》,要求从2020年7月10日起终端设备、网络设备...

    CFIEC
  • OCP-052考试题库汇总(2)-CUUG内部解答版

      Examine these commands and their output:

    用户5892232
  • 面试必备:CAS无锁机制

    通常我们提到保证多线程安全,会想到三种方式,一是使用Synchronize关键字,但是有个问题就是,使用了Synchronize加锁后的多线程相当于串行,执行效...

    niceyoo
  • OpenStack Icehouse私有云实战部署(完结)

    启动实例 SSH公钥注入 ? 启动一个实例 在OpenStack中启动实例需要指定一个VM 配置模板,首先查看可用模板 ? 创建一个拥有较小的内存设置的flav...

    小小科
  • rest_framework -- 认证

    py3study

扫码关注云+社区

领取腾讯云代金券