前言

继续了解构造函数


正文

列表初始化

构造函数最直接的就是初始化成员变量

1
2
3
4
5
6
7
8
9
10
11
#pragma once
class Hack{
public:
int hp;
int mp;

Hack(int hp,int mp){
this->hp = hp;
this->mp = mp;
}
};

除了在函数里面初始化,还可以使用列表方式

1
2
3
4
5
6
7
8
9
10
#pragma once
class Hack{
public:
int hp;
int mp;

Hack(int _hp, int _mp) :hp{ _hp },mp{_mp}{

}
};

列表初始化的话就不能用this指针了,所以你要手动去区分开形参和成员变量,就是不要起一个名字

那么说这两种都是初始化的方法,谁更好
答案是列表更好

  1. 效率高
  2. 在某些情况下只能用列表初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma once
class Hack{
public:
int hp=100;
int mp=200;

Hack(int _hp, int _mp){

std::cout << hp << " " << mp << std::endl;
this->hp = _hp;
this->mp = _mp;
std::cout << hp << " " << mp << std::endl;
}
};

当hp和mp有初始值,我们去看看执行的效果

可以看到先打印初始值在打印修改后的值
说明构造函数需要跳转需要空间。

再看看列表的情况。

1
2
3
4
5
6
7
8
9
10
11
12
#pragma once
#include<iostream>
class Hack{
public:
int hp=100;
int mp=200;

Hack(int _hp, int _mp):hp{ _hp },mp{_mp}{
std::cout << hp << " " << mp << std::endl;
std::cout << hp << " " << mp << std::endl;
}
};

看到都是实例化对象时候的值。
说明列表的方法实现是在执行函数之前完成的。

说明列表初始化的效率比传统在构造函数里初始化要快
至于某些情况要到继承的时候才知道

有优势就有劣势,在劣势方面,因为成员变量是顺序初始化,他没有等函数入栈前去看默认的值,但是如果调用的是未初始化的然后用来赋值就会造成问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#pragma once
#include<iostream>
class Hack{
public:
int h;
int hp=100;
int mp=200;


Hack(int _hp, int _mp) :hp{ _hp }, mp{ _mp }, h{ mp * 3 }{
std::cout << hp << " " << mp << " " << h << std::endl;
std::cout << hp << " " << mp << " " << h << std::endl;
}
};

会发现h肯定不会这么大。说明传递的时候mp的值不正常。

原理就是,传参是从右往左传递,h声明的比mp早,mp还没完成初始化是个随机值,如果h声明在mp之下,那值就正常了。

只不过这种方式只是治标不治本,因为成员变量一多,没人回去顾及这些顺序问题,

所以列表初始化的顺序是从右往左的,传统构造函数内初始化是因为调用函数之前,成员变量都已经完成了各自的初始化


委托构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#pragma once
#include<iostream>
class Hack{
public:

int hp=100;
int mp=200;
int h;

Hack(int _h, int _hp):Hack(_h) {
std::cout << h << " " << hp << std::endl;
std::cout << h << " " << hp << std::endl;
}

Hack(int _h) :h{ _h }{
std::cout << "hack _h" << std::endl;
}
};

会发现他先执行了_h的构造函数。

委托构造函数只能用一个,且使用了委托构造之后不能再用列表初始化的方式了


副本构造函数

之前也用到过

1
2
Hack ha;
Hack hc(ha);

其实就是一种拷贝的操作,编译器也同样为类指定了一个默认的副本构造函数,当然也可以手动指定。

看到这种右值引用的类型是我们没有写过的,这就是编译器自动产生的副本构造。

手写起来也不难

1
2
3
4
5
Hack(Hack &hack){
this->hp = hack.hp;
this->mp = hack.mp;
this->h = hack.h;
}

如果秉持不想改变这个传递的hack对象,也可以加上const。取决于个人严谨程度,毕竟只是为了拷贝,万一被修改了也怪麻烦的。

然后就是利用列表的形式:

1
2
3
Hack(Hack &hack) :hp{ hack.hp }, mp{ hack.mp }, h{ hack.h }{

}

活学活用。

虽然=也是会默认调用副本构造,但是第二次在使用=意义不同,因为第一次才是真正的构造这个类,第二次只是修改或者替换。


结语

同类型的类,在构造函数里是可以访问私有成员的。
构造函数目的就是初始化构造一个类。