[C]指针的进阶

本文探讨指针的进阶,对C语言的指针有更深入的了解和学习

目录

前言

一、指针

1.字符指针   

2.指针数组 

3.数组指针 

1.数组指针定义

2.数组名和&数组名

3.数组指针的数组 

4.函数指针

1.函数指针定义 

2. 函数指针数组

3.两个有趣的代码

5.回调函数

二、指针参数、数组参数

1.一级指针和一维数组的传参

 2.二级指针和二维数组的传参

三、指针和数组笔试题解析 


前言

作者:华丞臧.

各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果发现错误的地方,欢迎在评论区指出。 

9f025468dd8a49049e49c98fe32fc069.jpeg


一、指针

指针的概念 

(1)指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

(2)指针的大小是固定的4/8个字节(32位平台/64位平台)。

(3)指针是有类型,指针的类型决定了指针加上或减去一个整数所走的步长,指针解引用操作的时候的权限。 

1.字符指针   

首先来看下面一段代码:

#include <stdio.h>

int main()
{
	char* pc1 = "abcdef";
	char* pc2 = "abcdef";
	char str1[] = "abcdef";
	char str2[] = "abcdef";
	printf("%p\n", pc1);
	printf("%p\n", pc2);
	printf("%p\n", str1);
	printf("%p\n", str2);
	return 0;
}

调试运行发现, 字符指针pc1、pc2的地址相同,而数组str1和str2的首地址各不相同。

 在上面的代码中,字符指针pc1、pc2存放的是常量字符串"abcdef"的首字符地址;常量字符串会被存储到单独的一个内存区域,当指几个字符指针指向同一个字符串的时候,字符指针实际上会指向同一块内存,所以pc1和pc2存放的地址相同。

但是是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块,所以数组str1和str2的首地址各不相同。

2.指针数组 

类型 数组名[数组元素个数];
例:
int* p[10];    //整型指针数组  
char* pc[10];  //字符指针数组

 指针数组就是存放指针的数组。

3.数组指针 

 我们知道指针是用来存放地址的,整型指针存放整型类型的地址,字符指针存放字符类型的地址。那么数组指针呢?数组指针显然是一个指针,一个指向数组的指针。

1.数组指针定义

我们知道定义一个整型指针数组,也知道*的优先级低于[],所以在定义指针数组时p先与[]结合表示p是一个数组数组元素类型为int*。

int*  p[10];

数组指针是首先得是一个指针,所以*要先与p结合表示p是一个指针变量,再与[]结合表示指针p指向一个数组:

int (*p)[10];      //整型数组指针

去掉名字就是类型:int (*)[10]    //这就是p的类型,10不能丢

//p是一个数组指针,指向一个大小为10个整型的数组 

2.数组名和&数组名

数组名和&数组名是有区别的,单独的数组名只有在使用sizeof()的情况下是表示整个数组,而&数组名是取出的是整个数组的地址。

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	printf("%p\n", &arr);
	printf("%p\n", &arr + 1);
	return 0;
}

3.数组指针的数组 

套娃是不可避免的。 

以整型数组指针为例子:
int (*p)[10];      //整型数组指针

去掉名字就是类型:int (*)[10]    //10不能丢

//p是一个数组指针,指向一个大小为10个整型的数组 ;把p替换成数组会如何呢?

int (*parr[10])[10];
//[]的优先级高于*,parr首先与[10]结合,表示parr是个数组,数组元素个数为10,数组类型呢?
//去掉名字就是类型:int (*)[10],这是个数组指针类型。
//所以parr[10]就是有10个元素,数组元素类型为数组指针的数组。

4.函数指针

1.函数指针定义 

 首先来看一段代码:

#include <stdio.h>
void test()
{
	printf("hehe\n");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

运行结果告诉我们,函数是有地址的,函数名和&函数名都是表示函数的地址。当我们要保存函数的地址时,就需要用到函数指针了。 

函数返回类型 (*指针名)(函数参数1,函数参数2.....)
例:
int add(iny x,int y)   //函数计算两个数的和
{
    return x + y;
}


int (*padd)(int ,int ) //padd为指向add函数的指针,参数部分x,y可以不写
//去掉名字就是类型:int (*)(int ,int )

2. 函数指针数组

函数返回类型 (*指针名)(函数参数1,函数参数2.....)
例:
int add(iny x,int y)   //函数计算两个数的和
{
    return x + y;
}

int (*padd)(int ,int ) //padd为指向add函数的指针,参数部分x,y可以不写
//去掉名字就是类型:int (*)(int ,int )   //这就是指针padd的类型


//函数指针数组,首先是数组,然后数组元素是函数指针,那么就可以写成下面这种形式:
int (*parr[5])(int ,int )  
//parr先与[5]结合,表示parr[5]是一个有五个元素的数组
//去掉名字就是类型:int (*)(int ,int )  //这是一个函数指针类型
//所以int (*parr[5])(int ,int )表示parr是一个函数指针数组,
//数组元素个数为5,元素类型是int (*)(int ,int )

3.两个有趣的代码

在《C陷阱和缺陷》书中有如下两个代码:

//代码1 (*(void (*)())0)();

(* (void (*) () )0 ) ();

//代码2 void (*signal(int , void(*)(int)))(int); 

void (* signal (int , void(*)(int) ) ) (int); 

//为了更易于理解
typedef void (*pf_t)(int)  //类型重命名
pf_t signal(int,pf_t);     //等价于void (* signal (int , void(*)(int) ) ) (int);

5.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

 注意:回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方(通过函数指针)调用的,用于对该事件或条件进行响应。 

二、指针参数、数组参数

1.一级指针和一维数组的传参

 当一级指针作为函数参数时,函数能接收什么参数?

假设一个函数:
int fun(int* p)
{
   return 0;
}

p能接收的参数:
1.数组名
int arr[10] = { 0 };
fun(arr);
2.整型变量
int a = 0;
fun(&a);
3.整型指针
int* p1 ;
fun(p1);

一维数组传参:

 int fun(int arr[])

{
   return 0;

}

arr所能接收的参数:

1.整型数组

int arr[]={ 1,2,3,4 };

fun(arr);

2.整型指针

int arr[]={ 1,2,3,4 };

int* p = arr;

fun(p);

 2.二级指针和二维数组的传参

 当二级指针作为函数参数时,函数能接收什么参数?

假设一个函数:

int fun(int** pp)

{
       return 0;

}

pp能接收的参数:

int a = 10;
int* p = &a;

int** pp = p;

int* parr[10];

1.二级指针

fun(pp);

2.一级指针地址

fun(&p);

3.一级指针数组

fun(parr);

 当二维数组作为函数参数时,函数能接收什么参数?

假设一个函数:

int fun(int arr[][2])   //二维数组列不能省略

{
       return 0;

}

arr能接收的参数:

int arr[][2] = {1,2,3,4};

int (*parr)[2];

1.二维数组

fun(arr);

2.数组指针

//&二维数组名取出的是第一行的地址,二维数组第一行可以看成一个一维数组

fun(parr);

三、指针和数组笔试题解析 

//一维数组
int a[] = {1,2,3,4};               
printf("%d\n",sizeof(a));        //16,求整个数组大小
printf("%d\n",sizeof(a+0));      //4/8,a[0]的地址
printf("%d\n",sizeof(*a));       //4,a[0]这个元素
printf("%d\n",sizeof(a+1));      //4/8,a[1]的地址
printf("%d\n",sizeof(a[1]));     //4,a[1]这个元素
printf("%d\n",sizeof(&a));       //4/8,整个数组的地址
printf("%d\n",sizeof(*&a));      //16,*&a等价于a,求整个数组大小
printf("%d\n",sizeof(&a+1));     //4/8,&a+1取出a的地址加一跳过整个数组,但还是一个地址
printf("%d\n",sizeof(&a[0]));    //4/8,取出a[0]的地址
printf("%d\n",sizeof(&a[0]+1));  //4/8,表示a[1]的地址

//字符数组
char arr[] = { 'a','b','c','d','e','f' };//数组中无\0
printf("%d\n", sizeof(arr));        //6,整个数组的大小
printf("%d\n", sizeof(arr + 0));    //4/8,arr[0]的地址
printf("%d\n", sizeof(*arr));       //1,arr[0]这个元素
printf("%d\n", sizeof(arr[1]));     //1,arr[1]这个元素
printf("%d\n", sizeof(&arr));       //4/8,整个数组的地址

printf("%d\n", sizeof(&arr + 1));   
//4/8,跳过整个数组的地址,表示数组地址后面的地址

printf("%d\n", sizeof(&arr[0] + 1));//4/8,arr[1]的地址

printf("%d\n", strlen(arr));        
//随机值,从arr[0]开始数组中没有\0,strlen会计算到\0截止

printf("%d\n", strlen(arr + 0));    
//随机值,从arr[0]开始数组中没有\0,strlen会计算到\0截止

printf("%d\n", strlen(*arr));       
//错误,strlen里面放的地址,而*arr表示arr[0]这个元素

printf("%d\n", strlen(&arr[1]));    
//随机值-1,从arr[1]开始数组中没有\0,strlen会计算到\0截止

printf("%d\n", strlen(&arr));       
//随机值,从arr[1]开始数组中没有\0,strlen会计算到\0截止

printf("%d\n", strlen(&arr + 1));   
//随机值-6,从数组地址后一个地址开始,strlen会计算到\0截止

printf("%d\n", strlen(&arr[0] + 1));
//随机值-1,从arr[1]开始数组中没有\0,strlen会计算到\0截止

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));        //7,sizeof会计算\0,6个字符加一个\0 
printf("%d\n", sizeof(arr + 0));    //4/8,arr[0]的地址
printf("%d\n", sizeof(*arr));       //1,arr[0]这个元素
printf("%d\n", sizeof(arr[1]));     //1,arr[1]这个元素
printf("%d\n", sizeof(&arr));       //4/8,数组地址

