前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【C++修炼之路】C++类类和对象进一步探索,六个幕后英雄

【C++修炼之路】C++类类和对象进一步探索,六个幕后英雄

作者头像
f狐o狸x
发布2025-03-04 09:02:24
发布2025-03-04 09:02:24
5600
代码可运行
举报
运行总次数:0
代码可运行

在上一篇《C++类与对象入门:从封装到this指针的初探》中,我们学习了如何定义类、创建对象,并通过封装保护数据。然而,类的真正力量远不止于此——

当你在代码中写下MyClass obj;时,编译器默默为你生成了6个关键函数,它们掌控着对象的诞生、复制、移动与消亡。

一、类的默认6个成员函数

当你写了一个类,但是里面什么都没有,简称空类。

代码语言:javascript
代码运行次数:0
复制
class Date
{

};

空类里面就真的什么都没有吗?

并不是这样的,其实编译器已经帮你默认生成了六个函数

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

在C++中,构造函数是类的特殊成员函数,负责在对象创建时进行初始化。它是对象的“出生证明”,确保对象在诞生时处于有效状态。如果没有构造函数,对象可能包含未初始化的数据,导致程序行为不可预测。

2.1 构造函数的概念

构造函数的名称必须与类名相同,且没有返回值(包括void)。例如:

代码语言:javascript
代码运行次数:0
复制
class MyClass {
public:
    MyClass() {  // 默认构造函数
        std::cout << "默认构造函数被调用!" << std::endl;
    }
};

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

2.2 构造函数的特征

  1. 无参数或所有参数有默认值。
  2. 如果未定义任何构造函数,编译器会自动生成一个默认构造函数。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
代码语言:javascript
代码运行次数:0
复制
 class Date
{
 public:
     // 1.无参构造函数
     Date()
     {}
 
     // 2.带参构造函数
     Date(int year, int month, int day)
     {
         _year = year;
         _month = month;
         _day = day;
     }
 private:
     int _year;
     int _month;
     int _day;
};
 
 void TestDate()
{
     Date d1; // 调用无参构造函数
     Date d2(2015, 1, 1); // 调用带参的构造函数
}

5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦

用户显式定义编译器将不再生成。

6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

三、析构函数:对象的“临终遗言”

3.1析构函数的概念

在C++中,析构函数是类的特殊成员函数,负责在对象销毁时释放资源。它是对象的“临终遗言”,确保对象在离开内存舞台时不会留下“垃圾”。如果没有析构函数,动态分配的资源可能无法释放,导致内存泄漏或资源浪费。

与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

3.2 析构函数的特性

1. 析构函数名是在类名前加上字符 ~。 2. 无参数无返回值类型。 3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载 4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

代码语言:javascript
代码运行次数:0
复制
#define  _CRT_SECURE_NO_WARNINGS 1;

//class Date
//{
//public:
//	Date()
//	{
//		_year = 1900;
//		_month = 1;
//		_day = 1;
//	}
//	Date(int year = 1900, int month = 1, int day = 1)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//private:
//	int _year;
//	int _month;
//	int _day;
//};
 以下测试函数能通过编译吗?
//void main()
//{
//	Date d1(2004,2,5);
//}

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

5. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

四、拷贝构造函数:对象的“克隆术”

4.1 拷贝构造函数的概念

在C++中,拷贝构造函数是类的特殊成员函数,用于用一个对象初始化另一个对象。它是对象的“克隆术”,确保新对象的内容与原对象一致。如果没有拷贝构造函数,对象复制时可能导致资源管理问题(如内存泄漏或重复释放)。

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

4.2 拷贝构造函数的特性

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

代码语言:javascript
代码运行次数:0
复制
class MyClass {
public:
    MyClass(const MyClass& other) {  // 拷贝构造函数
        std::cout << "拷贝构造函数被调用!" << std::endl;
    }
};

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

代码语言:javascript
代码运行次数:0
复制
class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

4.3 浅拷贝和深拷贝

如果未定义拷贝构造函数,编译器会生成一个默认拷贝构造函数。但默认拷贝构造函数是浅拷贝,可能导致资源管理问题。

代码语言:javascript
代码运行次数:0
复制
class MyClass {
public:
    int* ptr;
    MyClass() {
        ptr = new int(10);  // 动态分配内存
    }
    // 未定义拷贝构造函数,编译器生成默认拷贝构造函数
};

上述代码中,默认拷贝构造函数会复制指针ptr的值,导致两个对象共享同一块内存。当其中一个对象销毁时,另一个对象的指针将指向无效内存。

深拷贝与浅拷贝

  • 浅拷贝:只复制指针的值,不复制指针指向的内容。
  • 深拷贝:复制指针指向的内容,确保每个对象拥有独立的资源。
