指针进阶(一)

指针进阶(一)

前言

指针的概念在之前的文章里已经讲解过了,其本质就是变量的地址。

  • 内存中的每个字节都会有自己的地址,每个字节的地址都能够用指针变量进行接收。
  • 指针变量的大小是4/8个字节,32位平台上占用4字节,64位平台上占用8个字节。
  • 指针变量是有类型的,指针变量的类型决定了指针±整数的步长,指针类型决定的指针变量的权限。
  • 相同类型的指针变量之间的减法运算的结果是:这两个指针指向的两个元素间的元素个数。(与高地址减去低地址还是低地址减去高地址无关)

1.字符指针

首先介绍字符指针,这是一种常见的使用方式:

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

还有一种使用方式如下:

int main()
{
     const char* pstr = "hello bit.";
     printf("%p\n", pstr);
     printf("%p\n",pstr+1);
    
     return 0;
}

这里第3行代码是将整个字符串放到了pstr变量中了吗?并不是的。通过打印其地址,可观察到,pstr变量只是存放的字符串首字符的地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l37WfQCp-1689663590120)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230716132145891.png)]

来个小测试吧:

//这段代码的运行结果是什么?
#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;
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bQzLxWif-1689663590122)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230716132344491.png)]

原因分析:这里由于str1和str2都是数组,这两个数组无论其内容是否相同,只要成功被创建,都会在栈区开辟一块空间,由于这两个数组的开辟空间是独立的,所以其地址也是不同的,str1和str2分别存储这两个字符串的首地址,所以str1和str2的值不同。但是str3和str4是两个指针变量,他们都存储同一个字符串的首地址,内存中也没有必要为这个相同的常量字符串开辟两块空间,即C/C++会把常量字符串存储到单独的一个内存区域,所以str3和str4的值是一样的。

2.指针数组

所谓指针数组,本质上就是一个存放指针的数组。

char* arr[10];//存放一级字符指针的数组
int* arr[10];//存放一级整形指针的数组
char** arr[10];//存放二级字符指针的数组

3. 数组指针

数组指针本质上是指针。

int *pint;是指向int数据类型的指针

float *pf;是指向float类型的指针

那么数组指针就是指向数组的指针,存放数组的起始地址。

下面两个哪个是数组指针呢?

int (*parr)[10];

int *parr[10];

其中 int(*parr)[10];才是数组指针的正确写法。而 int *parr[10];这种写法,parr变量会先和[]结合,这样子parr变量就被翻译成了数组名了,明显不符合要求。

数组指针的写法说明:parr先和*结合,说明parr是个指针变量,接着去掉*parr这部分,剩下的部分就是该指针指向的元素的类型,指向有10个元素的整形数组。

注意:[]的结合性是高于*所以需要一个小括号保证变量名先与*结合

4 &数组名和数组名的异同

对于所有的数组而言数组名代表的是数组首元素的地址

int arr[10];

对于这个整型数组来讲,arr和&arr有什么区别呢?

先看一段代码:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ULTHgke1-1689663590123)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230716142945034.png)]

可见运行结果是相同的,但是他们真的一样吗?
再来看一段代码:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uiIOw1r4-1689663590123)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230716145733733.png)]

虽然这两个的值相同,但是他们的类型是不一样的,对其进行加减操作便可知晓。

arr是数组名,代表的是数组首元素的地址,也就是int数据类型的地址,即arr的类型是int*型,要用int*接收, int *p = arr;

&arr取出的是整个数组的地址,也就是int [10]的地址,即&arr的类型是 int(*)[10],要用int(*)[10]接收, int (*p)[10] = &arr;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ih4wSanG-1689663590123)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230716150519244.png)]

通过警告信息来看,=右边的类型是int(*)[10],可再次佐证我们的说法.

4.1 数组指针的用法

数组指针可以用于接收数组的地址,如下:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	for (int i = 0; i < 10; i++)
	{
		printf("%d ",(*p)[i]);
	}
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sszjB9yg-1689663590124)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230716151107850.png)]

但是一般不这样使用。通常利用数组指针接收二维数组传参:

void print(int(*p)[3],int col,int row)
{
	int i = 0;
	for (i = 0; i < col; i++)
	{
		int j = 0;
		for (j = 0; j < row; j++)
		{
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[2][3] = {1,2,3,4,5,6};
	print(arr,2,3);
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RVLvtXKV-1689663590124)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230716151753813.png)]

我们通常可以将二维数组看做一维数组,这个一维数组的每个元素也是一个一维数组,那么就可以很好的解释上面这段代码了,主函数向print函数传参时,传递的是二维数组的数组名,这个数组名代表的二维数组首元素的地址,也就是一维数组的地址,那么在print函数的形参部分,可以选择使用二维数组接收,即 int arr[2][3];进行接收。也可以选择使用数组指针进行接收,即 int (*p)[3];进行接收。

这里[]内为什么写的是3而不是2呢?因为这里接收的一维数组的地址,该二维数组的每个元素都是含有三个元素的一维数组。

看看下面这段代码的含义吧

int arr[5];//整型数组
int *parr1[10];//整型指针数组
int (*parr2)[10];//整型数组指针
int (*parr3[10])[5];//整型数组指针数组

int (*parr2)[10];:先看变量名,先与*结合,说明parr2是个指针,接着将*parr2去掉,剩下的就是该指针指向的元素类型,即 int [10];即该指针指向一个含有10个元素的整型数组。

int (*parr3[10])[5];:先看parr3这个变量,它先于[]结合,说明parr3是个数组,将parr3[10]去掉,剩下部分就是数组元素类型,即 int(*)[5],所以数组的元素类型就是 int(*)[5];(整型指针数组)。

5.数组参数与指针参数

在写代码时,难免有时会将数组名传递给参数,那么我们该如何设计函数中用于接收数组名的形参呢?有两个方法:

  1. 将形参设计成数组的格式
  2. 将形参设计成指针的格式

我们来判断如下的函数部分的形参设计是否恰当呢?

一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
     int arr[10] = {0};
     int *arr2[20] = {0};
     test(arr);
     test2(arr2);
     return 0;
}	
  • 第2行:使用恰当。函数调用时传递的是一维数组的数组名,这里形参接收时,也可以写做一维数组的格式,但其本质上不是一维数组。
  • 第4行:使用恰当。函数调用时传递的是一维数组的数组名,这里形参接收时,也可以写做一维数组的格式,但其本质上不是一维数组,所以形参中,一维数组中的元素个数并不重要,这样写也是完全恰当的。
  • 第6行:使用恰当。函数调用时传递的是一维数组的数组名,数组名代表的是数组首元素的地址,也就是int数据类型的地址,形参可以用int*类型的指针进行接收。
  • 第8行:使用恰当。传参时传递的是一维数组的数组名,这里形参接收时,也可以写做一维数组的格式,但要知道其本质上不是一维数组。
  • 第10行:使用恰当。函数调用时传递的是一维数组的数组名,数组名代表的是数组首元素的地址,也就是int*数据类型的地址,形参可以用int**类型的指针进行接收。

二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}
  • 第1行:使用恰当。函数调用时传递的是二维数组的数组名,这里形参接收时,也可以写做二维数组的格式,但其本质上不是二维数组。并且该二维数组的列数不能省略行数可以省略。就等同于二维数组中每个一维数组的长度必须明确。
  • 第3行:使用不恰当。函数调用时传递的是二维数组的数组名,这里形参接收时,也可以写做二维数组的形式,并且列数是不能省略的
  • 第5行:使用恰当。数调用时传递的是二维数组的数组名,这里形参接收时,也可以写做二维数组的形式,并且这里没有省略列数
  • 第10行:使用不恰当。数组名代表的是数组首元素的地址,也就是数组中第一个一维数组的地址,形参部分应该使用数组指针进行接收,而这里是整型指针
  • 第12行:使用不恰当。数组名代表的是数组首元素的地址,也就是数组中第一个一维数组的地址,形参部分应该使用数组指针进行接收,而这里是指针数组
  • 第14行:使用恰当。数组名代表的是数组首元素的地址,也就是数组中第一个一维数组的地址,形参部分采用的是数组指针进行接收
  • 第16行:使用不恰当。数组名代表的是数组首元素的地址,也就是数组中第一个一维数组的地址,形参部分应该使用数组指针进行接收,而这里是二级整型指针

一级指针传参:

#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);test函数可以接收:整型变量的地址,一级整型指针,一维整型数组的数组名,整型指针数组的元素。(当然这里肯定没有说全)

二级指针传参:

#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;
 }

当函数的参数为二级指针时,可以接收哪些参数呢?

void test(int **p);test函数可以接收:一级指针的地址,二级指针,一维指针数组的数组名……

6.函数指针

先看一段代码:

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

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eR1iNsSe-1689663590124)(C:\Users\30539\AppData\Roaming\Typora\typora-user-images\image-20230716202412108.png)]

这里输出的是test函数的地址,那么函数的地址应该使用怎样的指针变量进行保存呢?

void test()
{
	 printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

pfun1先与*结合,说明pfun1是个指针,指向的函数无参,返回值为void。说明pfun1能存放test函数的地址。

阅读下面两段代码:

 

代码1是将0转化成一个(void (*)())这个函数指针,再接着对这个指针进行解引用操作,并调用该函数指针指向的函数。

代码2中,由于小括号的优先级比*高,signal先和()结合,signal代表函数名,这个函数有两个参数,分别是int类型的参数和void(*)int(函数指针)类型的参数,这第二个参数是函数指针类型,指向的函数的参数是int,返回值是void。接着将 signal(int , void(*)(int))这部分去掉,剩下的部分,即 void(*)int;signal这个函数的返回值是一个函数指针。所以代码2其实就是signal函数的声明。

关于typedef的在指针中的使用

下面这两段代码,哪个才能达到我们的效果呢?

typedef int(*parr)[10];
typedef int(*)[10] parr;

答案是前者第一种方式才是可行的。假若要被重新命名的类型中含有*,那么重新命名之后的类型名必须要在*的右边

函数指针也是类似的:

typedef int(*)(int,int) pf;//错误
typedef int(*pf)(int,int);//正确

了解了typedef在指针中的使用了之后,就可以对上面的两段代码进行改进了。

//代码1
(*(void (*)())0)();
//改进
typedef void(*p)();
(*(p)0)();
//代码2
void (*signal(int , void(*)(int)))(int);//类型重定义。
//改进
typedef void(*pv)(int);
pv signal(int,pv);

本节内容暂时到这里后面还会继续更新指针进阶的内容!!如有不足敬请指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

这里是彪彪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值