1、void指针的概念
void * 这种指针称为“空类型指针”,它不指向任何具体类型的数据,只提供一个纯地址。void 指针必须强制类型转换成具体类型的指针才有意义。
int i = 3;
void* p = &i;
//printf(" %d\n", *p); //报错
printf(" %d\n", *(int*)p); //必须强制转换成具体的类型
void *有什么用呢?
void *也被称为万能指针,所以在只知道内存,但是不知道是什么类型的时候,就可以用void *。
比如写一个类似memset的函数:mymemset()。
void mymemset(void* data, char num, int byte_size) {
char* p = (char*)data;
for (int i = 0; i < byte_size; i++) {
*p = num;
p++;
}
}
在这个mymemset()函数中,我们利用void指针接收不同类型的指针,利用char类型逐个字节读取内存中的每一个字节,然后依次填充指定的数字。由于char类型是一个具体类型,所以可以使用++或者--进行指针的移动。
2、void指针相关规则
(1)任何类型的指针都可以直接赋值给void指针, 且无需进行强制类型转换。
double obj = 3.14;
double *pd = &obj;
void* pv = &obj; // 正确,void* 能存放任意类型对象的地址
pv = pd; // 正确,pv 可以存放任意类型的指针
(2)如果要把 void 类型的指针赋值给其他类型的指针,需要进行显式转换。
double obj = 3.14;
double *pd = &obj;
void *pv = &obj;
double *pd1 = pv; // 错误,不可以直接赋值
double *pd2 = (double*)pv; // 必须进行显示类型转换
(3)void指针只有强制类型转换后才可以正常对其操作。
double obj = 3.14;
void *pv = &obj;
printf(" %d\n", *(double*)pv);
(4)当void指针作为函数的输入和输出时,表示可以接受任意类型的输入指针和输出任意类型的指针。
在函数调用过程中的使用 void 指针作为输入输出参数,可以灵活使用任意类型的指针,比如前面的mymemset()函数。
(5)void指针变量和普通指针一样可以通过 NULL 或 nullptr 来初始化,表示一个空指针。
(6)void指针可以直接和其他类型的指针进行比较指针存放的地址值是否相同。
double obj = 3.14;
double *pd = &obj;
void *pv = &obj;
cout << (pv == pd2) << endl;
3、NULL和null_ptr
3.1 NULL
NULL仅仅代表空值,就是指向一个不被使用的地址。
尽快初始化指针是个非常好的习惯,如果在指针声明后暂时不想使用它,最好把指针赋为NULL。
int *p = NULL;
在很多数据结构的实现中,NULL指针有特殊的特性。比如在链表中经常用NULL来表示尾部。
指针野可以用在逻辑表达式中,比如判断指针是否为NULL可以这么写:
if(p){
//不是NULL
}else{
//是NULL
}
3.2 null_ptr
在C++的中,我们可以看到NULL和nullptr两种关键字,其实nullptr是C++11版本中新加入的,它的出现是为了解决NULL表示空指针在C++中具有二义性的问题。
在C语言中,NULL通常被定义为:#define NULL ((void *)0)
而在C++语言中,NULL被定义为:#define NULL 0
可见,在C++中,NULL实际上是0。这样在C++中NULL会存在二义性问题,比如在函数重载时会把NULL认为是0。所以C++中引入了null_ptr。
3.3
野指针:没有初始化的指针。
迷途指针/悬空指针:迷途指针指一个长生命周期的指针指向了一个短生命周期的变量。比如短生命周期的变量已经消失,但指针依然指向这个不存在的变量。
int* invalidPointer() {
int tmp = 3;
return &tmp; //出函数后tmp对象已被释放,返回的指针成了: 迷途指针/悬空指针
}
int main()
{
int* p = invalidPointer();
return 0;
}