代码语言:javascript
代码运行次数:0
复制
class MyClass {
public:
    int* ptr;
    MyClass() {
        ptr = new int(10);  // 动态分配内存
    }
    MyClass(const MyClass& other) {  // 深拷贝构造函数
        ptr = new int(*other.ptr);  // 复制指针指向的内容
        std::cout << "深拷贝构造函数被调用!" << std::endl;
    }
    ~MyClass() {
        delete ptr;  // 释放内存
    }
};

五、运算符重载:让自定义类型“支持”运算符

在C++中,运算符重载是一种强大的特性,允许我们为自定义类型(如类)定义运算符的行为。通过运算符重载,可以使代码更直观、易读。例如,我们可以让两个对象直接相加,而不需要调用繁琐的函数。

5.1 运算符重载的基本特性

运算符重载的函数名是operator后接运算符符号(如operator+)。它可以是成员函数或全局函数。例如:

代码语言:javascript
代码运行次数:0
复制
class MyClass {
public:
    int value;

    // 成员函数形式的运算符重载
    MyClass operator+(const MyClass& other) {
        MyClass result;
        result.value = this->value + other.value;
        return result;
    }
};

5.2 常见运算符的重载

(1)算术运算符

算术运算符(如+-*/)通常用于数学运算。例如:

代码语言:javascript
代码运行次数:0
复制
class MyClass {
public:
    int value;

    // 加法运算符重载
    MyClass operator+(const MyClass& other) {
        MyClass result;
        result.value = this->value + other.value;
        return result;
    }

    // 减法运算符重载
    MyClass operator-(const MyClass& other) {
        MyClass result;
        result.value = this->value - other.value;
        return result;
    }
};
(2)关系运算符

关系运算符(如==!=<>)通常用于比较对象。例如:

代码语言:javascript
代码运行次数:0
复制
class MyClass {
public:
    int value;

    // 相等运算符重载
    bool operator==(const MyClass& other) {
        return this->value == other.value;
    }

    // 不等运算符重载
    bool operator!=(const MyClass& other) {
        return this->value != other.value;
    }
};
(3)赋值运算符

赋值运算符(如=)用于对象赋值。例如:

代码语言:javascript
代码运行次数:0
复制
class MyClass {
public:
    int* ptr;

    // 赋值运算符重载
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {  // 处理自赋值
            delete ptr;  // 释放原有资源
            ptr = new int(*other.ptr);  // 复制指针指向的内容
        }
        return *this;
    }
};
(4)流插入和提取运算符

流插入(<<)和提取(>>)运算符通常用于输入输出。例如:

代码语言:javascript
代码运行次数:0
复制
#include <iostream>

class MyClass {
public:
    int value;

    // 流插入运算符重载(全局函数)
    friend std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
        os << "MyClass value: " << obj.value;
        return os;
    }

    // 流提取运算符重载(全局函数)
    friend std::istream& operator>>(std::istream& is, MyClass& obj) {
        is >> obj.value;
        return is;
    }
};

5.3 注意事项

  • 不能重载的运算符: 部分运算符不能重载,如.::?:等。
  • 优先级和结合性: 运算符重载不能改变运算符的优先级和结合性。
  • 语义一致性: 运算符重载应保持语义一致性。例如,+应用于加法,而不是减法。

5.4 总结

运算符重载是C++中一种强大的特性,它允许我们为自定义类型定义运算符的行为。通过运算符重载,可以使代码更直观、易读。理解运算符重载的基本规则和使用场景,是掌握C++面向对象编程的重要一步。

六、取地址及const取地址操作符重载

这两个成员函数一般用的频率不高,所以这里就不详细讲了哈

这两个默认成员函数一般不用重新定义 ,编译器默认会生成

代码语言:javascript
代码运行次数:0
复制
class Date
{
public:
	Date* operator&()
	{
		return this;

	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

C++的面向对象编程是一个庞大而精妙的体系,掌握这些基础知识是迈向高级编程的必经之路。希望本文能为你打下坚实的基础,助你在C++的世界中游刃有余!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、类的默认6个成员函数
  • 二、构造函数:对象的“出生证明”
    • 2.1 构造函数的概念
    • 2.2 构造函数的特征
  • 三、析构函数:对象的“临终遗言”
    • 3.1析构函数的概念
    • 3.2 析构函数的特性
  • 四、拷贝构造函数:对象的“克隆术”
    • 4.1 拷贝构造函数的概念
    • 4.2 拷贝构造函数的特性
    • 4.3 浅拷贝和深拷贝
  • 五、运算符重载:让自定义类型“支持”运算符
    • 5.1 运算符重载的基本特性
    • 5.2 常见运算符的重载
      • (1)算术运算符
      • (2)关系运算符
      • (3)赋值运算符
      • (4)流插入和提取运算符
    • 5.3 注意事项
    • 5.4 总结
  • 六、取地址及const取地址操作符重载
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档