C++/类与对象/默认成员函数@构造函数的用法

2025-06-04 03:50

本文主要是介绍C++/类与对象/默认成员函数@构造函数的用法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教...

名词概念

默认构造函数:不用传参就可以调用的构造函数。有3种默认构造函数(但是只能存在一个):       

  • 1、构造函数的参数是全缺省的;       
  • 2、构造函数是无参的;       
  • 3、编译器自动生成的(当我们没有编写构造函数时)

显式构造函数:用户自己编写的构造函数。 

隐式构造函数:编译器主动生成的。注意:当用户自己编写了构造函数(包括拷贝构造函数),也就是出现显示构造函数时,便不会有隐式构造函数,即编译器不会主动生成了。 

默认成员函数

在C++的类中,有6个默认的成员函数。所谓的默认成员函数,就是用户自己没在类中编写的“特殊”成员函数,但是编译器会自动生成的成员函数(也叫“隐式成员函数”)。

(ps:不要跟上面的 “ 默认成员函数 ” 概念混淆了,说不清道不明的,靠自己领会了)

如下所示

#include <IOStream>
using namespace std;

class Date
{

};

int main()
{
	Date d;
	// cout<<sizeof(d)<<endl;  // 对象d的内存大小是1
	return 0;
}

我们定义了一个类Date,而在Date这个类中既没有成员变量,也没有成员函数,是一个空类。

然而实际上,编译器自己默默地生成6个成员函数。分别是 默认构造函数默认析构函数默认拷贝构造函数默认赋值运算符重载函数默认取地址操作符重载函数默认const取地址操作符重载函数

题外话,我们在main函数里面,创建了一个空类Date的对象(也叫实例),请问对象d的大小是多少呢?验证发现对象d的内存大小是1。面对这种情况我会产生两个疑问:

1、即然Date是空类,那由空类产生的对象不应该内存是0吗,怎么会是1?

2、即然有默认的成员函数,那成员函数不是也有内存大小吗?6个成员函数内存大小所占空间大小怎么会是1?

文章末尾对这两个疑问进行解答。

下面我们对默认构造函数,展开讲解 

构造函数

概念

在C++中,构造函数是一种特殊类型的成员函数,用于在对象被创建时执行初始化操作。构造函数的名称与类名相同,没有返回类型,甚至不使用 void。 

函数特征

  • ① 函数名 与 类名相同
  • ② 没有返回类型

如下面所示,类中的成员函数 Date 便是该类的构造函数

#include <iostream>
using namespace std;
class Date
{
public:
    // 构造函数
	Date()
	{
	}
};

C语言中,如果我们创建了一个变量,没有初始化,那么变量将是随机值。因此良好的编程习惯是,创建一个变量的同时,顺便给变量初始化,让变量的值是可知的,可预测的。  

在C++中同样的道理,当我们定义了一个类,用类创建对象时,顺便的就给对象初始化了。

与C语言的区别是,初始化的这个动作,不用我们自己做,编译器自动帮我们做了 (当我们编写定义了构造函数时,编译器自动去调用构造函数;当我们没有编写构造函数时,编译器自动调用自己生成的构造函数) 。

可以这么理解:在C++的类中,构造函数便是给对象初始化的函数(即 构造函数 就是 初始化函数)。 

为了方便理解,先从显示构造函数说起,也就是自己编写构造函数。 

显示构造函数

#include <iostream>
using namespace std;

class Date
{
public:
	// 成员函数1
	Date()
	{
		// 默认构造函数
		_year=2023;
		_month=11;
		_day=29;
	}
	// 成员函数2
	//Date(int year, int month, int day)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
	// 成员函数3
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;    // 会被编译器转换为 -->  d1.Date(&d1)
	d1.Print(); // 调用类中的成员函数Print,打印对象1中的日期
	//Date d2(1976,9,9)   // 对象2
	//d2.Print(); // 调用类中的成员函数Print,打印对象2中的日期
	return 0;
}

如上代码所示,我们用类Date实例出对象d1,然后调用类中的成员函数3打印对象d1的所www.chinasem.cn有成员变量(也就是日期),运行程序,结果如下

C++/类与对象/默认成员函数@构造函数的用法

