指针详解(3)

各位少年,大家好,我是博主那一脸阳光,今天介绍 二级指针 指针数组,还有个指针数组模拟二维数组。
前言:在浩瀚的C语言编程宇宙中,指针犹如一把打开内存世界大门的独特钥匙,它不仅是理解程序运行机制的关键要素,也是提升代码执行效率的重要工具。如同寻宝图上的经纬坐标,指针精准地指向了内存中的特定位置,使得我们能够直接操作数据的核心。

想象一下,计算机内存就像一座巨大的储物仓库,每个存储单元都承载着独一无二的信息宝藏。而指针,则如同仓库管理员手中的定位器,通过它可以迅速找到并访问任何一个角落的数据。当我们改变指针所指向的位置时,就好比调整了探索目标,实现对不同信息资源的快速定位和灵活调动。

因此,在深入学习C语言的过程中,掌握指针这一概念及其使用方法,就如同掌握了驾驭数据流动的秘密通道。它不仅有助于我们更深入地洞察程序运行的本质,还能使代码更为简洁高效,更具表现力。接下来,让我们一同踏上这段揭示指针奥秘的旅程,揭开其背后深藏的编程智慧与艺术。

二级指针的定义

int a10;
int*p=&a;
&p;
//p是指针变量,是一级指针
int **pp=&p;//int *是在说明,pp对象指向的对象的int*类型
//*说明pp是指针变量

这里的pp是二级指针,指针类型进行+1 -1的操作,执行解引用的权限。
注意这里的pp是另外开辟了一块空间。

int a = 10;
	int* p = &a;
	int**pp = &p;
	int*** ppp = &pp;
	return 0;
}

指针数组

我们类比一下
指针数组是指针还是数组呢?(数组中每个元素都是整形类型)
整形数组-存放整形数据的数组(数组中每个元素都是字符类型)
指针数组-存放指针的数组(数组中每个元素都是指针类型)

int arr[10];//整形数组 
char ch[5];//字符数组
double data[4];//多浮点型数组

希望有一个数组,数组有四个元素,每个元素是整形指针

int arr[4];

每个元素是整形指针,所以指针数组。

指针模拟二维数组

模拟二维数组的效果,但不是二维数组!
二维数组其实每一行都是一维数组。

#include
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int *arr[3] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

上面指针的方式存储了三个数组的地址,然后进行遍历,最后进行每一位,最后执行了二维数组的打印。

字符串指针类型

char ch='w';
char*pc=&ch;   //pc是字符指针
char *p"abcdef"://叫做常量字符串
printf("%c\n","abcdef"[3]);

字符指针的类型是可以赋值的,但非传统赋值,
不是把字符串abcdef\0存放在p中,
而是把第一个字符的地址存放在p中
意思就是说p存储a的地址,你可以把abcdef这个看做出一个数组。

1你可以把字符串想象为一个字符数组,但是这个数组是不能修改的
2当常量字符串出现在表达式中的时候,它的值是第一个字符的地址。

#include
int main()
{
	char* p = "abcdef";
	printf("%c\n", p[3]);
	p[3] = 'q';
	return 0;
}

此时注意p没办法间接修改它,因为p的字符串是常量,所以建议在char前面加const以避免误导。

剑指offer面试题

今天来介绍《剑指offer》一书中的题目

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

在这里插入图片描述
上面代码str1和str2不同这是为什么呢?
这就好比两个一模一样的背包,其中有一个可能不是你的,所以地址是不相同的。
str3和str4常量字符串,不可能修改了,就好比你和你女朋友的包,你都得背一样。
指针详解(3)_第1张图片
因为str3和srt4都是常量,无法更改,所以计算机偷懒了,只开辟了一块空间。

数组指针变量

指针数组:是数组,是存放指针的数组!
哪数组指针是什么呢?
我们类比一下:
整形指针:指向整形的指针。
字符指针:指向字符的指针。
浮点型指针:指向浮点型的指针。

数组指针 指向数组的指针!

数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针的变量。

整形指针变量存放的就是整形的地址
字符指针变量存放的就是字符的地址
数组指针变量存放的应该是数组的地址

 int*pa)[10];

数组指针变量存放数组的地址,里面每个类型都是int类型。

 int arr[10]={1,2,3,4,5,6,7,8,9,10};
 int(*parr)[10]=&arr;

指针详解(3)_第2张图片

解释:先和结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以
p是⼀个指针,指向⼀个数组,叫 数组指针。
这⾥要注意:[]的优先级要⾼于
号的,所以必须加上()来保证p先和*结合。

