1. 程式人生 > 遊戲 >Xbox360負責人、傳奇高管彼得·摩爾宣佈加入Unity

Xbox360負責人、傳奇高管彼得·摩爾宣佈加入Unity

技術標籤:C語言筆記c語言

指標是一種特殊的資料型別,它是一個可以儲存資料變數地址的資料型別。運用指標程式設計是C語言最主要的風格之一。利用指標變數可以表示各種資料結構;能很方便地使用陣列和字串;並能象組合語言一樣處理記憶體地址,從而編出精練而高效的程式。

地址指標的概念

在計算機中,軟體中的所有變數都是儲存在記憶體中,一般在記憶體中一個位元組就是一個儲存單元。不同的資料型別佔有的儲存單元的個數不一樣。如在32位系統中int型變數佔有四個位元組、char型變數佔有一個位元組等。那麼在計算機中為了正確的訪問每一個記憶體單元,因此每個記憶體單元都有特定的編號。根據一個記憶體單元的編號即可準確地找到某一個記憶體單元。記憶體單元的編號也叫做

地址

當每一個記憶體單元有一個特定的編號,那麼我們根據記憶體單元的編號(地址)可以找到對應編號的記憶體單元,那麼我們就將這個地址稱為指標。嚴格來說,一個地址就是一個指標,指標是一個常量。但是一般我們在將指標變數也簡稱為指標。

指標變數是指儲存記憶體單元的編號(指標或者地址)的一個指標型別的變數。這個變數可以儲存某一個變數、陣列、結構體和函式等一些型別的地址。由於陣列、結構體和函式等一些複雜的型別在記憶體中是連續儲存的,那麼當我們找到他們的首地址,就可以根據首地址訪問陣列、結構體和函式的類容。

瞭解完指標變數之後,我們還需要了解和指標變數對應的一個概念,這個概念就是變數的指標,變數的指標就指的是變數存放的地址(記憶體編號)

在我們使用指標的時候還需要理解記憶體單元的地址和記憶體單元的內容。記憶體單元的地址和記憶體單元的內容是兩個不同的概念。記憶體單元的地址就是我們要使用的變數儲存的位置。而記憶體單元的內容就是我們要獲取的值,一般我們使用指標的目的就是獲取記憶體單元中的內容。舉例:記憶體單元的地址就相當於酒店的客房,我們可以根據門牌號進行入住和退房。而記憶體單元的內容住裡面的人。

在C語言中,一種資料型別或資料結構往往都佔有一組連續的記憶體單元。 用“地址”這個概念並不能很好地描述一種資料型別或資料結構,而“指標”雖然實際上也是一個地址,但它卻是一個數據結構的首地址,它是“指向”一個數據結構,因而概念更為清楚,表示更為明確。 這也是引入“指標”概念的一個重要原因。

變數的指標(變數地址)和指向指標的變數(指標變數)

上面介紹過了,指標就是地址,那麼變數的指標就指的是變數的地址。指向指標的變數就是指標變數,是一個存放指標(地址)的變數。

1 指標變數的定義

指標變數的定義和其他變數的定義一樣,都需要指出是資料型別,這些資料型別包括基本的資料型別,也可以使用自定義的複雜型別。

定義格式如下:

資料型別 * 指標變數名;

1)資料型別指的是我們要定義的指標變數指向的地址中儲存資料的型別

2)*表示這是一個指標變數,這是區別於其他變數的定義,如果在定義的時候不使用*號,則表示定義的是普通變數而不是指標變數。

3)指標變數名就是我們定義 的指標變數的名稱,可以根據這個名稱操做指標變數。

例如:

int * a;//這是定一個int型的指標變數,這個指標變數只能儲存int型資料所在的地址。不能儲存其他型別的陣列所在地址。如果儲存其他型別的資料地址,資料可能發生錯亂導致軟體宕機。
int b;//這個沒有在b之前增加*,則這個表示為一個普通的int型變數,這個b中儲存的是一個int型資料,而不是地址。
int *p2; /*p2是指向整型變數的指標變數*/
float *p3; /*p3是指向浮點變數的指標變數*/
char *p4; /*p4是指向字元變數的指標變數*/

