编程范式——模板(概念及常用编程实践)

模板机制

编程范式分为两种:
1:面向对象
	封装
	继承
	多态
	运行时抽象
2:泛型编程
	类模板
	函数模板
	概念
	编译时抽象

一般编程中,较少使用到泛型编程,很多人认为泛型编程属于“专家编程”,实际工作中,基本都是绕着走,基本不用。
泛型编程应该成为正常编程活动的一部分————Bjarne stroustrup

模板

C++模板是一种编译时机制,编译时生成具体代码,使用实参模板定义实例化为具体的类型或函数,C++支持两种模板:
1:类模板
2:函数模板

模板实例化时,编译器会对实参类型进行检查,确保实参符合对模板参数的操作要求。C++模板参数支持两种:
1:类型参数,可隐式约束,也可显式约束
2:值参数,编译时常量,或constexpr函数。不同值参数是不同类型
3:可为模板参数提供默认值

对模板参数进行显式约束,即C++ 20的概念

模板类成员

1:普通成员:使用与主模板相同类型模板参数,包括:
	数据成员:变量,常量	
	成员函数
	静态成员:数据或函数
	成员类型别名
	成员类型
2:成员模板:使用与主模板不同的类型参数
	成员模板不能定义虚函数(模板实例化会导致连接器不断为虚表增加虚函数增项)

注意:所有普通类成员规则同样适用于模板类成员
//template <typename T=int, long Size=10> //T是类型参数,size是值参数
成员(数据、类型、函数、成员函数/类型、别名)
#include <iostream>
using namespace std;

template <typename T>
class Complex{
    T re, im;	//数据成员

public:
    //成员类型别名,以前用typedef,现在统一使用using
//    using SType=typename SumTraits<T>::SumType;	//简单理解:using SType=T;
    using SType=T;	//简单理解:using SType=T; 

	//struct Point{int x, int y};
    //如上写法提示出错:expected unqualified-id before ‘int’,为了和C一致,修改如下吧
    struct Point{
		int x; 
		int y;
	};//类型成员

    Complex(const Complex&)=default;

    T real() const {return re;}	//函数成员
    T imag() const {return im;}

    Complex(T _re, T _im):re{_re}, im{_im}
    {}

    virtual void print(){
        cout<<"["<<re<<","<<im<<"]"<<endl;
    }
    
	//以上是模板成员,都只使用T===以下是成员模板,不能是虚函数
    template<typename U>	//U是一个新的类型————成员模板
    Complex(const Complex<U>& c):
        re{static_cast<T>(c.real())},
        im{static_cast<T>(c.imag())}
        {

        }
    
    template<typename U>
    void add(U r, U i) // 不可以是虚函数
    {
        this->re+=r;
        this->im+=i;
    }
};

int main(){
    Complex<int> c1{100,200};
    Complex<double> c2{34.7, 89.9};
    Complex<int> c3=c2;

     c1.print();
     c2.print();
     c3.print();

    c2.add(100,200);
    c2.add(100.234,200.234);

     c2.print();
}
//编译执行:
kongcb@tcu-pc:~/testcode/template$ g++ member_template.cpp -o member_template
kongcb@tcu-pc:~/testcode/template$ ./member_template
[100,200]
[34.7,89.9]
[34,89]
[234.934,490.134]
类模板(虚函数)
#include <iostream>
using namespace std;

template <typename T> 
class Array {
private:
	T* ptr;
	int size;

public:
    Array(){}
	Array(T arr[], int s);
	void print();
    virtual void show();
};

template <typename T> 
Array<T>::Array(T arr[], int s)
{
	ptr = new T[s];
	size = s;
	for (int i = 0; i < size; i++)
		ptr[i] = arr[i];
}

template <typename T> 
void Array<T>::print()
{
	for (int i = 0; i < size; i++)
		cout << " " << *(ptr + i);	//此处如果+写错成*,只要不调用,也不报错
	cout << endl;
}

template <typename T> 
void Array<T>::show()	//虚函数无论是否调用,一定实例化
{
	for (int i = 0; i < size; i++)
		cout << " " << *(ptr + i);	//如果此处+写错*,无论是否调用,报错,
	cout << endl;
}

//如果上面show写错了,此处可以强制显式实例化,检查错误
template class Array<int>; //显式实例化模板

int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	Array<int> a(arr, 5);	//int替换T——模板实例化
	a.print();	//如果不调用print,即使print有错误,不会报错
}

//调试输出如下:
kongcb@tcu-pc:~/testcode/template$ ./template_class
 1 2 3 4 5
类模板(传参数)
#include <iostream>
#include <array>
using namespace std;

//值参数不能是浮点数或类对象,以及编译时不能确定的值不行
//template <typename T=int, long Size=10> //size类型和下面函数template不一致,
//之后定义类成员函数提示:对不完全的类型‘class Array<T, Size>’的非法使用——调试了好一会

template <typename T=int, size_t Size=10> //类型参数T,size值参数,提供默认参数
class Array {
private:
	T* ptr;

public:
    Array(){}
	Array(T arr[]);	//也可以Array(T* pArr);
	void print();
};

template <typename T, size_t Size> 
Array<T,Size>::Array(T arr[])	//类成员函数需要在上面template
{
	ptr = new T[Size];
	for (int i = 0; i < Size; i++)
		ptr[i] = arr[i];//*(pArr+i);//arr[i];
}

template <typename T, size_t Size> 
void Array<T,Size>::print()
{
	for (int i = 0; i < Size; i++)
		cout << " " << *(ptr + i);
	cout << endl;
}

int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };

	Array<int ,10> arrInt{arr};	//类型及相关参数传递
	arrInt.print();

//	Array<int, 100> arrLong{arr};//和Array<int ,10>是不同类型
}

//调试运行结果:
kongcb@tcu-pc:~/testcode/template$ g++ template_arg_value.cpp -o template_arg_value
kongcb@tcu-pc:~/testcode/template$ ./template_arg_value                            
 1 2 3 4 5 32767 0 0 4196832 0	//只保证arr[5],后面的值随机

类型别名与模板别名

1:为模板类使用指定别名
	类型别名(alias type):指定所有模板参数,得到完整类型
		//using svector=vector<string>; //指定所有参数,完整类型
		//可以直接使用:svector s{"hello"s, "boolan"s, "cpp"s, "camp"s};
	
	模板别名(alias template):指定部分参数,得到模板类型
		//template<typename T>
		//using SmallVec=vector<T, SmallAlloca<T>>;//指定部分参数,模板类型
		//使用时需要填充类型:SmallVec<int> si{1,2,3,4,5};
	
	成员类型别名:类模板中,通过定义类型别名,来定义“关联类型”
	别名与原始模板完全等价:包括使用模板特化时(但不支持特化别名)
	
2:优先使用using而不是typedef
	两者都可以声明类型别名、成员类型别名
	using可以定义别名模板,而typedef不能
	using可以免掉类型内typdef要求的typename前缀,和::type 后缀
别名示例(类型别名+模板别名)
#include <iostream>
#include <string>
#include <memory>
#include <vector>
using namespace std;

// template<typename T, typename Alloca=alloctor<T>> 
// vector;
using svector=vector<string>; //指定所有参数,完整类型————类型别名

template<typename T>
using SmallVec=vector<T, SmallAlloca<T>>;//指定部分参数,模板类型————模板别名

void process(){
    svector s{"hello"s, "boolan"s, "cpp"s, "camp"s};

    SmallVec<int> si{1,2,3,4,5};	//模板别名
}

template <typename T>
class Array {
private:
	T* ptr;
	int size;

public:
    using value_type=T;		//成员类型别名
    using iterator=Array_Iter<T>;	//成员类型,使用了另外一个模板类
};

int data;
Array<int>::value_type data;	//value_type此处就是int

模板参数类型自动推导

1:C++模板编译时支持对类型参数的自动推导
	普通函数参数
	类成员函数参数
	构造函数参数(C++17),模板所有类型参数都有值

2:模板类型推导时
	引用性被忽略:引用类型实参当非引用类型处理
	转发引用:左值按照左值,右值按照右值
	值传递:实参中const/volatile修饰会去掉

自动推导示例
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <utility>
using namespace std;

template<typename T>
void process1(T& value)	//函数模板
{
     cout<<value<<endl;
}

template<typename T>
void process2(T value)
{
    cout<<value<<endl;
}

template<typename T>
void invoke(T&& value)
{
    //do(std::forward<T>(value));
}

int main()
{
    vector<int> c1(100);//显式类模板推导
    vector c2{10,20,30};//自动推导,C++17支持
    
    pair<int, string> keyv1{100, "C++"s};
    pair keyv2{200,"Java"s};
    auto keyv3=make_pair(300,"GO"s);//函数模板,C++17之前就支持
    keyv3=keyv2;
    cout<<keyv3.first<<":"<<keyv3.second<<endl;

    int data1=100;
    int& data2=data1;
    const int data3=data1;
    const int& data4=data1;

    //template<typename T> 
    //void process1(T& value);————传引用
    process1(data1);//  T是int, value 是int&
    process1(data2);//  T是int, value 是int& ———— 引用性被忽略

    process1(data3);//  T是const int, value 是const int&
    process1(data4);//  T是const int, value 是const int&

    //template<typename T>
    //void process2(T value)————传值
    process2(data1);// T 是int, value 是 int
    process2(data2);// T 是int, value 是 int ********** 引用性被忽略

    process2(data3);// T 是int, value 是 int ********** 传值时,const/volatile修饰去掉了
    process2(data4);// T 是int, value 是 int **********
}

//执行结果
kongcb@tcu-pc:~/testcode/template$ g++ type_infer.cpp -o type_infer
kongcb@tcu-pc:~/testcode/template$ ./type_infer
200:Java
100
100
100
100
100
100
100
100
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超级波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值