C++速学仓促笔记

2024-02-24 13:40
文章标签 c++ 笔记 速学 仓促

本文主要是介绍C++速学仓促笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.大概认识C++类概念,基类,子类,继承,多态,封装,命名空间,迭代器,向量vector,异常处理,模板等概念,函数重载,list,map

(1)基本c++数据类型:常量,变量,指针,字符串,const,引用&,bool,枚举,数组,vector容器,复数类型,typedef,volatile,pair,类类


(2)表达式:算术操作符,关系操作符,赋值操作符,递增递减操作符,复数操作,sizeof操作符,逗号操作符
(3)语句:

3.基于过程的程序设计
(1)函数
函数声明,参数传递(引用,数组,指针,缺省参数,抽象容器类,省略号),返回值,递归,inline,链接指示符(extern “C”),函数指针
(2)域和生命周期
A.域
c++支持三种形式域:局部域,全局域,名字空间域,类域
局部域:指大括号所包含的一个空间(函数,复合语句)
全局域:指程序最外层的名字空间域。名字空间域也被包含在全局域之中,每个用户声明的名字空间都是一个不同的域,他们都与全局域不同,与全局

域相同的是,用户声明的名字空间可以包含对象,函数类型,模板的声明与定义,以及以及被嵌套期内的用户声明的名字空间
类域:每个类都引入一个独立的类域。

普通C++头文件可以通过创建预编译头文件来减少由于头文件过大而导致的编译时间过长

B.局部对象
分为三种:自动对象(定义的变量默认都为自动类型auto),寄存器对象(支持快速存取register修饰),局部静态对象(用static修饰)

C.动态分配对象
new

D.名字空间定义
namespace

(3)重载函数
重载函数匹配过程:
A。确定函数调用需要考虑的重载函数的集合,确定函数调用中实参表的属性(参数个数和类型)

B。从重载函数集合之中选择合适的函数(粗略匹配)
a。精确匹配
- 从左值到右值的转换
- 从数组到指针的转换
- 从函数到指针的转换
- 限定修饰符转换
精确匹配比提升好,提升比标准转换好,标准转换比用户定义的转换好

b。类型转换

c。无匹配

C。精确匹配
划分等级,选出最优匹配函数
精确匹配比提升好,提升比标准转换好,标准转换比用户定义的转换好

函数重载细节解析:
A。确定候选函数
- 该函数的声明在调用点上可见
- 如果函数类型是在一个名字空间中被声明的,则改名字空间中的与被调用函数同名的成员函数也被引入到候选函数集中

B。确定可行函数
可行函数的参数表或者与调用函数相同,或者多余调用函数,多余的参数要有缺省参数。可行函数对于每个实参,都存在到函数参数表中相应参数类型

之间的转换。
没有找到候选函数,编译错误。

C。选择最优可行函数
最优可行函数是具有与实参类型匹配最好的可行函数。对于每个可行函数来说,每个实参的类型转换都被划分了等级,以决定实参的匹配程序。
最佳可行函数满足下列条件的可行函数:
- 用在实参上的转换不必调用其他可行函数所需的转换更差
- 在某些参数转换上要比其他可行函数对该参数的转换要更好

(4)模板函数
A。函数模板定义
例:
template
Type min(Type a, Type b) {
return a < b ? a: b;
}

int main() {
// ok: int min(int, int);
min(10, 20);
// ok: douboe min(double. double);
min(10.0, 20.0);

return 0;

}

关键字template放在最前面,关键字后面是尖括号包围起来的参数表,参数表不能为空,它可以是模板类型参数,代表一种类型,也可以是模板非类型

参数,代表一个常量表达式

模板类型参数由关键字class或typename后加一个标识符构成。在函数的模板参数表中,这两个关键字意义相同,他们表示后面的参数名代表一个潜在

内置或用户定义的类型。模板参数名由程序员选择。上面的例子中,Type来命名模板参数名,他可以为任意名字。
模板非类型参数由一个普通声明构成,它代表一个潜在的值,而改值代表了模板定义中的一个常量,在编译时刻会被常量替换。
例:
template <class Type, int size>

Type min(const Type (&r_array)[size]) {
Type min_val = r_array[0];

for (int i = 1 ; i < size ; i++) {if (r_array[i] < min_val)min_val = r_array[i];
}
return min_val;

}

类型和值的替换过程被称为模板实例化(调用min(array, 20)时进行实例化)

注意:
a。全局域中声明了与模板参数同名的对象,函数或类型,则改全局名将被屏蔽
例:
typedef double Type;
template
Type min( Type a, Type b )
{
// tmp 类型为模板参数Type
// 不是全局typedef
Type tmp = a < b ? a : b;
return tmp;
}

b。在函数模板定义中声明的对象或类型不能与模板参数同名
例:
template
Type min( Type a, Type b )
{
// 错误: 重新声明模板参数Type
typedef double Type;
Type tmp = a < b ? a : b;
return tmp;
}

c。模板参数名可以被用来指定函数模板的返回位
例:
// ok: T1 表示min() 的返回类型
// T2 和T3 表示参数类型
template <class T1, class T2, class T3>
T1 min( T2, T3 );

d。模板参数名在同一模板参数表中只能被使用一次
例:
// 错误: 模板参数名Type 的非法重复使用
template <class Type, class Type>
Type min( Type, Type );

e。模板参数名可以再多个函数模板声明或定义之间被重复使用
例:
// ok: 名字Type 在不同模板之间重复使用
template
Type min( Type, Type );
template
Type max( Type, Type );

f。一个模板的定义和多个声明所使用的的模板参数名无需相同
例:
// 三个min() 的声明都指向同一个函数模板
// 模板的前向声明
template T min( T, T );
template U min( U, U );
// 模板的真正定义
template
Type min( Type a, Type b ) { /* … */ }

g。模板参数在函数参数表中可以出现的次数没有限制
例:
#include
// ok: 在模板函数的参数表中多次使用Type
template
Type sum( const vector &, Type );

h。一个函数模板有一个以上的模板类型参数,每个模板参数前面必须要有关键字class或typename(最早用的是class,typename是后来加入的)

i。
例:
template <class Parm, class U>
Parm minus( Parm* array, U value )
{
typename Parm::name * p; // ok: 指针声明
}
typename 指明了Parm::name * p是声明一个Parm::name 类型的指针,而不是乘法运算

j。函数模板也可以被声明为inline或extern
例:
// ok: 关键字跟在模板参数表之后
template
inline
Type min( Type, Type );
// 错误: inline 指示符放置的位置错误
inline
template
Type min( Array, int );

B。函数模板实例化
模板实例化指的就是构造过程(即类型被确定,常量被确定),它发生在被调用时或者是取其地址时
模板实参推演:用函数参数类型累决定模板参数类型和值的过程被称为模板实参推演

模板实参推演的函数实参类型不一定要严格匹配相应函数参数的类型,允许三种类型转换:
a。左值转换
- 从左值到右值的转换
- 从数组到指针的转换
- 从函数到指针的转换
b。限定转换(只针对于const和volatile)
c。到一个基类转换

模板实参推演的通用算法如下:
1 依次检查每个函数实参以确定在每个函数参数的类型中出现的模板参数
2 如果找到模板参数则通过检查函数实参的类型推演出相应的模板实参
3 函数参数类型和函数实参类型不必完全匹配下列类型转换可以被应用在函数实参上
以便将其转换成相应的函数参数的类型
* 左值转换
* 限定修饰转换
* 从派生类到基类类型的转换假定函数参数具有形式T T&或T*
则这里的参数表args至少含有一个模板参数
4 如果在多个函数参数中找到同一个模板参数则从每个相应函数实参推演出的模板实
参必须相同

C。显示模板实参(为了解决二义性或者无法推导出模板实参的类型)
例:
template T min5( T, T ) { /* … */ }
unsigned int ui;
int main() {
// 错误: 不能实例化min5( unsigned int, int )
// 必须是: min( unsigned int, unsigned int ) 或
// min( int, int )
min5( ui, 1024 );
}
min5< unsigned int >( ui, 1024 ); 显示指定了模板实参的类型为unsigned int,此时就不需进行模板实参推演

例:
// 以T或U作为返回类型?
template <class T, class U>
??? sum( T, U );

char ch; unsigned int ui;
// T和U都不用作返回类型
sum( ch, ui ); // ok: U sum( T, U );
sum( ui, ch ); // ok: T sum( T, U );

One Plan:
// T1 不出现在函数模板参数表中
template <class T1, class T2, class T3>
T1 sum( T2, T3 );

typedef unsigned int ui_type;
ui_type calc( char ch, ui_type ui ) {
// …
// 错误: T1不能被推演出来
ui_type loc1 = sum( ch, ui );
// ok: 模板实参被显式指定
// T1 和T3 是unsigned int, T2 是char
ui_type loc2 = sum< ui_type, char, ui_type >( ch, ui );
}

Two Plan:
在显式特化explicit specification 中我们只需列出不能被隐式推演的模板实参如同缺省实参一样我们只能省略尾部的实参
// ok: T3 是unsigned int
// T3 从ui 的类型中推演出来
ui_type loc3 = sum< ui_type, char >( ch, ui );

// ok: T2 是char, T3 是unsigned int
// T2 和T3 从pf 的类型中推演出来
ui_type (*pf)( char, ui_type ) = &sum< ui_type >;

// 错误: 只能省略尾部的实参
ui_type loc4 = sum< ui_type, , ui_type >( ch, ui );

例:
template <class T1, class T2, class T3>
T1 sum( T2 op1, T3 op2 ) { /* … */ }
void manipulate( int (*pf)( int,char ) );
void manipulate( double (*pf)( float,float ) );
int main( )
{
// 错误: 哪一个sum的实例?
// int sum( int, char ) 还是
// double sum( float, float ) ?
manipulate( &sum );
// 取实例: double sum( float, float )
// 调用: void manipulate( double (*pf)( float, float ) );
manipulate( &sum< double, float, float > );
}

D。模板编译模式(是为了解决函数模板在程序中的组织方式)
包含两种模式:包含模式,分离模式
a。包含模式
包含模式下,每个模板被实例化的文件中包含函数模板的定义,可以把定义统一放到一个头文件中
例:
// model1.h
// 包含模式: 模板定义放在头文件中
template
Type min( Type t1, Type t2 ) {
return t1 < t2 ? t1 : t2;
}

// 在使用模板实例之前包含模板定义
#include “model1.h”
int i, j;
double dobj = min( i, j );

该头文件可以被包含在许多程序文本文件中这意味着编译器必须在每个调用该实例的文件中实例化min()的整型实例吗不该程序必须表现得好像min()的

整型实例只被实例化一次但是真正的实例化动作发生在何时何地要取决于具体的编译器实现。

缺点:
- 模板函数描述了实现细节,这些细节可能用户想忽略或者隐藏
- 多个文件使用相同模板函数增加了不必要的编译时间

b。分离模式
分离模式下,函数模板声明放在头文件中,函数模板声明和定义的组织方式与程序中的非内联函数的声明和定义组织方式相同。。
例:
// model2.h
// 分离模式: 只提供模板声明
template Type min( Type t1, Type t2 );
// model2.C
// the template definition
export template
Type min( Type t1, Type t2 ) { /* …*/ }

// user.C
#include “model2.h”
int i, j;
double d = min( i, j ); // ok: 用法, 需要一个实例

。。。。。。。。。。。。。。。。。。。。。。。

(5)异常处理
A。抛出异常
raise或throw来抛出异常

throw 类对象或者其他对象(如枚举对象)

B。try块
try{
/* 可能会出问题的代码,其中包括了throw /
} catch(单个类对象/单个类型){
/
deal 。。。*/
}

如果try块之后没有catch,那么程序执行权将会转交给terminate()

C。捕获异常
catch用来捕获异常

找到一个catch子句以处理被抛出的异常的过程如下如果throw表达式位于try块中
则检查与try块相关联的catch子句看是否有一个子句能够处理该异常如果找到一个catch
子句则该异常被处理如果没有找到catch子句则在主调函数中继续查找如果一个函
数调用在退出时带着一个被抛出的异常并且这个调用位于一个try块中则检查与该try块
相关联的catch子句看是否有一个子句能够处理该异常如果找到了一个catch子句则该
异常被处理如果没有找到catch子句则查找过程在主调函数中继续这个过程沿着嵌套
函数调用链向上继续直到找到该异常的catch子句只要一遇到能够处理该异常的catch子
句就会进入该catch子句程序的执行在该处理代码中继续。

重新抛出异常rethrow,表达式形式为throw;
例:
catch ( exception eObj ) {
if ( canHandle( eObj ) )
// 处理异常
return;
else
// 重新抛出它, 并由另一个catch子句来处理
throw;
}

…可以捕获所有的异常

void manip() {
resource res;
res.lock();
try {
// 使用res
// 某些能够引起异常被抛出的动作
}
catch ( … ) {
res.release();
throw;
}
res.release(); // 如果抛出异常则跳过
}

D。异常规范
随着函数声明列出函数可能抛出的异常,保证函数不会抛出任何其他类型的异常。
例:
class iStack {
public:
// …
void pop( int &value ); // 抛出popOnEmpty
void push( int value ); // 抛出pushOnFull
private:
// …
};

class iStack {
public:
// …
void pop( int &value ) throw(popOnEmpty); //对于pop()的调用保证不会抛出任何popOnEmpty类型之外的异常
void push( int value ) throw(pushOnFull); //对于push()的调用保证不会抛出任何pushOnFull类型之外的异常
private:
// …
};
如果函数抛出了一个没有被列在异常规范中的异常会怎么样程序只有在遇到某种不正常情况时异常才会被抛出在编译时刻编译器不可能知道在执行时程

序是否会遇到这些异常因此一个函数的异常规范的违例只能在运行时刻才能被检测出来如果函数抛出了一个没有被列在其异常规范中的异常则系统调用

C++标准库中定义的函数unexpected()unexpected()的缺省行为是调用terminate() 在某些条件下可能有必要改变unexpected() 执行的动作C++标准库

提供了一种机制可让我们改变unexpected()的缺省行为STROUSTRUP97 更详细地讨论了这些

如果函数抛出了一个没有被列在其异常规范中的异常系统未必就会调用unexpected() 如果该函数自己处理该异常并且该异常在逃离该函数之前被处
理掉那么一切都不会有问题
例:
void recoup( int op1, int op2 ) throw(ExceptionType)
{
try {
// …
throw string(“we’re in control”);
}
// 处理抛出的异常
catch ( string ) {
// 做一些必要的工作
}
} // ok, unexpected()没有被调用

