8.1什么是指针
8.1.1 什么是指针和指针变量
首先解决一个问题;数据如何在计算机中储存 ,读取?
直接访问 间接访问
直接访问: int i;四个字节 :2000 2001 2002 2003 int j;四个字节 :2004 2005 2006 2007 int k; 四个字节 :2008 2009 2010 2011 在一次编译中,电脑分别给i j k;分配了四个字节 ;不同编译分配的字节地址可能不一样 编译的时候把变量名字转换为变量的地址; 变量名<===>地址 i======>2000... j======>2004... k======>2008... 存:电脑找到i,等于找到地址2000,数据存到地址2000中去; 取:电脑找到i,等于找到地址2000,把地址2000中的数据提取出来;
间接访问: int i;四个字节 :2000 2001 2002 2003 int j;四个字节 :2004 2005 2006 2007 int k; 四个字节 :2008 2009 2010 2011 把变量i的地址存放到另外一个变量中;然后通过该变量找到该地址,然后访问; 这个变量,是一个“中介”; i_pointer=&i;(把地址i先储存到变量中去)
把数字3送到i变量中去:
i=3; *i_pointer=3;
那么由此可以看出i=*i_pointer;后者指的是这个地址所指向的变量i;
那什么是指针,也就是变量的地址;所以,2000,2001…都是指针;如果有一变量专门贮存地址,那么我们称之为指针变量;
8.2指针变量
8.2.1使用指针变量的例子
引例:
#include <stdio.h> int main() { int a=100,b=10; int *pointer_1,*pinter_2;前面的*只是表示指针变量; pointer_1=&a; pointer_2=&b; printf("%d %d",a,b); printer("*pointer_1:%d,*pointer_2:%d",*pointer_1,*pointer_2); 这儿的*表示指向作用;意思是指向指向pointer—1中的地址;就是a; return 0; }
&:取地址运算符;
*:指针运算符;
8.2.2 怎样定义指针变量
咱们从例子中可以看到int *pointer_1,*pinter_2;
;这就是定义了;所以咱们可以归纳总结为:
类型名 *指针变量名
类型名:定义地址源数据的储存大小,储存方式;
*:表示是后边是指针的变量名;指针变量名字不带 *;比如
:pointer_a;pointer_b;
注意:
1.可以定义的时候初始化;
int *pointer_1=&a,*pinter_2=&b;
2.首先我去取用的时候,*给了pointer_1中的中的东西一个指向的 “魔法加持”,而pointer_1要给 *提供一些东西,就是源数据地址,以及隐含的源数据的几个字节和储存方式;那我有各种各样的数据;比如int ;float ; char ;那我指针变量是不是都能储存呢;不是;只能储存其中一种;所以说定义类型名就是要给这个指针变量限定只能储存按要求的源数据;
3.指针:地址(隐含数据的字节数以及储存方式)
指针变量:一个(限定了源数据具有特定大小;储存条件)只能储存地址的空间
8.2.3 怎样引用指针变量
引用的话就两个;
一个是p:指针变量,里面储存的是地址
一个是*p:地址指向的变量,就是源数据储存变量
例子: p=&a;这个是把指针变量p引用;把a地址赋值给p; printf("%d",*p);这个是引用指针变量中的地址,并且给予指向性作用;输出的是a数值 *p=1;把赋值给a; printf("%d",p);把地址直接输出;
8.2.4指针变量作为函数的参数(例)
/*输入 a,b两个数,按照先大后小的顺序输出a,b;*/ #include <stdio.h> int main() {void swap(int *p1,int *p2); int a,b; int *pointer_1,*pointer_2; printf("please enter two integer numbers:"); scanf("%d,%d",&a,&b); pointer_1=&a; pointer_2=&b; if(a<b) swap(pointer_1,pointer_2); printf("max=%d,min=%d\n",a,b); return 0; } void swap(int*p1,int*p2){ int temp; temp=*p1; *p1 =*p2; *p2=temp; } void swap(int*p1,int*p2){ int *temp; *temp=*p1; *p1 =*p2; *p2=*temp; } 解释一下这段代码;*temp 这儿定义了指针变量;与前面定义一样,电脑会自己给他分配一个数值先储存进去;同理在这儿如果你没有指定那个变量那么这儿电脑会自动分配某一个变量去指向;那么此时如果你把数值赋值给这个变量,你就会导致这个随意指定的变量的数值改变,导致内部出现未知错误;所以不提议这么做,最好就是定义一个源数据类型的数据形式就行;比如整形;就定义int;
解释一下该函数如何实现的
如上图,
int a,b; int *pointer_1,*pointer_2; pointer_1=&a; pointer_2=&b;
此时就是图(a);
void swap(int*p1,int*p2)
此时定义了两个形式参数,实参通过“值传递”把地址给了形式参数;在函数里面临时参数等价于一个临时定义的指针变量;这样原指针变量中的地址就给了现在这个指针;所以说如图b(2)左边图的意思;再看如图2
int temp; temp=*p1; *p1 =*p2; *p2=temp;
这串代码的意思是通过一个临时定义的变量储存空间,把指向的变量的值互换,由于指向的变量不属于临时变量,所以在主函数里面他们的数值真的互换了;所以输出的时候他们的数值就是互换的数值;
通过上面例子我们知道了 void swap(int *p1,int *p2); 做实参;一般而言,普通实参会返回一个数值;当实参是指针的时候,这样的返回值可能就是不只一个;会有多个数值的改变;
8.3 通过指针引用数组
8.3.1数组元素的指针
数组是由若干个数据组成的,所以我的指针变量是可以指向其中某一个数据的;
int a[10]; int *p; p=&a[0];
这就是把数组第一位的地址赋值给了p;这
还有一种等价形式:
p=&a[0]; p=a; a是数组,把数组赋值给变量p,但是变量p只能接受一个地址, 所以电脑会默认把第一个地址赋值给p;
但是注意不能写成p=&a,&a中有是个地址,后面变量不能一下接受10个地址;
8.3.2 在引用数组元素时指针的运算
咱们是可以通过a[0]直接计算出该数组中其他变量的地址,这样咱们就可以只定义一个指针变量,能指向数组中所有的元素;
int a[10]; int *p; p=&a[0];假设是2000; p+1 ;指向同一个数组中下一个元素;对于int 型p+1的结果不是2001;而是2004; p-1;指向同一个数组中上一个元素;结果是1996; p=a;2000;a是数组,把数组赋值给变量p,但是变量p只能接受一个地址, 所以电脑会默认把第一个地址赋值给p;假设地址是2000; p=a+1;相当于2004,与指针变量运算是一样;因为a也可以理解成指针变量; 编译后*(a);其中a是地址; p=a[1];2004 printf("%d",a[1]);电脑是如何调用a[1]的数值,首先电脑把a[1]当作*(a+1)处理; 然后输出数值;
p++; ++p; p--; --p; p1-p2;两个地址差值,然后除以数组长度;(2020-2012)/4;
#include <stdio.h> int main() { int a[4]={1,2,3,4}; int *p; p=a; p=++p; printf("max=%d\n",*(++p)); return 0; }
通过以上结论,咱们可以得出,数组a是不可以自增或者自减的;数组是可以加或者减;数组名a指针型常量;是一个固定不变的值;而指针变量p是可以自增自减的;也可以加减;
8.3.3 通过指针引用数组元素
引用一个数组元素,可以使用
下标法:a[i];在编译时转化为*(a+i)
指针法*(a+i);或者*(p+i);那么我*(p+i)也可以写成p[i];不过这个意思是当前p加上3;不一定等于a[3];
/*有一个整型数组a,有10个元素,要求输出数组中的全部元素*/ #include <stdio.h> int main() {int a[10]; int i; printf("please enter 10 integer numbers:"); for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0;i<10;i++) printf("%d",a[i]); printf("%\n"); return 0; } /*有一个整型数组a,有10个元素,要求输出数组中的全部元素*/ #include <stdio.h> int main() {int a[10]; int i; printf("please enter 10 integer numbers:"); for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0;i<10;i++) printf("%d\t",*(a+i)); printf("%\n"); return 0; } /*有一个整型数组a,有10个元素,要求输出数组中的全部元素*/ #include <stdio.h> int main() {int a[10]; int i; int *p; printf("please enter 10 integer numbers:"); for(i=0;i<10;i++) scanf("%d",&a[i]); for(p=a;p<(a+10);p++) printf("%d\t",*p); printf("%\n"); return 0; } /*有一个整型数组a,有10个元素,要求输出数组中的全部元素*/ #include <stdio.h> int main() {int a[10]; int i; int *p=a; printf("please enter 10 integer numbers:"); for(i=0;i<10;i++) scanf("%d",&*p++);//后面必须要有地址;*p++不是地址;p++是地址;&*p++是地址; p=a; for(i=0;i<10;i++,p++) printf("%d\t",*p); printf("\n"); return 0; }
*p++ =*(p++) 先用*p ,再让p加一; *(++p) 先让p加1;然后用*; ++(*p) 源数据加1;
8.3.4 用数组名字作函数参数
从字面意思去理解这个标题数组作函数参数;包括实参还有形参;
具体分为以下四类:
1.形式参数 和 实参都用数组名
inv(a,10); ... void inv(int x[],int n);
首先函数有实参把数值传给形参;形参是临时定义的;用完自动解散的一个储存单元;
实参用的是数组名,那么我电脑默认数组名传给形参的时候,我给的是数组中第一个地址;而形参定义的是数组,而电脑编译时就相当于x[]=*x;,此时指向的是第一个地址;而x就是指针变量;
2,形参用数组名字,实参用指针变量
inv(a,10); ... void inv(int *x,int n);
首先函数有实参把数值传给形参;形参是临时定义的;用完自动解散的一个储存单元;
实参用的是数组名,那么我电脑默认数组名传给形参的时候,我给的是数组中第一个地址;而形参定义的是指针变量,此时指向的是第一个地址;而x在函数体中就可以当作指针变量使用;
3,形参用指针变量 实参也用指针变量
int *p; int(p,10); ... void inv(int *x,int n);
首先函数有实参把数值传给形参;形参是临时定义的;用完自动解散的一个储存单元;
实参用的是指针变量,实参传给形参的时候,实参中包含的是地址,而我把实参中的地址给了形参而已,而形参定义的是指针变量,此时指向的是第一个地址;而x在函数体中就可以当作指针变量使用;
4,形参用指针变量 实参用数组
int *p; int(p,10); ... void inv(int a[],int n);
首先函数有实参把数值传给形参;形参是临时定义的;用完自动解散的一个储存单元; 实参用的是指针变量,实参传给形参的时候,实参中包含的是地址,而我把实参中的地址给了形参而已,而形参定义的是数组名,而电脑在编译的时候,会把数组名当作指针来处理,所以x就相当于定义了一个指针变量,此时指向的是第一个地址;而x在函数体中就可以当作指针变量使用; /*将数组中a个整数按照相反顺序存放* #include <stdio.h> int main() {void inv(int x[],int n); int i,a[10]={3,7,9,11,0,6,7,5,4,2}; printf("the original arrary:\n"); for(i=0;i<10;i++) printf("%d\t",a[i]); printf("\n"); inv(a,10); printf("the array has bee inverted :\n"); for(i=0;i<10;i++) printf("%d\t",a[i]); printf("\n"); return 0; } void inv(int x[],int n) {int temp,i,j,m=(n-1)/2;//m=4; for(i=0;i<=m;i++)//循环5次,因为\\a0 a9\\a1 a8\\a2 a7\\a3 a6\\a4 a5\\ { j=n-1-i;//当i都等于0时候,j=9;... temp=x[i]; x[i]=x[j]; x[j]=temp; //把数值交换 } return ; } */ /*将数组中a个整数按照相反顺序存放*/ #include <stdio.h> int main() {void inv(int *x,int n); int i,a[10]={3,7,9,11,0,6,7,5,4,2}; printf("the original arrary:\n"); for(i=0;i<10;i++) printf("%d\t",a[i]); printf("\n"); inv(a,10); printf("the array has bee inverted :\n"); for(i=0;i<10;i++) printf("%d\t",a[i]); printf("\n"); return 0; } void inv(int *x,int n) {int *p,temp,*i,*j,m=(n-1)/2; i=x;j=x+n-1;p=x+m; for(;i<=p;i++,j--){ temp=*i; *i=*j; *j=temp; } return ; }
8.3.5 多维数组的地址
a;表示一维数组第一个元素的首地址;假设是2000 a+1;表示一维数组第二个元素的地址;2004 a+2;表示一维数组第三个元素的地址;2008 他们代表的含义是具体某一个元素的地址; 二维数组: a;第0行一维数组的地址;2000; a+1;第1行一维数组的地址;2016; a+2;第2行一维数组的地址;2032; 他们所代表的含义都不是某一个具体元素的地址; 但是地址的数值本身可能和某一具体的地址重合; /// 把二维数组中的某一行四个元素都理解成一个抽象的新元素; 这样二维数组就变成了含有四个元素的“一维数组”; 这样a;a+!;a+2;就都是某一抽象元素的具体地址了;
一维数组: a[0];一维数组中第一个元素,电脑编译时相当于*a; a[1]; 一维数组中第二个元素,电脑编译时相当于*(a+1); a[2]; 一维数组中第三个元素,电脑编译时相当于*(a+2); // 二维数组: a[0]; 二维数组中第0行;算作是一个抽象的“一维数组”,a[0]是第一个元素的地址; 2000,等价于*a=*(a+0)=&a[0][];a表示行,在行的前面加一个*; 表示把行的地址转化为列的地址;这样2000原本作为第一行的开头地址, 现在变成了第0行第一位元素的具体地址;虽然a与*a 俩个都是2000;但是具体所表示的含义是截然不同的; a[0]+1; 第二个元素的地址;2004 =*(a)+1=&a[0][1] a[0]+2; 第三个元素的地址;2008=*(a)+2=&a[0][2] //
a;第0行一维数组的地址;2000;
a[0]; 二维数组中第0行;算作是一个抽象的“一维数组”,a[0]是第一个元素的地址;2000
这两个地址都是2000;表示的是同一个地址;但是两者的所代表的含义是截然不同的;
二维数组: a[0][0]; a[0][1]; a[0][2]; *(a[0]+0)=*(a[0])=*(*(a+0)+0)=*(*a)=*(*(a+0)); *(a[0]+1)=*(*a+1) *(a[0]+2)=*(*a+2)
看这张图,第三行有一个&a[1]为什么是一行首地址;这是因为,在指向行的指针前面加上一个*,就直接转化为指向列的指针;同理在指向列的指针前面加上&,就成为指向行的指针;所以原本a[1];是第一行中第一列元素的地址,但是这里把列转化为行了,所以说这里其实就是第1行的地址;
#include <stdio.h> int main() {int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; printf("%d %d\n",a,*a); printf("%d %d\n",a[0],*(a+0)); printf("%d %d\n",&a[0],&a[0][0]); printf("%d %d\n",a[1],a+1); printf("%d %d\n",&a[1][0],*(a+1)+0); printf("%d %d\n",a[2],*(a+2)); printf("%d %d\n",&a[2],a+2); printf("%d %d\n",a[1][0],*(*(a+1)+0)); printf("%d %d\n",*a[2],*(*(a+2)+0)); return 0; }
8.3.6 指向数组元素的指针变量
a.指向数组元素的指针变量
/*有一个3*4的二维数组,要求用指向多维数组元素的指针变量输出二维数组各个元素的数值*/ #include <stdio.h> int main() { int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; int *p; for(p=a[0];p<a[0]+12;p++) {if((p-a[0])%4==0)printf("\n"); printf("%4d",*p); } printf("\n"); return 0; } 仔细看着这个指针,你会发现*p直接就指向元素本身,就是整型数据的; p是具体某一个二维数组的地址;比如p是指向a[0];a[0][1];a[0][2];
b.指向m个元素组成的一维数组的指针变量
上面讲到了,他是指向某一个具体的元素的;比如a[0];a[0][0];
这里是不一样的,这次是指向包含了m个元素的一维数组;
这样的话只能这样赋值:
p=a[0];是错误的;改成p=&a[0];
指向某一个具体的数值的指针变量; p=a[0];含义是把第一行第一列的元素的地址赋值给p; p+1;表示下一个元素的具体地址; 指向m个元素组成的一维数组的指针变量; p=&a[0];含义是把第一行的地址赋值给变量p; p+2;表示下一行一维数组的具体地址;
#include <stdio.h> int main() {int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; int(*p)[4]; int i,j; p=a; printf("please enter row and colum:"); scanf("%d,%d",&i,&j); printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j)); return 0; } /// int(*p)[4]; 表示定义p为一个指针变量;他指向包含四个元素的一维数组; p被定义为指向一维整型数组的指针变量,一维数组有四个元素, 因此p的类型是一维数组,包含四个整型数据,其长度是16个字节; 注意!!基类型不是int* ;而是int * [4]; 两者是有区别的;前者定义的是整型;后者定义的是一维数组包含四个元素;
3.用指向数组的指针作函数的参数;
可以使用两个东西作为函数的参数(1.指向某一具体元素的指针变量,2.指向某m个元素构成的一维数组的指针变量;)
/*有一个班级,三名学生,各学四门课,计算总平均分数,以及第n个学生的成绩*/ #include <stdio.h> int main() {void average(float *p,int n); void search(float(*p)[4],int n); float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}}; average(*score,12); search(score,2); return 0; } void average(float *p,int n) {float *p_end; float sum=0,aver; p_end=p+n-1;//表示p+11;就是最后一个位置的元素的地址; for(;p<=p_end;p++) sum=sukm+(*p); aver=sum/n; printf("average=%5.2f\n",aver); } /// 这一行主要使用了指向数组元素的指针变量,float *p;这样对于p而言; 形式参数里面的初始化的地址就是某一个具体元素的地址; 实参是*score;其含义等价于*a;后者是第一行第一列元素的地址; 所以实参是第一行第一列的元素的地址; void search(float (*p)[4],int n) {int i; printf("the score of no.%d are :\n",n); for(i=0;i<4;i++) printf("%5.2f",*(*(p+n)+i));//输出某一个元素的具体值; printf("\n"); }这一行使用了指向m个元素组成的一维数组的指针变量,float (*p)[4]; 这意味着,形式参数的p的所代表的地址是一个个抽象的一维数组的地址的开头; 基类型也是一维数组;实参score;这相当于a;这是第一行一维数组的开头地址; 所以在函数体中,p初始化为第一行一维数组的首地址;
/*查找一门以上课程不及格的学生,输出他们全部课程的成绩*/ #include <stdio.h> int main() {void search(float (*p)[4],int n); float score[3][4]={{57,67,70,60},{80,87,90,81},{90,99,100,98}}; search(score,3); return 0; } void search(float(*p)[4],int n) {int i,j ,flag; for(j=0;j<n;j++) {flag =0; for(i=0;i<4;i++) if(*(*(p+j)+i)<60) flag=1; ///用的是*(*(p+j)+i)输出某一个具体的数值。 if(flag==1) {printf("no.%d fails,his scoresare:\n",j+1); for(i=0;i<4;i++) printf("%5.1f",*(*(p+j)+i)); printf("\n"); } } }
8.4通过指针引用字符串
8.4.1 字符串的引用方式
想引用一个字符串,在咱们之前学的;
1)用字符数组存放一个字符串,可以通过数组名和下标引用字符串中的一个字符,也可以数组名和格式声明“%s”输出该字符串;
/*定义一个字符数组,在其中存放字符串,“I LOVE CHINA !"输出该字符串和第八个字符*/ #include <stdio.h> int main() {char string[]="i love china!"; printf("%s\n",string); printf("%c\n",string[7]) ; return 0; }
从这串代码,咱们可以看出
调用某一个字符,只要输出某一个数组中某一位即可。string[7];%c; 调用某一个字符串,输出某一个数组就行;string ;%s;
2)通过字符指针变量输出一个字符串,或者一个字符(利用指针&数组知识)
/*定义一个字符数组,在其中存放字符串,“I LOVE CHINA !"输出该字符串和第八个字符*/ #include <stdio.h> int main() {char *string="i love china!"; printf("%s\n",string); return 0; } / {char *string="i love china!";这一句电脑是如何执行的, 电脑首先会自动在储存空间内部定义一个数组储存空间,这个数组储存空间用来存放这个 字符串,不论是字母还是空格 ,标点符号都属于字符,然后这些都线性分配储存空间之后, 会在字符数组的最后一位放一个\0,作为字符结束标识符; 此时此步骤可以拆分为两步骤: char *string; string="i love china!"; 这句话意思就是定义了一个指针变量,他的基类型是字符型; 然后之前电脑自动分配的数组第一个元素的地址分配给string; printf("%s\n",string); 这句话就是输出的时候string是字符串的第一个元素的地址, 然后应为输出的类型又是%s所以说知道遇到\0为止,输出所有字符; #include <stdio.h> int main() {char *string="i love china!"; printf("%c\n",*string); return 0; } 输出的是i,*string的意思是第一个元素的字符,输出他;
/*将字符串a复制到字符串b,然后输出字符串b*/ #include <stdio.h> int main() {char a[]="i am student.",b[20]; int i; for(i=0;*(a+i)!='\0';i++) *(b+i)=*(a+i); *(b+i)='\0'; printf("string a is :%s\n",a); printf("string b is :"); for(i=0;b[i]!='\0';i++) printf("%c",b[i]); printf("\n"); return 0; } /*将字符串a复制到字符串b,然后输出字符串b*/ #include <stdio.h> int main() {char a[]="i am student.",b[20]; char *p1,*p2; p1=a;p2=b; for(;*p1!='\0';p1++,p2++) *p2=*p1; *p2='\0'; printf("string a is :%s\n",a); printf("string b is :%s",b); return 0; }