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

系统 1797 0

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 这个表达式, a b 都是数组类型的变量,但是 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. 多维数组
int a[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语句” 那样一堆 case printf break ,如果漏写一个 break 就要出Bug。这个程序之所以简洁,是因为用数据代替了代码。具体来说, 通过下标访问字符串组成的数组可以代替一堆 case 分支判断,这样就可以把每个 case 里重复的代码( printf 调用)提取出来 ,从而又一次达到了“提取公因式”的效果。这种方法称为数据驱动的编程(Data-driven Programming),写代码最重要的是选择正确的数据结构来组织信息,设计控制流程和算法尚在其次,只要数据结构选择得正确,其它代码自然而然就变得容易理解和维护了,就像这里的 printf 自然而然就被提取出来了。

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

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


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论