C/C++语言基础--结构体知识详解(包括:结构体数组、字节对齐、位段等内容)

news2024/12/24 9:09:21

本专栏目的

  • 更新C/C++的基础语法,包括C++的一些新特性

前言

  • C语言地结构体是核心内容之一,他运行自定义数据类型,可以将不同地数据类型当作成一个整体,变成一个数据类型,运用及其广泛
  • 欢迎点赞 + 收藏 + 关注,本人将会持续更新加粗样式

文章目录

  • 结构体
    • 结构体是什么?
    • 结构体的申明
    • 结构体变量定义
    • 结构体变量初始化
    • 结构体变量的使用
    • 结构体嵌套
    • 结构体数组
    • 结构体字节对齐
      • 什么是字节对齐?
      • 为什么要字节对齐?
      • 字节对齐规则
    • 位段(位域)
      • 位段是什么?
      • 注意事项

结构体

结构体是什么?

在前面我们学习过基础的数据类型int float char 等,都只能用来表示基础的数据类型,那么要怎么来表示复杂的数据类型呢?

比如学生信息:

学号姓名性别年龄总分数
100maye18666
101椰汁19555
在这里插入图片描述

在这里插入图片描述

定义5个数组,然后每个数组的长度都一致是否可行?

int ids[N]={0};
char names[N][10]={0};
char sexs[N][3]={0};
int ages[N]={0};
int scores[N]={0};

看起来还不错,在没有学习结构体之前这样还是不错的,但是,学了结构体后,就会发现简单很多

**数组:**可存储相同数据类型的变量。

结构体:用户自定义的数据类型,它允许存储不同类型的数据项(数据项被称为"成员"),C语言因为有了结构体,所以C语言可以模拟面向对象的思想去写代码。

结构体的申明

为了定义结构体,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:

struct tag
{
    member1;
    member2;
    member3;
    ...
};
  • tag 是结构体标签;
  • member 是标准的变量定义,比如int i;char c;或者其他有效的变量定义。
  • 多个成员之间用分号分隔(不允许0成员结构体的定义)
  • 末尾的分号不可缺少。

那么对于上面的学生的信息,就可以用如下结构体表示学生结构体类型:

struct Student	//学生结构体类型
{
    int id;			//学号
    char name[10];	//姓名
    char sex;		//性别
    int age;		//年龄
    int score;		//总分
}

在这里插入图片描述

在C语言中结构体相当于是用户自定义的数据类型,是一组变量的集合,是一种封装的思想,而在C++、java等编程语言中,在结构体的思想上进行扩展延申,产生了,类不仅是一组变量的集合,也是一组自定义API接口的集合,后面等我们介绍C++、python、java等语言的时候可以反过来看。

结构体变量定义

结构体类型已经声明,如何使用结构体类型定义结构体变量呢?有三种方法:

  1. 先声明结构体类型再定义结构体变量
struct Student maye;
  1. 在声明结构体类型的同时定义变量
struct Student
{
    ...
}maye;
  1. typedef取别名之后再定义变量
typedef struct Student
{    
    ...
}Student;	//加了typedef之后,这里的Student就是struct Student 的别名了
Student maye;
  • 通过第一种方法定义结构体变量时,struct关键字不能省略

结构体变量初始化

在定义结构体变量的同时通过{}的方式为每一个成员变量进行赋初值

  • 全部初始化
struct Student maye = {100,"maye",1,18,666};
  • 部分初始化:未初始化部分自动初始化为0
struct Student maye = {100};
  • 全部初始化为0
struct Student maye = {0};
  • 初始化指定的成员(可以初始化任意成员,不必遵循定义顺序)
struct Student maye = {.id = 100,.age = 18};
  • 用另一个结构体变量初始化:
struct Student zc = maye;
zc = (struct Student){200"zc"};
  • 和数组初始化一样,只能从左边开始逐个初始化,不能跳过
  • 初始化时,类型及顺序要一一对应

结构体变量的使用

