C++ 类和对象篇(三) 空类和6个默认成员函数

目录

一、空类

[1. 是什么?](#1. 是什么?)

[2. 空类中的成员](#2. 空类中的成员)

[3. 空类的大小](#3. 空类的大小)

二、6个默认成员函数

[三、 构造函数](#三、 构造函数)

[1. 构造函数是什么?](#1. 构造函数是什么?)

[2. 为什么C++要引入构造函数?](#2. 为什么C++要引入构造函数?)

四、析构函数

[1. 析构函数是什么?](#1. 析构函数是什么?)

[2. 为什么要有析构函数?](#2. 为什么要有析构函数?)

五、拷贝构造函数

[1. 拷贝构造函数是什么?](#1. 拷贝构造函数是什么?)

[2. 为什么要有拷贝构造函数?](#2. 为什么要有拷贝构造函数?)

六、赋值运算符重载函数

[1. 运算符重载是什么?](#1. 运算符重载是什么?)

[2. 为什么要运算符重载?](#2. 为什么要运算符重载?)

[3. 默认赋值运算符重载函数](#3. 默认赋值运算符重载函数)

七、取地址运算符重载

[1. 为什么需要重载取地址运算符?](#1. 为什么需要重载取地址运算符?)

[2. 默认取地址运算符重载函数](#2. 默认取地址运算符重载函数)

[3. 默认const取地址运算符重载函数](#3. 默认const取地址运算符重载函数)

八、默认成员函数什么情况需要显示去写?


一、空类

1. 是什么?

如果一个类中没有显示定义任何成员,简称为空类。

cpp 复制代码
class A {};//一个空类

2. 空类中的成员

空类中真的什么都没有吗?

并不是,任何类都有6个不在类中实现,编译器会自动生成的默认成员函数。

3. 空类的大小

注意,没有成员变量的类(包括空类)的大小为1字节,不存储数据,目的是为了占位,标识对象存在,区分不同的对象。所以,空类同样可以被实例化,并且每个实例在内存中都有独一无二的地址。


二、6个默认成员函数

默认成员函数:用户在类中没有显式实现,编译器会自动生成的成员函数称为默认成员函数。

1)构造函数:在创建对象时初始化对象中的成员变量。

2)析构函数:完成对象的销毁。

3)拷贝构造函数:有了该函数能用同类对象初始化创建对象。

4)赋值重载函数:有了该函数能把一个对象赋值给另一个对象。

5)普通对象取地址重载函数:主要是对普通对象取地址,这个很少会自己实现。

6)const对象取地址重载函数:主要是对const对象取地址,这个很少会自己实现。
不能以普通函数的定义和调用规则去理解以上6个函数。


三、 构造函数

1. 构造函数是什么?

构造函数是一个特殊的成员函数用来初始化成员变量,函数名和类名相同,使用实例化对象时由编译器自动调用,并且在对象整个生命周期内只调用一次。

2. 为什么C++要引入构造函数?

怎么对类中的成员变量进行初始化?写一个成员函数专门用来初始化成员变量?但如果忘记调用了怎么办?为解决类初始化和忘记初始化类的问题,能不能在创建对象时就自动完成初始化的动作呢?

cpp 复制代码
举个小例子: 
有以下Date类:
class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};

如果要使用该Date类创建对象,必须通过Init公有方法给对象设置日期,否则成员变量都是随机值,
但每次创建对象时都调用该方法,这未免有点麻烦,那能否在对象创建的同时,就将信息设置进去呢?

为解决以上问题,C++中引入了构造函数:构造函数用于对象的初始化,在实例化对象时由编译器自动调用,保证了对象创建出来一定完成了初始化。虽然构造函数叫做构造,但构造函数并不用来开空间创建对象,而是用来初始化对象的。(也许构造函数更适合被称为初始化函数?)
想要更深入的了解析构函数,可以看看博主的文章: C++ 类和对象篇(四) 构造函数


四、析构函数

1. 析构函数是什么?

析构函数是一个特殊的成员函数,用来释放对象使用的资源( 如关闭文件、释放内存等**)。**

2. 为什么要有析构函数?

如何释放对象申请的系统资源?忘记释放怎么办?能不能在销毁对象时自动释放?

cpp 复制代码
举个小例子:
class Test
{
public:
	//构造函数
	Test()
	{
		_arr = (char*)malloc(1024*1024*1024);//申请1G空间
	}
	//销毁函数:用于释放资源
	void Destory()
	{
		free(_arr);
	}
private:
	char* _arr;
};

int main()
{
	Test* t = new Test;//在堆上创建一个对象
	delete t;//销毁一个对象
	while (1) {}
	return 0;
}

如果要销毁Test对象,必须先使用Destory公有方法来释放资源,否则会造成内存泄漏,
这未免有点麻烦,而且容易忘记,那能否在对象销毁的同时释放资源呢?

将以上程序运行起来,对比前后的内存变化,可以发现销毁对象前如果忘记释放资源,就会造成内存泄漏等问题。

程序运行前:

程序运行后:


所以为避免C++使用者在销毁对象时忘记释放对象使用的资源的问题,C++引入了析构函数,在析构函数里写释放资源的代码,在对象销毁时编译器会自动调用析构函数释放对象使用的资源。

析构函数相对于自己写的销毁函数,其优势在于不需要自己去显示调用。
想要更深入的了解构造函数,可以看看博主的文章:C++ 类和对象篇(五) 析构函数


五、拷贝构造函数

1. 拷贝构造函数是什么?

拷贝构造函数是一个特殊的构造函数,也是用来初始化对象的,不过它是用已经存在的对象来初始化同类对象。

2. 为什么要有拷贝构造函数?

在创建新对象时,可否用已经存在的同类对象来初始化这个新对象呢?能否快速拷贝出一个对象的副本呢?为解决以上问题,C++中引入了拷贝构造函数:拷贝构造函数用于实现对象的复制和初始化。
想要更深入的了解拷贝构造函数,可以看看博主的文章:C++ 类和对象篇(六) 拷贝构造函数


六、赋值运算符重载函数

1. 运算符重载是什么?

运算符重载是一种在编程中扩展或改变已有运算符功能的技术。简单来说,运算符重载就是对已有的运算符重新进行定义,让运算符能够处理特定类型的对象。

2. 为什么要运算符重载?

如果想要让两个对象比较大小该怎么办?有没有办法直接使用运算符>, <来比较两个对象的大小?

cpp 复制代码
class A
{
public:
	A(int a)
	{
		num = a;
	}
	int max(A b)
	{
		return num > b.num;
	}
	int operator> (A b)
	{
		return num > b.num;
	}
private:
	int num;
};
int main()
{
	A a(10);
	A b(20);
    //如果不使用运算符重载,只能编写函数来比较两个对象的大小
	cout << a.max(b) << endl;
    //使用运算符重载
    cout << (a > b) << endl;
	return 0;
}

运算符重载的目的是为了让自定义类型能够使用运算符。

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,其目的就是让自定义类型可以像内置类型一样可以直接使用运算符进行操作。

3. 默认赋值运算符重载函数

赋值运算符重载函数的特殊在于:**只能在类中定义,用户在类中没有显式实现时,编译器会生成一个默认赋值运算符重载函数,以值的方式逐字节拷贝。**注意:默认赋值运算符重载函数里内置类型成员变量是直接赋值的,而自定义类型成员变量会调用对应类的赋值运算符重载函数完成赋值:
想要更深入的了解赋值运算符重载函数,可以看看博主的文章:C++ 类和对象篇(七) 运算符重载


七、取地址运算符重载

1. 为什么需要重载取地址运算符?

在上面分析了重载运算符的原因:为了让自定义类型能使用运算符。重载取地址运算符的目的也是如此:让自定义类型也能使用取地址运算符(&)。

2. 默认取地址运算符重载函数

类中有6个默认成员函数,其中就有默认取地址运算符重载函数,所以即使类中没有定义取地址运算符重载函数,对象依然能使用取地址运算符(&):

3. 默认const取地址运算符重载函数

const取地址运算符重载函数也是默认成员函数,所以即使类中没有定义取地址运算符重载函数,const对象依然能使用取地址运算符(&):


想要更深入的了解取地址运算符重载函数,可以看看博主的文章:C++ 类和对象篇(八) const成员函数和取地址运算符重载


八、默认成员函数什么情况需要显示去写?

**1. 构造函数:**大部分类都建议显示写一个全缺省的构造函数,除了只有自定义类型成员变量的类。

**2. 析构函数:**只有在类中申请了系统资源(如:使用malloc在堆上申请空间、申请文件描述符)我们才需要显示写析构函数,在析构函数中释放这些申请的系统资源。

3. 拷贝构造函数: 在堆上申请了空间的类,需要我们显示写拷贝构造函数,默认的拷贝构造函数是浅拷贝,会产生重复析构问题。

**4. 赋值运算符重载函数:**在堆上申请了空间的类,需要我们显示写赋值运算符重载函数,默认的赋值运算符重载函数是浅拷贝,会产生重复析构问题。

**5. 取地址重载函数和const取地址重载函数:**绝大多数情况下不需要显示去写,默认生成的完全够用。特殊情况下,比如不想让别人获取对象的地址,就可以自己实现,返回nullptr。
如果一个类只有自定义类型成员变量,且自定义类型成员变量的构造、拷贝构造、赋值重载、默认析构都写好时,该类不需要显示写任何成员函数.


举个例子:当Stack类的构造、拷贝构造、赋值重载、默认析构都写好时,以下的MyQueue类的默认生成都可以用。


------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

相关推荐
Vae_Mars3 分钟前
QT-protected
开发语言·qt
JosieBook12 分钟前
【面试题】2025年百度校招Java后端面试题
java·开发语言·网络·百度
wjs202429 分钟前
CentOS Docker 安装
开发语言
深思慎考31 分钟前
计算机操作系统——进程控制(Linux)
linux·服务器·c++·c
捕鲸叉1 小时前
C++设计模式之组合模式实践原则
c++·设计模式·组合模式
阿熊不会编程1 小时前
【计网】自定义协议与序列化(一) —— Socket封装于服务器端改写
linux·开发语言·网络·c++·设计模式
小牛itbull1 小时前
Mono Repository方案与ReactPress的PNPM实践
开发语言·前端·javascript·reactpress
jokerest1231 小时前
pwn——test_your_nc1——测试
开发语言·php
碧海蓝天20222 小时前
接上一主题,C++14中如何设计类似于std::any,使集合在C++中与Python一样支持任意数据?
开发语言·c++·python
醉颜凉2 小时前
计算(a+b)/c的值
java·c语言·数据结构·c++·算法