首页 > 其他分享 >C语言——自定义类型

C语言——自定义类型

时间:2024-10-14 12:17:10浏览次数:9  
标签:位段 struct 自定义 int C语言 char 类型 对齐 结构

目录

一、结构体

        1、 结构体的定义与声明

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

        3、结构体的自引用

         4、结构体的内存对齐

         5、为什么要结构体的内存对齐

                1、性能原因 

                2、平台原因

        6、结构体传参 

 二、 位段

         1、位端的内存分配

        2、位段的跨平台问题 

        3、位段使⽤的注意事项 

 三、枚举

四、联合体(共用体) 

        1、联合体的大小计算

         2、联合体的特点


一、结构体

        1、 结构体的定义与声明

        基本架构:

        typede stuct 名字

        {

                成员;

                成员;

        }变量;

        typedef重命名结构体,接下来在创建结构体时可以直接使用重命名的名字

【注意】

        结构体定义完成后,必须在后面加上一个分号(;)表示结束

//基本架构
stuct 名字
{
   成员;
   成员; 
}变量;

//结构体普通声明
struct pop
{
    char[20] name;
    int age;
    char sex[5];
};//注意分号不能丢

//typedef重命名结构体,接下来在创建结构体时可以直接使用重命名的名字
typedef struct num
{
    int x;
    int y;
}Num;

int main()
{
    struct num a1 = { 1,2 };
    Num a2 = { 1,2 };
    printf("%d %d\n", a1.x, a1.y);// 1 2
    printf("%d %d\n", a2.x, a2.y);// 1 2
    return 0;
}

//结构体的特殊声明
//匿名结构体类型,这种类型只能使用一次
struct 
{
    int a;
    int b;
}num;
int main()
{
    //匿名结构体类型,这种类型只能使用一次
    num.a=1;
    printf("%d\n",num.a);// 1
    return 0;
}

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

struct num
{
	int x;
	int y;
}a1;//声明类型的同时定义变量
int main()
{
    struct num a2;//定义结构体变量
    struct num a3 = { 1,2 };//定义结构体变量并初始化
    return 0;
}

        3、结构体的自引用

                结构体中可以包含一个类型为结构体本身的成员,但是必须要是指针类型的,链表就是基于结构体的自引用实现的

//错误版本
struct node
{
  int a;
  struct node b;  
};
//在sizeof()计算大小时无法计算其大小

//正确版本
struct node
{
    int a;
    struct node* b;
};

         4、结构体的内存对齐

                引入

                        计算一下有着相同成员的结构体,成员位置不同的大小

//结构体的内存对齐
struct node1
{
    char q1;// 1
    char q2;// 1
    int q3;// 4
};

struct node2
{
    char q1;// 1
    int q2;// 4
    char q3;// 1
};
int main() 
{
    //分别计算两个结构体的大小
    printf("%d\n", sizeof(struct node1));// 8
    printf("%d\n", sizeof(struct node2));// 12
    return 0;
}

          为什么有着相同元素的结构体,只是单纯的调换了一下元素位置,所得到的结构体大写会不一样?                                    

         正是因为有着结构体的内存对齐,才会导致有着相同元素的结构体,因顺序不同,所占内存的大小可能会有所不同

        结构体的内存对齐的规则

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

2、对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值

      VS中默认的值为8

      Linux中gcc没有默认对齐数,对齐数就是成员自身大小

3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍

4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

        node1大小计算: 

struct node1
{
    //第一个成员在与结构体变量偏移量为0的地址处
    char q1;// 1
    //其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
    //编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 1 取最小值为 1
    //此时的偏移量为1,是对齐数的倍数
    char q2;// 1
    //编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 4 取最小值为 4
    //此时的偏移量为2,不是对齐数的倍数,所以要偏移到最近的对齐数倍数,也就是4处
    int q3;// 4
    //结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
    //此时的偏移量为7,不满足最大对齐数(4)的整数倍,所以要偏移到最近的最大对齐数倍数,也就是8处
    //因此结构体的总大小为8
};

        node2的大小计算: 

struct node2
{
    //第一个成员在与结构体变量偏移量为0的地址处
    char q1;// 1
    //其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
    
    //编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 4 取最小值为 4
    //此时的偏移量为1,不是对齐数的倍数,所以要偏移到最近的对齐数倍数,也就是4处
    int q2;// 4
    //编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 1 取最小值为 1
    //此时的偏移量为8,是对齐数的倍数
    char q3;// 1
    //结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
    //此时的偏移量为9,不满足最大对齐数(4)的整数倍,所以要偏移到最近的最大对齐数倍数,也就是12处
    //因此结构体的总大小为12
};

        嵌套结构体的大小计算: 

