C++的指针

本文探讨了C++中的空指针处理、nullptr的使用,以及指针、引用和void*的不同用途和特性。还介绍了auto关键字在指针和引用上的差异,以及如何在C++中替代void*的常见用法,如泛型编程、类型擦除和多态实现。

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

空指针

  • 解引用空指针会导致未定义的行为
  • 使用 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++指针的杂谈

C++除了接受按值传递和引用传递,还有可以使用地址传递(其实就是使用指针),在很多时候传入指针和传入引用是差不多的,因为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指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值