异常规范不允许从被抛出的异常类型到异常规范指定的类型之问的转换,如果convert() 抛出该异常则调用函数unexpected()
例:
int convert( int parm ) throw(string)
{
// …
if ( somethingRather )
// 程序错误:
// convert() 不允许const char* 型的异常
throw “help!”;
}

(6)泛型算法
A。使用泛型算法

---------------- 先省略不看

下面介绍类
1.类
(1)类定义
类定义包含两部分:类头,由关键字class及其后面的类名构成。类体,由一对花括号包围起来。类定义后面必须是一个分号结尾或是一列声明。
例:
class Screen{/* 。。。。 /};
class Screen{/
。。。。*/} myScreen, yourScreen;

在引入类类型之后,我们可以用两种方式引用这种类类型:
1.指定关键字class,后面紧跟类名。
2.只指定类名。
例:
class First {
int memi;
double memd;
};
class Second {
int memi;
double memd;
};
class First obj1;
Second obj2 = obj1; // 错误: obj1 和obj2 类型不同

A。数据成员
例:
#include
class Screen {
string _screen; // string( _height * _width )
string::size_type _cursor; // 当前屏幕Screen 位置
short _height; // 行数
short _width; // 列数
};

B。成员函数
例:
class Screen {
public:
void home();
void move( int, int );
char get();
char get( int, int );
bool checkRange( int, int );
// …
};

class Screen {
public:
// home() and get() 的定义
void home() { _cursor = 0; }
char get() { return _screen[_cursor]; }
// …
};

成员函数被声明在类中,在类域之外是不可见的,成员函数可以访问该累的共有和私有成员的特权。成员函数可以是重载的,但是成员函数只能重载所

属类中的其他成员函数。

C。成员访问
类成员的访问限制是通过类体内被标记为public private以及protected 的部分来指定的关键字public private和protected被称为访问限定符access

specifier在公有public 区内被声明的成员是公有成员在私有private 或被保护的protected区域内被声明的成员是私有或被保护的成员。
- 公有成员public member 在程序的任何地方都可以被访问实行信息隐藏的类将
其public成员限制在成员函数上这种函数定义了可以被一般程序用来操纵该类类
型对象的操作
- 私有成员private member 只能被成员函数和类的友元访问实行信息隐藏的类把其数据成员声明为private
- 被保护成员protected member 对派生类derived class 就像public成员一样对其他程序则表现得像private 我们在第2章的IntArray类中看

到了怎样使用protected成员的实例关于protected成员的完全讨论要到第17章才进行那时将介绍派生类以及继承inheritance 的概念。

没有指定访问限定符,缺省情况下,在类的开始左括号后面的是private。

D。友元
友元声明以关键字friend开头,它只能出现在类的声明中,友元函数不受public,private,protected影响。
例:
class Screen {
friend istream&
operator>>( istream&, Screen& );
friend ostream&
operator<< ( ostream&, const Screen& );
public:
// … Screen 类的其他部分
};

E。类声明和类定义

(2)类对象
类的定义,不会引起存储区分配内存,只有当定义一个类对象时,系统才会分配存储区。
例:
class Screen {
public:
// 成员函数
private:
string _screen;
string::size_type _cursor;
short _height;
short _width;
};
如下定义
Screen myScreen;
将分配一块足够包含Screen类的四个数据成员的存储区

一个对象可以被同一类类型的另一个对象初始化或赋值。缺省情况下,拷贝一个类对象与拷贝它的全部数据成员等价。
例:
Screen bufScreen = myScreen;
// bufScreen._height = myScreen._height
// bufScreen._width = myScreen._width
// bufScreen._cursor = myScreen._cursor
// bufScreen._screen = myScreen._screen

(3)类成员函数
A。inline和非inline成员函数
在类定义中定义的成员函数,默认都是inline,在类体外定义的成员函数通常都是非inline,可以显示指定为inline。

B。访问类成员
- 成员函数的定义可以引用任何一个类成员,无论该成员是私有还是公有,都不会破坏类访问限制
- 成员函数可以直接访问所属类的成员,无需用点和箭头成员访问操作符
例:
#include
void Screen::copy( const Screen &sobj )
{
// 如果这个Screen 对象与sobj 是同一个对象
// 则无需拷贝
// 我们将在13.4 节介绍this 指针
if ( this != &sobj )
{
_height = sobj._height;
_width = sobj._width;
_cursor = 0;
// 创建一个新字符串
// 它的内容与sobj._screen 相同
_screen = sobj._screen;
}
}

