深究C语言-5结构体后续(除结构体外的自定义类型)

简介: c99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。

上一篇我们说了好多关于结构体和链表的知识,但是还有一些知识我还是没有说到,今天来给大家梳理一下。


一,柔性数组


一,定义


c99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。


下面有两种定义方式。下标可以不写,也可以写作0;


struct s1 {
  int i;
  int a[];
};
struct s2 {
  int i;
  int a[0];
};


二,特点


ea20a59fd16c4d1688b898a23d3b1c9f.png

ee6800ed6013438397ac5bd372b8f6ac.png


三,使用


首先先看下面这两段代码


1.


#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
typedef struct s1 {
  int i;
  int a[];
}pnode;
int main() {
  //期待a数组的大小是10个整形
  pnode* ps = (pnode*)malloc(sizeof(pnode)+10*sizeof(int));
  //判断
  if (!ps)
    exit(1);
  //初始化数据
  ps->i = 10;
  for (int i = 0; i < 10; i++) {
    ps->a[i] = i;
  }
  //空间不足,运用realloc函数来增加。
  pnode* pss = (pnode*)realloc(ps,sizeof(pnode) + 20 * sizeof(int));
  //如果,分配内存成功,就把该指针的地址给ps,
  //要搞清楚这个函数的用法
  if (pss != NULL) {
    ps = pss;
  }
  //使用
  //释放
  free(ps);
  //释放后一定要记得置空
  ps = NULL;
  return 0;
}


2.


#define _CRT_SECURE_NO_WARNINGS 1
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
typedef struct s1 {
  int i;
  int *a;
}pnode;
int main() {
  pnode* ps = (pnode*)malloc(sizeof(pnode));
  //判断
  if (!ps)
    exit(1);
  //a此时并没有指向,是一个野指针,故现在要对其申请内存空间
  //给它10个整形的空间
  //这个地方也有不同的地方
  //这里是为了结构体指针变量ps指向的a指针申请内存空间,强制转换为int*类型。
  ps->a = (int *)malloc(10 * sizeof(int));
  //判断
  if (ps->a == NULL) {
    exit(1);
  }
  //初始化数据
  ps->i = 10;
  for (int i = 0; i < 10; i++) {
    ps->a[i] = i;
  }
  //空间不足,运用realloc函数来增加。这里我们需要单独对ps->a的地址来进行申请
  int* ptr = (int*)realloc(ps->a, 10 * sizeof(int));
  //判断,如果分配内存成功,就把该指针的地址给ps,
  if (ptr != NULL) {
    ps->a = ptr;
  }
  //使用
  //释放
  //这里的释放是有东西的,要先释放ps->a,因为如果先释放了ps,*a就成了野指针,他指向的空间已经被释放了,故不能再次使用。
  free(ps->a);
  ps->a = NULL;
  free(ps);
  ps = NULL;
  return 0;
}


9bdb0146674c4caf90970c3409182156.png


注意:


根据上面的两段代码,我们来看一看realloc这个函数。


21c1fa0b329b400fb0d4ecae132b798a.png


ptr必须是以前通过动态存储分配得到的指针。size是现在需要的空间大小,如果分配失败,返回NULL,同时原来ptr指向的存储块的内容不变。


如果成功,返回一片能存放大小为size的区块,并保证该块的内容与原块一致,如果size小于原来的区块大小,则内容为原块前size范围内的内容。如果新块更大,则原有数据存在新块的前一部分。后一部分记得要初始化。


如果分配成功,原存储块的内容就很可能被改变了,因此不允许再使用ptr去使用它。


二,位段


1de0011f835e41e183f1ff30de36fb31.png


成员也可以是char,char类型也是以整型的方式存储的。


ee9e23613b4a4bbaa07f22a91e5881db.png

3342c1192fd741059486e5711603c242.png


这个数字代表的是比特


cdddb4949b3148ceb633bed4b1589e67.png


该位段的大小为8个字节,由上可知,位段的大小空间开辟是根据需求以四个字节(int)或者一个字节(char)的方式来开辟的。


刚开始的时候开辟了四个字节用来存放,存完三个变量后,剩余的空间已经不足以用来存放d变量,故,需要再开辟一个int的空间,用来存放,所以,该位段的大小是8个字节。


再看,存放完a,b,c三个变量后,剩余15个比特位,C语言中并未说明这剩余的部分到底使不使用,所以一般使用位段时是不跨平台的。


