C++重读二:C++基本语法(下)

0.       类型转换中的提示实际上就是内置数据类型的提升,如char转换为intbool转换为intfloat转换为double等。

1.       类型转换中的标准转换有五种类型:(1)整值类型转换(不包括提升)(2)浮点转换(3)浮点-整值转换(4)指针转换(5)bool转换。前三种转换是有潜在危险的转换。所有的标准转换都是等价的。一些注意点:0可以被转换为任何指针类型,这样创建的指针称为空指针值,同时0也可以是任何整型常量表达式。常量表达式 0L 0x00都属于整值类型因此能够被转换为int *的空指针值。指针转换允许任何指针类型的实参转换成void *,但是函数指针不能用标准转换为void *

2.       对于引用参数来说,如果实参是该引用的有效初始值,则为精确匹配,否则为不匹配。

3.       模板非类型参数代表了一个常量表达式,由一个普通的参数声明构成,表示该参数名代表了模板定义种的一个常量。在实例化时,该常量会被一个编译时已知的常量值代替。

4.       在模板中,为了支持类型表达式,必须使用typename;如typename Param::name *p;定义了一个指针p;如果不使用typename则该表达式为乘法。另外,模板函数也可以被声明为inlineextern,必须把指示符放在参数模板后。如template <typename T> inline T fun1(){…}

5.      函数实参的推演中三种类型转换是允许的:(1)左值转换;(2)限定转换(constvolatile指示符)(3)到一个基类的转换:如果实参是一个类,它有一个从被指定为函数参数的类模板实例化而来的基类;如下面的代码:template <typename T> class Array {}; template <typename T> T min(Array<T> &array) {…}

6.       可以使用显式指定模板参数,模板参数被显式指定在逗号分隔的列表中,紧跟在函数模板实例的名字后面;如min<unsigned int>(a, b);代码强行指定模板参数以unsigned int转换。模板显式指定参数类似于默认参数,只能省略后面的参数

7.       C++支持两种模板编译模式:包含模式和分离模式。包含模式下,在每个模板被实例化的文件中包含函数模板的定义,并且往往把定义放在头文件中;分离模式下,函数模板的声明被放在头文件中,它的定义放在另外一个实现文件中,函数模板在实现文件中的定义必须使用关键字export

8.       C++支持模板的显式实例化,在显式实例化声明所在的文件中,函数模板的定义必须被给出。如template <typename T> T f(T t); 显式实例化为int *类型,template int *f(int *);

9.       C++支持模板的显式特化定义,如:template <typename T> T max(T t1, T t2){…}显式特化const char *类型的定义:template <> const char *max<const char *>(const char *c1, const char *c2);显式特化隐藏了通用模板对于该类型的实例如果模板实参可以从函数参数中推演出来,则模板实参的显式特化可以从其中取消。如上面的例子: template <> const char *max(const char *, const char *);其省略与显式指定模板参数一样。

10.   抛出异常可通过throw表达式来实现。如抛出一个popOnEmpty(类对象)的异常,表达式为throw popOnEmpty();同时在catch子句中,为了防止大型类对象的复制,可以将catch子句的异常声明改为引用

11.   在查找用来处理被抛出异常的catch子句时,因为异常而退出复合语句和函数定义,这个过程称为栈展开C++保证,随着栈的展开,尽管局部类对象的生命期是因为抛出异常而结束,但是所有的析构函数将被调用。要重新抛出接受到的异常,使用语句throw;重新抛出的过程中希望对接受到的异常对象进行修改,必须将catch子句的异常声明改为引用

12.   异常规范跟随在函数参数表之后,用throw指定,后面是异常类型表。如void pop(int &value) throw(popOnEmpty);异常规范保证不会抛出任何没有出现在异常类型表中的异常。如果在运行时抛出一个没有出现在异常类型表中的异常,则系统调用C++标准库中定义的函数unexpected,该函数直接调用了terminate函数结束程序。空的异常规范不会抛出任何异常。

13.   异常规范可以在函数声明中指出,当有异常规范的指针被初始化时,被用作右值的指针异常规范必须比用作左值的指针规范一样或者更严格,当然,参数类型表必须相同。

