文档章节

C和指针---第十章:结构和联合

fzyz_sb
 fzyz_sb
发布于 2013/09/19 11:05
字数 2150
阅读 59
收藏 0

10.1 结构基础知识

聚合数据类型能够同时存储超过一个的单独数据。C语言提供了数组和结构两种聚合数据类型。

数组是相同类型的元素的集合,它的每一个元素是通过下标引用或指针简介访问来选择的。而结构也是一些值的集合,这些值称为它的成员,但一个结构的各个成员可能具有不同的类型。

数组元素可以通过下标访问,因为数组的元素长度相同。但是由于结构的成员可能长度不同,所以不能使用下标来访问它们。相反,每个结构成员都有自己的名字,它们是通过名字访问的。

这个区别非常重要。结构并不是一个它自身成员的数组。当一个结构变量在表达式中使用时,它并不被替换成一个指针。结构变量也无法使用下标来选择特定的成员。

10.1.1 结构声明

在声明结构时,必须列出它包含的所有成员。这个列表包括每个成员的类型和名字:

struct tag { member-list } variable-list;
例如:
struct {
	int		a;
	char	b;
	float	c;
} x;
这个声明创建了一个名叫x的变量,它包含三个成员:一个整数,一个字符和一个浮点数。
struct {
	int    a;
	char	b;
	float	c;
} y[20], *z;
这个声明创建了y和z。y是一个数组,它包含了20个结构。z是一个贺子珍,它指向这个类型的结构。

警告:这两个声明是截然不同的两种类型,即使它们的成员列表完全相同。因此,变量y,z的类型和x的类型不同。所以下面这条语句:

z = &x;
是非法的。

但是,标签可以解决这个问题。

标签(tag)字段允许为成员列表提供一个名字,这样它就可以在后续的声明中使用。标签允许多个声明使用同一个成员列表,并且创建同一种类型的结构。例如:

struct SIMPLE {
	int	a;
	char	b;
	float	c;
};
我们可以声明相同类型的x,y,z了。
struct SIMPLE x;
struct SIMPLE y[20], *z;
备注:SIMPLE只是一个标签,所以声明前必须使用struct。

我们也可以通过typedef来声明一种新的类型:

typedef struct {
	int	a;
	char	b;
	float	c;
} Simple;
备注:Simple是新类型,所以声明时候不需要struct:
Simple x;
Simple y[20], *z;
备注:如果你想在多个源文件中使用同一种类型的结构,你应该把标签声明或typedef形式的声明放在一个头文件中。当源文件需要这个声明时可以使用#include指令把那个头文件包含进去。

10.1.2 结构成员

备注:结构只是一种类型!

struct COMPLEX{
	float	f;
	int	a[20];
	long	*lp;
	struct SIMPLE	a;
	struct SIMPLE	sa[10];
	struct SIMPLE	*sp;
};
10.1.3 结构成员的直接访问

结构变量的成员通过点操作符(.)访问的。

10.1.4 结构成员的间接访问

通过箭头操作数(->)来访问。

10.1.5 结构的自引用

在一个结构内部包含一个类型为该结构本身的成员是否合法呢?例如:

struct SELF_REF1{
	int		a;
	struct	SELF_REF1 b;
	int		c;
};
这种类型的自引用是非法的。因为b是一个完整的结构,会不断的自身引用,导致无穷的递归。

但是下面的声明确实合法的:

struct SELF_REF1{
	int		a;
	struct	SELF_REF2 *b;
	int		c;
};
因为b是一个指针。编译器在结构的长度确定之前就已经知道指针的长度了,所以这种类型的自引用是合法的。指针实际上指向的是同一种类型的不同结构!!!注意是不同的结构。

警告:

警惕下面的陷阱:

typedef struct {
	int		a;
	struct	SELF_REF3 *b;
	int		c;
} SELF_REF3;
因为类型名直到声明的末尾才定义,所以在结构声明中的内部它尚未定义。

解决方案是定义一个结构标签来声明b:

typedef struct SELF_REF3_TAG {
	int		a;
	struct	SELF_REF3_TAG *b;
	int		c;
} SELF_REF3;
10.1.6 不完整的声明

如果两个结构体相互引用,那么哪个先声明?

