C语言自定义类型中结构体、结构体声明、结构体自引用、结构体变量的定义和初始化、结构体内存对齐,结构体传参,位段等的介绍

本文主要是介绍C语言自定义类型中结构体、结构体声明、结构体自引用、结构体变量的定义和初始化、结构体内存对齐,结构体传参,位段等的介绍,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 一、结构体
  • 二、 结构体声明
  • 三、 特殊的声明----匿名结构体类型
  • 四、 结构体的自引用
    • (1)数据结构
    • (2)结构体的自引用
  • 五、 结构体变量的定义和初始化
  • 六、 结构体内存对齐
    • `1. 结构体的对齐规则`
      • (1)结构体大小案例1
      • (2)结构体大小案例2
      • (3)结构体大小案例3
    • 2. 为什么存在内存对齐?
      • 1. 平台原因(移植原因)
      • 2.性能原因:
    • 3. 总体来说
  • 七、修改默认对齐数
  • 八、结构体传参
  • 九、位段
    • 1. 什么是位段
    • 2. 位段的内存分配
    • 3. 位段的跨平台问题
    • 4. 位段的使用
  • 总结


前言

C语言自定义类型中结构体、结构体声明、结构体自引用、结构体变量的定义和初始化、结构体内存对齐,结构体传参,位段等的介绍


一、结构体

  • 结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。

二、 结构体声明

    1. 结构体类型的模板
struct tag
{member-list;
}variable-list;
  • struct 是 结构体类型的关键字
  • tag 是结构体类型的标签
  • member-list是 结构体的成员
  • variable-list是 基于当前结构类型创建的变量
  • 例如
struct Point
{int x;int y;
}; 

struct Stu
{char name[20];int age;char tele[12];
}s1,s2;
    1. 结构体嵌套声明
struct Score
{float math;float english;float chinese;int C;
};struct Person
{char name[20];int grade;struct Score s;
};

三、 特殊的声明----匿名结构体类型

  • 匿名结构体类型
  • 定义结构体类型时可以带标签,也可以不带标签。
  • 这种特殊的声明只能使用一次,即 基于当前结构体类型创建的变量。
struct
{int n;char ch;float pi;
}num1, num2;
  • 程序中只能使用基于这个结构体类型创建的 num1 和 num2变量,不能重新创建变量。
struct
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}a[20], * p;
  • 上述代码,两个匿名结构体的成员一样。
  • 第二个struct 和 * 结合表示 p是个指针变量
  • 但是编译器认为 &x 和 p 是两个不同类型的指针。

四、 结构体的自引用

(1)数据结构

  • 数据结构就是数据在内存中的存储结构
  • 数据结构有 线形(顺序表、链表等)、树形(二叉树等)等
  • 链表如下:
    在这里插入图片描述

(2)结构体的自引用

struct Node
{int date;struct Node* next;
};
  • 结构体中包含同类型的结构体指针,就叫做结构体自引用

typedef

typedef struct Node
{int date;struct Node* next;
}Node;// 此时
struct Node n1;
Node n2; 
// 等价的

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

  1. 在创建类型的同时,进行初始化。
struct Point
{int x;int y;
}p1 = {3,4};
  1. 使用声明的结构体类型进行初始化
struct Point
{int x;int y;
};int main()
{struct Point p2 = { 1 , 2 };return 0;
}
  1. 嵌套结构体类型的初始化
struct Score
{float math;float english;float chinese;int C;
};struct Person
{char name[20];int grade;struct Score s;
};#include <stdio.h>int main()
{struct Person p1 = { "zhangsan", 6, {99.5, 60.5,100.0, 98} };printf("%s %d %f %f  %f %d", p1.name, p1.grade, p1.s.math, p1.s.english, p1.s.chinese, p1.s.C);// 输出结果为 zhangsan 6 99.500000 60.500000  100.000000 98return 0;
}

六、 结构体内存对齐

1. 结构体的对齐规则

  1. 第一个成员在与结构体变量的偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
    - VS 中默认的值为 8。
    - 只有 VS 编译器有默认对齐数,其他编译器上的对齐数就是成员大小。
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
    offsetof 它是一个宏,它是用来 求 结构体成员在类型中距离起始位置的偏移量 offsetof( type, member)
    使用offsetof 宏 需要调用头文件 <stddef.h>

(1)结构体大小案例1

#include <stdio.h>
#include <stddef.h>
struct S1
{char c1;int i;char c2;
};
int main()
{printf("%d\n", sizeof(struct S1));// 12printf("%d\n", offsetof(struct S1, c1)); // 0printf("%d\n", offsetof(struct S1, i)); // 4printf("%d\n", offsetof(struct S1, c2)); // 8return 0;
}

在这里插入图片描述

(2)结构体大小案例2

#include <stdio.h>
#include <stddef.h>
struct S2
{char c1;char c2;int i;
};int main()
{printf("%d\n", sizeof(struct S2)); // 8printf("%d\n", offsetof(struct S2, c1)); // 0printf("%d\n", offsetof(struct S2, c2)); // 1printf("%d\n", offsetof(struct S2, i)); // 4return 0;
}

