C语言指针

目录

1、认识指针

2、为什么需要用指针

3、通过指针引用数组

4、指针和二维数组

5、数组指针

6、指针数组

7、函数指针

8、指针函数

9、二级(多级)指针

总结

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)

总结

各种指针的定义

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值