类和对象—>析构+拷贝+运算符重载

一、explicit 关键字:禁止隐式转换

在 C++ 中,单参数的构造函数允许隐式类型转换,这可能导致意外的行为。使用 explicit 关键字可以禁止这种隐式转换。

cpp 复制代码
class P {
private:
    int x, y;
public:
    P(int x, int y) :x(x),y(y){}
    explicit P(int x):x(x),y(0) {}
};

如果不加 explicit,以下代码可以编译通过:

cpp 复制代码
P p1 = 20;  // 隐式转换成 P(20),y 被默认初始化为 0

但加上 explicit 后,必须显式调用构造函数:

cpp 复制代码
P p1 = 20;   // 编译错误!禁止隐式转换
P p1(20);    // 正确,显式调用
P p1{20};    // 正确,列表初始化

最佳实践 :单参数构造函数一律加 explicit,除非你真的需要隐式转换。


二、运算符重载

2.1 类内重载成员函数方式

+= 运算符用于将另一个对象的坐标加到当前对象上。this 指针指向当前调用函数的对象。

cpp 复制代码
void operator+=(const P& p) {
    x += p.x;
    y += p.y;
}

> 运算符用于比较两个点的大小,只有当 x 和 y 都更大时才返回 true。

cs 复制代码
bool operator>(P& p) {
    return x > p.x && y > p.y;
}

2.2 类外重载普通函数方式

流运算符 << 通常需要在类外重载,因为左侧操作数是 ostream 对象而非本类对象。为了匹配 cout 的类型,需要放在 std 命名空间中。

cpp 复制代码
namespace std {
    ostream& operator<<(ostream& cout, P& p) {
        cout << "(" << p.getX() << "," << p.getY() << ") ";
        return cout;
    }
}

返回 ostream& 引用是为了支持链式调用,例如 cout << p1 << p2


三、不能重载的运算符

C++ 中有 5 个运算符不能被重载:

运算符 名称
. 成员访问
.* 成员指针访问
:: 作用域解析
sizeof 求大小
?: 三目条件

尝试重载这些运算符会导致编译错误。


四、输入输出运算符重载

可以为自定义类型重载 <<>> 运算符,使其可以直接使用 coutcin

cpp 复制代码
struct Stu {
    int sid;
    string name;
};

namespace std {
   ostream& operator<<(ostream& cout, Stu& s) {
       cout << s.name << "," << s.sid ;
       return cout;
   }

   istream& operator>>(istream& cin, Stu& s) {
       cout << "请输入学号与姓名:";
       cin >> s.sid >> s.name;
       return cin;
   }
}

使用示例:

cpp 复制代码
Stu s1{ 1001, "Lucy" };
cout << s1 << endl;  // 输出: Lucy,1001

Stu s2{0};
cin >> s2;           // 提示输入并读取
cout << s2 << endl;

五、完整运算符重载示例

以下是一个更完整的运算符重载示例,展示了多种运算符的实现方式。

cpp 复制代码
class Num {
private:
    int v;
public:
    explicit Num(int v):v(v){}
    int getV() { return v; }

    void operator+=(int&& v) {
        this->v += v;
    }
    void operator+=(const Num& v) {
        this->v += v.v;
    }

    void operator*=(int&& v) {
        this->v *= v;
    }

    void operator++() {
        ++v;
    }

    void operator++(int) {
        v++;
    }

    bool operator>(int&& v) {
        return this->v > v;
    }
    bool operator>(const Num& v) {
        return this->v > v.v;
    }
};

前置++与后置++的区别

前置 ++ 运算符没有参数,返回引用,效率较高。后置 ++ 运算符带一个 int 类型的占位参数,用于区分前置和后置版本。

表格

特性 前置++ 后置++
声明 operator++() operator++(int)
参数 int 占位参数
效率 高(直接修改) 低(需要保存旧值)
使用建议 优先使用 需要旧值时才用

六、深拷贝与浅拷贝

6.1 问题引入

当类包含指针成员时,默认的拷贝构造函数会进行浅拷贝,即只复制指针的值,而不是指针指向的内容。这会导致多个对象共享同一块内存,造成双重释放或数据混乱。

cpp 复制代码
class Person {
private:
    int pid;
    char* name;
public:
    Person(int pid, const char* name) :pid(pid) {
        this->name = new char[32] {0};
        memcpy(this->name, name, strlen(name));
    }
};

如果使用默认的拷贝构造函数:

