C语言重点突破(四)自定义类型详解

news2024/9/21 0:50:57

前言

本文意在介绍C语言里的常规自定义类型,它是C语言里最重要的概念之一,是我们从简单使用C语言到综合运用必不可少的知识之一,在C语言中具有重要的地位和作用,掌握自定义类型的使用方法和技巧对于写出高质量的C程序是非常重要的。

目录

本章重点
结构体
结构体类型的声明
结构的自引用
结构体变量的定义和初始化
结构体内存对齐
结构体传参
结构体实现位段(位段的填充&可移植性)
枚举
枚举类型的定义
枚举的优点
枚举的使用
联合
联合类型的定义
联合的特点
联合大小的计算

一.结构体

1.结构体类型的声明

C语言里已经内含了一些基本的数据类型(整型,字符型等),但在实际编程中,我们会碰到一些复杂的数据类型,例如描述一个学生,或者是一辆汽车等一些实际事物光靠基础的类型是不能简单描述的,这时候结构体就派上了用场。

结构体说白了,就是数据的集合,里面的成员可以有多种类型,例如描述一个学生,得有名字,性别,年龄,学号等一些信息,这时就可以用结构体来进行定义。

结构体定义如下

定义一个学生:

struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢

上面是常规的声明,缺点是每次定义时都要将struct关键字写入,影响编写效率,下面有一种特殊的声明,此时省略了结构体标签(匿名结构体类型,只能使用一次)

//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;

要注意,这时候定义的结构体变量(x,a【20】,*p)都是全局变量,而在主函数里进行定义则是局部变量(对主函数全局)。

那么问题来了,如果我此时再加上p = &x这一行代码,阁下又该如何应对呢?

可以试着编译一下,运行是没有问题的,但编译器会报警告,尽管两个结构体组成是一样的,但编译器会把它们当作不同的类型进行编译,这种做法不建议。

2.结构体的引用

既然结构体能存放不同的类型,那能不能存放结构体类型呢?

答案是可以的。

//代码1
struct Node
{
int data;
struct Node next;
};
//可行否?
如果可以,那sizeof(struct Node)是多少?

编译一下你会发现,甚至都无法编译,这相当于一个结构体里存放一个自己的结构体,同时这个结构体也可能会存放和自己的结构体,大小根本计算不了,那该如何引用呢?

那就引用地址嘛,通过地址就可以找到该结构体并进行引用,而同时存放地址的指针在编译器里的大小是确定的,这样一来也能计算该结构体的大小。

//代码2
struct Node
{
int data;
struct Node* next;
};

讲到这里我们再看看下面一段代码

//代码3
typedef struct
{
int data;
Node* next;
}Node;
//这样写代码,可行否?

答案显然是不行的,虽然是匿名结构体,但体内已经有了Node类型的指针,后面才生成Node类型,这就导致指针的类型是未定义的,要注意编译的先后顺序。

正确代码

//解决方案:
typedef struct Node
{
int data;
struct Node* next;
}Node;

 3.结构体变量的定义与初始化

下面是结构体变量的定义与初始化

struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

4.结构体内存对齐

前面我们留下了一个问题,就是关于结构体的大小应该如何计算,同时这也是一些大厂笔试特别热门的考点:结构体内存对齐

下面介绍一下结构体的对齐规则:

1.第一个成员在与结构体变量偏移量为0的地址处(偏移量就是地址相较于起始地址的差值)

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处,对齐数=编译器默认的一个对齐数与该成员大小的较小值。

3.结构体总大小为最大对齐数(每一个成员变量都有一个对齐数)的整数倍。

4.如果嵌套了结构体的情况,嵌套的结构对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

看到这里,你可能还有一点懵,我们来个例子解释一下:

struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));

 我们来分析一下:(在vs环境里默认对齐数是8)

char类型在内存里占1个字节,由于起始地址是0,与8比较起来1较小,所以对齐数是1,而内存起始地址我们设为0,可以看出,结构体的第一个成员永远放在内存的起始地址。

接下来是int类型,在内存中占4个字节,但0的下一位就是1,不是4的整数倍,根据对齐规则,就得对齐到4的位置进行存放4个字节。

最后是char,和第一个一样,直接存放下一个(8)即可。

现在结构体占的大小是0~8,一共九个字节,而结构体成员的最大对齐数是4,还得对齐到4的整数倍上才能算结构体的大小,就是12.

 很多人会有疑问了,为什么会存在内存对齐这种说法呢?

