第5章 指针 5.1 指针与指针变量 5.2 指针运算符 5.3 指针与一维数组 5.4 指向指针的指针 5.5 指针与结构 5.6 指向共用体和枚举型的指针 5.7 指针小结
第5章 指针 指针是C语言的精华,是C语言最重要的内容之一,也是最难掌握的部分。在程序中可以使用指针来处理数据、变量、数组、字符串、函数、结构体、文件及动态分配内存等。正确地使用指针,可以使程序精简、灵活、高效。指针的概念比较复杂,如果误用指针,程序运行将出现意想不到的错误,这是初学指针应注意的问题。因此指针是C语言的重点和难点。 本章讲述的主要内容包括:变量的地址和变量的值、指针的基本概念;指针变量的定义、赋值;指针基本运算 (包括取地址运算,存取指针所指的内容,移动指针,指针相减等);指针与一维数组的关系,数组名与地址的关系,使用指针存取数组元素;使用指针处理字符串;二维数组与指针的关系、二维数组与数组指针的关系;指向指针的指针的概念;使用指针变量存取结构体变量成员数据、指针与共用体变量的关系以及指针与枚举型变量的关系。建议本章授课8学时,上机6学时,自学10学时 返回目录
5.1 指针变量与数组 教学内容: 5.1.1 指针与指针变量 5.1.2 指针变量的定义 5.1.3 指针变量的赋值 返回目录
5.1.1 指针的基本概念 在第二章的2.1.3节中讲述了内存地址的概念,计算机内存是由一片连续的存储单元组成,操作系统给每个单元一个编号,这个编号称为内存单元的地址(简称地址),每个存储单元占内存一个字节。定义一个变量,不仅规定了该变量的类型、名称,而且还在内存中为该变量分配了一块(连续的)存储单元,我们把这块存储单元的首地址称为该变量的指针(也称为变量的地址)。例如定义变量float f; 假设系统为变量f分配到地址为1500、1501、1502、1503的4个连续存储单元,则地址1500就称为变量f的指针(也称变量f的地址是1500)。因此变量的地址就是变量的指针。变量的值和变量的地址是不同的概念,变量的值是该变量在内存单元中的数据。 返回目录
5.1.1 指针的基本概念 下面用一个卡片管理系统来说明如何存取变量的值。 一个卡片系统由许多张卡片组成。要快速存取每张卡片上的数据,需要给每张卡片编号(标明每张卡片的地址);为了方便存取卡片上的数据,便于人们记忆,还需要给每张卡片取一个名字,并把名字与编号一一对应起来。这样卡片有了编号和名字,就能高效、方便地管理整个卡片系统了。如图5.1 所示,可以通过卡片的名字存取卡片上的数据,例如只要指出卡片的名字f,就可以取出与之相对应的编号1500处的数据456.789。有一类很特殊的卡片,它的内容专门存放其它卡片的编号(地址)。如图5.1中,卡片p上的数据是1500,是卡片f的编号。通过卡片p也可以间接地存取卡片f的数据,先取到卡片p上的数据1500,再到1500编号处存取数据。 返回目录
5.1.1 指针的基本概念 变量值的存取与卡片数据的存取极为相似。每个变量都有名字和地址。程序员在编写程序时,使用变量名来存取该变量的值。与之相对应,计算机系统使用变量的地址来存取该变量的值。这是因为程序经过编译系统编译后,每个变量都分配了相应的存储单元,每个变量名都变成了与之相对应的地址。假设float类型的变量f的地址是1500,若执行语句 f=456.789; 计算机系统就把345.678存入到地址为1500开始的四个存储单元中(单精度实型占内存4字节);若执行语句printf("%f\n",f);则从地址1500开始的四个存储单元中取出数据456.789输出到屏幕。对于计算机而言,变量名都被翻译成了地址,系统按变量地址存取变量的值,这称为“直接存取”方式 返回目录
5.1.1 指针的基本概念 有一类特殊的变量,它的内容专门存放其它变量的地址,这样的变量称为指针变量。如图5.2中指针变量p的值为1500,是变量f的地址。通过变量p可以间接地存取变量f的数据,先取出p的值“地址1500”,再到“地址1500”处存取数据,这种通过变量p间接得到变量f的地址,然后再存取变量f的值的方式称为“间接存取”方式,见图5.2。 返回目录
5.1.1 指针的基本概念 用来存放指针(地址)的变量就称作指针变量。上述变量p就是一个指针变量。若把变量f的地址赋值给指针变量p,则称指针变量p指向变量f(见图5.3)。 返回目录
5.1.2 指针变量的定义 定义指针变量的一般形式为: 类型名 *标识符; 类型名 *标识符; 其中“标识符”是指针变量名,标识符前加“*”号表示该变量是指针变量,用于存放地址,“类型名”表示该指针变量所指向变量的类型。例如: int i, *ip, *jp; /* 定义ip与jp 两个指针变量,它们可以指向int类型的变量*/ float f, *p; /* 定义p为指针变量,它可以指向float类型的变量*/ 若 ip=&i; 则称指针变量ip指向整型变量i,因为把变量i的地址赋值给了ip。赋值语句ip=&f; 是非法的,一个指针变量只能指向类型相同的变量,一个指向整型变量的指针变量不允许指向实型变量 返回目录
5.1.3 指针变量的赋值 指针变量可以通过不同的方法获得一个地址值。 1.通过地址运算“&”赋值 地址运算符“&”是单目运算符,运算对象放在地址运算符“&” 的右边,用于求出运算对象的地址。通过地址运算“&”可以把一 个变量的地址赋给指针变量。若有语句 float f, *p; p=&f; 执行后把变量f的地址赋值给指针变量p,指针变量p就指向了变量f. 2.指针变量的初始化与动态变量的初值一样,在定义了一个(动 态的)指针变量之后,其初值也是一个不确定的值。可以在定义变 量时给指针变量赋初值,如float f, *p=&f; 则把变量f的地址赋值给 指针变量p,此语句相当于 float f, *p; p=&f; 这两条语句。 返回目录
5.1.3 指针变量的赋值 3.通过其它指针变量赋值 可以通过赋值运算符,把一个指针变量的地址值赋给另一个指针变量, 这样两个指针变量均指向同一地址。例如,有如下程序段: int i, *p1=&i, *p2; p2=p1; 执行后指针变量p1与p2都指向整型变量i。 注意,当把一个指针变量的地址值赋给另一个指针变量时,赋值号两 边指针变量所指的数据类型必须相同。例如: int i, *pi=&i; float *pf; 则语句pf=pi;是非法的,因为pf只能指向实型变量,而不能指向型 变量。 返回目录
5.1.3 指针变量的赋值 4.用NULL给指针变量赋空值 除了给指针变量赋地址值外,还可以给指针变量赋空值,如: p=NULL; NULL是在stdio.h头文件中定义的预定义标识符,因此在使用NULL时,应该在程序中加上文件包含#include "stdio.h"。在stdio.h头文件中NULL被定义成符号常量,与整数0对应。执行以上的赋值语句后,称p为空指针,在C语言中当指针值为NULL时,指针不指向任何有效数据,因此在程序中为了防止错误地使用指针来存取数据,常常在指针未使用之前,先赋初值为NULL。由于NULL与整数0相对应,所以下面三条语句等价: p=NULL; 或 p=0; 或 p='\0'; 但通常都使用p=NULL;的形式,因为这条语句的可读性好。NULL可以赋值给指向任何类型的指针变量。 返回目录
5.1.3 指针变量的赋值 5.通过调用标准函数赋值 可以调用库函数malloc和calloc在内存中开辟动态存储单元,并把所开 辟的动态存储单元的首地址赋给指针变量,由于这两个函数返回的是 “void*”无类型指针类型,因此将它们的返回值赋值给指针变量时要 进行强制类型转换。 (1)malloc函数 调用该函数,在内存的动态存储区中分配一个指定长度 (以字节 为单位)的连续存储空间,如果调用该函数成功,则返回所分配空间 的首地址,如果分配不成功,则返回NULL。调用形式为: malloc(表达式) 返回目录
5.1.3 指针变量的赋值 其中表达式的值表示所要分配存储空间的字节数。例如: int *p; p=(int*)malloc(sizeof(10*int)); 执行上述程序段,将在内存的动态存储区中分配izeof(10*int) 即20个字节的连续存储单元块,并把这块连续存储单元的首地址 赋值给指针变量p。其中用了(int*)强制类型转换,将 (void*)类型转换成(int*)类型。 返回目录
5.1.3 指针变量的赋值 (2)calloc函数 调用该函数,在内存动态存储区中分配一块连续的存储空间。函数调用 形式为: calloc (n,size) 分配n个长度为size(以字节为单位)的连续空间。如果调用该函数成功, 则返回所分配空间的首地址,如果分配不成功,则返回NULL。 (3)free函数 调用该函数,用来释放由malloc或calloc函数分配的存储空间,调用该函 数不返回任何值。函数调用形式为: free(指针变量) 返回目录
5.1.3 指针变量的赋值 其中指针变量应指向调用malloc或calloc时所分配存储区的首 地址。如free(p);在程序设计过程中,若动态分配的空间不再使用 时(如函数调用结束),应及时释放所分配的内存空间。 有关指针变量的赋值,不仅仅是上述5种,指针变量还可以 指向数组、字符串、结构体、函数、文件,本章以下的章节将讲 述前三种概念,在第六章涉及指针与函数,第八章涉及指针与文 件。 返回目录
5.2 指针运算符 教学内容: 5.2.1 指针运算符 5.2.2 无类型指针
5.2.1 指针运算符 在5.1.1节描述了怎样用指针变量间接存取变量的数据,本节讲述具体实现方法。 为了使用指针变量间接存取变量中的数据,C语言中提供了指针运算符“*”。指针运算符是单目运算符,运算对象必须放在指针运算符“*”右侧,而且运算对象只能是指针变量或地址,可以用“指针运算符”来存取相应的存储单元中的数据。 例如,有下面的程序段: int i=123,j,*p; p=&i; 则 j=*p;和j=*&i;都将把变量i的值赋给变量j。而*p=456;和*&i=456;都将把 整数456赋给变量i。此处,表达式*p和*&i都代表变量i。运算符 &和*都是单目运算符,它们具有相同的优先级,结合方向为均为 “从右到左”,因此*&i相当于*(&i),表达式&i求出变量i的地址。 由此得出结论: 返回目录
5.2.1 指针运算符 1.表达式 *&i 等价于变量i; 2.当一个指针变量p指向某变量i时(p=&i;),则表达式*p与变量 i等价。 例如: p=&i; printf("%d\n", *p); /*相当于printf("%d\n", i); */ j=(*p)++; /*相当于j=i++;*/ 注意表达式(*p)++中的括号不能省略,如果没有括号,由于“*”与“++”均为单目运算符,优先级相同,结合方向是“从右到左”,因此*p++等价于*(p++),此时先使用变量i的值,再使p的值改变,这样p就不再指向变量i了。 返回目录
5.2.1 指针运算符 指针变量的值是可以改变的,一个指针变量在某一时刻指向变量i在,另一时刻可以指向变量j。例如 int i=1, j=2, *p; p=&i; *p += 100; /* 相当于i+=100; */ p=&j; (*p)--; /* 相当于j--; */ 通过*p方式存取它所指向变量的值是以间接存取的形式进行的,所以比直接存取一个变量要费时间。例如执行上述语句(*p)+=100; 比执行i+=100; 多费时。但由于p是变量,我们可以通过改变它的指向,间接存取不同变量的值,这给程序设计带来灵活性,也使程序代码编写更为简洁、高效。 返回目录
5.2.1 指针运算符 取地址运算符“&”与指针运算符“*”作用在一起时,有相互“抵消”的作用。例如若有int i, *p=&i; 则: 相互等价 相互等价 1. *p i *&I 相互等价 2. &*p p 3.&*i 是非法的表达式,因为i不是地址、也不是指针变量。 前面叙述了当指针变量p指向一个变量i时,可以用*p的形式存 取该变量i的数据。际上只要指针变量p指向内存的某一存储单元, 就可用*p 的形式存取该存储单元的数据。 返回目录
5.2.1 指针运算符 例如下面的程序段: int *p; p=(int*)malloc(sizeof(int)); *p=789; 上述程序段第二条语句在内存动态存储区分配一块2字节的存储区, 并把其首地址赋值给指针变量p,即p指向这一存储区,第三条语句 把整数789存入这 一存储区。 另外,指针运算符“*”是单目运算, 指针运算符的运算对象必须是指 针变量或地址。而乘 法运算符“*” 是双目运算符,它的运算对象可以是实型数据或整型据。 返回目录
5.2.1 指针运算符 [例5.1]由键盘输入一个正整数,求出其最高位数字。用指针变量来完成本题。 main() { long i,*p; p=&i; printf("请输入一个正整数:"); scanf("%ld", p); /* 本语句等价于scanf("%ld", &i); */ while (*p>=10) *p /= 10; /* 求出该正整数的最高位数字 */ printf("最高位数字是:%ld。\n", *p); } 返回目录
5.2.2 无类型指针 ANSI新标准增加了一种无类型指针“void *”。可定义一个指针变量, 例如,有如下程序段: int *p1; char *p2; void *q; /* 定义q为无类型指针变量 */ q=malloc(sizeof(int)); p1=(int*)q; /* 将q的地址赋值给指针变量p1,q与p1指向同一地址*/ *p1=0x4142; /* 对p1所指的内存赋值0x4124(即十进制数16706) */ p2=(char *)q; /* 将q的地址赋值给指针变量p2,q与p2指向同一地址*/ printf("%d, %c,%c\n", *p1, *p2, *(p2+1) ); free(q); 以上程序段运行的结果是: 返回目录
5.2.2 无类型指针 从上面的程序段可知,可以用无类型指针q指向内存中的一块存储 的数据。若指向整型类型的指针p1指向q所指的地址时, 则可用 *p1存取整型数据 若指向字符型的指针p2指向q所指的地址时, 则可用*p2存取字符型数据,上述程序段中,0x41(即十进制数 65)是字符‘A’的ASCII码。 同样也可以用强制类型转换,把一个指针的值赋值给一个无类型 指针。 例如: float f, *p3=&f; void * q; q=(void *)p3; 则此时q和p3都指向实型变量f。 返回目录
5.3 指针与一维数组 教学内容: 5.3.1 指针与一维数组 5.3.2 移动指针及两指针相减运算 5.3.3 指针比较 5.3 指针与一维数组 教学内容: 5.3.1 指针与一维数组 5.3.2 移动指针及两指针相减运算 5.3.3 指针比较 5.3.4 字符串 5.3.5 指针与二维数组
5.3.1 指针与一维数组 1.一维数组和数组元素的地址 一个数组的元素在内存中是连续存放的,数组第一个元素的地址称 数组的首地址。C语言中,数组名是该数组的首地址。例如有以下定义语 句: int a[10],*p; 则语句p=a;和p=&a[0];是等价的,都表示指针变量p指向a数组的首 地址。数组首地址的值在C语言中是一个地址常量,是不能改变的。 因此,语句 a=p; 或a++;都是非法的。若数组的首地址是a,且指针变 量p指向该数组的首地址(即p=a;),则C语言还规定: 数组的第0个元素a[0]的地址是a(等价于p ); 数组的第1个元素a[1]的地址是a+1(等价于p+1,详见5.3.2的内容 ); 数组的第2个元素a[2]的地址是a+2 (等价于p+2 ); ... ... 数组的第i个元素a[i]的地址是a+i (等价于p+i ); 返回目录
5.3.1 指针与一维数组 例如,有下述程序段: int a[6]={1, 2, 3, 4, 5, 6}, *p=a; /*p指向整型数组a的首地址*/ double d[6]={1.1, 2.2, 3.3, 4.4, 5.5, 6.6}, *q=d; /*q指向双精度实型数组 d的首地址*/ 假设数组a的首地址是2000,假设数组d的首地址是3000,则上述两 个数组分配的内存情况如图5.4(a)与图5.4(b)所示,应注意(int)整型 数组每下移一个元素地址加sizeof(int)即2字节,(double)双精度实型 数组每下移一个元素地址加sizeof(double)即8字节。 返回目录
5.3.1 指针与一维数组 2.通过一维数组名所代表的地址存取数组元素 假设已定义一维数组a,由上述可知a+i是元素a[i]的地址,根据指针运算符“*”的运算规则知 *(a+i) 与元素 a[i]等价。例如,下述程序段: int a[ ]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; *(a+5)=50; /*相当于a[5]=50 ;*/ scanf("%d", &a[8]); /*相当于scanf("%d", a+8 );*/ printf("%d\n", *(a+5) ); /*相当于printf("%d\n", a[5]) )*/ 返回目录
5.3.1 指针与一维数组 3.通过指针运算符“*”存取数组元素 设有如下程序段: int a[10],*p; p=a; 即p指向a数组的首地址,由上述可知p+i是元素a[i]的地址,根据 指针运算符“*”的运算规则知 *(p+i) 与元素 a[i]等价。例如,下述 程序段: int a[ ]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, *p=a; *(p+5)=50; /*相当于a[5]=50 ;*/ scanf("%d", &a[8]); /*相当于scanf("%d", p+8 );*/ printf("%d\n", *(p+5) ); /*相当于printf("%d\n", a[5]) )*/ 返回目录
5.3.1 指针与一维数组 4.通过带下标的指针变量存取数组元素 C语言中的下标运算符“[ ]”可以构成表达式,假设p为指针变量,i为 整型表达式,则可以把p[i]看成是表达式,首先按p+i计算地址,然后 再存取此地单元中的值。因此p[i]与*(p+i)等价。例如,下述程序段: int a[ ]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, *p=a; p[5]=50; /*相当于a[5]=50 ;*/ scanf("%d", &a[8]); /*相当于scanf("%d", &p[8] );*/ printf("%d\n", p[5] ); /*相当于printf("%d\n", a[5]) )*/ 综上所述,若定义了一维数组a和指针变量p,且p=a; 则有如下等价规 则: 相互等价 相互等价 相互等价 a[i] p[i] *(a+i) *(p+i) 以上假设p所指的数据类型与数组a元素的数据类型一致,i为整型表达式 返回目录
5.3.2移动指针及两指针相减运算 1.移动指针 移动指针就是把指针变量加上或减去一个整数、或通过赋值运算, 使指 针变量指向邻近的存储单元。因此,只有当指针变量指向一 片连续的存储单元(通常是数组)时,指针的移动才有意义。若有下述程序段: 类型名 *p, *q; p=p+n; q=q-m; 此处“类型名”是指针变量p,q所指向变量的数据类型,并假定m, n为正整数,则系统将自动计算出: p指针向高地址方向位移的字节数=sizeof(类型名)*n; q指针向低地址方向位移的字节数=sizeof(类型名)*m; 指针变量每增1、减 1运算一次所位移的字节数等于其所指的数据 类型的大小,而不是简单地把指针变量的值加1或减1,见[例5.2]。 返回目录
5.3.2移动指针及两指针相减运算 2.两指针相减运算 指向同一块连续存储单元(通常是数组)的两个指针变量可以进 行相减运算。假设指针变量p,q指向同一数组,则p-q的值等于p所 指对象与q所指对象之间的元素个数,若p>q则取正值,p<q取负值 ,见[例5.2]。指针变量除了可以“位移”和“相减”之外,不能作其 它 算术运算。 返回目录
5.3.2移动指针及两指针相减运算 [例5.2] 本例说明指针变量“位移”和“相减”的操作。 main() {int a[6]={1, 2, 3, 4, 5, 6}, *p1=a, *p2; /*p1指向整型数组a的首地址*/ double d[6]={1.1, 2.2, 3.3, 4.4, 5.5, 6.6}, *q1=d, *q2; /*q1指向双 精度实型数组d的首地址*/ p1++; /*p1指向a数组的元素a[1],p1向下移一个元素(2个字节) */ p2=p1+3; /*p2指向a数组的元素a[4],p2与p1相距3个元素*/ printf(“%d\n”,p1-p2); /*输出-3到屏幕,因为p2与p1相距元素的个数 为3*/ q1++; /*q1指向d数组的元素d[1],q1向下移一个元素(8个字节)*/ q2=q1+3; /*q2指向d数组的元素d[4],q2与q1相距3个元素*/ printf(“%d\n”,q2-q1);/*输出3到屏幕,因为q2与q1相距元素的个数为 3*/ printf("%d, %d, %3.1f, %3.1f\n", *p1, *p2, *q1, *q2); } 返回目录
5.3.2移动指针及两指针相减运算 上例运行后指针指向情况见图5.5(a) 和图5.5(b),图中假设数 组a的首地址是2000,假设数组d的首地址是3000。 在[例5.2]中,指针变量p1,p2所指向的数据类型是 整型,这时p1,p2每位移一个元素,将使指针的地址值位移两2字节,设初始时p1的值为2000,则执行语句p1++;后p1的值为2002,见图5.5(a);而指针变量q1,q2所指向的数据类型是双精度实型,这时q1、q2每位移一个元素,将 使指针的地址值位移8个字节,设初始时q1的值为3000,则执行语句q1++;q2=q1+3;后q2的值为3032, 见图5.5(b) 。 返回目录
5.3.3指针比较 指向同一块连续存储单元(通常是数组)的两个指针变量可以进 行关系运算。假设指针变量p,q指向同一数组,则可用关系运算 则表示p,q指向数组的同一元素;若p指向地址较大元素,q指向 地址较小的元素,则 p>q 的值为1(真),且 p<q 的值为0(假)。 任何指针变量都可以与空指针 NULL相比较,如: if( (p=(double*)malloc(sizeof(double))) !=NULL )*p=123.456; 此语句功能是在动态存储区中分配一个连续8字节大小的存储区,如果分配成功,则把123.456存入该存储区。 返回目录
5.3.3指针比较 [例5.3] 输出数组的全部元素。 main() { int i, a[ ]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, *p=a; printf("\n"); for(i=0; i<10; i++) printf("%8d", a[i]); /* 循环语句1*/ for(i=0; i<10; i++) printf("%8d", *(a+i)); /* 循环语句2*/ for(i=0; i<10; i++) printf("%8d", *(p+i)); /* 循环语句3*/ for(i=0; i<10; i++) printf("%8d", p[i]); /* 循环语句4*/ for(i=0, p=a; i<10; i++) printf("%8d", *p++); /* 循环语句5*/ for (p=a; p<a+10; p++) printf("%8d", *p); /* 循环语句6*/ } 上例中的六个循环语句功能是一样的,都用来输出a数组的10个元素。 第5、第6条循环语句使用了p++移动指针,使指针不断地指向下个 元素。注意第6条循环语句采用了表达式p<a+10这样的指针比较运 算。由于第5条循环语句使指针p产生了位移,因此第6条循环语句 中的赋初值p=a表达式不可缺少。 返回目录
5.3.3指针比较 [例5.4] 指针运算符“*”与增1运算符“++”同时作用于一个指针变量的 情况。 main() {int i, a[]={ 11, 22, 33, 44, 55, 66 }, *p=a; printf(“%3d,”, (*p)++);/* 先输出*p,即输出a[0],再进行 (*p)++,即a[0]的值变成12,p值不变仍指向a[0] */ printf(“%3d,”, *p++);/* *p++相当于*(p++),先输出*p,即输出a[0], 再进行p++,p指向a[1] */ printf(“%3d,”, *++p); /**++p相当于*(++p),先进行++p,p指向a[2], 再输出*p,即输出a[2] */ printf(“%3d\n”, ++*p); /* ++*p相当于++(*p), 先进行++(*p), 即a[2]的值变成34,再输出*p, p仍指向a[2] */ for(p=a; p<a+6; p++) printf(“%3d,”,*p); /* 最后输出数组的全部元 素 */ printf("\n"); } 返回目录
5.3.3指针比较 在分析[例5.4]时,应掌握如下规则: 1.指针运算符“*”与增1运算符“++”均为单目运算符,运算优先级 相同,结合方向是“从右到左”; 2.增1运算符“++”作前缀运算符是“先加1,后使用”, 增1运算 符“++”作后缀运算符是“先使用,后加1”。 指针运算符“*”与减1运算符“--”同时作用于一个指针变量的情况与 上述类似,读者可以自已举例说明。 返回目录
5.3.4 字符串 C语言中可以用两种方式来处理字符串:用字符数组来处理字符 以及用指针来处理字符串。 1.字符数组 5.3.4 字符串 C语言中可以用两种方式来处理字符串:用字符数组来处理字符 以及用指针来处理字符串。 1.字符数组 4.1.2节中已经讲述了字符数组的概念。每个元素都是字符类型 的数组 称为字符数组。它的定义形式和元素的存取方法与一般数组相同 返回目录
5.3.4 字符串 [例5.5] 用字符数组存储字符串与字符。 main() { 5.3.4 字符串 [例5.5] 用字符数组存储字符串与字符。 main() { char str[]={‘O’,‘k’,‘!’,‘\0’}; /*数组名为str,由四个元素组成,每 个元素都存放相应的字符,如s[3]的值为‘\0’ */ char str1[]=“Ok!”; /*数组名为str1,由四个元素组成,元素赋值情况 与上面的str数组相同*/ char str2[]={‘O’,‘k’,‘!’}; /*数组名为str2,由三个元素组成,其中 str2[0]值为'O', str2[1]值为'k', str2[2] 值为'!' */ printf("%s",str); printf("%s\n",str1); printf("%c%c%c\n", str2[0], str2[1], str2[2]); } 返回目录
5.3.4 字符串 上例在定义字符数组的同时使用了两种赋初值的方式: (1)将字符逐个赋给数组中各个元素; 5.3.4 字符串 上例在定义字符数组的同时使用了两种赋初值的方式: (1)将字符逐个赋给数组中各个元素; (2)直接用字符串常量给字符数组赋初值,系统将字符串中 的字符顺 序逐个赋给数组中各个元素后,紧接着再把字符串结束标志‘\0 ’ 赋值给 后面的元素。在[例5.5]中,由于字符数组str2最后未赋字符串 结束标 志‘\0’因此不能视为字符串,不能整体输出。 返回目录
5.3.4 字符串 使用字符数组来表示字符串时,应注意: (1)字符数组与字符串的区别在于:每个字符串的最后都会加上一 5.3.4 字符串 使用字符数组来表示字符串时,应注意: (1)字符数组与字符串的区别在于:每个字符串的最后都会加上一 个字符‘\0’,而字符数组的每个元素用于存放一个字符,并没有规定 最后一个字符的内容。若在 字符数组中存放一系列字符之后, 接着加放一个字符‘\0’,这就等价于在字符数组中存放了一个字 符串。如果在字符数组中没有存放字符串结束标志‘\0’,该字符序 列就不是字符串。 (2)若要使用字符数组存放字符串,在定义字符数组时,其元素 的个数至少应该比字符串的长度多1,要留有存放字符串结束标志 ‘\0’的元素。 返回目录
5.3.4 字符串 (3)在定义字符数组时,可以用两种方式把一个字符串赋值给字符数组(见[例5.5]): 5.3.4 字符串 (3)在定义字符数组时,可以用两种方式把一个字符串赋值给字符数组(见[例5.5]): ① 将字符串中的字符依次逐个赋给数组中各个元素后,接着 加放一个字符‘\0’; ② 直接用字符串常量给字符数组赋初值,系统将字符串中 的字符顺序逐个赋给数组中各个元素后,自动把字符串结束 标志‘\0’赋值给后面紧接着的元素。 (4)非定义语句中,不能用赋值运算符“=”把字符串赋值给字符数 组。例如: char str[10]; str=“Ok! ”; 这里str是数组名,是地址常量,其值不能改变。因此,上述第二条赋 值语句str=“Ok!“;是非法的。程序运行时,若要把一个字符串赋值 给字符数组,通常用以下两种方式: 返回目录
5.3.4 字符串 ① 给字符数组元素逐个赋值,最后加放一个字符串结束标志, 5.3.4 字符串 ① 给字符数组元素逐个赋值,最后加放一个字符串结束标志, 下述程序段把字符串"Ok!"赋值给字符数组s。 char s[10]; s[0]= 'O'; s[1]= 'k'; s[2]= '!'; s[3]= '\0'; 用这种方法把字符串符赋值给字符数组,很繁琐也不直观. 更常用的方法是使用函数把字符串符赋值给字符数组; ② 使用C系统提供的库函数把字符串符赋值给字符数组,下 述程序段把字符串"Ok!"赋值给字符数组s。 char s[10]; strcpy(s, "Ok!"); 类似的函数还有strncpy,strcat等(参见第六章与附录F)。也可 使用输入函数: scanf,gets把从键盘输入的字符串赋值给字符数组。 返回目录
5.3.4 字符串 (5)允许使用输入、输出库函数(如gets,puts,scanf,printf)对字符数组中的字符串整体输入、输出。 5.3.4 字符串 (5)允许使用输入、输出库函数(如gets,puts,scanf,printf)对字符数组中的字符串整体输入、输出。 (6)对于较长的字符串,Turbo C允许使用下述分行方法来书写长字符串: char str2[5000]; char str1[]= “ 科学是关于自然的知识总体,它代表了人” “类的共同努力、洞察力、研究成果和智慧。\n当人” “们最初发现了在他们周围反复出现的” "各种关系时,就有了科学的开端。\n"; strcpy(str2, “通过对这些关系的仔细观察,人们开始了解了自然," “而由于自然的可靠性,\n人们还发现他们能够作出预测,” "从而有可能在某种程度上控制他们周围的环境。\n"); 返回目录
5.3.4 字符串 2.字符指针 字符串常量是由双引号括起来的字符序列。例如:“Ok!”。程序中 5.3.4 字符串 2.字符指针 字符串常量是由双引号括起来的字符序列。例如:“Ok!”。程序中 如果出 现字符串常量,系统就在内存中给该字符串常量分配一 连续的存储区 ,用以存放该字符串,系统还自动在该字符串的 末尾加上字符串的结束 标志‘\0’,因此一个字符串常量所占存储 区的字节数比它的长度(字符 的个数)多一个字节。可以用字 符指针指向字符串,然后通过字符指针 来存取字符串。例如: char *sp; sp="Ok!"; 返回目录
5.3.4 字符串 C语言中把字符串常量赋值给字符指针变量,相当于把该字符串 5.3.4 字符串 C语言中把字符串常量赋值给字符指针变量,相当于把该字符串 常量的首地址赋值给字符指针变量,上述语句sp=“Ok!”;使sp指 向字符串常量 “Ok!”,如图5.6所示(图中假设字符串常量“Ok!” 的首地址是1500。当sp指向某字符串常量时,就可以用sp来存取 该字符串,sp[i]或*(sp+i)就相当于字符串的第i+1个字符,如上述 sp[2]的值为‘!’。 返回目录
5.3.4 字符串 [例5.6] 利用字符指针把字符串s1复制到字符串s2。 main() { 5.3.4 字符串 [例5.6] 利用字符指针把字符串s1复制到字符串s2。 main() { char s1[]="Good!", s2[15]="How are you!", *from=s1,*to=s2; while (*from) *to++=*from++; *to='\0'; printf("%s\n%s\n",s1,s2); } 返回目录
5.3.4 字符串 图5.7说明了上例中复制前、后字符数组s2的变化情况,复制后 5.3.4 字符串 图5.7说明了上例中复制前、后字符数组s2的变化情况,复制后 输出s2时,遇第一个字符串结束标志(s2[5]的值为‘\0’)即停止 输出。元素s[13]与s[14]未用到,其值是任意字符。 返回目录
5.3.4 字符串 3.字符数组与字符指针的区别 表5.1总结了使用字符数组处理字符串与使用字符指针处理字符串的区别。 返回目录
5.3.4 字符串 [例5.7]删除一个字符串中所有的空格字符。 #include "stdio.h" main() { 5.3.4 字符串 [例5.7]删除一个字符串中所有的空格字符。 #include "stdio.h" main() { char s[500], *p1, *p2; printf("请输入一个字符串,该字符串内的空格将被删除:"); gets(s); p1=p2=s; while( *p1 ) if( *p1 == ' ' ) p1++; else *p2++ = *p1++; *p2='\0'; printf("删除空格后的字符串是:%s\n",s); } 返回目录
5.3.4 字符串 分析上例的算法:首先p1、p2指向字符数组s的首地址,然后用字符指针p1对字符数组s中的字符依次逐个检查,如果是空格就继续检查下一个字符;如果不是空格就把字符存放在原s数组中, 所放的位置由字符指针p2决定,然后p1、p2下移一个元素;最后 再添加上字符串结束标志,见 图5.8。其中语句*p2++ = *p1++;与 复合语句:{ *p2=*p1; p2++; p1++; }等价。 返回目录
5.3.5 指针与二维数组 1.二维数组和数组元素的地址 5.3.5 指针与二维数组 1.二维数组和数组元素的地址 C语言的二维数组由若干个一维数组构成。即先把二维数组视为一个一维数组,而这个一维数组的每一个元素又是一个一维数组。例如定义以下二维数组: int a[3][5]= { { 1, 2, 3, 4, 5 }, { 6 , 7, 8, 9, 10 }, {11 , 12 , 13 , 14, 15 } }; a为二维数组,a数组有3行5列,共15个元素。可以这样理解,数 组a由三个元素组成:a[0], a[1], a[2],而每个元素又是一个一维数 组,且都含有5个元素,即 a[0] 的元素有 a[0][0], a[0][1], a[0][2], a[0][3] , a[0][4];数组名 a[0]是这一数组的首地址; a[1] 的元素有 a[1][0], a[1][1], a[1][2], a[1][3] , a[1][4];数组名 a[1]是这一数组的首地址; a[2] 的元素有 a[2][0], a[2][1], a[2][2], a[2][3] , a[2][4];数组名 a[2]是这一数组的首地址; 返回目录
5.3.5 指针与二维数组 同样数组名a就是数组{a[0], a[1], a[2]}的首地址,如图5.9和图5.10 所示。 返回目录
5.3.5 指针与二维数组 在C语言中,二维数组名a同样是二维数组的首地址,其值为二维数组中第一个元素的地址。a也是二维数组第0行的首地址,a+1 是第1行的首地址,a+2是第2行的首地址。假设此二维数组的首地址为1000,由于第每行有5个整型元素,所以a+1值为1010,a+2值为1020。如图5.10所示。 返回目录
5.3.5 指针与二维数组 由于a[0],a[1],a[2]是一维数组名,它们就是对应数组的首地址。因此: 5.3.5 指针与二维数组 由于a[0],a[1],a[2]是一维数组名,它们就是对应数组的首地址。因此: a[0]是第0行第0列元素的地址&a[0][0],a[0]+1是第0行第1列元 素的地址 &a[0][1]..., a[1]是第1行第0列元素的地址&a[1][0],a[1]+1是第1行第1列元 素的地址 &a[1][1]..., a[i]+j是第i行第j列元素的地址&a[i][j]。 另外,由指针运算符“*”和下标运算符“[ ]”的运算规则得知:a[0] 与*(a+0)等价,a[1] 与*(a+1)等价]...,a[i]与*(a+i)等价,因此 a[i]+j与*(a+i)+j等价, 它们的值都是 &a[i][j],即元素a[i][j]的地址 返回目录
5.3.5 指针与二维数组 表5.2总结了二维数组元素的地址表示形式,表中假设二维数组a每行有5个元素,并假设a的首地址为1000。 5.3.5 指针与二维数组 表5.2总结了二维数组元素的地址表示形式,表中假设二维数组a每行有5个元素,并假设a的首地址为1000。 执行语句printf(“%p,%p”, a, *a); 可知 a和*a值是相同的,它们都 指向同一地址,但a是行指针,a+1指向下一排;而*a即为a[0], 是数组a第0 行第0 列元素的地址&a[0][0],*a+1指向下一个元素 a[0][1]。 返回目录
5.3.5 指针与二维数组 2.通过地址存取二维数组元素 假设有如下定义: int a[3][5], i, j; 5.3.5 指针与二维数组 2.通过地址存取二维数组元素 假设有如下定义: int a[3][5], i, j; 则二维数组a中的任一元素a[i][j],可以用下述表达式之一来等价表示: (1)*(a[i]+j) 由上述知a[i]+j是第i行第j列元素的地址,因此*(a[i]+j)与a[i][j]等价; (2)*(*(a+i)+j) *(a+i)+j也是第i行第j列元素的地址,因此*(*(a+i)+j)与a[i][j]等价; (3)(*(a+i))[j] 此表达式表示先取到*(a+i)+j处的地址,再到该 地址处存取数据, 因此(*(a+i))[j]与a[i][j]等价; 返回目录
5.3.5 指针与二维数组 (4)*(&a[0][0]+5*i+j) 由于每行5个元素,&a[0][0]+5*i+j就是第I 5.3.5 指针与二维数组 (4)*(&a[0][0]+5*i+j) 由于每行5个元素,&a[0][0]+5*i+j就是第I 行第j列元素的地址,因此*(&a[0][0]+5*i+j)也与a[i][j]等价。由表 5.2知&a[0][0]与*a或a[0]等价,因此有如下等价关系: 返回目录
5.3.5 指针与二维数组 [例5.8] 以下通过一个简单的例子来说明上述等价关系。 main() { int i, j, a[][5]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; printf( "%5d,", *(a[1]+4) ); /* 输出元素a[1][4] */ printf( “%5d\n”, --*(*a+3) ); /* 先计算--a[0][3],再输出a[0][3]的值 */ (*(a+1))[1] += 5; /* 等价于语句 a[1][1] += 5; */ *(&a[0][0]+5*2+3) = 55; /* 等价于语句 a[2][3] = 55; */ for(i=0; i<3; i++) for (j=0; j<5; j++) printf( "%5d", a[i][j] ); printf( "\n" ); } 返回目录
5.3.5 指针与二维数组 3.通过指向数组元素的指针变量存取二维数组元素 假设有如下程序段: int a[3][5], i, j, *p; 5.3.5 指针与二维数组 3.通过指向数组元素的指针变量存取二维数组元素 假设有如下程序段: int a[3][5], i, j, *p; p=&a[0][0]; 则二维数组a中的任一元素a[i][j]与表达式*(p+i*5+j)等价,此时a[i][j] 也与表达式 p[i*5+j]等价。这是因为p是一个指向整型变量的指针, 它也可以指向整型数组元素。 p+1就指向下一个元素,由于每行 5个元素,p+i*5+j就是第i行第j列元素的地址。由表5.2知,a[0] 与*a都是指向第0行第0列元素的地址(即&a[0][0]),因此在这里, 有如下的语句等价关系。 返回目录
5.3.5 指针与二维数组 [例5.9] 以下通过一个简单的例子来说明上述等价关系。 main() { 5.3.5 指针与二维数组 [例5.9] 以下通过一个简单的例子来说明上述等价关系。 main() { int i, j, *p, a[][5]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; p = *a; printf( "%5d,", *(p+2*5) ); /* 输出元素a[2][0] */ printf( “%5d\n”, (*(p+8))++ ); /* 先输出a[1][3]的值,再计算 a[1][3]++ */ *(p+14) += *(p+2*5+3); /* 等价于语句 a[2][4] += a[2][3]; */ for(i=0; i<3; i++) for (j=0; j<5; j++) printf( "%5d", a[i][j] ); printf( "\n" ); } 返回目录
5.3.5 指针与二维数组 4.通过指向一维数组的指针变量存取二维数组元素 假设有如下程序段: 5.3.5 指针与二维数组 4.通过指向一维数组的指针变量存取二维数组元素 假设有如下程序段: int a[3][5], i, j, (*pi)[5]; pi=a; 这里定义的(*pi)[5]中,圆括号 ( ) 内的*先与pi相结合,说明pi是 一个指针变量,然 后(*pi)再与右边的[5]相结合,说明指针变量pi指向“包含5个整型元素的一维数组”。由图5.9知数组名 a与pi一样,指向“包含5个整型元素的一维数组”。因此,当 pi=a; 时,pi+1将指向下一排(见图5.11),称pi为行指针,也 称pi是(指向一维)数组(的)指针。 返回目录
5.3.5 指针与二维数组 按照地址对应关系,二维数组a中的任一元素a[i][j],可以用下述 表达式之一来等价表示: 维数组名,是常量。 5.3.5 指针与二维数组 按照地址对应关系,二维数组a中的任一元素a[i][j],可以用下述 表达式之一来等价表示: 应该注意,这里的pi是一个指针变量,其值是可改变的;而a是二 维数组名,是常量。 返回目录
5.3.5 指针与二维数组 [例5.10] 输出一个二维数组的指定行和指定列。 main() 5.3.5 指针与二维数组 [例5.10] 输出一个二维数组的指定行和指定列。 main() { int a[][5]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; int i, row, col, (*p)[5]; p=a; printf("请输入二维数组的行(0~2)、列(0~4):"); scanf("%d%d", &row, &col); printf("第%d行的数组元素是:", row); for (i=0; i<5; i++) printf("%5d", *(*(p+row)+i) ); printf("\n第%d列的数组元素是:", col); for (i=0; i<3; i++) printf("%5d", *(p[i]+col) ); printf("\n"); } 返回目录
5.3.5 指针与二维数组 假设有如下程序段: int a[3][5], i, j, *pa[3]; 5.3.5 指针与二维数组 假设有如下程序段: int a[3][5], i, j, *pa[3]; for (i=0; i<3; i++) pa[i]=a[i]; 这里定义的*pa[3]中,下标运算符“[ ]”优先级高于“*”运算符,pa先与[ ]相 结合构成pa[3],说明pa是一个数组名,它有3个元素,左边的“*”表示pa的每个元素都是指针,都可以指向整型变量。for循环语句把二维数组a 的每行第0列元素的首地址a[i]赋值给指针变量p[i],如图5.12所示(图中假 设数组a的首地址为2000)。 返回目录
5.3.5 指针与二维数组 元素pa[i](其中i=0,1,2)是指向整型变量的指针,可以用它指向整型 5.3.5 指针与二维数组 元素pa[i](其中i=0,1,2)是指向整型变量的指针,可以用它指向整型 数组元素。pa[i]+1就指向下一个元素,按照地址对应关系,二维数 组a中的任一元素a[i][j],可以用下述表达式之一来等价表示: 应该注意,上述表达式等价的条件是:指针数组pa的每个元素pa[i]分 别指向二维数组a的第i行第0列元素的首地址a[i](即pa[i]=a[i],其中 i=0,1,2)。另外,pa[i]是一个指针变量,其值是可改变的;而a[i]是 常量,值不能改变。 返回目录
5.3.5 指针与二维数组 [例5.11]某班有4个学生,5门课程。编程完成下列操作:1.求出第3 5.3.5 指针与二维数组 [例5.11]某班有4个学生,5门课程。编程完成下列操作:1.求出第3 门课程的平均分;2.按总分由大到小排序,输出每位学生的学号、成 绩及总分。 main() { int i, j; float *pa[4], *temp, average=0; float score[][7]= { { 1, 76.5, 89.5, 78, 90.5, 66, 0.0 }, { 2, 56.5, 69.5, 49, 70.5, 73, 0.0 }, { 3, 82,90, 87.5, 90.5, 81.5, 0.0 }, { 4, 67, 69.5, 43, 70.5, 52.5, 0.0 } }; /* 二维数组socre用于存放学生的学号(第0列元素)、5门课程的 */ /* 学习成绩(第1列至第5列元素)、每位学生5门课程的总成绩(第6列 元素) */ 返回目录
5.3.5 指针与二维数组 for( i=0; i<4; i++) pa[i] = score[i]; /* 给指针数组的每一个元素赋初值 */ for( i=0; i<4; i++) average += *(pa[i]+3); /* 计算第3门课的总分 */ average /= 4.0; /* 计算第3门课的平均分 */ printf("第3门课的平均分是: %6.2f\n", average); for( i=0; i<4; i++) for (j=1; j<6; j++) pa[i][6] += ( (*(pa+i))[j] ); /* 计算每位学生5门课的 总成绩 */ for( i=0; i<4; i++) /* 采用冒泡法排序 */ for( j=0; j<=4-i; j++) if ( pa[j][6] < pa[j+1][6] ) /* 比较总分值 */ 返回目录
5.3.5 指针与二维数组 { temp = pa[j]; pa[j] = pa[j+1]; pa[j+1] = temp; 5.3.5 指针与二维数组 { temp = pa[j]; pa[j] = pa[j+1]; pa[j+1] = temp; } /* 以下程序段按总分由大到小顺序, 输出每位学生的学生的 学号、成绩及总分*/ for( i=0; i<4; i++) { printf("第%.0f号学生的成绩为:", pa[i][0]); for( j=1; j<6; j++) printf( "%-8.2f", *(*(pa+i)+j) ); printf("总分为:%6.2f\n", pa[i][6]); } 返回目录
5.3.5 指针与二维数组 上例程序说明了当pa[i]=score[i](其中i=0,1,2,3)时表达式 5.3.5 指针与二维数组 上例程序说明了当pa[i]=score[i](其中i=0,1,2,3)时表达式 *(pa[i]+j)、*(*(pa+i)+j)、(*(pa+i))[j]、pa[i][j]及score[i][j]均可相互 等价,在排序前可以用 score[i][j]来代替对应的表达式。指针的优点在于它的值可以改变,指针可以指向不同的对象,本例题只需 交换指针的值就可完成排序,排序后将使得元素pa[i]的下标i值越 小,则pa[i]指向总分值越大的那行的首列元素(见图5.13),即 pa[0]指向总分最大的学生的学号,pa[1]指向总分第二大的学生 的学号,...,pa[3]指向总分最小的学生的学号。本例题如果不用指针,只用二维数组来完成排序,每次交换的数据量是一行的内容,程序的执行效率将大大降低。 返回目录
5.3.5 指针与二维数组 6.通过二维字符数组构成字符串数组 5.3.5 指针与二维数组 6.通过二维字符数组构成字符串数组 如上所述,C语言中的二维数组可视为一个一维数组,这个一维数组的每个元素又是一个一维数组。因此可以用二维字符数组来 构成字符串数 组。例如: char book[50][128]; 数组book共有50元素,每个元素可存放128个字符,若用于存放字 符串,可以存放50个长度小于128的字符串。字符串数组可以在 定义时赋初值。例如下面的定义语句: char str[5][10]={ "BASIC", "ADA", "Pascal", "C", "Fortran" }; 上述语句的中二维数组的第一维的长度可以省略,系统将根据字 符串的个数来分配存储空间,与下面的定义语句等价 char str[ ][10]={ "BASIC", "ADA", "Pascal", "C", "Fortran" }; 返回目录
5.3.5 指针与二维数组 此二维数组str在内存中存储情况如图5.14所示。str[i](其中i=0, 1, 5.3.5 指针与二维数组 此二维数组str在内存中存储情况如图5.14所示。str[i](其中i=0, 1, 2, 3, 4)代表了第i个字符串的首地址,也可用str[i][j]来存取数组中的一个字符,设有输出语句 printf("%s, %c\n", str[2], str[4][1]); 该语句执行后输出结果是: Pascal, o 即str[2]指向字符串"Pascal",str[4][1]的值是'o'。 返回目录
5.3.5 指针与二维数组 由图5.14可知,用二维字符数组来存放字符串,各字符串都是从每行的第0个元素开始存放字符,有部分存储单元空闲着,各字符串的长度差别越大,空闲单元就越多,显然这将浪费内存。 为了使各字符串常量在内存中存放时不浪费内存空间,可以定义一个指针数组,并在定义时用字符串赋初值的方法,来构成 一个字符串数组。例如: char *ps[5]={ "BASIC", "ADA", "Pascal", "C", "Fortran"}; 返回目录
5.3.5 指针与二维数组 这一字符数组在内存的存储情况见图5.15。执行这条语句时,首先为每个字符串常量分配相应的存储空间,然后把相应字符串的首地址赋值给指针数组ps的5个对应的元素,即ps[i](其中i=0, 1, 2, 3, 4)指向第i个字符串,还可以用p[i][j]、p[i]+j)、 *(*(p+i)+j)、(*(p+i))[j]等形式存取第i个字符串中的第j个字符。 设有输出语句 printf("%s, %c\n", ps[4], ps[2][1]); 该语句执行后输出结果是: Fortran, a 即ps[4]指向字符串“Fortran”,ps[2][1]的值是‘a’。用这种方式组成的字 符串数组系统根据字符串常量的长度来分配存储空间,不会浪费内存空 间。 返回目录
5.3.5 指针与二维数组 [例5.12]编写一程序,输入数字星期几,则输出英文对应的星期几。例如,输入“0”,则输出“Sunday”,若输入“6”,则输出“Saturday”。 main() { char weeks[][10]={"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday","Friday", "Saturday"}; int i; do printf("请输入星期几(数字0~6):"); scanf("%d", &i); }while(i<0 || i>6); printf("%s\n", weeks[i]); } 返回目录
5.3.5 指针与二维数组 [例5.13]将字符串"BASIC", "ADA", "Pascal", "C", "Fortran" 按从小到大的顺序排序后输出。 #include "string.h" main() { char *temp, *ps[5]={"BASIC", "ADA", "Pascal", "C", "Fortran"}; int i, j, k; for (i=0; i<4; i++) { k=i; for (j=i+1; j<5; j++) if ( strcmp(ps[k], ps[j])>0 ) k=j; /*ps[k]总指向值较小的字符串*/ if ( k!=i ) 返回目录
5.3.5 指针与二维数组 { temp=ps[i]; ps[i]=ps[k]; ps[k]=temp; } 5.3.5 指针与二维数组 { temp=ps[i]; ps[i]=ps[k]; ps[k]=temp; } for(i=0; i<4; i++) printf("%s, ", ps[i]); printf("%s\n", ps[4]); 返回目录
5.3.5 指针与二维数组 上例程序利用选择法排序字符串,程序中用到字符串比较函数 5.3.5 指针与二维数组 上例程序利用选择法排序字符串,程序中用到字符串比较函数 strcmp来比较二个字符串的大小。比较过程中改变指针数组ps元 素的指向,最后将使得元素ps[i]的下标i值越小,则ps[i]指向值越 小的字符串。比较完成后,ps各元素的指向如5.16所示。 返回目录
5.4 指向指针的指针 教学内容: 5.4.1 指向指针的指针 5.4.2 定义指向指针的指针变量 5.4.3 指向指针的指针变量的应用
5.4.1指向指针的指针 通过一个指针变量来存取变量的值,这种方式称为“间接存取”方式。例如: double d, *p=&d; 指针变量p中存放的是一个变量d的地址,通过变量p可以间接地 存取变量d的数据, 先通过变量p到变量d的地址,然后再存取变 量d的值,这种通过一次“间接存取 就能存取变量的值,称为 “一级间址”方式,如图5.17(a)所示。如果一个指针变量中存放的是另一指针变量的地址,这就需要二次“间接存取”才能存取变 量的值 ,这称为“二级间址”, 返回目录
5.4.1指向指针的指针 如图5.17(b)所示,这样的指针变量称为指向指针的指针。 理论上,C语言的“间接存取”方式可以进行多次,构成“多级间 址”, 如图5.17(c) 所示,但实际应用中很少超过二级间址,因为级数越高,存取速 度越慢,也不易 理解其存取机制。 返回目录
5.4.2 定义指向指针的指针变量 指向指针的指针变量定义的形式为: 类型名 **指针变量名; 类型名 **指针变量名; 此处,指针变量名是“指向指针的指针”的变量名称,类型名是 该指针变量经过二级间址后所存取变量的数据类型。由于运算 符“*”的结合性是“从右到左”,因此“**指针变量名”等价于“*(* 指针变量名)”,表示该指针变量的值存放的是另个指针变量的地 址,要经过二次间接存取后才能存取到变量的值。例如语句: double **pp; 定义pp为指向指针的指针变量,它要经过二次间接存取后才能 存取到变量的值, 该变量的数据类型为double。 返回目录
5.4.3 指向指针的指针变量的应用 1.指向一个指针变量,间接存取变量的值 可以把一个指针变量的地址赋值给指向指针的指针变量,然后通过二级 可以把一个指针变量的地址赋值给指向指针的指针变量,然后通过二级 址方法存取变量的值。 [例5.14] 以下通过一个简单的例子说明如何通过二级间址方法存取变量的值。 main() { double d=123.456, *p, **pp; pp=&p; p=&d; printf("d=%8.3f, ", **pp); **pp += 543.21; printf("d=%8.3f\n", d); } 返回目录
5.4.3 指向指针的指针变量的应用 2.指向指针数组,存取指针数组元素所指内容 可以把一个指针数组的首地址赋值给指向指针的指针变量。例如: [例5.15] 有三个等级分,由键盘输入1,屏幕显示“pass”, 输入2显示“good”,输入3显示“excellent”。 main() { int grade; char *ps[ ]={"pass", "good", "excellent" }, **pp; pp=ps; printf("请输入等级分(1~3):"); scanf("%d", &grade); printf("%s\n", *(pp+grade-1) ); } 返回目录
5.4.3 指向指针的指针变量的应用 上述程序中pp指向指针数组ps的第一个元素ps[0],pp+1则指向ps的下一个元素p[1],pp+2指向p[2],如图5.19所示。因此*pp就 是字符串“pass”的首地址,*(pp+1)则是字符串“good”的首地址, *(pp+2)是字符串"excellent"的首地址。 返回目录
5.5 指针与结构 教学内容: 5.5.1 指向结构体变量的指针变量 5.5.2 指向结构体数组的指针变量 5.5 指针与结构 教学内容: 5.5.1 指向结构体变量的指针变量 5.5.2 指向结构体数组的指针变量 5.5.3 通过指针变量存取位段数据
5.5.1 指向结构体变量的指针变量 在第四章4.2节已经讲述了“结构体”的概念。在定义一个结构体变 量时,系统将在内存中分配一块连续的存储空间,用于存放结构 体成员的数据,这块连续存储空间的首地址称为结构体变量的指 针(也称为结构体变量的首地址)。可以定义指向结构体变量的 指针变量,若把某结构体变量 的首地址赋值给一个指针变量,则称这一指针变量指向该结构体变量 。 返回目录
5.5.1 指向结构体变量的指针变量 指向结构体的指针变量定义格式是: struct 类型名 *指针变量名; 5.5.1 指向结构体变量的指针变量 指向结构体的指针变量定义格式是: struct 类型名 *指针变量名; 上述类型名为结构体类型名。例如下面定义一个结构体类型booktp来存储书的基本信息: struct booktp { char name[60]; /*书名*/ char author[30]; /*作者*/ float price; /*价格*/ struct datetp unsigned year; unsigned month; } pubday; /*出版日期*/ }; 返回目录
5.5.1 指向结构体变量的指针变量 定义了结构体类型,就可以定义结构体变量和指向结构体变量的指 针, 5.5.1 指向结构体变量的指针变量 定义了结构体类型,就可以定义结构体变量和指向结构体变量的指 针, struct booktp book5, *p; 其中book5为结构体变量,p为指向结构体变量的指针。若p=&book5;则称指针变量p指向结构体变量book5,此时可用下述 三种方式之一存取结构体成员(三种方式是等价的): 1.结构体变量名.成员名 2.指针变量名->成员名 3.(*指针变量名).成员名 返回目录
5.5.1 指向结构体变量的指针变量 例如有下面程序段, 5.5.1 指向结构体变量的指针变量 例如有下面程序段, struct booktp *p, book5={“C++ Buider 网络开发实例”, “清汉计算机工作 室", 53, {2000, 9} }; p=&book5; 则book5.price,p->price以及(*p).price的值都是53.0, 而book5.pubday.year,p->pubday.year以及(*p).pubday.year的值都 是2000。 其中指向运算符“->”是由两个符号“-”和“>”组合在一起,就象一个箭 头指向结构成员。指针运算符“*”作用于指针变量p上,构成表达式 (*p),等价于结构体变量名book5。注意此处(*p).price的圆括号不能少, 若写成*p.price,由于结构体成员运算符“.”的优先级高于指针运算符 “*”,因此*p.price相当于*(p.price),这是一个非法的表达式。 返回目录
5.5.2 指向结构体数组的指针变量 指向结构体的指针变量也可以指向结构体数组及其元素。例如,下述程序段: 5.5.2 指向结构体数组的指针变量 指向结构体的指针变量也可以指向结构体数组及其元素。例如,下述程序段: struct booktp *p, book[3]; p=book; 这样,指针变量p指向结构体数组book的首地址, p+1指向下一个元素book[1],p+2指向元素book[2],如图5.20所示,图中假定book[0]的地址是3000,由于 sizeof(struct booktp)的值为98,每个结构体元素占内存空间98个字节,因此p+1指向地址3098处,p+2指向地址3196处。 返回目录
5.5.2 指向结构体数组的指针变量 使用指针变量指向结构体变量或结构体数组时,应注意运算符的优先级: 5.5.2 指向结构体数组的指针变量 使用指针变量指向结构体变量或结构体数组时,应注意运算符的优先级: 1.“( )”、“[ ]”、“->”、“.”四个运算符优先级相同,在C语言中具有 最高的优先级,运算的结合方向是“从左到右”; 2.“*”、“++”、“--”、“&”四个运算符优先级相同,在C语言优先级 的级别仅次于上述的四个运算符,运算的结合方向是“从右到左”。 [例5.16] 通过一个简单的例子说明指向结构体数组的指针的应用。 在程序 中,为了说明运算符的优先级和结合性的用法,改变了书的价格。 返回目录
5.5.2 指向结构体数组的指针变量 main() { struct booktp {char name[60]; /*书名*/ 5.5.2 指向结构体数组的指针变量 main() { struct booktp {char name[60]; /*书名*/ char author[30]; /*作者*/ float price; /*价格*/ struct datetp unsigned year; unsigned month; } pubday; /*出版日期*/ }; 返回目录
5.5.2 指向结构体数组的指针变量 struct booktp *p,book[3]= 5.5.2 指向结构体数组的指针变量 struct booktp *p,book[3]= { {“C++Buider 网络开发实例”, “清汉计算机工作室”, 53.0, {2000, 9} }, {"SQL Server 循序渐进教程", "Petkovic", 35.0, {1999, 6} }, {"VB开发指南", "Dianne Siebold", 28.0, {2000, 9 } }, }; p=book; printf(“%8.2f,”, ++p->price); /*上述表达式等价于++(p->price), 即 先计算 ++(book[0].price), 再输出book[0].price */ printf(“%8.2f,”, (++p)->price); /*上述语句先计算++p, p指向 book[1].price, 再输出 book[1].price */ 返回目录
5.5.2 指向结构体数组的指针变量 printf(“%8.2f,”, p++->price); /*上述表达式等价于(p++)->price, 先输出 book[1].price,再计算p++, p指向book[2]*/ printf(“%8.2f\n”, p->price++); /*上述表达式等价于(p->price)++, 先输 出book[2].price,再计算(book[2].price)++ */ for(p=book; p<book+3; p++) /* 输出结构体数组的所有数据 */ printf("%s,作者:%s,出版日期:%d年%d月,价格:%5.1f\n", p->name, (*p).author, p->pubday.year, p->pubday.month, p->price); } 返回目录
5.5.3 通过指针变量存取位段数据 不能用指针变量指向位段成员,但可以用指针变量指向一个带有位段的结构体变量,此时可用下述三种方式之一存取结构体成员的值(三种方式是等价的): 1.结构体变量名.位段名 2.指针变量名->位段名 3.(*指针变量名). 位段名 返回目录
5.5.3 通过指针变量存取位段数据 [例5.17]通过指针变量存取位段数据。 main() { struct packed_data 5.5.3 通过指针变量存取位段数据 [例5.17]通过指针变量存取位段数据。 main() { struct packed_data { unsigned a:4; unsigned b:3; unsigned c:9; } *p, data={ 9, 5, 60}; p=&data; printf("%u,%u,%u\n", p->a, (*p).b, data.c); p->a += 2; (*p).c >>= 2; } 返回目录
5.6 指向共用体和枚举型的指针 教学内容: 5.6.1 指向共用体变量的指针变量 5.6.2 指向枚举型的指针变量
5.6.1 指向共用体变量的指针变量 指针变量可以指向一个共用体变量,此时可用下述三种方式之一存取共用体成员(三种方式是等价的): 1.共用体变量名.共用体成员名 2.指针变量名->共用体成员名 3.(*指针变量名). 共用体成员名 返回目录
5.6.1 指向共用体变量的指针变量 [例5.18]通过指针变量存取共用体成员数据。 main() { struct byte_tp { unsigned char al, ah; }; union reg_tp unsigned ax; struct byte_tp h; union reg_tp a, *p; p = &a; 返回目录
5.6.1 指向共用体变量的指针变量 a.ax = 0x3b5e; printf("ax=%x, al=%x, ah=%x\n", p->ax, (*p).h.al, p->h.ah); p->h.al -= 3; p->h.ah &= 0x0f; } 返回目录
5.6.1 指向共用体变量的指针变量 上例说明了一个reg_tp共用体类型、以及定义相应的共用体变量a和指针变量p,变量a占内存两字节,可使用a.h.ah对它的高字节操作,使用a.h.al对它的低节操作,使用a.ax对高、低两字节同时操作,这类似于“字存取”及“字节存取”CPU寄存器上的数据,如图 5.21所示。当指针变量p指向共用体变量a时,有下等价表示关系。 返回目录
5.6.2 指向枚举型的指针变量 指针变量可以指向一个枚举型变量,如下定义一个枚举型变量e和指向枚 举型变量的指针变量p: 5.6.2 指向枚举型的指针变量 指针变量可以指向一个枚举型变量,如下定义一个枚举型变量e和指向枚 举型变量的指针变量p: enum 枚举型类型名 e, *p; 若p指向枚举型变量(即p=&e;),则*p与变量e是等价的。 [例5.19]通过指针变量存取枚举型变量的值。 main() { enum fruit_type { apple, orange, banana, pineapple }; enum fruit_type fruit[ ]={ orange, apple, pineapple, banana }, *p; char *fruit_name[ ]={"苹果", "桔子", "香蕉", "波萝"}; for( p=fruit; p<fruit+4; p++) printf("%s,", fruit_name[*p]); printf("\n"); } 返回目录
5.6.2 指向枚举型的指针变量 apple(对应整数0)、pineapple(对应整数3)、 5.6.2 指向枚举型的指针变量 上例程序中fruit是一个枚举型数组,有四个元素,用指针变量p依 次取出fruit数组四个元素的值:orange(对应整数1)、 apple(对应整数0)、pineapple(对应整数3)、 banana(对应整数2)。而fruit_name是一个指针数组, 共有四个元素,分别指四个字符串。输出时用*p做 fruit_name数组的下标,此时系统自动把*p的枚举型 enum fruit_type类型)转换成其对应的整型数据,如 fruit_name[banana]等于fruit_name[2]。 返回目录
本章讲述了利用指针处理内存中各种类型数据的方法,小结如下。 5.7 指针小结 本章讲述了利用指针处理内存中各种类型数据的方法,小结如下。 5.7.1 指针概念综述 5.7.2 指针运算小结 5.7.3 等价表达式
5.7.1 指针概念综述 1.变量的地址就是变量的指针。用于存储地址的变量称为指针变量。 5.7.1 指针概念综述 1.变量的地址就是变量的指针。用于存储地址的变量称为指针变量。 当一个变量的地址赋值给某一指针变量时,称这个指针变量指向该变 量。此时,既可用变量名直接存取变量的值,也可用指针变量间接存 取变量的值。 2.C语言中的数组变量、字符串数组变量、字符串、结构体变量、 共用体变 量、枚举型变量,甚至函数名及函数的参数(在第六章作详 细介绍)以及文件在第八章作详细介绍)等都有指针,可以定义相应 的指针变量存放这些指针。同样有两种方法存取变量的值:用变量名 直接存取或用指针变量间接存取。也有两种方法调用函数:用函数名 来调用或用指向函数的指针变量来调用(见第六章)。 3.指针运算符“*”作用在变量的地址上,即表达式“*变量的地址” 相当于间接存 取该变量的值。 4.一维数组名是该数组的首地址(第一个元素的地址)。当指针 变量p指向数组的某一个元素时,p+1指向下一个元素,p-1指向上一个 元素。 返回目录
5.7.1 指针概念综述 5.字符串可以存放在字符数组中,也能以字符串常量的形式出 现在程序中。程序中把一个字符串常量赋值给一个指针变量,实 5.7.1 指针概念综述 5.字符串可以存放在字符数组中,也能以字符串常量的形式出 现在程序中。程序中把一个字符串常量赋值给一个指针变量,实 际上是把存放该字符串常量的内存单元首地址赋值给指针变量。 6.可以把C语言的二维数组a视为一个一维数组(a[0],a[1], a[2],. . . ),而这个一维数组的每一个元素a[i]又是一个一维 数组(a[i][0],a[i][1],a[i][2],. . .)。因此,&a[i][j]、a[i]+j与*(a+i)+j三者相互等价,都是元素a[i][j] 的地址。一个行指针 变量pi指向的数据类型是一个有N个元素的一维数组,当pi指向二维数组的一行(设每行也有N个元素)时,pi+1指向下一行,pi-1指向上一行。 7.指针数组的每一个元素都是一个指针变量,指针数组的元素 可用来指向变量、数组元素、字符串等。 返回目录
5.7.1 指针概念综述 8.指向指针的指针要进行二次“间接存取”(二级间址)才能存取变量的值。 5.7.1 指针概念综述 8.指向指针的指针要进行二次“间接存取”(二级间址)才能存取变量的值。 9.通过指针变量存取结构体变量成员数据有二种方法:一种是通过指针运算符“*”,另一种 是通过指向运算符"->"。指针变量存取共用体变量的数据也与之类似。 10.在C程序中使用指针编程,可以写出灵活、简练、高效的好程序,实现许多用其它高级 语言难以实现的功能。 11.初学者利用指针编程较易出错,而且这种错误往往是隐蔽的、难以发现。比如由于未 对指针变量p赋值就对*p赋值,新值就代替了内存中某单元的内容,可能出现不可意料的错 误。因此使用指针编程,概念要清晰,并注意积累经验。 返回目录
5.7.2 指针运算小结 1.指针变量赋值 通过如下程序段来说明指针变量的赋值: 5.7.2 指针运算小结 1.指针变量赋值 通过如下程序段来说明指针变量的赋值: int i, j, a[10], b[5][9], *p, *q, (*pi)[9], *pa[5]; char *ps; char *pas[]={"abc", "defghij", "kn"}; /*将三个字符串常量"abc", "defghij", "kn" */ /* 的首地址分别赋值给指针数组pas的三个元素pas[0]、 pas[1]、pas[2] */ p=&i; /* 将一个变量地址赋给指针变量p */ q=p; /* p和q都是指针变量,将p的值赋给q */ q=NULL; /* 将NULL空指针赋值给指针变量q */ p=(int*)malloc(5*sizeof(int)); /* 在内存中分配10个字节的连续存储单元 块,并把这块连续存储单元的首地址赋值 给指针变量p */ p=a; /* 将一维数组a首地址赋给指针变量p */ 返回目录
5.7.2 指针运算小结 p=&a[i]; /* 将一维数组a的第i个元素地址赋给指针变量p */ 5.7.2 指针运算小结 p=&a[i]; /* 将一维数组a的第i个元素地址赋给指针变量p */ p=&b[i][j]; /* 此赋值语句等价于p=b[i]+j; 或 p=*(b+i)+j; 这三条语句 都是 将二维数组元素b[i][j]地址赋给指针变量p */ pi=b+i; /* 此赋值语句等价于pi=&b[i]; 表示行指针pi指向二维数组b 的第i行 */ for( i=0; i<5; i++) pa[i]=b[i]; /* 指针数组pa的元素pa[i](i=0, 1, …, 4),分别指向二维数组b的第i行的第0 列元素 */ ps="Hello!";/* 将字符串常量"Hello!"的首地址赋值给指针变量ps */ 返回目录
5.7.2 指针运算小结 2.指针变量加(减)一个整数 若有下述程序段: 类型名 *p, *q; p=p+n; q=q-m; 5.7.2 指针运算小结 2.指针变量加(减)一个整数 若有下述程序段: 类型名 *p, *q; p=p+n; q=q-m; 此处“类型名”是指针变量p,q所指向变量的数据类型,并假定m, n为正整数,则统 将自动计算出: p指针向高地址方向位移的字节数=sizeof(类型名)*n; q指针向低地址方向位移的字节数=sizeof(类型名)*m; 指针变量每增1、减 1一次所位移的字节数等于其所指的数据类 型的大小,而不是简单地把指针变量的值加1或减1。另外,上 述指向二维数组b的行指针pi++; pi则指向二维数组b的下一行 返回目录
5.7.2 指针运算小结 3.两个指针变量相比较 指向同一块连续存储单元(通常是数组)的两个指针变量可以进 5.7.2 指针运算小结 3.两个指针变量相比较 指向同一块连续存储单元(通常是数组)的两个指针变量可以进 行关系运算。假设指针变量p,q指向同一数组,则可用关系运算符 “<”,“>”,“>=”,“<=”,“==”“!=”进行关系运算。若p==q为真,则表 示p,q指向数组的同一元素;若p指向地址较大元素,q向地址较小 的元素,则 p>q 的值为1(真)。如果p和q不指向同一数组,则比 较无意义。任何指针变量或地址都可以与NULL作相等或不相等的比 较。 4.两指针变量相减 指向同一块连续存储单元(通常是数组)的两个指针变量可以进行相减 运算。假设指针变量p,q指向同一数组,则p-q的值等于p所指对象与q所 指对象之间的元素个数,若p>q则取正值,p<q取负值。 返回目录
5.7.3 等价表达式 用指针变量指向某一数据类型的变量时,就可以通过指针来存取该变量的值。表 5.3列出了通过指针存取变量值的相互等价表达式, 表中N、M为整型常量;并假设表中所有构造型数据类型均已正确定义完成。 返回目录
习题 一、 选择题(每题只有一个正确答案) 5.1 若已定义:int *p, a; 则语句 p=&a; 中的运算符“&”的含义是【1】。 【1】 A)位与运算 B)逻辑与运算 C)取指针内容 D)取变量地址 5.2 若已定义:int a, *p=&a; 则下列函数调用中错误的是【2】。 【2】 A)scanf("%d", &a); B)scanf("%d", p); C)printf("%d", a); D)printf("%d", p); 5.3 若已定义 int a=5; 对(1) int *p=&a; (2) *p=a;两个语句的正确解释 是【3】。 【3】 A)语句(1)和(2)中的 *p 含义相同,都表示给指针变量赋值 B)语句(1)和(2)的执行结果都是把变量 a 的地址赋给指针变量 p C)语句(1)是在对 p 进行说明的同时进行初始化,使 p 指向 a; 语句(2)是将变量 a 的值赋给指针变量 p D)语句(1)是在对 p 进行说明的同时,使 p 指向 a; 语句(2)是将变量 a 的值赋给指针变量 p 所指变量
习题 5.4 C语言中 NULL 表示【4】。 【4】 A)空指针 B)未定义的变量 C)字符串的结束符 D)文件的结束符 5.5 若有定义 char *p, ch; 则不能正确赋值的语句组是【5】。 【5】 A)p = &ch; scanf("%c",p); B)p = (char *)malloc(1); *p = getchar(); C)*p = getchar(); p = &ch; D)p = &ch; *p = getchar(); 5.6 以下程序段动态分配一个整型存储单元,单元的地址给s,请选择正确答案填空。 int *s; s= 【6】malloc( sizeof(int) ); 【6】 A)int * B)int C)(int *) D)void *
习题 5.7 以下程序段的功能是【7】。 char str1[300], str2[300], *s=str1, *t=str2; 5.7 以下程序段的功能是【7】。 char str1[300], str2[300], *s=str1, *t=str2; gets(s); gets(t); while( (*s) && (*t) && (*t==*s) ) { t++; s++; } printf("%d\n", *s-*t); 【7】 A)输出两个字符串长度之差 B)比较两个字符串,并输出其第一个不相同字符的ASCII码 的差值 C)把str2中的字符串复制到str1中,并输出两个字符串长度之 差 D)把str2字符串连接到str1之后,并输出其第一个不相同字符 的ASCII码的差值 5.8 若有以下定义和语句,且0≤i<5,则不能访问数组元素的是【8】。 int i, *p, a[]={ 1, 2, 3, 4, 5 }; p = a; 【8】 A)*( a+i ) B)p[ p-a ] C)p+i D)*( &a[i] )
习题 5.9 以下程序段的运行结果是【9】。 char s[]="ABC"; int i; 5.9 以下程序段的运行结果是【9】。 char s[]="ABC"; int i; for (i=0;i<3;i++) printf("%s", &s[i]); 【9】 A)ABC B)ABCABCABC C)AABABC D)ABCBCC 5.10 把字符串"Ok!"赋值给变量不正确的语句或语句组是【10】。 【10】 A)char a[] = "Ok!"; B)char a[8] = {'O','k','!','\0'}; C)char *p; D)char *p; p = "Ok!"; strcpy(p,"Ok!"); 5.11下列程序段的输出结果为【11】。 float y=0.0, a[]={2.0, 4.0, 6.0, 8.0, 10.0}, *p; int i; p=&a[1]; for(i=0; i<3; i++) y += *(p+i); printf("%f\n",y); 【11】 A)12.0000 B)28.0000 C)20.0000 D)18.0000
习题 5.12 下列程序段的输出结果为【12】。 char b[]="ABCD"; char *chp=&b[3]; while( chp>&b[0]) { putchar(*chp); --chp; } 【12】 A)DBCA B)DCB C)CB D)CBA 5.13 下列程序段的输出结果为【13】。 char s*= "Morning", *p=s; while(*p!='\0') p++; printf("%d\n", (p-s)); 【13】 A)0 B)7 C)8 D)9
习题 5.14 以下程序段的运行结果是【14】。 char c[]={ 'a', 'b', '\0', 'c', '\0' }; printf( "%s\n" , c ); 【14】 A)ab c B)'a''b' C)abc D)ab 5.15 以下各语句中,字符串"abcde"能正确赋值的操作是【15】。 【15】 A)char s[5] = { 'a','b','c','d','e' }; B)char *s; s = "abcde"; C)char *s; gets( s ); D)char *s; scanf( "%s", s ); 5.16 以下程序段的输出结果是【16】。 char s[10], *sp = "HELLO"; strcpy( s, sp ); s[0] = 'h'; s[6] = '!'; puts( s ); 【16】 A)hELLO B)HELLO C)hHELLO! D)h!
习题 5.17 以下程序段的运行结果是【17】。 char *s = "0123214"; int v1 = 0, v2 = 0, v3 = 0; while( *s ) { switch( *s ) { default : v3++; case '1' : v1++; break; case '2' : v2++; } s++; printf("%d,%d,%d\n", v1, v2, v3); 【17】 A)5,2,3 B)2,2,3 C)5,5,3 D)1,0,1
习题 5.18 若有以下定义和语句,值为9的正确表达式是【18】。 int a[][3]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, *p; p = &a[0][0]; 【18】 A)*(p+2*3+2) B)*(*(p+2)+2) C)p[2][2] D)*(p[2]+2) 5.19 若有定义 int a[3][5], i, j; (且0≤i<3,0≤j<5),则 a[i][j] 的 地址不正确表示是【19】。 【19】 A)&a[i][j] B)a[i]+j C)*(a+i)+j D)*(*(a+i)+j)
习题 5.20 若有以下定义和语句,则对 a 数组元素地址的正确引用是【20】。 int a[2][3], (*p)[3]; p=a; 【20】 A)*(p+2) B)p[2] C)p[1]+1 D)(p+1)+2 5.21 若有定义 int *p[4]; 则标识符 p 是一个【21】。 【21】 A)指向整型变量的指针变量 B)指向函数的指针变量 C)指向有四个整型元素的一维数组的指针变量 D)指针数组名,有四个元素,每个元素均为一个指向整型变量 的指针 5.22 若有定义 int (*ptr)[M]; 则标识符ptr是【22】。 【22】 A)M个指向整型变量的指针 B)指向M个整型变量的函数指针 C)一个行指针,指向有M个整型元素的一维数组 D)有M个指针元素的一维指针数组,每个元素都只能指 向整型变量
习题 5.23 设有如下定义,则以下说法中不正确的是【23】。 char *ps[2]={ "abc", "ABC"}; 【23】 A)ps 为指针变量,它指向含有两个数组元素的字符型一维数组 B)ps 为指针数组,其两个元素中各自存放了字符串“abc”和 "ABC"的首地址 C)ps[1][2] 的值为 'C' D)*(ps[0]+1) 的值为 'b‘ 5.24 以下程序段的运行结果是【24】。 int a[4][3]={ 1,2,3, 4,5,6, 7,8,9, 10,11,12 }; int *p[4],j; for( j=0; j<4; j++) p[j]=a[j]; printf( "%2d,%2d,%2d,%2d\n", *p[1], (*p)[1], p[3][2], *(p[3]+1) ); 【24】 A)4, 4, 9, 8 B)程序出错 C)4, 2,12,11 D)1, 1, 7, 5
习题 5.25 若有定义 char *language[]={ "FORTRAN", "BASIC", "PASCAL", “ JAVA", "C" }; 则 language[2] 的值是【25】。 【25】 A)一个字符 B)一个地址 C)一个字符串 D)不定值 5.26 设有以下定义,则下列能够正确表示数组元素a[1][2]的正确表达式 是【26】。 int a[][3]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; int *p=a[0]; 【26】 A)*((*p+1)[2]) B)*(*(p+5)) C)(*p+1)+2 D)*(*(a+1)+2) 5.27 设有以下语句,则值为11的表达式是:【27】。 struct st { int n; char ch; } a[3]={10, 'A', 20, 'B', 30, 'C'}, *p=&a[0];; 【27】 A)p++->n B)p->n++ C)(*p).n++ D)++p->n
习题 5.28 以下程序段的运行结果是【28】。 struct stu { int x, int *y; } *p ; { int x, int *y; } *p ; int dt[]={ 1, 2, 3, 4 }; struct stu a[4]={ 5, &dt[0], 6, &dt[1], 7, &dt[2], 8, &dt[3] }; p=a; printf( "%d," , (++p)->x ); printf( "%d," , ++p->x ); printf( "%d\n", ++(*p->y) ); 【28】 A)6,7,3 B)6,6,3 C)6,6,2 D)5,7,2
习题 5.29 阅读以下程序,当程序运行时输入 hi 后回车,则程序输出 6968; 若程序运行时输出为 6869,那么输入的是【29】。 #include <stdio.h> main() { union val char cval; int ival; } x; char *p; p=&x.cval+1; scanf( "%c%c", &x.cval, p ); printf( "%x\n", x.ival ); } 【29】 A)lp B)** C)jn D)ih
习题 5.30 以下程序段的输出结果是【30】。 union u_tp { char ch[2]; int a; } r={'A'}, *p=&r; p->ch[1]='B'; printf("%x\n", p->a); 【30】 A)4142 B)4241 C)AB D)BA
习题 二、填空题: 5.31 以下程序按逆序重新放置array数组中N个元素的值,array数组中的值由用户输入。 #include <stdio.h> #define N 10 main() { int array[N], i , *ps, *pe, temp; for( i=0; i<N; i++ ) scanf( "%d", array+【1】 ); for( ps=array, pe=array+N-1; ps<pe; ps++, 【2】) { temp=*ps; *ps=【3】; *pe=temp; } for( i=0; i<N; i++ ) printf( "%d ", array[i] ); printf( "\n" );
习题 5.32 以下程序中实现从主串 str 中取出一子串 sub,n 表示取出的起始位置,m 表示所取子串的字符个数,程序运行后输出 cdefg。 #include <stdio.h> main() { char str[100]="abcdefgh", sub[100], *p=str, *psub=sub; int n=3, m=5; for ( p=str+n-1; p<str+【4】; p++, psub++ ) *psub=*p; 【5】; puts(sub); }
习题 5.33 以下程序用来输出字符串. #include <stdio.h> main() { char *a[]={ "for", "switch", "if", "while" }; char **p; for( p=a; p<a+4; p++ ) printf( "%s\n", 【6】 ); }
习题 5.34 以下程序功能是当输入学生序号(1 或 2 或 3)后,能输出该学生的全部成绩(共有三位学生,每位学生有 4 门成绩)。 #include <stdio.h> main() { float score[][4] = { {60,70,80,90}, {56,89,67,88}, {34,78,90,66} }; float (*p)[4]; int n,i; p = 【7】; scanf( "%d", &n ); printf( "序号为%d 的学生成绩是:", n ); for( i=0; i<4; i++ ) printf( "%5.1f", p【8】 ); }
习题 5.35 下列程序 fun 函数实现从字符串 str 中取出连续的数字做为一个正整数,依次存放到 a 数组中,并统计共存放了多少个正整数n。下面程序将输出:123 456 16639 7890 。 #include <stdio.h> main() { char s[255]="ab123 x456,xy16639ghks7890# zxy", *p; int n=0, a[255], i, is=0; /* is 用于判定*p是否为‘1’...‘9’之间的字符, 是则is=1,否is=0 */ for ( p=s; p<s+strlen(s); p++) { if ( !is && *p>='0' && *p<='9' ) { is = 1; 【9】; a[n-1] = 0; }
习题 if ( is && ( *p<'0' ||【10】) ) is = 0; if ( is ) a[n-1] = a[n-1]*10 + *p - 【11】; } for ( i=0; i<n; i++) printf("%d ", a[i]); printf("\n");
习题 5.36 以下程序统计字符串 s 中元音字母(a,A,e,E,i,I,o,O,u,U)的个数。 #include <stdio.h> main() { char str[255], a[]="aAeEiIoOuU", *p, *s=str; int n=0; gets(str); while( *s ) { for( p=a; *p; p++) if(【12】) { n++; break; } 【13】; printf( "字符串中元音字母的个数为:%d\n", n) ;}
习题 三、编写程序( 要求用指针方法完成 ) 5.37 输入 10 个整数,找出其中最大数和次最大数。 5.38 编写程序,交换数组a和数组b中的对应元素。 5.39 有10个数围成一圈,求出相邻三个数之和的最小值。 5.40 M个人围成一圈,顺序排号,从第一个人开始报数(从1到N报数),凡报到N的人就从圈里出来;然后再从下一个人开始报数,直到只剩一个人为止。输出从圈里出来人的序号。 5.41 产生动态数组,输入数组大小后,通过动态分配内存函数ma1loc产生数组。 5.42 编写程序,复制字符串。 5.43 编写程序,连接两个字符串。 5.44 编写程序,将一个字符串反向存放。 5.45 编写程序,求字符串的长度。 5.46 输入一串英文文字,统计其中每个字母(不区分大小写)的数目。 5.47 由键盘输入的两个高精度正整数(不超过230位),求它们的和(精确值)。 5.48 对一个长度为n的字符串从其第k个字符起,删去m个字符,组成长度为n-m的新字符串,并输出处理后的字符串。
习题 5.49 编写程序,输入两个字符串str与substr,删除主字串 str 中的 所有子字串 substr。 5.50 编写程序,实现在字符串sl中的指定位置第k个字符处插入字 符串s2。 5.51 找出一个二维数组中的鞍点,即该位置上的元素在该行上最 大,在该列上最小。有可能没有鞍点。 5.52 输入n个字符串,用指向指针的指针的方法按从大到小的顺 序排列后输出。 5.53 输入n行英文字,对其进行一项英文语法检查,把每个英文 句子的第一个字母改为大写。假设每个英文句子可分别由标点符 号 '.'、'!' 或 '?' 结束。 5.54 将n个学生的数据包括学号、姓名、三门课成绩及总分放在一个结构体数组中,用指向指针的指针的方法,按三门课的总分从小到大的顺序排列后输出每个学生的数据。 5.55 桥牌使用52张扑克牌,编写一个模拟人工洗牌的程序,将 洗好的牌分别发给四个人。