C语言深入学习 |
您所在的位置:网站首页 › c语言结构体类型定义的关键词有哪些 › C语言深入学习 |
第四章 自定义类型:结构体,枚举,联合
结构体
结构体类型的声明 结构的自引用 结构体变量的定义和初始化 结构体的内存对齐 结构体实现位段(位段的填充 和 可移植性) 枚举枚举类型的定义 枚举的优点 枚举的使用 联合联合类型的定义 联合的特点 联合的大小计算 1.结构体的声明 1.1结构体的基本概念结构体是存放不同类型变量的元素集合。 1.2 结构的声明 struct tag { member-list; }variable-list;例:描述一本书 struct Book { char BookName[20]; int BookId; int price; }; 1.3 特殊的声明在声明结构时,可以不完全的声明。 如下: //匿名结构体类型 //注意:只能使用一次,它的局限性。 struct { int a; char ch; double b; }n; struct { int a; char ch; double b; }*p; int main() { p = &n; return 0; }以上两个结构体在声明的时候省略掉了标签(tag)。 注意:p = &n; 在编译器的角度里,它是非法的,编译器会把上面两个声明当成不同的类型。 1.4 结构体的自引用错误方式: struct Node { int data; struct Node n; };正确方式: struct Node { int data; struct Node* n; //[数据域 | 指针域] };注意: typedef struct { int data; Node* n; }Node; //这样写其实是不行的。 typedef struct Node { int data; struct Node* n; }Node; //这样写可行。 1.5 结构体变量的定义和初始化 struct p { int x; int y; }p1; //声明类型的同时定义变量p1 struct p p2;//定义结构体变量p2 struct p p3 = { 0 ,0 }; //结构体变量初始化 //全局变量 struct Student { char name[20]; int age; int id; char sex[5]; }; struct Student s1 = { "小明", 18, 123456, "男" }; //初始化 struct Node { int data; struct p p4; struct Node* n; }n1 = { 9, {3, 1}, NULL }; //结构体嵌套初始化 int main() //在主函数内定义的结构体变量是局部变量 { struct Student s2 = { "小红", 18, 123457, "女" }; //初始化 struct Node* n2 = &n1; //. //-> printf("%s %d %d %s\n",s2.name, s2.age, s2.id, s2.sex); printf("%d %d %d %p\n",n2->data, n2->p4.x, n2->p4.y, n2->n); return 0; } 1.6 结构体内存对齐结构体的内存对齐规则: 1.第一个成员在与结构体偏移量为0的地址。 2.其他成员变量要对齐到对齐数的整数倍的地址。 对齐数:该成员的大小与编译器默认的对齐数中取较小值。 VS编译器默认对齐数为8。 3.结构体的总大小是最大对齐数(每个成员都有一个对齐数)的整数倍。 4.如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。 1.6.1 内存对齐存在的意义1.平台原因(移植原因): 不是所有的硬件平台都能够任意访问地址上的任意数据的,某些硬件只能在某些地址取某些特定类型的数据,不然会出现硬件异常的问题。 2.性能原因: 数据结构(特别是栈)应该尽量地在自然边界上对齐。 因为为了访问未对齐的内容,处理器需要做两次内存访问;对齐的内容访问就只需要一次访问就够了。 总结: 结构体的内容对齐是拿空间来换取时间的做法。 在设计结构体时,既要满足对齐,又要节省空间。 应该让占用空间小的成员尽量集中在一块。 例: struct s1 { char c; int a; char b; }; struct s2 { char c; char b; int a; }; //s1和s2类型的成员是一样的,但是它们所占空间的大小不一样。 1.6.2 练习 #include //练习1 struct S1 { char c1; int i; char c2; }; //练习2 struct S2 { char c1; char c2; int i; }; //练习3 struct S3 { double d; char c; int i; }; //练习4-结构体嵌套问题 struct S4 { char c1; struct S3 s3; double d; }; int main() { printf("%d\n", sizeof(struct S1));//12 printf("%d\n", sizeof(struct S2));//8 printf("%d\n", sizeof(struct S3));//16 printf("%d\n", sizeof(struct S4));//32 return 0; } 1.7 修改默认对齐数#pragma这个预处理指令,可以改变默认对齐数。 #include #pragma pack(1) //设置对齐数为1 struct s1 { char a; int b; char c; }; #pragma pack(0) //取消设置对齐数,还原为默认。 #pragma pack(2) struct s2 { char a; int b; char c; }; #pragma pack(0) int main() { printf("%d\n", sizeof(struct s1)); printf("%d\n", sizeof(struct s2)); return 0; }总结: 结构在对齐方式不合适的时,可以自己修改默认对齐数。 笔试题: 写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明 考察:offsetof宏的实现 1.8 结构体传参 #include struct Node { int date[100]; int num; }; struct Node n = { {1,2,3,4,5,6}, 7 }; void print1(struct Node n) { printf("%d\n", n.num); } void print2(struct Node* n) { printf("%d\n", n->num); } int main() { print1(n); print2(&n); return 0; }以上代码中的print1和print2函数哪个更好呢? 首先选择print2函数。 原因: 函数传参时,参数需要压栈,会有时间和空间上的系统开销。 如果传递一个过大的结构体的对象时,参数压栈的系统开销较大,会导致性能下降。 总结: 结构体传参时,最好传结构体的地址。 2.位段 2.1 位段的定义位段的声明和定义与结构体是相似的,有以下两点不同: 1.位段的成员必须是int、unsigned int 或者 signed int。 2.位段的成员后边会有一个冒号和一个数字。 struct E { int _a : 1; int _b : 5; int _c : 10; int _d : 20; };这里的E就是一个位段类型。 来看以下位段E的大小: printf("%d\n", sizeof(struct E)); //8 2.2 位段的内存分配位段的成员可以是int unsigned int signed int 或者 char(整型家族)类型。 位段的空间按照需要以4个字节(int)或者是1个字节(char)来开辟的。 位段涉及较多不确定因素,位段不跨平台,注重可移植的程序应该避险使用。 #include struct X { char a : 3; char b : 5; char c : 2; char d : 4; }; struct X x = { 0 }; int main() { x.a = 12; x.b = 16; x.c = 9; x.d = 8; printf("%d\n", sizeof(struct X)); //2 return 0; } 2.3 位段的跨平台问题1.int位段是有符号数还是无符号数是不确定的。 2.位段中最大位的数目不能确定。(比如16位的机器,写成22,在16位机器会出现问题) 3.位段中的成员在内存中是从左向右开始分配 的,还是从右到左分配标准尚未定义。 4.当一个结构体包含了两个位段,第二个位段成员较大,无法容纳第一个位段剩下的空间时,是舍弃剩下的空间还是继续使用,这也是无法确定的。 总结: 跟结构体相比,位段可以做到同样的效果,可以很好的节省空间,但是存在跨平台的问题。 2.4 位段的应用作用:能够节省空间。 3. 枚举 3.1 枚举类型的定义 enum Color { GREEN, RED, YELLOW, BLACK }; enum Day { Mon, Tues, Wed, Thur, Fri, Sat, Sun };以上定义的enum Day, enum Color就是枚举类型。 {}中内容是枚举类型中的可能取值,也称为枚举常量。 它们都是有值的,默认是从0开始,依次递增1,在定义时也可以赋初始值。 enum Color { GREEN = 2, RED = 3, YELLOW = 4, BLACK = 5 }; 3.2 枚举的优点1.可增加代码的可读性和可维护性。 2.与#define定义的标识符对比,它有类型检查,更加严谨。 3.防止命名污染(封装)。 4.方便调试。 5.使用时方便,一次可以定义多个常量。 3.3 枚举的使用 enum Color { GREEN = 2, RED = 3, YELLOW = 4, BLACK = 5 }; enum Color C = BLACK; C = 5; //这种写法是错误的,有类型的差异,要用枚举常量给枚举变量赋值。 4.联合(共用体) 4.1 联合类型的定义该类型定义的变量包含一系列的成员,但这些成员公用同一块空间。 union un //联合体类型的声明 { char c; int i; }; int main() { union un u; //联合体变量的定义 printf("%d\n", sizeof(u)); //4 return 0; } 4.2 联合的特点联合体的成员公用一块空间,一个联合变量的大小,至少是最大成员的大小。 union un //联合体类型的声明 { char c; int i; }; int main() { union un un; printf("%d\n", &(un.i)); //18087412 printf("%d\n", &(un.c)); //18087412 un.i = 0x11223344; //11223347 un.c = 0x47; //47 printf("%x\n", un.i); printf("%x\n", un.c); return 0; }面试题: 判断当前计算机的大小端存储。 #include int check_sys() { union un { char c; int i; }u; u.i = 1; return u.c; } int main() { int ret = check_sys(); if (ret) { printf("小端\n"); } else { printf("大端\n"); } return 0; } 4.3 联合大小的计算联合大小至少是最大成员的大小,当最大成员不是最大对齐数的整数倍时,就需要对齐到最大对齐数的整数倍。 #include union Un1 { char c[5]; int i; }; union Un2 { short c[7]; int i; }; int main() { printf("%d\n", sizeof(union Un1)); //8 printf("%d\n", sizeof(union Un2)); //16 return; } union Un2)); 5.练习通讯录 test.c #define _CRT_SECURE_NO_WARNINGS 1 //通讯录 //1.能存放1000个人的信息 //每个人信息:姓名+年龄+性别+电话+地址 //2.增加人的信息 //3.删除指定的人的信息 //4.修改指定的人的信息 //5.查找指定的人的信息 //6.排序通讯录的信息 #include "Contact.h" enum Option { EXIT, ADD, DEL, MODIFY, SEARCH, SORT, PRINT }; void menu() { printf("********************************************************\n"); printf("********** 1.add 2.del ****************\n"); printf("********** 3.modify 4.search****************\n"); printf("********** 5.sort 6.print ****************\n"); printf("********** 0.exit *************\n"); printf("********************************************************\n"); } int main() { int input = 0; Contact con; Init_Contact(&con); do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case ADD: Add_Contact(&con); break; case DEL: Del_Contact(&con); break; case MODIFY: Modify_Contact(&con); break; case SEARCH: Search_Contact(&con); break; case SORT: break; case PRINT: Print_Contact(&con); break; case EXIT: return; break; default: printf("选择错误,请重新选择:>"); break; } } while (input); }Contact.c #include "Contact.h" static int Find_ByName(Contact* c,char name[]) { int i = 0; for (i = 0; i sz; i++) { if (strcmp(c->data[i].name ,name) == 0) { return i; } } return -1; } void Init_Contact(Contact* c) { c->sz = 0; memset(c->data, 0, sizeof(c->data)); } void Add_Contact(Contact* c) { if (c->sz == 1000) { printf("通讯录已满,无法增加\n"); return; } printf("请输入名字:>"); scanf("%s",c->data[c->sz].name ); printf("请输如年龄:>"); scanf("%d", &(c->data[c->sz].age)); printf("请输入性别:>"); scanf("%s", c->data[c->sz].sex); printf("请输入电话:>"); scanf("%s", c->data[c->sz].tele); printf("请输入地址:>"); scanf("%s", c->data[c->sz].addr); c->sz++; printf("增加成功\n"); } void Print_Contact(const Contact* c) { int i = 0; printf("%-10s\t%-5s\t%-8s\t%-15s\t%-20s\n", "姓名","年龄","性别","电话","地址"); for (i = 0; i sz; i++) { printf("%-10s\t%-5d\t%-8s\t%-15s\t%-20s\n", c->data[i].name, c->data[i].age, c->data[i].sex, c->data[i].tele, c->data[i].addr); } } void Del_Contact(Contact* c) { char name[MAX_NAME] = { 0 }; if (c->sz == 0) { printf("通讯录为空,无法删除\n"); } printf("请输入你要删除的信息:>"); scanf("%s", name); int pos = Find_ByName(c, name); if (pos == -1) { printf("你要删除的信息不存在\n"); } for (int i = 0; i sz - 1; i++) { c->data[i] = c->data[i + 1]; } c->sz--; printf("删除成功\n"); } void Modify_Contact(Contact* c) { char name[MAX_NAME] = { 0 }; printf("请输入你要修改的信息:>"); scanf("%s", name); int pos = Find_ByName(c, name); if (pos == -1) { printf("你要修改的信息不存在\n"); } else { printf("请输入名字:>"); scanf("%s", c->data[pos].name); printf("请输如年龄:>"); scanf("%d", &(c->data[pos].age)); printf("请输入性别:>"); scanf("%s", c->data[pos].sex); printf("请输入电话:>"); scanf("%s", c->data[pos].tele); printf("请输入地址:>"); scanf("%s", c->data[pos].addr); printf("修改成功\n"); } } void Search_Contact(Contact* c) { char name[MAX_NAME] = { 0 }; printf("请输入你要查找的信息:>"); scanf("%s", name); int pos = Find_ByName(c, name); if (pos == -1) { printf("你要查找的信息不存在\n"); } else { printf("%-10s\t%-5s\t%-8s\t%-15s\t%-20s\n", "姓名", "年龄", "性别", "电话", "地址"); printf("%-10s\t%-5d\t%-8s\t%-15s\t%-20s\n", c->data[pos].name, c->data[pos].age, c->data[pos].sex, c->data[pos].tele, c->data[pos].addr); } }Contact.h #define _CRT_SECURE_NO_WARNINGS 1 #include #define MAX_NAME 20 #define MAX_SEX 8 #define MAX_TELE 15 #define MAX_ADDR 30 #define MAX 1000 typedef struct PeoInfo { char name[MAX_NAME]; int age; char sex[MAX_SEX]; char tele[MAX_TELE]; char addr[MAX_ADDR]; }PeoInfo; typedef struct Contact { PeoInfo data[MAX]; int sz; }Contact; void Init_Contact(Contact* c); void Add_Contact(Contact* c); void Print_Contact(const Contact* c); void Del_Contact(Contact* c); void Modify_Contact(Contact* c); void Search_Contact(Contact* c); 上一章:C语言深入学习 — 3.字符函数和字符串函数 配套练习:C语言练习题110例(一) C语言练习题110例(二) C语言练习题110例(三) C语言练习题110例(四) C语言练习题110例(五) C语言练习题110例(六) C语言练习题110例(七) C语言练习题110例(八) C语言练习题110例(九) C语言练习题110例(十) C语言练习题110例(十一) |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |