<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語言指針重難點詳解

          共 15223字,需瀏覽 31分鐘

           ·

          2022-11-21 10:20


          1為什么使用指針

          假如我們定義了 char a=’A’ ,當需要使用 ‘A’ 時,除了直接調(diào)用變量 a ,還可以定義 char *p=&a ,調(diào)用 a 的地址,即指向 a 的指針 p ,變量 achar 類型)只占了一個字節(jié),指針本身的大小由可尋址的字長來決定,指針 p 占用 4 個字節(jié)。

          但如果要引用的是占用內(nèi)存空間比較大東西,用指針也還是 4 個字節(jié)即可。

          • 使用指針型變量在很多時候占用更小的內(nèi)存空間。

          變量為了表示數(shù)據(jù),指針可以更好的傳遞數(shù)據(jù),舉個例子:

          第一節(jié)課是 1 班語文, 2 班數(shù)學,第二節(jié)課顛倒過來, 1 班要上數(shù)學, 2 班要上語文,那么第一節(jié)課下課后需要怎樣作調(diào)整呢?方案一:課間 1 班學生全都去 2 班, 2 班學生全都來 1 班,當然,走的時候要攜帶上書本、筆紙、零食……場面一片狼藉;方案二:兩位老師課間互換教室。

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

          • 在數(shù)據(jù)傳遞時,如果數(shù)據(jù)塊較大,可以使用指針傳遞地址而不是實際數(shù)據(jù),即提高傳輸速度,又節(jié)省大量內(nèi)存。

          一個數(shù)據(jù)緩沖區(qū) char buf[100] ,如果其中 buf[0,1] 為命令號, buf[2,3] 為數(shù)據(jù)類型, buf[4~7] 為該類型的數(shù)值,類型為 int ,使用如下語句進行賦值:

          *(short*)&buf[0]=DataId;
          *(short*)&buf[2]=DataType;
          *(int*)&buf[4]=DataValue;
          • 數(shù)據(jù)轉(zhuǎn)換,利用指針的靈活的類型轉(zhuǎn)換,可以用來做數(shù)據(jù)類型轉(zhuǎn)換,比較常用于通訊緩沖區(qū)的填充。

          • 指針的機制比較簡單,其功能可以被集中重新實現(xiàn)成更抽象化的引用數(shù)據(jù)形式

          • 函數(shù)指針,形如: #define PMYFUN (void*)(int,int) ,可以用在大量分支處理的實例當中,如某通訊根據(jù)不同的命令號執(zhí)行不同類型的命令,則可以建立一個函數(shù)指針數(shù)組,進行散轉(zhuǎn)。

          • 在數(shù)據(jù)結(jié)構(gòu)中,鏈表、樹、圖等大量的應(yīng)用都離不開指針。

          2 指針是什么?

          操作系統(tǒng)將硬件和軟件結(jié)合起來,給程序員提供的一種對內(nèi)存使用的抽象,這種抽象機制使得程序使用的是虛擬存儲器,而不是直接操作和使用真實存在的物理存儲器。所有的虛擬地址形成的集合就是虛擬地址空間。

          內(nèi)存是一個很大的線性的字節(jié)數(shù)組,每個字節(jié)固定由 8 個二進制位組成,每個字節(jié)都有唯一的編號,如下圖,這是一個 4G 的內(nèi)存,他一共有 4x1024x1024x1024 = 4294967296 個字節(jié),那么它的地址范圍就是 0 ~ 4294967296 ,十六進制表示就是 0x00000000~0xffffffff ,當程序使用的數(shù)據(jù)載入內(nèi)存時,都有自己唯一的一個編號,這個編號就是這個數(shù)據(jù)的地址。指針就是這樣形成的。

          1
          #include <stdio.h>

          int main(void)
          {
              char ch = 'a';
              int  num = 97;
              printf("ch 的地址:%p\n",&ch);   
              //ch 的地址:00BEFDF7
              printf("num的地址:%p\n",&num);  
              //num的地址:00BEFDF8
              return 0;
          }

          指針不僅可以表示變量的地址,還可以存儲各種類型數(shù)據(jù)的地址,指針變量是用來保存這些地址的變量,與數(shù)組類似,依據(jù)地址存放的數(shù)據(jù)類型,指針也分為 int 指針類型,  double 指針類型, char 指針類型等等。

          綜上,指針的實質(zhì)就是數(shù)據(jù)在內(nèi)存中的地址,而指針變量是用來保存這些地址的變量。

          3 指針變量 和 指向關(guān)系

          用來保存 指針 的變量,就是指針變量。如果指針變量p保存了變量 num的地址,則就說:p指向了變量num,也可以說p指向了num所在的內(nèi)存塊,指針變量pp指向了p所在的內(nèi)存塊,以下面為例:

          #include <stdio.h>

          int main(void)
          {
            int num = 97;
            char ch = 'a';

            int *p = & num;
            int **pp = &p;
            char *p1 = & ch;

            printf("num 的地址:%p\n",&num);   
            printf("指針p的值:%p\n",p);   
            printf("指針p的地址:%p\n",&p);  
            printf("指針pp的值:%p\n",pp); 
            printf("ch 的地址:%p\n",&ch);  

            return 0;
          }

          • int型的num值為97占4個字節(jié),內(nèi)存地址為:0113F924,char 型的ch('a')值為97占1個字節(jié),內(nèi)存地址為:0113F91B。
          int型占4個字節(jié)

          char型占1個字節(jié)

          • num的地址為:0113F924,num的值為 97 ,指針 p 指向 num 的內(nèi)存塊,指針 p 地址為:0113F90Cp的內(nèi)存保存的值就是num的地址0113F924。
          0x0113F90C存儲的內(nèi)容為地址0113F924
          • 指針變量 pp 指向 指針 p,指針 pp 內(nèi)存值為 指針 p 的地址:0113F90C,形成了只想指針的指針。
          指針pp為指向指針p的指針

          定義指針變量

          C語言中,定義變量時,在變量名 前 寫一個 * 星號,這個變量就變成了對應(yīng)變量類型的指針變量。必要時要加( ) 來避免優(yōu)先級的問題。

          引申:C語言中,定義變量時,在定義的最前面寫上typedef ,那么這個變量名就成了一種類型,即這個類型的同義詞。

          int a ; //int類型變量 a
          int *a ; //int* 變量a
          int arr[3]; //arr是包含3個int元素的數(shù)組
          int (* arr )[3]; //arr是一個指向包含3個int元素的數(shù)組的指針變量

          int* p_int; //指向int類型變量的指針 
          double* p_double; //指向idouble類型變量的指針 
          struct Student *p_struct; //結(jié)構(gòu)體類型的指針
          int(*p_func)(int,int); //指向返回類型為int,有2個int形參的函數(shù)的指針 
          int(*p_arr)[3]; //指向含有3個int元素的數(shù)組的指針 
          int** p_pointer; //指向 一個整形變量指針的指針

          取地址

          既然有了指針變量,那就得讓他保存其它變量的地址,使用& 運算符取得一個變量的地址。

          int add(int a , int b)
          {
              return a + b;
          }

          int main(void)
          {
              int num = 97;
              float score = 10.00F;
              int arr[3] = {1,2,3};

              int* p_num = &num;
              float* p_score = &score;
              int (*p_arr)[3] = &arr;           
              int (*fp_add)(int ,int )  = add;  //p_add是指向函數(shù)add的函數(shù)指針
              return 0;
          }

          特殊的情況,他們并不一定需要使用&取地址

          • 數(shù)組名的值就是這個數(shù)組的第一個元素的地址。
          • 函數(shù)名的值就是這個函數(shù)的地址。
          • 字符串字面值常量作為右值時,就是這個字符串對應(yīng)的字符數(shù)組的名稱,也就是這個字符串在內(nèi)存中的地址。
          int add(int a , int b){
              return a + b;
          }
          int main(void)
          {
              int arr[3] = {1,2,3};
              int* p_first = arr;
              int (*fp_add)(int ,int )  =  add;
              const char* msg = "Hello world";
              return 0;
          }

          解地址

          對一個指針解地址,就可以取到這個內(nèi)存數(shù)據(jù),解地址 的寫法,就是在指針的前面加一個 * 號。

          解指針的實質(zhì)是:從指針指向的內(nèi)存塊中取出這個內(nèi)存數(shù)據(jù)。

          int main(void)
          {
              int age = 19;
              int*p_age = &age;
              *p_age  = 20;  //通過指針修改指向的內(nèi)存數(shù)據(jù)

              printf("age = %d",*p_age);   //通過指針讀取指向的內(nèi)存數(shù)據(jù)
              printf("age = %d",age);

              return 0;
          }

          空指針

          空指針在概念上不同于未初始化的指針??罩羔樋梢源_保不指向任何對象或函數(shù);而未初始化的指針則可能指向任何地方??罩羔槻皇且爸羔槨?/span>

          在C語言中,我們讓指針變量賦值為NULL表示一個空指針,而C語言中,NULL實質(zhì)是 ((void*)0) ,  在C++中,NULL實質(zhì)是0。

          #ifdef __cplusplus
               #define NULL    0
          #else    
               #define NULL    ((void *)0)
          #endif

          void*類型指針

          void是一種特殊的指針類型,可以用來存放任意對象的地址。一個void指針存放著一個地址,這一點和其他指針類似。不同的是,我們對它到底儲存的是什么對象的地址并不了解。

          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*指針只可以儲存變量地址,不可以直接操作它指向的對象

          由于void是空類型,只保存了指針的值,而丟失了類型信息,我們不知道他指向的數(shù)據(jù)是什么類型的,只指定這個數(shù)據(jù)在內(nèi)存中的起始地址,如果想要完整的提取指向的數(shù)據(jù),程序員就必須對這個指針做出正確的類型轉(zhuǎn)換,然后再解指針。

          數(shù)組和指針

          • 同類型指針變量可以相互賦值,數(shù)組不行,只能一個一個元素的賦值或拷貝
          • 數(shù)組在內(nèi)存中是連續(xù)存放的,開辟一塊連續(xù)的內(nèi)存空間。數(shù)組是根據(jù)數(shù)組的下進行訪問的。指針很靈活,它可以指向任意類型的數(shù)據(jù)。指針的類型說明了它所指向地址空間的內(nèi)存。
          • 數(shù)組所占存儲空間的內(nèi)存:sizeof(數(shù)組名) 數(shù)組的大小:sizeof(數(shù)組名)/sizeof(數(shù)據(jù)類型),在32位平臺下,無論指針的類型是什么,sizeof(指針名)都是 4 ,在 64 位平臺下,無論指針的類型是什么,sizeof(指針名)都是 8 。
          • 數(shù)組名作為右值的時候,就是第一個元素的地址
          int main(void)
          {
              int arr[5] = {1,2,3,4,5};

              int *p_first = arr;
              printf("%d",*p_first);  //1
              return 0;
          }
          • 指向數(shù)組元素的指針 支持 遞增 遞減 運算。p= p+1意思是,讓p指向原來指向的內(nèi)存塊的下一個相鄰的相同類型的內(nèi)存塊。在數(shù)組中相鄰內(nèi)存就是相鄰下標元素。

          函數(shù)與指針

          函數(shù)的參數(shù)和指針

          C語言中,實參傳遞給形參,是按值傳遞的,也就是說,函數(shù)中的形參是實參的拷貝份,形參和實參只是在值上面一樣,而不是同一個內(nèi)存數(shù)據(jù)對象。這就意味著:這種數(shù)據(jù)傳遞是單向的,即從調(diào)用者傳遞給被調(diào)函數(shù),而被調(diào)函數(shù)無法修改傳遞的參數(shù)達到回傳的效果。

          void change(int a)
          {
              a++;      //在函數(shù)中改變的只是這個函數(shù)的局部變量a,而隨著函數(shù)執(zhí)行結(jié)束,a被銷毀。age還是原來的age,紋絲不動。
          }
          int main(void)
          {
              int age = 60;
              change(age);
              printf("age = %d",age);   // age = 60
              return 0;
          }

          有時候我們可以使用函數(shù)的返回值來回傳數(shù)據(jù),在簡單的情況下是可以的,但是如果返回值有其它用途(例如返回函數(shù)的執(zhí)行狀態(tài)量),或者要回傳的數(shù)據(jù)不止一個,返回值就解決不了了。

          傳遞變量的指針可以輕松解決上述問題。

          void change(int* pa)
          {
              (*pa)++;   //因為傳遞的是age的地址,因此pa指向內(nèi)存數(shù)據(jù)age。當在函數(shù)中對指針pa解地址時,
                         //會直接去內(nèi)存中找到age這個數(shù)據(jù),然后把它增1。
          }
          int main(void)
          {
              int age = 160;
              change(&age);
              printf("age = %d",age);   // age = 61
              return 0;
          }

          比如指針的一個常見的使用例子:

          #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ù)的兩個形參pa和pb可以接收兩個整型變量的地址,并通過間接訪問的方式修改了它指向變量的值。在main函數(shù)中調(diào)用swap時,提供的實參分別為&a,&b,這樣就實現(xiàn)了pa=&a,pb=&b的賦值過程,這樣在swap函數(shù)中就通過*pa修改了 a 的值,通過*pb修改了 b 的值。因此,如果需要在被調(diào)函數(shù)中修改主調(diào)函數(shù)中變量的值,就需要經(jīng)過以下幾個步驟:

          • 定義函數(shù)的形參必須為指針類型,以接收主調(diào)函數(shù)中傳來的變量的地址;
          • 調(diào)用函數(shù)時實參為變量的地址;
          • 在被調(diào)函數(shù)中使用*間接訪問形參指向的內(nèi)存空間,實現(xiàn)修改主調(diào)函數(shù)中變量值的功能。

          指針作為函數(shù)的形參的另一個典型應(yīng)用是當函數(shù)有多個返回值的情形。比如,需要在一個函數(shù)中統(tǒng)計一個數(shù)組的最大值、最小值和平均值。當然你可以編寫三個函數(shù)分別完成統(tǒng)計三個值的功能。但比較啰嗦,如:

          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ù)中完成這個功能,由于函數(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;
          }

          函數(shù)的指針

          一個函數(shù)總是占用一段連續(xù)的內(nèi)存區(qū)域,函數(shù)名在表達式中有時也會被轉(zhuǎn)換為該函數(shù)所在內(nèi)存區(qū)域的首地址。我們可以把函數(shù)的這個首地址賦予一個指針變量,使指針變量指向函數(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ù)原型非常類似。

          用指針來實現(xiàn)對函數(shù)的調(diào)用:

          #include <stdio.h>
          //返回兩個數(shù)中較大的一個
          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;
          }

          結(jié)構(gòu)體和指針

          結(jié)構(gòu)體指針有特殊的語法: -> 符號

          如果p是一個結(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;
          }

          const 和 指針

          • 指向常量的指針,值不能改變,指向可改變
          • 常指針值能改變,指向不可改變
          • 指向常量的常指針,都不能改變
          #include <stdio.h>
           
          int main()
          {
            // 1 可改變指針
            const int a = 10;
            int *p = &a;
            *p = 1000;
            printf("*p = %d\n", *p);
           
            // 2 可改變指針
            const b = 10;
            int *pb = &b;
            pb = p;
            printf("*pb = %d\n", *pb);
           
            // 3
            const c = 10;
            int * const pc = &c;
            *pc = 1000;
            //pc = pb;不能改變
           
            //4
            const d = 10;
            const * int const pd = &d;
            //*pd = 1000; 不能改變
           
           
            printf("\n");
            return 0;
          }

          深拷貝和淺拷貝

          如果2個程序單元(例如2個函數(shù))是通過拷貝 他們所共享的數(shù)據(jù)的 指針來工作的,這就是淺拷貝,因為真正要訪問的數(shù)據(jù)并沒有被拷貝。如果被訪問的數(shù)據(jù)被拷貝了,在每個單元中都有自己的一份,對目標數(shù)據(jù)的操作相互 不受影響,則叫做深拷貝。


          #include <iostream>
          using namespace std;

          class CopyDemo
          {
          public:
            CopyDemo(int pa,char *cstr)  //構(gòu)造函數(shù),兩個參數(shù)
            {
               this->a = pa;
               this->str = new char[1024]; //指針數(shù)組,動態(tài)的用new在堆上分配存儲空間
               strcpy(this->str,cstr);    //拷貝過來
            }

          //沒寫,C++會自動幫忙寫一個復(fù)制構(gòu)造函數(shù),淺拷貝只復(fù)制指針,如下注釋部分
            //CopyDemo(CopyDemo& obj)  
            //{
            //   this->a = obj.a;
            //  this->str = obj.str; //這里是淺復(fù)制會出問題,要深復(fù)制
            //}

            CopyDemo(CopyDemo& obj)  //一般數(shù)據(jù)成員有指針要自己寫復(fù)制構(gòu)造函數(shù),如下
            {
               this->a = obj.a;
              // this->str = obj.str; //這里是淺復(fù)制會出問題,要深復(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;  //定義一個整型的數(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ù)上面實例可以看到,淺復(fù)制僅復(fù)制對象本身(其中包括是指針的成員),這樣不同被復(fù)制對象的成員中的對應(yīng)非空指針會指向同一對象,被成員指針引用的對象成為共享的,無法直接通過指針成員安全地刪除(因為若直接刪除,另外對象中的指針就會無效,形成所謂的野指針,而訪問無效指針是危險的;

          除非這些指針有引用計數(shù)或者其它手段確保被指對象的所有權(quán));而深復(fù)制在淺復(fù)制的基礎(chǔ)上,連同指針指向的對象也一起復(fù)制,代價比較高,但是相對容易管理。

          點【在看】是最大的支持 

          瀏覽 79
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  一本道中文字幕 | 久久国产精品国语对白 | 久久亚洲热 | 最近中文字幕mv第一季歌词免费 | 丁香网站|