程序員面試(c++)——指針與引用

本文是對《程序員面試寶典》第七章——指針與引用的學習總結,不足之處,歡迎批評指正。

1、指針和引用的區別?

(1)指針可以指向空值,int* p=null;而引用則必須總是指向某個對象。

(2)指針在使用之前應該總要被測試是否合法,而引用則不需要。

(3)引用一旦指向某個對象,則不可以在指向其他對象,然而它指向對象的值是可以被修改的。

(4)由於以上不同決定了,指針和引用的應用是不同的。

2、傳遞動態內存

2.1

看下面的函數是否有錯:

char* strA(){

char str[]="hello world";

return str;
}

上述代碼是錯誤的,這個str存的是函數strA棧幀“hello world”的首地址,函數一旦調用完成,棧幀就恢復到調用strA之前的狀態,strA棧幀不再屬於被訪問的範圍。

那麼上述函數改成下面的是否可行呢?

char* strA(){

char* str="hello world";

return str;
}

改成上述代碼,就沒問題了。爲了弄清楚問題,我們首先理解清楚char* str和char str[]的區別:

char c[]="hello world"——是分配一個局部數組。

char* c="hello world"——是分配一個指針變量。

局部數組是局部變量,它對應的是內存中的棧,而指針變量是全局變量,它所對應的是內存中的全局區域。這就是上面代碼可行的原因。另外,字符串常量保存在只讀的數據段,而不是像全局變量那樣保存在普通數據段(靜態存儲區)。

char *c="hello world";

*c='t'//這將是錯誤的,c佔用了一個存儲區域。

char c[]="hello world";

c[0]='t'//這是可以的,c不佔用存儲區域,局部區的數據是可以修改的。

2.2 指針和地址的關係

int a[3];a[0]=1;a[1]=1;a[2]=2;

int* p=a;

int* q=&a[2];

p的地址是a[0]的地址,q指向的是a[2]的地址,那麼a[q-p]結果是什麼呢?

實際上q-p的運算是:(q的地址值-p的地址值)/sizeof(int)。注意這裏需要除以sizeof(int)

3、函數指針

3.1區別一下幾種定義

const char* const * keywords;

這是一個二級指針,第一個const代表指針指向的變量是不可以修改的,第二個代表這是一個常量指針。(char** keywords)

const char const* keywords;

上述式子就相當於const char* keywords(實際上第二個const是修飾char的),因此上述表示一個指向const char的指針。

const char* const keywords;

第二個const是修飾指針的,說明這是一個指向const char的常量指針。

const char const keywords;

這是一個字符常量,等同於const char keywords。

注:可以認爲*是右結合的來進行學習記憶。

3.2 容易和函數指針混淆的表達式

float (**def)[10];

這是一個二級指針,指向一個一維數組的指針,數組的元素都是float類型。float(*a)[10]則代表是一個一維指針,指針指向一個10元素的數組,數組元素都是float類型。

double* (*gh)[10];

gh是一個指針,指向一個10元素數組,數組的元素都是double*類型的。

double (*f[10])();

f是一個數組,有10個元素,元素都是函數的指針,指向的函數無參數,且返回類型爲double。

int* ((*b)[10]);

等同於int* (*b)[10];b是一個指針,指向10元素的數組,數組元素是int*類型的。

long (*fun)(int);

這是一個函數指針fun,指向的函數參數類型爲int,返回值爲long。注意括號不可省略,括號省略之後的結果就變成

long *fun(int),那麼這就變成一個函數聲明,函數返回爲long*類型的。

int (*(*F)(int,int))(int);

這個看着比較複雜,其實分解開來也很簡單,F是一個函數的指針,指向的函數的類型是兩個int參數,並且返回指針的函數,返回的函數指針指向有一個int參數且返回int的函數。分解開來就是這樣的:

(*(*F)(int,int)):這是一個函數指針,即指向的函數參數爲(int,int),返回值的是函數指針,具體指針指向的類型則由後半部分決定,第一個*是修飾(*F)(int,int)的,和剩餘的結合起來表示的返回的是函數指針。

4、指針數組和數組指針

指針數組:主體是數組,數組元素是指針。

數組指針:主體是指針,指針指向數組。

例如:int (*a)[10]:右結合則可知這是一個數組指針,指針指向10個int元素的數組。

理解了這個,現在假設存在一個二維數組int v[2][10]={{...},{...}},並且讓a=&v。由於a+1其實表明指針a向後偏移了1*sizeof(數組大小);因爲指針指向的是10個int元素的數組。所以相當於向後移動了40個字節。*a代表二維數組v第一行元素,**a即代表第一行第一個元素,因此*a+1代表在二維數組第一行元素上向後移動sizeof(int)字節(a本來就是一個一維數組),其實就是移動了一個元素而已。

5、經過上述的學習,如果你能夠按要求寫出一下定義的話,那麼說明你已經充分掌握了知識。

一個整型數——int a;

一個指向整型數的指針——int* a;

