C提供了兩種類型的聚合數據類型,數組和結構.數組是相同元素的集合,它的每個元素是通過下標引用或指針間接訪問來選擇的.結構也是一些值的集合,這些值稱為它的成員但是一個結構類型的各個成員可能具有不同的類型;結構變量屬於標量類型,所以我們可以像對待其他標量類型一樣執行相同類型的操作.
一、聲明結構類型:
聲明結構的形式有多種方式,比如:
1、單純地聲明一個結構:
struct date
{
int month;
int day;
int year;
};
定義變量的方式為:struct date d1,d2; d1 和 d2 都是date ,裡面有month, day 和 year ;
在date的前面一定要加上struct關鍵字,不能單純地拿date來做類型的名字,要在date前面加上struct, 然後後面加上變量的名字.
2、聲明無名結構:
struct
{
int month;
int day;
int year;
}d1,d2;
struct
{
int month;
int day;
int year;
}*c;
這種方式沒有了名字,但是在大括號的後面跟了兩個變量d1 和 d2,d1 和 d2都是一種無名結構體,裡面依然有month , day 和 year,這種方式應用的場合是在程序員不打算在將來的某個場合再次使用這個結構體,而是在當前這個特定的場合只使用這麼一次,以後就不再使用,所以這種用法不太常見.
注:這兩個聲明被編譯器當作兩種截然不同的類型,即使它們的成員列表完全相同.因此,變量d1 和 c的類型是不同的,所以一下用法是非法的
c = &d1;
3、聲明一個結構並且定義變量:
struct date
{
int month;
int day;
int year;
}d1, d2;
對於第一和第三種形式,都聲明了結構體date,但是第二種形式沒有聲明date,只是定義了兩個變量d1 和 d2.
聲明結構時可以使用的另外一種良好的技巧是用typedef創建一個新的類型,在下文會有所提及.
二、結構的初始化
放在函數內部的變量我們叫做本地變量,本地變量是沒有默認的初始值的,如果沒有給定初始值的話,那麼本地變量的值將無法預測,結構體也不例外,所以我們需要給一個結構變量賦初始值.
在對數組賦初值的時候,我們可以用一對大括號“{ }”,來對數組賦初值,同樣我們也可以用一對大括號“ { }”來對結構進行賦初值.
(以上面聲明的結構體為例)
1、對整個結構賦初值:struct date today = {08, 21, 2016};
2、對結構的部分成員賦初值:struct date today = {.month = 8, .year = 2016 };
和數組的賦值是一樣的,如果只是對結構體的部分成員進行賦初值,那麼,那些沒有被賦初值的成員的值將會被初始化為0;
三、結構成員
結構和數組有點像,數組裡頭有很多的單元,結構裡頭有很多的成員,不一樣的是數組的單元必須是相同的數據類型,而結構的成員可以是不同類型
數組用“ [ ] ” 運算符和下標訪問其成員 a[ 0 ] = 1;
結構用“ . ” 運算符和名字來訪問其成員
today.day 、 today.year
p1.x 、p1.y
其中today 和 p1都是結構變量
四、 結構運算
1、要訪問整個結構,可以直接用結構變量的名字
2、 對於整個結構,可以做賦值、取地址, 也可以傳遞給函數參數
(1)、today = (struct date){ 8, 21, 2016}; //相當於today.month = 8; today.day = 21; today.year = 2016;
(2)、today1 = today2; //相當於today1.month = today2.month; today1.day = today2.day; today1.year = today2.year;
五、結構指針
和數組不同,結構變量的名字並不是結構變量的地址,必須使用&運算符,如:
struct date *pDate = &today;
六、結構作為函數參數
int numberofDays(struct date d)
1、整個結構可以作為參數的值傳入函數
2、這時候是在函數內新建一個結構變量,並復制調用者的結構的值
3、也可以返回一個結構
七、輸入一個結構
傳入結構和傳入數組是不一樣的,把一個結構傳入函數,然後在函數中操作,但是操作完的值卻沒有返回回去,問題就在於傳入函數的是外面那個結構的克隆體,而不是指針
沒有直接的方式可以一次scanf一個結構,所以有以下兩種方式:
1、在這個輸入函數中,完全可以創建一個臨時的結構變量,然後把這個結構返回給調用者.
struct date getStruct(void)
{
struct date today;
scanf("%d", &today.month);
scanf("%d", &today.day);
scanf("%d", &today.year);
return today;
}
void outPut(struct date p)
{
printf("%d, %d\n", p.month, p.day, p.year);
}
int main(int argc, char const argv[ ])
{
struct date y = {0, 0, 0};
y = getStruct();
outPut(y);
return 0;
}
2、結構指針作為參數
結構指針的操作方式:
struct date today;
struct date *p = &today;
(*p).month = 12;
p->month = 12;//推薦
“->”表示指針所指的結構變量中的成員
注:”->”的左邊跟的一定是一個指針
struct date* getStruct(structdate *p)
{
scanf("%d", &p->month);
scanf("%d", &p->day);
scanf("%d", &p->year);
printf("%d, %d, %d\n", p->month, p->day , p->year);
return p;
}
void pintf(const struct date *p)
{
printf("%d, %d, %d\n", p->month, p->day, p->year);
}
把結構體指針作為返回值返回的好處是能夠將該返回的指針作為函數參數傳入另外一個函數中,可以進行巧妙的操作,操作如下:
int main(int argc, char const argv[ ])
{
struct Point y = {0, 0, 0};
print(getStruct(&y));
return 0;
}
當然,我們也可以把返回值給去掉
void getStruct(structdate *p)
{
scanf("%d", &p->month);
scanf("%d", &p->day);
scanf("%d", &p->year);
printf("%d, %d, %d\n", p->month, p->day , p->year);
}
int main(int argc, char const argv[ ])
{
struct date y = {0, 0, 0};
outPut(*getStruct(&y)); //等價於getStruct(&y); outPut(y);
return 0;
}
八、結構數組
struct date days[5]; //長度為5的結構體數組
struct date days[ ] = { {4, 5, 2005}, { 2, 4, 2005} };
int main(void)
{
struct date testTimes[5] = { {11, 59,2011}, {12, 0 , 2012}, { 1, 29,2013}, {23, 59, 2014}, {19 , 12,2015}};
int i;
for(i = 0; i< 5; ++i)
{
printf("Time is %.2i : %.2i : %.2i\n",
testTimes[i].hour, testTimes[i].minuters, testTimes[i].seconds);//結構體數組的引用方式
}
return 0;
}
九、結構中的結構(嵌套的結構)
struct point
{
int x;
int y;
};
struct rectangle
{
struct point pt1;
struct point pt2;
};
如果有變量 struct rectangle r;
就可以有:
r.pt1.x、 r.pt1.y,
r.pt2.x、r.pt2.y
如果有變量定義:
struct rectangle r, *rp;
rp = &r;
下面的四種形式是等價的:
r.pt1.x <=> rp->pt1.x <=> (r.pt1).x <=> (rp->pt1).x
但是沒有rp->pt1->x (因為pt1不是指針)
十、結構中的結構的數組
struct point
{
int x;
int y;
};
struct rectangle
{
struct point p1;
struct point p2;
};
void printRect(struct rectangle r)
{
printf("<%d, %d> to <%d, %d>\n", r.p1.x, r.p1.y , r.p2.x, r.p2.y);
}
int main(int argc, char const *argv[ ])
{
int i;
struct rectangle rects[ ] = {
{{1, 2}, { 3, 4}}, //這兩行是屬於兩個結構,兩個結構中又有兩個結構
{{5, 6}, { 7, 8}}
};
for(i=0; i<2; i++)
printRect(rects[i]);
return 0;
}
十一、typedef與結構
typedef用來聲明新的類型名字,新的名字是某種類型的別名
用typedef來聲明新的名字常常會忘記原來的類型和新的類型名字順序應該怎樣寫:
typedef long int64_t;
中間的那個是舊的那個類型名,最後面的那個單詞才是新的類型名字.
在聲明結構的時候可以用typedef對該結構類型起一個新的別名,這樣以後再定義結構體變量的時候就不用再帶上struct關鍵字:
typedef struct date
{
int month;
int day;
int year;
}Date;
可以直接用Date來定義變量:
Date d = { 9, 1, 2005};
typedef struct
{
int month;
int day;
int year;
}Date;
如果沒有這個typedef,那麼我們現在在聲明一個無名結構,Date將會是它的一個變量名,但是現在有了這個typedef關鍵字之後
Date就不是這個結構的變量,而是變成了這個結構的一個新的別名,至於原來的struct是什麼名字,我們已經不關心了,因為我們有
一種更好的方式去表達它了.
十二、結構的自引用
在一個結構內部包含一個類型為該結構本身的成員是否合法:
1、
struct date
{
int a;
struct date b;
int c;
};
這種自引用時非法的,因為成員b是另一個完整的結構,其內容還將包含它自己的成員b. 這第二個成員又是另一個完整的結構,它還將包含自己的成員b,這樣重復永無止境.
2、
struct date
{
int a;
struct date *b;
int c;
};
這個聲明是合法的,區別就在於b現在是個指針而不是結構.編譯器在結構的長度確定之前就已經知道指針的長度,所以這種類型的自引用時合法的.
3、
typedef struct
{
int a;
struct Date*b;
int c;
}Date;
這個聲明的目的是為這個結構起一個新的別名Date,但是,這是錯誤的,因為類型名直到聲明的末尾才定義,所以在結構聲明的內部它尚未定義.
解決的方法如下:
typedef struct date
{
int a;
struct date*b;
int c;
}Date;
注:和其他的數據類型一樣,如果結構在函數的內部被聲明的話,那麼該結構體就只能在這個函數中使用,不能被外部其他的函數使用,所以,聲明結構體通常在函數的外部聲明,這樣就能夠被多個函數使用。