<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      指針詳解1

      運行環境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode為主

      1.指針的概念

      • 1.1 內存

        • 內存是CPU和硬盤之間交換數據的緩沖區,屬于存儲設備,斷電后數據會丟失動態運行著的程序會加載到內存中,如正在玩的游戲、正在聽的歌、正在編輯的課件、正在瀏覽的網頁等

        • 計算機CPU處理數據時,通常從內存中讀取數據,處理后的數據也會寫到內存中。內存被劃分為多個內存單元,每個內存單元的大小為1個字節Byte,即8個比特bit

        • 買手機時的 16GB + 256GB,16 表示運行內存(運存)為 16GB

        image

      • 1.2 地址

        • 計算機的每個內存單元都會有個編號(相當于門牌號),CPU通過該編號可以快速定位到內存空間

        • 在計算機中將內存單元的編號稱為地址,C語言又賦予這個地址一個新名稱——指針

        image

        • 計算機中的編址,并不是把每個字節的地址記錄下來,而是通過硬件設計完成。硬件之間通過“線”傳遞的信息協同工作,比如CPU和內存之間有大量數據交互,兩者通過“線”連接起來。這些“線”共有三類

          • 地址總線

            • 定位數據的“導航系統”

            • 傳遞CPU要訪問的內存單元或外設端口的地址信息,用于確定數據的來源或目的地。CPU讀寫數據前,先通過地址總線指定要操作的內存地址,內存根據地址定位對應的存儲單元,存儲單元中的數據通過數據總線傳入CPU寄存器

            • 32位機器有32根地址總線,尋址范圍為4GB;64位機器有64根地址總線,尋址范圍為18EB

          • 數據總線

            • 傳輸主句的“主干道”

            • CPU與內存、外設之間雙向傳輸實際的數據。如指令、運算結果、輸入輸出數據等

            • 數據既可從CPU發送到外部(如寫入內存),也可從外部傳輸到CPU(如讀取內存數據)

          • 控制總線

            • 協調操作的“指揮系統”

            • 傳輸各種控制信號和狀態信號,協調 CPU 與內存、外設之間的操作時序和同步

          image

      • 1.3 指針

        • 含義與特征

          • 指針也就是內存地址,指針變量是用來存放這些內存地址的變量

          • 不同類型的指針占用的存儲空間長度相同,因為它們都是地址,地址的長度與操作系統有關,與數據類型無關

        • 定義變量的實質

          • 編譯器根據變量的數據類型分配相應長度的存儲空間,該存儲空間內存儲的是變量的值,而存儲空間的地址就是變量的地址,即指針

          • 用戶僅通過變量名就可訪問存儲空間的方式稱為“直接訪問方式”。類似地,通過變量的地址解引用獲取變量的值,這種訪問方式稱為“間接訪問方式”

          image

      2.指針變量和地址

      • 2.1 取地址操作符&

        • 分析變量在內存中的存儲地址
        #include <stdio.h>
        
        int main(int argc, const char * argv[]) {
            // insert code here...
            int a = 10;
            printf("%d\n", a);
        
            return 0;
        }
        
        // 上述代碼創建整型變量 a 期間會向內存申請 4B,用于存放整數10,每個字節在內存中都有對應的地址
        // 變量 a 在內存中的地址開辟情況如下圖
        

        image

        • 獲取變量在內存中的存儲地址
        #include <stdio.h>
        
        int main(int argc, const char * argv[]) {
            // insert code here...
            int a = 0;
            &a;    // 獲取 a 的地址
            printf("%p\n", &a);
            
            return 0;
        }
        
        // &a 會取出變量 a 所占的 4 個字節中地址較小的字節地址
        // 只要知道了第 1 個字節的地址,就可以順藤摸瓜訪問 4 個字節的數據,這在指針訪問數組時指針的移動體現地尤為明顯
        
      • 2.2 指針變量

        • 通過取地址操作符&獲取的地址是一個數值,比如0xffc018,有時候也需要將該值存儲方便后期使用,像這樣的地址值不能用普通變量存儲,應當使用指針變量
        #include <stdio.h>
        
        int main(int argc, const char * argv[]) {
            // insert code here...
            int a = 10;
            int *pa = &a;   // 取出 a 的地址并存儲到指針變量 pa 中
            printf("%p\n", &a);
            
            return 0;
        }
        
        // 指針變量也是一種變量,主要用來存放地址,存放在指針變量中的值均會被系統解讀為地址
        
      • 2.3 拆解指針類型

        • pa左邊寫的是int **表示 pa是整型變量,int表示pa指向的是整型類型的對象

        • C 語言對*、類型和變量名之間的空格沒有嚴格語法限制,建議在*左側與類型名之間留一個空格

        int a = 10;
        int *pa = &a;
        int*pa = &a;    // 不建議采用這種方式,無空格,可讀性差
        
        char ch = 'w';
        char *pc = &ch;
        

        image

      • 2.4 解引用操作符

        • C語言中只要獲取了地址(指針),就可以通過該地址使用解引用操作符*找到它指向的對象

        • 借助指針修改代碼中變量的值在一定程度上可提高寫代碼的靈活性,并提升程序的運行效率

        #include <stdio.h>
        int main() {
            int a = 100;
            int *pa = &a;
            *pa = 0;    // *pa 即通過 pa 中存放的地址找到指向的空間,空間中存儲了變量 a 的值,由 10 改為 0
            printf("%d\n", a);
            return 0;
        }
        
      • 2.5 指針變量的大小

        • 假設 32 位機器有 32 根地址總線,每根地址線發出的電信號轉為數字信號后是 1 或 0。將 32 根地址線產生的二進制序列當做一個地址,共 32 bit,需要 4 個字節才能存儲

        • 類似地,假設 64 位機器共 64 根地址線,一個地址就是 64 個二進制位組成的二進制序列,需要 8 個字節才能存儲

        • 既然指針用來存放地址,那么指針變量的大小也應該為 4 或 8 個字節,具體取決于系統是 32 位平臺還是 64 位平臺

        #include <stdio.h>
        
        int main(int argc, const char * argv[]) {
            // insert code here...
            printf("%zd\n", sizeof(char *));
            printf("%zd\n", sizeof(short *));
            printf("%zd\n", sizeof(int *));
            printf("%zd\n", sizeof(double *));
            
            return 0;
        }
        
        // 由運行結果可知:1.運行系統為 x_64 平臺;2.指針變量的大小與類型無關。只要是指針類型的變量在同一平臺下,大小均相同
        // 思考:既然指針變量的大小和類型無關,為什么還要有各種各樣的指針類型呢?
        

        image

      • 2.6 指針變量類型的意義

        • 1.指針的解引用

          • 案例分析
            // 觀察以下代碼在調試時內存的變化
            // 代碼1
            #include <stdio.h>
          
            int main(int argc, const char * argv[]) {
                // insert code here...
                int n = 0x11223344;
                int *pi = &n;
                *pi = 0;
                
                return 0;
            }
          
            // 代碼2
            #include <stdio.h>
          
            int main(int argc, const char * argv[]) {
                // insert code here...
                int n = 0x11223344;
                char *pc = (char *)&n;
                *pc = 0;
            
            return 0;
            }
          
          • 代碼1運行前后的內存數據修改情況

            • 將變量 n 的 4 個字節全部改為 0,4 個字節對應整型數據的長度

            • int *型指針的解引用可訪問 4 個字節

          image

          image

          • 代碼2運行前后的內存數據修改情況

            • 將變量 n 的第 1 個字節改為 0,1 個字節對應字符型數據的長度
            • char *型指針的解引用只能訪問 1 個字節

          image

          image

        • 2.指針與整數的加減運算

          • 案例分析
          #include <stdio.h>
          
          int main(int argc, const char * argv[]) {
              // insert code here...
              int n = 10;
              char *pc = (char *)&n;
              int *pi = &n;
              
              printf("&n = %p\n", &n);
              printf("pc = %p\n", pc);
              printf("pc+1 = %p\n", pc + 1);    // char* 類型的指針變量加 1 跳過 1 個字節
              printf("pi = %p\n", pi);
              printf("pi+1 = %p\n", pi + 1);    // int* 類型的指針變量加 1 跳過 4 個字節
              
              return 0;
          }
          
          // 這就是指針變量的類型差異帶來的變化,指針加 1 即跳過 1 個指針指向的元素
          // 指針可以加 1,也可以減 1,指針的類型決定了指針向前或向后走一步有多少距離
          

          image

        • 3.void *指針

          • 無具體類型的指針(泛型指針),可以用來接受任意類型地址

          • 泛型指針不能直接進行指針的加減整數和解引用運算

          • 泛型指針通常使用在函數參數中,用于接收不同類型數據的地址,從而實現泛型編程的效果

          image

          image

      3.const修飾指針

      • 3.1 const修飾變量

        • 變量可以修改,將變量的地址賦值給指針變量,就可以通過指針變量修改變量的值

        • const關鍵字可以限制變量的屬性,使得變量的值不能被修改,即為常變量

        image

        image

      • 3.2 const修飾指針變量

        • const*的左邊,修飾的是指針指向的內容,此內容不能通過指針修改。但指針變量本身的內容(指向)可變

        image

        • const*的右邊,修飾的是指針變量本身,保證指針變量的內容(指向)不能修改,但指針指向的內容可變

        image

        image

      4.指針運算

      • 4.1 指針加減整數

        • 數組的元素值在內存中連續存放,只要確定了首元素地址,就可以順藤摸瓜找到后方所有元素
        #include <stdio.h>
        
        int main(int argc, const char * argv[]) {
            int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
            int *p = &arr[0];
            int sz = sizeof(arr) / sizeof(arr[0]);
            int i = 0;
            
            for (i = 0; i < sz; i++) {
                printf("%d ", *(p + i));    // p+i 就是指針加整數
            }
            return 0;
        }
        
        // 程序打印數組的所有內容
        
      • 4.2 指針減指針

        • 相減的前提是兩個指針指向了同一塊區間

        • 指針減指針的絕對值是兩指針間元素的個數

        #include <stdio.h>
        
        size_t my_strlen(char *s) {
            char *p = s;
            while (*p != '\0') {
                p++;
            }
            return p - s;
        }
        
        int main(int argc, const char * argv[]) {
            printf("%zd\n", my_strlen("abcdef"));    // 6,即字符串的長度
        
            return 0;
        }
        
      • 4.3 指針的關系運算

        • 將指針和數組名、數組長度關聯
        #include <stdio.h>
        
        int main(int argc, const char * argv[]) {
            int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
            int *p = &arr[0];
            int sz = sizeof(arr) / sizeof(arr[0]);
            
            while (p < arr + sz) {      // 指針的大小比較
                printf("%d ", *p);
                p++;
            }
            return 0;
        }
        
        // 程序打印數組的所有內容
        

      5.野指針

      • 5.1 野指針的成因

        • 野指針就是指針指向的位置不可知,即隨機的、不正確的、沒有明確限制的。指針未初始化、指針越界訪問、指針指向的空間沒有釋放都有可能產生野指針
        // 1.指針未初始化
        #include <stdio.h>
        
        int main(int argc, const char * argv[]) {
            int* p;    // 局部變量指針未初始化,默認為隨機值
            *p = 20;
        
            return 0;
        }  
        
        // 2.指針越界訪問
        #include <stdio.h>
        
        int main(int argc, const char * argv[]) {
            int arr[10] = {0};
            int* p = &arr[0];
            int i = 0;
            
            for (i = 0; i <= 11; i++) {
                *(p++) = i;     // 當指針指向的范圍超出數組 arr 時,p就是野指針
            }
            return 0;
        } 
        
        // 3.指針指向的空間釋放
        #include <stdio.h>
        
        int* test(void) {
            int n = 100;
            return &n;
        }
        // return &n 返回的是已經被釋放的內存地址,main 函數中接收該地址的指針 p 就成了 野指針
        
        int main(int argc, const char * argv[]) {     
            int* p = test();    // n的空間被釋放, p為野指針
            printf("%d\n", *p);    // 打印結果有可能是 100,但這不能說明程序正確
        
            return 0;
        }
        
        // test函數返回后,main函數緊接著訪問 *p 以讀取 n 原內存地址中的數據
        // 由于中間沒有其他函數調用或內存操作,這塊被釋放的內存還沒被新的數據覆蓋,仍然保留著100這個值
        // 因此,printf 可能恰好讀取到了殘留的舊值,表現為 “運行正確”。
        

        image

      • 5.2 規避野指針的方法

        • 1.指針初始化

          • 若明確知道指針的指向地址就直接賦值,否則給指針賦值NULL
          • NULL是 C 語言中定義的一個標識符常量,值為 0。0 也是地址,該地址無法使用,讀寫該地址會報錯
          #include <stdio.h>
          
          int main(int argc, const char * argv[]) {
              int num = 10;
              int* p1 = &num;
              int* p2 = NULL;    // 初始化為 NULL,避免生成野指針
          
              return 0;
          }
          
        • 2.避免指針越界

          • 一個程序向內存申請了哪些空間,通過指針也就只能訪問哪些空間,不能超出范圍訪問,超出了就是越界訪問
          #include <stdio.h>
          
          int main(int argc, const char * argv[]) {
              int arr[3] = {10, 20, 30};
              int *p = arr;
              
              // 正常訪問數組元素(索引 0-2)
              printf("正常訪問:\n");
              for (int i = 0; i < 3; i++) {
                  printf("arr[%d] = %d\n", i, *(p + i));
              }
              
              // 指針越界訪問(索引3及以上,超出數組范圍)
              printf("\n越界訪問:\n");
              for (int i = 3; i < 6; i++) {
                  // 此時p + i已經指向數組外的無效內存(野指針狀態)
                  printf("p + %d 指向的內存值:%d(地址:%p)\n", i, *(p + i), (void*)(p + i));
              }
              
              return 0;
          }
          

          image

        • 3.不再使用的指針變量及時置NULL,指針使用前檢查有效性

          • 當指針變量指向一塊區域時,可以通過指針訪問該區域。后期不再使用這個指針訪問空間時,可以將之置為NULL
          • 約定俗成的一個規則:只要是NULL就不去訪問,使用指針之前可以判斷指針是否為NULL
          #include <stdio.h>
          
          int main(int argc, const char * argv[]) {
              int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
              int *p = &arr[0];
              int i = 0;
              for (i = 0; i < 10; i++) {
                  *(p++) = i;
              }
              
              // 此時p已經越界了,可以將p置為NULL
              p = NULL;
              
              // 下次使用的時候,判斷p不為NULL的時候再使用
              p = &arr[0];    // 重新讓p獲得地址
              if (p != NULL) {
                  // ...
              }
              
              return 0;
          }
          
        • 4.避免返回局部變量的地址

          • 理解函數棧幀的生命周期和野指針的特性。以 5.1 第 3 段代碼為例,運行 “結果正確” 是因為野指針指向的內存數據尚未被覆蓋,屬于偶然現象,而非代碼邏輯正確。正確的做法是避免返回局部變量的地址

          • 解決方法

            • 使用靜態變量static int n = 100;

            • 動態分配內存int* n = malloc(sizeof(int)); *n=100; return n;

            • 由調用者傳入指針參數void test(int* p) { *p = 100; }

      6.assert斷言

      • 6.1 定義與運行原理

        • assert.h頭文件定義了宏assert(),用于在運行時確保程序符合指定條件。如果不符合,就報錯終止運行,這個宏常被稱為“斷言”
        assert(p != NULL);
        
        • 程序運行到上述代碼時,驗證變量p是否等于NULL。如果確實不等于NULL,程序繼續運行,否則就會終止運行,并給出報錯信息提示

        • assert()宏接收一個表達式作為參數。若表達式值為真,assert()不會產生任何作用,程序繼續運行。若表達式為假,assert()就會報錯

      • 6.2 使用斷言的優勢

        • 對程序員非常友好,不僅能自動標識文件和出問題的行號,還有無需更改代碼就能開啟或關閉assert()的機制

        • 若確認程序沒有問題,不需要再做斷言,就在#include <stdio.h>前面定義一個宏NDEBUG,再重新編譯程序,編譯器會禁用文件中所有assert()語句

        #define NDEBUG
        #include <assert.h>
        
      • 6.3 使用斷言的劣勢

        • 引入了額外的檢查,增加了程序的運行時間

        • 通常在Debug版本中使用,有利于程序員排查問題;Release版本中選擇禁用斷言,部分IDE會在Release版本中優化掉斷言,不影響用戶使用程序的效率

      7.指針的使用和傳址調用

      • 7.1 strlen的模擬實現

        • 庫函數strlen的功能是求字符串長度,統計的是字符串中\0之前的字符個數

        • 函數原型為size_t strlen(const char *str),參數str接收一個字符串的起始地址,然后開始統計字符串中\0之前的字符個數

        • 模擬實現的過程為從起始地址開始逐個字符向后遍歷,只要不是\0字符,計數器就加 1,直到\0就停止

        #include <stdio.h>
        #include <assert.h>
        
        size_t my_strlen(const char *str) {
            int count = 0;
            assert(str);
            
            while (*str) {
                count++;
                str++;
            }
            return count;
        }
        
        int main(int argc, const char * argv[]) {
            size_t len = my_strlen("abcdef");
            printf("%zd\n", len);
            return 0;
        }
        
      • 7.2 傳值調用

        • 實質:實參的值傳遞給形參的時候,形參會單獨創建一份臨時空間來接收實參,對形參的修改不影響實參
        #include <stdio.h>
        
        void Swap1(int x, int y) {
            int temp = x;
            x = y;
            y = temp;
        }
        
        int main(int argc, const char * argv[]) {
            int a = 0;
            int b = 0;
            scanf("%d %d", &a, &b);
            printf("交換前: a = %d b = %d\n", a, b);
            Swap1(a, b);
            printf("交換后: a = %d b = %d\n", a, b);
            
            return 0;
        }
        

        image

        image

        • 運行結果分析

          • 1.在main()函數內部創建了a和b,a的地址為0x0098fadcb的地址為0x0098fad0。調用Swap1()函數時,將a和b傳遞給了Swap1()函數

          • 2.在Swap1()函數內部創建了形參x和y接收a和b的值,但x的地址是0x0098f9f8(不同于a的地址),y的地址是0x0098f9fc(不同于b的地址),即x和y是獨立的空間

          • 3.因此在Swap1()函數內部交換x和y的值,不會影響a和b。當Swap1()函數調用結束后回到main()函數a和b并未交換

          • 4.Swap1()函數運行期間接收了來自main()函數的實參值,這種調用方式稱為傳值調用

          • 5.如何修改代碼才能運行出正確的結果?其實只要保證運行Swap1()函數時內部操作的是a和b就可以,結合指針的知識,在main()函數中將a和b的地址傳遞給Swap1()函數,就可以通過地址間接操作main()函數中的a和b

      • 7.3 傳址調用

        • 實質:使得其他函數與主函數之間建立真正的聯系,在函數內部課修改主調函數中的變量

        • 使用情形:若函數中只是需要主調函數中的變量值,可采用傳值調用;若函數內部要修改主調函數中的變量值,可以采用傳址調用

        #include <stdio.h>
        
        void Swap2(int *px, int *py) {
            int temp = 0;
            temp = *px;
            *px = *py;
            *py = temp;
        }
        
        int main(int argc, const char * argv[]) {
            int a = 0;
            int b = 0;
            scanf("%d %d", &a, &b);
            printf("交換前: a = %d b = %d\n", a, b);
            Swap2(&a, &b);    // 將變量的地址傳給了Swap2
            printf("交換后: a = %d b = %d\n", a, b);
            
            return 0;
        }
        

        image

      posted @ 2025-08-09 16:56  pycoder_666  閱讀(22)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 在线 | 国产精品99传媒a| 中文字幕乱码一区二区免费| 天天躁日日躁狠狠躁中文字幕| 办公室强奷漂亮少妇视频| 国产一区二区三区综合视频| 亚洲日韩精品无码一区二区三区| 久久丫精品国产| 久久久久四虎精品免费入口| 国产精品亚洲综合网一区| 亚洲国产性夜夜综合| 欧美成本人视频免费播放| 欧美精欧美乱码一二三四区| 人人爽人人模人人人爽人人爱| 兴城市| 欧美色欧美亚洲高清在线观看| 久久99精品久久久久久| 99无码中文字幕视频| 亚洲人成电影网站 久久影视| 国产av丝袜旗袍无码网站| 中文字幕免费一二三区乱码| 亚洲一区二区精品动漫| 久久久久影院色老大2020| 国产精品中文字幕久久| 两性午夜刺激性视频| 国产午夜精品福利免费不| 日日碰狠狠添天天爽五月婷| 摸丰满大乳奶水www免费| 亚洲精品一区二区三区大| 国产丰满乱子伦无码专区| 少妇伦子伦精品无吗| 精品亚洲综合一区二区三区| 国产成人a在线观看视频免费| 99久久精品美女高潮喷水| 精品一卡2卡三卡4卡乱码精品视频| 国产三级精品福利久久| 日本不卡一区| 最新精品国产自偷在自线| 久久国产免费观看精品3| 国产成人精品一区二区三区无码| 久久综合伊人| 国产啪视频免费观看视频|