指针【C语言课件】_第1页
指针【C语言课件】_第2页
指针【C语言课件】_第3页
指针【C语言课件】_第4页
指针【C语言课件】_第5页
已阅读5页,还剩83页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、指 针 指针变量的声明和初始化 指针运算符 函数的传引用调用 对指针使用const 限定符 使用传引用调用的泡沫排序法 指针表达式和指针的算术运算 指针和数组的关系 指针数组 实例研究 指向函数的指针指针变量的声明和初始化 本章要讨论C 语言功能最强大的特点之一 指针。指针是C 语言中最难掌握的概念。指针能够模拟传引用调用,并且能够建立和操作动态数据结构(即能够增长和缩小的数据结构)。本章介绍指针的基本概念7count7countCount 直接引用一个值为7 的变量countPtrcountPtr 间接引用一个值为 7 的变量 图7. 1 直接和间接引用一个变量 指针是把内存地址作为其值的变

2、量 变量通常直接包含一个具体的值,而指针包含的是拥有具体值的变量的地址。从这个意义上说,变量名直接引用了一个值,而指针是间接地引用了一个值(见图7.1)。通过指针引用某个值称为“间接引用” 和其他变量一样,指针(有时也称作指针变量)必须在使用前声明指针变量的声明和初始化 声明语句 int *countPtr, count;声明了int * 类型的的变量countPtr,即声明了一个指向int 型值的指针在这里,int 是相应指针的基类型,读法为“countPtr 是一个指向int 型整数值的指针,或“countPtr 指向整数类型值 。注意:出现在该语句中的变量count 被声明为是一个int

3、 型变量,而不是指向int 型整数值的指针,声明语句中的* 只用于countPtr。在声明语句中以这种方式使用*表示被声明的变量是一个指针 指针可被声明为指向任何数据类型 间接引用运算符 * 不针对声明语句中的所有变量。每一个指针都必须在其名字前面用前缀*声明 在指针变量名中包含字母Ptr 可清晰地说明这些变量是指针,对它们需要用适宜的方式进行处理指针变量的声明和初始化 指针应当用声明语句或赋值语句初始化。可以把指针初始化为0、NULL 或某个地址。具有值NULL 的指针不指向任何值。NULL 是在头文件以及其它几个头文件中定义的符号常量。把一个指针初始化为0等价于把它初始化为NULL, 但是

4、,用NULL 更好。 当把0 赋给指针时,编译程序先把0 转换为指向适宜数据类型的指针 值0 是唯一能够直接赋给指针变量的整数值 对指针初始化可防止相应程序运行时出现意想不到的结果指针运算符 运算符&或称为“地址运算符是一个单目运算符,它返回其操作数的地址。例如,假定有声明语句 int y = 5; int * yPtr;那么语句 yPtr = &y;把变量y 的地址赋给指针变量yPtr。变量yPtr 可称为“指向变量y 。图7.2 用图解方式反映了执行完上述语句后的内存现象y5yPtr图7. 2 用图形表示指向内存中的某个整型变量的指针指针运算符 假定变量y 存储在内存单元600000 中,

5、变量yPtr 存储在内存单元500000 中,yPtr 在内存中是如何表示的6000005500000600000yPtry图7. 3 y 和 yPtr 在内存中的表示 地址运算符的操作数必须是一个变量,不能把地址运算符用于常量、表达式或存储类别被声明为register 的变量上 运算符* 通常称为“间接引用运算符或“复引用运算符 ,它返回其操作数即一个指针所指向的对象的值。例如,语句 printf%d, *yPtr;打印变量y 的值即5。* 的这种用法称为“指针的复引用指针运算符 注意:如果被复引用的指针没有被正确地初始化或没有指向具体的内存单元,都会导致致命的执行错误或意外地修改重要的数据

6、。如果修改了重要的数据,虽然程序能够完成运行,但是,程序运行所产生的结果是非预期的,甚至是无法理解的 图7.4中的程序演示了指针运算符的用法。printf 的转换说明符%p 以十六进制的整数形式输出内存地址。从输出结果可以看到,a 的地址和aPtr 的值是相等的,因此,可以确信,a 的地址确实赋给了指针变量aPtr 运算符& 和 * 互补,把这两个运算符以任意顺序连续地用在aPtr 上,打印出的结果是相同的 表7.1 中列出了迄今学过的运算符的优先级和结合性 /* 运算符& 和 * 的用法 */ # include main() int a; /* a 是一个整型变量 */ int * aPt

