C++ 类构造函数的种类与调用以及等号创建对象

此部分提取自 C++ Primer 基础部分学习笔记

1. 默认构造函数:
通常无参数或所有的参数都有缺省值, 并且一个类中只能有一个默认构造函数, 否则将引起冲突, 如:

//二者取其一
CComplex();                 
CComplex(int i=10,int j=10);

当创建类对象时不提供参数将调用默认构造函数

2. 重载构造函数:
最为常见的构造函数, 根据不同类型的参数重载, 如:

CComplex(int nReal, int nImag);
CComplex(double nReal, double nImag);

编译器根据提供的实参类型调用特定的重载构造函数

3. 转换构造函数
即为单参数的重载构造函数
转换构造函数提供了由其参数类型向对应的类类型的隐式转换途径(即隐式类类型转换), 下头会具体介绍

//转换构造函数
CComplex(int nReal);

4. 拷贝构造函数
声明类似于重载构造函数, 但是形参为该类的引用(必须是引用), 如:

CComplex(const CComplex& srcObj);

拷贝构造函数主要应用于使用一个已存在的对象去初始化一个新对象,使新对象的属性和该对象保持一致, 注意: 编译器自动生成的临时对象不能算作是已存在的对象

具体调用拷贝构造函数的情况:

  • 使用一个以存在的对象初始化新对象:
CComplex num1(num); 	
CComplex num2=num;  	//注意: 这里不会调用operator=重载运算符

再次强调: 这里不会调用operator=重载运算符

  • 函数形参为对应类的值传递(其他情况如使用引用或指针时不会发生):
    相当于是在函数内部新创建了一个局部变量, 内部实现相当于第一种情况
//按值传递,复制对象
static void TransByValue(CComplex obj)
//传递引用,不复制对象
static void TransByRefence(const CComplex& obj)
  • 函数的返回值为对应的类对象(同样返回引用或指针时不会发生):
student returnStu(void){
		return *this;
}
stuObj4=stuObj1.returnStu ();	//这里会先调用拷贝构造函数, 再调用operator=重载运算符

关于函数返回值的原理, 主要是在返回值寄存器(临时变量)中保存返回值, 当返回的是类对象时, 则在返回值寄存器中构造一个类对象, 所以此时会调用拷贝构造函数

[拓展]: 应该定义拷贝构造函数的情况:
当没有提供拷贝构造函数时, 编译器会合成默认的拷贝构造函数, 但有时候会导致一些问题:
通常, 默认的拷贝构造函数会拷贝类中对应成员的值
当类成员中存在指针时, 其拷贝的就为指针指向的内存地址, 所以这会导致两个不同的类对象中的指针成员指向同一块内存地址(即称作发生了浅拷贝), 当使用其中一个类对象的指针成员操作指向的内存地址时, 另一个对象也会受到影响(有时这是我们不希望发生的, 比如对同一块内存空间两次释放, 会导致程序崩溃)

所以这个时候就需要提供自建的拷贝构造函数
进行深拷贝, 如在拷贝函数中申请新的内存等

class student{
public:
//素质吊差的构造函数:
	student(const string &str) {
		strptr=(char*)calloc((int)str.size ()+1,sizeof(char));
		strncpy(strptr, str.c_str (),str.size ()+1);
		return;
	}
	char *strptr;
	//...
}
//素质吊差的main
int main(void)
{
	class student stuObj1("WDNMD");
	printf("%s\n",stuObj1.strptr);
	class student stuObj2(stuObj1);
	printf("%s\n",stuObj2.strptr);
	scanf("%5s",stuObj1.strptr);
	printf("%s\n",stuObj1.strptr);
	printf("%s\n",stuObj2.strptr);
	return 0;
}

输出结果:

WDNMD
WDNMD
KKSK //输入
KKSK
KKSK //两个都被修改了

5. 移动构造函数(move):
STL库中vector的移动构造函数:
注意移动构造函数的参数必须是对应类的双层引用
在这里插入图片描述
移动构造函数的存在是为了优化程序的性能, 如果每个地方都使用的是深拷贝构造函数, 当拷贝的对象很大时, 会导致操作低效, 且并非所有的拷贝都需要深拷贝, 所以有时选用移动构造函数
相对于拷贝构造函数和拷贝赋值函数, 移动构造函数相当于浅拷贝, 将源对象的数据和状态转移到目标对象中, 而后源对象失效, 相当于将源对象"移动"到目标对象中

注意移动构造函数与拷贝构造函数的使用完全看编译器的优化, 但有以下情况通常会使用移动构造函数:

  • 编译器判定传递的参数为局部变量
  • 编译器判定下头的程序中没有再使用这个传递的参数

[注意: ]特殊情况 1

//类的部分定义:
class student{
public:
	student(int n): num(n){
		cout<<"Overload Constructor: int\n";
	}
	student(const student &x) : num(x.num) {
		cout<<"Copy Constructor\n";
	}
	student& operator =(const student &x){
		cout<<"Operator =\n";
		this->num=x.num;
		return *this;
	}
	//...
	int num;
};

这3条语句执行的效果是一样的

class student stuObj1=250;			//想使用隐式类类型转换
class student stuObj2=student(250);	//想使用operator=
class student stuObj3(student(250));//想使用拷贝构造函数

输出结果:

Overload Constructor: int
Overload Constructor: int
Overload Constructor: int

原因是编译器做了一定的优化

按理来说, 上头的代码至少会调用两次函数, 其中第一次为转换构造函数Overload Constructor: int ,生成一个临时变量, 另一次为operator=重载运算符或拷贝构造函数
但是编译器优化了其中的临时变量的产生, 通通使用转化构造函数直接构造目标对象, 使代码执行的效率更高

因为: 临时对象并非好东西,它是c++程序中很多BUG来源之一,c++编译器会在不影响程序最终执行效果的前提下,尽量减少临时对象的产生, 除非真的绕不开临时变量, 如:

cout<<student(250).num<<endl;	

输出结果:

Overload Constructor: int
250

其中编译器构造了一个临时的student类对象(有时称作匿名对象), 此临时对象的生命周期只有一条语句, 作用域也只在这一条语句中
对于函数的返回值问题(在上头)也如此

[注意: ]特殊情况 2

//仍然沿用上头的类定义
	class student stuObj2(25);
	stuObj1=12345;				//关键
	cout<<stuObj1<<endl;

如果提供了operator=重载运算符, 则此处使用operator=
如果没有提供operator=重载运算符, 则此处编译器调用的是Overload Constructor: int
(但是此时编译器仍然会合成默认的operator=, 但就是不用, 搞不懂…)

发布了17 篇原创文章 · 获赞 7 · 访问量 1355

猜你喜欢

转载自blog.csdn.net/qq_42683011/article/details/102138930