C++模板学习笔记——模板定义(1)

模板是什么

  • 什么是泛型编程:
    所谓的泛型编程就是以一种独立于任何特定类型的编程;
  • 模板是什么:
    模板就是创造泛型类或函数的公式或者蓝图(和我们的作文模板一样,可以根据具体的场景进行应用);

为什么要引入模板

如果想要对不同的类型执行相同的操作,我们可以采取函数重载的方法,但是变量的类型除了基本类型之外,还存在着许多自定义类型,我们总不可能将所有情况都写出来。那么这时模板就派上了用场,它可以生成一套公式,在具体用的时候可以生成不同的类型,这才符合我们写程序的初衷(简洁、快捷,最主要的就是可以少写点代码【如果有人为你的代码量买单,那么就不用考虑模板了】);

引出问题

如果我们要编写一个函数来比较两个数的大小,并给出具体的结果,那么针对不同的变量就需要通过重载函数来实现了

int compare(const int& v1,const int& v2){
	if(v1 < v2 ) return -1;
	if(v1 > v2 ) return 1;
	return 0;
}
int compare(const double& d1,const double& d2){
	if(d1 < d2 ) return -1;
	if(d1 > d2 ) return1;
	return 0;
}

如果采用这种策略,那么如果采用用户自定义的类型,这就是折磨呀(是真的费手呀!!)
如果采用了模板,那就是另外一种亚子了吧

template<typename T>
int compare(const T& v1,constT& v2){
	if(v1 < v2 ) return -1;
	if(v1 > v2 ) return 1;
	return 0;
}

一个模板就是一个公式,可以利用它生成特定类型的函数;template就是模板的关键字,<>就是模板参数列表,其中可以列举多个模板参数并用逗号分隔;
在模板定义中,模板参数列表不能为空
其实模板的作用很像函数参数列表,函数参数列表定义了若干个形参,但是未指出如何初始化他们。在运行时调用者提供实参来初始化形参;同样的道理,在使用模板的时候,我们(显式或隐式)指定模板实参,并将其绑定到模板参数上;

函数模板——模板是作用于函数的

在我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板实参(隐式);例如

cout<<compare(1,0)<<endl;   // T为int

实参类型是int,编译器会推断出模板实参为int,并将其绑定到模板实参T。
编译器实例化一个模板时,它使用实际的模板实参代替对应的模板参数来创建出一个新”实例”。如下面的调用:

cout<<compare(1,0) << endl;   //  T -> int
vector<int> vec1{1,2,3},vec2{4,5,6};
cout << compare(vec1,vec2) << endl;  // T -> vector<int>

//对于int 来说,其生成的模板实例为:
int compare(const int& v1,const int& v2){
	if(v1 < v2 ) return -1;
	if(v1 > v2 ) return 1;
	return  0; 
}
  • 模板参数类型:
    我们可以将模板参数类型看作类型说明符,可以像内置类型或者类类型说明符一样使用—— 可以用来指定返回类型或函数的参数类型、在函数体内声明变量或类型转换;
template <typename T> T foo(T* p){
	T tmp = *p;    //tmp 的类型是指针p指向的类型
	return tmp;
}

类型参数前必须使用关键字class 或 typename

template<typename T,U>    //错误: 在U之前必须加上typename 或 class
template<typename T,class U>  //正确写法
  • 非类型模板参数
    除了定义类型参数外,还可以在模板中定义非类型参数——可以使用特定的类型名来指定非类型参数
    当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替(这些值必须是常量表达式)。
    例如,我们可以编写一个compare版本处理字符串字面常量,由于我们希望能比较不同长度的字符串字面常量,因此可以为模板定义两个非类型的参数。其中,第一个模板参数表示第一个数组的长度,第二个参数表示第二个数组的长度
template<size_t N,size_t M>
int compare(const char(&p1)[N],const char(&p2)[M]){
	return strcmp(p1,p2);
}
//使用这个版本的函数
compare("hi","smj");

