C++干货系列——用了这么久,你真的懂nullptr吗?

C++11引入的nullptr解决了NULL作为空指针常量的类型转换问题,避免了潜在的错误。NULL是一个宏,可被误用为整型,导致类型匹配错误。nullptr有自己的类型std::nullptr_t,可以隐式转换为任何指针类型,且智能指针支持nullptr构造。使用nullptr可以使代码更安全、更易读,且与旧代码兼容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

你是否在混着用NULLnullptr
你是否分不清这二者到底有什么区别?
滥用NULL到底会带来什么不可思议的错误?

今天,我们就来讨论讨论nullptr这个空指针的救世主。在C++11之前,几乎所有人写代码时对NULL又爱又恨,爱是因为他确实能够起到空指针的作用,恨是刚躲过了这个坑,又遇见了那个妖,魑魅魍魉怎么它就这么多。还好,C++11给我们带来了希望的曙光——nulltpr

什么是NULL

这个问题想要回答好还真是不容易。从定义上来看,NULL是一个宏,它被定义为0(也就是int型的零),或者0Llong型的零),或者其他__零指针常数__;但是一般来说我们还是把它当作整形来看待。零指针常数之所以得名,是因为他们能够被转化为空指针。在C中,NULL也可以被理解为(void*)0,因为void类型的指针都可以被转化为其他任何指针类型。详情请看文章。。。

虽然NULL很明显是为了指针而被制造出来的,但是如果你把它当作参数传到函数中去,它就会“原形毕露”——编译器会把它当作一个int类型,或一个long类型,而不是一个指针类型。比如说下边的例子:

class Girlfriend {/* ... */};

void kissGirlfriend(Girlfriend* gf);
void kissGirlfriend(int gfId);

int main() {
	// 参数匹配后,其实调用的是第二个函数,你的第零号女朋友莫名其妙被亲了一下
	kissGirlfriend(NULL);
}

从上边代码来看,我们很明显是想传一个空指针进去,让程序调用第一个重载函数,但很可惜的是,这种情况下我们唯一能确定的事情就是我们的预想肯定不会发生——可能有两个结果。

  • 如果NULL被定义为0int零),那么编译器会自然而然调用第二个重载函数,毕竟这是一个完美参数匹配。
  • 如果NULL被定义为0L或者其他形式的整型零,编译器会报错,告诉你有一个指向不明的函数调用,——因为0L可以被等同地转化为空指针或int,由此就产生了二义性。

让我们看看这个问题怎么解决。第一个尝试:我们可以试着让一个enum来代替那个函数的int,然后去消除二义性。同时让我们给参数一个名字,让代码看起来指向性也更明确一些:

class Girlfriend {/* ... */};

enum GirlfriendId {/* ... */};
void kissGirlfriend(Girlfriend* gf);
void kissGirlfriend(GirlfriendId gfId);

int main() {
	auto noGf = NULL;
	kissGirlfriend(noGF);	//还是ERROR
}

在这里,noGf不是指针,是一个正儿八经的整形变量,另一方面,从整型零到空指针的转化只能发生在零指针常数上。所以编译器很无奈的告诉我们,他不知道该把noGf转化为一个GirlfriendId还是Girlfriend*

NULL的弊端

上边两个例子都向我们展示了问题所在:NULL终究只是一个宏。它是一个整型,它不是指针,所以一旦涉及类型转换就会有风险。因而随之带来的问题是,我们没有办法在不显示声明指针类型的情况下定义一个空指针。

nullptr

自从C++11以来,有一个小特性完美的解决了这个问题——nullptr。作为一个字面常量和一个零指针常数,它可以被隐式转换为任何指针类型。我们只需稍作修改上边的例子,你就会得到:

void kissGirlfriend(GirlFriend* gf);
void kissGirlfriend(int gfId);

int main() {
	kissGirlfriend(nullptr);	//调用第一个重载函数
}

这时编译器就会乖乖听话了:因为nullptr是没办法转换成int的,它会隐式转化为一个空的Girlfriend*从而正确的函数会被调用。下边这个例子对nullptr的类型进行说明:

class Girlfriend {/* ... */};

enum GirlfriendId {/* ... */};
void kissGirlfriend(Girlfriend* gf);
void kissGirlfriend(GirlfriendId gfId);

int main() {
	auto noGf = nullptr;
	kissGirlfriend(noGF);	//OK
}

nullptr有它自己的类型,std::nullptr_t,他可以隐式转换为任何指针类型。所以上例中noGf现在有了nullptr_t类型并且可以转换为Girlfriend*,而不是GirlfriendId。所以,无论在哪里,都请使用nullptr,而不是NULL0或者其他零指针常数。

nullptr和智能指针