C。特殊成员函数
初始化,赋值,内存管理,类型转换,析构
初始化函数被称为构造函数,每次定义一个对象或者用new表达式分配一个对象时就会调用它,构造函数名字必须与类名相同
例:
class Screen {
public:
Screen( int hi = 8, int wid = 40, char bkground = ‘#’);
// 其他的成员函数声明保持不变
};

Screen::Screen( int hi, int wid, char bk ) :
_height( hi ), // 用hi 初始化_height
_width( wid ), // 用wid 初始化_width
_cursor ( 0 ), // 初始化_cursor 为0
_screen( hi * wid, bk ) // _Screen 的大小为hi*wid
// 所有位置用bk 的字符值初始化
{ // 所有的工作都由成员初始化列表完成
// 14.5 节将讨论成员初始化列表
}

D。const和volatile成员函数
例:
const Screen blankScreen;
blankScreen.display(); // 读类对象
blankScreen.set( ‘*’ ); // 错误: 修改类对象

class Screen {
public:
char get() const { return _screen[_cursor]; }
// …
}

只有被声明为const的成员函数才能被一个const类对象调用。关键字const被放在成员函数参数表和函数体之间。对于在类体之外定义的const成员函数

,我们必须在它的定义和声明中同时制定关键字const。
例:
class Screen {
public:
bool isEqual( char ch ) const;
// …
private:
string::size_type _cursor;
string _screen;
// …
};
bool Screen::isEqual( char ch ) const
{
return ch == _screen[_cursor];
}

例:
#include
class Text {
public:
void bad( const string &parm ) const;
private:
char *_text;
};
void Text::bad( const string &parm ) const
{
_text = parm.c_str(); // 错误: 不能修改_text
for ( int ix = 0; ix < parm.size(); ++ix )
_text[ix] = parm[ix]; // 不好的风格, 但不是错误的
}
把一个成员函数声明为const可以保证这个成员函数不修改类的数据成员但是如果该类含有指针那么在const成员函数中就能修改指针所指的对象。

例:
const成员函数可以被相同参数表的非const成员函数重载
class Screen {
public:
char get(int x, int y);
char get(int x, int y) const;
// …
};

类对象的常量性决定了调用哪个函数
int main() {
const Screen cs;
Screen s;
char ch = cs.get(0,0); // 调用const 成员
ch = s.get(0,0); // 调用非const 成员
}

例:
class Screen {
public:
// 成员函数
private:
string _screen;
mutable string::size_type _cursor; // mutable 成员
short _height;
short _width;
};

// move() 是一个const 成员函数
inline void Screen::move( int r, int c ) const
{
// …
// ok: const 成员函数可以修改mutable 成员
_cursor = row + c - 1;
// …
}
为了修改一个类的数据成员,即使它是一个const对象的数据成员 ,我们也可以把该数据成员声明为mutable(易变的),mutable成员永远不会是

const成员,即使它是一个const对象的数据成员,它也可以被更新。

(4)隐含this指针
例:
int main() {
Screen myScreen( 3, 3 ), bufScreen;
myScreen.clear();
myScreen.move( 2, 2 );
myScreen.set( ‘*’ );
myScreen.display();
bufScreen.reSize( 5, 5 );
bufScreen.display();
}
每个类对象都将维护自己类数据成员的拷贝。

比那一起作出如下改变:
A。改变类成员函数定义
例:
// 伪代码, 说明编译器对一个成员函数定义的展开形式
// 不是合法的C++ 代码
inline void move( Screen* this, int r, int c )
{
if ( checkRange( r, c ) )
{
int row = (r-1) * this->_width;
this->_cursor = row + c - 1;
}
}
在这个成员函数定义中显式使用this指针来访问类数据成员_width和_cursor

B。改变每个类成员函数的调用,加上一个额外的实参—被调用对象的地址
例:
myScreen.move( 2, 2)被转化为move( &myScreen, 2, 2 )

(5)静态类成员
在类成员变量前加上static,该数据成员就成为静态的,静态成员同样遵循public/private/protected访问规则。静态类成员所有类对象共有,对于每

一个类对象都是唯一的,不是独立的。

同全局对象相比,使用静态数据成员有两个优势:
A。静态数据成员没有进入到程序的全局名字空间,不存在与程序中的全局名字冲突
B。可以实现信息隐藏,静态成员可以使private,而全局对象不能。

例:
class Account {
Account( double amount, const string &owner );
string owner() { return _owner; }
private:
static double _interestRate; //私有静态成员,唯一
double _amount;
string _owner;
};

// 静态类成员的显式初始化
#include “account.h”
double Account::_interestRate = 0.0589;

例:
class Bar {
public:
// …
private:
static Bar mem1; // ok
Bar *mem2; // ok
Bar mem3; // 错误
};
静态数据成员的类型可以使其所属类,非static数据成员只能被声明为该类的对象的指针或引用。

例:
extern int var;
class Foo {
private:
int var;
static int stcvar;
public:
// 错误: 被解析为非static 的Foo::var
// 没有相关的类对象
int mem1( int = var );
// ok: 解析为static 的Foo::stcvar
// 无需相关的类对象
int mem2( int = stcvar );
// ok: int var 的全局实例
int mem3( int = ::var );
};
静态数据成员可以被作为成员函数的缺省实参,而非static成员不行。

例:
class Account {
public:
static void raiseInterest( double incr );
static double interest() { return _interestRate; }
private:
static double _interestRate;
};
inline void Account::raiseInterest( double incr )
{
_interestRate += incr;
}
静态成员函数的声明除了在类体中的函数声明前加上关键字static,以及不能被声明为const或volatile之外,与非静态成员函数相同。出现在类体外

的函数定义不能指定关键字static。

(6)指向类成员的指针

(7)联合:一种节省空间的类
联合体默认声明的成员都为public类型,也可以生命为共有或私有
例:
union TokenValue {
public:
char _cval;
// …
private:
int priv;
};
int main() {
TokenValue tp;
tp._cval = ‘\n’; // ok
// 错误: main() 不能访问私有成员TokenValue::priv
tp.priv = 1024;
}

union不能有静态数据成员或引用数据成员,如果一个类类型定义了构造函数,析构函数或拷贝构造操作符,则它不能成为union成员类型。
例:
union illegal_members {
Screen s; // 错误: 有构造函数
Screen *ps; // ok
static int is; // 错误: 静态成员
int &rfi; // 错误: 引用成员
};

union内部可以定义成员函数,包括构造函数和析构函数
例:
union TokenValue {
public:
TokenValue(int ix) : _ival(ix) { }
TokenValue(char ch) : _cval(ch) { }
// …
int ival() { return _ival; }
char cval() { return _cval; }
private:
int _ival;
char _cval;
// …
};
int main() {
TokenValue tp(10);
int ix = tp.ival();
// …
}

匿名union
例:
class Token {
public:
TokenKind tok;
// 匿名union
union {
char _cval;
int _ival;
char *_sval;
double _dval;
};
};
匿名union的数据成员可以再定义union的域中被直接访问。
例:
int lex() {
Token curToken;
char *curString;
int curIval;
// … 确定语法单元
// … 设置curToken
case ID:
curToken.tok = ID;
curToken._sval = curString;
break;
case Constant: // 整数常量
curToken.tok = Constant;
curToken._ival = curIval;
break;
// … etc.
}

(8)位域:一种节省空间的成员
例:
typedef unsigned int Bit;
class File {
public:
Bit mode: 2;
Bit modified: 1;
Bit prot_owner: 3;
Bit prot_group: 3;
Bit prot_world: 3;
// …
};
相邻位域的位被存储在同一个整形的连续位中

取地址操作符(&)不能被应用在位域上,所以也没有能指向类的位域的指针。位域也不能是类的静态成员。C++中提供了一个bitset模板,他可以辅助

操纵位的集合。在可能的情况下,应尽可能使用它来取代位域。

(9)类域
类体本身就是一个域,在类体中,每一个类成员的声明都向它的类域中引入了一个成员名。

类域中名字解析,首先解析typedef类型定义,之后解析每个成员。

用在类定义中的名字(除了在inline成员函数定义中的名字和缺省参数的名字)期解析过程如下:
- 在名字使用之前出现的类成员的声明应予以考虑
- 第一步解析不成功,则在类定义之前的名字空间域中出现的声明应予以考虑。
例:
typedef double Money;
class Account {
// …
private:
static Money _interestRate;
static Money initInterest();
// …
};
编译器首先在类Account的域中查找Money的声明它只考虑在使用Money之前的成员声明因为没有找到这样的成员声明所以编译器接着在全局域中查找

Money的声明又因为只有在类Account定义之前的声明才会被考虑所以最终找到了全局typedef Money的声明它是在_interestRate和initInterest()的声

明中被使用的类型。

被用在类成员函数定义中的名字的解析过程如下:
- 在成员函数局部域中的声明首先被考虑
- 第一步解析不成功,则考虑所有类成员声明
- 第二不解析不成功,则考虑在成员函数定义之前的名字空间域中出现的声明

类静态成员定义中的名字解析过程如下:
- 考虑所有类成员声明
- 第一步失败,考虑在静态成员定义之前的名字空间域中出现的声明

(10)嵌套类
例:
class Node { /* … */ };
class Tree {
public:
// Node 被封装在Tree 的域中
// 在类域中Tree::Node 隐藏了::Node
class Node {…};
// ok: 被解析为嵌套类: Tree::Node
Node *tree;
};
// Tree::Node 在全局域中不可见
// Node 被解析为全局的Node 声明
Node *pnode;
class List {
public:
// Node 被封装在List 的域中
// 在类域List::Node 中隐藏了::Node
class Node {…};
// ok: 解析为: List::Node
Node *list;
};

嵌套类不能直接访问其外围类的非静态成员即使这些成员是公有的任何对外围类的非静态成员的访问都要求通过外围类的指针引用或对象来完成
例:
class List {
public:
int init( int );
private:
class ListItem {
public:
ListItem( int val = 0 );
void mf( const List & );
int value;
int memb;
};
};
List::ListItem::ListItem( int val )
{
// List::init() 是类List 的非静态成员
// 必须通过List 类型的对象或指针来使用
value = init( val ); // 错误: 非法使用init
}

嵌套类可以直接访问任何外围类的静态成员,类型名,枚举值(假定这些为共有的)。类型名为typedef名字,枚举类型名,或是一个类名。
例:
class List {
public:
typedef int (*pFunc)();
enum ListStatus { Good, Empty, Corrupted };
// …
private:
class ListItem {
public:
void check_status();
ListStatus status; // ok
pFunc action; // ok
// …
};
// …
};

A。嵌套类域中的名字解析
被用在嵌套类的定义中的名字(除了inline成员函数定义中的名字和缺省实参的名字之外)其解析过程如下:
- 考虑出现在名字使用点之前的嵌套类本身的成员声明
- 第一步不成功,则考虑出现在名字使用点之前的外围类的成员声明
- 第二步没成功,则考虑出现在嵌套类定义之前的名字空间域中的声明
例:
enum ListStatus { Good, Empty, Corrupted };
class List {
public:
// …
private:
class ListItem {
public:
// 查找:
// 1) 在List::ListItem 中
// 2) 在List 中
// 3) 在全局域中
ListStatus status; // 引用全局枚举
// …
};
// …
};

例:
class List {
private:
class ListItem;
// …
public:
enum ListStatus { Good, Empty, Corrupted };
// …
};
class List::ListItem {
public:
// 查找:
// 1) 在List::ListItem 中
// 2) 在List 中(考虑List所有的成员函数)
// 3) 在全局域中
ListStatus status; // List::ListStatus
// …
};

被用在嵌套类的成员函数定义中的名字其解析过程如下:
- 首先考虑成员函数局部域中的声明
- 第一步没成功,则考虑所有嵌套类成员声明
- 第二步没成功,则考虑所有外围类成员声明
- 第三步没成功,则考虑在成员函数定义之前的名字空间域中出现的声明。
例:
class List {
public:
enum ListStatus { Good, Empty, Corrupted };
// …
private:
class ListItem {
public:
void check_status();
ListStatus status; // ok
// …
};
ListItem *list;
// …
};
int list = 0;
void List::ListItem::check_status()
{
int value = list; // 哪个list?
}

(11)作为名字空间成员的类
在用户声明的名字空间中定义的类名只在该名字空间的域中可见而在全局域或其他名字空间中不可见
例:
namespace cplusplus_pri mer {
class Node { /* … / };
}
namespace DisneyFeatureAnimation {
class Node { /
… */ };
}
Node *pnode; // 错误: Node 在全局域中不可见
// OK: 声明nodeObj 的类型为DisneyFeatureAnimation::Node
DisneyFeatureAnimation::Node nodeObj;
// using 声明: 使得node 在全局域中可见
using cplusplus_primer::Node;
Node another; // cplusplus_primer::Node

(12)局部类
例:
局部类只能访问在外围局部域中定义的类型名,静态变量以及枚举值
int a, val;
void foo( int val )
{
static int si;
enum Loc { a = 1024, b };
class Bar {
public:
Loc locVal; // ok;
int barVal;
void fooBar( Loc l = a ) { // ok: Loc::a
barVal = val; // 错误: 局部对象
barVal = ::val; // OK: 全局对象
barVal = si; // ok: 静态局部对象
locVal = b; // ok: 枚举值
}
};
// …
}
在局部类体内不包括成员函数定义中的的名字解析过程是在外围域中查找出现在局部类定义之前的声明在局部类的成员函数体内的名字的解析过程是在

查找外围域之前首先直找该类的完整域。

2.类的初始化,赋值和析构
(1)类初始化
A。显示初始化
例:
class Data {
public:
int ival;
char *ptr;
};

int main()
{
// local1.ival = 0; local1.ptr = 0
Data local1 = { 0, 0 };
// local2.ival = 1024;
// local2.ptr = “Anna Livia Plurabelle”
Data local2 = { 1024, “Anna Livia Plurabelle” };
// …
}

B。构造函数初始化
构造函数与类同名,缺省构造函数没有任何参数,没有进行任何初始化操作,构造函数不能有任何返回值
例:
class Account {
public:
// 缺省构造函数
Account();
// …
private:
char *_name;
unsigned int _acct_nmbr;
double _balance;
};

构造函数可重载,数量不限,但是参数表要唯一

成员初始化表
例:
// 使用成员初始化表的缺省Account 构造函数
inline Account::
Account()
: _name( 0 ),
_balance( 0.0 ), _acct_nmbr( 0 )
{}
成员初始化表只能在构造函数定义中被指定,不能在声明中被指定。初始化参数表被放在参数表和构造函数体之间,由冒号开始。

例:
class Account{
public:
Account() const; // error
Account() volatile; // error
// …
};
被应用到类对象上的适当的构造函数与该对象是const,非const或volatile无关。只有当构造函数执行完毕,类对象已经被初始化的时候,该类对象的

常量性才被建立起来。一旦析构函数被调用,常量性消失。一个const类对象是“从其构造函数完成到析构函数开始”的这段时间内才被认为是const的

,对volatile类对象也一样。

例:
// 在某个头文件中
extern void print( const Account &acct );
// …
int main()
{
// 把"oops" 转换成一个Account 对象
// 用Account::Account( “oops”, 0.0 )
print( “oops” );
// …
}
缺省情况下,单参数构造函数(或者除了第一个参数,其他都有缺省值的情况下)被用作转换操作符。print调用里,Account构造函数被编译器隐式的

调用,一边把一个文字字符转换成一个Account对象。
无意的隐式转换,是很难跟踪的。关键字explicit可以通知编译器不需要提供隐式转换:
class Account{
public:
explicit Account( const char*, double=0.0 );
// …
};
explicit只能被应用在构造函数上

缺省构造函数是指不需要用户指定参数就能被调用的构造函数。
例:
// 每个都是缺省构造函数
Account::Account() { … }
iStack::iStack( int size = 0 ) { … }
Complex::Complex(double re=0.0,double im=0.0) { … }

int main()
{
Account acct;
// …
}
编译器首先检查Account类是否定义了缺省构造函数,以下情况之一会发生:
1 定义了缺省构造函数它被应用到acct上
2 定义了缺省构造函数但它不是公有的acct的定义被标记为编译时刻错误main()
没有访问权限
3 没有定义缺省构造函数但是定义了一个或者多个要求实参的构造函数acct的定义
被标记为编译时刻错误实参太少
4 没有定义缺省构造函数也没有定义其他构造函数该定义是合法的acct没有被初
始化没有调用任何构造函数

新用户常常会错误地认为如果不存在缺省构造函数则编译器会自动生成一个缺省构
造函数并将其应用在对象上以初始化类的数据成员对于我们定义的Account类来说这
就不是真的系统既没有生成缺省构造函数也没有调用它对于含有类数据成员或继承来的
比较复杂的类这在部分上是对的可能会生成一个缺省构造函数但是它不会为内置或复
合型的数据成员如指针或数组提供初始值
如果我们想初始化内置或复合型的数据成员则我们必须在一个或一组构造函数中显式
地完成如果不这样做就不可能知道局部或动态分配的类对象中的内置和复合型数据成
员是一个有效值还是一个未初始化的值

在实际的C++程序中,非公有的构造函数的主要用处是:
1.防止用一个类对象向该类的另一个类对象做拷贝
2.指出只有当一个类在继承层次中被用作基类,而不能直接被应用程序操作时,构造函数才能被调用???

拷贝构造函数
inline Account::
Account( const Accout &rhs )
: _balance( rhs._balance )
{
_name = new char[ strlen(rhs._name)+1 ];
strcpy( _name, rhs._name );
// 不能拷贝rhs._acct_nmbr
_acct_nmbr = get_unique_acct_nmbr();
}
Account acct2( acct1 );
编译器判断是否为Account类声明了一个显式的拷贝构造函数如果声明了拷贝构造函
数并且是可以访问的则调用它如果声明了拷贝构造函数但是不可访问则acct2的定义
就是一个编译时刻错误如果没有声明拷贝构造函数的实例则执行缺省的按成员初始化
如果我们后来引入或去掉了一个拷贝构造函数的声明则用户程序无需改变但是需要重
新编译它们

(3)类的析构函数
析构函数没有任何参数和返回值,不能被重载,他会构造函数形成互补。类初始化时,构造函数被调用,类生命期结束时,析构函数被调用。
例:
class Account {
public:
Account();
explicit Account( const char*, double=0.0 );
Account( const Account& );
~Account();
// …
private:
char *_name;
unsigned int _acct_nmbr;
double _balance;
};
inline
Account::~Account()
{
// 这是必要的
delete [] _name;
return_acct_nmbr( _acct_nmbr );
// 没有必要
_name = 0;
_balance = 0.0;
_acct_nmbr = 0;
}

析构函数的作用:
- 释放构造函数中申请的资源,比如:申请的内存或文件描述符,使用的锁等
- 执行需要在类结束时需要执行的任何操作

析构函数可能会导致代码膨胀
例:
Account acct ( “Tina Lee” );
int swt;
// …
switch( swt ) {
case 0:
return;
case 1:
// 进行操作
return;
case 2:
// 进行其他操作
return;
// 等等
}
在每个return语句之前析构函数都必须被内联地展开在Account类的析构函数的情况
下由于它的长度较小所以多次展开的相关开销也较小但是如果已经发现它确实是一
个问题则解决方案是或者把析构函数声明为非内联的或者重新改写程序代码一种可
能的重写方案是在每个case标签中用break语句代替return语句然后在switch语句后面引入一
个return语句
// 重写来提供一个返回点
switch( swt ) {
case 0:
break;
case 1:
// 进行操作
break;
case 2:
// 进行另一些操作
break;
// 等等
}
// 单个返回点
return;

(4)类对象数组和vector
例:
Account table[ 16 ]; 调用16次默认缺省构造函数

Account pooh_pals[] = { “Piglet”, “Eeyore”, “Tigger” };
定义了三个元素的数组三个元素依次用构造函数
Account( “Piglet”, 0.0 ); // 第一个元素
Account( “Eeyore”, 0.0 ); // 第二个元素
Account( “Tigger”, 0.0 ); // 第三个元素

例:
vector< Point > vec( 5 );
元素初始化过程如下:
- 先创建一个底层类类型的临时对象,并在其上进行该类的缺省构造函数
- 在vector的每个元素上依次进行拷贝构造函数,用临时类对象的拷贝初始化每一个类对象
- 删除临时对象

(5)成员函数初始化
例:
inline Account::
Account( const char *name, double opening_bal )
: _name( name ), _balance( opening_bal )
{
_acct_nmbr = get_unique_acct_nmbr();
}

inline Account::
Account( const char *name, double opening_bal )
{
_name = name;
_balance = opening_bal;
_acct_nmbr = get_unique_acct_nmbr();
}
两种实现的最终结果都是一样的,在两个构造函数的调用结束处,三个成员拥有相同的值,却别就是成员初始化表只提供该类的数据成员初始化,在构

造函数内对数据成员设置值是一个赋值操作。即成员初始化表只能初始化该类中的成员。
构造函数执行过程被分成两个阶段:隐式或显示初始化阶段,以及一般的计算阶段。计算阶段是由构造函数体内的所有语句构成。在计算阶段中,数据

成员的设置被认为是赋值,而不是初始化。初始化阶段可以是隐式的或者是显示的,取决于是否存在成员初始化表。隐式初始化阶段按照声明的顺序依

次调用所有基类的缺省构造函数。然后是所有成员类对象的缺省构造函数。
例:
inline Account::
Account()
{
_name = “”; //无意义,因为默认已经隐式初始化_name为空字串
_balance = 0.0;
_acct_nmbr = 0;
}
初始化阶段是隐式的,在构造函数被执行前,先调用与_name相关联的缺省string构造函数
更正确的实现如下:
inline Account::
Account() : _name( string() )
{
_balance = 0.0;
_acct_nmbr = 0;
}

内置数据类型初始化一般使用如下表示方法:
// 更受欢迎的初始化风格
inline Account::
Account() : _balanae( 0.0 ), _acct_nmbr( 0 )
{ }

const和引用数据成员必须要在成员初始化表中被初始化,否则,编译出现错误
例:
class ConstRef {
public:
ConstRef( int ii );
private:
int i;
const int ci;
int &ri;
};
ConstRef::
ConstRef( int ii )
{ // 赋值
i = ii; // ok
ci = ii; // 错误: 不能给一个const 赋值
ri = i; // 错误ri 没有被初始化
}
每个成员在初始化表中只能出现一次,初始化顺序不是由名字在初始化表中的顺序决定,而是由成员声明的顺序决定的。
例:
class Account {
public:
// …
private:
unsigned int _acct_nmbr;
double _balance;
string _name;
};
下面的缺省构造函数
inline Account::
Account() : _name( string() ), _balance( 0.0 ), _acct_nmbr( 0 )
{}
初始化顺序为,acct_nmbr _balance 然后是_name 。但是在初始化表中出现的成员(或者是在被隐式初始化的成员类对象中),总是在构造函数体内

成员被赋值前被初始化,例:
inline Account::
Account( const char *name, double bal )
: _name( name ), _balance( bal )
{
_acct_nmbr = get_unique_acct_nmbr();
}
初始化顺序是_balance,_name,_acct_nmbr。

(6)按成员初始化
例:
Account oldAcct( “Anna Livia Plurabelle” );
Account newAcct( oldAcct );
用一个类对象来初始化另一个类对象,被称为缺省的按成员初始化。缺省是因为它自动发生,无论我们是否提供显示构造函数。安城院是因为初始化的

单元是单个非静态成员,而不是对整个对象进行按位拷贝。

用一个类对象初始化该类的另一个对象发生在如下情形:
A。用一个类对象显示的初始化另一个类对象,例:
Account newAcct( oldAcct );
B。把一个类对象作为参数传递给一个函数,例:
extern bool cash_on_hand( Account acct );
if ( cash_on_hand( oldAcct ))
// …
把一个类对象作为函数函数返回值传递回来,例:
extern Account
consolidate_accts( const vector< Account >& )
{
Account final_acct;
// do the finances …
return final_acct;
}

C。非空顺序容器类型的定义,例:
// 五个string 拷贝构造函数被调用
vector < string > svec( 5 );
D。把一个类对象插入到另一个容器类型中,例:
svec.push_back( string( “pooh” ));

(7)按成员赋值
缺省的按成员赋值,用于一个类对象向该类的另一个对象的赋值操作。如果一个类没有定义按成员赋值的函数,则编译器会默认生成一个拷贝复制操作

符。
当一个类向该类的另一个对象赋值时,会发生如下几步操作:
- 检查该类是否声明了一个显示的拷贝赋值操作符
- 如果是,则检查访问权限,判断是否在这个程序部分可以调用
- 如果它不能被调用,则产生编译错误,否则,执行赋值操作
- 如果该类没有提供显示操作符,则执行缺省按成员赋值
- 在缺省按成员赋值下,每个内置类型或复合类型被赋值给相应的成员
- 对于每个内置类对象,递归前面所有步骤,直到所有内置或复合类型的数据成员都被赋值

(8)效率问题

3.重载操作符和用户定义的转换
(1)操作符重载
#include
class String;
istream& operator>>( istream &, String & );
ostream& operator<<( ostream &, const String & );
class String {
public:
// 构造函数的重载集合
// 提供自动初始化
String( const char * = 0 );
String( const String & );
// 析构函数: 自动释放初始化的对象

~String();
// 赋值操作符的重载集合
String& operator=( const String & );
String& operator=( const char * );
// 重载的下标操作符
char& operator const;
// 等于操作符的重载集合
// str1 == str2;
bool operator==( const char * ) const;
bool operator==( const String & ) const;
// 成员访问函数
int size() { return _size; }
char* c_str() { return _string; }
private:
int _size;
char *_string;
};

类中使用的操作符函数不会进行隐式转换,但是类之外定义的操作符可以进行隐式转换,至于究竟是在类内定义操作符函数还是类外定义操作符函数,

取决于效率(操作符使用的次数)。

一般情况下,怎样把一个操作符声明为类成员还是名字空间成员?在某些情况下,没有选择的余地:
- 如果一个重载操作符是类成员,那么只有当跟他一起被使用的左操作数是该类的对象时,他才会被调用。如果该操作符的操作数必须是其他

