C++对象模型的底层逻辑

发布于:2025-09-05 ⋅ 阅读:(30) ⋅ 点赞:(0)

各位C++战士们,欢迎来到面向对象的“觉醒时刻”!

Part1面向过程和面向对象

1.1、面向过程和面向对象的初步认识

编程思想的核心差异,从 C 语言和 C++ 的对比中可见一斑:

  • C 语言是面向过程的:关注 “步骤”,分析解决问题的流程,通过函数调用逐步执行步骤(例如,实现一个栈时,会分步骤写初始化、入栈、出栈等函数,调用时按顺序执行)。
  • C++ 是基于面向对象的:关注 “对象”,将问题拆分成不同的对象,通过对象间的交互完成任务(例如,实现栈时,将栈的属性(栈顶、容量、数据)和行为(初始化、入栈、出栈)封装成一个 “栈对象”,直接通过对象调用行为)。

面向对象编程(OOP)的核心思想是 “万物皆为对象”,将现实世界的事物抽象为程序中的对象,每个对象包含两部分:

  • 数据状态(属性,如栈的容量、当前元素个数);
  • 可执行行为(方法,如栈的入栈、出栈操作)。

面向对象的三大特性是其强大的核心:

  • 封装:将数据和行为捆绑,隐藏内部细节,只暴露必要接口(如栈的内部扩容逻辑对外隐藏,用户只需调用Push);
  • 继承:允许一个对象继承另一个对象的特性,快速构建相似对象(如 “学生” 继承 “人” 的基本属性,再添加 “学号” 等特有属性);
  • 多态:同一行为在不同对象上有不同表现(如 “动物叫”,猫会 “喵喵”,狗会 “汪汪”)。

简言之,面向对象通过 “对象” 作为基本单元,实现数据和行为的有机结合,让程序结构更清晰、易维护、可扩展。

1.2、类的引入

C 语言的结构体只能定义变量,而 C++ 对结构体进行了扩展:结构体中不仅可以定义变量,还可以定义函数。例如,用 C++ 实现栈:

typedef int DataType;
struct Stack {
    // 成员变量(属性)
    int top;
    int capacity;
    DataType* array;
    // 成员函数(行为)
    void Init() {
        top = 0;
        array = (DataType*)malloc(sizeof(DataType) * 4);
        if (array == nullptr) {
            perror("malloc failed");
            return;
        }
        capacity = 4;
    }
    void Push(DataType x) {
        if (top == capacity) { // 扩容
            DataType* tmp = (DataType*)realloc(array, sizeof(DataType) * (capacity * 2));
            if (tmp == nullptr) {
                perror("realloc failed");
                return;
            }
            array = tmp;
            capacity *= 2;
        }
        array[top++] = x;
    }
    DataType Top() {
        if (top == 0) {
            cout << "stack is empty" << endl;
            return -1;
        }
        return array[top - 1];
    }
};
int main() {
    Stack ss; // C++中可省略struct关键字
    ss.Init();  // 直接通过对象调用成员函数,无需传参
    ss.Push(1);
    ss.Push(2);
    cout << ss.Top() << endl; // 输出2
    return 0;
}

上述代码中,Stack结构体同时包含了变量(top、capacity等)和函数(Init、Push等),且调用函数时无需像 C 语言那样传递结构体指针 —— 这是因为函数内部可以直接访问结构体的成员变量。

不过,C++ 中更推荐用class关键字定义类(而非struct),两者功能类似,但存在访问权限的默认差异(后续会讲)。

 

1.3、类的定义

类的基本语法如下:

class ClassName {
    // 类体:成员变量(属性)和成员函数(方法)
}; // 注意末尾的分号
  • class是定义类的关键字;
  • ClassName是类名;
  • 类体中的变量称为成员变量(或属性);
  • 类体中的函数称为成员函数(或方法)。

类的两种定义方式

1.声明和定义全部放在类体中

成员函数在类内定义时,编译器可能将其视为内联函数(inline),适合简单函数。

class TestClass {
public:
    void Print() { // 类内定义,可能被视为内联
        cout << _a << " " << _b << endl;
    }
private:
    int _a; // 成员变量命名建议:加下划线区分
    int _b;
};

2.声明放在.h文件,定义放在.cpp文件

类声明和定义分离,适合复杂函数,避免重复编译。类外定义成员函数时,需用类名::指定作用域。

// test.h(声明)
class TestClass {
public:
    void Print(); // 声明
private:
    int _a;
    int _b;
};
// test.cpp(定义)
#include "test.h"
void TestClass::Print() { // 类外定义,需加TestClass::
    cout << _a << " " << _b << endl;
}

建议成员变量名前 / 后加下划线(如_name、age_),避免与函数参数或局部变量重名:

class Person {
public:
    void SetName(string name) {
        _name = name; // _name是成员变量,name是参数,清晰区分
    }
private:
    string _name; // 成员变量加下划线
};

1.4、类的访问限定符及封装

1.4.1、访问限定符

C++ 通过访问限定符控制成员的访问权限,实现 “封装” 的核心功能。常用的访问限定符有三种:

  • public(公有):类外可直接访问(如对象.公有成员);
  • private(私有):类外不可直接访问,仅类内成员函数可访问;
  • protected(保护):类外不可直接访问,主要用于继承(后续讲解)。

访问限定符的作用域

从限定符出现的位置开始,到下一个限定符出现为止;若没有下一个限定符,则到类体结束(})为止。

class 与 struct 的区别

  • class定义的类,默认访问权限为 private
  • struct定义的类,默认访问权限为 public(为兼容 C 语言的结构体)。

示例:

class A {
    int _a; // 默认private,类外不可访问
public:
    int _b; // public,类外可访问
private:
    int _c; // private,类外不可访问
};
struct B {
    int _x; // 默认public,类外可访问
private:
    int _y; // private,类外不可访问
};
int main() {
    A a;
    a._b = 10; // 正确(public)
    // a._a = 20; // 错误(private,类外不可访问)
    B b;
    b._x = 30; // 正确(public)
    // b._y = 40; // 错误(private,类外不可访问)
    return 0;
}

1.4.2、封装

封装的本质:将数据(成员变量)和操作数据的方法(成员函数)有机结合,隐藏内部实现细节,仅通过公开接口与外部交互。

例如,手机的封装:用户无需知道内部芯片、电路的工作原理,只需通过屏幕、按键(接口)操作即可。

C++ 中,封装通过访问限定符实现:

  • 隐藏细节:将内部数据(如栈的array指针)设为 private,避免外部直接修改导致错误;
  • 暴露接口:将必要操作(如Push、Pop)设为 public,供外部安全调用。

1.5、类的作用域

类定义了一个独立的作用域(类域),所有成员都属于这个作用域。在类外定义成员时,需用::(作用域操作符)指明所属类域。

示例:

class Person {
public:
    void PrintInfo(); // 声明:属于Person类域
private:
    char _name[20];
    int _age;
};
// 类外定义:需用Person::指定作用域
void Person::PrintInfo() {
    cout << _name << " " << _age << endl; // 可直接访问类内成员
}

1.6、类的实例化

类的实例化:用类类型创建对象的过程。

  • 类是 “模型”:描述对象的属性和行为,不占用实际内存(如 “学生表” 是模型,记录学生的属性,但本身不是具体学生);
  • 对象是 “实例”:类实例化出的具体实体,占用内存(如 “张三” 是 “学生表” 实例化出的具体学生,有具体的姓名、年龄)。

示例:

class Person {
public:
    char _name[20];
    int _age;
};
int main() {
    Person p1; // 实例化对象p1
    Person p2; // 实例化对象p2
    p1._age = 18; // 对象p1的年龄
    p2._age = 20; // 对象p2的年龄(不同对象的成员变量独立)
    return 0;
}

上述代码中,Person类本身不占内存,p1和p2是实例化的对象,各自占用内存存储_name和_age。

1.7、类的空间大小

类的空间大小仅取决于成员变量,成员函数存储在公共代码区(所有对象共享同一份函数代码)。

示例分析

