资讯专栏INFORMATION COLUMN

深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)

miracledan / 1170人阅读

摘要:所以是数组指针,而是指针数组。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。当二维数组数组名传参,形参接收时,数组的行可以省略,列不能省略,如果省略了列,我们就无法知道当指针加减跳过几个字节。

⏰ 写在前面

大家对于指针恐怕都不陌生!
没学过C语言那也一定听过指针吧,指针是C最强的优势,学不明白也就成了劣势!大家不必害怕,指针并没有那么恐怖,掌握了指针,让你的C语言更上一层楼!
bug郭和你一起将指针进阶学习一遍,一起加油!

? 本章介绍

可能有伙伴就要问了,咋一来就进阶指针!
不要慌问题不大,bug郭之前就写个一篇博客,介绍指针基础知识!
有兴趣的伙伴可以点击查看C语言指针,楼下大爷都能学会的小细节(和bug郭一起学C系列),建议收藏!
大家都复习完了指针基础吧,那我们就开始指针进阶的学习吧!

指针基础的一些概念:

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的+-
  4. 整数的步长,指针解引用操作的时候的权限。
  5. 指针的运算。

本章重点
6. 字符指针
7. 数组指针
8. 指针数组
9. 数组传参和指针传参
10. 函数指针
11. 函数指针数组
12. 指向函数指针数组的指针
13. 回调函数
14. 指针和数组面试题的解析

字符指针

字符指针顾名思义就是一个指针变量,指针指向的空间存放的是一个字符!

char* 字符指针

//基本用法int main(){  char ch = "c";  char* pc = &ch;     *pc = "w";	return 0;}

这种基本的用法,bug就不介绍了,相信大家都会!

//进阶int main(){		char* pstr = "abcdef"; 	//pstr字符指针存了字符串,第一个字符(a)的地址	printf("%s",pstr);	return 0;}

代码char* pstr = "abcdef";特别容易让我们以为是把字符串abcedf放到字符指针pstr里了,但是本质是把字符串abcdef 首字符的地址放到了pstr中。

我们可以知道通过字符指针pstr我们可以找到字符串abedef
为啥我们不直接创建一个字符串变量,而要用这种方式,有何不同呢?

//测试#include int main(){    char str1[] = "hello world.";    char str2[] = "hello world.";    char *str3 = "hello world.";    char *str4 = "hello world.";    if(str1 ==str2)        printf("str1 and str2 are same/n");    else        printf("str1 and str2 are not same/n");           if(str3 ==str4)        printf("str3 and str4 are same/n");    else        printf("str3 and str4 are not same/n");           return 0;}

输出结果


可以看到,字符数组str1str2不相同,因为str1str2是数组名,而数组名就是第一个数组的地址。str1str2分别开辟了两个数组空间,只不过它们存放的内容一样而已!
str3str4它们都是指向的同一块空间,因为它们指向的是字符串常量hello world.

这里str3str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到多带带的一个内存区域,
当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1str2不同,str3str4不同。

数组指针

数组指针,我们首先要明确的就是,数组指针是指针而不是数组,它是指向数组的指针!

//判断数组指针int* arr1[3];  //指针数组int (*arr2)[3]; //数组指针

我们之前学过C语言操作符,建议收藏,我们知道操作符[]的优先级高于*
所以arr2是数组指针,而arr1是指针数组。

int (arr2*)[3] : arr2是一个指针,指向的对象是整型数组,数组的元素个数为3

数组指针的使用

#includeint main(){	int arr[4] = { 1,2,3,4 };	int(*parr)[4] = &arr;  //数组指针存放数组arr的地址!	return 0;}

大家肯定很少见代码这么写吧,数组指针很少这样使用!
我们已经知道了数组名就是,数组的首元素地址,而取地址数组名是数组的地址 。
&arrarr有啥区别呢?

#includeint main(){	int arr[4] = { 1,2,3,4 };	int(*parr)[4] = &arr;  	printf("arr :%p/n",arr);	printf("&arr:%p/n", &arr);	return 0;}

居然都是第一个元素的地址!
但是我们知道,指针的类型决定了指针加减的步长!

#includeint main(){	int arr[4] = { 1,2,3,4 };	int(*parr)[4] = &arr;     	printf("arr :%p/n",arr);	printf("&arr:%p/n", &arr);	printf("arr+1 :%p/n", arr+1); //整型指针加1,加一个整型类型大小	printf("&arr+1:%p/n", &arr+1);//数组指针加1,加一个数组类型大小	return 0;}

