C++初学者指南-3.自定义类型(第一部分)-指针

C++初学者指南-3.自定义类型(第一部分)-指针

1.为什么我们需要它们?

观察对象

  • 不复制的间接引用:引用/跟踪对象
  • 如果我们想要在运行时更改间接目标 ⇒ 不能使用引用

访问动态内存

  • 访问具有动态存储期的对象,即生命周期不与变量或作用域绑定的对象(后续章节会介绍)。

构建动态的、基于节点的数据结构
在这里插入图片描述

2.T 类型的对象指针

  • 存储 T 类型对象的内存地址
  • 可用于检查/观察/修改目标对象
  • 可以重定向到不同的目标(与引用不同)
  • 也可能指向根本没有的对象(是 Null 指针)
    在这里插入图片描述

原始指针:T *

  • 本质上是一个(无符号的)整数变量,用于存储内存地址
  • 大小:64 位平台上为64 位
  • 许多原始指针可以指向相同的地址/对象
  • 指针和目标(被指向的)对象的生命周期是独立的

智能指针(C++11)

std::unique_pointer

  • 用于访问动态存储,即堆上的对象
  • 每个对象只能有一个 unique_ptr
  • 指针和目标对象具有相同的生命周期

std::shared_pointer
std::weak_pointer

  • 用于访问动态存储,即堆上的对象
  • 每个对象可以有多个shared_ptrs 或 weak_ptrs
  • 只要至少有一个shared_ptr指向目标对象,目标对象就会存在

我们将在后面的章节中学习如何使用这些智能指针。

3.操作符

地址操作符 &

char  c = 65;
char* p = &c;
  • T* 类型的原始指针变量可以存储 T 类型对象的地址。
  • &c 返回 C 的内存地址
    在这里插入图片描述

解引用运算符 *

char  c = 65;
char* p = &c;
*p = 88;
char  x = *p;
  • &c 返回 c 的内存地址
  • *p 访问 p 中地址的值
    在这里插入图片描述

成员访问操作符 ->

struct Coord {
  char x = 0; 
  char y = 0; 
};
Coord a {12,34};
Coord* p = &a;
char v = p->x;  // v = 12
char w = p->y;  // w = 34
// 另外的方式:
char s = (*p).x;  // s = 12
char t = (*p).y;  // t = 34

在这里插入图片描述

语法

*&
类型修饰符指针声明引用声明
一元运算符解引用
value = *pointer;
取得地址
pointer = &variable;
二元运算符乘法
product = expr1 * expr2;
按位与
bitand = expr1 & expr2;

声明陷阱

int*  p1, p2;    // int*, int
int  *p1, *p2;   // int*, int*

更好且更明确:

int* p1 = …;
int* p2 = …;

重定向

与引用不同,指针可以重定向

int a = 0;
int b = 0;            // a: 0    b: 0 
int* p = &a;          // p→a   a: 0    b: 0
*p = 2;               // p→a   a: 2    b: 0
p = &b;               // p→b   a: 2    b: 0
*p = 9;               // p→b   a: 2    b: 9
cout << a;  // 2
cout << b;  // 9

运行上面代码

4.nullptr (C++11)

  • 特殊指针值
  • 可以隐式转换为 false
  • 在内存中不一定用 0 表示! (取决于平台)

编码约定:nullptr 表示值不可用

  • 在初始化时将指针设置为 nullptr 或有效地址
  • 在解引用之前检查是否不是 nullptr
int* p = nullptr;   // 初始化为nullptr
if (…) {                             
  int i = 5;
  p = &i;  // 分配有效地址
  …
  // 在解引用之前检查!
  if (p) *p = 7;  
  …
  // 设置为nullptr,表示“不可用”。
  p = nullptr;   
}                             
// i的内存被释放,任何指向i的指针都会变得无效!

5.const 和指针

目的

  1. 只读访问对象
  2. 防止指针重定向

语法

指向类型 T 的指针指向值可修改指针本身可修改
T *可以可以
T const *不可以可以
T * const可以不可以
T const * const不可以不可以

从右到左读:“(const)指向(const)T的指针”
例子:

int i = 5;
int j = 8;
int const* cp = &i;
*cp = 8;   //  编译器错误:指向的值是const
cp = &j;   //  OK
int *const pc = &i;
*pc = 8;   //  OK
pc = &j;   //  编译器错误:指针本身是常量
int const*const cpc = &i;
*cpc = 8;  //  编译器错误:指向的值是常量
cpc = &j;  //  编译器错误:指针本身是常量

一个关于风格的持续辩论…

右const左const
一个一贯的规则: const 的剩余部分保持不变更普遍,但不太一致
int const c = …;
int const& cr = …;
int const* pc = …;
int *const cp = …;
int const * const cpc = …;
const int c = 1;
const int& cr = …;
const int* pc = …;
int *const cp = …;
const int *const cpc = …;

6. "this"指针

  • 成员函数内部可用
  • this 返回对象本身的地址
  • this-> 可用于访问成员
  • *this 访问对象本身
class IntRange {
  int l_ = 0;
  int r_ = 0;
public:
  explicit
  IntRange (int l, int r): l_{l}, r_{r} {
    if (l_ > r_) std::swap(l_, r_);
  }
  int left ()  const { return l_; }
  // can also use 'this' to access members:
  int right () const { return this->r_; }// returns reference to object itself
  IntRange& shift (int by) {
    l_ += by;
    r_ += by;
    return *this;
  }
  IntRange& widen (int by) {
    l_ -= by;
    r_ += by;
    return *this;
  }
};

运行上面代码

IntRange r1 {1,3};                  // 1 3
r1.shift(1);                        // 2 4
r1.shift(2).widen(1);               // 3 7

7.前置类型声明

有时候如果需要让两种类型相互引用的话是必要的:

// 前置声明
class Hub;
class Device {
  Hub* hub_;
  …
};
class Hub {
  std::vector<Device const*> devs_;
  …
};

在这里插入图片描述
为了定义一个类型,必须要知道它所有成员的内存大小。

  • 这只有在完全了解所有成员的定义的情况下才可能实现。
  • 但是,所有指针类型都具有相同的大小

⇒我们可以:
声明 Hub 的存在,因为 Device 只需要一个指向它的指针。

8. 尽量避免使用指针

指针容易悬空

  • 悬空 = 指向无效/无法访问的内存地址的指针
  • 存储在指针中的值可以是任何地址
  • 程序员必须确保指针目标有效/仍然存在
int* p;  // p 没有初始化!
*p = 7;  //  未知行为
p = nullptr;  
*p = 7;  //  访问空指针导致未知行为
{
  int x = 8;  
  p = &x;   
}        // x的生命周期已经结束
*p = 7;  // 访问已经释放的内存导致未知行为

容易出错的参数传递

void swap_values (int* a, int* b) {
  int t = *a;
  *a = *b;
  *b = t;
}
int x = 3, y = 4;
swap_values(&x, &y)        // OK
swap_values(&x, 0);        //  未知行为
swap_values(&x, nullptr);  //  未知行为

代码更难阅读

*p = *p * *p + (2 * *p + 1);   // 太多星号了!

建议:如果可能,首选引用,尤其是对于函数参数

附上原文地址
如果文章对您有用,请随手点个赞,谢谢!^_^

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值