注意:雖然指標變數中儲存的是一個地址,但是在指標變數使用的時候會根據資料型別指向一個記憶體空間的首地址,而這個記憶體空間的大小是根據資料型別而定,如果指標變數指向的資料空間大小和資料型別大小不一樣,那麼在使用指標變數的過程中可能會對其他的記憶體空間修改,會造成系統的崩潰。因此指標變數只能儲存跟指標變數相同的資料型別所在的地址,不能儲存其他型別變數所在的地址。如p2這個指標變數只能儲存 int型資料指向的地址,而不能儲存float、char等其他型別所在的地址。

2 指標變數的使用

指標變數的使用和普通變數的使用一樣,都需先對變數賦值,賦值之後才能使用變數。如果指標變數不賦值的話操作指標會造成系統的崩潰。指標變數只能儲存地址,因此給指標變數賦的值都是地址。指標變數在記憶體中佔有四個位元組大小。

1)指標兩個運算子

  1. 取出變數的地址使用&符號,這個符號就是取出變數的地址。如果是陣列或者結構體直接將陣列名可以賦值給指標變數,因為在陣列中陣列名就是陣列的首地址。
  2. 取出指標變數指向地址中的值使用*。這個符號是量指標變數指向地址中儲存的具體數值取出來。

例如:

int a; //定義一個普通的int型變數
int *b; //定義一個int型指標變數
b = &a;//這個語句是將a存放的地址取出來,賦值給指標變數。
a = *b;//這個語句是將指標變數指向的地址中的陣列取出來賦值給a變數

2) 指標變數的初始化

指標在定義的時候可以進行初始化賦值,初始化方法如下:

例如:

int a;
int *p = &a; //在定義的時候,可以給指標變數初始化賦值一個地址。

也可以在定義的時候不進行初始化,在使用之前進行初始化

例如:

int a;
int *p;
p= &a; //在定義的時候,可以給指標變數初始化賦值一個地址。

3)指標變數使用的注意事項

(1)不允許把一個數賦予指標變數,故下面的賦值是錯誤的。

例如:

int *p;
p = 1000;//這樣賦值按照邏輯是可以講通 但是這種賦值時講一個野指標賦值給指標變數,這樣會操做一些系統記憶體,因此可能會造成系統的崩潰,所以一般不會直接個指標變數賦值一個數值。

(2) 被賦值的指標變數前不能再加“*”說明符,如寫為*p=&a 也是錯誤的。

例如:

int *p; int a;
*p = &a;//這種賦值方式是不允許的,在p之前增加一個*代表的是取出p指標變數指向地址的值。如果p指標變數沒有初始化,會報錯。如果指標變數初始了,那麼*p代表的是一個普通的int型資料,而&a代表的是一個地址也是一個常量。常量不能給常量賦值。

(3)指標變數和一般變數一樣,存放在它們之中的值是可以改變的,也就是說可以改變它們的指向。

例如:

int a;int b;
int *p;
p = &a;//p指標變數指向a變數所在的地址
P = &b;//p指標指向b變數所在的地址

(4)通過指標訪問它所指向的一個變數是以間接訪問的形式進行的,所以比直接訪問一個變數要費時間,而且不直觀,因為通過指標要訪問哪一個變數,取決於指標的值(即指向)。

由上圖可見,p指標變數儲存的是a變數的地址0xff00,而0xff00的地址中儲存的是數值1000。而p指標是儲存在地址0xff04中。

如使用 p = &a語句則是將0xff00的儲存到p變數所對應的地址中。

使用 int c = *p語句則是將p指向的地址0xff00中儲存的資料1000賦值給c變數。

3 指標變數作為函式引數

指標變數和普通的變數一樣也可以作為函式的引數傳遞。普通變數的作為函式引數進行傳遞稱為值傳遞,而指標變數作為引數傳遞稱為地址傳遞。這兩者傳遞使用效果不一樣。使用值傳遞不會給變實參的值。但是使用地址傳遞的時候是可以給變實參的值的。

在函式引數中,如果使用陣列或者指標傳遞引數,函式都會將引數當做指標進行處理。因此在使用陣列和指標進行引數傳遞的時候,都需要傳遞指標或者陣列的大小。當時用字串陣列進行引數傳遞的時候,可以不用傳遞字元陣列的大小,字元陣列的大小可以根據 sizeof函式和 \0 標誌位進行獲取。

例如: 地址傳遞

swap(int *p1,int *p2)
{
     int temp;
    temp=*p1;
    *p1=*p2;
    *p2=temp;
}
main()
{
    int a,b;
    int *pointer_1,*pointer_2;
    scanf("%d,%d",&a,&b);
   pointer_1=&a;pointer_2=&b;
    if(a<b) swap(pointer_1,pointer_2);
    printf("\n%d,%d\n",a,b);
}

