C++面向对象编程:深入解析四大机制

发表时间: 2024-05-30 16:09

面向对象编程

面向对象编程(Object-Oriented Programming, OOP)是一种程序设计范式,它将现实世界中的每个事物抽象对象,在代码中用变量表示对象的特征,用函数表示对象的行为。具有相同特征和行为的一组对象还被抽象,它们的变量和函数被封装在类中。换句话说,类的成员包括变量(也称为类的属性)和函数(也称为类的方法)。此外,OOP利用继承多态等机制来实现代码的复用和扩展。

类和对象

类和对象的通俗解释:

  • 类:类描述了某一类事物应该具有哪些特征和行为。比如,如果我们想描述“人类”这个类别,类就会告诉我们“人类”通常有哪些属性,比如名字、肤色、年龄等;以及“人类”可以做哪些事情,比如说话、行走、思考等。但是,“人类”这个类别本身并不是真正的人,它只是告诉了我们人类应该是什么样的。
  • 对象:对象则是根据类创建出来的具体实例。当我们说“我的朋友叫张三”时,“张三”就是一个对象,他是根据“人类”这个类制造出来的具体实例。“张三”有他自己的名字(属性),也可以做出说话、行走、思考等行为(方法)。每个对象都是独一无二的,就像世界上没有两个完全相同的人一样。张三可能有他自己的名字、肤色、年龄等独特的属性,并且他的说话风格,走路姿势,思考方式等也会与其他人不同。

类和对象的专业解释:

  • 类:类是对具有相同属性和方法的一组对象的抽象描述。它定义了对象的结构和行为。类的成员包含属性(或称为成员变量),用于描述对象的静态特征(如名字、肤色、年龄等);以及方法(或称为成员函数),用于定义对象的动态行为(如说话、行走、思考等)。类是对象的抽象表示,它本身不占用内存空间。
  • 对象:每一个对象都是根据类创建的具有其属性和方法的实例,并且具有唯一的身份标识(如内存地址)和自己独特的属性值。 对象是占用内存空间的,它的属性值可以在运行时动态地改变。

类的基本组成

C++中类的基本组成包括类名,访问修饰符,成员变量(属性),成员函数(方法)。其中成员函数包括构造函数和析构函数。

类名

类名用于标识类的名称,是类的唯一标识符。例如,定义了一个名为Person的类:

class Person{};

访问修饰符

访问修饰符用于控制类成员(变量或函数)的访问权限,包括:

  • public:成员在类的内部和外部都可以被访问。
  • protected:成员可以在类内部和派生类中被访问,但不能在类的外部被直接访问。
  • private:成员只能在类内部被访问,类的外部和派生类都不能直接访问。
class Person{public:    int publicVar;  protected:    int protectedVar;private:    int privateVar;};

成员变量(属性)

成员变量是类中的变量,用于存储与类相关的数据。成员变量可以是任何有效的C++数据类型,包括基本数据类型、数组、指针、引用、其他类类型等。

例如:

#include <iostream>#include <string>class Person {private:    std::string name; // 私有成员变量    int age; // 私有成员变量public:};

成员函数(方法)

成员函数是类中定义的函数,它可以访问类的成员变量,也可以使用其他成员函数。

返回类型 函数名(参数){函数体} // 参数可有可无

不过,当一个成员函数被声明为const时,它不能修改类的任何成员变量(除非这个成员变量被声明为mutable):

返回类型 函数名(参数) const {函数体} 

构造函数和析构函数是两个特殊的成员函数:

构造函数:用于初始化类的对象。当创建类的对象时,构造函数会自动被调用。语法:

类名(参数){函数体} // 函数名必须与类名完全相同,并且没有返回类型,参数可有可无

可以用初始化列表语法来初始化属性值:

类名():属性1(值1),属性2(值2)...{函数体}

析构函数:用于在对象销毁前执行清理操作,如释放内存。当对象的生命周期结束时,析构函数会自动被调用。语法:

~类名(){函数体}

示例:

#include <iostream>#include <string>class Person {private:    std::string name; // 私有成员变量    int age; // 私有成员变量public:    // 构造函数    Person(std::string name_value, int age_value) : name(name_value), age(age_value) {        std::cout << "Create Person with name: " << name << ", age: " << age << std::endl;    }    // 析构函数    ~Person() {        std::cout << "Destroy Person with name: " << name << ", age: " << age << std::endl;    }    // 公有成员函数:获取名字    std::string getName() const {  // 不被允许修改成员变量        return name;    }    // 公有成员函数:获取年龄    int getAge() const { // 不被允许修改成员变量        return age;    }    // 公有成员函数:设置年龄    void setAge(int age_value) { // 可以修改成员变量        age = age_value;        std::cout << "Age updated to: " << age << std::endl;    }    // 公有成员函数:显示Person信息    void displayInfo() const {  // 不被允许修改成员变量        std::cout << "Name: " << name << ", Age: " << age << std::endl;    }};int main() {    // 创建Person对象,构造函数将被调用    Person* person1 = new Person("SanZhang", 30);     // 调用成员函数     person1->displayInfo();      person1->setAge(31);      person1->displayInfo();     // 创建另一个Person对象,构造函数将被调用    Person* person2 = new Person("SiLi", 25);      person2->displayInfo();    delete person1; // 调用person1的析构函数将    delete person2; // 调用person2的析构函数将    return 0;}
Create Person with name: SanZhang, age: 30Name: SanZhang, Age: 30Age updated to: 31Name: SanZhang, Age: 31Create Person with name: SiLi, age: 25Name: SiLi, Age: 25Destroy Person with name: SanZhang, age: 31Destroy Person with name: SiLi, age: 25

