C语言中自定义数据类型

文章目录

C语言中自定义数据类型

枚举类型

  • 形式:
    声明:enum 枚举类型名 { 枚举符列表, }枚举变量名;
    使用:enum 枚举类型名 枚举变量名 = 枚举符;

    • 枚举变量占4字节与int相同,存储方式相同,在cpu看来就是一个int型变量。
    • 枚举常量则是在编译的时候确定其值,#define宏常量是在预编译阶段进行简单替换。
  • 常规的枚举变量 (enum MY_enum color)

c 复制代码
#include <stdio.h>

enum MY_enum {
    red,
    green = 3,
    blue
};

int main() {
    enum MY_enum color = red;
    printf("%d", color);
    return 0;
}
  • 匿名枚举变量 (color)
c 复制代码
#include <stdio.h>

enum {
    red,
    green,
    blue
} color;

int main() {
    color = red;
    printf("%d", color);
    return 0;
}
  • 一些约束

    • 不推荐直接对其进行加减等运算。
    • 建议不要随便赋给枚举变量枚举符之外的值,赋值时会进行隐式类型转换与int类型一致。
    • 如果出现与enum的枚举符同名的变量,在之后的程序中都将按新的变量进行,不要使用枚举符同名的变量,有的编译器甚至不报错!
c 复制代码
#include <stdio.h>
enum MY_enum{
    red,
    green,
    blue
};

int main() {
    enum MY_enum color = blue;
    printf("%d / %d / %d \n",blue , color, sizeof(color));
    // color占4字节跟int一样

    int red = 0x05;    
    color = red;
    printf("%d", color);
    // 这里color的值变为0x05

    enum MY_enum* penum = &color;

    printf("%d", *penum);
    return 0;
}

结构体类型

  • 形式
    声明:'struct 结构体类型名 {结构体声明列表;} 结构体变量名;'
    使用: 'struct 结构体类名 结构体变量名 = {,,};'
c 复制代码
#include <stdio.h>

struct PointType
{
    int x;
    int y;
} point1 = {1,2};


int main() {
    struct PointType point2 = {.x = 20};
    struct PointType* p = &point1;
    printf("%d / %d", p->x, p->y);
    return 0;
}

{,}为初始化器(initializer),可用.的方式赋值及指定成员的初始化器(designated initializer)

结构体复合字面量(structural compund literal)

对于结构体变量用.的方式引出

对于结构体指针用->的方式引出

  • 位域(bit fields)
    • 形式:'类型 标识符 : 位宽;'
    • 约束:
  1. 不能对位域成员做取地址操作(在某些情况下会触发hard fault)
  2. 位域成员不能用sizeof
  3. 不能用对齐属性来修饰位域
  4. 位宽不能超出该类型的大小
c 复制代码
#include <stdio.h>
#include <stdint.h>

enum MYenum {
    ENUM1,
    ENUM2,
    ENUM3
};

int main() {
    struct BitField {
        uint32_t a : 6;
        // uint8_t b : 12; warring:invalid size for bit field
        // _Alignas(int32_t) int32_t a : 5; warring:attribute "_Alignas" does not apply to bit fields
        uint16_t b : sizeof(ENUM1);  // uint16_t b : 4;
        enum MYenum e : ENUM3; // enum MYenum e : 2;
    }bf1 = {10,20}, bf2 = {.a = 10, .e = ENUM3};
    return 0;
}
  • 位域的存储

    • 两相互毗邻的位域成员,若第一个成员仍有足够的存储空间,则第二个位域成员会与第一个
      成员打包在一起
    • 为了满足字节对齐,需在某些位域变量间补零
    • 一个单元内的位域分配次序从高位到低位还是从低位到高位由具体实现决定
           e  y  x      c               b      a
      (0) 10 1 0000 011101111(00000)01010 011000  
      高位  <-------------------------------> 低位
c 复制代码
#include <stdio.h> 
#include <stdint.h>
#include <stdbool.h>

int main() {
    enum MyEnum {
        ENUM1,
        ENUM2,
        ENUM3
    };
    struct MyStruct {
        int32_t a : 6;
        int16_t b : 5;
        int8_t c : 8;
        char x : 4;
        bool y : 1;
        enum MyEnum e : 2;
    }s = {0x18,0x0a,0x77,'\0',true,ENUM3};

    // s 的二进制表示为
    //      e  y  x      c               b      a
    // (0) 10 1 0000 011101111(00000)01010 011000
    //  高位  <-------------------------------> 低位

    return 0;
}

