指针详解三

                                                                hi~

 导语:

上文我们已经谈了指针的定义、创建、常见的错误、二级指针等相关问题。那么这篇文章就让我们深入探讨一下指针的应用。

正文:

字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ,一般使用如下方式定义。

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}

就按照我们介绍的,type+*,的方式来定义。但是除了这一种还有一种常用的方式:

int main()
{
  char*str="hello world";
  printf("%s\n",str);
  return 0;
}

请输出打印结果

很多人有疑问,我这里是字符指针,存储的是字符的地址,为什么能把字符串放进去?

其实不管是字符还是字符串,指针始终都只存储首元素的地址,所以首元素不都是字符吗那么我用字符指针来存储就是对的。

这里大家要注意,很多人以为是把字符串 hello world 放到字符指针 str 里了,但是/本质是把字符串 hello world. 首字符的地址放到了str中。

搞懂了这些我们来看看一道题目

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
    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;
}

请输出打印结果:

 这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当 几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。内存相同,地址就相同,那么指针就一样。

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

指针数组

先问一个问题:指针数组是指针还是数组?

答:是数组。指针只是它的修饰词,本质上还是数组。

数组有整型数组、字符数组,那指针数组有啥呢?

首先arr3是一个数组 ,这个数组有5个元素,每个元素是整型指针。

那么怎么定义的?其实就是type+*+【】

知道了这些,那就让我们分享几个指针数组吧:

 第一个是一个数组,数组有10个元素,每个元素是整型指针型的;

第二个是一个数组,数组有4个元素,每个元素是字符指针类型的;

第三个是一个数组,数组有5 个元素,每个元素是二级字符指针类型的。

数组指针

请问数组指针是数组还是指针?

有了上道题目的教训你现在应该知道了,数组指针是指针!

数组指针与指针数组的区别:

p1是指针数组,p2是数组指针 

解释:p先和*结合,说明这是一个指针,那它指向的是一个数组,这个数组有10个元素,每个元素是int 型。欧克,首先它是一个指针,然后它又指向一个数组,那么就是数组指针。

在这里要注意,【】的结合优先级是要比*高的,所以p1先和【】结合再和*结合,这也导致了为什么它是指针数组。

数组名与&数组名

为了方便后续的讲解,这里插入关于数组名的知识点。

对于下面的数组 

arr和&arr分别代表着什么?

我们知道数组名代表的是数组首元素的地址,那么&数组名有代表的是什么呢?为了打消疑问,我们通过编译器的打印结果来探究。

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr);
	return 0;
}
0000006B91FCFCB8
0000006B91FCFCB8

欸,结果是一样的,但是这就能说明二者代表的含义是一样的吗?我们再来看看下一个代码

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr);
	printf("%p\n", arr+1);
	printf("%p\n", &arr+1);
	return 0;
}
0000000AFD2FF648
0000000AFD2FF648
0000000AFD2FF64C
0000000AFD2FF670

欸,可以发现arr和&arr分别+1的结果是不一样的。这是为什么呢?

首先arr+1与arr相差4个字节,可以由表格数据得出。由此我们知道既然相差4个字节,那么代表他们之间相差1个整型元素,也就是1个数组元素,所以arr代表的是数组首元素的地址。

&arr与&arr+1相差的是(70-4c)16进制转换,得到是40个字节,这个值刚好是我们定义的数组的大小。那么我们知道&arr代表的是一整个数组。

所以我们发现,arr代表数组的首元素,&arr代表一整个数组,二者的含义不同,不过二者的地址是一样的。

好了引进了这些知识点之后,让我们深入的了解一下数组指针吧

数组指针的应用

数组指针是一个指向数组的指针,那么这个指针里面存放的是数组的地址

#include <stdio.h>
int main()
{
  int arr[10] = {1,2,3,4,5,6,7,8,9,0};
  int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
  //但是我们一般很少这样写代码
  return 0;
}

上述展示的用法虽然没有错,但是我们一般不会这样使用数组指针。参考下面代码:

#include <stdio.h>
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;
}

数组指针常用于函数的传参中,这里arr是首元素的地址,但是数组是二维数组,二维数组的首元素是二维数组的第一行,所以arr其实是第一行的地址,也就是数组的地址,用数组指针来接收。

好了,让我们做几道题目来检验一下目前的学习成果吧:

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

第一个是一个整型数组;

第二个是一个指针数组,数组有10个元素,每个元素是int*

第三个是数组指针,指向的是一个int型数组

第四个,首先看到(),【10】先和parr3结合,所以这是一个数组,接着数组的类型是int(*)[5]

这是一个指向数组的指针,所以总的来说这是一个指针数组,数组的每个元素是指向整型数组的指针。

数组参数、指针参数

一维数组传参、二维数组传参

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

void test(int arr[]);
void test(int arr[10]);
void test(int* arr);
void test2(int arr[][]);
void test2(int arr[][5]);
void test2(int* arr[5]);
void test2(int**arr);
void test2(int(*arr)[5]);

int main()
{
	int arr1[10];
	int arr2[3][5];
	test(arr1);
	test2(arr2);
	return 0;
}

现在请以此判断上述代码是否正确:

第一个:传参过去的数组名,代表的是一整个数组,定义一个数组来接收,没有问题。

第二个:要知道形参根本不管你数组的大小,就像是第一个,数组的大小直接省略了。所以这个也是可以的。

这里插入一个问题,要是形参的数组的大小与传过来的数组大小不匹配怎么办?会报错吗?

就像是这个例子,形参的数组大小是5,与传过来的数组大小不匹配,但是调试时,并未报错,运行时程序也通过了。只不过我们只知道这样写不会报错,但是能否正常实现功能就不知道了,还是建议大家不要这样写。 

言归正传,第三个:数组名是数组首元素的地址,要用指针来接收,int*符合条件,可以。

第四个:此时传参的是二维数组,二维数组用二维数组来接收,没有问题。但是,二维数组只能省略行的大小,不能省略列的大小,所以不可以。

第五个:由第四个可知,此时未省略列的大小,可以。

第六个:注意,这里是一个指针数组,我们传参时只见过指针来接收,或者数组来接收,从未见过用指针数组来接收,不可以。

第七个:二维数组名是首元素的地址。而二维数组本质上是一维数组的组合,所以二维数组的首元素就是一维数组,那么一维数组的地址是什么呢?并不是二级指针吧,所以不可以。

第八个注意,这是个数组指针。那么吃过上面一个的亏,有的同学就说,这个也不可以!但是,真的不可以吗?我们先想想二维数组是什么?二维数组本质上来说就是一维数组的组合,而一维数组要用地址来接收,那么一维数组的组合就是几个指针的组合,因此要用数组指针来接收。

一级指针传参、二级指针传参

#include <stdio.h>
void print(int *p, int sz)
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
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;
}

看到上面这个例子,定义了一个整型指针,传参整型指针,那我们就用整型指针来接收。

这很简单对吧,那么现在我有一个问题。倘若函数设计时参数部分为一级指针时,我们反过来能给函数传递什么参数呢?

void test(int*arr)

这个能接收什么样的参数?

首先传一个整型指针是可以的,将一个整型的地址传过去也是可以的,还可以将一个整型数组传过去。由此可知,我们在设计一个函数时,一定要考虑到这个函数可以接受什么样的类型。

#include <stdio.h>
void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);
 test(&p);
 return 0;
}

我们看到上面一个例子,P是一个整型指针,那么&P就是一个二级指针,所以pp是一个二级指针,要用二级指针来接收。

同样的,反过来思考当函数参数设计成二级指针时,我们能传什么样的参数?

void test(char **p)
{
 
}
int main()
{
 char c = 'b';
 char*pc = &c;
 char**ppc = &pc;
 char* arr[10];
 test(&pc);
 test(ppc);
 test(arr);//Ok?
 return 0;
}

这里都是可以的,我只以第三个为例子。arr是一个指针数组,数组的每一个元素是char*,那么数组名是首元素地址,就是char*的地址,那么就要用二级指针来接收。

函数指针

函数指针就是指向函数的指针。

函数也是又地址的,那么&函数名也是有意义的,它代表的是函数的地址。之前我们学到数组指针的时候,我们知道&arr代表的是数组的地址,arr代表的是数组首元素的地址,他们打印的地址都是一样的,那么类比过来,函数指针是否也有这样的概念呢?

#include<stdio.h>
void test(int arr[5])
{}
int main()
{
	int arr1[10];
	int arr2[3][5];
	printf("%p\n",test);
	printf("%p",&test);
	//test2(arr2);
	return 0;
}

请输出打印结果:

 可见这两个概念在函数指针里面也存在,他俩都代表函数的地址,只不过函数名就是函数的地址没有函数首元素的地址之说。

现在我们看看如何定义一个函数指针。

类比于数组指针,我们可以尝试一下

int (*p)(int,int )=&test;

首先要确保他是一个指针,这个指针指向的是一个函数,这个函数的参数与返回值的类型要确定。接着后面的括号是函数的参数类型,前面的int是函数的返回值的类型 。

看到这里你们学废了吗?学废了就让我们看看下面的代码练习一下吧:

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

你知道上面的两个代码的含义吗?

这样的代码比较奇葩,我们来逐步分析。

第一个:先看到最里面的括号,(*)说明是指针,去掉(*),发现这是指针的类型,这是一个函数指针,函数指针后面跟着一个0说明要将0强制类型转换为函数指针类型,强制类型转换后用*来解引用,变成了函数指针,后面跟着一个(),说明此时是在调用函数指针。(补:这个题目比较难,看不懂的话不做强求)

第二个:这个也是一个死变态,我们一步步来。先看到最里面的括号void(*)(int)是一个函数指针类型,signal(int,void(*)(int))是一个函数名与函数的参数,它的返回类型是void(*)(int)。所以这个代码就是一个signal的函数声明。这个代码看起来太过复杂,可不可以简化一下呢?

int main()
{
  typedef void (*ptr)(int);
  ptr signal(int,ptr);
}

重定义一下void(*)(int)为ptr,这样就可以大大简化代码了。

函数指针数组

先问一个问题,函数指针数组是函数还是数组还是指针?

根据前面我们学习的内容,可以推断出函数指针数组是数组,这个数组存放的是函数的地址,

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

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

以上的写法哪一种是正确的呢?

答案是第一个。parr1先和【10】结合说明他是一个数组,去掉parr1【10】后,int(*)()是一个函数指针类型,那么第一种写法就是函数指针数组。

前文讲了许多关于函数指针的内容,但是有人就会问了,我们学习函数指针有什么用呢?那么在下一篇文章我们就来介绍函数指针的作用,以及实现。

总结

经过这一篇文章的学习,我们掌握了各种指针类型的概念定义与使用,对不同指针类型的传参也更加熟练。在区别不同指针类型或者复杂的类型时,我们可以遵循以下步骤:

1将复杂结构分成单独的模块

2确定最底层的结构类型

3依次向外层辨认

希望大家能学有所获。

都看到这了,不给个赞吗?

这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!

如果有什么疑问或不同的见解,欢迎评论区留言欧👀。

好了我是happysky,编程之路你我一起探索,我们下期JAN

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值