【C++学习】第八章 指针与字符串 8.7-8.11节
第8章 指针与字符串8.7 指针表达式与指针算法
指针是算术表达式、赋值表达式和比较表达式中的有效操作数。但是,通常并不是这些表达式中使用的所有运算符都在指针变量中有效。本节介绍可以用指针操作数的运算符及这些运算符的用法。
只有少量操作可以对指针进行。指针可以自增(++)或自减(--),整数可以加进指针中(+或+=),也可以从指针中减去整数(-或-=),指针可以减去另一指针。
假设声明了数组int v[5],其第一个元素位于内存地址3000。假设指针vPtr已经初始化为指向数组v[0],即vPtr的值为3000。图8.18演示了在32位的机器中的这种情况。注意vPtr可以初始化为数组v的指针,如下所示:
vPtr = v;
vPtr = &v[ 0 ]
图8.18 数组v和指向v的指针变量vPtr
按照传统算法,3000+2得到3002。而指针算法通常不是这样。将指针增加或减去一个整数时,指针并不是直接增加或减去这个整数,而是加上指针所指对象长度的这个倍数。这些字节数取决于对象的数据类型。例如,下列语句:
vPtr += 2;
在用4字节内存空间存储整数时得到的值为3008(3000+2*4)。对数组v,这时vPtr指向v[2]。如果用2字节内存空间,则上述结果得到3004(3000+2*2)。如果数组为不同数据类型,则上述语句将指针递增指针所指对象长度的2倍。对字符数组进行指针算法时,结果与普通算法相同,因为每个字符的长度为一个字节。
如果vPtr递增到3016,指向v[4],则下列语句:
vptr -= 4;
将vPtr复位为3000,即数组开头。如果指针加1或减1,则可以用自增(++)和自减(--)运算符。
下列语句:
++vptr;
vPtr++;
将指针移到数组中的下一个位置。
下列语句:
--vPtr;
vPtr --;
将指针移到数组中的前一个位置。
指针变量还可以相减。例如,如果vPtr包含地址3000,v2Ptr包含地址3008,则下列浯句:
x= v2Ptr - vPtr;
将x指定为vPtr到v2Ptr的元素个数,这里为2。指针算法只在对数组进行时才有意义。我们不能假设两个相同类型的变量在内存中相邻的地址存放,除非它们是数组的相邻元素。
如果两个指针的类型相同,则可以将一个指针赋给另一个指针。否则要用强制类型转换运算符将赋值语句右边的指针值转换为赋值语句左边的指针值。这个规则的例外是void的指针(即void),该指针是个一般性指针,可以表示任何指针类型。所有指针类型都可以赋给void指针而不需要类型转换。但是,void指针不能直接赋给另一类型的指针,而要先将void指针转换为正确的指针类型。
void*指针不能复引用。例如,编译器知道int指针指向32位机器中的4字节内存,但void指针只是包含未知数据类型的内存地址,指针所指的字节数是编译器所不知道的。编泽器要知道数据类型才能确定该指针复引用时的字节数。对于void指针,无法从类型确定字节数。
指针可以用相等和关系运算符比较,但这种比较只在对相同数组成员进行时才有意义。指针比较是对指针存放的地址进行比较。例如,比较指向同一数组的两个指针可以表示一个指针所指的元素号比另一个指针所指的元素号更高。指针比较常用于确定指针是否为0。
8.8 指针与数组的关系C++中指针与数组关系密切,几乎可以互换使用。数组名可以看成常量指针,指针可以进行任何有关数组下标的操作。
假设声明了整数数组b[ 5 ]和整数指针变量bPtr。由于数组名(不带下标)是数组第一个元素的指针.因此可以用下列语句将bPtr设置为b数组第一个元素的地址:
bPtr = b;
这等于取数组第一个元素的地址,如下所示:
bPtr=&b[ 0 ];
数组元素b[3]也可以用指针表达式引用:
*( bPtr + 3 )
上述表达式中的3是指针的偏移量(offset)。指针指向数组开头时,偏移量表示要引用的数组元素,偏移量值等于数组下标。上述符号称为指针/偏移量符号(pointer/offset notation)。括号是必需的,因为*的优先顺序高于+的优先顺序。如果没有括号,则上述表达式将表达式*bPtr的值加上3(即3加到b[0]中,假设bPtr指向数组开头)。就像数组元素可以用指针表达式引用一样,下列地址:
&b[ 3 ]
可以写成指针表达式:
bPtr+ 3
数组本身可以当作指针并在指针算法中使用。例如,下列表达式:
*( b + 3)
同样引用数组元素b[3]。一般来说,所有带下标的数组表达式都可以写成指针加偏移量,这时使 用指针/偏移量符号,用数组名作为指针。注意,上述语句不修改数组名,b还是指向数组中第一个元素指针和数组一样可以加下标。
例如,下列表达式:
bPtr[ 1 ]
指数组元素b[1].这个表达式称为指针/下标符号(pointer/subscript notation)。
记住,数组名实际上是个常量指针,总是指向数组开头。因此下列表达式:
b+= 3
是无效的,因为该表达式试图用指针算法修改数组名的值。
图8. 20的程序用我们介绍的四种方法引用数组元素(数组下标、用数组名作为指针的指针/偏移量符号、指针下标和指针的指针/偏移量符号,打印数组的的4个元素)。
要演示数组和指针的互换性,还可以看看程序9.21中的两个字符串复制函数copy1和copy2。这两个函数都是将字符串复制到字符数组中。比较copy1和copy2的函数原型可以发现,函数基本相同(由于数组和指针具有互换性)。这些函数完成相同的任务,但用不同方法实现。
1 // Fig. 8.20: fig08_20.cpp
2 // Using subscripting and pointernotations with arrays
3
4 #include
5
6 int main()
7{
8 intb[] = { 10, 20, 30, 40 } ;
9 int *bPtr = b; // set bPtr topoint to array b
10
11 cout << "Array b printed with:n"
12 << "Array subscript notationn";
13
14 for(int i = 0; i < 4; i++ ),
15 cou << "b[ " << i << "] = << b[ i ] << 'n';
16
17
18 cout << "nPointer/offset notation wheren"
19 << "the pointer is the array namen";
2O
21 for (int offset = 0; offset < 4; offset++ )
22 cout << "* (b +" << offset << ")="
23 << *( b + offset ) << 'n';
24
25
26 cout << "nPointer subscript notationn";
28 for ( i = 0; i < 4; i++ )
29 cout << "bPtr[" << i << "] ="<< bPtr[ i ] << 'n';
31 cout << "nPointer/offset notationn";
32
33 for ( offset = 0; offset < 4; offset++ )
34 cout << "*(bPtr + "<< offset << ")="
35 << * ( bPtr + offset ) << ' n';
36
37 return 0;
38 }
输出结果:
Array b Printed with:
Array subscript notation
Pointer/offset notation where
the pointer is the array name
* (b + 0) = 10
* (b + 1) = 20
* (b + 2) = 30
* (b + 3) = 40
Pointer subscript notation
bPtr[ 0 ] = 10
bPtr[ 1 ] = 20
bPtr[ 2 ] = 30
bPtr{ 3 ] = 40
Pointer/offset notation
*(bPtr + 0) = 10
*(bPtr + 1) = 20
*(bPtr + 2) = 30
*(bPtr + 2) = 40
图8.20 用我们介绍的四种方法引用数组元素
1 // Fig. 8.21: fig08_21.cpp
2 // Copying a string using array notation
3 // and pointer notation.
4 #include
5
6 void copy1( char *, const char * );
7 void copy2( char *, const char * );
8
9 int main()
10 {
11
12 string3[ 10 ], string4[] = "Good Bye";
13
14 copy1( string1, string2 );
15 cout<< "string1 =" << string1 << endl;
16
17 copy2( string3, string4 );
18 cout << "string3 = "<< string3 << endl';
19
20 return 0;
21 }
22
23 // copy s2 to sl using array notation
24 void copy1( char *s1, const char *s2 )
25 {
26 for ( int i = 0; ( s1[ i ] = s2[ i ] ) != ''; i++ )
27 ; // do nothing in body
28 }
29
30 // copy s2 to sl using pointer notation
31 void copy2( char *s1, const char *s2 )
32 {
33 for ( ; ( *s1 = *s2 ) != ''; s1++, s2++ )
34 ; // do nothing in body
35 }
输出结果:
string1 = Hello
string3 = Good Bye
图8.21 使用数组和指针符号复制字符串
函数copy1用数组下标符号将s2中的字符串复制到字符数组s1中。函数声明一个作为数组下标的整型计数器变量i。for结构的首部进行整个复制操作,而for结构体本身是个空结构。首部中指定i初始化为0,并在每次循环时加1。for的条件“(s1[i]=s2[i])!='',从s2向s1一次一个字符地进行复制操作。遇到s2中的null终止符时,将其赋给s1,循环终止,因为null终止符等于''。
记住.赋值语句的值是赋给左边参数的值。
函数copy2用指针和指针算法将s2中的字符串复制到s1字符数组。同样是在for结构的首部进行整个复制操作.首部没有任何变量初始化。和copy1中一样,条件(*s1=*s1)!=''进行复制操作。
复引用指针s2,产生的字符赋给复引用的指针s1。进行条件中的赋值之后,指针分别移到指向s1数组的下一个元素和字符串s2的下一个字符。遇到s2中的null终止符时,将其赋给s1,循环终止。
注意copy1和copy2的第一个参数应当是足够大的数组,应能放下第二个参数中的字符串,否则可能会在写人数组边界以外的内存地址时发生错误。另外,注意每个函数中的第二个参数声明为const char*(常量字符串)。在两个函数中,第二个参数都复制到第一个参数,一次一个地从第二个参数复制字符,但不对字符做任何修改。因此,第二个参数声明为常量值的指针,实施最低权限原则。两个函数都不需要修改第二个参数,因此不向这两个函数提供修改第二个参数的功能。
8.9 指针数组数组可以包含指针,这种数据结构的常见用法是构成字符串数组,通常称为字符串数组(stringarray)。字符串数组中的每项都是字符串,但在C++中,字符串实际上是第一个字符的指针。因此,字符串数组中的每项实际上是字符串中第一个字符的指针。下列字符串数组suit的声明可以表示一副牌:
char‘*suit[ 4 ] ={ "Hearts","Diamonds","Clubs","Spades"};
声明的suit[4]部分表示4个元素的数组。声明的char*部分表示数组suit的每个元素是char类型的指针。数组中的4个值为”Hearts'’、”Diamonds”、”Clubs”和”Spades”。每个值在内存中存放成比引号中的字符数多一个字符的null终上字符串。4个字符串长度分别为7、9、6、7。尽管这些字符串好像是放在suil数组中,其实数组中只存放指针。每个指针指向对应字符串中的第一个字符。这样,尽管:suit数组是定长的,但可以访问任意长度的字符串,这是C++强大的数据结构功能所带来的灵活性。
suit字符串可以放在双下标数组中,每一行表示一个suit,每一列表示suit名的第一个字符、这种数据结构每一行应有固定列数,能够放下最长的字符串。因此,存放大量字符串而大部分字符串长度均比最长字符串短许多时,可能浪费很多内存空间。我们将在下一节用字符串数组帮助整理一副牌。
8.10 函数指针函数指针包含函数在内存中的地址。第3章介绍了数组名实际上是数组中第一个元素的内存地址。同样,函数名实际上是执行函数任务的代码在内存中的开始地址。函数指针可以传人函数、从函数返回、存放在数组中和赋给其他的函数指针。
要演示如何使用函数指针,我们修改图8.15的冒泡排序程序,变成图8.26的程序。新程序包括main和函数bubble、swap、ascending和descending。函数bubbleSort接收ascending或descending函数的函数指针参数以及一个整型数组和数组长度。程序提示用户选择按升序或降序排序。如果用户输入1,则向函数bubble传递ascending函数的指针,使数组按升序排列。如果用户输入2,则向函数bubble传递descending函数的指针,使数组按降序排列。图8.27显示了示例的执行结果。
1 // Fig. 8.26: fig08_26.cpp
2 // Multipurpose sorting program usingfunction pointers
3 #include
4 #include
5
6 void bubble( int [], const int, int (*)(int, int ) );
7 iht ascending( int, int );
8 int descending( int, int );
9
10 int main()
11 {
12 const int arraySize = 10;
13 int order,
14 counter,
15 a[ arraySize ] = { 2, 6, 4, 8, 10, 12, 89, 68, 45, 37 };
16
17 cout << "Enter 1 to sort in ascending order,n"
18 << "Enter 2 to sort descending order: ";
19 cin >> order;
20 cout << "nData items in original ordern";
21
22 for ( counter = 0; counter < arraySize; counter++ )
23 cout << setw( 4 ) << a[ counter ];
24
25 if ( order == 1 ) {
26 bubble( a, arraySize, ascending );
27 cout << "nData items in ascending ordern";
29 else {
30 bubble( a, arraySize, descending );
31 cout << "nData items in descending ordern";
32 }
33
34 for ( counter = 0; coun er < arraySize; counter++ )
35 cout << setw( 4 )<< a[ counter ]
36
37 cout << endl;
38 return 0;
39 }
40
41 void bubble( int work[ ], const intsize,
42 int (*compare)( int, int) )
43 {
44 void swap( int *, int* );
45
46 for ( int pass = 1; pass < size; pass++ )
47
48 for ( int count = 0; count < size - 1; count++ )
49
50 if((*compare)( work[ count ], work[count + 1 ] ) )
51 swap( &work[ count ], &work[ count + 1 ] );
52 }
53
54 void swap( int *element1Ptr, int*element2Ptr )
55 {
56 int temp;
57
58 temp = *element1Ptr;
59 *element1Ptr = *element2Ptr;
60 *element2Ptr = temp;
65 return b < a; // swap if b isless than a
66 }
67
68 int descending( int a, int b )
69 {
70 return b > a; // swap if b isgreater than a
71 }
图8.26 使用函数指针的多用途排序程序
输出结果:
Enter 1 to sort in ascending order,
Enter 2 to sort in descending order:1
Data items in original order
2 6 4 8 10 12 89 68 45 37
Data items in ascending order
2 4 6 8 10 12 37 45 68 89
Enter 1 to sort in ascendinq order,
En(e[ 2 to sort in descending order: 1
Data items in oriqinal order
2 6 4 8 10 12 89 68 45 37
Dats items in ascendinQ order
89 68 45 37 12 10 9 6 4 2
图8.27 使用函数指针的多用途排序程序的执行结果
注意这里只包括类型,程序员可以加上名称.但参数名只用于程序中的说明,编译器将其忽略。
if语句中调用传人bubble的函数,如下所示:
if(( *compare )( work[ count ], work[ count + 1 ] ))
就像复引用变量指针可以访问变量值一样,复引用函数指针可以执行这个函数。
也可以不复引用指针而调用函数,如下所示:
if ( compare( work[ count ],work[ count + 1 ] ) )
直接用指针作为函数名。我们更愿意使用第一种通过指针调用函数的方法,因为它显式说明compare是函数指针,通过复引用指针而调用这个函数。第二种通过指针调用函数的方法使compare好像是个实际函数。程序用户可能搞糊涂,想看看compare函数的定义却怎么也找不到。
函数指针的一个用法是建立菜单驱动系统,提示用户从菜单选择一个选项(例如从1到5)。每个选项由不同函数提供服务,每个函数的指针存放在函数指针数组中。用户选项作为数组下标,数组中的指针用于调用这个函数。
图8.28的程序提供了声明和使用函数指针数组的一般例子。这些函数(function1、function2和function3)都定义成取整数参数并且不返回值。这些函数的指针存放在数组f中,声明如下:
void(*f[ 3 ] )(int) = { function1, function2,function3}
声明从最左边的括号读起,表示f是3个函数指针的数组,各取整数参数并返回void。数组用三个函数名(是指针)初始化。用户输入0到2的值时,用这些值作为函数指针数组的下标。函数调用如下所示:
(*f[ choice ])(choice);
调用时,f[choice]选择数组中choice位置的指针。复引用指针以调用函数,并将choice作为参数传人函数中。每个函数打印自己的参数值和函数名,表示正确调用了这个函数。练习中要开发一个菜单驱动系统。
1 // Fig. 8.28: fig08_28.cpp
2 // Demonstrating an array of pointers tofunctions
3 #include
4 void functionl( int );
5 void function2( iht );
6 void function3( int );
7
8 int main()
9 {
10 void (* f[ 3 ] )( int ) = { function1, function2, function3 };
11 int choice;
12
13 cout << "Enter a number between 0 and 2, 3 to end: ";
14 cin >> choice;
15
16 while ( choice >= 0 && choice < 3 ) {
17 (* f[ choice ] )( choice );
18 cout << "Enter a number between 0 and 2, 3 to end: ";
19 cin >> choice;
2O }
21
22 cout << "Program execution completed." << endl;
23 return 0;
24 }
25
26 void function1( int a )
27 {
28 cout << "You entered " << a
29 << "so function1 was callednn";
30 }
31
32 void function2( int b )
33 {
34 cout << "you entered " << b
35 << "so function2 was callednn";
34 }
37
38 void function3( int c )
39 {
40 cout << "You entered "<< c
41 << "so function3 was callednn";
42 }
输出结果:
Enter a number between 0 and 2, 3 to end: 0
You entered 0 so functionl was called
Enter a number between 0 and 2, 3 to end: 1
You entered 1 so function2 was called
Enter a number between 0 and 2, 3 to end: 2
You entered 2 so function3 was called
Enter a number between 0 and 2, 3 to end: 3
Program execution completed
图8.28 声明和使用函数的指针数组
8.11 字符与字符串处理简介本节要介绍一些字符串处理的标准库函数。这里介绍的技术适用于开发文本编辑器、字处理器、桌面排版软件、计算机化打字系统和其他文本处理软件。我们这里使用基于指针的字符串,本书稍后还将介绍把字符串作为成熟的对象。
8.11.1 字符与字符串基础
字符是C++编程语言的基本组件。每个程序都是由一系列字符用有意义的方式组合而成的,计算机将其解释为一系列指令,用来完成一组任务。程序可能包含字符常量(character constant)。字符常量是表示为单引号字符的整数值。字符常量的值是机器字符集中该字符的整数值。例如,'z'表示z的整数值(在ASCII字符集中为122),'n'表示换行符的整数值(在ASCII字符集中为10)。
字符串就是把—系列字符当作一个单元处理。字符串可能包含字母、数字和+、-、*、/、$等各种特殊字符(special character)。C++中的字符串直接量(stringliteral)或字符串常量(stringconstant)放在双引号中如下所示:
“John Q.Doe” (姓名)
“9999 Nain Street” (街道)
“Waltham,Massachusetts” (州)
“(201)555—1212” (电话号码)
C++中的字符串是以null终止符('')结尾的字符数组。通过字符串中第一个字符的指针来访问字符串。字符串的值是字符串中第一个字符的地址(常量),这样,c++中可以说字符串是个常量指针,是指向字符串中第一个字符的指针。从这个意义上说,字符串像数组一样,因为数组名也是第一个元素的(常量)指针。
可以在声明中将字符串指定为字符数组或char*类型的变量。下列声明:
char color[] = "blue";
char *ColorPtr = "blue";
分别将变量初始化为"blue"。第一个声明生成5个元素的数组color,包含字符'b'、'l'、'u'、'e'和'w'。第二个声明生成指针变量colorPtr,指向内存中的字符串"blue"。
声明char color[]={"blue"};也可以改写成:
char color[] ={'b','l','u','e',''};
声明包含字符串的字符数组时,数组应足够大,能存放字符串及其null终止符。上述声明自动根据初始化值列表中提供的初始化值的个数确定数组长度。
字符数组中没有分配能存放字符串及其null终止符的足够空间。
生成或使用不包含null终止符的字符串。
字符串可以用cin通过流读取赋给数组。例如,下列语句将字符串赋给字符数组word[20]:
cin >> word;
用户输入的字符串存放在word中。上述语句读取字符,直到遇到空格、制表符、换行符或文件结束符。注意,字符串不能超过19个字符,因为还要留下nUll终止符的空间。第2章介绍的setw流操纵算子可以用于保证读取到word的字符串不超过字符数组长度。例如,下列语句:
cin>> setw( 20 ) >> word;
指定cin最多读取19个字符到数组word中,并将数组第20个位置用于保存字符串的null终止符。setw流操作算子只能用于输入的下一个值。
有时,可以将整行文本输入数组中。为此,C++提供了cin.getline函数。cin.getline函数取三个参数:存放该行文本的字符数组、长度和分隔符。例如,下列程序段:
char sentence[80];
cin.getline(sentence,80,'n');
声明80个字符的数组sentence,然后从键盘读取一行文本到该数组中。函数遇到分隔符'n'、输入文件结束符或读取的字符数比第二个参数的长度少1时停止读取字符(最后一个字符位置用于保存字符串的null终止符)。如果遇到分隔符,则读取该分隔符并不再读入下一字符。cin.getline函数的第三个参数默认值为'n',因此上述函数调用也可以改写成:
cin.getline(sentence,80);
第1章“C++输入/输出流”中详细介绍了cin.getline和其他函数。
将单个字符作为字符串处理可能导致致命的运行时错误。字符串是指针,可以对应一个大整数。而单个字符是个小整数(0--255的ASCII值)在许多系统中.这会导致错误,因为低内存地址是用于特殊用途的,如操作系统中断处理器,因此会发生“访问无效”的错误。
需要字符串参数时将字符传入函数可能导致致命的运行时错误。
需要字符参数时将字符串传入函数是个语法错误。
8.11.2 字符串处理库的字符串操作函数
字符串处理库提供许多操作字符串数据、比较字符串、搜索字符串中的字符与其他字符串、将字符串标记化(将字符串分成各个逻辑组件)和确定字符串长度的字符串操作函数。本节介绍字符串处理库(标准库)中常用的字符串操作函数。图8.29总结了这些函数。
注意图8.29中的几个函数包含size_t数据类型的参数。这是在头文件<标准库中的头文件,标准库中还有许多其他标准库头文件,包括<="" p="">
使用字符串处理库中的函数而不包括头文件。
函数stcpy将第二个参数(字符串)复制到第一个参数(字符数组)中,这个字符数组的长度应当足以放下字符串及其null终止符。函数strncpy与strcpy相似,只是strncpy指定从字符串复制到字符数组的字符数。注意函数strncpy不一定复制第二个参数的null终止符,null终止符要在复制的字符数比字符中长度至少多1时才复制。例如,如果第二个参数为“test”,则只在strncpy的第三个参数至少为5("test"的长度加null终止符)时才复制null终止符。如果第三个参数大于5,则数组后面添加null终止符,直到写入第三个参数指定的总字符数。
函数原型 函数说明
char *strcpy(char *s1,const char *s2)
将字符串s2复制到字符数组s1中、返回s1的值
char *strncpy(char *s1,char *s2,size_t n)
将字符串s2中最多n个字符复制到字符数组s1中,返回s1的值
char*strcat(char *s1,const char *s2)
将字符串s2添加到字符串s1后面。s2的第一个字符重定义s1的null终止符。返回s1的值
char *strncat(char *s1,const char *s2,size_t n)
将字符串s2中最多n个字符添加到字符串s1后面。s2的第一个字符重定义s1的null终止符。返回s1的值
int strcmp(const char *s1,const char *s2)
比较字符串s1与字符串s2函数在s1等于、小于或大于s2时分别返回0、小于0或大于0的值
int strncmp(const char *s1,const char *s2,size_t n)
比较字符串s1中的n个字符与字符串s2。函数在s1等于、小于或大于s2时分别返回0、小于0或大于0的值
char *strtok(char *s1,const char *s2)
用一系列strtok调用将s1字符串标记化(将字符串分成各个逻辑组件,如同一行文本中的每个单词),用字符串s2所包含的字符分隔。
第一个参数,后面调用继续标记化同一字符串,包含NULL为第一个参数。每次调用时返回当前标记的指针。如果函数调用时不再有更多标记,则返回NULL
size_t strlen(const char *s)
确定字符串的长度,返回null终止符之前的字符数
第三个参数小于或等于第二个参数的宇符串长度时不在strncpy的第一个参数中添加null终止料可能造成严重的运行时错误。
图8.30的程序用strcpy将数组x中的整个字符串复制到数组y中,并用strncpy将数组x的前14个字符复制到数组2中。将null字符('')添加到数组z,因为程序中调用strncpy时没有写入null终止符(第三个参数小于或等于第二个参数的字符串长度)。
1 // Fig. 8.30:fig08_30.cpp
2 // using strcpy and strncpy
3 #include
4 #include
5
6 int main()
7 {
8 char x[] = "Happy Birthday to You";
9 char x[ 25 ], Z[ 15 ];
10
11 cout << "The string in array x is: "<< x
12 << "nThe string in array y is: "<< strcpy( y, x )
13 << 'n';
14 strncpy( z, x, 14 ); // does notcopy null character
15 z[ 14 ] = '';
16 cout << "The string in array z is: " << z <<endl;< p="">
17
18 return 0;
19 }
输出结果:
The string in array x is: Happy Birthday toYou
The stringin array y is: Happy Birthday to You
The string in array z is: Happy Birthday
图8.30使用stcpy和strncpy函数
函数strcat将第二个参数(字符串)添加到第一个参数(字符数组)中。第二个参数的第一个字符代替终止第一个参数中字符串的null终止符('')。程序员要保证存放第一个字符串的数组应足以存放第一个字符串、第二个字符串和null终止符(从第二个字符串复制)的合并长度。函数strncat从第二个字符串添加指定字符数到第一个字符串中,并在结果中添加null终止符。图8.31的程序演示了函数stcat和strncat。
图8.32用strcmp和strncmp比较三个字符串。函数strcmp一次一个字符地比较第一个字符串参数与第二个字符串参数。如果字符串相等,则函数返回0;如果第一个字符串小于第二个字符串,则函数返回负值;如果第一个字符串大于第二个字符串,则函数返回正值。函数strncmp等价于函数strcmp,只是strncmp只比较到指定字符数。函数strncmp不比较字符串中null终止符后面的字符。
程序打印每次函数调用返回的整数值。
假设strcmp和strncmp在参数相等时返回1是个逻辑错误。strcmp和strncmp在参数相等时返回0(C++的false值)。因此测试两个字符串的相等性时,strcmp和strncmp的结果应与0比较,确定字符串是否相等。
1 // Fig. 8.31:fig08_31.cpp
2 // using strcat and strncat
3 #include
4 #include
5
6 int main()
7 {
8 char s1[ 20 ] = "Happy ";
9 char s2[] = "New Year ";
10 char s3[ 40 ] = "";
11
12 cout << "s1 =" << s1 << "ns2 ="<< s2;
13 cout << "nstrcat(s1, S2) = "<< strcat( s1, s2 );
14 cout << "nstrncat(s3, s1, 6) =" << strncat( S3,s1, 6 );
15 cout << "nstrcat(s3, s1) = "<< strcat( S3, s1 )<< endl;
16
17 return 0;
18 }
输出结果:
s1 = Happy
s2 = New Year
strcat(sl, s2) = Happy New Year
$trncat{s3, s1, 6) = Happy
strcat(s3, s1) = Happy Happy New Year
图 8.31 使用strcat和strncat函数
1 // Fig. 8.32: fig08_32.cpp
2
3 #include
4 #include
5 #include
6
7 int main()
8 {
9 char *s1 = "Happy New Year";
10 char *s2 = "Happy New Year";
11 char *s3 = "Happy Holidays";
12
13 cout << "s1 =" << s1 << "ns2 ="<< s2
14 << "ns3 = "<< s3 << "nnstrcmp(s1,S2) ="
15 << setw( 2 ) << strcmp( s1, s2 )
16 << "nstrcmp(s1, s3) = "<< setw( 2 )
17 << strcmp( s1, s3 ) << "nstrcmp(s3, s1) ="
18 << setw( 2 ) << strcmp( s3, s1 );
19
20 cout << "nnstrncmp(s1, s3, 6) = "<< setw( 2 )
21 << strncmp( si, s3, 6) << "nstrncmp(sl, s3, 7) ="
22 << setw( 2 ) << strncmp( s1, s3, 7 )
23 << "nstrncmp(s3, s1, 7) ="
24 << setw( 2 ) << strncmp( s3, s1, 7 ) << endl;
25 return 0;
26 }
输出结果:
s1 = Happy New Year
s2 = Happy New Year
s3 = Happy Holidays
strcmp(s1, s2) = 0
strcmp (s1, s3) = 1
strcmp (s3, s1) = -1
strncmp(s1, s3, 6) = 0
strncmp(s1, s3, 7) = 1
strncmp(s3, s1, 7) = -1
图 8.32 使用strcmp和strncmp函数
要了解一个字符串大于或小于另一字符串的含义,可以考虑一系列姓氏的字母顺序表。读者一定会把“Jones"放在“Smith"之前,因为"Jones"的第一个字母在"Smith"的第一个字母之前。
但字母表中不仅有26个字母,而是个字母顺序表,每个字母在表中有特定位置。“z”不仅表示字母,而且是字母表中第二十六个字母。
计算机怎么知道一个字母在另一字母之前呢?所有字符在计算机中均表示为数字代码,计算机比较两个字符串时,实际上是比较字符串中字符的数字代码。
ASCII和EBCDIC称为字符编码(character code)或字符集(character set)。字符串和字符操作实际上是在操作相应的数字代码,而不是操作字符本身。因此C++中字符和小整数具有互换性。由于数字代码之间有大于、等于、小于的关系,因此可以将不同字符或字符串通过字符编码相互比较。
附录A列出了ASCII字符编码。
函数strtok将字符串分解为一系列标记(token)标记就是一系列用分隔符(delimiting chracter,通常是空格或标点符号)分开的字符。例如,在一行文本中,每个单词可以作为标记,空格是分隔符。
需要多次调用strtok才能将字符串分解为标记(假设字符串中包含多个标记)。第一次调用strtok包含两个参数,即要标记化的字符串和包含用来分隔标记的字符的字符串(即分隔符):在图9.33的例子中,下列语句:
tokenPtr = Strtok(string " ");
将tokenPtr赋给string中第一个标记的指针。strtok的第二个参数””表示string中的标记用空格分开。函数strtok搜索string中不是分隔符(空格)的第一个字符,这是第一个标记的开头。然后函数寻找字符串中的下一个分隔符,将其换成null(,w,)字符,这是当前标记的终点。函数strtok保存string中标记后面的下一个字符的指针,并返回当前标记的指针。
后面再调用strtok时,第一个参数为NULL,继续将string标记化。NULL参数表示调用strtok继续从string中上次调用strtok时保存的位置开始标记化。如果调用strtok时已经没有标记,则strtok返回NULL。图8.33的程序用strtok将字符串”This is sentence with 7 tokens”标记化。分别打印每个标记。注意strtok修改输入字符串,因此,如果调用strtok之后还要在程序中使用这个字符串,则应复制这个字符串。
没有认识到strtok修改正在标记化的字符串,调用sstrtok后还在程序中使用这个字符串(以为还是原字符串) 函数strlen取一个字符串作为参数,并返回字符串中的字符个数,长度中不包括null终止符。
图8.34的程序演示了函数strlen。
1 // Fig. 8.33:fig08_33.cpp
2 // Using strtok
3 #include
4 #include
5
6 int main()
7 {
8 char string[] = "This is a sentence with 7 tokens";
9 char *tokenPtr;
10
11 cout << "The string to be tokenized is:n" << string
12 << "nnThe tokens are:n";
13
14 tokenPtr = strtok( string, " " );
15
16 while ( tokenPtr != NULL ) {
17 cout << tokenPtr << 'n';
18 tokenPtr = strtok( NULL, " " );
19 }
20
21 return 0;
22 }
输出结果:
The string to be tokenized is:
This is a sentence with 7 tokens
The tokens are:
This
is
a
sentence
7
tokens
图 8.33 使用strtok 函数
1 // Fig. 8.34: fig08_34.cpp
2 // Using strlen
3 #include
4 #include
5
6 int main()
7 {
8 char *string1 = "abcdefghijklmnopqrstuvwxyz";
9 char *string2 = "four";
10 char string3 = "Boston";
11
12 cout << "The length of "" << string1
13 << "" is "<< strlen( string1 )
14 << "nThe length of "" << string2
15 << "" is" <<strlen( string2="" )<="" p="">
16 << "nThe length of "" << string3
17 << "" is "<< strlen( string3 ) <<endl;< p="">
18
19 return 0;
20 }
--end--
声明:本文章由网友投稿作为教育分享用途,如有侵权原作者可通过邮件及时和我们联系删除:freemanzk@qq.com