《C++初阶之类和对象》【友元 + 内部类 + 匿名对象】

发布于:2025-06-28 ⋅ 阅读:(19) ⋅ 点赞:(0)

在这里插入图片描述

往期《C++初阶》回顾:

/------------ 入门基础 ------------/
【C++的前世今生】
【命名空间 + 输入&输出 + 缺省参数 + 函数重载】
【普通引用 + 常量引用 + 内联函数 + nullptr】
/------------ 类和对象 ------------/
【类 + 类域 + 访问限定符 + 对象的大小 + this指针】
【类的六大默认成员函数】
【初始化列表 + 自定义类型转换 + static成员】

前言:

hi~ 各位代码巫师学徒们(◕‿◕✿), 今天我们要学习的是习得难度为 C++ 的:【友元🎫 + 内部类🪆 + 匿名对象👤】
准备好接受这些神奇的 C++ 魔法了吗?🔮 让我们开始今天的咒语学习吧!(≧∇≦)ノ 🧙‍♂️

---------------友元---------------

什么是友元?

友元(Friend):是一种允许 非成员函数其他类 访问某个类的私有(private)和保护(protected)成员的机制。

  • 通过友元,类可以有选择地向特定的外部实体开放其封装的细节,增强灵活性的同时保持封装性的控制。

友元有哪三种形式?

友元的三种的形式分别是:

  1. 友元函数
  2. 友元类
  3. 友元成员函数

友元函数(Friend Function):允许外部函数访问类的私有 / 保护成员。
语法:在类中使用 friend 关键字声明函数。
示例

class Rectangle 
{
private:
    int width, height;
public:
    friend int getArea(const Rectangle& rect);  // 友元函数声明
};

int getArea(const Rectangle& rect) 
{
    return rect.width * rect.height;  // 可访问私有成员
}

友元类(Friend Class):允许整个类访问另一个类的私有 / 保护成员。
语法:在类中使用 friend class 声明友元类。
示例

class A 
{
private:
    int data;
    friend class B;  // 类B是类A的友元
};

class B 
{
public:
    void accessA(A& a) 
    {
        a.data = 42;  // 类B可访问类A的私有成员
    }
};

友元成员函数(Friend Member Function):允许某个类的特定成员函数访问另一个类的私有 / 保护成员。
语法:在类中声明另一个类的特定成员函数为友元。
示例

class A;  // 前向声明

class B 
{
public:
    void func(A& a);  // 成员函数声明
};

class A 
{
private:
    int data;
    friend void B::func(A& a);  // 声明B的成员函数为友元
};

void B::func(A& a) 
{
    a.data = 42;  // 可访问类A的私有成员
}

怎么使用友元函数?

#include<iostream>
using namespace std;

// 前置声明
// 因为func函数中需要用到B类,而B类的定义在后面
// 所以需要提前告诉编译器B是一个类
class B;

/*------------------------------类A的定义------------------------------*/
class A
{
    /*----------------------友元函数声明----------------------*/

    // 友元函数声明:func可以访问A的私有成员
    friend void func(const A& aa, const B& bb); // 注意:虽然func函数参数中用到了B类,但这里只需要B的前置声明

private:
    int _b1 = 3;  
    int _b2 = 4;  
};

/*------------------------------类B的定义------------------------------*/
class B
{
    // 注意:func不是B的友元,所以不能访问B的私有成员
    // 为了让func能访问_b1,我们需要将其设为public
public:
    int _b1 = 5;  
private:
    int _b2 = 6;  
};


/*----------------------友元函数的定义----------------------*/
/**
 * @brief 友元函数定义
 * @param aa A类对象的常量引用
 * @param bb B类对象的常量引用
 *
 * 此函数可以访问A的私有成员,但不能访问B的私有成员
 */