#include <iostream>
using namespace std;
class A {}; // 空类
class B {
private:
    int _a; // 成员变量
};
class C {
public:
    void Print() { cout << _a << endl; } // 成员函数(不占对象空间)
private:
    int _a; // 成员变量
};
int main() {
    cout << sizeof(A) << endl; // 输出1
    cout << sizeof(B) << endl; // 输出4
    cout << sizeof(C) << endl; // 输出4
    return 0;
}
  • 空类(A):编译器会分配 1 字节(占位符),用于唯一标识类的实例(否则无法区分不同对象);
  • 类 B:仅含 1 个int成员变量,大小为 4 字节;
  • 类 C:成员函数不占对象空间,仅int _a占 4 字节,故大小为 4 字节。

内存对齐规则

类的成员变量存储遵循结构体对齐规则(因为类的底层存储可视为 “带函数的结构体”):

  1. 第一个成员永远放在偏移量为 0 的位置;
  2. 从第二个成员开始,每个成员需对齐到 “对齐数” 的整数倍处(对齐数 = 成员自身大小与默认对齐数的较小值;VS 默认对齐数为 8,gcc 无默认对齐数,对齐数 = 成员自身大小);
  3. 类的总大小为 “最大对齐数” 的整数倍(最大对齐数 = 所有成员对齐数的最大值);
  4. 若嵌套类 / 结构体,嵌套的类 / 结构体需对齐到自身最大对齐数的整数倍处。

示例:

class D {
private:
    char _c; // 对齐数1(自身大小)
    int _i;  // 对齐数4(min(4, 8))
    double _d; // 对齐数8(min(8, 8))
};
// 内存布局:
// _c:偏移0(1字节)
// 填充3字节(偏移1-3),使_i对齐到4
// _i:偏移4(4字节)
// _d:偏移8(8字节,对齐到8)
// 总大小:16(最大对齐数8,16是8的整数倍)
cout << sizeof(D) << endl; // 输出16

1.8、this 指针

在类的成员函数中,隐含着一个this指针,它指向当前调用函数的对象。this指针的核心作用是区分成员变量和参数(或局部变量)。

示例:this 指针的使用

class Student {
private:
    int _age; // 成员变量
public:
    void SetAge(int age) { // 参数age与成员变量_age重名
        this->_age = age; // this指向当前对象,this->_age即成员变量
    }
    int GetAge() {
        return this->_age; // 可省略this(编译器自动补充)
    }
};
int main() {
    Student s;
    s.SetAge(18); // 调用时,this自动指向s
    cout << s.GetAge() << endl; // 输出18
    return 0;
}

上述代码中,SetAge函数的参数age与成员变量_age重名,通过this->_age明确访问成员变量。

this 指针的特性

  • 类型:类类型* const(例如Student* const),即指针本身不可修改(不能给this赋值);
  • 作用域:仅在成员函数内部可用;
  • 本质:成员函数的隐含形参,当对象调用函数时,编译器自动将对象地址作为实参传递给this(无需用户手动传递);
  • 存储:不存储在对象中,通常由编译器通过寄存器(如 ecx)传递。

Part2对象的构造和析构函数

在 C++ 中,对象的创建和销毁需要特定的函数来管理初始化和清理工作,这就是构造函数和析构函数。它们是面向对象编程中确保对象正确初始化和资源释放的核心机制。

2.1、构造函数:对象的 “出生证明”

构造函数是一种特殊的成员函数,用于在创建对象时自动完成初始化工作(如分配内存、初始化成员变量等)。

核心特性:

  • 函数名与类名相同:例如class Array的构造函数名为Array。
  • 无返回值:无需声明返回类型,也不能有return语句。
  • 自动调用:在对象创建时由编译器自动调用,用户无需(也不能)手动调用。

示例:基本构造函数

#include <iostream>
#include <cstdlib>
using namespace std;
class Array {
private:
    int* m_data;  // 数组数据地址
    int m_size;   // 数组大小
public:
    // 无参构造函数(函数名与类名相同,无返回值)
    Array() {
        cout << "Array的无参构造函数" << endl;
        m_size = 5;  // 初始化大小为5
        m_data = (int*)malloc(sizeof(int) * m_size);  // 分配内存
    }
    // 其他成员函数(省略)
    ~Array();  // 析构函数(后续讲解)
};
// 析构函数定义(释放资源)
Array::~Array() {
    cout << "Array的析构函数" << endl;
    if (m_data != NULL) {
        free(m_data);  // 释放动态分配的内存
        m_data = NULL;
    }
}
int main() {
    Array a1;  // 创建对象时,自动调用无参构造函数
    return 0;
}

