本博文为半摘记性质。
——
声明:部分知识点及例程修改自https://www.runoob.com/cprogramming/c-tutorial.html 菜鸟教程、http://c.biancheng.net/c/ C语言中文网,另有部分零散资料转自互联网,内容有一定改动,并非全文转载。
本人尊重各位的知识成果,大幅引用的文章原文网址已在各小节末尾给出。
结构体以及文件操作(上)
本文要点:
- 结构体(结构体类型、结构体变量)
- 结构体数组
- 枚举
- 共用体
1结构体
结构体可以在main 函数里面定义,也可以在main函数之外定义。在main函数外定义属于全局变量,可被多个函数使用;在main函数里面定义属于局部变量,只能在main函数中使用。
定义结构体类型
定义一个结构体类型的一般形式为:
struct 结构名
{成员列表};
其中成员列表中每个成员的形式为:
类型说明符 成员名;
- 两个结构体可以有相同的成员名。
- 结构体的成员也可以是结构体类型的数据,称作结构体类型的嵌套定义;也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
//此结构体的声明包含了其他的结构体
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};
//此结构体的声明包含了指向自己类型的指针
struct NODE
{
char string[100];
struct NODE *next_node;
};
定义结构体变量
结构体变量的几种定义模式如下:
- 在声明结构体类型的同时定义结构体变量:
struct 结构体名{
成员列表;
}变量名表;
- 匿名定义结构体类型时(通常用于之后不再定义新的变量):
struct {
成员列表;
}变量名表;
- 声明结构体类型时可省略结构体变量名:
struct 结构体名{
成员列表;
}; //分号不可省
之后如果要定义结构体变量:
struct 结构体名 结构体变量名;
——
对结构体变量中成员的引用形式为:
结构体变量名.成员名 //成员访问运算符(.)
==关于符号 -> ==
如果我们在C语言中定义了一个结构体,然后申明一个指针指向这个结构体,那么我们要用指针取出结构体中的数据,就要用到“->”
结构指针->结构成员
例:
struct Data A = {1,2,3}; ——声明变量专A
int x; ——声明一个变量x
p = &A ; ——让p指向A
x = p->a; ——取出p所指向的结构体中包含的数据项a赋值给x
由于此时p指向A,因而 p->a == A.a,也就是1。
——
结构体变量的初始化
定义时初始化
结构体变量在定义时允许初始化,在花括号内按照成员的定义顺序,分别给出变量中各成员的值。
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
或struct Books book = {"C 语言", "RUNOOB", "编程语言", 123456};
结构体变量作为函数参数
可以把结构体变量作为函数参数,传参方式与其他类型的变量或指针类似。
struct Books
{
...
};
/* 函数声明 */
void printBook( struct Books book );//括号内同结构体变量名定义形式
——
/*主函数内*/
struct Books Book1; //定义结构体变量
printBook( Book1 );//引用函数
指向结构的指针
结构体名和类型名是等价的,是一种类型说明符,只有在它定义了变量后系统才分配内存空间,不是地址。
同样,结构体变量名不是地址,要加取值符号&,相当于基本数据类型的变量名。
结构体每个成员地址同理。
引用结构体变量地址的指针为结构体指针,定义形式为:
struct 结构体名 *指针变量名;
——例
struct Books
{
math;
...
}Book1;
struct Books *struct_pointer;//定义
struct_pointer = &Book1;//在上述定义的指针变量中存储结构变量的地址
使用时:
printBook( struct_pointer );
等同于printBook( &Book1 );//这里省略步骤直接取了地址
为了使用指向该结构的指针访问结构的成员,一般引用形式是:
(*指针变量名).成员名
等价于:指针变量名->成员名
——例
(*struct_pointer).math
struct_pointer->math
结构作为函数参数和结构指针作为函数参数的对比
- 将结构体直接传递到函数参数,好处在于程序清晰、操作安全(改变的是副本);缺点在于效率很低,因为C语言的参数传递调用方式要求把参数的一份拷贝传递给函数,必须把结构体占用的内存空间复制到堆栈中,以后再丢弃。
- 将结构体指针直接传递到函数参数,由于指针比结构体小得多,所以把它压到堆栈上效率要提高很多;缺点在于结构体指针缺少对数据的保护。
结构体数组
结构体数组同数值型数组定义、初始化、用法类似。
常用结构体数组来表示具有相同数据结构的一个群体,结构体数组的每一个元素相当于一个结构体变量。
结构体数组的一般定义形式为:
struct 结构体名 结构体数组名[元素个数];
例struct Books Book[10];
对结构体数组元素成员的引用形式为:
结构体数组名[元素下标].成员名
对结构体数组的初始化形式为:
struct STU stu[5] = {{"小红", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}, {"小七", 23, 'F', "Z1207022"}, {"小欣", 20, 'F', "Z1207015"}, {"小天", 19, 'M', "Z1207024"}};
2枚举
枚举与结构构造类似,放置一起讨论。
枚举(enum)是 C 语言中的一种基本数据类型。枚举在C/C++/c#中,是一个被命名的整型常数的集合,第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。
2.1枚举类型的定义
其定义形式为:
enum 枚举名 {枚举成员1,枚举成员2,……};
例:enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
枚举成员是该枚举类型的命名常数,枚举中每个成员(标识符)结束符是","(逗号) 不是";"(冒号), 最后一个成员可省略","。
可以在定义枚举类型时改变枚举元素的值,而后面的值为前一元素加 1:
enum season {spring, summer=3, autumn, winter};
spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5。
注意可以赋值为负值,这是因为在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的。
2.2枚举变量的定义
同结构体变量的定义。
- 在声明枚举类型的同时定义枚举变量:
enum 枚举名{
枚举成员列表;
}变量名表;
- 匿名定义枚举类型时(通常用于之后不再定义新的变量):
enum {
枚举成员列表;
}变量名表;
- 声明结构体类型时可省略结构体变量名:
enum 枚举名{
枚举成员列表;
}; //分号不可省
之后如果要定义枚举变量:
enum 枚举名 枚举变量名;
与结构体不同的是,枚举变量没有初始化,只有枚举类型有初始化,这是因为枚举元素是常量。
——
只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量:
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
int main()
{
enum DAY day;
day = WED;//√
day = 3;//×
printf("%d",day);
return 0;
}
——
输出结果为3
3共用体
共用体(union)有时也称联合体,结构体和共用体的语法类似,但有本质的不同。
共用体的定义形式为:
union 共用体名{
成员列表
};
3.1共用体的特殊之处
结构体和共用体的区别在于:
结构体的各个成员会占用不同的内存,互相之间没有影响,结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙);
而共用体占用的内存等于最长的成员占用的内存。
特别注意:
共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员;
这是因为共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把其它成员的值覆盖掉。
详细来讲,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。
3.2共用体变量的定义
http://c.biancheng.net/view/2035.html C语言共用体(C语言union用法)详解
同结构体变量、枚举变量类似。
- 在声明共用体类型的同时定义共用体变量:
enum 共用体名{
共用体成员列表;
}变量名表;
- 匿名定义共用体类型时(通常用于之后不再定义新的变量):
enum {
共用体成员列表;
}变量名表;
- 声明结构体类型时可省略结构体变量名:
enum 共用体名{
共用体成员列表;
}; //分号不可省
之后如果要定义共用体变量:
enum 共用体名 共用体变量名;
————
对共用体变量中成员的引用形式为:
共用体变量名.成员名 //成员访问运算符(.)
3.3使用共用体的好处
个人认为最重要的好处就是使用共用体能够减少占用的内存空间,特别是对于内存空间有限的嵌入式设备。举个例子,有这么一个表格:
姓名 | 职业 | 工作单位地址 | 学校地址 |
---|---|---|---|
张三 | 卖艺 | 天桥 | |
赵四 | 学生 | 职校 |
在这个表格中【工作单位地址】和【学校地址】只要填一个就好,填哪一个取决于你的职业是否为学生。如果使用共用体记录数据,就可以节省另一个单元格内的空间,程序可编为:
struct{
char name[20];
char job[20];
union{
char school[20];
char workplace[20];
} sc;
4位域
信息的存取一般以字节为单位,有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。
C语言允许在一个 结构体 中 以位为单位 来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。
位域的定义形式为:
struct 结构体名(可省)
{
type [member_name] : width ;//成员n类型 成员n: 成员n位数 ;
例:unsigned n: 4;//unsigned若省略后一个关键字,大多数编译器都会认为是unsigned int
unsigned n;//可以省略位数,此时仅为普通的结构体成员
};
在嵌入式设备中常见的形式为:
struct
{
unsigned b0:1;
unsigned b1:1;
unsigned b2:1;
unsigned b3:1;
unsigned b4:1;
unsigned b5:1;
unsigned b6:1;
unsigned b7:1;
}bit_byte;
冒号(:)后面的数字用来限定成员变量占用的位数,其取值范围有限,过大会导致溢出,位域的宽度不能超过它所依附的数据类型的长度。
可用的数据类型
只有有限的几种数据类型可以用于位域。在 C99中,这几种数据类型是 int、signed int 、unsigned int(int 默认就是 signed int)和 Bool 。不过编译器在具体实现时进行了扩展,额外支持了 char、signed char、unsigned char 以及 enum 类型。
存储规则
- 当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止(见下一句);如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。
- 当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会让成员挨着存储,而 VC/VS 则会让成员按照各自的类型存储。
- 如果成员之间穿插着非位域成员,那么不会进行压缩。
基于位域的特性,使用&获取位域成员的地址是没有意义的。
无名位域
位域成员可以没有名称,只给出数据类型和位宽,一般用来作填充或者调整成员位置,不能被使用。
其定义形式为:
struct 结构体名(可省)
{
type : width ;
例:unsigned : 4;
};