-
Notifications
You must be signed in to change notification settings - Fork 25
/
ch07s01.html
47 lines (44 loc) · 14.7 KB
/
ch07s01.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>1. 复合类型与结构体</title><link rel="stylesheet" href="styles.css" type="text/css" /><meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /><link rel="start" href="index.html" title="Linux C编程一站式学习" /><link rel="up" href="ch07.html" title="第 7 章 结构体" /><link rel="prev" href="ch07.html" title="第 7 章 结构体" /><link rel="next" href="ch07s02.html" title="2. 数据抽象" /></head><body><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">1. 复合类型与结构体</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch07.html">上一页</a> </td><th width="60%" align="center">第 7 章 结构体</th><td width="20%" align="right"> <a accesskey="n" href="ch07s02.html">下一页</a></td></tr></table><hr /></div><div class="sect1" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id2730146"></a>1. 复合类型与结构体</h2></div></div></div><p>在编程语言中,最基本的、不可再分的数据类型称为基本类型(Primitive Type)<a id="id2729520" class="indexterm"></a>,例如整型、浮点型;根据语法规则由基本类型组合而成的类型称为复合类型(Compound Type)<a id="id2729529" class="indexterm"></a>,例如字符串是由很多字符组成的。有些场合下要把复合类型当作一个整体来用,而另外一些场合下需要分解组成这个复合类型的各种基本类型,复合类型的这种两面性为数据抽象(Data Abstraction)<a id="id2729540" class="indexterm"></a>奠定了基础。<a class="xref" href="bi01.html#bibli.sicp" title="Structure and Interpretation of Computer Programs">[<abbr class="abbrev">SICP</abbr>]</a>指出,在学习一门编程语言时要特别注意以下三个方面:</p><div class="orderedlist"><ol type="1"><li><p>这门语言提供了哪些Primitive,比如基本类型,比如基本运算符、表达式和语句。</p></li><li><p>这门语言提供了哪些组合规则,比如基本类型如何组成复合类型,比如简单的表达式和语句如何组成复杂的表达式和语句。</p></li><li><p>这门语言提供了哪些抽象机制,包括数据抽象和过程抽象(Procedure Abstraction)<a id="id2730187" class="indexterm"></a>。</p></li></ol></div><p>本章以结构体为例讲解数据类型的组合和数据抽象。至于过程抽象,我们在<a class="xref" href="ch04s02.html#cond.ifelse">第 2 节 “if/else语句”</a>已经见过最简单的形式,就是把一组语句用一个函数名封装起来,当作一个整体使用,本章将介绍更复杂的过程抽象。</p><p>现在我们用C语言表示一个复数。从直角座标系来看,复数由实部和虚部组成,从极座标系来看,复数由模和辐角组成,两种座标系可以相互转换,如下图所示:</p><div class="figure"><a id="id2730217"></a><p class="title"><b>图 7.1. 复数</b></p><div class="figure-contents"><div><img src="images/struct.complex.png" alt="复数" /></div></div></div><br class="figure-break" /><p>如果用实部和虚部表示一个复数,我们可以写成由两个<code class="literal">double</code>型组成的结构体:</p><pre class="programlisting">struct complex_struct {
double x, y;
};</pre><p>这一句定义了标识符<code class="literal">complex_struct</code>(同样遵循标识符的命名规则),这种标识符在C语言中称为Tag<a id="id2730254" class="indexterm"></a>,<code class="literal">struct complex_struct { double x, y; }</code>整个可以看作一个类型名<sup>[<a id="id2730268" href="#ftn.id2730268" class="footnote">12</a>]</sup>,就像<code class="literal">int</code>或<code class="literal">double</code>一样,只不过它是一个复合类型,如果用这个类型名来定义变量,可以这样写:</p><pre class="programlisting">struct complex_struct {
double x, y;
} z1, z2;</pre><p>这样<code class="literal">z1</code>和<code class="literal">z2</code>就是两个变量名,变量定义后面带个;号是我们早就习惯的。但即使像先前的例子那样只定义了<code class="literal">complex_struct</code>这个Tag而不定义变量,}后面的;号也不能少。这点一定要注意,类型定义也是一种声明,声明都要以;号结尾,结构体类型定义的}后面少;号是初学者常犯的错误。不管是用上面两种形式的哪一种定义了<code class="literal">complex_struct</code>这个Tag,以后都可以直接用<code class="literal">struct complex_struct</code>来代替类型名了。例如可以这样定义另外两个复数变量:</p><pre class="programlisting">struct complex_struct z3, z4;</pre><p>如果在定义结构体类型的同时定义了变量,也可以不必写Tag,例如:</p><pre class="programlisting">struct {
double x, y;
} z1, z2;</pre><p>但这样就没办法再次引用这个结构体类型了,因为它没有名字。每个复数变量都有两个成员(Member)<a id="id2730396" class="indexterm"></a>x和y,可以用.运算符(.号,Period)<a id="id2730404" class="indexterm"></a>来访问,这两个成员的存储空间是相邻的<sup>[<a id="id2730413" href="#ftn.id2730413" class="footnote">13</a>]</sup>,合在一起组成复数变量的存储空间。看下面的例子:</p><div class="example"><a id="id2730428"></a><p class="title"><b>例 7.1. 定义和访问结构体</b></p><div class="example-contents"><pre class="programlisting">#include <stdio.h>
int main(void)
{
struct complex_struct { double x, y; } z;
double x = 3.0;
z.x = x;
z.y = 4.0;
if (z.y < 0)
printf("z=%f%fi\n", z.x, z.y);
else
printf("z=%f+%fi\n", z.x, z.y);
return 0;
}</pre></div></div><br class="example-break" /><p>注意上例中变量<code class="literal">x</code>和变量<code class="literal">z</code>的成员<code class="literal">x</code>的名字并不冲突,因为变量<code class="literal">z</code>的成员<code class="literal">x</code>只能通过表达式<code class="literal">z.x</code>来访问,编译器可以从语法上区分哪个<code class="literal">x</code>是变量<code class="literal">x</code>,哪个<code class="literal">x</code>是变量<code class="literal">z</code>的成员<code class="literal">x</code>,<a class="xref" href="ch19s03.html#asmc.layout">第 3 节 “变量的存储布局”</a>会讲到这两个标识符<code class="literal">x</code>属于不同的命名空间。结构体Tag也可以定义在全局作用域中,这样定义的Tag在其定义之后的各函数中都可以使用。例如:</p><pre class="programlisting">struct complex_struct { double x, y; };
int main(void)
{
struct complex_struct z;
...
}</pre><p>结构体变量也可以在定义时初始化,例如:</p><pre class="programlisting">struct complex_struct z = { 3.0, 4.0 };</pre><p>Initializer中的数据依次赋给结构体的各成员。如果Initializer中的数据比结构体的成员多,编译器会报错,但如果只是末尾多个逗号则不算错。如果Initializer中的数据比结构体的成员少,未指定的成员将用0来初始化,就像未初始化的全局变量一样。例如以下几种形式的初始化都是合法的:</p><pre class="programlisting">double x = 3.0;
struct complex_struct z1 = { x, 4.0, }; /* z1.x=3.0, z1.y=4.0 */
struct complex_struct z2 = { 3.0, }; /* z2.x=3.0, z2.y=0.0 */
struct complex_struct z3 = { 0 }; /* z3.x=0.0, z3.y=0.0 */</pre><p>注意,<code class="literal">z1</code>必须是局部变量才能用另一个变量<code class="literal">x</code>的值来初始化它的成员,如果是全局变量就只能用常量表达式来初始化。这也是C99的新特性,C89只允许在{}中使用常量表达式来初始化,无论是初始化全局变量还是局部变量。</p><p>{}这种语法不能用于结构体的赋值,例如这样是错误的:</p><pre class="programlisting">struct complex_struct z1;
z1 = { 3.0, 4.0 };</pre><p>以前我们初始化基本类型的变量所使用的Initializer都是表达式,表达式当然也可以用来赋值,但现在这种由{}括起来的Initializer并不是表达式,所以不能用来赋值<sup>[<a id="id2730593" href="#ftn.id2730593" class="footnote">14</a>]</sup>。Initializer的语法总结如下:</p><div class="literallayout"><p>Initializer → 表达式<br />
Initializer → { 初始化列表 } <br />
初始化列表 → Designated-Initializer, Designated-Initializer, ...<br />
(最后一个Designated-Initializer末尾可以有一个多余的,号)<br />
Designated-Initializer → Initializer<br />
Designated-Initializer → .标识符 = Initializer<br />
Designated-Initializer → [常量表达式] = Initializer</p></div><p>Designated Initializer<a id="id2730636" class="indexterm"></a>是C99引入的新特性,用于初始化稀疏(Sparse)<a id="id2730644" class="indexterm"></a>结构体和稀疏数组很方便。有些时候结构体或数组中只有某一个或某几个成员需要初始化,其它成员都用0初始化即可,用Designated Initializer语法可以针对每个成员做初始化(Memberwise Initialization)<a id="id2730654" class="indexterm"></a>,很方便。例如:</p><pre class="programlisting">struct complex_struct z1 = { .y = 4.0 }; /* z1.x=0.0, z1.y=4.0 */</pre><p>数组的Memberwise Initialization语法将在下一章介绍。</p><p>结构体类型用在表达式中有很多限制,不像基本类型那么自由,比如+ - * /等算术运算符和&& || !等逻辑运算符都不能作用于结构体类型,<code class="literal">if</code>语句、<code class="literal">while</code>语句中的控制表达式的值也不能是结构体类型。严格来说,可以做算术运算的类型称为算术类型(Arithmetic Type)<a id="id2730692" class="indexterm"></a>,算术类型包括整型和浮点型。可以表示零和非零,可以参与逻辑与、或、非运算或者做控制表达式的类型称为标量类型(Scalar Type)<a id="id2730702" class="indexterm"></a>,标量类型包括算术类型和以后要讲的指针类型,详见<a class="xref" href="ch23s09.html#pointer.typesummary">图 23.5 “C语言类型总结”</a>。</p><p>结构体变量之间使用赋值运算符是允许的,用一个结构体变量初始化另一个结构体变量也是允许的,例如:</p><pre class="programlisting">struct complex_struct z1 = { 3.0, 4.0 };
struct complex_struct z2 = z1;
z1 = z2;</pre><p>同样地,<code class="literal">z2</code>必须是局部变量才能用变量<code class="literal">z1</code>的值来初始化。既然结构体变量之间可以相互赋值和初始化,也就可以当作函数的参数和返回值来传递:</p><pre class="programlisting">struct complex_struct add_complex(struct complex_struct z1, struct complex_struct z2)
{
z1.x = z1.x + z2.x;
z1.y = z1.y + z2.y;
return z1;
}</pre><p>这个函数实现了两个复数相加,如果在<code class="literal">main</code>函数中这样调用:</p><pre class="programlisting">struct complex_struct z = { 3.0, 4.0 };
z = add_complex(z, z);</pre><p>那么调用传参的过程如下图所示:</p><div class="figure"><a id="id2730772"></a><p class="title"><b>图 7.2. 结构体传参</b></p><div class="figure-contents"><div><img src="images/struct.parameter.png" alt="结构体传参" /></div></div></div><br class="figure-break" /><p>变量<code class="literal">z</code>在<code class="literal">main</code>函数的栈帧上,参数<code class="literal">z1</code>和<code class="literal">z2</code>在<code class="literal">add_complex</code>函数的栈帧上,<code class="literal">z</code>的值分别赋给<code class="literal">z1</code>和<code class="literal">z2</code>。在这个函数里,<code class="literal">z2</code>的实部和虚部被累加到<code class="literal">z1</code>中,然后<code class="literal">return z1;</code>可以看成是:</p><div class="orderedlist"><ol type="1"><li><p>用<code class="literal">z1</code>初始化一个临时变量。</p></li><li><p>函数返回并释放栈帧。</p></li><li><p>把临时变量的值赋给变量<code class="literal">z</code>,释放临时变量。</p></li></ol></div><p>由.运算符组成的表达式能不能做左值取决于.运算符左边的表达式能不能做左值。在上面的例子中,<code class="literal">z</code>是一个变量,可以做左值,因此表达式<code class="literal">z.x</code>也可以做左值,但表达式<code class="literal">add_complex(z, z).x</code>只能做右值而不能做左值,因为表达式<code class="literal">add_complex(z, z)</code>不能做左值。</p><div class="footnotes"><br /><hr width="100" align="left" /><div class="footnote"><p><sup>[<a id="ftn.id2730268" href="#id2730268" class="para">12</a>] </sup>其实C99已经定义了复数类型<code class="literal">_Complex</code>。如果包含C标准库的头文件<code class="literal">complex.h</code>,也可以用<code class="literal">complex</code>做类型名。当然,只要不包含头文件<code class="literal">complex.h</code>就可以自己定义标识符<code class="literal">complex</code>,但为了尽量减少混淆,本章的示例代码都用<code class="literal">complex_struct</code>做标识符而不用<code class="literal">complex</code>。</p></div><div class="footnote"><p><sup>[<a id="ftn.id2730413" href="#id2730413" class="para">13</a>] </sup>我们在<a class="xref" href="ch19s04.html#asmc.structunion">第 4 节 “结构体和联合体”</a>会看到,结构体成员之间也可能有若干个填充字节。</p></div><div class="footnote"><p><sup>[<a id="ftn.id2730593" href="#id2730593" class="para">14</a>] </sup>C99引入一种新的表达式语法Compound Literal<a id="id2730598" class="indexterm"></a>可以用来赋值,例如<code class="literal">z1 = (struct complex_struct){ 3.0, 4.0 };</code>,本书不使用这种新语法。</p></div></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch07.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch07.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch07s02.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">第 7 章 结构体 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 2. 数据抽象</td></tr></table></div></body></html>