C语言大学实用教程 第7章 指针 西南财经大学经济信息工程学院 刘家芬 jfliu@swufe.edu.cn.

Slides:



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

第七章 指针 计算机公共教学部.
第六章 指针 指针的概念 指针变量 指针与数组 指针与函数 返回指针值的函数.
第十章 指针 分析C程序的变量所存放的数据:  数值型数据:整数、实数  字符型数据:字符、字符串 这些变量具有以下性质:
第7章 指针 存储地址的变量的类型就是指针类型 能直接对内存地址操作, 实现动态存储管理 容易产生副作用, 初学者常会出错
第七章 指针 教 材: C程序设计导论 主 讲: 谭 成 予 武汉大学计算机学院.
二级指针与二维数组.
C语言程序设计基础 第10章 指针进阶 刘新国.
第 6 章 第 6 章 指 针 指 针 1.
C语言程序设计.
C语言基础——指针的高级应用 Week 05.
第6章 指针 6.1 指针的概念 6.2 变量与指针 6.3 数组与指针 6.4 字符串与指针 6.5 函数与指针 6.6 返回指针值的函数
6.4 字符串与指针 1. 用字符数组存放一个字符串.
第六节 二维数组和指针 二维数组的地址 对于一维数组: (1)数组名array表示数组的首地址, 即array[0]的地址;
8.1 指针的概念 8.2 指针变量 8.3 指针变量的基础类型 8.4 指针的运算 8.5 指针与一维数组 8.6 指针应用实例
C++中的声音处理 在传统Turbo C环境中,如果想用C语言控制电脑发声,可以用Sound函数。在VC6.6环境中如果想控制电脑发声则采用Beep函数。原型为: Beep(频率,持续时间) , 单位毫秒 暂停程序执行使用Sleep函数 Sleep(持续时间), 单位毫秒 引用这两个函数时,必须包含头文件
字符串与二维数组.
第九章 指针 目录 指针与指针变量的概念 变量的指针和指向变量的指针变量 数组的指针和指向数组的指针变量
第5章 函数与模块化设计 学习目的与要求: 掌握函数的定义及调用方法 理解并掌握参数的传递方法 理解函数的嵌套与递归调用
程序设计基础.
第6章 指针 学习目的与要求: 了解指针的概念和相关术语 熟练掌握指向变量、数组和字符串的指针变量的使用方法 了解指向函数的指针变量
指 针 为什么要使用指针 指针变量 指针与数组 返回指针值的函数 动态内存分配 通过指针引用字符串 指向函数的指针 小 结 习 题.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
目录 10.1 指针的基本概念 10.2 指向变量的指针变量 10.3 指向数组的指针变量 10.4 指向函数的指针变量和指针型函数
第八章 指 针 8.1 指针的概念与定义 8.2 指针作函数参数 8.3 指针与数组 8.4 指针与函数 8.5 复杂指针.
第 十 章 指 针.
项目六 用指针优化学生成绩排名 项目要求 项目分析
第七章 函数 目录 有参的加法函数的开发 函数定义的一般形式 函数参数和函数的值 函数的调用
第八章 函数.
第8章 善于利用指针 8.1 指针是什么 8.2 指针变量 8.3 通过指针引用数组 8.4 通过指针引用字符串 8.5 指向函数的指针
第8章 善于利用指针 8.1 指针是什么 8.2 指针变量 8.3 通过指针引用数组 8.4 通过指针引用字符串 8.5 指向函数的指针
二维数组的指针表示 与复杂的指针例子 专题研讨课之三.
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第8章 指针.
第八章 使用指针.
第十章 指针.
欲穷千里,更上层楼 第十章 指 针 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; 能很方便地使用数组和字符串; 并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。 学习指针是学习C语言中最重要的一环,
第五章 习题课 电子信息与计算机科学系 曾庆尚.
9.1 地址、指针和变量 9.2 指针运算 9.3 指针与数组 9.4 函数与指针 9.5 程序综合举例 9.6 上机实训.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C语言大学实用教程 第5章 函数与程序结构 西南财经大学经济信息工程学院 刘家芬
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C语言复习3----指针.
线 性 代 数 厦门大学线性代数教学组 2019年4月24日6时8分 / 45.
C语言大学实用教程 第6章 数组 西南财经大学经济信息工程学院 刘家芬
函数 概述 模块化程序设计 基本思想:将一个大的程序按功能分割成一些小模块, 特点: 开发方法: 自上向下,逐步分解,分而治之
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第6讲 指针与引用 6.1 指针 6.2 引用.
<编程达人入门课程> 本节内容 内存的使用 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群: ,
C语言程序设计 第一章 数据类型, 运算符与表达式 第二章 顺序程序设计 第三章 选择结构程序设计 第四章 循环控制 第五章 数组.
第六章 指针 C++程序设计中使用指针可以: 使程序简洁、紧凑、高效 有效地表示复杂的数据结构 动态分配内存 得到多于一个的函数返回值.
第6章 指针 6.1 指针的概念 6.2 变量与指针 6.3 数组与指针 6.4 字符串与指针 6.5 函数与指针 6.6 返回指针值的函数
第十章 指针 指针是C语言的重要概念,是C语言的特色,是C语言的精华。 10.1 地址和指针的概念 内存中的每一个字节都有一个地址。
C程序设计.
第九章 指针.
第九节 赋值运算符和赋值表达式.
3.16 枚举算法及其程序实现 ——数组的作用.
本节内容 结构体 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第8章 善于利用指针 8.1 指针是什么 8.2 指针变量 8.3 通过指针引用数组 8.4 通过指针引用字符串 8.5 指向函数的指针
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C程序设计.
本节内容 C语言的汇编表示 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
本节内容 结构体.
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
C语言程序设计 第8章 指针.
第九章 指针 C程序设计中使用指针可以: 使程序简洁、紧凑、高效 有效地表示复杂的数据结构 动态分配内存 得到多于一个的函数返回值.
第八章 指 针 北京邮电大学出版社.
第7章 地址和指针 7.1 地址和指针的概念 7.2 指针变量的定义和指针变量的基类型 7.3 给指针变量赋值 7.4 对指针变量的操作
Presentation transcript:

C语言大学实用教程 第7章 指针 西南财经大学经济信息工程学院 刘家芬 jfliu@swufe.edu.cn

数据在内存中的存储 计算机的内存区由连续的内存单元构成,每个内存单元的大小是一个字节,每一个字节有一个编号,也就是常说的"地址"。 每一个变量在内存中都占据一定内存空间,空间的大小由变量的类型确定。整型变量在内存中占用2个字节,float型变量在内存中占用4个字节。 在程序中一般是通过变量名来对内存单元进行存取操作的。而实际上,编译过程中所有的变量名都会被转换为变量的地址,计算机对变量的值的存取,都是通过地址来进行的。

假设程序中定义了2个整型变量i和j,编译器在编译的时候,为变量i分配地址编号为2000和2001的两个字节;而为变量j分配2002、2003的两个字节。 如果有语句 j=i+j,计算机在执行过程中将首先从地址2000、2001中取出其中的值(变量i的值),然后从地址2002、2003中再取出其中的值(变量j的值),然后将这两个值进行相加,将相加的结果保存在内存地址2002、2003中。 这种通过变量地址直接存取变量值的方式,称为"直接访问"方式。

除了"直接访问"方式外,还可以采用"间接访问"方式,来对一个变量的值进行存取: 将变量i的地址2000,存放在另一个变量i_pointer当中,访问的时候,通过取出i_pointer的值,找到变量i的地址2000,然后再从2000、2001中,取出变量i的值。 i_pointer i 这种间接访问,从变量i_pointer开始,最终也能访问到变量i的值。 2000 2000 由于通过地址能找到所需的变量,因此可以说地址指向该变量单元。在C语言中,将地址形象化地称作“指针”。

指针和地址的概念 变量的值保存在内存单元中,该内存单元可以通过地址来标识。 变量的地址即存放数据的内存地址,就是该变量的指针。 指针可以保存在变量中,这种专门用来保存指针的变量,称为指针变量。 指针变量i_pointer中保存了变量i的地址2000。2000指向变量i,我们也可以说指针变量i_pointer指向变量i。

变量的指针和指向变量的指针变量 变量的指针就是变量的地址,而指针变量就是指存放地址型数据的变量。 为了表示指针变量和所指向的变量之间的联系,C语言中用*表示“指向”关系。 右图中(*i_pointer)就是指针变 量i_pointer所指向的变量,即i。 指针变量的定义 格式:基类型 * 指针变量名; 基类型是该指针变量指向的变量的类型。例如: float *p; (p是指向浮点型变量的指针变量) int *q; (q是指向整型变量的指针变量) i_pointer *i_pointer 3 i 2000 2000

注意: 指针变量名前面的*表明该变量为指针变量,指针变量的名字是p、q,而不是*p、*q。 在定义指针变量时必须指定基类型。基类型规定了指针类型变量所指向的变量的类型,一个指针变量只能指向同一类型的变量。 例如: float *p; 这里定义的指针类型变量p只能指向float型的变量,也就是说,它只能保存一个float型变量的地址,而不能保存其他类型变量(比如int型的变量)的地址。以下是错误的: int i; float *p=&i;

定义了指针变量后,如何使其指向某个变量? 可以通过赋值语句使指针变量得到另一个变量的地址,从而使它指向另一个变量: int i=6; int *p; p=&i; (这个赋值语句将整型变量i的地址赋给了指针变量p,从而使p指向了变量i) p i &i 6

指针变量的引用 指针变量只能存放地址,不能将一个整数(或其他非地址类型的数据)赋给一个指针变量。例如下面是错误的: int *p=100; 两个有关的运算符: (1) & : 取地址运算符 (2) * : 指针运算符,取其指向的变量的内容 例:int a,*p; p=&a; *p=5; 使p所指向的那个变量的值为5,等价于a=5;

例7.1 #include <stdio.h> void main() { int a,b; int *p1,*p2; p1=&a;p2=&b; printf("%d,%d\n",a,b); printf("%d,%d\n",*p1,*p2); }

说明: 在开始定义了两个指针变量p1和p2,但是定义的时候,并没有给它们赋初值,这个时候,p1和p2并未指向任何变量。 "p1=&a;p2=&b;"这两个语句分别使用了取地址运算符,使p1指向了变量a,p2指向了变量b。 程序最后的*p1和*p2,其实就是变量a和b。所以最后两个printf函数的作用是相同的。 程序中两次出现了*p1和*p2,它们代表不同的意义。 第一次出现是: int *p1,*p2; 用于声明p1和p2是两个指针变量。 第二次出现是: printf("%d,%d\n",*p1,*p2); 这里*p1和*p2分别代表p1和p2所指向的变量的内容。