如果你使用了符合字面量(compound literal)赋值,gcc编译器会直接将函数内的普通结构体变量升级为global或static一样的变量(即拥有全局作用域或者静态生存期)来赋值

asm 复制代码
.LC0:
        .byte   152
        .byte   2
        .byte   119
        .byte   80

main:
        mov     eax, DWORD PTR .LC0[rip]
        mov     DWORD PTR [rbp-8], eax

对于单独对每个结构体变量赋值的操作就会相当麻烦,因为涉及存储时的字节对齐等,需要一堆移位和与或的运算。例如:

asm 复制代码
        movzx   eax, BYTE PTR [rbp-8]
        and     eax, -64
        or      eax, 24
        mov     BYTE PTR [rbp-8], al
        movzx   eax, WORD PTR [rbp-8]
        and     ax, -1985
        or      ax, 640
        mov     WORD PTR [rbp-8], ax
        mov     BYTE PTR [rbp-6], 119
        movzx   eax, BYTE PTR [rbp-5]
        and     eax, -16
        mov     BYTE PTR [rbp-5], al
        movzx   eax, BYTE PTR [rbp-5]
        or      eax, 16
        mov     BYTE PTR [rbp-5], al
        movzx   eax, BYTE PTR [rbp-5]
        and     eax, -97
        or      eax, 64
        mov     BYTE PTR [rbp-5], al
        
        ;MOVZX --- Move With Zero-Extend
        ;MOVSX/MOVSXD --- Move With Sign-Extension
  • 字节对齐与字节填充

一般会进行4字节对齐,即变量的起始地址一定是4字节的倍数。

  • _Alignof和_Alignas操作符

_Alignof(表达式)返回表达式所占的字节数

_Alignas()用户自定义该变量的字节对齐方式可以为(0,1,2,4,8,12,4的整数倍)

c 复制代码
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdalign.h>
int main() {
    size_t size = _Alignof(max_align_t);
    printf("%zu \n", size);
    size = alignof(bool);
    printf("%zu \n", size);
    return 0;
}
  • 结构体成员的字节对齐和字节填充
  1. 结构体第一个成员所在的偏移地址为0。
  2. 每个成员根据其类型或程序员指定对其字节数来判定偏移地址,如果无法堆积,则在两个成员之间补0,直至其地址可以对齐。
  3. 结构体对象的字节对齐要求与其成员中最大字节对齐要求相一致。
  4. 结构体对象的大小为其对齐字节数的倍数
c 复制代码
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdalign.h>

int main() {
    struct {
        int8_t a;
        int32_t b;
        int16_t c;
        int64_t d;
    } s = {0x01,0x02,0x03,0x04};
    return 0;
/*
  a               b        c                               d     
|  |        |           |     |                 |                       |
 10 00 00 00 20 00 00 00 30 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00
*/
}

复数类型

#include <complex.h>这个头文件之后便可使用复数类型。

但是谁会真的这样用捏,毕竟很多运行环境并不支持这么做

c 复制代码
#include <stdio.h> 
#include <stdint.h>
#include <stdbool.h>
#include <complex.h>

int main() {
    float complex comp = 2.5f + 1.5f;
    comp += 3.0f * I;
    float r = crealf(comp);
    float i = cimagf(comp);
    long double complex comp2 = {1.2f, 2.5f};
    return 0;
}

------ By Flier

2024.1.25

相关推荐
~yY…s<#>1 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
EricWang13583 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
我是谁??3 小时前
C/C++使用AddressSanitizer检测内存错误
c语言·c++
希言JY4 小时前
C字符串 | 字符串处理函数 | 使用 | 原理 | 实现
c语言·开发语言
午言若4 小时前
C语言比较两个字符串是否相同
c语言
TeYiToKu6 小时前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
互联网打工人no16 小时前
每日一题——第一百二十四题
c语言
爱吃生蚝的于勒6 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~6 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
洋2406 小时前
C语言常用标准库函数
c语言·开发语言