495 - 第二章 結(jié)構(gòu)、聯(lián)合和枚舉 41-50
結(jié)構(gòu)葬项、聯(lián)合和枚舉的相似點是可以定義新的類型泞当,首先,通過聲明結(jié)構(gòu)和聯(lián)合的成員或域或者構(gòu)成枚舉的常量來定義新的類型民珍。同時襟士,也可能需要給新類型賦一個標(biāo)簽(tag),以便在以后引用嚷量。定義新的類型之后陋桂,就可以立即或者稍后(通過使用標(biāo)簽)來聲明這個類型的實例了
更麻煩的事,也可以使用typedef來為用戶定義類型定義新的類型名稱蝶溶,如對其他任何類型一樣嗜历。但是,如果這樣做抖所,必須意識到類型定義名稱和標(biāo)簽名沒有任何關(guān)系
41 下面這兩個聲明有什么不同
struct x1 {...};
typedef struct {...} x2;
答:第一種形式聲明聲明了一個“結(jié)構(gòu)標(biāo)簽”梨州;第二種聲明了一個“類型定義”。主要區(qū)別在于第二種聲明更抽象以下--用戶不必知道它是一個結(jié)構(gòu)田轧,并且在聲明它的實例事也不需要使用struct關(guān)鍵字.
x2 b;
但使用標(biāo)簽聲明的結(jié)構(gòu)就必須用這樣的形式進(jìn)行定義
struct x1 a;
//(也可以同時使用兩種方法:
typedef struct x3 {...} x3;
//盡管有些晦澀暴匠,但為標(biāo)簽和類型定義使用同樣的名稱是合法的,因為它們處于獨立的命名空間中
42.這樣的代碼對不對
strcut x {...};
x thestruct;
答:C不是C++傻粘。不能用結(jié)構(gòu)標(biāo)簽自動生成類型定義名每窖。事實上,C語言中的結(jié)構(gòu)是這樣用關(guān)鍵字struct聲明的:
struct x thestruct;
如果你愿意弦悉,也可以在聲明結(jié)構(gòu)的時候聲明一個類型定義窒典,然后再用類型定義名稱去聲明真正的結(jié)構(gòu);
typedef struct {...} tx;
tx thestruct;
43.結(jié)構(gòu)可以包含自己的指針嗎稽莉?
答:當(dāng)然可以崇败,但如果你要使用typedef,則有可能產(chǎn)生問題。
44.在C語言中使用什么方法實現(xiàn)抽象數(shù)據(jù)類型最好后室?
答:讓客戶使用指向沒有公開定義(也許還隱藏在類型定義后邊)的結(jié)構(gòu)類型的指針是一個好辦法缩膝。換而言之,客戶使用結(jié)構(gòu)指針(及調(diào)用輸入和返回結(jié)構(gòu)指針的函數(shù))而不知道結(jié)構(gòu)的成員是什么岸霹,只要不需要結(jié)構(gòu)的細(xì)節(jié)--也就是說疾层,只要不使用->\sizeof\操作符幾真是的結(jié)構(gòu)聲明--C語言事實上可以正確處理不完全類型的結(jié)構(gòu)指針。只有在實現(xiàn)抽象數(shù)據(jù)類型的源文件中才需要次范圍內(nèi)的結(jié)構(gòu)的完整聲明
45贡避。在C語言中是否有模擬繼承等面向?qū)ο蟪绦蛟O(shè)計特性的好方法痛黎?
答:把函數(shù)指針直接加入到結(jié)構(gòu)中就可以實現(xiàn)簡單的“方法”。你可以使用各種不雅而暴力的方法來實現(xiàn)繼承刮吧,例如通過預(yù)處理機(jī)或讓“基類”的結(jié)構(gòu)作為初始的子集湖饱,但這些方法都不完美。很明顯杀捻,也沒有操作符的重載和覆蓋(例如井厌,“派生類”中的“方法”,那些必須人工去左)致讥。
47.我遇到這樣聲明結(jié)構(gòu)的代碼
struct name
{
int name[];
char namestr[1];
};
然后又使用一些內(nèi)存分配技巧事namestr數(shù)組用起來好像有多個元素仅仆,namelen記錄了元素的個數(shù),它是怎樣工作的垢袱?這樣事合法的可移植的嗎墓拜?
答:不清楚這樣做是否合法或者可移植。但這種技術(shù)十分普遍请契。這種技術(shù)的某種實現(xiàn)可能像這個樣子:
#include <stdlib.h>
#include <string.h>
struct name *makename (char *newname) {
struct name *ret = malloc(sizeof(struct name) - 1 + strlen(newname) + 1);
if(ret != NULL) {
ret->namelen = strlen(newname);
strcpy(ret->namestr, newname);
}
return ret;
}
這個函數(shù)分配了一個name結(jié)構(gòu)的實例并調(diào)整它的大小咳榜,以便將請求的名稱(不是結(jié)構(gòu)聲明所示的僅僅一個字符)置入namestr域中。
雖然很流行爽锥,但這種技術(shù)也在某種程度上惹人非議贿衍。Dennis Ritchie就稱之為“和C實現(xiàn)的無保證的親密接觸”,官方的解釋認(rèn)定他沒有嚴(yán)格遵守C語言標(biāo)準(zhǔn)救恨。(關(guān)于這種技術(shù)的合法性的完整討論超出了本書的范圍),這種技術(shù)也不能保證在所有的實現(xiàn)上是可移植的释树,(仔細(xì)檢查數(shù)組邊界的編譯器可能會發(fā)出警告)肠槽。
另一種可能是吧變長的元素聲明成很大,而不是很小奢啥。上面的例子可以這樣改寫:
#include <stdlib.h>
#include <string.h>
struct name
{
int nemalen;
char namestr[MAX];
};
struct name *makename (char *newname)
{
struct name *ret = malloc(sizeof(struct name) - MAX +strlen(newname) + 1);
if(ret !=NULL) {
ret->namelen = strlen(newname);
strcpy(ret->namestr, newname);
}
return ret;
}
當(dāng)然秸仙,此處的MAX應(yīng)該比任何可能存儲的名字長度都打。但是桩盲,這種技術(shù)似乎也不完全符合標(biāo)準(zhǔn)的嚴(yán)格解釋:
當(dāng)然寂纪,真正安全的正確的做法是使用字符指針,而不是數(shù)組
#include <stdlib.h>
#include <string.h>
struct name
{
int nemalen;
char *namep;
};
struct name *makename (char *newname)
{
struct name *ret = malloc(sizeof(struct name));
if(ret !=NULL) {
ret->namelen = strlen(newname);
ret->namep = malloc(ret->namelen + 1);
if(ret->namep ==NULL) {
free(ret);
return NULL;
}
strcpy(ret->namestr, newname);
}
return ret;
}
顯然,把長度和字符串保存在同一塊內(nèi)存中的“方便”已經(jīng)不復(fù)存在了捞蛋,而且在釋放這個結(jié)構(gòu)的實例的時候需要兩次調(diào)用free孝冒。
如果像上面的例子那樣,存儲的數(shù)據(jù)類型是字符拟杉,那么為保持連續(xù)性庄涡,可以直截了當(dāng)?shù)膶纱蝝alloc調(diào)用合成一次,(這樣也可以只用一次調(diào)用free就能釋放)搬设。
struct name makename(char *newname)
{
char *buf = malloc((struct name) + strlen(newname) +1);
struct name *ret = (struct name *)buf;
ret->namelen = strlen(newname);
ret->namep = buf + sizeof(struct name);
strcpy(ret->namep, newname);
return ret;
};
但是穴店,想這樣用一次malloc調(diào)用將第二個區(qū)域接上的技巧只有在第二個區(qū)域是char型數(shù)組的時候才可移植,對于任何大一些的類型拿穴,對齊變得十分重要泣洞,必須保持
49.為什么不能用內(nèi)建的==和!=操作符比較結(jié)構(gòu)默色?
答:沒有一個好的球凰,符合C語言的底層特性的方法讓編譯器來實現(xiàn)結(jié)構(gòu)比較。簡單的按字節(jié)比較的方法可能會在遇到結(jié)構(gòu)中沒有使用的“東 hole”的隨機(jī)內(nèi)容的時候失敗该窗。而安域比較在處理大結(jié)構(gòu)時可能需要難以接受的大量重復(fù)代碼弟蚀。任何編譯器生成的比較代碼都不能期望在所有情況下都能正確比較指針域。例如比較char * 域的時候一般都希望使用strcmp而不是==酗失;如果需要比較兩個結(jié)構(gòu)义钉,必須自己寫函數(shù)按域比較。
50.結(jié)構(gòu)傳遞和返回是如何實現(xiàn)的规肴?
答:當(dāng)結(jié)構(gòu)作為函數(shù)參數(shù)傳遞的時候捶闸,通常會把整個結(jié)構(gòu)都推進(jìn)棧,需要多少空間就使用多少空間拖刃。(正是為了避免這個代價删壮,程序員經(jīng)常使用指針而不是結(jié)構(gòu))。某些編譯器僅僅傳遞一個結(jié)構(gòu)的指針兑牡,但是為了保證按值傳遞的語義央碟,它們可能不得不保留一份局部fuben。
編譯器通常會提供一個額外的“隱藏”參數(shù)均函,用于指向函數(shù)返回的結(jié)構(gòu)亿虽,有些老式的編譯器使用一個特殊的靜態(tài)位置來返回結(jié)構(gòu),這回導(dǎo)致返回結(jié)構(gòu)的函數(shù)不可再入苞也,這是ANSIC所不允許的洛勉。