C语言细节 结构体

一.结构体的大小

结构体的大小可能大于其各成员的大小的和,这是因为在系统对数据进行校准的过程中,各成员之
间可能产生未使用的内存.如有些系统要求把所有成员都放在偶数地址上

二.结构体的指定初始化器

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条可以遍历整个链接结构的路径.很多更复杂的数据类型都是由
链接结构组成的,:队列,,哈希表,图表,二叉树(见下图)

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值