新聞中心
這篇博客很長,基本包含了所有指針及指針相關(guān)的知識以及一些細節(jié);相應(yīng)的可能會有一些羅嗦;認真看完一定會有所收獲(大佬除外)!
變量被解釋為計算機內(nèi)存中的位置,可以通過它們的標(biāo)識符(它們的名字)訪問。這樣,程序就不需要關(guān)心內(nèi)存中數(shù)據(jù)的物理地址; 它只是在需要引用變量時使用標(biāo)識符。
對于 C + + 程序來說,計算機的內(nèi)存就像一連串的內(nèi)存單元,每個單元的大小都是一個字節(jié),每個單元的地址都是唯一的。這些單字節(jié)內(nèi)存單元的排序方式允許大于一個字節(jié)的數(shù)據(jù)表示占用具有連續(xù)地址的內(nèi)存單元。
這樣,每個單元就可以通過其唯一地址很容易地定位在內(nèi)存中。例如,地址為1776的內(nèi)存單元總是緊跟在地址為1775的單元之后,在地址為1777的單元之前,正好是776之后的1000個單元,正好是2776之前的1000個單元。
當(dāng)聲明一個變量時,需要存儲它的值被分配到內(nèi)存中的一個特定位置(它的內(nèi)存地址)。一般來說,C + + 程序不會主動確定其變量存儲在確切內(nèi)存地址。這個任務(wù)留給了運行程序的環(huán)境——通常是一個操作系統(tǒng),它決定運行時的特定內(nèi)存位置。然而,程序能夠在運行時獲取變量的地址,以便訪問相對于變量處于某個位置的數(shù)據(jù)單元,這是有用的。
變量的地址可以通過在變量名前面加上與號(&)(稱為取地址運算符操作符)來獲得。例如:
|
|
這會將變量 myvar 的地址分配給 foo; 通過在變量 myvar 的名稱前面加上 運算符 &?,我們不再將變量本身的內(nèi)容分配給 foo,而是將其地址分配給 foo。
內(nèi)存中變量的實際地址在運行時之前無法知道,但是為了幫助澄清一些概念,我們假設(shè) myvar 在運行時放置在內(nèi)存地址1776中。
在這種情況下,考慮以下代碼片段:
|
每個變量執(zhí)行后所包含的值如下圖所示:
首先,我們將值25賦給 myvar (一個變量,我們假設(shè)它在內(nèi)存中的地址為1776)。
第二個語句指定 myvar 的地址,我們假設(shè)它是1776。
最后,第三個語句將 myvar 中包含的值賦給 bar。這是一個標(biāo)準(zhǔn)的分配操作。
第二個語句和第三個語句之間的主要區(qū)別是操作符(&)的出現(xiàn)。
存儲另一個變量地址的變量(如前面示例中的 foo)在 C + + 中稱為指針。指針是該語言的一個非常強大的特性,在低級編程中有許多用途。稍后,我們將看到如何聲明和使用指針。
如前所述,存儲另一個變量地址的變量稱為指針。指針被稱為“指向”它們所存儲的地址的變量。
指針的一個有趣的屬性是,它們可以用來直接訪問它們指向的變量。這是通過在指針名稱前面加上解引用運算符(*)來完成的。操作符本身可以被讀作“指向的值”。
因此,根據(jù)上一個示例的值,下面的語句:
baz = *foo;
這可以理解為: “ baz 等于 foo 指向的值”,這個語句實際上將值25賦給 baz,因為 foo 是1776,而1776指向的值(遵循上面的例子)是25。
清楚地區(qū)分 foo 指的是1776,而 * foo (標(biāo)識符前面帶有星號 *)指的是存儲在地址1776的值,在本例中為25,這一點很重要。注意包含或不包含解引用運算符的區(qū)別(我已經(jīng)添加了如何解讀這兩個表達的解釋性注釋) :
baz = foo; // baz equal to foo (1776)
baz = *foo; // baz equal to value pointed to by foo (25)
因此,引用運算符和解引用運算符是相輔相成的:
&
是地址操作符,可以簡單地理解為“某某的地址”*
是解引用運算符,可以理解為“指向的值”
因此,它們有相反的含義: 用 & 獲得的地址可以用 * 解引用。
前面,我們執(zhí)行了以下兩個賦值操作:
|
執(zhí)行上述兩個語句之后,下列所有表達式的結(jié)果都是 true:
|
由于指針能夠直接引用它所指向的值,所以當(dāng)指向 char 時,指針與指向 int 或 float 時具有不同的屬性。一旦解引用,就需要知道類型。為此,指針的聲明需要包含指針要指向的數(shù)據(jù)類型。
指針的聲明遵循以下語法:type * name;
其中 type 是指針指向的數(shù)據(jù)類型。此類型不是指針本身的類型,而是指針指向的數(shù)據(jù)的類型。例如:
int * number; char * character; double * decimals;
這是指針的三個聲明。每個指針都指向一種不同的數(shù)據(jù)類型,但實際上,它們都是指針,并且所有指針都可能占用相同的內(nèi)存空間(指針的內(nèi)存大小取決于程序運行的平臺)。然而,它們所指向的數(shù)據(jù)不占用相同的空間,也不屬于相同的類型: 第一個指向 int,第二個指向 char,最后一個指向 double。因此,盡管這三個示例變量都是指針,但它們實際上有不同的類型: int * 、 char * 和 double * ,這取決于它們指向的類型。
注意,在聲明一個指針時使用的星號(*)僅表示它是一個指針(它是它的類型復(fù)合說明符的一部分) ,不應(yīng)該與之前看到的解引用運算符混淆,但它也是用星號(*)寫的。它們只是用同一個符號表示的兩個不同的東西。
讓我們看一個關(guān)于指針的例子:
// my first pointer
#includeusing namespace std;
int main ()
{
int firstvalue, secondvalue;
int * mypointer;
mypointer = &firstvalue;
*mypointer = 10;
mypointer = &secondvalue;
*mypointer = 20;
cout<< "firstvalue is "<< firstvalue<< '\n';
cout<< "secondvalue is "<< secondvalue<< '\n';
return 0;
}
輸出: firstvalue is 10 secondvalue is 20?
在 cpp.sh 中編輯和運行
注意,即使第一個值和第二個值都沒有在程序中直接設(shè)置任何值,最終都會通過使用 mypoint 間接設(shè)置一個值。
為了證明一個指針在程序的生命周期內(nèi)可以指向不同的變量,這個示例使用第二個值和同一個指針 mypoint 重復(fù)這個過程。
這里有一個更詳細的例子:
// more pointers
#includeusing namespace std;
int main ()
{
int firstvalue = 5, secondvalue = 15;
int * p1, * p2;
p1 = &firstvalue; // p1 = address of firstvalue
p2 = &secondvalue; // p2 = address of secondvalue
*p1 = 10; // value pointed to by p1 = 10
*p2 = *p1; // value pointed to by p2 = value pointed to by p1
p1 = p2; // p1 = p2 (value of pointer is copied)
*p1 = 20; // value pointed to by p1 = 20
cout<< "firstvalue is "<< firstvalue<< '\n';
cout<< "secondvalue is "<< secondvalue<< '\n';
return 0;
}
輸出: firstvalue is 10 secondvalue is 20
Edit & run on cpp.sh
另一件事提醒注意的是
int * p1, * p2;
這聲明了前面示例中使用的兩個指針。但是請注意,每個指針都有一個星號(*) ,以便兩者都有 int * (指向 int 的指針)類型。由于優(yōu)先級規(guī)則,這是必需的。注意,如果代碼是:
int * p1, p2;
P1的確是 int * 類型,但是 p2的類型是 int。對于這個目的,空間一點也不重要。但是無論如何,對于大多數(shù)對每條語句聲明多個指針感興趣的指針用戶來說,只要記住在每個指針上加一個星號就足夠了。或者更好: 對每個變量使用不同的語句。
指針和數(shù)組數(shù)組的概念與指針的概念有關(guān)。實際上,數(shù)組的工作方式非常類似于指向它們的第一個元素的指針,而且,實際上,數(shù)組總是可以隱式地轉(zhuǎn)換為適當(dāng)類型的指針。例如,考慮以下兩個聲明:
int myarray [20]; int * mypointer;
下面的賦值操作是有效的:
mypointer = myarray;
之后,mypoint 和 myarray 將是等價的,并且具有非常相似的屬性。主要的區(qū)別在于,我的指針可以被賦予一個不同的地址,而 myarray 永遠不能被賦予任何東西,并且總是表示同一塊20個 int 類型的元素。因此,下列轉(zhuǎn)讓無效:
myarray = mypointer;
讓我們看一個混合數(shù)組和指針的例子:
// more pointers
#includeusing namespace std;
int main ()
{
int numbers[5];
int * p;
p = numbers; *p = 10;
p++; *p = 20;
p = &numbers[2]; *p = 30;
p = numbers + 3; *p = 40;
p = numbers; *(p+4) = 50;
for (int n=0; n<5; n++)
cout<< numbers[n]<< ", ";
return 0;
}
輸出:
10, 20, 30, 40, 50,
Edit & run on cpp.sh
指針和數(shù)組支持相同的操作集,兩者的含義相同。主要區(qū)別在于,指針可以分配新的地址,而數(shù)組不能。
在關(guān)于數(shù)組的章節(jié)中,方括號([])被解釋為指定數(shù)組元素的索引。實際上,這些括號是一個解引用運算符,稱為偏移運算符。它們像 * 一樣解引用它們所跟隨的變量,但是它們也將括號之間的數(shù)字添加到被解引用的地址。例如:
a[5] = 0; // a [offset of 5] = 0 *(a+5) = 0; // pointed to by (a+5) = 0
這兩個表達式是等價和有效的,a不僅?是一個指針,而且?是一個數(shù)組。請記住,數(shù)組,它的名稱可以像指向其第一個元素的指針一樣使用。
指針初始化指針可以初始化為在定義時指向特定位置的指針
int myvar; int * myptr = &myvar;
等同于:
int myvar; int * myptr; myptr = &myvar;
初始化指針時,初始化的是指向的地址(即 myptr) ,而不是指向的值(即 * myptr)。因此,上述守則不得與以下面混淆:
int myvar; int * myptr; *myptr = &myvar;
無論如何,這都沒有多大意義(而且也不是有效的代碼)。
可以將指針初始化為變量的地址(如上例所示) ,也可以初始化為另一個指針(或數(shù)組)的值:
int myvar; int *foo = &myvar; int *bar = foo;
指針?biāo)阈g(shù)對指針進行算術(shù)運算與對正則整數(shù)類型進行算術(shù)運算略有不同。首先,只允許執(zhí)行加法和減法操作; 其他操作在指針世界中毫無意義。但是,根據(jù)指針?biāo)赶虻臄?shù)據(jù)類型的大小,加法和減法在使用指針時的行為略有不同。
當(dāng)引入基本數(shù)據(jù)類型時,我們看到類型具有不同的大小。例如: char 的大小總是1字節(jié),short 通常比這個大,int 和 long 甚至更大; 它們的確切大小取決于系統(tǒng)。例如,假設(shè)在給定的系統(tǒng)中,char 占用1個字節(jié),short 占用2個字節(jié),long 占用4個字節(jié)。
假設(shè)現(xiàn)在我們在這個編譯器中定義了三個指針:
char *mychar; short *myshort; long *mylong;
假設(shè)我們知道它們分別指向內(nèi)存位置1000,2000和3000。
因此,如果我們寫:
++mychar; ++myshort; ++mylong;
正如預(yù)期的那樣,mychar 將包含值1001。但是不那么明顯,myshort 將包含值2002,mylong 將包含3004,盡管它們每個只增加了一次。原因是,當(dāng)向指針加1時,指針會指向下面同一類型的元素,因此,它所指向的類型的字節(jié)大小被添加到指針中。
這適用于對指針進行任意數(shù)字的加減運算。如果我們寫下:
|
關(guān)于遞增(+ +)和遞減(--)操作符,它們都可以用作表達式的前綴或后綴,但行為略有不同: 作為前綴,遞增發(fā)生在計算表達式之前,作為后綴,遞增發(fā)生在計算表達式之后。這也適用于遞增和遞減指針的表達式,這些指針可以成為更復(fù)雜的表達式的一部分,這些表達式還包括解引用操作符(*)。記住運算符優(yōu)先級規(guī)則,我們可以回想起后綴運算符,如遞增和遞減,比前綴運算符,如解引用運算符(*)具有更高的優(yōu)先級。因此,下面的表達:
*p++
等效于 * (p + +)。它所做的是增加 p 的值(所以它現(xiàn)在指向下一個元素) ,但是因為 + + 被用作后綴,所以整個表達式被計算為指針最初指向的值( 指向它在增加之前的地址)。
關(guān)于運算符優(yōu)先級:c++ 運算符優(yōu)先級表格
本質(zhì)上,這些是解引用運算符與遞增運算符的前綴和后綴版本的四種可能的組合(同樣適用于遞減運算符) :
*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
涉及這些運算符的一個典型但不那么簡單的語句是:
*p++ = *q++;
因為 + + 的優(yōu)先級高于 * ,所以 p 和 q 都是遞增的,但是因為兩個遞增運算符(+ +)都用作后綴而不是前綴,所以賦給 * p, * q 的值都是在遞增之前?p 和 q。然后兩者再遞增的。這大致相當(dāng)于:?
*p = *q;
++p;
++q;
|
- tips:通過添加括號(),增加表達式的易讀性來減少混淆。
指針可用于根據(jù)地址訪問變量,這種訪問可能包括修改指向的值。但也可以聲明指針,這些指針可以訪問指向的值來讀取它,但不能修改它。對于這一點,將指針指向的類型限定為 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 指向一個變量,但是以一種常量限定的方式指向它,這意味著它可以讀取指向的值,但是不能修改它。還要注意,表達式 & y 的類型為 int * ,但是它被賦值給類型為 const int * 的指針。這是允許的: 指向非常數(shù)的指針可以隱式轉(zhuǎn)換為指向常數(shù)的指針。但不是反過來!作為一個安全特性,常量指針不能隱式轉(zhuǎn)換為非常量指針。
指向 const 的元素的指針的用例之一是作為函數(shù)參數(shù): 將非 const 指針作為參數(shù)的函數(shù)可以修改作為參數(shù)傳遞的值,而將 const 指針作為參數(shù)的函數(shù)則不能。
// pointers as arguments:
#includeusing 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;
}
輸出:
11 21 31
注意 print _ all 使用指向常量元素的指針。這些指針指向它們無法修改的常量內(nèi)容,但它們本身不是常量: 也就是說,指針仍然可以遞增或分配不同的地址,盡管它們無法修改它們指向的內(nèi)容。
這就是將常量的第二個維度添加到指針的地方: 指針本身也可以是常量。這是通過將 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 和指針的語法肯定是有難度的,并且識別最適合每種用法的情況往往需要一些經(jīng)驗。在任何情況下,盡早正確使用指針(和引用)獲得常量都是很重要的,但是如果您第一次接觸到常量和指針的混合,那么您不應(yīng)該過于擔(dān)心現(xiàn)在還沒熟練掌握,慢慢熟悉它。
const 限定符可以在指針類型之前或之后,具有完全相同的含義:
const int * p2a = &x; // non-const pointer to const int
int const * p2b = &x; // also non-const pointer to const int
與星號周圍的空格一樣,本例中 const 的順序只是一個樣式問題。本章使用前綴 const,由于歷史原因,這似乎更加擴展,但兩者完全等價。每種風(fēng)格的優(yōu)點仍在互聯(lián)網(wǎng)上激烈爭論。
指針和字符串文字字符串文字是包含以空結(jié)尾的字符序列的數(shù)組。但也可以直接訪問。字符串文字是適當(dāng)數(shù)組類型的數(shù)組,它包含所有字符和結(jié)束的 空字符('\0'),每個元素都是 const char 類型(作為文字,它們永遠不能被修改)。例如:
const char * foo = "hello";
這將聲明一個數(shù)組,其文字表示為“ hello”,然后將指向其第一個元素的指針分配給 foo。如果我們假設(shè)“ hello”存儲在從地址1702開始的內(nèi)存位置,我們可以將前面的聲明表示為:
注意,這里 foo 是一個指針,包含值1702,而不是“ h”或“ hello”,盡管1702確實是這兩個值的地址。
指針 foo 指向一個字符序列。由于指針和數(shù)組在表達式中的行為方式基本相同,foo 可以用來訪問字符,其方式與以空結(jié)尾的字符序列的數(shù)組相同。例如:
*(foo+4)
foo[4]?
兩個表達式的值都為‘ o’(數(shù)組的第五個元素)。
指針指向指針C + + 允許使用指向指針的指針,這些指針依次指向數(shù)據(jù)(甚至指向其他指針)。語法只需要在指針的聲明中為每個間接級別加一個星號(*) :
|
假設(shè)為每個變量隨機選擇內(nèi)存位置為7230、8092和10502,這可以表示為:
每個變量的值在對應(yīng)的單元格中表示,它們各自在內(nèi)存中的地址由它們下面的值表示。
例如新變量 c,它是一個指向指針的指針,可以在三個不同的間接級別中使用,每個級別對應(yīng)一個不同的值:
- c的類型為 char * * ,值為8092
- * c 是 char * 類型,值為7230
- * * c 的類型為 char,值為‘ z’
指針的 void 類型是一種特殊類型的指針。在 C + + 中,void 表示沒有類型。因此,void 指針是指向沒有類型的值的指針(因此也是一個未確定的長度和未確定的解引用屬性)。
這使 void 指針具有很大的靈活性,可以指向任何數(shù)據(jù)類型,從整數(shù)值或浮點數(shù)到字符串。作為代價,它們有一個很大的局限性: 它們指向的數(shù)據(jù)不能被直接解引用(這是合乎邏輯的,因為我們沒有要解引用的類型) ,出于這個原因,void 指針中的任何地址都需要轉(zhuǎn)換成具體數(shù)據(jù)類型的指針類型,然后才能被解引用。
它的一個可能用途是將泛型參數(shù)傳遞給函數(shù),例如:
// increaser
#includeusing 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;
}
| y, 1603 |
Sizeof 是一個集成在 C + + 語言中的運算符,它返回參數(shù)的大小(以字節(jié)為單位)。對于非動態(tài)數(shù)據(jù)類型,此值為常數(shù)。因此,例如,sizeof (char)為1,因為 char 的大小總是為一個字節(jié)。
無效指針和空指針原則上,指針指向有效的地址,如變量的地址或數(shù)組中元素的地址。但是指針實際上可以指向任何地址,包括不引用任何有效元素的地址。典型的例子是指向數(shù)組中不存在元素的指針和未初始化的指針:
int * p; // 未初始化的指針(局部變量)uninitialized pointer (local variable)
// 為什么強調(diào)是局部變量,因為全局變量會有默認初始化
int myarray[10];
int * q = myarray+20; // 出界元素 || element out of bounds
P 和 q 都不指向已知包含值的地址,但是上面的語句都不會導(dǎo)致錯誤。在 C + + 中,指針可以獲取任何地址值,不管這個地址上是否真的有什么東西。可能導(dǎo)致錯誤的是解引用這類指針(即,實際訪問它們指向的值)。訪問這類指針會導(dǎo)致未定義行為,從運行時的錯誤到訪問一些隨機值。
但是,有時候,指針確實需要顯式地指向不存在的地方(nowhere:不知道如何翻譯恰當(dāng),就翻譯為不存在的地方,以下同),而不僅僅是一個無效的地址。對于這種情況,存在一個任何指針類型都可以使用的特殊值: 空指針值。這個值可以用兩種方式在 C + + 中表示: 或者使用一個整數(shù)值為零,或者使用 nullptr 關(guān)鍵字:
int * p = 0; int * q = nullptr; // 一般使用第二種,見名知意
在這里,p 和 q 都是空指針,這意味著它們顯式地指向不存在的地方,并且它們實際上相等: 所有空指針比較等于其他空指針。在舊代碼中,定義的常量 NULL 用于引用空指針值也很常見:
int * r = NULL; // 一般是c語言的寫法,c++ 寫nullptr 更規(guī)范
NULL 在標(biāo)準(zhǔn)庫的一些頭文件中定義,被定義為某個空指針常量值(如0或 nullptr)的別名。
不要將空指針(null pointers)與空指針(void
pointers)混淆!null指針是一個值,任何指針都可以用來表示它指向“不存在的地方”,而void
指針是一種指向某處的指針,沒有特定的類型。一個引用存儲在指針中的值,另一個引用它所指向的數(shù)據(jù)類型。
C + + 允許使用指向函數(shù)的指針進行操作。這種方法的典型用途是將函數(shù)作為參數(shù)傳遞給另一個函數(shù)。函數(shù)指針的聲明語法與常規(guī)函數(shù)聲明語法相同,只不過函數(shù)的名稱被括在括號()之間,并在名稱前插入星號(*) :
// pointer to functions
#includeusing 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<
在上面的示例中,minus
是指向具有兩個 int 類型參數(shù)的函數(shù)的指針。它被直接初始化為指向函數(shù)subtraction
:
int (* minus)(int,int) = subtraction;
函數(shù)指針在實際的主要的應(yīng)用是回調(diào)函數(shù),關(guān)于函數(shù)指針與回調(diào)函數(shù)的細節(jié)可以看下面的博客:
函數(shù)指針與回調(diào)函數(shù)
參考網(wǎng)址:https://cplusplus.com/doc/tutorial/pointers/?
一個在線c++運行的網(wǎng)站(沒有科學(xué)上網(wǎng),可能有時打不開):C++ Shell?
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
本文題目:C/C++中的指針[非常全面]-創(chuàng)新互聯(lián)
地址分享:http://www.dlmjj.cn/article/heiec.html