c语言设计(上)--翁恺慕课版本 万字长文

318 阅读38分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情

第一周:程序设计与c语言

算法

1.我们要让计算机做计算,就需要像这样找出计算的步骤,然后用编程语言写下来

2.计算机做的所有事情都叫做计算

程序的执行

1.解释:借助一个程序,那个程序能试图理解你的程序,然后按照你的要求执行

2.编译:借助一个程序,就像一个翻译,把你的程序翻译成计算机真正能懂的语言-机器语言-写的程序,然后,这个机器语言写的程序就能够直接执行了

解释语言vs编译语言

1.语言本来没有编译/解释之分

2.常用的执行方式不同而已

3.解释性语言有特殊的计算能力

4.编译型语言有确定的运算性能

c语言用在哪里?

1.操作系统,嵌入式系统,驱动程序,底层驱动,图形引擎、图像处理、声音效果

2.开发效率>>学习乐趣 开发效率>>开发乐趣 日常应用很少直接用c语言编写

3.学习c的过程主要是写练习代码

img

四则运算

四则运算c符号意义
++
--
×*
÷/
%取余
()()括号

%表示取两个数相除以后的余数

第二周:计算

2.1变量

算找零

如何能在程序运行时输入那个数字,然后计算输出结果?

需要:1.有地方放输入的数字-printf(输出),能够提示哪里输入数字

2.有办法输入数字-scanf(输入),提供输入数字的通道

3.输入的数字能参与计算

如何输入

输入也在终端窗口中

输入就是以行为单位进行的,行的结束标志就是你按下回车键。在你按下回车之前,你的程序是不会读到任何东西的

变量

int price = 0;

这一行定义了一个变量。变量的名字是price,类型是int,初始值是0

变量是一个保存数据的地方,当我们需要在程序里保存数据时,比如上面的例子中要记录用户输入的价格,就需要一个变量来保存它。用一个变量保存了数据,它才能参加到后面的计算中,比如计算找零

变量定义

变量定义的一边形式:<类型名称><变量名称>

int price;

int amount;

int price,amount;

变量的名字

1.变量需要一个名字,变量的名字是一种"标识符",意思是它是用来识别这个和那个的不同的名字

2.标识符有标识符的构造规则。基本的原则是:标识符只能由字母、数字和下划线组成,数字不可以出现在第一个位置上,c语言的关键字(有的地方叫他们保留字),不可以用作标识符

赋值和初始化

int price = 0;

这一行,定义了一个变量。变量的名字是price,类型是int,初始值是0

price = 0是一个式子,这里的"="是一个赋值运算符,表示将"="右边的值赋给左边的变量

赋值

和数学不同,a=b在数学中表示关系,即a和b的值是一样的;而在程序设计中,a=b表示要求计算机做一个动作:将b的值赋给a。关系是静态的,而动作是动态的。在数学中,a=b和b=a是等价的,而在程序设计中,两者的意思是完全相反的

初始化

当赋值发生在定义变量的时候,就像给变量price=0那样,就是变量的初始化。虽然c语言并没有强制要求所有的变量都在定义的地方做初始化,但是所有的变量在第一次被使用(出现在赋值运算符的右边)之前应该被赋值一次

问:如果没有初始化?答:变量会出现一个随机数,可能很大也可能很小不固定但一定不会是你想要的那个数

变量初始化

<类型名称><变量名称>=<初始值>;

int price = 0;

int amount = 100;

组合变量定义的时候,也可以在这个定义中单独给单个变量赋初值,如:int price = 0,amount = 0;

读整数

scanf("%d",&price);

要求scanf这个函数读入下一个整数,读到的结果赋值给变量price

小心price前面的&

表达式

“=”是赋值运算符,有运算符的式子就叫做表达式

price = 0;

change = 100-price;

变量类型

int price = 0;

这一行,定义了一个变量。变量的名字是price,类型是int,初始值是0.

C是一种有类型的语言,所有的变量在使用之前必须定义或者声明,所有的变量都必须具有确定的数据类型。数据类型表示在变量中可以存放什么样的数据,变量中只能存放指定类型的数据,程序运行过程中也不能改变变量的类型

c99可以在任何地方定义变量,ANSI C只能在代码开头的地方定义变量

常量

int change = 100 - price;

固定不变的数,是常数。直接写在程序里,我们称为直接量(literal)

更好的方式是定义一个常量

const int AMOUNT = 100;

const

const是一个修饰符,加在int的前面,用来给这个变量加上一个const(不变的)的属性。这个const的属性表示这个变量的值一旦初始化,就不能再修改了

int change = AMOUNT - price;

如果试图对常量做出修改,把他放在赋值运算符的左边,就会被编译器报错

tips

程序要求读入多个数字时,可以在一行输入,中间用空格分开,也可以在多行输入

在scanf的格式字符串中有几个%d,它就等待用户输入一个整数,当然,字符串后面也需要对应有那么多整数

两个整数运算的结果只能是整数

例如:10/3*3=9 10跟10.0在C中是完全不同的数

10.0是浮点数

浮点数

1.带小数点的数值,浮点这个词的本意就是指小数点是浮动的,是计算机内部表达非整数(包括分数和无理数)的一种方式。另一种方式叫做定点数。人们借用浮点数这个词来表达所有带小数点的数。

2.当浮点数和整数放到一起运算时,C会将整数转换成浮点数,然后进行浮点数的运算

double

1.inch是定义为int类型的变量,如果把int换成double,我们就把它改成double类型的浮点数变量了。

2.double的意思是"双",它本来是"双精度浮点数"的第一个单词,人们用来表示浮点数类型。除了double,还有float(意思就是浮点)表示单精度浮点数

在输入的时候数据类型定义为:%lf

在输出的时候数据类型定义为:%f

数据类型

整数:int printf("%d",...) scanf("%d",...)

带小数点的数: double printf("%f") scanf("%lf",....)

整数

整数类型不能表达有小数部分的数,整数和整数的运算结果还是整数。计算机里会有纯粹的整数这种奇怪的东西,是因为整数的运算比较快,而且占地方也小。其实人们日常生活中大量做的还是纯粹整数的计算,所以整数的用处还是很大的。

2.2表达式计算

表达式

一个表达式是一系列运算符和算子的组合,用来计算一个值

运算符

1.运算符是指进行运算的动作,比如加法运算符"+",减法运算符"-"

2.算子是指参与运算的值,这个值可能是常数,也可能是变量,还可能是一个方法的返回值

计算时间差

int hour1,minute1;
int hour2,minute2;
​
scanf("%d %d",&hour1,&minute1);
scanf("%d %d",&hour2,&minute2);
​
int t1 = hour1 * 60 + minute1;
int t2 = hour2 * 60 + minute2;
​
int t = t2 - t1;
​
printf("时间差是%d小时%d分钟",t/60,t%60);

求平均值

写一个程序,输入两个整数,输出他们的平均值

int a,b;
scanf("%d %d",&a &b);
double c = (a + b)/2.0;
printf("%d和%d的平均数是:%f",a,b,c);

运算符优先级

img

单目运算符

只有一个算子的运算符:+,-

例如-a,-b,+a,+b

表达式可以使用a*-b类似的形式

赋值运算符

1.赋值也是运算,也有结果

2.a=6的结果是a被赋予的值,也就是6

3.a=b=6 在计算机中形成原理 a = (b=6)

嵌入式赋值

提示:尽量避免"嵌入式赋值",不利于阅读也容易产生错误

例如:

result = a = b = 3 + c;
result = 2;
result = (result = result * 2) * 6 * (result = 3 + result);

计算复利