14.   使用函数指针来使得template适应更多的场合。内置的函数指针声明的模板定义,代码如下: template< typename Type, bool (*Comp)(const Type&, const Type &)> const Type& min(const Type *p, Comp comp);该方案的主要缺点是函数调用使它无法内联。使用函数对象来替代函数指针,函数对象是类实现,它重载了调用操作符,即operaotr ()函数。有两个优点:(1)如果被重载的调用操作符是inline,则编译器能执行内联编译;(2)函数对象可以拥有任意数目的额外数据来缓冲结果。改写模板使之能同时接受函数指针和函数对象(缺点是没有任何的原型检查)template<typename Type, typename Comp> min(const Type *p, Comp comp);

15.   函数对象一般有三个来源,(1)系统预定义的算术、关系和逻辑函数对象,包含头文件<functional>(2)预定义的函数适配器,它容许对预定义的函数对象进行特殊化或者扩展;(3)自定义的函数对象。

16.   标准库提供的函数适配器分为两类:(1)绑定器(binder),通过把二元函数对象的一个实参绑定到一个特殊的值上,将其转换为一元函数对象,有两种绑定器,bind1stbind2nd,分别将值绑定到第一个参数和第二个参数上;如下代码: count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));(2)取反器(negator),将一个函数的值翻转的适配器,标准库提供了两个not1not2

17.   函数对象类定义的简单形式是包含一个被重载的函数调用操作符。如
class less_equal_ten {
public:
bool operator() (int val) {return val <= 10;}    }
扩展该类,使之容许提供一个遇每个元素比较的值。如:
class less_equal_value {
public:
less_equal_value(int val) : _val(val) {}
bool operator() (int val) { return val <= _val; }
private:
int _val;   }
使用上面这个类:
count_if(vec.begin(), vec.end(), less_equal_value(25));
另外一种拓展该类的方法,不使用构造函数,它根据被比较值对类参数化,如
:
template <int _val> class less_equal_value2 {
public:
bool operator() (int val) { return val <= _val; }  }
使用该模板类: count_if(vec.begin(), vec.end(), less_equal_value2<25>());

18.   标准库提供了一组(一共是三个)插入iterator的适配器函数,它们返回特定的插入iterator(1)back_inserter,使用容器的push_back方法替代赋值操作符;(2)front_inserter,使用容易的push_front方法替代赋值操作符;(3)inserter,使用容器的insert替代赋值操作符,它需要两个实参,容器本身以及它的一个iterator指示起始插入位置。

19.   标准库为输入和输出iostreamiterator提供支持。类istream_iterator类支持在一个istream或其派生类上的iterator操作,同理ostream_iterator支持ostream及其派生类。使用头文件<iterator>

20.   标准库定义了五种iterator: (1)InputIterator,用来读取元素,不保证支持写入操作;(2)OutputIterator,用来写入元素,但不保证读入;(3)ForwardIterator,用来以某一个遍历方向向容器读或写;(4)BidirectionIterator,从两个方向读或写一个容器;(5)RandomAccessIterator,出来支持BidirectionIterator所有功能外,还提供在常数时间内访问容器的任意位置。其中(1)(2)是最基本的两个iterator,需要这两个iterator的地方可以使用(3)(4)(5)iterator作为替代;需要(3)的可以使用(4)(5);能够替代(4)的只有(5)。需要(3)的常用GA有:adjacent_find(),swap_range()replace()等;需要(4)GAinplace_merge(), next_permutation()reverse();需要(5)GAbinary_search(),sort_heap()nth_element()map,setlist维护了一个(4)类型iterator,所以它们不能使用需要(5)GA中。而vectorqueue维护了(5)类型的iterator,可以使用于所有的GA

21.   所有泛型算法的前两个实参都是一对iterator,通常成为firstlast标志出要操作容器或者内置数组中的元素范围。一些算法支持多个版本,一个用内置操作符,而第二个接受函数对象或函数指针,通常在函数名称后加上_if,比如count_if();对修改所操作容器的算法,一般有两个版本,一个替换版本,它改变被应用的容器,另外一个返回带有这些变化的容器复本,是拷贝版本,通常在函数名称后加上_copy,如replace_copy()。头文件是<algorithm>,在使用下列4个算术函数adjacenet_difference, accumulate, inner_productpartial_sum,必须使用头文件<numeric>

