目录
1、认识指针
指针==地址
一个变量的值可以通过变量名来访问,也可以通过指针也就是变量的内存地址来访问
指针变量就是存放地址的变量
#include <stdio.h>
int main(){
int i =10;
//定义一个指针变量
//这里的*是一个标识符,告诉系统我是一个指针变量,是用来保存别人的地址,和下方的运算符不同
int* p;
//把i的地址赋值给指针变量
p=&i;
printf("%d\n",i);
printf("%d\n",*p);//*在这里充当取值运算符--取出地址p中的数值
printf("%d\n",*(&i));
return 0;
}
运行结果
由此可得三种方式都能访问到变量i的值
2、为什么需要用指针
这里我们用一个案例来阐述指针的作用
封装一个函数实现两个数的交换
#include <stdio.h>
void chagedata(int a,int b){
int demo;
demo=a;
a=b;
b=demo;
}
int main(){
int a=10;
int b=20;
chagedata(a,b);
printf("a=%d b=%d",a,b);
return 0;
}
思考一下这里能成功交换两个变量的值吗?我们来看运行结果
由结果我们可以发现两个值并没有交换,下面我们用指针的方式来试一下
#include <stdio.h>
void chagedata(int* a,int* b){
int demo;
//注意是值交换要用*a而不是用a
demo=*a;
*a=*b;
*b=demo;
}
int main(){
int a=10;
int b=20;
chagedata(&a,&b);
printf("a=%d,b=%d",a,b);
return 0;
}
运行结果
用指针的方式可以是两个变量的值进行交换,而常规的方法是不能使变量的值进行交换的
解释:常规方式是进行实参和形参之间发生的值传递 运行到chagedeta函数时会开辟一个空间给函数,函数体内形参a与b也有相应的地址--与实参的地址毫无关联,在函数体内形参a与b是发生了值的交换,当函数运行结束之后这片内存空间也会被释放。这整个过程中实参并没有任何变化。而使用指针的方式实参与形参传递的是地址值,地址值直接指向变量值,然后使其进行交换。
3、通过指针引用数组
定义一个指针变量指向数组
# include <stdio.h>
int main(){
int arr[]={1,2,3,4,5,6};
int* p=arr;
printf("%d",*p);
return 0;
}
运行结果
由运行结果可知指针变量p指向的是数组的第一个元素的地址也就是数组的首地址,由此可知在C语言中,数组名(不包括形参数组名,形参数组并不占据实际的内存单元)代表数组中首元素(即序号为0的元素)的地址。因此,以下这两个语句等价
p =&a[0];//a是数组名,p的值是a[0]的地址
p=a;//p的值是数组a首元素(即a[0])的地址
指针增量和数组的关系,以下代码做了解释
#include <stdio.h>
int main(){
int a[]= {1,2,3,4,5};
int *p;
//p=&a[0];
//printf("%d ",*p);//输出为1;
p=a;//数组名就是数组的首地址
printf("%d \n",*&a[0]);//用数组第一个元素的地址取值
printf("%d \n",*a);//用数组名取值
printf("%d \n",*p);//用指针变量取值
printf("数组的第一个值%d 地址是%p\n",*p,p);
printf("数组的第二个值%d 地址是%p\n",*(p+1),p+1);
//这里的加一,不是指针变量的数值加一 ,而是地址增加(偏移)了一个空间大小,偏移的空间大小由 指针变量的类型决定
printf("数组的第三个值%d 地址是%p\n",*(p+2),p+2);
//这里的int类型的指针变量加一时,地址值加了4
printf("数组的第四个值%d 地址是%p\n",*(p+3),p+3);
printf("数组的第五个值%d 地址是%p\n",*(p+4),p+4);
//用for循环遍历
for(int i=0;i<5;i++){
//printf("数组的第%d个值是:%d\n",i+1,*(p+i));//用指针变量遍历
printf("%d ",*p++);
}
//这里如果你进行了两次for循环指针变量所代表的地址值会超出数组的地址,所以在第二次for循环的时候应该重新对指针变量赋上数组的首地址
p=a;
for(int i=0;i<5;i++){
//printf("数组的第%d个值是:%d\n",i+1,*(p+i));//用指针变量遍历
printf("%d ",*p++);
}
return 0;
}
运行结果
由此可见指针变量+1其实是地址值加数组内一个数组的字节数
取数组内容的方法
1、指针当数组名下标法来访问
2、在for循环时用数组名来加
3、可以指针变量*p++,但不能数组名arr++
#include <stdio.h>
int main(){
int arr[] = {1,2,3,4};
int *p;
p=arr;
printf("\n");
printf("%d\n",p[3]);//指针当作数组名,下标法访问
printf("%d\n",arr[3]);
for(int i =0;i<4;i++){
printf("%d ",*(p+i));
}
//用数组名来加
for(int i =0;i<4;i++){
printf("%d ",*(arr+i));
}
for(int i =0;i<4;i++){
printf("%d ",*p++);
}
/*
for(int i =0;i<4;i++){
printf("%d ",*arr++);//这里编译错误
} //p属于指针变量,而arr属于指针常量,指针常量是不能拿来++的
*/
}
4、指针和二维数组
二维数组我们可以把他比作父子数组,二维数组的本质还是数组,不同点是二维数组的数组元素还是个数组(子数组)
这里思考一个问题:a是谁的地址,a[0]又是什么,那*a或者*(a+0)呢
答案是:a表示父数组的地址,a[0]、*a都表示数组的首地址--也就是第一个子数组的地址
#include <stdio.h>
int main(){
int a[3][4]={{1,2,3,4},{1,2,3,4},{1,2,3,4}};
printf("a的地址是%p,a+1的地址是%p\n",a,a+1);
//a的地址是000000000061FDF0,a+1的地址是000000000061FE00
//+1指针的偏移量是二维数组的一个子数组的字节数
printf("a[0]的地址是%p,a[0]+1的地址是%p\n",a[0],a[0]+1);
//a[0]的地址是000000000061FDF0,a[0]+1的地址是000000000061FDF4
printf("a[0]的地址是%p,a[0]+1的地址是%p\n",&a[0][0],&a[0][0]+1);
//由这里可见对a、a[0]、a[0][0]三个分别取地址会产生a=a[0]=&a[0][0]
printf("a+1的地址是%p\na[1]的地址是%p\na[1][0]的地址是%p\n",a+1,a[1],&a[1][0]);
printf("&a[0] :%p\n",&a[0]);
printf("%p %p",a[0],*(a+1));
return 0;
}
二维数组a的有关指针----这个嵌入式工程师的笔试题可能会考
5、数组指针
定义数组指针int (*p)[4];
#include <stdio.h>
//定义数组指针
int main(){
int arr[3][4]={{1,2,3,4},{1,2,3,4},{1,2,3,4}};
int *p =arr[0];
int (*p2)[4]=arr;
//数组指针等同于数组名,他的增减也和数组名的增减一样,数组指针指向的是一个数组,指针变量指向的是数组里的一个值
for(int i =0;i<3;i++){
for(int j=0;j<4;j++){
printf("%d ",arr[i][j]);
}
}
//用数组指针遍历二维数组
printf("\n==========用一次for循环=============\n");
for(int i=0;i<3*4;i++){
printf("%d ",*(arr[0]+i));
}
printf("\n===========用两次for循环============\n");
for(int i =0;i<3;i++){
for(int j=0;j<4;j++){
printf("%d ",*(*(arr+i)+j));
}
}
return 0 ;
}
运行结果
6、指针数组
指针数组的解释
数组指针和指针数组的区别
指针数组
指针数组:指针数组可以说成是”指针的数组”,首先这个变量是一个数组。
其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型。
在 32 位系统中,指针占四个字节。
定义指针数组int *p[4];
数组指针
数组指针:数组指针可以说成是”数组的指针”,首先这个变量是一个指针。
其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。
定义数组指针int (*p)[4];
根据上面的解释,可以了解到指针数组和数组指针的区别,因为二者根本就是两种类型的变量。
#include <stdio.h>
int main(){
int a=1;
int b=2;
int c=3;
int *p[3]={&a,&b,&c};
int len =sizeof(p)/sizeof(p[0]);
for(int i =0;i<len;i++){
printf("%d ",*(p[i]));//根据存放的地址取值
//方式二可以好好自己理解一下:printf("%d %p\n",*(*(p+i)),*(p+i));
}
return 0;
}
运行结果
7、函数指针
函数地址--函数名就是地址
定义一个函数指针
#include <stdio.h>
void demo1(){
printf("学指针把人都给绕晕了\n");
}
int demoSum(int a,int b){
return a+b;
}
int main(){
int a=10;
int b=20;
int demosum;
//定义函数指针
void (*p)();//无参
p=demo1;
int (*sum)(int a,int b);//有参
sum=demoSum;
//用以下三种方试都可以调用函数
demo1();
p();
(*p)();
demosum=demoSum(a,b);
printf("两数之和是:%d",demosum);
demosum=sum(a,b);
printf("两数之和是:%d",demosum);
demosum=(*sum)(a,b);
printf("两数之和是:%d",demosum);
return 0;
}
运行结果
使用函数指针:函数的调用--有两种方式
1、直接访问:用变量名(函数名)访问
2、间接访问:用指针(函数指针)访问
使用函数指针的原因也就是好用之处,可以根据程序运行过程的不同情况调用不同的函数类似与Java的接口
#include <stdio.h>
# include <stdlib.h>
int getMax(int a,int b){
return a>b?a:b;
}
int getMin(int a,int b){
return a<b?a:b;
}
int getSum(int a,int b){
return a+b;
}
int dataHandler(int (*p)(int a,int b),int a,int b){
return p(a,b);
}
int main(){
int a =10;
int b =20;
int cmd;
int (*p)(int a,int b);
printf("请输入1(取大值),2(取小值),或者3(求和)\n");
scanf("%d",&cmd);
switch(cmd){
case 1:
p=getMax;
break;
case 2:
p=getMin;
break;
case 3:
p=getSum;
break;
default:
printf("您输入的有误,请输入1(取大值),2(取小值),或者3(求和)\n");
exit(1);//表示非正常运行导致退出程序,exit(0)表示正常运行程序并退出程序
// return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
}
printf("%d",dataHandler(p,a,b));
return 0;
}
函数指针数组
#include <stdio.h>
//函数指针数组
//使用函数封装三个函数,同时调用三个函数,对两个数进行取大值、取小值、求和操作
int Max(int a,int b){
return a>b?a:b;
}
int Min(int a,int b){
return a<b?a:b;
}
int Sum(int a,int b){
return a+b;
}
int main(){
int a=10;
int b=20;
int (*p[3])(int a,int b) ={Max,Min,Sum};//注意不要定义错了
for(int i=0;i<3;i++){
printf("%d ",p[i](a,b));
}
return 0;
}
8、指针函数
一个函数可以返回一个整型值、字符值等,也可以返回指针型的数据,即地址。其概念与以前类似,只是返回的值的类型是指针类型而已
例如“int *a(int x,int y);”,a是函数名,调用它以后可以得到一个int*型(指向整型数据)的指针,即整型数据的地址。x和y是函数a的形参,为整型。
请注意在*a两侧是没有括号的,在a两侧的分别是*运算符和()运算符。而()的优先级高于*,因此a先于()结合这显然是函数式,这个函数前面有一个*,表示此函数是指针型函数(函数值是指针)。最前面的int表示返回的指针指向整型变量
指针函数的使用
例:有a个学生,每个学生有b门课程成绩,要求用户输入学生序号以后就能输出该学生的全部成绩,用指针函数来实现。
#include <stdio.h>
int* getPosPerson(int (*p)[4],int i){
int *p1;
p1=p+i;
return p1;
}
int main(){
int scores[4][4]={{77,59,88,89},{66,69,79,90},{90,89,97,66},{96,88,99,77}};
int (*p)[4];
p=scores;
int i;
int *p1;
int size=sizeof(p[0])/sizeof(p[0][0]);
printf("一共有%d名学生,你想知道第几个学生的各科成绩",size);
scanf("%d",&i);
p1=getPosPerson(p,i-1);
for(int i=0;i<size;i++){
printf("%d ",*(p1+i));
}
}
运行结果
这里第4行出了一个警告:警告:从不兼容的指针类型“int (*)[]”分配给“int *” [-Win兼容指针类型]
提示类型不兼容这里进行一下类型的强制转换就可以去除警告 p1=(int*)p+i;
推荐一篇文章:
9、二级(多级)指针
二级指针的写法是int **p;其实二级(多级)指针和一级指针一样,其中区别是二级指针保存的是指针变量的地址。
温馨提示:二级指针不能直接指向二维数组
推荐一篇文章:C语言重点——指针篇(一篇让你完全搞懂指针) - 知乎 (zhihu.com)
总结
各种指针的定义