虛函數、純虛函數詳解
ios
1.首先:強調一個概念
定義一個函數爲虛函數,不表明函數爲不被實現的函數。定義他爲虛函數是爲了容許用基類的指針來調用子類的這個函數。
定義一個函數爲純虛函數,才表明函數沒有被實現。定義他是爲了實現一個接口,起到一個規範的做用,規範繼承這個。類的程序員必須實現這個函數。c++
2.關於實例化一個類:
有純虛函數的類是不可能生成類對象的,若是沒有純虛函數則能夠。好比:
class CA
{
public:
virtual void fun() = 0; // 說明fun函數爲純虛函數
virtual void fun1();
};程序員
class CB
{
public:
virtual void fun();
virtual void fun1();
};web
// CA,CB類的實現
...面試
void main()
{
CA a; // 不容許,由於類CA中有純虛函數
CB b; // 能夠,由於類CB中沒有純虛函數
...
}編程
3.虛函數在多態中間的使用:
多態通常就是經過指向基類的指針來實現的。
多態性(polymorphism)是面向對象程序設計的一個重要特徵。若是一種語言只支持類而不支持多態,是不能被稱爲面嚮對象語言的,只能說是基於對象的,如Ada、VB就屬此類。C++支持多態性,在C++程序設計中可以實現多態性。利用多態性能夠設計和實現一個易於擴展的系統。數組
顧名思義,多態的意思是一個事物有多種形態。多態性的英文單詞polymorphism來源於希臘詞根poly(意爲「不少」)和morph(意爲「形態」)。在C ++程序設計中,多態性是指具備不一樣功能的函數能夠用同一個函數名,這樣就能夠用一個函數名調用不一樣內容的函數。在面向對象方法中通常是這樣表述多態性的:向不一樣的對象發送同一個消息, 不一樣的對象在接收時會產生不一樣的行爲(即方法)。也就是說,每一個對象能夠用本身的方式去響應共同的消息。所謂消息,就是調用函數,不一樣的行爲就是指不一樣的實現,即執行不一樣的函數。安全
其實,咱們已經屢次接觸過多態性的現象,例如函數的重載、運算符重載都是多態現象。只是那時沒有用到多態性這一專門術語而已。例如,使用運算符「+」使兩個數值相加,就是發送一個消息,它要調用operator +函數。實際上,整型、單精度型、雙精度型的加法操做過程是互不相同的,是由不一樣內容的函數實現的。顯然,它們以不一樣的行爲或方法來響應同一消息。ide
在現實生活中能夠看到許多多態性的例子。如學校校長向社會發佈一個消息:9月1日新學年開學。不一樣的對象會做出不一樣的響應:學生要準備好課本準時到校上課;家長要籌集學費;教師要備好課;後勤部門要準備好教室、宿舍和食堂……因爲事先對各類人的任務已做了規定,所以,在獲得同一個消息時,各類人都知道本身應當怎麼作,這就是 多態性。能夠設想,若是不利用多態性,那麼校長就要分別給學生、家長、教師、後勤部門等許多不一樣的對象分別發通知,分別具體規定每一種人接到通知後應該怎麼作。顯然這是一件十分複雜而細緻的工做。一人包攬一切,吃力還不討好。如今,利用了多態性機制,校長在發佈消息時,沒必要一一具體考慮不一樣類型人員是怎樣執行的。至於各種人員在接到消息後應氣作什麼,並非臨時決定的,而是學校的工做機制事先安排決定好的。校長只需不斷髮布各類消息,各類人員就會按預約方案有條不紊地工做。函數
一樣,在C++程序設計中,在不一樣的類中定義了其響應消息的方法,那麼使用這些類 時,沒必要考慮它們是什麼類型,只要發佈消息便可。正如在使用運算符「 」時沒必要考慮相加的數值是整型、單精度型仍是雙精度型,直接使用「+」,不論哪類數值都能實現相加。能夠說這是以不變應萬變的方法,不論對象變幻無窮,用戶都是用同一形式的信息去調用它們,使它們根據事先的安排做出反應。
從系統實現的角度看,多態性分爲兩類:靜態多態性和動態多態性。之前學過的函數重載和運算符重載實現的多態性屬於靜態多態性,在程序編譯時系統就能決定調用的是哪一個函數,所以靜態多態性又稱編譯時的多態性。靜態多態性是經過函數的重載實現的(運算符重載實質上也是函數重載)。動態多態性是在程序運行過程當中才動態地肯定操做所針對的對象。它又稱運行時的多態性。動態多態性是經過虛函數(Virtual fiinction)實現的。
有關靜態多態性的應用,即函數的重載(請查看:C++函數重載)和運算符重載(請查看:C++運算符重載),已經介紹過了,這裏主要介紹動態多態性和虛函數。要研究的問題是:當一個基類被繼承爲不一樣的派生類時,各派生類可使用與基類成員相同的成員名,若是在運行時用同一個成員名調用類對象的成員,會調用哪一個對象的成員?也就是說,經過繼承而產生了相關的不一樣的派生類,與基類成員同名的成員在不一樣的派生類中有不一樣的含義。也能夠說,多態性是「一個接口,多種 方法」。
下面是一個承上啓下的例子。一方面它是有關繼承和運算符重載內容的綜合應用的例子,經過這個例子能夠進一步融會貫通前面所學的內容,另外一方面又是做爲討論多態性的一個基礎用例。
但願你們耐心、深刻地閱讀和消化這個程序,弄清其中的每個細節。
[例12.1] 先創建一個Point(點)類,包含數據成員x,y(座標點)。以它爲基類,派生出一個Circle(圓)類,增長數據成員r(半徑),再以Circle類爲直接基類,派生出一個Cylinder(圓柱體)類,再增長數據成員h(高)。要求編寫程序,重載運算符「<<」和「>>」,使之能用於輸出以上類對象。
這個例題難度不大,但程序很長。對於一個比較大的程序,應當分紅若干步驟進行。先聲明基類,再聲明派生類,逐級進行,分步調試。
1) 聲明基類Point
類可寫出聲明基類Point的部分以下:
#include <iostream>
//聲明類Point
class Point
{
public:
Point(float x=0,float y=0); //有默認參數的構造函數
void setPoint(float ,float); //設置座標值
float getX( )const {return x;} //讀x座標
float getY( )const {return y;} //讀y座標
friend ostream & operator <<(ostream &,const Point &); //重載運算符「<<」
protected: //受保護成員
float x, y;
};
//下面定義Point類的成員函數
Point::Point(float a,float b) //Point的構造函數
{ //對x,y初始化
x=a;
y=b;
}
void Point::setPoint(float a,float b) //設置x和y的座標值
{ //爲x,y賦新值
x=a;
y=b;
}
//重載運算符「<<」,使之能輸出點的座標
ostream & operator <<(ostream &output, const Point &p)
{
output<<"["<<p.x<<","<<p.y<<"]"<<endl;
return output;
}
以上完成了基類Point類的聲明。
爲了提升程序調試的效率,提倡對程序分步調試,不要將一個長的程序都寫完之後才統一調試,那樣在編譯時可能會同時出現大量的編譯錯誤,面對一個長的程序,程序人員每每難以迅速準確地找到出錯位置。要善於將一個大的程序分解爲若干個文件,分別編譯,或者分步調試,先經過最基本的部分,再逐步擴充。
如今要對上面寫的基類聲明進行調試,檢查它是否有錯,爲此要寫出main函數。實際上它是一個測試程序。
int main( )
{
Point p(3.5,6.4); //創建Point類對象p
cout<<"x="<<p.getX( )<<",y="<<p.getY( )<<endl; //輸出p的座標值
p.setPoint(8.5,6.8); //從新設置p的座標值
cout<<"p(new):"<<p<<endl; //用重載運算符「<<」輸出p點座標
return 0;
}
getX和getY函數聲明爲常成員函數,做用是隻容許函數引用類中的數據,而不容許修改它們,以保證類中數據的安全。數據成員x和y聲明爲protected,這樣能夠被派生類訪問(若是聲明爲private,派生類是不能訪問的)。
程序編譯經過,運行結果爲:
x=3.5,y=6.4
p(new):[8.5,6.8]
測試程序檢查了基類中各函數的功能,以及運算符重載的做用,證實程序是正確的。
2)聲明派生類Circle
在上面的基礎上,再寫出聲明派生類Circle的部分:
class Circle:public Point //circle是Point類的公用派生類
{
public:
Circle(float x=0,float y=0,float r=0); //構造函數
void setRadius(float ); //設置半徑值
float getRadius( )const; //讀取半徑值
float area ( )const; //計算圓面積
friend ostream &operator <<(ostream &,const Circle &); //重載運算符「<<」
private:
float radius;
};
//定義構造函數,對圓心座標和半徑初始化
Circle::Circle(float a,float b,float r):Point(a,b),radius(r){}
//設置半徑值
void Circle::setRadius(float r){radius=r;}
//讀取半徑值
float Circle::getRadius( )const {return radius;}
//計算圓面積
float Circle::area( )const
{
return 3.14159*radius*radius;
}
//重載運算符「<<」,使之按規定的形式輸出圓的信息
ostream &operator <<(ostream &output,const Circle &c)
{
output<<"Center=["<<c.x<<","<<c.y<<"],r="<<c.radius<<",area="<<c.area( )<<endl;
return output;
}
爲了測試以上Circle類的定義,能夠寫出下面的主函數:
int main( )
{
Circle c(3.5,6.4,5.2); //創建Circle類對象c,並給定圓心座標和半徑
cout<<"original circle:\\nx="<<c.getX()<<", y="<<c.getY()<<", r="<<c.getRadius( )<<", area="<<c.area( )<<endl; //輸出圓心座標、半徑和麪積
c.setRadius(7.5); //設置半徑值
c.setPoint(5,5); //設置圓心座標值x,y
cout<<"new circle:\\n"<<c; //用重載運算符「<<」輸出圓對象的信息
Point &pRef=c; //pRef是Point類的引用變量,被c初始化
cout<<"pRef:"<<pRef; //輸出pRef的信息
return 0;
}
程序編譯經過,運行結果爲:
original circle:(輸出原來的圓的數據)
x=3.5, y=6.4, r=5.2, area=84.9486
new circle:(輸出修改後的圓的數據)
Center=[5,5], r=7.5, area=176.714
pRef:[5,5] (輸出圓的圓心「點」的數據)
能夠看到,在Point類中聲明瞭一次運算符「 <<」重載函數,在Circle類中又聲明瞭一次運算符「 <<」,兩次重載的運算符「<<」內容是不一樣的,在編譯時編譯系統會根據輸出項的類型肯定調用哪個運算符重載函數。main函數第7行用「cout<< 」輸出c,調用的是在Circle類中聲明的運算符重載函數。
請注意main函數第8行:
Point & pRef = c;
定義了 Point類的引用變量pRef,並用派生類Circle對象c對其初始化。前面咱們已經講過,派生類對象能夠替代基類對象爲基類對象的引用初始化或賦值(詳情請查看:C++基類與派生類的轉換)。如今 Circle是Point的公用派生類,所以,pRef不能認爲是c的別名,它獲得了c的起始地址, 它只是c中基類部分的別名,與c中基類部分共享同一段存儲單元。因此用「cout<<pRef」輸出時,調用的不是在Circle中聲明的運算符重載函數,而是在Point中聲明的運算符重載函數,輸出的是「點」的信息,而不是「圓」的信息。
3) 聲明Circle的派生類Cylinder
前面已從基類Point派生出Circle類,如今再從Circle派生出Cylinder類。
class Cylinder:public Circle// Cylinder是Circle的公用派生類
{
public:
Cylinder (float x=0,float y=0,float r=0,float h=0); //構造函數
void setHeight(float ); //設置圓柱高
float getHeight( )const; //讀取圓柱高
loat area( )const; //計算圓表面積
float volume( )const; //計算圓柱體積
friend ostream& operator <<(ostream&,const Cylinder&); //重載運算符<<
protected:
float height;//圓柱高
};
//定義構造函數
Cylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h){}
//設置圓柱高
void Cylinder::setHeight(float h){height=h;}
//讀取圓柱高
float Cylinder::getHeight( )const {return height;}
//計算圓表面積
float Cylinder::area( )const { return 2*Circle::area( )+2*3.14159*radius*height;}
//計算圓柱體積
float Cylinder::volume()const {return Circle::area()*height;}
ostream &operator <<(ostream &output,const Cylinder& cy)
{
output<<"Center=["<<cy.x<<","<<cy.y<<"],r="<<cy.radius<<",h="<<cy.height <<"\\narea="<<cy.area( )<<", volume="<<cy.volume( )<<endl;
return output;
} //重載運算符「<<」
能夠寫出下面的主函數:
int main( )
{
Cylinder cy1(3.5,6.4,5.2,10);//定義Cylinder類對象cy1
cout<<"\\noriginal cylinder:\\nx="<<cy1.getX( )<<", y="<<cy1.getY( )<<", r="
<<cy1.getRadius( )<<", h="<<cy1.getHeight( )<<"\\narea="<<cy1.area()
<<",volume="<<cy1.volume()<<endl;//用系統定義的運算符「<<」輸出cy1的數據
cy1.setHeight(15);//設置圓柱高
cy1.setRadius(7.5);//設置圓半徑
cy1.setPoint(5,5);//設置圓心座標值x,y
cout<<"\\nnew cylinder:\\n"<<cy1;//用重載運算符「<<」輸出cy1的數據
Point &pRef=cy1;//pRef是Point類對象的引用變量
cout<<"\\npRef as a Point:"<<pRef;//pRef做爲一個「點」輸出
Circle &cRef=cy1;//cRef是Circle類對象的引用變量
cout<<"\\ncRef as a Circle:"<<cRef;//cRef做爲一個「圓」輸出
return 0;
}
運行結果以下:
original cylinder:(輸出cy1的初始值)
x=3.5, y=6.4, r=5.2, h=10 (圓心座標x,y。半徑r,高h)
area=496.623, volume=849.486 (圓柱表面積area和體積volume)
new cylinder: (輸出cy1的新值)
Center=[5,5], r=7.5, h=15 (以[5,5]形式輸出圓心座標)
area=1060.29, volume=2650.72(圓柱表面積area和體積volume)
pRef as a Point:[5,5] (pRef做爲一個「點」輸出)
cRef as a Circle:Center=[5,5], r=7.5, area=176.714(cRef做爲一個「圓」輸出)
說明:在Cylinder類中定義了 area函數,它與Circle類中的area函數同名,根據前面咱們講解的同名覆蓋的原則(詳情請查看:C++多重繼承的二義性問題),cy1.area( ) 調用的是Cylinder類的area函數(求圓柱表面積),而不是Circle類的area函數(圓面積)。請注意,這兩個area函數不是重載函數,它們不只函數名相同,並且函數類型和參數個數都相同,兩個同名函數不在同 —個類中,而是分別在基類和派生類中,屬於同名覆蓋。重載函數的參數個數和參數類型必須至少有一者不一樣,不然系統沒法肯定調用哪個函數。
main函數第9行用「cout<<cy1」來輸出cy1,此時調用的是在Cylinder類中聲明的重載運算符「<<」,按在重載時規定的方式輸出圓柱體cy1的有關數據。
main函數中最後4行的含義與在定義Circle類時的狀況相似。pRef是Point類的引用變量,用cy1對其初始化,但它不是cy1的別名,只是cy1中基類Point部分的別名,在輸出pRef時是做爲一個Point類對象輸出的,也就是說,它是一個「點」。一樣,cRef是Circle類的引用變量,用cy1對其初始化,但它只是cy1中的直接基類Circle部分的別名, 在輸出 cRef 時是做爲Circle類對象輸出的,它是一個"圓」,而不是一個「圓柱體」。從輸 出的結果能夠看出調用的是哪一個運算符函數。
在本例中存在靜態多態性,這是運算符重載引發的(注意3個運算符函數是重載而不是同名覆蓋,由於有一個形參類型不一樣)。能夠看到,在編譯時編譯系統便可以斷定應調用哪一個重載運算符函數。
4.有一點你必須明白,就是用父類的指針在運行時刻來調用子類:
例如,有個函數是這樣的:
void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}
參數maybedog_maybehorse在編譯時刻並不知道傳進來的是dog類仍是horse類,因此就把它設定爲animal類,具體到運行時決定了才決定用那個函數。也就是說用父類指針經過虛函數來決定運行時刻究竟是誰而指向誰的函數。
5.用虛函數
#include <iostream.h>
class animal
{
public:
animal();
~animal();
void fun1(animal *maybedog_maybehorse);
virtual void born();
};
void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}
animal::animal() { }
animal::~animal() { }
void animal::born()
{
cout<< "animal";
}
///////////////////////horse
class horse:public animal
{
public:
horse();
~horse();
virtual void born();
};
horse::horse() { }
horse::~horse() { }
void horse::born()
{
cout<<"horse";
}
///////////////////////main
void main()
{
animal a;
horse b;
a.fun1(&b);
}
上面定義的horse類是基類animal的派生,animal是他的繼承現講解繼承和派生
假設已經聲明瞭一個基類Student(基類Student的定義見上節:C++繼承與派生的概念),在此基礎上經過單繼承創建一個派生類Student1:
class Student1: public Student //聲明基類是Student
{
public:
void display_1( ) //新增長的成員函數
{
cout<<"age: "<<age<<endl;
cout<<"address: "<<addr<<endl;
}
private:
int age; //新增長的數據成員
string addr; //新增長的數據成員
};
仔細觀察第一行:
class Student1: public Student
在class後面的Student1是新建的類名,冒號後面的Student表示是已聲明的基類。在Student以前有一關鍵宇public,用來表示基類Student中的成員在派生類Studeml中的繼承方式。基類名前面有public的稱爲「公用繼承(public inheritance)」。
請你們仔細閱讀以上聲明的派生類Student1和基類Student,並將它們放在一塊兒進行分析。
聲明派生類的通常形式爲:
class 派生類名:[繼承方式] 基類名
{
派生類新增長的成員
};
繼承方式包括public (公用的)、private (私有的)和protected(受保護的),此項是可選的,若是不寫此項,則默認爲private(私有的)。
採用公用繼承方式時,基類的公用成員和保護成員在派生類中仍然保持其公用成員和保護成員的屬性,而基類的私有成員在派生類中並無成爲派生類的私有成員,它仍然是基類的私有成員,只有基類的成員函數能夠引用它,而不能被派生類的成員函數引用,所以就成爲派生類中的不可訪問的成員。
私有基類的公用成員和保護成員在派生類中的訪問屬性至關於派生類中的私有成員,即派生類的成員函數能訪問它們,而在派生類外不能訪問它們。私有基類的私有成員在派生類中成爲不可訪問的成員,只有基類的成員函數能夠引用它們。一個基類成員在基類中的訪問屬性和在派生類中的訪問屬性多是不一樣的。
//output: horse
6.不用虛函數
#include <iostream.h>
class animal
{
public:
animal();
~animal();
void fun1(animal *maybedog_maybehorse);
void born();
};
void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}
animal::animal() { }
animal::~animal() { }
void animal::born()
{
cout<< "animal";
}
////////////////////////horse
class horse:public animal
{
public:
horse();
~horse();
void born();
};
horse::horse() { }
horse::~horse() { }
void horse::born()
{
cout<<"horse";
}
////////////////////main
void main()
{
animal a;
horse b;
a.fun1(&b);
}
//output: animal
運算符重載
運算符重載的方法是定義一個重載運算符的函數,在須要執行被重載的運算符時,系統就自動調用該函數,以實現相應的運算。也就是說,運算符重載是經過定義函數實現的。運算符重載實質上是函數的重載。
重載運算符的函數通常格式以下:
函數類型 operator 運算符名稱 (形參表列)
{
// 對運算符的重載處理
}
例如,想將」+」用於Complex類(複數)的加法運算,函數的原型能夠是這樣的:
Complex operator+ (Complex& c1, Complex& c2);
在上面的通常格式中,operator是關鍵字,是專門用於定義重載運算符的函數的,運算符名稱就是C++提供給用戶的預約義運算符。注意,函數名是由operator和運算符組成,上面的operator+就是函數名,意思是「對運算符+重載」。只要掌握這點,就能夠發現,這 類函數和其餘函數在形式上沒有什麼區別。兩個形參是Complex類對象的引用,要求實參爲Complex類對象。
在定義了重載運算符的函數後,能夠說,函數operator +重載了運算符+。在執行復數相加的表達式c1 + c2時(假設c1和c2都已被定義爲Complex類對象),系統就會調用operator+函數,把c1和c2做爲實參,與形參進行虛實結合。
爲了說明在運算符重載後,執行表達式就是調用函數的過程,能夠把兩個整數相加也想像爲調用下面的函數:
int operator + (int a, int b)
{
return (a+b);
}
若是有表達式5+8,就調用此函數,將5和8做爲調用函數時的實參,函數的返回值爲13。這就是用函數的方法理解運算符。能夠在例10.1程序的基礎上重載運算符「+」,使之用於複數相加。
[例10.2] 改寫例10.1,重載運算符「+」,使之能用於兩個複數相加。
#include <iostream>
using namespace std;
class Complex
{
public:
Complex( ){real=0;imag=0;}
Complex(double r,double i){real=r;imag=i;}
Complex operator+(Complex &c2);//聲明重載運算符的函數
void display( );
private:
double real;
double imag;
};
Complex Complex::operator+(Complex &c2) //定義重載運算符的函數
{
Complex c;
c.real=real+c2.real;
c.imag=imag+c2.imag;
return c;
}
void Complex::display( )
{
cout<<"("<<real<<","<<imag<<"i)"<<endl;
}
int main( )
{
Complex c1(3,4),c2(5,-10),c3;
c3=c1+c2; //運算符+用於複數運算
cout<<"c1=";c1.display( );
cout<<"c2=";c2.display( );
cout<<"c1+c2=";c3.display( );
return 0;
}
運行結果與例10.1相同:
c1=(3+4i)
c2=(5-10i)
c1+c2=(8,-6i)
請比較例10.1和例10.2,只有兩處不一樣:
1) 在例10.2中以operator+函數取代了例10.1中的complex_add函數,並且只是函數名不一樣,函數體和函數返回值的類型都是相同的。
2) 在main函數中,以「c3=c1+c2;」取代了例10.1中的「c3=c1.complex_add(c2);」。在將運算符+重載爲類的成員函數後,C++編譯系統將程序中的表達式c1+c2解釋爲
c1.operator+(c2) //其中c1和c2是Complex類的對象
即以c2爲實參調用c1的運算符重載函數operator+(Complex &c2),進行求值,獲得兩個複數之和。
能夠看到,兩個程序的結構和執行過程基本上是相同的,做用相同,運行結果也相同。重載運算符是由相應的函數實現的。有人可能說,既然這樣,何須對運算符重載呢?咱們要從用戶的角度來看問題,雖然重載運算符所實現的功能徹底能夠用函數實現,可是使用運算符重載能使用戶程序易於編寫、閱讀和維護。在實際工做中,類的聲明和類的使用每每是分離的。假如在聲明Complex類時,對運算符+, -, *, /都進行了重載,那麼使用這個類的用戶在編程時能夠徹底不考慮函數是怎麼實現的,放心大膽地直接使用+, -, *, /進行復數的運算便可,十分方便。
對上面的運算符重載函數operator+還能夠改寫得更簡練一些:
Complex Complex::operator + (Complex &c2)
{return Complex(real+c2.real, imag+c2.imag);}
return語句中的Complex( real+c2.real, imag+c2.imag)是創建一個臨時對象,它沒有對名,是一個無名對象。在創建臨時對象過程當中調用構造函數。return語句將此臨時對象做爲函數返回值。
請思考,在例10.2中可否將一個常量和一個複數對象相加?如
c3=3+c2; //錯誤,與形參類型不匹配
應寫成對象形式,如
c3 = Complex (3,0) +c2; //正確,類型均爲對象
須要說明的是,運算符被重載後,其原有的功能仍然保留,沒有喪失或改變。經過運算符重載,擴大了C++已有運算符的做用範圍,使之能用於類對象。
運算符重載對C++有重要的意義,把運算符重載和類結合起來,能夠在C++程序中定義出頗有實用意義而使用方便的新的數據類型。運算符重載使C++具備更強大的功能、更好的可擴充性和適應性,這是C++最吸引人的特色之一。
引用
C++中的引用
【導讀】介紹C++引用的基本概念,經過詳細的應用分析與說明,對引用進行全面、透徹地闡述
引用是C++引入的新語言特性,是C++經常使用的一個重要內容之一,正確、靈活地使用引用,可使程序簡潔、高效。
引用簡介
引用就是某一變量(目標)的一個別名,對引用的操做與對變量直接操做徹底同樣。
引用的聲明方法:類型標識符 &引用名=目標變量名;
【例1】:int a; int &ra=a; //定義引用ra,它是變量a的引用,即別名
說明:
(1)&在此不是求地址運算,而是起標識做用。
(2)類型標識符是指目標變量的類型。
(3)聲明引用時,必須同時對其進行初始化。
(4)引用聲明完畢後,至關於目標變量名有兩個名稱,即該目標原名稱和引用名,且不能再把該引用名做爲其餘變量名的別名。
ra=1; 等價於 a=1;
(5)聲明一個引用,不是新定義了一個變量,它只表示該引用名是目標變量名的一個別名,它自己不是一種數據類型,所以引用自己不佔存儲單元,系統也不給引用分配存儲單元。故:對引用求地址,就是對目標變量求地址。&ra與&a相等。
(6)不能創建數組的引用。由於數組是一個由若干個元素所組成的集合,因此沒法創建一個數組的別名。
引用應用
一、引用做爲參數
引用的一個重要做用就是做爲函數的參數。之前的C語言中函數參數傳遞是值傳遞,若是有大塊數據做爲參數傳遞的時候,採用的方案每每是指針,由於這樣能夠避免將整塊數據所有壓棧,能夠提升程序的效率。可是如今(C++中)又增長了一種一樣有效率的選擇(在某些特殊狀況下又是必須的選擇),就是引用。
【例2】:
void swap(int &p1, int &p2) //此處函數的形參p1, p2都是引用
{ int p; p=p1; p1=p2; p2=p; }
爲在程序中調用該函數,則相應的主調函數的調用點處,直接以變量做爲實參進行調用便可,而不須要實參變量有任何的特殊要求。如:對應上面定義的swap函數,相應的主調函數可寫爲:
main( )
{
int a,b;
cin>>a>>b; //輸入a,b兩變量的值
swap(a,b); //直接以變量a和b做爲實參調用swap函數
cout<<a<< ' ' <<b; //輸出結果
}
上述程序運行時,若是輸入數據10 20並回車後,則輸出結果爲20 10。
由【例2】可看出:
(1)傳遞引用給函數與傳遞指針的效果是同樣的。這時,被調函數的形參就成爲原來主調函數中的實參變量或對象的一個別名來使用,因此在被調函數中對形參變量的操做就是對其相應的目標對象(在主調函數中)的操做。
(2)使用引用傳遞函數的參數,在內存中並無產生實參的副本,它是直接對實參操做;而使用通常變量傳遞函數的參數,當發生函數調用時,須要給形參分配存儲單元,形參變量是實參變量的副本;若是傳遞的是對象,還將調用拷貝構造函數。所以,當參數傳遞的數據較大時,用引用比用通常變量傳遞參數的效率和所佔空間都好。
(3)使用指針做爲函數的參數雖然也能達到與使用引用的效果,可是,在被調函數中一樣要給形參分配存儲單元,且須要重複使用"*指針變量名"的形式進行運算,這很容易產生錯誤且程序的閱讀性較差;另外一方面,在主調函數的調用點處,必須用變量的地址做爲實參。而引用更容易使用,更清晰。
若是既要利用引用提升程序的效率,又要保護傳遞給函數的數據不在函數中被改變,就應使用常引用。
二、常引用
常引用聲明方式:const 類型標識符 &引用名=目標變量名;
用這種方式聲明的引用,不能經過引用對目標變量的值進行修改,從而使引用的目標成爲const,達到了引用的安全性。
【例3】:
int a ;
const int &ra=a;
ra=1; //錯誤
a=1; //正確
這不光是讓代碼更健壯,也有些其它方面的須要。
【例4】:假設有以下函數聲明:
string foo( );
void bar(string & s);
那麼下面的表達式將是非法的:
bar(foo( ));
bar("hello world");
緣由在於foo( )和"hello world"串都會產生一個臨時對象,而在C++中,這些臨時對象都是const類型的。所以上面的表達式就是試圖將一個const類型的對象轉換爲非const類型,這是非法的。
引用型參數應該在能被定義爲const的狀況下,儘可能定義爲const 。
三、引用做爲返回值
要以引用返回函數值,則函數定義時要按如下格式:
類型標識符 &函數名(形參列表及類型說明)
{函數體}
說明:
(1)以引用返回函數值,定義函數時須要在函數名前加&
(2)用引用
返回一個函數值的最大好處是,在內存中不產生被返回值的副本。
【例5】如下程序中定義了一個普通的函數fn1(它用返回值的方法返回函數值),另一個函數fn2,它以引用的方法返回函數值。
#i nclude <iostream.h>
float temp; //定義全局變量temp
float fn1(float r); //聲明函數fn1
float &fn2(float r); //聲明函數fn2
float fn1(float r) //定義函數fn1,它以返回值的方法返回函數值
{
temp=(float)(r*r*3.14);
return temp;
}
float &fn2(float r) //定義函數fn2,它以引用方式返回函數值
{
temp=(float)(r*r*3.14);
return temp;
}
void main() //主函數
{
float a=fn1(10.0); //第1種狀況,系統生成要返回值的副本(即臨時變量)
float &b=fn1(10.0); //第2種狀況,可能會出錯(不一樣 C++系統有不一樣規定)
//不能從被調函數中返回一個臨時變量或局部變量的引用
float c=fn2(10.0); //第3種狀況,系統不生成返回值的副本
//能夠從被調函數中返回一個全局變量的引用
float &d=fn2(10.0); //第4種狀況,系統不生成返回值的副本
//能夠從被調函數中返回一個全局變量的引用
cout<<a<<c<<d;
}
引用做爲返回值,必須遵照如下規則:
(1)不能返回局部變量的引用。這條能夠參照Effective C++[1]的Item 31。主要緣由是局部變量會在函數返回後被銷燬,所以被返回的引用就成爲了"無所指"的引用,程序會進入未知狀態。
(2)不能返回函數內部new分配的內存的引用。這條能夠參照Effective C++[1]的Item 31。雖然不存在局部變量的被動銷燬問題,可對於這種狀況(返回函數內部new分配內存的引用),又面臨其它尷尬局面。例如,被函數返回的引用只是做爲一個臨時變量出現,而沒有被賦予一個實際的變量,那麼這個引用所指向的空間(由new分配)就沒法釋放,形成memory leak。
(3)能夠返回類成員的引用,但最好是const。這條原則能夠參照Effective C++[1]的Item 30。主要緣由是當對象的屬性是與某種業務規則(business rule)相關聯的時候,其賦值經常與某些其它屬性或者對象的狀態有關,所以有必要將賦值操做封裝在一個業務規則當中。若是其它對象能夠得到該屬性的很是量引用(或指針),那麼對該屬性的單純賦值就會破壞業務規則的完整性。
(4)引用與一些操做符的重載:
流操做符<<和>>,這兩個操做符經常但願被連續使用,例如:cout << "hello" << endl; 所以這兩個操做符的返回值應該是一個仍然支持這兩個操做符的流引用。可選的其它方案包括:返回一個流對象和返回一個流對象指針。可是對於返回一個流對象,程序必須從新(拷貝)構造一個新的流對象,也就是說,連續的兩個<<操做符其實是針對不一樣對象的!這沒法讓人接受。對於返回一個流指針則不能連續使用<<操做符。所以,返回一個流對象引用是唯一選擇。這個惟一選擇很關鍵,它說明了引用的重要性以及無可替代性,也許這就是C++語言中引入引用這個概念的緣由吧。 賦值操做符=。這個操做符象流操做符同樣,是能夠連續使用的,例如:x = j = 10;或者(x=10)=100;賦值操做符的返回值必須是一個左值,以即可以被繼續賦值。所以引用成了這個操做符的唯一返回值選擇。
【例6】 測試用返回引用的函數值做爲賦值表達式的左值。
#i nclude <iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; //以put(0)函數值做爲左值,等價於vals[0]=10;
put(9)=20; //以put(9)函數值做爲左值,等價於vals[9]=10;
cout<<vals[0];
cout<<vals[9];
}
int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n];
else { cout<<"subscript error"; return error; }
}
(5)在另外的一些操做符中,卻千萬不能返回引用:+-*/ 四則運算符。它們不能返回引用,Effective C++[1]的Item23詳細的討論了這個問題。主要緣由是這四個操做符沒有side effect,所以,它們必須構造一個對象做爲返回值,可選的方案包括:返回一個對象、返回一個局部變量的引用,返回一個new分配的對象的引用、返回一個靜態對象引用。根據前面提到的引用做爲返回值的三個規則,第二、3兩個方案都被否決了。靜態對象的引用又由於((a+b) == (c+d))會永遠爲true而致使錯誤。因此可選的只剩下返回一個對象了。
四、引用和多態
引用是除指針外另外一個能夠產生多態效果的手段。這意味着,一個基類的引用能夠指向它的派生類實例。
【例7】:
class A;
class B:public A{……};
B b;
A &Ref = b; // 用派生類對象初始化基類對象的引用
Ref 只能用來訪問派生類對象中從基類繼承下來的成員,是基類引用指向派生類。若是A類中定義有虛函數,而且在B類中重寫了這個虛函數,就能夠經過Ref產生多態效果。
引用總結
(1)在引用的使用中,單純給某個變量取個別名是毫無心義的,引用的目的主要用於在函數參數傳遞中,解決大塊數據或對象的傳遞效率和空間不如意的問題。
(2)用引用傳遞函數的參數,能保證參數傳遞中不產生副本,提升傳遞的效率,且經過const的使用,保證了引用傳遞的安全性。
(3)引用與指針的區別是,指針經過某個指針變量指向一個對象後,對它所指向的變量間接操做。程序中使用指針,程序的可讀性差;而引用自己就是目標變量的別名,對引用的操做就是對目標變量的操做。
(4)使用引用的時機。流操做符<<和>>、賦值操做符=的返回值、拷貝構造函數的參數、賦值操做符=的參數、其它狀況都推薦使用引用。
什麼是變量的引用
對一個數據可使用「引用(reference)」,這是C++對C的一個重要擴充,引用是一種新的變量類型,它的做用是爲一個變量起一個別名。假若有一個變量a,想給它起一個別名b,能夠這樣寫:
int a; //定義a是整型變量
int &b=a; //聲明b是a的引用
以上語句聲明瞭b是a的引用,即b是a的別名。通過這樣的聲明後,a或b的做用相同,都表明同一變量。
注意: 在上述聲明中,&是引用聲明符,並不表明地址。不要理解爲「把a的值賦給b的地址」。聲明變量b爲引用類型,並不須要另外開闢內存單元來存放b的值。b和a佔內存中的同一個存儲單元,它們具備同一地址。聲明b是a的引用,能夠理解爲: 使變量b具備變量a的地址。見圖6.26,若是a的值是20,則b的值也是20。
圖6.26
在聲明一個引用類型變量時,必須同時使之初始化,即聲明它表明哪個變量。在聲明變量b是變量a的引用後,在它們所在函數執行期間,該引用類型變量b始終與其表明的變量a相聯繫,不能再做爲其餘變量的引用(別名)。下面的用法不對:
int a1, a2;
int &b=a1;
int &b=a2; //企圖使b又變成a2的引用(別名)是不行的
引用的簡單使用
【例6.17】引用和變量的關係。
#include <iostream>
#include <iomanip>
using namespace std;
int main( )
{
int a=10;
int &b=a; //聲明b是a的引用
a=a*a; //a的值變化了,b的值也應一塊兒變化
cout<<a<<setw(6)<<b<<endl;
b=b/5; //b的值變化了,a的值也應一塊兒變化
cout<<b<<setw(6)<<a<<endl;
return 0;
}
a的值開始爲10,b是a的引用,它的值固然也應該是10,當a的值變爲100(a*a的值)時,b的值也隨之變爲100。在輸出a和b的值後,b的值變爲20,顯然a的值也應爲20。運行記錄以下:
100 100 (a和b的值都是100)
20 20 (a和b的值都是20)
對象引用
在程序中常常須要訪問對象中的成員。訪問對象中的成員能夠有3種方法:
經過對象名和成員運算符訪問對象中的成員;
經過指向對象的指針訪問對象中的成員;
經過對象的引用變量訪問對象中的成員。
經過對象名和成員運算符訪問對象中的成員
例如在程序中能夠寫出如下語句:
stud1.num=1001; //假設num已定義爲公用的整型數據成員
表示將整數1001賦給對象stud1中的數據成員num。其中「.」是成員運算符,用來對成員進行限定,指明所訪問的是哪個對象中的成員。注意不能只寫成員名而忽略對象名。
訪問對象中成員的通常形式爲:
對象名.成員名
不只能夠在類外引用對象的公用數據成員,並且還能夠調用對象的公用成員函數,但一樣必須指出對象名,如:
stud1.display( ); //正確,調用對象stud1的公用成員函數
display( ); //錯誤,沒有指明是哪個對象的display函數
因爲沒有指明對象名,編譯時把display做爲普通函數處理。應該注意所訪問的成員是公用的(public )仍是私有的(private ),只能訪問public成員,而不能訪問private成員。若是已定義num爲私有數據成員,下面的語句是錯誤的:
stud1.num=10101; //num是私有數據成員,不能被外界引用
在類外只能調用公用的成員函數。在一個類中應當至少有一個公用的成員函數,做爲對外的接口,不然就沒法對對象進行任何操做。
經過指向對象的指針訪問對象中的成員
前面已經介紹了指向結構體變量的指針(詳情請猛擊:指向結構體變量的指針),能夠經過指針引用結構體中的成員。用指針訪問對象中的成員的方法與此相似。若是有如下程序段:
class Time
{
public : //數據成員是公用的
int hour;
int minute;
};
Time t, *p; //定義對象t和指針變量p
p=&t; //使p指向對象t
cout<<p->hour; //輸出p指向的對象中的成員hour
在p指向t的前提下,p->hour,(*p).hour和t.hour三者等價。
經過對象的引用變量來訪問對象中的成員
若是爲一個對象定義了一個引用變量,它們是共佔同一段存儲單元的,實際上它們是同一個對象,只是用不一樣的名字表示而已。所以徹底能夠經過引用變量來訪問對象中的成員。
若是已聲明瞭Time類,並有如下定義語句:
Time t1; //定義對象t1
Time &t2=t1; //定義Time類引用變量t2,並使之初始化爲t1
cout<<t2.hour; //輸出對象t1中的成員hour
因爲t2與t1共佔同一段存儲單元(即t2是t1的別名),所以t2.hour就是t1.hour。
結構體變量引用
在定義告終構體變量之後,固然能夠引用這個變量,經常使用的方法有如下幾種。
1) 能夠將一個結構體變量的值賦給另外一個具備相同結構的結構體變量。
如上面的student1和student2都是student類型的變量,能夠這樣賦值:
student1= student2;
2) 能夠引用一個結構體變量中的一個成員的值。
例如, student1.num表示結構體變量student1中的成員的值,若是student1的值如圖7.2所示,則student1.num的值爲10001。
引用結構體變量中成員的通常方式爲:
結構體變量名.成員名
例如能夠這樣對變量的成員賦值:
student1.num=10010;
3) 若是成員自己也是一個結構體類型,則要用若干個成員運算符,一級一級地找到最低一級的成員。
例如,對上面定義的結構體變量student1,能夠這樣訪問各成員:
student1.num (引用結構體變量student1中的num成員)
若是想引用student1變量中的birthday成員中的month成員,不能寫成student1.month,必須逐級引用,即
student1.birthday.month=12; (引用結構體變量student1中的birthday成員中的month成員)
4) 不能將一個結構體變量做爲一個總體進行輸入和輸出。
例如,已定義student1和student2爲結構體變量,而且它們已有值。不能企圖這樣輸出結構體變量中的各成員的值
cin>>student1;
只能對結構體變量中的各個成員分別進行輸入和輸出。
5) 對結構體變量的成員能夠像普通變量同樣進行各類運算(根據其類型決定能夠進行的運算種類)。例如:
student2.score=student1.score;
sum=student1.score+student2.score;
student1.age++;
++student1.age;
因爲「.」運算符的優先級最高,student1.age++至關於(student1.age)++ 。++是對student1.age進行自加運算,而不是先對age進行自加運算。
6) 能夠引用結構體變量成員的地址,也能夠引用結構體變量的地址。如:
cout<<&student1; //輸出student1的首地址
cout<<&student1.age; //輸出student1.age的地址
結構體變量的地址主要用做函數參數,將結構體變量的地址傳遞給形參。
【例7.1】引用結構體變量中的成員。
#include <iostream>
using namespace std;
struct Date//聲明結構體類型Date
{
int month;
int day;
int year;
};
struct Student//聲明結構體類型Student
{
int num;
char name[20];
char sex;
Date birthday; //聲明birthday爲Date類型的成員
float score;
}student1,student2={10002,"Wang Li",'f',5,23,1982,89.5};
//定義Student 類型的變量student1,student2,並對student2初始化
int main( )
{
student1=student2; //將student2各成員的值賦予student1的相應成員
cout<<student1.num<<endl; //輸出student1中的num成員的值
cout<<student1.name<<endl; //輸出student1中的name成員的值
cout<<student1.sex<<endl; //輸出student1中的sex成員的值
cout<<student1.birthday.month<<'/'<<student1.birthday.day<<'/' <<student1.birthday.year<<endl; //輸出student1中的birthday各成員的值
cout<<student1.score<<endl;
return 0;
}
運行結果以下:
10002
Wang Li
f
5/23/1982
89.5
關於引用和指針的區別的文章不少不少,可是老是找不到他們的根本區別,偶然在codeproject上看到這篇文章,以爲講的挺好的,
因此翻譯了下,但願對你們有幫助。
原文地址: http://www.codeproject.com/KB/cpp/References_in_c__.aspx
引言
我選擇寫 C++ 中的引用是由於我感受大多數人誤解了引用。而我之因此有這個感覺是由於我主持過不少 C++ 的面試,而且我不多從面試者中獲得關於 C++ 引用的正確答案。
那麼 c++ 中引用到底意味這什麼呢?一般一個引用讓人想到是一個引用的變量的別名,而我討厭將 c++ 中引用定義爲變量的別名。這篇文章中,我將盡可能解釋清楚, c++ 中根本就沒有什麼叫作別名的東東。
背景
在 c/c++ 中,訪問一個變量只能經過兩種方式被訪問,傳遞,或者查詢。這兩種方式是:
1. 經過值 訪問 / 傳遞變量
2. 經過地址 訪問 / 傳遞變量 – 這種方法就是指針
除此以外沒有第三種訪問和傳遞變量值的方法。引用變量也就是個指針變量,它也擁有內存空間。最關鍵的是引用是一種會被編譯器自動解引用的指針。很難相信麼?讓咱們來看看吧。。。
下面是一段使用引用的簡單 c++ 代碼
引用其實就是 c++ 中的常量指針。表達式 int &i = j; 將會被編譯器轉化成 int *const i = &j; 而引用之因此要初始化是由於 const 類型變量必須初始化,這個指針也必須有所指。下面咱們再次聚焦到上面這段代碼,並使用編譯器的那套語法將引用替換掉。
讀者必定很奇怪爲何我上面這段代碼會跳過打印地址這步。這裏須要一些解釋。由於引用變量時會被編譯器自動解引用的,那麼一個諸如 cout << &j << endl; 的語句,編譯器就會將其轉化成語句 cout << &*j << endl; 如今 &* 會相互抵消,這句話變的毫無心義,而 cout 打印的 j 值就是 i 的地址,由於其定義語句爲 int *const j = &i;
因此語句 cout << &i << &j << endl; 變成了 cout << &i << &*j << endl; 這兩種狀況都是打印輸出 i 的地址。這就是當咱們打印普通變量和引用變量的時候會輸出相同地址的緣由。
下面給出一段複雜一些的代碼,來看看引用在級聯 (cascading) 中是如何運做的。
下面這段代碼是將上面代碼中的引用替換以後代碼,也就是說明咱們不依賴編譯器的自動替換功能,手動進行替換也能達到相同的目標。
咱們經過下面代碼能夠證實 c++ 的引用不是神馬別名,它也會佔用內存空間的。
結論
我但願這篇文章能把 c++ 引用的全部東東都解釋清楚,然而我要指出的是 c++ 標準並無解釋編譯器如何實現引用的行爲。因此實現取決於編譯器,而大多數狀況下就是將其實現爲一個 const 指針。
引用支持 c++ 虛函數機制的代碼
上述代碼使用引用支持虛函數機制。若是引用僅僅是一個別名,那如何實現虛函數機制,而虛函數機制所須要的動態信息只能經過指針才能實現,因此更加說明引用其實就是一個 const 指針。
函數指針與指針函數
1、
在學習arm過程當中發現這「指針函數」與「函數指針」容易搞錯,因此今天,我本身想一次把它搞清楚,找了一些資料,首先它們之間的定義:
一、指針函數是指帶指針的函數,即本質是一個函數。函數返回類型是某一類型的指針
類型標識符 *函數名(參數表)
int *f(x,y);
首先它是一個函數,只不過這個函數的返回值是一個地址值。函數返回值必須用同類型的指針變量來接受,也就是說,指針函數必定有函數返回值,並且,在主調函數中,函數返回值必須賦給同類型的指針變量。
表示:
float *fun();
float *p;
p = fun(a);
注意指針函數與函數指針表示方法的不一樣,千萬不要混淆。最簡單的辨別方式就是看函數名前面的指針*號有沒有被括號()包含,若是被包含就是函數指針,反之則是指針函數。
來說詳細一些吧!請看下面
指針函數:
當一個函數聲明其返回值爲一個指針時,實際上就是返回一個地址給調用函數,以用於須要指針或地址的表達式中。
格式:
類型說明符 * 函數名(參數)
固然了,因爲返回的是一個地址,因此類型說明符通常都是int。
例如:int *GetDate();
int * aaa(int,int);
函數返回的是一個地址值,常用在返回數組的某一元素地址上。
int * GetDate(int wk,int dy);
main()
{
int wk,dy;
do
{
printf(Enter week(1-5)day(1-7)\n);
scanf(%d%d,&wk,&dy);
}
while(wk<1||wk>5||dy<1||dy>7);
printf(%d\n,*GetDate(wk,dy));
}
int * GetDate(int wk,int dy)
{
static int calendar[5][7]=
{
{1,2,3,4,5,6,7},
{8,9,10,11,12,13,14},
{15,16,17,18,19,20,21},
{22,23,24,25,26,27,28},
{29,30,31,-1}
};
return &calendar[wk-1][dy-1];
}
程序應該是很好理解的,子函數返回的是數組某元素的地址。輸出的是這個地址裏的值。
二、函數指針是指向函數的指針變量,即本質是一個指針變量。
int (*f) (int x); /* 聲明一個函數指針 */
f=func; /* 將func函數的首地址賦給指針f */
指向函數的指針包含了函數的地址,能夠經過它來調用函數。聲明格式以下:
類型說明符 (*函數名)(參數)
其實這裏不能稱爲函數名,應該叫作指針的變量名。這個特殊的指針指向一個返回整型值的函數。指針的聲明筆削和它指向函數的聲明保持一致。
指針名和指針運算符外面的括號改變了默認的運算符優先級。若是沒有圓括號,就變成了一個返回整型指針的函數的原型聲明。
例如:
void (*fptr)();
把函數的地址賦值給函數指針,能夠採用下面兩種形式:
fptr=&Function;
fptr=Function;
取地址運算符&不是必需的,由於單單一個函數標識符就標號表示了它的地址,若是是函數調用,還必須包含一個圓括號括起來的參數表。
能夠採用以下兩種方式來經過指針調用函數:
x=(*fptr)();
x=fptr();
第二種格式看上去和函數調用無異。可是有些程序員傾向於使用第一種格式,由於它明確指出是經過指針而非函數名來調用函數的。下面舉一個例子:
void (*funcp)();
void FileFunc(),EditFunc();
main()
{
funcp=FileFunc;
(*funcp)();
funcp=EditFunc;
(*funcp)();
}
void FileFunc()
{
printf(FileFunc\n);
}
void EditFunc()
{
printf(EditFunc\n);
}
程序輸出爲:
FileFunc
EditFunc
主要的區別是一個是指針變量,一個是函數。在使用是必要要搞清楚才能正確使用
2、指針的指針
指針的指針看上去有些使人費解。它們的聲明有兩個星號。例如:
char ** cp;
若是有三個星號,那就是指針的指針的指針,四個星號就是指針的指針的指針的指針,依次類推。當你熟悉了簡單的例子之後,就能夠應付複雜的狀況了。固然,實際程序中,通常也只用到 二級指針,三個星號不常見,更別說四個星號了。
指針的指針須要用到指針的地址。
char c='A';
char *p=&c;
char **cp=&p;
經過指針的指針,不只能夠訪問它指向的指針,還能夠訪問它指向的指針所指向的數據。下面就是幾個這樣的例子:
char *p1=*cp;
char c1=**cp;
你可能想知道這樣的結構有什麼用。利用指針的指針能夠容許被調用函數修改局部指針變量和處理指針數組。
void FindCredit(int **);
main()
{
int vals[]={7,6,5,-4,3,2,1,0};
int *fp=vals;
FindCredit(&fp);
printf(%d\n,*fp);
}
void FindCredit(int ** fpp)
{
while(**fpp!=0)
if(**fpp<0) break;
else (*fpp)++;
}
首先用一個數組的地址初始化指針fp,而後把該指針的地址做爲實參傳遞給函數FindCredit()。FindCredit()函數經過表達式**fpp間接地獲得數組中的數據。爲遍歷數組以找到一個負值,FindCredit()函數進行自增運算的對象是調用者的指向數組的指針,而不是它本身的指向調用者指針的指針。語句(*fpp)++就是對形參指針指向的指針進行自增運算的。可是由於*運算符高於++運算符,因此圓括號在這裏是必須的,若是沒有圓括號,那麼++運算符將做用於二重指針fpp上。
3、指向指針數組的指針
指針的指針另外一用法舊處理指針數組。有些程序員喜歡用指針數組來代替多維數組,一個常見的用法就是處理字符串。
char *Names[]=
{
Bill,
Sam,
Jim,
Paul,
Charles,
0
};
main()
{
char **nm=Names;
while(*nm!=0) printf(%s\n,*nm++);
}
先用字符型指針數組Names的地址來初始化指針nm。每次printf()的調用都首先傳遞指針nm指向的字符型指針,而後對nm進行自增運算使其指向數組的下一個元素(仍是指針)。注意完成上述認爲的語法爲*nm++,它首先取得指針指向的內容,而後使指針自增。
注意數組中的最後一個元素被初始化爲0,while循環以次來判斷是否到了數組末尾。具備零值的指針經常被用作循環數組的終止符。程序員稱零值指針爲空指針(NULL)。採用空指針做爲終止符,在樹種增刪元素時,就沒必要改動遍歷數組的代碼,由於此時數組仍然以空指針做爲結束。