上文 C++系列:对象和类(一)介绍了最简单的类的示例,本文进一步介绍类的构造函数、析构函数、this 指针。
在上文中,我们的类是这样设计的:
1 // student.h 2 #pragma once 3 #include <iostream> 4 #include <string> 5 6 class Student 7 { 8 public: 9 void InitializeData(const std::string& name, int score); 10 void SetGrade(int val) { grade = val; }; 11 void ShowGrade(); 12 13 private: 14 int grade; 15 std::string name; 16 };
1 // student.cpp 2 #include "student.h" 3 4 void Student::InitializeData(const std::string& name_val, int grade_val) 5 { 6 name = name_val; 7 grade = grade_val; 8 } 9 10 void Student::ShowGrade() 11 { 12 using namespace std; 13 cout << name << " has grade of " << grade << "." << endl; 14 }
构造函数
类可以看作是一种自定义类型,根据该类型可以创建不同的实例,即对象。那么既然是自定义类型,那么 C++ 自然希望它能尽可能满足普通类型的语法。
比如说:
1 int x = 10; 2 float y = 6.6; 3 struct StudentRecord 4 { 5 string name; 6 int age; 7 }; 8 StudentRecord sr1 = {"wangwu", 20};
即声明时进行初始化,可问题是,现在我们的 Student 类,在创建对象时不能进行类似的初始化:
Student s = {"zhangsan", 90}; // compile error
这是因为对象外部不能直接访问私有成员,我们只能通过成员函数来进行初始化,因此,我们现在只能这样做:
Student s; s.InitializeData("wangwu", 90);
显然,这样的代码显得冗余。对于为私有数据成员进行初始化这种非常常见的操作,C++ 应该有某种机制与前面普通类型的语法统一起来,这就是构造函数。
构造函数用于替换 InitializeData 这样的用于初始化私有数据成员的方法,并且可以使用类似于普通类型的初始化语法。由于构造函数专门用于解决初始化问题,因此它设计为:在声明后自动调用构造函数,且仅有一次。
在 C++ 构造函数的设计方面,还有一些要点:
- 由于这个函数很特殊,因此规定它的函数名和类名相同;
- 由于我们不关心它的返回类型,因此规定它没有返回类型;
- 应该提供无参数的构造函数,可以通过使用重载,或者默认值实现。
构造函数的声明和定义
于是,在类声明部分,我们修改为:
1 // student.h 2 #pragma once 3 #include <iostream> 4 #include <string> 5 6 class Student 7 { 8 public: 9 Student(const std::string& name = "None", int score = -1); 10 void SetGrade(int val) { grade = val; }; 11 void ShowGrade(); 12 13 private: 14 int grade; 15 std::string name; 16 };
在类方法定义部分,修改为:
1 // student.cpp 2 #include "student.h" 3 4 Student::Student(const std::string& name_val, int grade_val) 5 { 6 name = name_val; 7 grade = grade_val; 8 } 9 10 void Student::ShowGrade() 11 { 12 using namespace std; 13 cout << name << " has grade of " << grade << "." << endl; 14 }
至此,实现了最简单的构造函数。
构造函数的使用
显然,和普通类型一样,可以这样使用构造函数(C++ 11 列表初始化):
Student s1 = { "zhangsan", 80 }; Student s2{ "lisi", 59 };
但是,一般推荐的是这两种方式:
Student s1 = Student("zhangsan", 80); Student s2("lisi", 59);
如果是使用 new 动态分配内存,则:
Student* s3 = new Student("wangwu", 90);
析构函数
与构造函数相对应,C++ 还提供了析构函数(destructor)。构造函数在对象创建时会自动调用,而析构函数在对象销毁时会自动调用。
析构函数用来解决这样的问题:如果对象运行过程中使用 new 动态创建了一些内存,析构函数可以用来做释放这些内存的收尾工作。
析构函数还有如下要点:
- 析构函数名在类名前面加上波浪号 ~;
- 没有返回值;
- 必定没有参数,因此也没有重载。
示例如下:
1 // student.h 2 #pragma once 3 #include <iostream> 4 #include <string> 5 6 class Student 7 { 8 public: 9 Student(const std::string& name = "None", int score = -1); 10 ~Student(); 11 void SetGrade(int val) { grade = val; }; 12 void ShowGrade(); 13 14 private: 15 int grade; 16 std::string name; 17 };
1 // student.cpp 2 #include "student.h" 3 4 Student::Student(const std::string& name_val, int grade_val) 5 { 6 name = name_val; 7 grade = grade_val; 8 } 9 10 Student::~Student() 11 { 12 using namespace std; 13 cout << name << " has expired." << endl; 14 } 15 16 void Student::ShowGrade() 17 { 18 using namespace std; 19 cout << name << " has grade of " << grade << "." << endl; 20 }
const 成员函数
对象可以声明为 const 类型,表示其数据成员不会发生修改。但是,这样的对象不能调用普通成员函数。
const Student s3 = Student("wangwu", 90); s3.ShowGrade(); // compile error
由于编译器不知道 ShowGrade 是否会篡改 const 对象的数据(即使该方法事实上不会修改数据),因此它会报错。
如果需要允许这样的成员函数/方法通过编译,需要修改函数头,标识为 const 成员函数。
类声明中修改为:
void ShowGrade() const;
类方法定义中修改为:
void Student::ShowGrade() const { using namespace std; cout << name << " has grade of " << grade << "." << endl; }
const 成员函数通用性更好,使其可以适用于 const 类型的对象。因此,只要类方法不会修改数据成员,应该尽可能把该方法声明为 const 成员函数。
this 指针
this 指针是指向当前调用对象的指针,目的就是为了访问当前对象。每个成员函数里面都隐式地包含这个指针。
如果方法需要返回当前对象,就需要 this 指针。
假设我们需要比较两个 Student 对象的得分情况,我们需要返回得分高的 Student 对象。
类定义中的函数头如下:
const Student& GetTopVal(const Student& s) const;
类方法定义如下:
const Student& Student::GetTopVal(const Student& s) const { if (s.grade > grade) { return s; } else { return *this; } }
*this 表示 this 指针所指向的对象,即 GetTopVal 方法的调用对象。
类中定义符号常量
类也构成了一种作用域,有时需要在类作用域中定义符号常量,但会有一些限制。
在 C++ 11 之前,类中的非静态成员不能在类中直接初始化。因此以下 Student 类中的常量声明在 C++ 11 之前会报错:
const int MINIMUM_PASSING_SCORE = 60;
C++ 11 之后提供了成员初始化,但是不能用于类似下面这样的数组声明:
const int MINIMUM_PASSING_SCORE = 60; int just_for_test[MINIMUM_PASSING_SCORE]; // compile error
一种方式是使用枚举:
enum { MINIMUM_PASSING_SCORE = 60 }; int just_for_test[MINIMUM_PASSING_SCORE]; // good
另一种方式是把符号常量声明为静态(static)存储:
static const int MINIMUM_PASSING_SCORE = 60; int just_for_test[MINIMUM_PASSING_SCORE]; // good
整个示例
综合前面的内容,再加上对象数组的声明和初始化(和普通类型类似),整个示例如下:
1 // student.h 2 #pragma once 3 #include <iostream> 4 #include <string> 5 6 class Student 7 { 8 public: 9 Student(const std::string& name = "None", int score = -1); 10 ~Student(); 11 void SetGrade(int val) { grade = val; }; 12 void ShowGrade() const; 13 const Student& GetTopVal(const Student& s) const; 14 15 private: 16 int grade; 17 std::string name; 18 static const int MINIMUM_PASSING_SCORE = 60; 19 };
1 // student.cpp 2 #include "student.h" 3 4 Student::Student(const std::string& name_val, int grade_val) 5 { 6 name = name_val; 7 grade = grade_val; 8 } 9 10 Student::~Student() 11 { 12 using namespace std; 13 cout << name << " has expired." << endl; 14 } 15 16 void Student::ShowGrade() const 17 { 18 using namespace std; 19 cout << name << " has grade of " << grade << "." << endl; 20 } 21 22 const Student& Student::GetTopVal(const Student& s) const 23 { 24 if (s.grade > grade) { 25 return s; 26 } 27 else { 28 return *this; 29 } 30 }
1 // main.cpp 2 #include "student.h" 3 4 int main() 5 { 6 using namespace std; 7 Student students[3] = { 8 Student("zhangsan", 80), 9 Student("lisi", 59), 10 Student("wangwu", 90) 11 }; 12 13 cout << "-----------Show Every Student Start-----------" << endl; 14 for (int i = 0; i < 3; i++) 15 { 16 students[i].ShowGrade(); 17 } 18 cout << "-----------Show Every Student End-----------" << endl; 19 20 Student top_student; 21 // the top grade 22 for (int i = 0; i < 3; i++) 23 { 24 top_student = top_student.GetTopVal(students[i]); 25 } 26 cout << "-----------Top Student Start-----------" << endl; 27 top_student.ShowGrade(); 28 cout << "-----------Top Student End-----------" << endl; 29 }
输出如下:
-----------Show Every Student Start----------- zhangsan has grade of 80. lisi has grade of 59. wangwu has grade of 90. -----------Show Every Student End----------- -----------Top Student Start----------- wangwu has grade of 90. -----------Top Student End----------- wangwu has expired. wangwu has expired. lisi has expired. zhangsan has expired.
参考
- 《C++ Primer Plus Sixth Edition》 by Stephen Prata