22.   泛型算法的一些限制(1)关联容器(mapset)在内部维护了元素的排序关系,不容许在关联容器上应用重新排序的泛型算法,如sortpartition等。(2)list不支持随机访问,所以merge, remove, reverse, sortunique等算法最好不要使用在list上。list提供了上述这些泛型算法的成员函数。

23.   在类体外定义inline函数,可以显式在类体的声明中使用关键字inline,或者在类体外的函数定义上显式使用关键字inline,或者两者都有。但是内联函数必须在调用它的每个文本文件中被定义,所以在类体外被定义的内联函数也必须被放在类定义出现的头文件中

24.   只有被声明为const的成员函数才能被一个const类对象调用。关键字const被放在成员函数的参数表和函数体之间,必须在声明和定义中同时指定const关键字。const函数不能修改类数据成员,但如果类中含有指针,则const函数能修改指针指向的内容。const函数可以被相同参数表的非const函数重载。volatile关键字类似于const

25.   为了允许修改一个类的数据成员,即使是const对象的数据成员,可以吧数据成员声明为mutable

26.   类的静态数据成员对每个类类型都只有一个拷贝,由该类类型的所有对象共享访问;它比全局对象有两个优点(1)静态数据成员没有进入全局名字空间(2)可以实施数据隐藏,比如设置为private的,而全局变量则不行。一般而言,静态数据成员在该类定义之外被初始化,静态成员的名字必须被其类名限定修饰,如int Account::a=90;和全局变量一样,静态数据成员也只能提供一个定义,也就是初始化不能放在头文件中。特别的是,有序型的const静态数据成员可以在类体中用常量值初始化。如 static const int Account::nameSize = 5;用常量值初始化的静态数据成员是一个常量表达式,同时该成员还必须在类定义之外被定义,即nameSize要起作用,必须在类定义再定义,可以不赋值。数组类型不属于有序,不能再类体中初始化。访问静态成员可以通过类对象使用成员访问操作符或者用被类名修饰的名字直接访问(Account::nameSize)

27.   静态数据成员的一些独特方式的使用:(1)静态数据成员的类型可以是其所属类,而static的数据成员只能声明为类对象的指针或者引用。如:
class A {
public:
static Bar mem1; //
合法的。
Bar *pMem2; //
合法的
Bar mem3; //
错误
} (2)
静态数据成员可以被作为类成员函数的缺省实参,而非static成员不能。

28.   静态成员函数的声明除了在类体中的函数声明前加上关键字static,以及不能声明为constvolatile之外,与非static成员函数相同。静态成员函数没有this指针。

29.   函数指针不能被赋值为成员函数的地址,即使返回类型和参数表完全匹配。成员函数指针的声明需要扩展的语法,如int (Account::*test)();声明了一个Account类,返回int的无输入参数的成员函数指针。类成员的指针必须总是通过特定的对象或指向该类类型的对象的指针来访问。如类的指针对象pobj调用test函数(pobj->*test)();或者类对象obj调用test函数(obj.*test)();调用中必须有括号;同理,类成员的指针也必须使用特殊的语法,如typedef int Account::*member; member=&Account::size;其中member就是Account类中size的指针,使用它也必须使用指针对象或者类对象。

30.   指向类的静态成员的指针是普通的指针,包括函数指针

31.   联合是一种特殊的类,一个联合中的数据成员在内存中的存储是相互重叠的,每个数据成员都在相同的内存地址开始,分配给联合的存储区数量是“要包含它最大的数据成员”所需的内存数。默认情况下,联合中所有数据都是public的。union不能有静态数据成员或引用成员,如果一个类类型定义了构造函数、析购函数或拷贝赋值操作符,则它不能成为union的成员类型。

32.   使用union的风险是通过一个不恰当的数据成员意外的获取到当前存储在union中的值,一般的解决方法是定义一个额外对象,来跟踪当前被存储在union中的值的类型,成为union的判别式(discriminet)。一个比较好的经验是为所有union数据类型提供一组访问函数。如:union TokenValue { int _iVal; char _cVal; } enum TokenKind { ID, Constant }; class Token{ TokenKind tok; TokenValue val; }这里的TokenKind就是判别式。union的名字是可选的,特殊的情况下union实例可以没有名字(定义在类体内部)

33.  位域是一种特殊的类数据成员,它可以被声明用来存放特定数目的位,位域必须是有序数据类型,可以有符号,也可以无符号。位域标志符后面跟一个冒号,然后是一个常量表达式指定位数,如 unsigned int mode : 2; 位域的访问方式和数据成员相同。取地址操作符不能应用在位域上

