C++ ——— 类的 6 个默认成员函数之 构造函数

目录

何为默认成员函数

一、构造函数

构造函数的概念

构造函数的特性

日期类的构造函数

栈的构造函数

编译器自动生成的构造函数

总结


何为默认成员函数

默认成员函数就是用户没有显示实现,但是编译器会自动生成的成员函数称为默认成员函数


一、构造函数

构造函数的概念

构造函数时特殊的成员函数,函数的名字与类名相同,创建类的类型对象时由编译器自动调用

以保证每个数据成员都有一个合适的初始值,并且再对象整个生命周期内只调用一次

构造函数的特性

构造函数虽然名为构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

其特征如下:

  1. 构造函数的函数名与类名相同

  2. 无返回值(不需要写返回值)

  3. 对象实例化时编译器自动调用对应的构造函数

  4. 构造函数可以重载(我们可以手动写多个构造函数,提供不同的初始化方式)

日期类的构造函数

代码演示:

复制代码
class Data
{
public:
	
	// 无参构造函数
	Data()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}

	// 打印
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

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

以上代码是一个简单的日期类,其中有一个无参的构造函数,并且给了初始值,那么是否实例化对象后,就能打印出初始化的值呢

代码验证:

可以发现,构造函数,确确实实初始化了成员变量

在特征中说了构造函数可以重载,那么如何传参进行初始化?

代码演示:

复制代码
// 带参构造函数
Data(int yrar, int month, int day)
{
	_year = yrar;
	_month = month;
	_day = day;
}

在以上的日期类中增加了一个带参数的构造函数

那么该如何传递参数进行初始化呢?

代码验证:

直接在实例化对象的后面初始化即可

合并以上两个构造函数:

复制代码
// 全缺省构造函数
Data(int yrar = 1, int month = 1, int day = 1)
{
	_year = yrar;
	_month = month;
	_day = day;
}

全缺省的构造函数就可以合并以上两个构造函数,并且初始化时会更灵活

代码验证:

栈的构造函数

代码演示:

复制代码
class Stack
{
public:
	// 无参构造函数
	Stack()
	{
		_a = nullptr;
		_top = _capacity = 0;
	}

	// 入栈
	void Push(int x)
	{
		// 先判断是否需要扩容
		if (_top == _capacity)
		{
			// 扩容
			int new_capacity = _capacity == 0 ? 4 : _capacity * 2;
			int* tmp = (int*)realloc(_a, sizeof(int) * new_capacity);

			// 判断是否扩容成功
			if (tmp == nullptr)
			{
				perror("realloc fail");
				return;
			}

			_a = tmp;
			_capacity = new_capacity;
		}

		// 入数据
		_a[_top++] = x;
	}

	// 访问栈顶元素
	int Top()
	{
		assert(_top >= 0);

		return _a[_top - 1];
	}

	// 出栈
	void Pop()
	{
		assert(_top > 0);

		_top--;
	}

	// 判断是否为空
	bool Empty()
	{
		return _top == 0;
	}

	// 打印
	void Print()
	{
		while (!Empty())
		{
			// 访问栈顶元素
			cout << Top() << " ";
	
			// 弹出栈顶元素
			Pop();
		}

		cout << endl;;
	}

	// 释放
	void Destroy()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}


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

以上是一个栈的简单实现,并且封装到类里面

其中有一个无参的构造函数,完成了对栈的初始化

代码验证:

但是这样的无参的构造函数有一个缺点,就是如果最开始就知道要开辟 1000 个空间

那么还是这个无参的构造函数的话,就会导致异地扩容的次数加大,降低效率

所以可以写成全缺省的构造函数

代码演示:

复制代码
Stack(int n = 4)
{
	if (n == 0)
	{
		_a = nullptr;
		_top = _capacity = 0;
	}
	else
	{
		_a = (int*)malloc(sizeof(int) * n);
		_top = 0;
		_capacity = n;
	}
}

这个构造函数的意思就是如果实参部分传递了 0 ,那么就初始化为 nullptr

如果实参部分没有传递值,或者传递了其他非 0 的正数值,那么就开辟多少空间

代码验证:

复制代码
// 不开辟空间
Stack s1(0);

// 开辟4个空间
Stack s2;

// 开辟100个空间
Stack s3(100);

编译器自动生成的构造函数

在概念中提到过,构造函数是默认成员函数,如果不手动添加,编译器也会自动生成

编译器生成的默认构造函数的特点:

  1. 不手动添加构造函数,编译器才会自动生成,手动添加了,编译器就不会生成了

  2. 内置类型的成员不会做处理(int、char、int*......这些类型称为内置类型)

  3. 对于自定义类型的成员才会处理(结构体、类......这些类型称为自定义类型),会去调用这个自定义类型成员的构造函数

证明第1、2点:

当我把日期类中的构造函数去掉后,如果实例化对象后直接打印,就会出现随机值

这就证明了编译器是自动生成了构造函数的,但只是这个构造函数对内置类型的成员不会做处理

所以打印出来才会是随机值

证明第3点:

代码演示:

复制代码
class Stack
{
public:
	// 全缺省构造函数
	Stack(int n = 4)
	{
        cout << "Stack(int n = 4)" << endl;
        
		if (n == 0)
		{
			_a = nullptr;
			_top = _capacity = 0;
		}
		else
		{
			_a = (int*)malloc(sizeof(int) * n);
			_top = 0;
			_capacity = n;
		}
	}

    // ........................
}

class MyQueue
{
private:
	Stack _PushSt;
	Stack _PopSt;
};

MyQueue 类中有两个自定义的成员,是两个栈对象的成员,而且我在栈这个类中加上了一句打印,只要实例化一个 MyQueue 类的对象就能证明第 3 点

代码验证:

可以看到,确实调用了栈这个类的构造函数,所以证明了第3点


总结

一般最好是自己手动写上构造函数

只有在成员是自定义类型的时候可以不用写,因为会去调用这些自定义成员的构造函数,

相关推荐
Y1nhl1 分钟前
Pyspark学习一:概述
数据库·人工智能·深度学习·学习·spark·pyspark·大数据技术
能来帮帮蒟蒻吗3 分钟前
Go语言学习(15)结构体标签与反射机制
开发语言·笔记·学习·golang
Aphelios3803 小时前
Java全栈面试宝典:线程机制与Spring IOC容器深度解析
java·开发语言·jvm·学习·rbac
日暮南城故里4 小时前
Java学习------源码解析之StringBuilder
java·开发语言·学习·源码
双叶8364 小时前
(C语言)虚数运算(结构体教程)(指针解法)(C语言教程)
c语言·开发语言·数据结构·c++·算法·microsoft
安全方案6 小时前
精心整理-2024最新网络安全-信息安全全套资料(学习路线、教程笔记、工具软件、面试文档).zip
笔记·学习·web安全
士别三日&&当刮目相看6 小时前
JAVA学习*Object类
java·开发语言·学习
牵牛老人7 小时前
C++设计模式-责任链模式:从基本介绍,内部原理、应用场景、使用方法,常见问题和解决方案进行深度解析
c++·设计模式·责任链模式
序属秋秋秋7 小时前
算法基础_基础算法【高精度 + 前缀和 + 差分 + 双指针】
c语言·c++·学习·算法
爱吃馒头爱吃鱼8 小时前
QML编程中的性能优化二
开发语言·qt·学习·性能优化