《C语言课件:第12章 指针和数组》由会员分享,可在线阅读,更多相关《C语言课件:第12章 指针和数组(49页珍藏版)》请在金锄头文库上搜索。
1、第12章:指针与数组1第 12 章指针和数组指针和数组第12章:指针与数组C语言允许对指向数组元素的指针进行算语言允许对指向数组元素的指针进行算术运算:加法和减法。术运算:加法和减法。这一特性表明能够用指针代替数组下标对数组进行处理。C语言中指针和数组的关系非常紧密。2第12章:指针与数组指针的算术运算指针可以指向数组元素:inta10,*p;p=&a0;用图形方式表示为:3第12章:指针与数组指针的算术运算可以通过p访问a0,例如,可以采用如下的方式将数值5存入a0中:*p=5;更新后的图形表示为:4第12章:指针与数组指针的算术运算如果指针如果指针p指向数组指向数组a的元素,则可以通过对指
2、的元素,则可以通过对指针针p进行进行指针算术运算指针算术运算(或(或地址算术运算地址算术运算)访)访问数组问数组a中其他元素:中其他元素:C语言支持三种类型的指针算术运算(且仅有这三种类型)指针加上整数指针加上整数指针减去整数指针减去整数两个指针相减两个指针相减5第12章:指针与数组指针加上整数指针指针p p加上整数加上整数j j产生一个新的指针,产生一个新的指针,指向指向p p 当前所指的元素位置之后当前所指的元素位置之后j j个个元素的位置。元素的位置。如果p指向数组元素ai,则p+j指向数组元素ai+j。6第12章:指针与数组指针加上整数指针加法运算示例:p=&a2;q=p+3;p+=6
3、;7假设有如下声明:inta10,*p,*q,i;第12章:指针与数组指针减去整数如果p指向ai,则p-j指向ai-j。示例:p=&a8;q=p-3;p-=6;8第12章:指针与数组两个指针相减当两个指针相减时,结果为指针之间的距离(以数当两个指针相减时,结果为指针之间的距离(以数组元素的个数作为度量)。组元素的个数作为度量)。如果p指向ai,q指向aj,则pq等于ij示例:p=&a5;q=&a1;i=p-q;/*iis4*/i=q-p;/*iis-4*/9第12章:指针与数组两个指针相减下列操作会导致未定义的行为:对一个并未指向数组元素的指针对一个并未指向数组元素的指针执行算术运算执行算术运
4、算当两个指针并非指向同一数组中当两个指针并非指向同一数组中的元素时,对其执行减法操作;的元素时,对其执行减法操作;10第12章:指针与数组指针比较指针可以采用关系运算符指针可以采用关系运算符 (, , =) 和判等和判等运算符运算符 (= 和和 !=)进行比较:进行比较:只有在两个指针指向同一数组(中的元素)时,用关系只有在两个指针指向同一数组(中的元素)时,用关系运算符进行指针比较才有意义。运算符进行指针比较才有意义。指针比较的结果依赖于指针指向的数组元素在数指针比较的结果依赖于指针指向的数组元素在数组中的相对位置。组中的相对位置。例如:通过如下的赋值操作p=&a5;q=&a1;有p=q的值
5、为1。11第12章:指针与数组指针用于数组处理指针的算术运算使我们能够通过对指针变量进指针的算术运算使我们能够通过对指针变量进行重复自增来逐一访问数组大元素。行重复自增来逐一访问数组大元素。下例采用循环对数组a中的元素进行求和:#defineN10intaN,sum,*p;sum=0;for(p=&a0;p&aN;p+)sum+=*p;12第12章:指针与数组指针用于数组处理第一轮迭代结束时:第二轮迭代结束时:第三轮迭代结束时:13第12章:指针与数组指针用于数组处理for 语句中的循环条件语句中的循环条件 p &aN 值得引起特值得引起特别的注意。别的注意。尽管元素尽管元素aN并不存在,但对
6、其并不存在,但对其进行取地址运算是合法的。进行取地址运算是合法的。采用指针算术运算通常可以节省执行时间。然而,对某些编译器而言,采用数组下标来遍历数组的效率可能更高。14第12章:指针与数组* 和 + 运算符的组合C程序员常在处理数组元素的语句中组合使用程序员常在处理数组元素的语句中组合使用 *(间接访问)和(间接访问)和 + 操作符。操作符。下述语句修改当前位置(下标i)的数组元素值,并修改下标前进到下一个元素的位置:ai+=j;相应地,可以采用指针实现同样功能:*p+=j;由于后缀的+运算符的优先级高于*,编译器将上述语句解释为:*(p+)=j;15第12章:指针与数组* 和 + 运算符的
7、组合可能的*和+运算符的组合:表达式含义*p+ or *(p+) 自增前表达式的值为自增前表达式的值为 *p,然后自增然后自增p;(*p)+自增前表达式的值为自增前表达式的值为 *p,然后自增然后自增*p;*+por*(+p) 先自增p,自增后表达式的值为*p;+*por+(*p) 先自增*p,自增后表达式的值为*p;16第12章:指针与数组* 和 + 运算符的组合最常见的组合是*p+,在循环中十分方便。例如:下述代码对数组a的元素进行求和for(p=&a0;p&aN;p+)sum+=*p;可以改写为:p=&a0;while(p&aN)sum+=*p+;17第12章:指针与数组* 和 + 运算
8、符的组合 * 和和 运算符的组合类似于运算符的组合类似于* 和和 +的组合的组合早期的实现利用整型变量top来跟踪记录contents数组中“栈顶”位置的变化情况。这里我们使用一个指针变量来替换top,该变量指向contents数组的第零个元素:int*top_ptr=&contents0;18第12章:指针与数组* 和 + 运算符的组合新的push和pop函数:void push(int i) if (is_full() stack_overflow(); else *top_ptr+ = i;int pop(void) if (is_empty() stack_underflow(); e
9、lse return *-top_ptr;19第12章:指针与数组用数组名作为指针用数组名作为指针指针的算术运算体现了数组与指针间相互关联指针的算术运算体现了数组与指针间相互关联的关系。的关系。另一种重要的关系是:可以用数组名作为指向数组第一个元素的指针可以用数组名作为指向数组第一个元素的指针这种关系简化了指针的算术运算,并且使得数组和指针更加通用。20第12章:指针与数组用数组名作为指针假设用如下方式声明数组a:inta10;使用数组名a作为指针的示例如下:*a=7;/*stores7ina0*/*(a+1)=12;/*stores12ina1*/通常情况下,通常情况下,a + i 和和 &
10、ai是等同的是等同的均表示指向数组均表示指向数组 a 中元素中元素 i 的指针的指针同样地,*(a+i)与ai也是等同的均表示数组a中的元素i自身21第12章:指针与数组用数组名作为指针数组名可以用作指针这一事实,使得编写遍历数组的循环更加容易。例如对于如下的循环:for(p=&a0;p&aN;p+)sum+=*p;可以简化为如下形式:for(p=a;pa+N;p+)sum+=*p;22第12章:指针与数组用数组名作为指针尽管数组名可以用作指针,但不能对其赋值尽管数组名可以用作指针,但不能对其赋值试图使其指向其它位置会导致错误发生:while(*a!=0)a+;/*WRONG*/这一限制对我们
11、不会造成太大损失,可以通过将a赋值给一个指针变量,实现对其的修改:p=a;while(*p!=0)p+;23第12章:指针与数组程序:数列反向(改进版)第八章的程序reverse.c实现了读入10个数,然后对其进行逆序输出。该程序将这些读入的数字存储到一个数组中,并利用下标来访问数组中的元素。reverse3.c是利用指针的算术运算取代数组下标运算后,得到的改进版本。24第12章:指针与数组reverse3.c/*Reversesaseriesofnumbers(pointerversion)*/#include#defineN10intmain(void)intaN,*p;printf(En
12、ter%dnumbers:,N);for(p=a;p=a;p-)printf(%d,*p);printf(n);return0;25第12章:指针与数组数组型参数数组型参数 (改进版改进版)当数组名被传递给函数时,总是被视为指针当数组名被传递给函数时,总是被视为指针示例:intfind_largest(inta,intn)inti,max;max=a0;for(i=1;imax)max=ai;returnmax;对find_largest函数的调用:largest=find_largest(b,N);该调用会把指向数组b第一个元素的指针赋值给a,注意:数组b自身并未被复制。26第12章:指针与
13、数组数组型参数 (改进版)将数组型参数作为指针处理会产生一些重要的将数组型参数作为指针处理会产生一些重要的影响和后果。影响和后果。后果后果 1: 当向函数传递普通变量时,当向函数传递普通变量时,变量的值会变量的值会被复制被复制,函数对形参的任何修改都不会影响到,函数对形参的任何修改都不会影响到该变量自身。该变量自身。与之形成对比的是,与之形成对比的是,当数组名被用作实参进行当数组名被用作实参进行参数传递时,无法保护函数对数组元素的修改参数传递时,无法保护函数对数组元素的修改27第12章:指针与数组数组型参数 (改进版)例如,下述函数将对传入的数组进行修改,结果是数组中前n个元素的值将被置为零。
14、voidstore_zeros(inta,intn)inti;for(i=0;in;i+)ai=0;28第12章:指针与数组数组型参数 (改进版)为指示函数不要对传入的数组参数进行修改,为指示函数不要对传入的数组参数进行修改,可以在形参声明中加上可以在形参声明中加上const 关键字:关键字:intfind_largest(constinta,intn)如果存在const关键字,编译器会检查find_largest函数,确保其中没有对数组a的元素进行赋值的操作。29第12章:指针与数组数组型参数 (改进版)后果后果2: 向函数传递数组所需的时间与数组的大向函数传递数组所需的时间与数组的大小无关
15、。小无关。由于没有对数组进行复制操作,所以传递大数组不由于没有对数组进行复制操作,所以传递大数组不会对性能构成影响。会对性能构成影响。30第12章:指针与数组数组型参数 (改进版)后果后果 3:如果需要,可以将数组型形式参数声明如果需要,可以将数组型形式参数声明为指针。为指针。例如:find_largest可以定义如下:intfind_largest(int*a,intn)将a声明为指针,等同于将其声明为数组,编译器将这两种声明方式看做是完全一样的。31第12章:指针与数组数组型参数 (改进版)尽管对尽管对形参形参而言,声明为数组和声明为指针是而言,声明为数组和声明为指针是一样的,但是对于一样
16、的,但是对于变量变量而言,二者是不同的。而言,二者是不同的。例如,如下声明会导致编译器预留10个整型变量的空间:inta10;而如下的声明只会导致编译器预留一个指针变量的空间:int*a;32第12章:指针与数组数组型参数 (改进版)在第二个声明中,a并不是一个数组,如果将其作为数组使用会导致灾难性后果。例如:如下的赋值操作*a=0;/*WRONG*/会将0存储到a指向的位置。由于我们并不知道a究竟指向哪里,因此该语句对程序的影响是无法预料的。33第12章:指针与数组数组型参数 (改进版)后果后果 4: 如果一个函数以数组作为形参,则可以如果一个函数以数组作为形参,则可以向其传递一个数组向其传
17、递一个数组“片段片段”:即一个连续存储:即一个连续存储的数组元素构成的序列。的数组元素构成的序列。例如:可以利用find_largest找出数组b中第5到第14号元素中的最大者:largest=find_largest(&b5,10);34第12章:指针与数组使用指针作为数组名C语言允许我们将指针视为数组名,并对其执行下标操作:#defineN10intaN,i,sum=0,*p=a;for(i=0;iN;i+)sum+=pi;编译器会将pi和*(p+i)等同进行处理35第12章:指针与数组指针和多维数组正如指针可以指向一维数组的元素一样,指针也可以指向多维数组的元素。36第12章:指针与数组
18、处理多维数组的元素C语言按行主序存储二维数组一个r 行的数组可以表示如下:如果指针p指向二维数组中的第一个元素(0行0列),则可以通过反复自增p的方式访问数组中的每一个元素。37第12章:指针与数组处理多维数组的元素示例:将如下二维数组中的所有元素初始化为0:intaNUM_ROWSNUM_COLS;显然我们可以采用嵌套的for循环:introw,col;for(row=0;rowNUM_ROWS;row+)for(col=0;colNUM_COLS;col+)arowcol=0;但是,如果将a视为一维的整型数组,一重循环就够了:int*p;for(p=&a00;p=&aNUM_ROWS-1N
19、UM_COLS-1;p+)*p=0;38第12章:指针与数组处理多维数组的元素尽管将二维数组作为一维数组处理有点尽管将二维数组作为一维数组处理有点“取巧取巧”的嫌疑,但绝大多数编译器都接受这样的做的嫌疑,但绝大多数编译器都接受这样的做法法然而这种做法显然破坏了程序的可读性破坏了程序的可读性,不过对于某些老旧的编译器而言,这种做法能够在程序执行效率方面得到一些补偿。对于大多数现代编译器而言,这样做对于程序性能的提高往往极少,甚至没有。39第12章:指针与数组处理多维数组的行指针变量p也可以用于处理二维数组中的某一行元素。为了访问二维数组a中的第i行元素,可以采用如下方式初始化指针p,使之指向数组
20、a中第i行的第0个元素:p=&ai0;或者,可以简单地写为:p=ai;对于任意二维数组对于任意二维数组a,表达式,表达式ai的结果是一个的结果是一个指针,指向数组指针,指向数组a中中第第i行的首元素行的首元素。40第12章:指针与数组处理多维数组的行ai和*(a+i)的等同关系由此可知,&ai0等同于&(*(ai+0),因此也等同于&*ai。显然,&*ai就是ai,因为&和*运算符的作用相互抵消。41第12章:指针与数组处理多维数组的行下述循环语句将数组a中第i行元素清零:intaNUM_ROWSNUM_COLS,*p,i;for(p=ai;pai+NUM_COLS;p+)*p=0;由于由于a
21、i 是指向数组是指向数组a中第中第i行元素的指针,因行元素的指针,因此可以将其作为参数传递给一个接受一维数组作此可以将其作为参数传递给一个接受一维数组作为形参的函数。为形参的函数。换言之,以一维数组为形参的函数,同样可以接换言之,以一维数组为形参的函数,同样可以接受二维数组中的一行作为参数传入。受二维数组中的一行作为参数传入。42第12章:指针与数组处理多维数组的行以find_largest函数为例,该函数用于找出一维数组中的最大元素。我们可以简单地使用find_largest来找出二维数组a中第i行的最大元素:largest=find_largest(ai,NUM_COLS);43第12章:
22、指针与数组处理多维数组的列处理二维数组中的一列元素要稍微麻烦一些,因为数组是逐行存储的(而不是逐列)。下述循环语句对二维数组a中第i列元素清零:intaNUM_ROWSNUM_COLS,(*p)NUM_COLS,i;for(p=&a0;p&aNUM_ROWS;p+)(*p)i=0;44第12章:指针与数组用多维数组名作为指针无论数组维数高低,任意数组的名字均可用作指针,但是在实际应用中需要十分小心。例如:intaNUM_ROWSNUM_COLS;a并不是指向a00的指针,而是指向而是指向a0的指针的指针C语言将a视为一维数组,且该数组的元素为一维数组当作为指针时,当作为指针时,a的类型为的类型
23、为int (*)NUM_COLS,即,即指向长度为指向长度为NUM_COLS的整型数组的指针。的整型数组的指针。 45第12章:指针与数组用多维数组名作为指针了解了解a指向指向 a0有助于简化处理二维数组元素有助于简化处理二维数组元素的循环。的循环。例如可以对如下循环进行简化:for(p=&a0;p&aNUM_ROWS;p+)(*p)i=0;改写为(对数组a第i列元素进行清零):for(p=a;pa+NUM_ROWS;p+)(*p)i=0;l可以通过这种方式让函数将多维数组看成一维可以通过这种方式让函数将多维数组看成一维数组。数组。46第12章:指针与数组用多维数组名作为指针如果尝试用find
24、_largest找出数组a中最大元素:largest=find_largest(a,NUM_ROWS*NUM_COLS);/*WRONG*/上述语句会报错,因为a的类型是int(*)NUM_COLS,而find_largest希望接受的参数类型是int*。正确的调用方式为:正确的调用方式为:largest = find_largest(a0, NUM_ROWS * NUM_COLS);a0 指向数组的指向数组的0行行0列元素,其类型为列元素,其类型为int * (经(经过编译器转换之后)过编译器转换之后)47第12章:指针与数组定义含义inti;int*p;intan;int*pn;int(*
25、p)n;intf();int*p();int(*p)();int*p;定义整型变量ip为指向整型对象的指针变量定义含n个元素的整型数组an个指向整型数据的指针变量组成的指针数组指针数组pp为指向含n个元素的一维整型数组的指针变量指针变量f为返回整型数的函数p为返回指针的函数,该指针指向一个整型数据p为指向函数的指针变量,该函数返回整型数p为指针变量,它指向一个指向整型数据的指针变量指针的数据类型为便于后续学习,此处给出C中各种指针的声明格式及含义:第12章:指针与数组例下列定义的含义(1)int*p3;(2)int(*p)3;(3)int*p(int);(4)int(*p)(int);(5)int*(*p)(int);(6)int(*p3)(int);(7)int*(*p3)(int);函数指针数组,函数返回int型指针指针数组指向一维数组的指针返回指针的函数指向函数的指针,函数返回int型变量指向函数的指针,函数返回int型指针函数指针数组,函数返回int型变量