结构体内存对齐是为了使结构体的访问更加高效。当结构体中的字段内存对齐后,CPU 可以更快地访问字段所对应的内存地址,因为它们与 CPU 的缓存结构更加匹配。如果结构体的字段没有进行内存对齐,则会导致 CPU 访问内存的效率较低,这会影响程序的性能。

此外,一些计算机体系结构需要结构体内存对齐才能正确工作。例如,一些处理器需要对 4 字节或 8 字节的内存地址进行访问,这意味着结构体中的字段必须按照 4 字节或 8 字节的边界进行对齐才能被正确访问。

因此,结构体内存对齐是为了提高程序的性能和可靠性,确保结构体中的字段可以被正确访问。

简单的来说就是:内存对齐是一种舍弃空间换取时间的方法。

不同的编译器默认的对齐数是不一样的,但可以通过 #pragma 这个预处理指令,改变我们的默认对齐数。

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
  //输出的结果是什么?
  printf("%d\n", sizeof(struct S1));
  printf("%d\n", sizeof(struct S2));
  return 0;
}

 结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。

5,结构体传参

关于结构体传参,有以下两种方式:

struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}

传结构体和传地址都能实现传参的功能,但那一种会比较好呢?

函数传参传地址和传变量是两种不同的方式。

当使用传地址方式时,函数的参数将是指向变量内存地址的指针。这意味着函数将直接访问变量的内存地址,对变量的操作将在原始地址上进行。这种方式通常用于需要在函数内部修改变量的情况。这种方式可以避免在函数内部对变量进行拷贝,从而提高性能和效率。

当使用传变量方式时,参数是变量本身。这意味着函数将使用变量的副本进行操作,并不会直接改变原始变量。这种方式通常用于不需要修改变量的情况,或者对变量进行操作时不需要改变原始值的情况。

总的来说,传地址方式更加灵活,可以实现更复杂的操作,但需要注意避免因为指针操作不当而导致的错误。传变量方式相对简单,使用起来更为直观,但不能直接在函数内部修改变量的值。

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

位段是一种数据结构,它允许程序员在内存中为字段指定特定数量的位数,而不是以字节为单位。这样做有时可以节省内存空间。

在C语言中,可以使用位段来定义一个包含多个字段的结构体。例如,假设我们要定义一个结构体来存储一个16位的数据包,其中包含4个不同的字段,每个字段分别占用4位,可以使用位段来定义这个结构体。

需要注意的是,使用位段可能会导致一些不便之处。例如,不能使用 sizeof 运算符来计算结构体的大小,因为它计算的是按字节对齐的大小。而且不同编译器可能会对位段的实现有所不同,导致可移植性问题。因此,使用位段时需要仔细考虑其适用性和安全性。

位段的声明

struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};

位段的内存分配

首先要知道的就是位段是比特为单位进行分配空间的

举个例子

如下图,a是char类型,占1个字节(8比特)在主函数里,给a赋值10,但位段要求,只能保留3为比特位,所以要进行截取保留3位,以此类推,当存放的位数已满足一个字节或剩余的比特位空间不够,此时就得再开辟一个字节进行存储。

 

位段在不同编译器和不同平台上的实现是有所不同的,这可能会导致跨平台问题。

最常见的问题之一是,如何对位段进行按位运算。在一些平台上,位段是定义为无符号整数,可以直接进行按位运算;但在另一些平台上,则需要将位段转换为整数类型,才能进行按位运算。

此外,位段的顺序和字节对齐方式也可能会发生变化。例如,在某些平台上,位段的顺序是从左向右,而在其他平台上,顺序是从右向左。同时,一些平台可能会对位段进行字节对齐,而其他平台则不会。

为了避免位段的跨平台问题,可以采取以下措施:

1. 避免在位段中使用多个类型。
2. 明确指定位段的顺序和字节对齐方式。
3. 避免使用位段进行按位运算,或者使用平台无关的按位运算规则。
4. 在不同平台上进行测试和调试,确保代码的可移植性和正确性。

总之,位段虽然能够节省内存空间,但也需要考虑其在不同平台上的实现和兼容性,以保证代码的正确性和可移植性。

二.枚举

枚举顾名思义就是一一枚举

1.枚举类型的定义

enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};

上面的Day、Sex、Color、都是枚举类型,括号里的叫做枚举常量,这些常量都是有值的,默认从0开始,一次递增1,也可以在定义的时候进行赋值。

enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};

