40篇學完C語言——(第八篇)【指標陣列以及指向指標的指標】

語言: CN / TW / HK

陣列

int a[10];

int a[10]; //a代表的是陣列首元素的地址

a+1 步長 4

&a+1 步長 40 ;&a代表整個陣列的地址;指標也是一種資料型別,指標的步長就看他指向記憶體空間的型別; 所以記憶體空間加1就是地址偏移40

#define  _CRT_SECURE_NO_WARNINGS 
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void main(void)
{
    int a[10]; //a代表的是陣列首元素的地址  &a代表整個陣列的地址  a+1 4  &a+1步長 40 .

    //
    {
        //定義一個數組型別
        typedef int(myTypeArray)[10]; //
        myTypeArray myArray;
        myArray[0] = 10;
        myArray[1] = 11;
        printf("%d \n", myArray[0]);
        printf("%d \n", myArray[1]);
    }

    {
        //定義一個指標陣列型別 
        typedef int(*PTypeArray)[10];  //int *p 

        PTypeArray myPArray; //sizeof(int) *10
        myPArray = &a;
        //int b = 10;
        //int *p = NULL;
        //p = &b;
        (*myPArray)[0] = 20;

        printf("a[0]: %d \n", a[0]);

    }

    {
        //定義一個指向 陣列型別的指標 陣列類的指標

        int(*MyPointer)[10]; //變數 告訴C編譯器 給我分配記憶體
        MyPointer = &a;
        (*MyPointer)[0] = 40;
        printf("a[0]: %d \n", a[0]);
    }

    printf("hello...\n");
    system("pause");
    return;
}

1、為什麼需要指標?

指標解決了一些程式設計中基本的問題。

第一,指標的使用使得不同區域的程式碼可以輕易的共享記憶體資料。當然你也可以通過資料的複製達到相同的效果,但是這樣往往效率不太好,因為諸如結構體等大型資料,佔用的位元組數多,複製很消耗效能。但使用指標就可以很好的避免這個問題,因為任何型別的指標佔用的位元組數都是一樣的(根據平臺不同,有4位元組或者8位元組或者其他可能)。

第二,指標使得一些複雜的連結性的資料結構的構建成為可能,比如連結串列,鏈式二叉樹等等。

第三,有些操作必須使用指標。如操作申請的堆記憶體。還有:C語言中的一切函式呼叫中,值傳遞都是“按值傳遞”的,如果我們要在函式中修改被傳遞過來的物件,就必須通過這個物件的指標來完成。

2、指標是什麼?

指標是程式資料在記憶體中的地址,而指標變數是用來儲存這些地址的變數。

由於記憶體中的每一個位元組都有一個唯一的編號,因此,在程式中使用的變數,常量,甚至數函式等資料,當他們被載入到記憶體中後,都有自己唯一的一個編號,這個編號就是這個資料的地址。指標就是這樣形成的。

char ch = 'a';
int  num = 97;

我們可以大致畫出變數ch和num在記憶體模型中的儲存。(假設 char佔1個位元組,int佔4位元組)

3、指標與變數的關係

用來儲存 指標 的變數,就是指標變數。如果指標變數p1儲存了變數 num的地址,則就說:p1指向了變數num,也可以說p1指向了num所在的記憶體塊 ,這種指向關係,在圖中一般用 箭頭表示。