34.   嵌套类是指一个类在另一个类中定义。嵌套类可以通过外围类的访问控制关键字进行控制,比如设置为private可以控制嵌套类无法被直接访问。嵌套类也可以定义在外围类之外,比如ListItem的外围类是List,可以定义class List::ListItem {};这样的代码可以保护嵌套类的代码不公开。嵌套类访问外围类的非静态成员必须通过对象或指针,但可以直接访问外围类的静态成员,类型名和枚举值(假定这些成员是公有的),类型名是一个typedef名字、枚举类型名或者一个类名。

35.   枚举值在定义枚举的域内可以被直接访问,因为枚举定义并不像类定义一样,它没有维护自己相关的域。

36.   C风格的显式初始化表有两个注意缺点:(1)它只能被应用到所有数据成员都是公有的类的对象上;(2)它增加了意外和错误的可能性,比如弄错了初始化的顺序。如Data loc = { “aaa”, 0}; // struct Data的定义是int ival; char* pstr;

37.   为构造函数指定实参有三种等价形式,(1) Account acct1(“Anna Press”); (2) Account acct2 = Account(“Anna Press”); (3) Account acct3 = “Anna Press”;第三种形式只能被用于指定单个实参的情形,一般推荐使用(1)。代码Account acct4(); 其实声明了一个函数形式。

38.   使用初始化表和在构造函数内使用数据成员的赋值,两种实现的最终结果是一样的,区别取决于数据成员的类型。构造函数可以认为分为两个阶段:(1)显式或隐式的初始化阶段(2)一般的计算阶段;计算阶段由构造函数内的所有语句构成。初始化阶段是隐式或者显式的取决于是否存在成员初始化表,隐式初始化阶段按照声明的顺序依次调用所有基类的缺省构造函数,然后是所有成员类对象的缺省构造函数。对于内置类型的成员,除了两个例外,初始化表和在构造函数内初始化在结果和性能上是等价的。两个例外是指const和引用数据成员,它们必须在初始化列表中被初始化。每个成员在初始化表中只能出现一次,初始化的顺序不是由名字在初始化表的顺序决定,而是由成员在类中被声明的顺序。如下的初始化列表会导致错误:class X { int I; int J; X(int v) : j(val), i(j){} … }这里i的值将会是未定义。

39.   用一个类对象初始化另一个类对象,称为缺省的按成员初始化。通常发生在下列情况:(1)用一个类对象显式初始化另一个类对象;(2)把一个类对象作为实参传递给一个函数或者作为一个函数的返回值;(3)非空顺序容器类型的定义,如vector<string> svec(5);创建一个临时对象,然后通过string的拷贝构造函数将该临时对象依次拷贝到vector5个元素中。(4)将一个类对象插入容器中。

40.   由于构造函数分为两个阶段,所以成员类对象的初始化最好在成员初始化表中初始化。如下代码: Account::Account(const Account &rhs) { _name = rhs._name; }这里_name是一个类对象,这行代码需要两次重复工作,首选初始化_name,然后用rhs._name来赋值。修正方法是使用初始化表。

41.  C++中类对象的初始化总是比赋值更有效率。如Matrix c = a+b; 的效率比Matrix c; c = a+b; 更有效率。在循环中使用临时对象初始化也更有效率,因为一般情况下不能直接用被返回的局部对象代替赋值的目的对象。

42.   操作符重载,只有在操作数是类类型的对象时,才能将该操作符作为类成员重载。但是同样可以声明非类成员的操作操作符。除此之外,C++要求,赋值(=),下标([]),调用(())和成员访问(->)操作符必须定义为类成员操作符。对于内置类型的操作符,不能被重载,也就是只能为类类型和枚举类型的操作数定义重载操作符。

43.   重载的operator()必须被声明为成员函数,它的参数表可以有任意数目的参数。

44.   为了区分前置操作符和后置操作符的声明,重载的递增(++)和递减(--)的后置操作符的声明有一个额外的int类型的参数。如:
Screen &operator ++(); //
前置操作符
Screen &operator ++(int); //
后置操作符