在银行存定期的时候,可以选择到期后自动转存,并将到期的利息计入本金合并转存。如果1年期的定期利率是3.3%,那么连续自动转存3年后,最初存入的x元定期会得到多少本息金额?

本息合计:x(1 + 3.3%)三次方

int x;
scanf("%d",&x);
double amount = x * (1 + 0.033) * (1 + 0.033) * (1 + 0.033);
printf("%f",amount);

要计算任意年以后的本息金额,就需要做(1+0.033)的n次方计算

交换两个变量

如果已经有:int a=6,b = 5 如何交换a,b两个变量值

答:需要有第三个变量来进行缓冲,如下:

int t = a ,a= b ,b = t ;

复合赋值

5个算术运算符,加 减 乘 除 取余可以和赋值运算符“=”结合起来,形成复合赋值运算符:“+=”“-=”“*=”“/=”“%=”

total += 5;

total = total + 5;

注意两个运算符中间不要有空格

递增递减运算符

1."++""--"是两个很特殊的运算符,它们是单目运算符,这个算子还必须是变量。这两个运算符分别叫做递增和递减运算符,他们的作用就是给这个变量+1或者-1

2.以下都是同一个意思:

count++ count +=1 count = count + 1

3.递增递减可以放在前面(前缀)马上生效,也可以放在后面(后缀)延迟生效

4.可以单独使用,但不要组合进表达式

第三周:3.1判断

计算时间差

输入两个时间,每个时间分别输入小时和分钟的值,然后输出两个时间之间的差,也以几小时几分表示

问题:如果直接分别减,会出现分钟借位的情况:1点40分和2点10分的差?

如果

1.用分别减的方案,然后判断有没有出现借位行不行?

2.借位的表现是,分钟减的结果小于0,找小时借一位,如下:im代指分钟,ih代指小时

if(im < 0){
    im = 60 + im;
    ih--;
}

if(条件成立){

....

}

判断的条件

计算两个值之间的关系,所以叫做关系运算

运算符意义
==相等
!=不相等
大于
>=大于或等于
<小于
<=小于或等于

关系运算的结果

  1. 当两个值的关系符合关系运算符的预期时,关系运算的结果为整数1,否则为整数0
  2. printf("%d\n",5==3);
  3. printf("%d\n",5>3);
  4. printf("%d\n",5<=3);

优先级

  1. 所有的关系运算符的优先级比算数运算的低,但是比赋值运算的高
  2. 7>=3+4 在这里面3+4先运算然后等于7,所以式子是可以运行的
  3. int r = a > 0; 在这里a>0先运算,式子成立,答案为1,所以r = 1

找零计算器

1.找零计算器需要用户做两个操作:输入购买的金额,输入支付吧票面,而找零计算器则根据用户的输入做出相应的动作:计算并打印找零,或告知用户余额不足以购买。

2.从计算机程序的角度看,这就是意味着程序需要读用户的两个输入,然后进行一些计算和判断,最后输出结果

//初始化
int price = 0;
int bill = 0;
//读入金额和票面
printf("请输入金额:");
scanf("%d",&price);
printf("请输入票面:");
scanf("%d",&bill);
//计算找零
printf("应该找您:%d\n",bill - price);

双斜杠//注释

双斜杠//是注释(comment)的意思:插入在程序代码中,用来向读者提供解释信息。它们对于程序的功能没有影响,但是往往能使得程序更容易被人类读者理解

/* */注释

1.延续多行数行的注释,要用多行注释的格式来写。多行注释由一对字符序列“/ ”开始,而以“ /”结束

2.也可以用于一行内的注释

3.int ak = 47/36/,y = 9;

当票面不够的情况

1.当票面不够的情况怎么办?这个时候就需要进行判断用户读入的金额是否超过了票面

2.对计算找零这个步骤进行优化:

if( bill >= price){
   printf("需要找您:%d\n元",bill - price);
}

这个是当票面够的情况才会显示出需要找多少钱,如果钱不够的话则就不会显示出需要找钱了

3.如果当钱不够的情况需要提示用户"你的钱不够"怎么做?

错误示范:

if( bill >= price){
   printf("需要找您:%d/n元",bill - price);
}
printf("你的钱不够\n");

这种情况不管钱够不够都会输出 你的钱不够

else

else = 否则的话

正确的做法是:

if( bill >= price){
   printf("需要找您:%d/n元",bill - price);
}else{
   printf("你的钱不够\n");
}

比较数的大小

比较数的大小的各种方法

int a,b;
printf("请输入两个整数:");
scanf("%d %d", &a, &b); 
​
int max = 0;
if(a>b){
    max = a;
} 
​
printf("大的那个是%d\n", max);

这个代码里面没有解决b>a的问题,当a>b的条件不成立时,程序就结束了,max没有得到值

方案有很多,列举3个

int a,b;
printf("请输入两个整数:");
scanf("%d %d", &a, &b); 
​
int max = 0;
if(a>b){
    max = a;
}
if(b>a){
    max = b;
}
printf("大的那个是%d\n", max);
int a,b;
printf("请输入两个整数:");
scanf("%d %d", &a, &b); 
​
int max = 0;
if(a>b){
    max = a;
}else{
    max = b;
}   
​
printf("大的那个是%d\n", max);
int a,b;
printf("请输入两个整数:");
scanf("%d %d", &a, &b); 
​
int max = b;
if(a>b){
    max = a;
}
​
printf("大的那个是%d\n", max);

if语句

在上面中我们已经充分了解到了if语句的用法跟含义

但其实if也可以不带中括号去执行(不建议),只能执行if接下来的一句内容

例如:

const double RATE = 8.25;
const double STANDARD = 40;
double pay = 0.0;
int hours;
​
printf("请输入工作的小时数:");
scanf("%d",&hours);
printf("\n");
if(hours > STANDARD)
    pay = STANDARD * RATE + (hours - STANDARD) * (RATE * 1.5);
else
    pay = hours * RATE;
printf("应付工资:%f\n",pay);
#include <stdio.h>int main()
{
​
    const int MINOR = 35;
​
    int age = 0;
​
    printf("请输入你的年龄: ");
    scanf("%d", &age);
​
    printf("你的年龄是%d岁。\n", age);
​
    if (age < MINOR) {
        printf("年轻是美好的,");
    }
​
    printf("年龄决定了你的精神世界,好好珍惜吧。\n");
​
    return 0;
}

3.2分支

嵌套的判断

当if的条件满足或者不满足的时候要执行的语句也可以是一条if或者if-else语句,这就是嵌套的if语句

else的匹配

else总是和最近的那个if匹配

缩进

缩进格式不能暗示else的匹配

if( code ==READY )
    if( count < 20 )
        printf("一切正常\n");
else
        printf("继续等待\n");

tips

1.在if或者else后面总是用{}

2.即使只有一条语句的时候

分段函数

if( x < 0 ){
  f = -1;
}else if( x == 0){
  f = 0;
} else {
  f = 2 * x;
}

if语句常见的错误

1.忘了大括号(永远在if和else后面加上大括号,即使当时后面只有一条语句)

2.if后面忘了分号

3.错误使用==和=

4.使人困惑的else

switch-case

1.控制表达式只能是整数型的结果

2.常量可以是常数,也可以是常数计算的表达式

3.根据表达式的结果,寻找匹配的case,并执行case后面的语句,一直到break为止

4.如果所有的case都不匹配,那么就执行default后面的语句;如果没有default,那么就什么都不做

switch(控制表达式){
case常量:
        语句
        ...
case常量:
        语句
        ...
default:
        语句
        ...
}

break

switch语句可以看作是一种基于计算的跳转,计算控制表达式的值后,程序会跳转到相匹配的case(分支标号)处。分支标号只是说明switch内部位置的路标,在执行完分支中的最后一条语句后,如果后面没有break,就会顺序执行到下面的case里去,直到遇到一个break,或者switch结束为止

第四周:循环

4.1循环

三位数逆序的题

#include <stdio.h>
int main(){
    int x;
    int n=0;
    scanf("%d", &x);
    n++;
    x /= 10;
    while(x>0){
        n++;
        x /= 10;
    } 
    printf("%d\n", n);
    return 0;
}

while循环

1.如果我们把while翻译作"当",那么一个while循环的意思就是:当条件满足的时候,不断的重复循环体内的语句

2.循环执行前判断是否继续循环,所以可能一次循环一次也没有执行

3.条件成立是循环继续的条件

验证

1.测试程序常使用边界数据,如有效范围两端的数据、特殊的倍数等

2.个位数,10,0,负数

调试

在程序适当的地方插入printf()来输出变量的内容

do while循环

在进入循环的时候不做检查,而是在执行完一轮循环体的代码之后,再来检查循环的条件是否满足,如果满足则继续下一轮循环,不满足则循环结束

do{
  循环体语句
}while(循环条件);

两种循环

do while循环 和 while循环很像,区别是在循环体执行结束的时候才来判断条件。也就是说,无论如何,循环都会执行一遍,然后再来判断条件。与while循环相同的是,条件满足时执行循环,条件不满足时结束循环

4.2循环计算

小套路

计算之前先保存原始的值,后面可能有用。重新定义一个变量来存放计算后的值

计数循环

#include <stdio.h>int main()
{
    int n = 3;
    
    while ( n>= 0 ) {
        printf("%d ", n);
        n--;
    }
    printf("发射\n");
    
    return 0;
}

1.这个循环需要执行多少次?

2.循环停下来的时候,有没有输出最后的0?

3.循环结束后,count的值是多少?

技巧:如果要模拟运行一个很大次数的循环,可以模拟较少的循环次数,然后做出推断

循环应用(猜数游戏)

需求

让计算机来想一个数,然后让用户来猜,用户每输入一个数,就告诉用户是大了还是小了,直到用户猜到为止,最后还要告诉用户他猜了几次

思路

因为需要不断重复让用户猜,所以需要用到循环

在实际写出程序之前,我们可以先用文字描述程序的思路

核心重点是循环的条件

人们往往会考虑循环终止的条件

随机数

每次召唤rand()就可以得到一个随机数

代码块

#include <stdio.h>
#include <stdlib.h>
#include <time.h>int main(){
    //不加srand(time(0));的话,每次循环的数都一样
    srand(time(0));
    int number = rand()%100+1;
    int count = 0;
    int a = 0;
    printf("我已经想好了一个1到100之间的数。");
    do {
        printf("请猜这个1到100之间数:");
        scanf("%d", &a);
        if ( a > number ) {
            printf("你猜的数大了。");
        } else if ( a < number ) {
            printf("你猜的数小了。");
        }
        count ++;
    } while (a != number);
    printf("太好了,你用了%d次就猜到了答案。\n", count);
    
    return 0;
}

%100

x%n的结果是[0,n-1]的一个整数

算平均数

需求

1.让用户输入一系列的正整数,最后输入-1表示输入结束,然后程序计算出这些数字的平均数,输出输入的数字的个数和平均数

2.变量->算法->流程图->程序

变量

1.一个记录读到的整数的变量

2.平均数要怎么算?

3.只需要每读到一个数,就把它加到一个累加的变量里,到全部数据读完,再拿它去除读到的数的个数就可以了

4.一个变量记录累加的结果,一个变量记录读到的数的个数

算法

1.初始化变量sum和count为0;

2.读入number;

3.如果number不是-1,则将number加入sum,并将count加1,回到2;

4.如果number是-1,则计算和打印出sum/count(注意换成浮点数来计算)

代码块

#include <stdio.h>int main()
{
    int sum = 0;
    int count = 0;
    int number;
    
    scanf("%d", &number);
    while ( number != -1 ) {
        sum += number;
        count ++;
        scanf("%d", &number);
    }
    
    double dsum = sum;
    printf("The average is %f.\n", dsum / count);
    
    return 0;
}

整数的分解

1.一个整数是由1-多位数字组成的,如何分解出整数的各个位上的数字,然后加以计算

2.对一个整数做%10的操作,就得到它的个位数

3.对一个整数做/10的操作,就去掉了它的个位数

4.然后再对2的结果做%10,就得到原来数的十位数

....以此类推

数的逆序

1.输出一个正整数,输出逆序的数

2.结尾的0的处理

第五周:循环控制

阶乘

1.n! = 1×2×3×4×...×n

2.写出一个程序,让用户输入n,然后计算输出n!

3.变量:显然读用户的输入需要一个int的n,然后计算的结果需要用一个变量保存,可以是int的factor,在计算中需要有一个变量不断地从1递增到n,那可以是int的i

int n;
​
scanf("%d",&n);
int fact = 1;
​
int i = 1;
for( i = 1;i <= n; i++){
    fact *= i;
}
​
printf("%d!=%d\n",n,fact);

5.1 for循环(第三种循环)

for循环像是一个计数循环:设定一个计数器,初始化它,然后在计数器到达某值之前,重复执行循环体,而每执行一轮循环,计数器值以一定 步进 进行调整,比如加一或者减一

例如:
for(i = 0;i < 5;i++){
  printf("%d",i);
}
​
​
for(初始动作;条件;每轮的动作){
}
//for中的每一个条件都是可以省略的,for(;条件;) == while(条件)

for = 对于

for(count = 10; count > 0; count-- )

可以解读成:对于一开始的count = 10,当count > 0的时候,重复做循环体,每一轮循环在做完循环体内语句后,使得count--

小套路

做求和的程序时,记录结果的变量应该初始化为0,而做求积的变量时,记录结果的变量应该初始化为1

循环控制变量i只在循环里被使用了,在循环外面它没有任何用处。因此,我们可以把变量i的定义写到for语句里面去

尝试(try)

  1. 1×1还是1,所以程序的循环需不需要从1开始,那么改成多少开始合适呢?这样修改之后,程序对于所有的n都正确吗?这样的改动有价值吗?
  2. 除了可以从1乘到n来计算n!,还可以从n乘到1来计算吧?试试换个方向来计算n。这时候,还需要循环控制变量i吗?

for == while

for( int i = 1;i <= n;i++ ){
    fact *= i;   
}
-------------------------------------------
int i = 1;
while(i <= n){
    fact *=i;
    i++;
}

循环次数

for( i = 0; i < n; i++)

这样循环的次数是n,而循环次数结束以后,i的值是n。循环的控制变量i,是选择从0开始还是从1开始,是判断 i<n还是判断i <=n,对循环次数,循环结束后变量的值都是有影响的

Tips for Loops(小贴士循环)

  1. 如果有固定次数,用for
  2. 如果必须执行一次,用 do while
  3. 其他情况用while

5.2循环控制

素数

只能被1和自身整除的数,不包括1

break VS continue

  1. break:跳出循环
  2. continue:跳过循环这一轮剩下的语句进入下一轮
  3. 都只能对它所在的那层循环生效

嵌套的循环

  1. 意思:循环的里面还是循环
  2. 嵌套循环时的break只会跳出当前所在的循环,如果嵌套了多层循环就会被卡在下一层循环上而无法真正的跳出所有循环

100以内的素数

如何写程序输出100以内的素数?

#include <stdio.h>
​
int main()
{
    int x;
    //我们需要有一个循环,从 1 到 100 
    for(x=2; x<=100; x++){
        int i;
        int isPrime = 1; // x是素数
        for ( i=2; i<x; i++ ) {
            if ( x % i == 0 ) {
                isPrime = 0;
                break;
            }
        }
        //当它是素数的时候就输出出来,不是素数就不要输出任何东西了 
        if ( isPrime == 1 ) {
            printf("%d ", x);
        }
    }
    return 0;
}

凑硬币

如何用1角、2角和5角的硬币凑出10元以下的金额?

#include <stdio.h>
​
int main()
{
    int x;
    int one, two, five;
    int exit = 0;
    scanf("%d", &x);
    for ( one = 1; one < x*10; one++ ) {
        for ( two = 1; two < x*10/2; two++ ) {
            for ( five = 1; five < x*10/5; five++ ) {
                if ( one + two*2 + five*5 == x*10 ) {
                    printf("可以用%d个1角加%d个2角加%d个5角得到%d元\n", 
                           one, two, five, x);
                    exit = 1;
                    break;
                }
            }
            if ( exit == 1 ) break;
        }
        if ( exit == 1 ) break;
    }
    return 0;
}

5.3循环应用

正序分解整数

  1. 输入一个非负整数,正序输出它的每一位数字
  2. 输入:13425
  3. 输出:1 3 4 2 5

分解整数输出(未解决结尾空格问题版本)

int x;
scanf("%d",&x);
​
do{
   int d = x % 10;
    printf("%d",d);
        x /= 10;
}while( x > 0);
printf("\n");
//(未解决结尾空格问题版本)
-----------------------------------------
int x;
scanf("%d",&x);
​
do{
   int d = x % 10;
    printf("%d",d);
        if( x > 9){
           printf(" ");
        }
        x /= 10;
}while( x > 0);
printf("\n");
​
-----------------------------------------------
    (如果有一个mask的话)
int x;
scanf("%d",&x);x = 13425;
int mask = 10000;
do{
   int d = x % 10;
    printf("%d",d);
        if( x > 9){
           printf(" ");
        }
        x %= mask;
        mask /= 10;
}while( mask > 0);
printf("\n");
---------------------------------------------------
 //   (计算x的位数)
x = 12345;
int mask = 10000;
int n = 0;
do{
  x /= 10;
      n++;
}while( x > 0);
printf("n = %d\n",n);

求最大公约数

  1. 输入两个数a和b,输出它们最大的公约数
  2. 输入:12,18
  3. 输出:6
  4. 枚举方法:过于麻烦,耗费较多资源
#include <stdio.h>
​
int main()
{
int a,b;
int min;
scanf("%d %d", &a, &b);
if ( a<b ) {
min = a;
} else {
min = b;
}
int ret = 0;
int i;
for ( i = 1; i < min; i++ ) {
if ( a%i == 0 ) {
if ( b%i == 0 ) {
ret = i;
}
}
}
printf("%d和%d的最大公约数是%d.\n", a, b, ret);
return 0;
}

辗转相除法

  1. 如果b等于0,计算结束,a就是最大的公约数;
  2. 否则,计算a除以b的余数,让a等于b,而b等于那个余数;
  3. 回到第一步
int a,b;
int t;
​
scanf("%d %d",&a,&b);
int origa = a;
int origb = b;
while( b != 0 ){
    t = a%b;
    a = b;
    b = t;
}
printf("%d和%d的最大公约数是%d",orida,origb,a);

第六周:数据类型

编程类型解析

//给定不超过6的正整数A,考虑从A开始的连续4个数字。请输出所有由它们组成的无重复数字的3位数
//输出格式:满足条件的3位数,要求从大到小,每行6个整数,整数间以空格分隔,但行末不能有多余空格
int main()
{
    int a;
    scanf("%d",&a);
    int i,j,k;
    int cnt = 0;
    
    i = a;
    while( i<=a+3){
        j = a;
        while( j<=a+3){
            k = a;
            while( k<=a+3){
                if( i!=j ){
                    if( i!=k ){
                        if( j!=k){
                            cnt++;
                            printf("%d%d%d",i,j,k);
                            if( cnt == 6){
                                printf("\n");
                                cnt = 0;
                            }else{
                                printf(" ");
                            }
                        }
                    }
                }
                k++;
            }
            j++;
        }
        i++;
    }
    return 0;    
}
#include<stdio.h>
int main() {
    int n;
    //scanf("%d", &n);
    n=3;
    int first = 1;
    int i = 1;
    while(i < n){
        first *= 10;
        i++;
    }
    //printf("first=%d\n", first);
    //遍历100-999
    i = first;
    while(i < first*10){
        //需要一个临时的变量去记录 i 
        int t = i;
        //需要一个"和"去记录每一位数的 n次幂 
        int sum = 0;
        do {
            int d = t % 10;
            t /= 10;
            //d^2 = d*d;  d^3 = d*d*d;
            /*
            int p = 1;
            int j = 0;
            while(j < n){
                p *= d;
                j++;
            }
            //或者*/ 
            int p = d;
            int j = 1;
            while(j < n){
                p *= d;
                j++;
            }
            sum += p;
        } while(t > 0);
        if (sum == i) {
            printf("%d\n", i);
        }
        i++;
    } 
    return 0;
} 
#include<stdio.h>
int main() {
    int n;
    //scanf("%d", &n);
    n = 9;
    int i,j;
    i = 1;
    while( i <= n) {
        j = 1;
        while(j <= i){
            printf("%d*%d=%d", j,i,i*j);
            //i,j会输出9*1= 9*2= 
            //我们想要输出1*9= 2*9= 
            //如果i*j小于10,比如1*1=1 小于 10 
            if( i*j < 10){
                //输出三个空格 
                printf("   ");
            } else {
                //输出两个空格 
                printf("  ");
            } 
            j++;
        }
        //还需要在每一个行加个回车
        printf("\n"); 
        i++;
    }
    
    return 0;
} 
#include<stdio.h>
int main() {
    int m,n;
    int i;
    //个数 
    int cnt = 0;
    int sum = 0;
    
    scanf("%d %d", &m, &n); 
    //如果m是 1,单独做一个特殊的判断 
    if(m == 1)
        m=2;
        
    for(i=m; i<=n; i++){
        int isPrime = 1;
        int k;
        //需要有另外一个循环去证明 i是不是isPrime
        for(k=2; k<i-1; k++){
            if(i%k == 0){
                isPrime = 0;
                break;
            }
        }
        // 判断 i 是否素数
        if(isPrime) {
            cnt++;
            sum+=i;
        } 
    }
    printf("%d %d\n", cnt, sum);
    
    return 0;
} 
#include <stdio.h>
int main(){
    //随机数,猜测的最大次数 
    int number,n;
    //用户每次猜测的数字 
    int inp;
    //  finished为 1 则表示猜中,0 没猜中 
    int finished = 0;
    // 记录猜测次数
    int cnt = 0;
    scanf("%d %d",&number,&n);
    while(scanf("%d",&inp)){
        cnt++;
        //如果这个数小于0 或 猜测次数大于最大猜测次数 ,则输入不合法
        if(inp < 0 || cnt > n){
            printf("Game Over\n");
            break;
        }
        // 输入猜测数太大
        if(inp > number){
            printf("Too big\n");
        // 输入猜测数太小
        }else if(inp < number){
            printf("Too small\n");
        // 输入猜测数与随机数相等
        }else{
            // 1次成功
            if(cnt == 1){
                printf("Bingo!\n");
             // 3次以内成功
            }else if(cnt <= 3){
                printf("Lucky You!\n");
            // 3次以上成功
            }else{
                printf("Good Guess!\n");
            }
            finished = 1;
            if(finished == 1) break;
        }
    }
    return 0;
}
#include<stdio.h>
int main() {
    int n;
    //分子,分母 
    double dividend,divisor;
    double sum = 0.0;
    int i;
    double t;
    
    scanf("%d", &n);
    // n = 2000;
    dividend = 2;
    divisor = 1;
    for(i = 1; i <= n; i++){
        sum += dividend/divisor;
        t = dividend;
        dividend = dividend + divisor;
        divisor = t;
    }
    printf("%.2f\n", sum);
    
    return 0;
} 
#include<stdio.h>
int main() {
    //分子,分母 
    int dividend,divisor;
    scanf("%d/%d", &dividend, &divisor);
    
    int a = dividend;
    int b = divisor;
    int t;
    //辗转相除法算出最大公约数 
    while(b > 0){
        t = a % b;
        a = b;
        b = t;
    }
    printf("%d/%d\n", dividend/a, divisor/a);
    
    return 0;
} 
#include<stdio.h>
int main() { 
    int x;
    scanf("%d", &x);
    
    if(x < 0){
        printf("fu ");
        x = -x;
    }
    int mask = 1;
    int t = x;
    //辗转相除法算出最大公约数 
    while(t > 9){
        t /= 10;
        mask *= 10;
    }
​
    do{
        int d = x / mask;
        switch(d){
            case 0: printf("ling"); break;
            case 1: printf("yi"); break;
            case 2: printf("er"); break;
            case 3: printf("san"); break;
            case 4: printf("si"); break;
            case 5: printf("wu"); break;
            case 6: printf("liu"); break;
            case 7: printf("qi"); break;
            case 8: printf("ba"); break;
            case 9: printf("jiu"); break;
        }
        // 最后不要有行末的空格 
        if (mask > 9) printf(" ");
        x %= mask; 
        mask /= 10;
    } while(mask > 0);
    printf("\n");
    
    return 0;
} 
#include<stdio.h>
int main() { 
    int a,n;
    scanf("%d %d", &a, &n);
    
    int sum = 0;
    int i;
    //每一轮的数字 
    int t = 0;
    // 0*10+2 2*10+2 (2*10+2)*10+2
    for(i=0; i<n; i++){
        t = t*10+a;
        sum += t;
    }
    printf("%d\n", sum);
    
    return 0;
} 