运行结果

Array的无参构造函数
Array的析构函数  // 程序结束时自动调用析构函数

2.2、构造函数的重载与调用

和普通函数一样,构造函数支持重载(多个构造函数参数列表不同)。创建对象时,编译器会根据传递的实参匹配对应的构造函数。

调用方式:

  • 括号法:Array a(10);(最常用)。
  • 等号法:Array a = 10;(仅适用于单参数构造函数)。
  • 逗号表达式:Array a = (1, 2);(实际传递最后一个参数,此处等价于Array a(2))。

示例:重载构造函数

class Array {
private:
    int* m_data;
    int m_size;
public:
    Array();                  // 无参构造函数
    Array(int s);             // 单参数构造函数(指定大小)
    Array(int s, int init);   // 双参数构造函数(指定大小和初始值)
    ~Array();
};
// 无参构造函数
Array::Array() {
    cout << "无参构造函数" << endl;
    m_size = 5;
    m_data = (int*)malloc(sizeof(int) * m_size);
}
// 单参数构造函数
Array::Array(int s) {
    cout << "单参数构造函数" << endl;
    m_size = s;
    m_data = (int*)malloc(sizeof(int) * m_size);
}
// 双参数构造函数
Array::Array(int s, int init) {
    cout << "双参数构造函数" << endl;
    m_size = s;
    m_data = (int*)malloc(sizeof(int) * m_size);
    for (int i = 0; i < m_size; i++) {
        m_data[i] = init;  // 初始化数组元素
    }
}
Array::~Array() {
    free(m_data);
    m_data = NULL;
}
int main() {
    Array a1;         // 调用无参构造函数
    Array a2(10);     // 调用单参数构造函数(括号法)
    Array a3 = 8;     // 调用单参数构造函数(等号法)
    Array a4(5, 0);   // 调用双参数构造函数
    Array a5 = (3, 6); // 逗号表达式,实际传递6,调用单参数构造函数
    return 0;
}

2.3、拷贝构造函数:对象的 “复制机”

拷贝构造函数是一种特殊的构造函数,用于用已存在的对象初始化新对象

声明格式:

类名(const 类名& 源对象);  // 参数为源对象的常量引用

调用时机:

  • 用一个对象初始化另一个对象:Array a2(a1); 或 Array a2 = a1;。
  • 函数参数为对象(值传递):当函数形参是对象时,会用实参拷贝初始化形参。
  • 函数返回值为对象(值返回):返回时会拷贝一个临时对象作为返回值。

示例:拷贝构造函数

class Array {
private:
    int* m_data;
    int m_size;
public:
    Array();                  // 无参构造函数
    Array(const Array& a);    // 拷贝构造函数
    ~Array();
};
Array::Array() {
    cout << "无参构造函数" << endl;
    m_size = 5;
    m_data = (int*)malloc(sizeof(int) * m_size);
}
// 拷贝构造函数(此处为简化示例,实际需实现深拷贝)
Array::Array(const Array& a) {
    cout << "拷贝构造函数" << endl;
    m_size = a.m_size;  // 复制大小
    m_data = (int*)malloc(sizeof(int) * m_size);  // 分配新内存
    // 后续需复制数据(深拷贝)
}
Array::~Array() {
    free(m_data);
    m_data = NULL;
}
// 场景2:函数参数为对象(值传递)
void print(Array a) {  // 调用拷贝构造函数:用实参初始化a
    // ...
}
// 场景3:函数返回值为对象
Array createArray() {
    Array temp;
    return temp;  // 调用拷贝构造函数:拷贝temp到返回值
}
int main() {
    Array a1;
    Array a2(a1);  // 场景1:用a1初始化a2
    print(a1);     // 场景2:触发拷贝构造函数
    Array a3 = createArray();  // 场景3:触发拷贝构造函数
    return 0;
}

2.4、深度拷贝:避免 “同归于尽” 的复制