要通过结构体变量访问成员,就需要用到成员访问运算符(. 或 ->)~

  • 普通结构体变量访问成员使用 .
struct Student hero = {007,"007特工"};
puts(hero.name);
  • 通过结构体指针访问成员使用 ->
struct Student *ph = &hero;
(*ph).name;  等价于:ph->name;

结构体嵌套

在一个结构体内包含另一个结构体作为其成员,有两种写法。

比如,给学生增加一个出生日期,包含年月日.

  1. 先定义好结构体,然后在另一个结构体中定义结构体变量
struct Date
{
    short year;
    short month;
    short day;
};

struct Student
{
    int id;
    char name[10];
    struct Date birth;	//出生日期
};
  1. 直接把结构体定义在另一个结构体内。
struct Student
{
    int id;
    char name[10];
    struct Date
	{
    	short year;
    	short month;
    	short day;
	}birth;//出生日期
};
  • 当出现结构体嵌套时,必须以级联方式访问结构体成员,即通过成员访问运算符逐级找到最底层的成员。
struct Student maye;
maye.birth.year = 2022;
maye.birth.month = 2;
maye.birth.day = 9;

struct Student zc = {2000,"顽石",{2021,5,14}};

结构体数组

一个结构体变量可以存放一个学生的一组信息,可是如果有 10 个学生呢?难道要定义 10 个结构体变量吗?难道上面的程序要复制和粘贴 10 次吗?

很明显不可能,这时就要使用数组,因为数组就是一组相同数据类型的集合,而结构体也是一种数据类型,结构体中也有数组,称为结构体数组,定义如下:

struct Student stus[10];

这就定义了一个结构体数组,共有 10 个元素,每个元素都是一个结构体变量,都包含所有的结构体成员。

下面编写一个程序:

编程要求:从键盘输入 5 个学生的基本信息,如学号、姓名、年龄、性别,然后将年龄最大的学生的基本信息输出到屏幕。

#include <stdio.h>

struct Student
{
    int id;
    char name[10];
    int age;
    char sex;
};
/*
1001 小红 22 F
1002 小明 21 M
1003 小欣 23 F
1004 小天 20 F
1005 小黑 19 M
*/

int main()
{
    struct Student stus[10];
    for (int i = 0; i < 5; i++)
    {
        printf("input %d stu>",i+1);
        scanf("%d %s %d %c",&stus[i].id,stus[i].name,&stus[i].age,&stus[i].sex);
    }

    struct Student maxStu = stus[0];
    for (int i = 0; i < 5; i++)
    {
        if (maxStu.age < stus[i].age)
        {
            maxStu = stus[i];
        }         
    }
    printf("%d %s %d %c\n", maxStu.id,maxStu.name, maxStu.age, maxStu.sex);

    return 0;
}

输出:

1001 小红 22 F
1002 小明 21 M
1003 小欣 23 F
1004 小天 20 F
1005 小黑 19 M
1003 小欣 23 F

另外一种方式:

结构体数组也是能够初始化的,如下:

int main()
{
    struct Student stus[10] = {{1001, "小红", 22, 'F'},{1002,"小明" ,21,' M'},{1003,"小欣" ,23, 'F'},{1004, "小天", 20, 'F'},{1005, "小黑", 19, 'M'}};

    struct Student maxStu = stus[0];
    for (int i = 0; i < 5; i++)
    {
        if (maxStu.age < stus[i].age)
        {
            maxStu = stus[i];
        }         
    }
    printf("%d %s %d %c\n", maxStu.id,maxStu.name, maxStu.age, maxStu.sex);

    return 0;
}

结构体字节对齐

每种类型在定义对象时,都会开辟内存,类型不同所占内存大小也不一样,用sizeof即可获取类型大小。

思考:结构体占用的内存大小是多少呢?

  • 是成员所占内存的总和吗?
  • 还是有其他的处理方式?

先来看几个例子吧:

#include<stdio.h>

struct Node
{
    int a;
    int b;
};