  类型,那么重载操作符必须是名字空间成员
- C++要求,赋值(=),下标([]),调用(())和成员访问箭头(->)操作数必须被定义为类成员操作符。任何把这些操作符定义为名字空	间成员都会被标记编译时刻错误

例:
// 错误: 必须是类成员
char& operator[]( String & ,int ix );

(2)友元
例:
class String {
friend bool operator==( const String &, const String & );
friend bool operator==( const char *, const String & );
friend bool operator==( const String &, const char * );
public:
// … String 类中的其他部分
};

(3)操作符=
例:
class String {
public:
// char* 的赋值操作符
String& operator=( const char * ); //字符串赋值类型必须为const,否则会引入问题
// …
private:
int _size;
char *_string;
};

String car (“Volks”);
car = “Studebaker”;

(4)操作符[]
例:
#include
inline char&
String::operator[]( int elem ) const
{
assert( elem >= 0 && elem < _size );
return _string[ elem ];
}

String entry( “extravagant” );
String mycopy;
for ( int ix = 0; ix < entry.size(); ++ix )
mycopy[ ix ] = entry[ ix ];

(5)操作符operator()
class absInt {
public:
int operator()( int val ) {
int result = val < 0 ? - val : val;
return result;
}
};

#include
#include
int main() {
int ia[] = { - 0, 1, - 1, - 2, 3, 5, - 5, 8 };
vector< int > ivec( ia, ia+8 );
// 把ivec 的每个元素设置为其绝对值
transform( ivec.begin(), ivec.end(), ivec.begin(), absInt() );
// …
}

transform解析:
transform()的第一个和第二个实参指示了absInt操作被应用的元素范围第三个实参指
向被用来存储absInt操作结果的向量的开始
typedef vector::iterator iter_type;
// transform() 的实例把
// 操作absInt 应用到int 型
// vector 的所有元素上
iter_type transform( iter_type iter, iter_type last,
iter_type result, absInt func )
{
while ( iter != last )
*result++ = func( *iter++ ); // 调用absInt::operator()
return iter;
}

(6)操作符->
class ScreenPtr {
// …
private:
Screen *ptr;
};

class ScreenPtr {
public:
ScreenPtr( Screen &s ) : ptr( &s ) { }
//…
};

// 支持指针行为的重载操作符
class ScreenPtr {
public:
Screen& operator*() { return ptr; }
Screen
operator->() { return ptr; }
//…
};

ScreenPtr类型的对象的定义必须提供初始值一个Screen类型的对象ScreenPtr对象
将指向它否则ScreenPtr对象的定义就是错误的
ScreenPtr p1; // 错误: ScreenPtr 没有缺省构造函数
Screen myScreen( 4, 4 );
ScreenPtr ps( myScreen ); // ok

(7)操作符++和–
例:
class ScreenPtr {
public:
ScreenPtr( Screen &s , int arraySize = 0 )
: ptr( &s ), size ( arraySize ), offset( 0 ) { }
private:
int size;
int offset;
Screen *ptr;
};

Screen myScreen( 4, 4 );
ScreenPtr pobj( myScreen ); // ok: 指向单个对象
const int arrSize = 10;
Screen *parray = new Screen[ arrSize ];
ScreenPtr parr( *parray, arrSize ); // ok: 指向数组

前置操作符++和–
class ScreenPtr {
public:
Screen& operator++();
Screen& operator- - ();
// …
};

Screen& ScreenPtr::operator++()
{
if ( size == 0 ) {
cerr << “cannot increment pointer to single object\n”;
return *ptr;
}
if ( offset >= size - 1 ) {
cerr << “already at the end of the array\n”;
return *ptr;
}
++offset;
return *++ptr;
}
Screen& ScreenPtr::operator–()
{
if ( size == 0 ) {
cerr << “cannot decrement pointer to single object\n”;
return *ptr;
}
if ( offset <= 0 ) {
cerr << “already at the beginning of the array\n”;
return *ptr;
}
–offset;
return *–ptr;
}

后置操作符++和–
class ScreenPtr {
public:
Screen& operator++(); // 前置操作符
Screen& operator- - ();
Screen& operator++(int); // 后置操作符
Screen& operator- - (int);
// …
};

Screen& ScreenPtr::operator++(int)
{
if ( size == 0 ) {
cerr << “cannot increment pointer to single object\n”;
return *ptr;
}
if ( offset == size ) {
cerr << “already one past the end of the array\n”;
return *ptr;
}
++offset;
return *ptr++;
}
Screen& ScreenPtr::operator–(int)
{
if ( size == 0 ) {
cerr << “cannot decrement pointer to single object\n”;
return *ptr;
}
if ( offset == - 1 ) {
cerr << “already one before the beginning of the array\n”;
return *ptr;
}
–offset;
return *ptr–;
}

(8)操作符new和delete
例:
class Screen {
public:
void *operator new( size_t );
// …
};

Screen *ps = new Screen; //调用类内定义的new表达式
Screen *ps = ::new Screen; //调用全局new表达式
当调用new表达式时,会首先检查类内是否定义,如果定义,则使用类内定义的new表达式,否则使用全局new表达式,全局new表达式可以显示调用,只

需加上全局域解析符即可

class Screen {
public:
void operator delete( void* ); //delete表达式的返回类型必须是void,并且第一个参数类型是void*
};
Screen *ps = new Screen;
delete ps; //调用类内delete表达式
::delete ps; //调用全局delete表达式
当delete表达式指向一个类类型对象的指针时,编译器首先检查该类是否定义了成员操作符delete(),如果有,则调用,否则,调用全局操作符

delete()。操作符的void*参数自动被初始化为ps值。

class Screen {
public:
// replaces:
// void operator delete( void* );
void operator delete( void , size_t );
};
为一个类类型而定义的delete操作符,如果它被表达式调用,则它可以有两个参数,第一个参数为void
,第二个参数必须是预定义类型size_t(size_t

被包含在库头文件中)

Screen *ptr = new Screen( 10, 20 );
等价于
// C++伪码
ptr = Screen::operator new( sizeof( Screen ) ); //先调用new表达式
Screen::Screen( ptr, 10, 20 ); //再调用构造函数

delete ptr;
等价于
// C++伪玛
Screen::~Screen( ptr ); //先调用析构函数
Screen::operator delete( ptr, sizeof( *ptr ) ); //再调用delete表达式

A。数组操作符new[]和delete[]
例:
class Screen {
public:
void *operator new;
// …
};
当一个类表达式创建一个类类型数组时,编译器首先检查类中是否有new[]操作符,如果有,则调用类内表达式new[],否则调用全局表达式new[]
Screen *ps = ::new Screen[10]; //全局new[]表达式

class Screen {
public:
void operator delete;
};

delete[] ps;
::delete[] ps;
当delete表达式的操作数是一个指向类类型指针时,编译器就会检查该类是否有成员操作符delete,如果有调用类内操作符,否则,调用全局操作

符。

创建数组的new表达式首先调用类操作符new来分配存贮区然后再调用缺省构造函
数依次初始化数组的每一个元素如果这类定义了构造函数但是没有缺省构造函数则相
应的new表达式就是错误的因为没有任何C++语法可以为数组元素指定初始值或者在数
组版本的new表达式中为类的构造函数指定实参

B。定位操作符new()和delete()
只要每个声明都有唯一的参数表,成员操作符new()可以重载。但是new操作符的第一个参数必须是size_t。
例:
class Screen {
public:
void operator new( size_t );
void operator new( size_t, Screen );
// …
};
void func( Screen start ) {
Screen ps = new (start) Screen;
// …
}
Screen ps = new (start) Screen; 调用步骤如下:
- 先调用操作符new( size_t, Screen
)
- 调用类screen的缺省构造函数初始化该对象
- 用screen对象的地址初始化ps
如果在上述第二步抛出了异常,则编译器会在screen类中找到与new[](size_t, Screen
)参数匹配的delete表达式,例:
void operator delete( void
, Screen
);
如果找到则调用这个delete表达式,否则,不调用任何delete,此时会造成内存泄漏

定位操作符new和delete也可以被重载:
class Screen {
public:
void operator new
;
void operator new[]( size_t, Screen );
void operator delete[]( void
, size_t );
void operator delete[]( void*, Screen* );
// …
};

(9)用户定义的转换
例:
class SmallInt {
friend operator+( const SmallInt &, int );
friend operator- ( const SmallInt &, int );
friend operator- ( int, const SmallInt & );
friend operator+( int, const SmallInt & );
public:
SmallInt( int ival ) : value( ival ) { }
operator+( const SmallInt & );
operator- ( const SmallInt & );
// …
private:
int value;
};

SmallInt si( 3 );
si + 3.14159
分两步解析:
- double类型的3.14159被转换成整形int 3
- 调用操作符operator+( const SmallInt &, int ),返回值6

例:
class SmallInt {
public:
SmallInt( int ival ) : value( ival ) { }
// 转换操作符
// SmallInt ==> int
operator int() { return value; }
// 没有提供重载操作符
private:
int value;
};
操作符int()是一个转换函数,他定义了一个用户定义的转换,用户定义的转换是在类类型和转换函数中指定的类型之间的转换。转换函数定义了应用

转换时编译器必须做出的动作。

SmallInt si( 3 );
si + 3.14159;
解析分为如下两步:
- 调用转换函数,产生整型值3
- 整型值3被提升为3.0,并与double文字常量3.14159相加,生成double类型6.14159

例:
#include
class SmallInt {
friend istream&
operator>>( istream &is, SmallInt &s );
friend ostream&
operator<< ( ostream &os, const SmallInt &s )
{ return os << s.value; }
public:
SmallInt( int i=0 ) : value( rangeCheck( i ) ){}
int operator=( int i )
{ return( value = rangeCheck( i ) ); }
operator int() { return value; }
private:
int rangeCheck( int );
int value;
};

istream& operator>>( istream &is, SmallInt &si ) {
int ix;
is >> ix;
si = ix; // SmallInt::operator=(int)
return is;
}
int SmallInt::rangeCheck( int i )
{
/* 如果前8位以外的位被置位

  • 则报告值太大了: 然后退出*/
    if ( i & ~0377 ) {
    cerr << “\en***SmallInt range error: "
    << i << " ***” << endl;
    exit( - 1 );
    }
    return i;
    }

A。转换函数
转换函数是一种特殊类型的成员函数,他定义了一个由用户定义的转换,一遍把一个类对象转换成某种其他类型。
例:
#include “SmallInt.h”
typedef char tName;
class Token {
public:
Token( char
, int );
operator SmallInt() { return val; }
operator tName() { return name; }
operator int() { return val; }
// 其他公有成员
private:
SmallInt val;
char *name;
};

#include “Token.h”
void print( int i )
{
cout << "print( int ) : " << i << endl;
}
Token t1( “integer constant”, 127 );
Token t2( “friend”, 255 );
int main()
{
print( t1 ); // t1.operator int()
print( t2 ); // t2.operator int()
return 0;
}

转换函数一般形式operator type(); type可以是内置类型。类类型,typedef名,type不能为数组或函数类型。转换函数必须是成员函数,它的声明不

能指定返回类型和参数表。例:
operator int( SmallInt & ); // 错误: 不是成员
class SmallInt {
public:
int operator int(); // 错误: 返回类型
operator int( int = 0 ); // 错误参数表
// …
};

#include “Token.h”
Token tok( “function”, 78 );
// 函数型的表示法: 调用Token::operator SmallInt()
SmallInt tokVal = SmallInt( tok );
// static_cast: 调用Token::operator tName()
char *tokName = static_cast< char * >( tok );

extern void calc( double );
Token tok( “constant”, 44 );
// 调用tok.operator int() 吗? 是的
// int --> double 通过标准转换
calc( tok );

B。用构造函数作为转换函数
在一个类的构造函数中,凡是只带一个参数的构造函数,例SmallInt(int) ,都定义了一组隐式转换,把构造函数的参数类型转换为该类的类型。
例:
extern void calc( SmallInt );
int i;
// 需要把i转换成一个SmallInt值
// SmallInt(int) 可以做到这一点
calc( i );

如果想不用隐式转换,则使用explicit,例:
class Number {
public:
// 不会被用来执行隐式转换
explicit Number( const SmallInt & );
// …
};

extern void func( Number );
SmallInt si(87);
int main()
{ // 错误: 从SmallInt 到Number 没有隐式转换
func( si );
func( Number( si ) ); // ok: cast
func( static_cast< Number >( si ) ); // ok: 强制转换
// …
}

(10)(11)(12)
。。。。。。

4.类模板
/* ******************************** /
/
******************************** /
/
******************************** /
/
******************************** /
/
******************************** /
/
******************************** /
/
******************************** */

面向对象程序设计
1.类继承和子类型
(1)定义一个类层次结构
C++通过以下几种形式支持多态性:
- 通过一个隐式转换,从派生类指针或引用转换到基类指针或引用
Query *pquery = new NameQuery( “Glass” );
- 通过虚拟函数机制
pquery->eval();
- 通过dynamic_cast和typeid操作符
if ( NameQuery pnq =
dynamic_cast< NameQuery
>( pquery )) …

例:一个Query父类,有四个子类,分别为:AndQuery,OrQuery,NotQuery,NameQuery
class Query{…};

class AndQuery:public Query{…};
class OrQuery:public Query{…};
class NotQuery:public Query{…};
class NameQuery:public Query{…};

单继承一般形式为:
访问类型 基类名
访问类型为public,protected,private之一,基类是之前定义的(基类必须已定义好才能被继承)

例:
// 错误: Query 必须已经被定义
class Query;
class NameQuery : public Query { … };

(2)确定层次的成员
派生类可以访问基类的public和protected区域中的成员,无法访问private区域中的成员
例:
#include
#include
#include
#include
typedef pair< short, short > location;
class Query {
public:
// 构造函数和析构函数在17.4 节讨论
// 拷贝构造函数和拷贝赋值操作符在17.6 节讨论
// 支持公有接口的操作
virtual void eval() = 0;
void display () const;
// 读访问函数
const set *solution() const;
const vector *locations() const { return &_loc; }
static const vector *text_file() {return _text_file;}
protected:
set _vec2set( const vector );
static vector *_text_file;
set _solution;
vector _loc;
};
inline const set

Query::
solution()
{
return _solution
? _solution
: _solution = _vec2set( &_loc );
}

virtual void eval() = 0;为纯虚函数,后续的派生类都将提供一个实际实例

定义子类
typedef vector loc;
class NameQuery : public Query {
public:
// …
// 改写virtual Query::eval() 实例29
void eval();
// 读访问函数
string name() const { return _name; }
static const map<string,loc*> word_map() { return _word_map; }
protected:
string _name;
static map<string,loc
> *_word_map;
};

class NotQuery : public Query {
public:
// …
// 另外一种语法: 显式的virtual 关键词
// 改写Query::eval()
virtual void eval();
// 读访问函数
const Query *op() const { return _op; }
static const vector*all_locs(){ return _all_locs; }
protected:
Query *_op;
static const vector< location > *_all_locs;
};

class OrQuery : public Query {
public:
// …
virtual void eval();
const Query *rop() const { return _rop; }
const Query *lop() const { return _lop; }
protected:
Query *_lop;
Query *_rop;
};

class AndQuery : public Query {
public:
// 构造函数在17.4 节讨论
virtual void eval();
const Query *rop() const { return _rop; }
const Query *lop() const { return _lop; }
static void max_col( const vector< int > *pcol )
{ if ( !_max_col ) _max_col = pcol; }
protected:
Query *_lop;
Query *_rop;
static const vector< int > *_max_col;
};

void
Query::
display()
{
if ( ! _solution->size() ) {
cout << “\n\tSorry, "
<< " no matching lines were found in text.\n”
<< endl;
}
set::const_iterator
it = _solution->begin(),
end_it = _solution->end();
for ( ; it != end_it; ++it ) {
int line = it;
// 文本行不要从0 开始, 这样会把用户弄糊涂…
cout << "( " << line+1 << " ) "
<< (
_text_file)[line] << ‘\n’;
}
cout << endl;
}

(3)基类成员访问
相同的数据成员在派生类中会覆盖基类中得成员
例:
class Diffident {
public: // …
protected:
int _mumble;
// …
};
class Shy : public Diffident {
public: // …
protected:
// 隐藏了Diffident::_mumble 的可视性
string _mumble;
// …
};

void
Shy::
turn_eyes_down()
{
// …
_mumble = “excuse me”; // ok
// 错误: int Diffident::_mumble 被隐藏
_mumble = -1;
// ok: 限定修饰基类的实例
Diffident::_mumble = -1;
}

相同的成员函数在派生类中会覆盖基类中的成员
例:
class Diffident {
public:
void mumble( int softness );
// …
};
class Shy : public Diffident {
public:
// 隐藏了Diffident::mumble 的可视性
// 它们没有形成一对重载实例
void mumble( string whatYaSay );
void print( int soft, string words );
// …
};

Shy simon;
// ok: Shy::mumble( string )
simon.mumble( “pardon me” );
// 错误: 期望第一个实参是string 类型
// Diffident::mumble( int ) 不可见
simon.mumble( 2 );

因为基类和子类之间所属域不同,所以同名函数或变量不能重载只能覆盖,但是可以使用using引入同名函数或变量
例:
class Shy : public Diffident {
public:
// ok: 在标准C++ 下通过using 声明
// 创建了基类和派生类成员的重载集合
void mumble( string whatYaSay );
using Diffident::mumble;
// …
};

派生类可以直接访问该类其他对象的protected和private成员。
例:
bool
NameQuery::
compare( const NameQuery *pname )
{
int myMatches = _loc.size(); // ok
int itsMatches = pname->_loc.size(); // ok as well
return myMatches == itsMatches;
}

例:
Query *pb = new NameQuery( “sprite” );
pb->eval(); // 调用NameQuery::eval()
注意:
- 如果Query和NameQuery都声明了一个同名的非虚拟成员,则通过pb调用的总是Query实例
- 如果Query和NameQuery都声明了一个同名的数据成员,则通过pb调用的总是Query实例
- 如果NameQuery引入了一个在Query中不存在的虚拟函数,那么,通过pb调用就会产生编译错误
例:
// 错误: suffix() 不是 Query 的成员
pb->suffix();
- 如果我们试图通过pq访问NameQuery的数据成员或非虚拟成员函数,也会产生编译时刻错误
例:
// 错误: _name 不是Query 的成员
pb->_name;
// 错误: Query 没有NameQuery 基类
pb->NameQuery::name();

对于静态成员而言,无论扩展了多少子类都是存在一个唯一的实例,它对于所有基类和子类而言是公共的
子类要想访问基类的私有成员只能通过在基类中将子类声明为friend友元才可以

(4)基类和派生类的构造
例:
class NameQuery : public Query {
public:
// …
protected:
bool _present;
string _name;
};

inline
NameQuery::
NameQuery( const string &name,
vector *ploc ) : _name( name ), Query( *ploc ), _present( true )
{}

构造函数调用顺序如下:
- 基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是在成员初始化表中的顺序
- 成员类对象构造函数。如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序,而不是在成员初始化表中的顺序
- 派生类构造函数

A。基类构造函数
class Query {
public:
// …
protected:
set *_solution;
vector _loc;
// …
};

inline Query::Query(): _solution( 0 ) {}

inline
Query::
Query( const vector< location > &loc )
: _solution( 0 ), _loc( loc )
{}

B。派生类构造函数
class NameQuery : public Query {
public:
explicit NameQuery( const string& );
NameQuery( const string&, vector* );
// …
protected:
// …
};

inline
NameQuery::
NameQuery( const string &name )
// Query::Query() 被隐式调用
: _name( name )
{}

inline
NameQuery::
NameQuery( const string &name, vector *ploc )
: _name( name ), Query( *ploc )
//显示调用
{}

inline NotQuery::
NotQuery( Query *op = 0 ) : _op( op ) {} // Query::Query() 被隐式调用
inline OrQuery::
OrQuery( Query *lop = 0, Query *rop = 0 )
: _lop( lop ), _rop( rop )
// Query::Query() 被隐式调用
{}
inline AndQuery::
AndQuery( Query *lop = 0, Query *rop = 0 )
: _lop( lop ), _rop( rop )
// Query::Query() 被隐式调用
{}

C。另一个类层次结构
派生类只能合法地调用其直接基类的构造函数,不能调用基类的基类。

D。析构函数
派生类析构函数调用顺序与它本身构造函数调用顺序相反。即先调用派生类本身的构造函数,再调用派生类内部的成员对象析构函数,最后调用基类析

构函数。

inline Query::
~Query(){ delete _solution; }
inline NotQuery::
~NotQuery(){ delete _op; }
inline OrQuery::
~OrQuery(){ delete _lop; delete _rop; }
inline AndQuery::
~AndQuery(){ delete _lop; delete _rop; }

(5)基类和派生类虚拟函数
什么时候会有多态性?
只有当通过基类指针或引用间接指向子类类型时,多态才会起作用。
例:
NameQuery nq( “lilacs” );
// ok: 但是nq 被"切割" 成一个Query 子对象
Query qobject = nq;

void print( Query object,
const Query *pointer, const Query &reference ) {
// 直到运行时刻才能确定
// 调用哪个print() 实例
pointer->print();
reference.print();
// 总是调用Query::print()
object.print();
}
int main()
{
NameQuery firebird( “firebird” );
print( firebird, &firebird, firebird );
}

基类中得虚拟函数要想在派生类中被实例化,则派生类中的函数原型必须要和基类中虚拟函数完全匹配才行。但是有特例,派生类中的基类虚拟函数的

返回值可以是基类虚拟函数反馈类型的公有派生类类型。

A,纯虚函数
例:
class Query {
public:
// 声明纯虚拟函数
virtual ostream& print( ostream&=cout ) const = 0;
// …
};
包含纯虚函数的类被称为抽象基类,抽象基类不能被实例化。
例:
// Query 声明了纯虚拟函数
// 所以, 程序员不能创建独立的Query 类对象
// ok: NameQuery 中的Query 子对象
Query *pq = new NameQuery( “Nostromo” );
// 错误: new 表达式分配Query 对象
Query *pq2 = new Query;

B。虚拟函数静态调用
例:
Query *pquery = new NameQuery( “dumbo” );
// 通过虚拟机制动态调用isA()
// 调用NameQuery::isA() 实例
pquery->isA();
// 在编译时刻静态调用isA
// 调用Query::isA 实例
pquery->Query::isA();

例:
class BinaryQuery : public Query {
public:
BinaryQuery( Query *lop, Query *rop, string oper )
: _lop(lop), _rop(rop), _oper(oper){}
~BinaryQuery() { delete _lop; delete _rop; }
ostream &print( ostream& =cout ) const = 0;
protected:
Query *_lop;
Query *_rop;
string _oper;
};

inline ostream&
BinaryQuery::
print( ostream &os ) const
{
if ( _lparen )
print_lparen( _lparen, os );
_lop->print( os );
os << ’ ’ << _oper << ’ ';
_rop->print( os );
if ( _rparen )
print_rparen( _rparen, os );
return os;
}

inline ostream&
AndQuery::
print( ostream &os ) const
{
// ok: 抑制虚拟机制
// 静态调用BinaryQuery::print
BinaryQuery::print( os );
}

(4)虚拟函数和缺省实参
例:
#include
class base {
public:
virtual int foo( int ival = 1024 ) {
cout < "base::foo() – ival: " < ival < endl;
return ival;
}
// …
};
class derived : public base {
public:
virtual int foo( int ival = 2048 ) {
cout << "derived::foo() – ival: " << ival << endl;
return ival;
}
// …
};

void
base::
foo( int ival = base_default_value )
{
int real_default_value = 1024;
if ( ival == base_default_value )
ival = real_default_value;
// …
}

void
derived::
foo( int ival = base_default_value )
{
int real_default_value = 2048;
if ( ival == base_default_value )
ival = real_default_value;
// …
}

int main()
{
derived *pd = new derived;
base *pb = pd;
int val = pb->foo();
cout << "main() : val through base: "
<< val << endl;
val = pd->foo();
cout << "main() : val through derived: "
<< val << endl;
}
输出结果:
derived::foo() – ival: 1024
main() : val through base: 1024
derived::foo() – ival: 2048
main() : val through derived: 2048

(5)虚拟析构函数
在继承机制下的析构函数行为如下:
派生类的析构函数先被调用,之后直接基类的析构函数被静态调用-如果是inline,则被内联展开。

例:
class Query {
public: // …
protected:
virtual ~Query();
// …
};
class NotQuery : public Query {
public:
~NotQuery();
// …
};

int main()
{
Query *pq = new NotQuery;
// 非法: 析构函数是protected
delete pq;
}
一般情况下,基类的析构函数被声明为虚拟的,而且应该是public共有的。

(6)虚拟函数
class Query {
public:
virtual void eval() = 0; //纯虚函数
// …
};
继承纯虚函数的基类如果不实例化纯虚函数,则它也是抽象类,不能被实例化。

(7)虚拟new操作符
例:
class Query {
public:
virtual Query *clone() = 0;
// …
};

class NameQuery : public Query {
public:
virtual Query *clone()
// 调用NameQuery 的拷贝构造函数
{ return new NameQuery( *this ); }
// …
};

Query *pq = new NameQuery( “Valery” );
Query *pq2 = pq->clone();

NameQuery *pnq = new NameQuery( “Rilke” );
NameQuery pnq2 =
static_cast<NameQuery
>( pnq->clone() );

class NameQuery : public Query {
public:
virtual NameQuery *clone()
{ return new NameQuery( *this ); }
// …
};

// Query *pq = new NameQuery( “Broch” );
Query *pq2 = pq->clone(); // ok
// NameQuery *pnq = new NameQuery( “Rilke” );
NameQuery *pnq2 = pnq->clone(); // ok

(8)虚拟函数构造函数和析构函数
如果基类构造函数中调用了一个虚拟函数,并且这个函数在基类和子类中都有实例化,那么在实际的子类构造函数进行时,在基类构造函数发生时,他

所调用的函数总是基类中得函数的实例,否则,程序走向可能是未知的(因为此时子类还未被初始化)。
对于派生类,在基类析构函数中也是如此,派生类部分由于已经被销毁,所以基类中调用的虚拟函数还是基类本身的。

6.按成员初始化赋值
例:
抽象基类
class Query {
public: // …
protected:
int _paren;
set *_solution;
vector _loc;
// …
};

NameQuery folk( “folk” );
NameQuery music = folk;

按成员初始化步骤:
1 编译器检查NameQuery是否定义了一个显式的拷贝构造函数实例答案是没有所
以编译器准备应用缺省的按成员初始化
2 编译器接下来检查NameQuery类是否含有基类子对象是的它含有Query基类子
对象
3 编译器检查Query基类是否定义了显式的拷贝构造函数实例答案也是没有所以编
译器准备应用缺省的按成员初始化
4 编译器检查Query类是否含有基类子对象没有
5 编译器以声明的顺序检查Query的每个非静态成员如果成员是非类对象如_paren
和_solution 则它用folk的成员值初始化music对象的成员如果成员是类对象如_loc
则它递归地应用步骤1 是的vector类定义了一个显式的拷贝构造函数实例该拷贝构造
函数被调用用folk._loc初始化music._loc
6 然后编译器按声明的顺序检查NameQuery类型的每个非静态成员string成员类对象
被识别出来它有一个显式的拷贝构造函数于是调用该拷贝构造函数用folk._name初始
化music._name

按成员赋值步骤:
按成员赋值与按成员初始化类似如果存在一个显式的拷贝赋值操作符则它被调用
用一个类对象向另一个类对象赋值否则应用缺省的按成员赋值
如果存在基类则首先对基类子对象按成员赋值如果基类提供了一个显式的拷贝赋值
操作符则调用它否则递归地对基类和基类子对象的成员应用缺省按成员赋值的动作
编译器按声明的顺序检查每个非静态数据成员如果它是非类类型则右边的实例被拷
贝到左边如果它是类类型并且该类定义了显式的拷贝赋值操作符则调用该操作符否
则递归地对基类和成员类对象的成员应用缺省按成员赋值的动作

多继承和虚拟继承
多继承和虚拟继承为了解决如下问题:
(1)单个基类的抽象度不够
(2)单个基类看起来很复杂不直观

1.多继承
例:
class Bear : public ZooAnimal { … };
class Panda : public Bear, public Endangered { … }; //只有当一个类的定义已经出现后它才能被列在多继承的基类表中
基类构造函数被调用的顺序以类派生表中声明的顺序为准,构造函数调用顺序不受基类在成员初始化表中是否存在以及被
列出的顺序的影响。析构函数调用顺序和构造函数相反。

单继承下,基类public和protected成员可以直接访问,就像他们是派生类成员一样,多继承也是一样,但实在多继承下,派生类可能会从基类中继承

多个同名成员,这种情况下,直接访问会产生二义性,在编译时刻会报错。
例:
extern void display( const Bear& );
extern void display( const Endangered&);

Panda ying_yang;
display( ying_yang ); // 错误: 二义性

例:
class Bear : public ZooAnimal {
public:
virtual ~Bear();
virtual ostream&print( ostream&) const;
virtual string isA() const;
// …
};
class Endangered {
public:
virtual ~Endangered();
virtual ostream&print( ostream&) const;
virtual void highlight() const;
// …
};

class Panda : public Bear, public Endangered
{
public:
virtual ~Panda();
virtual ostream&print( ostream&) const;
virtual void cuddle();
// …
};
当用Panda类对象的地址初始化或赋值Bear或ZooAnimal指针或引用时,Panda接口中“Panda特有部分”以及“Endangered部分”就都不能再被访问。
例:
Bear *pb = new Panda;
pb->print( cout ); // ok: Panda::print(ostream&)
pb->isA(); // ok: Bear::isA()
pb->cuddle(); // 错误: 不是Bear 接口的部分
pb->highlight(); // 错误: 不是Bear 接口的部分
delete pb; // ok: Panda::~Panda()

同理
Endangered *pe = new Panda;
pe->print( cout ); // ok: Panda::print(ostream&)
// 错误: 不是Endangered 的接口部分
pe->isA();
// 错误: 不是Endangered 的接口部分
pe->cuddle();
pe->highlight(); // ok: Endangered::highlight()
delete pe; // ok: Panda::~Panda()

无论删除对象所使用的指针类型是什么,虚拟析构函数的处理都是一致的,他与构造函数调用顺序相反。
例:
// ZooAnimal *pz = new Panda;
delete pz;
// Bear *pb = new Panda;
delete pb;
// Panda *pp = new Panda;
delete pp;
// Endangered *pe = new Panda;
delete pe;

通过多继承得到的派生类,它的按成员初始化和赋值与单继承的派生类相同。
例:
class Panda : public Bear, public Endangered
{ … };

Panda yin_yang;
Panda ling_ling = yin_yang;
调用了Bear拷贝构造函数但是因为Bear是从ZooAnimal派生来的所以在执行Bear
拷贝构造函数之前先调用ZooAnimal拷贝构造函数 然后再调用Endangered拷贝构造函
数以及执行Panda拷贝构造函数按成员赋值与此类似

2.public,private,protected继承
private继承的基类中得所有成员在子类中都变成private
public继承基类中成员类型限制不变
protected继承基类中得public成员都变成protected成员

3.继承下的类域
每个类都有自己的一个类域,如果一个名字在派生类类域中没有找到,则编译器会在外围类域中查找改名字定义
例:
class ZooAnimal {
public:
ostream &print( ostream&) const;
// 为了向外域暴露设为public
string is_a;
int ival;
private::
double dval;
};

class Bear : public ZooAnimal {
public:
ostream &print( ostream&) const;
int mumble( int );
// 为了向外域暴露设为public
string name;
int ival;
};

Bear bear;
bear.is_a;
bear.ival;
bear.ZooAnimal::ival; //访问基类中的ival
解析过程如下:
(1)先在Bear类域中查找is_a,没有找到,则进行第二步
(2)在ZooAnimal 中查找

例:
class Endangered {
public:
ostream&print( ostream&) const;
void highlight();
// …
};
class ZooAnimal {
public:
bool onExhibit() const;
// …
private:
bool highlight( int zoo_location );
// …
};
class Bear : public ZooAnimal {
public:
ostream&print( ostream&) const;
void dance( dance_type ) const;
// …
};

class Panda : public Bear, public Endangered {
public:
void cuddle() const;
// …
};

void Panda::mumble()
{
dance( Bear::macarena );
// …
}

int main()
{
Panda yin_yang;
// ok: Bear::dance()
yin_yang.dance( Bear::macarena );
// 错误: 二义
// Bear::print( ostream&) const
// Endangered::print( ostream&) const
Panda yin_yang;
yin_yang.print( cout );
}

inline ostream&
Panda::print( ostream &os ) const
{
Bear::print( os );
Endangered::print( os );
return os;
}

int main()
{
// ok
Panda yin_yang;
yin_yang.print( cout );
}

4.虚拟继承
虚拟继承:为了解决多继承中的二义性,引入虚拟继承
(1)虚拟基类声明
例:
// 关键字public 和virtual
// 的顺序不重要
class Bear : public virtual ZooAnimal { … };
class Raccoon : virtual public ZooAnimal { … };

(2)特殊的初始化语义
例:
#include
#include
class ZooAnimal;
extern ostream&
operator<<( ostream&, const ZooAnimal& );
class ZooAnimal {
public:
ZooAnimal( string name,
bool onExhibit, string fam_name )
: _name( name ),
_onExhibit( onExhibit), _fam_name( fam_name )
{}
virtual ~ZooAnimal();
virtual ostream& print( ostream& ) const;
string name() const { return _name; };
string family_name() const { return _fam_name; }
// …
protected:
bool _onExhibit;
string _name;
string _fam_name;
// …
};

class Bear : public virtual ZooAnimal {
public:
enum DanceType {
two_left_feet, macarena, fandango, waltz };
Bear( string name, bool onExhibit=true )
: ZooAnimal( name, onExhibit, “Bear” ),
_dance( two_left_feet ) {}
virtual ostream&print( ostream& ) const;
void dance( DanceType );
// …
protected:
DanceType _dance;
// …
};

class Raccoon : public virtual ZooAnimal {
public:
Raccoon( string name, bool onExhibit=true )
: ZooAnimal( name, onExhibit, “Raccoon” ),
_pettable( false )
{}
virtual ostream&print( ostream&) const;
bool pettable() const { return _pettable; }
void pettable( bool petval ) { _pettable = petval; }
// …
protected:
bool _pettable;
// …
};

class Panda : public Bear,
public Raccoon, public Endangered {
public:
Panda( string name, bool onExhibit=true );
virtual ostream& print( ostream& ) const;
bool sleeping() const { return _sleeping; }
void sleeping( bool newval ) { _sleeping = newval; }
// …
protected:
bool _sleeping;
// …
};

在非虚拟派生中,派生类只能显示初始化其直接基类,在虚拟派生中,虚基类的初始化只能放在最终派生类中进行。
即:
Panda::Panda( string name, bool onExhibit=true )
: ZooAnimal( name, onExhibit, “Panda” ), Bear( name, onExhibit ), Raccoon( name, onExhibit ), Endangered(

Endangered::environment,
Endangered::critical )
_sleeping( false ) {}

如果Panda构造函数没有显示为虚基类ZooAnimal显示指定构造函数,则会调用ZooAnimal的缺省构造函数,如果没有缺省构造函数,则编译器报错。
中间派生类对于虚拟基类构造函数调用被抑制。

(3)构造函数和析构函数顺序
无论虚拟基类出现在继承层次的哪个位置上,他们都是在非虚拟基类之前被构造,其余的构造函数顺序依然按照继承表中的声明顺序来进行。

(4)虚拟基类成员的可视性
在非虚拟派生下的解析引用过程中每个继承得到的实例都具有同样的权值所以未限
定修饰的引用将导致编译时刻二义性错误

在虚拟派生下对于虚拟基类成员的继承比该成员后来重新定义的实例的权值小
继承得到的Bear的onExhibit 实例比通过Raccoon继承得到的ZooAnimal实例优先

如果在同一派生级别上有两个或多个基类重新定义了一个虚拟基类成员则在派生类中
它们有相同的优先级

(5)多继承及虚拟继承实例
。。。。。。。。

C++中继承的用法
1.RRTI(运行时刻类型识别)
RTTI 运行时刻类型识别允许用指向基类的指针或引用来操纵对象的程序能够获取到这些指针或引用所指对象的实际派生类型
为了支持RTTI提供了两个操作符
1 dynamic_cast操作符它允许在运行时刻进行类型转换从而使程序能够在一个类层
次结构中安全地转换类型把基类指针转换成派生类指针或把指向基类的左值转换成派生
类的引用当然只有在保证转换能够成功的情况下才可以
2 typeid操作符它指出指针或引用指向的对象的实际派生类型

对于带有虚拟函数的类而言,RTTI操作符是运行时刻的事件,对于没有虚拟函数的类而言,RTTI是编译时刻事件。

(1) dynamic_cast操作符
dynamic_cast操作符可以用来把一个类类型对象的指针转换成同一类层次结构中的其他类的指针,同时也可以用它把一个类类型对象的左值转换成同一

类层次结构中其他类的引用。与C++支持的其他强制转换不同的是,dynamic_cast是在运行时刻执行的,如果指针或左值
操作数不能被转换成目标类型,则dynamic_cast将失败。如果针对指针类型的dynamic_cast 失败,则dynamic_cast的结果是0 。
如果针对引用类型的dynamic_cast失败,则dynamic_cast 会抛出一个异常。
例:
class employee {
public:
virtual int salary();
};
class manager : public employee {
public:
int salary();
};
class programmer : public employee {
public:
int salary();
int bonus();
};
void company::payroll( employee *pe ) {
// 使用pe->salary()
}

void company::payroll( employee *pe )
{
programmer pm = dynamic_cast< programmer >( pe );
// 如果pe 指向programmer 类型的一个对象
// 则dynamic_cast 成功
// 并且pm 指向programmer 对象的开始
if ( pm ) {
// 用pm 调用programmer::bonus()
}
// 如果pe 不是指向programmmer 类型的一个对象
// 则dynamic_cast 失败
// 并且pm 的值为0
else {
// 使用employee 的成员函数
}
}

void company::payroll( employee &re )
{
try {
programmer &rm = dynamic_cast< programmer & >( re );
// 用rm 调用programmer::bonus()
}
catch ( std::bad_cast ) {
// 使用employee 的成员函数
}
}

(2)typeid操作符
typeid操作符可用于获取一个表达式的类型。如果表达式是一个类类型,并且含有一个或多个虚拟成员函数,则答案会不同于表达式本身
的类型。
例:
#include <type_info>
programmer pobj;
employee &re = pobj;
// 我们将在下面关于type_info 的小节中
// 看到name()
// name() 返回C 风格字符串: “programmer”
cout << typeid( re ).name() << endl;

re的类型是employee,因为re是带有虚拟函数的类类型的引用,所以typeid操作符的结果指出底层对象的类型是programmer类型而不是操作数
的类型employee)。