如以上函式所示: swap函式就是一個以指標作為函式引數的函式。在呼叫swap函式的過程中將p1指向地址的變數和p2指向地址的變數互換。在main函式中輸出a和b的結果。則a和b的值發生了互換。

例如:值傳遞

swap(int a,int b)
{
    int temp;
   temp=a;
    a=b;
   b=a;
}
main()
{
    int a,b;
    int *pointer_1,*pointer_2;
  scanf("%d,%d",&a,&b);
  pointer_1=a;pointer_2=b;
  if(a<b) swap(pointer_1,pointer_2);
  printf("\n%d,%d\n",a,b);
}

以上這個函式是使用值傳遞進行更換a和b的值,輸出結果發現a和b的值並沒有發生互換,那麼為啥會出現這種情況。一下做一個簡要的說明:

在使用值傳遞的時候,函式會將實參的值傳遞給形參,這裡要特別說明一下,有些時候在呼叫的函式的時候實參和形參的是名稱是一樣的,但是這兩變數是獨立的之間沒有關聯。因此在值傳遞的時候,當實參把值傳遞給形參的時候就和呼叫的函式沒有關係了。而在子函式中雖然對形參的a和b的數值進行替換。但是在函式執行結束之後,軟體會自動釋放形參的值。因此當主函式輸出的時候,仍然輸出的是沒有改變的實參的值。

當時用地址傳遞的時候,我們將一個變數的地址以引數的形式傳遞過去,而在子函式中通過地址將地址中的值進行給變。當子函式執行結束之後所使用的變數釋放了,但是記憶體中的地址是唯一的,因此當我們在main函式中輸出a的值的時候,a變數所在地址中的值已經發生了改變。

4 指標變數作為函式返回值(指標型函式

所謂函式型別是指函式返回值的型別。在C語言中允許一個函式的返回值是一個指標(即地址),這種返回指標值的函式稱為指標型函式。指標作為函式返回值和普通變數一樣,只不過返回的是一個地址。程式設計師可根據返回的地址獲取或者修改地址中的值。

定義指標型函式的一般形式為:

型別說明符 *函式名(形參表) 
{ 
/*函式體*/
} 

其中函式名之前加了“*”號表明這是一個指標型函式,即返回值是一個指標。型別說明符表示了返回的指標值所指向的資料型別。

如:

int *ap(int x,int y)
{
    /*函式體*/
}

表示ap是一個返回指標值的指標型函式,它返回的指標指向一個整型變數。

使用指標作為函式返回值需要注意一下事項:

(1)當函式返回的指標變數中儲存的呼叫函式中定義的變數的地址,那麼在呼叫函式結束之後,此地址中的值會被銷燬。因此返回的指標中會有地址,但是此地址中的值無法獲取到。

(2)當函式返回的指標變數中儲存的是字元常量、其他常量、使用static修飾的靜態變數、全域性變數或者生命週期大於呼叫函式生命週期的變數地址的時候,返回的指標中可以獲取到地址和地址中的值。

(3)返回值的指標型別必須和函式定義的指標型別一致,並且保證返回的地址中一定有值陣列指標和指向陣列的指標變數

5 指標變數的運算子

賦值運算:指標變數的賦值運算有以下幾種形式。

指標變數初始化賦值,前面已作介紹。

1)把一個變數的地址賦予指向相同資料型別的指標變數。

例如:


int a,*pa;
pa=&a; /*把整型變數a的地址賦予整型指標變數pa*/

2)把一個指標變數的值賦予指向相同型別變數的另一個指標變數。

如:

int a,*pa=&a,*pb;
pb=pa; /*把a的地址賦予指標變數pb*/

由於pa,pb均為指向整型變數的指標變數,因此可以相互賦值。

3)把陣列的首地址賦予指向陣列的指標變數。

例如:

int a[5],*pa;
pa=a;

(陣列名錶示陣列的首地址,故可賦予指向陣列的指標變數pa)

也可寫為:

pa=&a[0]; /*陣列第一個元素的地址也是整個陣列的首地址, 也可賦予pa*/

當然也可採取初始化賦值的方法:

int a[5],*pa=a;

4)把字串的首地址賦予指向字元型別的指標變數。

例如:

char *pc;
pc="C Language";

或用初始化賦值的方法寫為:

char *pc="C Language";

這裡應說明的是並不是把整個字串裝入指標變數,而是把存放該字串的字元陣列的首地址裝入指標變數。在後面還將詳細介紹。

5)把函式的入口地址賦予指向函式的指標變數。

例如:

int (*pf)();
pf=f; /*f為函式名*/

6)加減算術運算

對於指向陣列的指標變數,可以加上或減去一個整數n。設pa是指向陣列a的指標變數,則pa+n,pa-n,pa++,++pa,pa--,--pa運算都是合法的。指標變數加或減一個整數n的意義是把指標指向的當前位置(指向某陣列元素)向前或向後移動n個位置。

應該注意,陣列指標變數向前或向後移動一個位置和地址加1或減1在概念上是不同的。因為陣列可以有不同的型別,各種型別的陣列元素所佔的位元組長度是不同的。如指標變數加1,即向後移動1 個位置表示指標變數指向下一個資料元素的首地址。而不是在原地址基礎上加1

例如:

int a[5],*pa;
pa=a; /*pa指向陣列a,也是指向a[0]*/
pa=pa+2; /*pa指向a[2],即pa的值為&pa[2]*/

指標變數的加減運算只能對陣列指標變數進行,對指向其它型別變數的指標變數作加減運算是毫無意義的。

兩個指標變數之間的運算:只有指向同一陣列的兩個指標變數之間才能進行運算,否則運算毫無意義。

兩指標變數相減:兩指標變數相減所得之差是兩個指標所指陣列元素之間相差的元素個數。實際上是兩個指標值(地址)相減之差再除以該陣列元素的長度(位元組數)。例如pf1和pf2是指向同一浮點陣列的兩個指標變數,設pf1的值為2010H,pf2的值為2000H,而浮點陣列每個元素佔4個位元組,所以pf1-pf2的結果為(2000H-2010H)/4=4,表示pf1和 pf2之間相差4個元素。兩個指標變數不能進行加法運算。 例如,pf1+pf2是什麼意思呢?毫無實際意義。

兩指標變數進行關係運算:指向同一陣列的兩指標變數進行關係運算可表示它們所指陣列元素之間的關係。

例如:

pf1==pf2表示pf1和pf2指向同一陣列元素;
pf1>pf2表示pf1處於高地址位置;
pf1<pf2表示pf2處於低地址位置。

指標變數還可以與0比較。

設p為指標變數,則p==0表明p是空指標,它不指向任何變數;

p!=0表示p不是空指標。

空指標是由對指標變數賦予0值而得到的。

例如:

#define NULL 0
int *p=NULL;

對指標變數賦0值和不賦值是不同的。指標變數未賦值時,可以是任意值,是不能使用的。否則將造成意外錯誤。而指標變數賦0值後,則可以使用,只是它不指向具體的變數而已。

字串的指標(字串的地址)和指向字串的針指變數(字串指標)

1 字串在記憶體中的儲存形式和使用方式

在C語言中,字串一般是以字元陣列的形式存在,即使在定義的時候可以直接將一個字串賦值給一個沒有設定大小的字元陣列中,但是在記憶體中字串和字元陣列的儲存形式一樣,只不過在在字元陣列的最後一個元素儲存的是字串結束標誌‘\0’。由於字串結束標誌的存在,因此字元陣列在直接賦值字串的時候可以不用設定字元陣列的大小,可以通過’\0’來控制和計算字元陣列的大小。

例如;

char str[] = “hello word”;//這個字元陣列沒有定義大小,但是可以使用,他等價於//char str[11] = {‘h’,’e’,’l’,’l’,’o’,’’,’w’,’o’,’r’,’d’,’\0’};

在記憶體中的儲存形式為:

……

……

str所在位置,既字串首地址str[0]

h

str[1]

e

str[2]

l

str[3]

l

str[4]

o

str[5]

空格

str[6]

w

str[7]

o

str[8]

r

str[9]

d

str 字元陣列的最後一位,結束標誌str[11]

\0

在C語言中字串有兩種使用方式:

1)使用一個字元陣列來儲存和訪問字串

例如:char str[] = “hello word”;

2)使用指標類訪問和儲存字串

例如:char *str = “hello word”;

需要注意的是當一個指標變數在未取得確定地址前使用是危險的,容易引起錯誤。但是對指標變數直接賦值是可以的。因為C系統對指標變數賦值時要給以確定的地址。

