友元

本文深入探讨了C++中友元函数和模板的使用方式,特别是它们如何与类的私有成员交互,包括非模板类、模板函数、模板类中的友元以及友元模板的应用。详细解释了友元的声明位置、作用范围以及如何在模板上下文中声明友元,同时还提供了一个完整的示例程序来展示这些概念的实际运用。

1,friend申明一个友元

friend一般为一句申明式,它位于一个类的内部,它申明一个类或者一个函数为该类的友元。friend并不是定义一个成员函数,所以friend放在public,protected或者private前都可以,完全是一样的。做为一个友元,即表示在该类或者该函数内部可以访问这个类的私有成员,你和朋友之间是不是应该没有什么隐藏的呢。例子:

class A
{
public:
 A(int _a) : a(_a) {}
 friend void test(A&);
 friend class B;
private:
   int a;
};

void test(A& x)
{
 x.a=100;//拥有私有成员a的访问权限
}

class B
{
public:
  void foo();
};

如果friend申明式为一般的非模板类或者函数,则该处可以为首次申明。对于一个类,只能是申明式,对于函数,可以是它的定义式。

class A
{
public:
 A(int _a) : a(_a) {}
 friend void test(A& x)
 {
    x.a = 100;//定义::test()友元函数
 }
 friend class B;
private:
   int a;
};

注意尽管将函数的定义式放在类内部,但它并不是一个成员函数,对于省略受限的定义形式它将成为一个全局函数::test(),当然你也可以申明另外一个类的成员函数为友元,如:

class A
{
public:
 A(int _a) : a(_a) {}
 friend void B::foo();
private:
   int a;
};

总的来说,如果你想在哪里访问类A的私有成员,就在类A内写上一句该处的申明式,并在前面加上friend关键字。

这是一般情况,很简单,但是它会破坏封装的初衷,所以尽量少用;Effective C++中有一个应用的例子,对一个类定义的二元操作符,如果你希望它能对操作数都进行隐式转化,那么就定义一个全局函数,并申明成该类的友元。

2,模板函数作友元

先给一个模板函数,它是一个模板,并不是一个函数:

template<typename T>
void foo1(T);

在定义foo1为某类的友元时,或者要实例化模板参数T,或者给出可演绎的申明式,而且就算是可以演绎的,一对尖括号也不能省。如:

class A
{
public:
  friend void foo1<char>(char);
  friend void foo1<>(double);
};

或者给出限制符:::

class A
{
public:
  friend void ::foo1(char);
};

当然,如果有一般函数具有这种形式,那会优先于模板函数匹配。最后这里的申明式都不能是定义式,必须前至申明(定义)。

3,模板类里的友元

模板类里也能申明2中的友元,但是模板类有模板参数,如果利用了这个模板参数的友元申明,就属这种情形。

template<typename T>
class A
{
public:
  friend void foo1<T>(T);
};

但是,在这里,必须要求foo1在这里是可见的,即不能是首次申明式。如果不使用模板参数,那会是一种有趣的情形。

template<typename T>
class A
{
public:
  friend void foo(){}
};

注意这里是一个定义式,它定义了一个::foo()函数为该模板类的友元,在该模板类具现的时候,::foo()也被具现出来,即:

A<int> a1;// ::foo()首次定义
A<char> a2;// ::foo()第二次定义,违背C++一次定义原则

4,友元模板

如果想定义一系列函数为该类的友元,可以使用友元模板。它和模板的申明式类似,只是在template<>后加了friend关键字。

class A
{
public:
 template<typename T>
 friend void foo();
};

5,能否做为定义式

能做为定义式的情况是:非受限,没有前至::,没有模板参数列表,没一对尖括号。如果是模板申明式,不能是首次申明,在该处必须是可见的。

6,一个完整的例子

template<typename T>
class Rat
{
public:
    Rat(T _a, T _b) : a(_a), b(_b) {}
    friend Rat<T> operator*<T>(Rat<T>&,Rat<T>&);
private:
    T a,b;
};

template<typename T>
Rat<T> operator*(Rat<T> & x, Rat<T> & y)
{
    return Rat<T>(x.a*y.a,x.b*y.b);
}

Rat<T>为T类型的有理数类,定义它的相乘运算,定义一个全局函数,并申明为友元,该函数也应该是模板,希望有如上的程序通过编译。在friend式之前没有operator*()的申明,所以这里不能是首次申明,在前面必须加上申明式:

template<typename T>
Rat<T> operator*(Rat<T> & x, Rat<T> & y);

在这之前又没有Rat<T>的申明,再加上:

template<typename T>
class Rat;

通过编译,或者改成友元模板:

template<typename T>
class Rat
{
public:
    Rat(T _a, T _b) : a(_a), b(_b) {}
    template<typename U>
    friend Rat<U> operator*(Rat<U>&,Rat<U>&);
private:
    T a,b;
};

template<typename T>
Rat<T> operator*(Rat<T> & x, Rat<T> & y)
{
    return Rat<T>(x.a*y.a,x.b*y.b);
}

有细微的不同,Rat<T>申明了一系列友元operator*<U>,当然没必要,只要operator*<T>就够了,但是形式上简单一些。还有一种更简单的形式,就是将定义式放在里面,正是Effective C++里使用的方法:

template<typename T>
class Rat
{
public:
    Rat(T _a, T _b) : a(_a), b(_b) {}
    friend Rat<T> operator*(Rat<T>&x,Rat<T>&y) //定义并申明了::operator*()
    {
        return Rat<T>(x.a*y.a,x.b*y.b);
    }
private:
    T a,b;
};