用处:
它用在高级的系统程序设计开发中例如设计构造调试
器或用来处理从数据库获取到的永久性对象在这样的系统中在一个调试会话中或者
向一个数据库存贮或获取对象期间当程序通过基类指针或引用操纵一个对象时程序需要
找到被操纵的对象的实际类型以便正确地列出对象的属性为了找到对象的实际类型我
们可以使用typeid

typeid操作符必须与表达式或类型名一起使用。内置类型的表达式和常量可以被用作typeid的操作数。例:
int iobj;
cout << typeid( iobj ).name() << endl; // 打印: int
cout << typeid( 8.16 ).name() << endl; // 打印: double

例:
class Base { /* 没有虚拟函数*/ };
class Derived : public Base { /* 没有虚拟函数*/ };
Derived dobj;
Base *pb = &dobj;
cout << typeid( pb ).name() << endl; // 打印: Base
typeid操作符的操作数是Base类型的,即表达式
pb的类型,因为Base不是一个带有虚拟函数的类类型,所以typeid的结果指出表达式的类型是Base 。

例:
#include <type_info>
employee pe = new manager;
employee& re = pe;
if ( typeid( pe ) == typeid( employee
) ) // true
// do something
/

if ( typeid( pe ) == typeid( manager* ) ) // false
if ( typeid( pe ) == typeid( employee ) ) // false
if ( typeid( pe ) == typeid( manager ) ) // false
*/