验证结论是,对象d1在创建时,编译器自动的去类里面寻找默认构造函数(也就是成员函数1),而我们在成员函数1里面完成了所有成员变量的初始化。总的效果来说编译器主动的帮我们对对象d1初始化了。

到此构造函数就可以结束了吗?你觉得该程序还有什么不好的点,可以有更友善灵活的点吗?

我们发现,不管我们用Date创建多少的对象,对象的初始化都是同一个日期2023-11-29。那么如果我要创建一个对象,但是初始化的日期是自己指定的呢?如下面创建一个对象d2,指定日期是1976-9-9。

Date d2(1976,9,9);

这时,编译器报错提示 “没有与参数列表匹配的构造函数”。

这是因为前面我们创建对象d1时,编译器找的是无参的、默认构造函数,而类中的成员函数1正好就与之匹配。而现在我们创建了对象d2,但是初始化时传入了我们指定的日期参数,这时编译器去类中找的是带有参数的构造函数(也就是成员函数2,代码中我注释掉了),而我们还没有编写带参的构造函数,所以编译器找不到匹配的构造函数,便报错编译不通过了。 

解决方法是,没有条件就给创建条件,我们再在类中定义一个带参的构造函数,也就成员函数2,如下:

	// 成员函数2
	Date(int year, int month, int day)
	{
		_year = year;
		_mhttp://www.chinasem.cnonth = month;
		_day = day;
	}

这时,我们创建对象2

Date d2(1976,9,9);
d2.Print();

时,编译器便会找到成员函数2,完成对象2的成员变量的初始化。(clue:成员函数2不是默认构造函数)

运行结果如下:

C++/类与对象/默认成员函数@构造函数的用法

到此,我们实现了构造函数的全部功能, 

  • 1、当创建对象不给定参数时,调用默认构造函数初始化; 
  • 2、当创建对象给指定参数时,调用带参数的构造函数初始化。 

但是,我们发现要实现这两个功能,要写两个构造函数,有点繁琐,而且也很怪怪的,两个构造函数…那么有没有方法用一个构造函数,实现上面两个构造函数的功能呢?如下:

实现方法:使用带有全缺省参数的构造函数(为了方便,记为成员函数4)。

class Date
{
public:
	// 成员函数4
	// 更好的实现(默认构造函数:不传参就可以调用的函数) -> 全缺省
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "Date()" &China编程lt;< this << endl; // 用来查看构造函数和析构函数 创建和销毁的顺序
	}
	// 成员函数3
	void Print()
	{
		cout <<  _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;    		// 会被编译器转换为 -->  d1.Date(&d1)
	d1.Print(); 		// 调用类中的成员函数Print,打印对象1中的日期
	Date d2(1976,9,9)   // 对象2
	d2.Print(); 		// 调用类中的成员函数Print,打印对象2中的日期
}

当创建对象不带参时,构造函数使用缺省值初始化;

当创建对象带参时,构造函数使用接收到的参数初始化。 

最后,我们对以上,成员函数1、2、4进行总结分析:

1、当在类中只定义了成员函数1时,可以创建不带参数的对象,但是却无法创建指定初始值(带参数)的对象,如

Date d1(2023,11,29);

编译器会报错,错误类型是“没有与参数列表匹配的构造函数” 

2、当在类中只定义了成员函数2时,可以创建指定初始值(带参数)的对象,但是却不能创建不带参数的对象,如

Date d1;

编译器会报错,错误类型是“类Date中没有默认构造函数”。 

3、当在类定义了成员函数1和成员函数2,就可以解决以上两种问题。

4、可以只在类中只定义成员函数4,便能实现成员函数1、2的功能。需要注意的是:当定义了成员函数4时,不能同时存在成员函数1 或 成员函数2,否则将导致编译器报错,因为编译器不知道该调用哪一个构造函数。

至此,显示构造函数学习完毕!下面继续来看看隐式构造函数。 

隐式构造函数

通过前面的介绍,我们能够得知,隐式构造函数就是,当用户自己没有编写定义构造函数时,编译器自动生js成的默认构造函数。

下面我们验证一下编译器自动生成的默认构造函数,在背后干了个啥。

