跳转至

0、面向对象概念简介

​ 面向对象编程(Object-Oriented Programming,简称OOP)是一种计算机编程范式,它将数据和行为封装在一个对象中,并允许对象之间相互交互,以实现复杂的功能。面向对象编程有以下四个特性:

1.封装(Encapsulation)

​ 封装是指将数据和对数据的操作封装在一个对象中,对外界隐藏对象的内部实现细节,使得对象只对外界暴露必要的接口。这样可以避免外界直接访问对象的内部数据,提高了代码的安全性和可维护性。

​ 在C++中,封装主要通过来实现。类定义了数据操作数据的方法,数据通常被定义为私有的(private),而操作数据的方法则被定义为公共的(public)。私有成员只能在类的内部访问,而公共成员可以在类的外部访问。这种访问控制使得类的用户只能通过类提供的公共接口来访问和修改数据,从而达到了封装的目的。

下面是一个简单的例子,演示了如何通过类来实现封装:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

class MyClass {
private:
    int data;   // 私有成员变量,外界无法访问

public:
    void setData(int value) {   // 公共成员函数,用于修改私有成员变量
        data = value;
    }

    int getData() {   // 公共成员函数,用于访问私有成员变量
        return data;
    }
};

int main() {
    MyClass obj;
    obj.setData(10);   // 通过公共接口设置私有成员变量
    cout << obj.getData() << endl;   // 通过公共接口访问私有成员变量
    return 0;
}

​ 在上面的代码中,MyClass是一个类,它有一个私有成员变量data和两个公共成员函数setData和getData。setData函数用于设置私有成员变量data的值,而getData函数用于获取私有成员变量data的值。由于data是私有成员变量,因此它无法直接被外界访问和修改。在程序中,我们通过MyClass类提供的公共接口来访问和修改data变量的值。

​ 需要注意的是,封装并不是完全阻止外界对类的私有成员变量的访问和修改,而是通过访问控制和公共接口来限制和管理这种访问和修改。在实际开发中,为了进一步提高封装的安全性和完整性,还需要使用其他技术,比如继承、多态等。

2.继承(Inheritance)

​ 继承是指通过建立一个类与类之间的父子关系,使得子类可以继承父类的属性和方法,从而减少了代码的重复性。子类可以通过重写父类的方法来实现不同的行为,从而实现了多态。

​ 继承是一种面向对象编程中的重要概念,它允许程序员创建一个新类,该类从一个或多个现有类继承了属性和方法,并且可以添加新的成员变量和成员函数来扩展或修改现有类的功能。在C++中,继承可以通过以下方式实现:

  1. 基类和派生类:基类是被继承的类,派生类是从基类派生的新类。派生类可以访问基类的公有和受保护的成员。
  2. 继承方式:C++支持三种继承方式:公有继承、私有继承和保护继承。公有继承意味着基类的公有成员在派生类中仍然是公有的,私有继承意味着基类的公有和受保护成员在派生类中都变为私有的,保护继承意味着基类的公有和受保护成员在派生类中都变为受保护的。
  3. 基类构造函数:派生类的构造函数可以调用基类的构造函数来初始化继承自基类的成员。在派生类的构造函数中,可以使用初始化列表或者在函数体内部调用基类的构造函数。
  4. 虚函数:虚函数是一种特殊的成员函数,它可以被派生类覆盖并在运行时动态绑定。通过在基类中将函数声明为虚函数,派生类可以覆盖该函数并实现自己的版本。
  5. 虚析构函数:在继承层次结构中,如果基类有虚析构函数,派生类的析构函数也应该是虚的。这可以确保当使用指向派生类对象的基类指针时,正确地调用派生类的析构函数以释放内存。

​ 继承使得程序员可以重用代码并且可以使代码更加容易维护。C++中的继承是一个非常强大的概念,但是在使用时需要谨慎,因为继承关系可能会变得很复杂,同时也可能会破坏封装性。

下面是一个简单的例子,演示了 C++ 中的继承:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;

class Shape {
   public:
      void setWidth(int w) {
         width = w;
      }
      void setHeight(int h) {
         height = h;
      }
   protected:
      int width;
      int height;
};

class Rectangle: public Shape {
   public:
      int getArea() {
         return (width * height);
      }
};

int main() {
   Rectangle Rect;

   Rect.setWidth(5);
   Rect.setHeight(7);

   cout << "Total area: " << Rect.getArea() << endl;

   return 0;
}

​ 在这个例子中,我们定义了一个基类 Shape,其中包含了两个属性 widthheight,以及两个成员函数 setWidthsetHeight,用于设置 widthheight 的值。然后我们定义了一个派生类 Rectangle,它继承了 Shape 类,并在其中定义了一个成员函数 getArea,用于计算面积。

