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

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

func 數列 去掉 line 行處理 靜態變量 課程 數組大小 元素

指針

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,但其構造或者釋放對資源不產生影響
- 僅作了解;

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