<think>首先,用户的问题是:“我想了解友元函数的相关信息 请问友元函数的概念、特点和使用方法”。我需要基于系统指令和参考引用来回答。 系统指令: - 所有行内数学表达式必须使用$...$格式。例如:$x^2$。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1]。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 用户可见层:回答结构清晰,逐步解决问题。 参考引用: - 引用[1]:友元函数的概念,破坏封装性,提高效率。 - 引用[2]:友元函数的定义、特点,如非成员函数访问私有成员。 - 引用[3]:示例代码,展示另一个类的成员函数作为友元。 我需要组织回答: 1. **概念**:解释友元函数是什么。 2. **特点**:列出友元函数的特点。 3. **使用方法**:如何声明和使用友元函数,包括示例代码。 4. **注意事项**:优缺点。 5. **生成相关问题**:在末尾添加相关问题。 确保使用中文。 结构: - 开头简要介绍。 - 分点解释概念、特点、使用方法。 - 提供代码示例。 - 添加引用标识。 - 最后生成相关问题。 从引用中提取信息: - 概念:友元函数是一种能够访问类中私有成员的非类成员函数。[^1] - 特点:非成员函数;访问私有和保护成员;声明用friend关键字;提高效率但破坏封装。[^1][^2] - 使用方法:在类定义中声明friend,定义在类外。[^2][^3] - 示例:引用[3]提供了代码示例。 添加自己的知识以确保真实可靠: - 友元函数常用于运算符重载或需要外部函数访问私有数据时。 - 示例:重载<<运算符用于输出。 数学表达式:用户问题中没有数学,但如果有,要用$...$。这里可能不需要数学。 生成相关问题: - 类似示例中的格式:§§相关问题§§ - 基于主题:友元函数。 相关问题想法: 1. 友元函数与成员函数的区别? 2. 友元函数在哪些场景下最有用? 3. 友元类是什么?如何声明? 完整回答: - 先回答用户问题。 - 结束时添加相关问题。</think>### 友元函数的概念、特点和使用方法 友元函数是C++中的一个特殊机制,它允许非成员函数访问类的私有(private)和保护(protected)成员。这提高了代码的灵活性,但需谨慎使用以避免破坏封装性。下面我将逐步解释其概念、特点和使用方法,确保内容真实可靠,并基于C++标准。 #### 1. **概念** 友元函数(Friend Function)是一种被声明为“友元”的普通函数,它不是类的成员函数,但能直接访问类的私有数据。这主要用于需要外部函数操作类内部数据的场景,如运算符重载或工具函数。例如,在数学应用中,友元函数可以方便地处理类中的私有变量,如计算点积或矩阵运算: $$ \text{点积} = \mathbf{a} \cdot \mathbf{b} = \sum_{i=1}^{n} a_i b_i $$ 其中,友元函数可直接访问向量类的私有坐标值[^1][^2]。 #### 2. **特点** 友元函数具有以下核心特点: - **非成员性**:友元函数不是类的成员,因此定义和调用方式与普通函数相同,无需通过对象实例。 - **访问权限**:它能访问类的所有成员(包括私有和保护成员),这突破了类的封装限制。 - **声明方式**:在类定义内部使用`friend`关键字声明,声明位置灵活(通常放在类开头或结尾以提高可读性)。 - **效率优势**:在需要频繁访问私有数据的场景(如高性能计算),友元函数可避免getter/setter的开销,提升运行效率。 - **封装破坏**:过度使用会削弱类的封装性,增加代码耦合度,因此只应在必要时使用,如实现全局运算符或跨类协作[^1][^2]。 #### 3. **使用方法** 友元函数的声明和定义需遵循特定步骤,下面结合代码示例说明。示例基于引用[2][^2]和引用[3][^3],展示一个简单场景:另一个类的成员函数作为友元。 ##### 步骤1:在类中声明友元函数 使用`friend`关键字在类定义内声明友元函数。函数可以是全局函数或另一个类的成员函数。 ```cpp #include <iostream> using namespace std; // 前置声明类,用于友元声明 class Object; class Base { private: int value; // 私有成员变量 public: Base(int x = 0) : value(x) {} // 构造函数 // 声明Object类的成员函数为友元函数 friend void Object::fun(Base& base); }; class Object { public: void fun(Base& base); // 成员函数声明 }; // Object成员函数的定义 void Object::fun(Base& base) { base.value += 10; // 友元函数可直接访问Base的私有成员 cout << "更新后的值: " << base.value << endl; } ``` ##### 步骤2:定义和使用友元函数 友元函数定义在类外,调用时无需对象引用(但需确保作用域可见)。 ```cpp int main() { Base b(5); // 创建Base对象,初始value=5 Object obj; // 创建Object对象 obj.fun(b); // 调用友元函数,输出: 更新后的值: 15 return 0; } ``` **关键点**: - 声明时,`friend`关键字只用于类内,函数定义无需重复。 - 如果友元函数是全局函数(非成员),声明更简单: ```cpp class Base { friend void globalFun(Base& base); // 全局友元函数声明 }; void globalFun(Base& base) { /* 访问私有成员 */ } ``` - **适用场景**:运算符重载(如`<<`输出运算符)、跨类数据共享或数学计算函数(例如,计算两个对象的欧几里得距离:$d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$)[^2][^3]。 #### 4. **注意事项** - **优点**:提高代码效率,简化接口,尤其在需要外部函数处理类内部逻辑时。 - **缺点**:破坏封装性,增加维护难度。建议仅在必要时使用,并优先考虑成员函数或设计模式(如工厂模式)。 - **最佳实践**:限制友元数量,并配合文档说明,以减少风险[^1][^2]。 通过以上步骤,您可安全地使用友元函数来增强C++程序的灵活性。如需深入,请参考C++标准文档或权威教程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值