int main(void)
{
    int num = 97;
    int *p1  = #
    char* p2 = (char*)(#);
    printf("%d\n",*p1);    //輸出  97
    putchar(*p2);          //輸出  a
    return 0;
}

指標的值:很好理解,如上面的num 變數 ,其地址的值就是0028FF40 ,因此 p1的值就是0028FF40。資料的地址用於在記憶體中定位和標識這個資料,因為任何2個記憶體不重疊的不同資料的地址都是不同的。

指標的型別:指標的型別決定了這個指標指向的記憶體的位元組數並如何解釋這些位元組資訊。一般指標變數的型別要和它指向的資料的型別匹配。

由於num的地址是0028FF40,因此p1 和 p2的值都是0028FF40

*p1 : 將從地址0028FF40 開始解析,因為p1是int型別指標,int佔4位元組,因此向後連續取4個位元組,並將這4個位元組的二進位制資料解析為一個整數 97。

*p2 : 將從地址0028FF40 開始解析,因為p2是char型別指標,char佔1位元組,因此向後連續取1個位元組,並將這1個位元組的二進位制資料解析為一個字元,即’a’。

同樣的地址,因為指標的型別不同,對它指向的記憶體的解釋就不同,得到的就是不同的資料。

4、指標型別

5、陣列指標和指標陣列

“陣列指標”和“指標陣列”,只要在名詞中間加上“的”字,就知道中心了——

陣列的指標:是一個指標,什麼樣的指標呢?指向陣列的指標。

指標的陣列:是一個數組,什麼樣的陣列呢?裝著指標的陣列。

然後,需要明確一個**優先順序順序:()>[]>***,所以:

(*p)[n]:根據優先順序,先看括號內,則p是一個指標,這個指標指向一個一維陣列,陣列長度為n,這是“陣列的指標”,即陣列指標;

* p[n]:根據優先順序,先看[],則p是一個數組,再結合 ,這個陣列的元素是指標型別,共n個元素,這是“指標的陣列”,即指標陣列。

根據上面兩個分析,可以看出,p是什麼,則片語的中心詞就是什麼,即陣列“指標”和指標“陣列”。

int *p1[5];//指標的陣列
int (*p2)[5];//陣列的指標
首先,對於語句int p1[5],因為“[]”的優先順序要比 要高,所以 p1 先與“[]”結合,構成一個數組的定義,陣列名為 p1,而“int

”修飾的是陣列的內容,即陣列的每個元素。也就是說,該陣列包含 5 個指向 int 型別資料的指標,如圖 1 所示,因此,它是一個指標陣列。


p2)[5],“()”的優先順序比“[]”高,“*”號和 p2 構成一個指標的定義,指標變數名為 p2,而 int 修飾的是陣列的內容,即陣列的每個元素。也就是說,p2 是一個指標,它指向一個包含 5 個 int 型別資料的陣列,如圖 2 所示。很顯然,它是一個數組指標,陣列在這裡並沒有名字,是個匿名陣列。

由此可見,對指標陣列來說,首先它是一個數組,陣列的元素都是指標,也就是說該陣列儲存的是指標,陣列佔多少個位元組由陣列本身決定;而對陣列指標來說,首先它是一個指標,它指向一個數組,也就是說它是指向陣列的指標,在 32 位系統下永遠佔 4 位元組,至於它指向的陣列佔多少位元組,這個不能夠確定,要看具體情況。

5.1、 陣列指標 (*p)[n]

#include "stdafx.h"

int main()
{
    //一維陣列
    int a[5] = { 1, 2, 3, 4, 5 };
    //步長為5的陣列指標,即數組裡有5個元素
    int (*p)[5];
    //把陣列a的地址賦給p,則p為陣列a的地址,則*p表示陣列a本身
    p = &a;

    //%p輸出地址, %d輸出十進位制
    //\n回車
    //在C中,在幾乎所有使用陣列的表示式中,陣列名的值是個指標常量,也就是陣列第一個元素的地址,它的型別取決於陣列元素的型別。
    printf("%p\n", a); //輸出陣列名,一般用陣列的首元素地址來標識一個數組,則輸出陣列首元素地址
    printf("%p\n", p); //根據上面,p為陣列a的地址,輸出陣列a的地址
    printf("%p\n", *p); //*p表示陣列a本身,一般用陣列的首元素地址來標識一個數組
    printf("%p\n", &a[0]); //a[0]的地址
    printf("%p\n", &a[1]); //a[1]的地址
    printf("%p\n", p[0]); //陣列首元素的地址
    printf("%d\n", **p); //*p為陣列a本身,即為陣列a首元素地址,則*(*p)為值,當*p為陣列首元素地址時,**p表示首元素的值1
    printf("%d\n", *p[0]); //根據優先順序,p[0] 表示首元素地址,則*p[0]表示首元素本身,即首元素的值1
    printf("%d\n", *p[1]); //為一個絕對值很大的負數,不表示a[1]...表示什麼我還不知道

    //將二維陣列賦給指標
    int b[3][4];
    int(*pp)[4]; //定義一個數組指標,指向含4個元素的一維陣列
    pp = b; //將該二維陣列的首地址賦給pp,也就是b[0]或&b[0],二維陣列中pp=b和pp=&b[0]是等價的
    pp++; //pp=pp+1,該語句執行過後pp的指向從行b[0][]變為了行b[1][],pp=&b[1]

    int k;
    scanf_s("%d", &k);

    return 0;
}

根據上面二維陣列可以得出,陣列指標也稱指向一維陣列的指標,所以陣列指標也稱行指標。

5.2、指標陣列 *p[n]

指標陣列:是陣列——裝著指標的陣列。

