C++ Templates 第一部分阅读笔记

第二章  函数模板

int const N = 100;//const用来修饰前面的int是个常值

int* const book mark;//指针不能改变,指针指向的值是可以改变的

typedef char* CHARS;
typedef CHARS const CPTR;//指向char类型的常量指针
typedef char* const CPTR;//仍然是指向char类型的常量指针
同样的规则也适用于volatile限定符

void foo(int const& x);//参数类型和参数名称是分离的

函数模板是那些被参数化的函数,是一个函数家族,有些函数元素是未确定的
关键字typename引入了所谓的类型参数T,到目前为止它是C++程序使用最广泛的模板参数
必须要保证所用类型支持模板的参数
这种用具体类型代替模板参数的过程叫做实例化,它产生了一个模板的实例


template<typename T>
inline T const& max(T const& a, T const& b);
...
max(4,7);
max(4,4.2);//两个参数的类型不同会产生报错
解决办法如下:
1. max(static_cast<double>(4), 4.2);
2. max<double>(4, 4.2)
3. 指定两个参数可以具有不同的类型

函数模板有两种类型的参数:模板参数和调用参数

template<typename T1, typename T2, typename RT>
inline RT max(T1 const& a, T2 const& b);
...
max<int, double, double>(4,4.2);//ok

template<typename RT, typename T1, typename T2>
inline RT max(T1 const& a, T2 const& b);
...
max<double>(4,4.2);//只是显示指定第一个实参,让演绎过程推导出其余的实参
返回值的类型是不能被隐式推导的,返回值的类型在函数调用参数的类型中是没有的。

函数模板也是可以重载的
一个非模板参数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板参数
重载解析的过程通常会调用非模板函数,而不会从该模板产生出一个实例
模板不允许自动类型转化,但普通函数可以进行自动类型的转换

函数所用的重载版本的声明都应该位于该函数被调用的位置之前。


第三章  类模板

类也可以被一种或多种类型参数化。容器类,通常被用于管理某种特定类型的元素
类模板的声明:
template<typename T>
class Stack
{
  ...
};

template <typename T>
class Stack {
    ...
    Stack (Stack<T> const&);//拷贝构造函数
    Stack<T>& operator=(Stack<T> const&);//赋值运算符
    ...
};
PS:这个类的类型是Stack<T>,其中T是模板参数,因此当在声明中需要使用该类的类型时,你必须使用Stack<T>
然而,当使用类名而不是类的类型时,只用Stack即可。

当vector为空的时候,它的back()方法(返回末端元素的值)和pop_back()方法(删除末端元素),判空之后就要抛出异常。
任何成员函数都可以写成内敛函数的形式,将它定义于类声明里面。
为了使用类模板,你必须显式的指定模板实参
c++的类型定义只是定义了一个“类型别名”,并没有定义一个新的类型,两者实际上是相同的类型,并且可以用于相互赋值
模板的参数可以是任何的类型,该类型必须提供被调用函数的所有操作
Stack<float*> floatPtrStack;//元素类型为浮点型指针的栈
Stack<Stack<int> > intStackStack;//元素类型为int栈的栈,两个“>”之间必须要空一格,否则编译器会报语法错误

类模板的特化:
你可以使用模板实参来特化类模板,进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应地被进行特化的类型取代。


局部特化:
如果有多个局部特化同等程度的匹配某个声明,那么该声明就具有二义性

缺省模板实参:
template<typename T, typename CONT = std::vector<T> >
class Stack
{
...
};
//int 栈,不传参数的话就默认用vector来管理。
Stack<int> intStack;

//double栈,它使用std::deque来管理元素
Stack<double,std::deque<double> > dblStack;
可以把用于管理元素的容器,定为第2个模板参数,并且使用std::vector<>作为它的缺省值

对于模板而言,只有那些被调用的成员函数,才会被实例化。

非类型模板参数:
模板参数也可以传数组等非类型的参数。
限制:浮点数和类对象(class-type)是不允许作为非类型模板参数的
由于字符串文字是内部链接对象(因为两个具有相同名称但处于不同模块的字符串,是两个完全不同的对象),所以你不能使用它们来作为模板的实参
另外你也不能使用全局指针作为模板参数,然而你可以这样使用:
template<char const* name>//这个常是修饰啥的?
class MyClass{
...
};
extern char const s[] = "hello";//全局字符数组s由“hello”初始化,是一个外部链接对象
MyClass<s> x;//ok
小结:
模板可以具有值模板参数,而不仅仅是类型模板参数。
对于非类型的模板参数,你不能使用浮点数,class类型的对象和内部链接对象(例如string)作为实参。
第五章 技巧性基础知识
typename 关键字
template<typename T>
class MyClass{
    typename T::SubType *ptr;//SubType是定义于类T内部的一种类型,因此ptr是一个指向T::SubType类型的指针。如果不使用typename,SubType会被认为是一个静态成员,那么它应该是一个具体的变量和对象
    
