C++ STL学习 之 泛型编程

发布于:2025-08-18 ⋅ 阅读:(14) ⋅ 点赞:(0)

一,泛型编程

泛型即泛泛,泛即泛指,是一种基于模板机制的一种编程方式。

传统的编程思想中,把软件设计建立在三维空间:数据类型,容器,算法的基础上,针对不同的数据类型,不同的容器对同一的算法设计不同的代码,但这样会生成大量源码,而且代码重用性较低。

而泛型思想把算法与容器、数据类型相分离,使同一算法适用于不同的容器和数据类型,成为通用性算法,这样可以最大限度地节省源代码,实现代码的重用。

二,函数模板

定义:建立一个通用的函数,函数的返回值类型和形参类型不具体指定,而是用一个虚拟的1类型来代表,这个类型叫做泛型。这样可以提高复用性,将类型参数化。

1.定义语法

通过函数前添加一行模板声明,来声明一个函数模板,有两种声明格式:
template<typename T>
template<class T>,此处的T即泛型

2.注意事项

  1)由于是自动类型推导,必须推导出一致的数据类型才能使用,比如传进去一个int ,一个char,就不一致,以交换两个数的函数为例:swap函数里面传入的参数类型必须一致,

template<typename T>
void mySwap(T& a, T& b) {
//这里加 & 用引用传递,就是为了高效又正确地实
// 现两个变量的交换,让函数能真正影响到外部的实参 。
    T temp = a;
    a = b;
    b = temp;
}
void test01() {
    int n1 = 10;
    char n2 = 'A';
    mySwap(n1,n2);//会报错


  2)函数的模板必须确定T的类型,如果不能自动推导,就需要手动指定,仍以mySwap(函数)为例:

void test01(){
    int n3 = 5;
    int n4 = 8;
    mySwap<int>(n3, n4);//相当于指定T为int类型。
    cout << "交换后n3=" << n3 << " n4=" << n4 << endl;
}
3.与普通函数的区别

 1)普通函数调用时可以发生隐式的自动类型转换
 2)函数模板在调用时,如果是自动类型推导,不会发生隐式转换,显式指定类型也可以发生隐式类型的转换,如下,显式指定泛型为Int类型后,会发生隐式转换,n4转换为int。

void test01(){
    int n3 = 5;
    double n4 = 8;
    mySwap<int>(n3, n4);//相当于指定T为int类型
    cout << "交换后n3=" << n3 << " n4=" << n4 << endl;
}

 关于隐式类型的转换,遵循一定的规则:

  • 在算术运算中,低类型可以转换为高类型。
  • 赋值表达式中,赋值运算符右边的类型会转为左边的类型。
  • 函数调用时,实参会转换为形参的类型。
  • 函数返回值时,返回表达式类型会转换为返回值类型。
  • 自定义类型的转换是不支持隐式转换的,因为不安全。需要手动转换,包括了静态转换
 4.调用规则

 1)如果函数模板和普通函数都可以实现, 实现了重载,优先调用普通函数,逻辑是:具体的实现 优先于 通用的实现。
 2)可以通过添加空的模板参数列表 <>来强制调用函数模板。
 3)函数模也可以直接发生重载。
 4)如果函数模板比普通函数可以产生更好的匹配效果,则会优先调用函数模板
案例:

void myPrint(int a, int b) { cout << "a=" << a << "b=" << b << "调用普通函数" << endl; }
template<typename T>
void myPrint(T a,T b) { cout << "a=" << a << "b=" << b << "调用函数模板" << endl; }
template<typename T>
void myPrint(T a, T b,T c) { cout << "a=" << a << "b=" << b << "c=" <<c<< "调用函数模板,三个参数" << endl; }
void test06()
{
    //1)如果函数模板和普通函数都可以实现,实现了重载,优先调用普通函数。逻辑是:具体的实现高于通用的实现。
    int a = 10, b = 20;
    myPrint(a, b);

    //2)可以通过添加空的模板参数列表<>来强制调用函数模板。
    myPrint<>(a, b);
    
    //3)函数模板之间也可以发生重载
    //重载就是函数名相同,但参数类型和数量不同,这里是数量不同;
    int c = 30;
    myPrint(a, b, c);
    
    //4)如果函数模板比普通函数可以产生更好的匹配效果,则会优先调用函数模板
    //因为普通函数是int类,但函数模板是任意类型,更加适配。
    char c1 = 'a';
    char c2 = 'b';
    myPrint(c1, c2);

}

5. 特定模板

template <typename T>
bool myCompare(T a, T b) {
    cout << "调用函数模板myCompare(T a,T b)" << endl;
    if (a == b) {
    //==比较运算符,不支持自定义类型
        return true;

    }
    else
    {

        return false;

    }

}
//自定义一个学生类
class Student {

public:
    string m_Name;
    int m_Age;
    //构造函数
    Student(string name,int age): m_Name(name),m_Age(age){}
};
//添加一个特定模板来解决整个不兼容问题
template<> bool myCompare(Student s1, Student s2) 
{
    cout << "调用特殊模板myCompare(Student s1, Student s2)" << endl;
    //比较对象类型就是比较,每个对象的属性的值是否相等
    if (s1.m_Name == s2.m_Name and s1.m_Age == s2.m_Age) {
        return true;
    }
    else {
        return false;

    }

}


