c++ 指針常量、常量指針、函數指針等詳解



指針

(1)指針本身就是個對象,允許對指針賦值和拷貝。生命週期內它可以先後指向幾個不同的對象。
(2)指針無需在定義時賦初值。和其他內置類型一樣,在塊作用域內定義的指針如果沒有被初始化,也將擁有一個不確定的值。
(3)指針存放某個對象的地址。可以使用取地址符 & 來獲取地址
(4)如果指針指向了一個對象,可以使用解引用符 * 來訪問該對象
(5)void* 可以存放任意對象的地址。但是我們對該地址中到底是個什麼類型的對象並不瞭解。可以拿它和別的指針比較、作爲函數的輸入輸出,或者賦值給另一個void指針。但是不能直接操作void指針所指向的對象,因爲我們並不知道這個對象到底是什麼類型。
(6)**表示指向指針的指針。***表示指向指針的指針的指針,以此類推
(7)指向指針的引用。引用不是對象,沒有實際地址,所以不能定義指向引用的指針。但是指針是對象,因此存在對指針的引用
(8)空指針:空指針不指向任何對象。得到空指針最直接的辦法就是用字面值 nullptr 來初始化指針(c++ 11引入),nullptr 是一種特殊類型的字面值,可以被轉換成任意其他的指針類型。
(9)我們之前一直用NULL來給指針賦值。NULL是一個預處理變量,在 cstdlib 中定義,它的值就是0。預處理器在編譯過程之前運行的,預處理變量不屬於命名空間std,由預處理器負責管理,因此在使用預處理變量之前不需要加上std::。當遇到一個預處理變量時,預處理器會自動地將它替換爲實際值,用NULL和0初始化指針是一樣的。



指針簡單的定義和使用:

#include <iostream>
#include <cstdlib>

using namespace std;

int main(){
	int i = 23;
	int *p1 = &i;	//p1被初始化,存放i的地址 
	int *p2;		//p2被初始化,沒有指向任何對象 
	p2 = p1;		//p2和p1指向同一個對象i 
	
	*p2 = 12;		//p2所指向的對象被改變了(此處是指針指向的對象被改變了) 
	p2 = 0;		    //p2不指向任何對象(此處是指針改變了) 
	
    return 0;
}


指向指針的引用:

//指向指針的引用
#include <iostream>
#include <cstdlib>
using namespace std;
int main(){
	int i = 1024;
	int *p;
	int *&r = p;	//r是對指針p的引用。此處從右向左解讀,&r可以知道r是一個引用,*&r可以知道 r引用的是一個指針 
	
	r = &i;			//r引用了一個指針,也就是p指向i
	*r = 0;			//解引用r得到i,也就是將i的值改爲0
	 
	cout << i << endl;
	
	return 0;
}



指向指針的指針

#include <iostream>
#include <cstdlib>

using namespace std;

int main()
{
    double* (*a)[3][6];             // a 是指向二維指針的數組,數組中存儲的元素都是double*
    
    cout << sizeof(a) << endl;      // a 是一個指針,所以sizeof(a)的值是4
    cout << sizeof(*a) << endl;     // *a 表示一個二維數組,元素類型是double*,是一個指針,佔用字節數爲4。sizeof(*a) = 3 * 6 * 4 = 72
    cout << sizeof(**a) << endl;    // *a 表示一個二維數組,**a爲數組的首元素,即*a[0],*a[0]也是一個數組,長度爲6,元素類型爲double *,即6 * 4 = 24
    cout << sizeof(***a) << endl;   // ***a是數組的一個元素,類型爲double *,故sizeof(***a) = 4
    cout << sizeof(****a) << endl;  // ****a 類型爲double,故sizeof(****a) = 8

    return 0;
}



指向數組的指針

對於一個數組 array[10]
array 是數組的名字,&array 獲取的是這個數組的地址,而且這個地址的值等於 &array[0] (首元素的地址)。雖然他們的值相等,但是含義是不一樣的:
如果 &array + 1 這個時候,地址偏移的是 &array + sizeof(array)
如果 &array[0] + 1 這個地址偏移是 &array[0] + sizeof(array[0]) ,也就是偏移到地址 &array[1]上。

 int a[5] = {1 , 2 , 3 , 4 ,5};
 int *p = (int *)(&a + 1);            // &a:表示數組a的首地址,&a + 1:= &a + sizeof(a),即p指向數組最後一個元素的後一個位置
 printf("%d \n" , *(a + 1 ));         // a+1表示指向數組的第二個元素的地址,因此*(a+1)就是2。
 printf("%d \n" ,  *(p -1));          // p -1就指向數組的最後一個元素,即5



