1、2019/7/10,1,C语言程序设计,第五章 一级指针与一维数组,主讲: 计算机学院 朱立华,2019/7/10,3,内容提要,直接引用与间接引用的不同方式及实质 指针是地址的类型,是对地址进行的一种类型抽象,用来实现间接访问,掌握以下指针的知识 指针类型与指针的基类型 指针的基本操作:间接引用、加减整数、逻辑及关系运算 指针与一维数组的关系,如何用指针操作一维数组 一维数组专用于一批类型相同的变量的处理 一维数组的定义及初始化,一维数组的基类型(数组的元素类型)与指针基类型的一致性,一维数组名就是指针常量 访问一维数组元素的方式间接引用方式,可随机访问 一维数组元素在内存中的存储方式,可随
2、机访问元素的实质 一维数组中的常见操作及算法:输入、输出、逆置、查找、插入、删除、排序,2019/7/10,4,间接引用的必要性,内容回顾:int m; 表示定义一个变量,m是变量名,即变量空间名,直接用m访问的是变量空间中的内容,称为直接引用 一个算法问题:输入10000个整数并求平均值,并且保留这些原始数据到最后统一输出 解决方案:将这10000个整数看作逻辑上前后相邻的并且可以存储在内存中一片连续的区域,只要知道这些数在内存中的起始地址,就能找到对应的空间,进而访问空间中的内容,这就是间接引用方式,因此: 要有变量可以存放地址-指针; 要有类型可以定义一个标识符代表一组连续空间的起始地址
3、-一维数组,间接引用方式下,传递某一个数据地址(一般是第1个数据的地址)相当于传递了全部数据,2019/7/10,5,一级指针类型(引言),C语言优越于其他高级语言的一个特性就是:可对硬件编程;通过第一章机器语言程序设计的学习,在计算机内部访问操作数首先是读取其在内存中的存储地址值,然后通过该地址值去访问该地址所在空间中的内容 C语言为了实现对硬件编程,必定要将内存地址抽象为一种类型,这就是(一级)指针类型,即指针类型是地址的类型,该类型的常量和变量值都是内存地址值。地址值是无符号整数,无论该地址的内存中数据是什么类型,地址值都占4个字节 显然,知道地址值是为了操作这个地址中所存储的内容。通过
4、地址来访问数据空间的方式称为间接引用 通过该地址去间接引用的数据类型称为地址的基类型 前面学习的通过存储空间的名称引用数据的方式称为直接引用,2019/7/10,6,一级指针类型(概述),直接引用与间接引用: 在C语言源程序中的直接引用方式经编译后在计算机内部实质上是间接引用,例如源程序中有下列代码: int n,m; /定义2个整型变量,n和m是变量名 double x; /定义1个双浮点型变量,x是变量名表 n=5; /直接引用,给n赋值5 m=n; /直接引用,将n中的值赋给m x=3.1415; /直接引用,给x赋值3.1415 经编译后,变量名消失,留下其地址及类型信息,如下表:,源
5、程序中,目标程序中,地址的基类型,到哪里访问数据,指示编译器怎样操作特定地址上的内存区域:该区域包含多少连续的字节,数据存储的格式,以及可以实施哪些基本操作,2019/7/10,7,一级指针类型(概述),C语言中,用一级指针类型作为地址的类型。 地址关联着两个存储空间,存放着两种不同的值,彼此又有紧密的联系,以上页表中地址0x12ff78为例如下:,整型变量m的空间,此为变量空间,编译后指向m的地址值,是指针常量值,因此,一级指针类型是复合类型,由指针的基类型名称加”*” 共同组成,例: int * -整型指针类型,即指针所指向的空间存int值 double * -双浮点型指针类型,指针字面值
6、常量,即指针常量空间的名称,2019/7/10,8,一级指针类型(用*间接引用),利用指针去访问它指向的基类型的空间分两步: (1)通过指针字面值常量取出基类型空间的地址,这是直接引用 (2)加间接引用运算符“*”,得到与基类型空间名称等价的表达式,称为间接引用表达式,这是间接引用,因此,*(int*)0x12ff78与m完全等价, m=5;也可以写成: *(int*)0x12ff78=5; 上机在VC+下演示程序5.1,总结:间接引用运算符*使得指针可以访问其基类型空间,2019/7/10,9,一级指针类型(基本操作),指针的基本操作加减一个整数 (1) 指针加一个整数i,是将当前地址值加上
7、i*sizeof(基类型) (2) 指针减一个整数i,是将当前地址值减去i*sizeof(基类型),总结:理论上,指针可以加减任何整数,但必须保证结果指针指向应用程序的数据空间,否则不能间接访问,sizeof(int)=4,(int*)0x12ff10+2=(int*)0x12ff18, 地址0x12ff18所指向的空间不是本程序的数据空间,无意义!,2019/7/10,10,一级指针类型(基本操作),指针的其它基本操作: (1) 指针可以进行逻辑运算,例:!(int*)0x12ff00的结果为0 (2) 指针可以进行关系运算,例:(int*)0x12ff00(int*)0x12ff08 的结
8、果为1(真) (3)两个类型相同的指针(即基类型相同的指针)可以相减,地址大的减地址小的,结果为两个地址间区域所含基类型数据个数。,2019/7/10,11,将物理上前后相邻、类型相同的一组变量作为一个整体引入C语言,这个整体称为(一维)数组类型的变量,简称(一维)数组,其中每一个变量称为数组元素,变量的个数称为数组长度或数组容量。 引入数组的目的:利用间接引用方式,访问一组数据。具体地说,从第一个数组元素的地址,计算出其他所有数组元素的地址,然后通过数组元素的地址,间接访问数组元素。 定义一个数组:需要: (1)指定数组名,这个名称代表着第一个数组元素的指针,是一个指针常量,等价于一个指针字
9、面值常量,称为数组指针 (2)指定数组元素的类型,它是数组指针的基类型,是间接引用方式的基础 (3)指定数组长度,即数组包含的元素个数,它决定了数组指针在进行加减整数的算术运算时的有效范围,一维数组类型(概述),2019/7/10,12,一维数组类型(数组定义),数组的定义格式为: 类型标识符 一维数组名整型常量表达式;例:int a5;,类型标识符是数组元素的类型(也称为数组的基类型)的标识,是1个合法的用户自定义标识符,代表整个数组空间的起始地址(第1个元素的地址),是1个指针常量,表示数组的长度,即数组元素的个数,必须用一个常量表达式而不能用变量,间接引用表达式,下标(索引)表达式,注意
10、:数组元素的下标从0开始,可以是整型常量或变量,需要保证范围在0整型常量表达式-1,否则越界,2019/7/10,13,一维数组类型(数组定义),2019/7/10,14,一维数组的初始化:在定义数组时为其全部或部分元素指定初值。 一维数组的初始化的形式:类型标识符 一维数组名整型常量表达式=常量1 ,常量2,常量n; 正确的初始化示例: int a5=1,2,3,4,5; int a =1,2,3,4,5; int a5=1,2*4; 错误的初始化示例:int a5=,2,3 ;int a5=1,2,3,4,5,6,7;,一维数组类型(初始化),所有元素都有初值,a0至a4的值依次为1,2,
11、3,4,5,相当于a5=1,2,3,4,5;当所有元素都有初值时,数组元素个数可缺省,自动等于初值个数,相当于a5=1,8,0,0,0;可以只对数组的前几个元素赋初值,其余元素的初值自动为0。注意!当不做初始化时所有数组元素的值均为随机数而不是0,对数组元素初始化时只能从左到右依次,只能缺省最右边的元素值,初值个数不能超过数组元素的个数,2019/7/10,15,一维数组定义(包括初始化)结束之后,只能对其元素进行访问,而不能对数组整体访问 错误的数组赋值示例:(1) int a5; a5=1,2,3,4,5; (2) int a5; a=1,2,3,4,5; (3) int a5=1,2,3
12、,4,5; int b5=a; b=a; 正确做法可以是:b0=a0; b1=a1;b2=a2;b3=a3;b4=a4; 最简洁通用的方法是:(设已有变量定义:int i;)for (i=0;i5;i+) bi=ai;,一维数组类型(赋值),错误原因:a5是对某个数组元素的引用,而长度为5的数组没有a5这个元素,不能用一组值给一个元素赋值,错误原因:数组名a在赋值语句中是指向第1个数组元素的指针常量&a0,常量不是左值,不能被赋值。,错误原因:一个数组不能给另一个数组初始化,错误原因:一个数组不能给另一个数组整体赋值,2019/7/10,16,程序5.2 编程定义并初始化一维数组,显示一维整型
13、数组所有元素的值及对应地址,并对数组的所有元素求和 思路: (1)首先需要定义并初始化数组(假设数组名为a),根据题意,还需要定义循环控制变量和累加器变量 (2)然后分别用一层循环输出所有的元素值、地址值 (3)再用一层循环求所有元素之和,最后输出和值 程序5.3从键盘上输入n(1n10)个整数,输出这些元素,再分别统计其中正数和负数的个数,并求出最大值与最小值 思路:,一维数组类型(举例),元素值有两种等价表示:ai和*(a+i),元素地址有两种等价表示:&ai和a+i,(1)首先需要定义数组以存储n个整数,再用一层循环输出元素值 (2)然后分别用一层循环作统计、找最大最小值 (3)最后输出
14、结果,2019/7/10,17,程序5.3:源程序代码,#include int main() int a10,i,n; int max,min; int positive=0,negative=0; do printf(“Please input n(110);printf(“Please input %d elements:n“,n);for (i=0;in;i+) scanf(“%d“,2019/7/10,18,程序5.3:源程序代码,for (i=0;i0) positive+; else if (aimax) max=*(a+i); else if (*(a+i)min) min=*
15、(a+i); printf(“positive=%dn“,positive); printf(“negative=%dn“,negative); printf(“max element=%dn“,max);printf(“min element=%dn“,min);return 0;,?思考:为什么这里的if不能省略?,?思考:为什么这里的if不能省略?,2019/7/10,19,回顾:int m; -m是一个整型变量的变量名,代表整型变量空间,m的类型标识为int 同理:int a5; -a是一个数组变量的变量名,代表整个数组空间,a的类型标识为int5 因此,数组名a有双重含义: (1)是
16、一个数组变量的变量名,代表整个数组空间,在&a,sizeof(a)中体现该含义,但是数组变量不能通过其名称直接引用数组元素,因此数组名称“退化”了 (2)是指向第一个数组元素的指针常量,所有元素的地址可根据该常量的地址值计算求得,从而方便实现间接访问数组的元素 程序5.4 设计程序验证d与&d的区别,重点关注d+1与&d+1的不同,(选讲)一维数组名的双重含义,2019/7/10,20,(选讲)一维数组名的双重含义,数组名具体代表什么意思,要依赖上下文而定,如下表:,2019/7/10,21,引入数组的目的:传递数组首元素地址,相当于传递整个数组空间所有元素的值。 数组首元素地址可以传给?基类
17、型相同的指针变量 指针变量的定义: 回顾: 整数类型 int - 整型变量 int x; 同理:基类型为整型的指针类型 int * - 指针变量 int * x; 一维数组名是指针常量,当然可以将值赋给基类型相同的指针变量,使指针变量和数组指针在间接引用方式下“共享”同一段数组空间,这相当于把数组空间的数据传给了指针变量,称为地址传递,例:int a5;int *p=a;,一级指针变量与一维数组,13ff58,a,p,13ff58,2019/7/10,22,当p=a后,有下列等价关系存在: (1)数组元素的表示:下标法:pi 等价于ai间接引用法:*(p+i) 等价于*(a+i) (2)数组元
18、素地址的表示:指针表达式法:p+i等价于a+i 元素取地址法:&pi等价于&ai 注意:(1)p是变量,因此它有存储空间可以取地址,即&p是指向p存储空间的指针常量,其基类型为int* (2)p是变量,一般获得数组首元素地址值,也可以获得任意元素的地址值,例:p=&a2 此时:p0等价于a2, p2等价于a2 程序5.5:用一级指针访问一维数组元素示例,一级指针变量与一维数组,13ff58,a,p,13ff58,13ff60,2019/7/10,23,一级指针变量定义时*的位置: 情况1:只定义一个指针变量,此时*位置可以近基类型名、近变量名或居中三者均可 例:int *p; int* p;
19、int * p; 情况2:一条定义语句既定义基类型的变量,又定义指针变量,此时,*近指针变量 例:int a,*p,b; 情况3:一条定义语句定义多个同类型的指针变量时,每个指针变量前都跟一个*号 例:int *p,*q; 简单总结: (1)“*”贴近指针变量总是正确的 (2)每个指针变量前面都必须有一个“*”,一级指针变量与一维数组,近变量名,近基类型名,居中,必须近变量名,2019/7/10,24,关于数组指针的基类型与指针变量基类型的一致性问题 目的1:如果传递数组指针的目的是传递数组的值,那么应该将数组指针传递给同类型的指针变量,类型不同时虽然可通过强制类型转换赋值,但无意义 例:in
20、t a5=10,15,20,25,30;double* pd; pd=(double*)a; float* pf; pf=(float*)a; 目的2:如果传递数组指针的目的在于高效使用内存资源,那么可以通过强制类型转换把该数组空间指针传递给其他类型的指针变量,把用过的数组空间用来存储其他类型的数据。 程序5.6 同一段空间,可用作不同类型的数组空间示例,一级指针变量与一维数组,强制类型转换使赋值有效但无意义,因为pd+ia+i且pdi ai,强制类型转换使赋值有效但无意义,因为虽然pf+i=a+i但pfi ai,这样做虽然节省了空间,但是容易引起数据访问的歧义实际编程中这种方法不常用,201
21、9/7/10,25,一级指针变量与一维数组,一个基类型的变量等价于长度为1的数组,2019/7/10,26,一级指针变量与一维数组,取址运算 却是错误的,因为m不代表地址,所以*m无意义。 回顾表5.1的内容: x=*(double*)0x12ff70两边同时进行取址运算“&”,再消掉互逆运算符得到: &x=&*(double*)0x12ff70=(double*)0x12ff70 最左和最右同时进行间接引用运算“*”,再消掉互逆运算符得到:*&x=*(double*)0x12ff70=x,2019/7/10,27,一级指针变量与一维数组,野指针:是指向”垃圾”内存的指针,有以下几种情况 (1
22、)指针变量没有被赋值,它存储的地址是不确定的 例:int *p; *p=100; (2)指针操作超越了变量的作用范围 例:int a3, *p=a; p=p+3; *p=100; (3)一个字面值指针常量,例如(int*)0x12ff7c,如果其值0x12ff7c不是系统已经分配给用户程序使用的空间地址。因此一般编程时很少直接使用字面值指针常量进行间接引用 (4)利用指针申请的动态空间被释放后,仍用该指针进行间接访问(具体见第6章6.5.3),一个没有赋值的“野”指针,用“野”指针间接引用很危险,指针获得数组首元素地址,指针增加3,出了数组范围,间接引用的空间不是程序的有效数据空间,2019/
23、7/10,28,一级指针变量与一维数组,void型指针:称为通用指针或泛指针。可以把任何类型变量的地址赋给它,但是不能利用这样的指针做相应类型的访问或处理。 例:void *p; int x=5; p= /非法 void指针主要用于单纯的内存数据的拷贝。在第11章的“流与文件”中,将遇到这样的指针。,2019/7/10,29,一级指针变量与一维数组,回顾:当一级指针变量的基类型与一维数组的基类型一致时,可以将一维数组名赋值给指针变量,这时对数组元素的访问既可以用一维数组也可以用指针变量。 用一维数组访问数组的元素只能通过改变下标的方式 用一级指针变量间接访问一维数组的元素可以通过移动下标(指针
24、不动)或移动指针(指针改变)两种方式 程序5.7 分别用移动下标的方法和移动指针的方法实现输出数组的所有元素。 在方法1中,pai+实际上先访问pai,再执行i=i+1,当循环结束时,指针pa还是等于a即&a0,pai+也可写成ai+ 在方法2中,*pa+相当于*(pa+),这里是后+,因此相当于先访问*pa,再执行pa=pa+1,当循环结束时,pa等于a+5。由于a是常量,故*pa+不能写成*a+,2019/7/10,30,一级指针变量与一维数组,移动下标与移动指针访问数组的元素例示: 程序5.8 调用随机函数产生10个1至100内的整数作为数组的元素,然后再逆置存放,输出初始序列和逆置以后
25、的序列。 问题分析:本题中首先需要定义一个含10个元素的整型数组,数组的元素值需要在一个循环体中通过调用随机函数逐个赋值,然后用循环输出所有元素。 接下来是数组的逆置,最简单的方法是从两边向中间对应位置的元素两两互换。 方法一:采用移动下标法,用i和j分别指示待交换的两个元素的下标,i从0开始递增,j从9开始递减,循环条件为ij。 方法二:采用移动指针法,设两个指针p和q分别指向第一个元素和最后一个元素,交换对应的*p和*q,然后p+同时q-,循环条件为pq。,2019/7/10,31,一级指针变量与一维数组,程序5.8 方法一核心代码 for (i=0,j=9;ij;i+,j-) temp=
26、ai;ai=aj;aj=temp; 等效于:(只用 i 控制下标) for (i=0;i10/2;i+) temp=ai;ai=a10-1-i;a10-1-i=temp; ,程序5.8 方法二核心代码 for (p=a,q=a+9;pq;p+,q-) temp=*p;*p=*q;*q=temp; 等效于:(只用 p 控制指针变化) for (p=a;pa+10/2;p+) temp=*p;*p=*(a+10-1-(p-a);*(a+10-1-(p-a)=temp; ,2019/7/10,32,一维数组应用举例(查找),应用之一 在数组中查找一个元素 查找算法很多,这里只介绍最简单的顺序查找法.
27、 数组元素的初始序列值无任何要求,即不要求有序。 查找的过程:只能是从第一个元素开始依次与待查找的元素进行比较,如果相等就查找成功,输出元素及对应下标;如果与所有元素都比较结束仍没有相等元素,则输出元素不存在的提示信息。 程序5.9 从键盘上输入n(1n10)个整数作为数组a的元素值,再读入一个待查找的整数x,在a数组中查找x,如果存在输出它的下标,否则提示:“Not present!” 提示:本程序完整的循环条件:in&x!=*(p+i),2019/7/10,33,一维数组应用举例(插入),应用之二 向数组中插入一个元素 插入算法有很多种,本程序是要求在有序数组中插入一个数据元素,并保持数组
28、有序。 具体步骤是: (1)确定待插入位置(此步骤伴随着查找过程); (2)元素后移,腾出相应位置(用递减循环实现,后移就是作形如aj+1=aj;的赋值) (3)在“空”位置上插入新元素(作一次赋值) 程序5.10 整型数组a中的元素值已按非递减有序排列,再读入一个待插入的整数x,将x插入数组中使a数组中的元素保持非递减有序。,2019/7/10,34,一维数组应用举例(删除),应用之三 删除数组中的一个元素 数组空间中的数据只能修改,不能“擦除”,也不能用“撤销”其空间的方法去掉。删除的具体步骤是: (1)确定待删除元素的位置(此步骤伴随着查找过程)(2)元素从删除位置开始依次前移,覆盖待删
29、除元素(用递增循环实现,前移是作形如aj=aj+1;的赋值) 最后,数组的最后一个元素实际上有两份拷贝,有效元素个数减一。 程序5.11 整型数组a中有若干个元素,再读入一个待删除的整数x,删除数组中第一个等于x的元素,如果x不是数组中的元素,则显示:“can not delete x!”。,提示:运行时测试删除的元素在最前、中间、最后以及不存在这几种情况,2019/7/10,35,一维数组应用举例(排序),应用之四 将数组的元素依一定顺序排序 排序算法很多,这里只介绍最简单的简单选择排序. 基本思想:把数组分为左右两个半区,左半区为有序子集,右半区为无序子集。开始时,左半区为空。假设数组有n
30、个数据元素,进行n-1趟选择。每一趟选择都在无序子集(右半区)中选择出最小元素,并与无序子集首元素交换,然后将该元素并入有序子集。 程序5.12 从键盘上输入n(1n10)个整数,首先输出这些元素,再用选择法排序,最后输出排序后的元素。 提示:共进行n-1趟排序,每趟最多交换一次,2019/7/10,36,本章小结,本章介绍了C语言中非常常用的两种数据类型指针和数组,非常重要! 重点掌握一级指针变量和一维数组的定义及使用,理解这两种类型对于高效率地处理一批性质相同数据的意义和原理: 直接访问与间接访问,间接访问的本质与高效性体现 指针是对地址进行的一种抽象类型,指针类型与其基类型之间的关系 与
31、指针有关的运算:取址&、间接引用*、赋值、加减等 一维数组的引入解决了对于一批类型相同的变量的处理问题,普通变量可以理解为长度为1的特殊一维数组 对数组元素的访问本质上就是间接引用,可以用数组名,也可以用基类型一致的指针通过移动下标或移动指针方式 地址可赋给指针,2019/7/10,37,本章小结,熟悉本章关于指针和数组应用的一些经典示例并能举一反三解决类似问题: 输入/输出数组的元素 -用数组或指针操作 调用随机函数产生数组的元素 数组元素的求和求平均求最大最小值问题 数组元素的逆置问题 查找某一个值是否是数组的元素 向数组中插入一个元素 从数组中删除一个元素 用选择法对数组元素进行排序 难点: 一维数组名的双重含义 通过指针字面值常量进行间接引用访问 野指针问题,数组的越界访问,The end of chapter 5,