8d68a295a46f4b4788f2dfe86e630613.png


需要注意的是,成员变量的大小不能大于一个int,因为充满了太多的不确定因素。


ce2f280365e74da7abe6e41b9560a7e5.png


三,枚举和联合体(共用体)


一,枚举


d6e922f1c7a547c6aab43640a61c9343.png


顾名思义,就是一一列举。


括号中间的就是它的成员常量,是枚举可能的取值,并不是说都会取到,一次使用只能取到其中一个常量。


f0bdaeff70694f97abf033ec82b3391a.png


枚举类型的成员都是常量,通常是指无符号整型,既然是常量,那么,他们都是有相应的值的。一般来说,都是从0开始,向后递增


52403d8b6bab4a7294d272effb2c2ac6.png


我们也可以根据自己的需要,对成员常量进行赋值。


那我们为什么要使用枚举呢?


0a955cac15d54a1783860b14a0710eb4.png


对于具体而言,枚举到底有什么用?以下面这个计算器的模拟为例。我设置了一个枚举类型,使程序的可读性增加。


#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void menu(void) {
  //提供菜单
  printf("************请选择****************\n");
  printf("******1.Add*********2.Sub*********\n");
  printf("******3.Mul*********4.Div*********\n"); 
  printf("************0.exit****************\n");
  printf("**********************************\n");
}
//写计算器要用的函数
int Add(int x, int y) {
  return x + y;
}
int Sub(int x, int y) {
  return x - y;
}
int Mul(int x, int y) {
  return x * y;
}
int Div(int x, int y) {
  return x / y;
}
enum s{
    exit,
    Add,
    Sub,
    Mul,
    Div
}
int main() {
  //因为计算器可以反复使用,所以用循环来写
  //至于用while还是do-while都是看自己喜好
  do {
    //在循环中定义变量,每次循环开始时初始化数据,就不会影响后面的运算
    int x, y, ret;
    x = y = ret = 0;
    int input=0;
    //输出菜单
    menu();
    //选择函数来运行程序,达到自己的目的
    printf("请选择:>");
    scanf("%d",&input);
    //因为选项很多,太过复杂。选择用switch结构
    switch (input) {
    case Add:
      printf("请输入2个操作数:>");
      scanf("%d%d", &x, &y);
      //调用函数
      ret = Add(x, y);
      printf("ret=%d\n", ret);
      break;
    case Sub:
      printf("请输入2个操作数:>");
      scanf("%d%d", &x, &y);
      ret = Sub(x, y);
      printf("ret=%d\n", ret);
      break;
    case Mul:
      printf("请输入2个操作数:>");
      scanf("%d%d", &x, &y);
      ret = Mul(x, y);
      printf("ret=%d\n", ret);
      break;
    case Div:
      printf("请输入2个操作数:>");
      scanf("%d%d", &x, &y);
      ret = Div(x, y);
      printf("ret=%d\n", ret);
      break;
    //如果输入为0,程序结束,直接跳出
    case exit:printf("程序结束");
      break;
    //如果输入的是其他数,给用户继续选择的机会。
    default:printf("选择错误,请重新选择");
      break;
    }
  } while (input);
  return 0;
}


还有一些操作


056a582c15aa49f5bcb2e1411bfadce4.png


我们可以直接把枚举类型变量定义为某一个变量,但是,不能直接赋值为一个整数,类型转换会导致数据的丢失。


d6a385663b314a8ea5bea99ba21bdfb1.png


也可以写一个限定符,限定blue就是枚举类型s的一个成员。


我们要知道的是,枚举类型的成员变量只能是int或者size_t int,所以,不管枚举类型中含有多少个成员,大小永远是4;


二,联合体(共用体)


一,定义


84bf2dd9d6b9464a8536134dfd3d9e7b.png


二,特点


915a6540ace444ea8ee33f42ca81b89f.png


8e4e66300a304087be205fed39511b0b.png


我们尝试来给新建的联合体变量赋值,我们发现,i和c里面存放了相同的值,这也就是所谓的共用体。


4464f02870c242719bd4bf197189c3a2.png


在联合体中最好是不要单独对其中的每个元素赋值,会影响其他成员的值。


7fb96087192e46328896c7ce2ac1951f.png


三,联合体的计算


bccfe72a51ae46d389d24e90ddc9d060.png