    ...
};
nested class(嵌套类)
模板的模板参数--身为模板的模板参数
为了访问模板参数为T的const_iterator类型,需要在声明的时候使用关键字typename来加以限定
typename T::const_iterator pos;

template <int N>
void printBitset(std::bitset<N> const& bs)
{
    std::cout<<bs.template to_string<char,char_traits<char>,allocator<char> >();
}
传入参数bs就是依赖于模板参数N的构造。只有当该前面存在依赖于模板的对象时,我们才需要在模板内部使用.template标记
现在建议你记住一条规则:对于那些在基类中声明,并且依赖于模板参数的符号(函数或者变量等)
你应该在它们前面使用 this->或者Base<T>::如果希望完全避免不确定性,你可以(使用诸如this->和Base<T>::等)限定(模板中的所有成员访问)
模板的模板参数
template<typename T,
            template<class ELEM> class CONT = std::deque>
class Stack{
    ...
};    
//ELEM如果不需要的话可以去掉
相应的成员函数也必须做出更改
template<typename T, template<typename> class CONT>
void Stack<T,CONT>::push (T const& elem)
{
    elem.push_back(elem);
}
函数模板并不支持模板的模板参数
函数模板的实参匹配:
template<typename T>
void foo()
{
    T x = T(); //如果T是内建类型,
}

template<typename T>
class MyClass{
    private:
    T x;
    public:
        MyClass() : x(){//确认x已经被初始化
        }
        ...        
};
第五章的小结:
1.要访问依赖于模板参数的类型名称,必须添加关键字typename
2.嵌套类和成员函数也可以是模板
3.赋值运算符的模板版本并没有取代缺省赋值运算符
4.类模板也可以作为模板参数,我们称之为模板的模板参数
5.模板的模板实参必须精确匹配,匹配时不会考虑其缺省的模板实参
6.通过显式调用缺省构造函数,可以保证模板的变量和成员已经用一个缺省值完成了初始化
7.对于字符串,在实参演绎的过程中,当且仅当参数不是引用的时候,会发生(array-to-pointer)的类型转换(称为decay)
第六章 模板实战
类和其他类型都被放在一个头文件中(.hpp)
对于全局变量和(非内联)函数,只有声明放在头文件中,定义则位于dot-C文件(拓展名一般为.cpp)中)中

typeid运算符用于输出一个字符串(显示调用者的类型)            
从包含模型中得出的另一个重要的结论是:非内联函数模板在调用的位子并不会被拓展,而是当它们基于某种类型进行实例化之后,才产生一份新的(基于该类型的)函数拷贝
关键字export用在template之前,你仍然可以隐式或者显式定义内联成员函数,然而内联函数确是不可导出的
类类型包括联合,而类不包括联合。
我们将使用函数模板和成员函数模板
模板实例化是一个通过使用具体值替换模板实参,从模板生出普通类、函数或者成员函数的过程。这个过程最后获得的实体就是我们通常所说的特化

template<typename T>//局部特化
class MyClass<bool,T>{
...
};
下列都属于非定义声明:
1.class C//类C的声明
2.void f(int p);//函数f的声明,其中p是一个被命名的参数
3.extern int v;//变量v的声明
另外宏定义和goto语句不属于声明
如果已经确定了这种C++构造(即声明)的细节,或者对于变量而言,已经为它分配了内存空间,那么声明就变成了定义(definition)
对于类类型(class type)或者函数的“定义”,这意味着必须提供一对花括号内部的实体。而对于变量而言,进行初始化和不具有extern关键字的声明都是定义
class C{}; //类C的定义(和声明)

void f(int p ){ //函数f()的定义(和声明)
    std::cout << p << std::endl;
}

extern int v =1; //一个初始化器使之成为V的定义

int w; //前面没有extern 的全局变量声明,同时也是定义
一个翻译单元(translarion unit):处理一个源文件所获得的的结果,就是说它包括#include指示符所包含的内容(即所包含的头文件)
模板参数是指:位于模板声明或者定义内部,关键字template后面所列举的名称。
模板实参是指:用来替换模板参数的各个对象。模板实参必须是一个可以在编译期确定的模板实体或者值
预处理头文件技术:
很多的程序最前面N行包含了很多相同的头文件,将前N行的程序编译完,并把编译器在这一点的完整状态存在一个所谓的预编译头文件中
再有文件需要编译的时候可以直接加载已保存的状态。第一次这个操作要比编译N行代码还要慢。
被包含的众多头文件的顺序也是很重要的
创建一个std.hpp的头文件,让它包含所有的标准头文件。同时我们应该对那些属于更稳定级别的头文件进行预编译,然后在不太稳定的头文件
中重用这个稳定的预编译头文件,从而提高整个编译的效率。
tracer类跟踪程序
实例化之后的模板代码会产生很长的符号串
浅式实例化???这个还是不太理解。。。

STLFilt的程序,提供了一种方法,用于解读多种编译器输出的stl错误信息。

猜你喜欢

转载自blog.csdn.net/qq_35353824/article/details/89321551