struct node1
{
    char q1;// 1
    char q2;// 1
    int q3;// 4
    struct node2 q4;// 12
};

struct node2
{
    char q1;// 1
    int q2;// 4
    char q3;// 1
};
 //第一个成员在与结构体变量偏移量为0的地址处
char q1;// 1

//其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
//编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 1 取最小值为 1
//此时的偏移量为1,是对齐数的倍数
char q2;// 1

//编译器默认的一个对齐数 与 该成员大小的较小值,就是 8 与 4 取最小值为 4
//此时的偏移量为2,不是对齐数的倍数,所以要偏移到最近的对齐数倍数,也就是4处
int q3;// 4

//嵌套的结构体对齐到自己的最大对齐数的整数倍处,struct node2 的最大对齐数为4
//此时的偏移量为8,是对齐数的倍数
struct node2 q4;// 12

//嵌套的结构体的结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
//此时的偏移量为20,满足最大对齐数(4)的整数倍
//因此结构体的总大小为20

#include <stdio.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
        //输出的是12         
	return 0;
}
//结构体嵌套计算大小
#include <stdio.h>
struct S3
{
	double d;
	char c;
	int i;
};
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
int main()
{
	printf("%d\n", sizeof(struct S3));
        //输出的是16
	printf("%d\n", sizeof(struct S4));
        //输出的是32
	return 0;
}

         5、为什么要结构体的内存对齐

                1、性能原因 

                        数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。 

                2、平台原因

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

        总体来说:结构体的内存对⻬是拿空间来换取时间的做法

        6、结构体传参 

        结构体传参的时候,建议传结构体的地址

原因:

       1、 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

       2、如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降

#include <stdio.h>
struct S
{
    int data[1000];
    int num;
};

//结构体传参
void print1(struct S s)
{
    printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
    printf("%d\n", ps->num);
}
int main()
{
    struct S s = { {1,2,3,4}, 1000 };
    print1(s); //传结构体
    print2(&s); //传地址
    return 0;
}

 二、 位段

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

        1、位段的成员必须是 int、unsigned int 或signed int 

        2、位段的成员名后边有一个冒号和一个数字

struct A
{
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};
int main()
{
    printf("%d\n", sizeof(struct A));//8
    return 0;
}

         1、位端的内存分配

        1、位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

        2、位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的

        3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段 


struct A
{
    char _a : 3;
    char _b : 4;
    char _c : 5;
    char _d : 4;
};
int main()
{
    struct A node = { 0 };
    node._a = 10;
    //10的二进制序列为 1010,但是 a 只占 3 个字节,因此在内存中只会存入 010
    node._b = 12;
    //12的二进制序列为 1100,但是 b 占 4 个字节,因此在内存中只会入 1100
    node._c = 3;
    //3的二进制序列为 11,但是 c 占 5 个字节,因此在内存中只会入 11
    //此时之前开辟的一个字节已经不够 5 个字节,所以内存会重新开辟一个字节
    node._d = 4;
    //4的二进制序列为 100,但是 d 占 4 个字节,因此在内存中只会入 100
    //此时之前开辟的一个字节已经不够 4 个字节,所以内存会重新开辟一个字节

    //最后一个 struct A 所占 3 个字节的内存大小
    //二进制序列为:01100010 00000011 00000100
    //十六进制序列为:0x62 03 04
    int a = 0;
    return 0;
}

 

        2、位段的跨平台问题 

        1、int 位段被当成有符号数还是无符号数是不确定的。

        2、位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。

        3、位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

        4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

        5、总结跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在

        3、位段使⽤的注意事项 

        1、位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的

        2、不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员

struct A
{
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};
int main()
{
    struct A node = { 0 };
    //不能对位段的成员使⽤&操作符
    scanf("%d", &node._a);//erreor

    int tmp = 0;
    scanf("%d", &tmp);
    node._a = tmp;//yes
    return 0;
}

 三、枚举

        枚举顾名思义就是一一列举,把可能的取值一一列举

enum Day//星期
{
    Mon,
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sun
};
enum Sex//性别
{
    MALE,
    FEMALE,
    SECRET
};
enum Color//颜色
{
    RED,
    GREEN,
    BLUE
};
int main()
{
    //创建枚举变量
    enum Color q1=RED;
    enum Sex q2=MALE;
    return 0;
}

         枚举的优点

        1、增加代码的可读性和可维护性

        2、和#define定义的标识符比较枚举有类型检查,更加严谨。

        3、防止了命名污染(封装)

        4、便于调试

        5、使用方便,一次可以定义多个常量

四、联合体(共用体) 

        1、联合也是一种特殊的自定义类型这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)

        2、改变一个值,其他的值也会随机改变

#include <stdio.h>
//联合体的定义 
union Un 
{ 
    char c; 
    int i; 
};

