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

【C++】类与对象:深入理解默认成员函数

类与对象:深入理解默认成员函数

  • 引言
  • 1、默认成员函数概述
  • 2、构造函数与析构函数
    • 2.1 默认构造函数
    • 2.2 析构函数
  • 3、拷贝控制成员
    • 3.1 拷贝构造函数
    • 3.2 赋值运算符重载
  • 4、移动语义(C++11)
    • 4.1 移动构造函数
    • 4.2 移动赋值运算符
  • 5、三五法则与最佳实践
    • 5.1 三五法则(Rule of Three/Five)
    • 5.2 显式控制
    • 5.3 建议
  • 6、总结

引言

C语言是面向过程的语言,强调函数和结构化编程,而C++在兼容C语言的基础上,增加了面向对象编程、泛型编程等特性。

  • 典型对比
// C:过程式
typedef struct Stack
{
	int* a;
	int top;
	int capacity;
} Stack;

void STInit(Stack* pst);
void STPush(Stack* pst, STDataType x);
// C++:面向对象
class Stack {
public:
    Stack() {}
    void push(int x){}
private:
    int* a_;
    int size_;
    int capcity_;
};

1、默认成员函数概述

  • 在C++中,当定义一个类时,编译器会自动生成6个默认成员函数(C++11起):
    • 默认构造函数
    • 析构函数
    • 拷贝构造函数
    • 赋值运算符重载
    • 移动构造函数(C++11)
    • 移动赋值运算符(C++11)
  • 这些函数在特定场景下会被隐式调用,理解它们的特性和行为对编写健壮的类至关重要。

2、构造函数与析构函数

2.1 默认构造函数

  • 作用:初始化对象成员。
  • 生成条件:当类中没有显示定义任何构造函数时。
  • 特点
    • 对内置类型不做处理(不进行初始化)。
    • 对自定义类型调用其默认构造函数。
  • 示例
class A {
public:
    int x;  // 随机值
    std::string s; // 调用std::string类的默认构造函数
};

int main() {
    A a;
    return 0;
}

[!WARNING]

当显示创建构造函数,编译器就不会生成默认构造函数。

class A {
public:
    A(int x, std::string s) {
        std::cout << "A" << std::endl;
    }
    int x;
    std::string s;
};

int main() {
    A a;
    return 0;
}

显示创建构造函数

2.2 析构函数

  • 作用:释放对象资源。
  • 生成条件:没有显示定义析构函数。
  • 特点
    • 内置类型不做处理。
    • 自定义类型调用其析构函数。
class A {
public:
    A() {
        std::cout << "A()" << std::endl;
    }
    ~A() {
        std::cout << "~A()" << std::endl;
    }
private:
    int x;
    std::string s;
};

int main() {
    A a; 
    // A()
    // ~A()
    return 0;
}

[!CAUTION]

当类管理资源(动态内存、文件句柄等)时,必须手动定义。

class Stack {
public:
    Stack(int n = 4) {
        a_ = (int*)malloc(sizeof(int) * n);
        top_ = 0;
		capacity_ = n;
    }
    ~Stack() {
        free(a_);
        a_ = nullptr;
        top_ = capacity_ = 0;
    }
private:
    int* a_;
    int capacity_;
    int top_;
};

3、拷贝控制成员

3.1 拷贝构造函数

  • 形式T(const T& val)
  • 特点
    • 内置类型不做处理,直接进行值拷贝。
    • 自定义类型调用其拷贝构造函数。

[!IMPORTANT]

如果不显示定义拷贝构造函数,编译器默认进行值拷贝。

class Stack {
public:
    Stack(int n = 4) {
        a_ = (int*)malloc(sizeof(int) * n);
        top_ = 0;
		capacity_ = n;
    }
    ~Stack() {
        free(a_);
        a_ = nullptr;
        top_ = capacity_ = 0;
    }
private:
    int* a_;
    int capacity_;
    int top_;
};

int main() {
    Stack s1;
    Stack s2(s1);
    return 0;
}
  • 调试可观察到:

浅拷贝

s1对象和s2对象内的数据数组具有相同的地址,表明是同一个。

浅拷贝

3.2 赋值运算符重载

  • 形式T& operator=(const T& val)
  • 示例