面向对象编程的四大机制

封装

封装通过将数据和对数据的操作(即属性和方法)组合到一个单元(类)中,并通过访问控制符(privateprotectedpublic)来限制对这些数据和操作的访问来实现。封装隐藏了对象的内部实现细节,只暴露必要的接口,提高了代码的可维护性和安全性。

在成员函数的示例代码中,就体现了封装性:

  • 成员变量 nameage 被声明为 private,这确保了它们只能通过类提供的公共接口(即成员函数)进行访问和修改,这是封装性的核心体现。
  • 构造函数、析构函数以及成员函数( getName(), getAge(), setAge(), displayInfo())作为公共接口,允许外部代码在受控的情况下与类的内部状态进行交互,同时保护了类的内部实现细节不被外部直接访问或修改。

继承

继承允许创建一个新类(派生类/子类)来继承一个重用类(基类/父类)的属性和方法,并在其基础上扩展新的功能。继承的基本语法为:class 派生类: public 基类

例如,我们可以创建一个新的类Student ,它继承自Person类。Student 继承Person类的属性和方法的同时可以添加一些新的属性和方法:

#include <iostream>  #include <string>    // 基类 Person  class Person {  private:      std::string name; // 私有成员变量      int age; // 私有成员变量    public:      // 构造函数      Person(std::string name_value, int age_value)           : name(name_value), age(age_value) {          std::cout << "Create Person with name: " << name << ", age: " << age << std::endl;      }        // 析构函数      ~Person() {          std::cout << "Destroy Person with name: " << name << ", age: " << age << std::endl;      }        // 公有成员函数:获取名字      std::string getName() const {          return name;      }        // 公有成员函数:获取年龄      int getAge() const {          return age;      }        // 公有成员函数:设置年龄      void setAge(int age_value) {          age = age_value;          std::cout << "Age updated to: " << age << std::endl;      }        // 公有成员函数:显示Person信息      void displayInfo() const {          std::cout << "Name: " << name << ", Age: " << age << std::endl;      }  };    // 派生类 Student  class Student : public Person {  private:      std::string studentId; // 添加的属性  public:      // 构造函数  可以使用用初始化列表来调用父类的构造函数    Student(std::string name_value, int age_value, std::string id)          : Person(name_value, age_value), studentId(id) {          std::cout << "Create Student with ID: " << studentId << std::endl;      }        // 析构函数    ~Student()  {         std::cout << "Destroy Student with ID: " << studentId << std::endl;      }        // 添加的方法    std::string getStudentId() const {          return studentId;      }      // 添加的方法    void displayStudentInfo() const {          std::cout << "Student Info: ";          displayInfo(); // 调用基类的displayInfo()          std::cout << "ID: " << studentId << std::endl;      }  };    int main() {      Student* student = new Student("SanZhang", 20, "A12138");      std::cout << "name:" << student->getName() << std::endl; // 调用父类的方法    student->displayStudentInfo();    // 调用添加的方法    // 当删除一个子类对象时,首先会调用子类的析构函数,然后再自动调用父类的析构函数    delete student;    return 0;  }
Create Person with name: SanZhang, age: 20Create Student with ID: A12138name:SanZhangStudent Info: Name: SanZhang, Age: 20ID: A12138Destroy Student with ID: A12138Destroy Person with name: SanZhang, age: 20

多态

多态性使得一个接口可以有多个实现,从而可以使用同一接口来调用不同的实际方法。C++实现多态的方式主要有两种:静态多态 (主要通过函数重载和模板来实现) 和动态多态。 在讨论面向对象编程多态性时,通常指的是动态多态。动态多态主要通过继承和虚函数来实现,其中虚函数是一种在基类中声明,并在派生类中可以重写的函数,通过在基类中使用virtual关键字声明。

实现动态多态的一个关键是子类对象可以被被赋予父类类型的引用, 其地址也可以赋予父类类型的指针。 这被称为向上转型或隐式类型转换。

示例:

#include <iostream>#include <string>// 抽象基类 Personclass Person {protected:    std::string name;    int age;public:    Person(std::string name_value, int age_value) : name(name_value), age(age_value) {        std::cout << "Create Person with name: " << name << ", age: " << age << std::endl;    }    virtual ~Person() {  // 虚析构函数        std::cout << "Destroy Person with name: " << name << ", age: " << age << std::endl;    }      virtual void displayInfo() const {        std::cout << "Name: " << name << ", Age: " << age << std::endl;    }};// 派生类class Student : public Person {private:    std::string studentId;public:    Student(std::string name_value, int age_value, std::string id)        : Person(name_value, age_value), studentId(id) {        std::cout << "Create Student with ID: " << studentId << std::endl;    }    ~Student() {        std::cout << "Destroy Student with ID: " << studentId << std::endl;    }    void displayInfo() const override {        std::cout << "Student Name: " << name << ", Age: " << age <<", ID: " << studentId << std::endl;    }};void displayInfo(Person* person) {    person->displayInfo();}int main() {    // 创建 Person 对象    Person* person = new Person("SiLi", 35);     // 创建 Student 对象    Person* student = new Student("SanZhang", 20, "A12138");  //面向对象的多态性允许不同的对象以相同的接口方式进行操作,但能做出不同的响应    displayInfo(person);    displayInfo(student);    delete person;    delete student;    return 0;}
Create Person with name: SiLi, age: 35Create Person with name: SanZhang, age: 20Create Student with ID: A12138Name: SiLi, Age: 35Student Name: SanZhang, Age: 20, ID: A12138Destroy Person with name: SiLi, age: 35Destroy Student with ID: A12138Destroy Person with name: SanZhang, age: 20

注: 继承的时候通常要将基类的析构函数定义为虚析构函数virtual ~Person(),这确保了当通过基类指针删除派生类对象时,派生类的析构函数也会被调用。这有助于防止资源泄露和其他与析构相关的问题。

抽象

抽象指的是将现实世界中的实体或概念转化为程序中对象的过程。这个过程中,我们识别并提取出这些实体或概念的共性,然后在程序中创建抽象类来代表这些共性。抽象类不能被实例化,但可以作为其他类的基类,为其他类提供一组通用的接口。这些接口一般是没有具体实现的纯虚函数,需要由派生类提供实现。

纯虚函数是在基类中声明的没有函数体(即没有定义)的虚函数,通常使用虚函数 = 0声明。纯虚函数必须在任何派生类中被重写,否则派生类也将成为抽象类(即不能被实例化)。

示例:

#include <iostream>#include <string>// 抽象基类 Personclass Person {protected:    std::string name;    int age;public:    Person(std::string name_value, int age_value) : name(name_value), age(age_value) {        std::cout << "Person created with name: " << name << ", age: " << age << std::endl;    }    virtual ~Person() {  // 虚析构函数        std::cout << "Person destroyed with name: " << name << ", age: " << age << std::endl;    }   // 纯虚函数    virtual void displayInfo() const = 0;};// 派生类class Student : public Person {private:    std::string studentId;public:    Student(std::string name_value, int age_value, std::string id)        : Person(name_value, age_value), studentId(id) {        std::cout << "Create Student with ID: " << studentId << std::endl;    }    ~Student() {        std::cout << "Destroy Student destroyed with ID: " << studentId << std::endl;    }    void displayInfo() const override {        std::cout << "Student Name: " << name << ", Age: " << age <<", ID: " << studentId << std::endl;    }};// 另一个派生类class Teacher : public Person {private:    std::string subject;public:    Teacher(std::string name_value, int age_value, std::string subj)        : Person(name_value, age_value), subject(subj) {        std::cout << "Create Teacher for subject: " << subject << std::endl;    }    ~Teacher() {        std::cout << "Destroy Teacher for subject: " << subject << std::endl;    }    void displayInfo() const override {        std::cout << "Teacher Name: " << name << ", Age: " << age <<", Subject: "<< subject << std::endl;    }};void displayInfo(Person* person) {    person->displayInfo();}int main() {    // `Person`类是一个抽象类,因此不能直接创建`Person`类的对象    // Person person("SiLi", 35); // 报错    // 创建 Student 对象    Person* student = new Student("SanZhang", 20, "A12138");    Person* teacher = new Teacher("SiLi", 35, "C++ Programming");    displayInfo(student);    displayInfo(teacher);    delete student;    delete teacher;        return 0;}
Person created with name: SanZhang, age: 20Create Student with ID: A12138Person created with name: SiLi, age: 35Create Teacher for subject: C++ ProgrammingStudent Name: SanZhang, Age: 20, ID: A12138Teacher Name: SiLi, Age: 35, Subject: C++ ProgrammingDestroy Student destroyed with ID: A12138Person destroyed with name: SanZhang, age: 20Destroy Teacher for subject: C++ ProgrammingPerson destroyed with name: SiLi, age: 35