typeid操作符实际返回的是类型为type_info的类对象。type_info类类型被定义在<type_info> 中。

(3)type_info类
class type_info {
// 依赖于编译器的实现
private:
type_info( const type_info& );
type_info& operator= ( const type_info& );
public:
virtual ~type_info();
int operator==( const type_info& ) const;
int operator!=( const type_info& ) const;
const char * name() const;
};

RRTI支持是与编译器实现相关的,有些编译器可能为类type_info提供了其他成员函数,而在上面没有被列出来,具体可以查看编译器手册来找到确切

的RTTI支持。
编译器用来扩展RTTI的一种常见技术是,从从type_info派生的类类型增加额外的信息,因为type_info类含有一个虚拟析构函数,所以dynamic_cast操

作符可以被用来判断是否有可用的特殊类型的RTTI扩展支持。extended_type_info是一个从type_info派生的类通过使用dynamic_cast 一个程序可以发

现typeid操作符返回的type_info对象是否为extended_type_info类型在程序中是否可以使用额外的RTTI支持。
例:
#include
// typeinfo 头文件包含extended_type_info 的定义
typedef extended_type_info eti;
void func( employee* p )
{
// 从type_info* 到extended_type_info* 向下转换
if ( eti *eti_p = dynamic_cast<eti *>( &typeid( *p ) ) )
{
// 如果dynamic_cast 成功
// 通过eti_p 使用extended_type_info 信息
}
else
{
// 如果dynamic_cast 失败
// 使用标准type_info 信息
}
}