看下面的例子進行理解:

陣列指標:是指標——指向陣列的指標。

看下面的例子進行理解

#include "stdafx.h"

int main()
{
    int a = 1;
    int b = 2;
    int *p[2];
    p[0] = &a;
    p[1] = &b;

    printf("%p\n", p[0]); //a的地址
    printf("%p\n", &a); //a的地址
    printf("%p\n", p[1]); //b的地址
    printf("%p\n", &b); //b的地址
    printf("%d\n", *p[0]); //p[0]表示a的地址,則*p[0]表示a的值
    printf("%d\n", *p[1]); //p[1]表示b的地址,則*p[1]表示b的值

    //將二維陣列賦給指標陣列
    int *pp[3]; //一個一維陣列記憶體放著三個指標變數,分別是p[0]、p[1]、p[2],所以要分別賦值
    int c[3][4];
    for (int i = 0; i<3; i++)
        pp[i] = c[i];

    int k;
    scanf_s("%d", &k);

    return 0;
}

最後,從上文來看:

陣列指標是一個指標變數,佔有記憶體中一個指標的儲存空間;

指標陣列是多個指標變數,以陣列的形式儲存在記憶體中,佔有多個指標的儲存空間。

6、舉例1

typedef struct
{
    unsigned short ad[3][5][100]; 
} photo_sample_data_stru;             
photo_sample_data_stru  *Photo_Sample_Read_Base_Point,Photo_Sample_Read_data ;

unsigned short data_0_0_x[100],data_0_1_x[100];
unsigned short   *Photo_Sample_Point0,*Photo_Sample_Point1;
uint32_t addr;
void data_init(void)
{
    uint16_t Photo_Sample_Line,photo_sample_time;
    for(Photo_Sample_Line=0;Photo_Sample_Line<5;Photo_Sample_Line++)
    {
        for(photo_sample_time=0;photo_sample_time<100;photo_sample_time++)
        {
            Photo_Sample_Read_Base_Point->ad[0][Photo_Sample_Line][photo_sample_time]= photo_sample_time; //第一路; 
            Photo_Sample_Read_Base_Point->ad[1][Photo_Sample_Line][photo_sample_time]= photo_sample_time; //第二路;
            Photo_Sample_Read_Base_Point->ad[2][Photo_Sample_Line][photo_sample_time]= photo_sample_time; //第三路;
        }
    }
}

