自定义类型:结构体,枚举,联合

目录

前言

1. 结构体的声明

1.1 结构的基础知识

1.2 结构的声明

1.3 特殊的声明

1.4 结构的自引用

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

1.6 结构体内存对齐

1.7 修改默认对齐数

1.8 结构体传参

2. 位段

2.1 什么是位段

2.2 位段的内存分配

2.3 位段的跨平台问题

2.4 位段的应用

3. 枚举

3.1 枚举类型的定义

3.2 枚举的优点

3.3 枚举的使用

4. 联合体(共用体)

4.1 联合类型的定义

4.2 联合的特点

4.3 联合大小的计算



前言

C语言中有:

  • 内置类型
  • 自定义类型

内置类型有:

char、short、int、long 、long long、float、double。

内置类型是C语言本身就具有的类型,可以直接拿来使用,除了这些类型之外C语言还有自定义类型,自定义类型是自己所创造的一种类型。

自定义类型有:

结构体、枚举、联合体,数组。

1. 结构体的声明

1.1 结构的基础知识

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

结构体其实是把一些值组合在一起作为某一个对象的属性。

1.2 结构的声明

结构体的语法形式:

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

struct是——关键字;

tag——是结构体标签,类似于结构体的名字,在C语言中使用结构体类型时,应该把struct和tag写在一起;大括号里放的是描述对象所需要的相关属性:

member-list——是成员列表,这样的每一个成员是成员变量;

variable-list——是变量列表。

如定义一个学生的结构体类型:(描述一个学生)

#include <stdio.h>
struct Stu//结构体标签是Stu
{
	char name[20];
	char sex[5];//数组
	int age;
	int height;//整型
//成员列表里有多个成员变量
};//这里是结构体类型:struct Stu
int main()
{
	struct Stu s1;//s1就是用struct Stu这个结构体类型创建的结构体变量
	return 0;
}
#include <stdio.h>
struct Stu
{
	char name[20];
	char sex[5];
	int age;
	int height;
}s2, s3, s4;//这里直接用上面的struct Stu结构体类型创建了s1,s2,s3,3个结构体变量
//这里用逗号隔开可以一下子创建多个结构体变量,所以这里叫变量列表

//s2,s3,s4是全局变量
int main()
{
	struct Stu s1;//结构体变量,s1是局部变量
	return 0;
}
#include <stdio.h>
struct Stu
{
	char name[20];
	char sex[5];
	int age;
	int height;
}s2, s3, s4;//全局变量,注意这里有分号

//单独再定义一个全局变量:
struct Stu s5;//全局变量

int main()
{
	struct Stu s1;//局部变量
		return 0;
}

结构体类型最好放在前面定义。

1.3 特殊的声明

结构体有一些特殊的声明:
在声明结构的时候,可以不完全的声明。

比如:把标签去掉——不完全声明,没有名字是匿名结构体类型

#include <stdio.h>
struct
{
	char c;
	int a;
	double d;
}x;
//匿名结构体类型
//这里用匿名结构体类型创建了一个变量叫x
int main()
{
	return 0;
}

匿名结构体类型的使用,必须紧挨着结构体类型创建变量,否则以后就不能定义变量了,没有名字就不能定义了。

匿名结构体类型只能用一次:在声明结构体类型的同时定义变量。

#include <stdio.h>
struct
{
	char c;
	int a;
	double d;
}x;//匿名结构体

struct
{
	char c;
	int a;
	double d;
}* ps;//ps是这样一个匿名结构体的指针

int main()
{
//ps指针存放x的地址
	ps = &x;//错误:=两边从*到*的类型不兼容
//即编译器认为这是两种类型,虽然成员一模一样但是编译器
//认为匿名结构体在这里使用会是两种类型,认为这两种结构体类型是不一样的
//此代码非法
	return 0;
}

1.4 结构的自引用

在结构中包含一个类型为该结构本身的成员是否可以呢?

数据结构:数据在内存中存储的结构。如:顺序表、链表、栈、队列、二叉树。

顺序表:如在内存中找一块连续的空间存储1,2,3,4,5,像数组一样。对顺序表提供增删查改的功能就是顺序表的数据结构。像一条线

链表:1存2的地址,1能找到2;2存3的地址,2能找到3……最后的数字5存NULL,不存有效地址,它没有任何指向。找到1就可以把2,3,4,5串起来,像一条线。

顺序表和链表都像一条线,所以顺序表和链表是线性数据结构。

两个指针之间的交叉点是结点,怎么描述这样的结点呢?

错误的自引用方式:

#include <stdio.h>
struct Node
{
	int data;
	struct Node next;
};
int main()
{
	return 0;
}

此时struct Node的大小是多少?即计算sizeof(struct Node)?

struct Node
{
	int data;//结点处放的整型数据
	struct Node next;//一个结点中包含下一个结点
};
//结构体中又包含一个自己结构体类型这种形式写法是绝对不行的,因为
//struct Node这个结构体包含一整型data,又包含一个这样的结构体变量next,则
//这个结构体变量里面还有一个data,还有自己的next……无限包含,则
//这个结构体大小就没办法计算了
int main()
{
	return 0;
}