如果是以字元陣列的方式使用字串的話,直接可以通過首地址str進行使用,也可以通過字元陣列下標的形式訪問字串中的每一個元素。

如果用字串指標指向一個字串字串指標變數的定義說明與指向字元變數的指標變數說明是相同的。只能按對指標變數的賦值不同來區別。對指向字元變數的指標變數應賦予該字元變數的地址

如:

char c,*p=&c;//表示p是一個指向字元變數c的指標變數。

而:

char *str = “hello word”;則表示str是一個指向字串的指標變數。把字串的首地址賦予str。

上例中,首先定義str是一個字元指標變數,然後把字串的首地址賦予str(應寫出整個字串,以便編譯系統把該串裝入連續的一塊記憶體單元),並把首地址送入string。

程式中的:

char *ps="C Language";

等效於:

char *ps;

ps="C Language";

對字元指標進行增加或者減少可以訪問字串中不同的元素,例如char *str = “hello word”;

str 指向的是字串的首地址h所在的地址,str+1則指向e字元所在的地址。因此通過字元指標可以是使用整個字串也可以獲取字元中的每一個元素。

2 使用字串指標變數與字元陣列的區別

字串指標變數就是指向字串首地址的指標變數。而字串的名稱就是字串的首地址,因此使用字串指標和字串名有一些類似之處。但是也有地方不同。例如用字元陣列和字元指標變數都可實現字串的儲存和運算,但是使用字串指標不能使用sizeof函式獲取字串的大小,但是使用字串名稱可以通過sizeof函式獲取字串的大小。

因此在使用時應注意以下幾個問題:

1)字串指標變數本身是一個變數,用於存放字串的首地址。而字串本身是存放在以該首地址為首的一塊連續的記憶體空間中並以‘\0’作為串的結束。字元陣列是由於若干個陣列元素組成的,它可用來存放整個字串。

對字串指標方式

char *ps="C Language";

可以寫為:

char *ps;

ps="C Language";

而對陣列方式:

static char st[]={"C Language"};

不能寫為:

char st[20];

st={"C Language"};

而只能對字元陣列的各元素逐個賦值。

從以上幾點可以看出字串指標變數與字元陣列在使用時的區別,同時也可看出使用指標變數更加方便。

前面說過,當一個指標變數在未取得確定地址前使用是危險的,容易引起錯誤。但是對指標變數直接賦值是可以的。因為C系統對指標變數賦值時要給以確定的地址。

因此,

char *ps="C Langage";

或者

char *ps;

ps="C Language";

都是合法的。

陣列指標(指向陣列的指標變數)

指標作為一個可以儲存變數地址的變數,不僅可以儲存普通變數的的地址,也可以儲存一些複雜資料結構和陣列。而當指標變數指向一個數組地址的時,可以通過指標變數訪問陣列儲存的地址對陣列進行操作。簡單點說陣列指標就是一個指向陣列地址的指標變數。

這個指標變數可以儲存了陣列中任意元素的地址(包括首地址或者其他地址)。

陣列指標在定義的時候,指標型別必須和陣列中元素的型別一樣。如果不一樣在使用的時候會出現錯誤。

1 陣列在記憶體中的儲存結構

在介紹陣列的時候就簡單介紹過陣列在記憶體中的儲存方式,下面就分別對一元陣列和二元陣列在記憶體中儲存的方式做一個簡單的介紹。

1)一元陣列在記憶體中的儲存結構

一元陣列就是相同型別資料的一個集合,是連續儲存在記憶體中的。而陣列名就是這個陣列在記憶體中的首地址。簡單的就可以將陣列的陣列名看成一個指標變量了。

例:

int p[10] = {0,1,2,3,4,5,6,7,8,9};

這是定義的一個int型整形陣列,陣列中儲存的資料為0~9.那他在記憶體中數如何儲存。

假設一下是一系列的儲存單元

內容

……

0

1

2

3

4

5

6

7

8

9

……

地址

……

P
(0xff00)


(0xff28)

由以上圖可以看出陣列的首地址的編號為0xff00,也是陣列下標為0 的元素所在位置。而在陣列中陣列的名稱就是陣列的首地址。因此陣列的首地址我們可以直接使用陣列名稱也可以取0號下標所在的地址。

