详解C++中的泛型编程及模版

1.泛型编程

所谓的泛型编程,指的是我们编写程序的时候,是独立于任何特定的类型来编程程序,那么什么叫做独立于 任何特定的类型呢?

 如:

        设计一个数组类型,这个数组有可能只能存放int类型的元素,但是我们在代码中使用这个数组的时 候,却不一定总是int类型的元素,可能需要数组能够存放任意类型的元素也就是说,数组只是一个 通用的概念,不应该局限于某一种特定的类型

所以,数组元素的类型,应该可以使用某一种方式独立表示,类似于函数的参数。可以在实例化数组的时候,指定数组中的元素类型

        泛型编程的定义

泛型编程是计算机科学中的一个分支,它允许一个值取不同的数据类型,并强调使用这种技术的编程风 格。泛型编程的目标是推出一种针对算法、数据结构和内存分配机制的分类方法,以及其他能够带来高 度可重用性、模块化和可用性的软件工具。

泛型编程强调算法的重要性,它使用最少的有关数据抽象的假设,并尽可能将具体算法提升到抽象的层 次,同时保持效率。通过这种方式,泛型编程表示成高度通用和抽象的组件集合,这些组件可以以多种 方式组合在一起,形成高效、具体的程序。

泛型编程在多个场景中有广泛应用,包括集合类和数据结构、自定义数据结构、泛型方法、接口和抽象 类以及异常处理等。在集合类和数据结构中,泛型允许创建存储特定类型元素的集合,并在编译时捕获 类型错误。在自定义数据结构中,泛型可以创建适应不同类型数据的通用数据结构。泛型方法则允许在 方法级别使用泛型,为只需要在特定方法中使用泛型的情况提供便利。

总的来说,泛型编程提供了一种灵活且高效的方式来处理不同数据类型,提高了软件的可重用性和模块 化程度。

泛型编程的优点        

  • 代码重用性:泛型编程可以编写通用的代码,这些代码可以在不同类型上进行操作,而无需为每种类型 单独编写代码。这大大提高了代码的重用性,减少了代码的冗余,从而降低了开发和维护成本。
  • 类型安全:泛型编程在编译时执行类型检查,这有助于捕获大部分类型相关的错误。这意味着在运行 时,由于类型不匹配而引发的异常会大大减少,从而提高了代码的安全性和稳定性。
  • 性能优化:泛型编程允许在编译时进行类型优化,减少运行时的类型转换和装箱/拆箱操作。这对于性能 敏感的应用程序尤为重要,因为它可以显著提高代码的执行效率。
  • 抽象性:泛型编程提供了一种更高层次的抽象,它隐藏了实现细节,使得代码更易于理解和维护。通过 使用泛型,程序员可以专注于算法和数据结构的本质,而不是陷入具体的类型细节中。
  • 灵活性:泛型编程提供了更大的灵活性,允许程序员在编译时定义和修改类型,从而根据需求调整程序 的行为。这使得泛型编程成为处理多种数据类型和适应不同场景的强大工具。
  • 简化代码:泛型代码通常更加清晰和易读,因为它减少了不必要的类型转换和类型检查,使得代码的结 构更加简洁和直观。
  • 泛型编程的核心思想是将数据类型视为参数,通过参数化类型来编写代码,使得代码能够在多种数据类 型上操作。这种技术使得程序员能够编写完全一般化并可重复使用的算法,而不需要针对每种数据类型 都编写特定的代码。

泛型编程如何实现呢? 使用模板实现。

2.模版

(1)模版定义

在C++中,使用模板实现泛型编程,或者说使用模板来表示一个通用的概念

模板是一种泛型编程的工具,它允许程序员创建一种可以处理任何数据类型的类或函数,而无需针对每 种数据类型分别编写代码。通过模板,C++程序可以以一种统一的方式处理多种数据类型,提高了代码 的重用性和灵活性

模板允许我们在定义函数或者类的时候,将类型作为一个参数,编译器后面可以根据你提供的类型自动生成 特定的函数或者类

简单的说,模板就是用来创建一个类或者一个函数的公式(蓝图)

模板可以分为两种:

         函数模板,可以通过这个模板创建具体类型的函数,其参数和返回值类型也可以是任意的。

         类模板,可以用来创建具体类型的类,其成员变量和成员函数可以使用任意类型。

在定义模板时,需要使用 template 关键字,并指定一个或多个类型参数。这些类型参数在模板实例化 时会被具体的类型所替换。