重新分析:一个结点中只要存储下一个结点的地址就可以了:

正确的自引用方式:

#include <stdio.h>
struct Node
{
	int data;
	struct Node* next;
};
int main()
{
	return 0;
}

自己类型的对象找自己类型对象的方法——要存储的是结构体指针而不是结构体对象,这是结构体的自引用。

#include <stdio.h>
struct Node
{
	int data;//4个字节
	struct Node* next;//存放一个地址(指针),4个字节或8个字节
};
//此时结构体大小就确定了

//此时创建的每个结点即可以保存一个数值data又可以保存一个地址(由next指向的结点)
//——这就实现了自己类型的对象找自己类型对象的方法,是结构体的自引用
int main()
{
	return 0;
}

这是链表的实现方式,也是结构体的自引用。

注意:

#include <stdio.h>
typedef struct
{
	int data;
	Node* next;
}Node;
int main()
{
	Node n;//这里定义结点n可以吗?
	return 0;
}

此时用typedef对struct和{}中的这个匿名结构体起名是Node,然后在结构体成员中写Node*next

——不可行!

因为运行时编译器显示错误:Node:未声明的标识符,即不认识Node,用typedef对结构体类型重命名时这个类型必须是清晰可见的,必须是存在的,即Node必须在有的前提下才能用typedef重新命名。

把一个结构体重命名,则这个结构体必须是清晰可见的。

#include <stdio.h>
typedef struct Node
{
	int data;
	//Node* next;//但是这里还没有产生Node,则:
	struct Node* next;
}Node;//这里是用typedef把struct Node这个结构体重命名为Node
int main()
{
    //重命名完之后就不用struct Node创建结构体变量了,直接用Node
    //但也还是可以用struct Node创建变量的
    //即struct Node和Node可以同时使用
	Node n = { 0 };
    struct Node n1 = { 0 };
	return 0;
}

typedef是关键字,是重命名结构体的。

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

有了类型就可以创建变量,即有了结构体类型就可以创建结构体变量。

定义结构体的方法:

#include <stdio.h>
struct Point//锁定点
{
	int x;//结构体成员x:横坐标
	int y;//结构体成员y:纵坐标
}p1;//定义结构体变量p1
//这里声明类型的同时定义了结构体变量p1

//这时就已经有了struct Point这个结构体类型

struct Point p2;//用struct Point这个结构体类型创建p2变量

struct Point p3 = { 1, 2 };//定义变量的同时赋初值

int main()
{
	return 0;
}

如一个结点的创建及初始化:

#include <stdio.h>
typedef struct Node
{
	int data;
	struct Node* next;
}Node;
int main()
{
	struct Node n = { 100,NULL };
	return 0;
}

如对一个学生类型初始化:

#include <stdio.h>
struct Stu
{
	char name[20];
	char sex[5];
	int age;
	int height;
};
int main()
{
	struct Stu s = { "zhangsan", "nan", 20, 180 };
	return 0;
}

初始化,只要把它的成员放在大括号中就可以了,同一个结构体多个成员初始化都用一个大括号,比如数组,结构体。

如果结构体成员是嵌套的,即一个结构体包含一个结构体,这时怎么进行初始化呢?

#include <stdio.h>
struct Stu
{
	char name[20];
	char sex[5];
	int age;
	int height;
};
struct Data
{
	struct Stu s;
//这是嵌套:结构体中包含一个结构体类型的数据
	char ch;
	double d;
};
int main()
{
	struct Data d = { {"lisi","nv",30,166},'w',3.14 };
//首先初始化s,因为s又是一个结构体成员所以用大括号括起来,后面在初始化ch、d
	return 0;
}
#include <stdio.h>
struct Point
{
	int x;
	int y;
}p1;
struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 10, {4, 5}, NULL };//结构体嵌套初始化

struct Node n2 = { 20,{5,6},NULL };//结构体嵌套初始化

int main()
{
	return 0;
}

结构体成员的访问可以用.和->这两个操作符访问。

1.6 结构体内存对齐

——重点

结构体内存对齐涉及到的最终问题就是计算结构体大小

#include <stdio.h>
struct s1
{
	char c1;//c1占1个字节大小
	int i;//4
	char c2;//1
};//6个字节空间即可
int main()
{
	printf("%d\n", sizeof(struct s1));//12,为什么?
	return 0;
}

研究c1、i、c2在内存中是如何存储的?
offsetof(),函数名是谁的偏移量,本身是一个宏,返回的是一个结构体成员相较于其结构体起始位置的偏移量。

size_t offsetof( structName, memberName );

参数是结构体名,结构体成员名;返回的值的类型是size_t,是个整型值。使用它需要包含头文件<stddef.h>。

#include <stdio.h>
#include <stddef.h>
struct s1
{
	char c1;
	int i;
	char c2;
};
int main()
{
	//printf("%d\n", sizeof(struct s1));
	printf("%d\n", offsetof(struct s1,c1));//计算c1的偏移量:0
	printf("%d\n", offsetof(struct s1, i));//计算i的偏移量:4
	printf("%d\n", offsetof(st
  • 37
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 35
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 35
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值