1、指針
學習 C 語言的指針既簡單又有趣。通過指針,可以簡化一些 C 編程任務的執行,還有一些任務,如動態內存分配,沒有指針是無法執行的。所以,想要成為一名優秀的 C 程序員,學習指針是很有必要的。
正如您所知道的,每一個變量都有一個內存位置,每一個內存位置都定義了可使用連字號(&)運算符訪問的地址,它表示了在內存中的一個地址。請看下面的實例,它將輸出定義的變量地址:
int var1; char var2[10]; printf("var1 變量的地址: %p\n", &var1 ); // var1 變量的地址: 0038FB78 printf("var2 變量的地址: %p\n", &var2 ); // var2 變量的地址: 0038FB64
通過上面的實例,我們了解了什么是內存地址以及如何訪問它。接下來讓我們看看什么是指針。
什么是指針?
指針是一個變量,其值為另一個變量的地址,即,內存位置的直接地址。就像其他變量或常量一樣,您必須在使用指針存儲其他變量地址之前,對其進行聲明。指針變量聲明的一般形式為:
type *var-name;
在這里,type 是指針的基類型,它必須是一個有效的 C 數據類型,var-name 是指針變量的名稱。用來聲明指針的星號 * 與乘法中使用的星號是相同的。但是,在這個語句中,星號是用來指定一個變量是指針。以下是有效的指針聲明:
int *ip; /* 一個整型的指針 */ double *dp; /* 一個 double 型的指針 */ float *fp; /* 一個浮點型的指針 */ char *ch; /* 一個字符型的指針 */
所有實際數據類型,不管是整型、浮點型、字符型,還是其他的數據類型,對應指針的值的類型都是一樣的,都是一個代表內存地址的長的十六進制數。
不同數據類型的指針之間唯一的不同是,指針所指向的變量或常量的數據類型不同。
如何使用指針?
使用指針時會頻繁進行以下幾個操作:定義一個指針變量、把變量地址賦值給指針、訪問指針變量中可用地址的值。這些是通過使用一元運算符 *來返回位於操作數所指定地址的變量的值。下面的實例涉及到了這些操作:
int var = 20; /* 實際變量的聲明 */ int *ip; /* 指針變量的聲明 */ ip = &var; /* 在指針變量中存儲 var 的地址 */ printf("Address of var variable: %p\n", &var ); // Address of var variable: 0045FBC0 /* 在指針變量中存儲的地址 */ printf("Address stored in ip variable: %p\n", ip ); // Address stored in ip variable: 0045FBC0 /* 使用指針訪問值 */ printf("Value of *ip variable: %d\n", *ip ); // Value of *ip variable: 20
C 中的 NULL 指針
在變量聲明的時候,如果沒有確切的地址可以賦值,為指針變量賦一個 NULL 值是一個良好的編程習慣。賦為 NULL 值的指針被稱為空指針。
NULL 指針是一個定義在標准庫中的值為零的常量。請看下面的程序:
int *ptr = NULL; printf("ptr 的地址是 %p\n", ptr ); // ptr 的地址是 00000000
在大多數的操作系統上,程序不允許訪問地址為 0 的內存,因為該內存是操作系統保留的。然而,內存地址 0 有特別重要的意義,它表明該指針不指向一個可訪問的內存位置。但按照慣例,如果指針包含空值(零值),則假定它不指向任何東西。
如需檢查一個空指針,您可以使用 if 語句,如下所示:
if(ptr) /* 如果 p 非空,則完成 */ if(!ptr) /* 如果 p 為空,則完成 */
2、C 指針的算術運算
C 指針是一個用數值表示的地址。因此,您可以對指針執行算術運算。可以對指針進行四種算術運算:++、--、+、-。
假設 ptr 是一個指向地址 1000 的整型指針,是一個 32 位的整數,讓我們對該指針執行下列的算術運算:
ptr++
在執行完上述的運算之后,ptr 將指向位置 1004,因為 ptr 每增加一次,它都將指向下一個整數位置,即當前位置往后移 4 個字節。這個運算會在不影響內存位置中實際值的情況下,移動指針到下一個內存位置。如果 ptr 指向一個地址為 1000 的字符,上面的運算會導致指針指向位置 1001,因為下一個字符位置是在 1001。
遞增一個指針
我們喜歡在程序中使用指針代替數組,因為變量指針可以遞增,而數組不能遞增,數組可以看成一個指針常量。下面的程序遞增變量指針,以便順序訪問數組中的每一個元素:
int var[] = {10, 100, 200}; int i, *ptr; /* 指針中的數組地址 */ ptr = var; for ( i = 0; i < 3; i++) { printf("存儲地址:var[%d] = %x\n", i, ptr ); printf("存儲值:var[%d] = %d\n", i, *ptr ); /* 移動到下一個位置 */ ptr++; }
當上面的代碼被編譯和執行時,它會產生下列結果:
存儲地址:var[0] = 45fc10 存儲值:var[0] = 10 存儲地址:var[1] = 45fc14 存儲值:var[1] = 100 存儲地址:var[2] = 45fc18 存儲值:var[2] = 200
遞減一個指針
同樣地,對指針進行遞減運算,即把值減去其數據類型的字節數,如下所示:
int var[] = {10, 100, 200}; int i, *ptr; /* 指針中最后一個元素的地址 */ ptr = &var[2]; for ( i = 3; i > 0; i--) { printf("存儲地址:var[%d] = %x\n", i-1, ptr ); printf("存儲值:var[%d] = %d\n", i-1, *ptr ); /* 移動到下一個位置 */ ptr--; }
當上面的代碼被編譯和執行時,它會產生下列結果:
存儲地址:var[2] = 3cfb30 存儲值:var[2] = 200 存儲地址:var[1] = 3cfb2c 存儲值:var[1] = 100 存儲地址:var[0] = 3cfb28 存儲值:var[0] = 10
指針的比較
指針可以用關系運算符進行比較,如 ==、< 和 >。如果 p1 和 p2 指向兩個相關的變量,比如同一個數組中的不同元素,則可對 p1 和 p2 進行大小比較。
下面的程序修改了上面的實例,只要變量指針所指向的地址小於或等於數組的最后一個元素的地址 &var[MAX - 1],則把變量指針進行遞增:
int var[] = {10, 100, 200}; int i, *ptr; /* 指針中第一個元素的地址 */ ptr = var; i = 0; while ( ptr <= &var[2] ) { printf("Address of var[%d] = %x\n", i, ptr ); printf("Value of var[%d] = %d\n", i, *ptr ); /* 指向上一個位置 */ ptr++; i++; }
當上面的代碼被編譯和執行時,它會產生下列結果:
Address of var[0] = 3af950 Value of var[0] = 10 Address of var[1] = 3af954 Value of var[1] = 100 Address of var[2] = 3af958 Value of var[2] = 200
3、C 指針數組
先讓我們來看一個實例,它用到了一個由 3 個整數組成的數組:
int var[] = {10, 100, 200}; int i; for (i = 0; i < 3; i++) { printf("Value of var[%d] = %d\n", i, var[i] ); }
當上面的代碼被編譯和執行時,它會產生下列結果:
Value of var[0] = 10 Value of var[1] = 100 Value of var[2] = 200
可能有一種情況,我們想要讓數組存儲指向 int 或 char 或其他數據類型的指針。下面是一個指向整數的指針數組的聲明:
int *ptr[MAX];
在這里,把 ptr 聲明為一個數組,由 MAX 個整數指針組成。因此,ptr 中的每個元素,都是一個指向 int 值的指針。下面的實例用到了三個整數,它們將存儲在一個指針數組中,如下所示:
int var[] = {10, 100, 200}; int i, *ptr[3]; for ( i = 0; i < 3; i++) { ptr[i] = &var[i]; /* 賦值為整數的地址 */ } for ( i = 0; i < 3; i++) { printf("Value of var[%d] = %d\n", i, *ptr[i] ); }
當上面的代碼被編譯和執行時,它會產生下列結果:
Value of var[0] = 10 Value of var[1] = 100 Value of var[2] = 200
您也可以用一個指向字符的指針數組來存儲一個字符串列表,如下:
const char *names[] = { "Zara Ali", "Hina Ali", "Nuha Ali", "Sara Ali", }; int i = 0; for ( i = 0; i < 4; i++) { printf("Value of names[%d] = %s\n", i, names[i] ); }
當上面的代碼被編譯和執行時,它會產生下列結果:
Value of names[0] = Zara Ali Value of names[1] = Hina Ali Value of names[2] = Nuha Ali Value of names[3] = Sara Ali
問題:指向字符的指針數組來存儲一個字符串列表 不明白為什么前面定義的是指針,到后面輸出 names[i] 沒有* ????
回答1:printf里用%s控制是要輸出字符串,輸出字符串對應的變量是字符串指針,names[]中就是字符串指針,為啥還要加*?加了*就是取字符串的內容,而這內容就是字符串第一個字符而不是整個字符串——因為C規定輸出字符串時(就是用%s控制時),對應變量那里只寫字符串第一個字符的指針就可以了,printf會在%s的控制下一個字符一個字符地把字符串中的字符依次輸出到屏幕上,直到遇到'\0'時停止。
回答2:printf對%s占位符有特殊處理,遇見該占位符時,將從后面參數中給出的字符指針指向的字符開始,逐個遍歷輸出字符,直到遇到結束標記'\0'。
4、C 指向指針的指針
指向指針的指針是一種多級間接尋址的形式,或者說是一個指針鏈。通常,一個指針包含一個變量的地址。當我們定義一個指向指針的指針時,第一個指針包含了第二個指針的地址,第二個指針指向包含實際值的位置。
一個指向指針的指針變量必須如下聲明,即在變量名前放置兩個星號。例如,下面聲明了一個指向 int 類型指針的指針:
int **var;
當一個目標值被一個指針間接指向到另一個指針時,訪問這個值需要使用兩個星號運算符,如下面實例所示:
int var; int *ptr; int **pptr; var = 3000; /* 獲取 var 的地址 */ ptr = &var; /* 使用運算符 & 獲取 ptr 的地址 */ pptr = &ptr; /* 使用 pptr 獲取值 */ printf("Value of var = %d\n", var ); printf("Value available at *ptr = %d\n", *ptr ); printf("Value available at **pptr = %d\n", **pptr);
當上面的代碼被編譯和執行時,它會產生下列結果:
Value of var = 3000 Value available at *ptr = 3000 Value available at **pptr = 3000
5、C 傳遞指針給函數
C 語言允許您傳遞指針給函數,只需要簡單地聲明函數參數為指針類型即可。
下面的實例中,我們傳遞一個無符號的 long 型指針給函數,並在函數內改變這個值:
#include <stdio.h> #include <time.h> void getSeconds(unsigned long *par); int main () { unsigned long sec; getSeconds( &sec ); /* 輸出實際值 */ printf("Number of seconds: %ld\n", sec ); return 0; } void getSeconds(unsigned long *par) { /* 獲取當前的秒數 */ *par = time( NULL ); return; }
當上面的代碼被編譯和執行時,它會產生下列結果:
Number of seconds :1294450468
能接受指針作為參數的函數,也能接受數組作為參數,如下所示:
#include <stdio.h> /* 函數聲明 */ double getAverage(int *arr, int size); int main () { /* 帶有 5 個元素的整型數組 */ int balance[5] = {1000, 2, 3, 17, 50}; double avg; /* 傳遞一個指向數組的指針作為參數 */ avg = getAverage( balance, 5 ) ; /* 輸出返回值 */ printf("Average value is: %f\n", avg ); return 0; } double getAverage(int *arr, int size) { int i, sum = 0; double avg; for (i = 0; i < size; ++i) { sum += arr[i]; } avg = (double)sum / size; return avg; }
當上面的代碼被編譯和執行時,它會產生下列結果:
Average value is: 214.40000
6、C 從函數返回指針
我們已經了解了 C 語言中如何從函數返回數組,類似地,C 允許您從函數返回指針。為了做到這點,您必須聲明一個返回指針的函數,如下所示:
int * myFunction() { . . . }
另外,C 語言不支持在調用函數時返回局部變量的地址,除非定義局部變量為 static 變量。
現在,讓我們來看下面的函數,它會生成 10 個隨機數,並使用表示指針的數組名(即第一個數組元素的地址)來返回它們,具體如下:
#include <stdio.h> #include <time.h> #include <stdlib.h> /* 要生成和返回隨機數的函數 */ int * getRandom( ) { static int r[10]; int i; /* 設置種子 */ srand( (unsigned)time( NULL ) ); for ( i = 0; i < 10; ++i) { r[i] = rand(); printf("%d\n", r[i] ); } return r; } /* 要調用上面定義函數的主函數 */ int main () { /* 一個指向整數的指針 */ int *p; int i; p = getRandom(); for ( i = 0; i < 10; i++ ) { printf("*(p + [%d]) : %d\n", i, *(p + i) ); } return 0; }
當上面的代碼被編譯和執行時,它會產生下列結果:
1523198053 1187214107 1108300978 430494959 1421301276 930971084 123250484 106932140 1604461820 149169022 *(p + [0]) : 1523198053 *(p + [1]) : 1187214107 *(p + [2]) : 1108300978 *(p + [3]) : 430494959 *(p + [4]) : 1421301276 *(p + [5]) : 930971084 *(p + [6]) : 123250484 *(p + [7]) : 106932140 *(p + [8]) : 1604461820 *(p + [9]) : 149169022
在計算機科學中,指針(Pointer)是編程語言中的一個對象,利用地址,它的值直接指向(points to)存在電腦存儲器中另一個地方的值。
由於通過地址能找到所需的變量單元,可以說,地址指向該變量單元。因此,將地址形象化的稱為“指針”。意思是通過它能找到以它為地址的內存單元。
7、sizeof是c語言中用來求字節運算符。
sizeof用來求一個對象(類型,變量,……)所占的內存大小(以字節為單位)。
sizeof(x) typeof(x) sizeof( typeof(x) )
不管你x是否存在,我(sizeof)只關心你的(x)的類型,
然后求這個類型應該占的字節大小。
x 可以是變量,數組,類型,表達式,……。只要x的類型是確定的。
原文地址:https://baijiahao.baidu.com/s?id=1626589052981505887&wfr=spider&for=pc