在这里插入图片描述

(3)结构体大小案例3

#include <stdio.h>
#include <stddef.h>
struct S2
{char c1;char c2;int i;
};
struct S3
{char c1;struct S2 s2; double d;
};
int main()
{printf("%d\n", sizeof(struct S3)); // 24printf("%d\n", offsetof(struct S3, c1)); // 0printf("%d\n", offsetof(struct S3, s2)); // 4printf("%d\n", offsetof(struct S3, d)); // 16return 0;
}
  • 因为如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

  • char c1位于 偏移量 0 处。

  • 由上可知, struct S2 s2 自己的最大对齐数是 4 , 所以它距离起始地址的偏移量为 4,大小为 8 个字节。

  • double d 位于 8 个倍数处, 即 偏移量为 16 的位置。大小 为 8 个字节。

  • 整个结构体大小为 所有最大对齐数的整数倍,所以 为 8 的倍数,即 24 个字节大小。

  • 由此可见,结构体的内存对齐会有空间的浪费。

2. 为什么存在内存对齐?

1. 平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;
某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2.性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对其的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

在这里插入图片描述

3. 总体来说

结构体的内存对齐是拿 空间 换取 时间 的做法。

在设计结构体的时候,我们既要满足对齐,又要省空间, 如何做到:

让空间小的成员尽量集中在一起

七、修改默认对齐数

使用 #pragma 这个预处理命令,可以修改默认对齐数

#pragma pack(1) // 设置默认对齐数为 8 
struct S1
{char c1;int i;char c2;
};
#pragma pack() // 取消设置的默认对齐数, 还原为默认
  • 结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。

八、结构体传参

#include <stdio.h>struct S 
{int data[1000];int count;
};void print1(struct S ss)
{int i = 0;for (i = 0; i < 3; i++){printf("%d ", ss.data[i]);}printf("%d\n", ss.count);
}void print2(struct S* ss)
{int i = 0;for (i = 0; i < 3; i++){printf("%d ", ss->data[i]);}printf("%d\n", ss->count);
}
int main()
{struct S s1 = { {1,2,3}, 100 };print1(s1); // 1 2 3 100print2(&s1); // 1 2 3 100return 0;
}
两种结构体传参都可以,但是传值调用,函数形参会重新开辟空间,降低性能。
但是 传址调用 只接收地址,不重新开辟空间,性能较好。
  • 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
  • 如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能下降。
  • 总之,结构体传参的时候,要传结构体的地址。

九、位段

1. 什么是位段

位段的声明和结构是类似的,有两个不同:

  1. 位段的成员必须是 int、unsigned int、signed int、或者char即整型家族。
  2. 位段的成员后边有一个冒号和数字。
#include <stdio.h>
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};int main()
{printf("%d\n", sizeof(struct A)); // 8return 0;
}
  • 位段 的成员变量 : 后边的数字指的是 这个变量占用 的比特位的个数。

2. 位段的内存分配

1. 位段的成员可以是 int unsigned int signed int 或者是 char(属于整形家族)类型
2. 位段的空间上是按照需要以4个字节(int)或者 1 个字节(char)的方式来开辟
3. 位段涉及很多不确定的因素,位段是不跨平台的,注重可移植性的程序应该避免使用位段。

3. 位段的跨平台问题

1. int 位段 被当成有符号数还是无符号数是不确定的。
2. 位段中最大的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准上未定义。
4. 当一个结构体包含两个位段,第二个位段成员比较大,无法容纳与第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

  • 跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

4. 位段的使用

  • 位段的在网络中有较好的作用,如 ip数据包。

总结

C语言自定义类型中结构体、结构体声明、结构体自引用、结构体变量的定义和初始化、结构体内存对齐,结构体传参,位段等的介绍

这篇关于C语言自定义类型中结构体、结构体声明、结构体自引用、结构体变量的定义和初始化、结构体内存对齐,结构体传参,位段等的介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/961772

相关文章

Rust中的BoxT之堆上的数据与递归类型详解

《Rust中的BoxT之堆上的数据与递归类型详解》本文介绍了Rust中的BoxT类型,包括其在堆与栈之间的内存分配,性能优势,以及如何利用BoxT来实现递归类型和处理大小未知类型,通过BoxT,Rus... 目录1. Box<T> 的基础知识1.1 堆与栈的分工1.2 性能优势2.1 递归类型的问题2.2

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

Python如何计算两个不同类型列表的相似度

《Python如何计算两个不同类型列表的相似度》在编程中,经常需要比较两个列表的相似度,尤其是当这两个列表包含不同类型的元素时,下面小编就来讲讲如何使用Python计算两个不同类型列表的相似度吧... 目录摘要引言数字类型相似度欧几里得距离曼哈顿距离字符串类型相似度Levenshtein距离Jaccard相

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型