C语言的类型

整数

  1. char:一字节(8比特)-128至127
  2. short:2字节 -32768至32767
  3. int:取决于编译器(CPU),通常的意义都是"1个字"
  4. long:取决于编译器(CPU),通常的意义是"1个字"(4或8字节)
  5. long long:8字节

*整数的内部表达

  1. 计算机内部一切都是二进制
  2. 18 -> 00010010
  3. 0 -> 00000000
  4. -18 ->?

*如何表示负数

  1. 十进制用"-"来表示负数,在做计算的时候
  2. 加减是做相反的运算
  3. 乘除时当作正数,计算完毕后对结果的符号取反

*二进制负数

  1. 1个字节可以表达的数:0000 0000 - 1111 1111(0-255)

三种方案:

  1. 仿照十进制,有一个特殊的标志表示负数
  2. 取中间的数为0,如100 0000表示0,比它小的是负数,比它大的是正数
  3. 补码(实际上使用的,方案1和2都有缺陷)

*补码

补码的意义就是拿补码和原码可以加出一个溢出的0

例子:因为0 - 1->-1,所以-1 = (1)0000 0000 - 0000 0001 ->1111 1111

1111 1111被当作纯二进制看待时,是255,被当作补码看待时是-1

同理,对于-a,其补码就是0-a,实际是2的n次方-a,n是这种类型的位数

数的范围

  1. 对于一个字节(8位),可以表达的是:0000 0000 ->1111 1111
  2. 其中0000 0000->0 、1111 1111至1000 0000 ->-1至-128 、 0000 0001至0111 1111->1至127

浮点数

float、double、long double

逻辑

bool

指针

自定义类型

类型有何不同

  1. 类型名称:int、long、double
  2. 输入输出时的格式化:%d、%ld、%lf
  3. 所表达的数的范围:char<short<int<float<double
  4. 内存中所占据的大小:1个字节到16个字节
  5. 内存中的表达形式:二进制数(补码)、编码

sizeof

  1. 是一个运算符,给出某个类型或者变量在内存中所占据的字节数

例如:sizeof(int)、size(i)

  1. 是静态运算符,它的结果在编译时刻就决定了
  2. 不要在sizeof的括号里做运算,这些运算不会进行的

unsigned

  1. 在整形类型前加上unsigned使得它们变成无符号的整数
  2. 内部的二进制表达没变,变的是如何看待它们。如何输出
  3. 1111 1111对于char来说是-1,对于unsigned char来说是255
  4. 如果一个字面量常数想要表达自己是unsigned,可以在后面加上U或者u,255U
  5. 用L或者l表示long( long)
  6. unsigned初衷并非扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位

整数越界

整数是以纯二进制方式进行计算的,所以:

  1. 1111 1111+1 -> 1 0000 0000 ->0
  2. 0111 1111+1 -> 1 0000 0000 ->-128
  3. 1000 0000-1 -> 0111 1111 ->127

整数的输入输出

只有两种形式:int或者long long

  1. %d:int
  2. %u:unsigned
  3. %ld:long long
  4. %lu:unsigned long long

8进制和16进制

  1. 一个以0开始的数字字面量是8进制
  2. 一个以0x开始的数字字面量是16进制
  3. %o用于8进制,%x用于16进制
  4. 8进制跟16进制只是如何把数字表达为字符串,与内部如何表达数字无关
  5. 16进制很适合表达二进制数据,因为4位二进制正好是一个16进制位
  6. 8进制的一位数字正好表达3位二进制
  7. 因为早期计算机的字长是12的倍数,而非8

选择整数类型

  1. 为什么整数要有那么多种?为了准确表达内存,做底层程序的需要
  2. 没有特殊需要就选择int
  3. 现在CPU的字长普遍是32位或者64位,一次内存读写就是一个int,一次计算也是一个int,选择更短的类型不会更快,甚至可能更慢
  4. 现代的编译器一般会设计内存对齐,所以更短的类型实际在内存中有可能也占据一个int的大小(虽然sizeof告诉你更小)
  5. unsigned与否只是输出的不同,内部计算是一样的

浮点数

浮点类型

类型字长范围有效数字
float32正负(1.20×100,正负inf,NaN)7
double64正负(2.20×100,正负inf,NaN)15

浮点的输入输出

类型scanfprintf
float%f%f,%e
double%lf%f,%e

科学计数法

  1. 可选的+或者-符号
  2. 可以用e或者E
  3. 整个词不能有空格
  4. 小数点也是可选的
  5. 符号可以是正负也可以省略(表示+)
  6. 例如:-5.67E+16

输出精度

在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做四舍五入的

  1. printf("%.3f\n",-0.0049);
  2. printf("%.30f\n",-0.0049);
  3. printf("%.3f\n",-0.00 49);

超出范围的浮点数

  1. printf输出inf表示超过范围的浮点数:+无穷
  2. printf输出nan表示不存在的浮点数

浮点运算的精度

  1. 带小数点的字面量是double而非float
  2. float需要用f或者F后缀来表明身份

浮点数的内部表达

  1. 浮点数在计算时是由专用的硬件部件实现的
  2. 计算double和float所用的部件是一样的

选择浮点类型

  1. 如果没有特殊需要,只使用double
  2. 现代CPU能直接对double做硬件运算,性能不会比float差,在64位的机器上,数据存储的速度也不比float慢

字符

字符类型

char是一种整数,也是一种特殊的类型:字符。这是因为:

  1. 用单引号表示的字符字面量:'a','1'
  2. "也是一个字符
  3. printf和scanf里用%c来输入输出字符

字符的输入输出

  1. 如何输入'1'这个字符给char c?

scanf("%c",&c);->1

scanf("%d",&i);c=i ;->49

  1. '1'的ASCII编码是49,所以当c==49时,它代表'1'
  2. 这是一个49的各自表述

混合输入

有何不同

scanf("%d %c",&i,&c);

scanf("%d%c",&i,&c);

字符计算

  1. 一个字符加一个数字得到ASCII码表中那个数之后的字符
  2. 两个字符的减,得到它们在表中的距离

大小写转化

  1. 字母在ASCII表中是顺序排列的
  2. 大写字母和小写字母是分开排列的,并不在一起
  3. ‘a’-‘A’可以得到两段之间的距离,于是a+‘a’-‘A’可以把一个大写字母变成小写字母,反之把这个运算中的‘a’与‘A’互相调换即可把一个小写字母变成大写字母

逃逸字符

用来表达无法印出来的控制字符或特殊字,在那些具有特殊符号的前面加上\即可将其当作普通字符使用

字符意义
\b回退一格
\t到下一个表格位
\n换行
\r回车
"双引号
'单引号
\反斜杠本身

制表位

  1. 每行的固定位置
  2. 一个\t使得输出从下一个制表位开始
  3. 用\t能使上下两行对齐

逻辑类型

bool

  1. 在宏定义中加上#include<stdbool.h>
  2. 之后就可以使用bool和true跟false

bool的运算

  1. bool实际上还是以int的手段实现的,所以可以当作int来计算
  2. 也只能当作int来输入输出

类型转换

自动类型转换

  1. 当运算符的两边出现不一致的类型时,会自动转换成较大的类型
  2. 大的意思是能表达的数的范围更大
  3. char->short->int->long->long long
  4. int->float->double
  5. 对于printf,任何小于int的类型会被转化成int;float会被转换成double
  6. 但是scanf不会,要输入short,需要%hd

强制类型转换

  1. 要把一个量强制转换成另一个类型(通常是较小的类型),需要:(类型)值
  2. 比如(int)10.2 (short)32
  3. 需要注意这个时候的安全性,小的变量不总能表达大的量
  4. 这个只是从那个变量计算出了一个新的类型的值,它并不改变那个变量,无论值还是类型都不改变
  5. 强制类型转换的优先级高于四则运算

有些运算

逻辑运算

  1. 逻辑运算是对逻辑量进行的运算,结果只有0或者1
  2. 逻辑量是关系运算或逻辑运算的结果
运算符描述示例结果
逻辑非!a如果a是true结果就是false如果a是fasle结果就是true
&&逻辑与a&&b如果a和b都是是true结果就是true否则就是false
逻辑或ab如果a和b有一个是true结果就是true两个都是false,结果才是false

try(尝试)

如何判断一个字符c是否是大写字母?

c >= ‘A’&&c<=‘Z’

逻辑优先级

!>&&>||

总体优先级排名

优先级运算符结合性
1()从左到右
2!+- ++ --从右到左(单目的+和-)
3/ %从左到右
4+ -从左到右
5< <= > >=从左到右
6== !=从左到右
7&&从左到右
8从左到右
9= += -= *= /= %=从右到左

短路

  1. 逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算了
  2. 对于&&,左边是false时就不会做右边的运算了
  3. 对于||,左边是true时就不会做右边的运算了
  4. 不要把赋值,包括复合赋值组合进表达式

条件运算符

  1. count = (count>20)?count-10:count+10;
  2. 当count>20是真的时候执行前者,是假执行后者
  3. 优先级:条件运算符的优先级高于赋值运算符,但是低于其他运算符
  4. 不要嵌套条件表达式(很容易出问题)

逗号运算符

  1. 逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。逗号的优先级是所有运算符中最低的,所以它两边的表达式会先计算;逗号的组合关系是自左像右,所以左边的表达式会先计算,而右边的表达式的值就留下来作为逗号运算的结果
  2. 比如在for中使用
  3. for(i = 1,j=8;i<j;i++,j--)

第七周:函数

7.1函数的定义与使用

#include<stdio.h>
int main() { 
    int m,n;
    int sum = 0;
    int cnt = 0;
    int i;
    
    scanf("%d %d",&m, &n);
    // m=10,n=31;
    if(m == 1) m=2;
    for(i=m; i<=n; i++){
        int isPrime = 1;
        int k;
        for(k=2; k<i-1; k++){
            if(i%k == 0){
                isPrime = 0;
                break;
            }
        }
        if(isPrime){
            sum += i;
            cnt++;
        }
    }
    printf("%d %d\n", cnt, sum); 
​
    return 0;
} 
​
// 把这段代码取出来
int isPrime = 1;
        int k;
        for(k=2; k<i-1; k++){
            if(i%k == 0){
                isPrime = 0;
                break;
            }
        }
#include<stdio.h>
//求1到10、20到30和35到45的三个和
void sum(int begin,int end)
{
    int i;
    int sum = 0;
    for( i = begin;i <= end; i++){
        sum += i;
    }
    printf("%d到%d的和是%d\n",begin,end,sum);
}
​
int main()
{
    sum(1,10);
    sum(20,30);
    sum(35,40);
    
    return 0;
}

什么是函数?

  1. 函数是一块代码,接收零个或者多个参数,做一件事情,并返回零个或一个值
  2. 可以先想像成数学中的函数:y=f(x)

函数定义

img

调用函数

  1. 函数名(参数值);
  2. ()起到了表示函数调用的重要性,即使没有参数也需要()
  3. 如果有参数,则需要给出正确的数量和顺序
  4. 这些值会被按顺序依次用来初始化函数中的参数

函数返回

函数知道每一次是在哪里调用它,会返回到正确的地方

从函数中返回值

  1. return停止函数的执行,并送回一个值
  2. return;
  3. return 表达式;
  4. 一个函数里可以出现多个return语句
  5. 例如:int c, c = max(10,12);=>可以赋值给变量或者再传递给函数甚至可以丢弃,有的时候要的是副作用

没有返回值的函数

  1. void 函数名(参数表)
  2. 不能使用带值的return
  3. 可以没有return
  4. 调用的时候不能做返回值的赋值
  5. 如果函数有返回值则必须使用带值的return

7.2函数的参数和变量

函数原型

函数的先后关系

#include<stdio.h>
void sum(int begin,int end)
{
    int i;
    int sum = 0;
    for( i = begin; i <= end; i++){
        sum += i;
    }
    printf("%d到%d的和是%d\n",begin,end)
}
​
int main()
{
    sum(1,10);
    sum(20,30);
    sum(35,45);
    
    return 0;
}
    
  1. 像这样把sum()写在上面,是因为:c的编译器自上而下顺序分析你的代码
  2. 在看到sum(1,10)的时候,它需要知道sum()的样子
  3. 也就是sum()要几个参数,每个参数的类型如何,返回什么类型。这样它才能检查你对sum()的调用是否正确

此函数原型

#include<stdio.h>
double max(double a,double b)   =>  这是函数原型
      
    int main()
{
int a,b,c;
    a = 5;
    b = 6;
    c = max(10,12);       =>这中间一段是根据原型判断
    printf("%d\n",c);
    max(12,13);
    
    return 0;
}
​
double max(double a, double b)  =>这是实际的函数头
  1. 函数头:以分号“;”结尾,就构成了函数的原型
  2. 函数原型的目的是告诉编译器这个函数长什么样子:名称、参数(数量及类型)、返回类型
  3. 旧标准习惯把函数原型写在调用它的函数里面
  4. 现在一般写在调用它的函数前面
  5. 原型里可以不写参数的名字,但是一般仍然写上

参数传递

调用函数

  1. 如果函数有参数,调用函数的时候必须传递给它数量、类型正确的值
  2. 可以传递给函数的值是表达式的结果,这包括:字面量、变量、函数的返回值、计算的结果
