专业编程基础技术教程

网站首页 > 基础教程 正文

C++友元类和友元函数是什么-Linu C++第63讲

ccvgpt 2025-03-29 15:39:36 基础教程 17 ℃

友元

1 友元的定义

在一个类中可以有公用的(public)成员和私有的(private)成员。在类外可以访问公用成员,只有本类中的函数可以方法本类的私有成员。例如:

class student{

C++友元类和友元函数是什么-Linu C++第63讲

private: //私有成员

char name[32]; //姓名

public: //公用成员

char addr[32]; //家庭地址

long long number; //电话号码

public: //公有成员

void print(){

cout << "name = " << name << endl;

cout << "addr = " << addr << endl;

cout << "number = " << number << endl;

}

private: //私有成员

long long get_number(){

return number;

}

};

此时,定义了一个student类。在类中定义name成员变量和get_number()成员函数是“private私有”属性。所以,只能够在类内访问。不可以在类外访问。对于addr, number成员变量和print()成员函数,是“public公有”属性,所以,可以在类外访问。

那么,有没有一些办法,可以让类的private成员可以被某些指定的对象访问?例如,可以授权给某些对象,让它可以访问类的private成员。

现在,我们来学习C++的一个新特性:友元(friend)

friend 的意思是朋友,或者说是友好,与友好的关系显然要比一般人亲密一些。那么,一些比较隐私的事情,可以与朋友一起沟通,让朋友知道。那么,在C++中,这种“朋友”关系以关键字 friend 声明。中文多译为“友元”。友元可以访问与其有友好关系的类中的私有成员。友元包括友元函数和友元类。

例如有C++类TestA和TestB,其中,TestA是TestB的“友元”,那么,TestA是TestB的朋友,可以访问TestB类的私有成员。

2 友元函数

如果在本类以外的其他地方定义了一个函数(这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数),在对本类进行声明时,在类体中用friend对该函数进行声明,此函数就称为本类的“友元函数”。一个类的友元函数可以访问这个类中的私有成员。

例如,定义一个func()函数,在C++的TestA类中声明func()函数是当前类的“友元”函数,那么,这个func()函数就可以访问TestA类的私有成员。

2.1 普通函数声明为友元函数

我们可以定义一个普通的函数,希望这个函数能够访问一个类中的成员变量,那么,我们就在定义类的时候,声明这个函数是该类的友元函数,相当是说:在定义类的时候,把该普通函数声明为自己的一个朋友,允许这个朋友访问自己的成员变量。

这样,一个函数可以被多个类声明为“朋友”,它可以访问多个类中的成员变量。

通过下面的例子,可以理解友元函数的性质和作用。如下是一个友元函数的测试例子:



程序运行结果如下:



可以看到,在print()函数中可以引用stud对象的private私有成员。是因为,print()函数在student类中声明为友元。

请注意print()是一个在类外定义的函数,它不是属于student类的一个函数,所以,在定义的时候,并没有使用类前缀。它是非成员函数,不属于任何类。但是,我们把它声明为student类的一个“朋友”,让它可以访问student类的私有成员变量。

所以,使用 friend 关键字,在student类中声明定义在别处的print() 函数为student类的一个友元,这样,print() 是student类的朋友,就可以访问它的私有成员变量。

注意:在print() 中访问私有成员变量的时候,必须加上“对象名”,不能够如下:

void print(student& s)//定义友元函数

{

cout << "name = " << name << endl;

cout << "addr = " << addr << endl;

cout << "number = " << number << endl;

}

因为,print() 不是student类中的一个成员函数,它只是student类的一个朋友。不能够默认引用student类的成员变量,必须指定要访问的对象。

2.2 友元成员函数

在前面的例子中,我们定义了一个普通的函数,然后,在定义一个类的时候,把这个函数声明为该类的“朋友”,可以让该函数访问类中的成员变量。

同样,我们也在定义一个类的时候,把另一个类的成员函数声明为该类的朋友,让它可以访问自己的成员变量。

所以,firent 函数不仅可以定义一个普通函数(非成员函数),而且,可以是另一个类中的成员函数。下面举例介绍友元成员函数的使用。

在这个例子中,除了介绍有关友元成员函数的简单应用外,还将用到类的“提前引用声明”。就是说,一个类在使用之前,没有被定义,而是放在后面定义,那么,我们可以在使用之前先声明它。有如下的类定义:

class student;//提前声明student类;

class my_print//定义 my_print 类;

{

public:

void print(student& s); //声明函数;

};

class student{

private: //定义私有类型成员变量

char name[32]; //姓名

char addr[32]; //家庭地址

long long number; //电话号码

public: //以下部分是公有部分

student(char* pn, char* pa, long long n)

{

strcpy(name, pn);

strcpy(addr, pa);

number = n;

}

friend void my_print::print(student& s);//声明友元函数;

};

可以看到,定义了my_print类和student类。其中,在my_print类中引用student类。而且,student类在最后面定义。

那么,就在my_print类之前,编写如下的语句:

class student;//提前声明student类;

表示提前声明student类,那么,就可以在my_print类中引用student类。完整的测试代码如下:



程序编译结果如下:

wkf@ubuntu:~/c++$ g++ test.cpp -o exe

test.cpp: In member function ‘void my_print::print(student&)’:

test.cpp:14: error: invalid use of incomplete type ‘struct student’

test.cpp:6: error: forward declaration of ‘struct student’

test.cpp:15: error: invalid use of incomplete type ‘struct student’

test.cpp:6: error: forward declaration of ‘struct student’

test.cpp:16: error: invalid use of incomplete type ‘struct student’

test.cpp:6: error: forward declaration of ‘struct student’

可以看到,在my_print::print()函数中,引用student类型的数据异常。为什么会出现这个问题?

因为,在my_print::print()函数中,引用student类的时候,这个student类还没有定义。我们只是在my_print类之前提前声明了student类。并没有定义student类,所以,编译器在my_print::print()函数中,无法访问s.name, s.addr等成员变量。

所以,我们可以把my_print::print()函数放在student类定义的后面。表示定义了student类之后,再定义my_print::print()函数,此时,编译器才知道s.name, s.addr成员变量是合法的定义。完整的测试代码如下:



程序运行结果如下:



可以看到,my_print::print()函数能够正确引用了student类中定义的private私有成员。这是因为,在student类中,声明my_print类的print()函数是友元类型,如下:

class student{

...

friend void my_print::print(student& s);//声明友元函数;

};

所以,student类中的私有成员,就可以在my_print类的print()函数中被访问。

2.3 编译器对类提前声明的处理

在一般情况下,类必须先声明,然后,才能使用它。但是,在特殊的情况下(例如上面的例子),在正式声明定义类之前,需要使用该类名。但是,提前声明的使用范围是有限的,提前声明的时候,只能使用它去声明一个对象,例如,声明一个参数,但是,不能够调用它去定义一个对象。因为,这个类还没有编译,还不知道它有什么成员变量,所以,还不能够使用它去定义对象。如下是一个测试的例子:



程序编译运行结果如下:

g++ test.cpp -o exe

wkf@ubuntu:~/c++$ ./exe

调用print()函数

程序编译OK,可以正常运行。因为,在print()函数中,只是定义了student类的引用,并没有实际定义对象。

那么,我们修改print()函数如下:

//定义函数引用 student 类;

void print(student& s)

{

cout << "调用print()函数" << endl;

//定义student类对象

student stud("wkf", "www.mylinux.vip", 13926572996);

}

程序编译异常,异常信息如下:

wkf@ubuntu:~/c++$ g++ test.cpp -o exe

test.cpp: In function ‘void print(student&)’:

test.cpp:12: error: variable ‘student stud’ has initializer but incomplete type

异常信息描述在print()函数中,定义的stud对象并不是合法的student类。因为,此时,编译器还没有编译student类,并不知道student类的具体定义,所以,无法使用student类来定义对象。

同样的道理,编译器还没有执行student类的编译时,无法知道student类的具体定义,所以,就不可以使用student类来定义对象。同时,也不可以访问student类中的成员。所以,我们把print()函数放在student类定义之前。程序测试代码如下:



编译运行结果如下:

wkf@ubuntu:~/c++$ g++ test.cpp -o exe

test.cpp: In function ‘void print(student&)’:

test.cpp:11: error: invalid use of incomplete type ‘struct student’

test.cpp:6: error: forward declaration of ‘struct student’

test.cpp:12: error: invalid use of incomplete type ‘struct student’

test.cpp:6: error: forward declaration of ‘struct student’

test.cpp:13: error: invalid use of incomplete type ‘struct student’

test.cpp:6: error: forward declaration of ‘struct student’

可以看到,在print()函数中引用student对象的成员变量,是非法的操作。

那么,根据这个思路,我们可以把print()函数放在student类定义来后面,就可以正确引用student类的成员了。测试代码如下:



程序运行结果如下:



可以看到,在student类定义的后面,在定义print()函数。那么,在print()函数中就可以访问student类的成员变量。

所以,充分理解这个机制,理解编译系统的工作过程,在编写代码的时候,就可以合理地设计代码,当编译C++代码,有编译错误的时候,可以知道怎么样解决。

特别是在设计模式中,多个类对象之间相互引用,此时,需要充分理解C++语言的特性,才可以根据设计模式的思想,编写出稳定、高效的代码。

3 友元类

不仅可以将一个函数声明为一个类的“朋友”,还可以将一个类(例如 B类)声明为另一个类(例如 A类)的“朋友”。这时B类就是A类的友元类。友元类B中的所有函数都是A类的友元函数,可以访问A类中的所有成员。

声明友元类的一般形式为:

friend class 类名;

所以,可以在A类的定义体中,可以使用如下语句声明B类为其友元类:

friend class B;

那么,类B是类A的友元类。所以,类B的成员函数就可以访问类A的私有成员。

如下是一个测试例子:



程序运行结果如下:



可以看到,定义了my_print类和student类。其中,在student类定义的时候,有:

friend class my_print; //声明友元类

所以,声明my_print类是student类的友元类。那么,my_print类的成员函数,就可以访问student类的私有成员。

所以,my_print::print()函数中,可以引用输出student类的name, addr等私有成员。

注意:my_print::print()函数的定义,需要在student类的后面,具体原理可以参考“编译器对类提前声明的处理”章节。就是上一章节,已经说了编译对类提前声明的处理。

有关友元,有下面两点需要说明:

(1) 友元的关系是单向的,而不是双向的。如果声明类A是类B的友元类,不等于类B也是类A的友元类。

(2) 友元的关系不能够传递,如果类B是类A 的友元,类C是类B的友元,并不等于类C就是类A的友元。

在实际工作中,除非确实有必要,否则,一般并不把整个类声明为友元类。而只将确实有需要的成员函数声明为友元函数,这样更方便安全一些。

有关友元利弊的分析:面向对象程序设计的一个基本原则是封装性和信息隐蔽,而友元却可以访问其他类中的私有成员,不能不说这是封装原则的一个小的破坏。但是,它能有助于数据共享,能提高程序的效率,在使用友元的时候,要注意到它的副作用,不用过多地使用友元,只有在使用它能使程序精炼,并大大提高程序的效率时才用友元。也就是说,要在数据共享和信息隐蔽之间选择一个恰当的平衡点。

总结

本章节我们学习了C++的友元特性,可以定义友元函数和友元类。

在面向对象编程的设计思想中,有一个重要的特性就是:信息的封装和隐藏;例如,C++提供了private关键字,可以定义C++类对象的成员类型,是“私有”属性,不可以被外界访问,从而降低对象之间传递消息的效率。

那么,为了提高对象之间传递消息的效率,就需要打破这种信息“隐藏”的特性。

所以,C++提供了友元的特性,可以解决这个问题。例如,C++声明一个函数是自己的“朋友(友元)”,那么,该函数就可以访问C++隐藏的信息。从而提高获取对象信息的效率。

读者要仔细分析,掌握好每一个知识点,然后,自己总结出对该知识点的理解。自己总结出来的知识,理解才深刻,才是自己掌握的知识。

最近发表
标签列表