C++ Primer笔记一、二《C++概述》《基本语言》

来源:互联网 发布:科研民工 知乎 编辑:程序博客网 时间:2024/05/01 04:01

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

为了打开一个文件供输入我们必须声明一个ifstream 类型的对象

ifstream infile( "name of file" );

if ( ! infile )

cerr << "Sorry! We were unable to open the file!/n";

 

iostream是输入输出流库标准文件。注意它没有后缀它包含cout 的信息这对我们

的程序是必需的。#include 是预处理器指示符preprocessor directive 它把iostream 的内

容读入我们的文本文件中。

在C++标准库中定义的名字如cout 不能在程序中直接使用除非在预处理器指示符

#include <iostream>

 

using namespace std;

这条语句被称作using 指示符。using directive C++标准库中的名字都是在一个称作

std 的名字空间中声明的这些名字在我们的程序文本文件中是不可见的,除非我们显式地使

它们可见。using 指示符告诉编译器要使用在名字空间std 中声明的名字。

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

在C++中,指针的主要用处是管理和操纵动态分配的内存。

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

C++预定义了一组数值数据类型可以用来表示整数浮点数和单个字符此外还预

定义了用来表示字符串的字符数组

字符型char 通常用来表示单个字符和小整数它可以用一个机器字节来表示

整型int 短整型short 长整型long 它们分别代表不同长度的整数值典型情况

下short 以半个字表示int 以一个机器字表示而long 为一个或两个机器字在

32 位机器中int 和long 通常长度相同

浮点型float 双精度double 和长双精度long double 分别表示单精度浮点数双

精度浮点数和扩展精度的浮点数值典型情况下float 为一个字double 是两个字

long double 为三个或四个字.

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

C++支持两种形式的初始化第一种形式是使用赋值操作符的显式语法形式

int ival = 1024;

string project = "Fantasia 2000";

在隐式形式中初始值被放在括号中

int ival( 1024 );

string project( "Fantasia 2001" );

在这两种情况中ival 都被初始化为1024 project 的初始值为"Fantasia 2000"

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

如果我们要做的仅仅是持有地址值可能是把一个地址同另一个地址作比较

那么指针的实际类型就不重要了C++提供了一种特殊的指针类型来支持这种需求空

void* 类型指针它可以被任何数据指针类型的地址值赋值函数指针不能赋值给它

// ok: void* 可以持有任何指针类型的地址值

void *pv = pi;

pv = pd;

void*表明相关的值是个地址但该地址的对象类型不知道我们不能够操作空类型指针

所指向的对象只能传送该地址值或将它与其他地址值作比较。

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

C 风格字符串的长度可以为0 因而被视为空串有两种方式字符指针被置为0 因

而它不指向任何对象或者指针已经被设置但是它指向的数组只包含一个空字符如

// pc1 不指向任何一个数组对象

char *pc1 = 0;

// pc2 指向空字符

const char *pc2 = "";

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

计算st的长度

#include <iostream>

const char *st = "The expense of spirit/n";

int main()

{

int len = 0;

while ( *st++ ) ++len;

st = st - len-1;

std::cout << len << ": " << st;

return 0;

}

22: The expense of spirit

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

const char *pc = ", ";

string s1( "hello" );

string s2( "world" );

string s3 = s1 + pc + s2 + "/n";

这种连接策略比较受欢迎因为它使s1 和s2 处于一种更容易被重用的形式这种方法

能够生效是由于string 类型能够自动将C 风格的字符串转换成string 对象例如这使我们

可以将一个C 风格的字符串赋给一个string 对象

string s1;

const char *pc = "a character array";

s1 = pc; // ok

但是反向的转换不能自动执行对隐式地将string 对象转换成C 风格的字符串string

类型没有提供支持例如下面试图用s1 初始化str 就会在编译时刻失败

char *str = s1; // 编译时刻类型错误

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

常量的应用

const double *pc = 0;   //*pc是常量,pc不是常量

const double minWage = 9.60;

// ok: 不能通过pc 修改minWage

pc = &minWage;

double dval = 3.14;

// ok: 不能通过pc 修改dval

// 虽然dval 本身不是一个常量

pc = &dval; // ok

dval = 3.14159; // ok

*pc = 3.14159; // 错误

 

在实际的程序中指向const 的指针常被用作函数的形式参数它作为一个约定来保证

被传递给函数的实际对象在函数中不会被修改例如

// 在实际的程序中, 指向常量的指针

// 往往被用作函数参数

int strcmp( const char *str1, const char *str2 );

 

我们可以定义一个const 指针指向一个const 或一个非const 对象例如

int errNumb = 0;

int *const curErr = &errNumb;

//curErr 是指向一个非const 对象的const 指针我们可以从右拄左把定义读作curErr

//是一个指向int 类型对象的const 指针这意味着不能赋给curErr 其他的地址值但可以

//修改curErr 指向的值

//下面的代码说明我们可以怎样使用curErr

do_something();

if ( *curErr ) {

errorHandler();

*curErr = 0; // ok: 重置指针所指的对象

}

curErr = &myErrNumb; // 错误

//指向const 对象的const 指针的定义就是将前面两种定义结合起来例如

const double pi = 3.14159;

const double *const pi_ptr = &pi;

//在这种情况下pi_ptr 指向的对象的值以及它的地址本身都不能被改变我们可以从

//右往左将定义读作pi_ptr 是指向被定义为const 的double 类型对象的const 指针

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

假定有下列代码

int &ri = ival, &ri2 = ival2;

当我们写出这样的赋值语句时

ri = ri2;

改变的是ival 而不是引用本身.赋值之后两个引用仍然指向原来的对象.

实际的C++程序很少使用指向独立对象的引用类型引用类型主要被用作函数的形式参数.

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

字符数组可以用一个由逗号分开的字符文字列表初始化.文字列表用花括号括起来或

者用一个字符串文字初始化.但是注意这两种形式不是等价的.字符串常量包含一个额外

的终止空字符例如

const char ca1[] = { 'C', '+', '+' };

const char ca2[] = "C++";

cal 的维数是3 ca2 的维数是4

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

数组定义由类型名标识符和维数组成.维数指定数组中包含的元素的数目.它被写在

一对方括号里边.我们必须为数组指定一个大于等于1 的维数.维数值必须是常量表达式——

即必须能在编译时刻计算出它的值.这意味着非const 的变量不能被用来指定数组的维数.

下面的例子包含合法的和非法的数组定义

extern int get_size();

// buf_size max_files 都是const

const int buf_size = 512, max_files = 20;

int staff_size = 27;

char input_buffer[ buf_size ];// ok: const 变量

char *fileTable[ max_files - 3 ];// ok 常量表达式: 20 - 3

double salaries[ staff_size ];// 错误:staff_sizeconst 变量

int test_scores[ get_size() ];// 错误:get_size()const 表达式

 

虽然staff_size 被一个文字常量初始化但是staff_size 本身是一个非const 对象系统只

能在运行时刻访问它的值因此它作为数组维数是非法

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

一个数组不能被另外一个数组初始化.也不能被赋值给另外一个数组而且C++不允

许声明一个引用数组(即由引用组成的数组)

const int array_size = 3;

int ix, jx, kx;

// ok: 类型为int*的指针的数组

int *iap [] = { &ix, &jx, &kx };// 错误: 不允许引用数组

int &iar[] = { ix, jx, kx };// 错误: 不能用另一个数组来初始化一个数组

int ia2[] = ia; // 错误

int main()

{

int ia3[ array_size ]; // ok

ia3 = ia;// 错误: 不能把一个数组赋给另一个数组

return 0;

}

要把一个数组拷贝到另一个中去必须按顺序拷贝每个元素

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

vector 容器类型

vector 类为内置数组提供了一种替代表示,为了使用vector 我们必须包含相关的头文件

#include <vector>

使用vector 有两种不同的形式即所谓的数组习惯和STL习惯.

在数组习惯用法中我们模仿内置数组的用法定义一个已知长度的vector

vector< int > ivec( 10 );

这与如下定义一个包含十个元素的内置数组相似

int ia[ 10 ];

 

我们可以用下标操作符访问vector 的元素.与访问内置数组的元素的方式一样例如

void simple_example()

{

const int elem_size = 10;

vector< int > ivec( elem_size );

int ia[ elem_size ];

for ( int ix = 0; ix < elem_size; ++ix )

ia[ ix ] = ivec[ ix ];

 

我们还可以为每个元素提供一个显式的初始值来完成初始化例如

vector< int > ivec( 10, -1 );

定义了ivec 它包含十个int 型的元素每个元素都被初始化为-1

对于内置数组我们可以显式地把数组的元素初始化为一组常量值例如

int ia[ 6 ] = { -2, -1, 0, 1, 2, 1024 };

我们不能用同样的方法显式地初始化vector 但是可以将vector 初始化为一个已有数

组的全部或一部分,只需指定希望被用来初始化vector 的数组的开始地址以及数组最末元素

的下一位置来实现例如

vector< int > ivec( ia, ia+6 ); // 把ia 的6 个元素拷贝到ivec 中

被传递给ivec 的两个指针标记了用来初始化对象的值的范围第二个指针总是指向要被

拷贝的末元素的下一位置标记出来的元素范围也可以是数组的一个子集例如

vector< int > ivec( &ia[ 2 ], &ia[ 5 ] );// 拷贝3 个元素ia[2], ia[3], ia[4]

 

与内置数组不同vector 可以被另一个vector 初始化或被赋给另一个vector 例如

vector< string > svec;

void init_and_assign()

{

// 用另一个vector 初始化一个vector

vector< string > user_names( svec );

// ...

// 把一个vector 拷贝给另一个vector

svec = user_names;

}

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

复数类型

复数complex number 类是标准库的一部分为了能够使用它我们必须包含其相关的头文件

#include <complex>

每个复数都有两部分实数部分和虚数部分.虚数代表负数的平方根这个术语是由笛卡儿首创的复数的一般表示法如下

2 + 3i

这里2 代表实数部分而3i 表示虚数部分.这两部分合起来表示单个复数

复数对象的定义一般可以使用以下形式

 

complex< double > purei( 0, 7 );// 纯虚数0 + 7i

 

complex< float > real_num( 3 );// 虚数部分缺省为0 3 + 0i

 

complex< long double > zero;// 实部和虚部均缺省为0 0 + 0i

 

complex< double > purei2( purei );// 用另一个复数对象来初始化一个复数对象

 

这里复数对象有float double 或long double 几种表示我们也可以声明复数对象的数组

complex< double > conjugate[ 2 ] = {

complex< double >( 2, 3 ),

complex< double >( 2, -3 )

};

我们也可以声明指针或引用

complex< double > *ptr = &conjugate[0];

complex< double > &ref = *ptr;

 

 

复数操作

#include <iostream>

#include <complex>

inline complex<double>&

operator+=( complex<double> &cval, double dval )

{

return cval += complex<double>( dval );

}

int main()

{

complex< double > cval( 4.0, 1.0 );

cout << cval << endl;

cval += 1;

cout << cval << endl;

}

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

volatile 限定修饰符

volatile 用法同const 非常相似——都是作为类型的附加修饰符例如

volatile int display_register;

volatile Task *curr_task;

volatile int ixa[ max_size ];

volatile Screen bitmap_buf;

 

volatile 修饰符的主要目的是提示编译器该对象的值可能在编译器未监测到的情况下被

改变,例如一个被系统时钟更新的变量,因此编译器不能武断地对引用这些对象的代码作优化处理.

 

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

typedef 名字

typedef 机制为我们提供了一种通用的类型定义设施.可以用来为内置的或用户定义的数据类型引入助记符号例如

typedef double wages;

typedef vector<int> vec_int;

typedef vec_int test_scores;

typedef bool in_attendance;

typedef int *Pint;

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

表格4.2 等于关系以及逻辑操作符

操作符 功能 用法

! 逻辑非 !expr

< 小于 expr < expr

<= 小于等于 expr <= expr

> 大于 expr > expr

>= 大于等于 expr >= expr

== 等于 expr == expr

!= 不等于 expr != expr

&& 逻辑与 expr && expr

|| 逻辑或 expr || expr

注这些操作符的结果是bool 类型(ture,flase,1,0)

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

pair 类型

   类型

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

条件操作符

#include <iostream>

int main()

{

int i = 10, j = 20, k = 30;

cout << "The larger value of "

<< i << " and " << j << " is "

<< ( i > j << i : j ) << endl;

cout << "The value of " << i << " is"

<< ( i % 2 << " odd." : " even." )

<< endl;

int max = ( (i > j)? (( i > k) ? i : k): ( j > k ) ? j : k);

cout << "The larger value of "

<< i << ", " << j << " and " << k

<< " is " << max << endl;

}

编译并运行这个程序产生下列输出

The larger value of 10 and 20 is 20

The value of 10 is even

The larger value of 10, 20 and 30 is 30

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

sizeof 操作符

返回值的类型是size_t

#include <cstddef>

siseof 操作符的作用是返回一个对象或类型名的字节长度.它有以下三种形式

sizeof (type name );//sizeof(int);

sizeof ( object );

sizeof object;

 

#include <cstddef>

int ia[] = { 0, 1, 2 };

size_t array_size = sizeof ia;// sizeof 返回整个数组的大小为12字节

size_t element_size = array_size / sizeof( int );// sizeof 返回int 类型的大小为4字节

size_t char_size = sizeof( char );//sizeof 操作符应用在char 类型上时在所有的C++实现中结果都是1

 

//例子VC++6.0中调试

#include <string>

#include <iostream>

#include <cstddef>

//使用using namespace std;可以把std::去掉

int main()

{

size_t ia;

ia = sizeof( ia ); // ok

ia = sizeof ia; // ok

ia = sizeof( int ); // ok

short *x = new short[ 12 ];

std::cout << "x: " << sizeof( x )<< "*x:" << sizeof( *x );

int *y = new int[ 12 ];

std::cout << "/ny: " << sizeof( y )<< "*y:" << sizeof( *y );

 

std::string st1( "foobar" );// 一个string 的大小与它所指的字符串的长度无关

std::string st2( "a mighty oak" );// 一个string 的大小与它所指的字符串的长度无关

std::string *ps = &st1;

std::cout << "/nst1:" <<sizeof(st1)<<"/nst2:"<<sizeof(st2)<<"/nps:"<<sizeof(ps)<<"/n*ps:"<<sizeof(*ps);

std::cout << "/nshort :" << sizeof(short);

std::cout << "/nshort* :" << sizeof(short*);

std::cout << "/nshort& :" << sizeof(short&);

std::cout << "/nshort[3]:" << sizeof(short[3]);

return 0;

}

 

x:4 *x:2

y:4 *y:4

st1: 16

st2: 16

ps: 4

*ps: 16

short : 2

short* : 4

short& : 2

short[3] : 6

如上所示应用在指针类型上的sizeof 操作符返回的是包含该类型地址所需的内存长度。

          应用在引用类型上的sizeof 操作符返回的是包含被引用对象所需的内存长度。

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

new 和delete 表达式

new 表达式返回指向新分配的对象的指针

int *pi = new int( 1024 );

它不但分配了这个对象而且用1024 将其初始化,要动态分配一个对象数组我们可以写成

int *pia = new int[ 10 ];

 

delete pi;

delete [] pia;

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

位操作符

 

 

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

bitset 操作

 

 

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

类型转换

cast-name< type >( expression );

这里的cast-name static_castconst_castdynamic_castreinterpret_cast 之一

 

const_cast 正如其名字所暗示的将转换掉表达式的常量性

extern char *string_copy( char* );

const char *pc_str;

char *pc = string_copy( const_cast< char* >( pc_str ));//转换掉表达式的常量性

 

编译器隐式执行的任何类型转换都可以由static_cast显式完成

double d=97.0;

char ch=d; //char ch = static_cast< char >( d );

 

reinterpret_cast 操作符来执行并标识出所有的显式指针强制转换

void *pv; int ival;

char *pc; double dval;

pc = (char*) pv;//pc=reinterpret_cast<char *>(pv);

 

dynamic_cast 支持在运行时刻识别由指针或引用指向的类对象. 19.1节再行介绍

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

栈类实例

 

 

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

if 语句

 

下面是我们的min()函数的第一次迭代。第二个参数occurs 将记录最小值出现的次数

我们将在函数中设置它同时确定并返回实际的最小值(使用引用来返回一个以上的值)

int min( const vector<int> &ivec, int &occurs )

{

int size = ivec.size();

// 处理空vector 异常

// occurs 被设置为0 表示空 vector

if ( ! size ) { occurs = 0; return 0; }

// ok: vector 至少含有一个元素

int minVal = ivec[ 0 ]; occurs = 1;

for ( int ix = 1; ix < size; ++ix )

{

if ( minVal == ivec[ ix ] )

++occurs;

else

if ( minVal > ivec[ ix ] ){

minVal = ivec[ ix ];

occurs = 1;

}

}

return minVal;

}

 

对于简单的if-else 语句条件操作符可以提供一个便捷的简写形式例如下列min()函数模板

template <class valueType>

inline const valueType&

min( valueType &val1, valueType &val2 )

{

if ( val1 < val2 )

return val1;

return val2;

}

可以写成

template <class valueType>

inline const valueType&

min( valueType &val1, valueType &val2 )

{

return ( val1 < val2 ) << val1 : val2;

}

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

switch 语句

·可选的default 标签.default 标签也被看作是一种else 子句.如果switch 表达式与任

  意一个case 标签都不匹配,则default 标签后面的语句被计算.

·关键字case 后面的值必须是一种整数类型的常量表达式.

·任意两个case 标签不能有同样的值.

·switch 表达式可以是任意复杂的表达式,包括函数调用的返回值.它的值与每个case 标

签相关联的值作比较,直到某个匹配成功或全部标签比较完毕,如果匹配到了某个标签则

程序从其后的语句继续执行,如果所有标签都不匹配,那么若有default 标签的话,则从default

后面的语句继续执行,否则程序从switch 语句后面的第一条语句继续执行.

 

#include<iostream>

int main()

{

char ch;

int aCnt=0, eCnt=0, iCnt=0, oCnt=0, uCnt=0;

while ( cin >> ch )

// 警告: 这是不正确的

switch ( ch ) {

case 'a':

++aCnt;

case 'e':

++eCnt;

case 'i'://假如输入的是i那么程序从case 'i':开始执行也会执行case 'o':case 'u':

++iCnt;

case 'o':

++oCnt;

case 'u':

++uCnt;

}

 

//正确

switch ( ch ) {

case 'a':

++aCnt;

break;

case 'e':

++eCnt;

break;

case 'i'://假如输入的是i那么程序从case 'i':执行后结束

++iCnt;

break;

case 'o':

++oCnt;

break;

case 'u':

++uCnt;

break;

}

 

default 标签给出了无条件else 子句的等价体如果所有的case 标签与switch 表达式的值

都不匹配,并且default 标签也存在,则执行default 标签后面的语句.例如给我们的程序

增加default 的情形使它能够记录辅音字母的个数.

isalpha()是标准C 库的一个例程如果它的参数是一个英文字母则返回值为true 为

了使用它程序员必须包含系统头文件ctype.h 我们将在第6 章详细介绍ctype.h 例程

#include <iostream>

#include <ctype.h>

int main()

{

char ch;

int aCnt=0, eCnt=0, iCnt=0, oCnt=0, uCnt=0,

consonantCnt = 0;

while ( cin >> ch )

switch ( ch )

{

case 'a': case 'A':

++aCnt;

break;

case 'e': case 'E':

++eCnt;

break;

case 'i': case 'I':

++iCnt;

break;

case 'o': case 'O':

++oCnt;

break;

case 'u': case 'U':

++uCnt;

break;

default:

if ( isalpha( ch ))

++consonantCnt;

break;

}

cout << "Number of vowel a: /t" << aCnt << '/n'

<< "Number of vowel e: /t" << eCnt << '/n'

<< "Number of vowel i: /t" << iCnt << '/n'

<< "Number of vowel o: /t" << oCnt << '/n'

<< "Number of vowel u: /t" << uCnt << '/n'

<< "Number of consonants: /t" << consonantCnt << '/n';

}

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

while 循环尤其擅长在某个条件保持为真时不停地执行.例如当没有到达文件尾时,

有读取下一个值.一般认为for 循环是一种按步循环,用一个索引按步遍历集合中一定范围

内的元素.while 可替代for循环。

 

do while 循环保证至少执行一次statement. do while 循环的语法形式如下

do

statement

while ( condition );

 

不像其他循环语句do while 循环的条件即condition 部分不支持对象定义——

不能写

// 错误: 在do 循环的condition 部分不支持声明语句

do {

// ...

mumble( foo );

} while ( int foo = get_foo() ) // 错误

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

break 语句

break 语句终止最近的while, do while ,for 或switch 语句.

 

如果一个break 语句出现在if 语句的内部,但是并不被包含在switch 或循环语句中,

那么这样的break 语句将导致编译错误,例如

if ( ptr ) {

if ( *ptr == "quit" )

break; // 错误: break 语句的非法出现

// ...

}

一般来说break 语句只能出现在循环或switch 语句中.

 

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

链表

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

抽象容器类型

 

顺序容器sequence container 拥有由单一类型元素组成的一个有序集合.两个主要的

顺序容器是list vector .第三个顺序容器为双端队列deque, 发音为deck 它提供了

与vector 相同的行为但是对于首元素的有效插入和删除提供了特殊的支持.例如在实现

队列时队列是一种抽象用户每次总是获取第一个元素deque 比vector 更为合适在

本书的剩余部分每当我们描述vector 所支持的操作时deque 也同样支持这些操作.

 

关联容器associative container 支持查询一个元素是否存在并且可以有效地获取元素.

两个基本的关联容器类型是map 映射和set 集合.map 是一个键/值key/value 对

键key 用于查询.而值value 包含我们希望使用的数据.例如map 可以很好地支持

电话目录,键是人名,值是相关联的电话号码.

set 包含一个单一键值有效支持关于元素是否存在的查询.例如当我们要创建一个单

词数据库且它包含在某个文本中出现的单词时,文本查询系统对能会生成一个单词集合以

排除the and 以及but 等等,程序将顺次读取文本中的每个单词检查它是否属于被排除单

词的集合并根据查询的结果将其丢弃或者放入数据库中.

map 和set 都只包含每个键的惟一出现即每个键只允许出现一次,multimap 多映射

和multiset 多集合支持同一个键的多次出现.例如我们的电话目录可能需要为单个用

户支持多个列表一种实现方法是使用multimap

 

 

vector 表示一段连续的内存区域每个元素被顺序存储在这段内存中.对vector 的随机

访问比如先访问元素5 然后访问15 然后再访问7 等等效率很高,因为每次访问离vector

起始处的位移都是固定的.但是在任意位置而不是在vector 末尾插人元素则效率很低

因为它需要把待插入元素右边的每个元素都拷贝一遍.类似地删除任意一个而不是vector

的最后一个元素效率同样很低,因为待删除元素右边的每个元素都必须被复制一遍

 

list 表示非连续的内存区域并通过一对指向首尾元素的指针双向链接起来从而允许

向前和向后两个方向进行遍历.在list 的任意位置插入和删除元素的效率都很高.指针必须

被重新赋值但是不需要用拷贝元素来实现移动.另一方面它对随机访问的支持并不好

访问一个元素需要遍历中间的元素,另外每个元素还有两个指针的额外空间开销

 

下面是选择顺序容器类型的一些准则

如果我们需要随机访问一个容器则vector 要比list 好得多

如果我们已知要存储元素的个数则vector 又是一个比list 好的选择

如果我们需要的不只是在容器两端插入和删除元素则list 显然要比vector 好

除非我们需要在容器首部插入和删除元素否则vector 要比deque 好

 

容量是指在容器下一次需要增长自己之前能够被加入到容器中的元素的总数.容量只与

连续存储的容器相关,例如vector ,deque 或string list 不要求容量为了知道一个容器的

容量我们调用它的capacity()操作.而长度size 是指容器当前拥有元素的个数.为了获

得容器的当前长度我们调用它的size()操作

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

定义一个顺序容器

 

为了定义一个容器对象我们必须先包含相关联的头文件应该是下列头文件之一

#include <vector>

#include <list>

#include <deque>

#include <map>

#include <set>

容器对象的定义以容器类型的名字开始,后面是所包含的元素的实际类型 例如

vector< string > svec;

list< int > ilist;

 

插入元素最简单的方法是push_back() 它将元素插入在容器的尾部例如

string text_word;

while ( cin >> text_word )

svec.push_back( text_word );

 

list deque 容器也支持push_front() 它把新元素插入在链表的前端,例如

int ia[ 4 ] = { 0, 1, 2, 3 };

//push_back()

for ( int ix = 0; ix < 4; ++ix )

ilist.push_back( ia[ ix ] );//创建序列0 1 2 3

//然而如果使用push_front()

for ( int ix = 0; ix < 4; ++ix )

ilist.push_front( ia[ ix ] );//则在ilist 中创建序列3 2 1 0

 

 

reserve()操作允许程序员将容器的容量设置成一个显式指定的值例如

int main()

{

vector< string > svec; // 使svec 的长度为0

svec.reserve( 32 ); // 把容量设置为32

}

 

 

另外我们或许希望为容器指定一个显式的长度,长度可以是常量也可以是非常量表达式

#include <list>

#include <vector>

#include <string>

extern int get_word_count( string file_name );

const int list_size = 64;

list< int > ilist( list_size );

vector< string > svec(get_word_count(string("Chimera")));

 

我们还可以指定一个值并用它来初始化每个元素,例如

list< int > ilist( list_size, - 1 );

vector< string > svec( 24, "pooh" );

除了给出初始长度外我们还可以通过resize()操作重新设置容器的长度

例如当我们写下面的代码时

svec.resize( 2 * svec.size() );//我们将svec 的长度加了一倍

 

如果我们希望把每个新元素初始化为某个其他值则可以把该值指定为第二个参数

svec.resize( 2 * svec.size(), "piglet" );// 将新元素初始化为piglet

 

我们也可以用一个现有的容器对象初始化一个新的容器对象例如

vector< string > svec2( svec );

list< int > ilist2( ilist );

 

每个容器支持一组关系操作符我们可以用来比较两个容器.这些关系操作符分别是

等于不等于小于大于小于等于以及大于等于.容器的比较是指两个容器的元素之

间成对进行比较.如果所有元素相等而且两个容器含有相同数目的元素则两个容器相等.

否则它们不相等.第一个不相等元素的比较决定了两个容器的小于或大于关系

ivec1: 1 3 5 7 9 12

ivec2: 1 3 5 7 2

ivec1>ivec2;//第一个不相等元素:9,2

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

迭代器

 

迭代器iterator 提供了一种一般化的方法对顺序或关联容器类型中的每个元素进行

连续访问.

 

每种容器类型都提供一个begin()和一个end()成员函数

begin()返回一个iterator 它指向容器的第一个元素

end()返回一个iterator 它指向容器的末元素的下一个位置

 

下面是一对iterator的定义它们指向一个内含string 元素的vector

vector<string> vec;

vector<string>::iterator iter = vec.begin(); // *iter为iter指针所指的元素

vector<string>::iterator iter_end = vec.end(); // iterator 是vector 类中定义的

 

除了iterator 类型,每个容器还定义了一个const iterator 类型,后者对于遍历const 容器

是必需的,const iterator 允许以只读方式访问容器的底层元素

#include <vector >

void even_odd( const vector<int> *pvec,vector<int> *pvec_even,vector<int> *pvec_odd )

{

vector<int>::const_iterator c_iter = pvec->begin(); // 必须声明一个const_iterator, 才能够遍历pvec

vector<int>::const_iterator c_iter_end = pvec->end();

for ( ; c_iter != c_iter_end; ++c_iter )

if ( *c_iter % 2 )

pvec_odd->push_back( *c_iter );

else pvec_even->push_back( *c_iter );

}

 

我们可以用标量算术运算使iterator 从当前位置偏移到某个位置上例如

vector<int>::iterator iter = vec.begin()+vec.size()/2; // 将iter 指向vec 的中间元素

iter += 2; // 将iter 向前移动两个元素

iterator 算术论算只适用于vector 或deque 而不适用于list 因为list 的元素在内存中不是连续存储的例如

ilist.begin() + 2; // error

 

我们可以定义一个新的vector 来拷贝svec 的全部或部分元素

int main()

{

vector<string> svec;

vector<string> svec2( svec.begin(), svec.end() ); // svec 的全部元素初始化svec2

vector<string>::iterator it =svec.begin() + svec.size()/2; // svec 的前半部分初始化svec3

vector<string> svec3( svec.begin(), it );

}

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

6.6 顺序容器操作

在容器的头部插入元素insert()

vector< string > svec;

list< string > slist;

string spouse( "Beth" );

slist.insert( slist.begin(), spouse );

svec.insert( svec.begin(), spouse );

 

更为随机的插入操作可以如下实现

string son( "Danny" );

list<string>::iterator iter;

iter = find( slist.begin(), slist.end(), son );

slist.insert( iter, spouse );

 

insert()方法的第二种形式支持在某个位置插入指定数量的元素

vector<string> svec;

string anna( "Anna" );

svec.insert( svec.begin(), 10, anna );

 

insert()方法的最后一种形式支持向容器插入一段范围内的元素

string sarray[4] = { "quasi", "simba", "frollo", "scar" };

我们可以向字符串vector 中插入数组中的全部或部分元素

svec.insert( svec.begin(), sarray, sarray+4 );

svec.insert( svec.begin() + svec.size()/2,sarray+2, sarray+4);

 

另外我们还可以通过一对iterator 来标记出待插入值的范围可以是另一个string 元素的vector

// 插入svec 中含有的元素

// svec_two 的中间开始

svec_two.insert( svec_two.begin() + svec_two.size()/2,svec.begin(), svec.end() );

或者更一般的情况下也可以是任意一种string 对象的容器

list< string > slist;

// svec 中含有的元素插入到slist stringVal 的前面

list< string >::iterator iter =find( slist.begin(), slist.end(), stringVal );

slist.insert( iter, svec.begin(), svec.end() );

 

6.6.1 删除操作

删除容器内元素的一般形式是一对erase()方法。一个删除单个元素,另一个删除由一对

iterator 标记的一段范围内的元素。删除容器末元素的简短方法由pop_back()方法支持

 

string searchValue( "Quasimodo" );

list< string >::iterator iter =find( slist.begin(), slist.end(), searchValue );

if ( iter != slist.end() )

slist.erase( iter ); // 删除单个元素

 

slist.erase( slist.begin(), slist.end() ); // 删除容器中的所有元素

list< string >::iterator first, last; // 删除由iterator 标记的一段范围内的元素

first = find( slist.begin(), slist.end(), val1 );

last = find( slist.begin(), slist.end(), val2 );

// ... 检查first last 的有效性

slist.erase( first, last );

 

6.6.2 赋值和对换

// slist1 含有10 个元素

// slist2 含有24 个元素

// 赋值之后都含有24 个元素

slist1 = slist2; // 赋值

 

swap()可以被看作是赋值操作符的互补操作当我们写

slist1.swap( slist2 ); // 对换

//slist124 string 元素,slist2 现在含有slist1原来有的10 个元素的拷贝

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

6.7 存储文本行------6.18 回顾iStack

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

原创粉丝点击