2.异常和继承
例:
class Excp { … };
class stackExcp : public Excp { … };
class popOnEmpty : public stackExcp { … };
class pushOnFull : public stackExcp { … };
class mathExcp : public Excp { … };
class zeroOp : public mathExcp { … };
class divideByZero : public mathExcp { … };

void iStack::push( int value )
{
if ( full() )
// value 被存储在异常对象中.
throw pushOnFull( value );
// …
}
throw 表达式发生如下几步操作:
(1)throw表达式通过调用该类的构造函数创建一个临时临时类对象
(2)创建一个pushOnFull类型的异常对象,并传递给异常处理代码,,该异常对象是第一步临时对象的拷贝。他通过调用pushOnFull的拷贝构造函数

创建。
(3)在throw表达式结束之后,第一步创建的临时对象被销毁。

例:
void iStack::push( int value ) {
if ( full() ) {
pushOnFull except( value );
stackExcp *pse = &except;
throw *pse; // 异常对象的类型为stackExcp
}
// …
}

如下情形发生,throw表达式出错:
(1)pushOnFull没有接收到int类型实参
(2)pushOnFull拷贝构造函数或者析构函数不可访问
(3)pushOnFull是抽象基类,因为程序不能创建抽象类类型对象

例:
int main( ) {
try {
// …
}
catch ( pushOnFull ) {
// 处理pushOnFull 异常
}
catch ( Excp ) {
// 处理popOnEmpty 和pushOnFull 异常
}

}
当异常被组织成层次结构时,类类型异常可能会被该类类型的公有基类的catch子句捕获到。派生类类型的catch子句必须先出现,这确保了再没有其他

合适合适catch时,才会进入基类类型catch子句。

例:
void calculate( int parm ) {
try {
mathFunc( parm ); // 抛出divideByZero 异常
}
catch ( mathExcp mExcp ) {
// 部分地处理当前异常
// 并重新抛出该异常对象
throw;
}
}
throw表达式抛出的是原来的异常对象,即divideByZero ,而不是mathExcp 异常。

例:
class pushOnFull {
public:
pushOnFull( int i ) : _value( i ) { }
int value() { return _value; }
~pushOnFull(); // 新声明的析构函数
private:
int _value;
};
当用throw抛出异常对象时,该异常对象是在最后一次catch子句中调用析构函数,因为异常可能会被重新抛出。

例:
catch ( Excp &eObj )
{
// 错误: Excp 没有成员函数value()
cerr << “trying to push the value " << eObj.value()
<< " on a full stack\n”;
}

// 定义了虚拟函数的新类定义
class Excp {
public:
virtual void print() {
cerr << “An exception has occurred”
<< endl;
}
};
class stackExcp : public Excp { };
class pushOnFull : public stackExcp {
public:
virtual void print() {
cerr << “trying to push the value " << _value
<< " on a full stack\n”;
}
// …
};

int main( ) {
try {
// iStack::push() throws a pushOnFull exception
} catch ( Excp eObj ) {
eobj.print(); // 调用虚拟函数
// 喔! 调用基类实例
}
}
打印:
An exception has occurred

在进入catch字句的时候,会进行基类子对象对基类对象的拷贝,所以打印的是父类对象的print。如果想引用派生类对象的虚拟函数,则异常声明必须

是一个指针或一个引用。
例:
int main( ) {
try {
// iStack::push() 抛出一个pushOnFull 异常
}
catch ( Excp &eObj ) {
eobj.print(); // 调用虚拟函数pushOnFull::print()
}
}

例:
class PTR {
public:
PTR() { ptr = new int[ chunk ]; }
~PTR() { delete[] ptr; }
private:
int *ptr;
};

void manip( int parm ) {
PTR localPtr;
// …
mathFunc( parm ); // 抛出divideByZero 异常
// …
}
由于mathFunc抛出异常,且未在try语句中,栈展开会搜寻调用manip之前是否包含catch语句,在这之前会把局部对象localPtr; 的析构函数和内存释

放掉。

class bad_alloc : public exception {
// …
public:
bad_alloc() throw();
bad_alloc( const bad_alloc & ) throw();
bad_alloc & operator=( const bad_alloc & ) throw();
virtual ~bad_alloc() throw();
virtual const char* what() const throw();
};

#include
// defines class overflow_error
class transport {
// …
public:
double cost( double, double ) throw ( overflow_error );
// …
};
// 错误: 异常规范不同于类成员表中的声明
double transport::cost( double rate, double distance ) { }
类中声明的成员函数和类外定义的成员函数异常规范必须要保持一致。

例:
class Base {
public:
virtual double f1( double ) throw ();
virtual int f2( int ) throw ( int );
virtual string f3( ) throw ( int, string );
// …
};
class Derived : public Base {
public:
// 错误: 异常规范没有base::f1() 的严格
double f1( double ) throw ( string );
// ok: 与base::f2() 相同的异常规范
int f2( int ) throw ( int );
// ok: 派生f3() 更严格
string f3( ) throw ( int );
// …
};

// 保证不会抛出异常
void compute( Base *pb ) throw()
{
try {
pb->f3( ); // 可能抛出int 或者string 类型的异常
}
// 处理来自Base::f3() 的异常
catch ( const string & ) { }
catch ( int ) { }
}

基类中虚拟的异常规范,可以与派生类中该写的异常规范不同,但是,派生类中的虚拟函数的异常规范必须与基类虚拟函数的异常规范一样或者更严格

。因为这样可以确保通过基类指针引用派生类对象时,不会违背基类成员函数的异常规范。

例:
class stackExcp : public Excp { };
class popOnEmpty : public stackExcp { };
class pushOnFull : public stackExcp { };
void stackManip() throw( stackExcp )
{
// …
}
stackManip可以抛出stackExcp 异常,也可以抛出公有派生类类型的异常。

例:
inline Account::
Account( const char* name, double opening_bal )
: _balance( opening_bal - ServiceCharge() )
{
_name = new char[ strlen(name)+1 ];
strcpy( _name, name );
_acct_nmbr = get_unique_acct_nmbr();
}

inline Account::
Account( const char* name, double opening_bal )
try
: _balance( opening_bal - serviceCharge() )
{
_name = new char[ strlen(name)+1 ];
strcpy( _name, name );
_acct_nmbr = get_unique_acct_nmbr();
}
catch ( … )
{
// 特殊处理
// 现在能够捕获来自ServiceCharge() 的异常了
}

C++标准库中定义了所有异常的基类exception,这个类被定义在头文件中,接口如下:
namespace std {
class exception {
public:
exception() throw();
exception( const exception & ) throw();
exception& operator=( const exception& ) throw();
virtual ~exception() throw();
virtual const char* what() const throw();
};
}
错误被分为两大类:逻辑错误和运行时刻错误。
逻辑错误由于程序内部逻辑导致的错误,逻辑错误偶可以避免,程序之前执行前可以被检测到。

C++标准库中定义的逻辑错误如下:
namespace std {
class logic_error : public exception {
public:
explicit logic_error( const string &what_arg );
};
class invalid_argument : public logic_error {
public:
explicit invalid_argument( const string &what_arg );
};
class out_of_range : public logic_error {
public:
explicit out_of_range( const string &what_arg );
};
class length_error : public logic_error {
public:
explicit length_error( const string &what_arg );
};
class domain_error : public logic_error {
public:
explicit domain_error( const string &what_arg );
};
}

C++标准库中定义的运行时刻错误如下:
namespace std {
class runtime_error : public exception {
public:
explicit runtime_error( const string &what_arg );
};
class range_error : public runtime_error {
public:
explicit range_error( const string &what_arg );
};
class overflow_error : public runtime_error {
public:
explicit overflow_error( const string &what_arg );
};
class underflow_error : public runtime_error {
public:
explicit underflow_error( const string &what_arg );
};
}

例:
#include
#include
template
class Array {
public:
// …
elemType& operator[]( int ix ) const
{
if ( ix < 0 || ix >= _size )
{
string eObj =
“out_of_range error in Array::operator”;
throw out_of_range( eObj );
}
return _ia[ix];
}
// …
private:
int _size;
elemType *_ia;
};

int main()
{
try {
// main() 函数同16.2 节中定义
}
catch ( const out_of_range &excp ) {
// 打印:
// out_of_range error in Array::operator
cerr << excp.what() << “\n”;
return -1;
}
}

使用预定义的异常类,程序需要包含头文件

iostream库
iostream是利用多继承和逆袭记成实现的面向对象层次结构,是作为C++标准库的一个组件而提供的。它的内置数据类型为输入输出提供了支持,同时

也支持文件的输入输出。类的设计者还可以通过扩展iostream库,来读写新的类型。

头文件
#include
输入输出操作是由istream 输入流和ostream 输出流类提供的第三个类iostream 类同时从istream和ostream派生允许双向输入输出,这个库定义了下

列三个标准流对象:
1 cin 发音为see-in 代表标准输入standard input 的istream类对象一般地cin
使我们能够从用户终端读入数据
2 cout 发音为see-out 代表标准输出standard output 的ostream类对象一般地
cout使我们能够向用户终端写数据
3 cerr 发音为see-err 代表标准错误standard error 的ostream类对象cerr是导出
程序错误消息的地方

输出主要由重载的左移操作符<< 来完成类似地输入主要由重载的右移操作符>>来完成
例:
#include
#include
int main() {
string in_string;
// 向用户终端写字符串
cout << "Please enter your name: ";
// 把用户输入的读取到in_string 中
cin >> in_string;
if ( in_string.empty() )
// 产生一个错误消息输出到用户终端
cerr << “error: input string is empty!\n”;
else cout << "hello, " << in_string << “!\n”;
}

除了对用户终端进行读写操作之外,iostream库还支持对文件的读写。下列三种类型提供了对文件支持:
1 ifstream 从istream派生把一个文件绑到程序上用来输入
2 ofstream 从ostream派生把一个文件绑到程序上用来输出
3 fstream 从iostream派生把一个文件绑到程序上用来输入和输出

头文件#include ,fstream头文件中也包含了iostream头文件,所以只需要在程序中引入iostream即可。

例:
#include
#include
#include
#include
int main()
{
string ifile;
cout << "Please enter file to sort: ";
cin >> ifile;
// 构造一个ifstream 输入文件对象
ifstream infile( ifile.c_str() );
if( ! infile ) {
cerr << "error: unable to open input file: "
<< ifile << endl;
return -1;
}
string ofile = ifile + “.sort”;
// 构造一个ofstream 输出文件对象
ofstream outfile( ofile.c_str() );
if( !outfile ) {
cerr << "error: unable to open output file: "
<< ofile << endl;
return -2;
}
string buffer;
vector< string, allocator > text;
int cnt = 1;
while ( infile >> buffer ) {
text.push_back( buffer );
cout << buffer << ( cnt++ % 8 ? " " : “\n” );
}
sort( text.begin(), text.end() );
// ok: 把排序后的词打印到outfile
vector<string, allocator>::iterator iter = text.begin();
for ( cnt = 1; iter != text.end(); ++iter, ++cnt )
outfile << *iter
<< (cnt%8 ? " " : “\n” );
return 0;
}

iostream库还支持内存输入输出,当流被附着在程序内存上的一个字符串时,我们可以用iostream输入输出操作符来对她进行读写。可以通过定义下列

三种类类型中的一个实例来定义一个iostream字符串对象:
1 istringstream 从istream派生从一个字符串中读取数据
2 ostringstream 从ostream派生写入到一个字符串中
3 stringstream 从iostream派生从字符串中读取或者写入到字符串中

头文件#include 。sstream头文件包含了iostream头文件因此我们无需同时包含这两个头文件

(1)输出操作符<<
例:
#include
int main() {
cout << “gossipaceous Anna Livia\n”;
}

#include
#include <string.h>
int main()
{
cout << “The length of “ulysses” is:\t”;
cout << strlen( “ulysses” );
cout << ‘\n’;
cout << “The size of “ulysses” is:\t”;
cout << sizeof( “ulysses” );
cout << endl;
}

endl是一个ostream操纵符manipulator ,他把一个换行符插入到输出流中,然后再刷新ostream缓冲区。

例:
#include
const char str = “vermeer”;
int main()
{
const char pstr = str;
cout << "The address of pstr is: "
<< pstr << endl;
}
输出:
The address of pstr is: vermeer
要想输出地址,则使用如下表达式:
cout << static_cast<void
>(const_cast<char
>(pstr))
输出:
The address of pstr is: 0x116e8

例:
#include
inline void
max_out( int val1, int val2 ) {
cout << ( val1 > val2 ) ? val1 : val2;
}
int main()
{
int ix = 10, jx = 20;
cout << "The larger of " << ix;
cout << ", " << jx << " is ";
max_out( ix, jx );
cout << endl;
}
程序输出:
The larger of 10, 20 is 0
由于<<操作符的优先级大于条件操作符,所以先执行cout << ( val1 > val2 ) 。

例:
#include
#include
#include
string pooh_pals[] = {
“Tigger”, “Piglet”, “Eeyore”, “Rabbit”
};
int main()
{
vector ppals( pooh_pals, pooh_pals+4 );
vector::iterator iter = ppals.begin();
vector::iterator iter_end = ppals.end();
cout << "These are Pooh’s pals: ";
for ( ; iter != iter_end; iter++ )
cout << *iter << " ";
cout << endl;
}