45.   如果一个类提供了两个分别称为操作符new()和操作符delete()的成员函数,那么它就可以承接自己的内存管理权。类成员操作符new()的返回类型必须是void *型,并且有个size_t类型的参数。类成员操作符delete()的返回类型必须是void,并且第一个参数的类型是void *。为一个类类型定义的delete操作符,如果它是被delete表达式调用的,则它可以有两个参数,第一个仍然必须是void *,第二个必须是预定义类型size_t,该参数值被自动初始化,其值等于所需内存的字节大小。例如:void operator delete(void *, size_t);操作符new()delete()是类的静态成员,它们被自动成为静态函数。

46.   重载针对数组分配的操作符new[]()delete[]()的重载。new[]()操作符,返回类型是void *,并且第一个参数类型是size_t。如 void *operator new[](size_t); 当调用如下代码时:Screen *ps = new Screen[10];操作符new[]()size_t参数被自动初始化,其值等于存放10Screen对象的数组所需内存的字节大小。通用可以定义定位的newdelete操作符。如void *operator new(size_t, Screen *);

47.  用户自定义转换函数可以在类类型和转换函数中指定的类型之间的转换。如:operator int();关键字operator之后的名字不一定必须是内置类型的名字,可以使用typedef定义的名字或者类名称等,但是不能为数组或者函数类型。注意,没有返回值

48.   在一个类的构造函数中,凡只带一个参数的构造函数,都定义了一组隐式转换,把构造函数的参数类型转换为该类的类型。可以使用关键字explicit来阻止编译器使用该构造函数进行隐式类型转换,但是该函数还是可以被用来执行类型转换,只要显式以强制转换。如类Number定义这样一个构造函数 Number(const SmallInt &); 有这样的函数void func(Number); 则代码在未使用explicit下可以使用SmallInt si; func(si); 在使用explicit后可以使用func(Number(si))或者func(static_cast<Number>(si));

49.  类模板参数也可以是一个非类型模板参数,绑定给非类型模板参数的表达式必须是一个常量表达式,即它能在编译时被计算。非const对象的值不是一个常量表达式,不能被用作非类型参数的实参,但是名字空间中任何对象的地址是一个常量表达式。如int i = 5;i不能用作非类型模板参数,但是&i可以。

50.  被定义在类模板定义之外的成员函数必须使用特殊的语法,来指明它是一个类模板的函数。如类Queue类的构造函数:template <typename Type> Queue <Type>::Queue() {…}。类模板的成员函数本身也是一个模板,标准C++要求这样的成员函数只有在被调用或者取地址时才被实例化,当类模板被实例化时,类模板的成员函数并不自动被实例化。

51.   有三种友元声明可以出现在类模板中:(1)非模板友元函数或者友元类;(2)绑定的友元类模板或者函数模板。如有模板函数和模板类如下:
template <typename Type> class foobar {…}
template <typename Type> void foo(QueueItem<Type>);
template <typename Type> class Queue { void bar(); … }
在类QueueItem增加以上的友元声明:
template <typename Type> class QueueItem {
friend class foobar<Type>;
friend void foo<Type>(QueueItem<Type>);
friend void Queue<Type>::bar(); …}
(3)
非绑定的友元类模板或者函数模板。如下代码:

template <typename Type>class QueueItem {
template <typename T> friend class foobar;
template <typename T> friend void foo(QueueItem<T>);
template <typename T> friend void Queue<T>::bar(); … }

52.  模板类成员也可以声明静态成员变量,类模板的每个实例都有自己的一组静态数据成员静态数据成员的模板定义必须出现在类模板定义之外,定义语法类似与类模板定义外的成员函数定义语法。只有当程序使用静态数据成员时,它才从模板定义中被真正实例化。类模板的静态成员本身就是一个模板,静态数据成员的模板定义不会引起任何内存被分配,只有对静态数据成员的某个特定的实例才会分配内存。

53.   类模板的嵌套类型,当外围类被实例化时,它的嵌套类不会自动被实例化,只有当上下文环境缺省需要嵌套类的完整类型时,嵌套类才会被实例化。

54.   函数模板或者类模板可以是一个普通类的成员,也可以是一个类模板的成员,成员模板的定义类似与一般模板的定义。而一个成员模板的定义在模板外部时,如:
template <class T> class Queue {
template <class Iter> void assign(Iter first, Iter last); … }
template <class T> template <class Iter> void Queue<T>::assign(Iter first, Iter last) {…}