void func(const A& aa, const B& bb)
{
    cout << aa._b1 << endl;     // 可以访问A的私有成员_b1(输出3)
    cout << aa._b2 << endl;  // 也可以访问_b2

    cout << bb._b1 << endl;     // 只能访问B的公有成员_b1(输出5)
    //cout << bb._b2 << endl;  // 错误!不能访问B的私有成员_b2
}

int main()
{
    A aa;  // 创建A类对象
    B bb;  // 创建B类对象

    func(aa, bb);  // 调用友元函数

    return 0;
}

/*
 * 关键点总结:
 * 1. 友元函数声明:
 *    - 在A类内部用friend声明func为友元函数
 *    - func可以访问A的所有成员(包括私有成员)
 *
 * 2. 前置声明:
 *    - 因为func的参数中用到了B类,而B类的定义在后面
 *    - 所以需要在文件开头使用class B;进行前置声明
 *
 * 3. 访问权限:
 *    - func可以访问A的私有成员_b1和_b2
 *    - func不能访问B的私有成员(除非B也声明func为友元)
 * 
 * 4. 注意事项:
 *    - 友元函数不是类的成员函数,只是被特别授权访问私有成员
 */

在这里插入图片描述

怎么使用友元类?

#include<iostream>
using namespace std;

/*------------------------------类B的定义------------------------------*/
//该类将B类声明为友元,允许B访问其私有成员
class A
{
    // 友元类声明:B是A的友元类
    // 这意味着B的所有成员函数都可以访问A的私有成员
    friend class B;
private:
    int _a1 = 1;  
    int _a2 = 2;  
};

/*------------------------------类B的定义------------------------------*/
//A的友元类,可以访问A的私有成员
class B
{
public:
    /*----------------------友元函数的定义----------------------*/
    /**
     * @brief 访问A的私有成员_a1和B的私有成员_b1
     * @param aa A类对象的常量引用
     */
    void func1(const A& aa)
    {
        cout << aa._a1 << endl;  
        cout << _b1 << endl;     
    }

    /*----------------------友元函数的定义----------------------*/
    /**
     * @brief 访问A的私有成员_a2和B的私有成员_b2
     * @param aa A类对象的常量引用
     */
    void func2(const A& aa)
    {
        cout << aa._a2 << endl;  
        cout << _b2 << endl;     
    }
private:
    int _b1 = 3;  
    int _b2 = 4;  
};

int main()
{
    A aa;  // 创建A类对象
    B bb;  // 创建B类对象

    // 调用B的成员函数,演示友元访问
    bb.func1(aa);  // 输出:1 和 3
    bb.func2(aa);  // 输出:2 和 4

    return 0;
}

/*
 * 关键点总结:
 * 1. 友元类声明:
 *    - 在A类内部使用 friend class B; 声明
 *    - 这使得B类的所有成员函数都可以访问A的私有成员
 *
 * 2. 访问权限:
 *    - B类可以访问A的私有成员_a1和_a2
 *    - 但A类不能访问B的私有成员(友元关系是单向的)
 */

在这里插入图片描述

关于友元我们有哪些需要注意的事情?

1. 访问权限相关

  • 突破封装:友元函数或友元类能访问对应类的privateprotected成员,打破了类的常规封装机制,让特定外部代码可操作类的内部数据,增强了灵活性,但也一定程度破坏了封装性,使用时需谨慎。
  • 访问权限不受限:在类中声明友元时,不管放在publicprivate还是protected部分,效果都一样,都能获得相应的访问权限。

2. 关系特性

  • 单向性:友元关系是单向的。
    • 若 A 类是 B 类的友元,不意味着 B 类是 A 类的友元,B 类仍不能随意访问 A 类的私有和保护成员。
    • 例如: A 类可访问 B 类私有成员,但 B 类对 A 类无此权限。
  • 非传递性:即便 A 类是 B 类的友元,B 类是 C 类的友元,A 类也不能自动成为 C 类的友元,不能访问 C 类的私有和保护成员。
  • 非继承性:友元关系不会被派生类继承。
    • 基类的友元不能访问派生类的私有和保护成员,派生类也不会自动成为基类友元类的友元。