可以看到,数组指针和首元素地址,指针的类型不同

数组名arr:指针类型是整型 指针加减1,步长为整型大小(4bit)
&数组名:指针类型是数组 指针加减1,步长为数组大小(16bit)

数组指针正确使用

#include void print_arr1(int arr[3][5], int row, int col){    int i = 0;    for(i=0; i<row; i++)    {        for(j=0; j<col; j++)        {            printf("%d ", arr[i][j]);        }        printf("/n");    }}void print_arr2(int (*arr)[5], int row, int col){    int i = 0;    for(i=0; i<row; i++)    {        for(j=0; j<col; j++)        {            printf("%d ", arr[i][j]);        }        printf("/n");    }}int main(){    int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};    print_arr1(arr, 3, 5);    //数组名arr,表示首元素的地址    //但是二维数组的首元素是二维数组的第一行    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址    //可以数组指针来接收    print_arr2(arr, 3, 5);    return 0;}

学了数组指针,是不是发现有点懵了!

//捋一捋int *arr1[3]; //指针数组//数组个数是3,元素是int指针类型的数据int (*arr2)[3];//数组指针//指针,指向数组,且数组的类型是int类型,且元素个数为3int* (*arr3)[3]; //数组指针//指针,指向数组,数组元素是int*类型,且元素个数为3int (*arr4[3])[3]; //数组指针指针//指针,指向一个数组指针,数组指针的类型是int(*) [3] 指向数组且为为int类型,元素个数为3......

就捋到吧,再捋下去就更懵了,兄弟们慢慢学,你可以了的!

数组参数、指针参数

在写代码的时候难免要把数组或者指针传给函数,那函数的参数该如何设计呢?

一维数组传参

#include void test(int arr[])//ok?{}  //int arr[] 接收就是以int * arr形式接收,因为*arr等价与 arr[]void test(int arr[10])//ok?{}  //int arr[10] 同上在形参中都是一个整型指针,形参中的数组长度无意义void test(int* arr)//ok?{} //整型指针接收数组名就是首元素地址也就是整型指针void test2(int* arr[20])//ok?{} //int* arr[20]等价于 int* arr[]等价于  int**arr 即二级指针   //而实参就是一个指向整型指针的指针也就是二级指针void test2(int** arr)//ok?{} //二级指针接收int main(){    int arr[10] = { 0 };    int* arr2[20] = { 0 };    test(arr);    test2(arr2);}

二维数组传参

void test(int arr[3][5])//ok?{}  //二维数组传参二维数组接收void test(int arr[][])//ok?{} //error 不知道二维数组中一维数组中元素个数void test(int arr[][5])//ok?{} //可以省略行不能省略列//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。//这样才方便运算。void test(int *arr)//ok?{} //error 二维数组的数组名就是首元素地址,即一维数组的地址,// 也就是数组指针,应该用数组指针接收 void test(int* arr[5])//ok?{}   //error,指针数组,实参是数组指针void test(int (*arr)[5])//ok?{}  //实参为数组指针与形参类型相同void test(int **arr)//ok?{} //error int main(){    int arr[3][5] = {0};    test(arr);}

我们来总结一下!

  • 二维数组的数组名就是首元素地址,而二维数组的元素就是一维数组,所以数组名的类型就是数组指针。
  • 当二维数组数组名传参,形参接收时,数组的行可以省略,列不能省略,如果省略了列,我们就无法知道当指针加减跳过几个字节。

一级指针传参

#include void print(int *p, int sz)  //一级指针传参,一级指针接收{    int i = 0;    for(i=0; i<sz; i++)    {        printf("%d/n", *(p+i));    }}//void print(int p[],int sz) //数组接收,也即一级指针接收,不提倡这样写int main(){    int arr[10] = {1,2,3,4,5,6,7,8,9};    int *p = arr;    int sz = sizeof(arr)/sizeof(arr[0]);    //一级指针p,传给函数    print(p, sz);    return 0;}

思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

//以int型指针为例void test(int* p){}int main(){	int x=0;	int* px=&x;	int arr[10];	test(&x);//整型地址	test(px);//一级指针	test(arr);//一维数组名,即首元素地址,int*	return 0;}

二级指针传参

