C语言深入学习

您所在的位置:网站首页 c语言结构体类型定义的关键词有哪些 C语言深入学习

C语言深入学习

2024-07-13 14:17:02| 来源: 网络整理| 查看: 265

第四章 自定义类型:结构体,枚举,联合 结构体

结构体类型的声明

结构的自引用

结构体变量的定义和初始化

结构体的内存对齐

结构体实现位段(位段的填充 和 可移植性)

枚举

枚举类型的定义

枚举的优点

枚举的使用

联合

联合类型的定义

联合的特点

联合的大小计算

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例(十一)



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