還有在陣列中相鄰元素之間的地址地址並不一定是連續,這是因為在陣列定義的時候不同的資料型別佔有大的記憶體大小不一樣,如上圖所示,定義的是一個int型的陣列,由於int型資料在記憶體中佔有四個位元組,因此相鄰元素的地址指向相差四個位元組。

2)二元陣列在記憶體中的儲存結構

如果說一元陣列是相同型別資料的集合的話,那麼二元陣列就是相同資料型別的一元陣列的集合。為什麼這麼說?C語言允許把一個二維陣列分解為多個一維陣列來處理。因此我們可以將一個二元陣列當做為幾個一元陣列來看待。

例:int a[5][3];//定義一個5行3列數二元陣列,那麼這5行3列的二元陣列就可以拆分為5個含有3個元素的一元陣列。

a = {{1,2,3},{4,5,6},{7,8,9},{10,11,12},{13,14,15}};

對於這個陣列而言我們一般看來的形式如下所示:

1列

2列

3列

第一行(a)

1 (a)(a[0]) (a[0][0])

2 (a[0][3])

3 (a[0][2])

第二行

4 (a[1]) (a[1][0])

5 (a[1][1])

6 (a[1][1])

第三行

7 (a[2]) (a[2][0])

8 (a[2][1])

9 (a[2][2])

第四行

10 (a[3]) (a[3][0])

11 (a[3][1])

12 (a[3][2])

第五行

13 (a[4]) (a[4][0])

14 (a[4][1])

15 (a[4][1])

而二元陣列在記憶體中的儲存方式並非如此,而是一行一行的進行儲存的。因此每一行都有一個起始地址。

……

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

……

……

這個是陣列首地址也是第一行首地址

第二行首地址

第三行首地址

第四行首地址

第五行首地址

……

由上圖可知二元陣列的名稱a首地址可以是陣列名、第一行a[0的首地址、第一個元素a[0][0]的地址。二元是陣列中的每一個元素是連續儲存在記憶體中,而且是一行一行程序儲存的。

2 陣列指標引用陣列元素

1)陣列指標引用一元陣列的元素

在使用指標變數指向陣列地址的時候,如果指標變數p已指向陣列中的一個元素,則p+1指向同一陣列中的下一個元素。

引入指標變數後,就可以用兩種方法來訪問陣列元素了。

如果p的初值為&a[0],則:p+i和a+i就是a[i]的地址,或者說它們指向a陣列的第i個元素。

*(p+i)或*(a+i)就是p+i或a+i所指向的陣列元素,即a[i]。例如,*(p+5)或*(a+5)就是a[5]。指向陣列的指標變數也可以帶下標,如p[i]與*(p+i)等價。

根據以上敘述,引用一個數組元素可以用:

(1)下標法,即用a[i]形式訪問陣列元素。在前面介紹陣列時都是採用這種方法。

(2)指標法,即採用*(a+i)或*(p+i)形式,用間接訪問的方法來訪問陣列元素,其中a是陣列名,p是指向陣列的指標變數,其處值p=a。

幾個注意的問題:

(1)指標變數可以實現本身的值的改變。如p++是合法的;而a++是錯誤的。因為a是陣列名,它是陣列的首地址,是常量。

(2)要注意指標變數的當前值。如果指標變數進行了運算 且指標變數發生了變化。那麼剛開始的指標變數會指向別的地址而不是剛開始的值。

(3)雖然定義陣列時指定它的個元素數確定的,但指標變數可以指到陣列以後的記憶體單元,系統並不認為非法。因此在指標指向陣列的時候一定要注意指標是否越界。

(4)*p++,由於++和*同優先順序,結合方向自右而左,等價於*(p++)。

(5)*(p++)與*(++p)作用不同。若p的初值為a,則*(p++)等價a[0],*(++p)等價a[1]。

(6)(*p)++表示p所指向的元素值加1。

(7)如果p當前指向a陣列中的第i個元素,則*(p--)相當於a[i--];*(++p)相當於a[++i];*(--p)相當於a[--i]。

2)陣列指標引用二元陣列的元素

前面介紹了二元陣列可以拆分為幾個一元陣列,因此二元陣列除了二元陣列的首地址之外,分別還對應有每個一元陣列的地址。

設有整型二維陣列a[3][4]

它的定義為:

int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}}