智能指针并不是真正的指针,他们是类。所以上边所有的隐式转化对诸如shared_ptr<T>等智能指针都不会奏效。但很幸运的是,对于nullptr自己的类型,智能指针类都重载了针对这种类型的构造器,所以下边的代码其实是合法的:

shared_ptr<Girlfriend> gfPtr = nullptr;
unique_ptr<Boyfriend> bfPtr = nullptr;

需要注意的是,除了从auto_ptrunique_ptr的转换,这是唯一一种智能指针的隐式构造函数。因此,当一个函数的参数类型是智能指针类型时,理论上你也可以直接传一个nullptr进去,而不必画蛇添足地再额外用nullptr创建一个智能指针出来:

void doSomething(unique_ptr<Object> objectPtr);

int main() {
	doSomething(nullptr);	//OK
}

向后兼容

更妙的是,C++11标准规定了任何零指针常量都可以隐式转化为nullptr,这让我们能够有效地兼容使用NULL的老代码。也就是说,如果你将nullptrnullptr_t引入老代码,代码将会很好地兼容以前的逻辑。尤其是当我们想向03版及之前的代码中引入智能指针时,我们更能体会到这种兼容特性的妙处:

//C++03 version:
void doSomething(Object* objectPtr) {
  //...
  delete objectPtr;
}

int main() {
  doSomething(NULL);
}

我们通过把原始的指针用unique_ptr替换来替换时,如果某处忘记了把NULL替换成nullptr怎么办?别担心,这样不会引起错误:

//引入 unique_ptr
void doSomething(unique_ptr<Object> objectPtr) {
	//...
}

int main() {
	doSomething(NULL);	// OK
}

原因就在于,NULL是一个零指针常数,可以隐式转换为nullptr,而nullptr又可以调用unique_ptr<T>的隐式构造器,通过两次隐式转换,我们就可以完美完成上边的调用。

结语

nullptr是一个有用的小特性,它能让你的代码更安全,也更readable。还有什么理由不用它呢?

<think>好的,我现在要回答用户关于C++指针未初始化是否默认为nullptr的问题。首先,我需要确认自己对C++标准的理解是否正确。 记得在C++中,局部变量如果没有显式初始化,它们的值是不确定的,也就是所谓的“未定义行为”。指针作为局部变量时,应该也遵循这个规则。也就是说,如果一个指针在声明时没有被初始化,它不会自动设置为nullptr,而是保留之前内存地址中的随机值。这样的话,访问这样的指针会导致未定义行为,比如程序崩溃或不可预测的结果。 然后想到全局变量和静态变量的情况。全局变量和静态存储期的变量(比如静态局部变量)会被默认初始化为零。对于指针来说,这意味着它们会被初始化为nullptr。所以需要区分变量的作用域和存储类别。 用户的问题可能更多关注局部指针变量,所以需要明确说明不同情况。同时,可能用户在使用指针时遇到问题,比如访问未初始化的指针导致崩溃,想确认是否默认有nullptr的保护机制。 另外,应该提醒正确的做法是始终显式初始化指针,无论是置为nullptr还是有效的地址。这样可以避免未定义行为,提高代码的健壮性。还可以提到现代C++中推荐使用智能指针来避免这类问题。 需要确认这些信息是否准确,可能需要查阅C++标准或权威资料。比如C++标准中关于默认初始化的部分,确认对于不同类型的变量处理方式。例如,C++11之后的规范是否有变化,但根据我的知识,这一点没有改变,未初始化的局部变量仍然保持不确定值。 总结回答的结构:先指出局部指针不会自动初始化,全局/静态会初始化为nullptr,强调未初始化的危险,推荐显式初始化和使用智能指针。</think>在C++中,**未显式初始化的指针不会自动设置为 `nullptr`**。其初始值取决于变量的作用域和存储类别: 1. **局部指针变量(自动存储期)** 若未初始化,其值是**未定义**的(随机垃圾值),访问它会导致未定义行为(如程序崩溃)。 ```cpp void example() { int* ptr; // 未初始化,指向随机内存地址 *ptr = 10; // 危险!未定义行为 } ``` 2. **全局/静态指针变量** 会被默认初始化为 `nullptr`,因为它们属于静态存储期。 ```cpp int* global_ptr; // 初始化为 nullptr static int* static_ptr; // 初始化为 nullptr void func() { static int* local_static_ptr; // 初始化为 nullptr(首次调用时) } ``` --- **安全建议**: - **始终显式初始化指针**: ```cpp int* ptr = nullptr; // 显式初始化为空 ``` - **使用智能指针**(如 `std::unique_ptr`、`std::shared_ptr`)以自动管理内存,减少手动指针错误。 ```cpp #include <memory> std::unique_ptr<int> safe_ptr = std::make_unique<int>(42); ``` 未初始化指针是常见的安全隐患,显式初始化或使用现代C++特性能有效避免此类问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值