空指针和Void指针的基本概念和用法

前言:本文只是限于说明空指针与void指针的基本性质和用法,关于更深层次的用法,则不介绍,因为本人自己还没有搞懂!!!

1:空指针

1.1空指针的基本定义

定义:在C语言中,如果一个指针不指向任何数据,我们就称之为空指针,用NULL表示,NULL在头文件中定义了。

空指针的定义

#include<stdio.h>
int *p1 = NULL;
char *P2 = NULL;
float *p3= NULL;
long *p4 = NULL;

注意:头文件是必不可少的,stdio.h中包含了NULL的定义。空指针定义时,是可以声明为不同的类型的(如整形,字符型 等。。。。)。虽然说在一个源文件中,同时定义多个不同类型空指针没有实际的作用。

#define NULL ((void *)0)

NULL(注意:全是大写) :为宏名;

((void*)0)  是宏参数,先看(void *)先强制定义一个void 类型的指针,0是常量,即把0变换为一个void类型的指针。看起来是不是很神奇

在不同的系统中,NULL并非总是和0等同,NULL仅仅代表空值,也就是指向一个不被使用的地址,在大多数系统中,都将0作为不被使用的地址,所以就有了这样的定义。
(void *)0表示把数值 0 强制转换为void *类型(从这里看出,任何常量都能被强制转换为指针变量),最外层的( )把宏定义的内容括起来,我们自己进行宏定义时也应该这么做。

但是,C规范中并没有将NULL与空指针强制一对一绑定。

结论:普通指针也能够被赋值为NULL

 int *p1=NULL;

    printf("打印p1中的地址%p\n",p1);

2 void指针

2.1 void指针的基本概念

void指针又称为空指针和泛型指针,他可以指向任意类型的数据

#include<stdio.h>
int main()
{
int Var1=10;
char Var2='a';
float Var3=3.1415927;
double Var4 = 1000.0001;
/*声明一个泛型指针Void,确认是否所有的数据都能被正确的写入地址*/
void *p;
p=&Var1;
printf("1:打印Var1的地址%p\n",&Var1);
printf("1:打印指针p的值%p\n",p);
p=&Var2;
printf("2:打印Var2的地址%p\n",&Var2);
printf("2:打印指针p的值%p\n",p);
p=&Var3;
printf("3:打印Var3的地址%p\n",&Var3);
printf("3:打印指针p的值%p\n",p);
p=&Var4;
printf("4:打印Var4的地址%p\n",&Var4);
printf("4:打印指针p的值%p\n",p);
}

看输出结果:

1:打印Var1的地址000000000061FE04
1:打印指针p的值000000000061FE04
2:打印Var2的地址000000000061FE03
2:打印指针p的值000000000061FE03
3:打印Var3的地址000000000061FDFC
3:打印指针p的值000000000061FDFC
4:打印Var4的地址000000000061FDF0
4:打印指针p的值000000000061FDF0

先定义的变量地址值越大。

延伸知识点,堆栈的基本概念和应用拓展:

堆栈的本质是一段内存。

先来了解栈

概念:栈是一种特殊的线性表,其只允许在固定的一端插入和删除操作。进行数据插入和删除操作的一端为栈顶另一端为栈底(栈低是固定的,不允许变化的)。栈中的数据元素遵守后进先出(Last In First Out)和后进先出的原则。

先进后出,是指先进栈后出栈(同理等价于后进栈先出栈)。我们先要了解汇编中的两个指令,push(压栈指令)pop(弹栈指令)和栈指针(*sp)。

当我们进入函数中时,系统会自动开辟一段栈内存(此时栈底和栈顶重叠),用来存放局部变量等相关参数。此时栈指针一定是指向栈底的。此时

遇到push Var_1(压栈)(Var_1是需要压入栈的变量,压栈指令之前已经定义好)指令,立即把Var_1,压入sp指针指向的地址(即栈底),同时栈顶与栈底分离,sp指针指向栈顶。接着push Var_2(压入),系统将Var_2,写入栈顶所在的位置,同时sp指针再次移动到栈顶的位置,一直反复。

再来看出栈操作,pop操作,指令:pop sp(栈指针)含义是将栈指针sp指向的数据弹出。执行完数据弹出后,栈顶变成了Var_1的位置,同理根据sp指针同步与栈顶运动的原理,sp此时也指向Var_1。再执行 pop sp 此时弹出数据Var_1。