答案:通过不完整声明来解决:

struct B;

struct A {
	struct B	*partner;
};

struct B {
	struct A	*partner;
};
10.1.7 结构的初始化

和数组很相像。一个位于一对花括号内部,由逗号分隔的初始值列表可用于结构各个成员的初始化。这些值根据结构成员列表的顺序写出。如果初始列表的值不够,剩余的结构成员将使用缺省值进行初始化。

struct INTF_EX {
	int		a;
	short	b[10];
	Simple  c;
}x = {
	10,
	{
		1,2,3,4,5
	},
	{
		25, 'x', 1.9
	}
};
10.2 结构,指针和成员
typedef struct {
	int	a;
	short	b[2];
} Ex2;
typedef struct Ex{
	int	a;
	char	b[3];
	Ex2	c;
	struct	Ex	*d;
} Ex;

Ex x = {10, "Hi", { 5, { -1, 25 }, }, 0 };
Ex *px = &x;

10.2.2 访问结构

表达式*px + 1是非法的,因为*px是一个结构,而C语言并未定义结构和整型值的加法。

而表达式*( px + 1 )中,1实际上代表的是一个结构体的移位,但是我们并为定义下一个结构体,所以这个表达式也是非法的。

10.2.3 访问结构成员

比较下面两个表达式:

*px和px->a。它们有什么区别?

1. 它们具有相同的地址,所以*px == px->a。

2. 但是,它们类型是不同的!!!变量px被声明为一个指向结构的指针,所以表达式*px的结果是整个结构,而不是它的第一个成员。

我们创建一个指向整型的指针:

int *pi;
表达式pi = px;是非法的,因为pi和px的类型不同,即使进行了强制转换也是非法的:pi = (int *)px;

正确的写法应该如下:

pi = &px->a;
10.2.4 访问嵌套的结构

我们来分析一个表达式:

*px->c.b;
比较好理解。唯一要正确认识到的是:px->c.b是一个数组名,而数组名是指针常量,所以可以直接用*来读取。这里的值是数组b的第一个元素。

10.2.5 访问指针成员

px->d;
其结果是0。但是
*px->d;
则是一个指向结构的指针。
Ex    y;
x.d = &y;
px->d->c.b[1];
这里要注意一点是:px->d指向是的一个Ex的指针。

10.4 作为函数参数的结构

假设有一个结构体:

typedef struct {
	char	product[PRODUCT_SIZE];
	int	quantity;
	float	unit_price;
	float	total_amount;
} Transaction;
我们在一个函数中调用了这个结构体;
void print_receipt( Transcation trans ){
	printf("%s\n", trans.product);
	printf("%d @ %.2f total %.2f\n", trans.quantity, trans.unit_price, trans.total_amount );
}
这里存在极度的资源浪费,我们需要拷贝整个Transcation进去。

实际上可以用指针的:

void print_receipt( Transcation *trans ){
	printf("%s\n", trans->product);
	printf("%d @ %.2f total %.2f\n", trans->quantity, trans->unit_price, trans->total_amount );
}
用指针有个显而易见的好处是:资源并不存在浪费的现象。

其次我们可以直接对原数组进行修改。

10.6 联合

联合的所有成员引用的是内存中的相同位置。当你想在不同的时刻把不同的东西存储于同一个位置时,就可以使用联合。

#include <stdio.h>

int main(void)
{
	union {
		float	f;
		int	i;
	} fi;

	fi.f = 3.14159;
	printf("%d\n", fi.i );
	printf("%d\n", (int)fi.f );

	return 0;
}
程序输出:

第二个输出为3倒没什么疑问,第一个为什么不也是3呢?

因为float类型在内存中的存储方式决定的,即3.14159的二进制码转成十进制后就是1078530000.

10.6.2 联合的初始化

联合变量可以被初始化,但是这个初始值必须是联合第一个成员的类型,而且它必须位于一对花括号里面。

union {
	int	a;
	float	b;
	char	c[4];
} x = { 5 };
把x.a初始化为5.

我们不能把这个类初始化为一个浮点值或者字符值。如果给出的是任何其他的类型,它将自动转换为一个整数并赋值给x.a。

习题:

1.

