C++之友元机制(友元函数和友元类)
我们已知道类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。
为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率(即减少了类型检查和安全性检查等都需要的时间开销),但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
1.友元函数的简单介绍
1.1为什么要使用友元函数
在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数。具体来说:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。
实际上具体大概有下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候。
1.2使用友元函数的优缺点
优点:能够提高效率,表达简单、清晰。
缺点:友元函数破环了封装机制,尽量不使用成员函数,除非不得已的情况下才使用友元函数。
2.友元函数的使用
2.1友元函数的参数:
因为友元函数没有this指针,则参数要有三种情况:
(1)要访问非static成员时,需要对象做参数;
(2)要访问static成员或全局变量时,则不需要对象做参数;
(3)如果做参数的对象是全局对象,则不需要对象做参数;
2.2友元函数的位置
因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。
2.3友元函数的调用
可以直接调用友元函数,不需要通过对象或指针
2.4友元函数的分类:
根据这个函数的来源不同,可以分为三种方法:
普通函数友元函数
目的:使普通函数能够访问类的友元
语法:
声明: friend + 普通函数声明
实现位置:可以在类外或类中
实现代码:与普通函数相同
调用:类似普通函数,直接调用
///
/// @file Point.cc
/// @author AlexCthon(AlexCthon@163.com)
/// @date 2018-06-12 09:40:14
///
#include <math.h>
#include <iostream>
using std::cout;
using std::endl; class Point
{
public:
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{
cout << "Point(int=0, int=0)" << endl;
} void print() const
{
cout << "(" << _ix
<< "," << _iy
<< ")" << endl;
} int getX() const { return _ix; } int getY() const { return _iy; } //友元之普通函数
friend float distance(const Point & lhs, const Point & rhs); private:
int _ix;
int _iy;
}; #if 0
float distance(const Point & lhs, const Point & rhs)
{
return sqrt((lhs.getX() - rhs.getX()) * (lhs.getX() - rhs.getX()) +
(lhs.getY() - rhs.getY()) * (lhs.getY() - rhs.getY()));
}
#endif float distance(const Point & lhs, const Point & rhs)
{
return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) + (lhs._iy - rhs._iy) * (lhs._iy - rhs._iy));
}
int main(void)
{
Point pt1(1, 2);
Point pt2(3, 4);
cout << "pt1和pt2之间的距离: " << distance(pt1, pt2) << endl;
return 0;
}
类Y的一个成员函数为类X的友元函数
目的:使类Y的一个成员函数成为类X的友元,具体而言:在类Y的这个成员函数中,借助参数X,可以直接以X的私有变量
语法:
声明位置:声明在公有中 (本身为函数)
声明:friend + 成员函数的声明
调用:先定义Y的对象y---使用y调用自己的成员函数---自己的成员函数中使用了友元机制
代码:
#include <math.h>
#include <iostream>
using std::cout;
using std::endl; class Point;//类的前向声明 class Line
{
public:
float distance(const Point & lhs, const Point & rhs);
}; class Point
{
public:
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{
cout << "Point(int=0, int=0)" << endl;
} void print() const
{
cout << "(" << _ix
<< "," << _iy
<< ")" << endl;
}
//友元之成员函数
friend float Line::distance(const Point & lhs, const Point & rhs); private:
int _ix;
int _iy;
}; #if 0
float distance(const Point & lhs, const Point & rhs)
{
return sqrt((lhs.getX() - rhs.getX()) * (lhs.getX() - rhs.getX()) +
(lhs.getY() - rhs.getY()) * (lhs.getY() - rhs.getY()));
}
#endif float Line::distance(const Point & lhs, const Point & rhs)
{
return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) +
(lhs._iy - rhs._iy) * (lhs._iy - rhs._iy));
} int main(void)
{
Point pt1(1, 2);
Point pt2(3, 4); Line line;
cout << "pt1和pt2之间的距离: " << line.distance(pt1, pt2) << endl; return 0;
}
类Y的所有成员函数都为类X友元函数—友元类
目的:使用单个声明使Y类的所有函数成为类X的友元,它提供一种类之间合作的一种方式,使类Y的对象可以具有类X和类Y的功能。
语法:
声明位置:公有私有均可,常写为私有(把类看成一个变量)
声明: friend + 类名(不是对象)
补充: 当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。类的前置声明。
#include <math.h>
#include <iostream>
using std::cout;
using std::endl; class Point;//类的前向声明 class Line
{
public:
float distance(const Point & lhs, const Point & rhs); void setPoint(int ix, int iy, Point & pt); private:
int _iz;
}; class Point
{
public:
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{
cout << "Point(int=0, int=0)" << endl;
} void print() const
{
cout << "(" << _ix
<< "," << _iy
<< ")" << endl;
}
//友元之友元类
//friend class Line;
friend Line;// 一定是破坏了类的封装性
//友元是单向的, 不具备传递性, 不能继承
//
//A -> B, B -> C ==> A -> C void setZ(Line & line, int iz)
{//Point不能访问Line的私有成员
line._iz = iz;
} private:
int _ix;
int _iy;
}; float Line::distance(const Point & lhs, const Point & rhs)
{
return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) +
(lhs._iy - rhs._iy) * (lhs._iy - rhs._iy));
} void Line::setPoint(int ix, int iy, Point & pt)
{
pt._ix = ix;
pt._iy = iy;
} int main(void)
{
Point pt1(1, 2);
Point pt2(3, 4); Line line;
cout << "pt1和pt2之间的距离: " << line.distance(pt1, pt2) << endl; line.setPoint(5, 6, pt1);
cout << "pt1 = ";
pt1.print(); return 0;
}
小结:其实一些操作符的重载实现也是要在类外实现的,那么通常这样的话,声明为类的友元是必须滴。
4.友元函数和类的成员函数的区别
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
成员函数有this指针,而友元函数没有this指针。这点其实和静态成员函数一样,静态成员函数也是没有this指针的,所以它只能访问静态成员变量或者通过对象访问非静态成员变量。
友元类是单向的,不可传递,不能被继承。
class Rect
{
public:
Rect() // 构造函数,计数器加1
{
count++;
}
//Rect(const Rect& r)
//{
// width = r.width;
// height = r.height;
// count++;
//}
~Rect() // 析构函数,计数器减1
{
count--;
}
static int getCount() // 返回计数器的值
{
return count;
}
friend int get();
private:
int width;
int height;
static int count; // 一静态成员做为计数器
}; int Rect::count = 0; // 初始化计数器 ,静态成员变量必须要在类外部初始化
int get()
{
return Rect::count;//友元函数通过类访问私有静态成员变量
}
int main()
{
Rect rect1;
cout<<"The count of Rect: "<<Rect::getCount()<<endl;//通过类访问公有静态成员函数,输出1 Rect rect2(rect1); // 使用rect1复制rect2,此时应该有两个对象
cout<<"The count of Rect: "<<Rect::getCount()<<endl; //输出1
cout << get() << endl;//输出1
//cout << Rect::count << endl;//不能编译通过,不能访问私有成员。this只能访问类中的非静态成员变量或成员函数
system("pause");
return 0;
}
注意:怎么理解友元类是单向的,不可传递,不能被继承?
1、友元类不能被继承。这个不多说,很容易理解。
2、友元类是单向的。A->B,B->A?这是错误的。
3、友元类是不可传递的。A->B,B->C, A=>C?这是错误的。
补充:当类的成员变量很多时,需要提供许多的get/set方法来实现成员变量的存取,在这种情况下,不妨用友元的方法。
///
/// @file Complex.cc
/// @author AlexCthon(AlexCthon@163.com)
/// @date 2018-06-12 10:10:03
/// #include <iostream>
using std::cout;
using std::endl; class Complex
{
public:
Complex(double dreal = 0, double dimag = 0)
: _dreal(dreal)
, _dimag(dimag)
{} void display() {
cout << _dreal << " + " << _dimag << "i" << endl;
} //成员函数的形式
Complex operator+(const Complex & rhs)
{
return Complex(_dreal + rhs._dreal, _dimag + rhs._dimag);
} private:
double _dreal;
double _dimag;
}; int main(void)
{
Complex c1(1, 2);
Complex c2(3, 4); Complex c3 = c1 + c2;
c3.display(); Complex c4 = c1 + 5;// c1.operator+(5);
c4.display(); Complex c5 = 5 + c1;//operator+(5, c1);
c5.display(); return 0;
}
有关运算符重载更多细节,参考这篇博客:https://www.cnblogs.com/cthon/p/9181404.html
C++之友元机制(友元函数和友元类)的更多相关文章
- C++ 友元 (全局函数做友元) (类做友元) (成员函数做友元)
1 //友元 全局函数做友元 2 /* 3 #include <iostream> 4 #include <string> 5 using namespace std; 6 7 ...
- C++复习:函数模板和类模板
前言 C++提供了函数模板(function template).所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表.这个通用函数就称为函数模板.凡是函数体 ...
- 友元(友元函数、友元类和友元成员函数) C++
有些情况下,允许特定的非成员函数访问一个类的私有成员,同时仍阻止一般的访问,这是很方便做到的.例如被重载的操作符,如输入或输出操作符,经常需要访问类的私有数据成员. 友元(frend)机制允许一个类将 ...
- C++友元(友元函数、友元类和友元成员函数)
友元(友元函数.友元类和友元成员函数) C++ 有些情况下,允许特定的非成员函数访问一个类的私有成员,同时仍阻止一般的访问,这是很方便做到的.例如被重载的操作符,如输入或输出操作符,经常需要访问类的私 ...
- C++类的友元机制说明
下面给出C++类的友元机制说明(对类private.protected成员访问),需要注意的是,友元机制尽量不用或者少用,虽然它会提供某种程度的效率,但会带来数据安全性的问题. 类的友元 友元是C++ ...
- C++学习12 友元函数和友元类
友元函数和友元类在实际开发中较少使用,想快速学习C++的读者可以跳过本节. 一个类中可以有 public.protected.private 三种属性的成员,通过对象可以访问 public 成员,只有 ...
- C++:成员运算符重载函数和友元运算符重载函数的比较
5.2.4 成员运算符重载函数和友元运算符重载函数的比较 (1)对双目运算符而言,成员运算符重载函数参数表中含有一个参数,而友元运算符重载函数参数表中有两个参数:对于单目运算符而言,成员运算符重载函数 ...
- C++:运算符重载函数之友元运算符重载
5.2.2 友元运算符重载函数 运算符重载函数一般采用两种形式定义: 一是定义为它将要操作的类的成员函数(简称运算符重载函数): 二是定义为类的友元函数(简称为友元运算符重载函数). 1.定义友元运算 ...
- C++:友元(非成员友元函数、成员友元函数、友元类)
3.8 友元:友元函数和友元类 友元函数 :既可以是不属于任何类的非成员函数,也可以是另一个类的成员函数,统称为友元函数.友元函数不是当前类的成员函数,而是独立于类的外部函数,但它可以访问该类所有的 ...
随机推荐
- 分布式版本号控制系统Git(二):github
前言 但凡是喜欢研究技术,或者听大牛们说起过的,都应该至少是听过github这个东西.详细就不介绍了.不了解的能够去了解了解,最基本的功能当然是代码托管啦,上面有各种各样的大牛写的项目. 另外这一章不 ...
- Lucene的基本应用
import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; ...
- Ubuntu 安装 spark
环境: Unbunt 12.04 Hadoop 2.2.x Sprak 0.9 Scala scala-2.9.0.final.tgz 步骤 1. 下载 scala 2. 解压scala,然后改动/e ...
- CentOS6.5_64bit下编译安装MySQL-5.6.23
转载请注明出处:http://blog.csdn.net/guoyjoe/article/details/44785511 ************************************** ...
- C语言-多重背包问题
多重背包问题 问题:有N种物品和一个容量为V的背包.第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大. 分 ...
- python(29)- 面向对象练习Ⅲ
题目: 基于授权定制自己的列表类型,要求定制的自己的__init__方法, 定制自己的append:只能向列表加入字符串类型的值 定制显示列表中间那个值的属性(提示:property) ...
- python 中的电子邮箱的操作
通过python 的代码实现对email的操作,包括发送邮件和读取邮件. import poplib import smtplib from email.header import decode_he ...
- idea刷新项目、清除项目缓存
点击File -> Invalidate caches ,点击之后在弹出框中点击确认,之后软件就自动重启了
- Spring Cloud(十二):Spring Cloud Zuul 限流详解(附源码)(转)
前面已经介绍了很多zuul的功能,本篇继续介绍它的另一大功能.在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud中如何实现限流. 在 Zuul 上实现限流是个不错的选 ...
- leetCode 104.Maximum Depth of Binary Tree(二叉树最大深度) 解题思路和方法
Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the long ...