3. 声明与定义相关

  • 声明的灵活性:友元声明可以在类定义的任意位置出现,位置不影响其访问权限的赋予。
  • 定义位置要求
    • 友元函数的定义可以在类内(成为内联函数),友元函数的定义也可在类外。
    • 友元类的定义则需在合适位置正常定义。
    • 但不管在哪定义,都要先在对应的类中进行友元声明。

4. 其他特性

  • 影响编译顺序:由于友元涉及不同类或函数间的相互访问,可能会影响编译顺序和依赖关系。
    • 例如: 先声明友元类,再定义友元类,需注意声明和定义的先后逻辑,否则可能出现编译错误。
  • 多类友元:友元函数可以是多个类的友元函数。
    • 这在某些场景下提供了便利,能方便地在不同类间共享特定操作逻辑。
    • 但同时,友元会增加类与类之间的耦合度,过度使用会严重破坏封装性,导致代码维护难度增大,所以友元不宜多用。

---------------内部类---------------

什么是内部类?

内部类(Nested Class):是定义在另一个类内部的类。

  • 内部类是一个独立的类,与定义在全局作用域相比,内部类仅受外部类的类域限制以及访问限定符的约束因此:外部类所定义的对象并不包含内部类的对象。

  • 内部类主要用于封装与外部类密切相关的辅助功能或数据结构,增强代码的模块化和可读性。

内部类的基本使用:

class Outer 
{
public:
    class Inner  //内部类定义
    {  
    public:
        void show() { cout << "Inner class\n"; }
    };
};

int main() 
{
    Outer::Inner innerObj;  //使用作用域解析符创建对象
    innerObj.show();        //输出: Inner class
    return 0;
}

内部类使用需要注意什么?

作用域与可见性:

  • 嵌套作用域:内部类定义在外部类内部,其作用域被限定在外部类中。在外部使用时,需通过外部类作用域来访问。
  • 名称隐藏:内部类名可与外部作用域中的其他名称相同,因为其作用域局限于外部类内,不会产生命名冲突 。

访问权限:

  • 内部类对外部类的访问

    • 内部类能直接访问外部类的 公有/私有静态成员
    • 但对于外部类的 非静态成员,需借助 外部类对象的引用或指针才能访问
    /*--------------------外部类--------------------*/
    class Outer 
    {
    private:
        static int staticData;  // 静态成员
        int nonStaticData;      // 非静态成员
    public:
        /*--------------------内部类--------------------*/
        class Inner 
        {
        public:
            void func(Outer& outer) 
            {
                staticData = 10;           // 合法:访问外部类静态成员
                outer.nonStaticData = 20;  // 需通过对象访问非静态成员
            }
        };
    };
    
  • 外部类对内部类的访问:外部类没有访问内部类 私有成员的特权,需通过内部类对象来访问其公有成员

    class Outer 
    {
    public:
        void accessInner() 
        {
            Inner inner;
            inner.innerFunc();  // 需通过对象访问内部类的公有成员
        }
    };
    
  • 访问限定符影响:内部类的访问权限由外部类的访问限定符(publicprivateprotected )决定。

    • public内部类可在外部被直接访问。
    • private内部类只能被外部类的成员访问。
    • protected内部类可被外部类及其派生类访问 。
    class Outer 
    {
    private:
        class PrivateInner { /* ... */ };  // 私有内部类
    public:
        class PublicInner { /* ... */ };   // 公有内部类
    };
          
    // 合法:可访问公有内部类
    Outer::PublicInner publicObj;
          
    // 错误:无法访问私有内部类
    // Outer::PrivateInner privateObj;
    

独立性:

  • 对象独立性:内部类对象可独立于外部类对象存在,有自己独立的生命周期,二者的生命周期互不影响 。
  • 继承与成员关系:内部类并不自动继承外部类的成员,也不与外部类有隐含的继承关系 。