#define MAX_SIZE 128
typedef struct {
	long	num1;
	long	num2;
	long	num3;
}Num;
struct inf{
	char	date[MAX_SIZE];
	char	time[MAX_SIZE];
	struct	telephoneNumber 
	{
		Num		yourNum;
		Num		otherNum;
		Num		payNum;
	};
};

2.

typedef struct Money{
	float	msrp;
	float	asp;
	float	st;
	float	lf;
}Money;

typedef struct Sales{
	float	msrp;
	float	asp;
	float	dp;
	float	sd;
	float	mp;
	int	lt;
}Sales;

typedef struct RunSales{
	float	msrp;
	float	asp;
	float	st;
	float	lf;
	float	dp;
	int	ld;
	float	ir;
	float	mp;
	char	nob[20];
};

struct inf{
	char	name[20];
	char	addr[20];
	union model{
		Money		moneyModel;
		Sales		salesModel;
		RunSales	runsalesModel;
	};
};


© 著作权归作者所有

共有 人打赏支持
fzyz_sb
粉丝 408
博文 209
码字总数 447144
作品 0
武汉
程序员
golang 内存分析之字节对齐规则

c 的字节对齐很重要,因为c 支持指针运算。在golang 里面一般是慎用指针运算的,所以,这部分很少用,但是有些场景为了性能不得不用到指针运算,这个时候,知道golang 的内存分配就很重要了。...

鼎铭
06/12
0
0
Head First C 学习日志 第十章 进程间通信 捕捉信号

Head First C 第十章 进程间通信 捕捉信号 关于信号 信号是操作系统控制程序的方式,举个栗子,操作系统在看到用户输入了Ctrl+C时,就会向程序发送中断信号。信号映射表 | 信号 | 处理函数 ...

AlexTuan
2016/04/17
32
0
《程序员面试宝典》精华 编程语言部分

《程序员面试宝典》精华 编程语言部分 正所谓取其精华,去其糟粕。本文谨记录下《程序员面试宝典》一些关键的知识点、易错点,对于一些虽然重要但书中没有解释清楚的地方不做记录。当然这里的...

modernizr
2014/08/11
410
2
JavaScript 入门教程--WisdomPlanet-Javascript-Primer

WisdomPlanet-Javascript-Primer 是智慧星球 Javascript 入门教程。 本教程写给:正准备踏入编程之路或对 Javascript 感兴趣的同学 作者 念念之间 版权声明 本文允许您用于非商业用途,若有商...

念念之间
2015/04/01
1K
0
《HTML+CSS3权威指南》笔记摘要 - 目录

主要是想借助这个平台让大家给我学习途中的错误和不好的地方给与纠正。 我会努力最短时间内完成更新,如果发现有错别字或者Code错误,请指出。 信息:建议使用Opera10以上或者Google浏览器测...

Contac
2011/12/02
0
1

没有更多内容

加载失败,请刷新页面

加载更多

[Hive]JsonSerde使用指南

注意: 重要的是每行必须是一个完整的JSON,一个JSON不能跨越多行,也就是说,serde不会对多行的Json有效。 因为这是由Hadoop处理文件的工作方式决定,文件必须是可拆分的,例如,Hadoop将在...

Mr_yul
15分钟前
0
0
54:mysql修改密码|连接mysql|mysql常用命令

1、mysql修改密码: root用户时mysql的超级管理员,默认mysql的密码是空的,直接可以连接上去的,不过这样不安全; 注释:为了方便的使用mysql,需要把mysql加入到环境变量里; #后续自己输入mys...

芬野de博客
22分钟前
0
0
鼠标单击复制粘贴标签中的内容

<span ref="spanContentOne" id="spanContentOne" style="font-size: 14px;">或许不是最亮眼,总比瞎买强一点</span><!--<input type="button" @click="copyClick('1')" value="复制" />-......

帝子兮
26分钟前
0
0
使用axel多线程疯狂下载

在Linux中比较常见见的下载工具是curl和wget,但是下载比较大的文件两者都不支持多线程, 断点续传的作用不见得能发挥到最大。今天介绍一个axel工具,开启多线程疯狂下载。 安装 Fedora/Cen...

linuxprobe16
28分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部