C语言第八章数组
上节回顾:
变量的作用域和存储类型
变量的作用域:可被访问的范围,分为局部变量和全局变量
变量的存储类型:变量在内存中被分配内存的方式,分为静态存储区、动态存储区
变量的存储类型决定变量的存储周期,静态存储区分配内存的变量生命周期比较长
auto:动态局部变量,容易与(static)静态局部变量,两者作用域相同,但是生存周期不一样。动态局部变量离开语句块再次进入该语句块,变量的值就消失了,变为随机值;而静态局部变量,下次再进入语句块,值仍然保留。动态局部变量每次进入语句块都需要初始化,否则是随机值;而静态局部变量则只需要初始化一次,如果不显式初始化的话,则默认初始化为0.
第八章 数组
函数之前,自底向上的设计,函数之后,自顶向下的设计。
数组之前,简单的数据结构。
数组之后,复杂的数据结构。
为什么要学习数组呢?
如果要读入十个数据,需要十个变量一个一个读取,用数组可以循环读入,只需要一条scanf语句。
数组适合处理大量的有相同数据类型的问题
一维数组的定义和初始化
int a[10];定义一个有10个元素的一维数组,编译器分配10个连续的整形存储空间,首地址用数组名来表示,即a。
不能定义一个动态可变长度的数组,一旦定义,就不能改变其长度。因此,在标准C中是不允许将数组下标定义成变量的。可以使用宏定义的方式来实现数组长度的修改。
根据数组的数据类型,为每一元素安排相应字节数的存储空间。
根据数组的存储类型,将其安排在内存的动态、静态存储区或者寄存器区。

一维数组的引用
允许快速随机访问,可以使用变量或者表达式,如a[i]。
未初始化的数组元素的值是什么?
静态数组和全局数组会自动初始化为0,其他的是随机值。
一维数组的初始化

怎么判断两个数组相等,或者说怎么把一个数组的值赋值给另外一个数组?
使用循环语句赋值即可。
更高效的数组初始化方法:用sizeof(a)获取数组a所占内存的字节数。
1 memset(a,0,sizeof(a));
更搞笑的数组赋值方法:将a数组的值赋给b
1 memcpy(b,a,sizeof(a));
使用以上两个函数,需要使用string.h头文件
例:编程实现用户输入月数显示天数(不考虑闰年)
访问数组的时候,下标越界是大忌,编译的时候不会报错,会发生运行时错误!!
当下标小于0或者越界的时候会出现什么情况?c和a的值会被修改,并且程序运行结束的时候会让你关闭程序。

二维数组的定义和初始化
二维数组逻辑上是有行有列的,在内存当中,仍然是线性存储的。n维数组需要n个下标确定元素在数组中的位置。
存放顺序:按行存放,线性存储。先顺序存放第0行,再第1行...
二维数组初始化的时候,第二维的大小不能省略,列数一定要指定,因为是按行存的。

二维数组的下标越界会出现什么情况?后果也很严重!

修改一下程序,下标越界后修改了数组中的值a[1][0]

再修改一下,第一次输出3行2列,第二次输出2行3列,会出现什么结果?

再把程序修改一下 :a[3][2]改为a[2][3],然后按照3行2列输出

例:从键盘输入某年某月(包括闰年),编程输出该年的该月拥有的天数