int a,b,c;
a = 5;
b = 6;
c = max(10,12);
c = max(a,b);
c = max(c,23);
c = max(max(23,45),a);
c = max(23+45,b);

类型不匹配

  1. 调用函数时给的值与参数的类型不匹配是C语言传统上最大的漏洞
  2. 编译器总是悄悄替你把类型转换好,但是这很可能不是你所期望的
  3. 后续的语言,C++/java在这方面很严格

C语言在调用函数时,永远只能传值给函数

#include<stdio.h>
void swap(int a,int b);  //这是参数int main()
{
    int a = 5;
    int b = 6;
    
    swap(a,b);   //这是值
    
    printf("a = %d b = %d\n",a,b);
    
    return 0;
}
​
void swap(int a,int b)   //参数
{
    int t = a;
    a = b;
    b = t;
}

传值

  1. 每个函数有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系
  2. 过去,对于函数参数表中的参数,叫做"形式参数",调用函数时给的值叫做"实际参数"(实参与形参)
  3. 由于容易让初学者误会实际参数就是实际在函数中进行计算的参数,误会调用函数的时候把变量而不是值传进去,所以我们不建议继续使用这种古老的方式来称呼它
  4. 我们认为,它们是参数和值的关系

本地变量

  1. 函数的每次运行,就会产生一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作本地变量
  2. 定义在函数内部的变量就是本地变量
  3. 参数也是本地变量

变量的生存期和作用域

  1. 生存期:什么时候这个变量开始出现了,到什么时候它消亡了
  2. 作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)
  3. 对于本地变量,这两个问题的答案是统一的:大括号内——块

本地变量的规则

1.本地变量是定义在块内的

  1. 它可以是定义在函数的块内
  2. 也可以定义在语句的块内
  3. 甚至可以随便拉一对大括号来定义变量

2.程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了

3.块外面定义的变量在里面仍然有效

4.块里面定义了和外面同名的变量则覆盖了外面的变量(块内的变量优先度更高)

5.不能在一个块内定义同名的变量

6.本地变量不会被默认初始化

7.参数在进入函数的时候被初始化了

其他细节

没有参数时

  1. void f(void)还是void f();
  2. 在传统C中,它表示f函数的参数表示未知,并不表示没有参数

逗号运算符?

  1. 调用函数时的逗号跟逗号运算符字母区分?
  2. 如下 => 调用函数时的圆括号内的逗号是标点符号,不是运算符
  3. 逗号:f(a,b) 逗号运算符:f((a,b))

函数里的函数?

C语言不允许函数嵌套定义

这是什么?

  1. int i,j,sum(int a,int b); //可以这样写但是不建议
  2. return(i); //加不加括号其实都无所谓,值都一样的,但加上括号会让人误以为这是一个函数,不要这样写没有好处

关于main

  1. int main()也是一个函数
  2. 要不要写成int main(void)? //void加不加都一样,但如果上面参数都不打算加,不妨把void写下去
  3. return的0有人看吗? //是可以看的起作用的,返回0表示正常的运行结束了,返回任何非0的值都是错误的

windows:if error level 1...

Unix Bash:echo $?

Csh:echo $status

第八周:数组

8.1-1初试数组

如何写入一个程序计算用户输入的数字的平均数?(不需要记录输入的每一个数)

#include<stdio.h>
int main(){
    int x;
    double sum = 0;
    int cnt = 0;
    scanf("%d",&x);
    while( x != -1){
        sum += x;
        cnt++;
        scanf("%d",&x);
    }
    if( cnt > 0 ){
        printf("%f\n",sum/cnt);
    }
    
    return 0;
}
  1. 如何写一个程序计算用户输入的数字的平均数,并输出所有大于平均数的数?
  2. 思路:必须先记录每一个输入的数字,计算平均数之后,再检查记录下来的每一个数字,与平均数比较,决定是否输出
  3. 该使用数组啦
#include<stdio.h>
int main(){
    int x;
    double sum = 0;
    int cnt = 0;
    int number[100];  //=>这里定义了数组,但具有隐患,当存放的内容超过了100个的时候就会出错,所有后续可以进行动态调整
    scanf("%d",&x);
    while( x != -1){
        number[cnt] = x;  //对数组中的元素赋值
        sum += x;
        cnt++;
        scanf("%d",&x);
    }
    if( cnt > 0){
        int i;
        double average = sum/cnt;
        for( i = 0;i < cnt; i++){
            if( number[i] > average){
                printf("%d",number[i]);  //遍历数组跟使用数组里面的元素
            }
        }
    }
    
    return 0;
}

8.1-2数组的定义和使用

定义数组

1.<类型>变量名称[元素数量];

  1. int grades[100];
  2. double weight[20];

2.元素数量必须是整数

3.C99之前:元素数量必须是编译时刻确定的字面量(了解下就行)

数组

是一种容器(放东西的东西),特点是:

  1. 其中所有的元素具有相同的数据类型;
  2. 一旦创建,不能改变大小
  3. *(数组中的元素在内存中是连续依次排列的)

int a[10];

  1. 一个int的数组
  2. 10个单元:a[0],a[1],a[2]....a[9];
  3. 每一个单元就是一个int类型的变量
  4. 可以出现在赋值的左边或右边:a[2] = a[1]+6;
  5. *在赋值左边的值叫左值(右边就叫右值咯)

数组的单元

  1. 数组的每个单元就是数组类型的一个变量
  2. 使用数组时放在[]中的数字叫做下标或索引,下标从0开始计数:例如1.grades[0]2.grades[99],average[5]

有效的下标范围

  1. 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写
  2. 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃segmentation fault
  3. 也可能运气好,没造成严重的后果
  4. 所有这是程序员的责任来保证程序只使用有效的下标值:[0,数组的大小-1]

计算平均数

int x;
double sum = 0;
int cnt;
printf("请输入数字的数量:");
scanf("%d",&cnt);
if( cnt > 0){
    int number[cnt];
    scanf("%d",&x);
    while( x != -1){
        number[cnt] = x;
        sum += x;
        cnt++;
        scanf("%d",&x);
    }
}

长度为0的数组?

int a[0];可以存在,但是没有卵用

8.1-3数组的例子:投票统计

写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-1表示结束

#include<stdio.h>
int main(){
    const int number = 10;  //数组的大小
    int x;
    int count[number];  //定义数组
    int i;
    
    for( i=0; i<number; i++){    //初始化数组
        count[i] = 0;
    }
    scanf("%d",&x);
    while( x != -1){
        if( x >= 0 && x <= 9){
            count[x]++;  //数组参与运算
        }
        scanf("%d",&x);
    }
    for( i = 0;i < number; i++){
        printf("%d:%d\n",i,count[i]);   //遍历数组输出
    }
    
    return 0;
}

8.2-1数组的运算

在一组给定的数据中,如何找出某个数据是否存在?

#include<stdio.h>
/**
找出key在数组a中的位置
@param key 要寻找的数字
@param a 要寻找的数组
@param length 数组a的长度
@return 如果找到,返回其在a中的位置;如果找不到则返回-1
*/ 
int search(int key,int a[], int length);
​
int main(void)
{
    int a[] = {2,4,6,7,1,3,5,9,11,13,23,14,32};
    int x;
    int loc;
    printf("请输入一个数字:");
    scanf("%d",&x);
    loc = search(x,a,sizeof(a)/sizeof(a[0]));
        if( loc != -1){
            printf("%d在第%d个位置上\n",x,loc);
        }else{
            printf("%d不存在\n",x);
        }
    return 0;
}
​
int search(int key, int a[], int length)
{
    int ret = -1;
    int i;
    for( i = 0; i <= length; i++){
        if( a[i] == key){
            ret = i;
            break;
        }
    }
    return ret;
}

数组的集成初始化

int a[] = {2,4,6,7,1,3,5,9,11,13,23,14,32};

  1. 直接用大括号给出数组的所有元素的初始值
  2. 不需要给出数组的大小,让编译器替你数

int b[20] = {2};

  1. 如果给出了数组的大小,但是后面的初始值数量不足,则其后的元素被初始化为0

集成初始化时的定位

int a[10] = {
    [0] = 2,[2] = 3,6,
};
  1. 用[n]在初始化数据中给出定位
  2. 没有定位的数据接在前面的位置后面
  3. 其他位置的值补零
  4. 也可以不给出数组大小,让编译器算
  5. 特别适合初始数据稀疏的数组

数组的大小

  1. sizeof给出整个数组所占据的内容的大小,单位时字节

得到数组的个数: sizeof(a)/sizeof(a[0])

  1. sizeof(a[0])给出数组中单个元素的大小,于是相除就得到了数组的单元个数
  2. 这样的代码,一旦修改数组中初始的数据,不需要修改遍历的代码

数组的赋值

  1. 数组变量本身不能被赋值
  2. 要把一个数组的所有元素交给另一个数组,必须采用遍历
for( i = 0; i<length;i++){
    b[i] = a[i];
}

遍历数组

  1. 通常都是使用for循环,让循环变量i从0到<数组的长度,这样循环体内最大的i正好是数组最大的有效下标
  2. 常见错误:循环结束条件是 <= 数组长度,或; 离开循环后,继续使用i的值来做数组元素的下标(错误的)

数组作为函数参数时:

  1. 不能在[]中给出数组的大小
  2. 不能再利用sizeof来计算数组的元素个数
  3. 往往必须再用另一个参数来传入数组的大小

8.2-2数组的例子:素数

判断素数

int is Prime(int x);
​
int main(void)
{
    int x;
    scanf("%d",&x);
    if( isPrime(x)){
        printf("%d是素数\n",x);
    }else{
        printf("%d不是素数\n",x);
    }
    return 0;
}

从2到x-1测试是否可以整除

int isPrime(int x)
{
    int ret = 1;
    int i;
    if( x == 1) ret = 0;
    for( i = 2;i < x; i++){
        if( x % i == 0 ){
            ret = 0;
            break;
        }
    }
    return ret;
}
​
//对于n要循环n-1遍,当n很大的时候就是n遍
int isPrime(int x)
{
    int ret = 1;
    int i;
    if( x == 1 || (x%2 == 0 && x != 2)) ret = 0;
    for( i = 3;i < x; i+=2){
        if( x % i == 0 ){
            ret = 0;
            break;
        }
    }
    return ret;
}
//如果x是偶数,立刻结束。否则要循环(n-3)/2+1遍。当n很大的时候就是n/2遍
int isPrime(int x)
{
    int ret = 1;
    int i;
    if( x == 1 || (x%2 == 0 && x != 2)) ret = 0;
    for( i = 3;i < sqrt(x); i+=2){
        if( x % i == 0 ){
            ret = 0;
            break;
        }
    }
    return ret;
}
//只需要循环sqrt(x)遍,sqrt(x)是x的平方根的意思

判断是否可以被已知的且<x的素数整除

#include<stdio.h>
#include<math.h>
int isPrime(int x, int knownPrimes[],int numberOfKnownPrimes);
​
int main(void){
    const int number = 10;
    int prime[number] = {2};
    int count = 1;
    int i = 3;
    // 为了输出好看,添加测试语句
    {
        int i;
        printf("\t\t");
        for(i=0; i<10; i++){
            printf("%d\t", i);
        }
        printf("\n");
    } 
    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++;
    }
    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 knownPrimes[],int numberOfKnownPrimes){
    int ret = 1;
    int i;
    for( i=0;i<numberOfKnownPrimes; i++){
        if(x%knownPrimes[i] == 0){
            ret = 0;
            break;
        }
    }
    return ret;
}

构造素数表

欲构造n以内的素数表

  1. 令x为2
  2. 将2x,3x,4x直至ax<n的数标记为非素数
  3. 令x为下一个没有被标记为非素数的数,重复2;直到所有的数都已经尝试完毕

欲构造n以内(不含)的素数表

  1. 开辟prime[n],初始化其所有元素为1,prime[x]为1表示x是素数
  2. 令x = 2;
  3. 如果x是素数,则对于(i = 2;xi < n; i++)令prime[ix]=0
  4. 令x++,如果x<n,重复3,否则结束
#include<stdio.h>
int main(void){
const int maxNumber = 25;
int isPrime[maxNumber];
int i;
int x;
for( i = 0;i < maxNumber;i++){
    isPrime[i] = 1;
}
for( x = 2;x < maxNumber;x++){
    if( isPrime[x] ){
        for(i = 2; i*x<maxNumber; i++){
            isPrime[i*x] = 0;
        }
    }
}
for( i = 2;i < maxNumber; i++){
    if( isPrime[i]){
        printf("%d\t",i);
    }
}
printf("\n");
    return 0;
}

8.2-3二维数组

二维数组

  1. int a3;
  2. 通常理解为a是一个3行5列的矩阵
a0a0a0a0a0
a1a1a1a1a1
a2a2a2a2a2

二维数组的遍历

for( i = 0;i < 3;i++){
    for( j = 0;j < 5; i++){
        a[i][j] = i*j;
    }
}
  1. ai是一个int
  2. 表示第i行第j列上的单元
  3. a[i,j]是什么? //是一个表达式,不是正确的表达二维数组的方式

二维数组的初始化

int a[][5] = {
    {0,1,2,3,4},
    {2,3,4,5,6},
};
  1. 列数是必须给出的,行数可以由编译器来数
  2. 每隔一个{},逗号分隔
  3. 最后一组逗号可以存在,以前的传统
  4. 如果数组有部分省略没写,编译器会自动补零(表示补零)
  5. 也可以使用定位

tic-tac-toe游戏

  1. 读入一个3×3的矩阵,矩阵中的数字为1表示该位置有一个X,为0表示为O
  2. 程序判断这个矩阵中是否有获胜的一方,输出表示获胜一方的字符X或者O,或输出无人获胜

读入矩阵

const int size = 3;
int board[size][size];
int i,j;
int numOfX;
int numOfO;
int result = -1;   //-1:没人赢,1:X赢了,0:O赢了
​
//读入矩阵
for( i = 0;i < size;i++){
    for( j = 0;j < size; j++){
        scanf("%d",&board[i][j]);
    }
}

检查行

//检查行
for( i = 0; i < size && result == -1; i++){
    numOfO = numOfX = 0;
    for( j = 0;j < size; j++){
        if( board[i][j] == 1 ){
            numOfX ++;
        }else{
            numOfO ++;
        }
    }
    if( numOfO == size){
        result = 0;
    }else if(numOfX == size){
        result = 1;
    }
}

检查列

if( result == -1){
    for( j = 0; j < size && result == -1; j++){
        numOfO = numOfX = 0;
        for( i = 0;i < size;i++){
            if( board[i][j] == 1){
                numOfX ++;
            }else{
                numOfO ++;
            }
        }
        if( numOfO == size){
            result == 0;
        }else if( numOfX == size){
            reshult == 1;
        }
    }
}

检查对角线

numOfO = numOfX = 0;
for( i = 0; i < size; i++){
    if( borad[i][size-i-1] == 1){
        numOfX ++;
    }else{
        numOfO ++;
    }
}
​
​
if( numOfO == size){
    result = 0;
}else if(numOfX == size){
    result == 1;
}