空指针
- 解引用空指针会导致未定义的行为
- 使用 nullptr 而不是 null ,因为 null 等价于零值的整数字面量,而 nullptr是一种单一的表示空指针的类型,在某些时候,使用 null 要努力和 0 区分开来。
- 注意 using nullptr_t = decltype(nullptr); 详细细节看std::nullptr_t - cppreference.com,主要目的之一就是为了区分nullptr和NULL类似的空指针。
std::nullptr_t
是空指针字面量 nullptr 的类型。它是既非指针类型亦非指向成员指针类型的独立类型。其值为空指针常量(见 NULL),并且可以隐式转换成任何指针或成员指针类型。
关于第三点,这里有一个很直接的例子:
#include <iostream>
#include <cstddef> //提供 nullptr_t
void print(std::nullptr_t)
{
std::cout << "std::nullptr_t"<<std::endl;
}
void print(int*)
{
std::cout << "int*"<<std::endl;
}
int main()
{
print(nullptr); //调用print(std::nullptr_t)
int x { 5 };
int* ptr { &x };
print(ptr);//调用print(int*)
ptr = nullptr;
print(ptr); //调用print(int*)
return 0;
}
在上面的例子中,函数调用print(nullptr)解析为print(std::nullptr_t)而不是print(int*),因为它最匹配(不需要进行隐式转换)。
在最后一个print(ptr)为啥是调用 print(int*) 呢?这是因为函数重载匹配的是类型,而不是值,而且ptr的类型是int*。因此,将匹配print(int*)。并且要注意指针类型不会隐式地转换为std::nullptr_t,而std::nullptr_t可以隐式转换为其他类型指针,所以哪怕没有 函数 print(int*) ,最后一个print 函数都不会调用 print(std::nullptr_t)。
C++指针的杂谈
在某些时候我们能看到这样子的函数
void fun(int * &refptr)
这里的是对于*refptr指针的引用。
在很多时候我们浏览C代码时候,会看到很多时候使用void*指针,它的一些作用如下:
- 可以作为函数的参数或返回值,实现一种简单的泛型编程,例如memcpy和memset等内存操作函数就使用了void*。
- 可以作为一种类型擦除的手段,将不同类型的指针转换为void*,屏蔽掉类型信息,例如在C语言中实现多态。
- 可以作为一种强制类型转换的中间步骤,将一个指针转换为void*,再转换为另一个指针,避免一些重载或隐式转换的干扰。
而重点是C++不是C,在C语言中,任何类型的指针都可以隐式地转换为void*,反过来也可以;在C++中,void*不能隐式地转换为其他类型的指针,必须使用强制类型转换。在C++中,如果想正确地删除void指向的动态分配的对象,需要先将void转换为原类型的指针,然后调用delete,否则可能导致内存泄漏或未定义行为
所以如果在C++中,我们应该怎么办来代替void*指针作用?
- 实现泛型编程,可以使用模板
-
实现一种类型擦除,可以使用std::any(C++17标准)(std::any - cppreference.com)
-
实现一种多态,可以使用虚函数
auto 在指针和引用一些区别和差异
我们都知道C++的auto是一个很好用的关键字,但是要注意auto在指针和引用一些不同的地方:
比如
#include <string>
std::string& getRef();
std::string* getTemp();
int main()
{
auto ref{ getRef() }; //ref推导是string
auto temp{ getTemp() };//temp推导是string*
return 0;
}
返回的string&在auto的自动类型推导下删除了&(auto在const和constexpr一样会丢弃),直接获取string对象,而string* 则是继续保留推导string*。
那么我们想要获取引用而不是这个对象呢?
#include <string>
std::string& getRef();
std::string* getTemp();
int main()
{
auto &ref{ getRef() }; //ref推导是string&
return 0;
}
下面我们来看一下关于auto 和 auto *
#include <string>
std::string* getPtr();
int main()
{
auto ptr1{ getPtr() }; // std::string*
auto* ptr2{ getPtr() }; // std::string*
return 0;
}
ptr1自动推导的类型就是string*,而在ptr2中,首先是auto自动推导为string然后返回这个指针。在大多数情况下,实际效果是相同的。但是要注意在某些细节方面可能有所不同。
#include <string>
std::string* getPtr();
int main()
{
auto ptr1{ getPtr() }; // std::string*
auto* ptr2{ getPtr() }; // std::string*
auto ptr3{ *getPtr() }; // std::string
auto* ptr4{ *getPtr() }; // 错误
return 0;
}
*getPtr返回一个string对象,而auto* 是推导成了一个string*,而这需要传入一个string*而不是一个string对象,所以这里报错了。
现在,让我们考虑以下auto和 auto* 与const的情况
#include <string>
std::string* getPtr(); // some function that returns a pointer
int main()
{
const auto ptr1{ getPtr() }; // std::string* const
auto const ptr2 { getPtr() }; // std::string* const
const auto* ptr3{ getPtr() }; // const std::string*
auto* const ptr4{ getPtr() }; // std::string* const
return 0;
}
对于auto来说,无论是ptr1和ptr2的结果都是一个顶级指针,const写左写右没有关系,但是对于auto*来说,const在左边就是底层指针,在右边就是底层指针,符合auto的逻辑,所以重点记住auto的const指针。