📄 c++b.txt
字号:
下列语句:
++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
14 for(int i = 0; i < 4; i++ ),
15 cou << "b[ " << i << "] = << b[ i ] << '\n';
16
17
18 cout << "\nPointer/offset notation where\n"
19 << "the pointer is the array name\n";
2O
21 for (int offset = 0; offset < 4; offset++ )
22 cout << "* (b +" << offset << ") ="
23 << *( b + offset ) << '\n';
24
25
26 cout << "\nPointer subscript notation\n";
28 for ( i = 0; i < 4; i++ )
29 cout << "bPtr[" << i << "] =" << bPtr[ i ] << '\n';
31 cout << "\nPointer/offset notation\n";
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
图5.20 用我们介绍的四种方法引用数组元素
1 // Fig. 5.21: figOS_21.cpp
2 // Copying a string using array notation
3 // and pointer notation.
4 #include <iostream.h>
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 ] ) != '\0'; 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 ) != '\0'; s1++, s2++ )
34 ; // do nothing in body
35 }
输出结果:
string1 = Hello
string3 = Good Bye
图5.21 使用数组和指针符号复制字符串
函数copy1用数组下标符号将s2中的字符串复制到字符数组s1中。函数声明一个作为数组下标的整型计数器变量i。for结构的首部进行整个复制操作,而for结构体本身是个空结构。首部中指定i初始化为0,并在每次循环时加1。for的条件“(s1[i]=s2[i])!='\0',从s2向s1一次一个字符地进行复制操作。遇到s2中的null终止符时,将其赋给s1,循环终止,因为null终止符等于'\0'。
记住.赋值语句的值是赋给左边参数的值。
函数copy2用指针和指针算法将s2中的字符串复制到s1字符数组。同样是在for结构的首部进行整个复制操作.首部没有任何变量初始化。和copy1中一样,条件(*s1=*s1)!='\0'进行复制操作。
复引用指针s2,产生的字符赋给复引用的指针s1。进行条件中的赋值之后,指针分别移到指向s1数组的下一个元素和字符串s2的下一个字符。遇到s2中的null终止符时,将其赋给s1,循环终止。
注意copy1和copy2的第一个参数应当是足够大的数组,应能放下第二个参数中的字符串,否则可能会在写人数组边界以外的内存地址时发生错误。另外,注意每个函数中的第二个参数声明为const char*(常量字符串)。在两个函数中,第二个参数都复制到第一个参数,一次一个地从第二个参数复制字符,但不对字符做任何修改。因此,第二个参数声明为常量值的指针,实施最低权限原则。两个函数都不需要修改第二个参数,因此不向这两个函数提供修改第二个参数的功能。
5.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数组中,其实数组中只存放指针(如图5.22)。每个指针指向对应字符串中的第一个字符。这样,尽管:suit数组是定长的,但可以访问任意长度的字符串,这是C++强大的数据结构功能所带来的灵活性。
?????????????????????流敢???潃癮牥整??金??吠楲污瘠牥楳湯??????????????????
图5. 22 suit数组的图形表示
suit字符串可以放在双下标数组中,每一行表示一个suit,每一列表示suit名的第一个字符、这种数据结构每一行应有固定列数,能够放下最长的字符串。因此,存放大量字符串而大部分字符串长度均比最长字符串短许多时,可能浪费很多内存空间。我们将在下一节用字符串数组帮助整理一副牌。
5.10 实例研究:洗牌与发牌
本节用随机数产生器开发一个洗牌与发牌程序。这个程序可以用于实现玩某种牌的游戏程序。
为了解决一些微妙的性能问题,我们故意用次优洗牌与发牌算法。练习中要开发更有效的算法。
利用自上而下逐步完善的方法,我们开发一个程序,洗52张牌并发52张牌。自上而下逐步完善的方法在解决大而复杂的问题时特别有用。
我们用4 x 13的双下标数组deck表示要玩的牌(如图5.23)。行表示花色,0表示红心,1表示方块,2表示梅花,3表示黑桃。列表示牌的面值,0到9对应A到10,10到12对应J、Q、K。我们要装入字符串数组suit,用字符串表示4个花色,用字符串数组face的字符串表示13张牌的面值。
这堆牌可以进行如下的洗牌:首先将数组deck清空,然后随机选择row(0--3)和column(0—12)。将数字插入数组元素deck[row][column](表示这个牌是洗出的牌中要发的第一张牌)。继续这个过程,在deck数组中随机插入数字2、3、…52,表示洗出的牌中要发的第二、三、…、五十二张牌。在deck数组填上牌号时,一张牌可能选择两次,即选择的时候deck[row][column]为非0值。
忽略这个选择,随机重复选择其他row和colunm,直到找出未选择的牌。最后,在52个deck元素中插入1到52的值。这时,就完全洗好了牌。
?????????????????????流敢???潃癮牥整??金??吠楲污瘠牥楳湯??????????????????
图5.23 双下标数组deck表示要玩的牌
这个洗牌算法在随机重复选择已经洗过的牌时可能需要无限长的时间。这种现象称为无穷延迟(indefinite postponement)。练习中将介绍更好的洗牌算法,消陈无穷延迟。
性能提示5.3
有时自然方式的算法可能包含无穷延迟等微妙的性能问题,应寻找能避免无穷延迟的算法。
要发第一张牌,我们要寻找匹配1的deck[row][column]元素,这是用嵌套for结构进行的,n,w取。到3t column取。到12。这个数组元素对应哪种牌呢?suit数组预先装入了四种花色,因此要取花色,只要打印字符串suit[row];同样,要取牌值,只要打印字符串face[column]还要打印字符串”of",按正确的顺序打印,即可得到每张牌如”King of Clubs"、”Ace of Diamonds',等等。
下面用自上而下逐步完善的方法进行。顶层为:
Shuffle and deal 52 cards
第一步完善结果为:
Initialize the suit array
Initialize the face array
Initialize the deck array
Shuffle the deck
Deal 52 cards
”Shumelhedeck”可以展开成:
For each of the 52 cards
Place card number in randomly selected unoccupied slot of deck
"Deal 52 cards" 可以展开成:
For each of the 52 cards
Find card number in deck array and print face and suit of card
合在一起得到第二步完善结果为:
Initialize the suit array
Initialize the face array
Initialize the deck array
For each of the 52 cards
Place card number in randomly selected unoccupied slot of deck
For each of the 52 cards
Find card number in deck array and print face and suit of card
"Place eard numberin radomly selected unoccupied slot of deck" 可以展开成:
Choose slot of deck randomly
While chosen slot of deck has been previously chosen
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -