-
Notifications
You must be signed in to change notification settings - Fork 25
/
ch08s01.html
25 lines (23 loc) · 11 KB
/
ch08s01.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
<?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="ch08.html" title="第 8 章 数组" /><link rel="prev" href="ch08.html" title="第 8 章 数组" /><link rel="next" href="ch08s02.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="ch08.html">上一页</a> </td><th width="60%" align="center">第 8 章 数组</th><td width="20%" align="right"> <a accesskey="n" href="ch08s02.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="id2733213"></a>1. 数组的基本概念</h2></div></div></div><p>数组(Array)<a id="id2732586" class="indexterm"></a>也是一种复合数据类型,它由一系列相同类型的元素(Element)<a id="id2732594" class="indexterm"></a>组成。例如定义一个由4个<code class="literal">int</code>型元素组成的数组count:</p><pre class="programlisting">int count[4];</pre><p>和结构体成员类似,数组<code class="literal">count</code>的4个元素的存储空间也是相邻的。结构体成员可以是基本数据类型,也可以是复合数据类型,数组中的元素也是如此。根据组合规则,我们可以定义一个由4个结构体元素组成的数组:</p><pre class="programlisting">struct complex_struct {
double x, y;
} a[4];</pre><p>也可以定义一个包含数组成员的结构体:</p><pre class="programlisting">struct {
double x, y;
int count[4];
} s;</pre><p>数组类型的长度应该用一个整数常量表达式来指定<sup>[<a id="id2733250" href="#ftn.id2733250" class="footnote">16</a>]</sup>。数组中的元素通过下标(或者叫索引,Index)<a id="id2733270" class="indexterm"></a>来访问。例如前面定义的由4个<code class="literal">int</code>型元素组成的数组<code class="literal">count</code>图示如下:</p><div class="figure"><a id="id2733290"></a><p class="title"><b>图 8.1. 数组count</b></p><div class="figure-contents"><div><img src="images/array.count.png" alt="数组count" /></div></div></div><br class="figure-break" /><p>整个数组占了4个<code class="literal">int</code>型的存储单元,存储单元用小方框表示,里面的数字是存储在这个单元中的数据(假设都是0),而框外面的数字是下标,这四个单元分别用<code class="literal">count[0]</code>、<code class="literal">count[1]</code>、<code class="literal">count[2]</code>、<code class="literal">count[3]</code>来访问。注意,在定义数组<code class="literal">int count[4];</code>时,方括号(Bracket)<a id="id2733345" class="indexterm"></a>中的数字4表示数组的长度,而在访问数组时,方括号中的数字表示访问数组的第几个元素。和我们平常数数不同,数组元素是从“<span class="quote">第0个</span>”开始数的,大多数编程语言都是这么规定的,所以计算机术语中有Zeroth<a id="id2733360" class="indexterm"></a>这个词。这样规定使得访问数组元素非常方便,比如<code class="literal">count</code>数组中的每个元素占4个字节,则<code class="literal">count[i]</code>表示从数组开头跳过<code class="literal">4*i</code>个字节之后的那个存储单元。这种数组下标的表达式不仅可以表示存储单元中的值,也可以表示存储单元本身,也就是说可以做左值,因此以下语句都是正确的:</p><pre class="programlisting">count[0] = 7;
count[1] = count[0] * 2;
++count[2];</pre><p>到目前为止我们学习了五种后缀运算符:后缀++、后缀--、结构体取成员.、数组取下标[]、函数调用()。还学习了五种单目运算符(或者叫前缀运算符):前缀++、前缀--、正号+、负号-、逻辑非!。在C语言中后缀运算符的优先级最高,单目运算符的优先级仅次于后缀运算符,比其它运算符的优先级都高,所以上面举例的<code class="literal">++count[2]</code>应该看作对<code class="literal">count[2]</code>做前缀++运算。</p><p>数组下标也可以是表达式,但表达式的值必须是整型的。例如:</p><pre class="programlisting">int i = 10;
count[i] = count[i+1];</pre><p>使用数组下标不能超出数组的长度范围,这一点在使用变量做数组下标时尤其要注意。C编译器并不检查<code class="literal">count[-1]</code>或是<code class="literal">count[100]</code>这样的访问越界错误,编译时能顺利通过,所以属于运行时错误<sup>[<a id="id2733456" href="#ftn.id2733456" class="footnote">17</a>]</sup>。但有时候这种错误很隐蔽,发生访问越界时程序可能并不会立即崩溃,而执行到后面某个正确的语句时却有可能突然崩溃(在<a class="xref" href="ch10s04.html#gdb.segfault">第 4 节 “段错误”</a>我们会看到这样的例子)。所以从一开始写代码时就要小心避免出问题,事后依靠调试来解决问题的成本是很高的。</p><p>数组也可以像结构体一样初始化,未赋初值的元素也是用0来初始化,例如:</p><pre class="programlisting">int count[4] = { 3, 2, };</pre><p>则<code class="literal">count[0]</code>等于3, <code class="literal">count[1]</code>等于2,后面两个元素等于0。如果定义数组的同时初始化它,也可以不指定数组的长度,例如:</p><pre class="programlisting">int count[] = { 3, 2, 1, };</pre><p>编译器会根据Initializer有三个元素确定数组的长度为3。利用C99的新特性也可以做Memberwise Initialization:</p><pre class="programlisting">int count[4] = { [2] = 3 };</pre><p>下面举一个完整的例子:</p><div class="example"><a id="id2733545"></a><p class="title"><b>例 8.1. 定义和访问数组</b></p><div class="example-contents"><pre class="programlisting">#include <stdio.h>
int main(void)
{
int count[4] = { 3, 2, }, i;
for (i = 0; i < 4; i++)
printf("count[%d]=%d\n", i, count[i]);
return 0;
}</pre></div></div><br class="example-break" /><p>这个例子通过循环把数组中的每个元素依次访问一遍,在计算机术语中称为遍历(Traversal)<a id="id2733562" class="indexterm"></a>。注意控制表达式<code class="literal">i < 4</code>,如果写成<code class="literal">i <= 4</code>就错了,因为<code class="literal">count[4]</code>是访问越界。</p><p>数组和结构体虽然有很多相似之处,但也有一个显著的不同:数组不能相互赋值或初始化。例如这样是错的:</p><pre class="programlisting">int a[5] = { 4, 3, 2, 1 };
int b[5] = a;</pre><p>相互赋值也是错的:</p><pre class="programlisting">a = b;</pre><p>既然不能相互赋值,也就<span class="emphasis"><em>不能用数组类型作为函数的参数或返回值</em></span>。如果写出这样的函数定义:</p><pre class="programlisting">void foo(int a[5])
{
...
}</pre><p>然后这样调用:</p><pre class="programlisting">int array[5] = {0};
foo(array);</pre><p>编译器也不会报错,但这样写并不是传一个数组类型参数的意思。对于数组类型有一条特殊规则:<span class="emphasis"><em>数组类型做右值使用时,自动转换成指向数组首元素的指针</em></span>。所以上面的函数调用其实是传一个指针类型的参数,而不是数组类型的参数。接下来的几章里有的函数需要访问数组,我们就把数组定义为全局变量给函数访问,等以后讲了指针再使用传参的办法。这也解释了为什么数组类型不能相互赋值或初始化,例如上面提到的<code class="literal">a = b</code>这个表达式,<code class="literal">a</code>和<code class="literal">b</code>都是数组类型的变量,但是<code class="literal">b</code>做右值使用,自动转换成指针类型,而左边仍然是数组类型,所以编译器报的错是<code class="literal">error: incompatible types in assignment</code>。</p><div class="simplesect" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2733680"></a>习题</h3></div></div></div><p>1、编写一个程序,定义两个类型和长度都相同的数组,将其中一个数组的所有元素拷贝给另一个。既然数组不能直接赋值,想想应该怎么实现。</p></div><div class="footnotes"><br /><hr width="100" align="left" /><div class="footnote"><p><sup>[<a id="ftn.id2733250" href="#id2733250" class="para">16</a>] </sup>C99的新特性允许在数组长度表达式中使用变量,称为变长数组(VLA,Variable Length Array)<a id="id2733257" class="indexterm"></a>,VLA只能定义为局部变量而不能是全局变量,与VLA有关的语法规则比较复杂,而且很多编译器不支持这种新特性,不建议使用。</p></div><div class="footnote"><p><sup>[<a id="ftn.id2733456" href="#id2733456" class="para">17</a>] </sup>你可能会想为什么编译器对这么明显的错误都视而不见?理由一,这种错误并不总是显而易见的,在<a class="xref" href="ch23s01.html#pointer.intro">第 1 节 “指针的基本概念”</a>会讲到通过指针而不是数组名来访问数组的情况,指针指向数组中的什么位置只有运行时才知道,编译时无法检查是否越界,而运行时每次访问数组元素都检查越界会严重影响性能,所以干脆不检查了;理由二,<a class="xref" href="bi01.html#bibli.rationale" title="Rationale for International Standard - Programming Languages - C">[<abbr class="abbrev">C99 Rationale</abbr>]</a>指出C语言的设计精神是:相信每个C程序员都是高手,不要阻止程序员去干他们需要干的事,高手们使用<code class="literal">count[-1]</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="ch08.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch08.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch08s02.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">第 8 章 数组 </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>