IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++::template -> 正文阅读

[C++知识库]C++::template

模板

C++另一种编程思想称为泛型编程,主要技术就是模板;
C++提供两种模板机制:函数模板和类模板

函数模板

// 作用
建立一个通用函数,其函数返回值类型和形参类型可以不用具体制定
用一个虚拟的类型来代表(类型参数化);

// 语法
template<typename T>
函数声明或定义;
template -- 声明创建模板;
typename -- 表明其后面的符号是一种数据类型,可以用class代替
T -- 通用的数据类型,名称可以替换,通常为大写字母
template<typename T>
void swap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

// 使用
int main(){
    int a = 10;
    int b = 20;
    // 自动类型推导
    swap(a, b);
    // 显示指定类型
    swap<int>(a,b);
}
// 注意事项
自动类型推导,必须推导出一致的数据类型T才可以使用;
模板必须要确定出T的数据类型,才可以使用;

普通函数和函数模板的区别

普通函数调用时可以发生自动类型转换(隐式类型转换)

函数模板调用时:
**模板<数据类型>(…):**不能发生类型转换,必须为指定类型
**模板<>(…):**自动推断合适的模板,也不能发生类型转换

普通函数与函数模板的调用规则

// 如果函数模板和普通函数都可以实现,优先调用普通函数
// 可以通过空模板参数列表来强制调用函数模板
// 函数模板可以发送重载
// 如果函数模板可以产生更好的匹配,优先调用函数模板
void func(int a,int b){
    cout << "普通函数" << endl;
}
template<typename T>
void func(T a,T b){
    cout << "函数模板" << endl;
}

int main(){
    int a = 10;
    int b = 10;
    func(a,b); // 优先调用普通函数
    func<>(a,b); // 强制调用函数模板
}

// 总结:既然提供了函数模板,就不要在提供普通函数了,不然容易出现二义性

模板的局限性

// 有些特定的自定义数据类型,需要用具体化方法做特殊实现
class A{
    public:
    A(int x,int y):a(x),b(y){}
    int a;
    int b;
};

template<typename T>
bool func(T &a,T &b){
    if (a == b){
        return true;
    }
    return false;
}

// 利用具体化A类,来实现重载比较
template<>bool func(A &a,A &b){
    if (a.a == b.a && a.b == b.b){
        return true;
    }
    return false;
}

int main(){
    A a1(10,10);
    A a2(20,10);
    bool a = func(a1,a2); // 走具体实现
}

控制实例化

// 显示实例化

// 表示模板会在template的地方实例化,而不是本文件的地方
extern template class Blod<string>; // 实例化声明
// 模板会在这里实例化
template int compare(int&,int&); // 实例化定义

// 实例化定义声明不能放在一起
// 可以有多个extern声明,但是只能有一个template定义
// 实例化定义会实例化所有成员

重载与模板

// 匹配中精确匹配优先,需要类型转换的优先级低
// 同样好的匹配,非模板函数优先
// 同样好的匹配,没有非模板函数,特例化的模板优先

可变参数模板

// Args是一个模板参数包,表示零或多个类型
template<typename T,typename... Args>
// rest是一个函数参数包,表示零或多个参数
void foo(const T& t, const Args& ... rest)
{
    // 类型参数数目
	std::cout << sizeof...(Args) << std::endl;
	// 函数参数数目
    std::cout << sizeof... (rest) << std::endl;
}
// 编写可变参数函数模板
// 可变参数函数通常是递归的
template<typename T>
std::ostream& print(std::ostream& os, const T& t)
{
    // 终止函数
	return os << t;
}

template<typename T, typename... Args>
std::ostream& print(std::ostream& os, const T& t, const Args&...args)
{
	os << t << ',';
   // 递归调用
	return print(os, args...);
}
// 包扩展
// 对于一个参数包,除了能获取大小,唯一能做的就是扩展
// 在模式右边放...号来触发扩展

print(os, show(args)...)
// 相当于
print(os, show(a1),show(a2) ... show(an));
// 扩展中的模式会独立地应用于包的每个元素
// 转发包
std::forward<Args>(args)...
// 不管是T&&、左值引用、右值引用,
   std::forward都会按照原来的类型完美转发。
   
// forward主要解决引用函数参数为右值时,
   传进来之后有了变量名就变成了左值。

模板特例化

// 正常模板
template<typename T>
int compare(const& T,const& T){...};
// 特例化模板
template<>
int compare(const char* const&,const char* const&){...};
// 匹配规则不变
// 注意,一个特例化版本本质上是一个实例,而非函数名重载
// 类模板相同
// 同时,类模板可以只将部分成员特例化,比如成员函数

如果有一个函数模板,它的模板参数既作为参数类型又作为返回类型,那么一定要首先声明函数的返回类型参数,否则就不能省略调用函数参数表中的任何类型的参数。

template<typename R,typename P >
R implicit_cast(const P& p)
{
	return p;
}

int main()
{
	int i = 1;
	float x = implicit_cast<float>(i);
	return 0;
}
// 如果将模板中R和P的位置对换,则上述代码不能通过编译
template<typename P,typename R >
R implicit_cast(const P& p)
{
	return p;
}
int i = 1;
// 必须完整调用
float x = implicit_cast<int, float>(i);

类模板

template<class NameType,class AgeType>
class A{
public:
    A(NameType name,AgeType age){
        this->_name = name;
        this->_age = age;
    }
    NameType _name;
    AgeType _age;    
};
int main(){
    A<string,int> a("zxl",18);
}

类模板与函数模板的区别

// 类模板没有自动类型推导的使用方式,必须为显示类型指定
// 类模板在模板参数列表中可以有默认参数
// 函数模板和类模板成员函数的定义放在头文件内
// 模板头文件既包含声明也包含定义

template<class NameType,class AgeType = int> // 默认AgeType类型为int
class A{
public:
    A(NameType name,AgeType age){
        this->_name = name;
        this->_age = age;
    }
    NameType _name;
    AgeType _age;    
};

int main(){
    A<string, int> a("zxl",100); // 类模板只能用这种方式实例化
    A<string> a("zxl",100); // 加上了默认参数类型
}

类模板中成员函数的创建时机

普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建

类模板对象做函数参数

指定传入的类型 – 直接显示对象的数据类型
参数模板化 – 将对象中的参数变为模板进行传递
整个类模板化 – 将这个对象类型模板化进行传递

template<class T1,class T2>
class A{
public:
    A(T1 name,T2 age){
        this->_name = name;
        this->_age = age;
    }
    T1 _name;
    T2 _age;    
};

// 指定传入
void func(A<string,int>&p);

// 参数模板
template<class T1,class T2>
void func(A<T1,T2>&p);

// 类模板化
template<class T>
void func( T );

int main(){
    A<string,int> a("zxl",100);
}

// 其中第一种是最常用的

成员模板

成员模板不能是虚函数

// 类模板的成员模板
template<typename T>
class A
{
    template<typename U>
    A(U b,U e);
}
// 类模板在上,成员模板在下
template<typename T>
template<typename U>
A<T>::A(U b, U e){};

类模板与继承

当子类继承的父类是一个类模板时,子类在声明的时候,要指出父类中T的类型
如果不指定,编译器无法给子类分配内存
如果想灵活指定出父类中T的类型,子类也需要为类模板

template<class T>
class Base{
    T m;
};

class Son:public Base<int>{
    // 如果使用这种方法继承,那么父类中的T只能为int类型
};


template<class T,class T1>
class Son1:public Base<T>{ // 父类的类型
    T1 b; // 子类的类型
}

// 总结:如果父类是类模板。子类需要指定出父类中的T的数据类型

类模板成员函数类外实现

需要加上模板
成员模板不能是虚函数

template<class T1,class T2>
class A{
public:
    A(T1 name, T2 age);
    void func();
    T1 _name;
    T2 _age
};

// 构造函数的类外实现
template<class T1,class T2>
A<T1,T2>::A(T1 name,T2 age){
    // 实现体
}

// 成员函数的类外实现
template<class T1,class T1>
void A<T1,T2>::func(){
    // 实现体
}

类模板分文件编写

问题
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决
1.直接包含.cpp源文件;
2.将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定名称,并不是强制的;
主流的解决方法是用第二种

类模板与友元

全局函数内类实现 – 直接内类声明
全局函数内外实现 – 特殊方法
一般而言没有特殊需求的化就用类内实现

// 如果是类外实现这两段代码必须放在类的上面
template<class T1,class T2>
class A;

// 成员函数
template<class T1,class T2>
void func1(A<T1,T2> p)
{
    cout << "类外" << endl;
}

// 类
template<class T1,class T2>
class A
{
    friend void func(A<T1,T2> p)
    {
        cout << "类内" << endl;
    }
    // 类外实现,加一个空模板参数列表
    friend void func1<>(A<T1,T2> p);
public:
    A(T1 name, T2 age){
        this->_name = name;
        this->_age = age;
    }
private:
    T1 _name;
    T2 _age
};

例子

#pragma once
#include<iostream>

template<class T>
class Arr {
public:
	Arr(int cap) {
		this->_cap = cap;
		this->_size = 0;
		this->padd = new T[this->_cap];
	}
	Arr(const Arr& arr) {
		this->_cap = arr._cap;
		this->_size = arr._size;
		this->padd = new T[arr._cap];
		for (int i = 0; i < this->_size; i++) {
			this->padd[i] = arr.padd[i];
		}
	}
	Arr& operator=(const Arr& arr) {
		if (this->padd != NULL) {
			delete[] this->padd;
			this->padd = NULL;
			this->_cap = 0;
			this->_size = 0;
		}
		this->_cap = arr._cap;
		this->_size = arr._size;
		this->padd = new T[arr._cap];
		for (int i = 0; i < this->_size; i++) {
			this->padd[i] = arr.padd[i];
		}
		return *this;
	}

	// 尾插
	void push_back(const T& val) {
		if (this->_cap == this->_size) { return; }
		this->padd[this->_size] = val;
		this->_size++;
	}

	// 尾删
	void pop_back() {
		if (this->_size == 0) { return; }
		this->_size--;
	}

	// 下标运算
	T& operator[](int index) {
		return this->padd[index];
	}

	~Arr()
	{
		if (this->padd != NULL) {
			delete[] this->padd;
			this->padd = NULL;
		}
	}
private:
	T* padd;
	int _cap;
	int _size;
};

非类型模板参数

// 一个非类型参数表示一个值而非一个类型
// 非类型模板参数的模板实参必须是常量表达式
template<unsigned N,unsigned M>
int compare(const char(&p1)[N], const char(&p2)[M])
{
	return strcmp(p1, p2);
}

模板编译

当编译器遇到一个模板定义时,它并不生产代码
只有当我们实例化出模板的一个特定版本时,编译器才生成代码

编译时编程

template<int n>
struct Factorial
{
	enum
	{
		val = Factorial<n - 1>::val * n
	};
};

template<>
struct Factorial<0>
{
	enum
	{
		val = 1
	};
};

int main()
{
    // 在编译时就已经计算完成了!
    // val由整数常量479001600替代
	cout << Factorial<12>::val << endl;
	return 0;
}

正如将进行类型参数代替作为一种方便的方法,这意味着产生了一种支持编译时编程的机制。这样的程序成为模板元程序(template metaprogram)。

实际上,模板元编程就是完全的图灵机(Turing complete),因为它支持选择(if-else)和循环(通过递归)。从理论上讲,可以用它执行任何的计算

template<int n>
struct Fib
{
	enum
	{
		val = Fib<n-1>::val + Fib<n-2>::val
	};
};

template<>
struct Fib<1>
{
	enum
	{
		val = 1
	};
};

template<>
struct Fib<0>
{
	enum
	{
		val = 0
	};
};


int main()
{
	cout << Fib<20>::val << endl;
	return 0;
}

表达式模板

表达式模板能够使某些计算得到全方面的编译时优化,表达式模板允许在没有临时变量的情况下使用同一个表达式。
例如:将三个个矩阵或向量相加
D = A + B + C;

这个表达式将会导致一些临时变量的产生,一个是A+B,一个是(A+B)+ C。当这些变量代表极大的矩阵或者向量时,资源的消耗时难以接受的。

#include<cstddef>
#include<cstdlib>
#include<ctime>
#include<iostream>
using namespace std;

// A proxy class for sums of vectors
template<typename, size_t>
class MyVectorSum;

template<typename T, size_t N>
class MyVector
{
private:
	T data[N];

public:
	MyVector<T, N>& operator=(const MyVector<T, N>& right)
	{
		for (size_t i = 0; i < N; ++i)
		{
			data[i] = right.data[i];
		}
		return *this;
	}

	MyVector<T, N>& operator=(const MyVectorSum<T, N>& right);
	
	const T& operator[](size_t i) const
	{
		return data[i];
	}

	T& operator[](size_t i)
	{
		return data[i];
	}

};

// proxy class hold references. uses lazy addition
template<typename T,size_t N>
class MyVectorSum
{
private:
	const MyVector<T, N>& left;
	const MyVector<T, N>& right;

public:
	MyVectorSum(const MyVector<T, N>& lhs,
		const MyVector<T, N>& rhs)
		: left(lhs), right(rhs) {};

	T operator[](size_t i) const
	{
		return left[i] + right[i];
	}
};

// Operator to support v3 = v1 + v2
template<typename T,size_t N>
MyVector<T, N>& MyVector<T, N>::operator=(const MyVectorSum<T, N>& right)
{
	for (size_t i = 0; i < N; ++i)
	{
		data[i] = right[i];
	}
	return *this;
}

// operator+ just stores references
template<typename T,size_t N>
inline MyVectorSum<T, N> operator+(const MyVector<T, N>& left,
	const MyVector<T, N>& right)
{
	return MyVectorSum<T, N>(left, right);
}

// test function
template<typename T,size_t N>
void print(MyVector<T, N>& v)
{
	for (size_t i = 0; i < N; ++i)
	{
		cout << v[i] << ' ';
	}
	cout << endl;
}

template<typename T,size_t N>
void init(MyVector<T, N>& v)
{
	for (size_t i = 0; i < N; ++i)
	{
		v[i] = rand() % 100;
	}
}

int main()
{
	srand(time(0));
	MyVector<int, 5> v1;
	init(v1);
	print(v1);
	MyVector<int, 5> v2;
	init(v2);
	print(v2);
	MyVector<int, 5> v3;
	v3 = v1 + v2;
	print(v3);
}

当MyVectorSum类产生时,它并不进行计算;它只是持有两个待加向量的引用,仅当访问一个向量和的成员时,计算才会发生
但这样并不支持多操作,例如:v4 = v1 + v2 + v3
没有定义MyVectorSum + MyVector的操作
可以通过模板解决

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-04 22:37:15  更:2022-07-04 22:40:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/12 17:39:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码