默认情况下,拷贝构造函数会执行浅拷贝(仅复制指针地址,不复制指针指向的内容),这会导致两个对象共享同一块内存,释放时出现 double free 错误。深拷贝则会复制指针指向的内容,确保每个对象拥有独立的资源。

示例:深拷贝的实现

Array::Array(const Array& a) {  // 深拷贝
    cout << "深拷贝构造函数" << endl;
    m_size = a.m_size;
    // 1. 为新对象分配独立内存
    m_data = (int*)malloc(sizeof(int) * m_size);
    // 2. 复制源对象的数据到新内存
    for (int i = 0; i < m_size; i++) {
        m_data[i] = a.m_data[i];
    }
}

深拷贝 vs 浅拷贝

  • 浅拷贝:m_data = a.m_data;(共享内存,危险)。
  • 深拷贝:重新分配内存并复制数据(独立内存,安全)。

2.5、默认构造函数:编译器的 “默认礼物”

当用户未定义任何构造函数时,编译器会自动生成默认无参构造函数(什么也不做)。但一旦用户定义了构造函数,编译器将不再提供默认版本。

规则:

  • 若用户定义了无参构造函数,编译器不再生成默认无参构造函数。
  • 若用户定义了有参构造函数,编译器不再生成默认无参构造函数(此时创建无参对象会报错)。

示例:默认构造函数的行为

class Demo {
public:
    // 若注释掉以下构造函数,编译器会生成默认无参构造函数
    Demo(int a) {  // 用户定义了有参构造函数
        cout << "有参构造函数" << endl;
    }
};
int main() {
    // Demo d;  // 报错:无合适的无参构造函数(编译器未生成)
    Demo d(10);  // 正确:调用用户定义的有参构造函数
    return 0;
}

2.6、析构函数:对象的 “死亡证明”

析构函数用于在对象销毁时自动完成清理工作(如释放动态内存、关闭文件等)。

核心特性:

  • 函数名:~类名(波浪线 + 类名)。
  • 无返回值:无需声明返回类型。
  • 无参数:不能重载,一个类只能有一个析构函数。
  • 自动调用:对象生命周期结束时(如离开作用域)由编译器自动调用。

示例:析构函数的作用

class Array {
private:
    int* m_data;
    int m_size;
public:
    Array(int size) {
        m_data = (int*)malloc(sizeof(int) * size);  // 分配内存
    }
    ~Array() {  // 析构函数:释放资源
        cout << "释放内存" << endl;
        if (m_data != NULL) {
            free(m_data);  // 释放动态分配的内存
            m_data = NULL;
        }
    }
};
int main() {
    {
        Array a(10);  // 创建对象,分配内存
    }  // a离开作用域,自动调用析构函数释放内存
    return 0;
}

2.7、匿名对象:“来也匆匆,去也匆匆”

匿名对象是指未命名的临时对象,格式为类名(参数)。其生命周期仅在当前行,执行完后立即销毁(调用析构函数)。

示例:匿名对象的特性

class Test {
public:
    Test() {
        cout << "构造函数" << endl;
    }
    ~Test() {
        cout << "析构函数" << endl;
    }
};
int main() {
    Test();  // 匿名对象:构造后立即销毁
    cout << "----------------" << endl;
    return 0;
}

运行结果

构造函数
析构函数
----------------

2.8、对象的动态创建和释放:new 与 delete

  • new:动态创建对象,会自动调用构造函数。
  • delete:释放动态创建的对象,会自动调用析构函数。

注意:malloc/free仅分配 / 释放内存,不会调用构造函数 / 析构函数,因此在 C++ 中应优先使用new/delete。

示例:new 与 delete 的使用

class Test {
public:
    Test() {
        cout << "无参构造函数" << endl;
    }
    Test(int a) {
        cout << "有参构造函数" << endl;
    }
    ~Test() {
        cout << "析构函数" << endl;
    }
};
int main() {
    // 动态创建对象(调用有参构造函数)
    Test* pt = new Test(10);
    // ... 使用对象
    delete pt;  // 释放对象(调用析构函数)
    // 动态创建数组(调用无参构造函数)
    Test* arr = new Test[3];
    delete[] arr;  // 释放数组(注意加[])
    return 0;
}

2.9、构造函数的形参初始化列表:初始化的 “快车道”

初始化列表用于在构造函数体执行前初始化成员变量,适用于以下场景:

  1. 成员变量为const修饰(必须初始化,不能赋值)。
  2. 成员变量是类对象,且该类没有无参构造函数(必须显式初始化)。

格式:

类名(参数) : 成员1(值1), 成员2(值2), ... {
    // 构造函数体
}

示例:初始化列表的使用

class Date {
private:
    int m_year, m_month, m_day;
public:
    // Date没有无参构造函数,必须通过有参构造函数初始化
    Date(int y, int m, int d) : m_year(y), m_month(m), m_day(d) {
        cout << "Date构造函数" << endl;
    }
};
class Student {
private:
    const int m_id;  // const成员必须初始化
    Date birth;      // Date对象,无无参构造函数
public:
    // 初始化列表:初始化m_id和birth
    Student(int id, int y, int m, int d) : m_id(id), birth(y, m, d) {
        cout << "Student构造函数" << endl;
    }
};
int main() {
    Student s(1001, 2000, 1, 1);  // 正确初始化
    return 0;
}

运行结果:

Date构造函数
Student构造函数

Part3静态成员变量和静态成员函数

在 C++ 中,静态成员(包括静态成员变量和静态成员函数)属于类本身,而非类的某个具体对象。它们是实现多个对象共享数据或功能的重要机制。

3.1、静态成员变量:类的 “共享仓库”

静态成员变量被所有类对象共享,不属于某个特定对象,其内存空间在程序启动时分配,生命周期贯穿整个程序。

核心特性:

  • 属于类,而非对象:所有对象访问的是同一份静态成员变量。
  • 必须在类外初始化:类内仅声明,初始化需在类外(通常在.cpp 文件中)。
  • 可通过类名直接访问:无需创建对象,格式为类名::静态变量名。
  • 普通成员函数可访问:静态成员变量对类的所有成员函数可见。

示例:静态成员变量的使用

#include <iostream>
using namespace std;
class Student {
public:
    static int count;  // 静态成员变量:记录学生总数(类内声明)
    int id;            // 普通成员变量:学生编号
public:
    Student() {
        count++;       // 每创建一个对象,总数+1
        id = count;    // 用总数作为编号
    }
    // 普通成员函数访问静态成员变量
    int GetCount() {
        return count;
    }
};
// 静态成员变量必须在类外初始化(定义)
int Student::count = 0;
int main() {
    Student s1;
    Student s2;
    // 访问静态成员变量的三种方式
    cout << s1.count << endl;       // 通过对象访问(不推荐)
    cout << s2.count << endl;       // 通过对象访问(不推荐)
    cout << Student::count << endl; // 通过类名访问(推荐)
    // 通过普通成员函数访问
    cout << s1.GetCount() << endl;  // 输出2
    return 0;
}

运行结果:

2
2
2
2

3.2、静态成员函数:类的 “工具函数”

静态成员函数属于类本身,用于处理与类相关的操作,不依赖具体对象。

核心特性:

  • 无 this 指针:不能访问普通成员变量(需依赖对象),只能访问静态成员变量。
  • 可通过类名直接调用:无需创建对象,格式为类名::静态函数名()。
  • 普通成员函数不可通过类名调用:普通函数依赖对象(有 this 指针)。

示例:静态成员函数的使用

class Student {
public:
    static int count;  // 静态成员变量
    int id;            // 普通成员变量
public:
    Student() {
        count++;
        id = count;
    }
    // 静态成员函数:只能访问静态成员变量
    static int GetCount1() {
        // return id;  // 错误:静态函数不能访问普通成员变量
        return count;  // 正确:访问静态成员变量
    }
    // 普通成员函数:可访问静态和普通成员变量
    int GetId() {
        return id;
    }
};
int Student::count = 0;
int main() {
    Student s1;
    Student s2;
    // 通过类名调用静态成员函数(推荐)
    cout << Student::GetCount1() << endl;  // 输出2
    // 通过对象调用静态成员函数(不推荐)
    cout << s1.GetCount1() << endl;        // 输出2
    // 普通成员函数必须通过对象调用
    cout << s1.GetId() << endl;            // 输出1
    return 0;
}

