C語言入門-指針

来源:https://www.cnblogs.com/mengd/archive/2019/10/02/11617982.html
-Advertisement-
Play Games

終於到了精髓的地方了,這確實有點懵,總感覺這太麻煩了,而且寫著也不爽,還是懷念py或者java,但也沒辦法,還是要繼續學下去。 一、運算符& 1. scanf("%d" , &i); 里的& 2. 獲取變數的地址,它的操作數必須是變數 3. 地址的大小是否與int相同取決於編譯器 &不能取的地址 & ...


終於到了精髓的地方了,這確實有點懵,總感覺這太麻煩了,而且寫著也不爽,還是懷念py或者java,但也沒辦法,還是要繼續學下去。

一、運算符&

  1. scanf("%d" , &i); 里的&
  2. 獲取變數的地址,它的操作數必須是變數
  3. 地址的大小是否與int相同取決於編譯器
#include <stdio.h>
int main(void)
{
    int i = 0;
    printf("0x%x\n", &i);
    // 0x62fe4c
    return 0;
}

&不能取的地址

&不能對沒有地址的東西取地址

  1. &(a+b) ?
  2. &(a++) ?

二、指針

  1. 如果能夠將獲得變數的地址傳遞個一個函數,能否通過這個地址在那個函數內訪問這個變數?
  2. scanf("%d" , &i);
  3. scanf()的原型應該是怎樣的?我們需要一個參數保存別的變數的地址,如何表達能夠保存地址的變數

指針

就是保存地址的變數 , *p

// p是一個指針,現在把i的地址交給了p
int i;
int* p = &i;

// 下麵兩種形式一樣,p是一個指針,而q是一個普通的int變數
int* p , q;
int *p , q;

指針變數

  1. 變數的值是記憶體的地址
  2. 普通變數的值是實際的值
  3. 指針變數的值是具有實際值的變數的地址

作為參數的指針

  1. void f(int *p);
  2. 在被調用的時候得到了某個變數的地址
  3. int i = 0;f(&i);
  4. 在函數裡面可以通過這個指針訪問到外面的的這個i
#include <stdio.h>

void f(int *p);

int main(void)
{
    int i = 6;
    printf("&i=%p\n", &i);
    f(&i);

    return 0;
}

void f(int *p)
{
    printf(" p=%p\n", p);
}

// 可以看到這裡獲取的地址是相同的
// &i=000000000062FE4C
//  p=000000000062FE4C

訪問那個地址上的變數*

  1. *是一個單目運算符,用來訪問指針的值所表示的地址上的變數
  2. 可以是右值也可以是左值
  3. int k = *p;
  4. *p = k + 1;

*左值之所以叫左值

  1. 是因為出現在賦值號左邊的不是變數,而是值,是表達式計算的結果
  2. a[0] = 2;
  3. *p = 3;
  4. 是特殊的值,所以叫左值
#include <stdio.h>

// 聲明兩個函數
void f(int *p);
void g(int k);


int main(void)
{
    int i = 6;
    printf("&i=%p\n", &i);
    f(&i);
    
    // 此時i的值已經發生了變化
    g(i);

    return 0;
}


// 傳入的是地址
void f(int *p)
{
    printf(" p=%p\n", p);
    printf("*p=%d\n", *p);
    *p = 66;
}

// 傳入的普通int
void g(int k){
    printf("k=%d\n", k);
}


// &i=000000000062FE4C
//  p=000000000062FE4C
// *p=6
// k=66

三、指針的使用

指針應用場景一

交換兩個變數的值

#include <stdio.h>

void swap(int *pa , int *pb);

int main()
{
    int a = 5;
    int b = 10;
    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;
}

指針應用場景二

  1. 函數返回多個值,某些值就只能通過指針返回
  2. 傳入的參數實際上是需要保存帶回的結果的變數
#include <stdio.h>

void minmax(int a[] , int len , int *min , int *max);

int main(void)
{
    int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,15,34,35,66,};
    int min , max;
    minmax(a , sizeof(a)/sizeof(a[0]) , &min , &max);
    printf("min = %d , max = %d \n", min , max);

    return 0;
}


void minmax(int a[] , int len , int *min , int *max)
{
    int i;
    *min = *max = a[0];
    for (i = 0; i < len; i++)
    {
        if (a[i] > *max)
        {
            *max = a[i];
        }
        if (a[i] < *min)
        {
            *min = a[i];
        }
    }
}

指針應用場景二b

  1. 函數返回運算的狀態,結果通過指針返回
  2. 常用的套路是讓函數返回特殊的不屬於有效範圍內的值表示出錯
    • -1 或 0
  3. 但是當任何數值都是有效的可能結果時,就得分開返回了
#include <stdio.h>

// 如果成功就返回1,否則就是0
int divide(int a , int b , int *result);

int main(void)
{
    int a = 5;
    int b = 2;
    int c;
    if (divide(a,b,&c))
    {
        printf("%d/%d = %d\n", a, b, c);
    }

    return 0;
}

int divide(int a , int b , int *result)
{
    int ret = 1;
    if ( b== 0)
    {
        ret = 0;
    }else{
        *result = a/b;
    }

    return ret;
}

指針常見的錯誤

定義了指針變數,還沒有指向任何變數,就開始使用了

四、指針與數組

傳入函數的數組成什麼了?

  1. 函數參數表中的數組實際上是指針
  2. sizeof(a) == sizeof(int*)
  3. 但是可以用數組的運算符[]進行運算

下麵的四種函數原型是等價的

int sum(int *ar , int n);
int sum(int* , int);
int sum(int ar[] , int n);
int sum(int[] , int);

數組變數是特殊的指針

數組變數本身表達地址,所以

  1. int a[10]; int*p = a; // 無需用&取地址
  2. 但是數組的單元表達的是變數,需要用&取地址
  3. a == &a[0]

[]運算符可以對數組做,也可以對指針做

  1. p[0] <===> a[0]

*運算符可以對指針做,也可以對數組做

  1. *a = 25

數組變數是const的指針,所以不能被賦值

五、指針與const

  1. 表示一旦得到了某個變數的地址,不能再指向其他變數
    // q內寫的地址不能被改變
    int *const q = &i;
    *q = 26; // ok
    q++;   // error
  1. 表示不能通過這個指針去修改那個變數(並不能使得那個變數成為const)
    const int *p = &i;
    *p = 26; // error  (*p是不能變的)
    i = 26; // ok
    p = &j; // ok
  1. 看懂下麵的意思?
    const int * p1 = &i;
    int const * p2 = &i;

    int *const  p3 = &i;

判斷那個被const的標誌是const在的前面還是後面
前面兩個
p不能被修改
,就像第二種情況

const數組

const int a[] = {1,2,3,4,5,6,};
  1. 數組變數已經是const的指針了,這裡的const表明數組的每個單元都是const int
  2. 所以必須通過初始化進行賦值

保護數組值

  1. 因為把數組傳入函數時傳遞的是地址,所以那個函數內部可以修改數組的值
  2. 為了保護數組不被函數破壞,可以設置參數為const
int sum(const int a[] , int length);

六、指針運算

1+1 = 2 ?那麼指針加1等於什麼呢

#include <stdio.h>

int main(void)
{
    char ac[] = {0,1,2,3,4,5,6,7,8,9,};
    char *p = ac;

    printf("p =%p\n", p);
    printf("p + 1 =%p\n\n", p+1);
    // p =000000000062FE30
    // p + 1 =000000000062FE31
    // 相差了1
    
    int ai[] = {0,1,2,3,4,5,6,7,8,9,};
    int *q = ai;

    printf("q =%p\n", q);
    printf("q + 1 =%p\n\n", q+1);
    //q =000000000062FE00
    //q + 1 =000000000062FE04
    // 相差了4

    return 0;
}

我們不難發現,在char中,相差了1,在int中相差了4,這是怎麼回事?

sizeof(char) = 1 , sizeof(int) = 4

對指針做一個加1的動作,意味著要把它移動到下一個單元去

int a[10];
int *p = a;
*(p+1) --> a[1]

如果指針不是指向一片連續分配的空間,如數組,那麼這運算沒有意義

可以對指針的算術運算

  1. 給指針加、減、一個整數(+、+=、-、-=)
  2. 遞增遞減(++/--)
  3. 兩個指針相減
#include <stdio.h>

int main(void)
{
    char ac[] = {0,1,2,3,4,20,6,7,8,9,};
    char *p1 = &ac[5];
    char *p2 = &ac[4];

    printf("p1 - p2 = %d\n" , p1 - p2);
    printf("*p1 - *p2 = %d\n" , *p1 - *p2);
    // p1 - p2 = 1   也就是 5 -4
    // *p1 - *p2 = 16  也就是 20 - 4

    
    int ai[] = {0,1,2,3,4,5,6,7,8,18,};
    int *q1 = &ai[9];
    int *q2 = &ai[2];

    printf("q2 - q1 = %d\n", q2 - q1);
    printf("*q2 - *q1 = %d\n", *q2 - *q1);
    // q2 - q1 = -7      也就是 2 - 9
    // *q2 - *q1 = -16   也就是 2 - 18

    return 0;
}

*p++

  1. 取出p所指的那個數據來,完事之後順便把p移動到下一個位置
  2. *的優先順序雖然高,但沒有++高
  3. 常用於數組類的連續空間操作
  4. 在某些cpu上,這可以直接被翻譯成一條彙編指令
    char ac[] = {0,1,2,3,4,20,6,7,8,9,-1};
    char *p = &ac[0];

    // 普通方法遍曆數組
    // int i ;
    // for (i = 0; i < sizeof(ac)/sizeof(ac[0]); i++)
    // {
    //  printf("%d\n", ac[i]);
    // }
    
    // 普通指針遍曆數組
    // for (p = ac; *p != -1; p++)
    // {
    //  printf("%d\n", *p);
    // }

    // printf("---------------\n");
    
    // 使用*p++,快
    while(*p != -1){
        printf("%d\n", *p++);
    }

0地址

  1. 當然你的記憶體中有0地址,但是0地址通常是個不能隨便碰的地址
  2. 所以你的指針不應該具有0值
  3. 因此可以用0地址來表示特殊的事情
    • 返回的指針是無效的
    • 指針沒有被真正的初始化(先初始化為0)
  4. NULL是一個預定定義的符號,表示0地址

指針的類型

  1. 無論指向什麼類型,所有的指針的大小都是一樣的,因為都是地址
  2. 但是指向不同類型的指針是不能直接互相賦值的
  3. 只是為了避免用錯指針

用指針來做什麼

  1. 需要傳入較大的數據時用作參數
  2. 傳入數組後對數組做操作
  3. 函數返回不止一個結果
  4. 需要用函數修改不止一個變數
  5. 動態申請記憶體時

七、動態記憶體分配

輸入數據

  1. 如果輸入數據時,先告訴你個數,然後再輸入,要記錄每個數據
  2. C99可以用變數做數組定義的大小,C99之前呢?
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int number;
    int *a;
    int i;
    printf("請輸入數量:\n");
    scanf("%d" , &number);

    // 動態記憶體分配用malloc,此時a就相當於數組
    a = (int*)malloc(number*sizeof(int));

    for (i = 0; i < number; i++)
    {
        scanf("%d" , &a[i]);
    }

    for (i = number - 1; i >=0; i--)
    {
        printf("%d\n", a[i]);
    }

    // 最後釋放空間
    free(a);

    return 0;
}

malloc函數

#include <stdlib.h>
void* malloc(size_t size);
  1. 向malloc申請的空間的大小是以位元組為單位的,需要什麼類型就在sizeof裡面寫什麼類型
  2. 返回的結果是void* , 需要類型轉換為自己需要的類型
  3. 最後釋放空間
(int*)malloc(n*sizeof(int));

沒空間了?

  1. 如果申請失敗則返回0 , 或者叫做NULL
  2. 你的系統可以給你多大的空間?
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    void *p;
    int cnt = 0;

    while((p = malloc(100 * 1024 *1024))){
        cnt ++ ;
    }

    printf("分配了%d00MB的空間\n", cnt);

    return 0;
}
// 分配了27200MB的空間

free()

  1. 把申請得來的空間還給系統
  2. 只能還申請的空間的首地址
  3. 一個malloc對應一個free

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • php-fpm.conf是PHP-FPM進程管理器的配置文件; php.ini是PHP解析器的配置文件; www.conf是php.ini的擴展文件 ...
  • 以下是紳士通過processon畫的一個比較簡單的架構,模板模式理清楚確實需要一點點時間 Doug Lea牛ban- 。- 最近在複習整理知識點,這上面的一些關鍵方法addWaiter();acquireQueued(),release()等方法會和後續對整體架構一起整理一份 如果有興趣或者看這個架 ...
  • [TOC]   機器學習是一門多領域交叉學科,涉及概率論、統計學、逼近論、凸分析、演算法複雜度理論等多門學科。專門研究電腦怎樣模擬或實現人類的學習行為,以獲取新的知識或技能,重新組織已有的知識結構使之不斷改善自身的性能。從數據中提取知識,也被稱為預測分析 或 統計學習。 &ems ...
  • 有些網站做了反爬技術,如:比較初級的通過判斷請求頭部中的user-agent欄位來檢測是否通過瀏覽器訪問的。 在爬這類網站時需要模擬user-agent user-agent.txt 百度網盤 鏈接:https://pan.baidu.com/s/1ramkIyjVSI2_GXbxypj1Dg 提取 ...
  • 使用Python編寫一個簡單的文本編輯器,需要展示一個用戶界面,功能包括打開、保存文本文件。 使用tkinter庫來編寫GUI。 ...
  • 先看一段代碼: mov eax,dword ptr [a] add eax,1 mov dword ptr [a],eax //這三行指令將a+1 mov ecx,dword ptr [a] mov dword ptr [ebp 0D0h],ecx //這兩行指令將a的值存儲到一個臨時地址(寄存器間 ...
  • Open Eclipse.- Open the menu "Help", menu item "Install new software..."- Click on the button "Add..." to add a new software site.- Fill in the name " ...
  • 1、ArrayList源碼解析 源碼解析: 如下源碼來自JDK8:。 (以上所有內容皆為個人筆記,如有錯誤之處還望指正。) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...