你必須知道的指針基礎-6.內存的初始化及結構體的使用

1、內存的使用

1.1 你建立的內存區域多是髒的

  當咱們建立一個內存區域的時候,內存中的數據多是亂七八糟的(多是其餘代碼用事後遺留的數據),以下面一段代碼:數組

int main(int argc, char *argv[])
{
    // 下面申請的20個字節的內存有可能被別人用過
    char chs[20];
    // 這個代碼打印出來的可能就是亂碼,由於printf的%s是「打印一直遇到'\0'」。
    printf("%s\n",chs);
    return 0;
}

  其運行結果是以下圖所示的亂碼,由於printf的%s是「打印一直遇到'\0'」。函數

1.2 解決髒內存區域的辦法

  那麼,如何解決上面咱們有可能會訪問的髒內存區域呢?在C語言中,能夠採用以下的兩種方法:spa

  (1)使用memset函數首先清理一下內存:指針

void *memset(void *s, int ch, unsigned n);code

將s所指向的某一塊內存中的每一個字節的內容所有設置爲ch指定的ASCII值,blog

塊的大小由第三個參數指定,這個函數一般爲新申請的內存作初始化工做,內存

其返回值爲指向S的指針。

  那麼,咱們可使用memset函數來清理內存,即填充建立的這塊內存區域:字符串

int main(int argc, char *argv[])
{
    // 下面申請的20個字節的內存有可能被別人用過
    char chs[20];
    // 這個代碼打印出來的可能就是亂碼,由於printf的%s是「打印一直遇到'\0'」。
    printf("%s\n",chs);
    
    // memset內存初始化:memset(void *,要填充的數據,要填充的字節個數)
    memset(chs,0,sizeof(chs));
    int i,len=sizeof(chs)/sizeof(char);
    for(i=0;i<len;i++)
    {
        printf("%d | ",chs[i]);
    }
    return 0;
}

  能夠看到該代碼的運行結果以下圖所示,將20個字節都置爲了0。get

  一般對於在堆空間中建立的內存區域,通常都會用到memset函數來清理。博客

  (2)使用初始化填充0:

   除了使用memset函數以外,另外一種比較直接的方式就是在初始化時直接指定要填充的數據,以下面的代碼:

int main(int argc, char *argv[])
{
    int i;
    char chs[20] = { 0 };
    for(i=0;i<sizeof(chs)/sizeof(char);i++)
    {
        printf("%d | ",chs[i]);
    }
    printf("\n");
    int nums[10] = { 7, 5 };
    for(i=0;i<sizeof(nums)/sizeof(int);i++)
    {
        printf("%d | ",nums[i]);
    }
    return 0;
}

  能夠看到運行結果:對於int數組,若是不爲其進行初始化,會默認填充0到內存區域。

2、結構體的使用

2.1 結構體的初始化

  結構體其實就是一大塊內存,咱們能夠對它進行格式化的存儲和讀取。以下代碼所示,咱們試着定義一個結構體:

struct _Person
{
    char *name; 
    int age; 
    double height; 
};

int main(int argc, char *argv[])
{
    struct _Person p1;
    // 不初始化內存區域是髒的
    printf("p1.age is %d\n",p1.age);
    return 0;
}

  在main函數中,咱們聲明瞭一個剛剛定義的結構體p1,可是咱們並無進行初始化。這時,會出現上面所提到的髒內存區域的問題,以下所示:

  如何解決呢,仍是採用上面所說的兩個辦法:

  (1)memset:

    // 方法一:使用memset進行清理
    memset(&p1,0,sizeof(struct _Person));
    printf("p1.age is %d\n",p1.age);
    p1.name = "周旭龍";
    p1.age = 26;
    printf("Name : %s , Age : %d\n",p1.name,p1.age);
    printf("p1.age is %d\n",p1.age);
    printf("------------------------------\n");

  注意這裏第三個參數是 sizeof(struct _Person)

  (2)初始化爲0:

    // 方法二:初始化
    struct _Person p2 = { 0 };
    p2.name = "劉德華";
    p2.age = 60;
    printf("Name : %s , Age : %d\n",p2.name,p2.age);

  兩塊代碼運行結果以下圖所示:

  第一行是未經清理的髒內存數據,第二部分是使用memset進行清理後再賦值的結果,第三部分是直接初始化後再賦值的結果。

2.2 包含指針的結構體大小

  對於普通數據類型的結構體,計算結構體的的大小是件容易的事。可是,若是是有包含有指針的結構體呢?我想,不少跟我同樣的菜鳥都會犯錯。那麼,咱們來看看剛剛那個結構體的大小是多少吧?

struct _Person
{
    char *name; // 指針爲4個字節,地址(int)
    int age; // 4個字節
    double height; // 8個字節
};

int main(int argc, char *argv[])
{
    struct _Person p1;
    printf("The size of p1 is %d\n",sizeof(struct _Person));

    return 0;
}

  經過sizeof函數計算該結構體的大小竟然爲16!沒錯,你沒有看錯!不是13,而是16。

  那麼,問題來了,爲何是16呢?原來,對於int、short等放到結構體中保存是佔用對應的字節,可是對於char*等,則只是保存它的指針(地址)。所謂地址,就是一個數字,那麼這裏就是一個整形數字表明內存地址,所以,它佔4個字節,4+4+8=16。

  那麼,問題又來了,假如我在main函數中,給name賦值了一個很長很長的字符串呢?

struct _Person p2 = { 0 };
p2.name = "劉德華劉德華劉德華劉德華劉德華劉德華劉德華劉德華劉德華劉德華";

  咱們再次經過sizeof計算大小,仍然是16!爲何呢,咱們能夠經過下面這張圖來看看:

  能夠看到,不管咱們爲name賦值多麼長的字符串,存儲的永遠只是一個指向具體字符串的指針,也就是一個地址(一個神奇的數字),結構體的大小不會由於具體指向的字符串的大小而變化。

2.3 使用typedef爲結構體取別名

  前面的代碼中,咱們每次使用結構體的時候都要聲明struct _Person ,好比:

struct _Person p1={0};
sizeof(struct _Person ); 

  這樣顯得比較麻煩,能夠藉助typedef來取一個別名:

typedef struct _Person
{
    char *name; // 指針爲4個字節,地址(int)
    int age; // 4個字節
    double height; // 8個字節
} Person;

int main(int argc, char *argv[])
{
    Person p = { 0 };
    p.name = "陳冠希";
    p.age = 34;
    printf("Name : %s , Age : %d\n",p.name,p.age);
    
    return 0;
}

  看看,是否是清爽得多?

3、結構體的拷貝賦值問題

3.1 結構體的複製實際上是「深拷貝」

  在C語言中,結構體的複製其實就是將總體拷貝一份而不是將地址拷貝一份,這與在.NET中的深拷貝的概念是相似的(深拷貝和淺拷貝是.NET中一個比較重要的概念)。例以下面一段代碼:

    Person p1 = { 0 };
    p1.name = "陳冠希";
    p1.age = 34;
    // 下面的複製實際上是拷貝了一份
    Person p2 = p1;
    p1.age = 100;
    printf("p1.Name : %s , p1.Age : %d\n",p1.name,p1.age);
    printf("p2.Name : %s , p2.Age : %d\n",p2.name,p2.age);
    printf("Address : %d , %d\n",&p1,&p2);

  從下面的運行結果能夠看出,即便咱們在拷貝後改變了原p1的age,但p2的age仍爲修改以前的值。最後,從兩個結構體的內存地址能夠看出,兩個結構體是相互獨立的內存空間(兩塊地址相隔了16個字節,恰好是該結構體的大小)。

3.2 如何實現結構體的「淺拷貝」

  假如咱們要在一個程序中屢次引用某個結構體,而不是但願每次複製都拷貝一份新的,這樣會增長內存使用量,也就是咱們在.NET中時常提到的淺拷貝(拷貝的只是引用地址)。因而,這時咱們就可使用一個指向結構體的指針來實現。

    Person* p3 = &p1;
    p1.age = 250;
    printf("p1.Name : %s , p1.Age : %d\n",p1.name,p1.age);
    printf("p3.Name : %s , p3.Age : %d\n",p3->name,p3->age);     // 對於結構體指針,取成員要使用->而不是.
    printf("Address : %d , %d\n",&p1,p3);

  這裏須要注意的就是,對於結構體指針,取成員要使用 -> 而不是 .

  再來看看運行結果,發現兩個地址同樣,說明都是使用的同一塊內存地址:

參考資料

  如鵬網,《C語言也能幹大事(第三版)》

 

相關文章
相關標籤/搜索