封装、继承、多态、重载:C++中的强大特性与代码设计

C/C++
164
0
0
2024-03-12
这里写目录标题
  • 封装
  • C语言封装风格
  • C++封装
  • 继承
  • 多态
  • 多态的实现
  • 虚函数概念:
  • 虚表指针
  • overload
  • overwrite
  • override
  • 抽象类
  • 重载

封装

C++中的封装是一种面向对象编程的概念,它将数据(成员变量)和操作(成员函数)封装在一个类中,通过访问控制来限制对类内部实现的访问。封装提供了类与外部世界之间的接口,隐藏了类的内部实现细节,提高了代码的可维护性和安全性。

在C++中,封装可以通过使用访问修饰符(public、private、protected)来实现:

public(公有)访问修饰符允许类的成员在类的外部被访问,也可以在类的内部被访问。通常将公有成员函数作为类的接口,供外部使用。 private(私有)访问修饰符将类的成员隐藏在类的内部,外部无法直接访问私有成员变量和私有成员函数。通常将私有成员用于实现类的内部逻辑。 protected(保护)访问修饰符与私有访问修饰符类似,但允许派生类(子类)访问基类中的保护成员。 下面是一个简单的封装示例:

class MyClass {
private:
    int privateData;  // 私有成员变量

public:
    void setPrivateData(int data) {  // 公有成员函数
        privateData = data;
    }

    int getPrivateData() const {  // 公有成员函数
        return privateData;
    }
};

int main() {
    MyClass obj;
    obj.setPrivateData(42);
    int data = obj.getPrivateData();
    return 0;
}

在上面的示例中,私有成员变量privateData被封装在类MyClass中,外部无法直接访问。通过公有成员函数setPrivateData和getPrivateData来操作私有成员变量。

封装可以提供更好的代码组织和管理方式,同时也增强了代码的安全性,因为外部代码无法直接修改和访问类的内部数据。

C语言封装风格

当单一变量无法完成描述需求的时候,结构体类型解决了这问题。可以将多个类型打包成一体,形成新的类型,这是c语言中封装的概念。但是,新类型并不包含对数据类的操作,所有操作都是通过函数的方式,去进行封装。

   #include<iostream>
   #include<string>
   using namespace std;
   
   struct person
   {
       string name;
       int age;
       int height;
  };
  void init(person* p)
  {
      p->name = "jiejie";
      p->age = 20;
      p->height = 130;
  }
  void show(person p)
  {
      cout<<"p.name = "<<p.name<<endl;
      cout<<"p.age = "<<p.age<<endl;
      cout<<"p.height = "<<p.height<<endl;
  }
  int main()
  {
      person per;
      init(&per);
      show(per);
  
      return 0;
  }

把数据放到一起用struct包装,然后把数据以引用或指针的方式传给行为。

C++封装

C++的封装认为C语言的封装不彻底,对于数据和行为分类,没有权限控制。 C++则提供控制选择,将数据和行为放在一起,对内开放数据,逻辑抽象。对外提供接口

   #include<iostream>
   using namespace std;
   
   class person
   {   
       public: 
           int num;
           void init()
           {
              cin>>this->num;
              cin>>this->age;
              cin>>this->height;
          }
          void show()
          {
              cout<<num<<endl;
              cout<<age<<endl;
              cout<<height<<endl;
          }
      
      protected:
          int age;
      private:
          int height;
  };
  int main()
  {
      person p;
      p.init();
      p.show();
      return 0;
  }

基本流程: 通过创建一个类,再通过创建类对象 再由对象调用行为,完成需求。

继承

C++中的继承是面向对象编程的一个重要概念,它允许一个类(派生类/子类)从另一个类(基类/父类)继承属性和行为。继承可以通过创建一个新类并从基类派生来实现,新类将自动获得基类的成员变量和成员函数,同时可以添加新的成员或重写基类的成员函数。

继承的主要目的是实现代码的重用和构建类之间的层次关系。在继承关系中,基类被称为超类或父类,派生类被称为子类。 一般定义格式如下:

class 派生类名标识符: [继承方式] 基类名标识符
{
[访问控制修饰符]
[成员声明列表]
};

继承方式有3种,分别为公有型,保护型和私有型,访问控制修饰符也是public,protected,private 类型。 成员声明列表中包含类成员变量以及成员函数,是派生类新增的成员。 “:”是一个运算符,表示基类和派生类之间的继承关系。

C++中的继承有以下几种类型:

公有继承(public inheritance):派生类继承了基类的公有成员和保护成员,并且这些成员在派生类中的访问权限与基类中的一样。派生类的对象可以直接访问基类的公有成员。

私有继承(private inheritance):派生类继承了基类的公有成员和保护成员,但是这些成员在派生类中的访问权限变为私有。派生类的对象不能直接访问基类的公有成员,只能通过派生类的成员函数来间接访问。

保护继承(protected inheritance):派生类继承了基类的公有成员和保护成员,但是这些成员在派生类中的访问权限变为保护。派生类的对象不能直接访问基类的公有成员,只能通过派生类的成员函数来间接访问。

在C++中,使用关键字class或struct来定义一个类,并使用关键字public、private或protected来指定成员的访问权限。

下面是一个简单的继承示例:

class Shape {
public:
    void draw() {
        std::cout << "Drawing a shape" << std::endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() {
        std::cout << "Drawing a rectangle" << std::endl;
    }
};

int main() {
    Shape shape;
    shape.draw();  // 输出:"Drawing a shape"

    Rectangle rectangle;
    rectangle.draw();  // 输出:"Drawing a rectangle"

    return 0;
}

在上面的示例中,Shape类是基类,它有一个公有成员函数draw。Rectangle类是派生类,通过public关键字继承了Shape类。Rectangle类重写了draw函数,实现了自己的绘制行为。在main函数中,我们分别创建了Shape对象和Rectangle对象,并调用了它们的draw函数。

继承是一种强大的代码复用工具,它可以使得类之间的关系更加清晰和有组织。通过继承,派生类可以继承基类的接口和实现,并且可以添加自己的功能或修改基类的行为。

继承是面对对象的主要特性之一,它使一个类可以从现有类中派生,而不必重新定义一个类。

实质:用已有的数据类型创建新的数据类型,并保留已有数据类型的特点,以旧类为基础创建新类,新类包含旧类的数据成员和成员函数。并且·可以在新类中添加新的数据成员和成员函数。 旧类被称为基类或者父类,新类被称为派生类或者子类。

多态

C++中的多态是面向对象编程的一个重要概念,它允许使用指针或引用来处理不同类型的对象,而实际上执行的是根据对象类型动态选择的相关操作。多态性可以提高代码的灵活性、可复用性和可扩展性。

C++中的多态性主要通过虚函数(virtual function)和运行时类型识别(runtime type identification)两个机制来实现:

虚函数:在基类中声明虚函数,并在派生类中进行重写。当通过指向基类的指针或引用调用虚函数时,将根据实际对象的类型来选择正确的函数实现。这种动态选择函数的机制称为动态绑定(dynamic binding)。虚函数通过关键字virtual进行声明。

运行时类型识别(RTTI):C++提供了dynamic_cast和typeid两个运算符来进行运行时类型识别。dynamic_cast用于将基类指针或引用转换为派生类指针或引用,如果转换成功,返回指向派生类的指针或引用;如果转换失败,则返回空指针或抛出bad_cast异常。typeid用于获取表达式的实际类型。

下面是一个简单的多态示例:

#include <iostream>

class Shape {
public:
    virtual void draw() {
        std::cout << "Drawing a shape" << std::endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a rectangle" << std::endl;
    }
};

class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a circle" << std::endl;
    }
};

int main() {
    Shape* shapePtr;

    Rectangle rectangle;
    Circle circle;

    shapePtr = &rectangle;
    shapePtr->draw();  // 输出:"Drawing a rectangle"

    shapePtr = &circle;
    shapePtr->draw();  // 输出:"Drawing a circle"

    return 0;
}

