指针
指针是一种数据类型,通过它可以定义一个指针变量,变量里存的是地址,通过这个地址就可以找到所需的变量单元。
但注意,指针本身也是数据,所以这意味着它也有属于自己的地址。
为什么要使用指针:
1)函数之间的命名空间互相独立,但函数之间的存储空间是统一的,使用指针可以做到跨函数共享变量 。(全局变量不宜多用,它可以被所有函数访问,可能会被其他函数无意间修改,且在程序运行期间一直占用存储单元,耗费空间)
2)指针可以接收malloc分配函数返回的内存首地址,即占用的是堆内存,内存的申请和释放是受程序员控制的。
3)函数之间有传参是值传递(内存拷贝),使用指针可以优化参数的传递效率。
如何使用指针: 定义: 类型* 变量名;(变量名_p)
赋值: 指针变量名 = 地址;
取值: *指针变量名 (变量名相当于地址,前面加个*表示取这个地址的内容)
1)类型决定了通过指针来访问内存时访问的字节量
2)*表示是指针变量 一个*搭配一个变量名 切不可偷懒int* p1,p2,p3 这是错误的
3)指针变量的默认值是随机的(野指针),如果在定义指针后没有立即指向某块合法地址,为了安全起见建议初始化为NULL(空)
举个例子:
int num = 10;
首先定义一个整型变量,那么编译器在内存中就开辟一个整型单元(4字节)存放变量num,假设num的地址为0x001,这块地址存放着数据10。接下来定义指针:
int* p = malloc(sizeof(int));
p = #
第一句定义指针 用malloc函数在堆内存中开辟一块整型单元给指针变量p 这里假设指针变量的地址为0x008。第二句赋值,即将num的地址作为数据存放到指针中 即0x008这块地址存放的数据是num的地址0x001,也就是我们说的指针p指向了num,我们可以通过指针访问到num 。
p = NULL;
如果定义指针后发现暂时没有地址需要指向,切记要么不定义,要么一定要将指针初始化为NULL。NULL在指针中表示空,即这是一个空指针,没有指向任何东西。当你在写程序时,用到一个外来指针时也一定要记得判断其是否为空指针(多一点勤劳,日后少一点查找bug的苦):
if(NULL == p) return;
那么,如果我定义指针后,既没有指向地址,也没有初始化为NULL,会怎么样呢?朋友,那你就危险了。这就会诞生一个怪物,它叫野指针。
产生野指针的后果是不可预料的:指针是个很强大的工具,操作不当的野指针甚至可能会引起系统崩溃。因为指针定义后没有初始化,其默认值是随机的,也就是说它想指哪里就指向哪里,运气好他可能指向一块空白的内存区域,运气差点指向存放重要数据的内存区或者是操作系统的关键内存区,那么你对你的指针的再操作,将有可能会导致你数据丢失,系统紊乱。
产生野指针的原因:
1)定义指针未初始化;
2)指针指向的是局部变量的地址;(局部变量 即函数中定义的变量 存储在栈区 随函数运行结束而释放)
3)指针被free后没有置为NULL;(内存被释放不代表指针会消亡或置为NULL,勤劳!!!)
没有天生的野指针,只有偷懒或粗心创造野指针的程序员 ,勤劳!
指针运算:
指针+整数 = 指针+(宽度 * 整数) //宽度为指针类型对应所占的字节数
指针-整数 = 指针-(宽度 * 整数)
指针-指针 = (指针-指针)/宽度 //两个指针之间相隔多少个元素
指针+指针 // 无意义
指针与数组:
数组名是一种特殊的指针,它的值不能修改,地址是对应关系,不是存储关系,对应的就是数组的第一个元素的地址。 因为数组名就是指针,所以数组名与指针的使用方式可以互换。
arr[i] == *(arr+i);
数组作为函数的参数时:当把数组作为参数传递给函数时,就脱变为了指针。需要额外增加一个参数(int len),把数组的长度传递过去。
int len = (sizeof(arr)/sizeof(arr[0]));
数组指针与指针数组:
变量类型判断:变量和哪个关键字先结合,该变量就是什么类型
(1)int (*p)[n]; //p先和*结合,故是一个数组指针 ,即指针指向一个数组
(2) int* p[n]; //p先和[n]结合,故是一个指针数组,即数组里面的成员是指针
(1) 数组指针,也称行指针,即指向一维数组的指针,这个一维数组的长度为n,n也叫做p的步长,因此,当p+1时就代表跨越了n个int型内存单元,即指向下一行的数组(在二维数组中),举个栗子:
#include<stdio.h>
int main(int argc, char const *argv[])
{
int b[4][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
int (*p)[4];//定义一个步长为4的数组指针
p=b; //赋值
printf("%d %d\n",(*p)[0],(*(p+1))[0]); //输出第一行第一个值 和第二行第一个值
}
在运行这段代码时,我发现其实 (*p)[1] == (*p+1)[0] 可以自己揣测下原由。
(2)指针数组,说明这是一个整型指针数组,它有n个指针类型的数组元素,此时不可用p=arr来赋值,因为数组中只有元素p[0],p[1],p[2]....p[n-1]。因此要通过循环来分别赋值。
int* p[4]; //定义一个指针数组 里面包含四个指针变量元素 p[0],p[1],p[2],p[3]
int arr[4][5]; //定义一个4行5列的二维数组
for(int i=0;i<4;i++)
{
p[i] = arr[i];
} //将二维数组的四行分别赋值给指针数组中的指针
函数指针与指针函数:
同理,函数指针和指针函数
int (*func)(int x); //()优先级高 先与*结合 所以这是一个指针 所以是函数指针
int* func(int x); // 由函数的定义可以知道这是一个返回值为int型指针的函数 所以是指针函数
(1) 函数指针。首先,之前说过函数是一段具有功能性的代码,其最终会被翻译成一堆二进制数据。既然函数也是一块数据,那么自然也可以用指针来指向它,这里函数名就是它的首地址。函数指针的作用就是提供不同实现的统一接口。(在多人合作开发中可以应用到,以及一些接口问题)
举个例子:
假如你是开发,上层用户要求你做一个加法小程序,好的你开始做,终于做完了。
#include <stdio.h>
int add(int a,int b)
{
return a+b;
}
int main()
{
int a,b;
scanf("%d",&a);
scanf("%d",&b);
int (*func_p)(int a,int b);
func_p = add;
printf("%d",(*func_p)(a,b));
return 0;
}
这时,上层用户突然说不要加法了,要减法。这时你不仅要写个减法程序,还要将调用过加法的地方全部改成减法,这是多么的麻烦。而当我们使用函数指针时,上层用户想让一个函数所做的东西会变化时,我们只需要改变底层实现,并用函数指针指向新的实现就行了。
如下:
#include <stdio.h>
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
int main()
{
int a,b;
scanf("%d",&a);
scanf("%d",&b);
int (*func_p)(int a,int b);
func_p = add;
func_p = sub;
printf("%d",(*func_p)(a,b));
return 0;
}
看到一个比喻很恰当,函数指针就相当于一只可以切换武器的手,前期的时候你可能没有太多武器,但是到了后期,武器多了,应对的困难也需要不同的武器解决,函数指针就能帮你做到切换不同武器去对付不同场景,让你更有效率的升级。
写到这里,一开始还以为自己已经理顺了,然后看了几个例子又蒙了。
int (*p[num])(int a);
int (*(*p)[num])(int a);
能分别说出上面两个都是什么吗?
答案放下篇博客吧,我也要好好琢磨琢磨。