C++ 面向对象程序设计

面向对象程序设计(Object-Oriented Programming, OOP)是一种编程范式,其关键的基本概念是类(Class)和对象(Object)。这种编程范式在 C++ 中得到了广泛的应用。在面向对象的思想中,我们把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

一、面向对象与面向过程的区别

面向对象是一种符合人类思维习惯的程序设计思想。现实生活中存在各种形态不同的事物,这些事物之间存在着各种各样的联系。在程序中使用对象映射现实中的事物,利用对象之间的关系描述事物之间的联系,这种思想就是面向对象。

面向过程是分析出解决问题所需要的步骤,然后用函数把这些步骤一一实现,使用的时候依次调用就可以了。

面向对象不同于面向过程,它是把构成问题的事物按照一定规则划分为多个独立的对象,然后通过调用对象的方法解决问题。当然,一个应用程序会包含多个对象,通过多个对象的相互配合即可实现应用程序所需的功能,这样当应用程序功能发生变动时,只需要修改个别对象就可以了,使代码更容易维护。

二、C++中的面向对象程序设计思想有三大特征:

1、封装:封装是面向对象程序设计中的一个重要特性,它允许我们将数据(变量)和操作数据的方法(函数)绑定在一起形成一个整体,这个整体就是我们所说的对象。

在C++中,类是实现封装的主要工具。类定义了一种新的类型,其中包含了数据成员(成员变量)和成员函数。在类的外部,我们不能直接访问类的数据成员,而只能通过成员函数来访问,这就实现了数据的隐藏和保护。这种隐藏和保护的级别可以通过访问修饰符(private、protected、public)来调整。

class MyClass {
private:
    int myVariable;  // 私有数据成员,只能在类的成员函数中直接访问

public:
    void setMyVariable(int value) {  // 公有成员函数,可以从类的外部调用
        myVariable = value;
    }

    int getMyVariable() {  // 公有成员函数,可以从类的外部调用
        return myVariable;
    }
};

在这个例子中,myVariable 是私有的,不能从类的外部直接访问,我们只能通过 setMyVariablegetMyVariable 这两个公有的成员函数来设置和获取 myVariable 的值。

封装的好处主要有两点:

①提高安全性:通过隐藏类的内部实现细节,我们可以防止外部代码误操作类的内部数据。

②提高可维护性:外部代码通过类的公有接口与类进行交互,这样即使类的内部实现发生改变,也不会影响到外部代码。

因此,封装是一种非常重要的编程技巧,是构建健壮、可维护代码的关键。

2、继承:在C++中,继承是一种创建新类(称为派生类)的机制,派生类继承了一个或多个已存在的类(称为基类)的属性和行为。

继承的主要目的是为了代码的复用,同时也可以实现类型的泛化和特化。泛化是指从多个类中提取公共部分形成一个更一般的类,这个过程也叫做提取公共基类。特化则是在基类的基础上增加新的属性和行为形成一个更具体的类。

以下面的例子为例:

class Animal {
public:
    void eat() {
        cout << "I can eat!" << endl;
    }

    void sleep() {
        cout << "I can sleep!" << endl;
    }
};

class Dog : public Animal {
public:
    void bark() {
        cout << "I can bark! Woof!" << endl;
    }
};

在这个例子中,Animal 是基类,Dog 是派生类。Dog 类通过公有继承 Animal 类,Dog 类就继承了 Animal 类的所有公有和保护成员。这意味着 Dog 类的对象可以像 Animal 类的对象一样吃(eat)和睡觉(sleep),同时 Dog 类还增加了新的行为:叫(bark)。

需要注意的是,在C++中有三种类型的继承:公有继承(public)、保护继承(protected)和私有继承(private)。

它们主要区别在于基类的成员在派生类中的访问级别。公有继承是最常用的,基类的公有成员和保护成员在派生类中保持原有的访问级别。保护继承和私有继承相对少用,它们会分别将基类的公有成员和保护成员变为派生类的保护成员和私有成员。

此外,C++支持多继承,也就是说一个派生类可以有多个基类。但是,多继承可能导致一些复杂的问题(如菱形继承问题),因此在设计类的继承关系时应谨慎使用多继承。

3、多态:多态是面向对象编程中的一个重要概念,允许我们以统一的方式处理不同类型的对象。在 C++ 中,多态主要表现为两种形式:编译时多态(静态多态)和运行时多态(动态多态)。

编译时多态:主要通过函数重载和模板实现。函数重载允许多个函数拥有相同的函数名但参数列表不同,编译器会根据函数的参数列表在编译时决定调用哪个函数。模板则允许我们定义一种模式,使得函数或类可以以多种数据类型进行操作。

函数重载是指在同一个作用域内,可以有多个同名函数,只要它们的参数列表不同即可。编译器会根据传入参数的类型和数量来决定调用哪个重载版本的函数。这种多态性发生在编译阶段,因此被称为编译时多态。

例如:

void print(int a) {
    std::cout << "Printing int: " << a << std::endl;
}

void print(double a) {
    std::cout << "Printing double: " << a << std::endl;
}

void print(const std::string& str) {
    std::cout << "Printing string: " << str << std::endl;
}

int main() {
    int a = 5;
    double b = 3.14;
    std::string c = "Hello, World!";

    print(a); // 调用 print(int) 函数
    print(b); // 调用 print(double) 函数
    print(c); // 调用 print(const std::string&) 函数

    return 0;
}

模板是C++中一种泛型编程的特性,它允许我们定义通用的函数或类,而不需要为每一种数据类型都重新编写相同的代码。模板在编译时会根据实际使用的类型生成对应的具体实现。这种多态性同样发生在编译阶段,因此也属于编译时多态。模板可以分为函数模板和类模板。

例如:

函数模板

template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    int a = 1, b = 2;
    double c = 1.5, d = 2.5;

    std::cout << add(a, b) << std::endl; // 调用 add<int> 函数
    std::cout << add(c, d) << std::endl; // 调用 add<double> 函数

    return 0;
}

类模板


template <typename T>
class MyContainer {
public:
    MyContainer(T item) : item_(item) {}

    T getItem() {
        return item_;
    }

private:
    T item_;
};

int main() {
    MyContainer<int> intContainer(42);
    MyContainer

运行时多态:主要通过虚函数(Virtual Functions)和抽象类(Abstract Classes)实现。通过将基类中的函数声明为虚函数,派生类就可以重写(override)这个函数,实现与基类不同的行为。在运行时,编译器会根据对象的实际类型决定调用哪个版本的虚函数,这就是运行时多态。

以下是一个运行时多态的例子:

class Animal {
public:
    virtual void makeSound() {
        cout << "The animal makes a sound." << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {
        cout << "The dog barks." << endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override {
        cout << "The cat meows." << endl;
    }
};

在这个例子中,Animal 是一个基类,定义了一个虚函数 makeSoundDogCat 都是 Animal 的派生类,它们分别重写了 makeSound 函数。现在,我们可以用 Animal 类型的指针或引用来调用 makeSound 函数,而在运行时,会根据实际的对象类型调用相应的函数版本。

Animal* animal1 = new Dog();
Animal* animal2 = new Cat();
animal1->makeSound();  // 输出 "The dog barks."
animal2->makeSound();  // 输出 "The cat meows."

这就是运行时多态的典型应用,它允许我们以一种统一和灵活的方式处理不同类型的对象。

猜你喜欢

转载自blog.csdn.net/2201_75772333/article/details/130474045