void main()
{
    int num=2021;
    printf("num addr = 0x%x\r\n",(int)#);
    printf("num  = %d\r\n",num);
    printf("\r\n");

    Photo_Sample_Read_Base_Point = &Photo_Sample_Read_data;
    data_init();
    printf("Photo_Sample_Read_Base_Point addr = 0x%x\r\n",(int)(Photo_Sample_Read_Base_Point));
    printf("Photo_Sample_Read_Base_Point  = %d\r\n",(int)(Photo_Sample_Read_Base_Point->ad[0][0][0]));
    printf("\r\n");

    Photo_Sample_Point0 = Photo_Sample_Read_Base_Point->ad[0][0];
    printf("Photo_Sample_Point0 addr = 0x%x\r\n",(int)Photo_Sample_Point0);
    for( i=0;i<10;i++)
    {
        data_0_0_x[i]=Photo_Sample_Point0[i];
        printf("Photo_Sample_Point0[%d] = 0x%x \r\n",i,Photo_Sample_Point0[i]);
    }
    printf("\r\n");

    Photo_Sample_Point1 = Photo_Sample_Read_Base_Point->ad[0][1];
    printf("Photo_Sample_Point1 addr = 0x%x\r\n",(int)Photo_Sample_Point1);
    for( i=0;i<10;i++)
    {
        printf("Photo_Sample_Point1[%d] = 0x%x\r\n",i,Photo_Sample_Point1[i]);
        data_0_1_x[i]=Photo_Sample_Point1[i];
    }
    printf("\r\n");
}

先看輸出結果:

分析1:Photo_Sample_Read_Base_Point是指向Photo_Sample_Read_data的指標。Photo_Sample_Read_Base_Point地址是0x1000034C

分析2:Photo_Sample_Point0,Photo_Sample_Point1這兩個指標,以及指標給陣列賦值。

Photo_Sample_Point0 addr = 0x1000034c

Photo_Sample_Point1 addr = 0x10000414

Photo_Sample_Point0[i] 實際為Photo_Sample_Read_data.ad[0][0][i]
Photo_Sample_Point1[i] 實際為Photo_Sample_Read_data.ad[0][1][i]

7、舉例2:

typedef struct
{
    unsigned char photo_ad_data[20][2];     
} photo_sample_stru;                       
photo_sample_stru         *photo_data_point[6];        
static photo_sample_stru   pmt_Data_Load[6];
static unsigned int Data_Loc_o;
static unsigned int Data_Loc_p; 
void main()
{
    for (Data_Loc_o=0;Data_Loc_o<6;Data_Loc_o++)  //  清空資料緩衝區  
    {
        photo_data_point[Data_Loc_o] =  &pmt_Data_Load[Data_Loc_o];
            //pmt_first =  photo_data_point[0];
        for (Data_Loc_p=0;Data_Loc_p<20;Data_Loc_p++)                    
        {
            photo_data_point[Data_Loc_o]->photo_ad_data[Data_Loc_p][0] = 0;
            photo_data_point[Data_Loc_o]->photo_ad_data[Data_Loc_p][1] = 0;

        }
    }
}

結果如下:

分析: 我們下面看出,photo_sample_stru *photo_data_point[6]; 是指向photo_sample_stru 型別的 指標陣列 (即指標的陣列)。利用指標把pmt_Data_Load裡面資料清零。

8、舉例3

#define MAX_SEG         100
#define MAX_CYCLE       10 //最大迴圈個數的巨集定義

typedef  uint8_t UCHAR ;
typedef struct
{
   short           RunNow;                       //執行標誌(正常開機,斷電後重啟)
   short           RunEnd;                       //執行結束標誌
   short           FolderName[12];                //目錄名
   short           FileName[12];                  //檔名
   short           SaveYear;                      //年(檔案的儲存時間)
   short           SaveMonth;                     //月(檔案的儲存時間)
   short           SaveDate;                      //日(檔案的儲存時間)
   short           SaveHour;                      //時(檔案的儲存時間)
   short           SaveMinute;                    //分(檔案的儲存時間)
   short           SaveSecond;                    //秒(檔案的儲存時間)
   short           HotlidSet;                      //熱蓋設定
   short           VolumeSet;                     //樣本容量
   short           ModeSet;                       //執行的模式
   short           TestModeSet;                   //模擬模式
   short           FirstPause;                    //首節暫停
   short           EditSeg;                       //節點個數
   short           EditCycle;                     //迴圈個數
   short           TempSet[MAX_SEG];              //節點溫度
   short           HourSet[MAX_SEG];              //節點時間---時
   short           MinuteSet[MAX_SEG];            //節點時間---分鐘
   short           SecondSet[MAX_SEG];            //節點時間---秒
   short           SpeedSet[MAX_SEG];             //速度
   short           TempCYCSet[MAX_SEG];           //每個迴圈節點提升溫度
   short           MinuteCYCSet[MAX_SEG];         //分――每個迴圈節點增加時間
   short           SecondCYCSet[MAX_SEG];         //秒――每個迴圈節點增加時間
   short           GradSet[MAX_SEG];              //梯度 
   short           CycleSet[MAX_CYCLE];           //每個迴圈次數
   short           CycleBeginSet[MAX_CYCLE];      //迴圈開始節點
   short           CycleEndSet[MAX_CYCLE];        //迴圈終止節點
   short           GradExtSet[6][MAX_SEG];        //獨立執行資訊
   //short           AloneSet[MAX_SEG];             //分割槽設定
   short           TubeVolSet;                    //試管體積
   short           TubeTypeSet;                   //試管型別
   short           Fill[580];             //用於填充,結構總長度2Kwords
}StructFile;

typedef struct
{
    unsigned long    File_Add;//地址
    short   Seg;
    short   cyc_inside;
    short   cyc_outside;
    short   outside_flag;
    short   OverShootFlag;
    short   OverTime;
    short   Now_Temp;
}StructCalTime;

StructCalTime *Cal_Point;
StructCalTime Cal_TimeA;

void main()
{
    uint8_t *u8_point;
    u8_point = (uint8_t*)(&Cal_TimeA.File_Add);
    for(uint16_t i=1;i<sizeof(StructCalTime);i++)
    *(u8_point+i+4)=i;//賦值

    Cal_TimeA.File_Add=(unsigned long)(&Cal_TimeA);//取地址
    Cal_RunFile = *((StructFile*)(Cal_TimeA.File_Add));//重指向

//  Cal_RunFile = *((StructFile*)((unsigned long)(&Cal_TimeA)));//直接賦值
}