7、r; /* aPtr 是一个指向 int 类型整数的指针 */ a = 7; aPtr = &a; printf(”The address of a is %p n”, &a); printf(”The value of aPtr is %p n n”, aPtr); printf(”The value of a is %d n”, a); printf(”The value of *aPtr is %d nn”, *aPtr); printf(”Proving that * and & are complements of” ” each other. n& *aPtr = %p n * &

8、aPtr = %pn”, & *aPtr, * & aPtr); return 0; 输出结果: The address of a is FFF4 The value of aPtr is FFF4 The value of a is 7 The value of *aPtr is 7 Proving that * and & are complements of each other. & *aPtr = FFF4 * &aPtr = FFF4图 7. 4 指针运算符 & 和 *表7. 1 运算符的优先级 运 算 符 结 合 性 类 型() 从左到右 最高优先级 + - + - ! * &(

9、类型) 从右到左 单目运算符* / % 从左到右 乘法运算符+ - 从左到右 加法运算符 = 从左到右 关系运算符= != 从左到右 相等测试运算符& 从左到右 逻辑与 | 从左到右 逻辑或? : 从右到左 条件运算符 = += -= *= /= %= 从右到左 赋值运算符 , 从左到右 逗号运算符指针运算符函数的传引用调用 给函数传递参数的方式有两种,即传值和传引用。C 语言中的所有函数调用都是传值调用。正如“函数一章所介绍的那样,使用return 语句可以把一个值从被调函数返回给主调函数或者从被调函数返回控制权而不传回一个值。许多函数要求能够修改主调函数中的一个或多个值,或能够传递指向大型

10、对象的指针以防止开销很大的传值调用传值调用需要对被传送对象做一份拷贝 C 语言用指针和间接引用运算符模拟传引用调用。在调用某个函数时,如果需要在被调函数中修改主调函数中作为实参的对象的值,应当给被调函数传递相应数据对象的地址 通常在需要修改值的变量的前面使用地址运算符& 实现这一点。传递数组不需要使用运算符&, 因为C 自动传递数组起始内存单元的地址数组名与&arrayName0 等价 当把变量的地址传递给被调函数时,可以在被调函数中用间接引用运算符* 修改主调函数中相应变量的值函数的传引用调用 图7.5和图7.6中的程序完成同样的功能,但它们调用完成同样功能的函数使用了不同的参数传递机制,由

11、此,可通过比照来了解它们间的差异 在这两个程序中,都设置了求一个整数值立方的函数,分别被命名为cubeByValue 和 cubeByReference ,它们虽然完成相同的功能,但参数设置不同,由此使得主调函数引用它们的方式有所不同 图7.5 中的程序用传值调用方式把变量number 的值传递给函数cubeByValue。函数cubeByValue 计算了该参数的立方并用return 语句把结果传会给调用它的main 函数,然后在main 函数中将此结果赋给了变量number number 的值传递给函数cubeByReference ,通过作为参数的指针的作用,该函数以计算结果改变了mai

12、n 函数中number 的值。 /* 用传值方式求变量的立方 */ # include int cubeByValue(int); main() int number = 5; printf(”The original value of number is %d n”, number); number = cubeByValue(number); printf(”The new value of number is %d n”, number); return 0; int cubeByValue(int n) return n * n * n; /*计算局部变量n 的立方 */ 输出结果:

13、The original value of number is 5 The new value of number is 125图 7. 5 用传值方式求变量的立方 /* 用传引用方式计算变量的立方 */ # include void cubeByReference(int *); main() int number = 5; printf( ”The original value of number is %d n”, number ) cubeByReference(&number); printf(”The new value of number is %d n”, number); r

14、eturn 0; void cubeByReference(int *nPtr) *nPtr = *nPtr * *nPtr * *nPtr ; /* 计算main 函数中变量的立方*/ 输出结果: The original value of number is 5 The new value of number is 125图 7. 6 用传引用方式计算变量的立方main() int number = 5; number = cubeByValue(number); 5numbermain() int number = 5; number = cubeByValue(number); 5nu

15、mbermain() int number = 5; number = cubeByValue(number); 5number int cubeByValue(int n) return n * n * n; 没有确定值nmain 调用cubeByValue 之前 main 调用cubeByValue 之后 int cubeByValue(int n) return n * n * n; 5nn * n * n125 int cubeByValue(int n) return 5ncubeByValue 函数计算了n 的立方之后 main() int number = 5; number =

