类的入门

类的声明与定义

类的声明主要用来告诉编译器某个类的名字及其成员的类型和布局,声明通常位于头文件中。声明需要分号在语句的背后。比如我声明一个叫作Jojo的类,他有一个string类型的名字,一个int,以及两个函数,一个没有返回值,一个返回string,我会这么编写这个声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//jojo.h

class Jojo {
public:
Jojo(int value = 3) {
this->value = value;
}
void display();
int getValue();

private:
int value;
std::string name;
};

然后在一个cpp文件中实现这个类的一些函数:

1
2
3
4
5
6
7
8
9
#include "jojo.h"

void Jojo::display() {
std::cout << "Value" << value << std::endl;
}

int Jojo::getValue() {
return value;
}

C++的分号规则

  1. 宏定义没有分号,他不是一个普通的C++语句。
  2. 函数定义没有分号,定义是一个代码块,因此不需要在定义的末尾加上分号。
  3. 函数声明末尾需要有一个分号。
  4. 类的声明和定义结束后都需要加一个分号,类的成员函数声明和常规函数一样需要加上分号,类的成员函数实现和常规函数一样不需要分号。

关于返回值

写久了Python对于返回值的类型可能就不太习惯了。

在C++中,函数的声明和定义都需要指定返回值类型,函数的返回值类型是函数签名的一部分,编译器需要根据它来确定函数的用途和返回值的类型。但是在现代C++(C++11及以后)中,可以使用auto关键字让编译器根据返回值类型自动推导,这样在定义时不需要显式指定返回值类型。

1
2
3
auto add(int a, int b) -> int {
return a + b;
}

关于生成类对象

我们可以用两种方法生成类对象,一种是把类对象生成在栈上,另一种是把类对象生成在堆上。

1
Jojo my_jojo = Jojo(3);

另一种是把类对象生成在栈上:

1
Jojo* my_jojo_pointer = new Jojo(3);

然后使用这个对象的方法也不一样,

1
2
std::cout << my_jojo.get_value() << std::endl;
std::cout << my_jojo_pointer->get_value() << std::endl;

他们两个的大小也不一样:

1
2
std::cout << sizeof(my_jojo) << std::endl;
std::cout << sizeof(my_jojo_pointer) << std::endl;

这两个的区别是,通过指针来生成对象时,是通过new关键字来把对象动态地分配在堆内存(heap)上,其实写这些编译语言本质上就是控制内存吗。直接生成对象,就相当于本身就开辟了这个对象的内存。而生成一个对象的指针,这个内存就不会有人来帮我们开辟哈。这种通过new方式来生成的对象的声明周期需要由我们来手动管理,而且必须调用delete来释放内存。如果忘记释放,就会导致内存泄漏。通过指针来访问成员函数使用->语句。

这种方式的优点是可以用于创建在运行时数量不固定的对象。

直接定义对象分配在栈内存(stack)中,由编译器自动管理,使用.来直接访问成员函数,但这种方式不适合在运行时创建数量不确定的对象。

生成类对象数组

和上面生成对象一样,生成类对象数组也有两种方式。一种是通过指针来开辟在堆内存上:

1
2
Jojo* my_class_list = new Jojo[3] {Jojo(1), Jojo(2), Jojo(3)};
delete[] my_class_list;

另一种是直接栈分配定义对象:

1
Jojo my_class_list[3] = {Jojo(1), Jojo(2), Jojo(3)};

大小也不一样哈,my_class_list是一个8字节的指针,my_class_list直接就是\(3\times40=120\)字节的对象。

但是使用第几个对象的方法还是一样的。