类的概念
类是面向对象编程的核心。我喜欢把类理解为一组数据和一个接口。例如机器人可以是一个类,他有一些数据,如颜色、大小、型号等,还有一个接口,如移动、转动、开关等。
类可以包含成员变量、成员函数、构造函数、析构函数等。成员变量是类的状态,成员函数是类的行为。
类的声明和定义
通常我们将类的定义放在头文件,而类行为的实现放在源文件中。
class Robot{
// private: 声明成员变量
// 禁止直接访问,只能通过接口函数访问
// 在初始化时,通过构造函数初始化
private:
string color;
float size;
float position[3];
float rotation[3];
// public: 声明接口函数
// 允许直接访问,可以被其他函数调用
public:
/*
构造函数
构造函数的作用是初始化类的状态
初始化方法:
直接初始化,如:Robot r("red", 1.0);
通过参数初始化,如:Robot r = Robot("red", 1.0);
*/
Robot(string c, float s, float p[3], float r[3]);
/*
析构函数
析构函数的作用是释放类的资源(定义类的程序块结束后)
一般不需要内容,除非使用new申请了内存需要手动释放
*/
~Robot( );
void move(float x, float y, float z);
void rotate(float x, float y, float z);
void show( );
};
下面是类的实现:
Robot::Robot(string c, float s, float p[3], float r[3]){
color = c;
size = s;
for(int i = 0; i < 3; i++){
position[i] = p[i];
rotation[i] = r[i];
}
}
Robot::~Robot( ){
// 释放内存
}
void Robot::move(float x, float y, float z){
for(int i = 0; i < 3; i++){
position[i] += x;
position[i] += y;
position[i] += z;
}
}
void Robot::rotate(float x, float y, float z){
for(int i = 0; i < 3; i++){
rotation[i] += x;
rotation[i] += y;
rotation[i] += z;
}
}
void Robot::show( ){
cout << "color: " << color << endl;
cout << "size: " << size << endl;
cout << "position: " << position[0] << " " << position[1] << " " << position[2] << endl;
cout << "rotation: " << rotation[0] << " " << rotation[1] << " " << rotation[2] << endl;
}
以上是类的声明和定义,我们可以看到,类中包含了一些成员变量,以及一些接口函数。
怎么理解接口?
接口是服务端为客户端封装好的,客户端只需要调用接口函数,就能完成某些功能。
服务端的责任是实现一些函数,并详细告知如何调用这些函数,以达到客户自己的目的。
而客户端的任务就是详细阅读接口文档,并调用接口函数,完成自己的需求。
所以,接口是服务端和客户端之间沟通的桥梁。
如何使用类
float potions[3] = {1.0, 2.0, 3.0};
float rotation[3] = {0.1, 0.2, 0.3};
// 类的创建和结构类似
Robot robot("red", 1.0, potions, rotation);
// 下面的创造方法实际上生成了一个临时对象,并将其赋值给了robot2
Robot robot2 = Robot("blue", 0.5, potions, rotation);
// 我们也可以定义一个指针变量指向类对象,但最后需要手动释放内存,因为析构函数没有delete
Robot *p = new Robot("green", 0.8, potions, rotation);
// 如果没有定义构造函数,可以不进行初始化赋值
Robot empty;
// 但如果已经定义了构造函数,这样的初始化是非法的。
// 但我们可以通过函数重载来使其合法
// 在类声明中,我们可以定义一个空参数的构造函数Robot();
// 在c++11中,我们可以用列表初始化的方式来初始化类对象
// 这需要列表内参数和某个构造函数参数一致
Robot robot3 = {"yellow", 0.7, potions, rotation};
Robot robot4{"black", 0.9, potions, rotation};
// 我们可以通过.来访问类的成员函数
robot.move(1.0, 2.0, 3.0);
robot.rotate(0.1, 0.2, 0.3);
robot.show( );
// 但当我们使用const来修饰类对象时,我们不能调用类的成员函数
// 因为成员函数可能修改类的状态,而const对象不能修改状态
// 所以我们可以将某些成员函数声明为const,这样就可以在const对象上调用
const Robot robot5 = robot;
void show() const; // 将shoow最后加上const,表示该函数不修改类的状态,即可被调用
this指针
this指针是一个隐含的指针,指向当前对象的地址。在类的成员函数中,我们可以通过this指针来访问类的成员变量。
假如我们想让一个类的成员函数返回这个类本身,我们可以用this指针来实现。
// 第一个Robot表明这是函数的返回值类型
// 第二个Robot表明这是Robot类的一个成员函数
const Robot& Robot::returnthisrobot( ){
return *this;
}
作用域
为什么定义类的成员函数是要加上类名称Robot::
呢?
原因是我们声明成员函数时,是在类内进行的,所以作用域局限在类内。
因此在类外进行定义时,需要加上类名称。
这也就意味着我们可以在类外定义另一个同名函数,而不会与类的成员函数冲突。
void move(float x, float y, float z);
那为什么在类的成员函数中,我们可以直接使用类内的成员变量呢?
原因是成员函数都是内联函数,因此编译器会将函数体内的变量直接替换成内存地址,因此可以直接访问。
至于类的声明周期,前文提过,当创造类的程序块结束时,类的析构函数会自动调用,释放类占用的资源。
如何在类内定义常量?
```cpp
private:
const int max_size = 100;
这样的方法是不行的,因为类声明时并没有存储空间,因此无法存储常量。
正确的方法是:
private:
static const int max_size = 100;
这样的声明方式会在整个程序中创建一个常量,因此可以被所有对象共享。