55.   模板类的显式实例声明,实例代码如下:
template <typename Type> class Queue {…}
template class Queue <int>; //
显式实例了int类型的Queue
模板类的特化,类似与函数模板的特化,为指定类型的做特别的处理,如

template<> class Queue<int> {…}
模板类的特化,支持部分特化,即,如果类模板有一个以上的模板参数,指定其中一个或者几个(非全部)的参数,称为部分特化。如:
template <int hi, int wid> class Screen {…};
template <int hi> class Screen<hi, 80> {…}

56.   基类成员名在派生类中被重用时,基类的成员将被隐藏,因为基类的成员属于基类的域。对于成员函数,可以使用using声明来导入基类的成员以构成函数重载。另外一个注意事项是,派生类可以访问其基类的protected的成员,但是不能访问其他的基类成员。如代码 bool NameQuery::compare(const Query *pquery); 该函数可以访问自己基类的成员,但是无法访问pqueryprotected的成员,这种形式的访问限制不适用于自己类的其他对象,也就是bool NameQuery::compare(const NameQuery *pquery)中可以访问pqueryprotected成员。

57.   派生类由一个或多个基类子对象以及派生类部分构成。派生类的构造函数次序永远是(1)基类构造函数;(2)成员类对象构造函数;(3)派生类构造函数。这个次序跟构造函数初始化列表中的顺序无关。析构函数的次序正好与构造函数次序相反。

58.   当用类域操作符调用虚拟函数时,改变了虚拟机制,使得虚拟函数在编译时刻被静态解析。如Query是基类,NameQuery是派生类,代码:Query *pquery = new NameQuery(); pquery->isA(); pquery->Query::isA(); 则两个isA函数调用不同,前一个使用虚拟机制调用NameQuery的函数isA,而后一个直接调用基类的函数。有定义的纯虚拟函数也支持被静态调用。

59.   如果通过基类指针或引用调用派生类实例,则传递给它的缺省实参由基类指定的。

60.   如果用基类的指针来删除一个派生类的对象,为了能正确执行,必须把基类的析构函数设置为virtual的。

61.   虽然不能把new操作符声明为虚拟的,但是可以提供一个代理new操作符,来负责对象分配并拷贝到空闲存储区,通常成为clone,以下是一个例子:virtual Query *clone() {return new NameQuery(*this); }

62.   派生类的赋值操作符operator =调用基类的赋值操作符,可以有两个语法:(1) this->Query::operator =(rhs); (2)(*static_cast<Query *>(this))=rhs;

63.   多继承中,基类构造函数被调用的顺序以类派生表中被声明的顺序为准。析构函数的调用顺序总是与构造函数顺序相反。

64.   public派生被称为类型继承,派生类是基类的子类型,派生类反应了一种is-a关系private派生被成为实现继承,派生类不直接支持基类的接口,它把基类的整个共有接口在派生类中变成private,但是它希望重用基类的实现。Protected派生把基类的所有公有成员变成protected。可以通过using指示符来还原基类的访问等级,但是不能比基类中原定的级别更严格或者更不严格。组合是一种has-a的关系,在只是简单重用实现时,可以按引用(使用一个指针)来组合,组合分为按值组合(通过类的实际对象被声明为一个成员)和按引用组合(通过类对象的引用或者指针成员)

65.   虚拟继承是一种可替代“按引用组合”的继承机制(普通继承是按值组合),在虚拟继承下只有一个共享的基类。通过用关键字virtual修改一个基类的声明可以将它指定为被虚拟派生。

66.   非虚拟派生中,派生类只能显式初始化其直接基类;而在虚拟派生中,只有最终派生类可以直接调用其虚拟基类的构造函数中间派生类对基类构造函数的调用被自动抑制了,必须由最终派生类提供。构造函数顺序上,虚拟基类在所有非虚拟基类构造前被构造。在虚拟派生下,对于虚拟基类成员的继承比“该成员后来重新定义的实例(即中间派生类重载了该成员)”的权值小,所以中间派生类的成员优先,而在非虚拟派生下则产生二义性。

67.   当一个类模板被用作基类时,必须用它完整的参数表对其进行修饰,如代码:template <class Type> class derived : public Base<type> {…}

