大家好,,我是唐唐,!
0.為什么使用指針 假如我們定義了 char a=’A’ ,當(dāng)需要使用 'A’ 時(shí),,除了直接調(diào)用變量 a ,還可以定義 char *p=&a ,,調(diào)用 a 的地址,,即指向 a 的指針 p ,變量 a( char 類型)只占了一個(gè)字節(jié),指針本身的大小由可尋址的字長來決定,,指針 p 占用 4 個(gè)字節(jié),。
但如果要引用的是占用內(nèi)存空間比較大東西,用指針也還是 4 個(gè)字節(jié)即可,。
使用指針型變量在很多時(shí)候占用更小的內(nèi)存空間,。變量為了表示數(shù)據(jù),指針可以更好的傳遞數(shù)據(jù),,舉個(gè)例子:
第一節(jié)課是 1 班語文,, 2 班數(shù)學(xué),第二節(jié)課顛倒過來, 1 班要上數(shù)學(xué),, 2 班要上語文,,那么第一節(jié)課下課后需要怎樣作調(diào)整呢?方案一:課間 1 班學(xué)生全都去 2 班,, 2 班學(xué)生全都來 1 班,,當(dāng)然,走的時(shí)候要攜帶上書本,、筆紙,、零食……場(chǎng)面一片狼藉;方案二:兩位老師課間互換教室,。
顯然,,方案二更好一些,方案二類似使用指針傳遞地址,,方案一將內(nèi)存中的內(nèi)容重新“復(fù)制”了一份,,效率比較低。
在數(shù)據(jù)傳遞時(shí),,如果數(shù)據(jù)塊較大,,可以使用指針傳遞地址而不是實(shí)際數(shù)據(jù),即提高傳輸速度,,又節(jié)省大量?jī)?nèi)存,。 一個(gè)數(shù)據(jù)緩沖區(qū) char buf[100] ,如果其中 buf[0,1] 為命令號(hào), buf[2,3] 為數(shù)據(jù)類型, buf[4~7] 為該類型的數(shù)值,,類型為 int ,,使用如下語句進(jìn)行賦值:
*(short*)&buf[0]=DataId; *(short*)&buf[2]=DataType; *(int*)&buf[4]=DataValue;
數(shù)據(jù)轉(zhuǎn)換,利用指針的靈活的類型轉(zhuǎn)換,,可以用來做數(shù)據(jù)類型轉(zhuǎn)換,,比較常用于通訊緩沖區(qū)的填充。 指針的機(jī)制比較簡(jiǎn)單,,其功能可以被集中重新實(shí)現(xiàn)成更抽象化的引用數(shù)據(jù)形式 函數(shù)指針,,形如: #define PMYFUN (void*)(int,int) ,可以用在大量分支處理的實(shí)例當(dāng)中,,如某通訊根據(jù)不同的命令號(hào)執(zhí)行不同類型的命令,,則可以建立一個(gè)函數(shù)指針數(shù)組,進(jìn)行散轉(zhuǎn),。 在數(shù)據(jù)結(jié)構(gòu)中,,鏈表、樹,、圖等大量的應(yīng)用都離不開指針,。 1. 指針強(qiáng)化 1.1 指針是一種數(shù)據(jù)類型操作系統(tǒng)將硬件和軟件結(jié)合起來,,給程序員提供的一種對(duì)內(nèi)存使用的抽象,這種抽象機(jī)制使得程序使用的是虛擬存儲(chǔ)器,而不是直接操作和使用真實(shí)存在的物理存儲(chǔ)器,。所有的虛擬地址形成的集合就是虛擬地址空間,。
內(nèi)存是一個(gè)很大的線性的字節(jié)數(shù)組,每個(gè)字節(jié)固定由 8 個(gè)二進(jìn)制位組成,,每個(gè)字節(jié)都有唯一的編號(hào),,如下圖,這是一個(gè) 4G 的內(nèi)存,,他一共有 4x1024x1024x1024 = 4294967296 個(gè)字節(jié),,那么它的地址范圍就是 0 ~ 4294967296 ,十六進(jìn)制表示就是 0x00000000~0xffffffff ,,當(dāng)程序使用的數(shù)據(jù)載入內(nèi)存時(shí),,都有自己唯一的一個(gè)編號(hào),這個(gè)編號(hào)就是這個(gè)數(shù)據(jù)的地址,。指針就是這樣形成的,。
1.1.1 指針變量
指針是一種數(shù)據(jù)類型,占用內(nèi)存空間,,用來保存內(nèi)存地址,。
void test01 (){ int* p1 = 0x1234; int*** p2 = 0x1111; printf ("p1 size:%d\n" ,sizeof(p1)); printf ("p2 size:%d\n" ,sizeof(p2)); //指針是變量,指針本身也占內(nèi)存空間,,指針也可以被賦值 int a = 10; p1 = &a; printf ("p1 address:%p\n" , &p1); printf ("p1 address:%p\n" , p1); printf ("a address:%p\n" , &a); }
1.1.2 野指針和空指針
1.1.2.1 空指針
標(biāo)準(zhǔn)定義了NULL指針,,它作為一個(gè)特殊的指針變量,表示不指向任何東西,。要使一個(gè)指針為NULL,可以給它賦值一個(gè)零值,。為了測(cè)試一個(gè)指針百年來那個(gè)是否為NULL,你可以將它與零值進(jìn)行比較。
對(duì)指針解引用操作可以獲得它所指向的值,。但從定義上看,,NULL指針并未指向任何東西,因?yàn)閷?duì)一個(gè)NULL指針因引用是一個(gè)非法的操作,,在解引用之前,,必須確保它不是一個(gè)NULL指針。
如果對(duì)一個(gè)NULL指針間接訪問會(huì)發(fā)生什么呢,?結(jié)果因編譯器而異。不允許向NULL和非法地址拷貝內(nèi)存:
void test (){ char *p = NULL; //給p指向的內(nèi)存區(qū)域拷貝內(nèi)容 strcpy(p, "1111" ); //err char *q = 0x1122; //給q指向的內(nèi)存區(qū)域拷貝內(nèi)容 strcpy(q, "2222" ); //err }
1.1.2.2 野指針
在使用指針時(shí),,要避免野指針的出現(xiàn):
野指針指向一個(gè)已刪除的對(duì)象或未申請(qǐng)?jiān)L問受限內(nèi)存區(qū)域的指針,。與空指針不同,野指針無法通過簡(jiǎn)單地判斷是否為 NULL避免,,而只能通過養(yǎng)成良好的編程習(xí)慣來盡力減少,。對(duì)野指針進(jìn)行操作很容易造成程序錯(cuò)誤,。
什么情況下會(huì)導(dǎo)致野指針?
任何指針變量剛被創(chuàng)建時(shí)不會(huì)自動(dòng)成為NULL指針,,它的缺省值是隨機(jī)的,,它會(huì)亂指一氣。所以,,指針變量在創(chuàng)建的同時(shí)應(yīng)當(dāng)被初始化,,要么將指針設(shè)置為NULL,要么讓它指向合法的內(nèi)存,。
有時(shí)指針在free或delete后未賦值 NULL,,便會(huì)使人以為是合法的。別看free和delete的名字(尤其是delete),,它們只是把指針?biāo)傅膬?nèi)存給釋放掉,,但并沒有把指針本身干掉。此時(shí)指針指向的就是“垃圾”內(nèi)存,。釋放后的指針應(yīng)立即將指針置為NULL,,防止產(chǎn)生“野指針”。
不要返回指向棧內(nèi)存的指針或引用,,因?yàn)闂?nèi)存在函數(shù)結(jié)束時(shí)會(huì)被釋放,。
void test (){ int* p = 0x001; //未初始化 printf ("%p\n" ,p); *p = 100; }
操作野指針是非常危險(xiǎn)的操作,應(yīng)該規(guī)避野指針的出現(xiàn):
指針變量一定要初始化為NULL,,因?yàn)槿魏沃羔樧兞縿偙粍?chuàng)建時(shí)不會(huì)自動(dòng)成為NULL指針,,它的缺省值是隨機(jī)的。
當(dāng)指針p指向的內(nèi)存空間釋放時(shí),,沒有設(shè)置指針p的值為NULL,。delete和free只是把內(nèi)存空間釋放了,但是并沒有將指針p的值賦為NULL,。通常判斷一個(gè)指針是否合法,,都是使用if語句測(cè)試該指針是否為NULL。
1.1.2.3 void*類型指針
void是一種特殊的指針類型,,可以用來存放任意對(duì)象的地址,。一個(gè)void指針存放著一個(gè)地址,這一點(diǎn)和其他指針類似,。不同的是,,我們對(duì)它到底儲(chǔ)存的是什么對(duì)象的地址并不了解。
double a=2.3; int b=5; void *p=&a; cout<<p<<endl; //輸出了a的地址 p=&b; cout<<p<<endl; //輸出了b的地址 //cout<<*p<<endl;這一行不可以執(zhí)行,,void*指針只可以儲(chǔ)存變量地址,,不可以直接操作它指向的對(duì)象
由于void是空類型,只保存了指針的值,,而丟失了類型信息,,我們不知道他指向的數(shù)據(jù)是什么類型的,,只指定這個(gè)數(shù)據(jù)在內(nèi)存中的起始地址,如果想要完整的提取指向的數(shù)據(jù),,程序員就必須對(duì)這個(gè)指針做出正確的類型轉(zhuǎn)換,,然后再解指針。
1.1.2.4 void*數(shù)組和指針
同類型指針變量可以相互賦值,,數(shù)組不行,,只能一個(gè)一個(gè)元素的賦值或拷貝 數(shù)組在內(nèi)存中是連續(xù)存放的,開辟一塊連續(xù)的內(nèi)存空間,。數(shù)組是根據(jù)數(shù)組的下進(jìn)行訪問的,。指針很靈活,它可以指向任意類型的數(shù)據(jù),。指針的類型說明了它所指向地址空間的內(nèi)存,。 數(shù)組所占存儲(chǔ)空間的內(nèi)存:sizeof(數(shù)組名) 數(shù)組的大小:sizeof(數(shù)組名)/sizeof(數(shù)據(jù)類型),,在32位平臺(tái)下,,無論指針的類型是什么,sizeof(指針名)都是 4 ,,在 64 位平臺(tái)下,,無論指針的類型是什么,sizeof(指針名)都是 8 ,。 數(shù)組名作為右值的時(shí)候,,就是第一個(gè)元素的地址 int main(void) { int arr[5] = {1,2,3,4,5}; int *p_first = arr; printf ("%d" ,*p_first); //1 return 0; }
指向數(shù)組元素的指針 支持 遞增 遞減 運(yùn)算。p= p+1意思是,,讓p指向原來指向的內(nèi)存塊的下一個(gè)相鄰的相同類型的內(nèi)存塊,。在數(shù)組中相鄰內(nèi)存就是相鄰下標(biāo)元素。 1.1.3 間接訪問操作符
通過一個(gè)指針訪問它所指向的地址的過程叫做間接訪問,,或者叫解引用指針,,這個(gè)用于執(zhí)行間接訪問的操作符是*。
注意 :對(duì)一個(gè)int類型指針解引用會(huì)產(chǎn)生一個(gè)整型值,,類似地,,對(duì)一個(gè)float 指針解引用會(huì)產(chǎn)生了一個(gè)float類型的值。
int arr[5]; int *p = * (&arr); int arr1[5][3] arr1 = int(*)[3]&arr1
1)在指針聲明時(shí),,* 號(hào)表示所聲明的變量為指針
2)在指針使用時(shí),,* 號(hào)表示操作指針?biāo)赶虻膬?nèi)存空間
*相當(dāng)通過地址(指針變量的值)找到指針指向的內(nèi)存,再操作內(nèi)存 *放在等號(hào)的左邊賦值(給內(nèi)存賦值,,寫內(nèi)存) *放在等號(hào)的右邊取值(從內(nèi)存中取值,,讀內(nèi)存) //解引用 void test01 (){ //定義指針 int* p = NULL; //指針指向誰,就把誰的地址賦給指針 int a = 10; p = &a; *p = 20;//*在左邊當(dāng)左值,,必須確保內(nèi)存可寫 //*號(hào)放右面,從內(nèi)存中讀值 int b = *p; //必須確保內(nèi)存可寫 char* str = "hello world!" ; *str = 'm' ; printf ("a:%d\n" , a); printf ("*p:%d\n" , *p); printf ("b:%d\n" , b); }
1.1.4 指針的步長
指針是一種數(shù)據(jù)類型,,是指它指向的內(nèi)存空間的數(shù)據(jù)類型。指針?biāo)赶虻膬?nèi)存空間決定了指針的步長,。指針的步長指的是,,當(dāng)指針+1時(shí)候,,移動(dòng)多少字節(jié)單位。
思考如下問題:
int a = 0xaabbccdd; unsigned int *p1 = &a; unsigned char *p2 = &a; //為什么*p1打印出來正確結(jié)果,?printf ("%x\n" , *p1); //為什么*p2沒有打印出來正確結(jié)果,?printf ("%x\n" , *p2); //為什么p1指針+1加了4字節(jié),?printf ("p1 =%d\n" , p1);printf ("p1+1=%d\n" , p1 + 1); //為什么p2指針+1加了1字節(jié)?printf ("p2 =%d\n" , p2);printf ("p2+1=%d\n" , p2 + 1);
1.1.5 函數(shù)與指針
1.1.5.1 函數(shù)的參數(shù)和指針
C語言中,,實(shí)參傳遞給形參,是按值傳遞的,,也就是說,,函數(shù)中的形參是實(shí)參的拷貝份,形參和實(shí)參只是在值上面一樣,,而不是同一個(gè)內(nèi)存數(shù)據(jù)對(duì)象,。這就意味著:這種數(shù)據(jù)傳遞是單向的,即從調(diào)用者傳遞給被調(diào)函數(shù),,而被調(diào)函數(shù)無法修改傳遞的參數(shù)達(dá)到回傳的效果。
void change(int a) { a++; //在函數(shù)中改變的只是這個(gè)函數(shù)的局部變量a,而隨著函數(shù)執(zhí)行結(jié)束,,a被銷毀。age還是原來的age,,紋絲不動(dòng)。 } int main(void) { int age = 60; change(age); printf ("age = %d" ,age); // age = 60 return 0; }
有時(shí)候我們可以使用函數(shù)的返回值來回傳數(shù)據(jù),,在簡(jiǎn)單的情況下是可以的,但是如果返回值有其它用途(例如返回函數(shù)的執(zhí)行狀態(tài)量),,或者要回傳的數(shù)據(jù)不止一個(gè),返回值就解決不了了,。
傳遞變量的指針可以輕松解決上述問題,。
void change(int* pa) { (*pa)++; //因?yàn)閭鬟f的是age的地址,,因此pa指向內(nèi)存數(shù)據(jù)age,。當(dāng)在函數(shù)中對(duì)指針pa解地址時(shí), //會(huì)直接去內(nèi)存中找到age這個(gè)數(shù)據(jù),,然后把它增1,。 } int main(void) { int age = 160; change(&age); printf ("age = %d" ,age); // age = 61 return 0; }
比如指針的一個(gè)常見的使用例子:
#include <stdio.h> #include <stdlib.h> #include <string.h> void swap(int *,int *); int main () { int a=5,b=10; printf ("a=%d,b=%d\n" ,a,b); swap(&a,&b); printf ("a=%d,b=%d\n" ,a,b); return 0; } void swap(int *pa,int *pb) { int t=*pa;*pa=*pb;*pb=t; }
在以上的例子中,swap函數(shù)的兩個(gè)形參pa和pb可以接收兩個(gè)整型變量的地址,,并通過間接訪問的方式修改了它指向變量的值,。在main函數(shù)中調(diào)用swap時(shí),,提供的實(shí)參分別為&a,&b,這樣就實(shí)現(xiàn)了pa=&a,pb=&b的賦值過程,,這樣在swap函數(shù)中就通過pa修改了 a 的值,通過 pb修改了 b 的值,。因此,如果需要在被調(diào)函數(shù)中修改主調(diào)函數(shù)中變量的值,,就需要經(jīng)過以下幾個(gè)步驟:
定義函數(shù)的形參必須為指針類型,以接收主調(diào)函數(shù)中傳來的變量的地址,; 調(diào)用函數(shù)時(shí)實(shí)參為變量的地址,; 在被調(diào)函數(shù)中使用*間接訪問形參指向的內(nèi)存空間,實(shí)現(xiàn)修改主調(diào)函數(shù)中變量值的功能,。 指針作為函數(shù)的形參的另一個(gè)典型應(yīng)用是當(dāng)函數(shù)有多個(gè)返回值的情形。比如,,需要在一個(gè)函數(shù)中統(tǒng)計(jì)一個(gè)數(shù)組的最大值、最小值和平均值,。當(dāng)然你可以編寫三個(gè)函數(shù)分別完成統(tǒng)計(jì)三個(gè)值的功能,。但比較啰嗦,,如:
int GetMax(int a[],int n) { int max=a[0],i; for (i=1;i<n;i++) { if (max<a[i]) max=a[i]; } return max; } int GetMin(int a[],int n) { int min=a[0],i; for (i=1;i<n;i++) { if (min>a[i]) min=a[i]; } return min; } double GetAvg(int a[],int n) { double avg=0; int i; for (i=0;i<n;i++) { avg+=a[i]; } return avg/n; }
其實(shí)我們完全可以在一個(gè)函數(shù)中完成這個(gè)功能,由于函數(shù)只能有一個(gè)返回值,,可以返回平均值,,最大值和最小值可以通過指針類型的形參來進(jìn)行實(shí)現(xiàn):
double Stat(int a[],int n,int *pmax,int *pmin) { double avg=a[0]; int i; *pmax=*pmin=a[0]; for (i=1;i<n;i++) { avg+=a[i]; if (*pmax<a[i]) *pmax=a[i]; if (*pmin>a[i]) *pmin=a[i]; } return avg/n; }
1.1.5.2 函數(shù)的指針
一個(gè)函數(shù)總是占用一段連續(xù)的內(nèi)存區(qū)域,,函數(shù)名在表達(dá)式中有時(shí)也會(huì)被轉(zhuǎn)換為該函數(shù)所在內(nèi)存區(qū)域的首地址。我們可以把函數(shù)的這個(gè)首地址賦予一個(gè)指針變量,,使指針變量指向函數(shù)所在的內(nèi)存區(qū)域,然后通過指針變量就可以找到并調(diào)用該函數(shù),。這種指針就是函數(shù)指針。
函數(shù)指針的定義形式為:
returnType (*pointerName)(param list);
returnType 為函數(shù)返回值類型,pointerNmae 為指針名稱,,param list 為函數(shù)參數(shù)列表,。參數(shù)列表中可以同時(shí)給出參數(shù)的類型和名稱,,也可以只給出參數(shù)的類型,省略參數(shù)的名稱,,這一點(diǎn)和函數(shù)原型非常類似。
用指針來實(shí)現(xiàn)對(duì)函數(shù)的調(diào)用:
#include <stdio.h> //返回兩個(gè)數(shù)中較大的一個(gè) int max(int a, int b) { return a>b ? a : b; } int main () { int x, y, maxval; //定義函數(shù)指針 int (*pmax)(int, int) = max; //也可以寫作int (*pmax)(int a, int b) printf ("Input two numbers:" ); scanf("%d %d" , &x, &y); maxval = (*pmax)(x, y); printf ("Max value: %d\n" , maxval); return 0; }
1.1.5.3 結(jié)構(gòu)體和指針
結(jié)構(gòu)體指針有特殊的語法:-> 符號(hào)
如果p是一個(gè)結(jié)構(gòu)體指針,則可以使用 p ->【成員】 的方法訪問結(jié)構(gòu)體的成員
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 " ,ps->name,ps->age); return 0; }
1.2 指針的意義_間接賦值1.2.1 間接賦值的三大條件
通過指針間接賦值成立的三大條件:
2個(gè)變量(一個(gè)普通變量一個(gè)指針變量、或者一個(gè)實(shí)參一個(gè)形參) void test (){ int a = 100; //兩個(gè)變量 int *p = NULL; //建立關(guān)系 //指針指向誰,,就把誰的地址賦值給指針 p = &a; //通過*操作內(nèi)存 *p = 22; }
1.2.2 如何定義合適的指針變量
void test (){ int b; int *q = &b; //0級(jí)指針 int **t = &q; int ***m = &t; }
1.2.3 間接賦值:從0級(jí)指針到1級(jí)指針
int func1 (){ return 10; } void func2(int a){ a = 100; } //指針的意義_間接賦值 void test02 (){ int a = 0; a = func1(); printf ("a = %d\n" , a); //為什么沒有修改,? func2(a); printf ("a = %d\n" , a); } //指針的間接賦值 void func3(int* a){ *a = 100; } void test03 (){ int a = 0; a = func1(); printf ("a = %d\n" , a); //修改 func3(&a); printf ("a = %d\n" , a); }
1.2.4 間接賦值:從1級(jí)指針到2級(jí)指針
void AllocateSpace(char** p){ *p = (char*)malloc(100); strcpy(*p, "hello world!" ); } void FreeSpace(char** p){ if (p == NULL){ return ; } if (*p != NULL){ free(*p); *p = NULL; } } void test (){ char* p = NULL; AllocateSpace(&p); printf ("%s\n" ,p); FreeSpace(&p); if (p == NULL){ printf ("p內(nèi)存釋放!\n" ); } }
1.2.4 間接賦值的推論
用1級(jí)指針形參,去間接修改了0級(jí)指針(實(shí)參)的值,。 用2級(jí)指針形參,,去間接修改了1級(jí)指針(實(shí)參)的值。 用3級(jí)指針形參,,去間接修改了2級(jí)指針(實(shí)參)的值,。 用n級(jí)指針形參,去間接修改了n-1級(jí)指針(實(shí)參)的值,。 1.3 指針做函數(shù)參數(shù)指針做函數(shù)參數(shù),,具備輸入和輸出特性:
輸入:主調(diào)函數(shù)分配內(nèi)存 輸出:被調(diào)用函數(shù)分配內(nèi)存 1.3.1 輸入特性
void fun(char *p /* in */) { //給p指向的內(nèi)存區(qū)域拷貝內(nèi)容 strcpy(p, "abcddsgsd" ); } void test (void) { //輸入,主調(diào)函數(shù)分配內(nèi)存 char buf[100] = { 0 }; fun(buf); printf ("buf = %s\n" , buf); }
1.3.2 輸出特性
void fun(char **p /* out */, int *len) { char *tmp = (char *)malloc(100); if (tmp == NULL) { return ; } strcpy(tmp, "adlsgjldsk" ); //間接賦值 *p = tmp; *len = strlen(tmp); } void test (void) { //輸出,,被調(diào)用函數(shù)分配內(nèi)存,,地址傳遞 char *p = NULL; int len = 0; fun(&p, &len); if (p != NULL) { printf ("p = %s, len = %d\n" , p, len); } }
1.4 字符串指針強(qiáng)化 1.4.1 字符串指針做函數(shù)參數(shù)
1.4.1.1 字符串基本操作
//字符串基本操作 //字符串是以0或者'\0' 結(jié)尾的字符數(shù)組,(數(shù)字0和字符'\0' 等價(jià)) void test01 (){ //字符數(shù)組只能初始化5個(gè)字符,,當(dāng)輸出的時(shí)候,,從開始位置直到找到0結(jié)束 char str1[] = { 'h' , 'e' , 'l' , 'l' , 'o' }; printf ("%s\n" ,str1); //字符數(shù)組部分初始化,剩余填0 char str2[100] = { 'h' , 'e' , 'l' , 'l' , 'o' }; printf ("%s\n" , str2); //如果以字符串初始化,,那么編譯器默認(rèn)會(huì)在字符串尾部添加'\0' char str3[] = "hello" ; printf ("%s\n" ,str3); printf ("sizeof str:%d\n" ,sizeof(str3)); printf ("strlen str:%d\n" ,strlen(str3)); //sizeof計(jì)算數(shù)組大小,,數(shù)組包含'\0' 字符 //strlen計(jì)算字符串的長度,到'\0' 結(jié)束 //那么如果我這么寫,結(jié)果是多少呢,? char str4[100] = "hello" ; printf ("sizeof str:%d\n" , sizeof(str4)); printf ("strlen str:%d\n" , strlen(str4)); //請(qǐng)問下面輸入結(jié)果是多少,?sizeof結(jié)果是多少?strlen結(jié)果是多少,? char str5[] = "hello\0world" ; printf ("%s\n" ,str5); printf ("sizeof str5:%d\n" ,sizeof(str5)); printf ("strlen str5:%d\n" ,strlen(str5)); //再請(qǐng)問下面輸入結(jié)果是多少,?sizeof結(jié)果是多少?strlen結(jié)果是多少,? char str6[] = "hello\012world" ; printf ("%s\n" , str6); printf ("sizeof str6:%d\n" , sizeof(str6)); printf ("strlen str6:%d\n" , strlen(str6)); }
八進(jìn)制和十六進(jìn)制轉(zhuǎn)義字符:
在C中有兩種特殊的字符,,八進(jìn)制轉(zhuǎn)義字符和十六進(jìn)制轉(zhuǎn)義字符,八進(jìn)制字符的一般形式是'\ddd'
,,d是0-7
的數(shù)字,。十六進(jìn)制字符的一般形式是'\xhh'
,h是0-9
或A-F
內(nèi)的一個(gè),。八進(jìn)制字符和十六進(jìn)制字符表示的是字符的ASCII碼對(duì)應(yīng)的數(shù)值,。
比如 :
'\063'
表示的是字符'3',,因?yàn)?3'的ASCII碼是30(十六進(jìn)制),48(十進(jìn)制),,63(八進(jìn)制),。'\x41
'表示的是字符'A',因?yàn)?A'的ASCII碼是41(十六進(jìn)制),,65(十進(jìn)制),,101(八進(jìn)制)。1.4.1.2 字符串拷貝功能實(shí)現(xiàn)
//拷貝方法1 void copy_string01(char* dest, char* source ){ for (int i = 0; source [i] != '\0' ;i++){ dest[i] = source [i]; } } //拷貝方法2 void copy_string02(char* dest, char* source ){ while (*source != '\0' /* *source != 0 */){ *dest = *source ; source ++; dest++; } } //拷貝方法3 void copy_string03(char* dest, char* source ){ //判斷*dest是否為0,,0則退出循環(huán) while (*dest++ = *source ++){} }
1.4.1.3 字符串反轉(zhuǎn)模型
void reverse_string(char* str){ if (str == NULL){ return ; } int begin = 0; int end = strlen(str) - 1; while (begin < end){ //交換兩個(gè)字符元素 char temp = str[begin]; str[begin] = str[end]; str[end] = temp; begin++; end--; } } void test (){ char str[] = "abcdefghijklmn" ; printf ("str:%s\n" , str); reverse_string(str); printf ("str:%s\n" , str); }
1.4.2字符串的格式化
1.4.2.1 sprintf
#include <stdio.h> int sprintf(char *str, const char *format, ...);
功能 :根據(jù)參數(shù)format字符串來轉(zhuǎn)換并格式化數(shù)據(jù),,然后將結(jié)果輸出到str指定的空間中,直到 出現(xiàn)字符串結(jié)束符 '\0' 為止,。
參數(shù) :
format:字符串格式,,用法和printf()一樣 返回值 :
成功:實(shí)際格式化的字符個(gè)數(shù) void test (){ //1. 格式化字符串 char buf[1024] = { 0 }; sprintf(buf, "你好,%s,歡迎加入我們!" , "John" ); printf ("buf:%s\n" ,buf); memset(buf, 0, 1024); sprintf(buf, "我今年%d歲了!" , 20); printf ("buf:%s\n" , buf); //2. 拼接字符串 memset(buf, 0, 1024); char str1[] = "hello" ; char str2[] = "world" ; int len = sprintf(buf,"%s %s" ,str1,str2); printf ("buf:%s len:%d\n" , buf,len); //3. 數(shù)字轉(zhuǎn)字符串 memset(buf, 0, 1024); int num = 100; sprintf(buf, "%d" , num); printf ("buf:%s\n" , buf); //設(shè)置寬度 右對(duì)齊 memset(buf, 0, 1024); sprintf(buf, "%8d" , num); printf ("buf:%s\n" , buf); //設(shè)置寬度 左對(duì)齊 memset(buf, 0, 1024); sprintf(buf, "%-8d" , num); printf ("buf:%s\n" , buf); //轉(zhuǎn)成16進(jìn)制字符串 小寫 memset(buf, 0, 1024); sprintf(buf, "0x%x" , num); printf ("buf:%s\n" , buf); //轉(zhuǎn)成8進(jìn)制字符串 memset(buf, 0, 1024); sprintf(buf, "0%o" , num); printf ("buf:%s\n" , buf); }
1.4.2.2 sscanf
#include <stdio.h> int sscanf(const char *str, const char *format, ...);
功能 :從str指定的字符串讀取數(shù)據(jù),,并根據(jù)參數(shù)format字符串來轉(zhuǎn)換并格式化數(shù)據(jù),。
參數(shù) :
format:字符串格式,用法和scanf()一樣 返回值 :
成功:成功則返回參數(shù)數(shù)目,,失敗則返回-1 //1. 跳過數(shù)據(jù) void test01 (){ char buf[1024] = { 0 }; //跳過前面的數(shù)字 //匹配第一個(gè)字符是否是數(shù)字,如果是,,則跳過 //如果不是則停止匹配 sscanf("123456aaaa" , "%*d%s" , buf); printf ("buf:%s\n" ,buf); } //2. 讀取指定寬度數(shù)據(jù) void test02 (){ char buf[1024] = { 0 }; //跳過前面的數(shù)字 sscanf("123456aaaa" , "%7s" , buf); printf ("buf:%s\n" , buf); } //3. 匹配a-z中任意字符 void test03 (){ char buf[1024] = { 0 }; //跳過前面的數(shù)字 //先匹配第一個(gè)字符,,判斷字符是否是a-z中的字符,如果是匹配 //如果不是停止匹配 sscanf("abcdefg123456" , "%[a-z]" , buf); printf ("buf:%s\n" , buf); } //4. 匹配aBc中的任何一個(gè) void test04 (){ char buf[1024] = { 0 }; //跳過前面的數(shù)字 //先匹配第一個(gè)字符是否是aBc中的一個(gè),,如果是,,則匹配,如果不是則停止匹配 sscanf("abcdefg123456" , "%[aBc]" , buf); printf ("buf:%s\n" , buf); } //5. 匹配非a的任意字符 void test05 (){ char buf[1024] = { 0 }; //跳過前面的數(shù)字 //先匹配第一個(gè)字符是否是aBc中的一個(gè),,如果是,,則匹配,,如果不是則停止匹配 sscanf("bcdefag123456" , "%[^a]" , buf); printf ("buf:%s\n" , buf); } //6. 匹配非a-z中的任意字符 void test06 (){ char buf[1024] = { 0 }; //跳過前面的數(shù)字 //先匹配第一個(gè)字符是否是aBc中的一個(gè),,如果是,則匹配,,如果不是則停止匹配 sscanf("123456ABCDbcdefag" , "%[^a-z]" , buf); printf ("buf:%s\n" , buf); }
1.5 一級(jí)指針易錯(cuò)點(diǎn)1.5.1 越界
void test (){ char buf[3] = "abc" ; printf ("buf:%s\n" ,buf); }
1.5.2 指針疊加會(huì)不斷改變指針指向
void test (){ char *p = (char *)malloc(50); char buf[] = "abcdef" ; int n = strlen(buf); int i = 0; for (i = 0; i < n; i++) { *p = buf[i]; p++; //修改原指針指向 } free(p); }
1.5.3 返回局部變量地址
char *get_str () { char str[] = "abcdedsgads" ; //棧區(qū),, printf ("[get_str]str = %s\n" , str); return str; }
1.5.4 同一塊內(nèi)存釋放多次(不可以釋放野指針)
void test (){ char *p = NULL; p = (char *)malloc(50); strcpy(p, "abcdef" ); if (p != NULL) { //free()函數(shù)的功能只是告訴系統(tǒng) p 指向的內(nèi)存可以回收了 // 就是說,p 指向的內(nèi)存使用權(quán)交還給系統(tǒng) //但是,,p的值還是原來的值(野指針),,p還是指向原來的內(nèi)存 free(p); } if (p != NULL) { free(p); } }
1.6 const使用//const修飾變量 void test01 (){ //1. const基本概念 const int i = 0; //i = 100; //錯(cuò)誤,只讀變量初始化之后不能修改 //2. 定義const變量最好初始化 const int j; //j = 100; //錯(cuò)誤,,不能再次賦值 //3. c語言的const是一個(gè)只讀變量,,并不是一個(gè)常量,,可通過指針間接修改 const int k = 10; //k = 100; //錯(cuò)誤,不可直接修改,,我們可通過指針間接修改 printf ("k:%d\n" , k); int* p = &k; *p = 100; printf ("k:%d\n" , k); } //const 修飾指針 void test02 (){ int a = 10; int b = 20; //const放在*號(hào)左側(cè) 修飾p_a指針指向的內(nèi)存空間不能修改,但可修改指針的指向 const int* p_a = &a; //*p_a = 100; //不可修改指針指向的內(nèi)存空間 p_a = &b; //可修改指針的指向 //const放在*號(hào)的右側(cè),, 修飾指針的指向不能修改,但是可修改指針指向的內(nèi)存空間 int* const p_b = &a; //p_b = &b; //不可修改指針的指向 *p_b = 100; //可修改指針指向的內(nèi)存空間 //指針的指向和指針指向的內(nèi)存空間都不能修改 const int* const p_c = &a; } //const指針用法 struct Person{ char name[64]; int id; int age; int score; }; //每次都對(duì)對(duì)象進(jìn)行拷貝,,效率低,,應(yīng)該用指針 void printPersonByValue(struct Person person){ printf ("Name:%s\n" , person.name); printf ("Name:%d\n" , person.id); printf ("Name:%d\n" , person.age); printf ("Name:%d\n" , person.score); } //但是用指針會(huì)有副作用,可能會(huì)不小心修改原數(shù)據(jù) void printPersonByPointer(const struct Person *person){ printf ("Name:%s\n" , person->name); printf ("Name:%d\n" , person->id); printf ("Name:%d\n" , person->age); printf ("Name:%d\n" , person->score); } void test03 (){ struct Person p = { "Obama" , 1101, 23, 87 }; //printPersonByValue(p); printPersonByPointer(&p); }
2. 指針的指針(二級(jí)指針) 2.1 二級(jí)指針基本概念這里讓我們花點(diǎn)時(shí)間來看一個(gè)例子,,揭開這個(gè)即將開始的序幕,。考慮下面這些聲明:
int a = 12; int *b = &a;
它們?nèi)缦聢D進(jìn)行內(nèi)存分配:
假定我們又有了第3個(gè)變量,,名叫c,并用下面這條語句對(duì)它進(jìn)行初始化:
c = &b;
它在內(nèi)存中的大概模樣大致如下:
c的類型是什么,?顯然它是一個(gè)指針,但它所指向的是什么,?
變量b是一個(gè)“指向整型的指針”,,所以任何指向b的類型必須是指向“指向整型的指針”的指針,更通俗地說,,是一個(gè)指針的指針,。
它合法嗎?
是的,!指針變量和其他變量一樣,,占據(jù)內(nèi)存中某個(gè)特定的位置,所以用&操作符取得它的地址是合法的,。
那么這個(gè)變量的聲明是怎樣的聲明的呢,?
int **c = &b;
那么這個(gè)**c如何理解呢?操作符具有從右想做的結(jié)合性,,所以這個(gè)表達(dá)式相當(dāng)于 (*c),我們從里向外逐層求職,。*c訪問c所指向的位置,我們知道這是變量b.第二個(gè)間接訪問操作符訪問這個(gè)位置所指向的地址,,也就是變量a.指針的指針并不難懂,,只需要留心所有的箭頭,如果表達(dá)式中出現(xiàn)了間接訪問操作符,,你就要隨箭頭訪問它所指向的位置,。
2.2 二級(jí)指針做形參輸出特性
二級(jí)指針做參數(shù)的輸出特性是指由被調(diào)函數(shù)分配內(nèi)存。
//被調(diào)函數(shù),由參數(shù)n確定分配多少個(gè)元素內(nèi)存 void allocate_space(int **arr,int n){ //堆上分配n個(gè)int類型元素內(nèi)存 int *temp = (int *)malloc(sizeof(int)* n); if (NULL == temp){ return ; } //給內(nèi)存初始化值 int *pTemp = temp; for (int i = 0; i < n;i ++){ //temp[i] = i + 100; *pTemp = i + 100; pTemp++; } //指針間接賦值 *arr = temp; } //打印數(shù)組 void print_array(int *arr,int n){ for (int i = 0; i < n;i ++){ printf ("%d " ,arr[i]); } printf ("\n" ); } //二級(jí)指針輸出特性(由被調(diào)函數(shù)分配內(nèi)存) void test (){ int *arr = NULL; int n = 10; //給arr指針間接賦值 allocate_space(&arr,n); //輸出arr指向數(shù)組的內(nèi)存 print_array(arr, n); //釋放arr所指向內(nèi)存空間的值 if (arr != NULL){ free(arr); arr = NULL; } }
2.3 二級(jí)指針做形參輸入特性二級(jí)指針做形參輸入特性是指由主調(diào)函數(shù)分配內(nèi)存,。
//打印數(shù)組 void print_array(int **arr,int n){ for (int i = 0; i < n;i ++){ printf ("%d " ,*(arr[i])); } printf ("\n" ); } //二級(jí)指針輸入特性(由主調(diào)函數(shù)分配內(nèi)存) void test (){ int a1 = 10; int a2 = 20; int a3 = 30; int a4 = 40; int a5 = 50; int n = 5; int** arr = (int **)malloc(sizeof(int *) * n); arr[0] = &a1; arr[1] = &a2; arr[2] = &a3; arr[3] = &a4; arr[4] = &a5; print_array(arr,n); free(arr); arr = NULL; }
2.4 強(qiáng)化訓(xùn)練_畫出內(nèi)存模型圖void mian () { //棧區(qū)指針數(shù)組 char *p1[] = { "aaaaa" , "bbbbb" , "ccccc" }; //堆區(qū)指針數(shù)組 char **p3 = (char **)malloc(3 * sizeof(char *)); //char *array[3]; int i = 0; for (i = 0; i < 3; i++) { p3[i] = (char *)malloc(10 * sizeof(char)); //char buf[10] sprintf(p3[i], "%d%d%d" , i, i, i); } }
2.4 多級(jí)指針將堆區(qū)數(shù)組指針案例改為三級(jí)指針案例:
//分配內(nèi)存 void allocate_memory(char*** p, int n){ if (n < 0){ return ; } char** temp = (char**)malloc(sizeof(char*)* n); if (temp == NULL){ return ; } //分別給每一個(gè)指針malloc分配內(nèi)存 for (int i = 0; i < n; i++){ temp[i] = malloc(sizeof(char)* 30); sprintf(temp[i], "%2d_hello world!" , i + 1); } *p = temp; } //打印數(shù)組 void array_print(char** arr, int len){ for (int i = 0; i < len; i++){ printf ("%s\n" , arr[i]); } printf ("----------------------\n" ); } //釋放內(nèi)存 void free_memory(char*** buf, int len){ if (buf == NULL){ return ; } char** temp = *buf; for (int i = 0; i < len; i++){ free(temp[i]); temp[i] = NULL; } free(temp); } void test (){ int n = 10; char** p = NULL; allocate_memory(&p, n); //打印數(shù)組 array_print(p, n); //釋放內(nèi)存 free_memory(&p, n); }
2.5 深拷貝和淺拷貝如果2個(gè)程序單元(例如2個(gè)函數(shù))是通過拷貝 他們所共享的數(shù)據(jù)的 指針來工作的,,這就是淺拷貝,因?yàn)檎嬲L問的數(shù)據(jù)并沒有被拷貝,。如果被訪問的數(shù)據(jù)被拷貝了,,在每個(gè)單元中都有自己的一份,,對(duì)目標(biāo)數(shù)據(jù)的操作相互 不受影響,則叫做深拷貝,。
#include <iostream> using namespace std; class CopyDemo { public: CopyDemo(int pa,char *cstr) //構(gòu)造函數(shù),,兩個(gè)參數(shù) { this->a = pa; this->str = new char[1024]; //指針數(shù)組,動(dòng)態(tài)的用new在堆上分配存儲(chǔ)空間 strcpy(this->str,cstr); //拷貝過來 } //沒寫,,C++會(huì)自動(dòng)幫忙寫一個(gè)復(fù)制構(gòu)造函數(shù),,淺拷貝只復(fù)制指針,如下注釋部分 //CopyDemo(CopyDemo& obj) //{ // this->a = obj.a; // this->str = obj.str; //這里是淺復(fù)制會(huì)出問題,要深復(fù)制 //} CopyDemo(CopyDemo& obj) //一般數(shù)據(jù)成員有指針要自己寫復(fù)制構(gòu)造函數(shù),,如下 { this->a = obj.a; // this->str = obj.str; //這里是淺復(fù)制會(huì)出問題,,要深復(fù)制 this->str = new char[1024];//應(yīng)該這樣寫 if (str != 0) strcpy(this->str,obj.str); //如果成功,把內(nèi)容復(fù)制過來 } ~CopyDemo() //析構(gòu)函數(shù) { delete str; } public: int a; //定義一個(gè)整型的數(shù)據(jù)成員 char *str; //字符串指針 }; int main () { CopyDemo A(100,"hello!!!" ); CopyDemo B = A; //復(fù)制構(gòu)造函數(shù),,把A的10和hello!!!復(fù)制給B cout <<"A:" << A.a << "," <<A.str << endl; //輸出A:100,,hello!!! cout <<"B:" << B.a << "," <<B.str << endl; //輸出B:100,hello!!! //修改后,發(fā)現(xiàn)A,B都被改變,,原因就是淺復(fù)制,,A,B指針指向同一地方,修改后都改變 B.a = 80; B.str[0] = 'k' ; cout <<"A:" << A.a << "," <<A.str << endl; //輸出A:100,,kello!!! cout <<"B:" << B.a << "," <<B.str << endl; //輸出B:80,,kello!!! return 0; }
根據(jù)上面實(shí)例可以看到,淺復(fù)制僅復(fù)制對(duì)象本身(其中包括是指針的成員),,這樣不同被復(fù)制對(duì)象的成員中的對(duì)應(yīng)非空指針會(huì)指向同一對(duì)象,,被成員指針引用的對(duì)象成為共享的,無法直接通過指針成員安全地刪除(因?yàn)槿糁苯觿h除,,另外對(duì)象中的指針就會(huì)無效,,形成所謂的野指針,而訪問無效指針是危險(xiǎn)的,;
除非這些指針有引用計(jì)數(shù)或者其它手段確保被指對(duì)象的所有權(quán)),;而深復(fù)制在淺復(fù)制的基礎(chǔ)上,連同指針指向的對(duì)象也一起復(fù)制,,代價(jià)比較高,,但是相對(duì)容易管理。
參考資料 https://www.cnblogs.com/lulipro/p/7460206.html