常量指針

解讀方法:* 爲分隔符,const關鍵字在 * 左邊,說明指針所指向的對象是const類型,如果const關鍵字在 * 右邊,說明指針是個const類型。

指向常量的指針,即指針指向的對象是const對象,即不允許用指針來改變其所指的const對象的值。

比如:

int i = 1;
int const * a = &i; // const在*的左邊,說明是a指向的對象是const類型,即不可修改i的值,但是可以修改a的指向

// *a = 2;             // 錯誤操作

int ii = 2;
a = &ii;

a是一個指針,指向一個const int,不需要被初始化,因爲a可以指向任何一個東西(即a不是一個const),但是a指向的東西是不能被改變的




指針常量

解讀方法:* 爲分隔符,const關鍵字在 * 左邊,說明指針所指向的對象是const類型,如果const關鍵字在 * 右邊,說明指針是個const類型。

指針常量值得是指針本身類型是const指針,需要給指針初始化值。
比如:

int i = 1;
int * const a = &i;		// const在*右邊,說明const指的是a是const類型,即不可以給a重新賦值

int ii = 2;
//a = &ii;		//錯誤做法,不可以修改指針的指向

*a = 2;			// 正確,可以修改指針指向的對象的值



指針常量 常量指針

double value = 1;

double * ptr = &value;                  // ptr是一個指向double類型的指針,ptr的值可以改變,ptr所指向的value的值也可以改變
const double * ptr1 = &value;           // ptr1是一個指向const double類型的指針,ptr1的值可以改變,不能通過ptr1改變value的值(const在*左邊,說明const修飾的是指針指向的對象value)
double * const ptr2 = &value;           // ptr2是一個指向double類型的const指針,ptr2的值不可以改變,可以通過ptr2改變value的值(const在*右邊,說明const修飾的是指針本身)
const double * const ptr3 = &value;     // ptr3是一個指向const double類型的const指針,ptr3的值不可以改變,也不能通過ptr3改變value的值




指向類的指針

一個指向 C++ 類的指針與指向結構的指針類似,訪問指向類的指針的成員,需要使用成員訪問運算符 ->

#include <iostream>
 
using namespace std;

class Box{
   public:
      // 構造函數定義
      Box(string s){
         cout <<"Constructor called." << endl;
      }
      
      string Volume(){
         return s + "test";
      }
    private:
		string s;
};

int main(void){
   Box Box1("box1");    
   Box Box2("box2");    
   Box *ptrBox;          

   // 保存第一個對象的地址
   ptrBox = &Box1;
   cout << "Volume of Box1: " << ptrBox->Volume() << endl;

   // 保存第二個對象的地址
   ptrBox = &Box2;
   cout << "Volume of Box2: " << ptrBox->Volume() << endl;
  
   return 0;
}

輸出:

Constructor called.
Constructor called.
Volume of Box1: test
Volume of Box2: test



函數指針

函數指針:指向函數的指針。函數指針指向某個特定的函數類型,函數類型由其返回類型以及參數表確定,與函數名無關
函數指針只能通過同類型的函數指針或0進行初始化或賦值。將函數指針初始化爲0表示該指針不指向任何函數。


將 pf 聲明爲指向函數的指針,指向的函數帶有兩個 const string & 類型的形參,和 bool 類型的返回值。 `bool (*pf)(const string & , const string &);` // ***pf 兩側的圓括號是必須的**。
可以用 typedef 簡化函數指針的定義。pFunc 是一種指向函數的指針類型的名字。使用這種函數指針類型時,只需直接使用 pFunc 即可,不必每次都把整個類型聲明寫出來 `typedef bool (*pFunc)(const string & , const string &);`

函數指針的簡單使用

typedef bool (*cmpFun)(const string & , const string &);		// 定義函數指針,別名爲cmpFun
bool lengthCompare(const string & , const string  &);			// 定義lengthCompare函數

// 直接引用函數名等效於在函數名上應用取地址符
cmpFun pf = lengthCompare;
cmpFun pf1 = &lengthCompare;

lengthCompare("hi" , "hello");			//直接調用lengthCompare函數
pf("hi" , "hello");						//通過函數指針調用lengthCompare函數,未使用*
(*pf)("hi" , "hello");					//通過函數指針調用lengthCompare函數,使用*

函數指針的複雜使用

#include <iostream>
using namespace std;

class Father
{
    public:
        void func()
        {
            cout << "Father func" << endl;
        }
        void  func2()
        {
            cout << "Father func2" << endl;
        }

};