与外部类的关系:

  • 无默认友元关系:内部类和外部类之间不存在默认的友元关系,若要让一方访问另一方的私有成员,需显式声明友元 。
  • 依赖关系:内部类的定义依赖于外部类,但外部类并不依赖内部类,即便没有实例化内部类,外部类也可正常使用 。

怎么使用内部类?

#include<iostream>
using namespace std;

/*----------------------外部类----------------------*/
//外部类,包含一个静态成员和一个内部类B
class A
{
private:
    static int _k;  // 静态私有成员变量,属于类A
    int _h = 1;     // 普通私有成员变量,每个A对象独有一份

public:
    /*----------------------内部类----------------------*/
    class B
    {
    public:
        /*----------------------foo函数的定义----------------------*/
        void foo(const A& a)
        {
            cout << _k << endl;      // 直接访问A的静态私有成员(合法)
            cout << a._h << endl;    // 通过对象访问A的非静态私有成员(合法)
        }

        int _b1;  // 内部类B的公有成员变量
    };
};


int A::_k = 1;  //静态成员变量必须在类外初始化

int main()
{
    /*----------------------测试1:查看A类的大小----------------------*/
    // 静态成员_k不占用类对象的内存空间
    // 只有普通成员_h占用空间(int类型通常4字节)
    cout << sizeof(A) << endl;  // 输出:4(在32位系统中)

    /*----------------------测试2:创建内部类B的对象----------------------*/
    //1.使用作用域解析运算符创建内部类对象
    A::B bb;  

    /*----------------------测试3:使用内部类访问外部类私有成员----------------------*/
    //1.直接创建的外部类的对象
    A aa;    
    //2.使用内部类的对象调用内部类的成员函数
    bb.foo(aa);  
    // 输出:
    // 1(静态成员_k的值)
    // 1(aa对象的_h的值)

    return 0;
}

/*
 * 关键点总结:
 *
 * 1. 访问权限:
 *    - B可以直接访问A的静态成员_k
 *    - B需要通过A的对象访问普通成员_h
 */

在这里插入图片描述

一道练习题,快来试一试吧!

开始挑战:JZ64 求1+2+3+…+n

题目介绍:

在这里插入图片描述

方法一:static成员

/*----------------------------定义 Sum 类----------------------------*/
class Sum  //辅助计算累加和
{
public:
    /*-----------------------成员函数-----------------------*/
    //1.定义构造函数 ---> 每次创建 Sum 对象时,执行累加逻辑
    Sum()
    {
        //1.将静态成员变量 _i 的值累加到静态成员变量 _ret 中
        _ret += _i;
        //2.静态成员变量 _i 自增,为下一次累加做准备
        ++_i;
    }

    //2.定义静态成员函数 ---> 用于获取最终的累加结果
    static int GetRet()
    {
        return _ret;
    }

private:
    /*-----------------------成员变量-----------------------*/
    //1.记录当前要累加的数值
    static int _i;

    //2.记录累加的结果
    static int _ret;
};

//1.类外初始化 Sum 类的静态成员变量 _i
int Sum::_i = 1;
//2.类外初始化 Sum 类的静态成员变量 _ret
int Sum::_ret = 0;



/*----------------------------定义 Solution 类----------------------------*/
class Solution  //用于提供计算 1 到 n 累加和的功能
{
public:
    /*-----------------------成员函数-----------------------*/
    int Sum_Solution(int n)  //接收参数 n,计算 1 + 2 +... + n 的结果
    {
        //1.定义一个变长数组
        Sum arr[n];
        /* 注意事项:
        *   1.创建一个包含 n 个 Sum 对象的数组,创建过程中会调用 Sum 的构造函数 n 次
        *   2.每次构造函数调用会完成 _ret 的累加和 _i 的自增*
        */

        //2.通过 Sum 类的静态成员函数 GetRet 获取累加结果并返回
        return Sum::GetRet();
    }
};

