在面向对象编程(Object-Oriented Programming, OOP)中,对象的四大基本特性是对象唯一性(Identity)、分类性(Classification)、继承性(Inheritance)和多态性(Polymorphism)。这些特性是理解和设计面向对象系统的基础。
### 1. 对象唯一性(Identity)
**概念**:
- 每个对象都有一个唯一的标识符,这个标识符在对象的生命周期内保持不变,即使对象的状态发生变化。这个标识符通常由内存地址实现。
- 不同的对象即使状态相同,它们的标识符也不同。
**示例**:
在C++中,可以通过对象的内存地址来表示唯一性。使用`&`运算符可以获得对象的地址。
```cpp
#include <iostream>
class MyClass {
public:
int value;
MyClass(int val) : value(val) {}
};
int main() {
MyClass obj1(10);
MyClass obj2(10);
std::cout << "Address of obj1: " << &obj1 << std::endl;
std::cout << "Address of obj2: " << &obj2 << std::endl;
return 0;
}
```
在这个例子中,`obj1`和`obj2`的值相同,但它们的地址(标识符)不同。
### 2. 分类性(Classification)
**概念**:
- 对象具有一致的数据结构(属性)和行为(方法),这些相似的对象可以被抽象为类(Class)。
- 类是对一组对象的抽象描述,定义了对象的属性和行为。
**示例**:
在C++中,类的定义如下:
```cpp
class Animal {
public:
std::string name;
int age;
void speak() {
std::cout << name << " says: Hello!" << std::endl;
}
};
```
在这个例子中,`Animal`类定义了一组具有`name`和`age`属性以及`speak`行为的对象。
### 3. 继承性(Inheritance)
**概念**:
- 继承性允许一个类(子类)继承另一个类(父类)的属性和方法,子类可以重用父类的代码,并可以扩展或修改父类的行为。
- 继承促进了代码重用和层次化设计。
**示例**:
在C++中,继承通过`:`实现:
```cpp
class Dog : public Animal {
public:
void bark() {
std::cout << name << " says: Woof!" << std::endl;
}
};
```
在这个例子中,`Dog`类继承了`Animal`的属性和方法,并增加了一个新的方法`bark`。
### 4. 多态性(Polymorphism)
**概念**:
- 多态性允许相同的操作作用于不同类型的对象并产生不同的结果。
- 通过多态性,可以在父类引用或指针上调用子类的方法,具体调用哪个方法由对象的实际类型决定。
**示例**:
在C++中,多态性通常通过虚函数实现:
```cpp
class Animal {
public:
virtual void speak() {
std::cout << "Animal speaks." << std::endl;
}
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Dog barks." << std::endl;
}
};
int main() {
Animal* animal = new Dog();
animal->speak(); // 调用的是 Dog 的 speak 方法
delete animal;
return 0;
}
```
在这个例子中,`Animal`类的`speak`方法被声明为虚函数,`Dog`类重写了`speak`方法。当通过`Animal`类型的指针调用`speak`时,实际调用的是`Dog`类的`speak`方法。
### 多态性(Polymorphism)
**概念**:
多态性是面向对象编程的一个核心特性,它允许同一个接口表现出不同的行为。多态性通常通过继承(Inheritance)和虚函数(Virtual Function)来实现。在C++中,多态性可以分为编译时多态性(静态多态性)和运行时多态性(动态多态性)。
**先修知识**:
1. **继承**:子类继承父类的属性和方法,可以重载或重写父类的方法。
2. **虚函数**:在基类中使用`virtual`关键字声明的函数,允许在派生类中重写,并在运行时进行动态绑定。
3. **指针和引用**:通过基类的指针或引用来指向派生类对象。这是实现运行时多态性的关键。
### 编译时多态性
编译时多态性主要通过函数重载和模板实现。编译器在编译时决定调用哪个函数。
#### 示例:函数重载
```cpp
#include <iostream>
class Print {
public:
void show(int i) {
std::cout << "Integer: " << i << std::endl;
}
void show(double d) {
std::cout << "Double: " << d << std::endl;
}
};
int main() {
Print printer;
printer.show(10); // 调用 show(int)
printer.show(3.14); // 调用 show(double)
return 0;
}
```
- **函数重载**:`show`函数有两个版本,一个接受`int`参数,一个接受`double`参数。在编译时,根据传入参数的类型决定调用哪个版本。
### 运行时多态性
运行时多态性通过继承和虚函数来实现。在运行时,根据对象的实际类型调用相应的函数版本。
#### 示例:虚函数
```cpp
#include <iostream>
class Base {
public:
virtual void display() const { // 虚函数
std::cout << "Display from Base" << std::endl;
}
};
class Derived : public Base {
public:
void display() const override { // 重写父类的虚函数
std::cout << "Display from Derived" << std::endl;
}
};
int main() {
Base* basePtr;
Derived derivedObj;
basePtr = &derivedObj;
basePtr->display(); // 调用Derived的display()
return 0;
}
```
**逐行解释**:
- `virtual void display() const {...}`:基类中的虚函数,允许子类重写。
- `void display() const override {...}`:派生类中的重写函数,`override`关键字增强代码可读性和安全性(C++11引入)。
- `Base* basePtr;`:声明一个基类指针。
- `basePtr = &derivedObj;`:基类指针指向派生类对象。
- `basePtr->display();`:调用派生类的`display()`,这是多态性的体现。由于`display()`是虚函数,运行时根据`basePtr`的实际指向对象(`derivedObj`)调用正确的函数版本。
### 应用场景
1. **接口的统一**:多态性允许不同的类实现相同的接口方法,从而使代码更加灵活和可扩展。
2. **库和框架设计**:在设计库和框架时,经常使用多态性来定义灵活的接口,允许用户定义自定义行为。
3. **事件驱动编程**:在GUI和游戏开发中,多态性用于处理不同的事件和用户交互。
### 类似题目设计
**题目**:设计一个动物类`Animal`,并派生两个类`Dog`和`Cat`,每个类都实现一个`makeSound`方法:`Dog`类输出"Woof!",`Cat`类输出"Meow!"。在`main`函数中,使用基类指针调用派生类的方法,展示多态性。
**思路**:
1. 定义基类`Animal`,并声明一个虚函数`makeSound`。
2. 定义派生类`Dog`和`Cat`,分别重写`makeSound`。
3. 在`main`中创建`Dog`和`Cat`对象,并使用`Animal`指针调用。
**代码**:
```cpp
#include <iostream>
#include <vector> // 用于存储对象指针的容器
class Animal {
public:
virtual void makeSound() const { // 虚函数
std::cout << "Some generic animal sound" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() const override { // 重写makeSound
std::cout << "Woof!" << std::endl;
}
};
class Cat : public Animal {
public:
void makeSound() const override { // 重写makeSound
std::cout << "Meow!" << std::endl;
}
};
int main() {
std::vector<Animal*> animals; // 创建一个Animal指针的向量
Dog dog;
Cat cat;
animals.push_back(&dog);
animals.push_back(&cat);
for (Animal* animal : animals) {
animal->makeSound(); // 调用实际对象的makeSound
}
return 0;
}
```
**逐行注释**:
- `class Animal {...}`:定义基类`Animal`。
- `virtual void makeSound() const {...}`:声明虚函数`makeSound`。
- `class Dog : public Animal {...}`:定义派生类`Dog`。
- `void makeSound() const override {...}`:在`Dog`中重写`makeSound`。
- `class Cat : public Animal {...}`:定义派生类`Cat`。
- `std::vector<Animal*> animals;`:创建一个存储`Animal`指针的向量。
- `animals.push_back(&dog);`:将`Dog`对象的地址添加到向量中。
- `animals.push_back(&cat);`:将`Cat`对象的地址添加到向量中。
- `for (Animal* animal : animals) {...}`:遍历向量,调用每个对象的`makeSound`方法。
通过这种方式,我们展示了多态性的强大之处:使用统一的接口来处理不同类型的对象。这种灵活性使得程序更具扩展性和可维护性。希望这些信息对你理解多态性有所帮助!如果你有任何疑问或需要进一步解释,请随时告知。