📄 05章 指针与字符串.txt
字号:
34 for (int pass = 0; pass < size - 1; pass++ )
35
36 for (int j = 0; j < size - 1; j++ )
37
38 if ( array[ j ] > array[ j + 1 ] )
39 swap( &array[ j ], &arra[ j + 1 ] );
4O }
41
42 void swap( int *element1Ptr, int *element2Ptr )
43 {
44 int hold = *elementlPtr;
45 *element1Ptr = *element2Ptr;
46 *element2Ptr = hold;
47 }
输出结果:
Data item: in Original Order
2 6 4 8 10 12 89 68 45 37
Data items in ascendinq Order
2 4 6 8 lO 12 37 45 68 89
图5.15 按引用调用的冒泡排序
注意函数bubbleSort中的几个特性。函数首部中将array声明为int* array而不是int array[],表示bubbleSort接收单下标数组作为参数(这些符号是可以互换的)。参数size声明为const以保证最低权限原则。尽管参数size接收main中数值的副本,且修改该副本并不改变main中的值,但是bubbleSort不必改变size即可完成任务。bubbleSort执行期间数组的长度保持不变,因此,size声明为const以保证不被修改。如果排序过程中修改数组长度,则排序算法无法正确执行。
bubbleSort函数体中包括了函数swap的原型,因为它是调用swap的惟一函数。将原型放在bubbleSort中,使得只能从bubbleSort正确地调用swap。其他函数要调用swap时无法访问正确的函数原型,这通常会造成语法错误,因为C++需要函数原型。
软件工程视点5.4
将函数原型放在其他函数中能保证最低权限原则,只能从该原型所在函数中正确地调用。
注意函数bubbleSort接收数组长度参数。函数必须知道数组长度才能排序数组。数组传递到函数时,函数接收数组第一个元素的内存地址。数组长度要单独传递给函数。
通过将函数bubbleSort定义成接收数组长度作为参数,可以让函数在排序任何长度单下标整型数组的程序中使用。
软件工程视点5.5
向函数传递数组时,同时传递数组长度(而不是在函数中建立数组长度信息),这样能使函数更加一般化,以便在许多程序中复用。
数组长度可以直接编程到函数内,这样会把函数的使用限制在特定长度的数组并减少其复用性。程序中只有处理特定长度的单下标整型数组时才能使用这个函数。
C++提供一元运算符sizeof,确定程序执行期间的数组长度或其他数据类型长度(字节数)。采用数组名时(如图5.16所示),sizeof算符返回数组中的总字节数为size_t类型的值,通常是unsigned int类型。这里使用的计算机将float类型的变量存放在4字节内存中,array声明为20个元素,因此array使用80字节内存空间。在接收数组参数的函数中采用指针参数时,sizeof运算符返回指针长度的字节数(4)而不是数组长度。
常见编程错误5. 7
在函数中用sizeof运算符寻找数组参数长度的字节数时返回指针长度的字节数而不是数组长度的字节数。
1 // Fig. 5.16: fig05_16.cpp
2 // Sizeof operator when used on an array name
3 // returns the number of bytes in the array.
4 #include <iostream.h>
5
6 size_t getSize( float * );
7
8 int main()
9 {
10 float array[ 20 ];
11
12 cout << "The number of bytes in the array is"
13 << sizeof( array )
14 << "\nThe number of bytes returned by getSize is"
15 << getSize( array ) << endl;
16
17 return 0;
18 }
19
20 size_t getSize( float *ptr )
21 {
22 return sizeof( ptr );
23 }
输出结果:
The number of bytes in the array is 80
The number of bytes returned by getSize is 4
图5.16 采用数组名时,sizeof运算符返回数组中的总字节数
数组中的元素个数也可以用两个sizeof操作的结果来确定。例如,考虑下列数组声明:
double realArray[ 22 ];
如果double数据类型的变量存放在8个字节的内存中,则数组realArray总共包含176个字节。要确定数组中的元素个数,可以用下列表达式:
sizeof realArray/sizeof(double)
这个表达式确定realArray数组中的字节数.并将这个值除以内存中存放—个double值的字节数,图5.17的程序用size运算符计算我们所用的个人计算机上存放每种标准数据类型时使用的字节数。
可移植性提示 5.3
存放特定数据类型时使用的字节数随系统的不同而不同。编写的程序依赖于数据类型长度而且要在几个计算机系统上运行时,用sizeof确定存放这种数椐类型时使用的宇节数。
1 // Fig. 5.17:fig05 17.cpp
2 // Demonstrating the sizeof operator
3 ~include <iostream.h>
4 ~include <iomanip.h>
5
76 ~nt main(}
8 char c;
9 short s;
10 iht i;
11 long 1;
12 float f;
13 double d;
14 long double ld;
15 int arras 20 ], *ptr = array;
16
17 cout << "sizeof c = " << sizeof c
18 << "\tsizeof(char) =" << sizeof( char )
19 << "\nsizeof s = "<< sizeof s
20 << "\tsizeof(short) = "<< sizeof( short )
2I "\nsizeof i = " << sizeof i
22 << "\tsizeof(int) =" << sizeof (int )
23 "\nsizeof 1 = "<<sizeof 1
24 << "\tsizeof(long) =" << sizeof( long )
25 << "\nsizeof f = "<< sizeof f
27 "\nsizeof d =" << sizof d
28 << "\tsizeof(double) =" << sizeof( double )
29 << "\nsizeof ld =" << sizeof ld
30 << "\tsizeof(long double) = "<< sizeof( long double )
31 << "\nsizeof array = "<< sizeof array
32 << "\nsizeof ptr = "<< sizeof ptr
33 << endl;
34 return 0;
35 }
输出结果:
sizeof c = 1 sizeof(char) = 1
sizeof s = 2 sizeof(short) = 2
sizeof i = 4 sizeof(int) = 4
sizeof l = 4 sizeof(long) = 4
sizeof f = 4 sizeof(float) = 4
sizeof d = 8 sizeof(double) = 8
sizeof ld = 8 sizeof(long double) = 8
sizeof array =
sizeof ptr = 4
图 5.17 用sizeof运算符计算存放每种标准数据类型时使用的字节数
sizeof运算符可以用于任何变量名、类型名或常量值。用于变量名(不是数组名)和常量值时,返回存放特定变量或常量类型所用的字节数。注意,如果提供类型名操作数,则sizeof使用的括号是必需的;如果提供变量名操作数,则sizeof使用的括号不是必需的。记住,sizeof是个运算符而不是个函数。
常见编程错误5.8
如果提供类型名操作数,而不在sizeof操作中使用括号则是个语法错误。
性能提示5.2
sizeof属于编译时的一元运算符,而不是个执行时函数。这样,使用sizeof并不对执行性能遣成不良影响。
5.7 指针表达式与指针算法
指针是算术表达式、赋值表达式和比较表达式中的有效操作数。但是,通常并不是这些表达式中使用的所有运算符都在指针变量中有效。本节介绍可以用指针操作数的运算符及这些运算符的用法。
只有少量操作可以对指针进行。指针可以自增(++)或自减(--),整数可以加进指针中(+或+=),也可以从指针中减去整数(-或-=),指针可以减去另一指针。
假设声明了数组int v[5],其第一个元素位于内存地址3000。假设指针vPtr已经初始化为指向数组v[0],即vPtr的值为3000。图5.18演示了在32位的机器中的这种情况。注意vPtr可以初始化为数组v的指针,如下所示:
vPtr = v;
vPtr = &v[ 0 ]
图5.18 数组v和指向v的指针变量vPtr
可移植性提示5.4
如今的大多数计算机都是16位或32位,有些较新的计算机用8字节整数。由于指针算法的结果取决于指针所指对象的长度,因此指针算法与机器有关。
按照传统算法,3000+2得到3002。而指针算法通常不是这样。将指针增加或减去一个整数时,指针并不是直接增加或减去这个整数,而是加上指针所指对象长度的这个倍数。这些字节数取决于对象的数据类型。例如,下列语句:
vPtr += 2;
在用4字节内存空间存储整数时得到的值为3008(3000+2*4)。对数组v,这时vPtr指向v[2]如图5.19。如果用2字节内存空间,则上述结果得到3004(3000+2*2)。如果数组为不同数据类型,则上述语句将指针递增指针所指对象长度的2倍。对字符数组进行指针算法时,结果与普通算法相同,因为每个字符的长度为一个字节。
图 5.19经过指针运算之后的vPtr
如果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。指针算法只在对数组进行时才有意义。我们不能假设两个相同类型的变量在内存中相邻的地址存放,除非它们是数组的相邻元素。
常见编程错误 5.9
对于不引用数组值的指针采用指针算法通常是个逻辑错误。
常见编程错误5.10
将两个不引用同一数组的指针相加或相减通常是个逻辑错误。
常见编程错误5.11
使用指针算法时超过数组边界通常是个逻辑错误。
如果两个指针的类型相同,则可以将一个指针赋给另一个指针。否则要用强制类型转换运算符将赋值语句右边的指针值转换为赋值语句左边的指针值。这个规则的例外是void的指针(即void),该指针是个一般性指针,可以表示任何指针类型。所有指针类型都可以赋给void指针而不需要类型转换。但是,void指针不能直接赋给另一类型的指针,而要先将void指针转换为正确的指针类型。
void*指针不能复引用。例如,编译器知道int指针指向32位机器中的4字节内存,但void指针只是包含未知数据类型的内存地址,指针所指的字节数是编译器所不知道的。编泽器要知道数据类型才能确定该指针复引用时的字节数。对于void指针,无法从类型确定字节数。
常见编程错误5.12
除了void*类型外,将一种类型的指针赋给另一种类型的指针而不先将一种类型的指针转换为另一种类型的指针是个语法错误。
常见编程错误5.13
复引用void*指针是个语法错误。
指针可以用相等和关系运算符比较,但这种比较只在对相同数组成员进行时才有意义。指针比较是对指针存放的地址进行比较。例如,比较指向同一数组的两个指针可以表示一个指针所指的元素号比另一个指针所指的元素号更高。指针比较常用于确定指针是否为0。
5.8 指针与数组的关系
C++中指针与数组关系密切,几乎可以互换使用。数组名可以看成常量指针,指针可以进行任何有关数组下标的操作。
编程技巧5. 4
操作数组时用数组符号而用指针符号。尽管程序编译时间可能稍长一些.但程序更加清晰。
假设声明了整数数组b[ 5 ]和整数指针变量bPtr。由于数组名(不带下标)是数组第一个元素的指针.因此可以用下列语句将bPtr设置为b数组第一个元素的地址:
bPtr = b;
这等于取数组第一个元素的地址,如下所示:
bPtr=&b[ O ];
数组元素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
是无效的,因为该表达式试图用指针算法修改数组名的值。
常见编程错误5.14
尽管数组是指向数组开头的S针,而指针可以在算术表达式中修改,但数组名不可以在算术表达式中修改,囚为数组名实际上是个常量指针。
图5. 20的程序用我们介绍的四种方法引用数组元素(数组下标、用数组名作为指针的指针/偏移量符号、指针下标和指针的指针/偏移量符号,打印数组的的4个元素)。
要演示数组和指针的互换性,还可以看看程序5.21中的两个字符串复制函数copy1和copy2。
这两个函数都是将字符串复制到字符数组中。比较copy1和copy2的函数原型可以发现,函数基本相同(由于数组和指针具有互换性)。这些函数完成相同的任务,但用不同方法实现。
1 // Fig. 5.20: f~g05_20.cpp
2 // Using subscripting and pointer notations with arrays
3
4 #include <iostream.h>
5
6 int main()
7{
8 int b[] = { 10, 20, 30, 40 } ;
9 int *bPtr = b; // set bPtr to point to array b
10
11 cout << "Array b printed with:\n"
12 << "Array subscript notation\n";
13
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -