C語言中多種指針相關類型詳解-創新互聯

目錄

創新互聯是一家專業提供南樂企業網站建設,專注與成都網站制作、成都做網站、H5開發、小程序制作等業務。10年已為南樂眾多企業、政府機構等服務。創新互聯專業網站制作公司優惠進行中。

指針數組

數組指針

arr與&arr的區別

數組指針

1.數組指針的創建格式:

2.數組指針的用處

數組參數,指針參數

一維數組傳參

二維數組傳參(注意事項)

一級指針傳參

二級指針傳參

函數指針

函數指針數組

函數指針真正用處用法


指針數組

指針數組是指在一組數中能夠存放多個相同指針類型的一組數

指針數組的書寫格式:

  • int* * *arr[5];——存放int *類型的數組
  • char* ch[10];——存放char *類型的數組
  • int** pp[5];——存放int** 二級指針類型的數組

當然還可以類比出三級指針,四級指針等等等等....

指針的初始化:

如int* arr[5]={0};

在這里我們將數組arr里的每一個元素賦了0,也就相當于將指針數組里面的所有元素都變為了空指針,這是因為在編譯器中,NULL相當于0被強制類型轉換為了(void*)類型,所以NULL本質上就是0。

數組指針

數組指針是一種指針類型,是一個指向數組的指針。

數組指針的書寫格式:

#includeint main()
{
    int arr[5]={0};
    char s[5]={0};
    int (*p)[5]=&arr;//這個就是數組指針的格式,
                     //也可以用其他類型如char等
    char (*b)[5]=&s;
    return 0;
}
arr與&arr的區別

創造一個int型數組int arr[5]={0}; 我們都知道arr是數組名,也就是首元素的地址,但&arr表示的是什么?

在編譯器中輸入以下代碼:

#includeint main()
{
  int arr[5] = { 0 };
  printf("%p\n", arr);
  printf("%p\n",arr + 1);
  printf("%p\n", &arr);
  printf("%p\n", &arr + 1);
  return 0;
}

最終輸出結果如下:

觀察上面的輸出后的圖,我們可以看到arr與&arr的地址是相同的,但arr與&arr其實是兩個完全不同的概念。

我們可以看到,arr+1后的地址與arr的地址相差了4,這是因為arr是數組名,相當于存放了數組首元素的地址,也就是一個int*類型的指針,所以每加一后都會直接跳過四個字節,讀取到數組中下一個元素。而我們再看向&arr與&arr+1的地址,經過計算后得到兩地址相差的數值大小為20,我們會發現,這剛好是我們創建數組的所占的總共空間大小(20個字節)。

也就是說,&arr其實取到的并不是數組首元素地址,類型并不為int*指針,而取到的是整個數組的地址,類型為數組指針,所以當我們加一的時候,由于是數組指針加一,所以會跳過20個字節。那么數組指針具體又是什么呢?

數組指針

就像int型會有int指針,char類型有char指針,數組也會有對應的數組指針,例如int型數組,數組指針的書寫格式就為int(*)[],具體用法如下:

數組指針的應用

1.數組指針的創建格式:

2.數組指針的用處

Ⅰ:訪問一維數組

#includeint main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int(*p)[10] = &arr;
  int i = 0;
  for (i = 0; i< 10; i++)
  {
    printf("%d ",*((*p) + i));
  }
  return 0;
}

這里雖然訪問一維數組用這種方法繁冗且不建議使用,但還是做下解釋。

Ⅱ:訪問二維數組

如果要寫一個函數,將遍歷二維數組將每一個元素打印出來,我們會怎么寫呢?

在以前會使用下面的方法:

#includevoid print(int a[3][5], int x, int y)
{
  int i = 0;
  int j = 0;
  for (i = 0; i< x; i++)
  {
    for (j = 0; j< y; j++)
    {
      printf("%d ", a[i][j]);
    }
    printf("\n");
  }
}
int main()
{
  int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
  print(arr, 3, 5);
  return 0;
}

但現在,我們可以用數組指針的方式來實現:

#includevoid print(int(*p)[5], int x, int y)
{
  int i = 0;
  int j = 0;
  for (i = 0; i< x; i++)
  {
    for (j = 0; j< y; j++)
    {
      printf("%d ", *(*(p + i) + j));
    }
    printf("\n");
  }
}
int main()
{
  int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
  print(arr, 3, 5);
  return 0;
}

在二維數組中例如arr[3][5],其中的“[3]”所代表的是有3行元素,“[5]”代表的是每行元素有5列。而arr[5]則代表一行的地址

所以當我們在傳參時,只需要將形參定為int型的指針數組,先將每一行的地址傳入,就可以實現遍歷數組的作用。

再次理解:

#includeint main()
{
  int a[10] = { 0 };
  int* p = a;
  //*(a+i)==*(p+i)==a[i]==p[i]
}
數組參數,指針參數 一維數組傳參

看下面代碼,判斷傳參是否正確:

#includevoid test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
    int arr[10]={0};
    int *arr2[20]={0};
    test(arr);
    test2(arr2);
    return 0;
}

一維數組傳參經驗總結:

  • 在數組傳參時可以使用數組形式,而且數組后可以寫大小也可不寫,因為數組在傳參過程中不會創建數組,所以數組大小填錯或不填都可以
  • 數組傳參傳入的數組名是首元素地址,可以用指針來接收

所以上述代碼中的傳參都是正確的。

二維數組傳參(注意事項)

看下面代碼,判斷傳參是否正確:

#includevoid test(int arr[3][5])//ok?  ----1
{}
void test(int arr[][])//ok?    ----2
{}
void test(int arr[][5])//ok?   ----3
{}
void test(int *arr)//ok?       ----4
{}
void test(int (*arr)[5])//ok?  ----5
{}
void test(int **arr)//ok?      ----6
{}
int main()
{
    int arr[3][5]={0};
    test(arr);
}

二維數組傳參經驗總結:

  • 二維數組傳參時可以寫成形參二維數組的形式,行可以不寫大小,但是列一定要寫大小。
  • 在傳參時傳入的是數組指針類型,所以不能用一級指針接收,也不能用指針數組、二級指針來接收,這樣是類型上就不對的了

綜上,2,4,6是錯誤的傳參形式。

一級指針傳參

Q:當函數的參數為一級指針時,函數能接收什么參數?

A:可以傳入一個地址,或數組名,或直接傳入一個一級指針。

二級指針傳參

Q:當函數的參數為二級指針時,函數能接收什么參數?

A:可以傳入一級指針的地址、二級指針、指針數組的數組名。

函數指針

函數不同于數組,函數名與在函數名前加上&本質上是沒有區別的,下面是函數指針的一些使用:

#includeint Add(int x, int y)
{
  return x + y;
}
void Fun(char x)
{
  ;
}
int main()
{
  //Add與&Add本質上沒有區別,都表示為同一個地址
  printf("%p\n", Add);
  printf("%p\n", &Add);
  //函數指針的創建舉例
  int (*p)(int, int) = Add;
  int (*pa)(int, int) = &Add;
  void (*ppa)(char) = Fun;
  //函數指針的調用也同函數名一樣,可以直接用名或者在前面加上解引用符
  printf("%p\n", p);
  printf("%p\n", *p);
  //函數的調用
  printf("%d\n", Add(1, 2));
  printf("%d\n", (&Add)(1, 2));//這里特別注意,要將&Add用小括號括起來,否則Add就會先與后面的(1,2)結合,從而無法調用函數。
  printf("%d\n", p(1, 2));
  printf("%d\n", (*p)(1, 2));//*p也是先要用小括號括起來,否則會與后面的(1,2)結合,從而無法調用函數得到想得到的結果。
  return 0;
}

特別注意:以上面代碼為例,若要以&Add或者*p這種形式來使用,要先用小括號括起來,否則Add/p就會先與后面的(1,2)結合,從而無法調用函數。

在學習函數指針時,又偶然發現了一道很經典,非常,特別,詭異的題目:

//請分析下面這段代碼:
(*(void(*)())0)();

??????????????????????????????????????????????????????????????????????????????????????

在剛看到這段代碼時我的心情如上👆。心想:“魔法,這一定是魔法”。但其實冷靜思考過后,最終還是理解了這一段離譜且詭異的代碼:

首先從最里開始,void(*)? ()這樣單獨拆開后我們不難發現,這是一個函數指針類型,只不過是一個void型的函數且不用傳參,在這整體上再套多一個小括號并在后面加上數字0,變為(void(*)?()?)0的樣子,這里最外一層的小括號其實是強制類型轉換符,將0這個地址上放入一個函數指針(也就是放了一個函數)。而最終變為(*(void(*)())0)(),其實是調用了0地址上的這個參數為無參,返回類型是void的函數。所以總結來說,整個代碼是在調用0地址處的函數

但是!!!!

當我們將上面代碼在編譯器中正式運行時是不可能的,會出現運行中止或報錯或根本不運行。這是因為地址0是我們普通用戶禁止被使用的地址,我們是無法使用的,因為0地址是開機啟動時才訪問的地址。

再來看一條簡單一點的代碼(可能)

void (*signal(int,void(*)(int)))(int);

上面代碼要這么看:void(* signal(int,void(*)(int)) )(int);

這樣子就不難看出(應該不難)signal(int,void(*)(int))是一個函數的聲明,因為函數的參數位置如果是調用的話,則不應該為類型,所以這個是函數的聲明,這是一個函數名為signal,參數為int型和函數指針型的函數,函數名有了,參數也有了,現在缺少的就是返回類型,這個返回的類型便是剩下的→void ( *)(int)。

Tip:在這里要特別注意,*總是要和名字在一起,不可以寫為void (?*)(int) signal(int,void(*)(int))的形式

看吧,不難吧(后仰攤手)

什么??還是難???那好吧,下面我們嘗試著簡化一下這樣的代碼(其實我也覺得難)

將void(*)(int)這樣類型的函數指針類型重命名為pfun_t,這里要用到typedef,但注意!!不是寫為這樣:typedef void(* )(int) pfun_t。而是要寫為:typedef void(* pfun_t)(int) 這里這么寫是因為*總要與名字在一起(上面tip里有說過)。

經過函數指針的重命名后,上面的代碼可以最終簡化為:

pfun_t signal(int,pfun_t);


函數指針數組

按照字面意思,我們可以知道,這是一組存放函數指針類型元素的數組。

函數指針數組的使用:

#includeint Add(int x, int y)
{
  return x + y;
}
int Sub(int x, int y)
{
  return x - y;
}
int Mul(int x, int y)
{
  return x * y;
}
int Div(int x, int y)
{
  return x / y;
}
int main()
{
  int (*arr[5])(int, int) = { Add,Sub,Mul,Div };
  int i = 0;
  for (i = 0; i< 5; i++)
  {
    printf("%d\n",arr[i](8, 4));
  }
  return 0;
}

函數指針真正用處用法

函數指針可以利用寫一個函數,使其形參的類型為函數指針類型,從而達到一個函數可以調用多個不同函數的一個函數。

我們先寫下下面這樣的一段代碼:

#includevoid Menu()
{
  printf("******************************\n");
  printf("****  1.Add      2.Sub    ****\n");
  printf("****  3.Mul 4.Div 0.exit  ****\n");
  printf("******************************\n");
}

int Add(int x, int y)
{
  return x + y;
}

int Sub(int x, int y)
{
  return x - y;
}

int Mul(int x, int y)
{
  return x * y;
}

int Div(int x, int y)
{
  return x / y;
}

int main()
{
  int input = 0;
  do
  {
    int x = 0;
    int y = 0;
    Menu();
    printf("請輸入你要的操作");
    scanf("%d", &input);
    switch (input)
    {
      case 0:
        printf("結束操作\n");
        break;
      case 1:
        printf("請輸入你想要操作的兩個數:");
        scanf("%d %d", &x, &y);
        printf("%d\n", Add(x, y));
        break;
      case 2:
        printf("請輸入你想要操作的兩個數:");
        scanf("%d %d", &x, &y);
        printf("%d\n", Sub(x, y));
        break;
      case 3:
        printf("請輸入你想要操作的兩個數:");
        scanf("%d %d", &x, &y);
        printf("%d\n", Mul(x, y));
        break;
      case 4:
        printf("請輸入你想要操作的兩個數:");
        scanf("%d %d", &x, &y);
        printf("%d\n", Div(x, y));
        break;
      default:
        printf("輸入錯誤,請重新輸入");
        break;
    }
  } while (input);
}

在上面我們會發現case1,2,3,4大量代碼是重復冗余的,且Add,Sub,Mul,Div函數都是返回的int型,且都有兩個int型的參數,對于這種情況,我們可以利用寫一個函數,來調用這些形式上差不多相同的函數,能夠做到縮減代碼量的目的,而調用這些函數以形參形式傳過去,就需要用到函數指針類型。

在這里我們創建一個Cal函數,其參數形式為函數指針型,若以上面函數來舉例,則寫出如下代碼:

void Cal(int (*fun)(int,int))
{}

然后再將重復的代碼放入Cal函數內之后刪去,便可以達到減少重復代碼的目的了。下面是完整的更改后的代碼:

#includevoid Menu()
{
  printf("******************************\n");
  printf("****  1.Add      2.Sub    ****\n");
  printf("****  3.Mul 4.Div 0.exit  ****\n");
  printf("******************************\n");
}

int Add(int x, int y)
{
  return x + y;
}

int Sub(int x, int y)
{
  return x - y;
}

int Mul(int x, int y)
{
  return x * y;
}

int Div(int x, int y)
{
  return x / y;
}

void Cal(int (*fun)(int, int))
{
  int x = 0;
  int y = 0;
  printf("請輸入你想要操作的兩個數:");
  scanf("%d %d", &x, &y);
  printf("%d\n", fun(x, y));
}

int main()
{
  int input = 0;
  do
  {
    Menu();
    printf("請輸入你要的操作");
    scanf("%d", &input);
    switch (input)
    {
    case 0:
      printf("結束操作\n");
      break;
    case 1:
      Cal(Add);
      break;
    case 2:
      Cal(Sub);
      break;
    case 3:
      Cal(Mul);
      break;
    case 4:
      Cal(Div);
      break;
    default:
      printf("輸入錯誤,請重新輸入");
      break;
    }
  } while (input);
}

看起來比上一種寫法干凈整潔了許多。

你是否還在尋找穩定的海外服務器提供商?創新互聯www.cdcxhl.cn海外機房具備T級流量清洗系統配攻擊溯源,準確流量調度確保服務器高可用性,企業級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧

當前名稱:C語言中多種指針相關類型詳解-創新互聯
當前鏈接:http://m.kartarina.com/article10/cciido.html

成都網站建設公司_創新互聯,為您提供全網營銷推廣網站設計公司靜態網站ChatGPT微信公眾號網頁設計公司

廣告

聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯

成都網頁設計公司
主站蜘蛛池模板: 成人无码视频97免费| 亚洲一本大道无码av天堂| 亚洲AV综合色区无码一区爱AV | 无码人妻丰满熟妇精品区| 久久久久久亚洲AV无码专区| 亚洲爆乳无码精品AAA片蜜桃| 国产午夜无码专区喷水| 亚洲色在线无码国产精品不卡| 久久精品无码一区二区三区日韩| 亚洲av无码一区二区三区天堂 | 无码人妻精品中文字幕免费东京热| 无码av天天av天天爽| 亚洲AV综合色区无码二区爱AV| 亚洲国产精品无码久久九九| 一本久道综合在线无码人妻 | 国产高清不卡无码视频| 乱色精品无码一区二区国产盗 | 精品无码久久久久国产动漫3d| 无码AⅤ精品一区二区三区| 精品无码国产自产在线观看水浒传| 国产精品无码av在线播放| 中文字幕无码久久人妻| 日韩AV无码一区二区三区不卡| 亚洲av无码专区在线电影天堂| 亚洲AV成人无码久久精品老人| 精品无码久久久久久久久久| 白嫩无码人妻丰满熟妇啪啪区百度| 无码av人妻一区二区三区四区 | 亚洲av无码无线在线观看 | 无码人妻啪啪一区二区| 中文字幕久久久人妻无码| 久久亚洲AV成人无码| 精品久久久无码人妻中文字幕| 无码少妇一区二区| 免费无遮挡无码永久视频| 亚洲日韩AV无码一区二区三区人| 色情无码WWW视频无码区小黄鸭| 亚洲AV无码成人精品区天堂| 亚洲AV无码成人专区片在线观看| 无码人妻精品中文字幕免费| 99国产精品无码|