#include
#include
#include
#include
string pooh_pals[] = {
“Tigger”, “Piglet”, “Eeyore”, “Rabbit”
};
int main()
{
vector ppals( pooh_pals, pooh_pals+4 );
vector::iterator iter = ppals.begin();
vector::iterator iter_end = ppals.end();
cout << "These are Pooh’s pals: ";
// 把每个元素拷贝到cout …
ostream_iterator< string > output( cout, " " );
copy( iter, iter_end, output );
cout << endl;
}

输出:These are Pooh’s pals: Tigger Piglet Eeyore Rabbit

(2)输入
例:
#include
#include
int main()
{
vector ivec;
int ival;
while ( cin >> ival )
ivec.push_back( ival );
// …
}

#include
#include
int main()
{
int item_number;
string item_name;
double item_price;
cout << "Please enter the item_number, item_name, and price: "
<< endl;
cin >> item_number;
cin >> item_name;
cin >> item_price;
cout << “The values entered are: item# "
<< item_number << " "
<< item_name << " @$”
<< item_price << endl;
}

例:
#include
int main()
{
char ch;
// 获取每个字符包括空白字符
while ( cin.get( ch ))
cout.put( ch );
// …
}

例:
#include
#include
#include
#include
int main()
{
istream_iterator< string > in( cin ), eos ;
vector< string > text ;
// 从标准输入向text 拷贝值
copy( in , eos , back_inserter( text ) ) ;
sort( text.begin() , text.end() ) ;
// 删除所有重复的值
vector< string >::iterator it ;
it = unique( text.begin() , text.end() ) ;
text.erase( it , text.end() ) ;
// 显示结果vector
int line_cnt = 1 ;
for ( vector< string >::iterator iter = text.begin();
iter != text.end() ; ++iter , ++line_cnt )
cout << *iter
<< ( line_cnt % 9 ? " " : “\n” ) ;
cout << endl;
}

例:
get(char *sink, streamsize size, char delimiter=’\n’)

int main()
{
const int max_line = 1024;
char line[ max_line ];
while ( cin.get( line, max_line ))
{
// 最大读取数量max_line - 1, 也可以为null
int get_count = cin.gcount();
cout << "characters actually read: "
<< get_count << endl;
// 处理每一行
// 如果遇到换行符
// 在读下一行之前去掉它
if ( get_count & max_line-1 )
cin.ignore();
}
}

characters actually read: 52
characters actually read: 60
characters actually read: 66
characters actually read: 63
characters actually read: 61
characters actually read: 43

getline(char *sink, streamsize size, char delimiter=’\n’)
#include
int main()
{
const lineSize = 1024;
int lcnt = 0; // 读入多少行
int max = -1; // 最长行的长度
char inBuf[ lineSize ];
// 读取1024 个字符或者遇到换行符
while (cin.getline( inBuf, lineSize ))
{
// 实际读入多少字符
int readin = cin.gcount();
// 统计: 行数最长行
++lcnt;
if ( readin > max )
max = readin;
cout << “Line #” << lcnt
<< "\tChars read: " << readin << endl;
cout.write( inBuf, readin).put(’\n’).put(’\n’);
}
cout << "Total lines read: " << lcnt << endl;
cout << "Longest line read: " << max << endl;
}

// 将字符放回iostream
putback( char c );
// 往回重置下一个istream 项
unget();
// 返回下一个字符或EOF
// 但不要提取出来
peek();

char ch, next, lookahead;
while ( cin.get( ch ))
{
switch (ch) {
case ‘/’:
// 是注释行吗? 用peek() 看一看:
// 是的? ignore() 余下的行
next = cin.peek();
if ( next == ‘/’ )
cin.ignore( lineSize, ‘\n’ );
break;
case ‘>’:
// 查找>>=
next = cin.peek();
if ( next == ‘>’ ) {
lookahead = cin.get();
next = cin.peek();
if ( next != ‘=’ )
cin.putback( lookahead ); }
// …
}

(4)重载操作符<<
#include
#include
#include
#include “Location.h”
class WordCount {
friend ostream& operator<<(ostream&, const WordCount&);
public:
WordCount(){}
WordCount( const string &word ) : _word(word){}
WordCount( const string &word, int ln, int col )
: _word( word ){ insert_location( ln, col ); }
string word() const { return _word; }
int occurs() const { return _occurList.size(); }
void found( int ln, int col )
{ insert_location( ln, col ); }
private:
void insert_location( int ln, int col )
{ _occurList.push_back( Location( ln, col )); }
string _word;
vector< Location > _occurList;
};

ostream&
operator <<( ostream& os, const WordCount& wd )
{
os << “<” << wd._occurList.size() << "> "
<< wd._word << endl;
int cnt = 0, onLine = 6;
vector< Location >::const_iterator first =
wd._occurList.begin();
vector< Location >::const_iterator last =
wd._occurList.end();
for ( ; first != last, ++first )
{
// os << Location
os << *first << " ";
// 格式化: 6 个一行
if ( ++cnt == onLine )
{ os << “\n”; cnt = 0; }
}
return os;
}

(5)重载操作符>>
例:
#include
#include “WordCount.h”
/* 必须修改WordCount, 指定输入操作符为友元
class WordCount {
friend ostream& operator<<( ostream&, const WordCount& );
friend istream& operator>>( istream&, WordCount& );
/
istream&
operator>>( istream &is, WordCount &wd )
{
/
WordCount 对象被读入的格式:

  • <2> string
  • <7,3> <12,36>
    /
    int ch;
    /
    读入小于符号, 如果不存在
  • 则设置istream 为失败状态并退出
    */
    if ((ch = is.get()) != ‘<’ )
    {
    is.setstate( ios_base::failbit );
    return is;
    }
    // 读入多少个
    int occurs;
    is >> occurs;
    // 取>; 不检查错误
    while ( is && (ch = is.get()) != ‘>’ ) ;
    is >> wd._word;
    // 读入位置
    // 每个位置的格式: < line, col >
    for ( int ix = 0; ix < occurs; ++ix )
    {
    int line, col;
    // 提取值
    while (is && (ch = is.get())!= ‘<’ ) ;
    is >> line;
    while (is && (ch = is.get())!= ‘,’ ) ;
    is >> col;
    while (is && (ch = is.get())!= ‘>’ ) ;
    wd.occurList.push_back( Location( line, col ));
    }
    return is;
    }

(6)文件输入输出
例:
#include
ofstream outfile( “copy.out”, ios_base::out ); //输出模式ios_base::out 或附加模式ios_base::app
// 缺省情况下, 以输出模式打开
ofstream outfile2( “copy.out” );

如果在输出模式下打开已经存在的文件则所有存储在该文件中的数据都将被丢弃。如果我们希望增加而不是替换现有文件中的数据则应该以附加模式打

开文件。在这两种模式下如果文件不存在程序都会创建一个新文件。

例:
#include
int main()
{
// 打开文件copy.out 用于输出
ofstream outFile( “copy.out” );
if ( ! outFile ) {
cerr << “Cannot open “copy.out” for output\n”;
return -1 ;
}
char ch;
while ( cin.get( ch ) )
outFile.put( ch );
}

例:
#include
int main()
{
cout << "filename: ";
string file_name;
cin >> file_name;
// 打开文件copy.out 用于输入
ifstream inFile( file_name.c_str() );
if ( !inFile ) {
cerr << “unable to open input file: "
<< file_name << " – bailing out!\n”;
return -1;
}
char ch;
while ( inFile.get( ch ))
cout.put( ch );
}

fstream io( “word.out”, ios_base::in|ios_base::app );

例:
#include
#include “WordCount.h”
int main()
{
WordCount wd;
fstream file;
file.open( “word.out”, ios_base::in );
file >> wd;
file.close();
cout << "Read in: " << wd << endl;
// ios_base::out 将丢弃当前的数据
file.open( “word.out”, ios_base::app );
file << endl << wd << endl;
file.close();
}

// 设置到文件中固定的位置上
seekg( pos_type current_position );
// 从当前位置向某个方向进行偏移
seekg( off_type offset_position, ios_base::seekdir dir );
1 ios_base::beg 文件的开始
2 ios_base::cur 文件的当前位置
3 ios_base::end 文件的结尾

// 标记出当前位置
ios_base::pos_type mark = writeFile.tellp();

inOut是附在文件copy.out上的fstream类对象它以输入和附加append 两种模式打
开以附加模式打开的文件将把数据写到文件尾部
inOut.get( ch )
inOut.seekg( 0 )

// 标记出当前位置
ios_base::pos_type mark = inOut.tellg();
inOut << cnt << sp;
inOut.seekg( mark ); // 恢复位置
inOut.clear(); // 清除状态标记(inOut处于遇到文件结束符的状态,只要inOut处于这种状态就不能再执行输入和输出操作)

例:
#include
#include
int main()
{
fstream inOut( “copy.out”, ios_base::in|ios_base::app );
int cnt=0;
char ch;
inOut.seekg(0);
while ( inOut.get( ch ) )
{
cout.put( ch );
cnt++;
if ( ch == ‘\n’ )
{
// 标记当前位置
ios_base::pos_type mark = inOut.tellg();
inOut << cnt << ’ ';
inOut.seekg( mark ); // 恢复位置
}
}
inOut.clear();
inOut << cnt << endl;
cout << “[ " << cnt << " ]\n”;
return 0;
}

(7)string流
iostream库支持在string对象上的内存操作ostringstream类向一个string插入字符
istringstream类从一个string对象读取字符而stringstream类可以用来支持读和写两种操作
为了使用string流字符串流 我们必须包含相关的头文件
#include

例:
#include
#include
#include
string read_file_into_string()
{
ifstream ifile( “alice_emma” );
ostringstream buf;
char ch;
while ( buf && ifile.get( ch ))
buf.put( ch );
return buf.str();
}

#include
#include
#include

int main()
{
int ival = 1024; int *pival = &ival;
double dval = 3.14159; double *pdval = &dval;
// 创建一个字符串存储每个值并
// 用空格作为分割
format_string << ival << " " << pival << " "
<< dval << " " << pdval << endl;
// 提取出被存储起来的ascii 值
// 把它们依次放在四个对象中
istringstream input_istring( format_string.str() );
input_istring >> ival >> pival

dval >> pdval;
}

(8)格式状态
每一个iostream库对象都维护一个格式状态,他控制格式化操作细节,比如输出整型值,十六进制,八进制等等。
例:
int main()
{
bool illustrate = true;
cout << "bool object illustrate: "
<< illustrate
<< "\nwith boolalpha applied: "
<< boolalpha << illustrate << ‘\n’;
// …
}

cout << boolalpha // 设置cout 的内部状态
<< illustrate
<< noboolalpha // 解除cout 内部状态

例:
#include
int main()
{
int ival = 16;
double dval = 16.0;
cout << "ival: " << ival
<< " oct set: " << oct << ival << “\n”;
cout << "dval: " << dval
<< " hex set: " << hex << dval << “\n”;
cout << "ival: " << ival
<< " dec set: " << dec << ival << “\n”;
}
输出:
ival: 16 oct set: 20
dval: 16 hex set: 16
ival: 10 dec set: 16

纵符showbase可以让一个整数值在输出时指明它的
基数形式如下
1 0x开头表明是十六进制数如果希望显示为大写字母则可以应用uppercase操作符
为了转回小写的x 我们可以应用nouppercase操作符
2 以0开头表示八进制数
3 没有任何前导字符表示十进制数
例:
#include
int main()
{
int ival = 16;
double dval = 16.0;
cout << showbase;
cout << "ival: " << ival
<< " oct set: " << oct << ival << “\n”;
cout << "dval: " << dval
<< " hex set: " << hex << dval << “\n”;
cout << "ival: " << ival << " dec set: "
<< dec << ival << “\n”;
cout << noshowbase;
}
输出:
ival: 16 oct set: 020
dval: 16 hex set: 16
ival: 0x10 dec set: 16

缺省情况下浮点值有6位的精度这个值可以用成员函数precision int 或流操作符
setprecision()来修改若使用后者则必须包含iomanip头文件 precision()返回当前的精
度值例如
例:
#include
#include
#include <math.h>
int main()
{
cout << "Precision: "
<< cout.precision() << endl
<< sqrt(2.0) << endl;
cout.precision(12);
cout << "\nPrecision: "
<< cout.precision() << endl
<< sqrt(2.0) << endl;
cout << "\nPrecision: " << setprecision(3)
<< cout.precision() << endl
<< sqrt(2.0) << endl;
return 0;
}

setprecision()的两个更深入的方面 整数值不受影响 浮
点值被四舍五入而不是被截取因此当精度为4时3.14159变成3.142 精度为3时变成3.14

例:
#include
#include
int main()
{
int ival = 16;
double dval = 3.14159;
cout << "ival: " << setw(12) << ival << ‘\n’
<< "dval: " << setw(12) << dval << ‘\n’;
}
输出:
ival: 16
dval: 3.14159

cout << setw(6) << setfill(’%’) << 100 << endl;
输出:
%%%100

所有操作符如下:
操 作 符 含 义
boolalpha 把true和false表示为字符串
*noboolalpha 把true和false表示为0 1
showbase 产生前缀指示数值的进制基数
*noshowbase 不产生进制基数前缀
showpoint 总是显示小数点
*noshowpoint 只有当小数部分存在时才显示小数点
Showpos 在非负数值中显示+
*noshowpos 在非负数值中不显示+
*skipws 输入操作符跳过空白字符
noskipws 输入操作符不跳过空白字符
uppercase 在十六进制下显示0X 科学计数法中显示E
*nouppercase 在十六进制下显示0x 科学计数法中显示e
*dec 以十进制显示
hex 以十六进制显示
oct 以八进制显示
left 将填充字符加到数值的右边
right 将填充字符加到数值的左边
Internal 将填充字符加到符号和数值的中间
*fixed 以小数形式显示浮点数
scientific 以科学计数法形式显示浮点数
flush 刷新ostream缓冲区
ends 插入空字符然后刷新ostream缓冲区
endl 插入换行符然后刷新ostream缓冲区
ws 吃掉 空白字符
// 以下这些要求#include
setfill(ch) 用ch填充空白字符
setprecision(n) 将浮点精度设置为n
setw(w) 按照w个字符来读或者写数值
setbase(b) 以进制基数b输出整数值

下面是我的个人公众号,喜欢linux c,c++,嵌入式的欢迎加入:
在这里插入图片描述

这篇关于C++速学仓促笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/742283

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现