int main()
{
    printf("%d\n",sizeof(struct Node));
    return 0;
}
struct Node
{
    int a;
    int b;
};	
输出:
8

这个结构体大小为8个字节,看起来是成员大小的总和,实际上这只是个巧合:当成员类型全部一样时,结构体大小就等于每个成员大小之和。

struct Node1
{
    int a;
    char b;
};	
输出:
8

这个结构体大小还是8个字节,为什么?

再来看:

struct Node1
{
    int a;
    char b;
    char c;
    char d;
    char e;
};	
输出:
8

这个还是8,为什么呢?

这实际上是编译器对结构体的空间进行了优化,就是所谓的字节对齐,核心就是对此思想

什么是字节对齐?

从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列,而不是简单地顺序排列,这就是内存对齐。

为什么要字节对齐?

原因:

  • 兼容平台:某些平台只能在特定的地址处访问特定类型的数据;
  • 提高存取数据的速度:比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。

字节对齐规则

C语言标准并没有规定内存对齐的细节,而是交给具体的编译器去实现,但是对齐的基本原则是一样的,核心思想对称思想

  • 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
  • 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;
  • 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

位段(位域)

位段是什么?

C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为位段。说白了就是自己指定储存大小,因为储存字节对其,可能会浪费资源,利用位段能够用较少的位数存储数据。

语法:

struct 结构体名
{
  	  整数类型 位段名1 : 位段大小;
      整数类型 位段名2 : 位段大小;
      整数类型 位段名3 : 位段大小;
      整数类型 位段名4 : 位段大小;
      ...
};
  • 整数类型:C语言标准规定,只有有限的几种数据类型可以用于位段。(所有整数类型以及char类型和_Bool类型)。
  • 位段名:即有效的标识符
  • 位段大小:此位段所占的位数,不能超过类型的最大位数。

范例:

struct BitField
{
    unsigned char a:1;
    unsigned char b:4;
    unsigned char c:3;
};
int main()
{
    //初始化
    struct BitField bit={1,2,3};
    //输出
    printf("first:%d %d %d\n",bit.a,bit.b,bit.c);
    //赋值
    bit.a = 2;
    bit.b = 20;
    bit.c = 8;
    //再次输出
    printf("last:%d %d %d\n",bit.a,bit.b,bit.c);
}

运行结果:

fast:1 2 3
last:0 4 0

第一次的输出结果都是完整的,第二次输出的结果令人摸不着头脑。

  • **第一次输出时:**a、b、c的值分别为1、2、3,转换成二进制分别是0b1、0b10、0b11,都没有超出限定的位数,能正常输出。
  • 第二次输出时:a、b、c的值分别为2、20、8,转换成二进制分别是0b10、0b10100、0b1000,所有位段都超出了限定的位数,不能正常输出
    • 超出部分被直接截去(从高位开始截断,即从左往右),截去之后的二进制分别为0b0、0b0100、0b000,换算成十进制分别为0、4、0

注意事项

1、位段的内存分配:位段占的二进制位数不能超过该基本类型所能表示的最大位数,如char是占1个字节,那么最多只能是8位;

struct Bit
{
  	char a:3;	//right
	char b:9;	//error C2034: “d”: 位域类型对位数太小
};

2、**位域的存储:**C语言标准并没有规定位域的具体存储方式,不同的编译器有不同的实现,但它们都尽量压缩存储空间。

当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。

struct Bf1
{
    char a:3:
    char b:3;
};
//sizeof(struct Bf1) == 1

struct Bf2
{
    char a:3:
    char b:3;
    char c:3;
};
//sizeof(struct Bf2) == 2

3、禁止对位段取地址:地址是字节(Byte)的编号,而不是位(Bit)的编号。

&bit.a;		//error C2104: 位域上的“&”被忽略

4、无名位段:位域成员可以没有名称,只给出数据类型和位宽

struct Bf
{
    int a:12;
    int :20;
    int b:4;
};

无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。

上面的例子中,如果没有位宽为 20 的无名成员,a、b 将会挨着存储,sizeof(struct Bf) 的结果为 4;有了这 20 位作为填充,a、b 将分开存储,sizeof(struct Bf) 的结果为 8

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

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

