概述
【c++类的构造函数】几种写法及问题
一、构造函数的定义
类通过一个或几个特殊的与类同名的成员函数来控制其对象的初始化过程,这些函数叫构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
该函数无返回类型(是 “ 无 ” 而不是 “ 空 ” (void))。但是该函数有返回值(这个类的对象)。并且一个对象也只能在被创建的时候调用构造函数,即一个对象只能调用一次构造函数。
(构造函数就相当于一个人的出生,析构函数就是一个人的终结。)
特别的,一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。
二、构造函数的分类
1、默认构造函数
默认构造函数就是在调用时不需要显示地传入实参的构造函数。
分为 无参数默认构造函数 跟 带缺省参数的默认构造函数
例如:
现在假设有一个类Time,该类有三个公有成员 Hour,Minute,Second。
将该类定义在“Time.h”的头文件里。再建一个“Time.cpp”用来放Time类的函数定义。最后建“MyProject.cpp”用来放主函数。
1.无参数默认构造函数:
“Time.h”:
在头文件的函数声明中只有与类名相同的函数名,无任何参数。
"Time.cpp":
初始化列表法:
Time::Time() Hour(1),Minute(1),Second(59){}
需要在函数定义中定义出具体的数值。(如果程序员没有定义任何构造函数,编译器会自动定义一个跟这个差不多类型的 / / 但看似是定义了个 啥也没有 )
也可以只在头文件中:
Time(){}
以 Time(){} 这样的方式来声明无参数构造函数,会带来一个问题,就是使得 其不再是 POD 类型,因此可能让编译器失去对这样的数据类型的优化功能。这是我们不希望看到的。因此最好使用 = default来修饰默认构造函数。
即:
Time() = default;
“MyProject.cpp”:
使用无参构造函数创建对象时,不应在对象名后面加上括号,否则会产生编译警告“warning C4930: “Sample s(void)”: 未调用原型函数(是否是有意用变量定义的?)”。
也就是不能写成:
int main()
{
Time mytime();
}
2.带缺省参数的默认构造函数:
“Time.h”:
定义缺省默认构造函数即为 定义所有参数都有默认值的函数。
"Time.cpp":
由于在头文件声明中已经初始化过了,所以不需要赋值。
“MyProject.cpp”:
同无参默认构造函数。(不能只加括号)
但缺省默认构造函数可以在括号中加入值,则初始值就是括号中的值。
int main()
{
Time mytime(1,2,3); //最终初始值即为1 2 3
}
注意:
1、以上两种默认构造函数不能同时出现。同时存在时,编译器会产生二义性,从而生成编译错误。
// error C2668: 对重载函数的调用不明确
2、只要定义了构造函数后,程序就不会自行提供默认构造函数了。
如果你定义了一个有参的构造函数,为了保证正确性,系统不会创建无参构造函数,这时候,如果你还想允许无参构造,程序员需要自己手动再声明一个。
一般选择Time();这种形式的默认构造函数。
3、编译器在什么情况下会生成默认构造函数?
有一句很经典的话可以用来回答这个问题:
惟有默认构造函数”被编译器需要“的时候,编译器才会生成默认构造函数。
那我们只需知道什么时候“被编译器需要”,就可以知道什么情况下会生成默认构造函数了。下面几种情况下,编译需要生成默认构造函数:
- 当该类的类对象数据成员有默认构造函数时。
- 当该类的基类有默认构造函数时。
- 当该类的基类为虚基类时。
- 当该类有虚函数时。
2、一般构造函数
这里主要举几个特例。 // 例子中加上一个成员函数void print();
1.三个参数的构造函数:(但在函数声明中有默认参数)
“Time.h”:
#ifndef __MYTIME__
#define __MYTIME__
#include<iostream>
using namespace std;
class Time {
public:
int Hour;
int Minute;
int Second;
Time(int tmphour,int tmpmin,int tmpsec=6); //在这里提供默认参数(所有成员变量全提供就是缺省默认)
void print()
{
cout << Hour << endl;
cout << Minute << endl;
cout << Second << endl;
}
};
#endif
"Time.cpp":
#include<iostream>
#include "Time.h"
using namespace std;
Time::Time(int tmphour, int tmpmin, int tmpsec)
{
Hour = tmphour;
Minute = tmpmin;
Second = tmpsec;
}
“MyProject.cpp”:
#include <iostream>
#include <vector>
#include "Time.h"
using namespace std;
int main()
{
Time mytime(1,2,3); //这样输出还是1 2 3
//把3去掉就是1 2 6,如:
//Time mytime(1,2);
mytime.print();
return 0;
}
可以任意含有任意个默认参数。
2.两个参数的构造函数:
“Time.h”:
#ifndef __MYTIME__
#define __MYTIME__
#include<iostream>
using namespace std;
class Time {
public:
int Hour;
int Minute;
int Second;
Time(int tmphour,int tmpmin); //只有两个参数
void print()
{
cout << Hour << endl;
cout << Minute << endl;
cout << Second << endl;
}
};
#endif
"Time.cpp":
#include<iostream>
#include "Time.h"
using namespace std;
Time::Time(int tmphour, int tmpmin)
{
Hour = tmphour;
Minute = tmpmin;
Second = 59; //这里初始化(这种初始化就不能在主函数内改动了)
}
“MyProject.cpp”:
#include <iostream>
#include <vector>
#include "Time.h"
using namespace std;
int main()
{
Time mytime(1,2); //只能赋两个参数值
mytime.print();
return 0;
}
3、初始化列表的用法:
https://www.csdn.net/gather_2f/MtTaEg0sMzgzOTgtYmxvZwO0O0OO0O0O.html
形如:
例:
“Time.h”:
#ifndef __MYTIME__
#define __MYTIME__
#include<iostream>
using namespace std;
class Time {
public:
int Hour;
int Minute;
int Second;
Time(int tmphour, int tmpmin, int tmpsec) :Hour(tmphour), Minute(tmpmin), Second(tmpsec){}
void print()
{
cout << Hour << endl;
cout << Minute << endl;
cout << Second << endl;
}
};
#endif
"Time.cpp":
#include<iostream>
#include "Time.h"
using namespace std;
啥都不用写。
“MyProject.cpp”:
#include <iostream>
#include <vector>
#include "Time.h"
using namespace std;
int main()
{
Time mytime(1,2,3); //随便赋值
mytime.print();
return 0;
}
3、拷贝构造函数
也称为复制构造函数。
拷贝构造函数参数为类对象本身的引用,根据一个已存在的对象复制出一个新的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中。
“Time.h”:
#ifndef __MYTIME__
#define __MYTIME__
#include<iostream>
using namespace std;
class Time {
public:
int Hour;
int Minute;
int Second;
Time(int tmphour, int tmpmin, int tmpsec) :Hour(tmphour), Minute(tmpmin), Second(tmpsec) {}
Time(Time& T);
void print()
{
cout << Hour << endl;
cout << Minute << endl;
cout << Second << endl;
}
};
#endif
"Time.cpp":
#include<iostream>
#include "Time.h"
using namespace std;
Time::Time(Time& T)
{
Hour = T.Hour;
Minute = T.Minute;
Second = T.Second;
}
“MyProject.cpp”:
#include <iostream>
#include <vector>
#include "Time.h"
using namespace std;
int main()
{
Time mytime(1,2,3); // 调用一般构造函数
Time mytime1(mytime); // 调用复制构造函数
mytime.print();
mytime1.print();
return 0;
}
注意:若没有显式定义拷贝构造函数,则系统会默认创建一个拷贝构造函数,当类中有指针成员时,由系统默认创建的拷贝构造函数会存在“浅拷贝”的风险,因此必须显式定义拷贝构造函数。
···浅拷贝指的是在对对象复制时,只对对象中的数据成员进行简单的赋值。 若存在动态成员,就是增加一个指针,指向原来已经存在的内存。这样就造成两个指针指向了堆里的同一个空间。当这两个对象生命周期结束时,析构函数会被调用两次,同一个空间被两次free,造成野指针。
···深拷贝就是对于对象中的动态成员,不是简单的赋值,而是重新分配空间。
4、转换构造函数
在C++中,类的构造函数可以省略不写,这时C++会为它自动创建一个隐式默认构造函数(implicit default constructor);也可以由用户定义带参数的构造函数,构造函数也是一个成员函数,他可以被重载;
当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数。
“Time.h”:
(省事儿直接内联函数了)
“MyProject.cpp”:
单参数构造函数中,引用的写法不同,可能就会涉及隐式转换的问题。
- 以下都为隐式初始化:
Time mytime = (12, 13, 14, 15); //15,1,1 明显的隐式转换
//这里是c语言的逗号表达式,整体的值为最后一个值
Time mytime = 16 ; //代码含糊不清,存在临时对象及隐式转换的问题/16,1,1
Time mytime = { 16 } ; //这种写法较正常,可以让系统明确调用带一个参数16的构造函数/16,1,1
如果,在“MyProject.cpp”中加一个 形参类型为Time 的普通函数fun();
void fun(Time myt) //这里如果写为void fun(Time& myt)就不能编译了
{
return;
}
// 这里如果写为void fun(Time& myt)就不能编译了
则用一个数字就能调用:
fun(16); //系统对16到对象myt做了一个隐式转换,产生了一个临时对象myt
所以出现了一个新的东西:explicit(显式):
如果构造函数声明中带有explicit,则这个构造函数只能用于初始化和显式类型转换。
“Time.h”:
explicit Time(int tmphour)
不能编译://隐式转换
Time mytime = (12, 13, 14, 15);
Time mytime = { 16 } ;
Time mytime = 16 ;
fun(16);
可以编译://显式转换
Time mytime ( 16 ); //少一个等号就可以
Time mytime { 16 } ;
Time mytime = Time(16); //或者Time{16}
fun(Time(16)); //生成临时对象
如果explicit用于无参的构造函数:
explicit Time();
则:
Time mytime {}; //可以,显式转换
Time mytime = {}; //不可以,多一个等号就是隐式初始化了(构造并初始化)
fun({}); //不可以,隐式转换
fun({1,2,3}); //不可以,隐式转换
fun(Time{}); //显式转换,调用无参构造函数生成临时对象
fun(Time{1,2,3}); //显式转换,调用三个参数的构造函数生成临时对象
总结:建议单参数构造函数都声明为explicit,防止出现未知错误
三、构造函数的一些问题
1、
不同于其他成员函数,构造函数不能被声明为const。
当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数在const对象的构造过程中可以向其写值。
2、
函数定义写在类的外部和写在类的内部有什么区别?
调用方式不同。
- 在类里面定义的是内联函数(涉及到内联函数inline):
调用的时候不发生控制权转移,作为函数体本身一个模块进行调用。
- 在类外面定义的函数:
情况相反,调用的时候需要开辟一部分空间。
总结就是类里面定义的调用更快,更节省内存
3、
在C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后写上“=default”(不含引号)来要求编译器生成构造函数。
其中,“=default”即可以和声明一起出现在类的内部,也可以作为定义出现在类的外部。
=default的详细用法:https://blog.csdn.net/weixin_38339025/article/details/89161324
4、
通常情况下,我们将构造函数声明为public的,可以供外部调用。然而有时候我们会将构造函数声明为private或protected的:
(1)如果类的作者不希望用户直接构造一个类对象,着只是希望用户构造这个类的子类,那么就可以将构造函数声明为protected,而将该类的子类声明为public。
(2)如果将构造函数声明为private,那只有这个类的成员函数才能构造这个类的对象。
5、
所有的构造函数都可以用初始化列表表示。
感谢以下文章:
https://blog.csdn.net/qq_40246263/article/details/78334675?utm_medium=distribute.pc_relevant.none-task-blog-title-7&spm=1001.2101.3001.4242
https://blog.csdn.net/china_jeffery/article/details/79288103?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param
https://blog.csdn.net/qq_37568748/article/details/82014064
https://blog.csdn.net/hongzhiyong118/article/details/87967935?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param
https://zhuanlan.zhihu.com/p/168787937
https://blog.csdn.net/lixiaogang_theanswer/article/details/81090622
最后
以上就是重要小虾米为你收集整理的【c++类的构造函数】几种写法及问题【c++类的构造函数】几种写法及问题的全部内容,希望文章能够帮你解决【c++类的构造函数】几种写法及问题【c++类的构造函数】几种写法及问题所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复