1. 程式人生 > >指標陣列&陣列指標的分配記憶體及函式引數 C語言版

指標陣列&陣列指標的分配記憶體及函式引數 C語言版

最近寫程式碼總是被基礎知識卡住,十分耽誤時間,今天又卡住了。所以下定決心一定要弄清楚這個問題,然後不好的是網上的資料總是解決的並不清楚,總是在糾結什麼是指標陣列什麼是陣列指標,看了《C缺陷和陷阱》也沒有我想要的東西。要麼就是C和C++混為一談,new int[5]這種語法C肯定是錯誤的,所以找個機會總結一下,然後希望以後不要再栽在基礎上。

定義

指標陣列:int p[10] 陣列指標 int(*p)[5]。[]的優先順序高所以 int *p[10]表示陣列中都是int ,而p指向陣列首地址。沒圖說個p
這裡寫圖片描述
反之int(*p)[5]表示 *p指向一個大小為5的陣列,p則是二維陣列的首地址。
這裡寫圖片描述

使用

指標陣列-本質是一個數組

    int *p[10];
    printf("%d\n",sizeof(p) );//是40
    for (i = 0; i < 10; ++i)
    {
        p[i] = malloc(sizeof(int)*i);
    }
    for (i = 0; i < 10; ++i)
    {
        free(p[i]);
    }

從sizeof(p)=40可以看出來p指向的是陣列的首地址,陣列也被分配到了棧上,所以只需要對其中10個int *分配就ok,free也是。對應的二維矩陣像是p[10][i]

陣列指標-本質是一個指標

    int (*p)[10];
    p = malloc(sizeof(int *)*2);
    printf("%d\n",sizeof(p) );
    free(p);

從sizeof(p)=4看出來這就是個指標,指標就一定要分配記憶體了,但是sizeof裡面只需要分配指標大小就可以。free對著p。這時p被分配到了堆。對應的二維矩陣像是p[i][10]

結構體

當然了只使用基本型別沒什麼難度,遇到結構體有可能就懵逼了。基本的原理都是一樣的

typedef struct block
{
    int a;
    int b;
    int c;
}block_t;

建議不要這麼定義結構體

typedef struct block
{
    int a;
    int b;
    int c;
}block_t *;

莫名給自己加個指標這種程式碼我怎麼也看不懂。。。。而且對你理解問題會造成困擾。如果你得到的是block_t *那麼使用->a其實計算的是對應首地址的偏移量,如果是block_t那麼使用.表示裡面的變數。
指標陣列

    block_t *x[5];
    for ( i = 0; i < 5; ++i)
    {
        x[i] = malloc(sizeof(block_t)*i);
    }
    x[2][1].a = 1;
    printf("%d\n", (*(x+2)+1)->a);

陣列指標

    block_t (*x)[5];
    int *a;
    x = malloc(sizeof(block_t *)*2);
    x[1][5].a = 5;
    x[1][3].b = 1;
    printf("%d\n",x[1][5].a );
    printf("%d\n",sizeof(x[1]));

    printf("%p %p %p %p\n",a,x,x[0],x[1] );

這個基本就可以看出是在棧還是堆分配的記憶體了

函式呼叫

往往我們還需要傳入函式,這時指標可能會讓你頭暈,或者不清楚是否傳入的是引用還是地址。
先從簡單說起吧,如果我們在函式中需要改變p,而我們的函式是這樣的

void fun(int p);

那肯定改變不了,這是最基本的知識了。
我們應該這麼做

void fun(int *p);

然後

fun(&p);

沒有任何問題吧。
那我們這樣行嗎?

int *p;
void fun(int p);
fun(*p);

別暈,不行,不是有了指標就ok了,傳入的依然是*p指向的int的引用。有了這點基礎,再加上我們上面對指標陣列和陣列指標本質的理解就應該可以想明白函式引數應該有幾個指標了。看個例子


void for_each(block_t *b)
{
    int i;
    for (i = 0; i < 5; ++i)
    {
        printf("%d\n",b[i].a );
    }
}

void test_fun(block_t *b)
{
    b = malloc(sizeof(block_t)*5);
    int i;
    for (i = 0; i < 5; ++i)
    {
        b[i].a = i;
    }
}
int main()
{
    block_t *b;
    int i;
    test_fun(b);
    for_each(b);
}

上面的例子能達到目的麼?不能,為什麼因為b是指向一個數組的首地址,傳入引數傳入的是引用,不是這個首地址的地址。。(真特麼繞,意思就是傳入的這個引數實際是引用,因為他是陣列的首地址)怎麼辦,加指標啊!

void for_each(block_t *b)
{
    int i;
    for (i = 0; i < 5; ++i)
    {
        printf("%d\n",b[i].a );
    }
}

void test_fun(block_t **b)
{
    *b = malloc(sizeof(block_t)*5);
    int i;
    for (i = 0; i < 5; ++i)
    {
        (*b+i)->a = i;
    }
}
int main()
{
    block_t *b;
    int i;
    test_fun(&b);
    for_each(b);
}

ok搞定!
上面的例子其實都是一維指標,然後傳入二維地址,跟我們的指標陣列和陣列指標(二維)沒有關係。但是記住,確定指標的維數然後再做就會減少犯錯的機會。還有就是指標陣列的地址是在棧分配的,所以如果在函式中分配記得分配至堆上或者從呼叫者傳入,(int **p)。
陣列指標多用於二維陣列傳入函式時的函式簽名中的型別定義,什麼意思

int a[][5];
void fun(int (*a)[]);
fun(a);

C裡面必須定義二維陣列的寬度,而且必須是編譯時可以確定的!C++是不一樣的