• C++模板


    写在前面

    现在我们来开启C++不同于C语言的地方,模板.大家都知道C语言没有标准的数据库,但是C++存在STL,这是由于C++支持泛型编程,这是我们今天需要知道重点.今天的模板就是初阶,先来简单的认识一下.

    泛型编程

    什么是泛型?所谓的泛型就是不再是针对某种类型,而是关注于广泛的类型.

    大家可能不太理解这句话,我们用一个简单的例子来和大家解释,在C语言中我们要写一个简单的两个数简单的交换,我们需要考虑这两个数的类型,写出不同的函数,而且C语言中不支持函数重载,不便利.但是使用泛型就不一样了,我们可以用一个泛型函数就可以解决了.

    大家可能现在还看不懂下面的代码,我们先知道有这个东西就可以了,后面一个一个和大家解释.

    template<class T>
    void swap(T& left, T& right)
    {
    	T temp = left;
    	left = right;
    	right = temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

    我们都知道活字印刷术,泛型函数就相当于一个模板,我们需要什么类型,就把什么给编译器,让编译器通过模板+我们给的类型自动的把这个函数给写出来.

    模板分类

    模板主要分为两类.

    • 函数模板
    • 类模板

    泛型关键字

    C++提供两个关键字来帮助我们学习模板,下面的代码就是.

    这句话的意思就是,我们声明下面的一个函数或者一个类是一个模板,T1,T2…代表是数据类型,我们使用的时候需要告诉编译器,后面会说.

    template<typename T1,typename T2 ...>
    
    • 1

    这里我要声明一下,一些旧的语法当中,我们也可以使用class替换typename,现在我们认为它们的作用一样就可以了.

    # 函数模板

    函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本.

    函数模板的格式

    函数模板就像当与一个模子,它本身没有地址,当我们传入参数后,编译器自动通过模板自动推出一个函数,这时这个函数才会有地址,但是模子没有改变.

    template<typename T1, typename T2,......,typename Tn>
    返回值 函数名(参数列表)
    {
    }
    
    • 1
    • 2
    • 3
    • 4

    我们先来看看函数模板,依旧先用用交换函数来举例,后面有很多例子.

    #include <iostream>
    using std::cout;
    using std::endl;
    
    template<class T>
    void Swap(T& left, T& right)
    {
    	T temp = left;
    	left = right;
    	right = temp;
    }
    
    int main()
    {
    	int a = 1;
    	int b = 2;
    	Swap(a, b);
    	cout << "a = " << a << " ";
    	cout << "b = " << b << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20220624172613638

    模板实例化

    我们先来解决一个不太困难的问题,我们通过提供不同的参数类型,是调用同一个函数吗?这个肯定不是的.大家先来看看现象.

    int main()
    {
    	int a = 1;
    	int b = 2;
    	double c = 1.0;
    	double d = 2.0;
    	Swap(a, b);
    	cout << "=========" << endl;
    	Swap(c, d);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20220624173308846

    这就是模板的实例化,编译器通过我们给的数据类型(或者自动推导)自动生成相应的函数.

    image-20220624174944914

    这里和大家说一下,以后再C++中,我们不需要再写交换函数了,C++标准库里面写了.

    image-20220624175629270

    隐式实例化

    template<class T>
    T add(T& a, T& b)
    {
    	return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这是我们提供个编译器的一个模板,一旦我们决定调用add函数,这里就会发生一件事.

    我们传入了参数,编译器会通过参数来自动推导数据类型,然后自动生成一个相应的函数.

    显式实例化

    现在我们存在了一个问题,难道编译器一定会通过参数推导类型吗?看看下面的函数.

    我们传入的参数的类型已经确定了,那么你告诉我返回值的类型该怎么确定?

    template<class T>
    T func(int n)
    {
    	return n;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里就需要现式的调用这个函数,编译器不知道,但是我们可以告诉它啊,看看下面的用法.

    template<class T>
    T func(int n)
    {
    	return n;
    }
    
    int main()
    {
    	int ret = func<int>(10);
    	cout << ret << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20220703042826328

    显式实例化 和 隐式实例化的优先级

    既然存在了两种实例化,那么我想知道如果同时存在,哪个起决定作用?

    答案很明显,显示起决定作用.

    template<class T>
    T func(T n)
    {
    	return n;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220703043506051

    存在模板参数的缺省吗

    是存在的,也就是说,我们可以通过提供给模板的缺省值,不过这个缺省值是一个类型,看看用法,你就会发现和函数参数的用法是很像的.

    template<class T = int>
    T func(int n)
    {
    	return n;
    }
    
    int main()
    {
    	int ret = func(10);
    	cout << ret << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20220703043919044

    可以声明和定义分离吗

    这个问题需要分开来看.

    同一文件

    既然模板可以认为是一个函数,他们可以申明和定义分离吗,我们都需要加上模板关键字.

    image-20220703044352632

    不同文件

    注意,我们要知道一件事事情,模板不支持声明和定义在两个文件中的,会出现链接错误,具体的原因我们需要在进阶中再谈.

    很多地方,我们都是把模板声明和定义放在同一个文件中,有的把这文件的后缀默认写为**.hpp**,这样可以告诉我们这是一个存在模板的头文件.但是不是必须的.

    image-20220703045117765

    这里简单的说一下,为什么报错.

    我们可以这么理解,再定义文件中,编译器式不知道T的类型的,也就是说编译器是不会把符号存在符号表中,链接的时候找不到的,这里有一个很挫的方法,使用显示实例化指定.

    也就是在定义文件中显示使用模板,但是很挫,你要把你用的都显示出来.例如使用int类型的,但是要使用其他的还要这么做,很麻烦.还不如不分离到两个文件中.

    image-20220703051319055

    模板参数的匹配规则

    我们存在一个问题,先来分析一下,下面的两个是否可以同时存在,是可以的,模板又不是函数.

    template<class T>
    T add(T& left, T& right)
    {
    	cout << "T add" << endl;
    	return left + right;
    }
    
    int add(int& left, int& right)
    {
    	cout << "int add" << endl;
    	return left + right;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    既然可以存在了,那么这个地方又有一个问题,编译器优先调用哪个?

    int main()
    {
    	int x = 1;
    	int y = 2;
    	add(x,y);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image-20220703201114612

    我们直接看现象就可以发现,优先调用函数,而不是函数模板,这个我们还是可以理解的,既然有现成的,还费力气去自己生成干嘛.

    那么我们如何让编译器调用模板,这里也有一个方法,使用显式实例化.

    image-20220703201342705


    类模板

    我们先来认识一下类模板.我们发现它们和普通的类没有什么区别,就是把相应的具体数据类型改成泛型了.

    template<class T>
    class Stack
    {
    public:
    	Stack(int cap = 4);
    	~Stack();
    	void push();
    
    private:
    	T* elme;
    	size_t size;
    	int cap;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    类模板格式

    类包含类的定义和里面的成员函数定义,我们先来分开谈.

    先说我们现在最基本的东西.

    但我们里面的成员函数的声明和定义不分离,这样就可以按照下面的方法做就可以了.

    class Student
    {
    public:
    	Student()
    	{
    		_cap = 4;
    		_elem = new T[_cap];
    		_size = 10;
    	}
    	~Student();
    private:
    	T* elem;
    	szie_t _size;
    	szie_t _cap;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    但是如果成员函数的声明和定义分离,我们就不能这么做了.

    可以这么说,每个成员函数都会带着template<class T>和Student<T>::这是编译器要求的.

    template<class T>
    class Student
    {
    public:
    	Student();
    	~Student();
    private:
    	T* elem;
    	szie_t _size;
    	szie_t _cap;
    };
    
    template<class T>
    
    Student<T>::Student()
    {
    	_cap = 4;
    	_elem = new T[_cap];
    	_size = 10;
    }
    
    template<class T>
    Student<T>::~Student()
    {
    	if (elem)
    	{
    		delete[] elem;
    		_size = 0;
    		_cap = 0;
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    类模板实例化

    类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类 .

    我们在使用的时候,记住显示实例化就可以了,我们看看用法.

    int main()
    {
    	Student<char> student;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220703193813096

    再析模板参数

    我们需要理解一下模板参数,这里需要大家来思考一个问题,下面的代码为何会报错?

    template<class T>
    T add(const T& left, const T& right)
    {
    	cout << "T add" << endl;
    	return left + right;
    }
    
    int main()
    {
    	add(1, 2.2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20220703204110082

    这是因为编译器不能够通过给定分数据来推出参数的,类型,一个int,一个double,编译器也会出现矛盾所以报错.

    我们可以通过下面的方法来解决问题.

    image-20220703204322863

    缺省模板参数

    这个我们可以和缺省函数放在一起理解,理解的思路是一样的.

    template<class K,class V>
    void func()
    {
    	cout << sizeof(K) << endl;
    	cout << sizeof(V) << endl;
    }
    
    int main()
    {
    	func<int,double>();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20220703205033913

    如果我们这么做呢?

    int main()
    {
    	func<int>();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    image-20220703205111572

    这就需要缺省模板参数的存在我们可以通过全缺省或者是部分缺省来保证程序正确.

    template<class K,class V = char>
    void func()
    {
    	cout << sizeof(K) << endl;
    	cout << sizeof(V) << endl;
    }
    
    int main()
    {
    	func<int>();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20220703205346211

  • 相关阅读:
    搜索算法(DFS和BFS 蓝桥杯 C++)
    [云原生] K8s之pod控制器详解
    【Javaweb】会话跟踪技术Cookie&Session
    AI歌手是否会取代流行歌手成为主流?
    vue ---列表渲染
    如何用Excel做最小二乘法②
    Linux常用指令(2)
    深入刨析 mysql 底层索引结构B+树
    我悟了!Mysql事务隔离级别其实是这样!
    js中的promise函数(ES6)
  • 原文地址:https://blog.csdn.net/m0_61334618/article/details/125589399