C++ 类和对象3---(拷贝构造 , 赋值运算符重载 , 取地址运算符重载)

目录

1. 拷贝构造函数

1.1 定义

1.2 语法规则

1.3 浅拷贝 vs 深拷贝

1.4 调用场景

1.5 补充

1.6 总结

2. 赋值运算符重载

2.1 运算符重载

1. 本质

2. 语法形式

3. 核心规则

4. 设计原则

5. 总结

2.2 赋值运算符重载

1. 概念

2. 特点

3. 手动写”赋值运算符重载

4. 编译器默认行为的“适用场景”

5. 总结 

3. 取地址运算符重载

 3.1 普通版本

1. 概念

2. 写法

3. 特点

3.2 const版本

1. 基础语法

2. 本质

3. 总结

3.3 总结

4. 总结


1. 拷贝构造函数

继构造函数和析构函数之后 , 我们来学习第三个类的默认成员函数——拷贝构造函数。

1.1 定义

  • 拷贝构造函数是C++中一种特殊的构造函数 , 也是 构造函数的一种特殊重载形式 , 专门用于 用已存在的对象初始化新对象(即 “拷贝对象” 行为) ,  它在对象创建时被调用 , 是对象“复制”行为的核心 , 理解它对掌握C++对象生命周期和内存管理至关重要。
  • 简单来说:拷贝构造函数的核心就是“复制已有对象的内容 , 来创建一个新的同类对象”。

1.2 语法规则

拷贝构造函数的语法格式有严格规定 , 核心是参数必须为当前类的常量引用 , 具体格式如下:

class 类名 
{
public:
    // 拷贝构造函数(参数是当前类的 const 引用)
    类名(const 类名& 引用变量名) 
    {
        // 拷贝逻辑:将引用变量名的成员复制到当前对象
        成员1 = 引用变量名.成员1;
        成员2 = 引用变量名.成员2;
        // ... 其他成员
    }
};

关键要素解析:

  • 1. 函数名 : 必须与类名完全相同(和普通构造函数一致)。
  • 2. 参数:只能有一个参数 , 且必须是  const 类名&  类型(当前类的常量引用)。
  • - 加 const : 防止在拷贝过程中修改原对象(保证安全性)。
  • - 用引用(&) : 避免参数传递时触发拷贝构造函数的递归调用(如果用值传递,会无限递归)。
  • 3. 返回值:构造函数没有返回值(连  void  都不写)。

示例 : 为 Date 类定义拷贝构造函数
假设 Date 类有  _year, _month, _day  三个成员,拷贝构造函数写法如下:
 

class Date 
{
private:
    int _year;
    int _month;
    int _day;
public:
    //普通构造函数(用于初始化)
    Date(int year, int month, int day)
    {
	_year = year;
	_month = month;
	_day = day;
    }
    // 拷贝构造函数(用已有 Date 对象初始化新对象)
    Date(const Date& other) 
    {  // 参数是const Date&
       // 复制other的成员到当前对象
        _year = other._year;    // other是被拷贝的对象
        _month = other._month;
        _day = other._day;
    }
};

调用示例:

Date d1(2025, 4, 25);  // 调用普通构造函数
Date d2(d1);           // 调用拷贝构造函数,用 d1 初始化 d2
Date d3 = d1;          // 同样调用拷贝构造函数(初始化场景的 = 号)
  • 为什么参数必须是引用?
  • 如果参数是值传递(类名 other) , 会触发“用实参初始化形参”的过程 , 而这又会调用拷贝构造函数 , 导致无限递归(死循环)。因此 , 参数必须是引用(避免拷贝) , 加 const 是为了防止在拷贝过程中修改原对象。

编译器的默认行为:
 
若类中 没有显式定义拷贝构造函数 , 编译器会 自动生成一个默认拷贝构造函数 , 行为规则:

  • 对 内置类型成员(int , double , 指针等) : 按字节逐字节拷贝(即 “浅拷贝” )。
  • 对 自定义类型成员(如Stack , string  等其他类的对象) : 调用该成员的拷贝构造函数完成拷贝。
     

1.3 浅拷贝 vs 深拷贝

这是拷贝构造函数最核心的 “坑点” , 也是必须手动实现拷贝构造的场景!
 
(1) 浅拷贝(编译器默认生成的拷贝行为为)

  • 特点:只拷贝成员变量的 “值” , 不拷贝指针/引用指向的 “资源”。
  • 问题:若类中包含 指针成员(指向堆内存 ) , 浅拷贝会导致 多个对象共享同一块堆内存。当其中一个对象销毁(调用析构函数释放堆内存 ) , 其他对象的指针就会变成 “野指针” , 再次访问或释放会崩溃。

示例(问题代码):

class Stack 
{
public:
    // 普通构造函数:动态分配堆内存
    Stack(int n = 4) 
    {
        _a = (int*)malloc(sizeof(int) * n);
        _top = 0;
        _capacity = n;
    }

    // 未显式定义拷贝构造函数,编译器生成默认浅拷贝
    // ~Stack()
    { free(_a); } // 析构函数释放堆内存

private:
    int* _a;       // 指向堆内存的指针
    int _top;
    int _capacity;
};

int main() 
{
    Stack st1;         // 构造:_a 指向堆内存
    Stack st2(st1);   // 浅拷贝:st2._a = st1._a(共享同一块堆内存)
    
    return 0; 
    // 销毁时:st2 析构释放 _a → st1 析构再释放 _a → 重复释放堆内存,程序崩溃!
}

(2) 深拷贝(必须手动实现的拷贝行为)

  • 特点 : 不仅拷贝成员变量的值 , 还会 重新分配资源(如堆内存) , 让新对象拥有独立的资源。
  • 适用场景 : 类中包含指针 , 引用等 “间接资源” 时 , 必须手动实现深拷贝 , 否则会引发崩溃。

修复上述 Stack 类的深拷贝示例:

class Stack 
{
public:
    // 普通构造函数:分配堆内存
    Stack(int n = 4) 
    {
        _a = (int*)malloc(sizeof(int) * n);
        _top = 0;
        _capacity = n;
    }
    // 深拷贝构造函数:手动分配新的堆内存
    Stack(const Stack& other) 
    {
        // 1. 分配新的堆内存(大小与 other 相同)
        _a = (int*)malloc(sizeof(int) * other._capacity);
        // 2. 拷贝数据(逐元素拷贝)
        memcpy(_a, other._a, sizeof(int) * other._top);
        // 3. 拷贝其他成员
        _top = other._top;
        _capacity = other._capacity;
    }
    // 析构函数:释放堆内存
    ~Stack() 
    {
        free(_a);
        _a = nullptr;
    }

private:
    int* _a;       
    int _top;
    int _capacity;
};

此时 st2 = st1 会调用深拷贝构造函数 , st2._a  指向新的堆内存 , st1  和  st2  独立 , 析构时不会重复释放内存。

总结:浅拷贝与深拷贝的对应关系

  • 浅拷贝是编译器默认生成的拷贝构造函数的行为 , 适用于没有动态内存成员的类(仅含基本类型);
  • 深拷贝是必须手动实现的拷贝构造函数的行为 , 适用于包含动态内存成员的类(如指针指向 new 分配的内存)。

二者是相对的:当默认的浅拷贝无法满足需求(会导致内存问题)时 , 就需要手动实现深拷贝来替代它。

1.4 调用场景

拷贝构造函数的调用场景不仅包括显式用括号初始化 , 还包括用等号( = )进行的初始化操作( 注意 : 这里的等号是“初始化”而非“赋值” , 两者本质不同)。

具体来说 , 拷贝构造函数的常见调用场景:

1. 显式括号初始化
直接在声明时用括号传入已有对象 , 例如:

Date d1(2025, 4, 25);  
Date d2(d1);  // 调用拷贝构造函数,用 d1 初始化 d2

2. 等号初始化(拷贝初始化)
用等号将已有对象赋值给新对象(注意 : 这是初始化 , 不是赋值! 新对象正在创建) , 例如:
 

Date d1(2025, 4, 25);  
Date d2 = d1;  // 调用拷贝构造函数,等价于 Date d2(d1);

1.5 补充