一個指向指針的指針,它指向的指針指向一個整型數——二維指針,int** a;

一個有10個整型數的數組——int a[10];

一個有10個指針的數組,該指針指向一個整型數——int *a[10];

一個指向10個整型數數組的指針——int (*a)[10];

一個函數指針,該函數有一個整型參數並返回一個整型數——int (*a)(int);

一個有10個指針的數組,該指針指向一個函數,該函數有一個整形參數並返回一個整型數——int (*a[10])(int);

其實上述式子都不難,只有我們做個有心人,一定可以將這些內容輕而易舉的拿下。

現在你可能已經掌握了上述內容,那麼再考考你,加入有以下代碼:

int a[]={1,2,3,4,5};

int* ptr=(int*)(&a+1);

printf("%d %d",*(a+1),*(ptr-1));

輸出結果是什麼?

我們知道數組名即代表指針,因此a+1很容易可以計算得出是指向第二個元素,因此輸出2.

那麼第二個元素輸出什麼呢?

我們知道數組名a可以代表指針,因此&a就可以代表二維指針,因此&a+1則代表偏移了一整個數組,相當於偏移了5個元素,因此ptr指向第六個元素,ptr-1則指向了第五個元素。因此最終結果是5,你答對了嗎?

6、迷途指針

什麼是迷途指針?迷途指針又稱爲懸浮指針,當你釋放(delete)內存時,並沒有將指向這塊內存的指針置爲空指針,那麼這個指針將成爲迷途指針。迷途指針和空指針是完全不一樣的兩個概念。

既然說到釋放內存(delete),我們知道c++裏面已經有malloc/free,那爲什麼還需要new/delete?它們之間有什麼不同?

malloc/free是c++/c裏面的標準庫函數,new/delete是c++的運算符。它們都可以用於動態申請和釋放內存,但是對於非內部數據類型的對象而言,malloc/free是無法滿足動態對象的要求的。對象在創建的時候自動運行構造函數,在消亡之前自動運行析構函數。由於它們是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和西溝函數的任務強加給malloc/free。因此c++語言需要一個完成內存分配和初始化工作的運算符new,以及清理與釋放內存的運算符delete。爲了更加容易理解,我們看看他們之間各自運行的工作都是什麼?

malloc函數的參數是接受需要分配的內存字節數,如果內存能夠滿足請求量,那麼將會返回指向被分配塊的起始地址

free函數釋放的是指針指向的內存(釋放的不是指針,而是指針指向的內存,指針依然存在)

new會有兩個事件發生:(1)內存被分配(2)爲被分配的內存調用一個或多個構造函數構建對象。int* p=new int;

delete也會有兩個事件發生:(1)爲被釋放的內存調用一個或多個析構函數(2)釋放內存。

如果想要更深入地學習new和delete,可以參考這個書《深入探索c++對象模型》

7、說到指針,有一個不得不提的指針,那就是this指針

this指針常常容易混淆的概念有如下:

this指針時時刻刻指向實例本身。

(1)this指針本質上是一個函數參數,只是編譯器隱藏起形式的、語法層面上的參數。this只能在成員函數中使用,全局函數和靜態函數都不可以this指針,因爲this指針是指向這個實例的。實際上,成員函數默認的第一個參數爲T* const this:如下

class A{

public:

int fun(int p){}
};

實際上是等價於int fun(A* const this, int p);

(2)this在成員函數的開始執行前構造,在成員函數執行結束後清除。這個週期內同任何函數的參數沒有什麼區別。當調用一個類的成員函數時,編譯器將類的指針作爲函數的this參數傳入進去。例如:

A a;

a.fun(10);編譯器將解釋爲fun(&a, 10);this即指向這個實例的指針。

(3)this指針並不佔用對象的空間。

所有成員函數的參數,不管是不是隱含的,都不會佔用對象的空間,只會佔用參數傳遞的棧空間,或者直接佔用一個寄存器。

(4)this指針是什麼時候開始創建的呢?

this指針在成員函數的開始執行前構造,在成員函數執行結束後清楚。

(5)this指針存放在何處?

this指針會因爲不同的編譯器而放置在不同的位置,可能是堆、棧,也可能是寄存器。

語法上,this是個指向對象的“常指針”,因此無法改變,它是一個指向相應對象的指針,所有對象的共同成員函數利用這個指針進行區別不同變量,this是“不同對象共享相同成員函數”保證。

(6)this指針是如何傳遞給類中的函數的呢?

大多數編譯器通過寄存器ecx傳遞this指針。

(7)我們只有獲得一個對象後,才能通過對象使用this指針,如果我們知道一個對象的this指針,那麼我們可以直接使用嗎?

this指針只有在成員函數中才有定義。因此,當你獲得一個對象時,也不能通過對象使用this指針。所以我們無法知道一個對象的this指針的位置(只有在成員函數裏纔有this指針的位置),當然如果在成員函數中,我們是可以得到this指針的位置&this,因此我們當然能夠使用它。








發佈了65 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章