前言
关于多态,是c++中最重要的东西,通过虚函数来实现多态这种特性
一、多态的概念
多态是面向对象编程(OOP)中的一个重要概念,它允许对象在不同的上下文中以不同的方式表现。这意味着相同的接口(函数、方法)可以在不同的类中有不同的实现,从而实现不同的行为。
在 C++ 中,多态主要有两种形式:
1. 编译时多态(静态多态)
编译时多态通过函数重载和模板实现。其特点是在编译阶段决定函数的具体调用方式。主要的实现方式有:
函数重载:多个函数具有相同的名称,但参数类型或个数不同。编译器会根据调用时提供的参数选择合适的函数版本。
void print(int i) {std::cout << "Integer: " << i << std::endl;
}void print(double d) {std::cout << "Double: " << d << std::endl;
}
模板:使用模板可以编写通用的代码,编译器根据传入的类型生成具体的函数或类。
template <typename T>
void print(T value) {std::cout << value << std::endl;
}
2. 运行时多态(动态多态)
运行时多态通过虚函数和继承实现。其特点是程序运行时才确定调用哪个函数。这是通过基类指针或引用指向派生类对象,并使用虚函数实现的。
虚函数(virtual functions):如果基类的函数被声明为虚函数,那么派生类可以覆盖该函数。在运行时,C++ 会根据实际对象的类型调用对应的派生类函数,而不是基类中的函数。
#include <iostream>
using namespace std;// 基类定义
class Animal {
public:// 定义虚函数virtual void sound() {cout << "Animal makes a sound" << endl;}// 普通函数void sleep() {cout << "Animal sleeps" << endl;}
};// 派生类 Dog
class Dog : public Animal {
public:// 覆盖基类的虚函数void sound() override {cout << "Dog barks" << endl;}
};// 派生类 Cat
class Cat : public Animal {
public:// 覆盖基类的虚函数void sound() override {cout << "Cat meows" << endl;}
};int main() {Animal* animal1 = new Dog(); // 基类指针指向派生类对象Animal* animal2 = new Cat(); // 基类指针指向派生类对象animal1->sound(); // 输出: Dog barksanimal2->sound(); // 输出: Cat meowsanimal1->sleep(); // 输出: Animal sleepsanimal2->sleep(); // 输出: Animal sleepsdelete animal1;delete animal2;return 0;
}
二、模板
为什么要在这里说到模板呢,是因为我本人对模板这个操作实现不怎么熟悉,尤其涉及到引用,对于引用,我无法和指针进行区分,但是这种方法在c++中用到比较多,所以必须克服
一、模板的基本概念
1. 函数模板
函数模板允许你编写一个函数,它可以接受不同类型的参数,而不必为每种类型编写不同的函数版本。
通常我们写一个函数是这样写的
void swap(int &a, int &b) {int temp = a;a = b;b = temp;
}
但是我们用了模板之后 ,用一个占位符来表示任意类型
template <typename T> // T 是占位符,表示任意类型
void swap(T &a, T &b) {T temp = a;a = b;b = temp;
}
上面这个就是一个函数模板,T
是一个模板参数,可以是任何类型。当你调用 swap
函数时,编译器会自动推导出具体的类型:
int x = 10, y = 20;
swap(x, y); // T 被推导为 intdouble a = 1.1, b = 2.2;
swap(a, b); // T 被推导为 double
2. 类模板
类模板类似于函数模板,但它用于创建一组通用类。例如,一个简单的栈类可以定义为模板类,使其可以处理不同类型的数据
template <typename T>
class Stack {
private:T arr[100]; // 数组存储元素,类型为 Tint top;
public:Stack() : top(-1) {}void push(T val) {arr[++top] = val;}T pop() {return arr[top--];}
};
你就可以用 int
、double
或其他类型创建一个栈对象:
Stack<int> intStack;
intStack.push(10);
intStack.push(20);Stack<double> doubleStack;
doubleStack.push(3.14);
doubleStack.push(2.71);
二、引用在模板中的使用
引用在 C++ 中是一种指向变量的别名,它允许你直接操作变量的原始值,而不是它的副本。当模板结合引用使用时,特别在函数模板中,它能让我们更加高效地处理大型对象,因为我们不必复制整个对象。这里就是我无法区分的地方,是因为我对引用的概念不怎么熟悉,
- 指针是一个变量,存储了另一个变量的内存地址,你可以对指针进行操作(如解引用、改变地址),并且它可以是
nullptr
。 - 引用是另一个变量的别名,必须在声明时绑定一个已有的变量,并且之后不能更改引用所指向的对象。引用更加直接和简洁。
1. 引用作为函数模板参数
当使用模板时,通常我们会传递对象的引用来避免复制。例如,当你传递一个大的对象(如一个 std::vector
)给函数时,直接传引用可以提高性能,并且允许函数修改原始对象:
template <typename T>
void printVector(const std::vector<T> &vec) { // 使用引用来避免拷贝for (const T &element : vec) {std::cout << element << " ";}std::cout << std::endl;
}
在上面的例子中,const std::vector<T> &vec
是一个常量引用,表示该函数不会修改传入的 vec
对象。这样,我们避免了向函数传递时进行不必要的拷贝,同时确保不会意外修改原始数据。
#include <iostream>
#include <vector>// 定义模板函数
template <typename T>
void printVector(const std::vector<T>& vec) {for (const T& element : vec) {std::cout << element << " "; // 打印每个元素}std::cout << std::endl; // 打印完后换行
}int main() {// 实例1:打印 int 类型的 vectorstd::vector<int> intVec = {1, 2, 3, 4, 5};std::cout << "Integer vector: ";printVector(intVec); // 调用模板函数打印 int 类型的 vector// 实例2:打印 double 类型的 vectorstd::vector<double> doubleVec = {1.1, 2.2, 3.3};std::cout << "Double vector: ";printVector(doubleVec); // 调用模板函数打印 double 类型的 vector// 实例3:打印 string 类型的 vectorstd::vector<std::string> stringVec = {"hello", "world", "template"};std::cout << "String vector: ";printVector(stringVec); // 调用模板函数打印 string 类型的 vectorreturn 0;
}