指針變量也是個變量,不過保存的是另外一個變量的地址。另外編譯器還會記住指針所指向變量的類型,從而在指針運算時根據變量類型採起不一樣操做。html
例如,char * a
定義了char 類型的指針變量 a,經過 *a
讀取數據時,每次只會讀一個字節(char 類型變量的長度)。而int * i
定義了 int 類型的指針變量 i,經過 *i
讀取數據時,每次會讀兩個或四個字節(int 類型變量的長度跟編譯器平臺有關)。web
#include <stdio.h> int main() { int a = 666; char c = 'a'; int * p1 = &a; // 至關於(int *) p1,表示 p1 是執行 int 類型的指針 char * p2; // 至關於(char *) p2,表示 p2 是執行 char 類型的指針 p2 = &c; // & 符號用於取變量的地址 printf("address of a is %#x, value of a is %d\n", p1, *p1); printf("address of c is %#x, value of c is %c\n", p2, *p2); }
輸出:編程
address of a is 0x107900bc, value of a is 666 address of c is 0x107900bb, value of c is a
#include <stdio.h> int main() { float a = 1.2; char * p = (char *)&a; // 這裏的 p 指向有符號字符型變量,符號位爲1時會打印4個字節 printf("%#x\n", *p); }
輸出:數組
0xffffff9a
要解決這個問題,把上面的 char * p = &a;
變成 unsigned char * p = &a;
便可。bash
有時咱們須要用 char * 按照字節大小讀取數據,可是非 char 類型的指針當作 char 指針處理時會報錯或警告。這時須要強制類型轉換:網絡
#include <stdio.h> int main() { int a = 0x77777777; char * p = (char *)&a; printf("%#x\n", *p); }
指針操做若有不慎,會常常看到 Segmentation Fault 段錯誤。這是由於指針指向了非法的內存,例以下面的代碼執行的內存地址,操做系統是不容許訪問的:svg
#include <stdio.h> int main() { int a = 0x12345678; int * p = &a; p = 0x00000001; printf("address of a is %#x, value of a is %d\n", p1, *p1); }
內存除了用於存放程序運行時的數據外,還有一部份內存用於操做硬件。例如內存的某一段連續空間用於映射顯存、I2C、USB 設備等。函數
對於單字節的 char 類型變量不存在這個問題。但多字節的變量,高字節存儲在內存的高地址仍是低地址,決定了採用哪一種存儲方式。優化
#include <stdio.h> int main() { int a = 0x12345678; unsigned char * p1 = &a; printf("address of a is %#x, value of a is %#x\n", p1, *p1); }
上面代碼在 32 位平臺上運行時,經過 char 類型的指針只讀取第一個字節,若是輸出 78 表示是小端存儲(低地址存放低位),不然是大端存儲。輸出:ui
address of a is 0xbb483e84, value of a is 0x78
目前Intel的80x86系列芯片是惟一還在堅持使用小端的芯片,ARM芯片默認採用小端,但能夠切換爲大端。另外,對於大小端的處理也和編譯器的實現有關,在C語言中,默認是小端(但在一些對於單片機的實現中倒是基於大端,好比Keil 51C),Java是平臺無關的,默認是大端。在網絡上傳輸數據廣泛採用的都是大端。
C 語言的 const 比較弱,很容易繞過去,例如經過指針。const 修飾的變量仍然存儲在讀寫區,而非只讀區。
const char * p; // 推薦使用,至關於 const ((char *) p) char const * p; // 不推薦
char * const p; // 推薦使用,至關於 (char *) (const p) char * p const; // 不推薦
const char * const p; // 至關於 const ((char *) (const p))
綜合示例:
#include <stdio.h> int main() { char * str1 = "hello\n"; // C 語言中字符串不可修改 char str2 [] = {"hello\n"};// 數組能夠修改 //str1[0] = 'a'; // str1 修改會致使 segmentation fault str2[0] = 'a'; printf("%s\n", str2); }
上面代碼中,字符串是不可修改的,因此能夠用 const 限制,若是有代碼則會在編譯時報錯:
int main() { char * str1 = "hello\n"; // C 語言中字符串不可修改 const char str2 [] = {"hello\n"};// 數組能夠修改 str1[0] = 'a'; // str1 修改會致使 segmentation fault str2[0] = 'a'; printf("%s\n", str2); }
編譯報錯:
/code/main.c: In function ‘main’: /code/main.c:9:2: error: assignment of read-only location ‘str2[0]’ str2[0] = 'a';
#include <stdio.h> int main() { int a = 0x66667777; int b = 0x11111111; int *p = &b; *(p+1) = 0xffffffff; printf("%#x\n", a); }
編譯器默認的優化是開啓的。但有時候咱們操做的內存是映射到硬件的,此時可能須要關閉優化。
volatile char * p = 0x20; while (*p == 0x20) ...
指針能夠指向任意類型的資源,例如 int、char、數組、函數。指定簡明易讀的別名能夠提升代碼可讀性。
char * name_t; typedef char * name_t; name_t myVar;
指針的加減操做,跟指針指向變量的具體類型有關。指針指向的變量佔幾個字節,指針每次加減一就是加減幾個字節,確保恰好能夠指向下一個同類型元素。
#include <stdio.h> int main() { const char *p = {"hello\n"}; int *s = p; printf("%c, %c, %c, %c, %#x\n", *p, *(p+1), *(p+2), *(p+3), *s); }
在數組中,保存的是相同類型的元素。經過下標能夠訪問到每個元素,不須要咱們在編程的時候關係元素佔幾個字節。這跟指針的加減運算是同樣的。p[0] 等價於 *p,p[1] 等價於 *(p+1),以此類推:
#include <stdio.h> int main() { const char *p = {"hello\n"}; printf("%c, %c, %c, %c\n", *p, p[0], *(p+1), p[1]); }
指針能夠進行比較,>= 、<= 、== 、!= 四種。
經常使用的是二維指針,二維以上基本上不用。
當在內存中有多個離散的變量時,爲了放在一個變量中統一訪問,就須要把這個用做訪問入口的統一變量設計爲數組,數組中的每一個元素都是指針,執行原始變量。
語法的簡單示例:
int 變量int a;
← int 變量的指針int * p = &a;
← int 變量的指針的指針int **p2 = &p;
bash 終端能夠在命令後面帶參數,編譯器會把全部參數彙總到 main 函數的參數中:
#include <stdio.h> int main(int argc, char ** argv) { int i; for (i = 0; i < argc; i++) { printf("argv[%d] is: %s\n", i, argv[i]); } i = 0; while(argv[i] != NULL) { printf("argv[%d] is: %s\n", i, argv[i++]); } return 0; }
# ./build 666 hello world ! argv[0] is: ./build argv[1] is: 666 argv[2] is: hello argv[3] is: world argv[4] is: ! argv[1] is: ./build argv[2] is: 666 argv[3] is: hello argv[4] is: world argv[5] is: !
數組是地址操做的一種形式,使用的時候跟指針幾乎同樣。經過數組分配的內存空間的特性以下:
int a[10]; // 分配 4*10Byte 的內存,a 是指向這個內存的標籤,不可變,不是指針
C 語言只有指針的概念,並無真正意義的數組,因此在用指針操做數組時,須要注意:不要越界。
#include <stdio.h> int main(int argc, char ** argv) { char a[] = {"hello\n"}; char * p = {"hello\n"}; printf("a is: %s\n", a); printf("p is: %s\n", p); //a = "hello"; // a 是標籤,數組不可變,不然編譯報錯 p = "world\n"; // p 是指針,能夠變 printf("a is: %s\n", a); printf("p is: %s\n", p); }
關於char、unsigned char 和 signed char 三種類型直接的差異,能夠參考:http://bbs.chinaunix.net/thread-889260-1-1.html
內存中的數據空間能夠分爲兩類:
char a[10];
。用 strcpy 複製數據,複製時以 \0 結束,或者用 strncpy 複製。unsigned char b[10]
。用 memcpy 複製數據,複製時須要指定字節個數。int buf[10]; int source[1000]; memcpy(buf, source, 10*sizeof(int));
注意:C 語言中只有字符串常量。由於 C 語言沒有字符串變量的概念,若是想修改字符串的值,必須將字符串存儲爲字符數組。全部字符串都以 \0 結尾。
char a[] = "hello\n"; // C 編譯器看到雙引號時,自動在末尾加 \0 char b[10] = {'h', 'e', 'l', 'l', 'o', '\n', '\0'}; // 未賦值的元素默認是0 char c[] = {"hello\n"}; // 由於雙引號和大括號都用來劃分存儲空間,可省略大括號 int i[] = {12, 23, 666};
char a[10]; a[0] = 'h'; a[1] = 'e'; ...
字符串是 C 語言中須要特別注意的地方。字符串常量賦值到數組時,實際上會先建立一個數組變量,而後依次把每一個字符拷貝到這個數組中,數組指向的變量跟字符串常量無關,能夠修改。但字符串賦值到指針時,指針指向的就是這個字符串常量,此時指針指向的值不可修改。
char a[10] = {"hello"}; // 內存中分配了一個字符串常量空間和一個字符串變量空間,變量 a 指向這個變量空間,能夠修改空間中的元素 a[2] = 'w'; // OK char *p = "hello"; // 內存中只有一個字符串常量空間和一個指向該常量的指針變量,指針變量 p 指向常量,不可修改 p[2] = 'w'; // 報錯 segmentation fault
C 語言中,數組中的每一個元素能夠修改,可是不可直接對數組名進行賦值。若是想再次賦值,只能逐個元素賦值。
int a[] = {2, 5, 6}; a = {3, 5}; // 編譯報錯,數組名相似函數名,是個常量標籤,不可賦值
內存空間逐一賦值操做很常見,因此 C 語言將其封裝爲字符串拷貝函數。能夠在 Linux 下經過 man 3 strcpy
之類的命令查看函數定義。
strcpy 函數碰到 0 就中止拷貝。若是源字符串太長,strcpy 可能致使內存泄漏,通常不用。函數原型以下:
char *strcpy(char *dest, const char *src);
char a[] = "666"; strcpy(a, "hello world");
strncpy 函數能夠限制拷貝的數量,防止發生越界。
char *strcpy(char *dest, const char *src, size_t n);
數組中存在指針,構成指針數組。指針數組就是二級指針。
int *a[10]; // 開闢 10 個空間存放數組 a,a 中放 (int *) 類型的指針 int **a; // ((int *) *) a
C 語言中,一維數組的數組名變量中放的就是數組首元素的地址,能夠直接賦值給指針,並用這個指針訪問數組中的元素。但二維數組跟二維指針沒有任何關係。
下面例子會報錯,p2 指向指針數組,但 b 指向兩個連續的內存塊,每塊內存由 5 個 int 類型變量組成
#include <stdio.h> int main() { int a[10]; // a 是數組標籤,表示一塊由 10 個 int 元素組成的空間 int b[2][5]; // b 是數組標籤,表示兩塊空間,各由 5 個 int 元素組成 int *p1 = a; int **p2 = b; // 這一行會報錯 int *p4 [5] = b; // 這一行會報錯,這裏 p4 是數組,其中的每個元素都是 int 類型的指針 int (*p3)[5] = b; // 正常編譯,這裏 p3 是指針,指向一塊由 5 個 int 元素組成的空間 printf("%d\n", a[5]); printf("%d\n", b[1][1]); printf("%d\n", p3[1][1]); }
對於三維數組 int a[2][3][4];
,能夠用指針表示:
int (*p) [3][4];