1、指针,指针的用途 允许以更简洁的方式引用大的数据结构。 使程序不同部分共享数据 能在程序执行过程中预留新的内存空间 可用来记录数据项之间的关系,指针的概念,指针(pointer) 是一个数据项,它的值是其他值在内存中的地址。,要想成为一个出色的C程序员,必须深入学习如何在应用中更有效地使用指针。,左值的概念,左值(lvalue) 任何一个指向能存储数据的内存位置的表达式称为。 左值可以出现在赋值语句的左边。,x = 1.0;,简单变量x是左值,intarray2 = 17;,数组引用intarray2是左值,5,常量不是左值,a + 3,算术表达式不是左值,左值的特点 每个左值都存储在内存中,
2、因此必有地址; 一旦声明左值,其内容可以改变,但其地址不能改变; 按照数据类型,不同左值需要不同大小的内存; 左值的地址本身也是数据,也能在内存进行操作和存储;,int i;,1000,地址也是一个整数,也可以存入内存。 地址在内存中的存储方式和其他整型数据一样。 为了强调地址和变量之间的关系,程序员常常在内存画上箭头。当然,在计算机内部是没有箭头的。 变量里的值是作为整数使用还是作为地址使用,取决于变量在程序中是怎样声明的。如果将变量声明为指针,就可以将其值理解为地址。,指针指向的值的类型,称为指针的基本类型(base type),指针变量的声明,语法: base-type *pointer
3、-variable;,int *p1;,char *p2;,A,*号在语法上属于变量名,不属于基本类型。 如果使用一个语句声明两个同类型的指针, 必须给每个变量都加上一个星号。,int *p1, *p2;,int *p1, p2;,基本的指针操作,&取地址 *取指向的值,int x, y;int *p1, *p2;x = -42;y = 163;p1 = ,x,y,p1,p2,1000,1002,*p1 = 17;,等价于,x = 17;,*p1就是变量x的另外一个名字,p1 = p2;,注意区分指针赋值和值赋值。p1 = p2;*p1 = *p2 ;,*p1 = *p2;,特殊指针NULL,
4、常量NULL 在stdlib.h中定义; 在机器内部表示地址为0; 将其赋值给指针变量,表示指针变量不指向任何有效数据;,int *p, x;p = NULL;,值为NULL的指针变量,不能使用*运算符。,x = *p;,会将地址0中的值赋值给x,*p = 10;,程序会崩溃,未初始化的指针变量会发生同样问题。int *p, x;x = *p;*p = 10;,注意:小心不要间接引用那些没有初始化或者值为NULL的指针,这样做会引用不属于本程序的内存空间,很可能会使程序崩溃。,通过引用传递参数,简单变量作函数参数 单向值传递。 在执行一个 被调用函数时,对形参值的改变 不会影响到主调函数中对应
5、实参的值。,5,5,0,void SetToZero(int var)var = 0;,SetToZero(x);,通过引用传递参数,指针作函数参数 地址传递。 被调用函数通过操作形参指针,可以改变主调函数 中对应变量的值。,?,1000,0,void SetToZero(int *ip)*ip = 0;,SetToZero(,设计函数SwapInteger,不用指针实现两个数组元素的交换,void SwapInteger(int array, int lh, int rh) int tmp; tmp = arraylh; arraylh = arrayrh; arrayrh = tmp;,S
6、wapInteger(array, lh, rh);,设计函数SwapInteger,使用指针实现两个数组元素的交换,void SwapInteger(int *p1, int *p2)int tmp;tmp = *p1;*p1 = *p2;*p2 = tmp;,SwapInteger(,设计函数SwapInteger,为何以下函数无法实现两个数组元素的交换?,void SwapInteger(int p1, int p2)int tmp;tmp = p1;p1 = p2;p2 = tmp;,SwapInteger(arraylh, arrayrh);,设计函数SwapInteger,为何以下
7、函数无法实现两个数组元素的交换?,void SwapInteger(int *p1, int *p2)int *tmp;tmp = p1;p1 = p2;p2 = tmp;,SwapInteger(,用引用调用返回多个结果,用引用调用返回多个结果 单个结果可以作为函数本身的值返回; 如果需要一个函数返回多个结果,可通过参数表来回传递值;,例程: 写一个函数,将用分钟表示的时间,转换成以小时和分钟表示的时间。例如:235分钟等于3小时55分钟。,#include #define MinutesPerHour 60void ConvertTimeToHM(int time, int *pHours
8、, int *pMinutes);main() int time, hours, minutes; printf(Test program to convert time valuesn); printf(Enter a time duration in minutes: ); scanf(%d, ,过度使用引用调用的危险,尽管引用调用的策略,具有很高的应用价值,但也很容易过度使用。,另一种策略 有返回值的函数通常比无返回值的过程更易使用。 主要是因为函数调用可以嵌套,可以把一个函数的结果作为参数传递给另一个函数,继续这一过程,直到应用不需要为止。 当使用过程时,必须把它们作为单独的语句一个一
9、个地调用。任何从一个过程传递到另一个过程的值都必须存储在一个变量里,并通过参数表传递。,main() int time; printf(Test program to convert time valuesn); printf(Enter a time duration in minutes: ); scanf(%d, ,指针和数组,double list3;,double *p = list;,1000,int i = 1;,p = ,数组list第i个元素的地址取决于i。 C语言在编译时无法计算这一地址。 为了确定这一地址,编译器会产生一段 指令,来在程序运行时计算这个地址。 &listi
10、 = 数组基地址 + 偏移量 偏移量 = i * 每个数组元素的字节数,1008,指针运算,指针运算(pointer arithmetic) 对指针值应用加减的过程。,指针运算规则 如果指针p指向数组arr的第一个元素且k为整型数,以下的等式总是成立的:p+k定义为&arrk,double list3;,double *p;,list0 = 1.0;list1 = 1.1;list2 = 1.2;,1.0,1.1,1.2,p = ,1000,p = p + 2;,1016,指针加法和传统加法是不同的,因为指针运算 必须考虑到基本类型的大小。 在本例中,因为double型需要8个字节,所以指针值
11、 每增加1个单位,内部数据值增加8。,P + k 的含义 不管每个元素占多少内存,p+k就是指针变量p目前 指向的地址后第k个元素的指针。,同理:P - k,* / %对指针来说是没有意义的,不能和指针 一起使用。,指针+和-运算的限制 可以指针+/-一个整数偏移量; 不能两个指针相加; 可将两个指针相减;,p1 + p2,p1 - p2,1016,1000,2,运算符+和-的新作用,+和- 对它们所操作的左值加1或减1。,两种应用形式 后缀式(postfix):x+先计算x的值,再将它加1。 返回给外部表达式的值是加1前的原始值。 前缀式(prefix): + x先将x的值加1,再使用新值
12、作为整个+操作所得的值。,例:对数组的每个元素赋值为0。,for (i=0; in; i+) arri = 0;,for (i=0; in; ) arri+ = 0;,尽管第二种编码方法,在某些机器上运行(如PDP-11)效率更高,但违背了for循环的精神,所带来的效率不足以作这种修改。 for循环头应该能够很确切地告诉读者每个循环周期中下标变量的行为。,例:将数组里的每一个元素值,都设为其下标值, arr0=0, arr1=1,for (i=0; in; ) arri = i+;,将+或-作为表达式的一部份,可能会造成歧义。,避免这种歧义的方法: 一般规则:进行+、-运算的变量,不应在同一表
13、达式中出现两次。前例中,i同时出现在赋值的左边和右边。 最好方法:限制自增和自减只以单独的形式出现,并避免在表达式内使用它们的值。,指针的自增和自减,*p+ 含义为何? 按优先级和结合性规则,等同于*(p+)。, 先计算p的值;,1000, 再将p的值加1;,p+, 返回给外部表达式加1前的原始值;,返回地址1000, 计算外部表达式的值;,*(1000),1.0,指针和数组的关系,数组作为函数参数传递,void SortIntegerArray(int array, int n);,void SortIntegerArray(int *array, int n);,一般原则:申明参数时必须能
14、体现出它的用途。 如果参数作为数组使用,从中选择元素,可声明为数组。 如果参数作为指针使用,对其间接引用,可声明为指针。,void SortIntegerArray(int array, int n);int FindSmallestInteger(int array, int low, int high);void SwapIntegerElements(int array, int p1, int p2);void SortIntegerArray(int array, int n)int lh, rh;for (lh = 0; lh n; lh+) rh = FindSmallestIn
15、teger(array, lh, n-1);SwapIntegerElements(array, lh, rh);int FindSmallestInteger(int array, int low, int high)int i, spos;spos = low;for (i = low; i = high; i+) if (arrayi arrayspos) spos = i; return (spos);void SwapIntegerElements(int array, int p1, int p2)int tmp;tmp = arrayp1;arrayp1 = arrayp2; a
16、rrayp2 = tmp;,选择排序(使用数组参数),void SortIntegerArray(int *array, int n);int *PointerToSmallestInteger(int *array, int n);void SwapIntegers(int *ip1, int *ip2);void SortIntegerArray(int *array, int n)int *lh, *rh, *end;end = array + n;for (lh = array; lh end; lh+) rh = PointerToSmallestInteger(lh, end -
17、lh);SwapIntegers(lh, rh);int *PointerToSmallestInteger(int *array, int n)int *ip, *sp;sp = array;for (ip = sp + 1; ip array + n; ip+) if (*ip *sp) sp = ip;return (sp);void SwapIntegers(int *ip1, int *ip2)int tmp;tmp = *ip1;*ip1 = *ip2;*ip2 = tmp;,选择排序(使用指针参数),指针和数组的关系,数组不作为函数参数传递,int array5;,int *p;
18、,最根本的区别在于:内存分配。,指针作为数组使用的途径 将数组基地址赋给指针变量来初始化指针。 p = array; 通过*p+、*(p+i)、pi形式访问数组中的每个元素。,指针与一维数组,数组名就是一个指针只是不能修改这个指针的指向可以定义函数的参数为数组指针也可当作数组名使用int *p, a10;p = a;数组元素的几种等价引用形式ai*(a+i)pi*(p+i),60006001600260036004600560066007,a,a+1,a+2,60006001600260036004600560066007,a,p+,p+,输入输出数组的全部元素,main() int a10;
19、 int i; for (i=0; i10; i+) scanf(%d, ,方法1:下标法,main() int a10; int *p, i; for (p=a; p(a+10); p+) scanf(%d, p); for (p=a; p= pos; i-) ai+1 = ai; /*向后移动*/apos = x; /*插入元素x到位置pos*/,指针与二维数组,C语言将二维数组看作一维数组,其每个数组元素又是一个一维数组,aa0+0,a+1a1+0,a0+1,a0+2,&a00,&a10,&a11,a1+1,&a12,&a01,&a02,a1+2,int a23;,指针与二维数组,例7.
20、8,任意输入英文的星期几,在查找星期表后输出其对应的数字。 char weekDay710 = Sunday, Monday, Tuesday,Wednesday, Thursday, Friday, Saturday;,表7-1 星期表的内容,例7.8,#include main() int i, pos;int findFlag = 0; char x10;char weekDay10 = Sunday,Monday,Tuesday, Wednesday,Thursday,Friday, Saturday; printf(Please enter a string:);scanf(%s,
21、x); for (i=0; i7 ,指针与二维数组,a 代表二维数组的首地址,第0行的地址a+i 代表第i行的地址*(a+i) 即 ai 代表第i行第0列的地址*(a+i)+j 即 ai+j 代表第i行第j列的地址*(*(a+i)+j ) 即 aij 代表第i行第j列的元素,行地址转变成列地址,指针与二维数组,元素aij的地址的几种等价的引用方式&aij ai+j *(a+i)+j &(*(a+i)j元素aij的几种等价的引用方式aij *(ai+j) *(*(a+i)+j) (*(a+i)j,指针与二维数组,二维数组的指针列指针int *p;p = *a; /用列地址初始化相对于数组起始地址
22、的偏移量i * m + jfor (i=0; in; i+)for (j=0; jm; j+) printf(%d,*(p+i*m+j);,p,p+,指针与二维数组,二维数组的指针行指针int (*p)3;p = a;/用行地址初始化for (i=0; in; i+)for (j=0; jm; j+) printf(%d, *(*(p+i)+j);,p,p+,例7.9 :求最高分及其所在班级和学号,int FindMax(int *p, int m, int n, int *pRow, int *pCol) int i, j, max;max = p0; *pRow = 0; *pCol =
23、0; for (i=0; i max) max = pi*n+j; *pRow = i; *pCol = j; return (max);,指针数组,元素均为指针类型数据的数组,称为指针数组 定义形式为: 类型关键字 *数组名数组长度;例如 char *pStr5;,例7.10:字符串按字典顺序排序,char strN10 = Pascal,Basic,Fortran, Java,Visual C; for (i=0; iN-1; i+) for (j = i+1; jN; j+)if (strcmp(strj, stri) 0) strcpy(temp,stri); strcpy(stri,
24、strj); strcpy(strj,temp); ,方法1:二维数组,方法1排序前后,例7.10:字符串按字典顺序排序,char *ptrN = Pascal,Basic,Fortran, Java,Visual C;for (i=0; iN-1; i+) for (j = i+1; jN; j+) if (strcmp(ptrj, ptri) 0) temp = ptri; ptri = ptrj; ptrj = temp; ,方法2:指针数组,方法2排序前后,指向指针的指针,如果指针变量中保存的是另一个指针变量的地址,这样的指针变量就称为指向指针的指针多级指针实质就是多级间接寻址(Mul
25、tiple Indirection)定义形式: 类型关键字 *变量名;,例,main() int i = 5; int *ip = ,例7.11,main() int i; char *ptr = Pascal,Basic,Fortran, Java,Visual C; char *p; p = ptr; for (i=0; i 1)printf(The other arguments are following:n);for (i = 1; iargc; i+) printf(%sn, argvi);,动态分配内存,在 和中均定义了下面的函数void* malloc(unsigned int
26、 size);向系统申请大小为size的内存块,把首地址返回。如果申请不成功,返回NULLvoid *calloc(unsigned int num, unsigned int size);向系统申请num个size大小的内存块,把首地址返回。如果申请不成功,返回NULLvoid free(void* p);释放由malloc()和calloc()申请的内存块。p是指向此块的指针,#include main()int *p = NULL, n, i, sum;printf(Please enter array size:);scanf(%d, ,例7.13:一维动态数组,例7.14:二维动态数
27、组,#include main()int *pScore = NULL, i, j, m, n, maxScore, row, col; printf(Please enter array size m,n:);scanf(%d,%d, ,动态分配,给变量分配内存空间的三种机制 静态分配(static allocation) 声明一个全局变量时。分配到内存的固定位置。 在整个程序中都有效。 自动分配(automatic allocation) 声明一个局部变量时,分配的空间在系统栈中。调用 函数时给变量分配内存空间,函数返回时释放空间。 动态分配(dynamic allocation) 在程序
28、运行时获得/释放新内存空间。在需要新内存的 时候得到内存。不需要内存时显式地释放这部分内存。,动态分配,程序可用的未分配的内存资源称为堆(heap)。 ANSI C提供了一些从堆中分配新的内存的函数。,函数malloc void *malloc(int nBytes); malloc(10); 分配10个字节的内存,返回一个指针(空间的起始地址)。 为了使用新分配的空间,必须将malloc的结果 存放在一个指针变量内。 int *ip; ip = malloc(sizeof(int);,Void*类型,在C语言中,指针是具有类型的。 int *ip;指向整型的指针变量。 char *cp;指向
29、字符型的指针变量。 如果把其它类型赋给这些指针变量,编译器会发出警告。,malloc函数返回的是什么类型的指针? 返回一个未确定类型的“通用”指针。 指向void类型的指针。 void *malloc(int nBytes);,Void*类型,void类型指针的特性 void *vp; 可以将任何类型的指针值存入该变量, 但不允许用*运算符间接引用vp。 编译器不知道vp的基本类型是什么, 所以没办法谈论vp指向的值。,ANSI C能在指向void的指针类型和 指向基本类型的指针类型间自动进行转换。 char *cp; cp = malloc(10); 自动将malloc返回的结果转换成指向字
30、符的指针。 也可以采用强制类型转换 cp = (char *)malloc(10);,注意以下区别:void f(.); void *f(.);后者声明了一个返回通用指针的函数。,动态数组,char *cp;cp = (char *)malloc(10);,动态数组(dynamic array) 分配在堆上并用指针变量引用的数组。,分配一个动态数组的步骤 声明一个指针变量,用以保存数组基地址。 调用malloc函数为数组中的元素分配内存。 分配的字节数 = 数组元素个数 * 每个元素字节大小 将malloc的结果赋给指针变量。,int *arr;arr = malloc(10 * sizeof
31、(int);,声明的数组和动态数组的主要区别: 内存分配的时机不同声明的数组:是在其所在函数帧建立时,作为帧的一部分自动分配的;动态数组:在调用molloc函数时才分配; 数组大小的定义不一样声明的数组:数组大小是固定不变的。动态数组:由于其内存来自于堆,大小可以是任意的。,int *IndexArray(int n)int i, *array;array = malloc(n * sizeof(int);for (i = 0; i n; i+) arrayi = i;return (array);,写一个函数IndexArray(n),返回指向动态分配的含n个元素的整型数组的指针,每个元素均初始化为自身的下标。,查找malloc中的错误,malloc错误的处理机制 计算机内存系统的大小是有限的,堆的空间会用完。 此时,malloc返回NULL,表示分配所需内存块的工作失败。 谨慎的程序员每次调用malloc都检查失败的可能性。 arr = malloc(10 * sizeof(int); if (arr = NULL) Error(“No memory available”);,释放内存,free函数:释放内存 保证不会发生内存不够的一种方法是:一旦使用完已分配的空间,就立刻释放它。 free函数:归还由malloc分配出去的堆内存。free(arr);,