通过使用模板,程序员可以编写更加通用和灵活的代码,减少代码冗余,提高代码的可维护性和可重用 性。同时,模板也可以提高代码的性能,因为编译器在编译时会根据具体的类型生成相应的代码,避免 了运行时类型转换的开销。

需要注意的是,模板不是一种运行时的特性,而是一种编译时的特性。编译器会根据模板的定义和实例 化请求生成具体的代码。因此,在使用模板时,需要确保模板的定义在实例化之前已经被编译器看到。

(2)函数模版

函数模板允许我们编写一个函数,该函数可以处理任意类型的数据。而无需针对每种数据类型分别编写 函数。

函数模板实际上是建立一个通用函数,其函数类型和形参类型中的全部或部分类型不具体指定,用一个 虚拟的类型来代替。这个通用函数称为函数模板,函数体相同的函数都可以用这个模板来代替。

函数模板定义格式:

template<typename T1, typename T2...> //模板头
返回值类型 函数名(参数列表)
{
    //函数体;
}
  • template 是C++语言中用于定义模板的关键字
  • T1,T2:是一个类型参数的形参名字,在后面的函数定义中,可以直接使用这个类型参数
  • 如果该函数模板涉及到多个类型参数,只需要在尖括号中以逗号隔开即可
  • 在函数模板调用的时候,编译器会自动的推导类型参数去匹配函数模板

函数模板调用格式:

函数模板名<参数类型>(实参列表);
//其中<参数类型> 是类型参数列表,它告诉编译器在实例化模板时应该使用哪种类型。在某些情况下,编译
//器能够自动推导出类型参数,这时可以省略

示例:定义一个函数模板,用于计算两个对象的和

#include<iostream>
    using namespace std;
    template<typename T1, typename T2> //T只是一个名字,表示一种类型
    T1 sum(T1 , T2 );
    template<typename T1, typename T2>
    T1 sum(T1 a, T2 b)
    {
        return a + b; //T类型的对象本身是可以相加的
    }

int main()
{
    cout << sum<int>(1, 2) << endl;//<int>可以省略
    cout << sum('0', 'A') << endl;
    string s1 = "abc";
    string s2 = "def";
    cout << sum(s1, s2) << endl;
    cout << sum(1, 'a') << endl;
    return 0;
}

(3)类模版

C++中除了支持函数模板还支持类模板(class template)

在C++中,类模板是一种特殊的类,它允许程序员定义一种可以用于多种数据类型的类,而不必为每种 数据类型都重新编写一个类。通过使用类模板,我们可以在不重复编写代码的情况下创建适用于不同类 型的类实例。

函数模板中定义的类型参数可以在函数的声明和定义中使用,类模板中定义的类型参数可以在类的声明 和实现中使用

类模板的目的是将数据的类型参数化,让类可以更加的通用,类模板的主要优势在于其灵活性和代码重用 性。通过使用类模板,我们可以编写出高度通用的代码,这些代码可以处理多种数据类型,而无需为每 个数据类型都编写特定的类。这不仅可以减少代码量,还可以提高代码的可读性和可维护性。

类模板定义格式:

template<typename T1,typename T2>
class 类名
{
    public:
    ...
    private:
    ...
    protected:
    ...
};

类模板和函数模板都是以template开头,后面跟上类型参数,类型参数不能为空

多个类型参数可以使用逗号隔开

一旦声明了类模板,就可以将类型参数用于类的成员变量和成员函数

include<iostream>
#include"test_1.h"
using namespace std;
class A
{
public:
    A(int num = 0) : m_num(num)
    {
    }
    friend ostream& operator<<(ostream& os, A& a)
    {
        os << a.m_num;
        return os;
    }
private:
    int m_num;
};
//使用模板类定义一个数组
template<typename T>
class Array
{
public:
    Array(int cnt) : m_parr(new T[cnt]), m_cnt(cnt)
    {}
    ~Array()
    {
        delete[]m_parr;
    }

    //获取数组大小
    int getSize()
    {
        return m_cnt;
    }

    //访问元素
    T& operator[](int index)
    {
        if (index < 0 || index > m_cnt)
    {
    cout << "下标输入有误" << endl;
    exit(-1);
}
    return m_parr[index];
}

    //拷贝构造
    Array(const Array& that)
    {
        m_cnt = that.m_cnt;
        m_parr = new T[m_cnt];
    for (int i = 0; i < m_cnt; i++)
    {
        m_parr[i] = that.m_parr[i];
    }
}

