结构是一个或多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下。
#include <stdio.h>
struct name
{
char* name;
int id;
}t, *pt;
char *name = "JOHN";
int main()
{
struct name s = {"MIKE", 1};
struct name ts = s;
t.name = "MAY";
t.id = 2;
pt = &t;
printf("%s %d\n", s.name, s.id);
printf("%s %d\n", ts.name, ts.id);
printf("%s %d\n", pt->name, pt->id);
printf("%s %d\n", t.name, t.id);
printf("%s\n", name);
return 0;
}
该程序中,我特地把结构成员、结构标志和普通变量名都定为name,以说明他们同名是合法的。需要指出的是,结构中也可以嵌套结构。
以上的程序展示了结构变量的两种声明方法和两种初始化的方法,以及怎么访问结构成员和结构的赋值。当然,也允许有结构数组。这些用法和基本类型无异,只是看起来在某些地方多了个关键字struct而已。
现在总结一下结构的合法操作:作为一个整体复制和赋值,通过&运算符取地址,访问其成员。其中,复制和赋值包括向函数传递参数以及从函数返回值。结构间不可以进行比较。可以用一个常量成员值列表初始化结构,自动结构也可以通过赋值进行初始化。
接着来看下sizeof这个编译时的一元运算符,它可以用来计算任一对象的长度。因为他是编译时运算符,类似sizeof(i++)的语句会在编译时确定i++类型的大小,所以在运行时候不会在递增i的值。
表达式
sizeof 对象
以及
sizeof(类型名)
将返回一个整数值,它等于指定对象或类型占用的存储空间字节数。
以下是利用sizeof来计算一个数组项数的两种写法:
#define NKEYS (sizeof keytab / sizeof(struct key))
#define NKEYS (sizeof keytab / sizeof keytab[0])
其中keytab是struct key类型的数组。
我想说的是,使用第二种写法的话,即使keytab类型改变了,也不需要改动程序。
结构的长度不一定等于各成员长度的和。因为不同的对象有不同的对齐要求,所以,结构中可能会出现未命名的“空穴”。例如,假如char类型占用一个字节,int类型占用4个字节,则下列结构:
struct
{
char c;
int i;
};
可能需要8个字节的存储空间,而不是5字节。使用上述的sizeof运算符可以返回正确的对象的长度。
C语言中提供了一个称为typedef的功能,它用来建立新的数据类型名。但从任何意义上讲,typedef声明并没有创建一个新类型,它只是为某个已存在的类型增加一个新的名称而已。
例如: typedef int Length;
类似地,声明
typedef char *String
将String定义与char *或字符指针同义,此后,便可以在类型声明中和类型转换中使用String。
可以发现,typedef类似于#define语句,但为什么要需要typedef语句呢?一定有什么不同的吧!下面我们来重点看看这两者的差异。
从表面上看,typedef是语句,后面有分号,而define定义结束后没有分号。
#define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。
而由于typedef是由编译器解释的,因此它的文本替换功能要超过预处理器的能力。例如:
typedef int (*PFI)(char *, char *);
该语句定义了类型PFI是“一个指向函数的指针”,该函数具有两个char *类型的参数,返回值类型为int *,它可用于某些上下文。
下面举个例子,解释一下define的简单文本替换和typedef有什么不同。
#include <stdio.h>
#define PINT int *
typedef int * pint ;
int main()
{
int a = 1;
int b = 2;
const PINT pa = &a; //pa可更改,但是p指向的内容不可更改。
const pint pb = &b; //pb不可更改,但p指向的内容可更改
//*pa = 4; 错误
//pb = &a; 错误
pa = &b;
*pb = 4; //正确
return 0; //正确
}
const PINT pa = &a;被简单替换成了const int *pa = &a;
在const pint pb = &b;中,pint被当成了一种类型。类比const int n = 3;这个语句使得n的值不能改变,同理const pint pb = &b;中pb的值不能更改,但指向的内容可以更改。
使用typedef的好处有:
1.使表达方式更简洁。
2.提高程序的可移植性。
3.为程序提供更好的说明性。
本章接下来讲了一个我从没接触过内容——联合。第一次遇到这个概念。
联合是可以(在不同时刻)保存不同类型和长度的对象的变量,编译器负责跟踪对象的长度和对其要求。联合提供了一种方式,以在单块存储区中管理不公类型的数据,而不需要在程序中嵌入任何机器有关的信息。
联合的语法基于结构:
union u_tag
{
int ival;
float fval;
char *sval;
} u;
变量u必须足够大,以保存类型中最大的一种,具体长度同具体的实现有关。这些类型中的任何一种类型的对象可以赋值给u。
#include <stdio.h>
union u_tag
{
int ival;
float fval;
char *sval;
} ;
int main()
{
union u_tag u = {3};//联合只能用其第一个成员类型的值进行初始化。
u.fval = 3.3;
u.ival = 3;
u.sval = "hello world.";
printf("%d\n", sizeof(union u_tag));
printf("%d\n", u.ival);
printf("%f\n", u.fval);
printf("%s\n", u.sval);
return 0;
}
以上程序只能正确显示最后一次赋值的字段。使用联合时,我们可以先判断在联合中存储什么类型,在操作相应的字段。
对联合允许的操作与对结构允许的操作相同。联合可以使用在结构和数组中,反之亦然。
C语言中还可以直接定义和访问一个字中的位字段,而不需要通过按位逻辑运算符。
struct
{
unsigned int is_keyword : 1;//字段被声明为unsigned int类型,以保证它们是无符号的。
unsigned int is_extern : 1;
unsigned int is_static : 1;
}flags;//这里定义了一个变量flags,它包含个位字段。冒号后面的数字表示字段的宽度。
这样,可以用结构访问成员的方式去操作相应的位。字段也可以仅仅声明为int,为方便移植,需要显示声明该int类型是signed还是unsigned类型。字段不是数组,并且没有地址,因此对它们不能使用&运算符。