void test(char** p ){}int main(){	char ch = "c";	char* pc = &ch;	char* *ppc = &pc;	char* arr[3];	test(&pc); //一级指针的地址,即二级指针	test(ppc); //二级指针	test(arr); //数组名,首元素地址,首元素为一级指针,所以为二级指针 	return 0;}

思考:
当函数的参数为二级指针的时候,可以接收什么参数?

其实和上面一样,你们可以思考一下!

函数指针

首先看一段代码:

#include void test(){    printf("hehe/n");}int main(){    printf("%p/n",  test); //函数名 就是函数地址    printf("%p/n", &test); //&函数名 也是函数地址        return 0;}

运行结果
那么如何将test()函数指针保存起来呢?

void test(){ printf("hehe/n");}//下面pfun1和pfun2哪个有能力存放test函数的地址?void (*pfun1)();  //函数指针类型void *pfun2();    //函数,函数的返回值是void*

函数指针类型
指针都是有类型的
整型指针 int*
数组指针 int (*)[]
函数指针 返回值 (*)(参数....)

#includeint Add(int x, int y){	return x + y;}int main(){	int (*pf)(int, int) = Add;	int sum = (*pf)(3, 5);   //对函数指针解引用	printf("sum = %d", sum);	sum = pf(3, 5);          //因为 Add和&Add相同,即Add等价于 pf	printf("sum = %d", sum);	return 0;}

有趣的代码

//代码1(*(void (*)())0)(); //void (*)()为函数指针//(void (*)())0 将0强制类型抓换成函数指针的地址//(*(void (*)())0)() *地址,调用0地址处的这个函数//函数的返回值空,参数为空
//代码2void (*signal(int , void(*)(int)))(int);//void(*)(int) 函数指针,返回值void,参数int //void (*signal(int , void(*)(int)))(int)// signal是函数名 //返回值是void(*)(int)// 参数int 和函数指针 void(*)(int)//这是一个函数的声明

当我们看到代码2很难看懂这个代码!
可以简化吗?

void (*signal(int , void(*)(int)))(int);//既然这个函数的返回值类型是 void(*)(int) //那我们可以写成// void(*)(int) signal(int , void(*)(int));//但是这样会语法错误 error

函数指针类型重命名
简化

typedef void(*ptr_t) (int);   //正确的类型重命名 ptr_t signal(int, ptr_t);  //简化


上面的代码出自《C陷阱和缺陷》
有兴趣的伙伴可以尝试阅读!

函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组
比如:

 int* arr[10]; //整型指针数组

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10]])(); //int *parr2[10]();
                 
               
              

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/122185.html

相关文章

  • C语言------(8道)试题全面解析

    摘要:因为指针指向的是整个数组,所以它的类型是数组指针,所以我们在它的前面进行强制类型转换,把它转换为类型,然后再存放到指针变量内部。 前言 通过8道指针笔试题的解析,可以充分的复习到指针的相关知识,并且题目中会结合许多之前的相关知识,希望通过本篇文章,对大家所学的知识进行一个复习。 提示:以下...

    vspiders 评论0 收藏0
  • 进阶—数组笔试题解析[建议收藏](二)

    摘要:指针笔试题笔试题上述代码运行结果是什么大家可以先思考一下答案如下代码运行结果如下笔试题假设的值为。欢迎大家点赞支持和指针 目录 写在前面指针笔试题笔试题1笔试题2...

    LuDongWei 评论0 收藏0
  • 4道经典试题讲解 ~

    摘要:结尾有关这四道经典的指针笔试题讲解就到此结束了,如果觉得文章对自己有所帮助,欢迎大家多多点赞收藏 ?前言 : 今天博主来讲解4道经典的指针笔试题,很多朋友没有深刻理...

    tianren124 评论0 收藏0
  • C语言进阶:进阶续

    摘要:故使用无具体类型,又称通用类型,即可以接收任意类型的指针,但是无法进行指针运算解引用,整数等。求指针所占字节而不是解引用访问权限大小。数组就是整个数组的大小,数组元素则是数组元素的大小,指针大小都为。 ...

    ingood 评论0 收藏0
  • 不要认为PHP就不需要C语言

    摘要:之所以这样说不要认为学就不需要学语言,是因为一味的只学而没有语言等这些基础语言的支撑,是很难深入理解的很多东西的。 之所以这样说不要认为学PHP就不需要学C语言,是因为一味的只学PHP而没有C语言等这些基础语言的支撑,是很难深入理解PHP的很多东西的。 这样的例子其实很多,这里我就举这个例子吧:PHP的数组和C语言的数组的区别和联系。 学过C语言的朋友当然知道C语言里有数组; PHP里...

    KoreyLee 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<