IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言进阶(十一) - 自定义数据类型 -> 正文阅读

[C++知识库]C语言进阶(十一) - 自定义数据类型

前言

C语言中本身包含了许多数据类型,但并不能够总是满足需要。自定义类型允许使用者创造出特定的且适合需要的类型。本文主要介绍结构体、位段、枚举与联合。


1. 结构体

结构体是一些值的集合,这些值的类型可以相同,也可以不同,称为结构体的成员变量。与数组相似但不同。结构体是常用的自定义类型。

1.1 结构体的声明

关键字struct

普通声明

struct tag{//结构体标签(结构体名struct tag)
    member_list;//结构体成员列表
}veriable_list;//结构体变量列表(可以没有)

例如描述一个学生的信息的结构体类型:

struct student{
    char name[20];//学生姓名
    char num[15];//学生学号
    double score;//学生平均成绩
};

隐式声明

隐式声明:省略结构体标签的结构体声明。

struct {
    member_list;//结构体成员列表
}veriable_list;//结构体变量列表
  • 隐式声明的结构体由于没有名字只能在声明时才能定义变量,在之后不能够定义变量。
  • 每个隐式声明的结构体类型都是不相同的,即使是成员变量完全相同的情况下。

例如:

#include <stdio.h>

struct {
    int a;
    char b;
}c;

struct {
    int a;
    char b;
}*p;

int main() {

    p = &c;//此处会发生隐式类型转换
	return 0;
}

1.2 结构体的自引用

一个结构体中包含本身(结构体)的指针作为结构体成员。

struct tag{
    int data;
    struct tag* next;
};

使用typedef对结构体进行重命名
正确写法

typedef struct Node{
    int data;
    struct Node* next;
}Node;

错误写法

typedef struct Node{
    int data;
    Node* next;
}Node;
//此处是先对结构体进行重命名为Node再使用Node* next作为结构体成员变量;还是先使用Node*next作为结构体成员变量再对结构体重命名为Node。这两种解释都会产生前后矛盾的情况。

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

在声明结构体的同时定义变量和对变量初始化

struct student{
    char name[20];//姓名
    int num;//学号
}s1;//结构体变量s1的定义
//-------------------------------
struct student{
    char name[20];//姓名
    int num;//学号
}s1, s2 = {"sunwukong", 1001};//结构体变量s1、s2的定义与s2的初始化
//--------------------------------
struct Node
{
    int data;
    struct student s;
    struct Node* next;
}n = {10, {"tangsheng", 1002}, NULL};//结构体嵌套定义与初始化

先声明结构体类型在定义变量和对变量初始化

struct student{
    char name[20];//姓名
    int num;//学号
};
struct student s1;//结构体变量s1的定义
//------------------------------
struct student{
    char name[20];//姓名
    int num;//学号
};
struct student s1;//结构体变量s1的定义
struct student s2 = {"sunwukong", 1001};//结构体变量s2的定义与s2的初始化
//------------------------------
struct Node
{
    int data;
    struct student s;
    struct Node* next;
};
struct Node n = {10, {"tangsheng", 1002}, NULL};//结构体嵌套定义与初始化

1.4 结构体变量的大小 - 结构体内存对齐

结构体是一些值的集合,定义一个结构体变量时,在内存中会分配一片连续的内存空间作为结构体变量的空间。那么这片连续的空间究竟是多大呢,这里需要直到结构体内存对齐的知识才能正确知道结构体变量的大小。

结构体对齐规则

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员要对齐到某个数字(对齐数)的整数倍的地址处。

某一成员的对齐数 = 编译器默认的一个对齐数(如果有的话)与该成员大小的较小值。

visual studio 2019编译器默认对齐数是8。
3. 结构体总大小是所有成员变量对齐数中的最大对齐数的整数倍。
3. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体变量的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

例子:

  • 相同成员的两个结构体,但是成员的顺序不同也会导致结构体变量的大小不同。
  • 结构成员所占内存小的集中放在前面会使结构体变量的大小更小。
#include <stdio.h>

struct S1{
    char c1;
	int i;
	char c2;
};

struct S2
{
    char c1;
    char c2;
    int i;
};

int main() {
	printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
	return 0;
}

image.png
运行结果:
image.png
嵌套结构体的大小:

#include <stdio.h>

struct S1 {
	char c1;
	int i;
	char c2;
};

struct S2
{
	char c1;
	struct S1 s2;
	double d;
};

int main() {
	printf("%d\n", sizeof(struct S2));
	return 0;
}

image.png
运行结果:
image.png

内存对齐产生的原因

  1. 平台原因

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

  1. 性能原因

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

这是空间换时间的方法。
定义结构体类型时让占用空间小的成员变量尽量集中在一起,用来减少内存对齐带来的空间的浪费。

修改默认对齐数

#pragma预处理指令#pragma pack()可以修改它后面代码的默认对齐数(如果有的话),直到再次出现#pragma pack() 结束对默认对齐数的修改。

#include <stdio.h>
#pragma pack(4)//设置默认对齐数为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;
}

运行结果:
image.png

1.5 结构体传参

传值(结构体)

#include <stdio.h>

struct student {
	char name[20];
	int num;
};
void Print(struct student stu) {
	printf("%s %d\n", stu.name, stu.num);
}

int main() {
	struct student s = { "sunwukong", 10001 };
	Print(s);
	return 0;
}

运行结果:
image.png

传地址

#include <stdio.h>

struct student {
	char name[20];
	int num;
};
void Print(const struct student* p) {
	printf("%s %d\n", p->name, p->num);
}

int main() {
	struct student s = { "sunwukong", 10001 };
	Print(&s);
	return 0;
}

运行结果:
image.png

在传地址与传值调用都可以完成任务时,传地址调用相比传值调用更好。

  • 因为函数传参时,参数是需要压栈的,会有时间和空间上的系统开销。
  • 在传递一个结构体对象时,如果结构体过大的话,参数压栈的系统开销也会较大,将会导致性能的下降。

结构体传参时主选传地址。


2. 位段 -结构体拓展

结构体具有实现位段的能力。也就是说二者比较像。

2.1 初识位段

位段的声明

与结构体声明类似,也有不同:

  • 位段的成员只能是整型家族的成员(包括char);
  • 位段的成员后面有一个冒号和一个数字。这个数字表示该成员占内存的几个bit(位)
struct S{
    int a:2;
    int b:4;
    int c:16;
};

2.2 位段的内存分配

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

visual studio 2019下的位段空间开辟举例

#include <stdio.h>

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main() {
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("%d\n", sizeof(s));
	return 0;
}

image.png
image.png

2.3 位段的跨平台问题

  1. 位段中int有符号还是无符号是未定义的,与普通情况下int是有符号的不同。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32)最大位如果是25那么在16位机器上编译不通过,在32位机器上正常运行。
  3. 位段中的成员在内存中是从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段成员,第二个位段成员比较大,第一个位段剩余的位无法容纳第二个位段成员时,是舍弃剩余的位还是利用是不确定的。

与结构相比,位段可以达到同样的效果,但是可以很好地节省空间,只是有跨平台的问题存在。
跨平台的问题不是说包含位段的程序不能够跨平台,只是说要写出适应该平台的位段代码。

2.4 位段的应用

网络中减少数据包的大小。
image.png


3. 枚举 - 可以列举的常量

枚举,顾名思义可以一一列举,并且是常量。
生活中有许多事物可以一一列举:星期、月份等……

3.1 枚举类型的定义

与结构体相似:
关键字enum

enum tag{//标签(枚举名enum tag)
    constant_list,//枚举常量- 即枚举类型的可能取值
}veriable_list;//枚举变量()可以没有
  • 枚举常量的是有值的,这些值默认从0开始递增,相邻枚举常量之间默认相差1。
  • 也可以对枚举常量赋初值,这样被赋值的枚举常量及之后的枚举常量的值都会随着初值而改变,它之前的还是默认值。

例子:

#include <stdio.h>

enum color {
    RED,//0
    ORANGE,//1
    YELLOW,//2
    GREEN,//3
    CYAN = 10,//10
    BLUE,//11
    PURPLE//12
}c1;//枚举变量c1的定义

int main() {
    c1 = RED;
    enum color c2 = BLUE;//枚举变量c2的定义与初始化
    printf("%d\n", c1);
    printf("%d\n", c2);
	return 0;
}

3.2 枚举的优点

#define也可以定义常量,实现与枚举相同的效果。但是枚举在此功能上相比#define有着几个优点:

  1. 增加代码的可读性和可维护性;
  2. #define定义的标识符相比枚举有类型检查,更加严谨。
  3. 防止了命名污染,把常量封装了起来。
  4. 便于调试。
  5. 使用方便,一次可以定义多个常量。

3.3 枚举的使用

#include <stdio.h>

enum week {
	MONDAY = 1,
	TUESDAY,
	WEDNESDAY,
	THURSDAY,
	FRIDAY,
	SATURDAY,
	SUNDAY
};
int main() {
	enum week a = MONDAY;//枚举变量a存放枚举常量MONDAY,值是1
	enum week b = 1;//枚举变量b存放变量1,虽然值是1,但是类型不匹配,在c++编译下不能通过编译。

	return 0;
}

4. 联合(共用体)

与结构体类似,但是成员共用一块内存空间。

4.1 联合类型的定义

联合类型包含一系列成员,这些成员共用同一块空间。

union tag{//标签(联合名union tag)
    member_list;//成员列表
}veriable_list;//变量列表(可以没有)

不能在定义联合变量的同时的其初始化。

#include <stdio.h>

union un {
	int a;
	char b;
}c;//联合变量c的定义

int main() {
	c.a = 10;
	union un d;//联合变量d的定义。

	return 0;
}

4.2 联合大小的计算

联合的大小至少是最大成员的大小
当最大成员的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
最大对齐数参考结构体。

visual studio 2019 举例

#include <stdio.h>

union Un1 {
	char c[5];
	int i;
};
union Un2 {
	short c[7];
	int i;
};

int main() {
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));

	return 0;
}

union Un1:

  • char c[5]所占大小是5个字节,单个元素是char,大小是1,默认对齐数是8,故其对齐数就是1
  • i所占大小是4个字节,默认对齐数是8,故对齐数是4
  • 最大成员大小是5个字节,最大对齐数是4,故联合的大小是8字节

union Un2:

  • short c[7]所占大小是14个字节,单个元素是short,大小是2,默认对齐数是8,故其对齐数就是2
  • i所占大小是4个字节,默认对齐数是8,故对齐数是4
  • 最大成员大小是14个字节,最大对齐数是4,故联合的大小是16个字节。

结语

本节主要介绍了自定义类型相关的结构体位段枚举联合。了解并熟悉这些自定义类型可以帮助理解数据结构等相关的知识。


END

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 18:34:21  更:2022-07-20 18:37:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 20:42:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码