- Visual C++實用教程
- 鄭阿奇編著
- 3083字
- 2018-12-30 12:04:32
1.7 指針和引用
指針和引用是C++語言中非常重要的概念。指針的使用比較復雜,但一旦正確熟練地掌握后,便能使程序變得簡潔高效。因此,在學習時要注意領會其特點和本質。
1.7.1 指針和指針變量
如果在程序中定義了一個變量,在編譯時系統就會給這個變量分配內存單元,并根據程序中定義的變量類型,分配一定長度的內存空間,每個內存單元中存放著變量的值。為了便于內存單元的存取,系統為每一個內存單元分配一個地址。在變量的生存期內,不管以后對該變量如何賦值,其內存地址總是固定不變的。
反映內存單元的位置(地址),稱為單元的指針。為了能直接訪問這些內存單元,C++引入了“指針變量”這個概念。指針變量(pointer)就是存放內存地址的變量。例如:
int i=5; int *p=&i;
其中,i是一個初值為5的整型變量,p是一個整型指針變量,&i是取變量i在內存中的地址。于是p的數值就等于變量i在內存中的地址值,當指針變量的值是變量存儲在內存中的地址時,稱指針變量“指向”這個變量。因此p是一個指向變量i的指針。
指針變量和所有變量一樣,遵循先定義后使用的規則。在C++中定義一個指針變量可按下列格式:
<數據類型> *<指針變量名1>[,*<指針變量名2>,…];
式中的“*”是一個定義指針變量的說明符,它不是指定變量名的一部分。每個指針變量前面都需要這樣的“*”來標明。例如:
int *pInt1,*pInt2; //pInt1,pInt2是指向整型變量的指針 float *pFloat; //pFloat是一個指向實型變量的指針 char *pChar; //pChar是一個指向字符型變量的指針,它通常用來處理字符串
在定義一個指針后,系統也會給指針分配一個內存單元,但分配的空間大小都是相同的,因為指針變量的數值是某個變量的地址,而地址值的長度是一樣的。需要說明的是,絕大多數情況下,都可以將指針變量簡稱為“指針”。
1.7.2 &和*運算符
C++中有兩個專門用于指針的運算符:&(取地址運算符)F和*(取值運算符)
運算符“&”只能對變量操作,作用是取該變量的地址。運算符“*”的作用是取指針或地址所指內存單元中存儲的內容。例如:
int a=3; // 整型變量,初值為3 int *p=&a; // 指向整型變量的指針,其值等于a的地址 int b=*p; // 將指針所指的地址中的內容賦值給b,值為3
上述賦值是在指針變量定義時進行的。當然,也可以在程序中進行賦值。例如:
int a=3; // 整型變量,初值為3 int *pi; // 指向整型變量的指針 pi=p; // 將指針p的地址賦給指針pi,使得它們都是指向a的指針 // 它等價于pi=&a; 注意在pi前沒有*
另外,還需要說明的是:
(1)在使用指針變量前,一定要進行初始化或有確定的地址數值。例如下面的操作會產生致命的錯誤:
int *pInt, a=10; *pInt = 10;
或
*pInt = a;
(2)指針變量只能賦一個指針的值,若給指針變量賦了一個變量的值而不是該變量的地址,或者賦了一個常量的值,則系統會以這個值作為地址。根據這個“地址”讀寫的結果將是致命的。
(3)給兩個指針變量進行賦值,必須使這兩個指針變量類型相同。否則,結果將是不可預測的。例如:
int *pi; float f=1.22, *pFloat=&f; pi=pFloat; // 盡管本身的賦值沒有錯誤,但結果是不可預測的 // 因為(*pi)的值不會等于1.22,也不會等于1
(4)給指針變量賦值實際上是“間接”地給指針所指向的變量賦值。例如:
int a=11, *p=&a; (*p)++; // 結果a的值為12
下面看一個示例。
【例Ex_CompUsePointer】 輸入a和b兩個整數,按大小順序輸出
#include <iostream.h> int main() { int *p1, *p2, *p, a, b; cout<<"輸入兩個整數:"; cin>>a>>b; p1=&a; p2=&b; if (a<b) { p=p1; p1=p2; p2=p; } cout<<"a = "<<a<<" , b = "<<b<<endl; cout<<"最大的值是:"<<*p1<<", 最小的值是:"<<*p2<<"\n"; return 0; }
程序中,當a≥b時,指針變量p1指向a p,2指向b;而當a<b時,通過指針交換p,1指向b,p2指向a。因此,上述程序代碼總是使p1指向數值最大的變量,使p2指向數值最小的變量。
程序運行結果如下:
輸入兩個整數:1128?
a = 11 , b = 28
最大的值是:28, 最小的值是:11
1.7.3 指針運算
除了前面的賦值運算外,指針還有算術運算和關系運算。
1.指針的算術運算
在實際應用中,指針的算術運算主要是對指針加上或減去一個整數。指針的這種運算的意義和通常情況下數值的加減運算的意義是不一樣的。例如,若有:
int *ptr;
指針變量ptr加上整數n后,即ptr = ptr + n。編譯器會把指針ptr的值加上sizeof(int)*n,在32位機器中,sizeof(int)等于4。由于地址是以字節為單位的,故ptr所指向的存儲單元向高地址方向移動了sizeof(int)*n字節。這里的int是指針變量ptr的數據類型,若定義成float型,則ptr =ptr + n是使ptr向高地址方向移動了sizeof(float)*n字節。因此,
<指針變量> = <指針變量> + n
它是使指針變量所指向的存儲單元向高地址方向移動了sizeof(指針變量類型)*n字節。類似的:
<指針變量> = <指針變量> - n
它是使指針變量所指向的存儲單元向低地址方向移動了sizeof(指針變量類型)*n字節。
當n為1時,指針變量的上述加減運算就是指針變量的自增(++)、自減(--)運算。
2.指針的關系運算
兩個指針變量的關系運算要根據兩個指針變量值的大小來進行比較。在實際應用中,通常是比較兩個指針反映地址的前后關系或判斷指針變量的值是否為0。
【例Ex_PointerOp】 將字符數組a中的n個字符按相反順序存放
#include <iostream.h> int main() { char a[]="Chinese"; char*p1=a,*p2=a,temp; while(*p2!='\0')p2++; p2--; // 將p2指向a的最后一個元素 while(p1<p2) { temp=*p1; *p1=*p2; *p2=temp; // 交換內容 p1++; p2--; } cout<<a<<endl; // 輸出結果 return 0; }
程序中,先將指針p1和p2分別指向同一個字符數組a,然后將p2指向字符數組a的最后一個元素,p1從數組a的首地址向后移動,p2從數組a的末地址向前移動,當p1的地址在p2之前時,交換地址的內容即交換字符數組a的元素內容,從而實現數組a中的字符按相反順序存放。結果如下:
esenihC
1.7.4 指針和數組
數組中所有元素都是依次存儲在內存單元中的,每個元素都有相應的地址。C++又規定數組名代表數組中下標為0的元素的地址,即數組的首地址。注意:數組名表示的首地址是一個地址(指針)常量。例如,當有下列數組定義時:
int a[5];
則a所表示的地址就是元素a[0]的地址,a是一個地址(指針)常量,a++是不合法的。需要說明的是,下標運算符[]具有下列含義:
a[i] = *(a+i)
這是因為a是一個地址(指針),a+i表示a[i]的地址值,它等價于&a[i],因而a[i]=*(a+i)。
在指針操作中,若定義了下列指針:
int *pi;
則
pi=a; // 等價于pi=&a[0];
通過指針能引用數組元素。例如:
*(pi+1) = 1;
和
a[1] = 1;
是等價的。由于指針變量和數組的數組名在本質上是一樣,都反映地址值,因此,指向數組的指針變量實際上也可像數組變量那樣使用下標,而數組變量又可像指針變量那樣使用指針。例如, pi[i]與*(pi+i)及a[i]是等價的,*(a+i)與*(pi+i) 是等價的。
【例Ex_SumUsePointer】 用指針運算來計算數組元素的和
#include <iostream.h> int main() { int a[6]={1,2,3,4,5,6}; int*p=a; // 用數組名a將指針初始化 int sum=0; for(int i=0;i<6;i++) { sum+=*p; p++; } cout<<sum<<endl; // 輸出結果 return 0; }
運行結果為21。用指針運算時,要注意分析。
【例Ex_ArrayAndPointer】 分析下列程序的輸出結果
#include <iostream.h> int main() { int a[]={5,8,7,6,2,7,3}; int y,*p=&a[1]; y = (*--p)++; cout<<y<<endl; return 0; }
程序中,最難理解的語句是“y = (*--p)++;”,由于取值運算符“*”和前綴自減運算符“--”有相同的優先級,但它們的結合方向是從右至左,因此先運算--p,也就是a[0]的地址,(*--p)是元素a[0]的值,為5;再運算“y = (*--p)++;”,它相當于“y = (*--p); (*--p) = (*--p)+1;”,故最終結果為5。
上述一維數組的指針比較容易理解,但對于多維數組的指針則要復雜許多。為了敘述方便,下面以二維數組的指針為例來進一步闡述(對于三維、四維數組等也可同樣分析)。
設有二維數組a,它有2×3個元素,如下面的定義:
int a[2][3]={{1,2,5},{7,9,11}};
可以理解成:a是數組名,a數組包含兩個元素a[0]和a[1],而每個元素又是一個一維數組,例如a[0]有a[0][0]、a[0][1]和a[0][2]三個元素,它可以用一個指針來表示,例如:
int *p1, *p2; p1 = a[0]; p2 = a[1];
而數組名a代表整個二維數組的首地址,又可理解成是指向一維數組的指針的一維數組,也就是說,a可以用指向指針的指針來表示:
int**p; // 該指針又稱為二級指針 p = a;
其中,p[0]或*p等價于p1或a[0],p[1]或*(p+1)等價于p2或a[1]。
【例Ex_MultiArrayAndPointer】 分析下列程序的輸出結果
#include <iostream.h> int main() { int a[3][3]={1,2,3,4,5,6,7,8,9}; int y=0; for (int i=0; i<3; i++) for (int j=0; j<3; j++) y += (*(a+i))[j]; cout<<y<<endl; return 0; }
程序中,理解“y += (*(a+i))[j];”是本程序的關鍵。事實上,*(a+i)就是a[i],因而(*(a+i))[j]就是a[i][j]。這里的“y += (*(a+i))[j];”語句就是求數組a中各個元素之和,結果是45。
1.7.5 指針和函數
指針既可以作為函數的形參和實參,又可以作為返回值,應用非常廣泛。
1.指針作為函數的參數
函數的參數可以是C++語言中任意的合法變量,自然,也可以是一個指針。如果函數的某個參數是指針,對這一個函數的調用就是按地址傳遞的函數調用,簡稱傳址調用。由于函數形參指針和實參指針指向同一個地址,因此形參內容的改變必將影響實參。在實際應用中,函數可以通過指針類型的參數帶回一個或多個值。
【例Ex_SwapUsePointer】 指針作為函數參數的調用方式
#include <iostream.h> void swap(int *x, int *y); int main() { int a=7, b=11; swap(&a, &b); cout<<"a = "<<a<< ", b = "<<b<<"\n"; return 0; } void swap(int *x, int *y) { int temp; temp=*x; *x=*y; *y=temp; cout<<"x = "<<*x<<", y = "<<*y<<"\n"; }
程序運行結果如下:
x = 11, y = 7
a = 11, b = 7
傳遞指針的函數調用實現過程如下:
① 函數聲明中指明指針參數,即示例中的“void swap(int *x, int *y);”。
② 函數調用的實參中指明變量的地址,即示例中的“swap(&a, &b);”。
③ 函數定義中對形參進行間接訪問。對*x和*y的操作,實際上就是訪問函數的實參變量a和b,通過局部變量temp的過渡,使變量a和b的值被修改。
2.返回指針的函數
函數可以返回一個指針,該指針指向一個已定義的任一類型的數據。定義返回指針的函數格式如下:
<函數類型> * <函數名>( <形式參數表> ){ <函數體> }
它與一般函數定義基本相同,只不過在函數名前面增加了一個“*”號,用來指明函數返回的是一個指針,該指針所指向的數據類型由函數類型決定。
【例Ex_PointerReturn】 返回指針的函數:用來將一個字符串逆序輸出
#include <iostream.h> char* flip(char *str) { char *p1, *p2, ch; p1 = p2 = str; while (*p2 != '\0') p2++; p2-- ; while (p1<p2) { ch=*p2; *p2=*p1; *p1=ch; // 交換字符 p1++; p2--; } return str; } int main() { char str[] = "ABCDEFGH"; cout<<flip(str)<<"\n"; return 0; }
程序運行結果如下:
HGFEDCBA
代碼中,函數flip定義成返回一個指向字符串的指針的函數,該函數的目的是將字符串str逆序后返回。
3.指向函數的指針
同變量相似,每一個函數都有地址。指向函數地址的指針稱為“函數指針”。函數指針指向內存空間中的某個函數,通過函數指針可以調用相應的函數。
函數指針的定義如下:
<函數類型>( * <指針名>)( <參數表> );
例如:
int (*func)(char a, char b);
就是定義的一個函數指針。int為函數的返回類型,*表示后面的func是一個指針變量名。該函數具有兩個字符型參數a和b。
需要說明的是,由于()的優先級大于*,所以下面是返回指針的函數定義而不是函數指針定義:
int *func(char a, char b);
一旦定義了函數指針變量,就可以給它賦值。由于函數名表示該函數的入口地址,因此可以將函數名賦給指向函數的指針變量。但一般來說,賦給函數指針變量的函數的返回值類型與參數個數、順序要和函數指針變量相同。例如:
int fn1(char a, char b);
int *fn2(char a, char b); int fn3(int n); int (*fp1)(char x, char y); int (*fp2)(int x); fp1=fn1; // 正確,fn1函數與指針fp1指向的函數一致 fp1=fn2; // 錯誤,fn2函數的返回值類型與指針fp1指向的函數不一致 fp2=fn3; // 正確,fn3函數與指針fp2指向的函數一致 fp2=fp1; // 錯誤,兩個指針指向的函數不一致 fp2=fn3(5); // 錯誤,函數賦給函數指針時,不能加括號
函數指針變量賦值后,就可以使用指針來調用函數了。調用函數的格式如下:
( * <指針名>)( <實數表> );
或
<指針名>( <實數表> );
例如:
(*fp2)(5); 或 fp2(5);
【例Ex_FuncPointer1】 函數指針的使用
#include <iostream.h> double add(double x, double y) { return(x+y); } double mul(double x, double y) { return(x*y); } int main() { double (*func)(double,double); // 定義一個函數指針變量 double a,b; char op; cout<<"輸入兩個實數及操作方式,'+'表示加,'*'表示乘:"; cin>>a>>b>>op; if(op=='+')func=add; // 將函數名賦給指針 else func=mul; cout<<a<<op<<b<<"="<<func(a,b)<<endl; // 函數調用 return 0; }
程序運行結果如下:

