`
txf2004
  • 浏览: 6866668 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

《Linux C一站式编程》第八章 数组

 
阅读更多

1. 数组的基本概念
数组(Array)也是一种复合数据类型,它由一系列相同类型的元素(Element)组成。
int count[4];
和结构体成员类似,数组count的4个元素的存储空间也是相邻的。结构体成员可以是基本数据类型,也可以是复合数据类型,数组中的元素也是如此。根据组合规则,我们可以定义一个由4个结构体元素组成的数组:
struct complex_struct {
double x, y;
} a[4];
struct {
double x, y;
int count[4];
} s;
使用数组下标不能超出数组的长度范围,这一点在使用变量做数组下标时尤其要注意。C编译器并不检查count[-1]或是count[100]这样的访问越界错误,编译时能顺利通过,所以属于运行时错误。但有时候这种错误很隐蔽,发生访问越界时程序可能并不会立即崩溃,而执行到后面某个正确的语句时却有可能突然崩溃(在第4节 “段错误”我们会看到这样的例子)。
C语言的设计精神是:相信每个C程序员都是高手,不要阻止程序员去干他们需要干的事,高手们使用count[-1]这种技巧其实并不少见,不应该当作错误。)
数组也可以像结构体一样初始化,未赋初值的元素也是用0来初始化,例如:

int count[4] = { 3, 2, };
数组和结构体虽然有很多相似之处,但也有一个显著的不同:数组不能相互赋值或初始化。例如这样是错的:
int a[5] = { 4, 3, 2, 1 };
int b[5] = a;
既然不能相互赋值,也就不能用数组类型作为函数的参数或返回值
void foo(int a[5])
{
...
}
int array[5] = { 0 };
foo(array);
编译器也不会报错,但这样写并不是传一个数组类型参数的意思。对于数组类型有一条特殊规则:数组类型做右值使用时,自动转换成指向数组首元素的指针。所以上面的函数调用其实是传一个指针类型的参数,而不是数组类型的参数。这也解释了为什么数组类型不能相互赋值或初始化,例如上面提到的a = b这个表达式,ab都是数组类型的变量,但是b做右值使用,自动转换成指针类型,而左边仍然是数组类型,所以编译器报的错是error: incompatible types in assignment
习题
1、编写一个程序,定义两个类型和长度都相同的数组,将其中一个数组的所有元素拷贝给另一个。既然数组不能直接赋值,想想应该怎么实现。
答:
int main(void)
{
int array1[10] = { 3, 5, 1, 2, 2, 9, 1, 3, 4, 8 };
int array2[10];

int i;
for (i = 0; i < 10; i++) {
array2[i] = array1[i];
}

for (i = 0; i < 10; i++) {
printf("%d, ", array2[i]);
}
return 0;
}



2. 3. 数组应用实例
调用C标准库得到的随机数其实是伪随机(Pseudorandom)数,是用数学公式算出来的确定的数,只不过这些数看起来很随机,并且从统计意义上也很接近均匀分布(Uniform Distribution)的随机数。C标准库中生成伪随机数的是rand函数,使用这个函数需要包含头文件stdlib.h,它没有参数,返回值是一个介于0和RAND_MAX之间的接近均匀分布的整数。RAND_MAX是该头文件中定义的一个常量,在不同的平台上有不同的取值,但可以肯定它是一个非常大的整数。
0~10的随机数:int x = rand() % 11;

把上面的程序运行几遍,你就会发现每次产生的随机数都是一样的,不仅如此,在别的计算机上运行该程序产生的随机数很可能也是这样的。这正说明了这些数是伪随机数,是用一套确定的公式基于某个初值算出来的,只要初值相同,随后的整个数列就都相同。实际应用中不可能使用每次都一样的随机数,例如开发一个麻将游戏,每次运行这个游戏摸到的牌不应该是一样的。因此,C标准库允许我们自己指定一个初值,然后在此基础上生成伪随机数,这个初值称为Seed,可以用srand函数指定Seed。通常我们通过别的途径得到一个不确定的数作为Seed,例如调用time函数得到当前系统时间距1970年1月1日00:00:00的秒数,然后传给srand

srand(time(NULL);

然后再调用rand,得到的随机数就和刚才完全不同了。调用time函数需要包含头文件time.h,这里的NULL表示空指针。

习题
1、用rand函数生成[10, 20]之间的随机整数,表达式应该怎么写?
答:

1.补完本节直方图程序的main函数,以可视化的形式打印直方图。
答:
int main(void)
{
gen_random(UPPER);

int i, histogram[UPPER] = {0};
for (i = 0; i < N; i++)
histogram[a[i]]++;

for (i = 0; i < UPPER; i++)
printf("%d\t", i);
printf("\n");
do { // 实际上只可能循环N次,因此外层while循环可改为for 0-> N-1,变量breakLoop也可以省了
int breakLoop = 1;
for (i = 0; i < UPPER; i++) {
if (histogram[i] > 0) {
printf("%c\t", '*');
histogram[i]--;
breakLoop = 0;
} else {

printf("\t");
}
}
printf("\n");
if (breakLoop)
break;
} while (1);

return 0;
}

2、定义一个数组,编程打印它的全排列。比如定义:
#define N 3
int a[N] = { 1, 2, 3 };

则运行结果是:
$ ./a.out
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
3 1 2

最后再考虑第三个问题:如果要求从N个数中取M个数做组合而不是做排列,就不能用原来的递归过程了,想想组合的递归过程应该怎么描述,编程实现它。
答:

4. 字符串
字符串可以看作一个数组,它的每个元素是字符型的。
注意每个字符串末尾都有一个字符'\0'做结束符,这里的\0是ASCII码的八进制表示,也就是ASCII码为0的Null字符,在C语言中这种字符串也称为以零结尾的字符串(Null-terminated String)。数组元素可以通过数组名加下标的方式访问,而字符串字面值也可以像数组名一样使用,可以加下标访问其中的字符:
char c = "Hello, world.\n"[0];
编译错误,字符串字面值是只读的。
"Hello, world.\n"[0] = 'A';
char str[10] = "Hello";
相当于
char str[10] = { 'H', 'e', 'l', 'l', 'o', '\0' };
最好让编译器自己计算:
char str[] = "Hello, world.\n";

printf("string: %s\n", str);

printf会从数组str的开头一直打印到Null字符为止,Null字符本身是Non-printable字符,不打印。
如果数组str中没有Null字符,那么printf函数就会访问数组越界,后果可能会很诡异:有时候打印出乱码,有时候看起来没错误,有时候引起程序崩溃。
5. 多维数组
inta[3][2]= { 1, 2, 3, 4, 5 };

图8.3.多维数组

多维数组
也可以:int a[][2] = { {1, 2} , {3, 4}, {5,} };
结构体数组
struct complex_struct {
double x, y;
} a[4] = { [0].x = 8.0; };

结构体元素是数组
struct {
double x, y;
int count[4];
} s = { .count[2] = 9 };

多维字符串数组
char days[8][10] = { "", "Monday", "Tuesday" ... "Sunday" };
printf("%s\n", days[day]);

图8.4.多维字符数组

多维字符数组

这个程序和例4.1 “switch语句”的功能其实是一样的,但是代码简洁多了。简洁的代码不仅可读性强,而且维护成本也低,像例4.1 “switch语句”那样一堆caseprintfbreak,如果漏写一个break就要出Bug。这个程序之所以简洁,是因为用数据代替了代码。具体来说,通过下标访问字符串组成的数组可以代替一堆case分支判断,这样就可以把每个case里重复的代码(printf调用)提取出来,从而又一次达到了“提取公因式”的效果。这种方法称为数据驱动的编程(Data-driven Programming),写代码最重要的是选择正确的数据结构来组织信息,设计控制流程和算法尚在其次,只要数据结构选择得正确,其它代码自然而然就变得容易理解和维护了,就像这里的printf自然而然就被提取出来了。

石头剪刀布游戏问题:
留给读者思考的问题是:(man - computer + 4) % 3 - 1这个神奇的表达式是如何比较出0、1、2这三个数字在“剪刀石头布”意义上的大小的?

分享到:
评论

相关推荐

    宋劲彬的嵌入式C语言一站式编程

    目录 历史 前言 I. C语言入门 1. 程序的基本概念 1. 程序和编程语言 2. 自然语言和形式语言 ...3. 在Linux C编程中使用Unicode和UTF-8 B. GNU Free Documentation License Version 1.3, 3 November 2008 参考书目 索引

    Linux c编程一站式学习

    Linux C 编程一站式学习 宋劲杉 目录 Linux C 编程一站式学习..............................................1 C 语言入门..........................................................5 第 1 章 程序的基本概念.....

    LINUX网站建设技术指南

    第8章 PHP主页设计 8.1 内嵌式脚本语言PHP概述 8.1.1 PHP发展历史 8.1.2 PHP的主要技术特点 8.2 PHP语句 8.2.1 初识PHP 8.2.2 PHP语句和HTML分离 8.3 PHP中的变量 8.3.1 变量名和变量类型 8.3.2 深入了解变量类型...

    “走马灯”算法的实现-解决组合问题

    使用“走马灯”算法解决组合问题(解答《Linux C 编程一站式学习》第 8 章第 8.3 节习题):从包含 N 个元素的数组中抽取 M 个元素的组合。

    代码之美(中文完整版).pdf

    第8章 图像处理中的即时代码生成 第9章 自顶向下的运算符优先级 9.1. JavaScript 9.2. 符号表 9.3. 语素 9.4. 优先级 9.5. 表达式 9.6. 中置运算符 9.7. 前置操作符 9.8. 赋值运算符 9.9. 常数 9.10. Scope 9.11. ...

    PHP程序开发范例宝典III

    第8章 SQL查询相关技术 305 8.1 数据库操作 306 实例193 创建数据库 306 实例194 查看数据库 307 实例195 删除数据库 308 8.2 数据表操作 308 实例196 创建数据表 309 实例197 查看数据表 310 实例...

    PHP开发实战1200例(第1卷).(清华出版.潘凯华.刘中华).part1

    实例128 获取数组中最后一个元素 158 实例129 去除数组中的重复元素 158 实例130 字符串与数组的转换 159 实例131 对数组元素进行随机排序 160 实例132 随机抽取数组中元素 161 实例133 二维数组的输出 162 实例134 ...

    PHP开发实战1200例(第1卷).(清华出版.潘凯华.刘中华).part2

    实例128 获取数组中最后一个元素 158 实例129 去除数组中的重复元素 158 实例130 字符串与数组的转换 159 实例131 对数组元素进行随机排序 160 实例132 随机抽取数组中元素 161 实例133 二维数组的输出 162 实例134 ...

    超级有影响力霸气的Java面试题大全文档

    当客户机第一次调用一个Stateful Session Bean 时,容器必须立即在服务器中创建一个新的Bean实例,并关联到客户机上,以后此客户机调用Stateful Session Bean 的方法时容器会把调用分派到与此客户机相关联的Bean实例...

    JAVA上百实例源码以及开源项目

     Java波浪文字,一个利用Java处理字符的实例,可以设置运动方向参数,显示文本的字符数组,高速文本颜色,显示字体的 FontMetrics对象,得到Graphics实例,得到Image实例,填充颜色数组数据,初始化颜色数组。...

    java 面试题 总结

    ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector...

    JAVA上百实例源码以及开源项目源代码

     Java波浪文字,一个利用Java处理字符的实例,可以设置运动方向参数,显示文本的字符数组,高速文本颜色,显示字体的 FontMetrics对象,得到Graphics实例,得到Image实例,填充颜色数组数据,初始化颜色数组。...

Global site tag (gtag.js) - Google Analytics