广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++超详细讲解构造函数
  • 653
分享到

C++超详细讲解构造函数

2024-04-02 19:04:59 653人浏览 八月长安
摘要

目录类的6个默认成员函数构造函数特性编译器生成的默认构造函数成员变量的命名风格类的6个默认成员函数 如果我们写了一个类,这个类我们只写了成员变量没有定义成员函数,那么这个类中就没有函

类的6个默认成员函数

如果我们写了一个类,这个类我们只写了成员变量没有定义成员函数,那么这个类中就没有函数了吗?并不是的,在我们定义类时即使我们没有写任何成员函数,编译器会自动生成下面6个默认成员函数。

class S
{
public:
	int _a;
};

这里就来详细介绍一下构造函数。

构造函数

使用C语言,我们用结构体创建一个变量时,变量的内容都是随机值,要想要能正确的操作变量中存储的数据,我们还需要调用对应的初始化函数,给成员变量赋一个合适的初值。那么c++呢,我们仍然使用这个方法来试试。

class Date
{
public:
	void SetDate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1, d2;
	d1.SetDate(2018, 5, 1);//初始化
	d1.Display();
	d2.SetDate(2018, 7, 1);//初始化
	d2.Display();
	return 0;
}

完全没问题的,毕竟C++完全兼容C嘛,不过这样子做未免有点麻烦,能不能做到我一创建好对象,对象的成员变量就是已经被初始化了而不是我们主动去调用呢?C++提供了一个特殊的函数:构造函数。

构造函数是一个特殊的成员函数,名字与类名相同,无返回值,每次使用类实例化对象时会自动调用,保证每个数据成员都有一个合适的初值,方便我们的后续使用,构造函数在对象的生命周期内只会被调用一次。

特性

虽然叫构造函数,但它的作用并不是构造一个对象(申请空间创建对象),而是初始化对象。

特征如下

  • 函数名与类名相同;
  • 无返回值;
  • 对象实例化时编译器自动调用对应的构造函数;
  • 构造函数可以重载;
class Date
{
public:
	Date()//无参的构造函数
	{

	}
	Date(int year, int month, int day)//带参的构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;//调用无参的构造函数
	Date d2(2001, 7, 28);//调用带参的构造函数
	d1.Display();
	d2.Display();
    Date d3();//这种写法要不得,会被当做函数名为d3,无参、返回类型为Date的函数声明。
    //调用无参的构造函数,一定不要加()否则编译器无法识别这是一个函数声明还是调用的无参构造。
	return 0;
}

如果编译器没有显式的定义构造函数,编译器会自动生成一个无参的默认构造函数,一旦我们显式定义,编译器就不再生成;

class Date
{
public:
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;//同样能创建一个对象
	d1.Display();
	return 0;
}

输出:

可以看到使用编译器生成的默认构造函数我们的日期仍然是随机值。

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

class Date
{
public:
	Date()//无参的默认构造函数
	{
	}
	Date(int year = 2001, int month = 7, int day = 28)//全缺省的默认构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;//这里无法编译通过,因为调用不明确。
	d1.Display();
	return 0;
}

一般我们使用全缺省的构造函数,既可以不传参用缺省值去初始化对象,也可以显式地去调用并用实参初始化对象。

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

欸好像这货没什么用啊,我刚刚使用这个自动生成的,我的对象的初值还是随机值啊,看起来就像这构造函数什么事都没有做。

其实C++把类型分成内置类型(基本类型)和自定义类型。

内置类型就是语法已经定义好的类型:如int/char…,自定义类型就是我们使用class/struct/uNIOn自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

不过这涉及到我们还没学过的知识——初始化列表,后面会讲。

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
	{
		cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl;
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
    //使用编译器默认的构造函数
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
    //内置类型
	int _year;
	int _month;
	int _day;
    //自定义类型
	Time _t;
};
int main()
{
	Date d1;
	d1.Display();
	return 0;
}

输出:

Time(int hour = 0,int minute = 0,int second = 0)

-858993460–858993460–858993460

事实上编译器生成的默认构造函数并不是什么都没有做,而是只处理了成员变量中的自定义类型,而没有去初始化内置类型,调用自定义类型成员的构造函数就是在初始化列表做的,下面会详细讲。

成员变量的命名风格

看看下面这种成员命名方式有什么缺陷?

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
	{
		cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl;
		hour = hour;
		minute = minute;
		second = second;
	}
private:
	int hour;
	int minute;
	int second;
};

