指針
(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 = ⅈ
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 = ⅈ //錯誤做法,不可以修改指針的指向
*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)
思路:
- 首先我們知道a是一個數組,數組類型先忽略,即
a[10]
- 其次,來看數組的類型,是個指針,指向一個函數,即
int (*f)(int)
- 然後把數組類型帶進數組的聲明中,即
int (*a[10])(int)
聲明一個指向含有10個元素的數組的指針,其中每個元素是一個函數指針,該函數的返回值是int,參數是int *。
答案:
思路:
- 首先我們先完成含有10個元素數組的書寫,即 int (*a[10])(int *)
- 指向數組的指針,即 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;