a是二維陣列名,a代表整個二維陣列的首地址,也是二維陣列0行的首地址,也會是二維陣列中第一個元素的地址。a+1代表第一行的首地址,a[0]是第一個一維陣列的陣列名和首地址,由此可得出:a+i,a[i],*(a+i),&a[i][0]是等同的,他們是每一個一元陣列的首地址。

由a[i]=*(a+i)得a[i]+j=*(a+i)+j。由於*(a+i)+j是二維陣列a的i行j列元素的首地址,所以,該元素的值等於*(*(a+i)+j)。

3指向多維陣列的指標變數

把二維陣列a分解為一維陣列a[0],a[1],a[2]之後,設p為指向二維陣列的指標變數。可定義為:

int (*p)[4]

它表示p是一個指標變數,它指向包含4個元素的一維陣列。若指向第一個一維陣列a[0],其值等於a,a[0],或&a[0][0]等。而p+i則指向一維陣列a[i]。從前面的分析可得出*(p+i)+j是二維陣列i行j 列的元素的地址,而*(*(p+i)+j)則是i行j列元素的值。

二維陣列指標變數說明的一般形式為:

型別說明符 (*指標變數名)[長度];

其中“型別說明符”為所指陣列的資料型別。“*”表示其後的變數是指標型別。“長度”表示二維陣列分解為多個一維陣列時,一維陣列的長度,也就是二維陣列的列數。應注意“(*指標變數名)”兩邊的括號不可少,如缺少括號則表示是指標陣列(本章後面介紹),意義就完全不同了。

使用指標和陣列名稱引用陣列元素的區別

使用指標和陣列名來引用陣列元素在賦值和取值中沒有太大差別,指示在獲取陣列大小的時候,我們使用sizeof通過陣列名獲取陣列大小,但是無法通過指標變數獲取陣列大小。

指標陣列

指標陣列指的是一個數組的儲存的元素型別為指標變數。 指標陣列是一組有序的指標的集合。 指標陣列的所有元素都必須是具有相同儲存型別和指向相同資料型別的指標變數。

指標陣列說明的一般形式為:

型別說明符 *陣列名[陣列長度]

其中型別說明符為指標值所指向的變數的型別。

例如:

int *pa[3]

表示pa是一個指標陣列,它有三個陣列元素,每個元素值都是一個指標,指向整型變數。

指標陣列和二維陣列指標變數的區別。這兩者雖然都可用來表示二維陣列,但是其表示方法和意義是不同的。

二維陣列指標變數是單個的變數,其一般形式中"(*指標變數名)"兩邊的括號不可少。而指標陣列型別表示的是多個指標(一組有序指標)在一般形式中"*指標陣列名"兩邊不能有括號。

例如:

int (*p)[3];

表示一個指向二維陣列的指標變數。該二維陣列的列數為3或分解為一維陣列的長度為3。

int *p[3]

表示p是一個指標陣列,有三個下標變數p[0],p[1],p[2]均為指標變數。

指標陣列也常用來表示一組字串,這時指標陣列的每個元素被賦予一個字串的首地址。指向字串的指標陣列的初始化更為簡單。

例如:

char *name[]={"Illagal day","Monday","Tuesday",
                "Wednesday","Thursday","Friday","Saturday","Sunday"};

完成這個初始化賦值之後,name[0]即指向字串"Illegal day",name[1]指向"Monday"......。

指標陣列也可以用作函式引數。

二級指標(指向指標的指標)

指標變數既然是一個變數,那麼他應給也是存放在記憶體中,只要是儲存在記憶體中,那必然有對應的地址。因此指標變數也是儲存在記憶體中,對應著一個記憶體地址。因此我們可以將一個指標變數的地址賦值給另一個指標變數。而儲存指標變數地址的指標變數稱為指向指標的指標。簡單來說就是一個指標變數存放的又是另一個指標變數的地址,則稱這個指標變數為指向指標的指標變數。指向指標的指標變數又稱為二級指標。

指向指標的指標變數的定義

char **p;

char 代表的是指向指標的指標變數的型別
** 兩個**表示這定義的是一個指向指標的指標變數
p為指向指標的變數名

1)指向指標的指標變數的使用

例如:

int a = 100;//定義一個普通變數,普通變數中儲存一個數字100
int *p = &a; //定義一個指標變數指向變數a的地址
int **pp =&p;//定義一個指向指標的指標變數,並且指向指標p的地址。

