一.结构体的大小
结构体的大小可能大于其各成员的大小的和,这是因为在系统对数据进行校准的过程中,各成员之
间可能产生未使用的内存.如有些系统要求把所有成员都放在偶数地址上
二.结构体的指定初始化器
C99和C11为结构体提供了"指定初始化器"(Designated Initializer),语法类似于数组的指
定初始化器,但使用结构体成员运算符和成员名(而非方括号和索引)表示特定的元素:
struct <struct_name> <var>={
.<name1>=<val1>,
.<name2>=<val2>,
...
};
//name和val的顺序是任意的;可以只初始化部分元素
//如果某个name被重复赋值,则其值是最后1次赋的值
//参数说明:
name,val:分别为结构体中的元素的变量名和值
//实例:
#include <stdio.h>
struct Student {
int age;
float score;
char gender;
char reach[3];
};
int main(void) {
struct Student s1={
.score=11.22,
.gender='M'
};
printf("%f",s1.score);
return 0;
}
//结果:
11.220000
三.结构体数组
1.声明结构体数组:
struct <struct_name> <sa>[<len>];
//参数说明:
sa:结构数组的变量名
len:数组长度
//结构与内容:
如果要创建1个很大的是自动变量的结构数组,这个结构数组需要被存储在栈中,编译器可能使用1
个默认大小的栈,这就可能导致栈溢出,进而导致报错.为解决这个问题,可以要求编译器使用指定
大小的栈,或创建静态/外部数组,或减小数组的大小
//实例:
#include <stdio.h>
struct Student {
int age;
float score;
};
int main(void) {
struct Student sa[10];
int i;
for (i=0;i<10;i++) {
struct Student s1={
2,3.11
};
sa[i]=s1;
}
printf("%d",sa[2].age);
return 0;
}
//结果:
2
2.访问结构体数组的成员:
访问结构数组中的元素:<sa>[<index>];
访问结构数组中的元素中的成员:<sa>[<index>].<name>;
//参数说明:
index:指定索引
name:指定结构体中的元素
//实例:见上
3.使用结构体数组的函数:
#include <stdio.h>
struct aaa {
char a[10];
int b;
double c;
};
void sum(struct aaa AAA[2]) {
printf("%d",AAA[0].b+AAA[1].b);//结果:3
}
int main(void) {
struct aaa AAA[2]={
{
"AAA1",
1,
11.1
},
{
"AAA2",
2,
22.2
}
};
sum(AAA);
return 0;
}
四.嵌套的结构体
#include <stdio.h>
struct score {
float mathematics;
float physics;
float CS;
};
struct Student {
int age;
struct score s;
};
int main(void) {
struct Student s1={
19,
{
96.2,
95.4,
96.5
}
};
printf("%f",s1.s.mathematics);//结果:96.199997
return 0;
}
五.结构体与指针
1.指向结构体的指针
使用结构体指针有以下优点:
①指向结构体的指针通常比结构体本身更容易操控
②在一些早期的C实现中,结构体不能作为参数传递给函数,但指向结构体的指针可以
③传递指针通常更有效率
④一些结构体中会包含指向其他结构体的指针
使用结构体指针的缺点是:
①无法保护数据,被调函数中的操作可能会意外影响到结构体中的数据(可通过const限定符解决)
(1)声明并初始化结构体指针:
struct <struct_name> * <struct_advar>[=&<var>];
//注意:和数组不同,结构体的变量名并不表示其内存地址
//参数说明:
struct_advar:结构体指针变量的变量名
//实例:
#include <stdio.h>
struct Student {
int age;
char major[20];
};
int main(void) {
struct Student s1={
19,
"economics"
};
struct Student * ps=&s1;
printf("%d\n",(*ps).age);
struct Student sa[3]={
{18,"physics"},
{18,"history"},
{19,"chemistry"}
};
ps=&sa[0];
printf("%s\n",(*ps).major);
printf("%s",(*(ps+1)).major);
return 0;
}
//结果:
19
physics
history
(2)使用结构体指针:
<struct_advar>-><name>
//详情参见 C语言基础.结构体.3.(2) 部分
//#####################################################################
(*<struct_advar>).<name>
//注意:必须加(...),因为.的优先级比*高
//实例:见(1)
2.结构体中的指针
(1)结构体中的字符数组与字符指针:
#include <stdio.h>
#define LEN 20
struct names {
char first[LEN];
char last[LEN];
};
struct pnames {
char * first;
char * last;
};
int main(void) {
struct names veep={"AAA","BBB"};
//veep中存储了字符串,共占用40B
struct pnames treas={"ccc","ddd"};
//treas中只存储了字符串的地址,而字符串本身存储在编译器存储常量的地方
printf("%s,%s\n",veep.first,treas.first);
return 0;
}
//结果:
AAA,ccc
使用字符指针可能导致一些问题:
struct pnames attorney;
scanf("%s",attorney.last);
由于attorney.last是未初始化的变量,地址可以是任何值,因此程序可能把字符串放在任何地方
这可能导致程序崩溃.因此,使用字符数组更不容易出错
(2)结构体与动态内存分配:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct namect {
char * fname;
};
void getinfo(struct namect * pst) {
char temp[3];
scanf("%s",&temp);
pst->fname=(char *)malloc(strlen(temp)+1);
strcpy(pst->fname,temp);
}
int main(void) {
struct namect person;
getinfo(&person);
printf("%s",(&person)->fname);
free((&person)->fname);
return 0;
}
//结果:
joe
joe
六.向函数传递结构体
某些早期的C实现不允许把结构体作为参数传递给函数,只能传递结构体指针;不过ANSI C已经移
除了该限制.另外,也可以传递结构体的某个成员
使用结构体的优点是:
①函数处理的是原始数据的副本,这保护了原始数据
②某些人会认为直接传递结构体更清楚、自然
而缺点是:
①较老版本的实现无法处理这样的代码,可移植性较差
②时间和内存开销更大,尤其是传递大小结构体而只使用其中的个别成员时
1.传递结构体的成员:
#include <stdio.h>
struct Student {
int age;
char major[20];
};
void f(int age/* int * age */) {
if (age<=19/* *age */) {
printf("OK");
} else {
printf("NO");
}
}
int main(void) {
struct Student s1={
19,
"economics"
};
f(s1.age);//结果:OK
//f()实际上不知道也不关心实参是否是结构体的成员,而只要求实参是int类型
//f(&s1.age);//也可以选择传入结构体指针
return 0;
}
2.传递结构体的地址:
#include <stdio.h>
struct Student {
int age;
char major[20];
};
void f(struct Student * s) {
printf("%d",(*s).age);//结果:19
printf("%s",s->major);//结果:economics
}
int main(void) {
struct Student s1={
19,
"economics"
};
f(&s1);
return 0;
}
3.传递结构体:
#include <stdio.h>
struct Student {
int age;
char major[20];
};
void f(struct Student s) {
printf("%s",s.major);
}
int main(void) {
struct Student s1={
19,
"economics"
};
f(s1);//结果:economics
return 0;
}
4.结构体可以作为函数的返回值:
#include <stdio.h>
struct Student {
int age;
char major[20];
};
struct Student f(struct Student s) {
s.age=20;
fgets(s.major,20,stdin);//输入:topology
return s;
}
int main(void) {
struct Student s1;
s1=f(s1);
printf("%s",s1.major);//结果:topology
return 0;
}
七.其他特性
1.复合字面量:
C99增加的复合字面量可用于结构体.通常用于只需要1个临时值的情况,如使用复合字面量创建1
个结构体作为函数的实参或赋给另1个结构体:
(struct <struct_name>) {<val1>,<val2>...};
//实例:
#include <stdio.h>
struct name {
char fname[20];
char lname[20];
};
int main(void) {
struct name n1=(struct name) {
"John","Smith"
};
printf("Your name is %s %s",n1.fname,n1.lname);
return 0;
}
//结果:
Your name is John Smith
2.伸缩型数组成员:
C99新增了名为"伸缩型数组成员"(Flexible Array Member)的特性.该特性具有以下规则:
①伸缩型数组成员必须是结构体的最后1个成员
②结构体中必须至少有1个其他成员
另外,这类数组并不会立即存在,而需要进行动态内存分配(实际上是给整个结构体分配内存).C99
的意图不是直接声明包含伸缩型数组的结构体变量,而是先声明1个指向这种结构体的指针,然后用
malloc()分配内存
声明伸缩型数组成员的语法如下:
struct <struct_name> {
<type> <name>;//至少有1个其他成员
...
<type> <name>[];//伸缩型数组成员
};
//实例:
#include <stdio.h>
#include <stdlib.h>
struct flex {
char fname[20];
char lname[];
};
int main(void) {
struct flex * pf;
pf=malloc(sizeof(struct flex)+20*sizeof(char));
//所有超出其他成员需求的内存都会被分配给lname[]
gets(pf->fname);//输入:John
gets(pf->lname);//输入:Smith
printf("%s %s",(*pf).fname,(*pf).lname);//结果:John Smith
return 0;
}
包含伸缩型数组成员的结构体有一些限制:
①不要将其赋值或拷贝给其他结构体:
struct flex * pf1,* pf2;
...
*pf2=*pf1;
这样只能拷贝除伸缩型数组外的其他成员.如果需要拷贝,应使用memcpy()
②不要把其作为实参传递给函数,原因同①,应传递其地址
③不要用其作为其他结构体或数组的成员
3.匿名结构体:
考虑1个嵌套的结构体:
struct name {
char first[20];
char last[20];
};
struct person {
int id;
struct name name1;
}
在C11中,可以使用匿名结构体定义person:
struct person {
int id;
struct {
char first[20];
char last[20];
};
};
初始化的方法与不使用匿名结构体时相同:
struct person ted={8483,{"Ted","Grass"}};
不过访问时把first和last看成ted的元素:
puts(ted.first);
八.保存结构体到文件
结构体可以存储不同类型的信息,是构建数据库的重要工具.存储在1个结构体变量中的整套信息称
为"记录"(Record),单独的项称为"字段"(Field).1个数据库文件中可以包含任意多条记录,而
所有这些结构体变量都来自同一个结构体类型.这些被存储在文件中的信息可以被再次检索
1.使用fprintf():
可以直接使用fprintf()实现存储结构体到文件:
#include <stdio.h>
struct book {
char name[20];
int length;
};
int main(void) {
struct book b1={
"The Scarlet King",
1992
};
FILE * fp=fopen("E:/program/aa.txt","ab+");
fprintf(fp,"%20s | %10d\n",b1.name,b1.length);
//保证任何1个字段在所有行的长度都相等,以确定各个字段的起始/结束位置
return 0;
}
不过这个方法在字段很多是十分不方便
2.使用fwrite():
也可以直接读写二进制数据:
#include <stdio.h>
struct book {
char name[20];
int length;
};
int main(void) {
struct book b1={
"The Scarlet King",
1992
};
FILE * fp=fopen("E:/program/aa.txt","ab+");
fwrite(&b1,sizeof(struct book),1,fp);
return 0;
}
这种方法的问题是:不同系统/编译器肯能使用不同的二进制表示法,所以可能不具有可移植性
九.链式结构
"链式结构"(Linked Structure)由一系列同类型的结构体构成.其中每个结构体中都包含一些
数据项和几个指向其他结构体的指针.所有这些互相链接的结构体共同构成1个链接结构.这些指针
将各个结构体链接起来,并提供了1条可以遍历整个链接结构的路径.很多更复杂的数据类型都是由
链接结构组成的,如:队列,堆,哈希表,图表,二叉树(见下图)