相关文章

UE管理内容 —— Alembic File Importer

目录 从Maya导出ABC缓存 导入ABC到UE 导入为静态网格体 导入为几何体缓存 导入为Skeletal Alembic文件格式(.abc)是一个开放的计算机图形交换框架&#xff0c;将复杂的动画化场景浓缩成一组非过程式的、与应用程序无关的烘焙几何结果&#xff1b;可以在外部自由地创建复杂…

如何查看ubuntu版本

在当前的技术环境中&#xff0c;了解操作系统的具体版本对于用户来说至关重要。这不仅能确保软件兼容性&#xff0c;还有助于进行系统管理和故障排查。对于使用Ubuntu系统的用户来说&#xff0c;有几种不同的方法可以查看当前系统的版本。下面将详细介绍如何查看您的Ubuntu系统…

CSS文本样式(二)

一、水平对齐文本 1、text-align属性 text-align​属性指定元素中文本的​水平对齐方式​。 默认情况下&#xff0c;您网站上的文字左对齐。 但是&#xff0c;有时您可能需要不同的对齐方式。 文本对齐属性值如下&#xff1a;​left​&#xff0c;​right​&#xff0c;​cen…

数据结构(Java实现):链表与LinkedList

文章目录 1. 单向链表1.1 链表的概念及结构1.2 链表的实现1.2.1 单向链表类和节点1.2.2 打印每个节点的值1.2.3 计算链表长度1.2.4 头插节点1.2.5 尾插节点1.2.6 在指定下标插入新节点1.2.7 判断是否存在某个节点1.2.8 移除某个节点1.2.9 移除所有指定节点1.2.10 清空链表1.2.1…

【Linux:管道】

进程间通信背景&#xff1a; 每一个进程想要访问物理内存&#xff0c;都是通过访问进程虚拟地址空间当中的虚拟地址进行访问&#xff0c;访问时&#xff0c;通过各自的页表结构&#xff0c;造成了每一个进程和每一个进程的数据独立&#xff0c;由于进程独立性的存在&#xff0c…

Java | Leetcode Java题解之第373题查找和最小的K对数字

题目&#xff1a; 题解&#xff1a; class Solution {public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {int m nums1.length;int n nums2.length;/*二分查找第 k 小的数对和的大小*/int left nums1[0] nums2[0];int right nums…

Github 2024-08-25 php开源项目日报 Top10

