C語言學習之基礎知識點—指針!上萬字的乾貨知識

指針

指針是c語言裡面最抽象的、最重要的、最常用的。

指針的概念:

指針變量也是一個變量,

指針存放的內容是一個地址,該地址指向一塊內存空間,

指針是一種數據類型(指針類型)。

--------------------------------------

“我是一名從事了10年開發的老程序員,最近我花了一些時間整理關於C語言、C++,自己有做的材料的整合,一個完整的學習C語言、C++的路線,學習材料和工具。進企鵝裙學習()免費送給大家。這裡是編程愛好者的聚集地,歡迎初學和進階中的小夥伴。希望你也能憑自己的努力,成為下一個優秀的程序員。


計算機內存的最小單位是什麼?BYTE(字節)

對於內存,每個BYTE(字節)都有一個唯一不同的編號,這個編號就是內存地址。

操作系統就給內存的每一個字節編了一個號,所以說:一個編號對應的是一個BYTE(字節)的空間大小。

打比方:

1 -> BYTE

2 -> BYTE

3 -> BYTE

4 -> BYTE

對應於

--------------------------------------

一個int多大?答:4個BYTE(字節),所以一個int佔用了了4個編號(即4個不同的內存地址)。

地址的編號:在32位系統下是一個4個字節的無符號整數;在64位系統下是一個8個字節的無符號整數。

(因為地址不可能是負的,又因為無符號數可以表達一個更大的地址,有符號數表示的最大地址會變小)

-----------------------------------------------------------------------------

指針變量的定義:

可以定義一個指向一個變量的指針變量。

-----------------------------------------------------------------------------

取地址運算符 &

& 可以取得一個變量在內存當中的地址。(取地址取的是內存地址)

register int a;//寄存器變量,這種變量不在內存裡面,而在cpu裡面,所以是沒有地址的,

所以寄存器變量不能使用&來得到地址。

-----------------------------------------------------------------------------

無類型指針

定義一個指針變量,但不指定它指向具體哪種數據類型。可以通過強制轉化將 void * 轉化為其他類型指針,

也可以用 (void *) 將其他類型強制轉化為void類型指針。

void *p; 指針之間賦值需要類型相同,但任何類型的指針都可以賦值給 void * 。

-----------------------------------------------------------------------------

linux下示例代碼如下:

int main()

{

int*p;//定義了一個可以指向int類型地址的指針變量,指針變量的名字叫p。*不是指針變量名字的一部分。

//int * 是一種數據類型。

inta;//定義了一個int類型的變量,int變量的名字叫a。

a =1;//int * 和 int是兩種不同的數據類型。

p = &a;//把a的內存地址賦值給指針變量p。

printf("%p\\n", p);//0x7fff5b2faedc 輸出的是a的首地址的編號,不會把四個編號都輸出的。

//而且注意:每一次執行該代碼後,輸出的編號都會發生變化!

*p =10;//通過指針變量間接的訪問a的值,*p代表指針指向變量的值,p代表指向變量的地址。

printf("a = %d\\n", a);//a = 10; 通過上面的方法把a的值改變了。

a =100;19printf("%d\\n", *p);//100 通過指針變量間接的訪問a的值。

intb =2;

p = &b;//又把b的內存地址賦值給p。

*p =20;

printf("b = %d\\n", b);//20

//char *p1 = &a; //相當於 char *p1; p1 = &a;//兩個類型不相同的地址。即指針類型不兼容。那麼我們強轉試試!

char*p1 = (char*)&a;

a =123456;

*p1 =0;

printf("a = %d\\n", a);//a = 123392 就算強轉後也會出現問題,所以要避免指針類型不兼容問題。

void*p2;//可以指向任何類型的地址,void代表無類型。

return0;35}

-----------------------------------------------------------------------------

指針佔用內存的說明

在同一個系統下,不管指針指向什麼樣類型的變量,地址的大小(或叫編號的大小)總是一樣的。

linux下示例代碼如下:

int main()

{

char*p1;

int*p2;

longlong*p3;

printf("%lu, %lu, %lu\\n",sizeof(p1),sizeof(p2),sizeof(p3));//實質是:編號的大小是多少?

return0;//輸出的是 8, 8, 8

//地址的編號:在32位系統下是一個4個字節的無符號整數;在64位系統下是一個8個字節的無符號整數。

//指針變量的名字叫p1、p2、p3。指針變量的大小是多大呢?因為指針變量對應的是某某的首地址的編號,

//即指針變量對應的是編號,而編號就是內存地址。即編號在64位系統下是一個8個字節的無符號整數。

//所以指針變量的大小就是編號的大小,而編號在64位系統下用8個字節的無符號整數表示。

//舉例子說明下:同一個酒店,房間的編號的長度都是一樣的。

}

--------------------------------------

再比如:

linux下示例代碼如下:

#include

int main()

{

int*p1;

inta =0;

p1 = &a;

*p1 =10;

//p1 = 10;

int*p2;

p2 = &a;

//*p2是什麼?不管是*p1還是*p2都代表變量a的值,但p1和p2確實是兩個不同的指針變量。

return0;

}

畫圖說明如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


=============================================================================

野指針 與 空指針

野指針:沒有指向任何有效地址的指針變量,所以在代碼中避免出現野指針,

如果一個指針不能確定指向任何一個變量地址,那麼就將這個指針變成空指針。

linux下示例代碼如下:

#include

int main()

{

int*p;

*p =100;//不能這樣寫,沒有初始化過值的指針,這種指針叫野指針。

return0;//因為地址編號所佔用的內存不是你程序要調用的內存。對於操作系統而言,不是你的內存你就不能改!

//如果你非要改的話,操作系統就會發現你在做非法操作,會直接把你清理出去了。即程序出錯。

}

編譯上段程序沒有錯誤,運行上段程序會出現一個錯誤:Segmentation fault(段錯誤,也即分段故障)

-----------------------------------------------------------------------------

空指針:就是指向了NULL的指針變量。

linux下示例代碼如下:

#include

int main()

{

int*p;//兩句代碼相當於一句:int *p = NULL;

p = NULL;//如果一個指針變量沒有明確的指向一塊內存,那麼就把這個指針變量指向NULL。

//這個指針就是空指針,空指針是合法的。

//實際上NULL並不是c語言的關鍵字,NULL在c語言中的定義是:#define NULL 0

//NULL在c語言裡面就是一個宏常量,值是0。那麼我們為什麼不直接寫0呢?

//NULL代表的是空指針,而不是一個整數零,這樣看的會舒服些。(這只是粗淺易懂的解釋)

return0;

}

程序中不要出現野指針,但可以出現空指針。

--------------------------------------

空指針理解的擴展:

注意:

inta =0;

int*p = &a;//相當於 int *p; p = &a;

int*node = NULL;//相當於:int *node; node = NULL;

NULL就是系統定義特殊的0,把你初始化的指針指向它,可以防止“野指針”的惡果。

NULL是個好東西,給一出生的指針一個安分的家。

--------------------------------------

用C語言編程不能不說指針,說道指針又不能不提NULL,那麼NULL究竟是個什麼東西呢? C語言中又定義,定義如下:

#undefNULL

#ifdefined(__cplusplus)

#defineNULL 0

#else

#defineNULL ((void *)0)

#endif

所以我覺得,如果一個指針被賦予NULL,應該就相當於這個指針執行了0x0000這個邏輯地址,

但是C語言中0x0000這個邏輯地址用戶是不能使用的,

(有些人說是因為0x0000沒有映射到物理地址,也有人說是因為0x0000映射到的地址是操作系統用於判斷野指針的,我也不太懂,總之就是用戶不能使用啦)

所以當你試圖取一個指向了NULL的指針的內容(或者叫值)時,就會提示段錯誤,聽著有點繞,看程序:

1int*node = NULL;2inta =0;3a = *node;//*node的意思是:取指針變量node的值。然後賦值給a。45printf("%d\\n", a);

*node的意思是:取指針變量node的值,也就是邏輯地址0x0000,而這個地址是不能被訪問的(即不能被取出來的),

c語言語法上沒有問題,所以編譯器編譯沒有問題,但是編譯器編譯後運行會出現段錯誤。

linux下示例代碼如下:

#include

int main() {

int*p = NULL;//相當於 int *p = 0; 但一般不這麼寫啊!

inta =0;

a = *p;

printf("%d\\n", a);

return0;

}

root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指針# gcc -o p6 p6.c

root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指針# p6

Segmentation fault(段錯誤,也即分段故障)

root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/指針#

=============================================================================

指針的兼容性(即指針類型之間一定要匹配)

指針之間賦值比普通數據類型賦值檢查更為嚴格,例如:不可以把一個 double * 賦值給int。

#include

int main()

{

int*p;

charb =1;

p = &b;//指針類型之間一定要匹配,不然會有警告,強行運行的話,結果不可控!

return0;10}

警告如下:

warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]

警告:不兼容的指針類型分配[-Wincompatible-pointer-types]

=============================================================================

我們不要把指針想象的特別神秘!其實指針變量也是一個變量。