16、 5numbercubeByValue(number);125int cubeByValue(int n) return n * n * n; 没有确定值n没有确定值nmain() int number = 5; number = cubeByValue(number); 125numberint cubeByValue(int n) return n * n * n; 从cubeByValue 返回到main 后main 完成给number 赋值之后图7. 7 典型的传值调用分析函数的传引用调用 void cubeByReference(int *nPtr) *nPtr = *nPtr *

17、*nPtr * *nPtr ; 不确定nPtr main() int number = 5; cubeByReference(&number); 5numbermain() int number = 5; cubeByReference(&number); 5number void cubeByReference(int *nPtr) *nPtr = *nPtr * *nPtr * *nPtr ; nPtr 以传引用方式调用cubeByReference 之前以传引用方式调用cubeByReference 之后、计算 *nPtr 立方之前main() int number = 5; cubeB

18、yReference(&number); 125number void cubeByReference(int *nPtr) nPtr *nPtr = *nPtr * *nPtr * *nPtr; 125计算完 *nPtr 的立方之后图 7. 8 典型的传引用调用分析函数的传引用调用 把地址作为参数的函数必须定义一个接收地址的指针参数。例如,函数cubeByReference 的头部为 void cubeByReferenceint *nPtr它说明:函数cubeByReference 的参数要求接收一个整型变量的地址,该地址存储在局部变量nPtr 中,并且函数没有返回值 函数的传引用调用 函

19、数cubeByReference 的原型在括号中包含了int *, 为: void cubeByReferenceint *;和其他变量类型一样,函数原型中也没有必要包含指针名。编译程序会忽略因为阅读文档方便而在函数原型中包含的名字 对要求用一个一维数组作为参数的函数来说,函数的头部和函数原型的参数列表中可以用指针表示一维数组。编译程序对函数是接收一个指针还是一个一维数组实不加区分的。当遇到形如一维数组int b 的函数参数时,编译程序把该参数转换为指针形式int *b. 这两种形式是可以互换的 建议:除非主调函数明确地要求被调函数修改主调函数环境中的参数变量,否那么,用传值调用给函数传递参数

20、。即如上述问题,应尽量采用图7.5 中的方式而不采用图7.6中的方式,虽然它们能够到达相同的目的对指针使用const 限定符 const 限定符通知编译程序禁止修改某个特定的变量。在早期版本的C语言中没有const 限定符,是后来添加的 使用const 限定赋限制被调函数的行为有利于提高主调函数与被调函数间的相对独立性,使程序易于修改和调试 用const 作为被调函数的参数限定符有6种可能的情况, 两种用于传值调用,四种用于传引用调用。选择使用其中的哪一种用于确定的函数参数,要做多方面的考虑。在现代,根本的指导性原那么应当是:为了完成指定的任务使被调函数实现确定的功能,应授予函数以操作参数值的

21、足够的权限,但不给之以更多的权限最低访问权原那么 如果传递给函数的值没有或不应该在函数体中被修改,应当用const 声明相应参数以防止相应对象被函数被调函数的执行所意外地修改。这样,如果函数试图修改用const声明的值,编译程序会捕捉到这种情况,并根据特定的编译程序或者给出警告信息或者给出错误消息对指针使用const 限定符 传值调用只能在主调函数中修改一个值。要在主调函数中修改多个值必须使用传引用调用 给函数传递的指针有如下4种情况:指向非常量数据的非常量指针,指向非常量数据的常量指针,指向常量数据的非常量指针,指向常量数据的常量指针。每一种情况都有不同级别的访问权 指向非常量数据的非常量指

22、针具有最高访问权。这种情况允许通过指针复引用间接访问来修改数据并能够通过修改指针使它指向其它数据项 把非常量指针传递给非常量数据不包含关键字const。这种指针可能会用来接收一个字符串参数一维数组convertToUppercase 把参数声明为指向一个非常量数据schar *s的非常量指针,函数用指针算法处理了字符串s 中的每一个字符, 将其中的小写字母转化为大写字母在ASCII 码表中,两者间相差32 /* 用指向非常量数据的非常量指针 */ /* 把字符串中的小写字母转换为大写 */ # include void convertToUppercase(char *); main() ch

23、ar string = ”characters”; printf(”The string before conversion is:%sn”, string); convertToUppercase(string); printf(”The string after conversion is: %sn”, string); return 0; 对指针使用const 限定符 void convertToUppercase(char * s) while(*s != 0) if(*s = a & *s = z) *s -= 32; /* 把小写字母转换为大写字母 */ +s; /* 把s 加 1

24、 使它指向下一个字符 */ 输出结果: The string before conversion is: characters The string after conversion is CHARACTERS图 7. 9 用指向非常量数据的非常量指针把字符串中的小写字母转换为大写 可以修改指向常量数据的非常量指针使它指向具有合适类型的任何数据项,但是,它所指向的数据是不能被修改的。这种指针可能会用来接收一个数组参数,然后让函数在不修改数据的情况下处理数组的每一个元素。例如,图7.10中的函数printCharacters 把参数s 声明为类型const char *(s 是一个指向字符常量的

25、指针)。函数用一个for 结构输出字符串中的每个字符,指针s 被在循环体中修改 /* 用指向常量数据的指针一次打印字符串中的一个字符 */ # include void printCharacters(const char *); main() char string = ”print characters of a string”; printf(”The string is: n”); printCharacters(string); putchar(n); return 0; 对指针使用const 限定符 void printCharacters(const char *s) for(;

26、 *s != 0; s+)/* 无循环控制变量的初始化环节 */ putchar(*s); 输出结果: The string is: print characters of a string图 7. 10 用指向常量数据的指针一次打印字符串中的一个字符对指针使用const 限定符 在以指向常量数据的非常量指针作为函数的参数时注意:在这里,指针数据在被调函数中是可被修改的,它是非常量 /* 试图用指向常量数据的非常量指针修改数据 */ #include void f(const int *); main() int y; f(&y); /* 函数f 试图非法地修改数据 */ return 0;

27、void f(const int *x) *x = 100; /* 不能修改常量对象 */ 编译输出: Compiling FIG7_11.C: Error FIG7_11.C11: Cannot modify const object Warning FIG7_11.C12: Parameter x is never used图7.11 试图用指向常量数据的非常量指针修改数据对指针使用const 限定符 由前面的相关讨论可知,数组是用一个名字存储许多具有相同类型的相关数据项的聚合数据类型。后面相关章节将要讨论的“结构 也称“结构体是另一种聚合数据类型,它能够用同一个名字存储许多不同类型的相关

28、数据项,例如存储某高校每一个学生的信息 当一个函数需要接收一个数组一维数组作为参数时,由前面相关讨论可知:一方面,主调函数可以用一个数组名作为实参来实现函数调用;另一方面,编译程序会将被调函数接收数组的参数理解为一个指针如int *、char * 等等。此时,数组名即是一个指向非常量的常量指针在被调函数中,这个数组名是常量,不能改变其值;而其所指向的数组那么是非常量的,被调函数可以通过下标来改变相应数组的元素值 相应的,当一个函数需要接收一个结构作为参数时,在 C语言中,是以传值方式将结构的一份拷贝传递给被调函数的从后面有关章节可以了解这一点对指针使用const 限定符 由于一个结构通常包括许

29、多成员,在程序设计中,一种可用的途径是在相应被调函数中使用指向结构的指针来作为接收结构的参数以减少函数调用时的数据传送量此时,相应主调函数中应取得指向相应结构的指针作为函数调用中的实参,这时,此指针也是一个指向非常量的常量指针类似于数组,被调函数不能改变这个指针值,但可以改变结构的成员 以上是指向非常量的常量指针在实用程序设计中的典型应用如果单纯从计算机语言的角度来看,指向非常量数据的常量指针总是指向同一个内存单元,其中的数据可以通过该指针修改,但指针本身不能被修改。声明为const 的指针必须在声明时初始化如果它是函数的参数,用传递给函数的指针初始化。图7.12 中, 指针ptr 被声明为i

30、nt * const 类型读法:指针ptr 是一个指向某整数的常量指针。程序中试图修改它,编译程序给出了相应的错误信息 /* 试图修改指向非常量数据的常量指针 */ # include main() int x, y; int * const ptr = &x; ptr = &y; return 0; 编译输出: Compiling FIG7_12.C: Error FIG7_12.C 9: Cannot modify a const objiect Warning FIG7_12.C11:ptr is assigned a value that is never used Warning F

31、IG7_12.C11:y is declared but never used图7. 12 试图修改指向非常量数据的常量指针对指针使用const 限定符 应当注意,对于指向非常量的常量指针,指针所指的对象对于相应的函数来说是可以修改的例如,函数可以通过使用下标来修改指针所指向的数组的元素,因为指针本身虽然是常量,不能被改变,但其所指向的对象是非常量的,是可修改的 指向常量数据的常量指针具有最低访问权。对于相应函数来说,不仅指针是常量,其值是不可改变的;指针所指向的对象也是常量,其值也是不可改变的。然而,按照相应的指针去读取其所指向的对象是可以的 指向常量的常量指针在实用程序设计中典型的用法是:

32、一个函数需要通过一个指针型参数如数组的第一个元素的地址来接收一个数组或结构,但函数并不改变数组元素的值或结构的成员,仅简单地使用相应的数据项值const int * const 类型的指针变量ptr,但程序试图修改ptr 的值和其指向的数据,因此,编译程序给出了相应的错误信息 /* 试图修改指向常量数据的常量指针 */ # include main() int x = 5, y; const int * const ptr = &x; *ptr = 7; ptr = &y; return 0; 编译输出: Compiling FIG7_13.C: Error FIG7_13.C8: Canno

33、t modify a const object Error FIG7_13.C9: Cannot modify a const objiect Warning FIG7_13.C11:ptr is assigned a value that is never used Warning FIG7_13.C11:y is deciared but never used图7. 13 试图修改指向常量数据的常量指针使用传引用调用的泡沫排序法 下面用两个函数bubbleSort 和swap 修改图6.14 中的泡沫排序法程序。函数bubbleSort 用来排序数组,它调用函数swap 交换数组元素arr

34、ayj 和arrayj + 1 见图7.14。因为函数之间的信息是相互隐藏的, 所以swap 无权访问bubbleSort 中的单个数组元素 由于bubbleSort 要让函数swap 访问要交换的数组元素,因此bubbleSort 把数组元素通过传引用的方式传递给函数swap即明确地传递数组元素的地址。尽管整个数组可自动以传引用方式传递, 但数组元素是标量,通常以传值方式传递。故此,bubbleSort 在调用swap 时对数组元素使用地址运算符&,从而实现了传引用调用: swap&arrayj, &arrayj + 1;函数swap 用指针element1Ptr 接收&arrayj 。尽管

35、swap 不知道数组元素的名字arrayj ,但通过间接引用*element1Ptr,它实际上访问了bubbleSort 中的arrayj 使用传引用调用的泡沫排序法 尽管函数swap 中不允许使用如下语句: temp = arrayj; arrayj = arrayj + 1; arrayj +1 = temp;来实现arrayj 值与arrayj + 1 值的交换,但是,使用下面的语句可到达同样的效果见图7.14: temp = *element1Ptr ; *element1Ptr = *element2Ptr ; *element2Ptr = temp;在这里,由于指针变量elemen

36、t1Ptr 中保存的是主调函数传送给它的&arrayj ,指针变量element2Ptr 中保存的是主调函数传送给它的&arrayj + 1 ,因此,*element1Ptr 就等价于arrayj , *element2Ptr 就等价于arrayj + 1 ,由此实现了arrayj 与arrayj + 1 间的值交换使用传引用调用的泡沫排序法 函数bubbleSort 有几个特点应该引起注意: 函数的头部把ayyay 声明为int *array 而不是int array 这两种写法是可以互换的,说明该参数接收一个int 型的一维数组 为使参数size 不在函数体中被修改,参数size 用con

37、st 予以声明。尽管size 接收main 中某个值的拷贝,且修改这个值不能改变main 中相应的值,但是bubbleSort 为完成其任务并不需要修改size, 数组的大小在执行bubbleSort 的过程中无需也不能被修改。因此,为保证size 的值不被修改,size 被声明为const 由于只有函数bubbleSort 函数调用函数swap, 故将swap 的函数原型放在bubbleSort 的函数体中,从而保证了只能在bubbleSort 中才能正确地调用swap 把函数原型放在其它函数定义中可保证只在出现函数原型的函数中才能正确地调用该函数 /* 本程序把存储在数组中的值按升序排序,

38、并打印排序后的数组 */ # include # define SIZE 10 void bubbleSort(int *, const int); main() int i, aSIZE = 2, 6, 4, 8, 10, 12, 89, 68, 45, 37; printf(”Data items in original order n”); for(i = 0; i = SIZE 1; i+) printf(”%4d”, ai); bubbleSort(a, SIZE); /* 数组排序 */ printf(”nData items in ascending order n”); for

39、(i = 0; i = SIZE 1; i+) printf(”%4d”, ai); printf(”n”); return 0; void bubbleSort(int *array, const int size) int pass, j; void swap(int *, int *); for(pass = 1; pass = size 1; pass+) for(j = 0; j arrayj + 1) swap(&arrayj, &arrayj + 1); void swap(int *element1Ptr, int *element2Ptr) int temp; temp =

