zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Java数据类型+练习
    在vue中使用echars不能自适应的解决方法
    使用js将Unix时间戳转换为普通时间
    vue-router2.0二级路由的简单使用
    vue父组件向子组件传递参数
    vue子组件向父组件传递参数的基本方式
    vuex----mutation和action的基本使用
    vuex----------state的基础用法
    数组判断重复
    在vue项目中快速使用element UI
  • 原文地址:https://www.cnblogs.com/noluye/p/12251350.html
Copyright © 2011-2022 走看看