2.枚举的优点

  1. 程序可读性强。枚举类型可以使用具有意义的符号名称来表示常量,使得程序的可读性更高,增加代码的可维护性。

  2. 减少代码中的魔数。枚举类型可以减少代码中出现的“魔数”(没有明确含义的数字),从而提高代码的可读性和可维护性。

  3. 编译器提供类型检查。枚举类型被视为一种类型,因此编译器可以进行类型检查,从而避免一些常见的错误,例如将一个枚举类型的值赋给另一个类型的变量。

  4. 枚举类型可以实现类型安全的类型别名。实际上,枚举类型可以用来实现一些类型安全的类型别名,例如使用枚举类型来定义一个有限的整数集合。

3.枚举的使用

enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5; //ok??

三.联合体(共用体) 

1.联合类型的定义

联合体是一种特殊的自定义类型,这种类型定义的变量也包含一系列成员,特征是这些成员公用一块空间。

//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));

2.联合的特点

联合体的成员是共用一块内存空间的,所以一个联合变量的大小,至少是最大成员的大小。

3.联合体的大小计算

要注意的问题:

1.联合的大小至少是最大成员的大小

2.当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1135577.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

链表顺序表—优缺点对比

目录 链表&#xff1a; 优点&#xff1a; 缺点&#xff1a; 顺序表&#xff1a; 缺点&#xff1a; 优点&#xff1a; 链表&#xff1a; 优点&#xff1a; 1、任意位置插入删除O(1) 2、按需申请释放空间 缺点&#xff1a; 1、不支持下标随机访问 2、CPU高速缓存命中率…

基于Qt实现的轻量级CAD画图软件

1.界面展示 2.功能展示 3.关于Graphics View绘图架构 由于QPainter画图形&#xff0c;不能实现对图形的选择、编辑、移动等操作&#xff0c;所以我的整个项目是基于Craphics View绘图架构来实现的 3.1 Craphics View &#xff08;1&#xff09;Craphics View绘图架构介绍 …

js echarts 词云图

<!DOCTYPE html> <html><head><meta charset"utf-8" /><title>词云图</title><script src"./echarts.js"></script><script src"./echarts-wordcloud.js"></script></head>&…

vivado简单仿真入门

