C++指針系列

整理以下兩篇文章,同時加上c++ primer:

(1)主要講指針的常用方法
http://www.cnblogs.com/ggjucheng/archive/2011/12/13/2286391.html

(2)主要講函數指針和回調函數
http://blog.csdn.net/kkk0526/article/details/17122081

以下內容分爲5大部分。

1.指針的基本概念。

2.指針運算。

3.指針和數組、結構類型的關係。

4.函數指針。

5.回調函數。

6.類中含有指針。

前言:

建議:儘量避免使用指針和數組

指針和數組容易產生不可預料的錯誤。其中一部分是概念上的問題:指針用於低級操作,容易產生與繁瑣細節相關的(bookkeeping)錯誤。其他錯誤則源於使用指針的語法規則,特別是聲明指針的語法。許多有用的程序都可不使用數組或指針實現,現代 C++程序採用 vector類型和迭代器取代一般的數組、採用 string 類型取代 C 風格字符串。

——————-來自於C++ primer

1.指針的基本概念。

C++ 語言使用 * 符號把一個標識符聲明爲指針,理解指針聲明語句時,請從右向左閱讀。

string *pstring;

閱讀:pstring定義爲一個指向string類型對象的指針變量。

推薦,聲明指針時,將*和變量放在一起。

下面,從4個角度理解指針:

(1)指針的類型

int *ptr; //指針的類型是int *  
char *ptr; //指針的類型是char *  
int **ptr; //指針的類型是 int **  
int (*ptr)[3]; //指針的類型是 int(*)[3]  
int *(*ptr)[4]; //指針的類型是 int *(*)[4] 

只要把指針聲明語句裏的指針名字去掉,剩下的部分就是這個指針的類型

(2)指針所指向的類型

當通過指針來訪問指針所指向的內存區時,指針所指向的類型決定了編譯器將把那片內存區裏的內容當做什麼來看待。

int *ptr; //指針所指向的類型是int  
char *ptr; //指針所指向的的類型是char  
int **ptr; //指針所指向的的類型是 int *  
int (*ptr)[3]; //指針所指向的的類型是 int()[3]  
int *(*ptr)[4]; //指針所指向的的類型是 int *()[4]

從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉,剩下的就是指針所指向的類型。

(3)指針的值

指針的值是指針本身存儲的數值,這個值將被編譯器當作一個地址,而不是一個一般的數值。在32位程序裏,所有類型的指針的值都是一個32位整數,因爲32位程序裏內存地址全都是32位長。

指針所指向的內存區就是從指針的值所代表的那個內存地址開始,長度爲sizeof(指針所指向的類型)的一片內存區。以後,我們說一個指針的值是XX,就相當於說該指針指向了以XX爲首地址的一片內存區域;我們說一個指針指向了某塊內存區域,就相當於說該指針的值是這塊內存區域的首地址。

指針所指向的內存區和指針所指向的類型是兩個完全不同的概念。在例一中,指針所指向的類型已經有了,但由於指針還未初始化,所以它所指向的內存區是不存在的,或者說是無意義的。

(4)指針本省所佔據的內存區

指針本身佔了多大的內存?你只要用函數sizeof(指針的類型)測一下就知道了。在32位平臺裏,指針本身佔據了4個字節的長度。

指針本身佔據的內存這個概念在判斷一個指針表達式是否是左值時很有用。

2.指針運算。

(1)指針的算術操作

指針的算術操作與普通類型的算術操作不一樣,指針的操作往往與指針的類型有關。

例1:

int ia[] = {0,2,4,6,8};
int *ip = ia; // ip points to ia[0]
ip = ia; // ok: ip points to ia[0]
int *ip2 = ip + 4; // ok: ip2 points to ia[4], the last element in ia

通常,在指針上加上(或減去)一個整型數值 n 等效於獲得一個新指針,
該新指針指向指針原來指向的元素之後(或之前)的第 n 個元素。

指針的算術操作只有在原指針和計算出來的新指針都指向同一個數組的元素,或指向該數組存儲空間的下一單元時纔是合法的。如果指針指向一對象,我們還可以在指針上加 1 從而獲取指向相鄰的下一個對象的指針。

例2:

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    char a[6] = "abcde";
    int *ptr = (int *)a;
    ptr++;
    cout << (char)(*ptr) << endl;
    return 0;
}

結果,輸出 e 。從中,也能體會到,對指針ptr進行加減n運算,相當於將指針上下移動n × sizeof(*ptr)個字節而得到的新的指針。

(2)指針和const限定符

指向 const 對象的指針

const double *cptr; // cptr may point to a double that is const

另一種寫法爲,(推薦使用上面一種):

double const *cptr;

從右往左閱讀,cptr 是一個指向 double 類型 const 對象的指針。因此,cptr 本身並不是const。在定義時不需要對它進行初始化,如果需要的話,允許給 cptr 重新賦值,使其指向另一個 const 對象。但不能通過 cptr 修改其所指對象的值。

但是,允許把非 const 對象的地址賦給指向 const 對象的指針。因此,不能使用指向 const 對象的指針修改基礎對象,然而如果該指針指向的是一個非 const 對象,可用其他方法修改其所指的對象。

double dval = 3.14159; // dval is not const
const double *cptr = &dval; 
*cptr = 3.14159; // error: cptr is a pointer to const
double *ptr = &dval; // ok: ptr points at non-const double
*ptr = 2.72; // ok: ptr is plain pointer
cout << *cptr; // ok: prints 2.72

如果把指向 const 的指針理解爲“自以爲指向 const 的指針”,這可能會對理解有所幫助。在實際的程序中,指向 const 的指針常用作函數的形參。將形參定義爲指向 const 的指針,以此確保傳遞給函數的實際對象在函數中不因爲形參而被修改。

const指針

int errNumb = 0;
int *const curErr = &errNumb; // curErr is a constant pointer
curErr = curErr; // error: curErr is const

這裏沒什麼好說的,errNumb爲常量指針,自然不能被修改。唯一需要注意的是上面容易混亂,尤其是double const *cptr 這種鬼。簡單記法就是,指針緊挨着const的右方,則爲const指針,否則爲指向const對象的指針。或者看const的位置,4個字符裏面,只有const的位置是變化的,1,2,號位是指向const對象的指針,3號位是const指針。

指向 const 對象的 const 指針

const double pi = 3.14159;
// pi_ptr is const and points to a const object
const double *const pi_ptr = &pi;

這裏也很清楚。

指針和 typedef

假設給出以下語句:

typedef string *pstring;
const pstring cstr;

自然地理解爲:

const string *cstr; // wrong interpretation of const pstring cstr

錯誤的原因在於將 typedef 當做文本擴展了。聲明 const pstring 時,const 修飾的是pstring 的類型,這是一個指針。因此,該聲明語句應該是把cstr 定義爲指向 string 類型對象的 const 指針,這個定義等價於:

string *const cstr; // equivalent to const pstring cstr

3.指針和數組、結構類型的關係。

指針和數組,這裏不詳細講解。這裏給出我個人的理解,數組名是一個const指針。

指針和結構類型的關係:

例3:

struct MyStruct  
{  
int a;  
int b;  
int c;  
}  

MyStruct ss={20,30,40};//聲明瞭結構對象ss,並把ss的三個成員初始化爲20,30和40。
MyStruct *ptr=&ss;//聲明瞭一個指向結構對象ss的指針。它的類型是
MyStruct*,它指向的類型是MyStruct。
int *pstr=(int*)&ss;//聲明瞭一個指向結構對象ss的指針。但是它的類型和它指向的類型和ptr是不同的。

通過指針ptr來訪問ss的三個成員變量:

ptr->a;  
ptr->b;  
ptr->c;  

通過指針pstr來訪問ss的三個成員變量:

*pstr;//訪問了ss的成員a。  
*(pstr+1);//訪問了ss的成員b。  
*(pstr+2)//訪問了ss的成員c。 

但是,這種訪問是不太合適的,有些情況下會出錯。原因在於ss的成員在內存中存放的位置有可能不連續。

4.函數指針。

概念:指針是一個變量,是用來指向內存地址的。一個程序運行時,所有和運行相關的物件都是需要加載到內存中,這就決定了程序運行時的任何物件都可以用指針來指向它。函數是存放在內存代碼區域內的,它們同樣有地址,因此同樣可以用指針來存取函數,把這種指向函數入口地址的指針稱爲函數指針。

例4:

void Invoke(char* s);

int main()
{
    void (*fp)(char* s);    //聲明一個函數指針(fp)        
    fp=Invoke;              //將Invoke函數的入口地址賦值給fp
    fp("Hello World!\n");   //函數指針fp實現函數調用
    return 0;
}

void Invoke(char* s)
{
    printf(s);
}

由上知道:函數指針函數的聲明之間唯一區別就是,用指針名fp代替了函數名Invoke,這樣這聲明瞭一個函數指針,然後進行賦值fp=Invoke就可以進行函數指針的調用了。聲明函數指針時,只要函數返回值類型、參數個數、參數類型等保持一致,就可以聲明一個函數指針了。注意,函數指針必須用括號括起來 void (fp)(char s)。

實際中,爲了方便,通常用宏定義的方式來聲明函數指針,實現程序如下:

例4:

typedef void (*FP)(char* s);
void Invoke(char* s);

int main(int argc,char* argv[])
{
    FP fp;      //通常是用宏FP來聲明一個函數指針fp
    fp=Invoke;
    fp("Hello World!\n");
    return 0;
}

void Invoke(char* s)
{
    printf(s);
}

函數指針數組

下面用程序對函數指針數組來個大致瞭解:

例5:

#include <iostream>
#include <string>
using namespace std;

typedef void (*FP)(char* s);
void f1(char* s){cout<<s;}
void f2(char* s){cout<<s;}
void f3(char* s){cout<<s;}

int main(int argc,char* argv[])
{
    void* a[]={f1,f2,f3};   //定義了指針數組,這裏a是一個普通指針
    a[0]("Hello World!\n"); //編譯錯誤,指針數組不能用下標的方式來調用函數

    FP f[]={f1,f2,f3};      //定義一個函數指針的數組,這裏的f是一個函數指針
    f[0]("Hello World!\n"); //正確,函數指針的數組進行下標操作可以進行函數的間接調用

    return 0;
}

5.回調函數。

概念:回調函數,顧名思義,就是使用者自己定義一個函數,使用者自己實現這個函數的程序內容,然後把這個函數作爲參數傳入別人(或系統)的函數中,由別人(或系統)的函數在運行時來調用的函數。函數是你實現的,但由別人(或系統)的函數在運行時通過參數傳遞的方式調用,這就是所謂的回調函數。簡單來說,就是由別人的函數運行期間來回調你實現的函數。

例6:

//定義帶參回調函數
void PrintfText(char* s) 
{
    printf(s);
}

//定義實現帶參回調函數的"調用函數"
void CallPrintfText(void (*callfuct)(char*),char* s)
{
    callfuct(s);
}

//在main函數中實現帶參的函數回調
int main(int argc,char* argv[])
{
    CallPrintfText(PrintfText,"Hello World!\n");
    return 0;
}

至此,對回調函數應該有了一個大致的瞭解。

6.類中含有指針。

類中有指針的情況,後續有時間在補上。簡單在這裏標記一下:

類可以分爲帶指針的類,和不帶指針的類。

帶指針的類,一般標配Big Three, 三個特殊函數。

(1)拷貝構造:ClassWPointer(const ClassWPointer& cwp);

(2)拷貝賦值:ClassWPointer& operator=(const ClassWPointer& cwp);

(3)析構函數:~ClassWPointer();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章