class Son:public Father
{
    public:
        void func2()
        {
            cout << "Son func2" << endl;
        }

};


typedef void(Father::*PF1)();
typedef void(Son::*PF2)();

int main()
{
    Father  f;
    Son s;

    PF1 pf1 = &Father::func;
    (f.*pf1)();
    (s.*pf1)();
    
    pf1 = &Father::func2;
    (f.*pf1)();
    (s.*pf1)();

    PF2 pf2 = &Son::func2;
    (s.*pf2)();
    
    return 0;
}

控制檯打印:

Father func
Father func
Father func2
Father func2
Son func2


函數指針,對象指針

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


//類指針的用法
class  CObjPoin
{
    public:
        typedef int (CObjPoin::*Proc)(int);             //*Proc:表示一個指向方法名的指針
    public:
        int add(int b)
        {
            return (a + b);
        }
        int sub(int b)
        {
            return (a - b);
        }
    public:
        int a;
};


//調用類指針
void  CalcClassPoint()
{
    CObjPoin cObj;
    cObj.a = 2;

    CObjPoin::Proc myproc[2] = {&CObjPoin::add, &CObjPoin::sub};      //Proc:表示指針,因此數組中的內容應該是函數名的引用

    for(int i = 0;i < 2;i++)
    {
        cout << (cObj.*myproc[i])(20) << endl;            //*myproc[i]表示解引用。得到函數名。
    }
}




//函數指針用法
int mul(int a,int b)
{
    return a * b;
}
int ormeth(int a,int b)
{
    return a ^ b;
}

typedef int (*MethodHandler) (int lhs,int rhs);


//調用函數指針
void TestMethod()
{
    MethodHandler myHandler[2] = {mul,ormeth};
    for(int i = 0;i < 2;i++)
    {
        cout << myHandler[i](2,4) << endl;
    }

}

int main()
{
    CalcClassPoint();
    TestMethod();
}



函數指針作爲形參

//函數指針做爲函數形參的2中聲明方式,這兩種寫法等價:
void FunParam(const string & , const string & , bool (const string & , const string &));
void FunParam1(const string & , const string & , bool (*)(const string & ,const string &));


返回指向函數的指針
閱讀函數指針的方法:從聲明的名字開始 由內而外 的理解

int (*ff(int))(int * , int);
// ff(int):可以發現ff聲明爲一個函數,帶有一個int類型的實參
// ff函數的返回值爲:int (*)(int * ,int) 它是一個指向函數的指針

聲明函數的返回值是函數指針最好是通過typedef,可以使定義簡明易懂,就不會那麼難以理解。上面聲明可改寫成

typedef int (*PF)(int * , int);
PF ff(int);		//函數ff返回一個函數指針

用變量a給出下面定義,一個有10個指針的數組,每個指針指向一個函數,該函數有一個整形參數並返回一個整形。
答案:int (*a[10])(int)
思路:

  1. 首先我們知道a是一個數組,數組類型先忽略,即 a[10]
  2. 其次,來看數組的類型,是個指針,指向一個函數,即 int (*f)(int)
  3. 然後把數組類型帶進數組的聲明中,即 int (*a[10])(int)

聲明一個指向含有10個元素的數組的指針,其中每個元素是一個函數指針,該函數的返回值是int,參數是int *。
答案:
思路:

  1. 首先我們先完成含有10個元素數組的書寫,即 int (*a[10])(int *)
  2. 指向數組的指針,即 int (*(*a)[10])(int *)


函數和函數指針

可以把函數形參定義爲函數類型,但函數的返回類型必須是指向函數的指針,不能是函數類型。
具有函數類型的形參 所對應的的實參將被自動轉換爲指向相應函數類型的指針。

typedef int func(int * ,int);		//func是一個函數,而不是一個函數指針
void f1(func);						//函數的形參可以使函數,也可以是函數指針
//func f2(int);						// 錯誤寫法,函數的返回值必須是函數指針不能是函數類型
func * f3(int);						//正確,f3返回一個函數指針



指向重載函數的指針

c++允許使用函數指針指向重載的函數。
指針的類型必須和重載函數的一個版本精確匹配。如果不精確匹配,則對該指針的初始化或賦值都將會導致編譯報錯

// 重載的函數ff
extern void ff(std::vector<double>);
extern void ff(unsigned int);

//定義函數指針pf1, pf1指向參數爲unsigned int版本的函數ff
void (*pf1)(unsigned int) = &ff;		

// 錯誤示例: 參數不匹配
// void (*pf2)(int) = &ff;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章