    //拷贝赋值
    Array& operator=(const Array& that)
    {
        m_cnt = that.m_cnt;
        if (this != &that)
        {
            //释放旧内存
            delete[]m_parr;
            //分配新内存
            m_parr = new T[m_cnt];
            for (int i = 0; i < m_cnt; i++)
            {
                m_parr[i] = that.m_parr[i];
            }
        }
        return *this;
    }
private:
    T* m_parr;//数组首地址
    int m_cnt;//数组元素个数
};

在这个例子中, typename T 是模板参数,它可以在类中使用 T 来表示任意数据类型。当创建类模 板的实例时,需要将 T 替换为实际的数据类型。

类模板的使用方式:

        调用函数模板只需要提供实际参数即可,编译器会自动的根据用户提供的值去推导值的类型,在自动的生成 相应的函数版本,然后调用。

但是我们在使用类模板的时候,必须显示的指定类型参数

格式:

模板类名<实际类型1,实际类型2> 对象名;

一旦类模板被实例化,就可以像使用普通类一样创建对象,并调用其成员函数和访问其成员变量。 每次使用不同的类型参数实例化类模板时,都会生成一个独立的类

int main()
{
    Array<int> intArray(5);//类模板的使用
    for (int i = 0;i < intArray.getSize();i++)
    {
        intArray[i] = i;
    }

    // 输出整型数组的内容
    for (int i = 0; i < intArray.getSize(); ++i)
    {
        std::cout << intArray[i] << " ";
    }
    std::cout << endl;
    Array<int> intArray1 = intArray;

    // 输出整型数组的内容
    for (int i = 0; i < intArray1.getSize(); ++i)
    {
        std::cout << intArray1[i] << " ";
    }
    std::cout << endl;
    Array<int> intArray2(3);
    intArray2 = intArray;
    for (int i = 0; i < intArray2.getSize(); ++i)
    {
        std::cout << intArray2[i] << " ";
    }
    std::cout << endl;
    std::cout << "-----------------" << endl;
    Array<A> a(5);
    for (int i = 0; i < a.getSize(); i++)
    {
        a[i] = i;
    }
    for (int i = 0; i < a.getSize(); i++)
    {
        std::cout << a[i] << " ";
    }
    return 0;
}

注意:

         如果模板类的成员函数在类外实现,则该成员函数必须有模板声明,并且作用域也必须指定模板 参数

        如果模板函数和类模板的声明和定义分开,可以将模板函数和类模板的声明写在.h文件中,而 将定义写在.cpp文件中。

但是,模板函数和类模板的定义和调用必须在同一个文件中(上述例子中,类模板的声明在 test_1.h文件中,定义和调用都在test.cpp文件中,是没问题的。但是如果将模板函数和类模 板的定有写在test_1.cpp,而调用写在test.cpp中就会报错)

在模板声明中,使用关键字typename来说明类型参数的名字。但是在早期的C++中,曾经使用的 关键字叫做class。但是在新标准中,我们建议使用typename

(4)函数模版和类模版的结合

类模板和函数模板在C++中可以共同使用,以实现更高级别的泛型编程。通过结合类模板和函数模板, 我们可以创建出既能够处理多种数据类型,又能够执行多种操作的灵活且可重用的代码。

在上述的案例中,我们想要将给数组赋值以及打印数组的操作放在模板函数中完成

Init函数写在类模板内,print写在类模板外

void Init(int size);//类模板内

template<typename T> void print(Array<T>& arr, int size);//类模板外
template<typename T>//定义
void Array<T>::Init(int size)
{
    for (int i = 0; i < size; i++)
    {
        m_parr[i] = i;
    }
}

template<typename T>
void print(Array<T>& arr, int size)
{
    for (int i = 0; i < size; i++)
    {
        std::cout << arr[i] << " ";
    }
}

    Array<int> intArray(5);
    //for (int i = 0; i < intArray.getSize(); i++)
    //{
        // intArray[i] = i;
    //}
intArray.Init(intArray.getSize());//调用
//Init(intArray, intArray.getSize());
// 输出整型数组的内容
//for (int i = 0; i < intArray.getSize(); ++i)
//{
// std::cout << intArray[i] << " ";
//}
print(intArray, intArray.getSize());//调用
std::cout << endl;

以上内容均为作者个人见解,如有错误,还请读者斧正

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值