当前位置: 首页 > news >正文

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

【友元 + 内部类 + 匿名对象】目录

  • 前言:
  • ---------------友元---------------
    • 什么是友元?
    • 友元有哪三种形式?
    • 怎么使用友元函数?
    • 怎么使用友元类?
    • 关于友元我们有哪些需要注意的事情?
  • ---------------内部类---------------
    • 什么是内部类?
    • 内部类使用需要注意什么?
    • 怎么使用内部类?
    • 一道练习题,快来试一试吧!
      • 题目介绍:
      • 方法一:static成员
      • 方法二:内部类
  • ---------------匿名对象---------------
    • 什么是匿名对象?
    • 匿名对象有哪些特点?
    • 匿名对象的使用场景有哪些?
    • 怎么使用匿名对象呢?

在这里插入图片描述

往期《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;  // 也可以访问_b2cout << 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 和 3bb.func2(aa);  // 输出:2 和 4return 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 classreturn 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;  // 静态私有成员变量,属于类Aint _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)); // 传递匿名对象给函数funcreturn 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. 生命周期说明:*    - 普通对象:作用域结束时析构*    - 匿名对象:当前语句执行完毕后立即析构*/

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

相关文章:

  • 【.net core】【sqlsugar】在where条件查询时使用原生SQL
  • 清理华为云服务器内存使用率
  • 2025-6-27-C++ 学习 模拟与高精度(7)
  • unityButton问题
  • Gitee 持续集成与交付(CI/CD)篇
  • 【VPX3U】国产嵌入式平台:RK3588J×JH930硬件架构与红外应用方案
  • 推荐一个基于C#开发的跨平台构建自动化系统!
  • 【项目开发】删除表中所有含重复字段的数据
  • JetBrains AI助手登陆Android Studio!智能编码提升Kotlin开发效能
  • 无法访问 文件或目录损坏且无法读取
  • SPL 报表开发:不依赖逻辑数仓的轻量级多数据源报表
  • Linux命令行操作基础
  • 封装nuxt3的SSR请求和CSR请求方法
  • Linux基本指令篇 —— tac指令
  • GraphQL API-1
  • AIOps与人工智能的融合:从智能运维到自适应IT生态的革命
  • Java项目:基于SSM框架实现的宠物综合服务平台管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告】
  • MCP-安全(代码实例)
  • 告别堡垒机和VPN!Teleport:下一代基础设施统一访问入口
  • 设计模式 | 适配器模式