在上面的示例中,Shape类有一个虚函数draw,Rectangle和Circle类分别是派生自Shape的两个子类,并重写了draw函数。在main函数中,我们使用指向基类的指针shapePtr来处理不同类型的对象。通过将shapePtr指向Rectangle对象和Circle对象,并调用draw函数,会根据对象的实际类型选择正确的函数实现。

多态性使得我们可以以一种统一的方式来处理不同类型的对象,而不需要关心对象的具体类型。这样可以使代码更具灵活性和可维护性,同时提供了一种机制来实现运行时的动态行为。

多态的实现

静态绑定:在编译期决定 函数重载 运算符重载 模板 动态绑定:在程序运行时执行 虚函数

虚函数概念:

在基类中冠以关键字virtual的成员函数 定义: virtual函数类型 函数名称(参数列表) 如果一个函数在基类中被声明为虚函数,则它在所有派生类中都是虚函数。 只有通过基类指针或引用调用虚函数才能引发动态绑定 虚函数不能声明为静态 如果一个类要做为多态基类,要将析构函数定义为虚函数

虚表指针

虚函数的动态绑定是通过虚表来实现的 包含虚函数的类头4个字节存放指向虚表的指针

overload

成员函数被重载的特征: 相同的范围(在同一类中) 函数名字相同 参数不同 virtual关键字可有可无

overwrite

覆盖是指派生类函数覆盖基类函数 特征是: 不同的范围 函数名字相同 参数相同 基类函数必须有virtual关键字

override

重定义(派生类与基类) 不同的范围(分贝位于派生类和基类) 函数名与参数都相同,无virtual关键字 函数名相同,参数不同,virtual可有可无

抽象类

作用: 抽象类作为抽象设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现 注意: 抽象类只能作为基类来使用 不能声明抽象类的对象,可以使用抽象类的指针和引用 构造函数不能是虚函数,析构函数可以是虚函数 可以试用指向抽象类的指针支持运行时多态性 派生类中必须实现基类中的纯虚函数,否则被看做一个抽象类

对于一个没有任何接口的类,如果想要将它定义成抽象类,只能将虚构函数声明为纯虚的 通常情况下在基类中纯虚函数不需要实现,例外是纯析构函数要给出实现

重载

在C++中,函数重载(Function Overloading)是指在同一个作用域内,可以定义多个具有相同名称但参数列表不同的函数。通过函数重载,可以根据参数的类型、顺序和个数来区分不同的函数,并且可以为相同的操作提供不同的实现。

函数重载的特点如下:

函数名称相同:重载的函数必须具有相同的名称。

参数列表不同:重载的函数必须具有不同的参数列表,可以通过参数的类型、顺序和个数的不同来区分。

返回值类型不同不足以实现函数重载:函数的返回值类型不能作为重载函数的区分标准,因为函数调用时通常会忽略返回值。

举例如下所示:

#include<iostream>
using namespace std;

class interage
{
    int inter;
    public:
        interage(int in)
        {
            inter = in;
        };
        ~interage(){};
        interage& operator++()
        {
            ++inter;
            return *this; 
        }
        interage operator++(int in)
        {
            interage temp(inter);
            inter++;
            return temp;
        }

        void display()
        {
            cout<<inter<<endl;
        }

};
int main()
{
    interage in(10);
    in.display();
    interage in2 = ++(++in);
    in2.display();
    
    interage in3 = in2++;
    in3.display();
    in3.display();
    return 0;
}

通过函数重载,我们可以在C++中实现更加灵活和易于使用的代码。函数重载允许我们使用相同的函数名,但根据参数的类型、顺序和个数来区分不同的函数。这样,我们可以提供不同的函数实现来处理各种情况,而无需为每种情况编写不同的函数名称。

函数重载使得代码更具可读性和可维护性,同时提供了一种方便的方式来进行函数扩展和适应不同的需求。然而,在使用函数重载时,需要注意避免产生歧义或混淆,确保函数之间的区分明确。

总而言之,函数重载是C++中一个强大的特性,可以使代码更加灵活和易于使用,为我们提供了更多的选择和可能性。合理地利用函数重载,可以使我们的代码更加清晰、高效,同时提高开发效率和代码的可维护性。