目录

一.结构体struct

       结构体的声明

       结构体的自引用

       结构体的定义和初始化

       结构体的内存对齐

       修改默认对齐数

        offsetof宏

        结构体传参

二.位段

              位段的定义

  位段的内存分配

       位段的跨平台问题

三.枚举enum

枚举的定义

枚举的有点

枚举enum与#define的区别

四.联合(共用体)union

 联合的定义

联合的特点

联合union的大小端

联合大小的计算

五.下一站


下面开始本节的内容

一.结构体struct

定义

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

结构体的声明

struct tag
{
member -list;
}variable-list;

实例

#include<stdio.h>
struct S
{
const char c;
int a;
double d;
char arr[20];
};
int main()
{
struct S s = { 'c', 100, 3.14, "hello world" };
printf("%c,%d,%lf,%s\n", s.c, s.a, s.d, s.arr);
return 0;
}

C语言进阶·自定义(结构体,枚举,联合)_#define结构体的自引用

在链表中需要自己用到的就是结构体的自引用

 实例

//创建链表节点
struct Node
{
int data;
struct Node* next;
};

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

实例

嵌套型结构体

struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

单类型结构体

struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};

多类型结构体

struct Stu        //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"lisi", 22};//初始化

结构体的内存对齐

定义

struct中的各成员变量的存储地址有一套对齐的机制(让CPU能够更舒服地访问变量)。
总的来说就是:拿空间换取时间的说法。

对齐原则

第一个成员在与结构体变量偏移量为0的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数=编译器默认的一个对齐数与该成员大小的较小值
VS中默认的值为8
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处、
结构体整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

原因

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

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

实例

#include<stdio.h>
struct S1
{
char c1;
int a;
char c2;

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

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

}

C语言进阶·自定义(结构体,枚举,联合)_位段_02

解释

c1




c2

c2

c2

c2

c3


  0            1        2         3           4         5           6      7            8         9

第一个成员c1在与结构体变量偏移量为0的地址处
对于c2它的对齐数为4(int大小为4,小于平台默认值8),
该变量要对齐到偏移量为4的倍数处,即从偏移量为4的位置开始存放
对于c3(char类型的对齐数为1,正数都为1的倍数),从偏移量为9的位置开始放
该结构体的总大小须为最大对齐数(每个成员变量都有一个对齐数)
(这里也就是4)的整数倍,故为12(已经占用了9个字节)

  修改默认对齐数

#pragma pack()

在程序中可以使用#pragma pack()预处理来修改默认对齐数
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为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;
}

C语言进阶·自定义(结构体,枚举,联合)_嵌套_03

  offsetof宏

计算结构体中某变量相对于首地址的偏移,并给出说明

示例

#include <stdio.h>
#include<stddef.h>
struct S
{
char c;
int i;
double d;

};
int main()
{
printf("%d\n", offsetof(struct S, c));
printf("%d\n", offsetof(struct S, i));
printf("%d\n", offsetof(struct S, d));
return 0;
}

C语言进阶·自定义(结构体,枚举,联合)_#define_04

  结构体传参

struct S
{

int a;
char c;
double d;
};
void init(struct S* ps)
{
ps->a = 100;
ps->c = 'z';
ps->d = 3.14;
}
void print1(struct S tmp)//传值
{
printf("%d,%c,%lf\n", tmp.a, tmp.c, tmp.d);
}
void print2(struct S *ps)//传地址
{
printf("%d,%c,%lf\n", ps->a,ps->c,ps->d);
}

int main()
{
struct S s = { 0 };
init(&s);
print1(s);
print2(&s);
return 0;
}

C语言进阶·自定义(结构体,枚举,联合)_位段_05

两者结果都是相同的,但是我更推荐结构体传地址

原因

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

如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

结论:结构体传参的时候,要传结构体的地址

二.位段

位段的定义

定义:和结构体的定义类似。

位段的成员必须是int,unsigned int。signed int.

位段的成员后边必须要有一个冒号和一个数字。

struct A
{
int a : 2;
int b : 5;
};

位段的内存分配

位段的成员可以是 int unsigned int signed int或者是 char (属于整形家族)类型
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
struct S
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
int main()
{
struct S s;
printf("%d\n", sizeof(s));
return 0;

}

C语言进阶·自定义(结构体,枚举,联合)_#define_06

位段的跨平台问题

存在问题

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

结构

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

三.枚举enum

枚举的定义

枚举是一个集合(可以一一列举的类似元素)
(枚举成员)是一些命名的整型常量(元素之间用逗号隔开)
enum Day
{
mon;
tues;
wed;
thur;
};

enum day是枚举类型

mon tues wed thur 是枚举的可能取值

实例

enum Day
{
mon,
tues,
wed,
thur,
};
int main()
{
//enum Day d = mon;
printf("%d%d%d%d\n", mon, tues, wed, thur);
return 0;
}//输出结果0123

C语言进阶·自定义(结构体,枚举,联合)_#define_07

枚举的优点

增加代码的可读性和可维护性
和#define定义的标识符比较枚举有类型检查,更加严谨
防止了命名污染(封装)
便于调试
使用方便,一次可以定义多个常量

枚举enum与#define的区别

作用的时期:

#define是在预处理阶段直接进行替换,并且不进行类型检查,
枚举则是在程序运行之后才起作用

储存位置:

枚举则是在程序运行之后才起作用
枚举常量存储在数据段的静态存储区里

赋值类型:

#define可以赋值多种类型数据
枚举变量的大小只能为整型数据(例如:0、1、2…)
(enum当我们不主动对它进行赋值时,第一个枚举成员的默认值为整型的0,
后续枚举成员的值在前一个成员上加1,#define则不会)

使用定义:

#define宏一次只能定义一个
枚举可以一次定义大量相关的常量

调试:

一般在编译器里,可以调试枚举常量,但是不能调试宏常量

定义类型:

枚举量具有类型,宏没有类型;枚举常量属于常量,宏定义不是常量

四.联合(共用体)union

联合的定义

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

示例

union un
{
char c;
int i;
};

联合的特点

联合变量的大小:

联合的成员是共用同一块内存空间的,所以联合变量至少是最大成员的大小
(联合至少得有能力保存最大的那个成员)
union un
{
char c;
int i;
};
int main()
{
union un u;
printf("%d\n", sizeof(u));
return 0;
}

C语言进阶·自定义(结构体,枚举,联合)_#define_08

联合union的大小端

实例

 面试题:计算当前计算机的大小端

int sys()
{
int a = 1;
return *(char*)&a;
}
int main()
{
int a = 1;
int ret = sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}

C语言进阶·自定义(结构体,枚举,联合)_#define_09

int sys()
{
union un
{
char c;
int i;
}u;
u.i = 1;
return u.c;

//int a = 1;
//return *(char*)&a;
}
int main()
{
int a = 1;
int ret = sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}

C语言进阶·自定义(结构体,枚举,联合)_位段_10

联合大小的计算

联合的大小至少是最大成员的大小。
当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐最大对齐数的整数倍。

实例

union un
{
int i;
char arr[5];

};
int main()
{
union un u;
printf("%d\n", sizeof(u));
return 0;
}

C语言进阶·自定义(结构体,枚举,联合)_嵌套_11

五.下一站

大路走尽还有小路,只要不停地走,就有数不尽的风光。

C语言进阶·自定义(结构体,枚举,联合)_嵌套_12

本章完