简直太难看了,hour = hour这是什么操作???到底哪个hour是成员变量,让人去猜吗,代码实在丑陋,因此我们在对成员变量命名时,为了初始化对象不会因为发生命名冲突,又能一眼看出来哪个形参是对应初始化哪个成员变量的,我们通常在对成员变量命名方式统一成成员变量名前加上下划线_。

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
	{
		cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl;
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

当然不止是这样,适合自己的才是最好的,不过一般都是采用加一个前缀或者加上一个后缀的方式来命名成员变量。

这里很容易弄混两个概念,在此强调一下:

默认构造函数是不需要参数的构造函数,有以下三种:

  • 编译器生成的;
  • 显式定义的无参的构造函数;
  • 显式定义的全缺省的构造函数;

默认成员函数是我们如果不写,编译器会自动生成的函数;

构造函数的初始化列表

前面说了,在实例化一个类的对象时会自动去调用类的构造函数进行对象的初始化操作,那在C++中一个自定义类型的过程可分为两个过程:

  • 为对象分配内存;
  • 对成员变量赋值;
  • 函数体内赋值;

那我们想想如果成员变量是具有常属性的,那么是不是2过程就无法生效了?那么对于那些具有常性的变量我们以前是怎么定义的呢?初始化操作可以完成这个问题。

初始化是什么?变量在定义的同时为它设定一个初值,例如:引用必须初始化

int& a = 10;,这就是一个典型的初始化操作,而我们要谈的初始化列表也是相似的,那么这种初始化操作有什么与众不同的呢?

答案是:对于那些一旦有初值就不能再被赋值的变量,初始化列表的作用就体现出来了,例如被const修饰的变量,或者是引用,这些都是具有常属性的,因此就需要在它们创建的过程中就给它们一个初值,保证其可以被正常初始化。

class S
{
public:
	S(int i = 0, int j = 0)
	{
		_i = i;
		_j = j;
	}
private:
	const int _i;//const修饰i具有常属性
	int _j;
};
int main()
{
	S s;
	return 0;
}

报错:

“S::_i”: 必须初始化常量限定类型的对象

error C2166: 左值指定 const 对象

即在编译器看来,这个对象包括对象中的成员变量在进入构造函数的函数体后,就都已经初始化完成了,因此const修饰的变量i就无法再被赋值了。

既然初始化列表是在创建变量阶段对变量进行的初始化,因此就可以使用初始化列表处理给那些无法修改的变量。

初始化列表格式如下:

class S
{
public:
	S(int i = 0, int j = 0)
		:_i(i),
		_j(j)
	{}
private:
	const int _i;
	int _j;
};
int main()
{
	S s;
	return 0;
}

程序正常运行

同时呢,建议能使用初始化列表就使用,尽量不在构造函数的函数体中为成员变量再赋值,养成好的习惯。

下面分析编译器生成的默认构造函数到底做了什么事情

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:_hour(hour),
		_minute(minute),
		_second(second)
	{
		cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl;
	}
	void DisPlay()
	{
		cout << _hour << "-" << _minute << "-" << _second << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	void Display()
	{
		cout << "Date:";
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << "Time:";
		_t.DisPlay();
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d1;
	d1.Display();
	return 0;
}

输出:

可以看到,Date类中的内置类型都未被初始化,而对于自定义类型是去调用了其默认构造函数并且初始化成功了的。

这里要记住一定是调用的自定义成员的默认构造函数,因为编译器生成的Date的默认构造函数调用Time的构造时默认是不传参的,毕竟它也不知道传什么嘛。

如果我们把Time构造函数的缺省值去掉,那么Time就没有默认构造函数,那么创建Date对象时就无法调用Time的默认构造函数,就出错了。如下

class Time
{
public:
	Time(int hour, int minute, int second)
		:_hour(hour),
		_minute(minute),
		_second(second)
	{
		cout << "Time(int hour = 1, int minute = 1, int second = 1)" << endl;
	}
	void DisPlay()
	{
		cout << _hour << "-" << _minute << "-" << _second << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	void Display()
	{
		cout << "Date:";
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << "Time:";
		_t.DisPlay();
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d1;
	d1.Display();
	return 0;
}

报错:

message : “Date::Date(void)”: 由于 数据成员“Date::_t”不具备相应的 默认构造函数

那我们现在已经知道了编译器生成的默认构造函数它能做什么了,接下来我们显式地去定义一个构造函数。

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:_hour(hour),
		_minute(minute),
		_second(second)
	{
		cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl;
	}
	void DisPlay()
	{
		cout << _hour << "-" << _minute << "-" << _second << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date()
		:
		_year(),//调用了int的默认构造函数,并且会给初始化为0
		_month(),
		_day(),
		_t()//调用了Time的默认构造函数,这里不写也会自动调用
	{
		cout << "Date()" << endl;
	}
	void Display()
	{
		cout << "Date:";
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << "Time:";
		_t.DisPlay();
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d1;
	d1.Display();
	return 0;
}

我们为Date定义一个无参的默认构造函数,在初始化列表我不止处理了内置类型还处理了自定义类型。

在C++中支持这样一种定义变量的方法:

	int a;//a是随机值
	int b(1);
	int();//创建匿名变量,调用默认构造

这和自定义类型的定义是一样的格式,这是为了让内置类型也能按照自定义类型的方式去定义和初始化,看起来是去调用了int的构造函数。

这样,我们在初始化列表中调用了初始化了内置类型和自定义类型,Date的内置类型也都被初始化为0了,这也印证了编译器生成的默认构造函数并没有去调用int的默认构造,而只调用了自定义的默认构造。

注意:就算我们显式的定义了构造函数,如果在初始化列表中不显式的调用Time的构造函数,那么编译器也会默认去调用它的默认构造(创建自定义类型成员变量时就一定会调用),而我们一旦显式的去调用了,那么走我们的调用。

C++中有这样一个特性,编译器能帮你做的,就算你不做它会自动帮你完成,而你一旦做了他就会按照你的方式去完成。

具体什么意思呢?看代码:

class Time
{
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:_hour(hour),
		_minute(minute),
		_second(second)
	{
		cout << "Time(int hour = 0, int minute = 0, int second = 0)" << endl;
	}
	void DisPlay()
	{
		cout << _hour << "-" << _minute << "-" << _second << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date()
		:
	_t()//这里即使不写,编译器会自动去调
	{
		cout << "Date()" << endl;
	}
	void Display()
	{
		cout << "Date:";
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << "Time:";
		_t.DisPlay();
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d1;
	d1.Display();
	cout << int() << endl;
	return 0;
}

其实这个Date类的构造函数的初始化过程可以说就是编译器默认生成的构造相同了。

总之就是编译器生成的默认构造函数会在初始化列表中调用成员中自定义类型的默认构造,而不会处理内置类型,不论是我们显式定义的还是编译器生成的,编译器都会默认去调用自定义类的构造函数,除非我们在初始化列表中显式的去调用了成员的构造函数。

再言简意骇,就是无论什么构造函数(自动生成,显式定义)编译器都会在初始化列表调用自定义类型成员的构造函数,而如果我们自己显式调用了成员的构造,就执行我们所写的。

可以不显式定义构造函数的情况

如果成员变量都是自定义类型并且不需要显式调用构造函数,那么编译器生成的默认构造函数就足以处理这种情况

​ 其他情况都需要我们自己去定义构造函数。

到此这篇关于C++超详细讲解构造函数的文章就介绍到这了,更多相关C++构造函数内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++超详细讲解构造函数

本文链接: https://www.lsjlt.com/news/150408.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++超详细讲解构造函数
    目录类的6个默认成员函数构造函数特性编译器生成的默认构造函数成员变量的命名风格类的6个默认成员函数 如果我们写了一个类,这个类我们只写了成员变量没有定义成员函数,那么这个类中就没有函...
    99+
    2022-11-13
  • C++超详细讲解拷贝构造函数
    目录构造函数特征编译器生成的拷贝构造拷贝构造的初始化列表显式定义拷贝构造的误区结论构造函数 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对...
    99+
    2022-11-13
  • C++超详细讲解析构函数
    目录特性析构函数处理自定义类型编译器生成的默认析构函数特性 析构函数是特殊的成员函数 特征如下: 析构函数名是~类名;无参数无返回值;一个类有且只有一个析构函数;对象声明周期结束,编...
    99+
    2022-11-13
  • C++超详细讲解构造函数与析构函数的用法及实现
    目录写在前面构造函数和析构函数语法作用代码实现两大分类方式三种调用方式括号法显示法隐式转换法正确调用拷贝构造函数正常调用值传递的方式给函数参数传值值传递方式返回局部对象构造函数的调用...
    99+
    2022-11-13
  • Golang创建构造函数的方法超详细讲解
    目录组合字面量自定义构造函数从构造函数返回错误interface构造函数最佳实践基本构造函数主包类型多个构造函数组合字面量 组合字面量是最直接方式初始化Go对象,假设定义了Book类...
    99+
    2023-01-28
    Go创建构造函数 Go构造函数
  • c++详细讲解构造函数的拷贝流程
    #include <iostream> #include <string> using namespace std; void func(string str...
    99+
    2022-11-13
  • C++超详细讲解函数对象
    目录一、客户需求二、存在的问题三、解决方案四、函数对象五、小结一、客户需求 编写一个函数 函数可以获得斐波那契数列每项的值每调用一次返回一个值函数可根据需要重复使用 下面来看第一个...
    99+
    2022-11-13
  • C++超详细讲解函数重载
    目录1 函数重载的定义2 构成函数重载的条件3 编译器调用重载函数的准则4 函数重载的注意事项4.1 避开重载带有指定默认值参数的函数4.2 注意函数重载遇上函数指针4.3 C++编...
    99+
    2022-11-13
  • C语言超详细讲解库函数
    目录1 返回整数的getchar函数2 更新顺序文件3 缓冲输出与内存分配4 库函数练习1 返回整数的getchar函数 代码: #include<stdio.h> ...
    99+
    2022-11-13
  • C++详细讲解对象的构造
    目录一、对象的构造(上)1.1 对象的初始值1.2 对象的初始化1.3 小结二、对象的构造(中)2.1 构造函数2.2小实例2.3 小结三、对象的构造(下)3.1 特殊的构造函数3....
    99+
    2022-11-13
  • C语言函数超详细讲解上篇
    目录前言1、函数是什么?2、C语言中函数的分类2.1 库函数2.1.1 如何学会使用库函数2.1.2 自定义函数3、函数的参数3.1 实际参数(实参)3.2 形式参数(形参)4、函数...
    99+
    2022-11-13
  • C语言函数超详细讲解下篇
    目录前言函数的声明和定义函数声明函数定义举例简单的求和函数把加法单独改写成函数添加函数声明带头文件和函数声明静态库(.lib)的生成静态库文件的使用方法函数递归什么是递归?递归的两个...
    99+
    2022-11-13
  • C++超详细分析讲解内联函数
    目录宏函数(带参数的宏)的缺点inline修饰的函数就是内联函数内联函数的特点宏函数和内联函数的区别宏函数(带参数的宏)的缺点 第一个问题:宏函数看起来像一个函数调用,但是会有隐藏一...
    99+
    2022-11-13
  • Python构造函数与析构函数超详细分析
    目录1.构造函数2.析构函数1.构造函数 __init__(self), 这个方法就是构造函数,在实例化的时候自动调用。 所有如果这个函数内有打印的方法,当实例出来的时候会打印里面的...
    99+
    2022-11-13
    Python构造函数与析构函数 Python析构函数 Python构造函数
  • Spring超详细讲解BeanUtils改造
    目录1.基本原理2.使用3.性能4.提醒1.基本原理 原理:https://www.jb51.net/article/252384.htm 浅拷贝:https://www.jb51....
    99+
    2022-11-13
  • C++ 数据结构超详细讲解顺序表
    目录前言一、顺序表是什么概念及结构二、顺序表的实现顺序表的缺点几道练手题总结(●’◡’●) 前言 线性表是n个具有相同特性的数据元素的有限序列。线性表是一种...
    99+
    2022-11-13
  • C++ 数据结构超详细讲解单链表
    目录前言一、链表是什么链表的分类二、链表的实现总结(❁´◡`❁) 单链表 前言 上篇顺序表结尾了解了顺序表的诸多缺点,链表的特性很好的解决了这些问题,本期我们来认识单链表...
    99+
    2022-11-13
  • C语言结构体超详细讲解
    目录前言1、结构体的声明1.1 结构的基础知识1.2 结构的声明1.3 结构成员的类型1.4 结构体变量的定义和初始化2、结构体成员的访问2.1 点操作符访问2.2 ->操作符...
    99+
    2022-11-13
  • 详解C++构造函数
    目录1.作用2.代码举例2.1 示例1:2.2 示例2:3. 使用3.1 使用构造函数初始化3.2 有参数的构造函数3.3 默认的构造函数4. 成员初始化列表例1:正常初始化例2:成...
    99+
    2022-11-12
  • C#构造函数详解
    一、简介 构造函数,基本用法是在类对象声明的时候完成初始化工作。 二、实例构造函数 1、构造函数的名字与类名相同。 2、使用 new 表达式创建类的对象或者结构(例如int)时,会调...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作