void test07() {

    Student s1("Tom", 12);
    Student s2("Tom", 12);
    cout << myCompare(s1, s2);
}

三,类模板

1.类模板的概念

(1) 类模板是比函数模板更加通用的一种方式;类模板就是建立一个更加通用的类,类中的成 员等用到的数据类型都是不仅具体指定,用泛型来表示

 (2)与函数模板的区别:
 在使用时,类模板没有自动类型推到的方式,只能是显式指定泛型的类型;

 (3)语法:
  类前面加一行模板声明:
  template<typename 泛型名称>或者template<class 泛型名称>

举例:

template<class NameType ,class AgeType=int>
class Person {
    NameType m_Name;// 没有指定具体的类,泛型来表示
    AgeType m_Age;
public:
    Person(NameType name, AgeType age)
    {
        m_Name = name;
        m_Age = age;
    }
 void show() { cout << "name: " << m_Name << ", age: " << m_Age << endl; }
 void setvalue(NameType name, AgeType age) {
        m_Name = name;
        m_Age = age;
    }

};
void test01() {
    Person<string, int>  p1("szl", 34);
    Person<string, string> p2("saz", "二十岁");
    p1.show();
    p2.show();
    p1.setvalue("tyz", 23);
    p1.show();

}
2. 与函数模板的结合

根据结合的范围分为以下几种情况
1)指定类模板对象的泛型类型(便于理解,但是灵活度不高,传参的时候必须按照指定类型去传)

2)不指定类模板对象的泛型类型,而是通过函数模板的模板参数类别来指定(提高了灵活性,可以在使用这个函数模板的时候再进行指定)

3)将整个类当成一个泛型,使用函数模板的模板参数列表来指定(灵活性最高,可以传入任意类型的对象)

3. 类模板遇到继承

    如果父类是类模板,会有几种变化:
    1)父类是类模板,子类可以是普通类,也可以是类模板

    2)父类和子类都是类模板,子类和父类的泛型可以不同,各自指定自己的泛型类型。但是子类在继承父类时需要指定父类的泛型类型。


    子类的泛型类型可以在使用的时候再去指定。
    3)父类和子类都是类模板,让子类和父类共同使用同一个泛型类型。


    总之,可以根据实际需要,在继承一个类模板的时候,直接确定他的泛型类型,或者继续用一个泛型代替,在子类实例化的时候再确定。
 

练习:
 

使用模板实现一个模板数组类:TemplateArray,数组中可以存储任意类型的数据,类的属性有:

​    T* data;//数组的地址,数据存储在堆内存
​    int capacity;//数组的容量,即数组中最多可以容纳的元素个数
​    int size=0;//数组中实际元素的个数,初始值为零

并提供以下几个类方法:

从尾部添加数据元素: void addBack(T value);

从尾部删除元素:void delBack();

打印数组: void printArray();

返回数组中元素的个数:int getSize();

返回数组的容量:int getCapacity();

返回指定下标位置的元素:T getValueByIndex(int index);

参考代码:

template<class T>
class TemplateArray
{
private:
	T* data = NULL;
	int capacity;
	int size = 0;
public:
	TemplateArray(int cap)
	{
		data = new T[cap];//在对空间申请了对应大小的内存
		capacity = cap;
	}
	~TemplateArray()
	{
		if (data!=NULL)
		{
			delete[]data;
			data = NULL;
		}
	}
	void addBack(T value)
	{
		//判断数组是否已满,已满就不能添加了
		if (capacity==size)
		{
			cout << "数组已满,无法添加元素" << endl;
		}
		else
		{
			data[size] = value;
			size++;
		}
	}

	void delBack()
	{
		//先判断有没有元素,有元素才能删除
		if (size>0)
		{
			//删除元素其实就是让可访问的有效元素范围进行变化,从尾部删除就是将最后一个元素排除,那么修改size的值即可
			size--;
		}
	}

	void printArray()
	{
		for (size_t i = 0; i < size; i++)
		{
			cout << data[i] << ",";
		}
		cout << endl;
	}

	int getSize()
	{
		return size;
	}

	int getCapacity()
	{
		return capacity;
	}

	T getValueByIndex(int index)
	{
		return data[index];
	}
};
void test04()
{
	//整型数组
	TemplateArray<int> tarr(6);
	tarr.addBack(1);//添加元素
	tarr.addBack(2);
	tarr.addBack(3);
	tarr.addBack(4);
	tarr.addBack(5);
	tarr.addBack(6);
	tarr.addBack(7);//已满,无法添加元素
	tarr.printArray();//打印元素
	tarr.delBack();//从尾部删除元素
	tarr.printArray();
	cout << "容量:" << tarr.getCapacity() << ",元素个数:" << tarr.getSize() << ",下标为2的元素" << tarr.getValueByIndex(2) << endl;

	//字符数组
	TemplateArray<char> tarr_char(3);
	tarr_char.addBack('a');
	tarr_char.addBack('b');
	tarr_char.addBack('c');
	tarr_char.addBack('d');//已满,无法添加
	tarr_char.printArray();
	tarr_char.delBack();
	tarr_char.printArray();
	cout << "容量:" << tarr_char.getCapacity() << ",元素个数:" << tarr_char.getSize() << ",下标为0的元素" << tarr_char.getValueByIndex(0) << endl;
}


 


网站公告

今日签到

点亮在社区的每一天
去签到