在 C++ 中 , 编译器会默认生成拷贝构造函数 , 但这种默认生成存在特定的条件和限制 , 具体如下:

1. 默认生成的前提 : 未手动定义拷贝构造函数


 编译器生成默认拷贝构造函数的核心条件是:用户没有手动定义任何拷贝构造函数。

  • 如果类中没有显式定义拷贝构造函数 , 编译器会自动生成一个默认版本。
  • 如果用户手动定义了任何形式的拷贝构造函数(即使参数不同 , 只要符合拷贝构造函数的特征) , 编译器就不会再生成默认版本。

2. 特殊情况 : 默认生成被抑制的场景


即使未手动定义拷贝构造函数 , 在某些情况下 , 编译器也不会生成默认拷贝构造函数 , 此时类将没有拷贝构造函数(尝试使用时会编译报错)。这些场景包括:

  • 类中存在“无法拷贝”的成员:
  • 若类的成员变量是“不可拷贝”类型(如带  delete  标记的拷贝构造函数的类型、某些无法复制的资源类型),编译器无法为类生成默认拷贝构造函数。
  • 类的基类无法拷贝:
  • 若类的基类没有可访问的拷贝构造函数(如基类的拷贝构造函数是私有的,或被删除),编译器也无法为派生类生成默认拷贝构造函数。

3. 默认拷贝构造函数的行为


编译器生成的默认拷贝构造函数,本质是浅拷贝:

  • 对于基本类型成员(int , double 等) , 直接复制值。
  • 对于类类型成员 , 调用该成员自身的拷贝构造函数。
  • 对于指针/引用成员 , 仅复制指针/引用本身(即地址) , 不复制指向的内容。

总结:

默认拷贝构造函数的生成条件可简化为:

  • 当且仅当用户未手动定义任何拷贝构造函数 , 且类的所有成员和基类都允许拷贝时 , 编译器才会自动生成默认拷贝构造函数 , 执行浅拷贝操作。
  • 若不满足上述条件(如手动定义了拷贝构造函数 , 或存在不可拷贝的成员) , 则默认拷贝构造函数不会生成。

1.6 总结

拷贝构造函数是C++中用于用已存在的同类对象初始化新对象的特殊构造函数 , 核心内容可总结为:

  • 1. 定义与语法:
  • 形式为  类名(const 类名& 已有对象) , 参数必须是同类对象的常量引用(避免递归调用) , 例如  Date(const Date& other) 。
  • 2. 调用场景:
  • 当用已有对象创建新对象时触发 , 比如:
  • 直接初始化 : Date d2(d1); 
  • 赋值形式的初始化 : Date d3 = d1; (注意:这是初始化 , 不是赋值操作)。
  • 3. 默认与自定义:
  • 若未手动定义 , 编译器会生成默认拷贝构造函数 , 执行浅拷贝(复制基本类型成员的值 , 指针仅复制地址)。
  • 当类包含动态内存成员(如指针指向 new 分配的内存)时 , 浅拷贝会导致内存冲突(同一块内存被多次释放) , 需手动实现深拷贝(为新对象重新分配内存并复制内容)。
  • 4. 核心作用:
  • 确保新对象与原对象的初始状态一致 , 同时在涉及动态资源时 , 避免因共享资源导致的内存问题 , 保证对象独立性。

2. 赋值运算符重载

2.1 运算符重载

运算符重载是一个宽泛的概念,而赋值运算符重载是运算符重载中的一种具体情况,二者是包含关系。

1. 本质

“让自定义类型(如  Date , Stack  类)能像内置类型( int , double  )一样用运算符”。
比如:

  • 内置类型可以 int a=1; a=a+2; 
  • 自定义类型想 Date d; d = d + 10;(让日期+天数) , 就需要运算符重载 , 告诉编译器 “ + 对 Date 类怎么算”。

2. 语法形式

运算符重载是特殊的函数 , 格式:

返回值类型 operator运算符(参数列表) 
{
    // 自定义运算逻辑
}
  • operator 是关键字,后面跟要重载的运算符(如  + 、 == 、 ++  )。
  • 本质是用函数实现运算符功能,调用时编译器自动替换。

3. 核心规则