3.多态(Polymorphism)

​ 多态是指同一消息在不同的对象上有不同的行为。通过继承和接口实现,一个对象可以对不同的消息作出不同的响应,从而提高了代码的灵活性和可扩展性。多态可以通过虚函数和函数重载等方式实现。

​ 在C++中,多态性可以通过虚函数纯虚函数实现。虚函数是一个特殊的成员函数,可以在子类中重写父类的函数,从而使得不同的子类对象对同一个消息作出不同的响应。而纯虚函数是一个没有实现的虚函数,它的作用是让子类强制实现该函数,从而达到约束子类行为的目的。

1.虚函数

虚函数是通过在函数声明前面加上关键字virtual来定义的。例如:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Animal {
public:
  virtual void speak() {
    std::cout << "I am an animal." << std::endl;
  }
};

class Cat : public Animal {
public:
  void speak() override {
    std::cout << "Meow!" << std::endl;
  }
};

​ 上面的代码定义了一个Animal类和一个Cat类。Animal类中的speak函数被声明为虚函数,而Cat类中的speak函数重写了父类的虚函数。这意味着当我们调用一个指向Cat对象的Animal指针的speak函数时,实际上会调用Cat类中的speak函数。例如:

C++
1
2
Animal* animal = new Cat();
animal->speak();  // 输出 "Meow!"
2.纯虚函数

纯虚函数是通过在函数声明前面加上关键字virtual并在函数体中赋值0来定义的。例如:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Shape {
public:
  virtual double area() const = 0;
};

class Circle : public Shape {
public:
  double area() const override {
    return radius * radius * 3.14;
  }
private:
  double radius;
};

​ 上面的代码定义了一个Shape类和一个Circle类。Shape类中的area函数被声明为纯虚函数,这意味着它没有实现,而Circle类中的area函数实现了计算圆形面积的功能。这样,任何继承自Shape类的子类都必须实现自己的area函数,从而达到约束子类行为的目的。例如:

C++
1
2
Shape* shape = new Circle();
double a = shape->area();

​ 上面的代码通过指向Circle对象的Shape指针来计算圆形的面积。由于Shape类中的area函数是纯虚函数,因此任何Shape类的子类都必须实现自己的area函数,从而使得该代码在运行时可以调用正确的子类的函数。

4.抽象(Abstraction)

​ 抽象是面向对象编程中的一个重要概念,指的是从具体的实例中提取出普遍特性和规律,将其提炼成抽象的概念和模型。抽象的目的是为了降低复杂度、提高可扩展性和可维护性。

​ 在面向对象编程中,抽象通过抽象类和接口来实现。抽象类是指不能被实例化的类,其中可以定义抽象方法和非抽象方法。抽象方法是没有实现的方法,只有方法的签名,需要子类实现具体的方法体。非抽象方法则是有实现的方法,可以直接被调用。接口则是一种特殊的抽象类,其中只包含抽象方法,没有任何实现。

​ 通过使用抽象类和接口,可以将系统中的对象分为多个抽象层次,每个层次具有一定的抽象度,可以更好地组织和管理系统的对象,同时也方便了代码的维护和扩展。

以下是一个简单的 C++ 代码示例,用于演示类、继承和抽象的概念:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
using namespace std;

// 基类 Animal
class Animal {
public:
    virtual void sound() = 0;  // 纯虚函数,用于抽象

    void eat() {
        cout << "I am eating..." << endl;
    }
};

// 派生类 Dog
class Dog : public Animal {
public:
    void sound() {
        cout << "The dog barks." << endl;
    }
};

// 派生类 Cat
class Cat : public Animal {
public:
    void sound() {
        cout << "The cat meows." << endl;
    }
};

int main() {
    Animal* animal;
    Dog dog;
    Cat cat;

    // 动态绑定
    animal = &dog;
    animal->sound();  // 输出:The dog barks.

    animal = &cat;
    animal->sound();  // 输出:The cat meows.

    return 0;
}

​ 在这个例子中,我们定义了一个基类 Animal,其中包含一个纯虚函数 sound() 和一个实现函数 eat(),以及两个派生类 DogCat,它们分别实现了 sound() 函数。Animal 类是一个抽象类,因为它包含一个纯虚函数。纯虚函数没有函数体,必须在派生类中进行实现。我们可以使用 virtual 关键字定义虚函数,在派生类中使用 override 关键字重写该函数,从而实现多态性。Animal* 是一个指向 Animal 类型对象的指针,我们可以使用它来动态地选择要调用的函数。在主函数中,我们定义了 DogCat 的对象,并使用 Animal* 指针调用它们的 sound() 函数,这是运行时多态的一个例子。