前言

1
2
3
4
5
6
7
8
9
10
11
class ROLE{
public:
int hp; //成员变量
int mp; //成员变量

void Init(){ //成员函数
hpadd = 3;
}
private:
int hpadd; //成员变量
};

谁的成员,无非就是要知道作用域在哪里。


正文

与常规函数不同

1
2
3
4
5
6
7
8
9
10
#include<iostream>

int add(int a,int b){
return a + b;
}

int main(){

return 0;
}

这个时候函数add是全局函数,搁哪调用都没问题。
相反的,类中的成员函数,就只能被这个类调用。

1
2
3
4
5
6
7
8
9
10
class Box{
public:
void test(){
std::cout << "hello Box\n";
}
};

void test(){
std::cout << "hello ::\n";
}

这种时候在主函数中调用test()想都不用想调用的是外面的全局函数

::之前说过前面没有东西时就代表全局作用域,那么要调用Box的test就要Box::test();
不过由于没有实例化,所以没意义,顶多在外面修改函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Box{
public:
void test();
};

void test(){
std::cout << "hello ::\n";
}

void Box::test(){
std::cout << "hello ::Box\n";
}

int main(){
test();
Box b;
b.test();

return 0;
}

注意box类中的函数,定义要通过Box::写在外面的话,那么类中的函数只能是声明,否则会报错已有主体
这里就是想在外面写,所以去掉了类中的函数主体。


成员函数的大小

可能这种写法刚从c风格过渡而来时会不习惯,但是一般类中的成员初始化或者函数定义都是写在外面的。

结构体的内存占用存在对齐情况,2 4 8的倍数,这是板上钉钉的事实。
但是类的内存占用呢,变量没办法,声明了就有,但是成员函数,不是每个对象都需要,它的内存占用该怎么办

1
2
3
4
5
6
7
8
9
10
11
class Box{
public:
int a;
int b;
void test();
};
int main(){
std::cout << sizeof(Box);

return 0;
}

发现他在统计类的内存大小的时候,忽略掉了函数?

如果是以为没定义的话其实也陷进去了。

1
2
3
4
5
6
7
8
class Box{
public:
int a;
int b;
void test(){
std::cout << "hello \n";
}
};

发现还是忽略的。

但是要注意:虽然统计类的大小时不计函数,但是函数本质上还是有占用的

对象的大小按照数据成员所占空间之和算,和结构体类似。但是类中的函数,是类通用的,所以不算做对象的占用,因此成员函数也不存在对象的内存空间。换言之就可能在调用时才跳转到代码区什么的找到函数的地址。


空类的大小

然后就是另一个有意思的地方。当类是空的时候它的占用会是0吗

ps: C++的空类是指这个类不带任何数据,即类中没有非静态(non-static)数据成员变量,没有虚函数(virtual function),也没有虚基类(virtual base class)。

1
2
3
class hack{

};

可以看到结果是1.

其实也是因为C++标准规定,凡是一个独立的(非附属)对象都必须具有非零大小。

不然这个类创建多个对象的时候,这几个不同的对象的内存地址是一样的不是很荒唐吗

1
2
3
hack h1, h2;
std::cout << &h1 << std::endl;
std::cout << &h2 << std::endl;

切成release模式才会相邻,debug可能还有啥没优化的挡着了。


构建class文件

vs的操作流程吧,因为类可能单独写在一个文件[一个源文件+一个头文件]里

就会得到一个源文件和一个头文件,这里Class类名以Hack举例

1
2
//Hack.cpp
#include "Hack.h"
1
2
3
4
5
//Hack.h
#pragma once
class Hack
{
};

然后可以用main.cpp 引入Hack的头文件,这样就能访问类。

1
2
3
4
5
6
7
#pragma once
class Hack{
public:
int hp;
int mp;
void test();
};
1
2
3
4
5
6
#include<iostream>
#include "Hack.h"

void Hack::test(){
std::cout << "test" << std::endl;
}

这样函数的定义部分也可在源文件操作。除了内联函数以外,我们尽量将内联函数写在头文件中,毕竟这个内联的简易的话它是直接拿来替换的,没必要让他翻几个文件再替换。


重写原则

因为函数可以只先声明,而后定义,所以最后参考的是定义部分

1
2
3
4
5
6
7
8
9
#pragma once
class Hack{
public:
int hp;
int mp;
void test();
int add(int a,int b,int c);
};

1
2
3
4
5
6
7
8
9
10
#include<iostream>
#include "Hack.h"

void Hack::test(){
std::cout << "test" << std::endl;
}

int Hack::add(int a,int b,int c = 3){ //假如声明的时候已经给c做了默认值,那么此处在定义默认值就会报错
return hp + mp;
}

其实本质上是要注意默认参数的情况,虽然我们形参习惯放于函数声明的部分。
但是默认参数其实也可以放在声明的地方。

当默认参数放在声明部分时,定义的时候就不可以再重复出现默认参数了,否则会产生错误。

所以原则上:

  1. 当定义和声明分开时,参数的默认值就尽量放在声明
  2. 定义和声明不分开的时候,就老老实实放着就行了。

this

1
2
3
4
5
6
7
8
9
#include<iostream>
#include "Hack.h"

void Hack::test(){
this->hp = 200;
this->mp = 300;
std::cout << "test" << std::endl;
}

如果只在此处初始化的话其实跟hp=200;差别不大。

但是this的意义肯定不在于此,不然早就被废弃了。

this指针是一个自动生成、自动隐藏的成员,当一个对象被实例化时,this指针自动指向对象的首地址

比如说比较两个对象的某个参数,然后返回指针。

1
2
3
4
5
6
7
8
#pragma once
class Hack{
public:
int hp;
int mp;
int lv;
Hack *Hmax(Hack *ha);
};
1
2
3
4
5
6
#include<iostream>
#include "Hack.h"

Hack* Hack::Hmax(Hack *ha){
return ha->lv > lv ? ha : this;
}

如果说没有this指针,就不好确定比较的对象的地址。

1
2
3
4
5
6
int main(){
Hack ha;
Hack hc;

hc.Hmax(&hc);
}

这样返回的还是一个指针,我们可以去接受它做其它有意义的事情。


还有就是比如说传值初始化的时候this可以区分出来

1
2
3
void Hack::setHp(int hp){
hp = hp;
}

当形参名没有考究,跟类的成员名一样的时候,其实就给编译器整活了。但是你加上this的话就一目了然

1
2
3
void Hack::setHp(int hp){
this->hp = hp;
}

this还有一种套娃用法。

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

Hack &setHp(int hp);
Hack &setMp(int mp);
Hack &setLv(int lv);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
#include "Hack.h"

Hack& Hack::setHp(int hp){
this->hp = hp;
return *this;
}
Hack &Hack::setMp(int mp){
this->mp = mp;
return *this;
}
Hack &Hack::setLv(int lv){
this->lv = lv;
return *this;
}

1
2
3
4
int main(){
Hack ha;
ha.setHp(200).setMp(300).setLv(1);
}

会发现可以连起来用。因为它的返回值是this指针,所以当ha开始调用的时候,只要后面的参数也返回的this指针就可以一直套娃套下去。这一种就像类的连续初始化版~

当然还是要归功于引用这一特性,如果没有引用,就没法返回一个解引用之后还原成this的指针。
不能返回指针,而是返回类的话,是会存在大量的内存消耗的一个问题。
就跟结构体那会提到的类似,返回一个结构体和返回一个结构体指针肯定是不一样的,返回结构体指针还原出本身,和新建临时对象返回。


结语

熟悉下定义类的成员函数定义方式,和this指针的巧妙运用。