68.   RTTI(运行时刻类型识别),有两个操作符,dynamic_cast操作符:容许在运行时刻进行类型转换,它可以把一个类类型对象的指针转换成同一类层次结构中的其他类的指针,如果指针转换失败则结果为0,如果引用转换失败则抛出异常;typeid操作符,指出了指针或引用指向对象的实际派生类型。

69.   dynamic_cast操作符一次执行两个操作,它检验所请求的转换是否有效,只有有效时才进行转换,而检验过程是发生在运行时刻。它被用来执行从基类指针到派生类指针的安全转换,是一种安全的向下转换。当用于引用时,它抛出std::bad_cast异常。

70.   typeid操作符用于获取一个表达式的类型,它必须与表达式或者类型一起使用,可以是内置的类型或者类类型。可以对typeid的结果进行比较,它区分指针和对象,如class A; A a; typeid(&a)typeid(a)不同,前者是一个指针,判断指针是typeid(A *),后者是类对象。它其实返回一个类型为type_info的类对象,该类型定义在<typeinfo>。该类的构造函数都是private,无法自己创建该类的对象,只能通过typeid操作符。该类的实现依赖于编译器

71.   抛出异常:throw popOnFull(value);执行的步骤(1)throw表达式通过调用类类型pushOnFull的构造函数创建一个该类的临时对象;(2)创建一个pushOnFull类型的异常,并传递给异常处理代码,该异常对象是第一步throw表达式创建的临时对象的拷贝;(3)在开始查找异常处理代码之前,第一步中的临时对象被销毁。

72.   派生类中虚拟函数的异常规范必须与基类的对应的虚拟函数的异常规范一样或者更严格。

73.  通常情况下,在“被抛出的异常的类型”和“异常规范中指定的类型”之间不容许进行类型转换;但是,当异常规范指定一个类类型或者类类型的指针时,则可以抛出“从该类公有继承的类类型”。

74.   C++标准库中的异常层次的根类为exception,包含在头文件<exception>。它包含一个what虚拟成员函数。C++标准库将错误分为两大类,逻辑错误和运行时刻错误,并提供了一些从exception继承的类。通过new分配错误会抛出bad_alloc异常。

75.   输出操作符<<默认对C风格的字符串(即类型const char *)做了特殊处理,如果想输出字符串的地址,必须进行强制转换。ostream_iteratoristream_iterator会简化操作。

76.   其它几种常用的输入/输出操作符:(1)get(char &ch)从输入流中提前提取一个字符,包括空白字符,它返回被应用的istream对象。对应的ostream提供了put(),写入一个字符。(2)get的第二个版本和第一个版本功能一样,只是返回值不同,它返回字符值,返回类型是int因为可能返回文件尾标志,该标志通常为-1(3)get的第三个版本get(char *sink, streamsize size, char delimiter=’/n’); 读取size个字符,除非遇到字符delimiter或者遇到文件尾。该函数不把delimiter字符读入,而是将该字符继续留在流中,所以必须使用ignore来丢弃该字符。(4)getline函数类似与get的第三个版本,除了它自动丢弃了delimiter字符。想确定读入了多少字符,使用gcount函数。(5)istreamread函数和ostreamwrite函数读取指定长度的字符,除非遇到文件尾。

77.  由于不正确的格式而导致失败,istream应该把状态标志为falseis.setstate(ios_base::fallbit);对于错误状态的iostream,提取和插入操作没有影响,但是对它的测试将为false

78.   通过seekgseekp函数,可以对fstream类对象重新定位(g表示为了getting字符而定位,而p表示为了putting而定义)。类似的,tellptellg获取当前的输出和输入流的位置。

79.   每个流对象都维护了一组条件标志,通过这些条件标志,可以监视当前流的状态:(1)当一个流遇到文件结尾eof返回true(2)如果试图做一个无效操作,如seek的操作超出文件尾,则bad返回true(3)如果操作不成功,比如打开文件流对象失败或者遇到无效的输入格式,则fail返回true(4)如果其他条件都不为true,则good()true。显式修改流对象的条件状态有两种方式:(1)clear函数,可以把条件状态复位到一个显式的值;(2)使用setstate函数在现有的条件状态再增加一个条件。可用的条件值是ios_base::badbit, ios_base::eofbit, ios_base::failbit, ios_base::goodbit。同时rdstate函数可以读取当前状态值,返回类型是ios_base::iostate

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值