第九章 指针
C语言的数据类型 基本类型 C数据类型 构造类型 (组合类型) 指针类型 空类型(void) 有符号整型 有符号长整型 整型 数值类型 有符号短整型 无符号整型 无符号长整型 无符号短整型 实型 单精度型 双精度型 字符类型 枚举类型 构造类型 (组合类型) 数组类型 结构类型 共用类型 文件类型 指针类型 空类型(void)
问题提出 为什么要引入指针?指针的作用有哪些? 变量名、变量的值和变量地址之间是什么关系? 如何定义指针变量?指针变量的基本运算有哪些? 如何使用指针操作所指向的变量或数组? 如何使用字符指针来处理字符串? 指针作为函数参数的作用是什么? 如何使用指针间接实现函数调用得到多个结果? 如何利用指针实现内存的动态分配? 如何使用指针访问结构体? 如何用指针和结构体来处理链表? 如何处理多个字符串? 如何使用指针来处理二维数组? 如何使用函数指针?
C语言的高效、高能主要来自于指针 可以通过指针来操作某些不能被直接访问的数据; 能够通过指针来实现调用一次函数得到多个结果; 能够通过指针来进行计算机的动态内存分配; 能够通过指针来处理复杂的数据结构,如链表、树、图等; 在函数调用需要传递大量数据时,可以使用指针传递地址而不是复制大量的实际数据,既提高传输速度,又节省大量内存; 在重复性操作数据的情况下,使用指针来读取数据,可以利用指针对某一块内存区重复操作,明显改善程序的读写性能,例如在遍历字符串,查取表格及操作树状结构时。
C语言-指针 1 指针变量 指针与数组 指针与字符串 指针与函数 指针与结构体 特殊指针 2 3 4 5 6
C语言-指针 ——变量的内存地址
行列号——Excel中的位置标识 公式:B3=B1*B2 没有变量名称 唯一性 连续性 不存在重名问题 单元格与行列号:1对1 按顺序紧密排列 不跳跃、不间断
数据、地址与内存(64KB) C变量示例 (首)地址示例 字节 数值 short i=1234; 0x2000 0xD2 高级语言先说明变量名称及类型,再由编译器分配地址 数据与地址本质上都使用二进制表示,十六进制更短更易阅读 先储存低字节D2再储存高字节的04(Intel) 16位或32位整型数。负数使用补码表示。 C变量示例 (首)地址示例 字节 数值 short i=1234; 0x2000 0xD2 1234 (0x04D2) 0x2001 0x04 float x=0.123456; 0x2004 0x80 0.123456 (0x3DFCD680) 0x2005 0xD6 0x2006 0xFC 0x2007 0x3D char s[4]="Abc"; 0x2008 0x41 "Abc" ('A','b','c',0) 0x2009 0x62 0x200A 0x63 0x200B 0x00 short *p=&i; 0x200C &i (0x2000) 0x200D 0x20 0x200E 0x200F 带小数点数值、字符符号,分别编码转换为二进制。 每8位二进制组成1个字节,并分配1个地址 地址分配是连续的,从最小0到最大值。64KB的存储空间使用16位地址线。
指针类比 按实验室编号来找到实验室类似于C语言中通过指针来间接访问变量。 要理解指针,首先要理解变量、内存单元和地址之间的关系。
变量的三要素 内存:计算机内的存储部件 所有指令和数据都保存在内存里 速度快,可随机访问,但掉电即失 编译或函数调用时为变量分配内存单元 变量的地址(Address) int a=0; a 0x0022ff44 Contents 变量名 变量的值 某存储区域
变量的直接寻址 直接寻址:按变量地址存取变量值 如何读写内存中的数据? scanf("%d", &a); Contents int a=0; a &a 0x0022ff44 Contents 0x0022ff45 0x0022ff46 0x0022ff47 scanf("%d", &a); 某存储区域 scanf("%d", &a)语句中用取地址运算符“&”作用于变量名a就表示取出a变量的内存地址
间接寻址:通过存放变量地址的变量去访问变量 变量的间接寻址 间接寻址:通过存放变量地址的变量去访问变量 如何读写内存中的数据? int a=0; a &a 0x0022ff44 Contents 0x0022ff44 0x0022ff45 0x0022ff46 0x0022ff47 某存储区域 能存放变量地址的变量就是指针
C语言-指针基础知识汇总 ——指针变量
指针变量 变量的指针——变量在内存中存储的首地址称为该变量的指针 指针变量——存放变量地址的变量 注意存放变量的地址需要一种特殊类型的变量,即指针变量 指针变量 指向 变量 变量的地址 变量值
指针变量的定义 数据类型名 * 指针变量名[=初值] int *p; float *fp; char *cp; 数据类型名 * 指针变量名[=初值] 指针声明符 指针变量所指向的变量的类型 int *p; p 是整型指针,将指向整型变量 float *fp; fp 是浮点型指针,将指向浮点型变量 char *cp; cp 是字符型指针,将指向字符型变量
指针及运算 p a &a 指针变量定义:变量名前带*,表示该变量为指针变量 int *p; p是一个指针变量 p的值是一个int类型变量的首地址 取址运算符(&):对常规变量取其地址,如有 int a = 0; int *p; 那么 p = &a; 把 a 的地址赋给 p,即 p 指向 a 注意只有同一类型变量的地址才能放到指向该类型变量的指针变量中 取值运算符(*):“间接访问”指针所指向的变量 *p:指针变量 p 所指向的变量,等价于 a *p=1;//修改指针变量p所指向的变量a的值,修改后a变量值为1 a &a p
下面都是合法的定义和初始化: pointer_1=&a; 正确 *pointer_1=&a; 错误 pointer_3=&a; 错误 float *pointer_3; char *pointer_4; int a,b; int *pointer_1=&a,*pointer_2=&b; pointer_1=&a; 正确 *pointer_1=&a; 错误 pointer_3=&a; 错误
printf(“%d,%d\n”, *p1 , *p2 ); return 0; } 注意区别 int main( ) { int a,b; int *p1,*p2; a=100,b=10; p1=&a; /*把变量a的地址赋给p1*/ p2=&b; /*把变量b的地址赋给p2*/ printf(“%d,%d\n”,a,b); printf(“%d,%d\n”, *p1 , *p2 ); return 0; } 二者的含意不同 结果:100,10 100,10 定义中的*p1,*p2表示p1,p2为指针变量; printf中的*p1,*p2表示间接访问p1,p2所指向的变量。
指针使用举例 int n, m; int *p; scanf( "%d", &n ); p = &n; m = *p; printf( "%d\n", m ); int n, m; int *p; m=n;
指针使用举例 int x, y, *p, *q; //定义x与y为整型变量,p与q为指针变量 【例】通过指针方式实现y=x p = &x; //指针p指向x(将x的首地址赋值给p) q = p; //指针q同样指向x(指针p的指向赋值给q) y = *q; //x的值赋值给y(指针q所指x的值赋值给y)
p1 a p &a 5 &b 9 p2 b p1 a &b 5 &a 9 p2 b #include <stdio.h> int main() { int *p1, *p2, *p, a,b; printf(“integer numbers:"); scanf(“%d,%d”,&a,&b);//a=5,b=9 p1=&a; p2=&b; if(a<b) { p=p1; p1=p2; p2=p; } printf(“a=%d,b=%d\n”,a,b); printf(“%d,%d\n”,*p1,*p2); return 0; } &b 9 p2 b p1 a &b 5 &a 9 p2 b
指针变量使用主要注意点 1)指针必须先指向某个变量,然后才能使用该指针。 int x,*p; x = *p; //在C语法上没有错误,但语义上存在问题 2)如果指针不指向任何变量,置该指针为NULL,NULL称为空指针。 int x,*p = NULL; //指针初始化为空指针 …… //中间代码略(可能会对p赋值) if (p!=NULL) //当指针是一个有效指针时 x = *p; //指针所指的值赋值给x 3)指针通过“==”或“!=”判断是否指向同一个变量。 int x,*p = &x; //定义变量x和指针p if (p==&x) //判断指针变量p与指针表达式&x是否指向同一个变量 x = *p+1; //变量x增加1,等价于(*p)++、x++、*p=x+1等
指针变量使用主要注意点 (4)如果指针指向单一变量(非数组),除赋值外,只允许3个运 算:*、==、!=。 int x,y,*p=&x,*q=&y; //定义变量和指针,并初始化指针 //语法错误的表达式:p+q、p*q、p/q、2*p、p/2等(指针不 能直接乘除加运算) //语义错误的表达式:p+1、p-q、p<q等(p与q指向同一个数组 区域时才有意义)
【例】指针变量错误使用示例 #include<stdio.h> int main() { int a=0; int *pa; //定义指针变量pa但没有对其赋值或初始化 printf("a is %d,&a is %x,pa is%x\n",a,&a,pa); *pa=5; //直接用未赋值的指针做写操作会出错 return 0; }
【例】输入浮点数,输出其在内存中存储的32位二进制(以十六进制方式输出) 分析:浮点数为float类型,占4个字节,其首地址转换为长整型 的首地址,取出长整型数并输出。 #include <stdio.h> int main() { float x, *p = &x; //定义浮点型变量及指针 long n, *q; //定义长整型变量与指针 scanf("%f",&x); //输入浮点数 q = (long *)p; //浮点指针转换为长整型指针 n = *q; //取长整型指针所指数据 printf("0x%08lX\n",n); //以8位十六进制方式输出 return 0; }
C语言-指针基础知识汇总 ——通过指针遍历访问数组
数组名和地址间的关系 a+i 是距数组基地址 表示地址 内存地址 内容 数组元素 int a[100]; 数组名a代表数组首 a或&a[0] 0x0022ff10 a[0]或*(a+0) 表示地址 内存地址 内容 数组元素 int a[100]; 数组名a代表数组首 元素的地址(基地 址) a+i 是距数组基地址 的第i个偏移,即a[i] 的地址 a+1或&a[1] 0x0022ff14 a[1]或*(a+1) a+2或&a[2] 0x0022ff18 a[2]或*(a+2) ……… a+i或&a[i] ………… a[i]或*(a+i) ……… sum = 0; for(i = 0; i < 100; i++) sum = sum + a[i] ; *(a+i)
【例】用数组名做基地址完成数组的输入输出 #include <stdio.h> int main() { int a[10]; int i; for(i=0;i<10;i++) scanf("%d",a+i); //这里用数组名+偏移表示数组元素的地址 printf("%d ",*(a+i));//这里用指针常量引用法表示数组元素 return 0; }
通过指针变量访问数组 表示地址 内存地址 内容 数组元素 p或&p[0] 0x0022ff10 p[0]或*(p+0) 如有int a[10], *p; p=a;//或p=&a[0]; 那么p就指向a[0], *p就等价于数组元素a[0]。 表达式p+1表示的是a[1] 的地址(即&a[1]),*(p+1) 就表示数组元素a[1]。 表达式p+i表示的是a[i]的 地址(即&a[i]),*(p+i) 就表示a[i]。 p或&p[0] 0x0022ff10 p[0]或*(p+0) 表示地址 内存地址 内容 数组元素 p+1或&p[1] 0x0022ff14 p[1]或*(p+1) p+2或&p[2] 0x0022ff18 p[2]或*(p+2) ……… p+i或&p[i] ………… p[i]或*(p+i) ……… sum = 0; for(i = 0; i < 100; i++) sum = sum + a[i] ; *(p+i)
【例】用指针变量引用法完成数组的输入输出 #include <stdio.h> int main() { int a[10]; int *p; for(p=a;p<a+10;p++) /*对p赋初值为数组名a(也就是数组首元素a[0]的地址),即p指向a[0]*/ scanf("%d",p); /*这里用指针变量p表示当前指向的数组元素的地址,进行数据输入,输入完一个元素后,p++,即p指向下一个数组元素,再输入*/ for(p=a;p<a+10;p++) printf("%d ",*p); //这里用指针变量引用法逐个访问数组元素 return 0; } //用指针变量直接指向元素,不必每次都重新计算地址,能大大提高执行效率
指针的算术运算和比较运算 double *p, *q; q - p p + 1 / p-1 p + n / p-n p < q p q 3000 a[0] 地址 内容 数组元素 3008 a[1] a a+1 double *p, *q; q - p 两个相同类型的指针相减,表示它们之间相隔的存储单元的数目(只有p和q都指向同一数组中的元素时才有意义) p + 1 / p-1 指向下一个存储单元 / 指向上一个存储单元 p + n / p-n 指向往后的第n个存储单元 / 指向往前的第n个存储单元 p < q 两个相同类型指针可以用关系运算符比较大小 指针相加、相乘、相除,指针加上和减去浮点数没有意义
例:使用指针计算数组元素个数和数组元素的存储单元数 p q 3000 a[0] 地址 内容 数组元素 3008 a[1] a a+1 # include <stdio.h> int main (void) { double a[2], *p, *q; p = &a[0]; q = p + 1; printf ("%d\n", q - p); printf ("%d\n", (int) q - (int) p); return 0; } 1 8 指针p和q之间元素的个数 指针p和q之间的字节数 地址值
注意区分 p++与(++p)的含义 (p)++ 或(p) – –
指向数组元素的指针主要注意点 1)指针指向数组时,数组元素有多种访问方式 2)指针指向数组元素时,允许的其他运算符 int a[10], *p = &a[0]; //访问元素4种表达式:a[i]、 *(p+i)、 *(a+i)、 p[i] //元素地址4种表达式:&a[i]、 p+i、 a+i、 &p[i] //元素递增表达式示例:a[i]++、*(p+i)=a[i]+1、++p[i] //下标运算[]与取值运算*等价,&与[]可以对消,&与*可以对消 2)指针指向数组元素时,允许的其他运算符 int a[10], i, *p = &a[0], *q = &a[2]; //当指针指向数组元素时 //允许指针±整数:p+i、p-i、p++、++p、p--、--p //允许指针-指针:p-q、q-p 例如 q=a+2,p=a,则q-p等于2 //判断指针大小:p<q、p<=q、p>q、p>=q、p==q、p!=q 如 p<q 成立:,p指向a[0],q指向a[2],p所指排在q所指的前面 3)特殊情况下,允许下标取负值 int a[10], *p = a, *q = a+2; //访问a[0]的6种表达式:a[0]、*a、p[0]、*p、q[-2]、*(q-2)
【例】用指针完成数组的逆序输出 分析:使用指针实现数组的循环遍历,先从前到后输入,再从后到前输出。 #include <stdio.h> int main() { int a[10], *p; //定义数组及指针 for (p=a; p<a+10; p++) //指针从前向后遍历 scanf("%d",p); //逐个输入数组元素 for (p=a+9; p>=a; p--) //指针从后向前遍历 printf("%d ",*p); //逐个输出数组元素 return 0; }
C语言-指针基础知识汇总 ——指针与字符串
字符指针 C语言并没有为字符串提供任何专门的表示法,完全使用字符数组和字符指针来处理 "Hello China" 字符串是一串用双引号引起来的字符 H e l l o C h i n a \0 数组最后一个元素必须是'\0'才表示字符串 字符数组就是每个元素都是字符型的数组 pStr 字符指针就是指向字符类型数据的指针
字符数组与字符指针的重要区别 char sa[ ] = "This is a string"; char *sp = "This is a string"; sa T h i s a t r n g \0 sp T h i s a t r n g \0 如果要改变数组sa所代表的字符串,只能改变数组元素的内容 如果要改变指针sp所代表的字符串,通常直接改变指针的值,让它指向新的字符串
示例 char sa[ ] = "This is a string"; char *sp = "This is a string"; strcpy (sa, "Hello"); sp = "Hello"; sa = “Hello”; 非法 数组名是常量,不能对它赋值
字符串输入到字符数组的基本方法 1)在输入前准备好一个足够“大”的字符数组 char str[80]; //不管输入字符可能有多少,数组长度最好 80或80以上 2)输入一行字符串(行) //按“行”输入,允许输入空行(直接回车) gets(str); //输入一行字符串,以回车为结束符 3)输入一个字符串(词) //按“词”输入 scanf("%s",str); //输入一个字符串,以回车、空格、TAB为 结束符
字符指针指向输入字符串的方法 定义字符指针后,如果没有对它赋值,指针的值 不确定。 char *s ; scanf(“%s”, s); char *s, str[80]; s = str; 错误:不要引用未赋值的指针 正确:指针已经指向数组空间
字符串常量处理 char str[80] = “Hello”; //定义字符数组,初始字符串为 Hello 1)在字符串初始化中应用 char str[80] = “Hello”; //定义字符数组,初始字符串为 Hello char *s = "Hello"; //字符指针s指向字符串Hello 2)字符指针重新指向时应用 char *s; s = "Hello"; //字符指针s指向字符串Hello 3)在函数中直接使用 printf("%s Wang\n","Hello");
使用字符指针遍历字符串 当字符指针指向字符串常量或一个输入的字符串后,通过 字符型指针遍历字符串的一般模板如下: for ( ; *s != '\0'; s++) 说明:当字符指针s指向字符串常量或一个输入的字符串 后,s就是字符串的首地址,*s就表示字符串首字符。 s++后是下一个字符的地址,再*s就表示下一个字符,如 此遍历字符串的各个字符。
[例] 生成字符串(产生26个大写字母的字符串) char str[80], *s; int i,j; //基本定义 //方法1:通过下标生成 for (i=0; i<26; i++) str[i] = 'A' + i; str[i] = '\0'; //方法2:通过指针生成 for (s=str, i=0; i<26; i++) { *s = 'A' + i; s++; } *s = '\0'; //生成时必须在字符串末尾加上字符串结束符('\0')
字符串输出的基本方法 【例】用不同方式输出同一个字符串 #include<stdio.h> int main() { char *s="hello"; int i; //方法一 printf("method 1:"); printf("%s\n",s); //方法二 printf("method 2:"); puts(s); /*……*/
/. ……. / //方法三 printf("method 3:"); for (i=0;. (s+i). = '\0'; i++) //用 /*……*/ //方法三 printf("method 3:"); for (i=0; *(s+i)!= '\0'; i++) //用*(指针+下标)的方式输出每个字符,指针变量s不动,永远指向字符串的首字符 putchar(*(s+i)); printf("\n"); //方法四 printf("method 4:"); for ( ; *s != '\0'; s++) //用指针变量逐个向后移动的的方式输出每个字符 putchar(*s); }
字符串最常用函数 //目标串、源串是字符数组、字符指针或字符串常量 #include <string.h> //使用字符串函数应包含string.h strlen(字符串); //返回字符串长度 strcpy(目标串,源串); //复制源串到目标串上 strcat(目标串,源串); //源串拼接到目标串末尾,形成一个更大的字符串 strcmp(串1,串2); //比较串1与串2在字典中顺序,串1在前返回负数,串1在后返回正数,完全相同返回0
处理字符串主要注意点 1)字符串输入时数组空间太小会溢出 char str[5]; //数组长度不要太小 gets(str); //键盘输入"Hello"时溢出 2)字符串赋值应使用专用函数 char strs[80], strt[80]; //字符数组 //将字符串strs复制到(赋值给)strt strt = strs; //错误,数组名不能直接赋值,用字符指针可以 strcpy(strt,strs); //正确,调用专用函数 3)字符串比较应使用专用函数 char *s1, *s2; //字符数组或字符指针 //比较字符串s1和s2在字典中顺序(如姓名排序时应用到) if ( s1 < s2 ) … //错误,不能直接比较 if ( strcmp(s1,s2) < 0 ) … //正确,调用专用函数
指针处理字符串主要注意点 4)字符串初始化 char str[80]="Hello"; //正确,定义并初始化 char str[80];str="Hello"; //错误,不允许单独直接赋值 char str[80];strcpy(str,"Hello"); //正确,使用函数 char *s = "Hello"; //正确,定义并初始化 char *s; s = "Hello"; //正确,先定义再重新指向 char *s; strcpy(s,"Hello"); //错误,指针s必须向指向某个字符数组 5)区别字符串长度与字符数组长度 char str[80]="Hello",*s=str; strlen(str) //返回字符串长度 = 5个字符 sizeof(str) //返回字符数组长度 = 80个字节 strlen(s) //返回字符串长度 = 5个字符 sizeof(s) //返回字符指针长度 = 4个字节 6)区别字符'\0'、'0'和0 '0' //10个数字字符中的第一个字符,char类型,ASCII编码为48 '\0' //一个特殊的字符,char类型,ASCII编码为0,字符串结束专用字符 0 //一个整型常量,int类型,二进制编码为32位0 c - '0' //变量c为字符类型,值为数字字符('0'至'9')时,从数字字符到整数 n + '0' //变量n为整数类型,值为1位整数(0至9)时,从整数到数字字符
【例】输入一行字符串,按大写字母、数字字符 分别提取,合并成新字符串并输出 【例】输入一行字符串,按大写字母、数字字符 分别提取,合并成新字符串并输出 输出格式要求:[大写字母部分][数字字符部分] 分析:定义输入串为str数组、大写字母串为s1数组、数字字符串为s2数组,遍历2次输入串,依次提取其中的大写字母和数字字符,分别生成在串s1和s2中。 #include <stdio.h> int main() { char str[80], s1[80], s2[80], *p, *q; gets(str); //输入str串 for (q=s1,p=str; *p!='\0'; p++) //遍历str串 if (*p>='A' && *p<='Z') //如果是大写字母 { *q = *p; q++; } //生成到s1串中 *q = '\0'; //生成串结束符 for (q=s2,p=str; *p!='\0'; p++) //遍历str串 if (*p>='0' && *p<='9') //如果是数字字母 { *q = *p; q++; } //生成到s2串中 printf("[%s][%s]\n",s1,s2); //按格式输出 return 0; }
C语言-指针基础知识汇总 ——指针与函数
问题提出 虽然指针可以间接访问变量和数组,但不是比之前学过的直接访问(即用名字访问)方式更复杂了么? 前面函数的章节中学习过如何在函数之间传递整型、实型、字符型等类型变量的值,其实在函数之间也可以传递指针(即是函数中的“传地址”调用),而且功能还更加丰富。本节主要讨论在函数之间传递指针的几种方式,继而体会到指针的必要性和优势。
例:编写函数实现两数的互换 主调函数 被调函数 5 a b x y 5 9 9 5 5 9 a b x y 形 参 实 参 void Swap1(int x, int y) { int temp; temp = x; x = y; y = temp; } int main() { int a, b; a = 5; b = 9; Swap1(a, b); printf("a=%d,b=%d",a,b); return 0; } temp 5 a b x y 5 9 9 5 5 9 实 参 形 参 a b x y x 和 y是内部变量 单向值传递
调用说明:main函数调用swap1函数时,先给两个形参指针变量x和y分配空间,然后将实参的值按顺序单向传递给形参,相当于x=a,y=b,然后swap1函数里交换x和y的值。swap1函数返回时,swap1函数内的形参变量x和y的空间已经被释放,而主函数内的两个变量a和b的值没有任何改变。
例:编写函数实现两数的互换 主调函数 被调函数 5 x y a b 9 5 5 9 &a &b *x *y a b x y &a &b int main() { int a, b; a = 5; b = 9; Swap2( &a, &b ); printf("a=%d,b=%d",a,b); return 0; } void Swap2(int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; } temp 5 x y a b 动画 9 5 5 9 &a &b &a &b *x *y 形 参 实 参 交换的是x 和 y 指向的单元内容 a b x y
调用说明:main函数调用swap2函数时,先给两个形参指针变量x和y分配空间,然后将实参&a和&b的值按顺序单向传递给形参,所以x接收实参&a,y接收实参&b,相当于x=&a,y=&b,此时即建立了指向关系:x指向a变量,y指向b变量,所以操作*x等价于操作a,操作*y等价于操作b,swap2函数里交换*x和*y的值就是交换main函数里a和b的值。swap2函数返回时,虽然swap2函数内的形参变量x和y的空间已经被释放,但是a和b的值已经交换好了。
指针作为函数参数的应用 a b x y 5 9 Swap2 (&a, &b); void Swap2(int *x, int *y) { int t; t = *x; *x = *y; *y = t; } 5 9 要通过函数调用来改变主调函数中某个变量的值: (1) 在主调函数中,将该变量的地址或者指向该变量的指针作为实参 (2) 在被调函数中,用指针类型形参接受该变量的地址 (3) 在被调函数中,改变形参所指向变量的值 (4) 也可以利用指针作为函数参数达到调用一次函数得到多个返回值的目的。
传递单个变量的指针 (授权让函数操作变量) //传递单个变量指针的程序模板 func(int *p,...) //形参为指针 { *p = ... //函数中对*p的操作相当于对main中n的操作 } main() int n; func(&n,...); //传递n的值地址给func,同时授权func改变n的值
【例】编写函数求出main函数的两个整数变量a和b的最大值和最小值,并把结果存储到main函数的max和min变量中。 # include <stdio.h> int main (void) { int a, b,max,min; void maxmin( int x, int y,int *pmax,int *pmin ); scanf("%d%d",&a,&b); maxmin(a, b,&max,&min);/*注意实参除了a和b,还有变量max和min的地址,希望函数maxmin内通过地址直接修改max和min变量的值*/ printf ("max=%d min=%d", max, min); return 0; } /*……*/
【例】编写函数求出main函数的两个整数变量a和b的最大值和最小值,并把结果存储到main函数的max和min变量中。 /. …… 【例】编写函数求出main函数的两个整数变量a和b的最大值和最小值,并把结果存储到main函数的max和min变量中。 /*……*/ void maxmin( int x, int y,int *pmax,int *pmin) /*前两个形参为普通变量,后两个形参是指针变量,来存放实参传递的地址*/ { *pmax=x>y?x:y; //函数参数传递时pmax指向max了,所以操作*pmax等价于操作main函数中的max *pmin=x<y?x:y; //函数参数传递时pmin指向min了,所以操作*pmin等价于操作main函数中的min }
数组名作为函数的参数 数组名是一个指向数组首元素的常量指针,数组名 作函数参数,相当于指针作为函数的参数 数组名做为实参,形参是指针变量(数组) 形参也可以写成数组形式,如: int a[ ] int sum (int *a, int n) { int i, s = 0; for(i=0; i<n; i++) s += a[i]; return(s); } 例 int main(void ) { int i; int b[5] = {1, 4, 5, 7, 9}; printf("%d\n", sum(b, 5)); return 0; } 或*(a+i)
int sum (int *a, int n) { int i, s = 0; for(i=0; i<n; i++) s += a[i]; return(s);} int main(void ) { int i; int b[5] = {1, 4, 5, 7, 9}; printf("%d\n", sum(b, 5)); return 0;} b b[0] b[5] sum(b, 5) b[0]+b[1]+...+b[4] a sum(b, 3) b[0]+b[1]+b[2] sum(b+1, 3) b[1]+b[2]+b[3] sum(&b[2], 3) b[2]+b[3]+b[4] 语法没错误, 运行错,超界 sum(&b[2], 10)
向函数传递一维数组 传递整个数组给另一个函数,可将数组的首地址作为参数传过去 用数组名作函数参数 只复制一个地址自然比复制全部数据效率高 由于首地址相同,故实参数组与形参数组占用同一段内存 在该函数内,不仅可以读这个数组的元素,还可以修改它们
向函数传递字符串 向函数传递字符串时,既可使用字符数组作函数参数,也可使用字符指针作函数参数 本质上都是传地址调用
返回指针的函数 函数可以用return关键字向调用它的函数返回指针类型的数据,主要的目的是为了告诉主调函数某变量或数组的地址。 返回指针的函数定义一般形式如下: 类型名 *函数名(形参列表) { 函数体语句; return(指针变量);//或return (&变量名) }
【例】数组元素查找(直至元素值<0)并返回找到元素的指针 int. find(int 【例】数组元素查找(直至元素值<0)并返回找到元素的指针 int *find(int *p,int x) //形参为指向数组的指针,返回类型也为指针 { for (;*p>=0;p++) //遍历数组,元素<0为结束标志 if (*p==x) //如果找到 return p; //返回元素的指针 return NULL; //如果没有找到,返回空指针 } main() int a[]={10,20,30,40,50,-1}; //数组定义并初始化 int *p = find(a,30); //传递数组,查找数值30 printf("a[%d]\n",p-a); //输出30所在的下标 } //输出a[2]
【例】编写一个子函数,在主函数定义的字符串中找到第一个大写字母,并向主函数返回这个字母的地址。 #include <stdio 【例】编写一个子函数,在主函数定义的字符串中找到第一个大写字母,并向主函数返回这个字母的地址。 #include <stdio.h> char * findfirstletter(char *s) //函数的返回值是一个字符指针{ for(;*s!='\0';s++) if(*s>='A' && *s<='Z') break; return s; //返回字符串中首大写字母的地址} int main(){ char str[81]; char *pos; gets(str); pos=findfirstletter(str); //把返回的地址赋给pos变量 if(*pos=='\0') puts("No Captial Letter"); else puts(pos); //从首大写字母开始输出后面的字符串 return 0;}
【例】输入10个成绩,排序后,输出前3名成绩。 分析:数组的输入、输出、排序分别用函数实现,形参为指针或数组,实参为数组名 #include <stdio.h> void sca(int *p,int n) //形参为指针 { int i; for (i=0; i<n; i++,p++) //遍历 scanf("%d",p); //p指向当前数组元素 } void prt(int *p,int n) //形参为指针 printf("%d ",*p); //p指向当前数组元素 printf("\n");
void sort(int a[],int n) //形参为数组名 { int i, j, t; for (i=0; i<n-1; i++) for (j=0; j<n-1-i; j++) if (a[j]<a[j+1]) //从大到小排序 { t=a[j]; a[j]=a[j+1]; a[j+1]=t; } } int main() int a[10]; sca(a,10); //输入10个成绩 sort(a,10); //10个成绩进行排序 prt(a,3); //输出前3位成绩 return 0;
C语言-指针基础知识汇总 ——指针与结构体
结构体指针的概念 结构体指针就是指向结构类型变量的指针 通过结构体指针可以访问结构体变量或者结构体数组。 结构体指针的一般定义形式为: struct 结构体类型名 *结构体指针变量名; 如有结构体类型定义 struct student{ int num; /* 学号 */ char name[10]; /* 姓名 */ int math,english,computer; /* 三门课程成绩 */ }; 可以定义一个结构体指针变量p struct student *p;
结构体指针赋值 结构体指针定义后必须先赋值才能使用,也就是把结构体变量或者结构体数组名赋值给结构指针,如有语句: struct student s1 = {1001, "ZhangLi", 78, 87, 85}; p = &s1; 就建立了如下图所示的指向关系。
结构体指针的使用 (1) 用*p访问结构成员。如: (*p).num = 1001; (2) 用指向运算符“->”访问指针指向的结构成员。如: p->num = 1001; 当p指向结构变量s1时,下面三条语句的效果是一样的: s1.num = 1001; (*p).num = 1001; p->num = 1001;
例题 【例】综合演示结构体指针的定义、使用、赋值方法 # include <stdio.h> struct student{ int num; /* 学号 */ char name[10]; /* 姓名 */ int math,english,computer; /* 三门课程成绩 */ }; /*......*/
例题 【例】综合演示结构体指针的定义、使用、赋值方法 int main (void) { struct student s1 = {1001, "ZhangLi", 78, 87, 85}; struct student *p; p = &s1; //p指向结构体变量s1 printf("s1:%d %s %d %d %d\n",s1.num,s1.name,s1.math,s1.english,s1.computer); printf("s1:%d %s %d %d %d\n",p->num, p->name, p->math, p->english, p->computer); return 0; }
指针访问结构体数组 如何用结构体指针访问结构体数组? students[100] pt stu[0] stu[1] stu[2] ...... stu[99] struct student students[100]; struct student *pt = students;//或p = &stu[0]; pt指向students[0] pt -> num 等价于 students[0]. num 使pt++,使pt指向students[1] students[1]. num stu[i].num //学生stu[i]的学号 (p+i)->num //学生stu[i]的学号
示例 【例】用结构体指针来处理结构体数组 # include <stdio.h> struct student{ int num; /* 学号 */ char name[10]; /* 姓名 */ }; int main(void) { struct student students[3]={ {1001,"ZhangLi" },{1002, "WangWu",},{1003,"LiYan"}}; struct student *p; /* 用指针来遍历结构体数组的各个元素 */ for(p = students; p < students+3; p++) printf("The number %d student's name is %s\n", p->num, p->name); }
向函数传递结构体 向函数传递结构体的完整结构 向函数传递结构体的首地址 用结构体变量作为函数参数 复制整个结构体变量的所有成员 函数内对形参结构变量的修改不影响实参结构变量 向函数传递结构体的首地址 用结构体数组或者结构体指针作为函数参数 仅复制地址一个数据 函数内可以修改结构体指针所指向的结构体变量或数组的内容
结构指针作为函数参数 用结构体指针或结构数组名作为函数参数仅仅只需要传递结构体变量或者是结构体数组的地址,因而效率更高;而且被调函数内还可以修改结构体指针所指向的结构体变量或数组的内容。 例如 func(struct student *p) //传递结构体指针 func(struct student stu[]) //传递结构体数组 struct student *func(...) //返回结构体指针
示例 【例】用子函数来输出主函数中定义的结构体数组 # include <stdio.h> struct student{ int num; /* 学号 */ char name[10]; /* 姓名 */ }; void output_student(struct student students[ ]); int main(void) { struct student students[3]={ {1001,"ZhangLi" },{1002, "WangWu",},{1003,"LiYan"}}; output_student(students); return 0; }
示例 void output_student(struct student students[ ]) { struct student *p; /* 用指针来遍历结构体数组的各个元素 */ for(p = students; p < students+3; p++) printf("The number %d student's score name is %s\n", p->num, p->name); }
C语言-指针 ——特殊指针
指针数组 C语言中的数组元素可以是任何类型,如果数组的各个元素都是指针类型,用于存放内存地址,那么这个数组就是指针数组。 指针数组就是元素均为同类型指针的数组 定义格式: 类型名 *指针数组名[数组长度]; 数组是多个相同类型数据的集合,任何类型都可以作数组的基本数据类型,指针数组就是用指针作数组的基本类型
指针数组 int a[10]; char *pname[10]; a是一个数组,它有10个元素 每个元素的类型都是整型 每个元素的类型都是字符指针,可以处理一个字符串 两个数组: 整型做基本数据类型 字符指针做基本数据类型,每个元素可以指向从而处理一个字符串
指针数组 多个指针组成的数组,如char *pname[10]; 如对指针数组做初始化,指向结果如图 定义并且初始化以后,指针数组的每个元素都指向一个字符串,如pname[0]指向,… pname[2]指向“Sun”
pname[2]指向”Sun” 的首字符’S’,pname[2]+1就指向’u’,*(pname[2]+1)就是访问字符’u’。 使用指针数组打印多个字符串。 for(i = 0; i < 10; i++) puts(pname[i]); /*pname[i]是每个字符串的首地址*/ 每个字符的两种表示方式详解: 1 2 pname[2]是字符串“Sun”的首地址,看成是数组名,那么根据指针表示数据的等价性,有
多个字符串的处理探讨 多个字符串可以采用二维的字符数组来处理,二维数组的每一行可以存储一个字符串。 也可以采用字符型的指针数组来处理,指针数组的每一个元素都指向一个字符串。 比较如下:
【例】名字字符串排序—二维数组 物理排序 N MAX_LEN char name[N][MAX_LEN]={“American”,”,”England”,”Australia”,…}; for (i=0; i<N-1; i++) { k=i; for (j = i+1; j<N; j++) if(strcmp(str[j],str[i])<0) k=j; if(k!=i){ strcpy(temp,str[i]); strcpy(str[i],str[j]); strcpy(str[j],temp); } N MAX_LEN 物理排序 用二维数组的方式:浪费空间(需要事先按最长的字符串定义二维数组的列长),效率也不高(排序时反复通过strcpy交换字符串) 交换字符数组中的字符串
【例】名字字符串排序—指针数组 索引排序 N char *ptr[N]={“American”,”,”England”,”Australia”,…}; for (i=0; i<N-1; i++) { k=i; for (j = i+1; j<N; j++) if(strcmp(ptr[j],ptr[i])<0) k=j; if(k!=i){ temp = ptr[i]; ptr[i] = ptr[j]; ptr[j] = temp; } N 索引排序 节省空间(每个指针各自指向一个字符串,不需要大块连续的空间,而且每个字符串长度也不一定相同) 提高效率(当需要交换两个字符串时,只须交换指针数组相应两元素的内容(地址)即可,即改变指向关系就好了,不必交换字符串本身。) 交换指针数组中的字符串指针
指针数组处理多个字符串 结论:用指针数组处理多个字符串更为灵活和高效。 【例】人名按字典顺序排序 分析: 由于存放不同人名的字符串的实际长度不同,为了提高存储效率,同时也为了避免通过字符串复制函数反复交换字符串内容而使程序执行的速度变慢,考虑用指针数组来存储若干字符串。即: 建立一个字符型的指针数组,该数组的每一个元素都用来存放一个字符串(人名)的首地址。 总结
[例:姓名排序的完整代码] #define N 10 #include <string.h> #include <stdio.h> int main() { char *name[]={"Tom","Jane","Alexander","Dennis","Sue","David","Rose","Jeffery","Linda","Mary"};/*初始化后,指针数组的每个元素都指向一个人名字符串常量*/ char *pt; 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){ pt=name[i]; name[i]=name[k]; name[k]=pt;}} for(i=0;i<N;i++) puts(name[i]); return 0;}
指针数组用于表示命令行参数 GUI界面之前,计算机的操作界面都是字符式的命令行界面(DOS、UNIX、Linux) 通过命令行参数(Command Line Arguments),使用户可以根据需要来决定程序干什么、怎么干 main(int argc, char* argv[]) 当你把main函数写成这样时 argc的值为:参数的数目+1 argv[0]为指向命令名的字符指针 argv[x](x>1)为指向每个参数的字符指针 随着以Windows为代表的GUI的普及,命令行参数已经显得不那么重要了。但对计算机业内人员来说,它仍然是一个必修科目。它可以让你与程序更深入地交流
命令行参数与main形参之间的关系 argv[0] test\0 argv[1] computer\0 argv[2] language\0 int main(int argc, char *argv[]) { int i; printf("The number of command line arguments is:%d\n",argc); printf("The program name is:%s\n", argv[0]); if (argc > 1) printf("The other arguments are following:\n"); for (i = 1; i<argc; i++) printf("%s\n", argv[i]); } return 0; 带参数的程序如何运行? 结果是什么? argv[0] test\0 argv[1] argv[2] computer\0 language\0 指针数组 字符串 The number of command line arguments is: 3 The program name is: test The other arguments are following: computer language
二级指针 如果一个指针变量存放的是另一个指针变量的地址,则称这个指针变量为二级的指针变量,也称为指向指针的指针。 二级指针变量定义的一般形式为: 类型名 **指针变量名;
二级指针 char *pname[ ] = {“Zhao”, “Qian”, “Sun”, …}; char **p2; p2=pname; 或 p2=&pname[0];//定义二级指针并赋值 第i个姓名:pname[i] 或 p2[i] 或 *(p2+i) 第i个姓名的第j个字符 pname[i][j] 或 p2[i][j] 或 *(pname[i]+j) 或 *(*(p2+i)+j)
二级指针 p2=pname, p2[2][1] 等价于pname[2][1],就可以访问字符’u’。另一种等价表示法:p2指向pname[0],p2+2指向pname[2],*(p2+2)就是pname[2],所以*(*(p2+2)+1)就等价于*(pname[2]+1),就是访问字符’u’。 打印多个字符串 char *pname[ ] = {"Zhao", "Qian", "Sun", "Li","Zhou","Wu","Zheng","Wang","Feng","Chen"}; char **p2; p2=pname; for(i = 0; i < 10; i++) puts(*(p2+i));/*p2+i指向pname[i],*(p2+i)就是pname[i]*/
【例】将5个颜色字符串排序,将多字符串排序的功能用函数来实现。 #define N 5 #include <stdio 【例】将5个颜色字符串排序,将多字符串排序的功能用函数来实现。 #define N 5 #include <stdio.h> #include <string.h> int main(void ) { int i; char *pcolor[ ] = {"red", "blue", "yellow", "green", "purple"}; /*初始化后,指针数组的每个元素都指向一个字符串常量*/ char **p2=pcolor; /* p2的值为指针数组名pcolor,即p2指向pcolor[0]*/ void fsort(char *color[ ], int n); fsort(p2, N); /*二级指针作为实参调用函数 */ for(i = 0; i < N; i++) puts(pcolor[i]); return 0;}
void fsort(char *color[ ], int n) /*指针数组名作为形参*/ { int i, j; char *temp; for(i = 1; i < n; i++) for(j = 0; j < n-i; j++) if(strcmp(color[j],color[j+1])>0){ temp = color[j]; color[j] = color[j+1]; color[j+1] = temp; }
指针和二维数组间的关系 可将二维数组看作一维数组,其每个数组元素又是一个一维数组 按行顺序存放所有元素 a[0][0] int a[2][3]; a[0][0] a[0] a[0][1] a[0][2] a[1][0] a[1] a[1][1] a[1][2] a a[0]+2 a[1] a[1]+1 a[1]+2
a 代表二维数组的首地址,第0行的地址,行地址 指针和二维数组间的关系 a 代表二维数组的首地址,第0行的地址,行地址 a + i 代表第i行的地址 但并非增加i个字节! a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] a int a[2][3]; a[0][0] a[0] a[0][1] a[0][2] a[1][0] a[1] a[1][1] a[1][2] a a+1
指针和二维数组间的关系 二维数组的行指针 逐行查找-〉逐列查找 p int (*p)[3]; a p = a; //用行地址初始化 int a[2][3]; a p a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] a+1
指针和二维数组间的关系 二维数组的行指针 逐行查找-〉逐列查找 p int (*p)[3]; p = a; //用行地址初始化 a int a[2][3]; 二维数组的行指针 int (*p)[3]; p = a; //用行地址初始化 逐行查找-〉逐列查找 a a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] for (i=0; i<m; i++) for (j=0; j<n; j++) printf("%d",*(*(p+i)+j)); p a+1
【例】输入一个3行4列的二维数组,然后输出这个二维数组的元素值
形参声明为指向列数已知的二维数组的行指针
小结 int (*p)[4]; 定义了一个行指针p,能指向含有4个元素的一维数组。 在实际的编程应用中,可以使用指向一维数组的指针(即行指针)来处理二维数组。
函数指针的概念 函数名具有类似数组名的地址特性: 数组名 — 该数组的首地址 函数名 — 该函数的入口地址 一个函数作为一段程序,在内存中占据一片连续的存储单元,其中第一条执行指令所在的位置称为函数的入口地址,函数名就代表了这个入口地址,取值为该地址的指针就称为指向函数的指针变量,简称函数指针。 通过函数指针可以调用并执行函数。
函数指针的使用 1. 定义函数指针 2. 函数指针指向函数 3. 通过指针调用函数 函数指针数组 定义:返回类型 (*指针变量名)(函数形参表); 如:double (*pfunc) (double ); 2. 函数指针指向函数 C语言有库函数:double sin( double ); 可以:pfunc=sin; 3. 通过指针调用函数 如:y = (*pfunc) ( x ); 等价于 y=sin(x); 函数指针数组 如:double (*pfs[3])(double); 数组的每一个元素都可以用来指向一个参数是double,返回值是double的函数
注意事项 给函数指针赋值时,只需给出函数名而不必 (也不能)给出参数。如: int a, b, c, max(int, int), (*p)( ); p=max; /* p为函数指针变量,max为函数名*/ 函数可通过函数名调用,也可通过函数指针调用, 如上例后,只需用(*p)代替函数名max即可。如: c=max(a, b); /* 通过函数名调用 */ c=(*p)(a, b); /* 通过函数指针调用 */ 对函数指针变量,象p+i、p++、p--等运算无意义。
应用举例 函数指针做函数的参数 #include <math.h> 【例】求三角函数sin(),cos()和tan()在30度,60度,90度,120度,150度,180度时的数值。 #include <math.h> void printvalue(double (*fun)(double), int n ) { int i; for( i=1; i<=n; i++ ) printf( "%d\t%f\n", i*30, (*fun)(3.141593*i*30/180) ); } int main() { printf("sin:\n"); printvalue(sin, 6 ); printf("cos:\n"); printvalue(cos, 6 ); printf("tan:\n"); printvalue(tan, 6 ); return 0; }
文件指针 #include <stdio.h> FILE *fp;
小结 指针即地址,指针变量就是专门用于存放变量内存地址值的变量。利用指针可以间接访问它指向的变量或者数组。结构体指针就是指向结构类型变量的指针,通过结构体指针可以间接访问结构体变量或者结构体数组。指针可以作为函数参数,也可以作为函数返回值,在主调函数和被调函数间传递变量、数组、结构体的地址。 int *p;定义了p是一个指向整型变量的指针变量;int *p[3]; 定义了p是一个指针数组,该数组的元素(p[0]、p[1]、p[2])都是指向整型变量的指针变量;int **p;定义了一个二级指针变量p;int (*p)[4]; 定义了一个行指针p,能指向含有4个元素的一维数组。 在实际的编程应用中,可以使用字符型的指针数组来处理多个字符串,这比用二维字符数组处理多个字符串更加灵活和高效;也可以使用指向一维数组的指针(即行指针)来处理二维数组。
本章结束