第5章 指针 5.1 指针与指针变量 5.2 指针运算符 5.3 指针与一维数组 5.4 指向指针的指针 5.5 指针与结构

Slides:



Advertisements
Similar presentations
第九章 指针 西安工程大学.
Advertisements

第七章 指针 计算机公共教学部.
第六章 指针 指针的概念 指针变量 指针与数组 指针与函数 返回指针值的函数.
第7章 指针 存储地址的变量的类型就是指针类型 能直接对内存地址操作, 实现动态存储管理 容易产生副作用, 初学者常会出错
二级指针与二维数组.
C语言程序设计基础 第10章 指针进阶 刘新国.
10.1 二级指针 10.2 指针与二维数组 10.3 指针的动态存储分配 10.4 函数指针 10.5 main函数的参数
C语言程序设计.
第6章 指针 6.1 指针的概念 6.2 变量与指针 6.3 数组与指针 6.4 字符串与指针 6.5 函数与指针 6.6 返回指针值的函数
6.4 字符串与指针 1. 用字符数组存放一个字符串.
第六节 二维数组和指针 二维数组的地址 对于一维数组: (1)数组名array表示数组的首地址, 即array[0]的地址;
C语言实验 第一课 标题:学号+姓名.
8.1 指针的概念 8.2 指针变量 8.3 指针变量的基础类型 8.4 指针的运算 8.5 指针与一维数组 8.6 指针应用实例
C++中的声音处理 在传统Turbo C环境中,如果想用C语言控制电脑发声,可以用Sound函数。在VC6.6环境中如果想控制电脑发声则采用Beep函数。原型为: Beep(频率,持续时间) , 单位毫秒 暂停程序执行使用Sleep函数 Sleep(持续时间), 单位毫秒 引用这两个函数时,必须包含头文件
指 针 为什么要使用指针 指针变量 指针与数组 返回指针值的函数 动态内存分配 通过指针引用字符串 指向函数的指针 小 结 习 题.
第 十 章 指 针.
C语言高级编程(第四部分) 字符串 北京大学 信息科学技术学院.
Chap 8 指针 8.1 寻找保险箱密码 8.2 角色互换 8.3 冒泡排序 8.4 电码加密 8.5 任意个整数求和*
走进编程 程序的顺序结构(二).
辅导课程六.
元素替换法 ——行列式按行(列)展开(推论)
Zhao4zhong1 (赵中) C语言指针与汇编语言地址.
Zhao4zhong1 (赵中) C语言指针与汇编语言地址.
二维数组的指针表示 与复杂的指针例子 专题研讨课之三.
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
C语言 程序设计基础与试验 刘新国、2012年秋.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第二章 Java语言基础.
第五章 习题课 电子信息与计算机科学系 曾庆尚.
用event class 从input的root文件中,由DmpDataBuffer::ReadObject读取数据的问题
第五章 指针 5.1 指针的概念 5.2 指针与数组 5.3 字符串指针.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
1.3 C语言的语句和关键字 一、C语言的语句 与其它高级语言一样,C语言也是利用函数体中的可执行 语句,向计算机系统发出操作命令。按照语句功能或构成的不 同,可将C语言的语句分为五类。 goto, return.
C语言程序设计 主讲教师:陆幼利.
学习目标 1、什么是字符集 2、字符集四个级别 3、如何选择字符集.
C语言概述 第一章.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
线 性 代 数 厦门大学线性代数教学组 2019年4月24日6时8分 / 45.
C语言大学实用教程 第7章 指针 西南财经大学经济信息工程学院 刘家芬
指针 几个概念:  指针也是一种数据类型,具有指针类型的变量,称为指针变量。
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第6讲 指针与引用 6.1 指针 6.2 引用.
<编程达人入门课程> 本节内容 内存的使用 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群: ,
C语言程序设计 第一章 数据类型, 运算符与表达式 第二章 顺序程序设计 第三章 选择结构程序设计 第四章 循环控制 第五章 数组.
第4章 Excel电子表格制作软件 4.4 函数(一).
C++语言程序设计 C++语言程序设计 第四章 数组及自定义数据类型 C++语言程序设计.
第九节 赋值运算符和赋值表达式.
§6.7 子空间的直和 一、直和的定义 二、直和的判定 三、多个子空间的直和.
3.16 枚举算法及其程序实现 ——数组的作用.
本节内容 结构体 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第4课时 绝对值.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
ASP.NET实用教程 清华大学出版社 第4章 C#编程语言 教学目标 教学重点 教学过程 2019年5月5日.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C程序设计.
2.6 字符型数据 一、 字符常量 1、字符常量的定义 用一对单引号括起来的单个字符,称为字符常量。 例如,‘A’、‘1’、‘+’等。
第二章 Java基本语法 讲师:复凡.
C程序设计 实验二 数据类型、运算符和表达式 第6讲
第15讲 特征值与特征向量的性质 主要内容:特征值与特征向量的性质.
第二章 Java基本语法 讲师:复凡.
本节内容 结构体.
本节内容 指针类型的使用 视频提供:昆山爱达人信息技术有限公司.
程序设计基础A(C语言) 第一章 C语言概述 主讲教师: 许 康
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
C语言程序设计 第8章 指针.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
基本知识 数据类型、变量、常量、运算符.
第八章 指 针 北京邮电大学出版社.
§4.5 最大公因式的矩阵求法( Ⅱ ).
第7章 地址和指针 7.1 地址和指针的概念 7.2 指针变量的定义和指针变量的基类型 7.3 给指针变量赋值 7.4 对指针变量的操作
Presentation transcript:

第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张扑克牌,编写一个模拟人工洗牌的程序,将 洗好的牌分别发给四个人。