C++(七)模板

1.模板概念

        什么是模板?

        模板可以看作是函数或类的蓝图。与其指定具体的数据类型(如 intdouble),你可以使用一个占位符(通常称为“模板参数”),这个占位符在使用模板时被替换为具体的类型。

        *(概念,可跳过)在这里提一个小点:泛型编程是一种编程范式,它允许编写代码时使用类型参数,从而使得代码能够处理多种数据类型而不需要重复编写相似的代码。通过泛型编程,开发者可以编写更加通用、可复用的代码,提高代码的灵活性和可维护性。

        在C++中,泛型编程主要通过模板来实现。模板是一种编译器机制,它允许在编写函数或类时使用类型参数。类型参数可以在实例化模板时由具体的类型来替代。

 先看下面用c语言写的一段代码:

void Swap(int* x,int* y)
{
    int temp = *x;
    *x = *y;
    *y = temp;
}
void Swap(double* x,double* y)
{
    double temp = *x;
    *x = *y;
    *y = temp;
}

int main()
{
	int a = 0, b = 1;
    double c = 1.1, d = 2.2;

	Swap(&a, &b);//当int类型的a和b会传地址调用第一个Swap
	Swap(&c, &d);//当double类型的c和d会传地址调用第二个Swap

	return 0;
}

明明是只是更换类型,代码却要重复编写,如果还需要使用更多类型的Swap会让代码更冗余,重复的部分过多所以C++做出改进:提出模板。

 2.模板的分类

在C++语言种,模板分为:函数模板和类模板。

3.模板的简单使用(函数模板)

        使用模板来实现上面C语言代码中需要的功能:(模板的写法)

        关键字:template,typename

// 函数模板实例化生成具体函数
// 函数模板根据调用,自己推导模板参数的类型,实例化出对应的函数
template<typename T>
void Swap(T* x,T* y)
{
    T* temp=x;
    *x=*y;
    *y=*temp;
}

int main()
{
	int a = 0, b = 1;
    double c = 1.1, d = 2.2;

	Swap(&a, &b); // 使用模板函数交换 int 类型
	Swap(&c, &d); // 使用模板函数交换 double 类型
	return 0;
}

         观察上面代码,template<typename T>就是模板的写法,可以把T看作是可以变化的类型,实际上T通过传来的参数 推演出T该用的类型。

        当使用模板时,编译器会在编译期间根据你提供的具体类型生成相应的代码。。如上面例子,当你调用 Swap(a, b) 时,编译器会生成一个针对 int 类型的 Swap 函数版本;当你调用 Swap(c, d) 时,编译器会生成一个针对 double 类型的 Swap 函数版本。

  注意:在C++并不是没有生成 Swap(double* x,double* y)和Swap(int* x,int* y) 函数,只是不需要我们自己定义,而是交给编译器去做。

下面看函数模板更多的用法:

// 函数模板实例化生成具体函数
// 函数模板根据调用,自己推导模板参数的类型,实例化出对应的函数
template<class T>
T Add(const T& left, const T& right)
{
   return left + right;
}

template<class T>
T* Alloc(int n)
{
   return new T[n];
}

int main()
{
   int a1 = 10, a2 = 20;
   double d1 = 10.1, d2 = 20.2;
   // 实参传递的类型,推演T的类型
   cout << Add(a1, a2) << endl;
   cout << Add(d1, d2) << endl;
   cout << Add(a1, (int)d1) << endl;
   cout << Add((double)a1, d1) << endl;

   // 显示实例化,用指定类型实例化
   cout << Add<int>(a1, d1) << endl;
   cout << Add<double>(a1, d1) << endl;

   // 有些函数无法自动推,只能显示实例化
   double* p1 = Alloc<double>(10);

   return 0;
}

上面代码出现了模板 新的用法那么Add<int>(a1,d1)是什么意思呢?

        这个用法叫做:显示实例化,意思就是当调用Add函数时,直接告诉编译器T的类型就是int,不用推演了! 通用下面Add<double>也是一样的道理。

所以可以看出图1、2行,和3、4行代码效果是一样的。

最后看看Alloc函数传参的类型和模板类型T没关系,所以,编译器无法自动推演出此class T,究竟是什么类型,所以只能显示实例化,否则报错!

4.类模板

  • 类模板:用于定义通用的类,可以操作不同类型的数据

类模板和函数模板的使用非常类似:

        其实就是在整个类中,储存和处理数据,数据类型都用模板template<typename T>中的T