有如下程序段: int a,*p; p=&a; 问:(1) &*p的含义是什么? 分析:&和*这两个运算符的优先级相同,按照右结合,先进行*p的运算,它就是变量a,然后再执行&运算,即&a运算,得到变量a的地址。 (2) *&a的含义是什么? 分析:和上面类似,&a运算是取变量a的地址,然后执行*运算,即&a所指向的变量,即是变量a。

例7.2 输入a和b两个整数,按照先大后小的顺序输出a和b。 #include <stdio.h> void main( ) { int *p1,*p2,*p, a, b; scanf("%d,%d",&a,&b); p1=&a; p2=&b; if(a<b) {p=p1; p1=p2; p2=p;} printf("max=%d,min=%d\n",*p1,*p2); }

这个程序的算法思想是:使用p1和p2分别指向a和b。当a小于b的时候,交换p1和p2的内容,使得p1指向b,p2指向a。输出时,先输出p1指向的那个变量,后输出p2指向的那个变量。 5 5 p2 p2 b b &b &a 10 10 (2)输出时 (1)开始时

指针变量作为函数参数 指针变量也可以作为函数参数来传递信息 例7.3 输入两个整数,按照先大后小的顺序输出。

#include <stdio.h> void main() { void swap(int *p1,int *p2); int a,b, *point_1,*point_2; scanf("%d,%d",&a,&b); point_1=&a; point_2=&b; if(a<b) swap(point_1,point_2); printf("max=%d,min=%d\n",a,b); } void swap(int *p1,int *p2) int temp; temp = *p1; *p1 = *p2; *p2 = temp;

分析程序: 1.在main函数中,swap的实参是point_1,point_2。这两个指针变量分别保存了a和b的地址。 2.在swap被调用时,形参p1和p2分别被赋予实参point_1和point_2的值,因此,p1指向变量a,p2指向变量b 3.swap函数的执行,使p1指向的变量(a)的内容和p2指向的变量(b)的内容进行了交换。 4.在main中按先a后b的顺序打印结果。这个时候,a中保存的值,大于b中保存的值的。

p1 p1 &a a &a a point_1 5 point_1 9 &a &a p2 p2 &b b &b b point_2 9 point_2 5 &b &b swap结束时 swap开始时

思考 能否使用普通变量作函数参数,即 void swap(int x,int y) { int temp; temp=x; x=y; y=temp; } 并在main函数中调用swap: swap(a,b);

答案:不行。因为a和b的值在传递给swap的形参x和y后,不会改变(我们称为值传递): 若要使被调函数对某个变量的值的改变,在主调函数中可见,那么可以使用指针变量作函数参数。调用时,传递变量的地址给被调函数。 a b a b 5 9 5 9 5 9 9 5 x y x y swap开始时 swap即将结束时

另一个解决方案,是否可行? #include <stdio.h> void main() { void swap(int *p1,int *p2); int a,b, *point_1,*point_2; scanf("%d,%d",&a,&b); point_1=&a; point_2=&b; if(a<b) swap(point_1,point_2); printf("max=%d,min=%d\n",*point_1,*point_2); } void swap(int *p1,int *p2) { int *p; p=p1; p1=p2; p2=p; 编程者意图通过swap,交换main函数中的point_1和point_2的内容。但是这是不能实现的,因为swap只能交换形参p1和p2的内容,而不能把这种影响反映到main中的point_1和point_2中去。

*p是指针变量p所指向的变量,而p指向哪里? 如果对*p赋值,可能造成系统崩溃! 再一个解决方案,是否可行? #include <stdio.h> void main() { void swap(int *p1,int *p2); int a,b, *point_1,*point_2; scanf("%d,%d",&a,&b); point_1=&a; point_2=&b; if(a<b) swap(point_1,point_2); printf("max=%d,min=%d\n",*point_1,*point_2); } void swap(int *p1,int *p2) { int *p; *p=*p1; *p1=*p2; *p2=*p; *p是指针变量p所指向的变量,而p指向哪里? 如果对*p赋值,可能造成系统崩溃!

数组和指针 数组也要占用内存。数组占用一个连续的内存区域,数组中元素在内存中的存储位置是相邻的。例如:int a[6]; 2000 2002 2004 2006 2008 2010 a a[0] a[1] a[2] a[3] a[4] a[5]

指向数组元素的指针 数组元素相当于变量,所以指向数组元素的指针,和指向变量的指针相同: int a[10]; int *p; p= &a[0]; (p指向数组a中的第一个元素a[0]) 数组名代表数组的首地址,而数组的首地址和数组中第一个元素的地址是相同的,所以下面两条语句等价: p= &a[0]; p=a;

通过指针引用数组元素 程序段:int a[10]; int *p=&a[9]; *p=100; 的作用是? 将数组a中最后一个元素a[9]赋值为100

C语言规定:如果指针变量p已经指向数组中的一个元素,则p+1指向同一个数组中的下一个元素。例如: int a[10]; int *p=&a[0]; *(p+1)=100;(等价于a[1]=100;) 这里p+1不是简单的对地址数值进行加1。假设数组a的首地址为2000,p初始值为2000,p+1的值为2002,因为int型的变量在内存中占两个字节,p+1为2002,指向元素a[1] 若p指向数组中第一个元素a[0],p+i和a+i就是a[i]的地址,指向数组元素a[i];*(p+i)和*(a+i)就代表数组元素a[i]。

注意:指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价 数组名也是一个指针变量,它指向数组的首地址。因此: int a[10]; *(a+5)=100;等价于a[5]=100;

例7.4 输出数组中全部元素 (1)使用下标法访问 #include <stdio.h> void main() { int a[10]; int i; for(i=0;i<10;i++) scanf("%d",&a[i]); printf("%d ", a[i]); }