静态成员的典型用途:

  • 统计类的对象总数(如示例中的count)。
  • 实现单例模式(全局唯一实例)。
  • 定义工具函数(如数学计算、数据转换等与类相关的通用操作)。

Part4友元函数与友元类

友元(friend)机制允许类外部的函数或其他类访问其私有成员,打破了类的封装性,用于解决特定场景下的访问需求(需谨慎使用,避免过度破坏封装)。

4.1、友元函数:类的 “特殊访客”

友元函数是在类中声明的外部函数(非成员函数),可以访问类的所有成员(包括私有成员)。

声明方式:

在类内用friend关键字声明函数:

class 类名 {
    friend 返回类型 函数名(参数);  // 声明友元函数
};

特性:

  • 友元函数不属于类,没有 this 指针。
  • 必须通过参数传递对象,才能访问该对象的成员。
  • 可访问类的 public、protected、private 成员。

示例:友元函数的使用

#include <iostream>
using namespace std;
class Test {
    // 声明show为友元函数,允许其访问Test的所有成员
    friend void show(Test& t);
private:
    int m_a;  // 私有成员
public:
    void set(int a) {
        m_a = a;  // 类内函数可访问私有成员
    }
};
// 友元函数定义(类外)
void show(Test& t) {
    // 友元函数可直接访问Test的私有成员m_a
    cout << "Test的私有成员m_a = " << t.m_a << endl;
}
int main() {
    Test t1;
    t1.set(100);
    show(t1);  // 调用友元函数,输出:Test的私有成员m_a = 100
    return 0;
}

4.2、友元类:类的 “信任伙伴”

若类 B 被声明为类 A 的友元类,则类 B 的所有成员函数都能访问类 A 的所有成员(包括私有成员)。

声明方式:

在类 A 中用friend class声明类 B:

class A {
    friend class B;  // 声明B为A的友元类
};

特性:

  • 单向性:B 是 A 的友元,但 A 不是 B 的友元(除非 B 也声明 A 为友元)。
  • 不传递性:若 B 是 A 的友元,C 是 B 的友元,C 不一定是 A 的友元。
  • 类 B 的所有成员函数都自动成为类 A 的友元函数。

示例:友元类的使用

#include <iostream>
using namespace std;
class A {
    friend class B;  // 声明B为A的友元类
private:
    int m_a;  // A的私有成员
public:
    void set(int a) {
        m_a = a;
    }
};
class B {
private:
    int m_b;
public:
    // B的成员函数可访问A的私有成员
    void print(A& a) {
        cout << "A的私有成员m_a = " << a.m_a << endl;
    }
};
int main() {
    A a1;
    a1.set(200);
    B b1;
    b1.print(a1);  // 输出:A的私有成员m_a = 200
    return 0;
}

友元的注意事项:

  • 友元机制破坏了类的封装性,应尽量少用。
  • 友元关系无法继承(子类不会继承父类的友元关系)。
  • 友元关系是固定的,不能在运行时动态改变。

往期推荐

知识点精讲:深入理解C/C++指针

总被 “算法” 难住?程序员怎样学好算法?

小米C++校招二面:epoll和poll还有select区别,底层方式?

顺时针螺旋移动法 | 彻底弄懂复杂C/C++嵌套声明、const常量声明!!!

C++ 基于原子操作实现高并发跳表结构

为什么很多人劝退学 C++,但大厂核心岗位还是要 C++?

手撕线程池:C++程序员的能力试金石

【大厂标准】Linux C/C++ 后端进阶学习路线

打破认知:Linux管道到底有多快?

C++的三种参数传递机制:从底层原理到实战

顺时针螺旋移动法 | 彻底弄懂复杂C/C++嵌套声明、const常量声明!!!

阿里面试官:千万级订单表新增字段,你会怎么弄?

C++内存模型实例解析

字节跳动2面:为了性能,你会牺牲数据库三范式吗?

字节C++一面:enum和enum class的区别?

Redis分布式锁:C++高并发开发的必修课

C++内存对齐:从实例看结构体大小的玄机


网站公告

今日签到

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