int main()
{
    //创建联合体变量
    union Un un;
    //计算大小
    printf("%d\n", sizeof(un));
    //输出的是4
}

        

        1、联合体的大小计算

        1、联合的大小至少是最大成员的大小

        2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍

#include <stdio.h>
union Un1
{
	char c[5];//对齐数是1
	int i;
};
union Un2
{
	short c[7];//对齐数是2
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));
        //输出的是8
	printf("%d\n", sizeof(union Un2));
        //输出的是16
	return 0;
}

         

        2、联合体的特点

       联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的⼤⼩,⾄少是最⼤成员的⼤⼩

//联合体的定义 
union Un
{
    int o;
    int i;
};

int main()
{
    //创建联合体变量
    union Un un;
    //改变一个值,其他的值也会随机改变
    un.o = 20;
    un.i = 10;
    printf("%d %d\n", un.o, un.i);
    //10 10
    return 0;
}

标签:位段,struct,自定义,int,C语言,char,类型,对齐,结构
From: https://blog.csdn.net/LVZHUO_2022/article/details/142887862

相关文章

  • (C语言)算法数据结构
    王道数据结构以及本人上课的笔记             ......
  • 与C语言的旅程之分支与循环
                    C语⾔是结构化的程序设计语⾔,这⾥的结构指的是顺序结构、选择结构、循环结构,        C语⾔是能够实现这三种结构的,其实我们如果仔细分析,我们⽇常所⻅的事情都可以拆分为这三种结构或者这三种结构的组合。        我们......
  • C语言分支与循环的学习(小知识)
    学习目录 1.if 表达式成立(为真),则语句执行;表达式不成立(为假),这语句不执行。    注解:C语言中,非零表示真,0表示假。如果一个表达式的结果不是0,这语句执行。反之,则语句执行。实例:输入一个整数,判断是否为奇数该程序的执行逻辑是包含头文件#include<stdio.h>,输入主函数,......
  • 有关C语言中的数据类型(持续更新)
    有关计算机中的数据单位:计算机存储容量基本单位是字节(byte)字节byte:8个二进制位(bit)为一个字节(B),最常用的单位。一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。计算机的最小存储单位:比特(bit)位bit(比特)(BinaryDigits):存放一位二进制数,即......
  • C语言中输入/输出缓冲区行为乱序的问题
    问题代码这一串代码就是输出提示,读取输入,这样做3遍但是看到运行结果会发现,第二个和第三个的提示字符串输出到同一行了,没法输入操作符原因这是因为输入缓冲区的缘故当我们输入第一个数字1的时候,按下回车确认,但同样的,回车的换行符也同样保留在输入缓冲区了,数字1被读取消耗掉......
  • [java/spring/web] 深入理解:Spring @ExceptionHandler => 自定义应用异常处理器(Appli
    1概述:Spring@ExceptionHandler∈spring-web作用ExceptionHandler是Spring框架(spring-web模块)提供的一个注解,用于处理应用程序中的异常。当应用程序中发生异常时,ExceptionHandler将优先地拦截异常并处理它,然后将处理结果返回到前端。该注解可用于类级别和方法级别,以......
  • C++中的数据类型
    C++中的数据类型分为四大类:基本数据类型、派生数据类型、用户定义的数据类型和空类型。每类都有特定的用途和特性。1. 基本数据类型(Built-inDataTypes)这是C++中最基本的数据类型,包含整数、浮点数、字符和布尔类型。整数类型(IntegerTypes):int:标准整数类型。shortint......
  • 刷c语言练习题8(牛客网)
    1、如果有inta=5,b=3,在执行!a&&b++;后a和b的值分别是()A、5,3B、0,1C、0,3D、5,4答案:A解析:按照优先级顺序,先计算!a,得到0。由短路法则,b++不进行计算,又!a并没有改变a的值,所以a和b的值分别是5,3,选择选项A。2、以下程序的输出结果是()1234567main(){     ......
  • 【妙趣横生】01_C语言的指针是啥?为啥那么难懂?
      引入:C语言的指针是啥?为啥那么难懂?C语言中的指针是C语言的一个核心概念,也是其强大和灵活性的重要来源之一。然而,对于初学者来说,指针确实可能是一个难以理解的概念。下面我会尽量用简单的语言来解释什么是C语言中的指针,以及为什么它可能会让人觉得难懂。趣味解释:C语言......
  • 【趣学C语言和数据结构100例】
    【趣学C语言和数据结构100例】问题描述找出一个二维数组中的鞍点,即该位置上的元素在该行上最大、在该列上最小。也可能没有鞍点。有15个数按由大到小顺序存放在一个数组中,输入一个数,要求用折半查找法找出该数是数组中第几个元素的值。如果该数不在数组中,则输出“无......