4.4 使用数组的举例
图 4.3 的程序用 for 重复结构将10个元家的整型数组n的元素初始化为0,并用表格形式打印数组。第一个输出语句显示for结构中所打印列的列标题。记住,setw 指定下一个值的输出域宽。
可以在数组声明中用等号和逗号分隔的列表(放在花括号中)将数组中的元素初始化。程序4.4将七个元素的整型数组初始化并用表格形式打印数组。
// Fig. 4.3: fig04_03.cpp // initializing an array #include <iostream.h> #include <iomanip.h> int main(){ int i, n[10]; for ( i = 0; i < 10; i++ ) // initialize array n[ i ] = 0; cout << "Element" << setw( 13 ) << "Value" << endl; for(i=0;i<10;i++) // print array cout << setw( 7 ) << i << setw( 13 ) << n[ i ] << endl; return O; }
输出结果:
Element value 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0
图 4.3 将 10 个元素的整型数组 n 的元素初始化为 0
// Fig. 4.4:fig04 04.cpp // Initializing an array with a declaration #include <iostream.h> #include <iomanip.h> int main(){ int n[ 10 ] = { 32, 27, 64, 18, 95, 14, 90, 70, 60, 37 }; cout << "Element" << setw( 13 ) << "Value" << endl; for ( int i = 0; i < 10; i++ ) cout << setw( 7 ) << i << setw( 13 ) << n[ i ] << endl; return 0; }
输出结果:
Element Value 0 32 1 27 2 64 3 18 4 95 5 14 6 9O 7 7O 8 6O 9 37
图 4.4 用声明将数组中的元素初始化
如果初始化的元素比数组中的元素少,则其余元素自动初始化为0。例如,可以用下列声明将图4.3中数组n的元素初始化为0:
int n[1O] = {O};
其显式地将第一个元素初始化为0,隐式地将其余元素自动初始化为0,因为初始化值比数组中的元素少。程序员至少要显式地将第一个元素初始化为0,才能将其余元素自动初始化为0。图4.3的方法可以在程序执行时重复进行。
常见编程错误4.2
需要初始化数组元素而没有初始化数组元素是个逻辑错误。
下列数组声明是个逻辑错误:
int n[5] = {32, 27, 64, 18, 95, 14};
因为有6个初始化值,而数组只有5个元素。
常见编程错误4.3
初始化值超过数组元素个数是个逻辑错误。
如果带初始化值列表的声明中省略数组长度,则数组中的元素个数就是初始化值列表中的元素个数。例如:
int n[] = {1, 2, 3, 4, 5};
生成五个元素的数组。
性能提示4.1
如果不用执行时的赋值语句初始化数组而用数组初始化值列表在编译时初始化数组,则程序执行速度更快。
图 4.5 的程序将 10 个元素的数组s初始化为整数 2、4、6、…20,并以表格形式打印数组。这些数值是将循环计数器的值乘以2再加上2产生的。
// Fig. 4.5: figO405.cpp // Initialize array s to the even integers from 2 to 20. #include <iostream.h> #include <iomanip.h> int main(){ const int arraySize = 10; int j, s[arraySize]; for ( j = 0; j < arraySize; j++ ) // set the values si [j] = 2 + 2* j; cout << "Element" << setw( 13 ) << "Value" << endl; for ( j - 0; j < arraysize; j++ ) // print the values cout << setw( 7 ) << j << setw( 13 ) << s[ j ] << endl; return 0; }
输出结果:
Element Value 0 2 1 4 2 6 3 8 4 10 5 12 6 14 7 16 8 18 9 20
图4.5 将产生的值赋给数组元素
下列语句:
const int arraySlze:10
用 const 限定符声明常量变量 arrayySize 的值为 10。常量变量应在声明时初始化为常量表达式,此后不能改变(图 4.6 和图 4.7)。常量变量也称为命名常量(named constant)或只读变量(read-only variable)。
注意,常量变量一词是自相矛盾的,称为逆喻(像 龙虾 之类的名词)。
// Fig. 4.6: fig04_06.cpp // Using a properly initialized constant variable #include <iostream.h> int main(){ const int x - 7; // initialized constant variable cout << "The value of constant variable x is:" << x << endl; return 0; }
输出结果:
The value of constant variable x is: 7
图 4.6 正确地初始化和使用常量变量
// Fig. 4.7:fig04 07.cpp // A const object must be initialized int main(){ const int x; // Error: x must be initialized x = 7; // Error: cannot modify a const variable return 0; }
输出结果:
Compiling FIG04-7.CPP:
Error FIG04_7.CPP 6:Constant variable,x’must be initialized
Error FIG04_7.CPP 8:Cannot modify a const object
图 4.7 const 对象应初始化
常见编程错误 4.4
在执行语句中对常量变量赋值是个语法错误。
常量变量可以放在任何出现常量表达式的地方。图 4.5中,用常量变量 arraySize 指定数组 s 的长度:
int j,s[arraySize];
常见编程错误 4.5
只能用常量声明自动和静态数蛆,否则是个语法错误。
用常量变量声明数组长度使程序的伸缩性更强。图4.5中,要让第一个for循环填上1000个数组元素,只要将anaySize的值从10变为1000即可。如果不用常量变量arraySize,则要在程序中进行三处改变才能处理1000个数组元素。随着程序加大,这个方法在编写清晰的程序中越来越有用。
软件工程视点 4.1
将每个数组的长度定义为常量变量而不是常量,能使程序的伸缩性更强。
编程技巧 4.1
将每个数组的长度定义为常量变量而不是常量,能使程序更清晰。这个方法可以取消“魔数”,例如,在处理 10 元素数组的程序中重复出现长度10使数字10人为地变得重要,程序中存在的与数组长度无关的其他数字10时可能使读者搞乱。
图 4.8 中的程序求12个元素的整型数组a中的元素和,for 循环体中的语句进行求和。请注意,数组a的初始化值通常是用户从键盘输入的。例如,下列 for 结构:
for ( int j=0; j < arraySize;j++ ) cin>>a[j];
一次一个地从键盘读取数值,并将数值存放在元素 a[j] 中。
下一个例子用数组汇总调查中收集的数据。考虑下列问题:
40个学生用1到10的分数评价学生咖啡屋中的食品质量(1表示很差,10表示很好)。将40个值放在整型数组中,并汇总调查结果。
这是典型的数组应用(如图 4.9)。我们要汇总每种回答(1 到 10)的个数。数组 responses 是 40 个元素的评分数组。我们用11个元素的数组 frequency 计算每个答案的个数,忽略第一个元素frequeney[0],因为用 1 分对应 frequency[1] 而不是对应 frequency[0] 更好理解。这样可以直接用回答的分数作为 frequency 数组的下标。
// Fig. 4.8:fig04 OS.cppf // Compute the sum of the elements of the array #include <iostream.h> int main(){ const iht arraySize = 12; int a[ arraySize ] = { 1, 3, 5, 4, 7, 2, 99, 16, 45, 67, 89, 45 }; int total = 0; for (int i = 0; i < arraySize ; i++ ) total += a[ i ]; cout << "Total of array element values is "<< total << endl; return 0; }
输出结果:
Total of array element values is 383
图 4.8 计算数组元素和
// Fig. 4.9: fig04_09.cpp // Student poll program #include <iostream.h> #include <iomanip.h> int main(){ const int responseSize = 40, frequencySize = 11; int responses[ responseSize I = { 1, 2, 6, 4, 8, 5, 9, 7, 8, 10, 1, 6, 3, 8, 6, 10, 3, 8, 2, 7, 6, 5, 7, 6, 8, 6, 7, 5, 6, 6, 5, 6, 7, 5, 6, 4, 8, 6, 8, 10 ]; int frequency [ frequencySize ] = { 0 }; for (int answer = 0; answer < responseSize; answer++ ) ++frequency [ responses[ answer ] ]; cout << "Rating" << setw( 17 ) << "Frequency" << endl; for ( int rating = 1; rating < frequencySize; rating++ ) cout << setw( 6 ) << rating << setw( 17 ) << frequency [ rating ] << endl; return 0; }
输出结果:
Rating Frequency 1 2 2 2 3 2 4 2 5 5 6 11 7 5 8 7 9 l 10 3
图 4.9 学生调查分析程序
编程技巧 4.2
努力保证程序的清晰性,有时甚至可以为保证程序清晰而牺牲内存与处理器时间的使用效率。
性能堤示 4.2
有时性能考虑比保证程序的清晰性更重要。
第一个 for 循环一次一个地从 responses 数组取得回答,并将 frequeney 数组中的 10 个计数器(frequency[l] 到 frequency[10])之一加 1。这个循环中的关键语句如下:
++frequency[ responses[answer>
这个语句根据 responses[answer] 的值相应递增 frequency 计数器。例如,计数器 answer 为 0 时,responses[answer] 为 1,因此 ++frequency[responses[answer> 实际解释如下:
++frequency[1];
将数组下标为1的元素加1。计数器 answer 为 1 时,responses[answer] 为 2,因此 ++frqueney[responses[answer> 实际解释如下:
++frequency[2];
将数组下标为2的元素加1。计数器 answer 为 2 时,response[answer] 为 6,因此 ++frequency[responses[answer> 实际解释如下:
++frequency[6];
将数组下标为6的元素加1等等。注意,不管调查中处理多少个回答,都只需要11个元素的数组(忽略元素 0)即可汇总结果。如果数据中包含13之类的无效值,则程序对 frequency[13] 加 1,在数组边界之外。C++ 没有数组边界检查,无法阻止计算机到引用不存在的元素。
这样执行程序可能超出数组边界,而不产生任伺警告。程序员应保证所有数组引用都在数组边界之内。C++ 是个可扩展语言,第8章介绍扩展 C++,用类将数组实现为用户自定义类型。新的数组定义能进行许多 C++ 内建数组中没有的操作,例如,可以直接比较数组,将一个数组赋给另一数组,用 cin 和 cout 输入和输出整个数组,自动初始化数组,防止访问超界数组元素和改变下标范围(甚至改变下标类型),使数组第一个元素下标不一定为 0。
常见编程错误 4.6
引用超出数组边界的元素是个执行时的逻辑错误,而不是语法错误。
测试与调试提示 4.1
对数组进行循环操作时,数组下标不能低于0,不能高于数组中的元素总数(应比数组中的元素总数少1)。保证避免循环终止条件访问超界数组元素。
测试与调试提示 4.2
程序应验证所有输入值的正确性,防止错误信息影响程序计算。
可移植性提示 4.1
访问超界数组元素所带来的影响(通常很严重)是与系统有关。
测试与调试提示 4.3
第6章开始介绍类时,将介绍如何开发“智能”数组,可以在运行时自动检查所有下标引用在边界之内。使用这种智能数组能消除一定的缺陷。
下一个例子(图 4.10)从数组读取数值,并用条形图或直方图进行描述。打印每个数值,然后在数字旁边打印该数字所指定星号个数的条形图。嵌套for循环实现绘制条形图。注意用endl结束直方图。
常见编程错误 4.7
尽管一个 for 循环和另一个嵌套 for 循环中可以用相同计数器变量,但这通常是个逻辑错误。
// Fig. 4.10: fig04_10.cpp // Histogram printing program #include <iostream.h> #include <iomanip.h> int main(){ const int arraySize = 10; int n[ arraySize ] = { 19, 3, 15, 7, 11, 9, 13, 5, 17, 1 }; cout << "Element" << setw( 13 ) << "Value" << setw( 17 ) << "Histogram" << endl; for (int i = 0; i < arraySize ; i++ ) { cout << setw( 7 ) << i << setw( 13 ) << n[ i ] << setw( 9 ); for ( int j = 0; j < n[ i ] ; j++ ) // print one bar cout << '*'; cout << endl; } return 0; }
输出结果:
Element Value Histogram 0 19 ******************* 1 3 *** 2 15 *************** 3 7 ******* 4 11 ************ $ 9 ********* 6 13 ************* 7 5 ****** 8 17 ***************** 9 1
测试与调试提示 4.4
尽管 for 循环中的计数器变量可以修改,但这样容易造成一些缺陷,应当避免。
第3章介绍了投骰子程序(参见图 3.8),就是将六面体骰子投 6000 次,测试随机数产生器是否真的产生随机数。图 4.11 显示了这个程序的数组版本。
// Fig. 4.11: figo411.cpp // Roll a six-sided die 6000 times #include <iostream,h> #include <iomanip.h> #include <stdlib.h> #include <time.h> int main(){ const iht arraySize = 7; int face, frequency[ arraySize ] = { 0 }; stand( time( 0 ) ); for ( int roll =1; roll <= 6000; roll++ ) ++frequency[ 1 + rand() % 6 ]; // replaces 20-line switch // of Fig. 3.8 cout << "Face" << setw( 13 ) << "Frequency" << endl; // ignore element 0 in the frequency array for ( face = 1; face < arraySize ; face++ ) cout << setw{ 4 ) << face << setw( 13 ) << frequency[ face ] << endl; return 0; }
输出结果:
Face Frequency 1 1037 2 987 3 1013 4 1028 5 952 6 983
图 4.11 用数组而不用 switch 结构设计投骰子程序
前面只介绍了整型数组,但数组可以是任何类型,下面要介绍如何用字符数组存放字符串。前面介绍的字符串处理功能只有用cout和<<输入字符串,“hello'’之类的字符串其实就是一个字符数组。字符数组有几个特性。
字符数组可以用字符串直接量初始化。例如,下列声明:
char string1[] = "first";
将数组 string1 的元素初始化为字符串 first 中的各个元索。上述声明中string1的长度是编译器根据字符串长度确定的。注意,字符串“first”包括五个字符加一个特殊字符串终止符,称为空字符(null character),这样,字符串string1实际上包含6个元素。空字符对应的字符常量为 \0(反斜杠加 0),所有字符串均用这个空字符结尾。表示字符串的字符数组应该足以放置字符中中的所有字符和空字符。
字符数组还可以用初始化值列表中的各个字符常量初始化。上述语句也可以写成:
char string1[] ={ 'f', 'i', 'r', 's', 't', '\0' }
由于字符串其实是字符数组,因此可以用数组下标符号直接访问字符串中的各个字符。例如,string1[O]是字符'f',string1[3]是字符's'。
我们还可以用cin和>>直接从键盘输入字符数组。例如,下列声明:
char string2[20];
生成的字符数组能存放19个字符和一个null终止符的字符串。
下列语句:
cin>>string2;
从键盘中将字符串读取到string2中。注意,上述语句中只提供了数组名,没有提供数组长度的信息。程序员要负责保证接收字符串的数组能够放置用户从键盘输入的任何字符串。cin从键盘读取字符,直到遇到第一个空白字符,它不管数组长度如何。这样,用cin和>>输人数据可能插入到数组边界之外(5.12节介绍如何防止插入到数组边界之外)。
常见编程错误 4.8
如果cin>>提供足够大的数组,则键盘输入时可能造成数据丢失和其他严重的运行时错误。
可以用 cout 和 << 输出表示空字符终止字符串的字符数组。下列语句打印数组 string2:
cout<<string2<<endl;
注意 cout<< 和 cin>> 一样不在乎字符数组的长度。一直打印字符串的字符,直到遇到 null 终止符为止。
图 4.12 演示直接用字符串初始化字符数组、将字符串读取到字符数组中、将字符数组作为字符串打印以及访问字符串的各个字符。
// Fig. 4_12: fig04_12.cpp // Treating character arrays as strings #include <iostream.h> int main(){ char string1[ 20 ], string2[] = "string literal"; cout << "Enter a string: "; cin >> string1; cout << "string1 is: "<< string1 << "\nstring2 is: " << string2 << "stringl with spaces between characters is:\n"; for ( int i = 0; string1[ i ] != '\0'; i++ ) cout << string1[ i ] << ' '; cin >> stringl; // reads "there" cout << "\nstring1 is: "<< string1 << endl; cout << endl; return 0; }
输出结果:
Enter a string: Hello there string1 is: Hello string2 is: string literal string1 with spaces between characters is: Hello string1 is: there
图 4.12 将字符数组当作字符串
图 4.12 用 for 结构在 string1 数组中循环,并打印各个字符,用空格分开。for结构的条件string1[i]!='\0'在遇到字符串的null终止符之前一直为真。
第3章介绍了存储类说明符 static。函数体中的 static 局部变量在程序执行期间存在,但只能在函数体中访问该变量。
性能提示 4.3
可以用 static 对局部数组声明,使该数组不必在每次调用函数时生成和初始化,该数组在程序中每次退出函数时不会删除,这样可以提高性能。
声明为static的数组在程序装入时初始化。如果程序员不把static数组显式初始化,则编译器将这个数组初始化为0。
图 4.13 显示了带有声明为 static 的局部效组的 staticArrayInit 函数和带自动局部数组的 automaticArrayInit 函数。staticArrayInit 调用两次,编译器将 static 局部数组初始化为 0。该函数打印数组,将每个元素加5,然后再次打印该数组;函数第二次调用时,static 数组包含第一次调用时所存放的值。函数 automaticArrayInit 也调用两次,但自动局部数组的元素用数值 1、2、3 初始化。该函数打印数组,将每个元素加 5,然后再次打印该数组;函数第二次调用时,数组重新初始化为 1、2、3,因为这个数组是自动存储类。
// Fig. 4.13: fig0413.cpp // Static arrays are initialized to zero #include <iostream.h> void staticArrayInit( void ); void automaticArrayInit( void ); int main(){ cout << "First call to each function:\n"; staticArrayInit(); automaticArrayInit(); cout << "\n\nSecond call to each function:\n"; staticArrayInit(); automaticArrayInit(); cout << endl; return 0; // function to demonstrate a static local array void staticArrayInit( void ){ static iht arrayl[ 3 ] ; int i; cout << "\nValues on entering staticArrayInit:\n"; for ( i = 0; i < 3; i++ ) cout << "array1[ " << i << " ] =" << array1[ i ] << " "; cout << "\nValues on exiting staticArrayInit:\n"; for ( i = 0; i < 3; i++ ) cout << "array1[ " << i << " ] = " << ( arrayl[ i ] += 5 ) <<" "; } // function to demonstrate an automatic local array void automaticArrayInit( void ){ int i, array2[ 3 ] = { 1, 2, 3 }; cout << "\n\nValues on entering automaticArrayInit:\n"; for ( i = 0; i < 3; i++ ) cout << "array2[ "<< i << "] = "<< array2[ i ] << " " ; cout << "\nValues on exiting automaticArrayInit:\n"; for ( i = 0; i < 3; i++ ) cout << "array2[ "<< i << "] =" << ( array2[ i ] += 5 ) << " " ; } }
输出结果:
First call to each function: Values on entering staticArrayInit: arrayl[ 0 ] = 0 array1[ 1 I = 0 array1[ 2 ] = 0 Values on exiting staticArrayInit: arrayl[ 0 ] = 5 array1[ 1 ] = 5 array1[ 2 ] = S Values on entering automaticArrayInit: array2[ 0 ] = 1 array2[ 1 ] = 2 array2[ 2 ] = 3 Values on exitimg automaticArraylmit: arrayl[ 0 ] = 6 array2[ 1 ] = 7 array2[ 2 ] = 8 second call to each function: Values on entering staticArrayImit: arrayl[ 0 ] = 5 arrayl[ 1 ] = 5 arrayl[ 2 ] = 5 Values on exiting staticArrayInit: arrayl[ 0 ] = 10 arrayl[ 1 ] = 10 arrayl[ 2 ] = 10 Values on entering automaticArrayInit: array2[ 0 ] = 1 array[ 1 ] = 2 array2[ 2 ] = 3 Values on exiting automaticArrayInit: array2[ 0 ] = 6 array2[ 1 ] = 7 array2[ 2 ] = 8
图 4.13 比较 static 数组初始化和自动数组初始化
常见编程错误 4.9
假设每次调用函数时函数局部 static 数组的元素初始化为 0 将导致程序中的逻辑错误。