<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          熬夜整理的C/C++萬字總結(jié)(二)

          共 29320字,需瀏覽 59分鐘

           ·

          2021-08-24 04:54


          大家好,我是唐唐!

          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í)候要攜帶上書本、筆紙、零食……場面一片狼藉;方案二:兩位老師課間互換教室。

          顯然,方案二更好一些,方案二類似使用指針傳遞地址,方案一將內(nèi)存中的內(nèi)容重新“復(fù)制”了一份,效率比較低。

          • 在數(shù)據(jù)傳遞時(shí),如果數(shù)據(jù)塊較大,可以使用指針傳遞地址而不是實(shí)際數(shù)據(jù),即提高傳輸速度,又節(jié)省大量內(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ī)制比較簡單,其功能可以被集中重新實(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è)零值。為了測試一個(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ì)象或未申請?jiān)L問受限內(nèi)存區(qū)域的指針。與空指針不同,野指針無法通過簡單地判斷是否為 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):

          • 初始化時(shí)置 NULL

          指針變量一定要初始化為NULL,因?yàn)槿魏沃羔樧兞縿偙粍?chuàng)建時(shí)不會(huì)自動(dòng)成為NULL指針,它的缺省值是隨機(jī)的。

          • 釋放時(shí)置 NULL

          當(dāng)指針p指向的內(nèi)存空間釋放時(shí),沒有設(shè)置指針p的值為NULL。delete和free只是把內(nèi)存空間釋放了,但是并沒有將指針p的值賦為NULL。通常判斷一個(gè)指針是否合法,都是使用if語句測試該指針是否為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ù),在簡單的情況下是可以的,但是如果返回值有其它用途(例如返回函數(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è)形參)
          • 建立關(guān)系
          • 通過 * 操作指針指向的內(nèi)存
          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));

           //請問下面輸入結(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));

           //再請問下面輸入結(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-9A-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ù)

          • str:字符串首地址
          • format:字符串格式,用法和printf()一樣

          返回值

          • 成功:實(shí)際格式化的字符個(gè)數(shù)
          • 失敗:- 1
          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ù)

          • str:指定的字符串首地址
          • format:字符串格式,用法和scanf()一樣

          返回值

          • 成功:成功則返回參數(shù)數(shù)目,失敗則返回-1
          • 失敗:- 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ì)容易管理。

          參考資料

          1. C Primer Plus(第五版)中文版
          2. https://www.cnblogs.com/lulipro/p/7460206.html

          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美区一| 成人三级视频在线 | 免费在线看黄色 | 日本无码视频播放 | 亚洲免费一级 |