当前位置 博文首页 > jtwqwq的博客:翁恺老师C语言程序设计网课(7)

    jtwqwq的博客:翁恺老师C语言程序设计网课(7)

    作者:[db:作者] 时间:2021-08-19 09:52

    8.1.1 初试数组

    之前提到过如何计算用户输入的数字的平均数?
    之前的做法:每读到一个数(!=-1)加到sum里,cnt++,最后sum/cnt
    这样我们不需要记录每一个数
    如果题目还要求:输出所有大于平均数的数?这样就必须记录每一个数了,因为我们是最后才计算平均数的,要最后再用每个数和平均数做判断
    如何记录很多数?int num1,num2……?不好。这样下去无穷无尽。

    使用数组

    int number[100];//定义数组,表示数组可以放100个int
    scanf(%d”,&x);
    while(x!=-1){
    	number[cnt]=x;//对数组中的元素赋值
    	cnt++;
    	scanf(%d”,&x);
    }
    

    最后再加个cnt长度的循环,判断每一个数与平均数比较大小

    if(cnt>0){
    	int i;
    	double average=sum/cnt;
    	for(i=0;i<cnt;i++){
    		if(number[i]>average){//使用数组中的元素
    			printf("%d ",number[i]);//遍历数组
    		}
    	}
    }
    

    这个程序的安全隐患在于没有考虑使用的数组下标是否会超过100.定义的时候注意要求。

    8.1.2 数组的使用:如何定义和使用数组,数组的下标和下标的范围

    定义数组:
    <类型>变量名称[元素数量];//方括号表示这是个数组

    int grades[100];
    double weight[20];
    

    元素数量必须是整数。在c99之前,元素数量必须是编译时确定的字面量。(a[n]不行)vscode中好像就不行,提示variable-sized object may not be initialized
    数组是一种容器,特点是:
    其中所有元素具有相同的数据类型;
    一旦创建,不能改变大小;
    其中元素在内存中连续依次排列(从0开始);
    如:定义十个单元a[10]→a[0]~a[9]
    在这里插入图片描述

    每个单元就是一个int类型的变量。像普通变量一样可以出现在赋值的左边或右边。左边的叫左值,右边的叫右值。
    数组的每个单元就是数组类型的一个变量。使用数组时[]中的数字/变量叫下标或索引,从0开始计数
    (要习惯数数从0开始到n-1)
    但编译器和运行环境不会检查数组下标是否越界,无论读、写数组单元。
    不过数组越界时可能出问题:segmentation fault,运气好的话不会造成严重的后果。
    所以这是程序员的责任来保证程序只适用有效的下标值(范围:[0,数组大小-1])

    防止读入数字超过100个的方法:
    方法一:cnt=100之后停止读数;
    方法二:利用c99数组大小可以是动态的的特性,定义number[cnt];//用户先输入cnt

    可不可以int a[0];?
    可以,但是没用。

    8.1.3 数组的例子:统计个数

    不停输入0~9范围内的整数,读到-1停止,统计每种数字出现的次数。
    和上一道题不同的是,不用记录每次输入的数字,我们需要记录的是每种数字出现的次数。
    学到了定义数组为0的方法:

    for(int i=0;i<10;i++)count[i]=0;
    

    和打印方法:

    for(int i=0;i<10;i++)printf(%d\n”,count[i]);
    

    该题中出现多次数字10。根据之前学到的方法,我们可以定义const number=10(c99才能用);每一个10用number代替。

    通常用到数组的程序都需要的环节:

    1. 确定数组大小;
    2. 定义数组;
    3. 初始化数组;
    4. 数组参与运算;
    5. 遍历数组输出。

    8.2.1 数组运算

    搜索:在一组给定数据中,怎样找出某个数据是否存在?
    (往函数中传数组:int sum(a[]))
    数组的集成初始化:

    int a[]={2,4,6,7,1,3,5,9}
    /*直接用大括号给出数组所有元素的初始值;
    不需要给出数组的大小,编译器替你数了。*/
    

    依次初始化数组的每一个单元
    如果a[13]={2};只有a[0]是2,后面的单元都是0
    所以如果想定义一个数组全为0:a[13]={0};
    C99还可以在大括号里给指定的位置赋值。
    用[n]在初始化数据中给出定位,没有定位的数据接在前面的位置后面;其他位置的值补0.

    int a[0]={[0]=2,[2]=3,6};
    这个例子里,a[0]=2,a[2]=3,a[3]=6
    我们也可以不给出数组大小,让编译器计算。比如上例可写为:
    int a[]={[0]=2,[2]=3,6};
    这样会根据大括号里最大的数字下标来算数组的大小。即下标最大为3
    这样特别适合初始数据稀疏的数组。

    数组的大小

    sizeof给出整个数组所占据的内容的大小,单位是字节。(n*4,sizeof(a)/sizeof(a[0])就能得到数组元素个数)
    不能直接把一个数组赋给另一个数组b[]=a;
    数组变量本身不能被赋值。如果想把一个数组的值全部交给另一个数组,必须遍历。

    遍历数组

    通常使用for循环,从0开始到<n,这样循环体最大的i正好是数组最大的有效下标。
    常见错误:1.循环结束条件是<=数组长度
    2.离开循环之后继续使用i作为数组元素下标。

    数组作为函数参数时,往往需要另一个参数来传递数组大小。
    原因:1、数组传入函数之后我们不能用sizeof来计算数组的元素个数;
    2.不能在[]中给出数组的大小。
    具体原因后面再说。

    8.2.2 数组例子:素数

    之前找素数的例子。我们可以定义isPrime()函数来判断一个数是否是素数。
    isPrime()函数:我们从2到x-1都拿去除x,循环要走很多遍,重复执行的次数很多(程序很差)。
    优化:当x是!=2的偶数,一定不是素数,就直接不用判断。
    因为剩下需要判断的书都是奇数,肯定%2=1,这样我们判断接下来的数时for循环除数就可以从3开始的奇数判断。

    for(int i=3;i<x;i+=2)
    

    再次优化:我们不需要走到x。我们只要走到sqrt(x)就够了。

    for(int i=3;i<=sqrt(x);i+=2)
    

    引入:当我们想了解一个函数时,在编译器中输入man 函数名称(man sqrt)就能得到其相关信息。(man:manual手册)
    Windows用户:打开浏览器搜索。
    再再次优化:我们不需要拿比x小的数字来测试x是不是素数,我们只需要拿比x小的素数就够了。

    int isPrime(int x,int knowsPrimes[],int numberofKnownPrimes)
    int main()
    {
    	const int number=100;
    	int prime[number]={2};
    	int count=1;
    	int i=3;
    	while(count<number){
    		if(isPrime(i,prime,count)){
    			prime[count++]=i;
    		}
    		i++;
    	}//prime数组装着所有素数
    	for(i=0;i<number;i++){
    		printf("%d",prime[i]);
    		if((i+1)%5)printf("\t");
    		else printf("\n");
    	}
    	return 0;
    }
     int isPrime(int x,int knowsPrimes[],int numberofKnownPrimes)
     {
     	int ret=1;
     	int i;
     	for(i=0;i<numberofKnownPrimes;i++){
     		if(x%knownPrimes[i]==0){
     			ret=0;
     			break;
     		}
     	}
     	return ret;
     }
    

    一边构造素数表,一边利用表来证明素数。
    其中prime[cnt++]=i;一举两得,cnt++的同时还把i的值放到了对应的数组位上。

    while(count<number){
    	if(isPrime(i,prime,count)){
    		prime[count++]=i;
    	}
    	{
    		printf("i=%d \tcnt=%d\t",i,count);
    		int i;
    		for(i=0;i<number;i++){
    			printf("%d\t",prime[i]);
    		}
    		printf("\n");
    	}
    	i++;
    }
    

    这样加个括号在里面int i之后,我们使用i就不会影响到外面的i的值了(但是我宁愿重新定义个变量j=i。因为太绕了)

    同样的方法,我们可以先输出一个表头。

    {
    	int i;
    	printf("\t\t\t\t");
    	for(i=0;i<number;i++){
    		printf("%d\t",i);
    	}
    	printf("\n");
    }
    

    在这里插入图片描述

    换一个思路,使得最后这张表里留下来的数都是素数。
    欲构造n以内的素数表:

    1. 令x=2
    2. 将2x,3x……直到ax<n的数标记为非素数
    3. 令x为下一个没有被标记为非素数的数,重复2;直到所有的数都已经被尝试完毕。
      (从2,3,4,5,6,7,8……删掉2的所有倍数,再删掉3的所有倍数,再删掉5的所有倍数……)
    4. 先开辟数组prime[n],初始化其所有元素为1,prime[x]=1表示x是素数,prime[x]=0表示x不是素数
    5. 令x=2
    6. 如果x是素数,则for(int i=2;x*i<n;i++)prime[i*x]=0
    7. x++,重复3直到x==n,否则结束。
    int main()
    {
    	const int maxNumber=25;
    	int isPrime[maxNumber];
    	int i,x;
    	for(i=0;i<maxNumber;i++)
    	{
    		isPrime[i]=1;
    	}
    	for(x=2;x<maxNumber;i++)
    	{
    		if(isPrime[x])
    		{
    			for(i=2;i*x<maxNumber;i++) isPrime[i*x]=0;
    		}
    	}
    	printf("\n");
    	return 0;
    }
    

    如此可见,算法的思考方式不见得与人相同。

    8.2.3 二维数组

    int a[3][5];通常理解为a是一个3行5列的矩阵
    在这里插入图片描述

    最好先行号再列号,和线性代数也是相对应的。
    二维数组的遍历需要两个for循环

    int a[][5]={
    	{0,1,2,3,4},
    	{2,3,4,5,6},
    };
    

    a[i][j]表示一个int
    a[i,j]是逗号运算符(等于逗号右边的值),表示a[j],不是正确表达二维数组的方式。
    二维数组的列数必须给出,行数可以交给编译器来数。

    给数的时候每行一个{},用逗号分隔。如果省略表示0。
    也可以用定位(注意只能是c99)
    二维数组是逐行填满的,所以也可以不加大括号,当做一维数组去初始化
    PS: tic-tac-toe 井字棋判断输赢问题:行列对角线分开检查。

    const int size = 3;
    int board[size][size];
    int i,j;
    int num0fX;//X是一方
    int num0fO;//O是一方
    int result=-1;