iOS開發系列--C語言之構造型別

KenshinCui發表於2014-07-20

概述

在第一節中我們就提到C語言的構造型別,分為:陣列、結構體、列舉、共用體,當然前面陣列的內容已經說了很多了,這一節將會重點說一下其他三種型別。

  1. 結構體
  2. 列舉
  3. 共用體

結構體

陣列中儲存的是一系列相同的資料型別,那麼如果想讓一個變數儲存不同的資料型別就要使用結構體,結構體定義類似於C++、C#、Java等高階語言中類的定義,但事實上它們又有著很大的區別。結構體是一種型別,並非一個變數,只是這種型別可以由其他C語言基本型別共同組成。

//
//  main.c
//  ConstructedType
//
//  Created by Kenshin Cui on 14-7-18.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

//結構體型別Date
struct Date{
    int year;
    int month;
    int day;
};

struct Person{
    char *name;
    int age;
    struct Date birthday;//一個結構體中使用了另一個結構體型別,結構體型別變數宣告前必須加上struct關鍵字
    float height;
};

int main(int argc, const char * argv[]) {
    struct Person p={"Kenshin",28,{1986,8,8},1.72};
    //定義結構體變數並初始化,不允許先定義再直接初始化,例如:struct Person p;p={"Kenshin",28,{1986,8,8},1.72};是錯誤的,但是可以分別賦值,例如p.name="Kenshin"
    
    printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",p.name,p.age,p.birthday.year,p.birthday.month,p.birthday.day,p.height); 
    //結果:name=Kenshin,age=28,birthday=1986-8-8,height=1.72,結構體的引用是通過"結構體變數.成員名稱"(注意和結構體指標訪問結構體成員變數區分,結構體指標使用p->a的形式訪問)
    
    printf("len(Date)=%lu,len(Person)=%lu\n",sizeof(struct Date),sizeof(struct Person)); 
    //結果:len(Date)=12,len(Person)=32
    
    return 0;
}

對於上面的例子需要做出如下說明:

  1. 可以在定義結構體型別的同時宣告結構體變數;
  2. 如果定義結構體型別的同時宣告結構體變數,此時結構體名稱可以省略;
  3. 定義結構體型別並不會分配記憶體,在定義結構體變數的時候才進行記憶體分配(同基本型別時類似的);
  4. 結構體型別的所佔用記憶體大型等於所有成員佔用記憶體大小之和(如果不考慮記憶體對齊的前提下);

對第4點需要進行說明,例如上面程式碼是在64位編譯器下執行的結果(int長度4,char長度1,float型別4),Date=4+4+4=12。但是對於Person卻沒有那麼簡單了,因為按照正常方式計算Person=8+4+12+4=28,但是從上面程式碼中給出的結果是32,為什麼呢?這裡不得不引入一個概念“記憶體對齊”,關於記憶體對齊的概念在這裡不做詳細說明,大家需要了解的是:在Mac OS X中對齊引數預設為8(可以通過在程式碼中新增#pragma pack(8)改變對齊引數),如果結構體中的型別不大於8,那麼結構體長度就是其成員型別之和,但是如果成員變數的長度大於這個對齊引數那麼得到的結果就不一定是各個成員變數之和了。Person型別的長度之所以是32,其實主要原因是因為Date型別長度12在儲存時其偏移量12不是8的倍數,考慮到記憶體對齊的原因需要新增4個補齊長度,這裡使用表格的形式列出了具體原因:

memoryAlign

表格具體來源請觀看下面的視訊(注意由於錄製軟體的原因前幾秒不清晰但是不影響分析):

<embed>

接下來看一下結構體陣列、指向結構體的指標:

//
//  main.c
//  ConstructedType
//
//  Created by Kenshin Cui on 14-7-18.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

struct Date{
    int year;
    int month;
    int day;
};

struct Person{
    char *name;
    int age;
    struct Date birthday;
    float height;
};

void changeValue(struct Person person){
    person.height=1.80;
}

int main(int argc, const char * argv[]) {
    struct Person persons[]={
        {"Kenshin",28,{1986,8,8},1.72},
        {"Kaoru",27,{1987,8,8},1.60},
        {"Rosa",29,{1985,8,8},1.60}
    };
    for (int i=0; i<3; ++i) {
        printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",
               persons[i].name,
               persons[i].age,
               persons[i].birthday.year,
               persons[i].birthday.month,
               persons[i].birthday.day,
               persons[i].height);
    }
    /*輸出結果:
     name=Kenshin,age=28,birthday=1986-8-8,height=1.72
     name=Kaoru,age=27,birthday=1987-8-8,height=1.60
     name=Rosa,age=29,birthday=1985-8-8,height=1.60
     */
    
    
    
    struct Person person=persons[0];
    changeValue(person);
    printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",
           persons[0].name,
           persons[0].age,
           persons[0].birthday.year,
           persons[0].birthday.month,
           persons[0].birthday.day,
           persons[0].height);
    /*輸出結果:
     name=Kenshin,age=28,birthday=1986-8-8,height=1.72
     */
    
    
    struct Person *p=&person;
    printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",
           (*p).name,
           (*p).age,
           (*p).birthday.year,
           (*p).birthday.month,
           (*p).birthday.day,
           (*p).height);
    /*輸出結果:
     name=Kenshin,age=28,birthday=1986-8-8,height=1.72
     */
    printf("name=%s,age=%d,birthday=%d-%d-%d,height=%.2f\n",
           p->name,
           p->age,
           p->birthday.year,
           p->birthday.month,
           p->birthday.day,
           p->height);
    /*輸出結果:
     name=Kenshin,age=28,birthday=1986-8-8,height=1.72
     */
    
    return 0;
}