class Stack {
public:
    Stack(int n = 4) {
        a_ = (int*)malloc(sizeof(int) * n);
        top_ = 0;
        capacity_ = n;
    }
    Stack(const Stack &s) {
        a_ = (int*)malloc(sizeof(int) * capacity_);
        top_ = s.top_;
        capacity_ = s.capacity_;
        for (int i = 0; i < top_; ++i) {
            a_[i] = s.a_[i];
        }
    }
    Stack& operator=(const Stack &s) { // 赋值运算符重载
        if (this != &s) { // 杜绝自己赋值给自己
            free(a_); // 将原数组释放,防止内存泄漏
            a_ = (int*)malloc(sizeof(int) * capacity_);
            top_ = s.top_;
            capacity_ = s.capacity_;
            for (int i = 0; i < top_; ++i) {
                a_[i] = s.a_[i];
            }
        }
        return *this;
    }
    ~Stack() {
        free(a_);
        a_ = nullptr;
        top_ = capacity_ = 0;
    }
private:
    int* a_;
    int capacity_;
    int top_;
};

4、移动语义(C++11)

4.1 移动构造函数

  • 形式T(T&&)
  • 关键:转移资源所有权而非拷贝。
  • 标记:使用noexcept保证异常安全。
class Vector {
public:
    Vector(int n = 4) {
        data_ = new int[n];
        size_ = 0;
        capacity_ = n;
    }
    Vector(Vector&& v) noexcept
        : data_(v.data_), size_(v.size_), capacity_(v.capacity_) {
        v.data_ = nullptr;
        v.size_ = 0;
        v.capacity_ = 0;
    }
    ~Vector() {
        delete[] data_;
        size_ = capacity_ = 0;
    }
private:
    int* data_;
    int size_;
    int capacity_;
};

4.2 移动赋值运算符

  • 形式T& operator=(T&&)
Vector& operator=(Vector&& v) noexcept {
    if (this != &v) {
        data_ = v.data_;
        size_ = v.size_;
        capacity_ = v.capacity_;
        v.data_ = nullptr;
        v.size_ = 0;
        v.capacity_ = 0;
    }
    return *this;
}
  • 生成规则
    • 没有自定义拷贝控制成员时。
    • 类未声明移动操作。
    • 析构函数未显示定义。

5、三五法则与最佳实践

5.1 三五法则(Rule of Three/Five)

  • 需要自定义析构函数 ⇒ \Rightarrow 必须处理拷贝(三法则)。
  • C++11后扩展为五法则(包含移动操作)。

5.2 显式控制

  • =default:显示要求编译器生成默认版本。
  • =delete:禁用特定成员函数。

5.3 建议

  • 优先使用移动语义。
  • 使用智能指针管理资源。
  • 默认使用=default保持代码简洁。

6、总结

成员函数默认行为自定义场景
默认构造函数内置类型不做处理,自定义类型调用其默认构造函数需要特定初始化逻辑
析构函数内置类型不做处理,自定义类型调用其默认析构函数管理资源释放
拷贝构造函数浅拷贝需要深拷贝或禁止拷贝
赋值运算符重载浅拷贝同拷贝构造函数
移动构造函数转移资源(C++11)优化资源转移
移动赋值运算符转移资源(C++11)同移动构造函数

相关文章:

  • Qt信号和槽
  • uniapp 系统学习,从入门到实战(八)—— Vuex 的使用
  • Oracle删除重复数据保留其中一条
  • C++:volatile、const、mutable关键字
  • SQL-labs less9-12 闯关记录
  • 2继续NTS库学习(读取shapefile)
  • 【前端】WebStorm多功能计时工具:网页版模拟时钟、秒表与倒计时器
  • Microsoft.Office.Interop.Excel 的简单操作
  • LeetCode 面试题 17.19. 消失的两个数字
  • 学习笔记-DeepSeek 开源第五天: 3FS 文件系统和 Smallpond 数据处理框架
  • 特征分解(Eigen decomposition)在深度学习中的应用与理解
  • 小程序接入mqtt并需要启动第三方机构的证书认证配置案例
  • KVM虚拟机磁盘创建探究-2
  • 2022java面试总结,1000道(集合+JVM+并发编程+Spring+Mybatis)的Java高频面试题
  • 数据结构——队列
  • 图形化界面MySQL(MySQL)(超级详细)
  • 系统讨论Qt的并发编程2——介绍一下Qt并发的一些常用的东西
  • windows上执行scp命令
  • 《基于大数据的相州镇新农村商务数据分析与研究》开题报告
  • k8s 中各种发布方式介绍以及对比
  • 移动端网站怎么做seo/销售找客户最好的app
  • 2017主流网站开发语言/公众号运营收费价格表
  • 做产品封面的网站/友链交易平台
  • django网站开发过程/广州百度
  • 织梦后台搭建网站并调用标签建设/百度地图排名怎么优化
  • 吴忠市建设网站/关键词歌词