40、*element1Ptr ; *element1Ptr = *element2Ptr ; *element2Ptr = temp; 输出结果: 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图 7.14 用传引用调用实现泡沫排序 为了排序数组,函数bubbleSort 必须知道数组的大小,所以把数组大小作为其参数之一。在把数组传递给某个函数的时候,数组第一个元素的内存地址也就传递给了函数。该地址没有给函数提供关于数组元素个

41、数的任何信息,因此编程人员必须给函数提供数组的大小 程序明确地把数组大小传递给函数bubbleSort。这样做有两个好处,一是增强了函数的相对独立性,二是使实现函数的程序的质量得以改善。这种函数可以被任何程序用来排序一维int 型数组,其数组可以是任意大小 在给函数传递数组时也传递数组大小。这可以使函数具有更好的通用性。通用的函数经常能够用于许多程序中使用传引用调用的泡沫排序法 也可以把数组的大小存储在某个能够被整个程序访问到的全局变量中,由于不需要把数组大小的拷贝传递个函数,所以效率更高。但是,其它需要排序int 型数组的程序可能并没有同样的全局变量,这样也就限制了函数的通用性 全局变量的存

42、在降低了函数的相对独立性,有违人们所提倡的最低访问权原那么,因而在现代程序开发中不提倡使用 设置全局变量所带来的好处是减少了参数传递所带来的有关时空资源开销尽管这在现代计算机应用中通常是微缺乏道的 数组的大小也可以直接在函数体中指定。但这样做限制了函数的通用性 为了在编译时确定数组或其它数据类型的大小所占字节数C 语言提供了专门的单目运算符sizeof。在用于数组名时见图7.15中的程序, sizeof 运算符以整数形式返回数组占用的字节数float 变量:4 字节 /* sizeof 运算符用于数组名时返回数组占用的字节数 */ # include main() float array20;

