1. 程式人生 > >清華大學《C++語言程式設計基礎》線上課程筆記04---指標

清華大學《C++語言程式設計基礎》線上課程筆記04---指標

指標

static int i;
static int* ptr = &i;
  • 此處的*表示ptr是指標型別(地址型別),用來存放目標資料的地址
  • 其本身也有地址,所以又指向指標的指標;
  • *前面的 int 代表其指向的資料型別是 int 型,從目標i的起始單元地址取 int 資料型別位元組長度的內容進行處理;
*ptr=3;
  • 此處的 * 表示指標運算,即定址過程,按照地址尋找資料單元;
    其逆運算為 & 地址運算,即返回資料單元的起始地址.

指標變數的初始化

定義變數後不進行初始化,會預設儲存垃圾資料;
指標變數必須儲存合法取得的地址;

int a;  //1.
int *pa = &a; 

1.用變數地址作為初值時,該變數必須在指標初始化之前已宣告過,且變數型別應與指標型別一致;
2.可以用一個已有合法值的指標去初始化另一個指標變數;(沒找到例子)
3.不要用一個內部(區域性)非靜態變數去初始化 static 指標。(區域性變數消亡後原本的地址就沒有了意義,或者儲存了其他資料)

指標變數的賦值

向指標變數賦的值必須是地址常量或變數,不能是普通整數,
例如:
1.通過地址運算“&”求得已定義的變數和物件的起始地址;
2.動態記憶體分配成功時返回的地址.

  • 允許定義或宣告指向void型別的指標。該指標可以被賦予任何型別物件的地址,但只用來存放地址,不能進行指標運算.
void *general;

//void型別指標的使用

int main() {
//!void voidObject; 錯,不能宣告 void 型別的變數,編譯器無法分配儲存區域大小
void *pv; //對,可以宣告void型別的指標
int i = 5;
pv = &i; //void型別指標指向整型變數
int *pint = static_cast<int *>(pv); //void指標轉換為int指標
cout << "*pint = " << *pint << endl;
return 0;
}

P.S.空指標

int *p=0;
double *q=NULL; //這兩種為舊時代的用法,有隱藏 BUG

float *a=nullptr;//C++11標準後的安全空指標

指向常量的指標

指標儲存的地址可以更改,但不能改變所指向的物件的值

int a;
const int *p1 = &a; //p1是指向常量的指標
int b;
p1 = &b; //正確,p1本身的值可以改變
*p1 = 1; //編譯時出錯,不能通過p1改變所指的物件

指標型別的常量

若宣告指標常量,則指標本身的值不能被改變。

int a;
int * const p2 = &a;
p2 = &b; //錯誤,p2是指標常量,值不能改變

指標的算術運算

short a[4];
short* p=a;  //陣列名便是陣列首地址a[0]

*(p+2)等同於a[2];

p++後指標往後移動一個short型別長度,讀取下一個short型別資料;
  • 運算的結果值取決於指標指向的資料型別,總是指向一個完整資料的起始位置;
  • 當指標指向連續儲存的同類型資料時,指標與整數的加減運和自增自減算才有意義。
    因為如果是單個變數,算術運算後移動了n個數據型別的長度,取到的是無意義資料.

指標型別的關係運算

  • 指向相同型別資料的指標之間可以進行各種關係運算;
  • 指向不同資料型別的指標,以及指標與一般整數變數之間的關係運算是無意義的;

    P.S.可以和零之間進行等於或不等於的關係運算,來判斷是不是空指標.

例如:p==0或p!=0
用指標訪問陣列元素
int a[10], *pa;
pa=&a[0]; 或 pa=a;

pa就是a[0],(pa+1)就是a[1],... ,*(pa+i)就是a[i];

a[i], *(pa+i), *(a+i), pa[i]都是等效的。
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };

1.
for (int i = 0; i < 10; i++)
cout << a[i] << " ";

2.
for (int *p = a; p < (a + 10); p++)  //此處a為首地址,a+10此處運算類似指標的算術運算,是地址往後移動10個a型別的長度
cout << *p << " ";

3.
for (int i = 0; i < 10; i++)
cout << *(a+i) << " ";

4.
for (int *p = a,i=0; i<10; i++)
cout << p[i] << " ";
指標陣列
int main() {
int line1[] = { 1, 0, 0 }; //矩陣的第一行
int line2[] = { 0, 1, 0 }; //矩陣的第二行
int line3[] = { 0, 0, 1 }; //矩陣的第三行

int *pLine[3] = { line1, line2, line3 }; //定義整型指標陣列並初始化

//輸出矩陣
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++)
cout << pLine[i][j] << " ";  //此處pLine[1]等價於陣列名line1,所以可以套用“陣列名+下標”的方式表示陣列中的某一個數據,即pLine[0][1]等價於line1[1];
cout << endl;
}

指標陣列與二維陣列的顯著區別在於:

  • 二維陣列的每一個行都是等長的;
  • 而指標陣列是用多個一維陣列進行堆砌,形成一個類似二維陣列的集合,每一行可以不等長;

以指標作為函式引數

為什麼需要用指標做引數?