如何向函数传递一维数组,就是将数组作为函数参数?
数组作为函数参数,按地址调用;传递数组的首地址,实参与形参数组占同一段内存单元
普通变量作函数参数,按值调用;传递变量值的副本,实参与形参变量占不同的内存单元。
传递数组首地址,效率更高。
例1:计算平均分:计数控制-键盘输入学生人数
例2:计算平均数:标记控制-负值作为输入结束标记
例3:计算最高分 int score[5] = {84, 83, 88 ,87 ,61]
返回最高分的数组下标
1 int FindMaxNum(int Score[], int n){ 2 int i,maxNum; 3 maxNum = 0; 4 for(i=1; i<n; i++){ 5 if(Score[i] > Score[maxNum]){ 6 maxNum = i; 7 } 8 } 9 return maxNum; 10 }
排序和查找是常用的操作
比如,对扑克牌进行排序;青年歌手大奖赛成绩排名;从图书馆的书架上查找某一本书;
百度、谷歌等网站搜索
排序算法
交换排序、选择排序、冒泡排序、插入排序、快速排序
1、交换法排序
n个数需要n-1次比较
例:使用交换法对成绩降序排序
借助中间变量实现两数交换
1 for ( i=0; i<n-1; i++){ 2 for(j=i+1; j<n; j++){ 3 if(score[i] < score[j]){ 4 temp = score[i]; 5 socre[i] = score[j]; 6 score[j] = temp; 7 } 8 } 9 }
不借助中间变量完成两数交换,这种方法不安全并且可读性不高。加法可能会产生溢出,除法可能除数为0,此类方法不如有中间变量的交换
a = a+b;
b = a-b;
a = a-b;
或者
b = a*b
a = b/a
b = b/a
2、选择排序
选择一个最高的,记住其下标位置
升序还是降序看大于号还是小于号;
按照成绩降序排序:
1 for ( i=0; i<n-1; i++){ 2 k = i; 3 for( j = i+1; j<n; j++){ 4 if(score[j] > score[k]){ 5 k = j; 6 } 7 } 8 if (k!= i){ 9 temp = score[k]; 10 score[k] = score[i]; 11 score[i] = temp; 12 13 temp2 = num[k]; 14 num[k] = num[i] 15 num[i] = temp2; 16 }
数组越界:
例如 int a[2][3];
a[0][3] 的值其实就是a[1][0],这种越界虽然不会会带来隐患。
选择排序的效率比交换要高
查找算法
1、顺序查找
事先不必排序,考虑最坏情况的话,查找次数等于数据量大小,效率低下
找到返回该值,找不到返回-1。
1 int LinSearch(long num[], long x, int n){ 2 int i; 3 for(i=0;i<n;i++){ 4 if(num[i] == x){ 5 return i; 6 } 7 } 8 return -1; 9 }
例:顺序查找某学号对应的学生的成绩

猜数游戏解决问题的方法:找中间数猜
2、二分查找(折半查找)
1024个查找需要10次,即2的幂次方。
折半查找首先要进行排序,找到的情况:

什么情况下没找到呢? low>high
例:折半查找算法:
1 int BinSearch(long num[] ,long x, int n){ 2 int low,high,mid; 3 low =0; 4 high = n-1; 5 while(low<=high){ 6 mid = (high+low)/2; 7 if(x > num[mid]){ 8 low = mid+1; 9 } 10 else if(x < num[mid]){ 11 high = mid-1; 12 } 13 else{ 14 return mid; 15 } 16 } 17 return -1; 18 }
如果这个数组很大,high和low都很大的时候,数组下标mid可能会溢出,使mid成为一个负数。
防止溢出的解决办法:修改计算中间值的方法,用减法代替加法
mid = low + (high-low)/2
我们这一章很大程度上都在讲安全性,safety(程序内部的安全) security(抵御外部入侵)
向函数传递二维数组
在声明函数的二维数组形参时,为什么不能省略数组第二维的长度?
元素a[i][j]在数组中相对于第一个元素的位置:
i*第二维长度+j
元素地址:首地址+偏移量
实际的地址:首地址+偏移量*每个数组元素所占的内存字节数(sizeof(short))
数组的应用
1、保存n个学生一门课程的成绩,用一维数组
int Average(int score[],int n);
通常不指定数组的长度,用另一个形参来指定数组的大小
2、保存n个学生m门课程的成绩
用二维数组
void Average(int score[][COURSE_N], float aver[],int n);
可以省略数组第一维的长度,不能省略第二维的长度
数组aver可保存每个学生的平均分,或每门课程的平均分
例:计算每个学生的总分和平均分
1 void AverforStud(int score[][COURSE_N], int sum[],float aver[], int n){ 2 int i,j; 3 for(i = 0; i<n; i++){//先遍历每个学生 4 sum[i] = 0 5 for(j=0; j<COURSE_N; j++){//遍历每门课程 6 sum[i] = sum[i] + score[i][j]; 7 } 8 aver[i] = (float) sum[i]/COURSE_N; 9 } 10 }
如何计算每门课程的总分和平均分:先遍历每门课,再遍历每个学生
数组的其他应用
1、如何判断一个整数x是否是素数
不能被1和x以外的其他整数的正整数
试商法,用2~x-1之间的整数去试商看能否整除
用2~sqrt(x)之间的整数去试商看能否被整除即可。
1 int IsPrime(int x){ 2 int i,flag = 1; 3 int squareRoot = sqrt(x); 4 if(x<=1) flag = 0; 5 for(i = 2; i<=squareRoot && flag;i++){ 6 if(x%i == 0) flag = 0; 7 } 8 return flag; 9 }
求100以内的所有素数,可以将上面的程序循环100次,但是效率低下。
使用筛法求100以内的所有素数(排除法)
依次从数组a中筛选2的倍数,3的倍数,5的倍数,7的倍数...
自顶向下,逐步求精设计算法
1:设计总体算法
初始化数组a,使a[2]=2,a[3]=3,...a[N]=[N]
对i=2,3.....sqrt(N)分别做“筛掉a中所有a[i]的倍数”
输出数组中余下的数(a[i]!=0的数)
2:对“筛掉a中所有的a[i]的倍数”求精
对数组a中的下标i后j对应所有的数分别做:
若“改数a[j]是a[i]的倍数”,则“筛掉该数a[j]”
3:对若“该数a[j]是a[i]的倍数”,则“筛掉该数a[j]"求精
if(a[i]!=0 && a[j]!=0 && a[j]%a[i] == 0){ a[j] = 0; }
整个程序:
1 int i,j,a[N+1] 2 for( i=2; i<=N; i++){ 3 a[i] = i; 4 } 5 6 for(i = 2; i<=sqrt(N); i++){ 7 for(j = i+1; j<=N; j++){ 8 if(a[i]!=0 && a[j]!= 0 && a[j]%a[i]==0 ){ 9 a[j] = 0; 10 } 11 } 12 }
筛法用在其他问题上:
1、鲁智深吃馒头
据说,鲁智深一天中午匆匆来到开封府大相国寺,想蹭顿饭吃,当时大相国寺有99个和尚,只做了99个馒头。智清长老不愿得罪鲁智深,便把他安排在一个特定位置,之后对所有人说:从我开始报数(围成一圈),第5个人可以吃到馒头(并退下)
按此方法,所有和尚都吃到了馒头,唯独鲁智深没有吃上,请问他在什么位置?借鉴筛法求出剩下的最后一个人的位置
2、50位的n!计算?
为什么结果会这样?小数点16位之后都是0.
实数是按阶码和尾数来存储的,存在精度问题,double类型精度32位,但是表示范围就小了。
挑战类型表示的极限-大数存储的问题,用数组的每个元素来存储每一位,并且实现自动进位
小结
如何定义数组
如何向函数传递一位数组
传递数组的首地址,形参数组和实参数组共享同一段内存
通常不指定数组的长度,用另一个形参来指定数组的大小
如何向函数传递二维数组
可省略数组第一维的长度,不能省略第二维的长度
常用算法:排序、查找、求最值

浙公网安备 33010602011771号