它裡面放的就是一個地址的編號,地址的編號就是一個8個字節的無符號的整數(64位系統下)。

區別是:這個整數不能直接賦值,而是來自於對另外一個變量的取地址操作而得到!

=============================================================================

不同的數據類型在內存中佔用的地址

我們先看幾個現象:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


每一次編譯後執行,輸出的地址會發生變化,但是相鄰地址間的間隔不變。

--------------------------------------

再比如:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


每一次編譯後執行,輸出的地址會發生變化,但是相鄰地址間的間隔不變。

其餘的類型就不一一舉例啦!

=============================================================================

指向常量的指針 和 指針常量

const int *p; //定義一個指向常量的指針。

int *const p; //定義一個指針常量,一旦指向某一變量的地址後,不可再指向其他變量的地址。(注意:指針常量也叫常量指針

二者區別:

const int *p;//p是一個變量,但指向一個常量。(即p可以指向任何地址,但是隻能通過*p來讀這塊地址的內容,不能通過*p來寫這塊地址的內容)

int *const p;//p是一個常量,但指向一個變量或者常量。(即如果一旦p指向了任何一個有效的地址後,就不可再指向其他變量的地址,但可以通過*p來讀寫這塊地址的內容)

--------------------------------------

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


=============================================================================

指針與數組的關係


C語言學習之基礎知識點—指針!上萬字的乾貨知識


--------------------------------------

一級指針畫圖小說明如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


=============================================================================

指針運算

指針變量可以進行計算,如果是 int * 類型每加一,變化4個整數;

如果是 char * 類型每加一,變化1個整數。其他類型以此類推。

linux下示例代碼如下:

#include

int main()

{

inta =0;

int*p = &a;

printf("%p, %p, %p\\n", p, p +1, p +2);//0x7fff5c2a518c, 0x7fff5c2a5190, 0x7fff5c2a5194

return0;

}

-----------------------------------------------------------------------------

指針運算小例子:

linux下示例代碼如下:

#include

int main()

{

inta[10] = {0 };

int*p = a;

p +=5; 9*p =1;

p -=2;12*p =3;

int i;

for(i =0; i <10; i++)

{

printf("a[%d] = %d\\n", i, a[i]);

}

return0;

}

輸出的是:

a[0] =0

a[1] =0

a[2] =0

a[3] =3

a[4] =0

a[5] =1

a[6] =0

a[7] =0

a[8] =0

a[9] =0

-----------------------------------------------------------------------------

通過指針使用數組元素

linux下示例代碼如下:

#include

int main()

{

inta[10] = {1,2,3,4,5,6,7,8,9,10 };

int*p = a;//指針p指向a的首地址。

p[3] =100;//等價於 *(p + 3) = 100; 一般寫成左邊那樣。

printf("a[%d] = %d\\n", i, a[i]);

int i;

for(i =0; i <10; i++)

{

}

return0;

}

=============================================================================

不同類型的指針的區別以及與數組的關係

極端例子如下:

linux下示例代碼如下:

#include

int main()

{

inta =0x12345678;

char*p = (char*)&a;

printf("%x, %x, %x, %x, %x\\n", *p, p[0], p[1], p[2], p[3]);//%x的意思是按照十六進制的有符號整數輸出(小寫)

printf("--------------------\\n");

*p =0;

p[3] =0;

printf("%08x\\n", a);

printf("--------------------\\n");

charb[20] = {0 };

int*p1 = (int*)&b;

p1[3] =0x12345678;

int i;

for(i =0; i <20; i++)

{

printf("b[%d] = %x\\n", i, b[i]);

}

printf("--------------------\\n");

return0;

}

輸出的結果是:

78,78,56,34,12

--------------------

0034560033

--------------------

b[0] =0

b[1] =0

b[2] =0

b[3] =0

b[4] =0

b[5] =0

b[6] =0

b[7] =0

b[8] =0

b[9] =0

b[10] =0

b[11] =0

b[12] =78

b[13] =56

b[14] =34

b[15] =12

b[16] =0

b[17] =0

b[18] =0

b[19] =0

--------------------

說明:小端對齊輸出。

小結:c語言中所有的數據類型都可以理解為一個char的數組。

-----------------------------------------------------------------------------

小案例:int類型與ip地址的對應關係。

實際上的ip地址是一個無符號的整數構成的。1個int,4個字節。

1、把整數轉換為ip地址

ip地址的格式:

0.0.0.0 ~ 255.255.255.255

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


-----------------------------------------------------------------------------

2、把ip地址轉換為整數

輸入一個ip地址

char a[100] = "192.168.2.5"