函數指針變量可用做函數的參數。
【例Ex_FuncPointer2】 函數指針變量用做函數的參數
#include <iostream.h> double add(double x, double y) { return(x+y); } double mul(double x, double y) { return(x*y); } void op(double(*func)(double,double), double x, double y) { cout<<"x = "<<x<<", y = "<<y<<", result = "<<func(x,y)<<"\n"; } int main() { cout<<"使用加法函數: "; op(add, 3, 7); cout<<"使用乘法函數: "; op(mul, 3, 7); return 0; }
程序運行結果如下:
使用加法函數: x = 3, y = 7, result = 10
使用乘法函數: x = 3, y = 7, result = 21
代碼中,op函數的第一個參數為函數指針,該指針指向的函數有兩個double參數并返回double類型值。定義的add和mul函數也有兩個double參數并返回double類型值,因此它們可以作為實參賦給函數指針func。
與一般變量指針數組一樣,函數指針也可構成指針數組。
【例Ex_FuncPointerArray】 函數指針數組的使用
#include <iostream.h> void add(double x, double y) { cout<<x<<"+"<<y<<"="<<x+y<<"\n"; } void sub(double x, double y) { cout<<x<<"-"<<y<<"="<<x-y<<"\n"; } void mul(double x, double y) { cout<<x<<"*"<<y<<"="<<x*y<<"\n"; } void div(double x, double y) { cout<<x<<"/"<<y<<"="<<x/y<<"\n"; } void(*func[4])(double,double)={add,sub,mul,div}; // 函數指針數組定義和初始化 int main() { double x = 3, y = 7; char op; do{ cout <<"+------- 相加\n" <<"-------- 相減\n" <<"*------- 相乘\n" <<"/------- 相除\n" <<"0------- 退出\n"; cin>>op; switch(op) { case '+': func[0](x, y); break; case '-': func[1](x, y); break; case '*': func[2](x, y); break; case '/': func[3](x, y); break; case '0': return; } }while(1); return 0; }
1.7.6 new和delete
在C++中,使用運算符new和delete能有效、直接地進行動態內存的分配和釋放。
運算符new返回指定類型的一個指針,如果分配失敗(如沒有足夠的內存空間)則返回0。例如:
double *p; p = new double; *p=30.4; // 將值存放在開辟的單元中
系統自動根據double類型的空間大小開辟一個內存單元,并將地址放在指針p中。當然,也可在開辟內存單元時,對單元里的值進行初始化。例如上述代碼可寫成:
double *p; p = new double(30.4);
運算符delete操作是釋放new請求到的內存。例如:
delete p;
它的作用是將p指針的內存單元釋放,指針變量p仍然有效,它可以重新指向另一個內存單元。
需要注意的是:
(1)new和delete必須配對使用。也就是說,用new為指針分配內存,當使用結束之后,一定要用delete來釋放已分配的內存空間。
(2)運算符delete必須用于先前new分配的有效指針。如果使用了未定義的其他任何類型的指針,就會帶來嚴重問題,如系統崩潰等。
(3)new可以為數組分配內存,但當釋放時,也可告訴delete數組有多少個元素。例如:
int *p; p=new int[10]; // 分配整型數組的內存,數組中有10個元素 if ( !p ) { cout<<”內存分配失敗!”; exit(1); // 中斷程序執行 } for (int i=0; i<10; i++) p[i]=i; // 給數組賦值 //… delete [10]p; // 告訴delete數組有多少個元素,或delete[]p;
1.7.7 引用和引用傳遞
C++提供了一個與指針密切相關的特殊數據類型——引用。引用是一個變量的別名。定義引用類型變量,實質上是給一個已定義的變量起一個別名,系統不會為引用類型變量分配內存空間,只是使引用類型變量與其相關聯的變量使用同一個內存空間。
1.引用定義和使用
定義引用類型變量的一般格式為:
<數據類型> &<引用名>=<變量名>
或
<數據類型> &<引用名>(<變量名>)
其中,變量名必須是一個已定義過的變量。例如:
int a = 3; int &ra = a;
這樣,ra就是一個引用,它是變量a的別名。所有對這個引用ra的操作,實質上就是對被引用對象a的操作。例如:
ra = ra +2;
實質上是a+2,a的結果為5。但是如果給引用賦一個新值,結果會怎樣呢?
【例Ex_Reference】 給引用重新賦值
#include <iostream.h> int main() { int a; int &ra = a; a = 5; cout<<"a = "<<a<<"\n"; cout<<"ra = "<<ra<<"\n"; cout<<"a的地址是:"<<&a<<"\n"; cout<<"ra的地址是:"<<&ra<<"\n"; int b = 8; ra = b; cout<<"a = "<<a<<"\n"; cout<<"b = "<<b<<"\n"; cout<<"ra = "<<ra<<"\n"; cout<<"a的地址是:"<<&a<<"\n"; cout<<"b的地址是:"<<&b<<"\n"; cout<<"ra的地址是:"<<&ra<<"\n"; return 0; }
程序運行結果如下:
a = 5
ra = 5
a的地址是:0x0012FF7C
ra的地址是:0x0012FF7C
a = 8
b = 8
ra = 8
a的地址是:0x0012FF7C
b的地址是:0x0012FF74
ra的地址是:0x0012FF7C
程序中,引用ra被重新賦值為變量b。但從運行結果可以看出,ra與a的地址仍然相同,只不過它們的值都等于b的值。
從這個例子可以看出,引用與指針的最大區別是:指針是一個變量,可以把它再賦值成指向別處的地址,而引用一旦初始化后,其地址不會再改變。
當然,在使用引用時,還需要注意的是:
(1)定義引用類型變量時,必須將其初始化。而且引用變量類型必須與為它初始化的變量類型相同。例如:
float fVal; int&rfVal=fVal; // 錯誤:類型不同
(2)若所引用類型變量的初始化值是常數,則必須將該引用定義成const類型。例如:
const int&ref=2; //const類型的引用
(3)可以引用一個結構體,但不能引用一個數組,這是因為數組是某個數據類型元素的集合,數組名表示該元素集合空間的起始地址,它自己不是一個真正的數據類型。例如:
int a[10]; int&ra=a; // 錯誤:不能建立數組的引用
(4)引用本身不是一種數據類型,所以沒有引用的引用,也沒有引用的指針。例如:
int a; int &ra = a; int&rra=ra; // 正確,變量a的另一個引用 int&*p=&ra; // 錯誤:企圖定義一個引用的指針
2.引用傳遞
前面已提到過,當指針作為函數的參數時,形參改變后,相應的實參也會改變。但是如果在函數中反復使用指針,容易產生錯誤且難以閱讀和理解。如果以引用作為參數,則既可以實現指針所帶來的功能,又簡便自然。
使一個函數能使用引用傳遞的方式是在函數定義時將形參前加上引用運算符“&”。
【例Ex_SwapUseReference】 引用作為函數參數的調用方式
#include <iostream.h> void swap(int &x, int &y); int main() { int a(7), b(11); swap(a, b); cout<<"a = "<<a<< ", b = "<<b<<"\n"; return 0; } void swap(int &x, int &y) { int temp; temp=x; x=y; y=temp; cout<<"x = "<<x<<", y = "<<y<<"\n"; }
程序運行結果如下:
x = 11, y = 7
a = 11, b = 7
函數swap中的&x和&y就是形參的引用說明。在執行swap(a, b);時,雖然看起來是簡單的變量傳遞,但實際上傳遞的是實參a, b的地址,也就是說,形參的任何操作都會改變相應的實參的數值。引用除了可作為函數的參數外,還可作為函數的返回值。
【例Ex_RefReturn】 返回引用的函數的使用
#include <iostream.h> double area; double &CalArea(double r) { area = 3.141593 * r * r; return area; } int main() { double c = CalArea(5.0); double &d = CalArea(10.0); cout<<c<<"\n"; cout<<d<<"\n"; return 0; }
程序運行結果如下:
78.5398
314.159
注意:
絕對不要返回不在作用域內的變量的引用,因為一旦變量退出作用域,對它的引用也沒有意義了。
- Boost.Asio C++ Network Programming(Second Edition)
- 三維圖形化C++趣味編程
- 趣學Python算法100例
- Python王者歸來
- C語言程序設計
- Windows內核編程
- Lift Application Development Cookbook
- Struts 2.x權威指南
- Java7程序設計入門經典
- Hands-On Robotics Programming with C++
- 例說FPGA:可直接用于工程項目的第一手經驗
- PHP 7 Programming Blueprints
- C#程序開發參考手冊
- Android從入門到精通
- Daniel Arbuckle's Mastering Python