class Time
{
public:
	// 构造函数
	Time
	{
		_hour = 0;
		_minute = 0;
		_second = 0;
		cout<<"Time()"<<" :"<<"  ";
		cout<<_hour<<_minute<<second<<endl;	 // 将初始化后的结果打印输出
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	// 成员函数1
	void Print()
	{
		cout<<"Date:\t";
		cout <<  _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;   // 类Time 实例出的对象 _t
};

int main()
{
	Date d1;
	d1.Print();
}

运行程序,结果如下:

C++/类与对象/默认成员函数@构造函数的用法

运行结果表明,编译器自动生成的,隐式构造函数做了以下的事情:

1、对于内置类型的成员变量,没有做任何处理;

2、对于自定义类型的成员变量,会去调用自定义类型对象的构造函数。 

下面分析代码:

1、首先我们定义了两个类,分别是类Time 和 类Date;

2、在类Time中定义了无参的构造函数Time()。在函数内,对所有成员变量都初始化为0,同时打印输出初始化的结果;

3、在类Date中,我们没有定义构造函数,由编译器自主去生成。只定义了成员函数Print(),在函数内,打印出经过编译器生成的构造函数初始化后的成员变量。注意:在类Date的成员变量中,存在着一个由类Time实例出的对象 _t;

4、在main函数中,我们用类Date实例出一个对象d1,当我们创建出对象时,编译器便会主动去调用自主产生的构造函数,对对象d1初始化。然后我们调用对象d1中的成员函数Print(),打印出初始化后的成员变量。根据最终的结果,我们可以总结出以下的结论

编译器自主生成的隐式构造函数,是一个大型的双标现场,因为隐式构造函数针对内置类型的成员变量并没有做任何处理,

而对于自定义类型的成员变量,会去调用自定义类型创建的对象的构造函数进行初始化。(如果自定义类型中也没有显示构造函数,则调用自定义类型中的隐式构造函数)

ps:

内置类型:char 、 int 、 double 、 float …

自定义类型 : 自己定义的类型,如类、结构体、枚举…

以上,便是对于C++中,类中6个默认成员函数之一的构造函数的学习记录。

结局彩蛋:

问题1:

给空类Date创建的对象d分配1个字节的空间,是因为存在

虽然Date是空类,但是用Date创建出来的对象d是客观上存在的,不要主观的认为Date是空类,Date创建的对象d也就不存在了。  

即然对象d是客观上实实在在存在的,而每个对象又是独一无二的,因此每个对象在内存中都有自己独一无二的内存地址。所以编译器会为空类Date创建的对象d分配一个字节的内存,确保对象d也有自己独特的地址,保证了“确保每个对象都有独特的地址”的原则。

ps:这个字节通常被称为 “空对象占用的内存” 或者 “对象的内存对齐” 。它不包含任何用户定义的成员变量,而是用于区分不同对象在内存中的位置。 

问题2:  

计算对象的大小时,是不考虑成员函数的大小的,只考虑成员变量的大小。思考一下,这样设计的用意。  

当我们用类创建很多不同的对象时,对于对象而言,类中的什么成员是必须的,而什么成员不是硬需的呢?  

对于每个对象而言,拥有各自的成员变量是必须的,比如日期类Date(成员变量为年、月、日),对象之间拥有自己特定的记录的年、月、日,才有意义,如果所有对象都是统一固定的,那将没有意义了,因为不管创编程建多少对象,表示的都是同一个信息。  

而成员函数是实现一些功能,比如日期的加减等等,而这些功能函数对于所有的对象来说,都是通用的,因此如果每个对象都存入这些通用的函数,是不是对于内存很不友善,于是在类中的成员函数的内存是不计入对象中的内存中的,成员函数是被存到代码区,供所有类的对象使用。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于C++/类与对象/默认成员函数@构造函数的用法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154896

相关文章

postgresql使用UUID函数的方法

《postgresql使用UUID函数的方法》本文给大家介绍postgresql使用UUID函数的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录PostgreSQL有两种生成uuid的方法。可以先通过sql查看是否已安装扩展函数,和可以安装的扩展函数

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更

PostgreSQL中rank()窗口函数实用指南与示例

《PostgreSQL中rank()窗口函数实用指南与示例》在数据分析和数据库管理中,经常需要对数据进行排名操作,PostgreSQL提供了强大的窗口函数rank(),可以方便地对结果集中的行进行排名... 目录一、rank()函数简介二、基础示例:部门内员工薪资排名示例数据排名查询三、高级应用示例1. 每

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问