printf("%d\n", sizeof(&arr + 1));   
//4/8,&arr是整个数组的地址,+1还是一个地址

printf("%d\n", sizeof(&arr[0] + 1));
//4/8,&arr[0]取出arr[0]的地址,+1表示arr[1]的地址

printf("%d\n", strlen(arr));        //6,strlen计算字符串长度  
printf("%d\n", strlen(arr + 0));    //6,strlen计算字符串长度 
//printf("%d\n", strlen(*arr));     //错误,*arr不是一个地址或指针

printf("%d\n", strlen(&arr[1]));    
//5,从arr[1]开始strlen计算字符串长度
 
printf("%d\n", strlen(&arr));       
//6,strlen计算字符串长度 

printf("%d\n", strlen(&arr + 1));   
//随机值,从arr数组后的地址开始到\0

printf("%d\n", strlen(&arr[0] + 1));
//5,从arr[1]开始strlen计算字符串长度 

 

char* p = "abcdef";
printf("%d\n", sizeof(p));      
//4/8,p表示字符串的首地址

printf("%d\n", sizeof(p + 1));  
//4/8,p+1表示字符串第二个字符的地址

printf("%d\n", sizeof(*p));     
//1,*p表示字符串第一个元素的大小

printf("%d\n", sizeof(p[0]));   
//1,p[0]表示p表示字符串的首地址

printf("%d\n", sizeof(&p));     //4/8,p的地址

printf("%d\n", sizeof(&p + 1)); 
//4/8,&p表示取出p的地址,+1还是一个地址

printf("%d\n", sizeof(&p[0] + 1)); 
//4/8,&p[0] + 1表示字符串第二个元素的地址

printf("%d\n", strlen(p));      
//6,从第一个元素开始求字符串长度

printf("%d\n", strlen(p + 1));  
//5,从第二个元素开始求字符串长度

//printf("%d\n", strlen(*p));   //错误

printf("%d\n", strlen(&p[0]));   
//6,从第一个元素开始求字符串长度

printf("%d\n", strlen(&p));     //随机
printf("%d\n", strlen(&p + 1)); //随机

printf("%d\n", strlen(&p[0] + 1)); 
//5,从第二个元素开始求字符串长度

//二维数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));            //48,求整个二维数组的大小
printf("%d\n", sizeof(a[0][0]));      //4,a[0][0]这个元素的大小
printf("%d\n", sizeof(a[0]));         //16,求第一行的大小
printf("%d\n", sizeof(a[0] + 1));     //16
printf("%d\n", sizeof(*(a[0] + 1)));  //4,*(a[0]+1)表示a[0][1]元素
printf("%d\n", sizeof(a + 1));        //16
printf("%d\n", sizeof(*(a + 1)));     //4,*(a+1)表示a[0][1]元素
printf("%d\n", sizeof(&a[0] + 1));    //4/8,&a[0]+1表示第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1))); //4
printf("%d\n", sizeof(*a));           //4
printf("%d\n", sizeof(a[3]));         
//16,求第四行的大小,相当于求类型的大小,类似sizeof(int)

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

华丞臧.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值