1. 變量
不同類型的變量在內存中占據不同的字節空間。
內存中存儲數據的最小基本單位是字節,每一個字節都有一個內存地址,這個地址是一個十六進制的數。
聲明一個變量,在內存中是從高字節向低字節分配連續的指定字節數的空間。
任何數據在內存中都是以其二進制的補碼形式存儲的,低位存儲在低字節,高位存儲在高字節。
變量的值:存儲在變量中的數據,叫做變量的值。
變量的地址:一個變量是由一個或者多個字節組成的,組成這個變量的低字節的地址,就是這個變量的地址。
如何取出變量的地址:使用&(取地址運算符)運算符,&變量名;這個表達式的值就是這個變量的地址。使用%p控制度輸出變量的地址。
什么是指針:變量的地址叫做指針,指針就是地址,地址就是指針。
下面通過一張圖可以更直觀的理解內存中的地址
內存中的地址演示圖
2. 指針
指針是C語言的靈魂。指針變量占據8個字節。
變量在內存中的存儲。
變量的值:存儲在變量中的數據,叫做變量的值。
變量的地址:組成這個變量的低字節的地址,就是這個變量的地址。
取出變量的地址,用&運算符 %p輸出變量的地址。
變量的地址就叫做指針,我們可以使用一個指針變量來存儲變量的地址。
指針變量:
指針變量就是專門用來存儲?地址?的變量,那么我們就說指針變量指向了另外一個變量,存儲著另外一個變量的地址。
指針可以使訪問一個變量的方式分為兩種
a. 直接訪問
b. 可以通過指針變量,找到這個指針指向的變量
所以通過指針變量可以間接的訪問指針變量指向的另外一個變量。
如何聲明一個專門用來存儲地址的指針變量
數據類型
*?指針變量的名稱 ---?int * p1;
指針變量的名字叫做p1,這個指針變量的類型是int*讀作int指針。
*表示這個變量不是一個普通變量,而是一個專門用來存儲地址的指針變量,所以有哪些普通的數據類型,就可以有哪些類型的指針。
聲明的時候注意,
*的位置 建議?int* p這樣提醒我們這是一個int*類型的指針。
一個指針變量并不是可以存儲任意類型的變量的地址,而是有限定的,只能存儲和這個指針類型相同的普通變量的地址。?所以P指針變量中只能存儲int類型變量的地址。
指針變量的初始化?
intnum=10;int*p = ?#
建議int* p = #這樣寫
p指針指向了num變量。因為p指針的值就是num變量的地址,不能直接賦值一個非地址類型的常量數據,也不能直接賦值一個變量給指針。
p指針自己也有地址, 因為指針變量也是一個變量,&p取到指針p的地址。
p操作的是p這個指針變量,可以取p得值,也可以為p賦值
指針變量的使用
可以使用指針間接的操作指針指向的變量。
*p代表p指針指向的變量。
*p完全等價于num即*p = 100完全等價于num = 100。
*p = 100;表示將100賦值給p指針指向的變量,也就是num變量
使用指針變量的時候注意點
int* p1 ,p2, p3 ;此時p1是int *指針,而p2,p3是int類型數據 如果希望全部都是指針需要int *p1, * p2, * p3;
野指針
我們聲明一個指針變量,如果沒有為其初始化,那么這個時候這個指針變量中是有值的,是垃圾值,隨機數。因為這個時候,這個指針變量有可能指向了一塊隨機的空間,這個空間可能無人使用,也可能別的程序在用,也可能系統在用,這個時候,去訪問指針指向的的變量的時候,就會報錯。BAD_ACCESS壞地址訪問錯誤,像這樣的指針我們就叫做野指針。
NULL值 完全等價于0
為了防止野指針的產生,建議聲明一個指針變量后,最好為其初始化,如果沒有變量的地址初始化給這個指針變量。那么就初始化一個NULL值。NULL值代表指針變量不指向內存中的任何地址,這樣就不會出現野指針,NULL完全等價于0,所以也可以直接賦值給一個指針變量0。
如果一個指針變量的值是NULL,那么去訪問這個指針指向的變量的時候一定會報錯。
多個指針指向同一個變量,修改其中一個所有指針指向的值都會改變。因為多個個指針指向的是同一塊地址。即?*?會使指針間接的操作指針指向的變量。
指針作為函數的參數
如果函數的參數是一個指針,那么就必須要為這個指針傳遞一個和指針類型相同的普通變量的地址,這個時候,在函數的內部去訪問參數指針的變量的時候,其實訪問的就是實參變量
指針作為函數的參數,可以實現什么效果?
函數的內部可以修改實參變量的值。那么什么時候使用指針作為參數呢?
一般函數只能返回一個數據,那么當函數需要返回多個數據的時候就可以使用指針作為參數,讓調用者將自己的變量的地址傳遞給函數內部,函數內部通過指針就可以修改參數,函數無需將數值傳回來,就已經修改了參數的值。其實scanf函數傳遞的就是指針,因此當函數需要多個返回值的時候就可以使用指針作為參數。
// 從下面代碼中就可以看出,
我們可以直接在函數中修改兩個變量的值。
相當于函數有兩個返回值。
voidchangeValue(int* p1 ,int* p2)
{ ? ?*p1 =100; ? ?*p2 =200;}
intmain(intargc,constchar* argv[])
{
? ?intnum1 =1;intnum2 =2; ? ?changeValue(&num1, &num2);
? ?printf("num1 = %d\n",num1);
? ?printf("num2 = %d",num2);return0;}
指針為什么要分類型
指針變量既然是一個變量就要在內存中占用字節空間
指針變量在內存中占據多少字節數?
無論指針是什么類型在內存中都是占據8個字節。
那為什么指針還要分類呢?
p指針變量中存儲的是num變量的地址,也就是num變量低字節的地址,通過p指針只能找到這個地址的字節,這個時候,通過p指針找到這個字節,操作的時候,操作多少個字節是則是根據指針的類型來決定的。
所以指針變量的類型決定了通過這個指針找到字節以后,連續操作多少個字節空間。
int 指針 連續操作4個字節空間
double 指針 連續操作8個字節空間
float 指針 連續操作4個字節空間
char 指針 連續操作1個字節空間
因此,指針的類型如果不和指向的變量的類型相同的話,那么通過指針就無法正確的操作指向的變量,所以,指針的變量一定要指向一個和自己類型相同的普通變量才可以。
指針為什么要分類型?
多級指針
一個指針變量中存儲的是一個一級指針的地址,那么它就是二級指針,一個指針變量中存儲的是一個二級指針的地址,那么它就是三級指針。
二級指針:數據類型 ** 指針名
二級指針只能存儲一級指針變量的地址。
多級指針在開發中很少用到,遇到多級指針耐心分析一定可以理清其中的關系。
指針與整數的加減法
指針可以和整數進行加減運算,指針+1并不是在指針地址的基礎之上加一個字節的地址,而是在這個指針地址的基礎之上加一個單位變量占用的字節數,例如:如果指針類型是int *則+1代表加4個字節地址,以此類推。
指針與數組
我們可以使用指針來遍歷數組,因為數組的本質其實就是指針,當我們創建數組的時候,系統會在內存中由高地址向低地址分配連續的類型所占的空間字節數 * 數組內元素的個數的字節控件。而數組名則代表了數組的低字節地址,也就是數組的地址。
1). 使用指針遍歷數組的第一種方式.
//在內存中高地址向低地址分配連續的類型
所占的空間字節數據*數組內元素的個數7*4=28
int arr[7] = {10,20,30,40,50,60,70};//p1指針指向了數組的第0個元素
int* p1 = arr; for(int i =0; i <7; i++) { ? ?printf("%d\n",*(p1+i)); }
2). 使用指針遍歷數組的第二種方式.
int arr[7] = {10,20,30,40,50,60,70}; for(int i =0; i <7; i++) { ? ?printf("%d\n",*(arr+i)); }
3). 使用指針遍歷數組的第三種方式.
int arr[7] = {10,20,30,40,50,60,70}; int* p1 = arr; for(int i =0; i <7; i++) { ? ?printf("%d\n",*(p1++)); }注意的地方,每次循環,p1的值都會變化。 最后1次執行完畢之后,p1指針指向數組外面去了,
p1就不再執行數組中的任何元素了。
注意: 數組名代表數組的地址,而數組一旦創建,數組的地址就確定了,不能改變。
所以,我們不能為數組名賦值也不能修改數組名的值,但是可以使用數組名的值。
arr是數組的地址,也是數組中第0個元素的地址,arr+1就是數組中第一個元素的地址,數據名就是一個地址常量,無法改變。
數組作為函數的參數的本質
當數組作為函數的參數的時候,在聲明這個參數數組的時候,并不是去創建一個數組,而是去創建一個用來存儲地址的指針變量,如果我們為函數寫了一個數組作為參數,其實編譯器在編譯的時候,已經把這個數組變成了指針,這也就是為什么我們通過sizeof計算參數數組得到的永遠都是8,所以以后我們的函數如果帶了一個數組參數,建議直接寫一個指向數組的第0個元素的指針,在傳入數組的長度
索引的本質
指針變量后面可以使用中括號,在中括弧中寫上下標來訪問數據。
p[n];前提p是一個指針變量,完全等價于*(p + n);
所以arr[0]就等價于* [arr + 0]。
操作數組我們雖然使用中括弧下標來操作,實際上內部本質仍然是使用的指針來操作。
存儲指針的數組
如果一個數組是用來存儲指針類型的數據的話,那么這個數組就叫做存儲指針的數組
格式 :元素類型 數組名[數組長度];int * arr[3];
arr數組里面存儲int指針數據,最多存儲3個。
指針與指針之間的減法運算
指針與指針之間可以做減法運算,結果是一個long類型的數據,
結果的意義代表兩個指針指向的變量之間相差多少個單位變量,絕大多數情況下,我們用在判斷數組的兩個元素之間相差多少個元素
如果參與減法運算的兩個指針不指向同一個數組,結果就會出現問題
結果 = 兩個指針的差 / 每一個指針變量對應的普通變量占用的字節數。
并且只能做減法運算,用在數組當中判斷兩個元素之間相差多少個元素。
指針與指針在之間的比較運算?<, <=, >, >=, ==, !=都可以使用
可以用來判斷兩個指針指向的變量的字節,誰在高字節,誰在低字節。或者兩個指針的地址是不是同一個地址。
指針和字符變量
char *name = "jack";表示直接將一個字符串數據初始化給一個字符指針。
字符指針存儲和字符數組存儲的區別
// 字符數組存儲:
將字符串數據的每個字符存儲到字符數組的元素中,
并追加一個 \n 表示結束
//直接為字符指針初始化一個字符串數據
charname[5] ="jack";char*name ="jack";
1.) 當他們都是局部變量的時候
字符數組是申請在棧區的,字符串的每一個字符存儲在字符數組的每一個元素中。
指針變量是聲明在棧區的。但是此時字符串數據是以字符數組的形式存儲在常量區的。此時指針變量中存儲的是字符串在常量區的地址。
2.) 當他們作為全局變量的時候
字符數組是存儲在常量區的,字符串的每一個字符存儲在這個數組中的每一個元素中。
字符指針也是存儲在常量區,字符串也是以字符數組的形式存儲在常量區,指針中存儲的是字符串在常量區的地址。
以字符數組存儲的字符串數據,可以修改字符數組的元素。可變
以字符指針的形式存儲字符串數據,這個時候字符指針指向的字符串數據是無法修改的,不可變
字符串的恒定型
前提:以字符指針形式存儲的字符串
1.) 當我們以字符指針的形式存儲字符串的時候,無論如何,字符串數據是存儲在常量區的,并且,一旦存儲到常量去中去,這個字符串數據就無法更改。
2.) 當我們以字符指針的形式要將字符串數據存儲到常量區的時候,并不是直接將字符串存儲到常量區,而是先檢查常量區中是否有相同內容的字符串,如果有,直接將這個字符串的地址拿過來返回,如果沒有,才會將這個字符串數據存儲到常量區中。
3.) 當我們重新為字符指針初始化一個字符串的時候,并不是修改原來的字符串,因為原來的字符串數據是不可更改的,而是重新的創建了一個字符串,把這個新的字符串的地址賦給他。建議使用字符指針來存儲字符串數據。優勢:長度任意。
//這樣可以 但是并不是把jack改成了rose,
而是重新創建一個"rose"把rose地址賦值給name
char *name ="jack";nsme ="rose";
字符串數組
char *names[4] = {"aa","bb","cc","dd"};
names數組的元素的類型是char指針,初始化給元素的字符串數據是存儲在常量區的。元素中存儲的是字符串在常量區的地址。
因此這是一個存儲指針的數組,每一個元素的類型是一個指針,占用得內存為8個字節。
指向函數的指針
程序在運行的時候,會將程序加載到內存,內存的代碼段中主要存儲的就是程序的代碼,而程序的代碼就包括函數。既然函數要存儲在內存中,那么肯定要用1塊空間來存儲,那么這個塊空間一定有1個地址。
因此我們就可以聲明1個指針用來存儲這個函數的地址,也就是說讓這個指針指向這個函數。這樣我們就可以使用指針來間接的調用函數。
優勢: 調用函數有了兩種方式。
1.) 直接使用函數名調用
2.) 使用指向函數的指針間接調用.
指向函數的指針的聲明
一個指向函數的指針,并不是任意的函數都可以指向,而是有限定的,要求指向的函數的返回值類型和參數描述必須要與指針的描述一樣。
聲明語法
返回值類型
(*指針名)([參數列表]);
//表示聲明了1個指向函數的指針,叫做pFunction
//這個指針只能指向沒有返回值,并且沒有參數的函數
void (*pFunction)();
//表示聲明了1個指向函數的指針,叫pFun,
這個指針只能指向返回值為int類型,
并且有兩個整型的參數的函數
int(*pFun)(int num1,int num2)
指向函數的指針的初始化
函數的名稱就代表函數的地址,因此我們直接將符合條件的函數的名稱賦值給這個指針。
并且我們有兩種方法可以通過指針來調用這個函數。
intMaxValue(inta,intb)
{
? ?returna > b ? a : b;}
intmain(intargc,constchar* argv[])
{
//?創建一個返回int 并且有兩個int型參數的函數指針,并賦值int(*pMaxValue)(inta,intb) = MaxValue;//通過指針調用函數方法1 printf("%d\n", pMaxValue(5,10));//通過指針調用函數方法2printf("%d\n",(*pMaxValue)(6,9));//調用函數printf("%d\n",MaxValue(9,13));//輸出函數的地址return 0;printf("%p\n",MaxValue);}