(2) 使用数组名计算数组元素地址,找出元素的值 #include <stdio.h> void main() { int a[10]; int i; for(i=0;i<10;i++) scanf("%d",&a[i]); printf("%d ", *(a+i)); }

#include <stdio.h> void main() { int a[10]; int i,*p; (3) 使用指针变量指向数组元素 #include <stdio.h> void main() { int a[10]; int i,*p; for(i=0;i<10;i++) scanf("%d",&a[i]); for(p=a;p<(a+10);p++) printf("%d ", *p); } 注意:p作为指向数组元素的指针变量,可以通过改变指针变量的值指向不同的元素。可以进行p++操作,即p的值可以更改;数组名a虽然也作为指针,但是a的值不能修改,因此不能进行a++操作。

注意: 1. int a[10]; int *p=a; 则p+10指向的内存地址,已经超出了数组a的范围。如果程序中出现 *(p+10)=100; 已经超出了数组a的边界。因为不确定(p+10)指向的内存区域用于何种目的,所以这样赋值可能会使系统崩溃。 2.比较*(p++) *(++p) ++(*p) *p,然后p指向数组下一元素 p++使p指向数组下一元素,读取当前*p 使p指向的内存单元中的值加1

指向同一数组的指针变量之间的减法运算 指针变量1-指针变量2 结果为:两个指针指向的数组元素间的距离 例:指向同一数组的指针变量相减 float x[10],*p1=x+2,*p2=&x[8]; p1-p2 的值是-6 p2-p1 的值是6 说明两个元素之间的距离是6

用数组名作函数参数 第六章中已经讲过:用数组名作函数参数时,实际上传递的只是实参数组的首地址;在内存中只分配了一个数组空间,而不是两个。被调函数中对形参数组的修改,在主调函数中可见。 在掌握了指针的概念之后,我们再来深入分析一下用数组名作函数参数。

void f(int arr[ ],int n) { …… } 实参数组名array代表该数组的首地址,形参用来接收从实参传递过来的首地址,因此形参应该是一个指针变量,因为只有指针变量能存放地址。 使用数组名作函数参数时,形参int arr[ ] 表示arr是数组名,C编译器会将形参数组名作为指针变量来处理。因此void f(int arr[ ],int n) 和void f(int *arr,int n) 是等价的,编译器会建立一个指针变量arr,用来存放从主调函数中传过来的实参数组的首地址。 void main( ) { …… int array[10]; f(array,10); }

实参数组名为array ,当调函数f时,形参arr接受array数组的首地址,因此arr也指向array数组。 根据前面讲的指向数组元素的指针所具有的性质,我们可以使用几种等价的方式来访问数组array中的元素。例如array[1]还可以用以下两种方式来表示:arr[1]、*(arr+1)。 arr array arr+1

数组名作函数参数时,C编译器将形参数组名作为指针变量来处理。传递给这个形参的值是实参数组的首地址。 注意: 1. 因为形参数组名被编译成指针变量,所以可以改变它的值。例如 "arr++;" 是允许的。 2. 而实参数组名array虽然也指向数组中第一个元素,但是array的值不能被修改,它是一个指针常量。例如 "array++;" 是错误的。

例7.5 将数组a中的n个整数按相反顺序存放 #include <stdio.h> void main() { void inv(int x[ ],int n); int i, a[10]={3,7,9,1,0,6,7,5,4,2}; printf("The original array:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); inv(a,10); printf("The array has been inverted:\n"); }

3 7 9 1 6 5 4 2 2 4 5 7 6 1 9 3 void inv(int x[],int n) { int temp,i,j,m=(n-1)/2; for(i=0; i<=m; i++) j=n-1-i; temp=x[i]; x[i]=x[j]; x[j]=temp; } 3 7 9 1 6 5 4 2 j i m 2 4 5 7 6 1 9 3

算法:将a[0]与a[n-1]互换,再将a[1]与a[n-2]互换…直到中间元素。用for循环处理,i和j分别指示需要对换的两个元素位置,初值为0和n-1,然后i++并且j--,直到i=(n-1)/2。 第一个形参x实际上是一个指针变量,x指向数组a的首元素。通过x[i]指向的实际上就是a[i]。 第二个形参n用来接收需要进行处理的元素的个数。可以通过inv(a,5)表示对数组前5个元素进行颠倒。 注意:算法并不是唯一的!

对程序进行修改,将inv函数中的参数x改成指针: #include <stdio.h> void main() { void inv(int *x,int n); int i, a[10]={1,3,5,7,9,11,13,15,17,19}; printf("The original array:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); inv(a,10); printf("The array has been inverted:\n"); }

3 7 9 1 这个函数里采用了指向整型变量 的指针变量i和j,分别指向将要 6 交换的两个数组元素。 5 4 2 a数组 i,x void inv(int *x,int n) { int *p, temp,*i, *j, m=(n-1)/2; i=x;j=x+n-1;p=x+m; for(;i<=p;i++,j--) temp=*i; *i=*j; *j=temp; } 这个函数里采用了指向整型变量 的指针变量i和j,分别指向将要 交换的两个数组元素。 3 7 9 1 6 5 4 2 a[0] a[1] a[2] p=x+m a[3] a[4] a[5] a[6] a[7] a[8] j a[9]

归纳起来,如果有一个实参数组,要想在函数中改变此数组中的元素的值,实参和形参可分别为: 形参和实参都用数组名 实参用数组名,形参用指针变量 实参形参都用指针变量 实参用指针变量,形参用数组名 void main( ) { int a[10]; int *p=a; … f(p,10); } void f(int x[ ],int n) void main( ) { int a[10]; int *p=a; … f(p,10); } void f(int *x,int n) void main( ) { int a[10]; … f(a,10); } void f(int *x,int n) void main( ) { int a[10]; … f(a,10); } void f(int x[],int n)

这个main()有问题吗? #include <stdio.h> void main() { void inv(int *x,int n); int i, *a; printf("The original array:\n"); for(i=0;i<10;i++) scanf("%d",a+i); printf("\n"); inv(a,10); printf("The array has been inverted:\n"); printf("%d",*(a+i)); } 这个main()有问题吗?

总结:在指针变量的值未被初始化时,没有指向某个变量,不要使用该指针变量! #include <stdio.h> void main() { int a=5,*p1=&a,*p2; *p2=*p1;  printf("%d,%d\n",*p1,*p2); } warning:p2变量没有初始化。 p2指向一个随机的内存空间,程序通过*p2=*p1;这条语句,对这个未知内存空间的值进行改变,这是很危险的。如果这个地址存放的是系统关键程序内容,可能会使系统崩溃。 总结:在指针变量的值未被初始化时,没有指向某个变量,不要使用该指针变量!

例7.6选择法排序 void sort(int x[],int n) { int i, j, k, t; for(i=0;i<n-1;i++) k=i; for(j=i+1;j<n;j++) if (x[j]>x[i]) k=j; if(k!=i) {t=k[i];x[i]=x[k];x[k]=t;} }

多维数组与指针 回顾"一维数组与指针"小节所讲的内容: 1.数组名是个指针常量,它指向数组中的首元素。 2.若数组名为a,a+i指向数组中第i+1个元素a[i];对数组中第i个元素的访问,可以使用a[i],也可以使用*(a+i)。

多维数组元素的地址 C语言把二维数组,作为一维数组来处理;这个一维数组的每个元素,又是一个一维数组。 例如int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}} 数组a是一个二维数组。C语言把a被看作一个一维数组,它包含3个元素a[0],a[1],a[2]。 而每一个元素,又是一个一维数组。例如a[0]是一维数组名,a[0]这个一维数组包括了a[0][0], a[0][1], a[0][2],a[0][3]这4个元素。

a(2000) a[0] 1 3 5 7 = a[1] = 9 11 13 15 a[2] = 17 19 21 23 按照一维数组的知识: 数组名是个指针常量,它指向数组中的第一个元素。可以得出:二维数组名a是个指针常量,它指向数组中第一个元素a[0] (a[0]同时也是一个一维数组{1, 3, 5, 7})。 同时,我们也可以得出:a=2000的话,a+1=2008,因为a+1指向a数组中第二个元素a[1](每一行是一个一维数组,包含4个整型数,占用8字节) 因为a+1指向a数组中的第二个元素a[1],因此可以用*(a+1)来表示第二个元素a[1](a[1]是一维数组{9,11,13,15})

a(2000) a[0] 1 3 5 7 = a[1] = 9 11 13 15 a[2] = 17 19 21 23 a[0],a[1],a[2]是3个一维数组。因此,a[0]作为一维数组的名字,代表数组{1,3,5,7}的首地址,即&a[0][0]。同样,a[1]中的值是&a[1][0],a[2]中的值是&a[2][0]。 考虑0行1列的元素地址如何表示? 答:a[0]+1。也可以用&a[0][1]来表示。a[0]的值为2000,a[0]+1的值为2002。 a[0]可以用*(a+0)来表示 因此0行1列的元素的地址a[0]+1也可以用*(a+0)+1来表示 i行j列的元素的地址为a[i]+j或*(a+i)+j 那么i行j列的元素的值,可以用*(*(a+i)+j)来表示。

在分析二维数组的时候,首先把二维数组看成一个一维数组,然后按照一维数组的方法来处理。 二维数组作为一维数组处理,其中的每一个元素,又是一个一维数组。对这些一维数组,也使用一维数组的方法来处理。

指向多维数组元素的指针变量 (1)指向数组元素的指针变量 例7.7 用指针变量输出二维数组元素的值 include <stdio.h> void main() { int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; int *p; for(p=a[0];p<a[0]+12;p++) { if((p-a[0])%4==0) printf("\n"); printf("%4d",*p); } printf("\n");

因为p是指向int型变量的指针变量,每次p++操作,p指向下一个数组元素。 分析: 因为p是指向int型变量的指针变量,每次p++操作,p指向下一个数组元素。 二维数组元素按行依次存放,先存第一行,然后存第二行,再存第三行...... 1 3 5 7 9 11 13 15 17 19 21 23 p p+1

注意:a[i][j]的地址是&a[0][0]+i*m+j。其中m是二维数组的第二维的大小 a[1][2]是a[0][0]后面第1*4+2个元素

指向一维数组的指针变量 分析下面几个定义的含义: int *p; (定义一个指针变量p,p指向一个整型变量) int a[10];(定义一个长度为10的整型数组a。a是数组名,因此可以看作指针常量,a指向数组的首元素) int (*q)[10]; (定义一个指针变量q,它指向一个长度为10的整型数组) 以上定义的p和a,它们指向的内容是一个整型元素,占2个字节。但指针q,它指向的是一个数组,占2*10个字节。

例7.8 输出二维数组中任意一个元素 include <stdio.h> void main() { int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; int (*p)[4],i,j; /*定义p为指针变量,指向包含4个元素的整型数组*/ p=a; for(i=0;i<3;i++) for(j=0;j<4;j++) printf("%2d ",*(*(p+i)+j)); printf("\n"); }

说明: “int (*p)[4];” 表示p是一个指针变量,它指向包含4个整型元素的一维数组。这里如果不加括号int *p[4],表示定义一个指针数组(后面会讲到),因此括号不能省略。 a是二维数组名,因此a指向数组中的第一个元素a[0]。a[0]是一个包含4个整型元素的一维数组。因此,p=a这个赋值是合理的。

3. p+i是二维数组a的i行的起始地址,即元素a[i]的地址。 而*(p+i)表示的是二维数组的第i个元素a[i],它即是包含4个整型元素的普通一维数组名。因此对应的第j个元素的地址可以用*(p+i)+j来表示,这个地址中存放的值就用*(*(p+i)+j)表示。 p a[0] 1 3 5 7 a[1] 9 11 13 15 p+2 17 19 21 23 a[2]

用指向数组的指针作函数参数 一维数组名可以作函数参数,二维数组名也可以作函数参数。二维数组名,就是一个指向一维数组的指针。 例7.9 有一个班,3个学生,学习4门课程,计算总的平均分,以及第n个学生的成绩。

#include <stdio.h> void main() { void average(float *p, int n); void search(float (*p)[4], int n); float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}}; average(*score,12);/*12个成绩的平均值*/ search(score,2);/*求序号为2的学生成绩*/ } void average(float *p,int n) /*指向数组元素的指针p*/ { float *p_end; float sum=0,aver; p_end=p+n-1; for(;p<=p_end;p++) sum=sum+(*p); aver=sum/n; printf("average=%5.2f",aver);

void search(float (*p)[4], int n) /*指向长度为4的一维数组的指针p*/ { int i; printf("the score of No.%d are:\n",n); for(i=0;i<4;i++) printf("%5.2f",*(*(p+n)+i)); }

字符串与指针 在C语言中,可以用两种方法访问一个字符串: 1. 用字符数组存放一个字符串,然后输出该字符串。例如: void main() { char string[]="I love china!"; printf("%s\n",string); }

2. 用字符指针指向一个字符串。例如: void main() { char *string="I love China!"; printf("%s\n",string); } C编译器首先在内存中分配一个字符数组空间来保存字符串常量“I love China!”,然后,把这个字符串的第一个字符的地址,赋给指针变量string;不要误认为是把整个字符串"I love China!"赋给了变量string。 对一个字符串,可以通过首地址,使用%s来输出。

程序中,a和b都是数组名,因此*(a+i)表示的就是a[i],*(b+i)表示的是b[i]。 void main() { char a[]="I am a boy.", b[20]; int i; for(i=0;*(a+i)!='\0';i++) *(b+i)=*(a+i); *(b+i)='\0'; printf("string a is:%s\n",a); printf("string b is:"); for(i=0;b[i]!='\0';i++) printf("%c",b[i]); printf("\n"); } 程序中,a和b都是数组名,因此*(a+i)表示的就是a[i],*(b+i)表示的是b[i]。 输出一个字符串,可以使用%s输出,还可以通过%c依次输出每个字符。

字符指针作函数参数 例7.11用函数调用实现字符串的复制 (1)用字符数组作函数参数 void main() { void copy(char from[],char to[]); char a[]="I am a teacher"; char b[]="You are a student"; copy(a,b); printf("string b=%s\n", b); } void copy(char from[],char to[]) { int i=0; while(from[i]!='\0') to[i]=from[i]; i++; } to[i]='\0';

(2)使用字符指针变量作形参 void main() { void copy(char *from,char *to); char a[]="I am a teacher"; char b[]="You are a student"; char *x=a,*y=b; copy(x,y); /*使用copy(a,b);也可以*/ printf("string b=%s\n",b); } void copy(char *from,char *to) { for(;*from!='\0';form++,to++) *to=*from; *to='\0';

对字符指针变量和字符数组的说明 1.字符数组由若干个元素组成,每个元素存放一个字符。而字符指针变量用来存放一个地址。 2.字符指针变量可以通过赋值进行字符串的复制,但字符数组不行,只能通过字符串拷贝函数来复制。 char *p; char str[14], a[]="I am a boy"; p=a;(正确) str=a; (错误)

3. 对字符指针变量赋初值: char *p="I am a boy"; 是将字符串“I am a boy”第一个元素的地址赋给p。 4. 指针变量的值是可以改变的。例 void main() { char *a="I love China"; a=a+7; printf("%s\n",a); } 输出结果:China

指向函数的指针 函数也要占用内存。一个函数在编译之后,会生成二进制代码,并且编译器会为这个函数分配一个地址空间,这个地址空间的起始地址(入口地址),就称为函数的指针。 可以用一个指针变量指向函数,然后通过这个指针变量来调用此函数。

回顾第一章的例子: #include <stdio.h> void main( ) { int max(int x, int y); int a,b,c; scanf ("%d, %d" ,&a ,&b); c= max (a, b); printf ("max= %d" , c) } int max( int x , int y) { int z; if (x>y) z=x; else z=y; return (z); }

改写这个例子中的主函数: #include <stdio.h> void main( ) { int max(int x, int y); int (*p)(int, int); /*定义指向函数的指针变量p*/ p=max;/*max是函数名,指向函数入口地址*/ int a,b,c; scanf ("%d, %d" ,&a ,&b); c= (*p)(a, b); /*通过指针p来调用max*/ printf ("max= %d" , c) }

分析: 1. int (*p)(int, int); 这条语句是定义一个指向函数的指针变量p,该函数具有两个整型参数,并且函数返回值为整型。 定义中(*p)的括号不能省略,如果省去括号 int *p(int, int); 代表声明一个函数p,它的返回值是一个指向整型变量的指针。

3. 要调用max函数,可以直接使用函数名: 2. 与数组名指向数组首元素类似,函数名max也是一个地址常量,它保存该函数的起始地址。 p=max;这条赋值语句 使p指向函数max。 3. 要调用max函数,可以直接使用函数名: max(a,b); 也可以通过指针p (*p)(a,b); 因此,程序里有语句: c=(*p)(a,b); max p 指令1 指令2

指向函数的指针变量的定义的一般形式为: 数据类型 (*指针变量名)(函数参数表列) 例如: int (*p)(int, int); 指向函数的指针p,不能使用p++使p指向下一条指令。p+n或者p-n是无意义的。

指向函数的指针变量作函数参数 例7.12 设一个函数process,在调用它的时候,每次实现不同的功能。输入a和b两个数,第一次调用process时找出a和b中大的那个数,第二次找出其中小者,第三次求a与b之和。

#include <stdio.h> void main() { int max(int,int); int min(int,int); int add(int,int); void process(int,int,int(*fun)(int,int)); int a,b; scanf("%d,%d",&a,&b); printf("max="); process(a,b,max); printf("min="); process(a,b,min); printf("sum="); process(a,b,add); } int max(int x,int y) {return(x>y?x:y);} int min(int x,int y) {return(x>y?y:x);} int add(int x,int y) {return(x+y);} void process(int x,int y,int (*fun)(int,int)) { int result; result=(*fun)(x,y); printf("%d\n",result); }

说明: 在这个程序中,max,min,add分别是三个函数名,分别指向三个函数的起始位置。 main函数中3次调用了process,每一次的执行都不相同。因为process使用了指向函数的指针作参数,3次执行,分别调用了max,min和add函数。 这种程序设计方法增加了设计的灵活性。在UNIX操作系统中,内核和驱动程序接口部分的代码,就大量使用了指向函数的指针变量作函数参数。

返回指针值的函数 一个函数可以返回整型值,字符型值,浮点型值,还可以返回指针型值。 返回指针值的函数的一般形式: 类型名 * 函数名(参数表列); 例如int *a(int x,int y); a是函数名,调用它之后,能得到一个指向整型数据的指针。x和y是函数a的形参。 注意:*a的两侧没有加括号,如果加了括号 int (*a)(int x,int y); 就是定义了一个指向函数的指针变量a。

例7.13 有若干名学生的成绩(每个学生4门课程),要求在用户输入学生序号之后,输出该学生的全部成绩。 #include <stdio.h> void main() { float score[][4]={{60,70,80,90},{56,89,67,88}, {34,78,90,66}}; float *search(float (*pointer)[4],int n); float *p; int i,m; printf(“Enter the number of student:"); scanf("&d",&m); p=search(score,m); for(i=0;i<4;i++) printf("%5.2f",*(p+i)); printf("\n"); } score是一个二维数组,score指向数组的第一行 a. 形参pointer是一个指向一维浮点数组的指针 b. 函数的返回值,是一个指向float型变量的指针。 实参score和形参pointer都是指向float型一维数组的指针,score传值给pointer。

float *search(float(*pointer)[4],int n) { float *pt; pt=*(pointer+n); return(pt); } 调用search函数返回的指针,即指向第n的学生的第一门成绩(这是第n行一维数组的第一个元素)。因此,可以使用循环语句打印*(p+i),即为各门成绩 pt为指向浮点型变量的指针,pt=*(pointer+n); pointer接受来自实参score的值,指向二维数组的第0行,因此pointer+n指向二维数组的第n行。 *(pointer+n)即为第n行构成的一维数组的首地址。将*(pointer+n)赋值给pt,使得pt指向这个一维数组的首元素。然后由return语句返回指针pt的值。

指针数组和指向指针的指针 指针数组 本质是一个数组,其中每个元素均为指针类型,称为指针数组。 指针数组的一般定义形式: 类型名 *数组名[数组长度]; 例如: int *p[4]; 定义了一个一维数组p,该数组长度为4,每个数组元素都为指向整型变量的指针。

指针数组的用途 指针数组适合用来指向若干个字符串,使字符串处理更加灵活。 考虑:图书馆有若干本书,书名为字符串,因此考虑用字符数组来存放书名。因为每个书名都是一个字符串,那么很多本书,就需要二维数组来存放。 从图中可以看出来,二维数组存放,比较浪费内存空间,且需要一个很大的连续内存空间才能存放。 F o l l o w m e \0 B A S I C \0 G r e a t W a l l \0 F O R T R A N \0 C o m p u t e r d e s i g n \0

如果使用指针数组,各元素分别指向各书名构成的字符串。这些字符串可单独存放,比用二维数组存放更节省空间。如果想对字符串排序也不用改变字符串的位置,直接对指针数组中的元素值进行修改,指向不同的字符串即可。 指针数组name 字符串 name[0] Follow me name[1] BASIC name[2] Great Wall name[3] FORTRAN name[4] Computer design

例7.14 将若干字符串按字母顺序输出 #include <stdio.h> #include <string.h> 例7.14 将若干字符串按字母顺序输出 #include <stdio.h> #include <string.h> void main() { void sort(char *name[],int n); void print(char *name[],int n); char *name[]={"Follow me","BASIC“, "Great Wall", "FORTRAN", "Computer design"}; int n=5; sort(name,n); print(name,n); } 定义了一个指针数组name,它有5个元素,初值分别是"Follow me", "BASIC“,"Great Wall", "FORTRAN", "Computer design"这5个字符串的起始地址。

void sort(char *name[],int n) { char *temp; int i,j,k; for(i=0;i<n-1;i++) { k=i; for(j=i+1;j<n;j++) if(strcmp(name[k],name[j])>0) k=j; if(k!=i) { temp=name[i]; name[i]=name[k]; name[k]=temp; } sort函数中交换的是name数组中的元素,其实就是使name数组中的指针元素,按从小到大的次序依次指向5个字符串。5个字符串在内存中始终未改变其存储地址。 sort函数使用strcmp对字符串进行比较。strcmp的两个实参name[k]和name[j]分别指向第k个和第j个字符串。strcmp的返回值表明了两个字符串的大小。 sort函数用于对字符串进行排序。sort函数的形参与实参名字相同name,是指针数组名。数组名作为函数参数时,形参name数组和实参name数组指向同一个数组。 void print(char *name[],int n) { int i; for(i=0;i<n;i++) printf("%s\n",name[i]); }

指向指针的指针 回顾指针数组,指针数组名name,是个指针常量,它指向数组中第一个元素name[0]。而元素name[0] 又是一个指针,指向字符串“Follow me"。因此,name就是指向指针的指针。 可以定义一个指针变量p,使它指向指针数组的元素,那么p就是指向指针的指针变量。 Follow me BASIC Great Wall FORTRAN Computer design name name[0] name[1] name[2] name[3] name[4] p

如何定义指向指针的指针变量? int **p; 相当于int *(*p); 先把(*p)看成一个整体,因此可以知道(*p)是一个指向整型量的指针变量。再对(*p)进行分解,p是指向指针变量的指针变量。 如果有: p=name+2; printf("%o",*p); printf("%s",*p); 第一个打印语句输出name[2]的值,即字符串“Great Wall”的地址; 第二个打印语句输出"Great Wall"。

例7.15 使用指向指针的指针 #include <stdio.h> void main() { char *name[]={"Follow me","BASIC","Great Wall", "FORTRAN","Computer design"}; char **p; int i; for(i=0;i<5;i++) { p=name+i; printf("%s\n",*p); } 定义了一个指针数组name并进行初始化,使数组中每一个元素都指向一个字符串。name作为数组名,指向首元素name[0]。 定义了一个指向指针的指针变量p,p和name都是指向指针的指针,处于等同的地位。用name对p赋值:p=name+i; 这个赋值语句使p指向数组元素name[i]。引用*p等同于引用name[i]。 printf语句通过%s输出name[i]

指针相关的数据类型总结 定义 含 义 int i; 定义整型变量i int *p p为指向整型量的指针变量 int a[n]; 含 义 int i; 定义整型变量i int *p p为指向整型量的指针变量 int a[n]; 定义整型数组a,它有n个元素 int *p[n]; 定义指针数组p,它由n个指向整型数据的指针元素组成 int (*p)[n]; p为指向长度为n的一维数组的指针变量 int f(); 定义函数f,该函数返回一个整型量 int *p(); 定义函数p,该函数返回一个指针,该指针指向整型数据 int (*p)(); p为指向函数的指针,该函数返回一个整型值 int **p; P是一个指针变量,它指向一个指向整型数据的指针变量

一维数组与指针 int a[10]; int *p; p= &a[0]; p为指向数组元素的指针 p= &a[0]等价于p=a 通过a[i]或者*(p+i)来访问数组元素

二维数组与指针 int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a是一个二维数组,包含3个元素a[0],a[1],a[2]。 a作为数组名,指向其第一个元素a[0],a=&a[0] 而a[0]又是一个一维数组,所以数组名a[0]=&a[0][0] int *p=a[0]; p是指向数组元素的指针,赋值语句为p=a[0]; int (*p)[4]; p是指向一维数组的指针,赋值语句为p=a; int *p[4]; p是一个一维数组的名字,该数组的元素为指向整型量的指针

指针与函数 int (*p)(int, int); 指针p为指向函数的指针,该函数具有两个整型参数,并且返回值为整型。赋值语句为p=max; 函数调用语句max(a,b);相当于(*p)(a,b); int *p(int, int); 定义一个函数p,该函数的返回值为指向整型量的指针 。

指针相关的运算 指针变量加(减)一个整数: 例如:p++、p--、p+i、p-i、p+=i、p-=i 指针变量加1(减)是将该指针变量的值(为地址)和它指向的变量所占用的内存单元字节数进行加(减)。 两个指针变量可以相减:如果两个指针变量指向同一个数组的元素,则两个指针变量值之差是两个指针之间的元素个数。

指针相关的运算 指针变量赋值:将一个变量的地址赋给一个指针变量。 p=&a; (将变量a的地址赋给p) 不能执行p=1000; p=array; (将数组array的首地址赋给p) p=&array[i]; (将数组array第i个元素的地址赋给p) p=max; (max为已定义的函数,将max的入口地址赋给p) p1=p2; (p1和p2都是指针变量,将p2的值赋给p1) 不能执行p=1000; 指针变量可以有空值,即该指针变量不指向任何变量:p=NULL;

作业 P308 页练习题7.11 有n个人围坐一圈,顺序排号(n<500)。从第一个人开始1-3重复报数。凡报到3的退出圈子,请问最后剩下的是原来第几号的那位?