最后说一下,为何栈的先入后出原理,这么难讲解清楚(包括很多资料和书籍),让初学者看到云里雾里,根本原因在于:

1)只做栈运行方式的文字说明,确实把栈如何工作讲的很清楚,但是无法从系统操作原理上讲清楚为什么,栈为什么要这样操作。

2)不从,汇编和编译原理上去解释栈的各种方式,只从C语言的基础是没办法讲解清楚栈的结构的。因为我们在敲代码时,不会直接写入压栈弹栈指令,代码编译连接时,编译器会根据需要,在把源代码翻译成汇编代码时,自动添加压栈和弹栈指令)

1、内存没有方向的概念,只有高低的区别。
2、如果压栈的顺序是从低地址往高低地址依次压栈,则是向上生长。
高地址往低地址依次压栈,则是向下生长。
这里其实有个潜台词,要先确定栈底位置,比如linux,一块栈内存,栈底位于低地址,所以压栈是从低地址往上增长。windows,栈底则位于高地址,压栈是从高地址往下增长。
3、关于函数参数,从右往左压栈,比如我们例子中的c–>b–>a从栈低依次压栈。
4、关于局部变量,其实也有规律,linux平台是先定义,后入栈。Windows,则是先定义,先入栈()

可以看到void类型指针,可以指向任意数据,同样根据上面代码分析还能得出下面结论,就是各种不同类型的指针,也是可以直接赋值给void类型指针的。不需要进行类型转换,但是注意反过来却是不行的,即空指针不能直接赋值给其他类型指针。

/*以下是非法操作*/
int *p1;
void *p2=&Var1;
p1=p2;  //错误不能进行赋值
p1=(int*)p2  //正确,可以进行赋值

补充说明: 从上面代码输出的结果,看栈结构的存储机制

2.2 另外一种类型的void指针

另外一种重要的空指针类型,(准确说是空指针,最常见应用)malloc()也就是在堆上分配指定内存。大家还记得学习malloc时的方法了吧!

教材中说,malloc在堆上开辟了一段指定Byte的内存,如果开辟成功则返回一个类型为void类型,地址为开辟内存首地址的指针变量。如果内存开辟不成功则返回一个空指针。此时我们学习的知识形成完美的闭环。

void *p3=malloc(5);
//代码解释:先在堆上开辟了5个Byte的内存,然后将首字节的地址作为voi类型的指针返回给我们定义的void类型指针
int *p4=(int*)malloc(sizeof(int))
//这是我们以前学习malloc使用的格式,前面需要加上一个强制类型转换

/*接下来,是对内存分配是否成功的判断,储备知识,分配不成功则返回NULL*/
if(p3==NULL)
printf("p3的内存方配不成功")
else
printf("p3的内存方配成功,且p3所存储的地址是:%p",p3);

 2.3void类型指针不能直接操作指向的数据

根据上面的学习,我们知道了,无论哪种类型的指针变量,其位数都是固定的。且上面的代码中,我们将各种不同类型的数据,赋值给了void类型的数据,且都打印出了正确的地址。有同学会问,那为什么不直接废除指针类型的定义,所有指针都使用void修饰。

int c[2]={100,101};
    void *p3=c;
    p3+=1;
    printf("打印p3中的值%d\n",*p3);
    return 0;

以上代码,我们是要执行打印数组的第二个值 101,。先编译,我们发现编译阶段直接报错

且代码14行,其实就出现问题,void类型指针,+1,编译器是无法确定地址是增长几位的。修改上述代码

int c[2]={100,101};
    void *p3=c;
    printf("1:打印p3中的地址%p\n",p3);
    p3+=1;
    printf("2:打印p3中的地址%p\n",p3);

运行结果:

可以看到地址只是默认增加了一位。

总结:

我们对该地址中到底是个什么类型的对象并不了解,因此不能直接操作 void* 指针所指的对象,也就无法确定能在这个对象上做哪些操作。
概括来说,以 void* 的视角来看内存空间也就是仅仅是内存空间,没办法访问内存空间中所存的对象,因此只有对其进行恰当的类型转换之后才可以对其进行相应的访问。
也就是说一个 void 指针必须要经过强制类型转换以后才有意义。

  • 38
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值