template<typename T>
class Stack {
private: //类成员声明
    T* data;
    int top;
    int capacity;
public:
    Stack(int size) : capacity(size), top(-1) {
        data = new T[capacity];
    }
    ~Stack() {
        delete[] data;
    }
    void push(const T& value) {
        if (top < capacity - 1) {
            data[++top] = value;
        }
    }
    void pop() {
        if (top >= 0) {
            --top;
        }
    }
    T peek() const {
        if (top >= 0) {
            return data[top];
        }
        throw std::out_of_range("Stack is empty");
    }
    bool isEmpty() const {
        return top == -1;
    }
};

        代码解析:这是自己实现的一个栈Stack,栈中存放的数据类型我不确定(可能是int,可能是float),所以当在类中函数形参 返回值等,需要声明栈内数据的数据类型都用T,(类前面使用类模板template<typename T>)。

详解:

  • template<typename T> 声明了一个模板。T 是一个类型参数,表示栈中元素的类型。这个类型在你创建栈对象时确定。模板允许代码泛型化,使得同一段代码可以处理不同的数据类型,而不需要重复编写代码。
  • 类成员声明
    1. T* data: 一个指向 T 类型数组的指针,用于存储栈中的元素。
    2. int top: 表示栈顶元素的索引。初始值为 -1,表示栈为空。
    3. int capacity: 栈的容量,即最多可以存储多少个元素。
  • 构造函数
    1. Stack(int size): 构造函数,用于创建一个容量为 size 的栈。
    2. : capacity(size), top(-1) 是初始化列表,初始化栈的容量为 size,并将 top 设为 -1
    3. data = new T[capacity]; 动态分配一个容量为 capacityT 类型数组,用于存储栈的元素。
  • 析构函数
    1. ~Stack() 是析构函数,当栈对象的生命周期结束时调用,用于释放栈中动态分配的内存 (data)。
    2. delete[] data; 释放用 new 动态分配的数组内存,防止内存泄漏。
  • push
    1. void push(const T& value): 向栈中添加一个元素。元素类型根据模板走
    2. if (top < capacity - 1): 检查栈是否已满。如果栈未满,执行以下操作:data[++top] = value;: 将 top 递增 1 后,把 value 存储在数组 data 的栈顶位置。
  • pop() 从栈中移除栈顶元素。

完成类声明后,使用这个栈储存int类型数据 例子如下:

#include <iostream>
#include <stdexcept> // 为了处理 std::out_of_range 异常
using namespace std;

int main() {
    // 创建一个可以存储 int 类型元素的栈,容量为5
    Stack<int> intStack(5);

    // 向栈中压入元素
    intStack.push(10);
    intStack.push(20);
    intStack.push(30);

    // 取出栈顶元素并打印
    cout << "Top element: " << intStack.peek() << endl; // 此时应输出:30

    // 弹出栈顶元素
    intStack.pop();

    // 再次取出栈顶元素并打印
    cout << "Top element after pop: " << intStack.peek() << endl; // 此时应输出:20

    // 检查栈是否为空
    if (!intStack.isEmpty()) 
    {
        cout << "Stack is not empty." << endl;
    } 
    else {
        cout << "Stack is empty." << endl;
    }

    // 捕获尝试从空栈中取元素的异常
    try {
        Stack<int> emptyStack(5);
        emptyStack.peek(); // 这将抛出 std::out_of_range 异常
    } catch (const std::out_of_range& e) {
        cout << "Exception caught: " << e.what() << endl; // 输出:Exception caught: Stack is empty
    }

    return 0;
}

也可以让这个栈储存double类型数据:

#include <iostream>
#include <stdexcept> // 为了处理 std::out_of_range 异常
using namespace std;

int main() {
    // 创建一个可以存储 double 类型元素的栈,容量为5
    Stack<double> doubleStack(5);

    // 向栈中压入元素
    doubleStack.push(3.14);
    doubleStack.push(2.718);
    doubleStack.push(1.618);

    // 取出栈顶元素并打印
    cout << "Top element: " << doubleStack.peek() << endl; // 输出:1.618

    // 弹出栈顶元素
    doubleStack.pop();

    // 再次取出栈顶元素并打印
    cout << "Top element after pop: " << doubleStack.peek() << endl; // 输出:2.718

    // 检查栈是否为空
    if (!doubleStack.isEmpty()) {
        cout << "Stack is not empty." << endl;
    } else {
        cout << "Stack is empty." << endl;
    }

    // 捕获尝试从空栈中取元素的异常
    try {
        Stack<double> emptyStack(5);
        emptyStack.peek(); // 这将抛出 std::out_of_range 异常
    } catch (const std::out_of_range& e) {
        cout << "Exception caught: " << e.what() << endl; // 输出:Exception caught: Stack is empty
    }

    return 0;
}

当然,还可以实例化成char型、long型等,在这里我就不一一演示了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值