[C程序設計語言]第三部分,c程序設計第三部分
聲明:原創作品,轉載時請注明文章來自
SAP師太博客,並以超鏈接形式標明文章原始出處,否則將追究法律責任!
初始化... 1
指針... 1
指針與地址... 3
指針與數組... 4
指針數組、數組指針... 5
int (*a)[3]、int *a[]區別... 6
幾種不同指針的定義... 8
初始化
在不進行顯示初始化時,外部變量與靜態變量都將被初始化為0,而自動變量和寄存器變量的初值則沒有定義。
對於外部變量與靜態變量來說,初始化表達式必須是常量表達式,且只初始化一次(在程序開始執行前進行初始化)。對於自動變量與寄存器變量,則在每次進行函數或程序塊時都將被初始化。
在定義數組時,可以指定數組大小的同時又初始化內容,也可只指定元素內容,則不指定大小,這時數組的大小是初始化元素的個數。
在既指定大小又指定內容時,指定的大小可以大於初始化元素個數,沒有初始化的元素的內容為隨機數。但指定的大小不能小於後面初始化的元素的個數,否則編譯時會有警告,並且即使初始化了超過指定大小的元素內容,訪問時得到的也是隨機值。
如果初始化元素個數比指定的數組大小小時(但一定要初始化,那怕初始化時沒有內容,只聲明不初始時元素的值還是一個隨機值),則沒有初始化的表達式元素將被初始化為0,如:
int a[4] ={};//每個元素的值為0
int b[4]={1};//除第一個元素外,其他三個元素的值都是0
int c[4];//所有元素都是隨機值
字符數組的初始化有以下同人種方式:
char str[] = "string";
char str[] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
指針
指針是把其他變量地址作為其值的變量。變量通常直接包含一個具體的值,而指針包含的是擁有具體值的變量的地址。變量名直接引用了一個值,而指針是間接地引用了一個值。
countPtr是一個指向整數值的指針
可以把指針初始化為0、NULL或某個地址。具有值NULL的指針不指向任何值。NULL是在頭文件<stdio.h>中定義的符號常量。把一個指針初始化為0等價於把它初始化為NULL。
當把0賦值給指針時,編譯器先把0轉換為指向合適數據類型的指針。
值0是唯一能夠直接賦給指針變量的整數值。
int y;
int * yPtr;
y = 7;
yPtr = &y;
printf("%p %p\n\n", &y, yPtr);//0022FF4C 0022FF4C
printf("%d %d\n\n", y, *yPtr);//7 7
printf("%p %p %p\n\n",&yPtr, &*yPtr, *&yPtr);//0022FF48 0022FF4C 0022FF4C
地址運算&的操作數必須是一個變量,不能把地址運算符用於常量、表達式或存儲類別被聲明為register的變量上。
引用沒有被正確初始化或沒有指向具體的內存單元都會導致致命執行錯誤或意外地修改重要的數據。
C語言中數組是以引用的方式傳遞的,而結構是以傳值方式傳遞。數組是用同一個名字存儲許多具有相同類型的相關數據項聚合數據類型,結構是用同一個名稱存儲許多不同類型的相關數據項。
用指向常量數據的指針傳遞諸如結構一樣的大型對象可獲得傳引用調用的性能和傳值調用的安全性。
如果兩個指針類型相同,那麼可以把一個指針賦給另一個指針,否則必須用強制類型轉換運算符把賦值運算符右邊指針的類型轉換為賦值運算符左邊指針的類型(一般編譯時出現警告,但如果是不同類型的非指針變量,如將一個float 賦值給一個int時不會出現警告)。指向void類型的指針(即 void *)是個例外情形,它可以表示任何類型的指針。任何類型的指針都都可以賦給指向void類型的指針,指向void類型的指針也可以賦給任何類型的指針。這兩種情況都不需要使用類型轉換。
一個指向void類型的指針僅僅包含了不知道數據類型的地址,編譯器不知道該指針到底要引用多少字節,編譯器必須知道數據類型後才能確定某個特定的指針要引用的字節數,因些,對於void類型的指針,編譯器不能根據類型確定它引用字節數,所以除了不能引用void類型指向外,還不能對它進行指針相關的運算。
指針比較常用來判斷某個指針是否是NULL,除非是指向同一數組,指針運算是沒有意義的。
指針可以轉換為整型,但此整型必須足夠大。整型對象可以顯式地轉換為指針。這種映射總是將一個足夠寬的從指針轉換來的整數轉換為同一個指針
指向某一類型的指針可以轉換為指向另一類型的指針,但是,如果該指針指向的對象不滿足一定的存儲對齊要求,則結果指針可能會導致地址異常。指向某對象的指針可以轉換為一個指向具有更小或相同存儲對齊限制的對象的指針,並以保證原封不動地再轉換回來。
指針可以轉換為void * ,並可以再原封不動的轉換回來
指向任何對象的指針都可以轉換為void*類型,且不會丟失信息。如果將結果再轉換為初始指針類型,則可以恢復初始指針。執行指針到指針的轉換時,一般需要顯式的強制轉換,這裡所不同的是,指針可以被賦值為void*類型的指針,也可以賦值給void* 類型的指針,並可與void* 類型的指針進行比較
指針與地址
&符號可用來取一個變量的地址。地址運算符只能應用於內存中的對象,即變量與數組,它不能用於表達式,常量(如 &1、&"str",但可直接將字符串賦值給一個char類型的指針,因為在C中字符串其實就是一個字符數組,而數組變量本身就是一個指向首元素的指針)或register類型的變量。
*星號用於取地址中的內容。
指向void類型的指針可以存放指向任何類型的指針(編譯時好像只出現警告,沒有出現錯誤),但它不能間接引用其自身。
一元運算符*和&的優先級比算術運算符的優先級高。
++*p與 (*p)++ 效果相當,語句中的圓括號是必需的,否則是地址先加一,而不是對象加一。類似於*和++這樣的一元運算符遵循從右到左的結合順序。
[]的優先級高於*的優先級。
指針參數使得被調用函數能夠訪問和修改主調函數中對象的值,比如交換兩個變量值的函數其參數就應該定義成指針形式。
指針與數組
通過數組下標所能完成的任何操作都可以通過指針來實現。一般來說,用指針編寫的程序比用數組下標編寫的程序執行速度快。
pa = &a[0] 與 pa = a是等效的
對數組元素a[i]的引用也可以寫成*(a+i)這種形式,在計算數組元素a[i]的值時,C語言實際上先將其轉換為*(a+i)的形式,然後再進行求值,因此在程序中這兩種形式是等價的。另外,&a[i]與 a + i 含義也是相同的。如果pa指向了a數組,則pa[i]與*(pa+1)也是等價的。簡而言之,一個通過數組和下標實現的表達式可以等價地通過指針和偏移量實現。
但是,數組名和指針之間有一個不同之處,指針是一個變量,因此,pa=a和pa++都是合法的,但數組名不是變量,因此,類似於a = pa,和a++的語句是非法的。請注意,只要是數組類型,就不能改變其指向(除非將它傳遞到其他函數中進行處理時,因為此是是重新拷貝一份),如 char *p[],也是不能修改其指向的,如 p++ 是非法的。
數組類型的變量雖然是一個指針,但與指針的區別的是一旦聲明後就不能再修改它的指向了(因為數組名是一個指向數組起始地址的常量指針),下面程序是錯誤的:
char a[1] = "1";
char b [2] = "b"
b=a;
char *p,如果確信相應的元素存在,也可以通過下標訪問當前指針所指元素前面的元素,可以使用負的下標,如p[-1]、p[-2]等,這也是合法的。
char amessage[] = "now is the time";
char *pmessage = "now is the time";
amessage始終指向同一個存儲位置,而pmessage是一個指針,其初值指向一個字符串常量,之後它可以被修改以指向其他地址。
如果一個數組的長度是n,&table[-1]和&tab[n]都超出了數組tab的范圍,前者在運行過程中肯定是非法的,但是,C語言的定義保證數組末尾之後的第一個元素(即&tab[n])的
指針算術運算可以正確執行。
數組下標在編譯的時候要被轉換成指針表示法,所以用指針編寫數組下標表達式可節省編譯時間。如果 int * p,則p[0]也是可以的:
int main(
int argc,
char * argv[]) {
int x = 5;
int * p = &x;
printf("%d %d", *p, p[0]);//5 5
return 0;
}
指針數組、數組指針
指針數組(本質是數組)(char *c[3]):數組裡存儲的是指針,就像整型數組一樣(裡面存儲的是整型數據),下面是字符指針數組結構圖:
數組指針(本質是指針)(指向的是一個數組):int (*a)[3] ,這種聲明表明是
a是一個指針,它指向具有3個整型元素的一維數組(指針移動時以3個整型為單位進行移動,可以將a看作是二維的整型數組,所以數組的大小不能省略),因為方括號[]的優先級高於*的優先級,所以上述聲明中必須使用圓括號:
如果去掉圓括號,則聲明成 int *a[3] (此時是一個
指針數組),這相當於聲明了一個數組,該數組有3個元素,其中每個元素都有一個指向某個整型變量的指針(數組變量本身是指向首元素,指針移動時以1個整型為單位進行移動,a是一個一維的整型數組):
指針數組的一個重要優點在於,數組的每一行長度可以不同。比如存放具有不同長度的字符串。
當一個指針加上或減去一個整數時,指針並非簡單地加上或減去該整數值,而是加上該整數與指針引用的對象大小的乘積,而對象大小(字節數)取決於對象的數據類型。
因為除了數組元素外,我們不能認為兩個相同類型的變量是在內存中連續存儲的,所以指針算術運算除了用於數組外沒有什麼意義。
int (*a)[3]、int *a[]區別
//
多維數組只有第一維可以省略,第三個元素沒有指定內容,這裡會自動初始化為0
int a[][3] = { { 11, 12 }, { 21, 22, 23 } };
//可以有以下四種方式訪問a[1][0]元素,方括號[]比*優
//先級高,所以下面是可以省略的
printf("1、%d\n", a[1][0]);//21
printf("2、%d\n", *((a + 1)[0]));//21
printf("3、%d\n", *(a[1]));//21
printf("4、%d\n", **(a + 1));//21
//在定義時可以不指定指針移動的單位,即指針單位長度
//但如果不指定,則不能對指針進行移動操作,否則編譯出錯
int (*p1)[] = a;
//在不移動指針的條件下可以訪問第一個元素
printf("5、%d\n", **p1);//11
//編譯不能通過,因為上面在定義p1時未指定指針的最小移動單位
//所以不能移動
// printf("%d", **(p1 + 1));
//一維數組指針
int b[]={1,2,3};
int *pb=b;
printf("6、%d\n", *(pb + 1));//2
//指定長度後就可以對指針進行移動
int (*p2)[3] = a;
//以3個int為單位進行移動
printf("7、%d\n", **(p2 + 1));//21
//編譯時警告。以2個int單位進行移動
int (*p3)[2] = a;
printf("8、%d\n", **(p3 + 1));//0
int (*a)[3]本質是指針變量,它指向一個具有3列的某個整型數組,3是指該指針移動的基本單位是3格,至於每格多少位要看聲明時類型,這裡是int,所以指針在向前或向後移動時是以3個整型為單位移動。
int *a[]本質是一個一維數組,數組裡的每個元素是一個地址,該地址可以是一個整型數組的地址,也可以是一個整型變量的地址。
//這裡方括號[]中的長度可以省,這與 char (*a)[]類型的指針不同,因為每次指針移動一個char類型已明確
void val(
char *a[],
int *b[]) {
//這裡的*(a[1]+3)與*(b[1]+1)不能寫成*(a[1][3])與*(b[1][1]),因為a與b不是一個二維數組
printf("%s %c %c %d %d\n", a[1], *(a[1] + 3), *a[2], *(b[1] + 1), *b[2]);//cegi i a 3 4
//取第一個字符串中的第二個字符
printf("%c\n", *++(a[0]));//m
//取第二個字符串中的第一個字符:a先加1,再取第二個字符串,最後取第二個串中的第一個字符
printf("%c\n", (*++a)[0]);//c
//取第二個字符串中的第2個字符:a[0]為第二個字符串地址,++a[0]第二個字符串首地址下移,*++a[0]取出相應地址中的字符
printf("%c\n", *++a[0]);//e
//指針a再次向下移動一個單元,此時指向了第三個元素,它是一個字符
printf("%c\n", **++a);//a
//注,這裡 ++b 沒有問題,而main函數中不能,這主要是因為這裡的b指針是在原指針的基礎上
//重新拷貝的一份,所以可以隨時改變它的指向
printf("%d\n", **++b);//2
}
int main(
int argc,
char * argv[]) {
char a1 = 'a';
//字符串使用字符數組來存儲
char a2[]={'c','e','g','i','\0'};
//可以直接賦值一個常量字符串,或是一個字符數組變量,或是一個字符變量的地址
char *a[] = { "kmuw", a2, &a1 };
int b1[] = { 1 };
int b2[] = { 2, 3 };
int b3 = 4;
int *b[] = { b1, b2, &b3 };
//編譯出錯,因為b是一個一維的指針數組,b是數組名,所以不能
//修改其指向,但傳遞給其他函數後又可以修改,比如這裡的 val 函數裡
//printf("%d\n", **(++b));
val(a, b);
return 0;
}
幾種不同指針的定義
char **p:指向字符指針的指針,其實與字符指針數組 char *argv[]是一樣
char a1 = 'a';
char *a[] = { "str1", "str2", &a1 };
char **p = a;
printf("%c\n", **(p+2));//a
int (*p)[3]:指向具有3列的二維整型數組的指針。
int a[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
int (*p)[3] = a;
printf("%d\n", **++p);//4
int *p[3]:具有3個元素的一維整型指針數組。這裡的3可以省略
int b1[] = { 1 };
int b2[] = { 2, 3 };
int b3 = 4;
int *b[] = { b1, b2, &b3 };
printf("%d\n", **(b+1));//2
void * comp():返回一個空指針類型的函數。
void *
comp() {
char c = 'a';
return &c;
}
int main(
int argc,
char * argv[]) {
printf("%c\n",*((
char*)comp()));//a
return 0;
}
void * (*comp)():指向返回類型為空指針的函數的指針。
void *
comp() {
char c = 'a';
return &c;
}
void *
v(
void * (*comp)()){
return (*comp)();
}
int main(
int argc,
char * argv[]) {
printf("%c\n",*((
char*)v(comp)));
return 0;
}
char (*(*x())[])():x是一個函數,它返回一個指針,該指針指向一個一維數組,該一維數組的元素為指針,這些指針分別指向多個函數,這些函數的返回值為char類型。
char (*(*x[3])())[5] int 整型
int * 指向整型的指針
int *[3] 包含3個指向整型的指針的數組
int (*)[] 指向未指定元素個數的整型數組的指針
int *() 未指定參數、返回指向整型的指針的函數
int (*[])(void) 一個數組,其長度未指定,數組的元素為指向函數的指針,該函數沒有參數且返回一個整型值