方法二:内部类

/*----------------------------外部类----------------------------*/
class Solution //解决方案类
{
public:
	/*----------------------------成员变量----------------------------*/
	//1.记录当前累加的数值
	static int _i;
	//2.存储累加的总和
	static int _ret;

	/*----------------------------成员变量----------------------------*/
	int Sum_Solution(int n) //原理:通过创建n个Sum对象,触发n次构造函数执行累加逻辑
	{
		//1. 创建长度为n的Sum对象数组
		// 注意:每次创建Sum对象时,其构造函数会自动执行累加操作
		Sum arr[n];


		//2. 返回累加结果(此时_ret已完成1+2+...+n的计算)
		return _ret;
	}

private:
	/*----------------------------内部类----------------------------*/
	class Sum  //辅助类 ---> 利用构造函数执行累加逻辑
	{
	public:
		//1.实现:“默认构造函数”
		Sum()
		{
			_ret += _i;
			_i++;
		}
		/* 注意事项:每次创建Sum对象时
		*	  1.将当前_i的值累加到_ret
		*	  2._i自增1
		*/
	};
};


//1.类外初始化 Sum 类的静态成员变量 _i
int Solution::_i = 1;
//2.类外初始化 Sum 类的静态成员变量 _ret
int Solution::_ret = 0;

---------------匿名对象---------------

什么是匿名对象?

匿名对象:是指在创建时未显式声明名称的对象。

  • 通常在表达式结束时立即销毁。

  • 它是 C++ 中一种高效的临时值表示方式,常用于函数传参、返回值等场景。

匿名对象有哪些特点?

匿名对象的特点:

  • 无标识符:在定义时没有名字,通过在类名后加一对空括号来实例化。
    • 例如ClassName(),可以是自定义类类型,也可以是内置类型(有了模板后,内置类型也可创建匿名对象 )
  • 生命周期:通常是临时的,在创建它们的表达式结束后很快就会被销毁 。
    • 例如:在函数返回对象时产生的临时匿名对象,当完成返回操作后就会被销毁,但如果用常量引用来引用匿名对象,其生命周期会延长至引用作用域结束 。
  • 右值特性:属于右值(无法取地址)
class Data 
{
public:
    Data(int x) { cout << "构造:" << x << endl; }
    ~Data() { cout << "析构" << endl; }
};

int main() 
{
    Data(10);  // 创建匿名对象,本行结束立即析构
    cout << "------" << endl;
    return 0;
}

匿名对象的使用场景有哪些?

匿名对象常见的使用场景:

1. 作为函数参数传递

当函数接受对象作为参数时,可直接传递匿名对象,尤其适用于有默认参数的函数。

#include <iostream>
using namespace std;

class MyClass
{
public:
    MyClass(int num) : data(num) {}

    void printData() const
    {
        cout << "Data: " << data << endl;
    }
private:
    int data;
};

void func(MyClass obj = MyClass(0))
{
    obj.printData();
}

int main()
{
    /*------------------------作为函数参数传递------------------------*/
    func(MyClass(5)); // 传递匿名对象给函数func
    return 0;
}

2. 临时调用成员函数

仅需临时使用对象并调用其成员函数,后续不再使用该对象时,可创建匿名对象。

#include <iostream>
using namespace std;

class Printer
{
public:
    void printMessage(const string& msg)
    {
        cout << msg << endl;
    }
};

int main()
{
    /*------------------------临时调用成员函数------------------------*/
    Printer().printMessage("Hello, World!"); // 创建匿名Printer对象并调用成员函数
    return 0;
}

3. 避免不必要的拷贝构造(优化性能)

在函数返回对象时,返回匿名对象可让编译器进行返回值优化(RVO),减少对象拷贝。

#include <iostream>
using namespace std;