打开软件 创建工程 create project ![在这里插入图片描述](https://img-blog.csdnimg.cn/892eda626d394733920854b71ca8f726.png)先next,保留工程路径&#xff0c;配置环境 配置芯片环境 本次芯片类型 xc7k325tffg900-2 创建之后完整的demo 编写仿真内容 timescale 1ns/1…

一、高效构建Java应用:Maven入门和进阶

一、高效构建Java应用&#xff1a;Maven入门和进阶 目录 一、Maven简介和快速入门 1.1 Maven介绍1.2 Maven主要作用理解1.3 Maven安装和配置 二、基于IDEA的Maven工程创建 2.1梳理Maven工程GAVP属性2.2 Idea构建Maven JavaSE工程2.3 Idea构建Maven JavaEE工程2.4 Maven工程项…

态路小课堂丨InfiniBand AOC有源光缆简介

TARLUZ态路 InfiniBand&#xff08;直译为“无限带宽”技术&#xff0c;缩写为IB&#xff09;是一个用于高性能计算的计算机网络通信标准&#xff0c;它具有极高的吞吐量和极低的延迟&#xff0c;用于计算机与计算机之间的数据互连。InfiniBand也用作服务器与存储系统之间的直…

如何在《阴阳师》游戏中使用单机单窗口软件工具进行防封技巧?

如何在《阴阳师》游戏中使用单机单窗口软件工具进行防封技巧&#xff1f; 首先&#xff0c;定义在《阴阳师》游戏中&#xff0c;使用单机单窗口软件工具进行防封技巧涉及到如何安装和配置软件&#xff0c;以及如何在游戏中应用这些技巧。 我曾经使用过在《阴阳师》游戏中防封…

Fatal error in launcher: Unable to create process using

python在安装库的时候报如下错误&#xff1a; Fatal error in launcher: Unable to create process using "e:\python3.8.8\python.exe" "D:\python3.8.8\Scripts\pip.exe" install qrcode[pil]: ??????????? 除了卸载重装之外&#xff0c;还…

zk-Bench:SNARKs性能对比评估工具

1. 引言 JENS ERNSTBERGER等人2023年论文《zk-Bench: A Toolset for Comparative Evaluation and Performance Benchmarking of SNARKs》。 zk-Bench&#xff0c;定位为&#xff1a; 定位为首个公钥密码学性能评估基准测试框架和工具&#xff0c;重点关注通用ZKP系统的实测评…

JVS规则引擎决策流:轻松解决规则执行中的潜在问题

规则引擎的在线调试功能是指用户在编辑、创建或修改规则时&#xff0c;可以实时地测试这些规则执行的效果&#xff0c;以便及时发现和解决潜在的问题。 接下来我们详细看看在线调试的功能&#xff1a; 1、配置对应的规则&#xff08;这里略过&#xff09;如下图所示&#xff…

蓝桥云课--1024 第 2 场算法双周赛

2-铺地板【算法赛】&#xff08;找规律&#xff09; 一、题目要求 二、思路 &#xff08;1&#xff09;因为每块地砖都是2*3的规格&#xff1a; 1.n<2或者m<2的时候&#xff0c;则不能使用上述规格的瓷砖 No 2.n<3或者m<3的时候&#xff0c;也不能使用上述规格…

项目管理中,如何建立里程碑式管理?

项目进度控制是项目管理中的重要环节&#xff0c;也是最具挑战性的工作之一。在项目管理中&#xff0c;项目进度失控受到多种因素的影响&#xff0c;导致项目失控。 为了解决这个问题&#xff0c;我们可以借鉴在旅途中学到的经验&#xff0c;通过设立里程碑来了解项目进度&am…

macOS 12 Monterey v12.7.1正式版:开启全新的操作系统体验

macOS 12 Monterey已经向所有兼容的Mac设备推出&#xff0c;为您带来了一系列强大的新功能和改进。这个全新的操作系统版本&#xff0c;不仅带来了更流畅的用户体验&#xff0c;还增强了与iOS设备的无缝集成&#xff0c;让您的设备使用更加高效&#xff0c;更加便捷。 macOS 1…

一文说尽零售数据分析指标体系

零售的本质业务模式是通过在各种渠道上吸引客户来购买我们的商品来实现盈利&#xff0c;其实就是客户-渠道-商品&#xff0c;也就是我们常说的“人、场、货”&#xff0c;除此之外还有供应链、财务等起到重要的辅助作用。因此如果要构建起系统化的零售数据分析指标体系&#xf…

压敏电阻有哪些原理?|深圳比创达电子EMC

压敏电阻是一种金属氧化物陶瓷半导体电阻器。它以氧化锌(ZnO)为基料&#xff0c;加入多种(一般5&#xff5e;10种)其它添加剂&#xff0c;经压制成坯体&#xff0c;高温烧结&#xff0c;成为具有晶界特性的多晶半导体陶瓷组件。氧化锌压敏电阻器的微观结构如下图1所示。 氧化锌…

linux 内存检测工具 kfence 详解(二)

系列博文&#xff1a; linux 内存检测工具 kfence 详解(一) linux 内存检测工具 kfence 详解(二) 回上一篇博文 0. 前言 kfence虽然代码不多&#xff0c;但设计的内容、逻辑比较多。为了更加清晰、轻松地理解kfence&#xff0c;笔者将其知识点分两篇博文&#xff1a; 第一篇…

美海军用于情报、监视和侦察的中大型无人系统概述

源自&#xff1a;中国指挥与控制学会 “人工智能技术与咨询” 发布 简 介 1 引 言 2 超大型无人潜航器 图1 波音“回声航行者”无人潜航器 图2 “虎鲸”超大型无人潜航器 3 中型无人水面舰艇 图3 “海鹰”无人水面舰艇 图4 “海上猎人”无人水面舰艇 4 …

今天玩到一个微信钓鱼小游戏,很有趣,居然还能玩萝卜刀

这款钓鱼冠军微信小游戏很有创意&#xff0c;除了传统的钓鱼玩法&#xff0c;居然还融合了黄金矿工的玩法&#xff0c;很不错的想法&#xff0c;而且居然还能玩最近比较火的萝卜刀&#xff0c;快来扫码体验一下吧&#xff0c;或者微信里搜索 《钓鱼冠军》小游戏&#xff0c;认…

Python轮廓追踪【OpenCV形态学操作】

文章目录 概要代码运行结果 概要 一些理论知识 OpenCV形态学操作理论1 OpenCV形态学操作理论2 OpenCV轮廓操作|轮廓类似详解 代码 代码如下&#xff0c;可以直接运行 import cv2 as cv# 定义结构元素 kernel cv.getStructuringElement(cv.MORPH_RECT, (3, 3)) # print kern…

YOLOv8官方教程:如何训练模型

文章目录 一、简介二、YOLOv8的优点train模式特色 三、训练3.1 单 GPU 和 CPU 训练示例3.2 多GPU训练3.3 Apple M1 和 M2 MPS 训练3.4 恢复中断的训练 四、其他参数五、训练Log5.1 Comet5.2 ClearML5.3 TensorBoard 一、简介 本文介绍YOLOv8如何在单GPU、多GPU和Apple设备上训…