我们调试也能看到 &arr 和 p 的类型是完全⼀致的。
数组指针类型解析:

int (*p) [10] = &arr;
 | | |
 | | |
 | | p指向数组的元素个数
 | p是数组指针变量名
 p指向的数组的元素类型
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int(*p)[10]=&arr;
printf("%p\n",arr);
printf("%p\n,&arr+1);

printf("%p\n",p);
printf("%p\n,p+1);

还记得我们之前说过这个代码,如果打印整个数组的地址+1跳过整个数组,
如果取地址数组名打印跳过整个数组。那好我们看打印结果
指针详解(3)_第3张图片
接下来介绍数组指针是怎么打印的呢?

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

在这里插入图片描述
因为数组指针,是数组的地址+1跳过了整个数组,但是解决办法还是有的。
p==&arr
因为数组指针,数组本来就是地址所以p等于取地址arr。

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

这样就可以是数组指针每个元素了。

二维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了。
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:

void test(int a[3][5], int r, int c)
{
 int i = 0;
 int j = 0;
  for(i=0; i

这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
如下图:
指针详解(3)_第4张图片
所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀
维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类
型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀
⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

#include 
void test(int (*p)[5], int r, int c)
{
 int i = 0;
int j = 0;
 for(i=0; i<r; i++)
 {
 for(j=0; j<c; j++)
 {
 printf("%d ", *(*(p+i)+j));
 }
 printf("\n");
 }
}
int main()
{
 int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
 test(arr, 3, 5);
 return 0;
}

总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

函数指针变量

函数指针顾名思义函数的指针的,函数的地址,哪问题来了,它有什么用呢?

数组名- -数组首元素的地址
&数组名–整个数组的地址。
函数名:函数的地址
&函数名:函数的地址

函数指针的写的方法和数组指针的创建方式非常的类似的

data type(*Pointer name)(Function Parameter type)

数据类型 指针名字 和指针类型组成的函数指针,
这里大家可能看不懂,下面我来分享给大家例子。

int(*pf)(int,int)=&Add;

pf就是函数指针变量。下面例子是函数指针基本的格式。这里必须加(),否则int先和*结合就是函数传参了。

int *pf=(int,int);.//这里不加()就变成函数传参 如果再加等于Add的话直接报错

函数的使用的例子

#include
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf)(int,int) = &Add;//pf就是函数指针变量
	//原来我们是不是这样写
	int ret=Add(35);
	printf("%d\n",ret);//8
	//新的写法
	int ret2=(*pf)(4,9);//pf解引用,然后传入两个值
	printf("%d\n",ret2);//13
	return 0;
}

接下来再引导出一个概念,函数名和&数组名是一样的,函数的地址都是一样的。

int*pf2)(int,int)=Add;
int ret=(*pf2)(5,6);
printf("%d\n",ret3);

新问题来了 add把哪个地址放到pf2里头,pf2也是地址呀,大家可能不理解,大家只要记住可以这样写就好。

int ret4=pf2(5,6);
printf("%d\n",ret4);
#include
char* test(int a, char c)
{
	return NULL;
}
int main()
{
	pt = test;
	return 0;
}

接下来分享一道题,大家不要自定义函数,以及如何使用的NULL
大家想想它该怎么写成函数指针

char*(*pt)(int,char) = test;

既然上面是char了,那我们这块也要写成char才能对称。

C陷阱与缺陷

接下来分享两段有趣的代码均出自C陷阱和缺陷这本书中。

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

指针详解(3)_第5张图片
上面代码中红色代表括号,蓝色代表函数指针类型,
(int)0这叫什么意思呢?(void(*))0叫做强制转换了
把0强制转换成地址 然后解引用了,然后后面括号是 参数。

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

我们发现*signed没有阔括号再一起,因为优先级所以signal是函数名
还记得我们之前写函数指针,**都是和函数阔在一起的,所以叫做函数名
它的第一个参数是int 第二次参数是函数指针类型,函数返回的是函数指针类型

typedef关键字

typedef叫做类型的重定义 把一个复杂名字简单化,把int改成uint

typedef unsigned int uint
int main()
{
unsigne int num;
uint num2;
return 0}
typedef int*PArr_t)[10];

数组指针也可以重新命名,但必须在星号的右边```

pArr_t pa;
typedef int(*pf_t)(int,int);
typedef int(*pf2)(int,int);

好,我们把之前的代码也简化一下

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

今天就分享到这里,剩下今天给大家分享给大家指针的使用

你可能感兴趣的:(c,c语言,开发语言)