43、 printf(”The number of bytes in the array is %d n”, sizeof(array); return 0; 输出结果: The number of bytes in the array is 80 图 7.15 sizeof 运算符用于数组名时返回数组占用的字节数使用传引用调用的泡沫排序法使用传引用调用的泡沫排序法 PC 兼容机用来存储每一种标准数据类型的字节数 存储特定数据类型的字节数是随计算机系统的变化而变化的。在编写依赖于数据类型大小的程序和要在多种系统上运行的程序时,sizeof 确定用来存储某种类型数据的字节数 运算符sizeof 可用于

44、任何变量名、变量类型和常量 sizeof 在用于变量名非数组名或常量时,返回用来存储特定变量类型或常量所占计算机内存的字节数 注意:如果操作数是类型名,sizeof 的圆括号时必需的,省略圆括号会导致语法错误;如果操作数是变量名,sizeof 的圆括号不是必需的 /* 演示 sizeof 运算符 */ # include main() printf(” sizeof(char)= %d n”, sizeof(char); printf(” sizeof(short)= %d n”, sizeof(short); printf(” sizeof(int)= %d n”, sizeof(int);

45、 printf(” sizeof(long)= %d n”, sizeof(long); printf(” sizeof(float)= %d n”, sizeof(float); printf(” sizeof(double)= %d n”, sizeof(double); printf(” sizeof(long double)= %d n”, sizeof(long double); return 0; 输出结果: sizeof(char) = 1 sizeof(short) = 2 sizeof(int) = 2 sizeof(long) = 4 sizeof(float) = 4 s

46、izeof(double) = 8 sizeof(long double) = 10图 7. 16 用 sizeof 运算符确定标准数据类型的大小指针表达式和指针的算术运算 在算术表达式、赋值表达式和比较表达式关系表达式、逻辑表达式中,指针是合法的操作数。但是,并非所有的运算符在与指针变量一起使用时都是合法的。本节介绍能够把指针作为操作数的运算符以及怎样使用这些运算符 可以对指针进行有限的算术运算,包括自增运算+ +、自减运算- -、加上一个整数或 +=、减去一个整数或 -=以及减去另一个指针 假定声明了数组int v10, 其第一个元素的存储单元为3000,指针vPtr 被初始化为指向 v0

47、 即vPtr 的值为3000。3000 v0v1v2v3v43004 3008 3012 3016 存储单元指针变量vPtr 图7. 17 数组v 和指向 v 的指针变量 vPtr图7.17 图示了这些变量在int 型数占4个字节的计算机中的内存映象指针表达式和指针的算术运算 用如下任意一条语句都可以把vPtr 初始化为指向数组v: vPtr = v; vPtr = &v0; 当今大多数计算机都是用2个字节或4个字节存储int 型数据。某些较新的计算机用8个字节存储int 型数据。因为指针算术运算的结果依赖于指针所指向对象的大小,所以,指针算术运算与机器有关3000 v0v1v2v3v4300

48、4 3008 3012 3016 存储单元指针变量vPtr 图7. 18 经过算术运算后的指针 vPtr 在常规的算术运算中,3000+2的结果为3002,但是,指针算术运算通常不是这种情况。当一个指针加上或减去一个整数时,并非简单地加上或减去该整数值,而是加减该整数与指针引用的对象的大小的乘积指针表达式和指针的算术运算 也就是说,当把一个指针变量的值与一个整数相加减时,其结果随指针所指对象的大小所占内存单元的字节数的不同而不同。对象的大小字节数取决于对象的数据类型。例如,语句 vPtr += 2;当一个整数在内存中占4个字节时,结果为30083000+2*4,vPtr 指向了数组中的v2 见

49、图7.18。当一个整数在内存中占2个字节时,上述计算结果使指针指向存储单元30043000+2*2。如果数组是其它数据类型, 上述语句就会把指针加上该数据类型的一个对象所占内存字节数的两倍 如果vPtr 已经被增加到3016指向v4,语句 vPtr -= 4;就会把vPtr 设置为3000数组的起始地址,即指向数组元素v0指针表达式和指针的算术运算 如果要把指针加1或减1,这时可使用自增运算符+ +或自减运算符- -。语句 +vPtr; vPtr+;都把指针指向数组中的下一个元素。语句 -vPtr; vPtr-;都把指针指向数组中的上一个元素 可以从指针变量中减去另一个指针变量。例如,如果vP

50、tr 包含存储单元地址3000指向数组元素v0,而v2Ptr 包含存储单元地址3008指向数组元素v2 ,那么语句 x = v2Ptr vPtr;把从vPtr 到v2Ptr的数组元素的个数赋给变量x本例为2指针表达式和指针的算术运算 注意,除了数组组元素外,不能认为两个相同类型的变量是在内存中连续存储的,所以,指针算术运算除了用于数组外没有什么意义 在涉及指针算术运算的程序处理时更准确地讲是在编程时,容易出现的错误是: 对不指向数组的指针进行算术运算 把不指向同一个数组的两个指针相减或比较 指针算术运算的结果超出了数组的范围 如果两个指针类型相同即它们指向相同类型的数据对象,那么可以把一个指针

51、变量的值赋给另一个指针变量。否那么,必须用强制类型转换运算符把赋值运算符右边指针的类型转换为赋值运算符左边指针的类型。指向void 类型的指针即void *是例外,它可以表示任何类型的指针。任何类型的指针都可赋给指向void 类型的指针,指向void 类型的指针也可赋给任何类型的指针,两者都不需要强制类型转换指针表达式和指针的算术运算 不能复引用间接访问指向void 类型的指针。例如,在整数int 类型占4个字节的机器上,编译程序知道指向int 类型的指针引用了内存的4个字节,但是一个指向void 类型的指针仅仅包含了不知道数据类型的地址,编译程序不知道该指针到底要引用多少字节数。编译程序必须

52、知道数据类型后才能确定某个特定的指针要复引用的字节数。因此,对于void 类型的指针,编译程序不能根据类型确定它引用的字节数 把一种类型的指针赋给另一种类型的指针,而当这两种指针都不是void 类型时,这是一种语法错误。复引用void * 指针,也是一种语法错误 可以用相等测试运算符和关系运算符比较两个指针,但是,除非它们是指向同一个数组,否那么,这种比较是没有意义的。比较两个指针实际上是比较所存储的地址。例如,比较两个指向同一数组的指针反映了指针所指向元素的先后顺序。指针比较常用来判断某个指针是否是NULL指针和数组的关系 C 语言中的指针和数组有着密切的关系。数组名可以认为是一个常量指针,

53、指针可用来完成涉及数组下标的操作 由于数组下标在编译的时候要被转换成指针表示法,因此用指针编写数组下标表达式可节省编译时间 然而,在操作数组时最好用下标表示法而不使用指针表示法。因为,尽管下标表示法在源程序编译时要稍微多花点时间,但程序的可读性通常会更好 假定已经声明了int 型数组b5 和int * 类型的指针变量bPtr。由于数组名不带下标是指向数组第一个元素的指针,因此, 可用如下语句使bPtr 指向等于数组b 的第一个元素的地址: bPtr = b;该语句等价于取数组第一个元素的地址: bPtr = &b0;指针和数组的关系 如下的指针表达式引用了数组元素b3: *bPtr + 3表达

54、式中的3是相对于指针的偏移量。当该指针指向数组的起始位置时,偏移量说明了引用哪一个数组元素,它等于数组的下标。上述表示法称为“指针/ 偏移量表示法 。因为 * 的优先级高于 + 的优先级,所以圆括号是必需的。如果没有圆括号,上述表达式就会把3 和表达式*bPtr 的值相加假定bPtr 指向数组的起始位置,表达式就把3 和b0 相加 就象能够用指针表达式引用数组元素一样,也可用指针表达式 bPtr + 3引用地址 &b3 指针和数组的关系 数组名本身就是一个指针,可用在指针算术运算中。例如,表达式 *b + 3也引用了数组元素b3 。通常,所有带有数组下标的表达式都可以用指针和偏移量表示,这时要

55、把数组名作为指针。注意,上述语句没有修改数组名,b 仍然指向数组的第一个元素。 指针也可以象数组一样带下标。例如,表达式 bPtr1引用了数组元素b1 。这种表示方法称为“指针/下标表示法 切记,数组名实际上是一个常量指针,它总是指向数组的起始位置。因此,表达式 b += 3是不正确的,因为它试图用指针算术运算修改数组名的值指针和数组的关系 试图用指针算术运算修改数组名是一种语法错误 由此可见,在C 语言中,引用一个确定数组的数组元素可有四种方法: 下标表示法使用带下标的数组元素名 把数组名作为指针的指针/偏移量表示法如前面已见到的 *b+3 指针/下标表示法 使用指针变量的指针/偏移量表示法

56、int 型数组b 中的四个元素 /* 用下标和指针操作数组 */ # include main() int i, offset, b = 10, 20, 30, 40 ; int * bPtr = b; /* 使bPtr 指向数组b */ printf(”Array b printed with: n Array subscript notation n”); for(i = 0; i = 3; i+) printf(”b%d = %d n”, i, bi); printf(”nPointer/offset notation where n ” ”the pointer is the arra

57、y name n”); for(offset = 0; offset = 3; offset+) printf(” *(b + %d)= %d n”, offset, *(b + offset); printf(”nPointer subscript notation n”); for(i = 0; i = 3; i+) printf(”bPtr%d = %d n”, i, bPtri); printf(”nPointer/offset notation n”); for(offset = 0; offset = 3; offset+) printf(” *(bPtr + %d)= %d n”

58、, offset, *(bPtr + offset); return 0; 输出结果: Array b printed with: array subscript notation b0 = 10 b1 = 20 b2 = 30 b3 = 40 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 bPtr0 = 10 bPtr1 = 20 bPtr2 = 30

59、 bPtr3 = 40 Pointer / offset notation *(bPtr + 0) = 10 *(bPtr + 1) = 20 *(bPtr + 2) = 30 *(bPtr + 3) = 40图 7. 19 用四种方法引用数组元素指针和数组的关系 为进一步说明数组和指针的互换性, 我们讨论一以下图7.20 的程序中的两个字符串拷贝函数copy1 和 copy2。这两个函数都把一个字符串可能是一个字符数组拷贝到一个字符数组中。比较这两个函数的函数原型可以发现,其形式是相同的。虽然这两个函数完成同样的功能,但它们的的实现过程是不同的 函数copy1 用数组下标表示法把s2 中的字

60、符串拷贝到字符数组s1 中。函数中声明了一个用作数组下标的int 型计数器变量i 。for 结构的头部完成了整个拷贝操作for 结构体是空语句。此for 结构的头部把i 初始化为0, 并在每次循环后把i递增1,条件s1i = s2i 执行拷贝操作,即把s2 中的字符拷贝到s1 中,直到遇见0 止 函数copy2 用指针和指针算术运算把s2 中的字符拷贝到s1 中。for 结构的头部也完成了整个拷贝操作。头部没有包含变量的初始化指针和数组的关系 和函数copy1 一样,在函数copy2 中,拷贝操作是在条件*s1 = *s2中完成的,即复引用指针s2 并把结果赋给复引用指针s1,直到遇见0 止

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论