继续介绍两个 C++ 特有的,针对类/结构体的语法。
上一篇文章,我们建造的学生类如下:
struct Student
{
private:
int age;
string name;
public:
Student(int age, string name) : age(age), name(name){};
Student(int age)
{
this->age = age;
this->name = "Student";
}
Student operator+(Student anoStu);
void operator<<(ostream& os) const;
void printStudent();
friend void operator<<(ostream& os, Student& stu);
};
具体实现我就不写了。
析构函数是类的成员函数之一,当类被销毁时会运行这个函数,语法如下:
~ClassName()
比如我们写个学生类的析构函数:
~Student()
{
cout << "This student has been destroied" << endl;
}
这样,在 main 函数结束的时候,能看到析构函数被调用的打印
就目前来看,析构函数似乎… …没有作用?
你只要明白一点就是,C++里任何和现代编程语言不太一样的设定,大概率就是由指针导致的。
我们修改类的定义,在类中新增一个 char 指针,存储游戏名。出于测试的考虑,我们删除了多余的方法,但保留打印方法方便调试。这个类的声明如下:
struct Student
{
private:
int age;
string name;
char *gameName;
public:
Student(int age, string name, char *gameName) : age(age), name(name), gameName(gameName){};
Student(int age)
{
this->age = age;
this->name = "Student";
this->gameName = new char[20];
strcpy(this->gameName, "default game");
}
~Student()
{
cout << "This student has been destroied" << endl;
}
friend ostream &operator<<(ostream &os, Student &stu);
};
事实上,如果仅仅是这样的话,分配给 gameName 的地址将不会被释放。因为局部变量本身只是一个指针,指针销毁但分配给变量的内存没有销毁。这种动态分配内存的操作,就需要在析构函数中使用 delete 释放掉内存。如下:
~Student()
{
delete[] this->gameName;
cout << "This student has been destroied" << endl;
}
注意使用的是 delete[] ,因为 gameName 是一个 char 数组。
现代操作系统的内存管理比较智能,而且内存还大,这样的内存泄露也许根本看不出来。但是在一些比如嵌入式的设备上,这种内存泄露会随频率的上升而越发明显。
我们再看一个例子:
首先,我们修改了构造与析构函数,使得调用的时候可以打印出来析构的对象:
Student(int age, string name, char *gameName)
{
this->age = age;
this->name = name;
auto len = strlen(gameName);
this->gameName = new char[len + 1];
strcpy(this->gameName,gameName);
}
~Student()
{
cout << "This student "<< this->name << " with gameName = " << this->gameName <<" has been destroied" << endl;
delete[] this->gameName;
}
接下来我们修改了部分 main 的代码:
void printStudentByValue(Student stu)
{
cout << "== PASS BY VALUE ==" << endl;
cout << stu;
}
int main()
{
using std::endl;{
Student wenZhi{18, "WenZhi", "Tricolor Lovestory1"};
Student moXiaoju = Student(18, "MoXiaoju", "Tricolor Lovestory2");
Student qiuCheng = {18, "QiuCheng", "Tricolor Lovestory3"};
cout << "BASIC:" << wenZhi << endl;
cout << "BASIC:" << moXiaoju << endl;
cout << "BASIC:" << qiuCheng << endl;
printStudentByValue(wenZhi);
cout << "SECOND" << wenZhi << endl;
}
cout << "End of prog" << endl;
}
首先定义了一个函数,此函数按值传递 Student,但只是打印出来。
其次,我们定义了三个 Student ,按值传递其中一个后,再把它打印出来。
这段代码的运行结果有很多种,取决于你的编译器。在我的电脑上gcc version 9.2.0 (tdm64-1)
,程序将运行成功,无异常报错,但 printStudentByValue
后面的代码将不会运行且控制台返回 -1
事实上,只要把按值传递改成引用传递,这问题就解决了。
由于按值传递会调用 Student 的复制构造函数,而我们没有编写复制构造函数,所以C++会帮我们生成一个。而这个默认的复制构造,原理也很简单,将每个变量的值机械地复制一份。
类似于:
Student(const Student& stu)
{
this->age = stu.age;
this->name = stu.name;
this->gameName = stu.gameName;
}
如果你够细心,你会发现 gameName 的处理很不恰当,复制一份指针相当于让两个指针指向同一块内存,而按值传递创建的临时变量离开函数的栈会自动销毁,这意味着要调用析构函数,销毁指针指向的内存,于是,原来的 Student ,也就是被复制的 gameName 指向的内存也被销毁了。
我们可以显式提供一个复制构造函数,并处理下这段流程:
Student(const Student& stu)
{
this->age = stu.age;
this->name = stu.name;
auto len = strlen(stu.gameName);
this->gameName = new char[len + 1];
strcpy(this->gameName,stu.gameName);
}
这样代码就运行正常了。