根据Github Trendings的统计,今日(2024-08-25统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10Blade项目1Laravel: 以优雅语法简化Web开发 创建周期:4028 天开发语言:PHP协议类型:MIT LicenseStar数量:30824 个Fork数量:1052…

windows安装wsl,出现错误WslRegisterDistribution failed with error: 0x8007019e的解决方案

错误WslRegisterDistribution failed with error: 0x8007019e 笔者直接从Microsoft Store 安装 WSL后&#xff0c;没有其他操作&#xff0c;直接打开WSL&#xff0c;结果出现Error: 0x8007019e错误提示&#xff1a; Error 0x8007019e 解决方案 &#xff08;1&#xff09;Win…

滑块自动化分析

大家好!我是炒青椒不放辣,关注我,收看每期的编程干货。 滑块分析是爬虫工程师进阶必备技能,当我们遇到一个问题时可能会有多种解决途径,而如何做出高效的抉择和完善的解决流程又需要经验的积累。本期文章将以实战的方式,带你使用 playwright 进行滑块分析,不仅会告诉你应…

iPhone抹掉数据后能恢复吗?详解数据恢复的可能性与方法

在使用iPhone的过程中&#xff0c;有时候我们会因为各种原因选择“抹掉所有内容和设置”&#xff0c;以期望将手机恢复到出厂状态。然而&#xff0c;一旦执行了这个操作&#xff0c;很多用户就会开始担心&#xff1a;iPhone抹掉数据后&#xff0c;这些数据还能恢复吗&#xff1…

VMware安装Ubuntu20.04

1. 下载 整理的镜像链接 阿里网盘&#xff1a;阿里云盘快传 2. 新建虚拟机向导 选择自定义&#xff0c;然后下一步。 默认配置&#xff0c;下一步。 选择稍后安装操作系统&#xff0c;下一步。 选择操作系统Linux&#xff0c;版本Ubuntu64位&#xff0c;下一步。 给虚拟机命名…

2534. 乘方 [CSP-J 2022]

代码 #include<bits/stdc.h> using namespace std; int main() {long long n,m,i,sum1;cin>>n>>m;for(i1;i<m;i){sum*n;if(sum>1000000000){cout<<-1;return 0;;}}cout<<sum;return 0; } 记得点赞关注收藏&#xff01;&#xff01;&…

根据股票列表获取资金流入情况

获取股票列表 作为演示&#xff0c;以创业板为例&#xff08;数据不多&#xff09;&#xff0c;我们通过自编的 get_stock_list 方法获取股票列表&#xff1a; import pandas from bad import BigAData from tqdm.notebook import tqdmplate cyb bad BigAData() json bad.…

180页某项目可视化智能停车场系统技术解决方案WORD

今天分享的是一份《180页某项目可视化智能停车场系统技术解决方案WORD》&#xff0c;资料详细完整的描述了关于数智化停车场的建设方案&#xff0c;参考价值很高。 传统停车场存在进出场效率低、找车位难、找车难、管理难、管理成本高等诸多问题&#xff0c;本次建设的XX项目将…

四、控制结构

文章目录 引言一、顺序控制二、分支控制&#xff08;if&#xff0c;else&#xff0c;switch&#xff09;2.1 if 单分支2.2 if 双分支2.3 if 多分支2.4 if 嵌套分支2.5 switch分支结构2.6 switch和if的比较 三、循环控制&#xff08;for&#xff0c;while&#xff0c;dowhile&am…

[Linux#47][网络] 网络协议 | TCP/IP模型 | 以太网通信

目录 1.网络协议 2.协议分层 2.1 OSI七层模型 2.2TCP/IP五层(四层)模型 2.3 以太网通信 1.网络协议 "协议"本质就是一种约定 计算机之间的传输媒介是光信号和电信号. 通过 "频率" 和 "强弱" 来表示 0 和 1 这样的 信息. 要想传递各种不同…

全志616系统启动和登录

一、系统启动 刷完机烧入镜像&#xff0c;直接用MobaXterm软件串口登陆 约定固定的波特率115200。 默认登录&#xff1a; 用户&#xff1a;orangepi 密码&#xff1a;orangepi 或用户&#xff1a;root 密码&#xff1a;orangepi 在输入密码时…

SEO之网站结构优化(十三-网站地图)

** 初创企业搭建网站的朋友看1号文章&#xff1b;想学习云计算&#xff0c;怎么入门看2号文章谢谢支持&#xff1a; ** 1、我给不会敲代码又想搭建网站的人建议 2、“新手上云”能够为你开启探索云世界的第一步 博客&#xff1a;阿幸SEO~探索搜索排名之道 网站无论大小&…

5分钟学会使用Linux的 grep、find、ls、wc 命令

Linux基础命令和工具 一、前导&#xff1a;概述1.1、监控1.2、测试1.3、优化 二、grep 搜索字符三、find 查找文件四、ls 显示文件五、wc 命令六、总结 一、前导&#xff1a;概述 本系列主要讲解Linux运行时命令&#xff0c;包括网络、磁盘、内存、CPU相关参数等&#xff0c;主…

伏图芯片应力仿真功能介绍

随着电子产品向小型化、规模化、集成化方向发展&#xff0c;机械应力对器件性能的影响日益显著。产品在晶圆加工、芯片封装、元器件装配等过程中均会受到机械应力的作用&#xff0c;可能会直接影响芯片的电性能和可靠性。 仿真技术在芯片产品研发设计和故障排查阶段扮演着至关…