610 likes | 766 Views
第 7 章指针. 指针是 C 语言的重要特色,也是一种非常实用的数据类型。合理使用指针,可以使程序简洁、高效、紧凑。 但是,由于指针的概念很抽象,使用起来也很灵活,因此,给初学者学习指针带来了较大的困难。. 7.1 指针概述 指针的概念、指针变量的定义和引用、指针变量作为函数参数 7.2 指向一维数组的指针 7.2.1 指向一维数组的指针变量的定义 7.2.2 指针变量的算术运算 7.2.3 指针变量的应用举例 7.2.4 指向字符串的指针变量 7.3 指向结构体的指针 7.3.1 指向结构体变量的指针 7.3.2 指向结构体数组的指针
E N D
第7章指针 • 指针是C语言的重要特色,也是一种非常实用的数据类型。合理使用指针,可以使程序简洁、高效、紧凑。 • 但是,由于指针的概念很抽象,使用起来也很灵活,因此,给初学者学习指针带来了较大的困难。
7.1 指针概述 指针的概念、指针变量的定义和引用、指针变量作为函数参数 7.2指向一维数组的指针 7.2.1 指向一维数组的指针变量的定义 7.2.2 指针变量的算术运算 7.2.3 指针变量的应用举例 7.2.4 指向字符串的指针变量 7.3 指向结构体的指针 7.3.1 指向结构体变量的指针 7.3.2 指向结构体数组的指针 7.4 返回指针的函数 7.5 动态内存分配 7.6 指针数组 7.7 指向二维数组的指针 7.8 指向函数的指针 7.9 指向指针的指针 7.10 链表与案例实现
【学习目标】 • 掌握指针的基本概念、定义和引用 • 掌握采用指针变量作为函数参数进行传址调用的方法 • 掌握指向一维数组的指针及其算术运算 • 掌握指向字符串的指针变量 • 了解指向结构体变量的指针 • 了解返回指针的函数 • 了解动态数组、指针数组和指向二维数组的指针 • 了解指向函数的指针和指向指针的指针 • 了解链表的建立、插入与删除操作
7.1 指针概述 7.1.1 指针的概念 变量所占存储单元的地址(指针)。 【例7-1】分析下面程序的执行结果。 #include <stdio.h> void main() { int a,b; float c,d; double e,f; printf("&a=%d, &b=%d\n",&a,&b); printf("&c=%d, &d=%d\n",&c,&d); printf("&e=%d, &e=%d\n",&e,&f); }
7.1.2 指针变量的定义和引用 1. 指针变量的定义 • 指针变量用来存放变量的地址。定义格式: 基类型 *指针变量名; 例如: int a=2, *pa, *pb; pa=&a; char c='A', *pc; pc=&c; 假设系统给变量a分配的地址是3000
2. 指针变量的引用 通过指针运算符“*”访问指针所指向的变量。 【例7-2】分析下面程序的执行结果。 程序代码如下: #include <stdio.h> void main() { int a=5, b, *pa; pa = &a; b = *pa; printf("a的值为:%d, pa的值为:%d, b的值为:%d\n",a,pa,b); }
7.1.3 指针变量作为函数参数 【例7-4】编写一个实现两数交换的自定义函数swap(),要求形参采用指针变量,在main()函数中输入两个整数,调用swap()函数后输出结果。 #include<stdio.h> void main() { int m,n; void swap(int *pm,int *pn); printf("请输入两个整数:"); scanf("%d%d",&m,&n); printf("交换前:m=%d,n=%d\n",m,n swap(&m,&n); printf("交换后:m=%d,n=%d\n",m,n } void swap(int *pm,int *pn) { int temp; temp=*pm; *pm=*pn; *pn=temp; }
7.2 指向一维数组的指针 7.2.1指向一维数组的指针变量的定义 例如:int a[10]= {10,20,30,40,50,60,70,80,90,100}, *p; 使指针变量指向数组元素: (1) p=a; (2) p=&a[i];
7.2.2 指针变量的算术运算 1. 加减整数运算 例如: int a[10], *p; p=a; p=p+9; 将指针变量p从当前所指向的位置向后移动i个元素。 2. 自增自减运算 p++; (或++p;)
7.2.4 指向字符串的指针变量 定义格式如下: char *指针变量名; 通过指针变量访问字符串的方法如下: (1) 对于存放在字符数组中的字符串。 例如: char a[]="China"; char *p=a; printf("%s\n",p);
【例7-7】编写一个类似库函数strcpy()的字符串复制函数,并在main()函数中验证该自定义函数的功能。【例7-7】编写一个类似库函数strcpy()的字符串复制函数,并在main()函数中验证该自定义函数的功能。
#include <stdio.h> void main() { char str1[30],str2[30]; void strcpy1(char *,char *); printf("请输入字符串:"); gets(str2); strcpy1(str1,str2); puts(str1); strcpy1(str1,"China"); puts(str1); } void strcpy1(char *p1,char *p2) { while(*p2!='\0') { *p1=*p2; p1++; p2++; } *p1='\0'; }
【例7-8】 编写一个类似库函数strcmp(char *s1,char *s2)的字符串比较函数,将两个字符串s1和s2进行比较。若字符串s1大于s2,则函数返回一个正整数;若字符串s1和s2相等,则函数返回0;若字符串s1小于s2,则函数返回一个负整数。在main()函数中验证该自定义函数的功能。
#include <stdio.h> void main() { char str1[30],str2[20]; int strcmp1(char *s1,char *s2); puts("请输入两个字符串:"); gets(str1); gets(str2); if(strcmp1 (str1,str2)>0) printf("str1 > str2\n"); else if(strcmp1 (str1,str2)<0) printf("str1 < str2\n"); else printf("str1 = str2\n"); } int strcmp1(char *p1, char *p2) { while(*p1==*p2 && *p1!= '\0') { p1++; p2++; } return (*p1-*p2); }
7.3 指向结构体的指针7.3.1指向结构体变量的指针 结构体指针变量的定义格式如下: 结构体类型名 *指针变量名; 例如: struct stu_type{ char num[15]; //学号 char name[10]; //姓名 int age; //年龄 int c; //C语言分数 int math; //数学分数 int en; //英语分数 int sum; //总分 float ave; //平均分 }; struct stu_type stu1,*p=&stu1;
访问结构体成员的数据 结构体变量名.成员名 (*结构体指针变量名).成员名 结构体指针变量名->成员名 • 例如:(接上例) • int score; • score=(*p).math; • score=p->math;
【例7-9】采用三种方式输出学生信息管理系统中某个学生的信息。 #include <stdio.h> void main() { struct stu_type{ char num[15]; char name[10]; int age; int c; int math; int en; int sum; float ave; }stu1={"2011110101","张小天",22,60,70,75},*p; p=&stu1; stu1.sum=stu1.c+stu1.math+stu1.en; stu1.ave=(float)stu1.sum/3; printf("学生学号\t姓名\t年龄\tC语言\t数学\t英语\t总分\t平均分\n"); printf("%-16s%s\t%d\t%d\t%d\t%d\t%d\t%5.1f\n",stu1.num,stu1.name, stu1.age,stu1.c,stu1.math,stu1.en,stu1.sum,stu1.ave); printf("%-16s%s\t%d\t%d\t%d\t%d\t%d\t%5.1f\n",(*p).num,(*p).name, (*p).age,(*p).c,(*p).math,(*p).en,(*p).sum,(*p).ave); printf("%-16s%s\t%d\t%d\t%d\t%d\t%d\t%5.1f\n",p->num,p->name, p->age,p->c,p->math,p->en,p->sum,p->ave); }
7.3.2 指向结构体数组的指针 【例7-10】采用指针方式输出学生信息管理系统中多个学生的信息。
#include <stdio.h> void main() { struct stu_type{ char num[15]; char name[10]; int age; int c; int math; int en; int sum; float ave; } *p,stu[3]={{"2011110101","张小天",22,60,70,75}, {"2011110102","王小红",22,65,75,75}, {"2011110103","李小兵",21,68,78,75}}; printf("学生学号\t姓名\t年龄\tC语言\t数学\t英语\t总分\t平均分\n"); for (p=stu;p<stu+3;p++) { p->sum= p->c+ p->math+ p->en; p->ave=(float) p->sum/3; printf("%-16s%s\t%d\t%d\t%d\t%d\t%d\t%5.1f\n",p->num, p->name,p->age,p->c,p->math,p->en,p->sum,p->ave); } }
7.4 返回指针的函数 定义返回指针的函数的一般形式如下: 类型名 *函数名(参数列表) {…} 由于函数返回值的类型应与函数的类型一致,因此,函数中通过return语句返回的值必须是指针。
【例7-11】编写一个求子串的函数substr(char *s,int n1,int n2),从s指向的字符串中提取下标范围在n1~n2之间的字符组成一个新的字符串,返回这个新串的首地址。 编程思路:由于函数要返回新串的首地址,因此,函数的类型应该设为char *。
#include <stdio.h> char *substr(const char *s,int n1,int n2) { static char a[80]; char *p=a; int i,j=0; for (i=n1;i<=n2;i++,j++) a[j]=s[i]; a[j]='\0'; return p; } void main(void) { char s[80],*sub; int n1,n2; printf("请输入原字符串:"); scanf("%s",s); printf("请输入起止位置:"); scanf("%d%d",&n1,&n2); sub=substr(s,n1,n2); printf("子串为:%s\n",sub); }
7.5 动态内存分配 在C语言中,定义数组时必须明确地说明其大小,但在实际编程中,经常遇到这样的情况:数组大小最初不能预料,只有在程序运行过程中根据具体情况才能确定。 例如,在学生信息管理系统中,如果将学生数组定义为stu[100] ,有何弊端?
#include <stdio.h>#include <stdlib.h>void main(){ struct stu_type{ char num[15]; char name[10]; int age; int c; int math; int en; int sum; float ave; } *p,*p1; int n,i; printf("请输入学生人数:"); scanf("%d",&n); 【例7-13】在学生信息管理系统中,从键盘输入学生人数,按实际人数申请存储空间,并输入每个学生的信息后输出。
p=(struct stu_type *)malloc(sizeof(struct stu_type)*n); p1=p; for(i=0;i<n;i++) //输入学生信息 { printf("请输入第%d个学生的信息\n",i+1); printf("请输入学号:"); scanf("%s",p1->num); printf("请输入姓名:"); scanf("%s",p1->name); printf("请输入年龄:"); scanf("%d",&p1->age); printf("请输入C语言成绩:"); scanf("%d",&p1->c); printf("请输入数学成绩:"); scanf("%d",&p1->math); printf("请输入英语成绩:"); scanf("%d",&p1->en); p1->sum=p1->c+p1->math+p1->en; p1->ave=(float)p1->sum/3; p1++; } printf("学生学号\t姓名\t年龄\tC语言\t数学\t英语\t总分\t平均分\n"); for(i=0;i<n;i++) //输出学生信息 { printf("%-16s%s\t%d\t%d\t%d\t%d\t%d\t%5.1f\n",p->num,p->name, p->age,p->c,p->math,p->en,p->sum,p->ave); p++; } }
#include <stdio.h> #include <stdlib.h> char *substr(const char *s,int n1,int n2) { char *p=(char *)malloc(n2-n1+2);//申请存储空间 int i,j=0; for (i=n1;i<=n2;i++,j++) p[j]=s[i]; p[j]='\0'; return p; } void main() { char s[80],*sub; int n1,n2; printf("请输入原字符串:"); scanf("%s",s); printf("请输入起止位置:"); scanf("%d%d",&n1,&n2); sub=substr(s,n1,n2); printf("子串为:%s\n",sub); free(sub);//释放sub所占用的空间 }
7.6 指针数组 一维指针数组的定义格式如下: 基类型 *指针变量名[数组长度]; 例如: int *p[3], i=1, j=2, k=3; p[0] = &i; p[1] = &j; p[2] = &k; printf("%d,%d,%d\n",*p[0],*p[1],*p[2]);
【例7-14】对参加北京奥运会的国家名称按由小到大的顺序排序后输出。 • 采用两种方法来实现,并比较它们的优劣,以便体会到采用指针的好处。 • 方法(1)的编程思路:采用“冒泡排序法”对一维数组中的数据进行排序。 • 本例中每个国家的名称是一个字符串,一个字符串可以用一个一维字符数组来保存,这样可以定义一个二维数组来存储多个国家的名称。 • 假设最多有N个国家参加,所有国家的国名最长为LEN个字符,则二维数组的定义如下: char name[N][LEN]
#include <stdio.h> #include <string.h> #define N 4 #define LEN 20 void main() { int i,j; char temp[LEN],name[N][LEN]={“China”,”America”,”Australia”,”Japan”}; for(i=1;i<=N;i++) //排序,控制趟数 for(j=0;j<N-i;j++) //控制每一趟的比较次数 if(strcmp(name[j],name[j+1])>0 { strcpy(temp,name[j]); strcpy(name[j],name[j+1]); strcpy(name[j+1],temp); } for(i=0;i<N;i++) printf("%s\n",name[i]); }
二维数组可以看成由多个一维数组组成。本例中,可以将二维数组name看成是由name[0]、name[1]、name[2]和name[3]四个元素组成的一维数组,name是它的数组名。二维数组可以看成由多个一维数组组成。本例中,可以将二维数组name看成是由name[0]、name[1]、name[2]和name[3]四个元素组成的一维数组,name是它的数组名。 • 其次,再将这四个元素分别看成是由LEN个字符组成的一维数组。二维数组在内存中的存储形式如图7-9所示。
排序过程中,交换时需要移动整个字符串,因此,排序速度较慢,算法的效率较低。是否有更高效的排序办法呢? • 方法(2)的编程思路:假设有N个字符串。可以定义一个长度为N的字符指针数组,数组中的每个元素分别指向一个字符串。
#include <stdio.h> #include <string.h> #define N 4 void main() { int i,j; char *temp,*name[N]={"China","America","Australia","Japan"}; for(i=1;i<=N;i++) //排序,控制趟数 for(j=0;j<N-i;j++) //控制每一趟的比较次数 if(strcmp(name[j],name[j+1])>0 { temp=name[j]; name[j]=name[j+1]; name[j+1]=temp; } for(i=0;i<N;i++) printf("%s\n",name[i]); }
方法(2)在内存空间的分配上优于方法(1)。每个字符串常量在内存中所占存储空间的大小与其实际大小一致,这样可以节省内存空间。 • 方法(2)在执行速度上优于方法(1)。排序时,并不移动字符串的位置,只是改变了指针数组中各元素的指向,这样排序的速度较快。
7.7 指向二维数组的指针 7.7.1 二维数组的地址 例如:int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}}; (1)二维数组a可以看成是由a[0]、a[1]、a[2]三个元素组成的一维数组,a是它的数组名。 (2)a[0]、a[1]和a[2]这三个元素又可以分别看成是一个一维数组。以a[0]为例,a[0]是一维数组的数组名,它包含四个数组元素:a[0][0]、a[0][1]、a[0][2]和a[0][3]。
a+1跨过了二维数组的一行,所以,a、a+1和a+2称为行地址。a+1跨过了二维数组的一行,所以,a、a+1和a+2称为行地址。 • 相对于a[0]而言,a[0]+1只跨过了二维数组的一个元素,所以将a[0]、a[0]+1、a[0]+2等称为列地址。
等价关系如下: a[i][j] ←→ (*(a+i))[j] ←→ *(a[i]+j) ←→ *(*(a+i)+j)
7.7.2 指向二维数组的指针 • 例如,int a[3][4],定义指针变量: int (*p)[4]; • 对行指针进行赋值: p=a;
7.8 指向函数的指针 • 程序运行时,函数的目标代码在内存中占用一段连续的存储空间,该存储空间的起始位置叫做函数的入口地址。可以将函数的入口地址赋给一个指针变量,然后通过该指针变量来调用相应的函数。定义指向函数的指针变量的一般格式如下: 函数类型 (*指针变量名) (形参类型列表); • 例如: • int (*p)(int, int); • float (*p1)();
【例7-16】求两个整数中较小的数,要求采用指向函数的指针变量来调用函数。【例7-16】求两个整数中较小的数,要求采用指向函数的指针变量来调用函数。 #include <stdio.h> void main() { int min(int a, int b);//声明自定义函数min int (*p)(int, int); //定义一个指向函数的指针变量p int x, y, z; p=min; //给指针变量p赋值,使其指向min()函数 printf("请输入两个整数:"); scanf("%d%d",&x, &y); z=(*p)(x, y); printf("较小的数为:%d\n", z); } int min(int a, int b) { if(a<b) return a; else return b; }
7.9 指向指针的指针 1. 指向指针的指针的定义、赋值及引用 • 如果一个指针变量存放的是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量,简称为“二级指针”变量,其定义的一般格式如下: 基类型名 **(指针变量名); • 例如: • int i=10; • int *p=&i; • int **pp=&p;
【例7-18】输出下面程序的运行结果。 #include <stdio.h> void main() { int i=10; int *p=&i; int **pp=&p; printf("pp=%d, &p=%d\n", pp, &p); printf("*pp=%d, p=%d, &i=%d\n", *pp, p, &i); printf("**pp=%d, *p=%d, i=%d\n", **pp, *p, i); }
7.10 链表与案例实现 • 数组的优点是:便于快速、随机地存取数组的任一元素; • 缺点是:数组需要在定义时就指定大小,不够灵活。另外,对数组进行删除和插入操作时需要移动大量的数组元素。 • 有没有更合理的组织程序数据的方法呢?
7.10.2 学生信息链表中结点的定义 学生信息链表中结点的定义如下: struct stu_node{ //结构体类型 char num[15]; //学号 char name[10]; //姓名 int age; //年龄 int c; //C语言分数 int m; //数学分数 int e; //英语分数 int sum; //总分 float ave; //平均分 struct stu_node *next; //指向下一个结点的指针 };
为了简化程序的设计,结点的结构重新定义如下:为了简化程序的设计,结点的结构重新定义如下: struct stu_node //结点结构体的类型 { char num[15]; //学号 int score; //某门课程的分数 struct stu_node *next; }; 定义结点后,就可以定义指向结点的指针变量,如: struct stu_node *head, *newN, *tail; 其中,head为头指针,用来指向链表的第一个节点;newN用来指向新建结点;tail用来指向链表的尾结点。
7.10.3 学生信息链表的操作 1 创建学生信息链表 从无到有创建学生信息链表的过程如下: (1)开辟一个新结点。 程序代码如下: char num[15]= "001"; int score=90; newN=(struct stu_node *)malloc(sizeof(struct stu_node));//开辟一个新结点 strcpy(newN->num,num);//给学号赋值 newN->score=score; //给分数赋值 newN->next=NULL; //给next指针赋值
(2)将新结点链接到表尾。 对于空表,程序代码如下: head=newN; 对于非空表,程序代码如下: tail->next=newN; (3)将链表的尾指针tail指向新结点,以便添加新的结点。程序代码如下: tail=newN;