C++系列:对象和类(二)

上文 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. 由于这个函数很特殊,因此规定它的函数名和类名相同;
  2. 由于我们不关心它的返回类型,因此规定它没有返回类型;
  3. 应该提供无参数的构造函数,可以通过使用重载,或者默认值实现。

构造函数的声明和定义

于是,在类声明部分,我们修改为:

 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. 析构函数名在类名前面加上波浪号 ~;
  2. 没有返回值;
  3. 必定没有参数,因此也没有重载。

示例如下:

 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
原文地址:https://www.cnblogs.com/noluye/p/12251350.html