cpp 复制代码
Person p1(1001, "disen");
Person p2 = p1;  // 浅拷贝,p1 和 p2 的 name 指向同一块内存

浅拷贝的问题在于:

  • 修改 p2 的 name 会影响 p1

  • 析构时会 delete[] name 两次,导致程序崩溃

6.2 深拷贝解决方案

自定义拷贝构造函数,为指针成员申请新的内存空间,并复制内容。

cpp 复制代码
Person(const Person& other) {
    pid = other.pid;
    name = new char[32] {0};
    memcpy(name, other.name, strlen(other.name));
}

深拷贝确保每个对象拥有独立的内存空间,互不干扰。

特性 浅拷贝 深拷贝
行为 复制指针值 申请新空间,复制内容
适用场景 无指针成员 有指针成员(堆内存)
问题 双重释放、数据共享 内存开销稍大
实现 编译器默认提供 必须自定义拷贝构造

七、构造函数与析构函数

7.1 对象生命周期

对象的生命周期包括三个阶段:

  1. 编译器创建对象空间

  2. 调用构造函数初始化(编译器默认提供无参构造)

  3. 对象离开有效作用域时,调用析构函数释放资源(编译器默认也提供)

7.2 析构函数的定义

析构函数的特点:

  • 无返回类型

  • 函数名同类名,前面加 ~

  • 无参数表

cpp 复制代码
class Point {
private:
    int x, y;
public:
    Point(int x, int y) :x(x), y(y) { 
        cout << "Point(int x, int y):" <<this << endl;
    }
    
    ~Point() {
        cout << " ~Point():" << this << endl;
    }
};

7.3 构造与析构的区别

特性 构造函数 析构函数
功能 初始化对象 回收资源
重载 可重载 只有一个,且无参
初始化列表 无析构列表

7.4 生命周期演示

cpp 复制代码
int main() {
    Point p1(10, 20);
    {
        Point p2(1, 2);
        p2.draw();
    }
    cout << "-------p1-------" << endl;
    p1.draw();
    cout << "----end p1---" << endl;
    return 0;
}

执行流程:

  1. 构造 p1

  2. 构造 p2

  3. p2 离开作用域,析构 p2

  4. 输出分隔线

  5. 使用 p1

  6. main 结束,析构 p1

栈对象遵循"先构造的后析构"原则。


八、完整代码解析

cpp 复制代码
#include <iostream>
using namespace std;

class P {
private:
    int x, y;
public:
    P(int x, int y) :x(x),y(y){}
    explicit P(int x):x(x),y(0) {}

    void operator+=(const P& p) {
        x += p.x;
        y += p.y;
    }

    bool operator>(P& p) {
        return x > p.x && y > p.y;
    }

    int getX() { return x; }
    int getY() { return y; }
};

namespace std {
    ostream& operator<<(ostream& cout, P& p) {
        cout << "(" << p.getX() << "," << p.getY() << ") ";
        return cout;
    }
}

int main() {
    P p1( 20 );
    P p2(1, 2);
    P p3(4, 5);
    cout << p1 << ", " << p2 << "," << p3 << endl;

    p1 += p2;
    cout << (p3 > p1) << endl;

    return 0;
}

代码执行结果:

  • p1 初始化为 (20, 0),因为使用了单参数 explicit 构造函数

  • p2 为 (1, 2),p3 为 (4, 5)

  • p1 += p2 后,p1 变为 (21, 2)

  • p3 > p1 比较 (4>21 && 5>2),结果为 false,输出 0

相关推荐
柒.梧.2 小时前
深入理解AQS:Java并发编程的核心基石
java·开发语言
清风徐来QCQ2 小时前
js中的常用api
开发语言·javascript·ecmascript
人道领域2 小时前
LeetCode【刷题日记】:数组篇(1)含原理讲解
算法·leetcode·职场和发展
leo__5202 小时前
基于Matlab和CPLEX的2变量机组组合调度程序
开发语言·matlab
csbysj20202 小时前
CSS 伪类详解
开发语言
RTC老炮2 小时前
webrtc弱网-BBRv2算法原理
网络·算法·webrtc
Reisentyan2 小时前
[backend]GoLang Learn Data Day 2
开发语言·后端·golang
RTC老炮2 小时前
webrtc弱网-BBRv1算法原理
网络·算法·webrtc
MobotStone3 小时前
我的 AI 代码清理方法论:从原型到生产,只需 5 步
算法·程序员·架构