編寫如下程式碼:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int a = 100;
    int *p = &a;
    int **pp = &p;
    printf("%p\n",&a);
    printf("%p\n", p);
    printf("%p\n", &p);
    printf("%p\n", pp);
    printf("===================\n");
    printf("%d\n", a);
    printf("%d\n", *p);
    printf("%p\n", *pp);
    printf("%d\n", **pp);
    system("pause");
    return 0;
}

輸出結果如下:

0113F95C
0113F95C
0113F950
0113F950
===================
100
100
0113F95C
100

根據結構可以看出 p的輸出結果是0113F95C,他是變數a的地址,pp的輸出結果為0113F950他是指標變數p的地址。由此可見指標變數指向的是變數的地址,而指向指標的變數指向的是指標的地址。

*p的輸出結果是100,*pp輸出的結果是p的地址。**pp輸出的結果是100.

由此可見在指向指標的指標變數前面多加一個*,則指向的內容會向前推進一級。

將**pp可以看成*(*pp),pp中儲存的是指標指標變數的地址,因此*pp會取出指向指標的指標變數的內容,而指向指標的指標變數的內容為p的地址。因此*(*pp)等價於*(p)。

*(p)等價於*p等價於a變數的值。因此**pp等價於*p等價於a。

函式指標

1函式指標的定義

在C語言中,一個函式總是佔用一段連續的記憶體區,而函式名就是該函式所佔記憶體區的首地址。我們可以把函式的這個首地址(或稱入口地址)賦予一個指標變數,使該指標變數指向該函式。然後通過指標變數就可以找到並呼叫這個函式。我們把這種指向函式的指標變數稱為“函式指標變數”。

函式指標變數定義的一般形式為:

型別說明符 (*指標變數名)();

其中“型別說明符”表示被指函式的返回值的型別。“(* 指標變數名)”表示“*”後面的變數是定義的指標變數。最後的空括號表示指標變數所指的是一個函式。

2函式指標的使用

呼叫函式的一般形式為:

(*指標變數名) (實參表)

使用函式指標變數還應注意以下兩點:

1) 函式指標變數不能進行算術運算,這是與陣列指標變數不同的。陣列指標變數加減一個整數可使指標移動指向後面或前面的陣列元素,而函式指標的移動是毫無意義的。

2)函式呼叫中"(*指標變數名)"的兩邊的括號不可少,其中的*不應該理解為求值運算,在此處它只是一種表示符號。

例如:

#include <stdio.h>
#include <stdlib.h>
void fun(int a);//定義需要呼叫的函式,如果函式的實現在main函式後面。則需要在main函式之前定義
main()
{
    int a = 10; //定義一個常量a,賦值為100
    void (*fun1)();//定義一個函式指標
    fun1 = fun;//將要呼叫的函式賦值給函式在指標,
    fun1(a); //通過函式指標呼叫函式
    system("pause");
}

void fun(int a)//函式的實現
{
    printf("%d\n", a);
}

輸出結果為

10

三大指標

一、野指標

野指標指的是將一個未知內容的地址值賦值給一個指標變數。野指標的存在可能會導致軟體出現讀寫的錯誤。

例:

int *p = 10000;//給一個int型指標變數賦值一個數值,這個資料可以認為是一個地址,但是這個地址中儲存的資料我們不知道。這種就是野指標

二、空指標

空指標指的是給一個指標變數賦值一個為0的空間地址(NULL),操作空指標變數軟體會出現報錯,但是空指標可用於條件判斷。

int *p = NULL;//這種賦值是可以的,可以用於判斷,但是後續不能在對此指標變數不能操作。

萬能指標

萬能指標指的是使用void定義的指標變數,萬能指標在使用的時候只需要使用強轉將為自己想要的型別即可。

例:

void * p;
(int *)p= &a;//將萬能指標變數強制轉化為int * 型別的指標變數

const修飾指標

1 const修飾普通變數

const int a = 0;

const修飾普通變數,雖然不能直接給a 賦值,但是可以通過指標變數修改變數地址中的值

2 const修飾指標型別

const int * p = &a;

使用const 修飾指標型別,則不能改變指標變數指向的記憶體地址中的值,但是可以修改指標變數指向的地址。

3 const修飾指標變數

int * const p = &a;

使用const修飾指標變數,這種修飾能改變指標變數指向地址中的值,但是不能改變指標指向的地址。

4 const修飾指標型別也修飾指標變數

const int * const p = &a;

const修飾指標型別,也修飾指標變數,不能改變指標指向地址的值也不能修改指標指向的地址