class MyData
{
public:
    MyData(int num) : data(num)
    {
        cout << "构造函数调用" << endl;
    }
    MyData(const MyData& other) : data(other.data)
    {
        cout << "拷贝构造函数调用" << endl;
    }
    ~MyData()
    {
        cout << "析构函数调用" << endl;
    }

    int getData() const
    {
        return data;
    }
private:
    int data;
};

MyData getMyData()
{
    /*-----------------------避免不必要的拷贝构造-----------------------*/
    return MyData(10); // 返回匿名对象,利于编译器进行返回值优化
}

int main()
{
    MyData obj = getMyData();
    cout << "Data: " << obj.getData() << endl;
    return 0;
}

4. 类型转换
在涉及类型转换时,可利用匿名对象来满足类型要求。

#include <iostream>
using namespace std;

class A
{
public:
    A(int num) : value(num) {}
    int getValue() const
    {
        return value;
    }
private:
    int value;
};

void processInt(int num)
{
    cout << "processInt: " << num << endl;
}

int main()
{
    A a(5);
    /*-----------------------类型转换-----------------------*/
    processInt(A(5).getValue()); // 创建匿名A对象进行类型转换后传递给函数
    return 0;
}

在这里插入图片描述

怎么使用匿名对象呢?

#include <iostream>
using namespace std;

/*----------------------A类定义----------------------*/
class A
{
public:
    /*----------------------构造函数(带默认参数)----------------------*/
    A(int a = 0)
        :_a(a)  
    {
        cout << "A(int a)" << endl;  
    }

    /*----------------------析构函数----------------------*/
    ~A()
    {
        cout << "~A()" << endl;  
    }
private:
    int _a;  
};

/*----------------------Solution类定义----------------------*/ 
class Solution 
{

public:
    /*----------------------默认构造函数----------------------*/
    Solution()
    {
        cout << "Solution()" << endl;
    }
    /*----------------------析构函数----------------------*/
    ~Solution()
    {
        cout << "~Solution()" << endl;
    }

    /*----------------------计算求和的函数----------------------*/
    int Sum_Solution(int n) 
    {
        //... 实际实现逻辑省略
        return n;
    }
};

int main()
{
    /*----------------------------普通对象定义(调用默认构造函数)----------------------------*/
    A aa1;  // 输出:A(int a)
    // 注意:aa1对象会在main函数结束时析构

    //注意:以下写法会被编译器解析为函数声明,不是对象定义
    // A aa1();  // 这声明了一个返回A类型的函数aa1,而不是创建对象

    /*----------------------------匿名对象的使用(生命周期仅限当前行)----------------------------*/
    A();   // 输出:A(int a) 和 ~A() (构造后立即析构)
    A(1);  // 输出:A(int a) 和 ~A() (构造后立即析构)

    /*----------------------------显式调用带参构造函数----------------------------*/ 
    A aa2(2);  // 输出:A(int a)
    // aa2对象会在main函数结束时析构

    /*----------------------------匿名对象的实际应用场景----------------------------*/
    Solution().Sum_Solution(10);
    // 创建一个匿名Solution对象,调用其方法后立即销毁
    // 适用于只需要临时使用一次对象的情况

    return 0;
    // 输出(按顺序):
    // ~A() - aa2析构
    // ~A() - aa1析构
}

/*
 * 关键点总结:
 * 1. 匿名对象特性:
 *    - 语法:直接使用类名加构造函数参数(如:A() 或 A(1))
 *    - 生命周期:仅存在于定义它的那一行语句
 *    - 用途:临时使用对象,避免命名和长期保存
 *
 * 2. 对象定义注意事项:
 *    - A aa1;     // 正确:调用默认构造函数
 *    - A aa1();   // 错误:被解析为函数声明
 *    - A aa1(1);  // 正确:调用带参构造函数
 *
 * 3. 生命周期说明:
 *    - 普通对象:作用域结束时析构
 *    - 匿名对象:当前语句执行完毕后立即析构
 */

在这里插入图片描述
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到