把這個ip轉化為unsigned int類型的整數。

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


-----------------------------------------------------------------------------

3、使用指針給二維數組排序

linux下示例代碼如下:

#include

int main()

{

chara[2][5] = { {4,3,5,9,78}, {52,21,5,6,4 } };

char*p = (char*)a;

int i, j;

for(i =0; i <10; i++)

{

for(j =1; j <10- i; j++)

{

if(p[j] < p[j -1])

{

chartmp = p[j];

p[j] = p[j -1];

p[j -1] = tmp;

}

}

}

for(i =0; i <2; i++)

{

for(j =0; j <5; j++)

{

printf("%d\\n", a[i][j]);

}

}

return0;

}

輸出的結果是:

3

4

4

5

5

6

9

21

52

78

=============================================================================

指針數組

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


-----------------------------------------------------------------------------

linux下示例代碼如下:

#include

int main()

{

int*a[10] = { NULL };//定義了一個指針數組,指針數組的名字叫a,每 個成員是int *類型的,一共有10個成員。

intb, c, d;//對於指針數組來說,要先有指針的性質,再有數組的性質, 即先得獲得地址,然後對數組進行操作。

a[0] = &b;//a是指針數組的名字。

a[1] = &c;//a[0]是指針變量,

a[2] = &d;

*a[0] =10;//*a[0]是一個數組的成員之一。

printf("%d\\n", b);

return0;17}

=============================================================================

二級指針(指向指針的指針)

指針是一個變量,既然是變量就也存在內存地址,所以可以定義一個指向指針的指針。

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


二級指針說明圖如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


三級指針及其以上指針

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


linux下示例代碼如下圖所示:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


特別注意:

能用一級指針解決的問題不要用二級指針,能用二級指針解決的不用三級指針,指針級數過多會導致程序很複雜。

工作中大量使用的是一級指針,二級指針也很常用,三級指針就很罕見了,四級指針幾乎沒有。但筆試會考你哦!可以畫圖解決!

=============================================================================

函數的參數為指針變量(指針變量作為函數的參數)

實際上指針更多的時候用在函數的參數上。

函數的參數可以使是指針類型。它的作用是將一個變量的地址編號傳送給另一個函數。

void test(int *p); //定義一個函數,形參是int *類型。

c語言中如果想通過在一個函數的內部修改外部實參的值,那麼就需要給函數的參數傳遞這個實參的地址。

-----------------------------------------------------------------------------

linux下示例代碼如下:

#include

voidswap(inta,intb)//swap是交換的意思。

{

inttmp = a;

a = b;

b = tmp;

printf("a = %d, b = %d\\n", a, b);//a = 2, b = 1 形參中的值發生變化了。

}

int main()

{

inta =1;

intb =2;

swap(a, b);

printf("a = %d, b = %d\\n", a, b);//a = 1, b = 2 c語言中實參的值會傳給形參,而形參值的改變並不會影響到實參。

return0;

}

----------------------------------------------------------------------------

那麼現在我就想在一個函數的內部修改外部實參的值,那麼就需要給函數的參數傳遞這個實參的地址。代碼如下:

#include

voidswap(int*a,int*b)

{

inttmp = *a;

*a = *b;

*b = tmp;

printf("a = %d, b = %d\\n", a, b);//a = 2, b = 1 形參中的值發生變化了。

}

int main()

{

inta =1;

intb =2;

swap(&a, &b);//那麼現在我就想在一個函數的內部修改外部實參的值,那麼就需要給函數的參數傳遞這個實參的地址。

printf("a = %d, b = %d\\n", a, b);//a = 2, b = 1 實參中的值發生變化了。return0;

}

函數參數是指針變量的畫圖說明如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


即:c語言想通過函數內部來修改實參的值,只能給函數傳遞實參的地址來間接的修改實參的值。

例如scanf函數:

int a;

scanf("%d", &a);//scanf是一個函數,現在要通過函數內部來修改實參a的值,只能用傳遞a的地址的方式修改a的值

=============================================================================

函數的參數為數組名時(即數組名作為函數的參數)

當一個數組名作為函數的形參的時候,c語言將數組名解釋為指針變量,其實是一個指針變量名。

如果數組名作為函數的參數,那麼這個就不是數組名了,而是一個指針變量名。

當把一個數組名作為函數的參數時,修改形參的值的時候,同時也影響實參的數組成員的值。

如果把一個數組名作為函數的參數,那麼在函數內部就不知道這個數組的元素個數了,需要再增加一個參數來標明這個數組的大小。

如果將一個數組作為函數的形參進行傳遞,那麼數組的內容可以在被調用函數的內部進行修改,

有時候不希望這樣的事情發生,所以要對形參採用const進行修飾。

-----------------------------------------------------------------------------

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


C語言學習之基礎知識點—指針!上萬字的乾貨知識


=============================================================================

函數的返回值為指針時(即指針作為函數的返回值)

int*test()//函數的返回值類型是指針類型(具體的講解在下一節:內存管理)

{

return NULL;

}

=============================================================================

幾個c語言的庫函數:memset、memcpy、memmove函數,使用的時候需要包含頭文件 #include <string.h>

這三個函數分別實現內存設置、內存複製、內存移動功能。

--------------------------------------

memset的功能是:將指定區域的內存置空(設置為0)。

void *memset(void *s, int c, size_t n);

第一個參數是:指定要置空的內存的首地址;

第二個參數是:要設置的值,一般寫0;

第三個參數是:這塊內存的大小,單位:字節。

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


-----------------------------------------------------------------------------

memcpy功能是:兩塊內存之間拷貝數據。

使用memcpy時,首先一定要確保內存沒有重疊區域。

void *memcpy(void *dest, const void *src, size_t n);

第一個參數是:目標地址(目標內存首地址);

第二個參數是:源地址(源內存首地址);

第三個參數是:拷貝多少內容,單位字節。

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


--------------------------------------

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


內存拷貝說明畫圖如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


-----------------------------------------------------------------------------

memmove功能是:內存移動,參數與memcpy一致。

void *memmove(void *dest, const void *src, size_t n);

第一個參數是:目標地址(目標內存首地址);

第二個參數是:源地址(源內存首地址);

第三個參數是:拷貝多少內容,單位字節。

內存重疊區域說明如下圖所示:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


=============================================================================

指針小結

定義 說明

int i; 定義個一個int類型的變量

int *p; 定義一個指向int類型的指針變量

int a[10]; 定義一個int類型的數組

int *p[10]; 定義一個指針數組,其中每一個數組元素指向一個int類型變量的地址

int func(); 定義一個函數,返回值類型為int類型

int *func(); 定義一個函數,返回值類型為int*類型

int **p; 定義一個指向int類型的指針的指針,二級指針

=============================================================================

字符指針 與 字符串

在c語言中,大多數的字符串操作其實就是指針操作。

--------------------------------------

1、通過指針訪問字符串數組

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


-----------------------------------------------------------------------------

通過指針使得字符串逆置

法一:使用一個指針

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


--------------------------------------

法二:使用二個指針

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


=============================================================================

函數的參數為char *(即char *作為函數的參數)

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


如果將一個數組作為函數的形參進行傳遞,那麼數組的內容可以在被調用函數的內部進行修改,

有時候不希望這樣的事情發生,所以要對形參採用const進行修飾。代碼如下:

voidtest(constchar*a)

{

printf("%s\\n", a);

//a[3] = '4';

}

-----------------------------------------------------------------------------

自定義函數實現求字符串長度和字符串拷貝

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


-----------------------------------------------------------------------------

如果一個數組作為函數的參數,那麼數組的成員數量在函數內部是不可見的。

解決方法:在傳遞一個數組的時候,需要同時提供另外一個參數,標明這個數組有幾個成員變量。

例外:如果函數的參數是一個字符串時,那麼並不需要再傳遞一個參數說明這個字符串有多長。

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


=============================================================================

指針數組作為main函數的形參

先來看一個指針數組作為函數的參數(此時把指針數組解釋為二級指針)

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


輸出結果是:

32,8

hello

h

abc

a

world

w

haha

h

-----------------------------------------------------------------------------

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


args是命令行參數的字符串數組,argc代表命令行參數的數量,程序名字本身就算一個參數。

main函數是由系統調用的,所以main函數的參數功能是:得到命令行的參數。

-----------------------------------------------------------------------------

舉個小例子:用到main函數的參數,實現計算兩個數的和

例如:

程序名 數1 數2

一回車結果就出來了。

a 15 45

60

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


-----------------------------------------------------------------------------

課後作業

寫一個程序,需要用到main函數的參數

例如:

程序名 整數1 運算符 整數2,程序運行的結果是計算結果。

a 5 + 6注意:中間的加號是字符串。

11

a 5 * 6

30

......

+ - * /都要實現

linux下示例代碼如下:


C語言學習之基礎知識點—指針!上萬字的乾貨知識


C語言學習之基礎知識點—指針!上萬字的乾貨知識


C語言學習之基礎知識點—指針!上萬字的乾貨知識



分享到:


相關文章: