前言

构造函数也是类的成员函数,但是比较特殊。
类一共有两个特殊的函数,一个是构造函数,另一个是析构函数。


正文

初始化

1
2
3
4
5
6
#pragma once
class Hack{
public:
int hp;
int mp;
};
1
2
3
int main(){
Hack ha{100,200};
}

在成员变量都为public属性时,可以通过这种方式顺序初始化。

打印一下看到值是正确被赋予了。

但是如果有个成员是私有的,那么这种顺序就被打乱了

1
2
3
4
5
6
7
#pragma once
class Hack{
public:
int hp;
private:
int mp;
};
1
2
3
int main(){
Hack ha{ 100,200 }; //error
}

1是不匹配,2是设置的参数超了。

归根结底就是类型没有对上,那么新建个类,然后给指定的参数赋值,在拷贝给这个类。

1
2
3
4
5
int main(){
Hack ha;
ha.hp = 100;
Hack hc = ha;
}

这种方法是可行的,因为这样的拷贝就是只带上hp的值。

打印出来也是行得通,但是这个私有的mp是肯定没法操作。


setter和getter

其实接触c++面向对象的时候前面上学学过一点java的。
就有两种方法,一种是传参 初始化私有的成员变量,一种是返回这个私有变量的值。

手写也不难

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

void SetMp(int mp){
this->mp = mp;
}
int GetMp(){
return mp;
}
private:
int mp;
};

的的确确是能够达到这种效果的。


构造函数

如果每次都要先创建一个被拷贝的类,还有疯狂调用set和get方法,显得很麻烦。

所以就要说一下这个特殊的构造函数。

  1. 它属于成员函数在public:下
  2. 构造函数自动调用,且没有返回值
  3. 构造函数跟类同名
  4. 每个类至少有一个构造函数

所以说很特殊,但是很高级~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once
#include<iostream>
class Hack{
public:
int hp;

void SetMp(int mp){
this->mp = mp;
}
int GetMp(){
return mp;
}

Hack(){
std::cout << "构造函数" << std::endl;
hp = 100;
mp = 200;
}

private:
int mp;
};

看到在实例化对象的时候和在打印参数值之前,肯定是调用了构造函数的,因为我们在构造函数里面也加了一条打印。

还有就是构造函数不止一个,说明它也可以重载

1
2
3
4
Hack(int hp, int mp){
this->hp = hp;
this->mp = mp;
}

这里继续强调,形参名如果养成良好习惯就不要跟成员变量重复,除非你像我一样习惯用this指针去分开,否则就是一种无用行为,形参=形参编译器都傻了

为了区分也加个cout

1
2
3
4
5
Hack(int hp, int mp){
std::cout << "构造函数1" << std::endl;
this->hp = hp;
this->mp = mp;
}

可以看到效果很妙。

重载嘛,把类的对象当参数也ok,因为之前是用=号去赋值

1
2
3
4
5
Hack(Hack& H){
std::cout << "构造函数2" << std::endl;
this->hp = H.hp;
this->mp = H.mp;
}

默认构造函数

这个就很好理解,就是你没有主动写,那么编译器自动给它加上一个类名(){}
这个默认构造函数,无参数无返回值,就是空的放在那里。

1
2
3
4
5
6
class Box{
public:
int hp;
int mp;
//Box(){} 在没有定义构造函数的时候,默认存在的就是这样的。
};

default

前面说到只要定义过构造函数,就不会出现默认构造函数,但有的时候可能还需要一个默认构造函数
就可以用到default关键字去声明。

类名(){} 默认构造函数
类名()=default;用关键字定义默认构造函数

当我们注释掉之前写的构造函数

1
2
3
4
5
/*Hack(){
std::cout << "构造函数" << std::endl;
hp = 100;
mp = 200;
}*/

这个时候你再去创建一个类的对象就会报错了

解决的方法就是给它留个默认构造函数或者指定

1
2
3
4
Hack(){

}
Hack() = default;

当然这两种方法只能存在一个,否则也是定义冲突。

这两种孰优孰劣要看默认构造函数里面是否要存放什么比如初始化,如果要就选前者,如果不需要就选后者。


explicit

被explicit关键字修饰的构造函数会被禁止类型转换

1
2
3
4
5
6
bool toMax(Hack hack){
return hack.GetMp() > this->mp;
}
Hack(int mp){
this->mp = mp;
}

布尔值,返回就是0或者1,这个很直观。

逻辑上也一点问题没有。

但是有个神奇的地方。

你会发现,我toMax函数明明参数是Hack类型的,居然放数字也成功了

其实本质上就是编译器偷偷转换了,因为我们有个构造函数就是让传值赋给mp的。
你在调用std::cout << ha.toMax(900) << std::endl;
他就会先把这个900传进给一个临时的hack对象,然后再用这个临时的对象去和ha作比较。

那么说去掉这个构造函数就行,也确实是一个办法,但是有的时候我这个构造函数存在有他的道理,我只是不希望他被拿来做转换运算了。就可以使用explicit这个关键字

1
2
3
explicit Hack(int mp){
this->mp = mp;
}

调用的地方就会发生报错了,无法转换。

这个在后面可以规避掉很多问题。


结语

未完结待续。