C++初學者指南-3.自定義類型(第一部分)-指針
文章目錄
- C++初學者指南-3.自定義類型(第一部分)-指針
- 1.為什么我們需要它們?
- 2.T 類型的對象指針
- 原始指針:T *
- 智能指針(C++11)
- 3.操作符
- 地址操作符 &
- 解引用運算符 *
- 成員訪問操作符 ->
- 語法
- 重定向
- 4.nullptr (C++11)
- 5.const 和指針
- 6. "this"指針
- 7.前置類型聲明
- 8. 盡量避免使用指針
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 和指針
目的
- 只讀訪問對象
- 防止指針重定向
語法
指向類型 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:explicitIntRange (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 itselfIntRange& 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); // 太多星號了!
建議:如果可能,首選引用,尤其是對于函數參數
附上原文地址
如果文章對您有用,請隨手點個贊,謝謝!^_^