1.需要資料雙向傳遞時(引用也可以達到此效果)

用指標作為函式的引數,可以使被調函式通過形參指標存取主調函式中實參指標指向的資料,實現資料的雙向傳遞

2.需要傳遞一組資料,只傳首地址執行效率比較高;

實參是陣列名時,形參可以是指標

和引用一樣,如果只想讀取資料而不想讓其更改資料,可以使用指向常量的指標

const int* p;

指標型別的函式

若函式的返回值是指標,該函式就是指標型別的函式

int* function();
  • 不要將非靜態區域性地址用作函式的返回值(非靜態區域性變數返回時已經消亡);
  • 返回的指標要確保在主調函式中是有效、合法的地址;
    比如:

    1.主函式定義的陣列;
    2.在子函式中通過動態記憶體分配new操作取得的記憶體地址,但要記得在主函式中進行delete;

函式指標

函式為:
int example(int a)

指向該函式的指標為:
int (*function)(int)   //名字可以隨便起

p.s.與指標型別的函式區別在於:  
將*和函式名包含起來的小括號()+後面小括號裡的引數型別
int *function();
int* function() //返回int*,即int型指標的函式

指標儲存記憶體地址;
函式的程式碼在記憶體中擁有地址;
所以可用指標存取函式程式碼首地址,並據此指向函式.

函式指標的典型用途——實現函式回撥

int compute(int a, int b, int(*func)(int, int))
{ return func(a, b);}

int max(int a, int b)
{ return ((a > b) ? a: b);} 

int min(int a, int b)
{ return ((a < b) ? a: b);}

int sum(int a, int b)
{ return a + b;}


res = compute(a, b, & max);//將函式程式碼首地址傳給函式指標
res = compute(a, b, & min);
res = compute(a, b, & sum);

物件指標

Point a(5,10);
Piont *ptr;
ptr=&a;

物件指標名->成員名
例:ptr->getx() 相當於 (*ptr).getx();

this 指標

  • 指向當前物件自己;
  • 隱含於類的每一個非靜態成員函式中;
  • 當通過一個物件呼叫成員函式時,系統先將該物件的地址賦給this指標,然後呼叫成員函式,成員函式對物件的資料成員進行操作時,就隱含使用了this指標。
例如:Point類的getX函式中的語句:  
return x;  
相當於:  
return this->x;  //指向呼叫該函式的類的例項化物件

動態記憶體分配

指標不可替代的作用

動態申請記憶體操作符 new

  • new 型別名T(初始化引數列表)
Point *ptr1 = new Point(1,2); 
  • 在程式執行期間,申請用於存放T型別物件的記憶體空間,並依初值列表賦以
    初值。
  • 結果值(不一定成功):成功:T型別的指標,指向新分配的記憶體;失敗:丟擲異常。

釋放記憶體操作符 delete

釋放指標p所指向的記憶體

分配和釋放動態陣列

寫程式時不知道要用到的資料規模有多大時,可以動態建立陣列,用完後主動釋放;

new 型別名T [ 陣列長度 ];

delete[] 陣列名p

例子:
Point *ptr = new Point[2]; //建立物件陣列
ptr[0].move(5, 10); //通過指標訪問陣列元素的成員,首地址名+下標
ptr[1].move(15, 20); 
delete[] ptr; //刪除整個物件陣列

動態建立多維陣列

new 型別名T[第1維長度][第2維長度]…;

例子1:
char (*fp)[3];  //去掉第一個[],留下剩下的值
fp = new char[2][3]; //fp獲得第一行的首地址, fp+1 指向第二行的首地址


例子2:
int (*cp)[9][8] = new int[7][9][8];

for (int i = 0; i < 7; i++)
    for (int j = 0; j < 9; j++) 
        for (int k = 0; k < 8; k++)
            cout << cp[i][j][k] << " ";

delete[] cp;

將動態陣列封裝成類(可用vector代替該功能)

  • 更加簡潔,便於管理;
  • 可以在訪問陣列元素前檢查下標是否越界
class ArrayOfPoints { //動態陣列類
public:
ArrayOfPoints(int size) : size(size){  //建構函式
points = new Point[size];  //建立動態陣列
}

~ArrayOfPoints() {  //解構函式
cout << "Deleting..." << endl;
delete[] points;
}

Point& element(int index) {  //返回引用可以用來操作封裝陣列物件內部的陣列元素,返回值則只是一份副本
assert(index >= 0 && index < size);  //檢查是否越界
return points[index];
}

private:
Point *points; //指向動態陣列首地址
int size; //陣列大小
}


int count;
cout << "Please enter the count of points: ";
cin >> count;
ArrayOfPoints points(count); //建立陣列物件
points.element(0).move(5, 0); //物件.move()
points.element(1).move(15, 20); 

智慧指標(C++11)

- unique_ptr :不允許多個指標共享資源,指標地址不能被複制,但可以用標準庫中的move函式轉移到其他指標中,轉移後原指標被清空.
- shared_ptr :多個指標共享資源
- weak_ptr :可複製shared_ptr,但其構造或者釋放對資源不產生影響
- 僅作了解;