495 - 第二章 结构、联合和枚举 41-50
结构、联合和枚举的相似点是可以定义新的类型,首先,通过声明结构和联合的成员或域或者构成枚举的常量来定义新的类型。同时,也可能需要给新类型赋一个标签(tag),以便在以后引用。定义新的类型之后,就可以立即或者稍后(通过使用标签)来声明这个类型的实例了
更麻烦的事,也可以使用typedef来为用户定义类型定义新的类型名称,如对其他任何类型一样。但是,如果这样做,必须意识到类型定义名称和标签名没有任何关系
41 下面这两个声明有什么不同
struct x1 {...};
typedef struct {...} x2;
答:第一种形式声明声明了一个“结构标签”;第二种声明了一个“类型定义”。主要区别在于第二种声明更抽象以下--用户不必知道它是一个结构,并且在声明它的实例事也不需要使用struct关键字.
x2 b;
但使用标签声明的结构就必须用这样的形式进行定义
struct x1 a;
//(也可以同时使用两种方法:
typedef struct x3 {...} x3;
//尽管有些晦涩,但为标签和类型定义使用同样的名称是合法的,因为它们处于独立的命名空间中
42.这样的代码对不对
strcut x {...};
x thestruct;
答:C不是C++。不能用结构标签自动生成类型定义名。事实上,C语言中的结构是这样用关键字struct声明的:
struct x thestruct;
如果你愿意,也可以在声明结构的时候声明一个类型定义,然后再用类型定义名称去声明真正的结构;
typedef struct {...} tx;
tx thestruct;
43.结构可以包含自己的指针吗?
答:当然可以,但如果你要使用typedef,则有可能产生问题。
44.在C语言中使用什么方法实现抽象数据类型最好?
答:让客户使用指向没有公开定义(也许还隐藏在类型定义后边)的结构类型的指针是一个好办法。换而言之,客户使用结构指针(及调用输入和返回结构指针的函数)而不知道结构的成员是什么,只要不需要结构的细节--也就是说,只要不使用->\sizeof\操作符几真是的结构声明--C语言事实上可以正确处理不完全类型的结构指针。只有在实现抽象数据类型的源文件中才需要次范围内的结构的完整声明
45。在C语言中是否有模拟继承等面向对象程序设计特性的好方法?
答:把函数指针直接加入到结构中就可以实现简单的“方法”。你可以使用各种不雅而暴力的方法来实现继承,例如通过预处理机或让“基类”的结构作为初始的子集,但这些方法都不完美。很明显,也没有操作符的重载和覆盖(例如,“派生类”中的“方法”,那些必须人工去左)。
47.我遇到这样声明结构的代码
struct name
{
int name[];
char namestr[1];
};
然后又使用一些内存分配技巧事namestr数组用起来好像有多个元素,namelen记录了元素的个数,它是怎样工作的?这样事合法的可移植的吗?
答:不清楚这样做是否合法或者可移植。但这种技术十分普遍。这种技术的某种实现可能像这个样子:
#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;
}
这个函数分配了一个name结构的实例并调整它的大小,以便将请求的名称(不是结构声明所示的仅仅一个字符)置入namestr域中。
虽然很流行,但这种技术也在某种程度上惹人非议。Dennis Ritchie就称之为“和C实现的无保证的亲密接触”,官方的解释认定他没有严格遵守C语言标准。(关于这种技术的合法性的完整讨论超出了本书的范围),这种技术也不能保证在所有的实现上是可移植的,(仔细检查数组边界的编译器可能会发出警告)。
另一种可能是吧变长的元素声明成很大,而不是很小。上面的例子可以这样改写:
#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;
}
当然,此处的MAX应该比任何可能存储的名字长度都打。但是,这种技术似乎也不完全符合标准的严格解释:
当然,真正安全的正确的做法是使用字符指针,而不是数组
#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;
}
显然,把长度和字符串保存在同一块内存中的“方便”已经不复存在了,而且在释放这个结构的实例的时候需要两次调用free。
如果像上面的例子那样,存储的数据类型是字符,那么为保持连续性,可以直截了当的将两次malloc调用合成一次,(这样也可以只用一次调用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调用将第二个区域接上的技巧只有在第二个区域是char型数组的时候才可移植,对于任何大一些的类型,对齐变得十分重要,必须保持
49.为什么不能用内建的==和!=操作符比较结构?
答:没有一个好的,符合C语言的底层特性的方法让编译器来实现结构比较。简单的按字节比较的方法可能会在遇到结构中没有使用的“东 hole”的随机内容的时候失败。而安域比较在处理大结构时可能需要难以接受的大量重复代码。任何编译器生成的比较代码都不能期望在所有情况下都能正确比较指针域。例如比较char * 域的时候一般都希望使用strcmp而不是==;如果需要比较两个结构,必须自己写函数按域比较。
50.结构传递和返回是如何实现的?
答:当结构作为函数参数传递的时候,通常会把整个结构都推进栈,需要多少空间就使用多少空间。(正是为了避免这个代价,程序员经常使用指针而不是结构)。某些编译器仅仅传递一个结构的指针,但是为了保证按值传递的语义,它们可能不得不保留一份局部fuben。
编译器通常会提供一个额外的“隐藏”参数,用于指向函数返回的结构,有些老式的编译器使用一个特殊的静态位置来返回结构,这回导致返回结构的函数不可再入,这是ANSIC所不允许的。
网友评论