1. 参数个数与运算对象数量一致

  1. 运算符作用的对象数量,决定参数个数:
  2. 一元运算符(如  ++ 、 !  ):1个参数(只有1个运算对象)。
  3. 二元运算符(如  + 、 ==  ):2个参数(左侧对象+右侧对象)。
  4. 但如果是成员函数 , this  指针会“默认占一个参数”,因此:
  5. 成员函数版的二元运算符 , 参数比运算对象少1个(因为  this  代表左侧对象 )。

例子(Date类重载 + 实现日期 + 天数 ):
 

class Date 
{
public:
    // 成员函数版:this 代表左侧对象(如 d1),参数代表右侧对象(如 10)
    Date operator+(int days) 
    { 
        Date temp = *this;
        // ... 日期+天数逻辑 ...
        return temp;
    }
};
调用:d1 = d1 + 10; 
等价于 d1.operator+(10),this 是 d1,参数是 10

2. 不能重载的运算符
图片里提到5个不能重载的运算符,必须死记:

.*  ::  sizeof  ?:  . 

这些是 C++ 语法“基石”, 重载会破坏语言逻辑(比如 sizeof 是编译期关键字 , 无法改变行为

3. 至少有一个类类型参数
运算符重载不能修改内置类型的含义。例如:
 

// 非法!试图改变 int 的 + 含义,编译器直接报错
int operator+(int x, int y) 
{ 
    return x - y; 
}

必须至少有一个参数是自定义类型(如Date Stack) , 保证“只扩展自定义类型的运算符行为”。

4. 优先级与结合性不变
运算符重载后,优先级和结合性和内置类型一致。
比如:

  • 内置类型 a + b * c 先算 b*c (乘法优先级高)。
  • 自定义类型 d + s * t 也会先算 s*t (即使 + 和 * 都重载了)。

5. 前置++与后置++的区分

  1. 前置 ++(如 ++d )和后置++(如 d++ )的重载函数名都是 operator++,无法区分。
  2. 因此C++规定 : 后置++重载时 , 必须额外加一个int形参(仅用于语法区分 , 实际不用)。

例子( Date  类区分前置/后置++ ):
 

class Date 
{
public:
    // 前置++:无额外参数
    Date& operator++() 
    { 
        // 日期 +1 逻辑 ...
        return *this;
    }

    // 后置++:必须加 int 形参(约定俗称写 0 或不用)
    Date operator++(int) 
    { 
        Date temp = *this;
        // 日期 +1 逻辑 ...
        return temp;
    }
};

// 调用区分:
Date d;
++d; // 调用前置++(无参数版)
d++; // 调用后置++(int参数版,实际传 0)

6. << 和 >> 的特殊处理

  1. 如果重载  <<(如 cout << d )或 >> ,不能写成成员函数:
  2. 成员函数版会让 this 占第一个参数,导致调用变成 d << cout(不符合习惯 )。
  3. 正确做法:写成全局函数,把流对象(ostream / istream)放第一个参数。 

例子( Date  类重载  <<  支持  cout << d  ):
 

class Date { /* ... */ };

// 全局函数版:ostream 放第一个参数
ostream& operator<<(ostream& cout, const Date& d) 
{ 
    cout << d._year << "-" << d._month << "-" << d._day;
    return cout;
}

// 调用:cout << d; 
// 等价于 operator<<(cout, d)

4. 设计原则

  1. 只重载有意义的运算符:比如  Date  类重载 - (日期差)有意义,但重载 * (日期乘天数)无意义,别瞎用。
  2. 保持语义直观:让运算符行为符合内置类型习惯(比如 + 就做“相加”,别搞成减法逻辑)。

5. 总结

运算符重载是 C++ 给自定义类型“开的后门”,让类对象能像 int 一样用运算符。核心要记住:

  • 1. 成员函数 vs 全局函数的参数区别(this 指针的影响 )。
  • 2. 不能重载的5个运算符( .*  ::  sizeof  ?:  .  )。
  • 3. 前置/后置++的区分(额外 int 参数 )。
  • 4.  << / >>  必须用全局函数。

 掌握这些,就能看懂/写自定义类型的运算符逻辑(比如日期类、栈类的运算符重载 ),避开常见坑点~

2.2 赋值运算符重载

1. 概念

赋值运算符重载是C++中为让自定义类型(类/结构体)对象间赋值能按开发者需求执行,对 operator=  运算符进行重新定义的机制 ,让我们可自定义对象赋值时的具体逻辑,本质是特殊成员函数,用于控制两个已有对象间拷贝赋值的行为 。

区分“赋值”与“拷贝构造”
赋值运算符重载 , 解决的是两个已存在对象间的拷贝赋值。比如:
 

class Person { /* ... */ };
Person a;    // a 已存在
Person b;    // b 已存在
b = a;       // 这就是赋值,调用赋值运算符重载

而拷贝构造,是用已存在对象初始化新对象(对象创建阶段)
 

Person a;
Person b(a); // 调用拷贝构造,b 是新创建的

核心区别:对象是否已存在 —— 赋值是“已有对象间赋值”,拷贝构造是“用已有对象创建新对象”。

2. 特点

1. “必须作为成员函数重载” + “参数建议用 const 引用”

  • 必须是成员函数:C++ 语法规定 , 赋值运算符 operator=  必须重载为类的成员函数。因为它需要访问类的私有成员 , 且逻辑上与类的“对象间赋值”强相关。
  • 参数用 const 类类型& :
  • 如果写成值传递(比如 Person operator=(Person other) ) , 传参时会额外拷贝一个 other 对象 , 既浪费性能 , 又可能引发递归问题(如果类里有复杂成员 , 拷贝时又会调用赋值)。
  • 用 const 引用 (const Person& other) , 直接引用已有的对象 , 避免不必要的拷贝 , 效率更高。const 则保证不会修改传入的 other 对象 , 逻辑更安全。

2. “返回值建议用类类型引用”
 
赋值运算符重载的返回值 , 通常设计为  类类型& (比如 Person& operator=(const Person& other) ) , 作用有两个:

  • 支持连续赋值:
  • 比如  a = b = c;  ,实际是  b = c  先执行,返回  b  的引用,再执行  a = (b = c)  。如果返回值是值类型(比如  Person operator=(...) ), b = c  会返回一个临时拷贝,连续赋值就会出问题。
  • 提高效率:
  • 返回引用,避免了“拷贝返回值”的开销。直接返回对象本身的引用,性能更优。

3. “编译器的默认行为”
 
如果类中没有显式写 operator= , 编译器会自动生成一个默认赋值运算符重载,行为规则:

  • 对内置类型成员( int 、 double  等):直接“浅拷贝”(字节级逐字节拷贝)。
  • 对自定义类型成员(比如类里有 Stack 类型的成员):调用该成员自身的赋值运算符重载。

这逻辑和“默认拷贝构造函数”类似,但注意:默认行为是“浅拷贝” ,如果类里有指针、动态资源(比如  new  出来的内存),就会出问题!

3. 手动写”赋值运算符重载

编译器的默认赋值,只适合 “简单类”(成员都是内置类型、或成员的默认赋值行为符合需求)。遇到以下场景,必须手动实现:

场景 1:类里有动态资源(指针、堆内存等)
比如一个简单的  Stack  类,用指针管理堆内存:

class Stack 
{
public:
    int* data;  // 指向堆内存
    int size;

    Stack(int n) : size(n) 
    {
        data = new int[size]; // 动态分配内存
    }

    ~Stack() {
        delete[] data; // 析构时释放内存
    }
};

如果用编译器默认的赋值:

Stack s1(10);
Stack s2(5);
s2 = s1; // 编译器默认赋值:浅拷贝!

问题: s1.data  和  s2.data  会指向同一块堆内存。后续:

  • 如果  s1  析构(释放  data ), s2.data  就变成野指针。
  • 如果  s2  再析构,会导致重复释放内存(程序崩溃)。

解决方法:手动实现赋值运算符重载,做“深拷贝”:

Stack& operator=(const Stack& other) 
{
    // 1. 释放当前对象的旧资源(避免内存泄漏)
    delete[] data;

    // 2. 深拷贝:分配新内存,拷贝数据
    size = other.size;
    data = new int[size];
    for (int i = 0; i < size; ++i) 
    {
        data[i] = other.data[i];
    }

    // 3. 返回自身引用,支持连续赋值
    return *this;
}

场景 2:类里有“自定义类型成员”,但依赖其赋值逻辑
比如类  MyQueue  里有  Stack  类型的成员:
 

class MyQueue 
{
public:
    Stack s; // 自定义类型成员
    // ...
};

如果  Stack  已经手动实现了正确的赋值运算符重载,那么  MyQueue  即使不写自己的  operator=  ,编译器默认的赋值也能正确工作 —— 因为默认赋值会调用  Stack  的赋值运算符重载,帮你完成  s  的深拷贝。

4. 编译器默认行为的“适用场景”

如果类的成员都是 “简单类型”(没有动态资源、没有复杂自定义成员),编译器默认的赋值就足够用。比如:
 

class Date 
{
public:
    int year, month, day;
    // 没有动态资源,成员都是 int
};

Date d1{2025, 8, 11};
Date d2;
d2 = d1; // 编译器默认赋值,直接拷贝 year/month/day,完全没问题

“显式析构 + 资源释放” , 必须写赋值

一个简单判断规则:如果类显式写了析构函数(释放资源),十有八九需要手动写赋值运算符重载。
因为:

  • 析构函数释放资源 → 类里有动态资源(堆内存、文件句柄等)。
  • 有动态资源 → 编译器默认的“浅拷贝赋值”会出问题(同一块资源被多个对象释放)。

比如前面的  Stack  类,写了析构函数释放  data  ,就必须手动实现赋值运算符重载,做深拷贝。

5. 总结 

  • 1. 核心作用:实现“已有对象间的赋值”,区别于“拷贝构造(创建新对象)”。
  • 2. 语法约束:必须作为成员函数,参数建议  const 类类型&  ,返回值建议  类类型&  。
  • 3. 编译器默认行为:浅拷贝(内置类型逐字节拷贝,自定义类型调用其赋值)。
  • 4. 必须手动实现的场景:类里有动态资源(指针、堆内存等),或显式写了析构函数释放资源时,必须手动写赋值运算符重载(做深拷贝)。

 理解这些,就能避开“浅拷贝导致的内存泄漏、重复释放”等大坑,写出更健壮的 C++ 类

3. 取地址运算符重载

取地址运算符重载按对象类型分为两种版本:

  • 1. 普通版本:用于普通对象(非 const 对象),它是非 const 成员函数;
  • 2. const 版本:用于const 对象,它是const 成员函数(函数后带  const )。

 3.1 普通版本

1. 概念

当你对一个普通对象(比如 A obj;)用 &obj 取地址时,就会调用这个普通版重载。 

2. 写法

class A 
{
public:
    // 普通版取地址重载(非 const 成员函数)
    A* operator&() 
    { 
        return this;  // 直接返回当前对象的地址(this指针)
    }
};
  • 函数名是  operator& ,没有参数。
  • 关键:不带 const 修饰(参数列表后面没有  const ) , 所以是非 const 成员函数。

3. 特点

普通对象本身可以被修改 , 所以这个重载函数内部也能修改对象(虽然实际中几乎不会这么做,通常只是返回地址)。
举例:

A obj;
A* p = &obj;  //这里就会调用上面的普通版重载,p指向obj

简单说:普通版取地址重载,就是给“能改的对象”用的,函数本身也允许改对象(虽然一般不用),返回的是普通指针(可以通过指针改对象)。

3.2 const版本

1. 基础语法

在 C++ 里,在成员函数的参数列表后加 const , 就定义了一个 const 成员函数。语法长这样:

class Date 
{
public:
    void Print() const  //参数列表后加 const
    {   
        // ... 
    }
};

注意: const  是函数名的一部分。如果一个函数声明时带 const ,定义时也必须带 , 否则编译器会认为是两个不同的函数。

2. 本质

const  实际修饰的是成员函数里“隐藏的  this  指针” 。
 
回忆成员函数里的 this 指针 , C++ 中 , 每个非静态成员函数里 , 都隐藏着一个 this 指针 , 指向当前调用函数的对象。比如:
 

class Date 
{
public:
    void Print() 
    {
        // 这里的 this 是 Date* const 类型
        // 表示:this 是一个指针,指向 Date 对象,且指针本身不能被修改(const 修饰指针本身)
        cout << this->_year << endl; 
    }
private:
    int _year;
};

此时 , this 的类型是 Date* const (指针本身不能变 , 但可以通过指针修改对象内容)。
 
const 成员函数对  this  的修改
当成员函数被 const 修饰后 , this 的类型会变成 const Date* const :

  • 第一个 const  :修饰指针指向的内容 → 不能通过 this 修改对象的成员变量(只读)。
  • 第二个  const  :修饰指针本身 → this 指针的值(地址)不能被修改(和之前一样)。
// 普通成员函数的 this:Date* const this
void Print() { ... }  

// const 成员函数的 this:const Date* const this
void Print() const { ... }  

这直接导致在 const 成员函数里 , 任何修改成员变量的操作都会报错(除非成员被  mutable 修饰)。

3. 总结

const  成员函数的核心是通过修改  this  指针的类型,强制让函数“只读”对象状态。它的价值在于:

  • 1. 保护  const  对象:让  const  对象只能调用“不会修改自己”的函数。
  • 2. 明确语义:通过  const  修饰,清晰区分“只读操作”和“可写操作”。
  • 3. 增强代码健壮性:防止在逻辑上“只读”的函数里,不小心修改成员变量。

3.3 总结

取地址运算符重载(包括普通版和 const 版), 正是 C++ 类中6个默认成员函数的最后两个。
6个默认成员函数完整列表:

  • 1. 构造函数
  • 2. 析构函数
  • 3. 拷贝构造函数
  • 4. 赋值运算符重载
  • 5. 普通版取地址运算符重载
  • 6. const版取地址运算符重载

为什么这两个也算默认成员函数?
和前4个一样,如果类中不手动实现,编译器会自动生成默认版本:

  • 默认普通版:返回当前对象的地址( return this; )。
  • 默认 const 版:返默认 const 版:返回当前 const 对象的地址( return this; ),且函数是 const 成员函数。

 这俩默认版本非常“低调”,因为平时直接用 &对象 取地址时,编译器生成的默认版本就够用了,很少需要手动重载。只有特殊场景(比如想禁止取地址,就手动实现并设为私有)才会自己写。
所以:它们确实是 6 个默认成员函数的最后两位,编译器会自动生成,功能就是返回对象的地址

4. 总结

本文系统讲解了C++中类的6个默认成员函数,重点剖析了拷贝构造函数和赋值运算符重载。拷贝构造函数用于用已存在对象初始化新对象,需注意参数必须是常量引用以避免递归调用。编译器默认生成浅拷贝版本,当类包含动态内存时需手动实现深拷贝。赋值运算符重载用于已有对象间的赋值操作,参数建议使用const引用以提升效率。当类包含动态资源时,必须手动重载以避免内存问题。此外,文章还介绍了取地址运算符重载的普通版和const版本,以及const成员函数的本质。6个默认成员函数共同构成了C++对象生命周期的自动管理体系,为对象的创建、销毁、复制和访问提供了基础框架。理解这些概念对编写健壮、高效的C++代码至关重要。

到目前为止,我们已经把类和对象中6个默认的成员函数已经全部学习完了。

C++ 类的 6 个默认成员函数,共同构成了对象生命周期的“自动管理体系”,其核心价值在于:

  • 构造与析构:前者为对象“接生”,确保诞生即初始化;后者为对象“送终”,保证消亡时资源清零,从创建到销毁守护对象的完整性。
  • 拷贝与赋值:让对象的“复制”与“更新”行为有章可循,既支持简单对象的直接复用,也为复杂对象(含动态资源)的安全拷贝提供基础框架。
  • 取地址重载:默默支撑对象地址的获取逻辑,既满足普通场景下的地址访问需求,也通过 const 版本维护了常量对象的只读性,让地址操作与对象属性自洽。

这 6 个函数看似基础,却从根本上解决了“对象如何安全创建、销毁、复制和被访问”的核心问题,是 C++ 封装特性得以落地的底层支柱,让类与对象的使用既灵活又可靠。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值