今天在准备计算机等级考试的时候,被一系列的友元函数给搞混了,现在结合自己的理解和查阅的资料来总结下友元函数和友元类。
百度百科上对友元函数是这样定义的:友元函数是指某些虽然不是类成员却能够访问类的所有成员的函数。。类授予它的友元特别的访问权。通常同一个开发者会出于技术和非技术的原因,控制类的友元和成员函数(否则当你想更新你的类时,还要征得其它部分的拥有者的同意)。
为什么会有友元函数?
结合着类的特性和类中一般成员函数,我们可以这样理解:类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。
为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样。下面举一例子说明友元函数的应用。
{
public:
Point(double xx, double yy) { x=xx; y=yy; }
void Getxy();
friend double Distance(Point &a, Point &b);
private:
double x, y;
};
void Point::Getxy()
{
cout<<"("<<<","<<Y<<")"<<ENDL;< FONT>
}
double Distance(Point &a, Point &b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx*dx+dy*dy);
}
void main()
{
Point p1(3.0, 4.0), p2(6.0, 8.0);
p1.Getxy();
p2.Getxy();
double d = Distance(p1, p2);
cout<<"Distance is"<<
FONT>
}
说明:在该程序中的Point类中说明了一个友元函数Distance(),它在说明时前边加friend关键字,标识它不是成员函数,而是友元函数。它的定义方法与普通函数定义一样,而不同于成员函数的定义,因为它不需要指出所属的类。但是,它可以引用类中的私有成员,函数体中 a.x,b.x,a.y,b.y都是类的私有成员,它们是通过对象引用的。在调用友元函数时,也是同普通函数的调用一样,不要像成员函数那样调用。
本例中,p1.Getxy()和p2.Getxy()这是成员函数的调用,要用对象来表示。而Distance(p1, p2)是友元函数的调用,它直接调用,不需要对象表示,它的参数是对象。
再举几个例子,我们来分析下全局函数作友元、其它类的成员函数作友元、运算符重载中使用友元的情况:
全局函数作友元
要使用全局函数作友元函数,也就是说全局函数中要使用当前这个类的实例,因此,全局函数的定义必须放在这个类的后面,否则,编译时这个类的实例就成了未定义的数据类型了。
#include <string>
using namespace std;
class ca {
string id;
void setId(string s) {
id = s;
}
protected:
string name;
void setName(string s) {
name = s;
}
public:
void print() {
cout << id << " " << name << " " << endl;
}
friend void fun(ca& a); //申明全局函数作友元,允许它访问私有保护成员
};
class derive : public ca { }; //ca类的派生类
void fun(ca& a) { //作友元的函数要定义在那个类的后面
a.id = "987"; //这是ca类的私有成员
a.setName("xyz"); //这是ca类的保护成员
}
int main ( )
{
ca a;
//a.fun(...); //友元函数不是当前类的成员函数,所以这句编译出错
fun(a);
a.print();
derive d;
fun(d); //作参数时,类型转换为ca类
d.print();
return 0;
}
运行结果 为
其它类的成员函数作友元
别的类的成员函数作友元,也就是说这2个类相互调用,这样无论谁定义在前在后,编译时都有冲突。要解决这个问题,只要将
C++中的默认值
在C++中,编译器不会默认检查对象是否被初始化,这就会导致对象在不初始化的情况下被使用,造成不可预计的后果。因此,C++程序员必须保证自己所使用的变量已经有了一个明确的初始值。
一般情况下,在C++中,如果一个变量为全局代码段,则一般编译器会给一个初始的定值,如int类型会赋0。而定义为局部变量时,则默认值为随机数。但是在vs调试时,DEBUG版本和Release版本版本还有区别: http://www.dewen.org/q/8450/C%2B%2B+%E4%B8%AD+bool+%E5%8F%98%E9%87%8F%E7%9A%84%E9%BB%98%E8%AE%A4%E5%80%BC%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F
C++提供的标准模板库,会保证变量会有一个默认的初始值(非随机值),int初始化为0,string初始化为空串等。
确保对象使用前被初始化
尽管程序员对C++的默认行为很了解,但是默认的值和机器,编译器等都有关系,不确定性太多。因此最好的做法是,在使用对象之前,永远手动给对象初始化。如:
const char* text = "ABC";
double d;
std::cin >> d;
如果是程序员自定义的类型,则满足以下规则:确保每一个构造函数都将对象的每一个成员初始化。但是要注意一点,“赋值”和“初始化”这两个操作在构造函数中很容易被混淆。看下面的例子:
public:
A(const std::string& name, const std::list<std::string>& namelist);
private:
std::string name;
std::list<std::string> namelist;
int nameCount;
};
A::A(const std::string& n, const std::list<std::string>& nl)
{
name = n; //这些都是赋值,不是初始化。
namelist = nl;
nameCount = 0;
}
在上面的代码中,name,namelist在构造函数中的动作都是赋值而不是初始化。初始化的动作发生在进入构造函数本体之前。 但是nameCount属于基本类型,不一定会在本构造函数之前经过初始化。
对于构造函数一个比较好的写法是,采用C++定义的成员初值列(member initialization)来执行:
:name(n), namelist(nl), nameCount(0)
{
}
现在,这些都是初始化操作,name, namelist都调用了其copy构造函数进行初始化,且比赋值操作的构造函数效率较高,因为赋值的构造函数在赋值之前已经会对一些成员调用了默认的构造方法进行初始化。对大多数类型来说,直接调用copy构造函数比先调用默认构造再调用copy赋值都是高效的。
使用初值列还有一个好处就是:如果成员变量的类型是const或references,那么它们就不能在构造函数中进行赋值,而必须进行初始化。因此简单的做法是:永远使用初值列。而且在初值列中列出所有成员变量,即使有些不需要进行初始化,但是这样做的话利于维护和代码的阅读。
需要注意一点:C++的成员初始化的次序是固定的,它总是按照其声明的顺序进行初始化。在上面的例子中,初始化的顺序永远是:name, namelist, nameCount,而且这个顺序和初值列中写的顺序无关。(为了避免混淆,初值列中的顺序最好和声明的顺序相同)
“不同编译单元内定义之non-local static对象”的初始化
static对象是指:global对象,定义在namespace作用域的对象,在class,函数,文件作用域中的static对象。其中,函数内的static对象是local static,其它的static对象是non-local static对象。static对象在程序结束时被销毁。不同编译单元,是指产生的目标文件不同,基本上一个编译单元是指其源码文件加上其头文件。
我们遇到的问题是:如果在一个编译单元内的某个non-local static对象初始化用到了另一编译单元内的non-local对象,但是它所用到的这个对象尚未被初始化,就会出现错误。C++对这一初始化顺序并未进行定义。
如下面的例子(effective C++原书的例子):
public:
...
std::size_t numDisks() const;
...
};
extern FileSystem tfs;
在另一个编译单元中有如下代码:
public:
Directory(params);
...
};
Directory::Directory(params)
{
...
std::size_t disks = tfs.numDisk();
...
}
Directory tempDir( params );
那么,上述代码就必须要求tfs在tempDir之前进行初始化。但是C++编译器对这一顺序并无明确定义,因为要明确分析这一顺序是非常困难的。然而我们可以做一个设计来解决这一问题:将每个non-local static对象搬到自己的专属函数内,然后返回该对象的引用。这一设计类似Singleton模式。因为C++会保证:函数内的local static对象会在该函数被首次调用时被初始化。而且还有一个好处,如果从来没有调用过这个函数,这个对象将不会被初始化,减小析构成本,对于上述例子的non-local static对象不会做到这种效果。
用这个设计修改上述示例:
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
class Directory {}; //同前
Directory::Directory(params)
{
...
std::size_t disks = tfs().numDisk();
...
}
Directory& tempDir()
{
static Directory td;
return td;
}
上述三点:
1. 为内置类型对象进行手工初始化,因为C++不保证初始化它们。
2. 构造函数最好使用成员初值列(member initialization list)。初值列的成员变量,排列次序应与在class中声明的次序相同。
3. 以local static对象替换non-local static对象。
本文链接
浅析epoll – epoll函数深入讲解
浅析epoll-为何多路复用I/O要使用epoll
Win8虚拟机安装失败
[软件资讯]百度发布富文本开源编辑器UEditor
如何增加博客的订阅量? 无觅