各位少年,大家好,我是博主那一脸阳光
,今天介绍 二级指针 指针数组,还有个指针数组模拟二维数组。
前言:在浩瀚的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<stdio.h>
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<stdio.h>
int main()
{
char* p = "abcdef";
printf("%c\n", p[3]);
p[3] = 'q';
return 0;
}
此时注意p没办法间接修改它,因为p的字符串是常量,所以建议在char前面加const
以避免误导。
剑指offer面试题
今天来介绍《剑指offer》一书中的题目
#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;
}
上面代码str1和str2不同这是为什么呢?
这就好比两个一模一样的背包,其中有一个可能不是你的,所以地址是不相同的。
str3和str4常量字符串,不可能修改了,就好比你和你女朋友的包,你都得背一样。
因为str3和srt4都是常量,无法更改,所以计算机偷懒了,只开辟了一块空间。
数组指针变量
指针数组:是数组,是存放指针的数组!
哪数组指针是什么呢?
我们类比一下:
整形指针:指向整形的指针。
字符指针:指向字符的指针。
浮点型指针:指向浮点型的指针。
数组指针 指向数组的指针!
数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针的变量。
整形指针变量存放的就是整形的地址
字符指针变量存放的就是字符的地址
数组指针变量存放的应该是数组的地址
int(*pa)[10];
数组指针变量存放数组的地址,里面每个类型都是int类型。
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int(*parr)[10]=&arr;
解释:先和结合,说明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跳过整个数组,
如果取地址数组名打印跳过整个数组。那好我们看打印结果
接下来介绍数组指针是怎么打印的呢?
#include<stdio.h>
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<stdio.h>
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<r; i++)
{
for(j=0; j<c; j++)
{
printf("%d ", a[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;
}
这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
如下图:
所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀
维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类
型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀
⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:
#include <stdio.h>
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<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf)(int,int) = &Add;//pf就是函数指针变量
//原来我们是不是这样写
int ret=Add(3,5);
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<stdio.h>
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)();
上面代码中红色代表括号,蓝色代表函数指针类型,
(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);
今天就分享到这里,剩下今天给大家分享给大家指针的使用