基礎(chǔ)知識
物理內(nèi)存
? 維修電腦師傅眼中的內(nèi)存:內(nèi)存在物理上是由一組DRAM芯片組成的.
軟件內(nèi)存
? 操作系統(tǒng)將RAM
(分為兩大類:SRAM
和DRAM
)等硬件和軟件結(jié)合起來萝嘁,給程序員提供的一種對內(nèi)存使用的抽象前硫。這種抽象機制使得程序使用的是虛擬存儲器,而不是直接操作和使用真實存在的物理存儲器,所有的虛擬地址形成的集合就是虛擬地址空間。 如下圖:
系統(tǒng)中的實際內(nèi)存排列:
? 內(nèi)存是一個很大的命黔,線性的字節(jié)數(shù)組(平坦尋址)澜沟。每一個字節(jié)都是固定的大小够坐,由8個二進制位組成托嚣。最關(guān)鍵的是,每一個字節(jié)都有一個唯一的編號,編號從0開始患久,一直到最后一個字節(jié)椅寺。如上圖中,這是一個256M的內(nèi)存蒋失,他一共有256x1024x1024 = 268435456個字節(jié)返帕,那么它的地址范圍就是 0 ~268435455 。
? 系統(tǒng)中的內(nèi)存每一個字節(jié)都有一個唯一的編號篙挽。因此荆萤,在程序中使用的變量,常量铣卡,甚至函數(shù)等數(shù)據(jù)链韭,當它們被載入到內(nèi)存中后,都有自己唯一的編號煮落,這個編號就是這個數(shù)據(jù)的地址敞峭,指針就是這樣形成的。
bit(位): 一個二進制數(shù)據(jù)0或1蝉仇,是
1bit
旋讹;byte(字節(jié)): 存儲空間的基本計量單位,
1byte = 8 bit
;1 字母 = 1 byte = 8 bit;
1 漢字 = 2 byte = 16 bit;
64 位的編譯器各類型字節(jié)數(shù)顯示
//不同的編譯器位數(shù),其字節(jié)數(shù)顯示是不同的量淌。通過在程序中打悠濉:
printf("%lu", sizeof(int)); //在Xcode中輸出:4嫌褪,說明是64位的編譯器
char: 1個字節(jié)
short: 2個字節(jié)
int: 4個字節(jié)
long: 8個字節(jié)
float: 4個字節(jié)
double: 8個字節(jié)
? 查看操作系統(tǒng)位數(shù)呀枢,在終端輸入:uname -a
在末尾有x86_64
說明是64位的操作系統(tǒng)。
指針介紹
int a = -12;
char b = M;
float c = 3.14
變量尋址
編譯器將變量名和地址進行掛鉤琅拌,內(nèi)存中沒有存放變量的名字;
? 指針的值實質(zhì)是內(nèi)存單元(即字節(jié))的編號摘刑,所以指針單獨從數(shù)值上看进宝,也是整數(shù),他們一般用16進制表示枷恕。指針的值(虛擬地址值)使用一個機器字的大小來存儲,也就是說,對于一個機器字為w位的電腦而言,它的虛擬地址空間是0~2w - 1 ,程序最多能訪問2w個字節(jié)党晋。
? 所以可以把指針看做是一個變量,其值為另一個變量的地址徐块,即內(nèi)存位置的直接地址未玻。
//地址是計算機內(nèi)存中的某個位置,而指針是專門用來存放地址的特殊類型變量
type *pointer;
type: 指針的基類型胡控;
* : 申明指針扳剿;
pointer: 指針變量;//作用:是用來存放地址和對地址進行索引
DEMO圖
? 要掌握指針的四方面的內(nèi)容:指針的類型昼激、指針所指向的類型庇绽、指針的值(或者叫指針所指向的內(nèi)存區(qū))、指針本身所占據(jù)的內(nèi)存區(qū)橙困。
指針的類型: 從語法的角度看瞧掺,把指針聲明語句里的指針名字去掉,剩下的部分就是指針的類型凡傅;
指針所指向的類型:決定了編譯器將把那片內(nèi)存區(qū)里的內(nèi)容當做什么來看待夸盟。從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉像捶,剩下的就是指針所指向的類型上陕;
指針的值:是指針本身存儲的數(shù)值,這個值被編譯器當作一個地址拓春,而不是一個一般的數(shù)值释簿。在64 位程序里,所有類型指針的值都是一個64 位整數(shù)硼莽,因為64 位程序里內(nèi)存地址全都是64 位長庶溶。
指針的值
代表那個內(nèi)存地址的開始,長度為sizeof(數(shù)據(jù)類型)
的一片內(nèi)存區(qū)懂鸵。以后偏螺,我們說一個指針的值是XX,就相當于說該指針指向了以XX 為首地址的一片內(nèi)存區(qū)域匆光;我們說一個指針指向了某塊內(nèi)存區(qū)域套像,相當于說該指針的值是這塊內(nèi)存區(qū)域的首地址。指針的值
和指針所指向的類型
是兩個完全不同的概念终息。在上圖中夺巩,指針所指向的類型已經(jīng)有了贞让,但由于指針還未初始化,所以它所指向的內(nèi)存區(qū)是不存在的柳譬,或者說是無意義的喳张。指針本身所占據(jù)的內(nèi)存區(qū):指針本身占用的內(nèi)存可以用函數(shù)
sizeof(數(shù)據(jù)類型)
測一下就知道了。在64 位平臺里美澳,指針本身占據(jù)了8 個字節(jié)的長度销部。指針本身占據(jù)的內(nèi)存這個概念在判斷一個指針表達式是否是左值時很有用。
//普通的整型變量
int p;
//首先從P 處開始,先與*結(jié)合,所以說明P 是一個指針,然后再與int 結(jié)合,說明指針所指向的類型為int 型.所以P是一個返回整型數(shù)據(jù)的指針
int *p;
//首先從P 處開始,先與[]結(jié)合,說明P 是一個數(shù)組,然后與int 結(jié)合,說明數(shù)組里的元素是整型的,所以P 是一個由整型數(shù)據(jù)組成的數(shù)組
int p[3];
//首先從P 處開始,先與[]結(jié)合,因為其優(yōu)先級比*高,所以P 是一個數(shù)組,然后再與*結(jié)合,說明數(shù)組里的元素是指針類型,
//然后再與int 結(jié)合,說明指針所指向的類型是整型的,所以P 是一個返回整型數(shù)據(jù)的指針所組成的數(shù)組
int *p[3];
//首先從P 處開始,先與*結(jié)合,說明P 是一個指針然后再與[]結(jié)合,說明指針所指向的內(nèi)容是一個數(shù)組,
//然后再與int 結(jié)合,說明數(shù)組里的元素是整型的.所以P 是一個指向整型數(shù)據(jù)組成的數(shù)組的指針
int (*p)[3];
//首先從P 開始,先與*結(jié)合,說是P 是一個指針,然后再與*結(jié)合,說明指針所指向的元素是指針,然后再與int 結(jié)合,說明該指針所指向的元素是整型數(shù)據(jù).
//由于二級指針以及更高級的指針極少用在復(fù)雜的類型中,所以后面更復(fù)雜的類型我們就不考慮多級指針了,最多只考慮一級指針.
int **p;
//從P 處起,先與()結(jié)合,說明P 是一個函數(shù),然后進入()里分析,說明該函數(shù)有一個整型變量的參數(shù),然后再與外面的int 結(jié)合,說明函數(shù)的返回值是一個整型數(shù)據(jù)
int p(int);
//從P 處開始,先與指針結(jié)合,說明P 是一個指針,然后與()結(jié)合,說明指針指向的是一個函數(shù),然后再與()里的int 結(jié)合,說明函數(shù)有一個int 型的參數(shù),
//再與最外層的int 結(jié)合,說明函數(shù)的返回類型是整型,所以P 是一個指向有一個整型參數(shù)且返回類型為整型的函數(shù)的指針
Int (*p)(int);
//從P 開始,先與()結(jié)合,說明P 是一個函數(shù),然后進入()里面,與int 結(jié)合,說明函數(shù)有一個整型變量參數(shù),然后再與外面的*結(jié)合,說明函數(shù)返回的是一個指針,
//然后到最外面一層,先與[]結(jié)合,說明返回的指針指向的是一個數(shù)組,然后再與*結(jié)合,說明數(shù)組里的元素是指針,然后再與int 結(jié)合,說明指針指向的內(nèi)容是整型數(shù)據(jù).
//所以P 是一個參數(shù)為一個整數(shù)據(jù)且返回一個指向由整型指針變量組成的數(shù)組的指針變量的函數(shù).
int *(*p(int))[3];
指針的操作
打又聘:
a = 10, &a = 0x7ffeefbff4fc
b = 0x7ffeefbff4fc, *b = 10, &b = 0x7ffeefbff4f0
上圖可以看到柴墩,當打印 c 時就會Crash,報錯看圖凫岖。
void contactAddressAndPointer()
{
int num = 97;
//定義一個指針pointer
int *pointer;
//對 num 取地址(&num, 使用連字號&運算符訪問的地址), 賦值給指針變量pointer
pointer = #
/*
int num=97;
//在定義指針pointer的同時將num的地址賦給指針pointer
int *pointer=#
*/
//pointer:指向的變量num的地址江咳;
// &pointer:指針pointer的地址,因為指針也是一個變量自己也有地址的哥放;
//&num:變量 num 的地址歼指;
printf("地址:\np:%p, &p:%p, &num:%p\n\n",pointer, &pointer, &num);
//*pointer:變量 num 的值;
//i:變量 i 的值甥雕;
printf("值:\n指針變量*pointer:%d, num的值:%d\n",*pointer,num);
}
打印值:
地址: p:0x7ffeefbff57c, &p:0x7ffeefbff570, &num:0x7ffeefbff57c
值: 指針變量*pointer:97, num的值:97
? 用來保存 指針 的變量踩身,就是指針變量。如果指針變量pointer保存了變量 num的地址社露,則就說:pointer指向了變量num挟阻,也可以說pointer指向了num所在的內(nèi)存塊 ,這種指向關(guān)系峭弟,在圖中一般用 箭頭表示附鸽。
? 指針變量pointer指向了num所在的內(nèi)存塊 ,即從地址0028FF40開始的4個byte 的內(nèi)存塊瞒瘸。
取址
int pointerFunc(int x, int b){
return x + b;
}
int num = 97;
float scrore = 10.00F;
int arr[3] = {1, 2, 3};
int (*ptr)(int, int);
int *p_num = #
float *p_scrore = &scrore;
int (*p_arr)[3] = &arr; //指向含有3個int元素的數(shù)組的指針
ptr = *pointerFunc;
const char *msg = "Hello Pointer";
printf(" p_num: %p, *p_num: %d, &num:%p\n", p_num, *p_num, &num);
printf(" p_scrore: %p, *p_scrore: %f, &num:%p\n", p_scrore, *p_scrore, &scrore);
printf(" p_arr: %p, *p_arr:%p, &arr:%p\n", p_arr, *p_arr, &arr);
printf(" pointerFunc 地址:%p, &pointerFunc: %p, *pointerFunc:%p\n", pointerFunc, &pointerFunc, *pointerFunc);
printf(" msg: %p, *msg:%c,&msg:%p\n", msg, *msg, &msg);
int n = (*ptr)(10, 5);
printf(" n = %d", n);
打涌辣浮:
p_num: 0x7ffeefbff534, *p_num: 97, &num:0x7ffeefbff534
p_scrore: 0x7ffeefbff530, *p_scrore: 10.000000, &scrore:0x7ffeefbff530
p_arr: 0x7ffeefbff54c, *p_arr:0x7ffeefbff54c, &arr:0x7ffeefbff54c
pointerFunc 地址:0x100001360, &pointerFunc: 0x100001360, *pointerFunc:0x100001360
msg: 0x100001c60, *msg:H,&msg:0x7ffeefbff508
n = 15
特殊的情況,他們并不一定需要使用&取地址:
- 數(shù)組名的值就是這個數(shù)組的第一個元素的地址情臭。
- 函數(shù)名的值就是這個函數(shù)的地址省撑。
- 字符串字面值常量作為右值時,就是這個字符串對應(yīng)的字符數(shù)組的名稱,也就是這個字符串在內(nèi)存中的地址俯在。
解址
? 實質(zhì)是:從指針指向的內(nèi)存塊中取出這個內(nèi)存數(shù)據(jù)竟秫。
? 對一個指針解地址,就可以取到這個內(nèi)存數(shù)據(jù)跷乐,解地址 的寫法肥败,就是在指針的前面加一個*號。
int age = 19;
int*p_age = &age;
*p_age = 20; //通過指針修改指向的內(nèi)存數(shù)據(jù)
printf("age = %d\n",*p_age); //通過指針讀取指向的內(nèi)存數(shù)據(jù)
printf("age = %d\n",age);
打印:
age = 20
age = 20
注意:
指針所保留的是內(nèi)存中一個地址拙吉,它并不保存指向的數(shù)據(jù)的值本身潮孽。因此揪荣,務(wù)必確保指針對應(yīng)一個已經(jīng)存在的變量或者一塊已經(jīng)分配了的內(nèi)存筷黔;
-
星號有兩種用途:
- 用于創(chuàng)建指針:
int *myPointer = &myInt;
- 對指針進行解引用:
*myPointer = 3998;
- 用于創(chuàng)建指針:
指針之間的賦值
? 指針賦值和int變量賦值一樣,就是將地址的值拷貝給另外一個仗颈。指針之間的賦值是一種淺拷貝佛舱,是在多個編程單元之間共享內(nèi)存數(shù)據(jù)的高效的方法。
//通過指針 p1 挨决、 p3 都可以對內(nèi)存數(shù)據(jù) num 進行讀寫请祖,如果2個函數(shù)分別使用了p1 和p3,那么這2個函數(shù)就共享了數(shù)據(jù)num脖祈。
int num = 10;
int* p1 = & num;
int* p3 = p1;
printf("num值沒改變:num= %d, *p1= %d, *p3= %d", num, *p1, *p3);
num = 100;
printf("\n\nnum值改變后:num= %d, *p1= %d, *p3= %d", num, *p1, *p3);
輸出值:
num值沒改變:num= 10, *p1= 10, *p3= 10
num值改變后:num= 100, *p1= 100, *p3= 100
指針的屬性
int main(void)
{
int num = 97;
int *p1 = #
char* p2 = (char*)(&num);
printf("%d\n",*p1); //輸出 97
putchar(*p2); //輸出 a
return 0;
}
指針的值
:很好理解肆捕,如上面的num 變量 ,其地址的值就是0028FF40 盖高,因此 p1的值就是0028FF40慎陵。數(shù)據(jù)的地址用于在內(nèi)存中定位和標識這個數(shù)據(jù),因為任何2個內(nèi)存不重疊的不同數(shù)據(jù)的地址都是不同的喻奥。
指針的類型
:指針的類型決定了這個指針指向的內(nèi)存的字節(jié)數(shù)并如何解釋這些字節(jié)信息席纽。一般指針變量的類型要和它指向的數(shù)據(jù)的類型匹配。
由于num的地址是0028FF40撞蚕,因此p1 和 p2的值都是0028FF40
*p1 : 將從地址0028FF40 開始解析润梯,因為p1是int類型指針,int占4字節(jié)甥厦,因此向后連續(xù)取4個字節(jié)纺铭,并將這4個字節(jié)的二進制數(shù)據(jù)解析為一個整數(shù) 97。
*p2 : 將從地址0028FF40 開始解析刀疙,因為p2是char類型指針彤蔽,char占1字節(jié),因此向后連續(xù)取1個字節(jié)庙洼,并將這1個字節(jié)的二進制數(shù)據(jù)解析為一個字符顿痪,即'a'。
同樣的地址油够,因為指針的類型不同蚁袭,對它指向的內(nèi)存的解釋就不同,得到的就是不同的數(shù)據(jù)石咬。