编译器会使用字面常量的大小来代替N和M,从而实例化模板;

一个非类型参数可以是一个整型或者是一个指向对象或函数类型的指针或(左值)引用。
绑定到非类型整型参数必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期;指针参数也可以用nullptr 或一个值为0的常量表达式来实例化
在模板定义中,非类型参数就是一个常量值

  • inline 和 constexpr 的函数模板
    函数模板也可以声明为inline和constexpr,其说明符放置位置为模板参数列表之后,返回类型之前(其实也没变)
template<typename T> inline T min(const T&,const T&);  //正确写法
inline template<typename T> T min(const T&,const T&);  //错误
  • 编写类型无关的代码 —— 模板程序应该尽量减少对实参类型的要求
    对于上面的比较函数来说,它就要求了实参类型的变量必须实现了"<" 和 “>” 的操作;

  • 模板编译
    当编译器遇到一个模板定义时,它并不生成代码。只有我们实例化出模板的一个特定版本时,编译器才会生成代码;但是这个特性影响了我们如何组织代码以及错误何时被检测到(用到它的时候才知道有没有错)。

  • 函数模板和类模板成员函数的定义通常放在头文件中;
    模板的设计者应该提供一个头文件,包含模板定义以及在类模板或成员定义中用到的所有名字的声明。模板的用户必须包含模板的头文件,以及用来实例化模板的任何类型的头文件;

  • 保证传递给模板的实参支持模板所要求的操作,以及这些操作在模板中能正确的工作是调用者的责任。

类模板

  • 类模板是用来生成类的蓝图的。与函数模板不同,编译器不能为类模板推断模板参数类型。因此我们必须显式给出模板实参类型。即在模板名后的尖括号中提供模板实参类型。
  • 定义类模板 —— Blob 类 提供了对 vector 容器操作的 检查(下标越界)
template<typename T> 
class Blob{
public:
	typedef typename std::vector<T>::size_type size_type;
	//构造函数
	Blob();
	Blob(std::initializer_list<T> il);
	//Blob中的元素数目
	size_type size() const {return data->size();}
	bool empty() const { return data->empty();}
	//添加和删除元素
	void push_back(const T& t) { data -> push_back(t);}
	//移动版本
	void push_back(T&& t) { data->push_back(std::move(t));}
	void pop_back();
	//元素访问
	T& back();
	T& operator[](size_type i);
private:
	std::shared_ptr<std::vector<T>> data;
	//若data[i] 无效,则抛出msg
	void check(size_type i, const std::string &msg) const;
};

//实例化Blob
Blob<int> ia;         
Blob<int> ia2 = {0,1,2,3,4};
//对于我们指定的每一中元素类型,编译器都生成一个不同的类
Blob<std::string> names;
Blob<double> price;

一个类模板的每个实例都形成一个独立的类,即两个不同类型的类没有任何关联,也不会对任何其他Blob类型的成员有特殊访问权限

  • 在模板作用域中引用模板类型
    类模板用来实例化类型,而一个实例化的类型总是包含模板参数的(换句话说就是,类模板就是英语作文模板,而试卷上的作文是套用模板的——总不能模板画线你也画线,你不填让判卷老师给你填?)

  • 类模板的成员函数
    与其他类相同,我们既可以在类模板内部,也可以在类模板外部为其定义成员函数,且定义在类模板内的成员函数被隐式声明为内联函数。
    类模板的成员函数本身是一个普通函数。但是,类模板的每个实例都有其自己版本的成员函数。因此,类模板的成员函数具有和模板相同的模板参数,因而,定义在类模板之外的成员函数必须以与类模板相同的模板开头(总不能让外面的成员函数不知道它是谁家的吧!!)。具体如下所示:

template <typename T> void Blob<T>::check(size_type i,const std::string &msg) const {
	if( i >= data->size() || i < 0){
		throw std::out_of_range(msg);
	}
}

