定义
类似于java中的bean,只不过里面的属性都是public,不需要setter,getter,并且不能赋初值。
定义结构体使用struct关键字。如下:
struct Person{
int age;
char * name;
};
该声明描述了一个由char*与int组成的结构体,并没有创建一个实际的数据对象。
首先使用struct表示接下来的是一个结构体;后面跟的是一个可选的标记(Person),它是用来引用该结构体的快速标识,在声明该结构体变量时可以用struct Person p;形式。
再接下来花括号中的内容是结构体的成员列表。每一个成员的类型是不固定的,可以是基本数据类型,也可以是另外一个结构体。各个成员之间用分号隔开。
花括号外面的分号表示结构体定义结束。在定义结构体的最后需要加分号。
比较
如果两个结构体的声明都不使用标识,并且使用相同的成员(成员名与类型都匹配),那么两个结构体具有相同的类型。
引用
对于变量来说,可以通过extern来引用定义在虽的c/cpp文件中的非static变量,但它也只能是引用变量,不能用来引用结构体,如果想使用,必须将结构体的定义复制到当前文件中来。参考
当然,可以将结构体定义在一个头文件中,然后通过不同的文件引用相同的头文件,即可将结构体的定义引入到自己的文件中。
声明变量
使用结构体声明变量时前面必须加上struct关键字。对结构体中的变量进行赋值时,可以通过.来引用。如下:
struct Person p;//前面必须加上struct关键字,否则报错
p.age=12;
p.name="zhangsan";
printf("%d %s",p.age,p.name);
结构体本身不占用任何内存,只有当用定义的结构体声明结构体变量时,系统才会分配内存。结构体所占的内存大小为结构体中各个成员变量所占内存大小之和。如对上面的变量p执行sizeof()方法返回的结果是8,因为int占4个字节,name占4个字节。
也可以在定义结构体的时候进行声明。如下:
struct book{
char name[100];
double price;
} b1;
struct {
char name[100];
int age;
} p1;
第一个定义了一个标识为book的结构体,并且同时定义了一个值为b1的结构体变量,而且在别的地方可以使用struct book b2;进行再次定义。
第二个在声明一个结构体的同时也进行了定义p1变量,但无法在别的地方使用该结构体模板。因为它没有定义标识。
因此,如果想多次使用一个结构模板,就需要使用带有标记的形式。
初始化与赋值
可以通过.引用变量的各个字段,然后分别进行赋值。也可以将一个结构体变量直接赋值给另一个。
struct Person p;
p.age=12;
p.name="zhangsan";
struct Person p2 = p;
printf("%d %s %p %p",p2.age,p2.name,&p2,&p);
前两个值分别为12,zhangsan,但后两个值不相同。
还可以直接在声明变量的时候赋值,使用{}将各个字段的值括起来。如下
struct Person{
int age;
char * name;
int score;
};
int main()
{
struct Person p={19,"lisi",10};
printf("%d %s %d",p.age,p.name,p.score);//和变量一一对应
return -1;
}
在赋值时,{}内的值和成员变量一一对应。
另外,在c99中还支持指定初始化项目,使用点与成员名来标识具体的字段。可以按照任何顺序指定初始化的项目,指定初始化字段之后的值就按顺序为下面的字段进行初始化。另外,对特定成员变量的最后一次赋值才是它的真正的值。如下:
struct book{
char name[100];
double price;
};
void test() {
struct book b1 = {
.price = 349;
.name = "test";
432;
};
}
上述先初始化了price,再初始化了name,后面又初始化了price(因为price在name后面),所以最终price的值为432,而不是349。
结构数组
和别的数组一样定义,访问方式也一样。可以简单的将struct book理解为一种数据类型。如下:
struct book bs[10];//定义
bs[0].name = "name";//访问变量,并为字段赋值
结构体的嵌套
声明结构体变量一样,只需要用struct name arg即可。
struct person {
char first[40];
char second[40];
};
struct book {
char name[100];
struct person author; //引用上面定义的结构体
double price;
};
struct book bs = { "book.name", { "first", "second" }, 123.0 };
在初始化时,需要按结构体初始化方式将内部结构体给初始些。引用方式为:bs.author.first。
结构指针
指向结构体变量的指针叫结构指针。如
struct Person p;
p.age=12;
p.name="zhangsan";
struct Person * pp = &p;//结构指针,指向了结构体变量p
printf("%d %s %d",pp->age,pp->name,sizeof(pp));//使用指针访问结构体中的属性时,使用的是->,而不是.
有一点需要注意:使用结构指针访问结构体中的属性时,使用->,而不是点"."。上述程序的输出结果为12 zhangsan。使用结构变量名访问字段时,使用点。
与数组不同,一个结构的名字不是该结构的地址,必须使用&取地址。
复合文字
与数组类似,结构体也可以使用复合文字进行描述。语法是:把类型名写在圆括号中,后跟一个用花括号括起来的初始化项目列表。
(struct book){"nmame",432.01};
其中book是一个结构体标识。
我们知道,在声明结构体变量的时候,可以使用{}为该变量进行初始化。但,不能使用该种方式为结构体进行赋值(非初始化)。如:
b1 = {
"name",
234
};
这段代码是错误的。但如果b1前面有结构体类型就可以,如struct book b1,这种代码是正确的。
同时,如果使用复合文字,就可以为b1直接赋值,在{}前面加上(struct book)即可。
伸缩型数组成员
c99中允许结构体的最后一个成员是具有特殊属性的数组:它不存在,至少应该是不立即存在;可以在代码中指定该数组的大小。有以下规则:
1,必须是最后一个成员,且类型必须是数组。
2,结构中必须至少有一个其他的成员。
3,声明数组的时候,方括号中是空的。如下:
struct book {
char name[100];
double price;
int test[];
};
使用该种结构时,应该声明了一个指向struct book类型的指针,然后使用malloc()为该指针进行赋值和分配足够的空间。如:
struct book* b1;
b1 = malloc(sizeof(struct book)+5*sizeof(int));//分配足够的空间
b1->test[1] = 3;
printf("%d",b1->test[1]);
使用malloc()分配空间时,sizeof(struct book)为book中的常规成员分配了足够的空间,使用5*sizeof(int)为结构体中的test成员分配了5个int所占的字节。
该语句声明之后,b1中的test成员的长度就为5。
声明之后,可以按正常的成员进行访问与修改。
说明
1,如果结构中有一个字段为字符串,使用字符数组,而不是使用指向的指针。如下:
struct book {
char* name;
double price;
};
void test() {
struct book b1;
scanf("%s",b1.name);//这里有一个潜在的问题
}
上述代码使用指针来存储字符串,因为指针在声明的时候不会分配用于存储其值的空间,所以name指向的位置是不固定的。当使用scanf()为b1.name赋值时,用户输入的信息就会被存储到的位置不固定,这将有可能导致程序崩溃。
如果使用字符数组就没事,因为声明数组的时候就为该数组分配了存储空间,使用scanf()时能保证数据存储到正确的位置中。
如果确定想使用指针,必须先使用malloc()为该成员分配内存空间。当然,在使用完毕之后必须调用free()对malloc()分配的内存进行释放。
位字段
基础
基本数据类型的大小,都是以字节为单位的。如果只是用来表示一个是或否,其余用一个位就行了。这样就能节约内存空间。而位字段就是来实现该功能的:它可以使用位,而不是使用字节。
位字段是一个unsigned int或者signed int中一组相邻的位,它由使用一个结构体进行声明,并为每一个字段指定一个标签,用于标识该字段占据几位。该结构体中的成员只能是int类型或unsigned int类型的。如:
struct testBit{
unsigned int test1:1;
unsigned int test2:2;
} bit;
上述结构体中,test1占一个bit位,test2占两个bit位。而bit被存储在一个unsigned int大小的存储单元中,但仅有其中的3(test1占1个,test2占2个,共3个)个bit位被使用了。
说明
1,不允许一个字段跨越两个unsigned int之间的边界,如果位字段的总位数超过unsigned int的大小,那编译器会自动将一个字段移动到下一个unsigned int中。
2,使用宽度为0的字段将迫使下一个字段与下一个整数对齐。
3,即使一个结构中只有一个1bit位的字段,该结构的大小也与unsigned int的大小相同。
4,可以使用未命名字段来填充或者调字段之间的位置,未命令的字段是不能使用的。如:
struct testBit{
unsigned int test1:1;
unsigned int :2;
unsigned int test3:3;
};
其中第二个字段是未命名的,那么test1与test3中间就隔着两个bit位的间隔。
5,由于位字段存储时是按bit位进行的,而地址是按字节排列的,所以无法对位字段使用取地址符&,它本身就没有地址。
6,通常可以使用位运算符来代替位字段,因为通过位操作可以取出任何位上的值。只不过这种方法通常较为麻烦。