重新学习《C++Primer5》第16章-模板与泛型编程

16.1定义模板

16.1.1 函数模板

1.模板的定义:template<typename T1,class T2>....
2.实例化模板函数:
3.非类型模板参数:template<unsigned N,unsigned M>当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断的值所代替。模板实参必须是常亮表达式
4.两个重要原则:

  • 模板中的函数参数是const的引用
  • 函数体中的添加判断仅使用<比较运算

5.模板编译:函数模板和类模板成员函数的定义通常放在头文件中

//练习题
template<typename T1,typename T2>
T1 Find(T1 begin, T1 end, T2 value)
{
    T1 it;
    for (it = begin; it != end;++it)
    if (*it == value)return it; 
    return it;
}
template<typename T,unsigned N>
void print(const T (&arr)[N])
{
    for (auto ele : arr)
        cout << ele<<endl;
}
template<typename T,size_t N>
const T* Begin(const T (&a)[N])
{
    return &a[0];
}
template<typename T, size_t N>
const T* End(const T (&a)[N])
{
    return &a[N];
}
template<typename T,size_t N>
size_t constexpr(const T (&a)[N])
{
    return End(a) - Begin(a);
}
//养成使用迭代器和!=的习惯,这两个模板都有定义。

16.1.2 类模板

1.模板的定义和实例化:参数不同实例化出不同的独立的类。
2.成员函数的定义可以在类内或者类外,在类外定义需要加上模板以及参数形式。

template<typename T>
void Blob<T>::check(....)const{}

3.对于一个实例化了的类模板,其成员只有在使用时才被实例化。
4.在类模板作用域中,可以直接使用模板名,而不提供实参。
5.声明友元:template<typename> class BlobPtr;
第一种是将模板类中所以实例都声明为该类的友元:tempalte<teypename T> friend class Pal;
第二种是限定特定实例为友元:首先需前置声明:template<typename T> class Pal;然后是类(类C)中声明:friend class Pal<C>;

template<typename T> class C2{
    friend class Pal<T>;//C2的每个实例都将实例化的Pal声明为友元,Pal的模板声明必须在作用域内
    template<typename X> friend class Pal2;//Pal2所有实例都是C2的每个实例的友元,无需前置声明
    friend class Pal3;//Pal3是非模板类,是C2所有实例的友元,无需前置声明

6.类模板的static成员:与普通类static成员一样,每个static成员必须有且仅有一个定义:template size_t Foo::count=0;
但是,类模板每个实例都有一个独有的static对象,访问时必须引用一个特定的实例:

Foo<int> fi;
auto ct=Foo<int>::count();
ct=fi.count();//使用Foo<int>::count
ct=Foo::count();//错误:使用哪个模板实例的count?

16.1.3 模板参数

1.模板参数名字和作用域:
2.默认模板实参:

//compare有个默认实参less<T>和一个默认函数实参F()
template<typename T,typename F=less<T>>
int compare(const T& v2,const T &v2,F f=F())
{
    if(f(v1,v2))return -1;
    if(f(v2,v1))return 1;
    return 0;
}
//习题
template<typename T>
void print(const T &t)
{
    for (typename T::size_type i = 0; i < t.size(); ++i)
        cout << t.at(i) >> endl;
}

16.1.4 成员模板

1.在普通类中定义函数模板:
2.类模板中的成员模板:类和成员有自己独立的模板参数

template<typename T>//类的类型参数
template<typename It>//构造函数的类型参数
    Blob<T>::Blob(It b,It e):data(make_shared<vector<T>>(b,e)){}

3.实例化成员模板:必须提供类和函数的模板实参,与普通函数模板相同,根据传递的实参来推断模板实参。

16.1.5 控制实例化

1.多个文件实例化相同模板的额外开销非常严重,通过显示实例化来避免这种开销。

extern template declaration;//实例化声明
template declaration;//实例化定义
extern template class Blob<string>;//声明
template int compare(const int&,const int&);//定义

16.2 模板实例推断

16.2.1 类型转换与模板类型参数

1.与非模板函数相比,其实参的转换仅限一下两种:

1.const转换:非const对象的reference(pointer),传递给const的reference(pointer);
2.数组或函数指针转换:如果形参不是reference类型,一个数组实参可以转换为指向首元素的指针。一个函数实参可以转换为一个该函数类型的指针。
template<typename T>T fobj(T,T);
template<typename T>T fref(const T&,conts T&):
string s1("a value");
const string s2("another value");
fobj(s1,s2);//const被忽略,实参被拷贝,因此原对象是否是const没有关系。
fref(s1,s2);//对于引用来说,转换成const是合法的
int a[10],b[10];
fobj(a,b);//数组大小无关紧要,被转换成指针
fref(a,b);//错误:如果形参是引用,则不会转换成指针

Note:自动转换的只有const转换及数组或函数指针的转换,而算术转换,派生到基类转换以及用户定义的转换,都不能应用于模板。

2.如果使用相同模板参数的函数形参,其实参必须是相同的,否则类型不匹配。

//compare接受两个const T&参数
long l1;
compare(l1,1024);//错误
如下正确:
template<typename A,typename B>
int complare(const A&,const B&);

3.如果函数参数不是模板参数,则对实参转换正常进行。

16.2.2 函数模板显式实参

1.当函数返回类型与参数列表任何类型都不相同时,可以指定显式模板参数。

template<typename T1,typename T2,typename T3>
T1 sum(T2,T3);
auto val3=sum<long,long>(a,b);//显式指定T1类型,显式模板实参从左到右与模板参数列表对应,后面的可忽略。所以将要推断的放最后面,必须显式指定所有参数。

auto a=max<double>(1,2.0);
compare<string>("hello","world");

16.2.3 尾置返回类型与类型转换

1.尾置返回

template<typename It>
auto fcn(It beg,It end)->decltype<*beg>
{
    return *beg;
}

16.2.4 函数指针和实参推断

1.当参数是一个函数模板实例的地址时,对每个模板参数,能唯一确定其类型或值。

template<typename T>int compare(const T&,const T&);
void func(int(*)(const string&,const string&));
void func(int(*)(const int&,const int&));
func(compare);//错误:使用哪个compare实例

16.2.5 模板实参推断的引用

1.从左值引用函数参数推断类型

template<typename T> void f1(T&);
f1(i);//i为int,推断出T为int
f1(ci);//ci为const int,推断出T为const int
f1(1);//错误:传递给&必须是一个左值

template<typename T> void f2(const T&);
f2(i);//T为int
f2(ci);//T为int
f2(2);//const &可以绑定到右值,T为int

2.从右值引用函数参数推断类型

template<typename T>void f3(T&&);
f3(3);//实参是一个int类型的右值,模板参数T为int

3.引用折叠和右值引用参数
如上对于f3(i)调用时,通常认为不合法,因为不能将右值引用绑定到左值上。但C++定义了两个例外规则,允许这种绑定:

  • 规则一:当我们调用f3(i)时,编译器推断T为int&。当我们将左值传递给右值引用参数,编译器推断模板类型为实参的左值引用类型。
  • 如果我们坚决创建一个引用的引用,则这些引用形成了“折叠”。
  • X& &,X& &&和X&& &都折叠成类型X&
  • 类型X&& &&折叠成X&&
f3(i);//实参是一个左值,T为int&
f3(ci);//实参是一个左值,T为const int&
//总结:如果函数参数是右值引用,则它可以被绑定到左值;且如果实参是一个左值,则推断出模板实参类型是一个左值引用,且函数参数将被实例化为一个左值引用参数(T&)。

template<typename T>void f4(T&& val)
{
    T t=val;//绑定还是拷贝?
    t=fcn(t);//只会改变t,还是既改变t又变val?
    if(val==t);//true 还是 false?
}
//分析:如果调用f4(i),则会绑定引用,都会改变,一直为ture;如果调用f4(4),则拷贝,只是改变t,false。

16.2.6 理解std::move

1.std::move如何定义

template<typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return static_cast<typename remove_reference<T>::type&&>(t);
}

