對指針進行算術操作和整數類型稍有不同。首先,指針只有加法和減法運算是允許的;其他的對指針來講是沒成心義的。但是,不管是加法和減法對指針,根據他們指向數據類型的大小,都有1個略微不同的表現。
當引入基本數據類型,我們看到類型有不同的大小。例如:char大小為1字節,short1般更大,int和long更大;具體大小依賴于系統的精確。例如,讓在1個給定的系統中,假設char需要1個字節,那末short需要2個字節,而long需要4個字節。
現在加入我們在編譯器中有3個指針:
char *mychar;
short *myshort;
long *mylong;
指針的初始地址分別為:1000,2000和3000.
那末如果我們履行下面操作:
++mychar;
++myshort;
++mylong;
那末和我們期待的1樣,mychar將會指向1001地址,但是myshort將會指向2002,而mylong將會指向3004. 雖然他們都只被增加了1次。緣由是,當向指針添加1個時,指針指向同1類型的下1個元素,因此,它所指向的類型的字節的大小將被添加到指針上。
當增加和減去任何數字到1個指針也是適用的。如果我們寫成下面這樣,那末實現的功能和上面將會完全相同:
mychar = mychar + 1;
myshort = myshort + 1;
mylong = mylong + 1;
關于自增(+ +)和自減(–)操作,他們都可以被用來作為前綴或后綴的表達,在功能上有輕微的差異:作為1個前綴,表達式在增量產生之前的履行,作為1個后綴,表達式在增量產生后履行。這也適用于表達自增和自減指針,它可以成為更復雜的表達式,還包括部份援用操作符()。記住運算符優先級的規則,我們可以回想1下,后綴式操作符,如自增和自減,比前綴操作符的優先級更高的,如解援用操作符()。因此,下面的表達式:
*p++
等價于 *(p++),它所做的就是增加P值(所以它現在指向下1個元素),但由于++作為后綴,全部表達式被求值后指向原來的指針(它指向的地址遞增)。
從本質上講,這些是用的前綴和后綴增量運算符和與解援用操作符的4種可能的組合(一樣適用也減量運算符):
*p++ // same as *(p++): increment pointer, and dereference unincremented address
*++p // same as *(++p): increment pointer, and dereference incremented address
++*p // same as ++(*p): dereference pointer, and increment the value it points to
(*p)++ // dereference pointer, and post-increment the value it points to
1個典型的-但不是那末簡單的語句是:
*p++ = *q++;
由于++的優先級比*更高,p和q是先自增,但由于增量運算符(++)作為后綴而不是前綴,分配到*p值和*Q是自增的。這樣都自增。這大致相當于:
*p = *q;
++p;
++q;
一樣,加括號更能清晰表達以減少混亂。
指針可以被用來訪問1個變量的地址,包括修改指向地址的值。但是也能夠在訪問指向地址指針讀取的時候聲明,這樣就不可以修改它。對這1點,那末所指向的類型就為const指針。例如:
int x;
int y = 10;
const int * p = &y;
x = *p; // ok: reading p
*p = x; // error: modifying p, which is const-qualified
這里p指向1個變量,但指出它是const方式,這意味著它是可讀的指針,但不能修改它。注意,這表達&y是int*類型,但這是賦給1個指針類型const int *。這是允許的:1個指向非const可以隱式轉換為指向const對象的指針。但不是其他方式!為了安全,指向const指針不可隱式轉換為非const指針。
指向const元素的1個指針例子是作為函數的參數:1個函數接受1個指向非const參數作為傳遞參數的值,而函數不能接受1個指向const對象的指針作為參數。
// pointers as arguments:
#include <iostream>
using namespace std;
void increment_all (int* start, int* stop)
{
int * current = start;
while (current != stop) {
++(*current); // increment value pointed
++current; // increment pointer
}
}
void print_all (const int* start, const int* stop)
{
const int * current = start;
while (current != stop) {
cout << *current << '\n';
++current; // increment pointer
}
}
int main ()
{
int numbers[] = {10,20,30};
increment_all (numbers,numbers+3);
print_all (numbers,numbers+3);
return 0;
}
注意,print_all使用指針指向常量元素。這些指針指向常量的內容不能修改,但他們不是常量本身:即指針依然可以遞增或分配不同的地址,雖然他們他們指向的內容不能修改。
下面是將第2維度添加到指針:指針也能夠是const指針。這是通過添加const指出類型指定(星號后面):
int x;
int * p1 = &x; // non-const pointer to non-const int
const int * p2 = &x; // non-const pointer to const int
int * const p3 = &x; // const pointer to non-const int
const int * const p4 = &x; // const pointer to const int
用const與指針的語法是很辣手的,合適的使用常常會需要1些經驗。在任何情況下,取得指針常量(援用)的權限宜早不宜遲,但也不應當過分擔心,如果是第1次接觸到const和指針的組合,那末更多例子在后面的章節中顯示出來。
const指針的語法更混亂的操作是,const限定符可以位于前面或后面,具有的意義相同:
const int * p2a = &x; // non-const pointer to const int
int const * p2b = &x; // also non-const pointer to const int
與星號相鄰,在這類情況下,const的順序是1件簡單的風格。本章使用前綴const,由于歷史的緣由,可以擴大很多種,但二者是完全等價的。每種風格的優點在互聯網上仍有劇烈的爭辯。
前面指出,字符串是包括空終止字符序列的數組。在前面的章節,字符串被用來直接插入cout來初始化字符串和字符數組。
但也能夠直接訪問。字符串數組類型是包括所有字符加終止null字符的數組,每一個元素的類型是const char(像字面表示的1樣,永久不會被修改)。例如:
const char * foo = "hello";
這里聲明1個“hello”的字符串數組,然后Foo指針指向其第1個元素。如果我們假定“hello”是存儲在地址1702的內存位置,我們可以表示為:
注意foo是1個值為1702的指針變量,不是‘h’,也不是‘hello’,雖然地址1702是指向這些元素的地址。
Foo是指向這1序列的指針,由于指針與數組的表示方式是相通的,foo可以1下面兩種方式訪問:
*(foo+4)
foo[4]
上面兩種方式都可以得到值‘o’(即數組的第5個元素)。
C++允許定義指向指針的指針,也就是指向了數據(或其他指針),所使用的符號就是在指針前面簡單的加上*。
char a;
char * b;
char ** c;
a = 'z';
b = &a;
c = &b;
像這里,隨機初始化3個內存地址為7230,8092,10502,那末上面就能夠表示為:
每一個變量的值在其相應的單元格中表示的每一個變量,它們在內存中的值所表示的值在他們內存值上上方。
在這個例子中的c是1個指向指針的指針,可間接用于3個不同的層次,它們中的每個對應于1個不同的值:
(1) C是char**類型,值為8092
(2) *c是char*類型,值為7230
(3) **c為char類型,值為‘z’
空指針是1種特殊的類型,在c++,void代表空類型。因此,void空指針指向的數據沒有類型(因此它的長度和相干屬性不肯定)。
這就是的空指針有很大的靈活性,可以指向任何數據類型,從1個整型或1個浮點型到1個字符串字符的空指針。一樣相反,他們就有很大的局限性:他們指向的數據不能直接援用(這是合乎邏輯的,由于我們不知道類型),由于這個緣由,在1個void指針需要轉化為其他1些指針類型時,被援用之前需要指向1個具體的數據類型的地址。
下面是1個傳遞函數參數的例子:
// increaser
#include <iostream>
using namespace std;
void increase (void* data, int psize)
{
if ( psize == sizeof(char) )
{ char* pchar; pchar=(char*)data; ++(*pchar); }
else if (psize == sizeof(int) )
{ int* pint; pint=(int*)data; ++(*pint); }
}
int main ()
{
char a = 'x';
int b = 1602;
increase (&a,sizeof(a));
increase (&b,sizeof(b));
cout << a << ", " << b << '\n';
return 0;
}
C++中sizeof是1個獲得數據類型大小的操作符。對非動態的數據,這個值是常量。因此例如sizeof(char)是1,由于char型數據占用1個字節大小。
在原則上,指針需要指向有效的地址,如變量的地址或數組中元素的地址。但指針實際上可以指向任何地址,包括沒有任何有效元素的地址。典型的例子是未初始化的指針和指向數組中不存在的元素:
int * p; // uninitialized pointer (local variable)
int myarray[10];
int * q = myarray+20; // element out of bounds
不管p還是q都沒有指向某個已知值的地址,但上述任何語句都不會致使毛病。在C++中,指針是可以指向任何地址值,不管在那個地址是不是有值。甚么會致使毛病對的是援用這樣1個指針(即,訪問他們實際指向的值)。這樣的訪問指針會致使未定義行動,在運行時出現毛病值訪問1些隨機值。
有時,1個指針需要顯式指向無處,而不單單是1個無效的地址。對這類情況,存在1個特殊的值,即任何指針類型都可以采取:null指針值。這個值可以以兩種方式在C++表示:1個整數為零的值,或nullptr關鍵字:
int * p = 0;
int * q = nullptr;
在這里,P和Q都是空指針,意味著它們不指向任何地方,這里它們實際上是相等的:所有的null指針等于其他的null指針。也很常見的是,在舊的代碼中使用定義的常量 NULL來援用空指針值:
int * r = NULL;
NULL包括在標準庫中,被定義成null常量指針(比如0或nullptr)
不要將null指針與void指針弄混淆了。1個null指針是1個值,任何指針都可以表示它,不指向任何地方,而1個void指針是1個指向沒有特定類型的指針的指針類型。1個是指存儲在指針中的值,另外一個是指向它指向的數據類型。
C++允許操作的函數指針。典型用處是傳遞函數作為對另外一個函數的1個參數。函數指針與語法與1個普通的函數相同,除函數的名字是用括號括起來()和在名字之前插入星號(*)。
// pointer to functions
#include <iostream>
using namespace std;
int addition (int a, int b)
{ return (a+b); }
int subtraction (int a, int b)
{ return (a-b); }
int operation (int x, int y, int (*functocall)(int,int))
{
int g;
g = (*functocall)(x,y);
return (g);
}
int main ()
{
int m,n;
int (*minus)(int,int) = subtraction;
m = operation (7, 5, addition);
n = operation (20, m, minus);
cout <<n;
return 0;
}
在上面的例子中,minus就是1個有兩個int參數類型的函數指針。它直接初始化到函數subtraction:
int (* minus)(int,int) = subtraction;
上一篇 注解(一)——注解入門