ccc2d2511aa24b608677dfd5b077f675.png


以这个题目为例,这个联合体的大小就是8.


好了,大概就说这么多,已经足够大家使用和学习了。


祝诸君学业有成,诸事顺利。

目录
相关文章
|
8月前
|
存储 安全 C语言
【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】
分支的语句,这可能不是预期的行为,这种现象被称为“case穿透”,在某些特定情况下可以利用这一特性来简化代码,但在大多数情况下,需要谨慎使用。编写一个程序,该程序需输入个人数据,进而预测其成年后的身高。根据提示,在右侧编辑器补充代码,计算并输出最终预测的身高。分支下的语句,提示用户输入无效。常量的值必须是唯一的,且在同一个。语句的作用至关重要,如果遗漏。开始你的任务吧,祝你成功!,程序将会继续执行下一个。常量都不匹配,就会执行。来确保程序的正确性。
240 10
|
8月前
|
小程序 C语言
【C语言程序设计——基础】顺序结构程序设计(头歌实践教学平台习题)【合集】
目录 任务描述 相关知识 编程要求 测试说明 我的通关代码: 测试结果: 任务描述 相关知识 编程编写一个程序,从键盘输入3个变量的值,例如a=5,b=6,c=7,然后将3个变量的值进行交换,使得a=6,b=7,c=5。面积=sqrt(s(s−a)(s−b)(s−c)),s=(a+b+c)/2。使用输入函数获取半径,格式指示符与数据类型一致,实验一下,不一致会如何。根据提示,在右侧编辑器补充代码,计算并输出圆的周长和面积。
161 10
|
8月前
|
存储 编译器 C语言
【C语言程序设计——选择结构程序设计】求一元二次方程的根(头歌实践教学平台习题)【合集】
本任务要求根据求根公式计算并输出一元二次方程的两个实根,精确到小数点后两位。若方程无实根,则输出提示信息。主要内容包括: - **任务描述**:使用求根公式计算一元二次方程的实根。 - **相关知识**:掌握 `sqrt()` 函数的基本使用方法,判断方程是否有实根。 - **编程要求**:根据输入的系数,计算并输出方程的根或提示无实根。 - **测试说明**:提供两组测试数据及预期输出,确保代码正确性。 - **通关代码**:包含完整的 C 语言代码示例,实现上述功能。 通过本任务,你将学会如何处理一元二次方程的求解问题,并熟悉 `sqrt()` 函数的使用。
132 5
|
8月前
|
存储 算法 安全
【C语言程序设计——选择结构程序设计】按从小到大排序三个数(头歌实践教学平台习题)【合集】
本任务要求从键盘输入三个数,并按从小到大的顺序排序后输出。主要内容包括: - **任务描述**:实现三个数的排序并输出。 - **编程要求**:根据提示在编辑器中补充代码。 - **相关知识**: - 选择结构(if、if-else、switch) - 主要语句类型(条件语句) - 比较操作与交换操作 - **测试说明**:提供两组测试数据及预期输出。 - **通关代码**:完整代码示例。 - **测试结果**:展示测试通过的结果。 通过本任务,你将掌握基本的选择结构和排序算法的应用。祝你成功!
112 4
|
9月前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
659 14
|
9月前
|
存储 编译器 C语言
【C语言】结构体详解 -《探索C语言的 “小宇宙” 》
结构体通过`struct`关键字定义。定义结构体时,需要指定结构体的名称以及结构体内部的成员变量。
434 10
|
10月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
829 13
|
10月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
311 12
|
10月前
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
8月前
|
存储 算法 安全
【C语言程序设计——选择结构程序设计】求阶跃函数的值(头歌实践教学平台习题)【合集】
本任务要求输入x的值,计算并输出特定阶跃函数的结果。主要内容包括: 1. **选择结构基本概念**:介绍if、if-else、switch语句。 2. **主要语句类型**:详细解释if、if-else、switch语句的使用方法。 3. **跃迁函数中变量的取值范围**:说明如何根据条件判断变量范围。 4. **计算阶跃函数的值**:通过示例展示如何根据给定条件计算函数值。 编程要求:在右侧编辑器Begin-End之间补充代码,实现阶跃函数的计算和输出。测试说明提供了多个输入及其预期输出,确保代码正确性。最后提供通关代码和测试结果,帮助理解整个过程。
109 0