导图社区 第八章 善于利用指针
C程序设计(第五版)第八章 善于利用指针,同时在讲述指针是什么、指针变量、通过指针引用数组、通过指针引用字符串、指向函数的指针、指针数组和多重数组等内容。
编辑于2022-12-16 10:15:10第八章 善于利用指针
1、指针是什么
一个变量的地址称为该变量的“指针”
例如,地址2000是变量i的指针
如果有一个变量专门用来存放另一变量的地址(即指针),则它称为“指针变量”
i_pointer就是一个指针变量。指针变量 就是地址变量,用来存放地址的变量,指 針变量的值是地址(即指针)
“指针”和“指针变量”是不同的概念
可以说变量i的指针是2000,而不能说i的 指针变量是2000
指针是一个地址,而指针变量是存放地址 的变量
2、指针变量
使用指针变量的例子
怎样定义指针变量
定义指针变量的一般形式为
类型 * 指针变量名;
如:int *pointer_1, *pointer_2;
int是为指针变量指定的“基类型”
基类型指定指针变量可指向的变量类型
如pointer_1可以指向整型变量,但不能指 向浮点型变量
怎样引用指针变量
在引用指针变量时,可能有三种情况:
给指针变量赋值。如:p=&a;
使p指向a
引用指针变量指向的变量
如有p=&a; *p=1; 则执行printf(“%d”,*p); 将输出1
*p相当于a
引用指针变量的值。如:printf(“%o”,p);
以八进制输出a的地址
要熟练掌握两个有关的运算符
(1) & 取地址运算符。
&a是变量a的地址
(2) * 指针运算符(“间接访问”运算符)
如果: p指向变量a,则*p就代表a
k=*p; (把a的值赋给k)
*p=1; (把1赋给a)
指针变量作为函数参数
如果想通过函数调用得到n个要改变的值:
① 在主调函数中设n个变量,用n个指针变量指向它们
② 设计一个函数,有n个指针形参。在这个函数中 改变这n个形参的值
③ 在主调函数中调用这个函数,在调用时将这n个 指针变量作实参,将它们的地址传给该函数的形 参
④ 在执行该函数的过程中,通过形参指针变量,改变它们所指向的n个变量的值
⑤主调函数中就可以使用这些改变了值的变量
注意:函数的调用可以(而且只可以)得到一个返回值(即函数值),而使用指针 变量作参数,可以得到多个变化了的值。 如果不用指针变量是难以做到这一点的。
3、通过指针引用数组
数组元素的指针
一个变量有地址,一个数组包含若干元素,每个数组元素都有相应的地址
指针变量可以指向数组元素(把某一元素的地址放到一个指针变量中)
所谓数组元素的指针就是数组元素的地址
可以用一个指针变量指向一个数组元素
int *p; p=&a[0];
注意:数组名a不代表整个数组, 只代表数组首元素的地址。“p=a;” 的作用是“把a数组的首元素的地 址赋给指针变量p”,而不是“把 数组a各元素的值赋给p”。
在引用数组元素时指针的运算
在指针指向数组元素时,允许以下运算:
加一个整数用( + 或 +=) ,如 p+1
减一个整数 用( - 或 -=) ,如 p-1
自加运算,如 p++,++p
自减运算,如 p-- ,--p
两个指针相减,如p1-p2 (只有p1和p2都 指向同一数组中的元素时才有意义)
①如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p-1指向同一数组中的上一个元素。
float a[10],*p=a; 假设a[0]的地址为2000,则
p的值为2000
p+1的值为2004
P-1的值为1996
②如果p的初值为&a[0],则p+i和a+i就是数组元素a[i]的地址, 或者说,它们指向a数组序号为i的元素
③*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。
④如果指针p1和p2都指向同一数组 p2-p1的值是4 不能p1+p2
通过指针引用数组元素
引用一个数组元素,可用下面两种方法:
其中a是数组名,p是指向数组元素的指针 变量,其初值p=a
(1) 下标法,如a[i]形式
(2) 指针法,如*(a+i)或*(p+i)
例题
有一个整型数组a,有10个元素, 要求输出数组中的全部元素。
解题思路:引用数组中各元素的值有3种 方法:(1)下标法; (2)通过数组名计算数组元素地址,找出元素的值; (3) 用指针变量指向数组元素
(1) 下标法
#include <stdio.h> int main() { int a[10]; int i; printf(“enter 10 integer numbers:\n"); for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0;i<10;i++) printf(“%d ”,a[i]); printf("%\n"); return 0; }
(2) 通过数组名计算数组元素地址,找出元素的值
#include <stdio.h> int main() { int a[10]; int i; printf(“enter 10 integer numbers:\n"); for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0;i<10;i++) printf(“%d ”,*(a+i)); printf("\n"); return 0; }
(3) 用指针变量指向数组元素
#include <stdio.h> int main() { int a[10]; int *p,i; printf(“enter 10 integer numbers:\n"); for(i=0;i<10;i++) scanf("%d",&a[i]); for(p=a;p<(a+10);p++) printf(“%d ”,*p); printf("\n"); return 0; }
3种方法的比较
① 第(1)和第(2)种方法执行效率相同
C编译系统是将a[i]转换为*(a+i)处理的, 即先计算元素地址。
因此用第(1)和第(2)种方法找数组元素费时 较多。
② 第(3)种方法比第(1)、第(2)种方法快
用指针变量直接指向元素,不必每次都重新计 算地址,像p++这样的自加操作是比较快的
这种有规律地改变地址值(p++)能大大提高 执行效率
③ 用下标法比较直观,能直接知道是第几个 元素。
用地址法或指针变量的方法不直观,难以 很快地判断出当前处理的是哪一个元素
用数组名作函数参数
用数组名作函数参数时,因为实参数组名代表该数组首元素的地址,形参应该是一 个指针变量
C编译都是将形参数组名作为指针变量来处理的
实参数组名是指针常量,但形参数组名是按指针变量处理
在函数调用进行虚实结合后,它的值就是 实参数组首元素的地址
在函数执行期间,形参数组可以再被赋值
void fun (arr[ ],int n) { printf(″%d\n″, *arr); arr=arr+3; printf(″%d\n″, *arr); }
通过指针引用多维数组
指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。但在概念上 和使用方法上,多维数组的指针比一维数组的指针要复杂一些
1. 多维数组元素的地址
int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
a代表第0行首地址 a+1代表第1行首地址 a+2代表第2行首地址
行指针每加1,走一行
a[i]+j代表a[i][j]的地址
*(a[i]+j)代表元素a[i][j]
*(*(a+i)+j)代表与*(a[i]+j)等价
2. 指向多维数组元素的指针变量
(1) 指向数组元素的指针变量
(2) 指向由m个元素组成的一维数组的指针 变量
3. 用指向数组的指针作函数参数
4、通过指针引用字符串
字符串的引用方式
字符串是存放在字符数组中的。引用一个 字符串,可以用以下两种方法。
(1) 用字符数组存放一个字符串,可以通过数组 名和格式声明“%s”输出该字符串,也可以 通过数组名和下标引用字符串中一个字符
(2) 用字符指针变量指向一个字符串常量,通过 字符指针变量引用字符串常量
字符指针作函数参数
如果想把一个字符串从一个函数“传递” 到另一个函数,可以用地址传递的办法, 即用字符数组名作参数,也可以用字符指 针变量作参数
在被调用的函数中可以改变字符串的内容
在主调函数中可以引用改变后的字符串
使用字符指针变量和字符数组的比较
用字符数组和字符指针变量都能实现字符 串的存储和运算,但它们二者之间是有区 别的,不应混为一谈,主要有以下几点
(1) 字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地 址(字符串第1个字符的地址),决不是将字符串放到字符指针变量中
(2) 赋值方式。可以对字符指针变量赋值,但不能对数组名赋值
(3)初始化的含义
char *a=”I love China!”;与char *a; a=”I love China!”;等价
char str[14]= ”I love China!”;与char str[14]; str[]=”I love China!”; 不等价
(4) 存储单元的内容
编译时为字符数组分配若干存储单元,以存放各元素的值,而对字符指针变量,只分配 一个存储单元
(5) 指针变量的值是可以改变的,而数组名代 表一个固定的值(数组首元素的地址),不能 改变
(6) 字符数组中各元素的值是可以改变的,但 字符指针变量指向的字符串常量中的内容是 不可以被取代的
(7) 引用数组元数
对字符数组可以用下标法和地址法引用数组元素(a[5],*(a+5))。如果字符指针变 量p=a,则也可以用指针变量带下标的形式和地址法引用(p[5],*(p+5))
(8) 用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串
5、指向函数的指针
什么是函数指针
如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储 空间,这段存储空间的起始地址,称为这个函数的指针
可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味 着此指针变量指向该函数
例如:int (*p)(int,int);
定义p是指向函数的指针变量,它可以指向类型为整型且有两个整型参数的函 数
p的类型用int (*)(int,int)表示
用函数指针变量调用函数
例:用函数求整数a和b中的大者
解题思路:定义一个函数max,实现求两个整数中的大者。在主函数调用max 函数,除了可以通过函数名调用外,还可以通过指向函数的指针变量来实现。 分别编程并作比较
(1)通过函数名调用函数
#include <stdio.h> int main() { int max(int,int); int a,b,c; printf("please enter a and b:"); scanf("%d,%d",&a,&b); c=max(a,b); printf(“%d,%d,max=%d\n",a,b,c); return 0; } int max(int x,int y) { int z; if(x>y) z=x; else z=y; return(z); }
(2)通过指针变量访问它所指向的函数
#include <stdio.h> int main() { int max(int,int); int (*p)(int,int);//只能指向函数返回值为整型且有两个整型参数的函数 int a,b,c; p=max;//必须先指向,若写成p=max(a,b); 错 printf("please enter a and b:"); scanf("%d,%d",&a,&b); c=(*p)(a,b); printf(“%d,%d,max=%d\n",a,b,c); return 0; }
怎样定义和使用指向函数的指针变量
定义指向函数的指针变量的一般形式为
数据类型 (*指针变量名)(函数参数表列);
如 int (*p)(int,int);
p=max; 对 p=max(a,b); 错 p+n,p++,p--等运算无意义
用指向函数的指针作函数参数
指向函数的指针变量的一个重要用途是把函数的地址作为参数传递到其他函数
指向函数的指针可以作为函数参数,把函数的入口地址传递给形参,这样就能 够在被调用的函数中使用实参函数
9、有关指针的小结
1.首先要准确地弄清楚指针的含义。指针就是地址,凡是出现“指针”的地方,都可以用“地址”代替,例如,变量的指针就是变量的地址,指针变量就是地址变量
要区别指针和指针变量。指针就是地址本身,而指针变量是用来存放地址的变量
2. 什么叫“指向”?地址就意味着指向,因为通过地址能找到具有该地址的对象。 对于指针变量来说,把谁的地址存放在指针变量中,就说此指针变量指向谁。但应 注意:只有与指针变量的基类型相同的数据的地址才能存放在相应的指针变量中
3. 要深入掌握在对数组的操作中怎样正确地使用指针,搞清楚指针的指向。一维数组名代表数组首元素的地址
4.指针运算
(1)指针变量加(减)一个整数
例如:p++,p--,p+i,p-i,p+=i,p-=i等均是指针变量加(减)一个整数
将该指针变量的原值(是一个地址)和它指向的变量所占用的存储单元的字节数相加 (减)
(2)指针变量赋值
将一个变量地址赋给一个指针变量
不应把一个整数赋给指针变量
(3) 两个指针变量可以相减
如果两个指针变量都指向同一个数组中的元素,则两个指针变量值之差是两个指针 之间的元素个数
(4) 两个指针变量比较
若两个指针指向同一个数组的元素,则可以进行比较
指向前面的元素的指针变量“小于”指向后面元素的指针变量
如果p1和p2不指向同一数组则比较无意义
(5) 指针变量可以有空值,即该指针变量不 指向任何变量,可以这样表示: p=NULL;
8、 动态内存分配与指向它的指针变量
什么是内存的动态分配
非静态的局部变量是分配在内存中的动态存储区的,这个存储区是一个称为栈的区域
C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据需要时随时开辟,不需要时随时释放。这些数据是临时存放在一个特别的自由存储区,称为堆区
怎样建立内存的动态分配
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,free,realloc这4个函数
1、malloc函数
其函数原型为 void *malloc(unsigned int size);
其作用是在内存的动态存储区中分配一个长度为size的连续空间
函数的值是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指 针指向该分配域的开头位置
malloc(100);
开辟100字节的临时分配域,函数值为其第1个字节的地址
注意指针的基类型为void,即不指向任何类型的数据,只提供一个地址
如果此函数未能成功地执行(例如内存空间不足),则返回空指针(NULL)
2.calloc函数
其函数原型为 void *calloc(unsigned n,unsigned size);
其作用是在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大, 足以保存一个数组
用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的起始位置的指针;如果分配不成功,返回NULL
如: p=calloc(50,4);
开辟50×4个字节的临时分配域,把起始 地址赋给指针变量p
3.free函数
其函数原型为 void free(void *p);
其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值
free(p);
释放指针变量p所指向的已分配的动态空间
free函数无返回值
4. realloc函数
其函数原型为 void *realloc(void *p,unsigned int size);
如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用recalloc函数重新分配。
用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL。如realloc(p,50); 将p所指向的已分配的动态空间改为50字节
以上4个函数的声明在stdlib.h头文件中, 在用到这些函数时应当用“#include <stdlib.h>”指令把stdlib.h头文件包含 到程序文件中
void指针类型
例题
建立动态数组,输入5个学生的成绩,另外用一个函放数检查其中有无低于60分的,输出不合格的成绩
解题思路:用malloc函数开辟一个动态自由区域,用来存5个学生的成绩,会得到这个动态域第一个字节的地址,它的基类型是void型。用一个基类型为int的指针变量p来指向动态数组的各元素,并输出它们的值。但必须先把malloc函数返回的void指针转换为整型指针,然后赋给p1
#include <stdio.h> #include <stdlib.h> int main() { void check(int *); int *p1,i; p1=(int *)malloc(5*sizeof(int)); for(i=0;i<5;i++) scanf("%d",p1+i); check(p1); return 0; } void check(int *p) { int i; printf("They are fail:"); for(i=0;i<5;i++) if (p[i]<60) printf("%d ",p[i]); printf("\n"); }
7、指针数组和多重指针
什么是指针数组
一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量
定义一维指针数组的一般形式为
类型名*数组名[数组长度]; int *p[4];
指针数组比较适合用来指向若干个字符串,使字符串处理更加方便灵活
可以分别定义一些字符串,然后用指针数组中的元素分别指向各字符串
由于各字符串长度一般是不相等的,所以比用二维数组节省内存单元
指向指针数据的指针
在了解了指针数组的基础上,需要了解指向指针数据的指针变量,简称为指向指针的指针
例题
有一个指针数组,其元素分别指向一个整型数组的元素,用指向指针数据的指针变量,输出整型数组各元素的值
#include <stdio.h> int main() {int a[5]={1,3,5,7,9}; int *num[5]={&a[0],&a[1],&a[2], &a[3],&a[4]}; int **p,i; p=num; for(i=0;i<5;i++) { printf("%d ",**p); p++; } printf("\n"); return 0; }
指针数组作main函数的形参
指针数组的一个重要应用是作为main函数的形参。在以往的程序中,main函数的第一行一般写成以下形式:int main() 或 int main(void)
表示main函数没有参数,调用main函数时不必给出实参
实际上,在某些情况下,main函数可以有参数,例如:int main(int argc,char *argv[])其中,argc和argv就是main函数的形参,它们是程序的“命令行参数”
argv是*char指针数组,数组中每一个元素(其值为指针)指向命令行中的一个字符串。
通常main函数和其他函数组成一个文件模块,有一个文件名
对这个文件进行编译和连接,得到可执行文件(后缀为.exe)。用户执行这个可执行文件,操作系统就调用main函数,然后由main函数调用其他函数,从而完成程序的功能
main函数是操作系统调用的,实参只能由操作系统给出
6、返回指针值的函数
一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址。其概念与以前类似,只是返回的值的类型是指针类型而已
定义返回指针值的函数的一般形式为类型名 *函数名(参数表列);
例题
有a个学生,每个学生有b门课程的成绩。要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数实现
解题思路:
定义二维数组 score 存放成绩
定义输出某学生全部成绩的函数 search ,它是返回指针的函数,形参是行指针和整型
主函数将 score 和要找的学号 k 传递给形参
函数的返回值是 &score[k][0](k 号学生的序号为0 的课程地址)
在主函数中输出该生的全部成绩