結構體作為函式引數傳遞的是成員的值(值傳遞而不是引用傳遞),對於結構體指標而言可以通過”->”操作符進行訪問。

列舉

列舉型別是比較簡單的一種資料型別,事實上在C語言中列舉型別是作為整形常量進行處理的,通常稱為“列舉常量”。

//
//  main.c
//  ConstructedType
//
//  Created by Kenshin Cui on 14-7-18.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

enum Season{ //預設情況下spring=0,summer=1,autumn=2,winter=3
    spring,
    summer,
    autumn,
    winter
};

int main(int argc, const char * argv[]) {
    enum Season season=summer; //列舉賦值,等價於season=1
    printf("summer=%d\n",season); //結果:summer=1
    
    for(season=spring;season<=winter;++season){
        printf("element value=%d\n",season);
    }
    /*結果:
     element value=0
     element value=1
     element value=2
     element value=3
     */
    return 0;
}

需要注意的是列舉成員預設值從0開始,如果給其中一個成員賦值,其它後面的成員將依次賦值,例如上面如果summer手動指定為8,則autumn=9,winter=10,而sprint還是0。

共用體

共用體又叫聯合,因為它的關鍵字是union(貌似資料庫操作經常使用這個關鍵字),它的使用不像列舉和結構體那麼頻繁,但是作為C語言中的一種資料型別我們也有必要弄清它的用法。從前面的分析我們知道結構體的總長度等於所有成員的和(當然此時還可能遇到對齊問題),但是和結構體不同的是共用體所有成員共用一塊記憶體,順序從低地址開始存放,一次只能使用其中一個成員,union最終大小由共用體中最大的成員決定,對某一成員賦值可能會覆蓋另一個成員。

//
//  main.c
//  ConstructedType
//
//  Created by Kenshin Cui on 14-7-20.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#include <stdio.h>

union Type{
    char a;
    short int b;
    int c;
};

int main(int argc, const char * argv[]) {
    union Type t;
    t.a='a';
    t.b=10;
    t.c=65796;
    
    printf("address(Type)=%x,address(t.a)=%x,address(t.b)=%x,address(t.c)=%x\n",&t,&t.a,&t.b,&t.c);
    //結果:address(Type)=5fbff7b8,address(t.a)=5fbff7b8,address(t.b)=5fbff7b8,address(t.c)=5fbff7b8
    
    printf("len(Type)=%d\n",sizeof(union Type));
    //結果:len(Type)=4
    
    printf("t.a=%d,t.b=%d,t.c=%d\n",t.a,t.b,t.c);
    //結果:t.a=4,t.b=260,t.c=65796
    
    return 0;
}

 

這裡需要重點解釋一個問題:為什麼t.a、t.b、t.c輸出結果分別是4、260、65796,當然t.c等於65796並不奇怪,但是t.a前面賦值為’a’不應該是97嗎,而t.b不應該是10嗎?其實如果弄清這個問題共用體的概念基本就清楚了。

根據前面提到的,共用體其實每次只能使用其中一個成員,對於上面的程式碼經過三次賦值最終使用的其實就是t.c,而通過上面的輸出結果我們也確實看到c是有效的。共用體有一個特點就是它的成員儲存在同一塊記憶體區域,這塊區域的大小需要根據它的成員中長度最大的成員長度而定。由於上面的程式碼是在64位編譯器下編譯的,具體長度:char=1,short int=2,int=4,所以得出結論,Type的長度為4,又根據上面輸出的地址,可以得到下面的儲存資訊(注意資料的儲存方式:高地址儲存高位,低地址儲存地位):

union

當讀取c的時候,它的二進位制是“00000000  00000001  00000001  00000100”,換算成十進位制就是65796;而經過三次賦值後,此時b的儲存就已經被c成員的低位資料覆蓋,b的長度是二,所以從起始地址取兩個位元組得到的二進位制資料此時是“00000001  00000100”(b原來的資料已經被c低2位資料覆蓋,其實此時就是c的低2位資料),換算成十進位制就是260;類似的a此時的資料就是c的低一位資料”00000100”,換算成十進位制就是4。

相關文章