template<typename T> T& Blob<T>::back(){
	check(0,"back on empty Blob");
	return data->back();
}
template <typename T> T& Blob<T>::operator[](size_type i){
	check(i,"subscript out of range");
	return (*data)[i];
}
template<typename T>  void Blob<T>::pop_back(){
	check(0,"pop_back on empty Blob");
	data->pop_back();
} 
template<typename T> Blob<T>::Blob():data(std::make_shared<std::vector<T>>){}
template<typename T> Blob<T>::Blob(std::initlalizer_list<T> il):data(std::make_shared<std::vector<T>>(il)){}
  • 类模板成员函数的实例化——一个类模板的成员函数只有当程序用到它时才进行实例化
    默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化
  • 在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板实参
  • 类模板和友元
    当一个类包含一个友元声明时,类与友元各自是否时模板是相互无关的。如果一个类模板包含一个非模板友元,则友元被授权可以访问所有模板实例。如果友元自身是模板,类可以授权给所有友元模板实例,也可以只授权给特定实例。
  1. 一对一友好关系(我只认得你的脸,你要是连脸都换了,那我们再见只是陌生人!):
//前置声明
template<typename> class BlobPtr;
template<typename> class Blob;
template<typename T> bool operator==(const Blob<T>&,const Blob<T>&);
template<typename T> class Blob{
	//每个Blob实例将访问权限赋予用相同类型实例化的BlobPtr和相等运算符
	friend class BlobPtr<T>;
	friend bool operator==(const Blob<T>&,const BlobPtr<T>&);
	.....
};
Blob<char> ca;
Blob<int> ia;

上面的代码说明BlobPtr< char> 的成员可以访问ca(或其他Blob< char>对象)的非public部分,但ca 对于 ia (或其他Blob< char>对象)或其他类型实例都没有特殊访问权限

  1. 通用和特定的模板友好关系(如果你是我的朋友,换件衣服也是)
template<typename T> class Pal;
class C {
	friend class Pal<C>;
	template <typename T> friend class Pal2;
};
template<typename T> class C2{}
	friend class Pal<T>;
	template<typename X> friend class Pal2;
	friend class Pal3;
;

为了让所有实例成为友元,友元声明中必须使用与类模板本身不同的模板实参

  1. 令模板自己的类型参数成为友元
template<typename Type> class Bar{
	friend Type;  //将访问权限授予用来实例化Bar类型
};

上述代码将用来实例化Bar的类型声明为友元,即对于某个类型名Foo,Foo将成为Bar< Foo>的友元;
值得注意的是,虽然友元通常来说应该是一个类或是一个函数,但我们可以完全用一个内置类型来实例化Bar,这种内置类型的友好关系是允许的,以便我们能过用内置类型来实例化Bar这样的类。

  • 模板类型别名——一个模板类型别名是一族类的别名
template<typename T> using twin = pair<T,T>;
twin<string> author ;  //author 是一个pair<string,string>

//当我们定义一个模板类型别名时,可以固定一个或多个模板参数
template<typename T> using partNo = pair<T,unsigned>;
partNo<string> books;  //books 是一个 pair<string,unsigned>
  • 类模板的static 成员
template<typename T> class Foo{
public:
	static std::size_t count(){return ctr;}
private:
	static std::size_t ctr;
};

以上代码可以解释为所有Foo< X>类型的对象共享相同的ctr对象和count函数。
与其他static的数据成员类似,模板类的每个static数据成员必须有且仅有一个定义。但是,类模板的每个实例都有一个独有的static对象,因此,与定义模板的成员函数类似,我们将static数据成员也定义为模板。
类似于其他成员函数,一个static成员函数只有在使用时才会实例化。


参考文献:C++ Primer(第五版)
以上仅为本人学习笔记,仅做学习记录用途

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值