這不是我第一次寫關於C指針的文章了,只是由於指針對於C來講過重要,並且隨着本身編程經歷越多,對指針的理解越多,所以有了本文。然而,想要全面理解指針,除了要對C語言有熟練的掌握外,還要有計算機硬件以及操做系統等方方面面的基本知識。因此我想經過一篇文章來儘量的講解指針,以對得起這個文章的標題吧。程序員
指針解決了一些編程中基本的問題。編程
而做爲一個程序員,咱們不須要了解內存的物理結構,操做系統將DRAM等硬件和軟件結合起來,給程序員提供的一種對物理內存使用的抽象。這種抽象機制使得程序使用的是虛擬存儲器,而不是直接操做物理存儲器。全部的虛擬地址造成的集合就是虛擬地址空間。 數組
在程序員眼中的內存應該是下面這樣的。(假設使用的是32位系統平臺,虛擬存儲空間爲4GB)數據結構
也就是說,虛擬存儲器是一個很大的,線性的字節數組(平坦尋址)。每個字節都是固定的大小,由8個二進制位組成。最關鍵的是,每個字節都有一個惟一的編號,編號從0開始,一直到最後一個字節。如上圖中,這是一個4GB的虛擬存儲器的模型,它一共有4x1024x1024x1024 個字節,那麼它的虛擬地址範圍就是 0 ~ 4x1024x1024x1024-1 。編程語言
因爲內存中的每個字節都有一個惟一的編號,所以,在程序中使用的變量,常量,甚至數函數等數據,當他們被載入到內存中後,都有本身惟一的一個編號,這個編號就是這個數據的地址。指針就是這樣造成的。函數
下面用代碼說明性能
#include <stdio.h> int main(void) { char ch = 'a'; int num = 97; printf("ch 的地址:%p\n",&ch); //ch 的地址:0028FF47 printf("num的地址:%p\n",&num); //num的地址:0028FF40 return 0; }
爲了簡單起見,這裏就用上面例子中的 int num = 97 這個局部變量來分析變量在內存中的存儲模型。測試
用來保存 指針 的變量,就是指針變量。若是指針變量p1保存了變量 num的地址,則就說:p1指向了變量num,也能夠說p1指向了num所在的內存塊 ,這種指向關係,在圖中通常用 箭頭表示。spa
上圖中,指針變量p1指向了num所在的內存塊 ,即從地址0028FF40開始的4個byte 的內存塊。操作系統
int a ; //int類型變量 a int *a ; //int* 變量a int arr[3]; //arr是包含3個int元素的數組 int (* arr )[3]; //arr是一個指向包含3個int元素的數組的指針變量 //-----------------各類類型的指針------------------------------ int* p_int; //指向int類型變量的指針 double* p_double; //指向idouble類型變量的指針 struct Student *p_struct; //結構體類型的指針 int(*p_func)(int,int); //指向返回類型爲int,有2個int形參的函數的指針 int(*p_arr)[3]; //指向含有3個int元素的數組的指針 int** p_pointer; //指向 一個整形變量指針的指針
int add(int a , int b) { return a + b; } int main(void) { int num = 97; float score = 10.00F; int arr[3] = {1,2,3}; //----------------------- int* p_num = # float* p_score = &score; int (*p_arr)[3] = &arr; int (*fp_add)(int ,int ) = add; //p_add是指向函數add的函數指針 return 0; }
int add(int a , int b){ return a + b; } int main(void) { int arr[3] = {1,2,3}; //----------------------- int* p_first = arr; int (*fp_add)(int ,int ) = add; const char* msg = "Hello world"; return 0; }
int main(void) { int age = 19; int*p_age = &age; *p_age = 20; //經過指針修改指向的內存數據 printf("age = %d\n",*p_age); //經過指針讀取指向的內存數據 printf("age = %d\n",age); return 0; }
int* p1 = & num; int* p3 = p1; //經過指針 p1 、 p3 均可以對內存數據 num 進行讀寫,若是2個函數分別使用了p1 和p3,那麼這2個函數就共享了數據num。
#ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif
void opp() { int*p = NULL; *p = 10; //Oops! 不能對NULL解地址 } void foo() { int*p; *p = 10; //Oops! 不能對一個未知的地址解地址 } void bar() { int*p = (int*)1000; *p =10; //Oops! 不能對一個可能不屬於本程序的內存的地址的指針解地址 }
指針也是一種數據,指針變量也是一種變量,所以指針 這種數據也符合前面 變量和內存 主題中的特性。 這裏我只想強調2個屬性: 指針的類型,指針的值。
int main(void) { int num = 97; int *p1 = # char* p2 = (char*)(&num); printf("%d\n",*p1); //輸出 97 putchar(*p2); //輸出 a return 0; }
typedef struct { char name[31]; int age; float score; }Student; int main(void) { Student stu = {"Bob" , 19, 98.0}; Student*ps = &stu; ps->age = 20; ps->score = 99.0; printf("name:%s age:%d\n",ps->name,ps->age); return 0; }
int main(void) { int arr[3] = {1,2,3}; int*p_first = arr; printf("%d\n",*p_first); //1 return 0; }
int main(void) { int arr[3] = {1,2,3}; int*p = arr; for(;p!=arr+3;p++){ printf("%d\n",*p); } return 0; }
int main(void) { int arr[3] = {1,2,3}; int*p = arr; printf("sizeof(arr)=%d\n",sizeof(arr)); //sizeof(arr)=12 printf("sizeof(p)=%d\n",sizeof(p)); //sizeof(p)=4 return 0; }
void change(int a) { a++; //在函數中改變的只是這個函數的局部變量a,而隨着函數執行結束,a被銷燬。age仍是原來的age,紋絲不動。 } int main(void) { int age = 19; change(age); printf("age = %d\n",age); // age = 19 return 0; }
void change(int* pa) { (*pa)++; //由於傳遞的是age的地址,所以pa指向內存數據age。當在函數中對指針pa解地址時, //會直接去內存中找到age這個數據,而後把它增1。 } int main(void) { int age = 19; change(&age); printf("age = %d\n",age); // age = 20 return 0; }
再來一個老生常談的,用函數交換2個變量的值的例子:
#include<stdio.h> void swap_bad(int a,int b); void swap_ok(int*pa,int*pb); int main() { int a = 5; int b = 3; swap_bad(a,b); //Can`t swap; swap_ok(&a,&b); //OK return 0; } //錯誤的寫法 void swap_bad(int a,int b) { int t; t=a; a=b; b=t; } //正確的寫法:經過指針 void swap_ok(int*pa,int*pb) { int t; t=*pa; *pa=*pb; *pb=t; }
typedef struct { char name[31]; int age; float score; }Student; //打印Student變量信息 void show(const Student * ps) { printf("name:%s , age:%d , score:%.2f\n",ps->name,ps->age,ps->score); }
void echo(const char *msg) { printf("%s",msg); } int main(void) { void(*p)(const char*) = echo; //函數指針變量指向echo這個函數 p("Hello "); //經過函數的指針p調用函數,等價於echo("Hello ") echo("World\n"); return 0; }
若是const 後面是一個類型,則跳過最近的原子類型,修飾後面的數據。(原子類型是不可再分割的類型,如int, short , char,以及typedef包裝後的類型)
int main() { int a = 1; int const *p1 = &a; //const後面是*p1,實質是數據a,則修飾*p1,經過p1不能修改a的值 const int*p2 = &a; //const後面是int類型,則跳過int ,修飾*p2, 效果同上 int* const p3 = NULL; //const後面是數據p3。也就是指針p3自己是const . const int* const p4 = &a; // 經過p4不能改變a 的值,同時p4自己也是 const int const* const p5 = &a; //效果同上 return 0; }
typedef int* pint_t; //將 int* 類型 包裝爲 pint_t,則pint_t 如今是一個完整的原子類型 int main() { int a = 1; const pint_t p1 = &a; //一樣,const跳過類型pint_t,修飾p1,指針p1自己是const pint_t const p2 = &a; //const 直接修飾p,同上 return 0; }
若是2個程序單元(例如2個函數)是經過拷貝 他們所共享的數據的 指針來工做的,這就是淺拷貝,由於真正要訪問的數據並無被拷貝。若是被訪問的數據被拷貝了,在每一個單元中都有本身的一份,對目標數據的操做相互 不受影響,則叫作深拷貝。
指針和引用這個2個名詞的區別。他們本質上來講是一樣的東西。指針經常使用在C語言中,而引用,則用於諸如Java,C#等 在語言層面封裝了對指針的直接操做的編程語言中。引用是編程語言提供給程序員的抽象機制,而指針是操做系統提供給軟件開發模型的抽象機制。
//測試機器使用的是否爲小端模式。是,則返回true,不然返回false
//這個方法判別的依據就是:C語言中一個對象的地址就是這個對象佔用的字節中,地址值最小的那個字節的地址。
bool isSmallIndain() { unsigned int val = 'A'; unsigned char* p = (unsigned char*)&val; //C/C++:對於多字節數據,取地址是取的數據對象的第一個字節的地址,也就是數據的低地址 return *p == 'A'; }