2.std::move如何工作

string s1("hi!"),s2;
s2=std::move(string("bye!"));
//传递右值,推断T为string,move返回类型是string&&,函数体返回static_cast<string&&>(t),t已经是string&&,转换类型什么都不做。
s2=std::move(s2);
//传递左值,推断T为string&,move返回类型仍是string&&,move的函数参数t实例化为string& &&,会折叠为string&,使用static_cast<string&&>(t),将其转换string&&。

16.2.7 转发

16.3 重载与模板

1.当有多个重载模板对一个调用提供同样好的匹配时,应选择最特例化的版本。

template<typename T> string debug_rep(const T &t)
{
    ostringstream ret;
    ret<<t;
    return ret.str();
}
template<typename T> string debug_rep(T *p)
{
    ostringstream ret;
    ret<<"pointer"<<p;
    if(p)
        ret<<" "<<debug_rep(*p);
    else
        ret<<"null pointer";
    return ret.str();
}
string s("hi");
cout<<debug_rep(s)<<endl;//第一个版本需要进行普通指针到const的转换,第二个调用是精确匹配。
const string *sp=&s;
cout<<debug_rep(sp)<<endl;//第二个更特例化

2.对于一个调用,如果非函数模板和函数模板提供同样好的调用,则选择非函数模板。

16.4 可变参数模板

1.编译器可以推断包中参数的数目

template<typename T,typename... Args>
void foo(const T &t.const Args&... rest);
int i=0;double d=3.14;string s="hello world";
foo(i,s,42,d);//包中有三个参数string,int,doule
foo(s,42,"hi");//包中有两个参数

2.可以使用sizeof运算符得到参数的数目

template<typename...Args>void g(Args...args)
{
    cout<<sizeof...(Args)<<endl;
    cout<<sizeof...(args)<<endl
}

16.4.1 编写可变参数函数模板

ostream& print(ostream &os, const T& t)
{
    return os << t;
}
template<typename T,typename...Args>
ostream& print(ostream& os, const T& t, const Args&...args)
{
    os << t << ",";
    return print(os, args...);
}

16.4.2 包扩展

template<typename... Args>
ostream &errorMsg(ostream &os,const Args&...rest)
{
    return print(os,debug_rep(rest)...);
}
errorMsg(cerr,fcnName,code.num(),otherData,"other",item);//相当于print(cerr,debug_rep(fcnName),debug_rep(code.num())...);

16.4.3 转发参数包

16.5 模板特例化

1.定义函数模板特例化

template<>
int compare(const char* const &p1,const char* const &p2)
{
    return strcmp(p1,p2);
}
//尖括号指出我们将为原模板的所有模板参数提供实参

Note:模板的特例化是在已有的通用模板不再适用于一些特殊的类型参数时,而针对这些特殊的类型参数专门实现的模板
2.特例化的本质是实例化一个模板,而非重载。因此,不影响函数匹配。
3.类模板特例化
4.类模板部分特例化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值