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
比较粗暴,就靠首字母的大小写区分。没法做到细粒度的控制,不过看起来也不影响绝大多数场景的工程实现。所以是否这样化繁为简的设计哲学,也是一种优雅的设计呢?
C++雾中风景1:友元类与面向对象的更多相关文章
- C++之友元机制(友元函数和友元类)
一.为什么引入友元机制? 总的来说就是为了让非成员函数即普通函数或其他类可以访问类的私有成员,这确实破坏了类的封装性和数据的隐蔽性,但为什么要这么做呢? (c++ primer:尽管友元被授予从外部访 ...
- C++中友元类使用场合
在C++中我们可以將函数定义成类的友元函数,这样在函数中就可以访问类的私有成员.与函数相同,类也可以作为另一个类的友元类,在友元类中可以访问另外一个类的所有成员. 声明友元类的方法很简单,只需在类中写 ...
- c++-友元函数和友元类
友元函数 友元函数和友元类(破坏类的封装性) 面向对象编程思想 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include & ...
- C++的友元类和友元函数实例
#include <math.h> #include<iostream> using namespace std; class Point { public: Point(do ...
- 不可或缺 Windows Native (20) - C++: 友元函数, 友元类
[源码下载] 不可或缺 Windows Native (20) - C++: 友元函数, 友元类 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 友元函数 友元类 示例演 ...
- C++学习12 友元函数和友元类
友元函数和友元类在实际开发中较少使用,想快速学习C++的读者可以跳过本节. 一个类中可以有 public.protected.private 三种属性的成员,通过对象可以访问 public 成员,只有 ...
- YTU 2619: B 友元类-计算两点间距离
2619: B 友元类-计算两点间距离 时间限制: 1 Sec 内存限制: 128 MB 提交: 469 解决: 252 题目描述 类Distance定义为类Point的友元类来实现计算两点之间距 ...
- C++:友元(非成员友元函数、成员友元函数、友元类)
3.8 友元:友元函数和友元类 友元函数 :既可以是不属于任何类的非成员函数,也可以是另一个类的成员函数,统称为友元函数.友元函数不是当前类的成员函数,而是独立于类的外部函数,但它可以访问该类所有的 ...
- c++ 友元类
一.友元类相关概念 要将私有成员数据或函数暴露给另一个类,必须将后者声明为友元类. 注意三点: (1)友元关系不能传递 (2)友元关系不能继承 (3)友元关系不能互通
随机推荐
- Python 实现的随机森林
随机森林是一个高度灵活的机器学习方法,拥有广泛的应用前景,从市场营销到医疗保健保险. 既可以用来做市场营销模拟的建模,统计客户来源,保留和流失.也可用来预测疾病的风险和病患者的易感性. 随机森林是一个 ...
- mysql慢查询分析工具 mysqlsla 安装
概述 mysqlsla 是一款很不错的mysql慢查询日志分析工具,而且简单易用.mysqlsla 是用perl编写的脚本,运行需要perl-DBI和per-DBD-Mysql两模块的支持.mysql ...
- [ZJOI2005]九数码游戏
[ZJOI2005]九数码游戏 题目描述 输入输出格式 输入格式: 输入文件中包含三行三列九个数,同行的相邻两数用空格隔开,表示初始状态每个方格上的数字.初始状态不会是目标状态. 输出格式: 如果目标 ...
- Django实现用户密码重置
使用Django内置的认证视图实现简单的通过邮箱重置密码的功能版本:django 1.11 在django.contrib.auth.views中提供了四个类视图用于密码重置 class Passwo ...
- JIRA-6.3.6安装与破解
首先下载JIRA-6.3.6的安装包: wget http://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-6.3. ...
- 怎么用SQL语句备份和恢复数据库?
BACKUP DATABASE "mydb" TO DISK ='C:\mybak.db' with init RESTORE DATABASE "mydb" ...
- PHP 支付
蚂蚁金服开放平台 2.下载PHP的SDK&demo 3.申请应用 OR 使用沙箱环境 4.生成应用私钥&应用公钥 5.配置config.php 蚂蚁金服开放平台",对,没错, ...
- win10 uwp 通知Toast
win10通知使用Toast 可以使用win10 模板添加通知 var t = Windows.UI.Notifications.ToastTemplateType.ToastText02; 使用Ge ...
- WPF DelegateCommand 出现Specified cast is not valid
使用 DelegateCommand 出现 Specified cast is not valid 最近写快捷键需要 DelegateCommand ,于是用了 DelegateCommand< ...
- HTML5之appcache语法理解/HTML5应用程序缓存/manifest缓存文件官方用法翻译
习惯性的贴几个参考链接: W3School-HTML 5 应用程序缓存 官方 MDN window.applicationCache 接口文档 官方 MDN 用法示例 看所有的教程不如直接看最原始的官 ...