《C++标准程序库》第二章摘录与笔记

来源:互联网 发布:海康设备域名怎么设置 编辑:程序博客网 时间:2024/06/06 20:35

《C++标准程序库》第二章,摘录与笔记

2.2.1 模板

所谓“template”,是针对“一个或多个尚未明确的型别”所撰写的函数或类别。使用template时,可以显示或者隐式地将型别当做参数来传递。
template并非一次编译便生出适合所有型别的代码,而是针对被使用的某个(或某组)型别进行编译。这导致一个重要的问题:实际处理template时,

面对函数模板,你必须先提供他的某个实作品,然后才能调用,如此方可通过编译。

————————————————————————————————————————————————————————————————————————

(摘录自《C++primer》16.3节模板编译模型)两种编译模型:
两种模型中,构造程序的方式很大程度上是相同的:类模板定义和函数模板声明放在头文件中,而函数模板定义和类成员函数定义放在源文件中。
(与普通意义上的程序文件组织相同,但是不同的是:模板在进行实例化时需要编译器必须能够访问定义模板的源代码。所以当调用函数模板或类模板的成员函数的时候,
编译器需要函数的定义,需要那些通常放在源文件中的代码。)两种模型的不同在于,编译器怎样使用来自源文件的定义。
(1)包含编译模型。
编译器必须看到用到的所有模板的定义。一般而言,可以通过在声明函数模板或类模板的头文件中添加一条#include指示使定义可用,该#include引入了包含相关定义的源文件。
示例:
// header file test.h#ifndef TEST_H#define TEST_Htemplate <typename T> int func(const T&, const T&);// other declarations#include "test.cpp"// get the definitions for func etc.#endif // TEST_H// implementation file test.cpptemplate <typename T> int func(const T &v1, const T &v2){...... // define body}// other definitions

(2)分别编译模型。
编译器会为我们跟踪相关模板定义。我们必须让编译器知道要记住给定的模板定义,可以使用export关键字来做这件事(现有标准C++编译器的实现中大部分还未实现export用于分别编译模型)。export指明给定的定义可能会需要在其他文件中产生实例化。在一个程序中一个模板只能定义为一次。编译器在需要产生致谢实例化时及时出怎样定位模板定义。export不必再模板声明中出现。
————————————————————————————————————————————————————————————————————————
模板参数既可以有类型参数也可以有非类型参数,还可以提供默认参数。

标准C++还提供了typename关键字用于作为类型之前的标识符,指示之后的标识符为类型而不是非类型。如:
template <class T>class MyClass {typename T::SubType *ptr;...};
这里ptr是一个类型的对象,而不是与之前标识符的乘积。此处任何用来取代T的类型,其内部必须提供一个内部类型SubType的定义。

class Q{typedef int SubType;// class SubType;// OK too....};
C++的一般规则是,除了以typename修饰之外,template内部的任何标识符号都被视为一个值而非一个类型。

成员模板:

类成员函数可以是个模板,但这样的成员模板既不能是虚函数,也不能有缺省参数。
如:
class MyClass{template <typename T>void func();...};
这个特性通常为模板类中的成员提供自动类型转换。如:
template <typename T>class MyClass{private:T value;public:void assign(const MyClasls<T> &x){ // x must have same type as *thisvalue = x.value;}};
即使两个类型可以自动转换,如果我们对assign使用不同的template类型,也会出错:
void func(){MyClass<double> d;MyClass<int> i;d.assign(d); // OKd.assign(i); // ERROR: i is MyClass<int> but MyClass<double> is required}
使用成员模板设计类模板
template <typename T>class MyClass {private: T value;public:template <type X> // member template void assign(const MyClass<X> &x){ // allows different template typesvalue = x.getValue();}T getValue() const// {return value;}...};void func(){MyClass<double> d;MyClass<int> i;d.assign(d); // OKd.assign(i); // OK(int is assignable to double)}
注意:在编译时,生成两个类型,d和i是两种不同的类型对象。而在类模板的定义中,成员模板assign参数x和*this类型不一定相同同,所以不能直接存取MyClass<>的private和protected成员,如在d.assign(i)编译时,(此时生成了一个新的类)assign参数的类型为MyClass<int>而*this的类型为MyClass<double>,这是两个不同的类,所以不能在一个类中直接使用另一个类的private和protected成员,可以使用共有成员函数进行处理,如上。

构造函数模板时成员模板的一种特殊形式。构造函数模板通常用于“在复制对象时实现隐式类型转换”。注意,构造函数模板并不遮蔽(hide)隐式拷贝构造函数(implicit copy constructor)。如果类型完全吻合,隐式拷贝构造函数就会被产生出来并被调用。如:
template <typename T>class MyClass{public:// copy sonstructor with implicit type conversion// -does not hide implicit copy constructortemplate <typename U>MyClass(const MyClass<U>& x);...};void func(){MyClass<double> xd;...MyClass<double> xd2(xd);// calls built-in copy constructorMyClass<int> xi(xd);// calls template constructor...}

2.2.2 基本类型的显示初始化

如果采用不含参数、明确的构造函数调用语法,基本类型会被初始化为0:
int i1; // undefined value
int i2 = int(); // initialized with zero
这个特性可以确保我们在撰写模板代码时,任何类型都有一个确切的初值!如:
template <typename T>void f(){T x = T(); // x is initialized with zero...}

2.2.3 异常处理

通过异常处理,C++标准程序库可以在不“污染”函数接口(亦即参数和返回值)的情况下处理异常。

可以运用所谓的异常规格来指明某个函数可能抛出哪些异常。如:
void f() throw(bad_alloc); // f()只可能抛出bad_alloc异常
如果声明一个空白异常规格,那就表明该函数不会抛出任何异常:
void f() throw(); // f()不抛出任何异常

2.2.6 关键字explicit

通过关键字explicit的作用,我们可以禁止“单参数构造函数”被用于自动类型转换。如:

class Stack{explict Stack(int size);// create stack with initial size...};Stack s;...s = 40; // Error,不会进行自动类型转换(int——>Stack)
注意 explicit同样也能阻绝“以赋值语法进行带有转型操作的初始化”:
Stack s1(40); // OK
Stack s2 = 40; // Error,int 40无法转换为大小为40个元素的Stack。
原因:
X x;
Y y(x); // explicit conversion

X x;
Y y = x; // implicit conversion,会产生临时变量,效率也低

2.2.7 新的类型转换操作符

dynamic_cast类型转换操作符作用,将多态类型向下转型为其实际静态类型。(本来是子类类型,先经过向上转型后,现在向下转型则需要这个操作符)
这是唯一在执行期进行检验的转型动作。你可以用它来检验某个多态对象的类型。(如果经过向下转换不是指向某个对象的静态类型,则返回NULL。若是向上转型时是引用则抛出异常)另外,C++提供的四个转型操作符只能接受一个参数,如果有多个参数,按逗号操作符对待,只是用最后一个参数。

2.2.8 常量静态成员

常量静态成员初始化,可以在类声明中直接对其进行初始化,这个常数便可以用于class之中,如:
class MyClass{static const int num = 10;int elems[num];...};
注意,你还必须为class之中声明的常量静态成员定义一个空间:
const int MyClass::num; // no initialization here

原创粉丝点击