1.1.1 计算机和编程语言

计算机如何处理问题

计算机语言

  • 程序是用特殊的编程语言写出来用计算机来表达如何解决问题的
  • 不是用编程语言来和计算机交谈,而是描述要求它如何做事情的过程或方法

算法

  • 我们要让计算机做计算,就需要像这样找出计算的步骤,然后再用编程语言写出来
  • 计算机做的所有的事情都叫做计算
  • 计算的步骤就是算法

1.1.2 计算机的思维方式

计算机的思维

例如:
枚举
二分法 等等

程序的执行

  • 解释:借助一个程序,那个程序能试图理解你的程序,然后按照你的要求执行
  • 编译:借助一个程序,就像一个翻译,把你的程序翻译成计算机真正能懂的语言——机器语言——写的程序,然后,这个机器语言写的程序就能直接执行了

解释语言VS编译语言

  • 语言本无解释/编译之分
  • 常用的执行方法而已
  • 解释型语言有特殊的计算能力
  • 编译型语言有确定的运算性能

1.2.1 为什么是C

其他语言?

  • 现代的编程语言在语法上的差异很小
  • 几乎都是C-like语言
  • 语言的能力/通用领域主要是有库和传统所决定的

1.2.2 简单历史

  • C语言是从B语言发展而来的 ,B语言是从BCPL发展而来的,BCPL是从FORTRAN发展而来的
  • BCPL和B都支持指针间接方式,所以C也支持
  • C语言还受到了PL/I的影响,还和PDP-II的机器语言有很大的关系
  • 1973年3月,第三版的Unix上出现了C语言的编译器
  • 1973年11月,第四版的Unix(System Four)发布了,这个版本是完全用C语言重新写的

1.2.3 编程软件

C语言用在哪

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

C是一种工业语言

  • 开发效率 >> 学习过程
  • 开发效率 >> 开发乐趣
  • 日常应用很少直接用C语言编写
  • 学习C的过程主要是练习代码 而非真实软件

编译 =》运行

  • C需要被编译才能运行,所以你需要
    • 编辑器
    • 编译器
    • 或者,IDE(集成开发环境)

1.3.1 第一个C程序

Hello World

#include <stdio.h>
int main() 
{
	printf("Hello World!\n");
	return 0;
}

1.3.2 详解第一个程序

程序框架

#include <stdio.h>
int main() 
{

	return 0;
}
  • 本课程中所有的程序都需要这一段代码
  • 知道学函数之前,我们的代码都只是在这个框架中间

输出

  • printf(“Hello World!\n”);
  • ""里面的内容叫做“字符串”,printf会把其中的内容原封不动地输出
  • \n表示需要在输出的结果后面换一行

1.3.3 做点计算

做计算

  • printf(“%d\n”,23+43);
#include <stdio.h>
int main() 
{
	printf("%d",23+43);
	
	return 0;
}
  • %d说明后面有一个整数要输出在这个位置上
  • printf(“23+43=%d\n”,23+43);
#include <stdio.h>
int main() 
{
	printf("23+43=%d",23+43);
	
	return 0;
}

四则运算

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

2.1.1 变量定义

算找零

printf(“100-23=%d\n”,100-23);

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

需要:

  1. 有地方放输入的数字;
  2. 有办法输入数字;
  3. 输入的数字能参与计算。

change

#include <stdio.h>
int main(){
	int price = 0;
	printf("请输入金额(元):");
	scanf("%d",&price);
	int change = 100 - price;
	printf("找给你%d元。\n",change);
	return 0;
}

如何输入

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

变量

  • int price = 0;
  • 这一行,定义了一个变量。变量的名字是price,类型是int,初始值是0.
  • 变量是一个保存数据的地方,当我们需要在程序里保存数据时,比如上面的例子中要记录用户输入的价格,就需要一个变量来保存它。用一个变量保存了数据,它才能参加到后面的计算中,比如计算找零。

变量定义

  • 变量定义的一般形式就是:
    • <类型名称><类型名称>;
  • int price
  • int amount
  • int price,amount

变量的名称

  • 变量需要一个名字,变量的名字是一种“标识符”,意思是它是用来识别这个和那个的不同的名字。
  • 标识符有标识符的构造规则。基本的原则是:标识符只能由字母,数字和下划线组成,数字不可以出现在第一个位置上,C语言的关键字(有的地方叫他们保存字)不可以用作标识符。

C语言的保留字

2.1.2 变量赋值与初始化

赋值和初始化

  • int price = 0;
  • 这一行,定义了一个变量。变量的名字是price,类型是int,初始值是0
  • price = 0 是一个式子,这里的“=”是一个赋值运算符,表示将“=”右边的值给左边的变量

赋值

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

初始化

  • 当赋值发生在定义变量的时候,就像程序l中的第七行那样,就是变量的初始化。虽然C语言并没有强制要求所有的变量都在定义的地方初始化,但是所有的变量都在第一次被使用(出现在赋值运算符的右边)之前被应该赋值一遍
  • 如果没有初始化?

变量初始化

  • <类型名称><类型名称>=<初始值>
    • int price = 0
    • int amount = 0
  • 组合变量定义的时候,也可以在这个定义中单独给单个变量赋初值,如:
    • int price = 0,amount = 0

表达式

  • “=”是赋值运算符,有运算符的式子就叫它表达式
    • price = 0
    • change = 100 -price

变量类型

  • int price = 0
  • 这一行,定义了一个变量。变量的名字是price,类型是int,初始值是0
  • C是一个有类型的语言,所有的变量在使用之前必须定义或声明,所有的变量必须具有确定的数据类型。数据类型表示在变量中可以存放什么样的数据,变量中只能存放指定类型的数据,程序运行过程中也不能改变变量的类型

第二个变量

  • int change = 100 - price
  • 定义了第二个变量change
  • 并且做了计算
  • 这是C99 的写法

ANSI C

  • 只能在代码开头的地方定义变量
#include <stdio.h>
int main(){
	int price = 0;
	int change = 0;
	printf("请输入金额(元):");
	scanf("%d",&price);
	change = 100 - price;
	printf("找给你%d元。\n",change);
	return 0;
}

ANSI C

#include <stdio.h>
int main(){
	int price = 0;
	printf("请输入金额(元):");
	scanf("%d",&price);
	int change = 100 - price;
	printf("找给你%d元。\n",change);
	return 0;
}

C99

2.1.3 变量输入

读整数

  • scanf(“%d”,&price);
  • 要求scanf这个函数读入下一个整数,读到的结果赋值给变量price
  • 小心price前面的&

2.1.4 常量VS变量

常量

  • int change = 100 - price;
  • 固定不变的数,是常数。直接写在程序里,我们称作直接量(literal)
  • 更好的方式,是定义一个常量
    • const int AMOUNT = 100
  • C99!
#include <stdio.h>
int main(){
	int price = 0;
	const int AMOUNT = 100;
	printf("请输入金额(元):");
	scanf("%d",&price);
	int change = AMOUNT - price;
	printf("找给你%d元。\n",change);
	return 0;
}

const

  • const 是一个修饰符,加在int的前面,用来给这个变量加上一个const(不变的)的属性。这个const的属性表示这个变量的值一旦初始化,就不能再改变了。
    • int change = AMOUNT - price;
  • 如果你试图对常量做修改,把他放在赋值运算符的左边,就会被编译器发现,指出为一个错误。
  • C99!

try

  • 让用户输入变量AMOUNT的值,而不是使用固定的初始值
  • 这个变量在哪里定义合适呢?
#include <stdio.h>
int main(){
	int price = 0;
	int amount = 100;
	printf("请输入金额(元):");
	scanf("%d",&price);
	printf("请输入票面(元):");
	scanf("%d",&amount);
	int change = amount - price;
	printf("找给你%d元。\n",change);
	return 0;
}

plus.c

#include <stdio.h>
int main(){
	int a;
	int b;
	printf("请输入两个整数:");
	scanf("%d %d",&a,&b);
	printf("%d + %d = %d\n",a,b,a+b);
	return 0;
}

2.1.5 浮点数

计算身高的程序

#include <stdio.h> 

int main(){
	printf("请分别输入身高的英尺和英寸,"
	"如输入\"5 7\"表示5英尺7英寸:");
	int foot;
	int inch;
	scanf("%d %d",&foot,&inch);
	printf("身高是%f米。\n",
	((foot + inch / 12 ) * 0.3048));
	return 0;
}

你会发现 输出的结果并非我们想要的

因为?

  • 因为俩个整数的运算的结果只能是整数
    • 10/3*3 => ?
  • 10和10.0在C中是完全不同的数
  • 10.0是浮点数

浮点数

  • 带小数点的数值。浮点这个词的本意就是指小数点是浮动的,是计算机内部表达非整数(包含分数和无理数)的一种方式。另一种方式叫做定点数,不过C语言中你不会碰到定点数。人们借用浮点数这个词来表达所有的带小数点的数

改进程序

  • ((foot + inch / 12 ) * 0.3048)); 改成:
  • ((foot + inch / 12.0 ) * 0.3048));
  • 当浮点数和整数放在一起计算时,C会将整数转换成浮点数,然后进行浮点数的运算

double

  • inch 是定义为int类型的变量,如果把int换成double,我们就把它改成double类型的浮点数变量了
  • double的意思是”双“,它本来是”双精度浮点数“的第一个单词,人们用来表达浮点数类型。除了double还有float(意思就是浮点!)表示单精度浮点数。

数据类型

  • 整数
    • int
    • printf(“%d”,…)
    • scanf(“%d”,…)
  • 带小数点的数
    • double
    • printf(“%f”,…)
    • scanf(“%lf”,…)

整数

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

2.2.1 表达式

表达式

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

运算符

  • 运算符(operator)是指进行运算的动作,比如加法运算符“+”,减法运算符“-”。
  • 算子(operand)是指参与于是暖的值,这个值可能是常数,也可能是变量,还可能是一个方法的返回值

计算时间差

  • 输入两个时间,每个时间分别输入小时和分钟的值,然后输出两个小时之间的差,也以几小时几分表示
//输入两个时间,每个时间分别输入小时和分钟的值,
//然后输出两个小时之间的差,也以几小时几分表示
#include <stdio.h>
int main(){
	int hour1,minute1;
	int hour2,minute2;
	scanf("%d %d",&hour1,&minute1);
	scanf("%d %d",&hour2,&minute2);
	int hour3,minute3;
	hour3 = ((hour2 - hour1)*60 +(minute2 - minute1))/60 ;
	minute3 = ((hour2 - hour1)*60 +(minute2 - minute1))%60 ;
	printf("时间为:%d 小时 %d 分钟",hour3,minute3);
	return 0;
}
  • (hour2 - hour1)*60 +(minute2 - minute1)把数据换成以分钟为单位
  • /60 ->小时部分 %60->分钟部分

2.2.2 运算符优先级

求平均数

  • 写一个程序,输入俩个整数,输出他们的平均值
#include <stdio.h>
int main(){
	int a,b;
	scanf("%d %d",&a,&b);
	double c = (a + b)/2.0;
	printf("%d和%d的平均数为%f\n",a,b,c);
	return 0;
}

运算符优先级

赋值运算符

  • 赋值也是运算,也有结果
  • a=6的结果是a被赋予的值,也就是6
  • a=b=6 —> a=(b=6)

”嵌入式赋值“

int a= 6;
int b;
int c = 1+(b=a);
  • 不利于阅读
  • 容易产生错误

结合关系

  • 一般自左向右
  • 单目± 和赋值= 自右向左
result = a = b = 3+c;
result = 2;
result  = (result = result *2) * 6 * (result = 3 +result);

这样的表达式太复杂,不容易阅读和理解,容易造成读程序时的误解。所以,要避免写出这样的复杂表达式来的。这个表达式应该被拆成若干个表达式,然后以明显的正确的顺序来进行计算。

2.2.3 交换两个变量

交换俩个变量

  • 如果已经有:
int a = 6;
int b = 5;

任何交换a,b俩个变量的值?

程序是按步执行的

  • 程序表达的是顺序执行的动作,而不是关系
    a=b ;
    b=a;
    是依次执行的,结果使得a和b都得到b原本的值

交换

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

2.2.4 复合赋值和递增递减

复合赋值

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

    • total += 5
    • total = total +5
  • 注意俩个运算符之间不要有空格

  • total += (sum+100)/2;

    • total = total +sum+100)/2;
  • total *= sum+12;

    • total = total * (sum + 12);

递增递减运算符

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

前缀和后缀

  • ++和-- 可以放在变量的前面,叫做前缀形式,也可以放在变量的后面,叫做后缀形式
  • a++ 的值是a加1以后的值,而++a的值是加上了1以后的值,无论那个,a自己的值都加了1

2.3.1 练习题

02-0. 整数四则运算(10)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
乔林(清华大学)
本题要求编写程序,计算2个正整数的和、差、积、商并输出。题目保证输入和输出全部在整型范围内。

输入格式:

输入在一行中给出2个正整数A和B。

输出格式:

在4行中按照格式“A 运算符 B = 结果”顺序输出和、差、积、商。

输入样例:
3 2
输出样例:
3 + 2 = 5
3 - 2 = 1
3 * 2 = 6
3 / 2 = 1

自己写的代码

//计算2个正整数的和、差、积、商并输出。
//输入在一行中给出2个正整数A和B。
//在4行中按照格式“A 运算符 B = 结果”顺序输出和、差、积、商。

#include <stdio.h>
 
int main()
{
	int A;
	int B;
	printf("请输入数字A B的值,得到A和B之间的四则运算结果");
	scanf("%d %d",&A,&B);
	int C,D,E,F,G;
	C = A+B;
	D = A-B;
	E = A/B;
	F = A*B;
	printf("%d+%d的值=%d\n",A,B,C);
	printf("%d-%d的值=%d\n",A,B,D);
	printf("%d/%d的值=%d\n",A,B,E);
	printf("%d*%d的值=%d\n",A,B,F);
    return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	int A;
	int B;
	int X, Y, Z, W;
	scanf("%d %d", &A, &B);
	X = A + B;
	Y = A - B;
	Z = A * B;
	W = A / B;
	printf("%d + %d = %d\n" ,A, B, X);
	printf("%d - %d = %d\n", A, B, Y);
	printf("%d * %d = %d\n", A, B, Z);
	printf("%d / %d = %d\n", A, B, W);
	
    return 0;
}

02-1. 厘米换算英尺英寸(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
翁恺(浙江大学)
如果已知英制长度的英尺foot和英寸inch的值,那么对应的米是(foot+inch/12)*0.3048。现在,如果用户输入的是厘米数,那么对应英制长度的英尺和英寸是多少呢?别忘了1英尺等于12英寸。

输入格式:

输入在一行中给出1个正整数,单位是厘米。

输出格式:

在一行中输出这个厘米数对应英制长度的英尺和英寸的整数值,中间用空格分开。

输入样例:
170
输出样例:
5 6

自己写的

//如果已知英制长度的英尺foot和英寸inch的值,
//那么对应的米是(foot+inch/12)*0.3048。
//现在,如果用户输入的是厘米数,
//那么对应英制长度的英尺和英寸是多少呢?
//别忘了1英尺等于12英寸。
#include <stdio.h>
int main()
{
	int centimeter;
	printf("请输入厘米的值");
	scanf("%d",&centimeter);
	int foot;
	int inch;
	foot = centimeter * 0.0328084;
	inch = ((centimeter * 0.0328084) - foot) * 12;
	printf("%d %d",foot,inch);
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	int cm;
	scanf("%d", &cm);
	int foot = cm / 30.48;
	int inch = (cm/30.48 - foot) * 12;
	printf("%d %d", foot, inch);
    return 0;
 }

02-2. 然后是几点(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
翁恺(浙江大学)
有时候人们用四位数字表示一个时间,比如1106表示11点零6分。现在,你的程序要根据起始时间和流逝的时间计算出终止时间。读入两个数字,第一个数字以这样的四位数字表示当前时间,第二个数字表示分钟数,计算当前时间经过那么多分钟后是几点,结果也表示为四位数字。当小时为个位数时,没有前导的零,即5点30分表示为530。注意,第二个数字表示的分钟数可能超过60,也可能是负数。

输入格式:

输入在一行中给出2个整数,分别是四位数字表示的起始时间、以及流逝的分钟数,其间以空格分隔。注意:在起始时间中,当小时为个位数时,没有前导的零,即5点30分表示为530;流逝的分钟数可能超过60,也可能是负数。

输出格式:

输出四位数字表示的终止时间。题目保证起始时间和终止时间在同一天内。

输入样例:
1120 110
输出样例:
1310

自己写的

//输入在一行中给出2个整数,
//分别是四位数字表示的起始时间、以及流逝的分钟数,
//其间以空格分隔。
//注意:
//在起始时间中,当小时为个位数时,没有前导的零,
//即5点30分表示为530;
//流逝的分钟数可能超过60,也可能是负数。
#include <stdio.h>
int main()
{
	int a;
	int b;
	printf("请输入2个整数,分别是四位数字表示的起始时间、以及流逝的分钟数");
	scanf("%d %d", &a, &b);
	//c是a的分钟,d是a的小时
	int c, d;
	c = a % 100;
	d = ((a % 10000) - c) / 100;

	//x是新的小时,y是新的分钟,z是新整体 
	int x, y, z;
	x = ((c + d * 60) + b) / 60;
	y = ((c + d * 60) + b) % 60;
	z = x*100 + y;
	printf("%d ", z);
	return 0;
}

参考答案


#include <stdio.h>
int main()
{
	int i, j, x = 0, y, z;
	scanf("%d%d", &i, &j);
	if(i >= 1000)
	{
	       x = i / 1000;
	       y = (i % 1000) / 100;
	       z = (i %1000) %100;
	}
	else
	{
	       y = i / 100;
	       z = i % 100; 
	}
	int s = (10*x + y) * 60 + z;
	int n = s + j;
	i = n / 60 *100 + (n - ((n / 60 )* 60));
	printf("%d", i); 
	return 0;
}

02-3. 逆序的三位数(10)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
翁恺(浙江大学)
程序每次读入一个正3位数,然后输出按位逆序的数字。注意:当输入的数字含有结尾的0时,输出不应带有前导的0。比如输入700,输出应该是7。

输入格式:

每个测试是一个3位的正整数。

输出格式:

输出按位逆序的数。

输入样例:
123
输出样例:
321

自己写的

//程序每次读入一个正3位数,然后输出按位逆序的数字。
//注意:当输入的数字含有结尾的0时,输出不应带有前导的0。
//比如输入700,输出应该是7。
#include <stdio.h>
int main(){
	int a;
	printf("请输入一个正3位数");
	scanf("%d",&a);
	//x是第一位,y是第二位,z是第三位 
	int x,y,z;
	x = (a%1000)/100;
	y = (a%100)/10;
	z = (a%10);
	//新的数字 
	int b;
	b = z*100 + y*10 + x;
	printf("%d",b);
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
 {
 	int i;
 	scanf("%d", &i);
 	int a = i % 100 %10;
 	int b = i / 100;
 	int c = i %100 / 10;
 	i = a*100 + b + c*10;
 	printf("%d", i);
 	return  0;
 }

02-4. BCD解密(10)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
翁恺(浙江大学)
BCD数是用一个字节来表达两位十进制的数,每四个比特表示一位。所以如果一个BCD数的十六进制是0x12,它表达的就是十进制的12。但是小明没学过BCD,把所有的BCD数都当作二进制数转换成十进制输出了。于是BCD的0x12被输出成了十进制的18了!

现在,你的程序要读入这个错误的十进制数,然后输出正确的十进制数。提示:你可以把18转换回0x12,然后再转换回12。

输入格式:

输入在一行中给出一个[0, 153]范围内的正整数,保证能转换回有效的BCD数,也就是说这个整数转换成十六进制时不会出现A-F的数字。

输出格式:

输出对应的十进制数。

输入样例:
18
输出样例:
12

 #include <stdio.h>
 
int main()
 {
	int i;
	scanf("%d", &i);
	int t = i % 16 + i / 16 * 10;
	printf("%d", t);
	return 0;
 }
 #include <stdio.h>
 
int main()
 {
	int i;
	scanf("%d", &i);
	printf("%i", i);
	return 0;
 }

3.1.1 做判断

如果

if(条件成立){

}

3.1.2 判断的条件

条件

  • 计算俩个值之间的关系,所以叫做关系运算
运算符 意义
= = 相等
!= 不相等
> 大于
>= 大于或等于
< 小于
<= 小于或等于

关系运算的结果

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

优先级

  • 所有的关系运算符的优先级比算术运算符都低,但是比赋值运算符的高
    • 7 >= 3+4
    • int r = a>0;
  • 判断是否相等的==和!= 的优先级比其他的低,而连续的关系运算是从左到右经行的
    • 5 > 3==6>4
    • 6>5>4
    • a == b == 6
    • a == b > 0

3.1.3 找零计算器

  • 找零计算器需要用户做俩个操作:输入购买的金额,输入支付的票面,而找零计算器则根据用户的输入做出相应的动作:计算并打印找零,或告知用户余额不足以购买。
  • 以计算机程序的角度看,这就是意味着程序需要读用户的俩个输入,然后经行一些计算和判断,最后输出结果
#include <stdio.h>
int main(){
	//初始化
	int price = 0;
	int bill = 0;
	//读入金额和票面
	printf("请输入金额:");
	scanf("%d",&price); 
	printf("请输入票面:");
	scanf("%d",&bill); 
	//计算找零 
	printf("应该找你:%d\n",bill-price);
	return 0;
}

注释

  • 以俩个斜杠“//”开头的语句把程序分为了几大部分
  • “//”是C99的注释 , ANSI C不支持
  • 注释(comment)插入在程序代码中,用来向读者提供解释信息。他们对于程序的功能没有如何影响,但是往往能使得程序更容易被人类读者理解。

/**/注释

  • 延续数行的注释,要用多行注释的格式来写。多行注释由一对字符序列以“/*" 开始,而已" */"结束
  • 也可以用于一行内的注释
    • int ak = 47 /* 36 */,y=9;

判断票面够不够

#include <stdio.h>
int main(){
	//初始化
	int price = 0;
	int bill = 0;
	//读入金额和票面
	printf("请输入金额:");
	scanf("%d",&price); 
	printf("请输入票面:");
	scanf("%d",&bill); 
	//计算找零 
	if(bill >= price){
		printf("应该找你:%d\n",bill-price);
	}
	return 0;
}

3.1.4 否则的话

else

  • else = 否则的话
#include <stdio.h>
int main(){
	//初始化
	int price = 0;
	int bill = 0;
	//读入金额和票面
	printf("请输入金额:");
	scanf("%d",&price); 
	printf("请输入票面:");
	scanf("%d",&bill); 
	//计算找零 
	if(bill >= price){
		printf("应该找你:%d\n",bill-price);
	}
	else{
		printf("你的钱不够\n"); 
	}
	return 0;
}

比较俩个数的大小

#include <stdio.h>
int main(){
	int a,b;
	printf("请输入俩个整数");
	scanf("%d %d",&a,&b);
	int max = 0;
	if(a > b){
		max = a;
	}
	printf("大的那个是%d\n",max);
	return 0;
}

他并没有解决b大于a的问题,当a大于b的条件不成立的时候,程序就结束了,max并没有得到值

方案一

#include <stdio.h>
int main(){
	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);
	return 0;
}

方案二

#include <stdio.h>
int main(){
	int a,b;
	printf("请输入俩个整数");
	scanf("%d %d",&a,&b);
	int max = 0;
	if(a > b){
		max = a;
	}else{
		max = b;
	}
	printf("大的那个是%d\n",max);
	return 0;
}

方案三

#include <stdio.h>
int main(){
	int a,b;
	printf("请输入俩个整数");
	scanf("%d %d",&a,&b);
	int max = b;
	if(a > b){
		max = a;
	}
	printf("大的那个是%d\n",max);
	return 0;
}

3.1.5 if语句再探

if语句

  • 一个基本的if语句由一个关键字if开头,跟上在括号里的一个表达条件的逻辑表达式,然后是一对大括号”{}“之间的若干条语句。如果表示条件的逻辑表达式的结果不是零,那么就执行后面跟着的这对大括号的语句,否则就跳过这些语句不执行,而继续下面的其他语句。
    if(total > amount)
        total+=amount+10;
  • if语句这一行结束的时候并没有表示语句结束的”;“,而后面的赋值语句写在if的下一行,并且缩进了,在这一行结束的时候有一个表示语句结束的”;“。这表明这条赋值语句是if语句的一部分,if语句拥有和控制这条赋值语句,决定它是否要被执行

计算薪水

#include <stdio.h>
int main(){
	const double RATE = 8.25;
	const int 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);
	return 0;
}

3.2.1 嵌套的if-else

三个数的比大小

#include <stdio.h>
int main(){
	int a,b,c;
	scanf("%d %d %d",&a,&b,&c);
	int max = 0;
	if(a > b){
		if(a>c){
			max = a;
		}else{
			max = c;
		}
	}
	else{
		if(b>c){
			max = b;
		}else{
			max = c;
		}
	}
	printf("%d",max);
	return 0;
} 

嵌套的判断

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

缩进

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

3.2.2 级联的if-else if

分段函数

// f(x) = -1; x<0
//	   0; x=0
//	  2x; x>0
#include <stdio.h>
int main(){
	int x;
	scanf("%d",&x);
	int f;
	if(x<0){
		f=-1;
	}else if(x == 0){
		f=0;
	}else{
		f = 2*x;
	}
	printf("%d",f);
	return 0;
}	

级联的if-else if

if(exp1)
	st1;
else if(exp2)
	st2;
else
	st3;

3.2.3 if-else的常见错误

  • 忘了大括号
  • if后面的分号
  • 错误使用== 和=
  • 使人困惑的else

代码风格

  • 在if和else之后必须加上大括号形成语句块;
  • 大括号内的语句缩进一个tab的位置;

3.2.4 多路分支

switch-case

#include <stdio.h>
int main(){
	int type;
	scanf("%d",&type);
	switch(type){
		case 1:
			printf("你好");
			break;
		case 2:
			printf("早上好");
			break;
		case 3:
			printf("晚上好");
			break;
		case 4:
			printf("再见");
			break;	
		default:
			printf("你干嘛啊?");
	}
	return 0;
}
switch(控制表达式){
case 常量:
	语句
	...
case 常量:
	语句
	...
default:
	语句
	...
}
  • 控制表达式只能是整数型的结果
  • 常量可以是常数,也可以是常数计算的表达式

break

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

分支-06 成绩转换

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
沈睿(浙江大学)
本题要求编写程序将一个百分制成绩转换为五分制成绩。转换规则:

大于等于90分为A;
小于90且大于等于80为B;
小于80且大于等于70为C;
小于70且大于等于60为D;
小于60为E。
输入格式:

输入在一行中给出1个整数的百分制成绩。

输出格式:

在一行中输出对应的五分制成绩。

输入样例:
90
输出样例:
A
switch-case版本

//大于等于90分为A;
//小于90且大于等于80为B;
//小于80且大于等于70为C;
//小于70且大于等于60为D;
//小于60为E。
#include <stdio.h> 
int main(){
	int grade;
	scanf("%d",&grade);
	grade /= 10;
	switch(grade){
		case 10:
		case 9:
			printf("A\n");
			break;
		case 8:
			printf("B\n");
			break;
		case 7:
			printf("C\n");
			break;
		case 6:
			printf("D\n");
			break;
		default:
			printf("F\n");
			break;
	}
	return 0;
}

if-else版本

#include <stdio.h>
 
int main()
{
  int i;
  scanf("%d", &i);
  if(i >= 90)
    printf("A");
  else if(i < 90 && i >= 80)
    printf("B");
  else if(i < 80 && i >= 70)
    printf("C");
  else if(i < 70 && i >= 60)
    printf("D");
  else if(i < 60)
    printf("E");
    
  return 0;
}

3.3.1 练习

03-0. 超速判断(10)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
杨起帆(浙江大学城市学院)
模拟交通警察的雷达测速仪。输入汽车速度,如果速度超出60 mph,则显示“Speeding”,否则显示“OK”。

输入格式:

输入在一行中给出1个不超过500的非负整数,即雷达测到的车速。

输出格式:

在一行中输出测速仪显示结果,格式为:“Speed: V - S”,其中V是车速,S或者是Speeding、或者是OK。

输入样例1:
40
输出样例1:
Speed: 40 - OK
输入样例2:
75
输出样例2:
Speed: 75 - Speeding

自己写的

#include <stdio.h>
int main(){
//	输入格式:
//输入在一行中给出1个不超过500的非负整数,即雷达测到的车速。
//输出格式:
//在一行中输出测速仪显示结果,格式为:“Speed: V - S”,
//其中V是车速,S或者是Speeding、或者是OK。
	int V;
	printf("请输入一个不超过500的非负整数,即雷达测到的车速\n");
	scanf("%d",&V);
	if(V > 60){
		printf("Speed: %d - Speeding",V);
	}else{
		printf("Speed: %d - OK",V);
	}
	return 0;
} 

参考答案

#include <stdio.h>
 
int main()
{
  int i;
  scanf("%d", &i);
  if(i <= 60)
    printf("Speed: %d - OK", i);
  else
    printf("Speed: %d - Speeding", i);
  return 0;
}

03-1. 三天打鱼两天晒网(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
中国有句俗语叫“三天打鱼两天晒网”。假设某人从某天起,开始“三天打鱼两天晒网”,问这个人在以后的第N天中是“打鱼”还是“晒网”?

输入格式:

输入在一行中给出1个不超过1000的正整数N。

输出格式:

在一行中输出此人在第N天中是“Fishing”(即“打鱼”)还是“Drying”(即“晒网”),并且输出“in day N”。

输入样例1:
103
输出样例1:
Fishing in day 103
输入样例2:
34
输出样例2:
Drying in day 34

自己写的

#include <stdio.h>
int main(){
//	输入在一行中给出1个不超过1000的正整数N。
//	在一行中输出此人在第N天中是“Fishing”
//	(即“打鱼”)还是“Drying”(即“晒网”),
//	并且输出“in day N”。
	int day;
	printf("给出1个不超过1000的正整数N\n");
	scanf("%d",&day);
	if((day % 5)>3 || (day % 5 == 0)){
		printf("Drying in day %d",day);
	}else{
		printf("Fishing in day %d",day);
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	int i;
	scanf("%d", &i);	
	if(i%5 <= 3 && i%5 != 0)
	     printf("Fishing in day %d", i);
	else 
	     printf("Drying in day %d", i);
	return 0;
} 

03-2. 用天平找小球(10)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
三个球A、B、C,大小形状相同且其中有一个球与其他球重量不同。要求找出这个不一样的球。

输入格式:

输入在一行中给出3个正整数,顺序对应球A、B、C的重量。

输出格式:

在一行中输出唯一的那个不一样的球。

输入样例:
1 1 2
输出样例:
C

自己的

#include <stdio.h>
int main(){
//输入格式:
//输入在一行中给出3个正整数,顺序对应球A、B、C的重量。
//输出格式:
//在一行中输出唯一的那个不一样的球。
	int a,b,c;
	printf("给出3个正整数,顺序对应球A、B、C的重量\n");
	scanf("%d %d %d",&a,&b,&c);
	if(a != b && a != c){
		printf("A");
	}else if(b !=a && b != c ){
		printf("B");
	}else{
		printf("C");
	}
	return 0;
}

参考答案

# include <stdio.h>
 
int main()
{
     int A, B, C;
     scanf("%d%d%d",&A, &B, &C);
     if(A != B && B ==C)
           printf("A");
     else if(B != A && A == C)
           printf("B");
     else if(C != A && A == B)
           printf("C");
    return 0;
}

03-3. 12-24小时制(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
翁恺(浙江大学)
编写一个程序,要求用户输入24小时制的时间,然后显示12小时制的时间。

输入格式:

输入在一行中给出带有中间的“:”符号(半角的冒号)的24小时制的时间,如12:34表示12点34分。当小时或分钟数小于10时,均没有前导的零,如5:6表示5点零6分。

提示:在scanf的格式字符串中加入“:”,让scanf来处理这个冒号。

输出格式:

在一行中输出这个时间对应的12小时制的时间,数字部分格式与输入的相同,然后跟上空格,再跟上表示上午的字符串“AM”或表示下午的字符串“PM”。如“5:6 PM”表示下午5点零6分。注意,在英文的习惯中,中午12点被认为是下午,所以24小时制的12:00就是12小时制的12:0 PM;而0点被认为是第二天的时间,所以是0:0 AM。

输入样例:
21:11
输出样例:
9:11 PM

by 自己

#include <stdio.h>
int main(){
//输入在一行中给出带有中间的“:”
//符号(半角的冒号)的24小时制的时间,如12:34表示12点34分。
//当小时或分钟数小于10时,均没有前导的零,如5:6表示5点零6分。
	int x,y;
	scanf("%d:%d",&x,&y);
	if(x >= 0 && x < 12){
		printf("%d : %d AM",x,y);
	}else if(x == 12){
		printf("%d : %d PM",x,y);
	}else if(x == 24){
		printf("%d : %d AM",x - 24,y);
	}else{
		printf("%d : %d PM",x - 12,y);
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	int i, j;
	scanf("%d:%d", &i, &j);
	if(i >= 0 && i < 12)
		printf("%d:%d AM", i, j);
	else if(i == 12)
		printf("%d:%d PM", i, j);
	else if(i == 24)
		printf("%d:%d AM", i-24, j);
	else
		printf("%d:%d PM", i-12, j);
		
	return 0;
}

03-4. 成绩转换(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
沈睿(浙江大学)
本题要求编写程序将一个百分制成绩转换为五分制成绩。转换规则:

大于等于90分为A;
小于90且大于等于80为B;
小于80且大于等于70为C;
小于70且大于等于60为D;
小于60为E。
输入格式:

输入在一行中给出1个整数的百分制成绩。

输出格式:

在一行中输出对应的五分制成绩。

输入样例:
90
输出样例:
A

#include <stdio.h>
 
int main()
{
  int i;
  scanf("%d", &i);
  if(i >= 90)
    printf("A");
  else if(i < 90 && i >= 80)
    printf("B");
  else if(i < 80 && i >= 70)
    printf("C");
  else if(i < 70 && i >= 60)
    printf("D");
  else if(i < 60)
    printf("E");
    
  return 0;
}

4.1.1 循环

人和计算机

  • 人的方式:眼睛一看就知道
    • 352 —> 3位数!
  • 计算机的方式:判断数的范围来决定他的位数
    • 352∈[100,999] —> 3位数!
    • 人不擅长,因为人对数字的计算能力比文字弱

判断数的位数

程序的实现

#include <stdio.h>
int main(){
	int x;
	int n =1;
	scanf("%d",&x);
	if(x>999){
		n = 4;
	}else if(x > 99){
		n = 3;
	}else if(x > 9){
		n = 2;
	}
	printf("%d\n",n);
	return 0;
} 
  • 因为题目明确了4位数及以下的正整数,所以可以简化一些判断
  • 因为从高处往下判断,所以不需要判断上限了
    • 反过来不行
  • 问题:任意范围的正整数怎么办?
#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;
} 

4.1.2 while循环

20220704143034
循环体内要有改变条件的机会

while循环

  • 如果我们把while翻译成“当”,那么一个while循环的意思就是:当条件满足时,不断的重复循环体内的语句。
  • 循环执行之前判断是否继续循环,所以有可能循环一次也没有被执行;
  • 条件成立是循环继续的条件
#include <stdio.h>
int main(){
	int x;
	int n =0;
	scanf("%d",&x);
	while(x>0){
		n++;
		x /= 10;
	}
	printf("%d\n",n);
	return 0;
} 

看程序运行结果

  • 人脑模拟计算机的运行,在纸上列出所有的变量,随着程序的进展不断重新计算变量的值。当程序运行结束时,留在表格最下面的就是程序的最终结果

验证

  • 测试程序常使用边界数据,如有效范围两端的数据,特殊的倍数等
    • 个位数;
    • 10;
    • 0;
    • 负数。

4.1.3 do-while循环

数位数的算法

  1. 用户输入x;
  2. 初始化n为0;
  3. x = x / 10 ,去掉个位;
  4. n++;
  5. 如果x>0,回到3;
  6. 否则n就是结果。

do-while循环

20220704144848
20220704144908

俩种循环

  • do-while循环和while循环很像,区别是在循环体执行结束的时候才来判断条件。也就是说,无论如何,循环都会执行至少一遍,然后再来判断条件。与while循环相同的是,条件满足时执行循环,条件不满足时结束循环。
#include <stdio.h>
int main(){
	int x;
	int n =0;
	scanf("%d",&x);
	do{
		x /= 10;
		n++;
	}while(x>0);
	printf("%d\n",n);
	return 0;
} 

4.2.1 循环计算

#include <stdio.h>
int main(){
	int x;
	int ret = 0;
//	scanf("%d",&x);
	x = 64;
	while(x > 1){
		x /= 2;
		ret ++;
	}	
	printf("log2 of %d is %d.",x,ret);
	return 0;
}

小套路

  • 计算之前先保留原始的值,后面可能会用
#include <stdio.h>
int main(){
	int x;
	int ret = 0;
	scanf("%d",&x);
	int t = x;
//	x = 64;
	while(x > 1){
		x /= 2;
		ret ++;
	}	
	printf("log2 of %d is %d.",t,ret);
	return 0;
}

计数循环

#include <stdio.h>
int main(){
	int count = 100;
	while(count >= 0){
		count --;
		printf("%d\n",count);
	}
	printf("发射!\n");
	return 0;
}
  • 这个循环需要执行多少次?
  • 循环停下来的时候,有没有输出最后的0?
  • 循环结束后,count的值是多少?
    小套路

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

回答

  • 这个循环需要执行多少次?
    • 101次
  • 循环停下来的时候,有没有输出最后的0?
  • 循环结束后,count的值是多少?
    • -1

4.2.2 猜数游戏

  • 让计算机来想一个数,然后让用户来猜,用户每输入一个数,就告诉他是大了还是小了,直到用户猜中为止,最后还要后告诉用户他猜了多少次。
  • 因为需要不断重复让用户猜,所以需要用到循环
  • 在实际写出程序之前,我们可以先用文字描述程序的思路
  • 核心重点是循环的条件
    • 人们往往会考虑循环终止的条件

20220704153209
循环的条件是a和number不相等

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

随机数

  • 每次召唤rand()就得到一个随机的整数

%100

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

4.2.3 算平均数

20220704154539

变量

  • 一个记录读到的整数的变量
  • 平均数要怎么算?
    • 只需要每读到一个数,就把他加到一个累加的变量里,到全部数据读完了,再拿他去除读到的数的个数就可以了
  • 一个变量记录累加的结果,一个变量记录读到的数的个数

20220704154946

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

改进

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

4.2.4 整数逆序

整数的分解

  • 一个整数是由1至多位数字组成的,如何分解出整数的各个位上的数字,然后加以计算
    • 对一个整数做%10的操作,就得到他的个位数
    • 对一个整数做/10的操作,就去掉他的个位数
    • 然后在对2的结果做%10,就得到原来数的十位数了
    • 以此类推

数的逆序

  • 输入一个正整数,输入逆序的数
    • 结尾的0的处理
#include <stdio.h>
int main(){
	int x ;
//	scanf("%d",&x);
	x = 12345;
	int digit;
	int ret = 0;
	while(x>0){
		digit = x%10;
//		printf("%d",digit);
		ret = ret*10 + digit;
		printf("x=%d,digit=%d,ret=%d\n",x,digit,ret);
		x /= 10;
	}
	printf("%d",ret);
	return 0;
}

5.1.1 For循环

阶乘

  • n! = 1 * 2 * 3 * 4 * … * n
  • 写一个程序,让用户输出n,然后计算输出n!
  • 变量:
    • 显然读用户的输入需要一个int的n,然后计算的结果需要用一个变量保存,可以是int的factor,在计算中需要有一个变量不断地从1递增到n,那可以是int的i

while

#include <stdio.h>
int main(){
	int n;
	scanf("%d",&n);
	int fact = 1;
	
	int i = 1;
	while(i <= n){
		fact *= i;
		i++;
	}
	printf("%d!=%d\n",n,fact);
	return 0;
} 

for

#include <stdio.h>
int main(){
	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);
	return 0;
} 
  • i=1是初始条件
  • i<=n是循环继续的条件
  • i++是循环每轮要做的动作

for循环

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

for(i=0;i<5;i=i+1){
	printf("%d",i);
}

for = 对于

  • for(count=10 ; count >0 ; count–)
  • 就读成:“对于一开始的count=10,当count> 0时,重复做循环体,每一轮循环在做完循环体内语句后,使得count–”

小套路

  • 做求和的程序时,记录结果的变量应该初始化为0,而做求积的变量时,记录结果的变量应该初始化为1
  • 循环控制变量i只在循环里被使用了,在循环外面他没有如何用处。因此,我们可以把变量i的定义写在for循环语句里面

try

  • 1 * 1还是1,所以程序的循环不需要从1开始,那么改成从多少开始合适呢?这样修改之后,程序对所有的n都正确吗?这样的改动有价值吗?
#include <stdio.h>
int main(){
	int n;
	scanf("%d",&n);
	int fact = 1;
	for( int i=2;i<=n;i++){
		fact *= i;
	}
	printf("%d!=%d\n",n,fact);
	return 0;
} 
  • 除了可以从1乘到n来计算n!,还可以从n乘到1来计算吧?试试把循环换一个方向来计算n。这时候,还需要循环控制变量i吗?
#include <stdio.h>
int main(){
	int n;
	scanf("%d",&n);
	int fact = 1;
//	for( int i=2;i<=n;i++){
//		fact *= i;
//	}
	for(int i =n ;i>1;i--){
		fact *= i;
	}
	printf("%d!=%d\n",n,fact);
	return 0;
} 

5.1.2 循环的计算和选择

循环次数

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

for == while

20220705101458

for循环

for(初始动作 ; 条件 ; 每轮的动作){
}

  • for中的每一个表达式都可以省略的
    for( ; 条件; ) == while( 条件 )

三种循环

20220705101816

Tips for loops

  • 如果有固定次数,用for
  • 如果必须执行一次,用do_while
  • 其他情况用while

5.2.1 循环控制

素数

  • 只能被1和自己整除的数
    • 1,2,3,5,7,11,13,17,19…
#include <stdio.h>
int main(){
	int x;
	scanf("%d",&x);
	int isPrime = 1;
	for(int i = 2 ;i < x;i++){
		if(x % i == 0){
			isPrime = 0;
			break; 
		}
	}
	if(isPrime == 1){
		printf("%d是素数",x);
	}else{
		printf("%d不是素数",x);
	}
	return 0;
}

break VS continue

  • break: 跳出循环
  • continue: 跳出循环这一轮剩下的语句进入下一轮
    20220705110808

5.2.2 嵌套的循环

100以内的素数

  • 如何写程序输出100以内的素数
#include <stdio.h>
int main(){
	for(int x = 1;x<=100;x++ ){
		int isPrime = 1;
		for(int i = 2 ;i < x;i++){
			if(x % i == 0){
				isPrime = 0;
				break; 
			}
		}
		if(isPrime == 1){
			printf("%d是素数\n",x);
		}
	}
	return 0;
}

嵌套的循环

  • 嵌套里面还是循环
#include <stdio.h>
int main(){
	int cnt = 0;
	for(int x = 1;cnt < 50;x++ ){
		int isPrime = 1;
		for(int i = 2 ;i < x;i++){
			if(x % i == 0){
				isPrime = 0;
				break; 
			}
		}
		if(isPrime == 1){
			cnt ++;
			printf("%d\t",x);
			if(cnt % 5 ==0){
				printf("\n");
			}
		}
	}
	return 0;
}

5.2.3 从嵌套的循环中跳出

凑硬币

  • 如何用1角、2角和5角的硬币凑出10元以下的金额呢?
#include <stdio.h>
int main(){
	int x;
	int one,two,five;
	scanf("%d",&x);
	for(one = 1;one < x*10; one++){
		for(two = 1;two < x*10; two++){
			for(five = 1;five < x*10; five++){
				if(one + two*2 + five*5 == x*10){
					printf("可以用%d个1角加上%d个2角加上%d个5角得到%d元\n",one,two,five,x);
				}
			}
		}
	}
	return 0;
}

break 和 continue

  • 只能对它所在的那层循环做

接力break

#include <stdio.h>
int main(){
	int x;
	int one,two,five;
	scanf("%d",&x);
	int exit = 0;
	for(one = 1;one < x*10; one++){
		for(two = 1;two < x*10; two++){
			for(five = 1;five < x*10; 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;
}

goto

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

5.3.1 前n项求和

求和

#include <stdio.h>
int main(){
	int x;
	double sum = 0.0;
	scanf("%d",&x);
	for(int i = 1;i <= x;i++){
		sum += 1.0/i;
	}
	printf("f(%d)=%f\n",x,sum);
	return 0;
}
 #include <stdio.h>
int main(){
	int x;
	double sum = 0.0;
//	int sign = 1;
	double sign = 1.0;	
	scanf("%d",&x);
	for(int i = 1;i <= x;i++){
		sum += sign/i;
		sign = -sign;
	}
	printf("f(%d)=%f\n",x,sum);
	return 0;
}

5.3.2 整数分解

正序分解整数

  • 输入一个非负整数,正序输出他的每一个数字
  • 输入:13425
  • 输出:1 3 4 2 5
#include <stdio.h>
int main(){
	int x;
	scanf("%d",&x);
	int t;
	for(t = 0;x > 0;x /= 10){
		int d = x%10;
		t = t*10+d;
	}
	printf("%d\n",t);
	x = t;
	for(;x>0;x/=10){
		int d = x%10;
		printf("%d",d);
		if(x>9){
			printf(" ");
		}
	}
	printf("\n");
	return 0;
}

有bug
例如:70000
只能输出7
改进

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

5.3.3 求最大公约数

求最大公约数

  • 输入俩个数a和b,输出他们的最大公约数
  • 输入:12 18
  • 输出: 6
    20220705151751

辗转相除法

  1. 如果b等于0,计算结束,a就是最大公约数;
  2. 否则,计算a除以b的余数,让a等于b,而b等于那个余数;
  3. 回到第一步;
#include <stdio.h>
int main(){
	int a=12;
	int b=18;
	int t =0;
	while(b!=0){
		t = a%b;
		a = b;
		b=t;
	}
	printf("%d",a);
	return 0;
}

5.4.1 4阶段以及5阶段练习

04-0. 求符合给定条件的整数集(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
徐镜春(浙江大学)
给定不超过6的正整数A,考虑从A开始的连续4个数字。请输出所有由它们组成的无重复数字的3位数。

输入格式:

输入在一行中给出A。

输出格式:

输出满足条件的的3位数,要求从小到大,每行6个整数。整数间以空格分隔,但行末不能有多余空格。

输入样例:
2
输出样例:
234 235 243 245 253 254
324 325 342 345 352 354
423 425 432 435 452 453
523 524 532 534 542 543

自己

#include <stdio.h>
int main(){
	int a;
	scanf("%d",&a);
	int b = 0;
	int c = 0;
	int d = 0;
	int lineBreaks = 0;
	for(b=a;b<a+4;++b){
		for(c=a;c<a+4;++c){
			for(d=a;d<a+4;++d){
//				printf("%d,%d,%d,%d\n",a,b,c,d);
				if(b!=c&&c!=d&&b!=d){
					printf("%d",b*100+c*10+d);
					printf(" ");
					lineBreaks++;
					if(lineBreaks %6 == 0){
						printf("\n");
					}
				}
			}
		}
	}
	return 0;
} 

参考答案

#include <stdio.h>
 
int main()
{
	int A, i, j, k, p = 0;
	scanf("%d", &A);
	for(i=A; i<A+4; ++i)
		for(j=A; j<A+4; ++j)
			for(k=A; k<A+4; ++k)
			{
				if(i != j && i != k && j != k)
				{
	
					printf("%d", i*100+10*j+k);
					p++;
					if(p%6 == 0)
						printf("\n");
					else 
						printf(" ");
				}
			}
	
	return 0; 
}

04-1. 水仙花数(20)

时间限制
2000 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
徐镜春(浙江大学)
水仙花数是指一个N位正整数(N>=3),它的每个位上的数字的N次幂之和等于它本身。例如:153 = 1^3 + 5^3+ 3^3。本题要求编写程序,计算所有N位水仙花数。

输入格式:

输入在一行中给出一个正整数N(3<=N<=7)。

输出格式:

按递增顺序输出所有N位水仙花数,每个数字占一行。

输入样例:
3
输出样例:
153
370
371
407

自己

#include <stdio.h>
int main() {
	int n;
	scanf("%d", &n);
	int first = 1;
	//first是范围 
	int i = 1;
	while (i<n) {
		first *= 10;
		i++;
	}
	//printf("%d",first);
	//遍历 first-first*10
	i = first;
	while (i < first * 10) {
		int t = i;
		int sum = 0;
		while (t > 0) {
			int d = t % 10;
			t /= 10;
			int p = d;
			int j = 1;
			while (j < n) {
				p *= d;
				j++;
			}
			sum += p;
		}
		if (sum == i) {
			printf("%d\n", i);
		}
		i++;
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	int n = 0, m = 0, l = 0, k = 0, roll = 0;
	int min = 1, t = 0, sum = 0, sum1 = 0;
	scanf("%d", &n);
	m=n;
	while(m > 1)   //built the minimum for n digit
	{
		min*=10;
		m--;
	}
	t=min;
	while(t < 10*min)  //all the number for n digit
	{
		roll = t;		
		m = n;
		sum = 1;
		while(m > 0)
		{
			sum = sum*(roll%10);		
			m--;
		}
		l = n;
		while(l > 0)
		{
			roll = roll / 10;
			k = roll % 10;
			m = n;
			sum1 = 1;
			while(m > 0)
			{
				sum1 = sum1 * k;
				m--;
			}
			sum = sum + sum1;
			l--;
		}	
		if(t == sum)
		{
			printf("%d\n", t);
		}
		t++;
	}
	return 0;
}

04-2. 打印九九口诀表(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
徐镜春(浙江大学)
下面是一个完整的下三角九九口诀表:

11=1
1
2=2 22=4
1
3=3 23=6 33=9
14=4 24=8 34=12 44=16
15=5 25=10 35=15 45=20 55=25
1
6=6 26=12 36=18 46=24 56=30 66=36
1
7=7 27=14 37=21 47=28 57=35 67=42 77=49
18=8 28=16 38=24 48=32 58=40 68=48 78=56 88=64
19=9 29=18 39=27 49=36 59=45 69=54 79=63 89=72 99=81
本题要求对任意给定的1位正整数N,输出从1
1到N*N的部分口诀表。

输入格式:

输入在一行中给出一个正整数N(1<=N<=9)。

输出格式:

输出下三角N*N部分口诀表,其中等号右边数字占4位、左对齐。

输入样例:
4
输出样例:
11=1
1
2=2 22=4
1
3=3 23=6 33=9
14=4 24=8 34=12 44=16

自己

#include <stdio.h>
int main(){
	int n;
	scanf("%d",&n);
	for(int x=1;x<=n;x++){
		for(int y = 1;y<=x;y++){
			printf("%d*%d=%d",x,y,x*y);
			if(x==y){
				printf("\n");
			}else{
				printf("	");
			}
		}
	}
	return 0;
}

参考答案

#include <stdio.h> 
 
int main(void) 
{ 
    int n; 
    scanf("%d", &n); 
    int i, j; 
    for (i=1; i<=n; i++) 
    { 
        for (j=1; j<=i; j++) 
            printf("%d*%d=%-4d", j, i, i*j); 
        puts(""); 
    } 
 
    return 0;
}

04-3. 统计素数并求和(20)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
张彤彧(浙江大学)
本题要求统计给定整数M和N区间内素数的个数并对它们求和。

输入格式:

输入在一行中给出2个正整数M和N(1<=M<=N<=500)。

输出格式:

在一行中顺序输出M和N区间内素数的个数以及它们的和,数字间以空格分隔。

输入样例:
10 31
输出样例:
7 143

自己

#include <stdio.h>
int main(){
	int m,n;
	scanf("%d %d",&m,&n);
	//素数不能为1,所以在区间内做修改 
	if(m==1){
		m=2; 
	}
	//count是计数,sum是和 
	int count = 0;
	int sum = 0;
	for(int i =m;i<=n;i++){
		int isPrime = 1;
		for(int j = 2;j<i;j++){
			if(i%j==0){
				isPrime = 0;
				break;
			}
		}
		if(isPrime == 1){
			count++;
			sum += i;
		}
	}
	printf("%d %d",count,sum);
	return 0;
}

参考答案

//统计一个区间内,所有素数的和,这题好像没有说是开区间还是闭区间
//我是按照闭区间来做的
#include <stdio.h>
 
int main(){
//输入区间的边界值
	int m,n;
	scanf("%d %d",&m,&n);
	int i,j;
//count用来统计区间内有多少个素数,sum用来求区间内素数的和
	int count = 0;
	int sum = 0;
	 //因为是自定义区间嘛,所以边界值为1的可能性也是有的,对1进行特殊处理
	if(m == 1){
		m =2;
	} 
//以下就是求素数的常用套路了
	for(i=m;i<=n;i++){
//定义一个变量,用给变量赋值的不同来进行判断是否为素数
		int isPrime = 1;
		for(j=2;j<i;j++){
//i%j可以为0的话,就说明这个i不是素数,把这个变量赋为0,
			if(i%j == 0){
				isPrime = 0;
				break;
			}
		}
//判断变量是否为1,为1的话就是素数,然后进行相关操作
		if(isPrime==1){
			count++;
			sum += i;
		}
	}
	//这个区间内所有的素数和他们的和 
	printf("%d %d",count,sum);
	return 0;
} 

04-4. 猜数字游戏(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
猜数字游戏是令系统随机产生一个100以内的正整数,用户输入一个数对其进行猜测,需要你编写程序自动对其与随机产生的被猜数进行比较,并提示大了(“Too big”),还是小了(“Too small”),相等表示猜到了。如果猜到,则结束程序。程序还要求统计猜的次数,如果1次猜出该数,提示“Bingo!”;如果3次以内猜到该数,则提示“Lucky You!”;如果超过3次但是在N(>3)次以内(包括第N次)猜到该数,则提示“Good Guess!”;如果超过N次都没有猜到,则提示“Game Over”,并结束程序。如果在到达N次之前,用户输入了一个负数,也输出“Game Over”,并结束程序。

输入格式:

输入第一行中给出2个不超过100的正整数,分别是系统产生的随机数、以及猜测的最大次数N。随后每行给出一个用户的输入,直到出现负数为止。

输出格式:

在一行中输出每次猜测相应的结果,直到输出猜对的结果或“Game Over”则结束。

输入样例:
58 4
70
50
56
58
60
-2
输出样例:
Too big
Too small
Too small
Good Guess!

自己

#include <stdio.h>
int main(){
	int n,m;
	int count = 0;
	scanf("%d %d",&n,&m);
	int x;
	for(int i=i;i<100;i++){
		scanf("%d",&x);
		count ++;
		if(x<0){
			printf("Game Over\n");
			break;
		}
		if(x == n){
			if(count == 1){
				printf("Bingo!\n");
				break;
			}else if(count == 2 || count == 3){
				printf("Lucky You!\n");
				break;
			}else if(count > 3 && count <= m){
				printf("Good Guess!\n");
				break;
			}else if(count>m){
				printf("Game Over!\n");
				break;
			}
		}else if(x>n){
			printf("Too big\n");
		}else if(x<n){
			 printf("Too small\n");    
		}
	}
	return 0;
}

参考答案

 #include <stdio.h>
 
 int main()
 {
 	int n, times;
 	int i, t = 0;
 	scanf("%d %d", &n, &times);
 	while(1)
 	{
 		scanf("%d", &i);	
 	    ++t;
 		if(i == n && t == 1)
 		{
 			printf("Bingo!\n");
 			break;
		}
		else if(i < 0 || t > times )
		{
			printf("Game Over");
			break;
		}
		else if(i == n && t > 1 && t <= 3 && t <= times)
		{
			printf("Lucky You!\n");
			break;
		}
		else if(i > n)
		{
			printf("Too big\n");
		}
		else if(i < n && i > 0)
		{
			printf("Too small\n");
		}
		else if(i == n && t > 3 && t <= times)
		{
			printf("Good Guess!\n");
			break;		
		}
	}
 	
 	return 0;
}

05-0. 求序列前N项和(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
张彤彧(浙江大学)
本题要求编写程序,计算序列 2/1+3/2+5/3+8/5+… 的前N项之和。注意该序列从第2项起,每一项的分子是前一项分子与分母的和,分母是前一项的分子。

输入格式:

输入在一行中给出一个正整数N。

输出格式:

在一行中输出部分和的值,精确到小数点后2位。题目保证计算结果不超过双精度范围。

输入样例:
20
输出样例:
32.66

自己

#include <stdio.h>
int main(){
//输入格式:
//输入在一行中给出一个正整数N。
//输出格式:
//在一行中输出部分和的值,精确到小数点后2位。题目保证计算结果不超过双精度范围。
//输入样例:
//20
//输出样例:
//32.66
	int n;
	int a = 1;
	int b = 2;
	int c = 0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		c = b;
		double sum;
		sum += 1.0*b/a;
		b += a;
		a = c;
		printf("%.2f\n",sum);
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	int n = 1, i;
	double a = 2, b = 1, t, s = 0;
	scanf("%d", &n);
	for(i=1; i<=n; ++i)
	{
		t = a;
		s += a/b;
		a += b;
		b = t;
	}
	printf("%.2lf", s);
 
	return 0;
}

05-1. 约分最简分式(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
翁恺(浙江大学)
分数可以表示为“分子/分母”的形式。编写一个程序,要求用户输入一个分数,然后将其约分为最简分式。最简分式是指分子和分母不具有可以约分的成分了。如6/12可以被约分为1/2。当分子大于分母时,不需要表达为整数又分数的形式,即11/8还是11/8;而当分子分母相等时,仍然表达为1/1的分数形式。

输入格式:

输入在一行中给出一个分数,分子和分母中间以斜杠“/”分隔,如:12/34表示34分之12。分子和分母都是正整数(不包含0,如果不清楚正整数的定义的话)。

提示:在scanf的格式字符串中加入“/”,让scanf来处理这个斜杠。

输出格式:

在一行中输出这个分数对应的最简分式,格式与输入的相同,即采用“分子/分母”的形式表示分数。如5/6表示6分之5。

输入样例:
60/120
输出样例:
1/2

自己

#include <stdio.h>
int main(){
	//输入的数 
	int m,n;
	//比较俩数谁是最小的数 
	int min;
	//最大公约数 
	int ret;
	scanf("%d/%d",&m,&n);
	if(m>n){
		min = n;
	}else if(n>m){
		min = m;
	}
	for(int i = 1;i<=min;i++){
		if(m%i==0){
			if(n%i==0){
				ret = i;
			}
		}
	}
	printf("%d/%d",m/ret,n/ret);
	return 0;
}

参考答案

#include <stdio.h>

int zd(int a, int b){

    int k,t;

    while(b != 0){

        t = b;

        b = a % b;

        a = t;

    }

    return a;

}

int main(){

    int a,b,c;

    scanf("%d/%d",&a,&b);

    c = zd(a,b);

    printf("%d/%d",a/c,b/c);

}

05-2. 念数字(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
翁恺(浙江大学)
输入一个整数,输出每个数字对应的拼音。当整数为负数时,先输出“fu”字。十个数字对应的拼音如下:

0: ling
1: yi
2: er
3: san
4: si
5: wu
6: liu
7: qi
8: ba
9: jiu
输入格式:

输入在一行中给出一个整数,如:1234。

提示:整数包括负数、零和正数。

输出格式:

在一行中输出这个整数对应的拼音,每个数字的拼音之间用空格分开,行末没有最后的空格。如yi er san si。

输入样例:
-600
输出样例:
fu liu ling ling

自己

#include <stdio.h>
int main(){
	int x;	
	int i;
	int j;
	scanf("%d",&x);
	//判断正负 
	if(x<0){
		printf("fu ");
		i = -x;
		j = -x;
	}else{
		i = x;
		j = x;
	}
	//求x的位数
	int mask =1;
	while(i>9){
		i /= 10;
		mask *= 10;
	}
	while(mask > 0){
		int d = j/mask;
		if(d==1){
			printf("yi");
		}else if(d==2){
			printf("er");
		}else if(d==3){
			printf("san");
		}else if(d==4){
			printf("si");
		}else if(d==5){
			printf("wu");
		}else if(d==6){
			printf("liu");
		}else if(d==7){
			printf("qi");
		}else if(d==8){
			printf("ba");
		}else if(d==9){
			printf("jiu");
		}else if(d==0){
			printf("ling");
		}
		
		if(mask>9){
			printf(" ");
		}
		j %= mask;
		mask /= 10;
	}
//	printf("%d %d %d",x,i,mask);
	return 0;
}

参考答案

#include <stdio.h>
#include <string.h>
char str[20];
const char *num[] = {"ling", "yi", "er", "san", "si", "wu", "liu", "qi", "ba", "jiu"};
 
int main()
{
	int i, k;
	scanf("%s", str);
	k = strlen(str);
	if ( str[0] == '-') printf("fu");
	else 
		printf("%s", num[str[0]-'0']);
	for ( i=1; i<k; i++ )
	{
		printf(" %s", num[str[i]-'0']);
	}
	printf("\n");
 
	return 0;
}

05-3. 求a的连续和(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
翁恺(浙江大学)
输入两个整数a和n,a的范围是[0,9],n的范围是[1,8],求数列之和S = a+aa+aaa+…+aaa…a(n个a)。如a为2、n为8时输出的是2+22+222+…+22222222的和。

输入格式:

输入在一行中给出两个整数,先后表示a和n。

输出格式:

在一行中输出要求的数列之和。

输入样例:
2 4
输出样例:
2468

自己

#include <stdio.h>
int main(){
	int a,n;
	int x =0;
	a = 2;
	n = 4;
	int b=a;
	int sum=0;
	for(int i = 1;i<=n;i++){
		sum += b;
		b = b *10 +a;
	}	
	printf("%d",sum);
	return 0;
}

参考答案

#include <stdio.h>
#include <math.h>
 
int main()
{
	int a, n, i, s = 0;
	scanf("%d%d", &a, &n);
	for(i=1; i<=n; ++i)
	{
		s += (pow(10.0,i)-1) * a / 9;
	}
	printf("%d", s);
	
	return 0;
}

6.1.1 数据类型:C语言有哪些基础数据类型,sizeof可以做什么

C是有类型的语言

  • C语言的变量,必须:
    • 在使用前定义,并且
    • 确认类型
  • C以后的语言向俩个方向发展
    • C++/Java更强调类型,对类型的检查更严格
    • Javascript、Python、PHP不看重类型,甚至不需要事先定义

类型安全

  • 支持强类型的观点认为明确的类型有助于尽早发现程序中的简单错误
  • 反对强类型的观点认为过于强调类型迫使程序员面对底层、实现而非事务逻辑
  • 总的来说,早起语言强调类型,面向底层的语言强调类型
  • C语言需要类型,但是对类型的安全检查并不足够

C语言的类型

  • 整数
  • 浮点数
  • 逻辑
  • 指针
  • 自定义类型

红色的表示是C99的类型

类型有何不同

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

sizeof

  • 是一个运算符,给出某个类型或变量在内存中所占据的字节数
    • sizeof(i)
    • sizeof(int)
  • 是静态运算符,它的结果在编译时刻就决定了
  • 不要在sizeof的括号里做运算,这些运算不会做的

6.1.2 整数类型:除了int,还有多少整数类型

整数

  • char:1字节(8比特)
  • short:2字节
  • int:取决于编译器(CPU),通常的意义是”1个字“
  • long:取决于编译器(CPU),通常的意义是”1个字“
  • long long:8字节

20220707155401

6.1.3 整数的内部表达:整数是如何表达的,尤其是负数是如何表达的

*整数的内部表达

  • 计算机内部一切都是二进制
    • 18 —>00010010
    • 0 —> 00000000
    • -18 —>?

*如何表示负数

  • 十进制用”-“来表示负数,在做计算的时候

20220707155941

*二进制负数

  • 1个字节可以表达的数:
    • 00000000 - 11111111(0-255)
  • 三种方案
    1. 仿照十进制,有一个特殊的标志表示负数
    2. 取中间的数为0,如1000000表示0,比它小的是负数,比它大的是正数
    3. 补码

* 补码

  • 考虑-1,我们希望-1+1 —> 0。如何能做到?
    • 0---->00000000
    • 1---->10000000
    • 11111111 + 00000001 -----> 100000000
  • 因为0-1—> -1 ,所以,-1=
    • (1)00000000 - 00000001 ------> 11111111
    • 11111111被当作纯二进制看待时,是255 ,被当作补码看待时是-1
  • 同理,对于-a,其补码就是0-a,实际是2^n-a,n是这种类型的位数
    补码的意义就是拿补码和原码可以加出一个溢出的”零“

另一种解释(非老师提供,个人觉得讲解的不错)

在计算机中,负数以其正值的补码形式表达
什么叫补码呢?这得从原码,反码说起。
原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。
比如

 00000000 00000000 00000000 00000101

是 5的 原码。
反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。
取反操作指:原为1,得0;原为0,得1。(1变0; 0变1)
比如:将

00000000 00000000 00000000 00000101

每一位取反,得

11111111 11111111 11111111 11111010

称:

11111111 11111111 11111111 11111010

00000000 00000000 00000000 00000101

的反码。
反码是相互的,所以也可称:

11111111 11111111 11111111 11111010 

00000000 00000000 00000000 00000101 

互为反码。
补码:反码加1称为补码。
也就是说,要得到一个数的补码,先得到反码,然后将反码加上1,所得数称为补码。
比如:

00000000 00000000 00000000 00000101

的反码是:

11111111 11111111 11111111 11111010

那么,补码为:

11111111 11111111 11111111 11111010 + 1 
=
11111111 11111111 11111111 11111011

所以,-5 在计算机中表达为:

11111111 11111111 11111111 11111011

转换为十六进制:

0xFFFFFFFB

再举一例,我们来看整数-1在计算机中如何表示。
假设这也是一个int类型,那么:
1、先取1的原码:

00000000 00000000 00000000 00000001

2、得反码:

11111111 11111111 11111111 11111110

3、得补码:

11111111 11111111 11111111 11111111

可见,-1在计算机里用二进制表达就是全1。
16进制为:

0xFFFFFF

具体解析可以移步看此人博客负数的二进制表示

6.1.4 整数的范围:如何推算整数类型所能表达的数的范围,越界了会怎样

数的范围

  • 对于一个字节(8位),可以表达的是:
    • 00000000 - 11111111
  • 其中
    • 00000000 ——> 0
    • 11111111 ~ 10000000——>-1 ~ -128
    • 00000001 ~ 01111111——> 1 ~ 127

整数的范围

  • char:1字节:-128 ~ 127
  • short:2字节:-32768 ~ 32767
  • int:取决于编译器(CPU),通常的意义是”1个字“
  • long:4字节
  • long long:8字节

unsigned

  • 如果一个字面量常数想要表达自己是unsigned,可以在后面加u或者U
    • 255U
  • 用l或者L表示long(long)
  • *unsigned的初衷并非扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位

整数越界

  • 整数是以纯二进制方式进行运算的,所以
    • 11111111+1——>100000000——>0
    • 01111111+1——>10000000——>-128
    • 10000000-1——>01111111——>127

6.1.5 整数的格式化:如何格式化输入输出整数,如何处理8进制和16进制

整数的输入输出

  • 只有两种形式:int 或者 long long
    • %d : int
    • %u : unsigned
    • %ld : long long
    • %lu : unsigned long long

8进制和16进制

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

6.1.6 选择整数类型:没什么特殊需要就只用int就好了

选择整数类型

20220707170525

6.1.7 浮点类型:double和float,他们是什么,如何输入输出

浮点类型

类型 字长 范围 有效数字
float 32 ±(1.20 * 10-38 ~3.40 * 1038 ),0,±inf,nan 7
double 64 ±(2.20 * 10-308 ~1.79 * 10308 ),0,±inf,nan 15

浮点的输入输出

类型 scanf printf
float %f %f,%e
double %lf %f,%e

科学计数法

20220708103226

输出精度

  • 在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做四舍五入的
    • printf(“%.3f\n”,-0.0049);
    • printf(“%.30f\n”,-0.0049);
    • printf(“%.3f\n”,-0.00049);

6.1.8 浮点的范围与精度:浮点数到底能表达那些数

超过范围的浮点数

  • printf输出inf表示超过范围的浮点数:±∞
  • printf输出nan表示不存在的浮点数

浮点运算的精度

20220708104819

  • f1 == f2可能失败
  • fabs(f1-f2) < le-12

*浮点数的内部表达

20220708105224

选择浮点类型

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

6.1.9 字符类型:char是整数也是字符

字符类型

  • char是一种整数,也是一种特殊的类型:字符。这是因为:
    • 用单引号表示的字符字面量:‘a’,‘1’
    • ''也是一个字符
    • printf和scanf里用%c来输入输出字符

字符的输入输出

  • 如何输入’1’这个字符给char c
    • scanf(“%c”,&c); —> 1
    • scanf(“%d,&i”);c=i; —>49
  • ‘1’的ASCII编码是49,所以当c==49时,他代表’1’
    • printf(“%i %c\n”,c,c);
  • 一个49各自表述!

混合输入

  • 有何不同?
    • scanf(“%d %c”,&i,&c);
    • scanf(“%d%c”,&i,&c);

字符的计算

20220708111708

大小写转换

  • 字母在ASCII表中是顺序排列的
  • 大写字母和小写字母是分开排列的,并不是在一起
  • ‘a’-'A’可以得到俩段之间的距离,于是
    • a+‘a’-'A’可以把一个大写字母变成小写字母,而
    • a+‘A’-'a’可以把一个小写字母变成大写字母

6.1.10逃逸字符:反斜杠开头的字符是特殊的控制字符

逃逸字符

  • 用来表达无法印出来的控制字符或特殊字符,它由一个反斜杠""开头,后面跟着另一个字符,这俩个字符合起来,组成了一个字符
printf("请分别输入身高的英尺和英寸,"
	"如输入\“5 7\“表示5英尺7英寸");
字符 意义 字符 意义
\b 回退一格 " 双引号
\t 到下一个表格位 单引号
\n 换行 \ 反斜杠本身
\r 回车

制表位

20220708112948

6.1.11类型转换:如何在不同类型之间做转换

自动类型转换

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

强制类型转换

  • 要把一个量强制转换成另一个类型(通常是较小的类型),需要:
    • (类型)值
  • 比如 :
    • (int)10.2
    • (short)32
  • 注意这时候的安全性,小的变量不总能表达大的量
    • (short)32768

20220708114044

6.2.1 逻辑类型:表示关系运算和逻辑运算结果的量

bool

  • #include <stdbool.h>
  • 之后就可以使用bool和true、false

6.2.2 逻辑运算:对逻辑量进行与、或非运算

逻辑运算

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

TRY

  • 如果要表达数学中的区间,如:x∈(4,6)或者x属于[4,6],应该如何写C的表达式?

像4<x<6这样的式子,不是C能正确计算的式子,因为4<x的结果是一个逻辑值(0或者1)
x>4&&x<6
x>=4&&x<=6

  • 如何判断一个字符c是否是大写字母
    • c>=‘A’&&c<=‘Z’

理解一下

  • age>20&&age<30
  • index<0 || index>99
  • !age<20

优先级

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

短路

  • 逻辑运算是从左往右进行的,如果左边的结果已经能够决定结果了,就不能做右边的计算
    • a6 && b1
    • a==6 && b+=1
  • 对于&&,左边是false时就不做右边了
  • 对于||,左边是true时就不做右边了

不要把赋值,包括复合赋值组合进表达式!

6.2.3 条件运算与逗号运算

条件运算(三元运算符)

  • count=(count>20)?count -10:count
  • 条件、条件满足时的值和条件不满足时的值
if(count>20)
	count = count-10;
else
	count = count+10;

优先级

  • 条件运算符的优先级高于赋值运算符,但是低于其他运算符

m<n ? x : a+5
a++ >= 1 && b-- > 2 ? a : b
x=3 * a > 5 ? 5 : 20

嵌套条件表达式

  • count = (count>20)?(count<50)?count-10:count-5:(count<10)?count+10:count+5;
  • 条件运算符是自右向左结合的
    • w<x ? x+w : x<y ? x:y

我们不希望你使用嵌套的条件表达式

逗号运算

  • 逗号用来连接俩个表达式,并以其右边的表达式的值作为它的结果。逗号的优先级是所有的运算符中最低的,所以他两边的表达式会先计算;逗号的组合关系是自左向右,所以左边的表达式会先计算,而右边的表达式的值就留下来作为逗号运算的结果

在for中使用

  • for(i=0,j=10;i<j;i++,j–)…

6.3.1 习题练习

06-0. 混合类型数据格式化输入(5)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
乔林(清华大学)
本题要求编写程序,顺序读入浮点数1、整数、字符、浮点数2,再按照字符、整数、浮点数1、浮点数2的顺序输出。

输入格式:

输入在一行中顺序给出浮点数1、整数、字符、浮点数2,其间以1个空格分隔。

输出格式:

在一行中按照字符、整数、浮点数1、浮点数2的顺序输出,其中浮点数保留小数点后2位。

输入样例:
2.12 88 c 4.7
输出样例:
c 88 2.12 4.70

自己

#include <stdio.h>
int main(){
//	输入在一行中顺序给出浮点数1、整数、字符、浮点数2,其间以1个空格分隔。
//在一行中按照字符、整数、浮点数1、浮点数2的顺序输出,其中浮点数保留小数点后2位。
	char a;
	double c,d;
	int i;
	scanf("%lf %d %c %lf",&c,&i,&a,&d);
	printf("%c %d %.2f %.2f",a,i,c,d);
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
  float f1, f2;
  char ch;
  int i;
  scanf("%f %d %c %f", &f1, &i, &ch, &f2);
  printf("%c %d %.2f %.2f", ch, i, f1, f2);
  
  return 0;
}

06-1. 简单计算器(20)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
张彤彧(浙江大学)
模拟简单运算器的工作。假设计算器只能进行加减乘除运算,运算数和结果都是整数,4种运算符的优先级相同,按从左到右的顺序计算。

输入格式:

输入在一行中给出一个四则运算算式,没有空格,且至少有一个操作数。遇等号”=”说明输入结束。

输出格式:

在一行中输出算式的运算结果,或者如果除法分母为0或有非法运算符,则输出错误信息“ERROR”。

输入样例:
1+2*10-10/2=
输出样例:
10

自己的

#include <stdio.h>
int main(){
	char a = '0';
	int i,j;
	int b = 0;
	scanf("%d",&i);
	while(a != '='){
		scanf("%c",&a);
		if(a == '='){
			break;
		}
		scanf("%d",&j);
		if(a == '*'){
			i*=j;
		}else if(a == '+'){
			i+=j;
		}else if(a == '-'){
			i-=j;
		}else if(a == '/'){
			if(j == 0){
				b = 1;
			}else{
				i/=j;
			}
		}else{
			b = 1;
		}
		
	} 
	if(b){
		printf("ERROR\n");
	}else{
		printf("%d",i);
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	char ch = '0';
	int result, i, flag = 0;
	scanf("%d", &result);
	while( ch != '=' )
	{
		scanf("%c", &ch);
		if(ch == '=')
			break;
		scanf("%d", &i);
		if( ch == '+' )
			result += i;
		else if( ch == '-' )
			result -= i;
		else if( ch == '*' )
			result *= i;
		else if( ch == '/' )
		{
			if( i != 0 )
				result /= i;
			else
				flag = 1;
		}
		else
			flag = 1;
	}
	if(flag)
		printf("ERROR\n");
	else
		printf("%d", result);
		
	return 0;
}

06-2. 字符串字母大小写转换(10)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
张彤彧(浙江大学)
输入一个以#结束的字符串,本题要求将小写字母全部转换成大写字母,把大写字母全部转换成小写字母,其它字符不变。

输入格式:

输入在一行中给出一个长度不超过40的、以#结束的非空字符串。

输出格式:

在一行中按照要求输出转换后的字符串。

输入样例:
Hello World! 123#
输出样例:
hELLO wORLD! 123

自己

#include <stdio.h>
int main(){
	char a;
	scanf("%c",&a);
	while(a != '#'){
		if(a >= 'a' && a <= 'z'){
			a -= 32;
		}else if(a >= 'A' && a <= 'Z'){
			a += 32;
		}
		printf("%c",a);
		scanf("%c",&a);
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	char c;
	c = getchar();
	while(c != '#') 
	{
        if(c >= 'a' && c <= 'z')
		{
            c -= 32;  
        }
		else if(c >= 'A' && c <= 'Z')
		{
            c += 32;  
        }
        printf("%c", c);
        c = getchar();
	}
	
	return 0;
}

06-3. 单词长度(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
翁恺(浙江大学)
你的程序要读入一行文本,其中以空格分隔为若干个单词,以‘.’结束。你要输出每个单词的长度。这里的单词与语言无关,可以包括各种符号,比如“it’s”算一个单词,长度为4。注意,行中可能出现连续的空格;最后的‘.’不计算在内。

输入格式:

输入在一行中给出一行文本,以‘.’结束。

提示:用scanf(“%c”,…);来读入一个字符,直到读到‘.’为止。

输出格式:

在一行中输出这行文本对应的单词的长度,每个长度之间以空格隔开,行末没有最后的空格。

输入样例:

It’s great to see you here.
输出样例:

4 5 2 3 3 4

自己写的

#include <stdio.h>
int main() {
	char a;
	int c = 0;
	scanf("%c", &a);
	while (a != '.') {
		int b = sizeof(a);
		c += b;
		if(a == ' '){
			printf("%d", c-1);
			c = 0;
			printf("%c",' ');
		}
		
		scanf("%c", &a);
	}
	if(a == '.') {
		int b = sizeof(a);
		c += b;
		printf("%d", c-1);
		c = 0;
		scanf("%c", &a);
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
    char s[100];
    int i = 0;
    do 
	{
        scanf("%c", &s[i]);
        i++;
    } while(s[i-1] != '.');
    int j;
    int cnt = 0;
    int k = 0;
    for(j=0; j<i; j++) 
	{
        if(s[j] != ' ') 
		{
            cnt++;
            if(k != 0 && s[j] != '.') 
			{
                printf(" ");
                k = 0;
            }
            if(s[j] == '.' && s[j - 1] != ' ' && cnt != 1) 
			{
                printf("%d", cnt-1);
            }
        }
        else if(cnt != 0) 
		{
            printf("%d", cnt);
            cnt = 0;
            k = 1;
        }
    }
    
    return 0;
}

7.1.1 初见函数

素数求和

原先的代码

#include <stdio.h>
int main(){
	int m,n;
	scanf("%d %d",&m,&n);
	//素数不能为1,所以在区间内做修改 
	if(m==1){
		m=2; 
	}
	//count是计数,sum是和 
	int count = 0;
	int sum = 0;
	for(int i =m;i<=n;i++){
		int isPrime = 1;
		for(int j = 2;j<i;j++){
			if(i%j==0){
				isPrime = 0;
				break;
			}
		}
		if(isPrime == 1){
			count++;
			sum += i;
		}
	}
	printf("%d %d",count,sum);
	return 0;

改进后

#include <stdio.h>
int isPrime(int i){
	int ret = 1;
		for(int j = 2;j<i;j++){
			if(i%j==0){
				ret = 0;
				break;
			}
		}
	return ret;
} 
int main(){
	int m,n;
	scanf("%d %d",&m,&n);
	//素数不能为1,所以在区间内做修改 
	if(m==1){
		m=2; 
	}
	//count是计数,sum是和 
	int count = 0;
	int sum = 0;
	for(int i =m;i<=n;i++){
//		int isPrime = 1;
//		for(int j = 2;j<i;j++){
//			if(i%j==0){
//				isPrime = 0;
//				break;
//			}
//		}
		if(isPrime(i)){
			count++;
			sum += i;
		}
	}
	printf("%d %d",count,sum);
	return 0;
}

数字求和

  • 求出1到10、20到30、和35到45的和

“代码复制”是程序质量不良的表现

自己写的

#include <stdio.h>
int main(){
	Sum(1);
	Sum(20);
	Sum(35);
	return 0;
}
void Sum(int i){
	int sum = 0;
	for(int j = i;j<=i+10;j++){
		sum += j;
	}
	printf("%d\n",sum);
}

老师的参考答案

#include <stdio.h>
int main(){
	Sum(1,10);
	Sum(20,30);
	Sum(35,45);
	return 0;
}
void Sum(int begin,int end){
	int sum = 0;
	for(int i = begin;i<=end;i++){
		sum += i;
	}
	printf("%d\n",sum);
}

7.1.2 函数的定义和调用

什么是函数?

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

函数定义

void Sum(int begin,int end)
{
	int sum = 0;
	for(int i = begin;i<=end;i++){
		sum += i;
	}
	printf("%d\n",sum);
}

其中

void Sum(int begin,int end){

是函数头

sum

是函数名

void

返回类型

int begin,int end

参数表

int sum = 0;
for(int i = begin;i<=end;i++){
sum += i;
}
printf(“%d\n”,sum);

是函数体

调用函数

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

函数返回

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

7.1.3 从函数中返回

从函数中返回值

int max(int a,int b){
	int ret;
	if(a > b){
		ret = a;
	}else{
		ret = b;
	}
	return ret;
}
  • return停止函数的执行,并返回一个值
  • return;
  • return 表达式;
#include <stdio.h>
int max(int a,int b){
	int ret;
	if(a > b){
		ret = a;
	}else{
		ret = b;
	}
	return ret;
}
int main(){
	int a,b,c;
	a = 5;
	b = 6;
	c = max(10,12);
	c = max(a,b);
	c = max(c,23);
	c = max(max(c,a),5);
	printf("%d\n",max(a,b));
	return 0;
}
  • 可以赋值给变量
  • 可以再传递给函数
  • 甚至可以丢弃

没有返回值的函数

  • void函数名(参数表)
  • 不能使用带值的return
    • 可以没有return
  • 调用的时候不能做返回值的赋值

示例

void Sum(int begin,int end){
	int sum = 0;
	for(int i = begin;i<=end;i++){
		sum += i;
	}
	printf("%d\n",sum);
}

如果函数有返回值,则必须使用带值的return

7.2.1 函数原型:用来告诉编译器这个函数长什么样子

函数先后关系

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

如果不知道

  • 也就是把要调用的函数放在下面了
#include <stdio.h>
void Sum(int begin,int end);		//声明 
int main(){
	Sum(1,10);	//	int Sum(int,int) 
	Sum(20,30);
	Sum(35,45);
	return 0;
}
void Sum(int begin,int end){		//定义 
	int sum = 0;
	for(int i = begin;i<=end;i++){
		sum += i;
	}
	printf("%d\n",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",c);
	max(12,13);
	return 0;
}
double max(double a,double b){
	int ret;
	if(a > b){
		ret = a;
	}else{
		ret = b;
	}
	return ret;
}
  • 函数头,以分号“;”结尾,就构成了函数的原型
  • 函数原型的目的是告诉编译器这个函数长什么样
    • 名称
    • 参数(数量及类型)
    • 返回类型

double max(double a,double b);

函数原型

double max(double a,double b){

实际的函数头

max(12,13);

根据原型判断(且会发生自动类型转换)

  • 旧标准习惯把函数原型写在调用它的函数里面
  • 现在一般写在调用它的函数前面
  • 原型里可以不写参数的名字,但是一般仍然写上

7.2.2 参数传递:调用函数的时候,是用表达式的值来初始化函数的参数

调用函数

  • 如果函数有参数,调用函数时必须传递给它数量、类型正确的值
  • 可以传递给函数的值是表达式的结果,这包括
    • 字面量
    • 变量
    • 函数的返回值
    • 计算的结果

类型不匹配?

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

传过去的是什么?

#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;
}
  • 这样的代码能交换a和b的值吗?

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;
}
  • 每个函数有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系
  • 过去,对于函数参数表中的参数,叫做“形式参数”,调用函数时给的值,叫做“实际参数”

void swap(int a,int b);
void swap(int a,int b){
中的int a,int b

形式参数

swap(a,b);

实际参数

  • 由于容易让初学者误会实际参数就是实际在函数中进行计算的参数,误会调用函数的时候把变量而不是值传进去了,所以我们不建议继续用这种古老的方式来称呼他们
  • 我们认为,他们是参数和值的关系

void swap(int a,int b);
void swap(int a,int b){
中的int a,int b

参数

swap(a,b);

7.2.3 本地变量:定义在函数内部的变量是本地变量,参数也是本地变量

本地变量

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

变量的生存期和作用域

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

本地变量的规则

  • 本地变量是定义在块内的
    • 它可以是定义在函数的块内
    • 也可以定义在语句的块内
    • 甚至可以随便拉一对大括号来定义变量
  • 程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了
  • 块外面定义的变量在里面仍然有效
  • 块里面定义了和外面同名的变量则掩盖了外面的
    • 但是离开大括号后,输出外面的同名变量还是原先外面的同名变量的值
  • 不能在一个块内定义同名的变量
  • 本地变量不会被默认初始化
  • 参数在进入函数的时候被初始化了

7.2.4 函数庶事:一些有关函数的细节问题,main()是什么

其他细节

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

逗号运算符?

  • 调用函数时的逗号和逗号运算符怎么区分?
  • 调用函数时的圆括号里的逗号是标点符号,不是运算符
    • f(a,b) ———— 其中表示标点符号
    • f((a,b)) ———— 其中表示运算符

函数里的函数?

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

这是什么?

  • int i,j,sum(int a,int b);
  • return (i);

关于main

  • int main()也是一个函数
  • 要不要写成int main(void)?
  • return的0有人看吗?
    • Windows:if errorlevel 1…
    • Unix Bash:echo $?
    • Csh:echo $status

7.3.1 习题训练

07-0. 写出这个数 (20)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
CHEN, Yue
读入一个自然数n,计算其各位数字之和,用汉语拼音写出和的每一位数字。

输入格式:每个测试输入包含1个测试用例,即给出自然数n的值。这里保证n小于10100。

输出格式:在一行内输出n的各位数字之和的每一位,拼音数字间有1空格,但一行中最后一个拼音数字后没有空格。

输入样例:
1234567890987654321123456789
输出样例:
yi san wu

自己

#include <stdio.h> 
int main(){
	char n;
	int sum = 0;
	int cnt = 1;
	scanf("%c",&n);
	//计算sum的值 
	while(n != '\n' ){
		if(n == '1'){
			sum += 1;
		}else if(n == '2'){
			sum += 2;
		}else if(n == '3'){
			sum += 3;
		}else if(n == '4'){
			sum += 4;
		}else if(n == '5'){
			sum += 5;
		}else if(n == '6'){
			sum += 6;
		}else if(n == '7'){
			sum += 7;
		}else if(n == '8'){
			sum += 8;
		}else if(n == '9'){
			sum += 9;
		}else if(n == '0'){
			sum += 0;
		}
		scanf("%c",&n);
	}
//	printf("%d\n",sum);
	//计算sum的位数 
	int i = sum;
	int j = 0;
	while(j<i){
		i /= 10;
		cnt ++;
	}
	i = sum;
	cnt = cnt -1;
//	printf("%d\n",cnt);
	//逆序sum的值 
	int digit;
	int ret = 0;
	while(i>0){
		digit = i%10;
		ret = ret*10 + digit;
//		printf("x=%d,digit=%d,ret=%d\n",i,digit,ret);
		i /= 10;
	}
//	printf("%d\n",ret);
	i = ret;
//	得到sum每一位的数,并输出
	for(int x = 0;x<cnt;x++){
		int d = 0;
		d = i%10;
		//判断每一位数的值 
		if(d == 1){
			char a,b;
			a = 'y';
			b = 'i';
			printf("%c%c ",a,b);
		}else if(d == 2){
			char a,b;
			a = 'e';
			b = 'r';
			printf("%c%c ",a,b);
		}else if(d == 3){
			char a,b,c;
			a = 's';
			b = 'a';
			c = 'n';
			printf("%c%c%c ",a,b,c);
		}else if(d == 4){
			char a,b;
			a = 's';
			b = 'i';
			printf("%c%c ",a,b);
		}else if(d == 5){
			char a,b;
			a = 'w';
			b = 'u';
			printf("%c%c ",a,b);
		}else if(d == 6){
			char a,b,c;
			a = 'l';
			b = 'i';
			c = 'u';
			printf("%c%c%c ",a,b,c);
		}else if(d == 7){
			char a,b;
			a = 'q';
			b = 'i';
			printf("%c%c ",a,b);
		}else if(d == 8){
			char a,b;
			a = 'b';
			b = 'a';
			printf("%c%c ",a,b);
		}else if(d == 9){
			char a,b,c;
			a = 'j';
			b = 'i';
			c = 'u';
			printf("%c%c%c ",a,b,c);
		}else if(d == 0){
			char a,b,c,d;
			a = 'l';
			b = 'i';
			c = 'n';
			d = 'g';
			printf("%c%c%c%c ",a,b,c,d);
		}
//		printf("%d\n",d);
		i /= 10;
	}

	return 0;
}

参考答案

#include <stdio.h>
#include <string.h>
 
const char *num[] = {"ling", "yi", "er", "san", "si", "wu", "liu", "qi", "ba", "jiu"};
 
int main()
{
	char ch, number[10];
	int s = 0, i;
	while( (ch = getchar()) != '\n' )
	{
		s += ch-'0';
	}
	sprintf(number, "%d", s);
	printf("%s", num[number[0]-'0']);
	for ( i=1; i<strlen(number); i++ )
	{
		printf(" %s", num[number[i]-'0']);
	}
	printf("\n");
 
	return 0;
}

07-1. 换个格式输出整数 (15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
CHEN, Yue
让我们用字母B来表示“百”、字母S表示“十”,用“12…n”来表示个位数字n(<10),换个格式来输出任一个不超过3位的正整数。例如234应该被输出为BBSSS1234,因为它有2个“百”、3个“十”、以及个位的4。

输入格式:每个测试输入包含1个测试用例,给出正整数n(<1000)。

输出格式:每个测试用例的输出占一行,用规定的格式输出n。

输入样例1:
234
输出样例1:
BBSSS1234
输入样例2:
23
输出样例2:
SS123

自己

#include <stdio.h>
int main(){
	int n;
	int S = 0;
	int B = 0;
	scanf("%d",&n);
	if(n>=1 && n<1000){
		//小于十的时候遍历 
		if(n>=1 && n<10){
			for(int i = 1;i<=n;i++){
				printf("%d",i);
			}
		}
		//十位数的时候
		if(n>=1 && n<100){
			S = n/10;
			for(int i = 1;i<=S;i++){
				printf("%c",'B');
			}
			n = n%10;
//			printf("%d",n);
			for(int i = 1;i<=n;i++){
				printf("%d",i);
			}
		} 
		//百位数
		if(n>=1 && n<1000){
			B = n/100;
			for(int i = 1;i<=B;i++){
				printf("%c",'S');
			}
			S = n%100/10;
			for(int i = 1;i<=S;i++){
				printf("%c",'B');
			}
			n = n%10;
			for(int i = 1;i<=n;i++){
				printf("%d",i);
			}
		} 
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	int i, j, B, S;
	scanf("%d", &i);
	if(i >= 1 && i < 1000) 
	{
		if(i >= 1 && i < 10) 
		{
			for(j=1; j<=i; ++j) 
			{
				printf("%d", j);
			}
		}
		if(i >= 10 && i < 100) 
		{
			S = i / 10;
			for(j=1; j<=S; ++j) 
			{
				printf("S");
			}
			i = i % 10;
			for(j=1; j<=i; ++j) 
			{
				printf("%d", j);
			}
		}
		if(i >= 100 && i < 1000) 
		{
			B = i / 100;
			for(j=1; j<=B; ++j) 
			{
				printf("B");
		    }
			S = i /10 % 10;
			for(j=1; j<=S; ++j) 
			{
				printf("S");
			}
			i = i % 10;
			for(j=1; j<=i; ++j) 
			{
				printf("%d", j);	
		    }
		}
	}
	
	return 0;
}

07-2. A+B和C (15)

时间限制
50 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
HOU, Qiming
给定区间[-231, 231]内的3个整数A、B和C,请判断A+B是否大于C。

输入格式:

输入第1行给出正整数T(<=10),是测试用例的个数。随后给出T组测试用例,每组占一行,顺序给出A、B和C。整数间以空格分隔。

输出格式:

对每组测试用例,在一行中输出“Case #X: true”如果A+B>C,否则输出“Case #X: false”,其中X是测试用例的编号(从1开始)。

输入样例:
4
1 2 3
2 3 4
2147483647 0 2147483646
0 -2147483648 -2147483647
输出样例:
Case #1: false
Case #2: true
Case #3: true
Case #4: false

自己

#include <stdio.h>
int main(){
	int a;
	long x,y,z;
	scanf("%d",&a);
	if(a>0 && a<=10){
		for(int i = 1;i<=a;i++){
			scanf("%ld %ld %ld",&x,&y,&z);
			if((x+y)>z){
				printf("Case #%d: true",i);
			}else{
				printf("Case #%d: false",i);
			}
		}
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int main()
{
	int t, i;
	long a, j, k;
	scanf("%d", &t);
	if(t >= 1 && t <= 10) 
	{
		for(i=1; i<=t; ++i) 
		{
			scanf("%ld%ld%ld", &a, &j, &k);
			if(a + j > k)
			{
				printf("Case #%d: true\n", i);
			}
			else 
			{
				printf("Case #%d: false\n", i);
			}
		}
	}
	
	return 0;
}

07-3. 数素数 (20)

时间限制
100 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
CHEN, Yue
令Pi表示第i个素数。现任给两个正整数M <= N <= 104,请输出PM到PN的所有素数。

输入格式:

输入在一行中给出M和N,其间以空格分隔。

输出格式:

输出从PM到PN的所有素数,每10个数字占1行,其间以空格分隔,但行末不得有多余空格。

输入样例:
5 27
输出样例:
11 13 17 19 23 29 31 37 41 43
47 53 59 61 67 71 73 79 83 89
97 101 103

自己

#include <stdio.h>
int main() {
	int m, n;
	scanf("%d %d", &m, &n);
	int i, j;
	//count用来统计区间内有多少个素数
	int count = 0;
	for (i = 2; count <=n; i++) {
		int isPrime = 1;
		for (j = 2; j < i; j++) {
			if (i % j == 0) {
				isPrime = 0;
				break;
			}
		}
		if (isPrime == 1) {
			count++;
		}
		if (count >= m && count <= n && isPrime == 1) {
			printf("%d\t", i);
			if (count % 10 == 0) {
				printf("\n");
			}else if (count != n) {
				printf(" ");
			}
		}
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int a[10001];
 
int main()
{
	int i, c = 0, j, p, m, n;
	a[0] = 2;
	for ( i=3; ;i+=2 )
	{
		p = 1;
		for ( j=2; j*j<=i; j++ )
		{
			if ( i % j == 0 ) 
			{
				p = 0;
				break;
			}
		}
		if ( p )
		{
			a[++c] = i;
		}
		if ( c == 10000 ) break;
	}
	c = 0;
	scanf("%d %d", &m, &n);
	for ( i=m; i<=n; i++ )
	{
		c++;
		printf("%d", a[i-1]);
		if ( c % 10 == 0 )
			printf("\n");
		else if ( i < n )
			printf(" ");
	}
	if ( c % 5 != 0 )
		printf("\n");
	
	return 0;
}

8.1-1 初试数组

  • 如何写一个程序计算用户输入的数字的平均数
#include <stdio.h>
int main(){
	int number;
	int sum = 0;
	int count = 0;
	scanf("%d",&number);
	while(number != -1){
		sum += number;
		count ++;
		scanf("%d",&number);
	}
	printf("%f\n",1.0*sum/count);
	return 0;
}
  • 不需要记录输入的每一个数

  • 如何写一个程序计算用户输入的数字的平均数,并输出所有大于平均数的数

如何记录很多数

  • int num1,num2,num3…?

数组

  • int number[100];
  • scanf(“%d”,&number[i]);
#include <stdio.h>
int main(){
	int number;
	int sum = 0;
	int count = 0;
	int x[100];
	scanf("%d",&number);
	while(number != -1){
		x[count] = number;
		//
		{
			int i;
			printf("%d\t",count);
			for(i=0;i<=count;i++){
				printf("%d\t",x[i]);
			}
			printf("\n");
		}
		//
		sum += number;
		count ++;
		scanf("%d",&number);
	}
	if(count>0){
		printf("%f\n",1.0*sum/count);
		for(int i = 0;i<count;i++){
			if(x[i] > sum/count){
				printf("%d\n",x[i]);
			}
		}
	}

	return 0;
}
	int x[100];

定义数组

 x[count] = number;

对数组中的元素赋值

	if(x[i] > sum/count){
		printf("%d\n",x[i]);

使用数组中的元素

for(int i = 0;i<count;i++){
	if(x[i] > sum/count){
		printf("%d\n",x[i]);
	}
}

遍历数组

这个程序存在安全隐患,是什么?

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

定义数组

  • <类型>变量名称[元素数量];
    • int grades[100];
    • double weight[20];
  • 元素数量必须是整数
  • C99之前:元素数量必须是编译时刻确认的字面量

数组

  • 是一个容器(放东西的东西),特点是:
    • 其中所有的元素具有相同的数据类型;
    • 一旦创建,不能改变大小
    • *(数组中的元素在内存中是连续依次排序的)

int a[10]

20220713162009

数组的单元

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

有效的下标范围

  • 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写
  • 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃
    • segmentation fault
  • 但是也可能运气好,没造成严重的后果
  • 所以这是程序员的责任来保证程序只使用有效的下标值:[0,数组的大小-1]
#include <stdio.h>
int main(){
	int number;
	int sum = 0;
	int count = 0;
	scanf("%d",&number);
	while(number != -1){
		sum += number;
		count ++;
		scanf("%d",&number);
	}
	printf("%f\n",1.0*sum/count);
	return 0;
}
  • 这个程序是危险的,因为输入的数据可能超过100个

计算平均数

  • 如果先让用户输入有多少数字要计算,可以用C99的新功能来实现
#include <stdio.h>
int main(){
	int number;
	int sum = 0;
	int count;
	printf("请输入数字的数量:");
	scanf("%d",&count);
	if(count>0){
		int x[count];
		printf("%d",&number); 
		while(number != -1){
			x[count] = number;
			sum += number;
			count ++;
			scanf("%d",&number);
		}
		printf("%f\n",1.0*sum/count);
	}

	return 0;
}

长度为0的数组

  • int a[0];
  • 可以存在,但是无用

8.1-3 数组的例子:统计个数

统计个数

  • 写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-1表示结束
#include<stdio.h>
int main(){
	const int number = 10;
	int x;
	int i;
	int count[number];
	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;
} 

分析程序

const int number = 10;

数组的大小C99

int count[number];

定义数组

for(i = 0;i<number;i++){
	count[i]=0;
}

初始化数组

count[x]++;

数组参与运算

for(i = 0;i<number;i++){
	printf("%d:%d\n",i,count[i]);
}

遍历数组输出

8.2-1 数组运算

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

数组的集成初始化

int a[] = {2,4,6,7,1,3,5,9,11,13,23,14,32}
  • 直接用大括号给出数组的所有元素的初始值
  • 不需要给出数组的大小,编译器替你数数

集成初始化时的定位

int a[10] = {
	[0]=2,[2]=3,6,
};

C99 ONLY!

  • 用[n]在初始化数据中给出定位
  • 没有定位的数据接在前面的位置后面
  • 其他位置的值补零
  • 也可以不给出数组大小,让编译器算
  • 特别适合初始数据稀疏的数组

数组的大小

  • sizeof给出整个数组所占据的内容的大小,单位是字节
    老师想说的是字节!!!
sizeof(a)/sizeof(a[0])
  • sizeof(a[0])给出数组中单个元素的大小,于是相除就得到了数组的单元个数
  • 这样的代码,一旦修改数组的初始的数据,不需要修改遍历的代码

数组的赋值

int a[] = {2,4,6,7,1,3,5,9,11,13,23,14,32};
int b[] = a; //不可以这么写
  • 数组变量本身不能被赋值
  • 要把一个数组的所有元素交给另一个数组,必须采用遍历
for(i=0;i<length;i++){
	b[i]=a[i];
}

遍历数组

20220714152127

搜索程序

20220714152329

数组作为函数参数时,往往必须再用另一个参数来传入数组的大小

  • 数组作为函数的参数时
    • 不能在[]中给出数组的大小
    • 不能再利用sizeof来计算数组的元素个数!

8.2-2 数组例子:素数

判断素数

#include <stdio.h>
int isPrime(int x);
int main(){
	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遍

去掉偶数后,从3到x-1,每次加2

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遍

无须到x-1,到sqrt(x)就够了

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)遍

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

int isPrime(int x,int knownPrimes[],int numberOfKnownPrimes);
int main(){
	const int number = 100;
	int prime[100] = {[0]=2,};
	int count = 1;
	int i = 3;
	while(count < number){
		if(isPrime(i,prime,count)){
			prime[count++] = i;
		}
		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;x * i <n;i++)令prime[i * x]=0
  4. 令x++,如果x<n,则重复3,否则结束
#include <stdio.h>
int main(){
	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 二维数组

二维数组

  • int a[3][5];
  • 通常理解为a是3行5列的矩阵
a[0][0] a[0][1] a[0][2] a[0][3] a[0][4]
a[1][0] a[1][1] a[1][2] a[1][3] a[1][4]
a[2][0] a[2][1] a[2][2] a[2][3] a[2][4]

二维数组的遍历

for(int i = 0;i<3;i++){
	for(int j =0;j<5;j++){
		a[i][j] = i*j;
	}
}
  • a[i][j]是一个int
  • 表示第i行第j列上的单元
    • a[i,j]是什么?
    • a[i,j] 其中的 i,j 是一个表达式,结果是j
    • 所以a[i,j]的意思是a[j]

二维数组的初始化

int a[][5] = {
	{0,1,2,3,4},
	{2,3,4,5,6},
};
  • 列数是必须给出的,行数可以由编译器来数
  • 每行一个{},逗号分离
  • 最后的逗号可以存在,有古老的传统
  • 如果省略,表示补零
  • 也可以用定位(*C99 ONLY)

tic-tac-toe游戏

20220714163728

读入矩阵

	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){
				result = 1;
			}
		}
	} 

行和列?

20220714165102

检查对角线

20220714165116

8.3 练习

08-0. 查找整数(10)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
杨起帆(浙江大学城市学院)
本题要求从输入的N个整数中查找给定的X。如果找到,输出X的位置(从0开始数);如果没有找到,输出“Not Found”。

输入格式:

输入在第1行中给出2个正整数N(<=20)和X,第2行给出N个整数。数字均不超过长整型,其间以空格分隔。

输出格式:

在一行中输出X的位置,或者“Not Found”。

输入样例1:
5 7
3 5 7 1 9
输出样例1:
2
输入样例2:
5 7
3 5 8 1 9
输出样例2:
Not Found

自己

#include <stdio.h>

int main(){
	int N,X;	
	//N是个数 , X是查找的整数
	int i;
	int a[20];
	//N <= 20
	scanf("%d %d",&N,&X);
	for(i = 0;i<N;i++){
		scanf("%d",&a[i]);	
	}  
	for(i = 0;i<N;i++){
		if(a[i] == X){
			printf("%d\n",i);
			break;
		}	
	}  
	
	if(i == N){
		printf("Not Found\n");
	}
	return 0;
} 

参考答案

#include <stdio.h>
#define N 1000
 
int a[N];
 
int main()
{
	int n, key, i;
	scanf("%d%d", &n, &key);
	for ( i=0; i<n; i++ ) 
	{
		scanf("%d", &a[i]);
	}
	for ( i=0; i<n; i++ )
	{
		if ( a[i] == key )
		{
			printf("%d\n", i);
			break;
		}
	}
	if ( i == n )
	{
		printf("Not Found\n");
	}
	
	return 0;
}

08-1. 求一批整数中出现最多的个位数字(20)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
徐镜春(浙江大学)
给定一批整数,分析每个整数的每一位数字,求出现次数最多的个位数字。例如给定3个整数1234、2345、3456,其中出现最多次数的数字是3和4,均出现了3次。

输入格式:

输入在第1行中给出正整数N(<=1000),在第2行中给出N个不超过整型范围的正整数,数字间以空格分隔。

输出格式:

在一行中按格式“M: n1 n2 …”输出,其中M是最大次数,n1、n2、……为出现次数最多的个位数字,按从小到大的顺序排列。数字间以空格分隔,但末尾不得有多余空格。

输入样例:
3
1234 2345 3456
输出样例:
3: 3 4

自己

#include <stdio.h>
int main(){
	int N;
	//N是数量个数
	int i;
	int M;
	//M是输入的数字 
	int max = 0;
	//max是最大次数
	int a[10] ={0};
	//定义数组以及给数组的值都付为0,用于计算每个个位数出现的次数 
	scanf("%d",&N);
	for(i = 0;i<N;i++){
		scanf("%d",&M);
		while(M>0){
			a[M%10]++;
			M/=10;
		}
	} 
	for(i = 0;i < 10;i++ ){
		if(max < a[i]){
			max = a[i];
		}
	}
	printf("%d:",max);
	for(i = 0;i<10;i++){
		if(max == a[i]){
			printf("%d ",i);
		}
	}
	return 0;
}

参考答案

#include <stdio.h>
#include <string.h>
 
int a[1002];
 
int main()
{
	int t, i, max = 0;
	int count[10];
	scanf("%d", &t);
	for ( i=0; i<10; i++ )
	{
		count[i] = 0;
	}
	for ( i=0; i<t; i++ )
	{
		scanf("%d", &a[i]);
	}
	for ( i=0; i<t; i++ )
	{
		while (a[i])
		{
			count[a[i]%10]++;
			a[i] /= 10;
		}
	}
	for ( i=0; i<10; i++ )
	{
		if ( max < count[i] )
		{
			max = count[i];
		}
	}
	printf("%d:", max);
	for ( i=0; i<10; i++ )
	{
		if ( count[i] == max )
		{
			printf(" %d", i);
		}
	}
	printf("\n");
	
	return 0;
}

08-2. 求矩阵的局部极大值(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
徐镜春(浙江大学)
给定M行N列的整数矩阵A,如果A的非边界元素A[i][j]大于相邻的上下左右4个元素,那么就称元素A[i][j]是矩阵的局部极大值。本题要求给定矩阵的全部局部极大值及其所在的位置。

输入格式:

输入在第1行中给出矩阵A的行数M和列数N(3<=M,N<=20);最后M行,每行给出A在该行的N个元素的值。数字间以空格分隔。

输出格式:

每行按照“元素值 行号 列号”的格式输出一个局部极大值,其中行、列编号从1开始。要求按照行号递增输出;若同行有超过1个局部极大值,则该行按列号递增输出。若没有局部极大值,则输出“None 总行数 总列数”。

输入样例1:
4 5
1 1 1 1 1
1 3 9 3 1
1 5 3 5 1
1 1 1 1 1
输出样例1:
9 2 3
5 3 2
5 3 4
输入样例2:
3 5
1 1 1 1 1
9 3 9 9 1
1 5 3 5 1
输出样例2:
None 3 5

自己

#include <stdio.h>
int main(){
	int a[4][21] = {0};
	//因为M<=3,N<=20
	int M,N;
	int i,j;
	//M是行数,N是列数 
	int isMax = 0;
	//判断是否有局部较大值 
	scanf("%d %d",&M,&N); 
	//初始化
	for(i = 0;i<M;i++){
		for(j = 0;j<N;j++){
			scanf("%d",&a[i][j]);
		}
	} 
//	printf("%d",a[M][N]);
	//遍历数组 
	for(i = 1;i<M-1;i++){
		for(j =1;j<N-1;j++){
//			printf("%d",a[i][j]);
			if(a[i][j]>a[i-1][j] && a[i][j]>a[i+1][j] && a[i][j] > a[i][j+1] && a[i][j] > a[i][j-1]){
				printf("%d %d %d\n",a[i][j],i+1,j+1);
				isMax = 1;
			}
		}
	}
	if(isMax == 0){
		printf("None %d %d\n",M,N);
	}
	return 0;
} 

参考答案

#include <stdio.h>
 
int a[21][21];
 
int main()
{
	int m, n, i, j, p = 0;
	scanf("%d %d", &m, &n);
	for ( i=0; i<m; i++ )
	{
		for ( j=0; j<n; j++ )
		{
			scanf("%d", &a[i][j]);
		}
	}
	for ( i=1; i<m-1; i++ )
	{
		for ( j=1; j<n-1; j++ )
		{
			if ( a[i][j] > a[i][j+1] && a[i][j] > a[i][j-1] && 
			    a[i][j] > a[i+1][j] && a[i][j] > a[i-1][j] ) 
			{
		    	printf("%d %d %d\n", a[i][j], i+1, j+1);
		    	p = 1;
			}
		}
	}
	if ( !p )
	{
		printf("None %d %d\n", m, n);
	}
	
	return 0;
}

08-3. 组个最小数 (20)

时间限制
100 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
CAO, Peng
给定数字0-9各若干个。你可以以任意顺序排列这些数字,但必须全部使用。目标是使得最后得到的数尽可能小(注意0不能做首位)。例如:给定两个0,两个1,三个5,一个8,我们得到的最小的数就是10015558。

现给定数字,请编写程序输出能够组成的最小的数。

输入格式:

每个输入包含1个测试用例。每个测试用例在一行中给出10个非负整数,顺序表示我们拥有数字0、数字1、……数字9的个数。整数间用一个空格分隔。10个数字的总个数不超过50,且至少拥有1个非0的数字。

输出格式:

在一行中输出能够组成的最小的数。

输入样例:
2 2 0 0 0 3 0 0 1 0
输出样例:
10015558

自己

#include <stdio.h>
int main(){
	int a[10] = {0};
	//初始化数组
	int i;
	//给出0-9的个数值 
	for(i = 0;i<10;i++){
		scanf("%d",&a[i]);
	}
//对0的位数进行处理,如果有0,将后面个数不为0的数输出一个,并且该数个数减一
	for(i=1;i<10;i++){
		if(a[i]!=0){
			break;
		} 
	}
	printf("%d",i);//输出第一位数
	a[i]--;
	for(i = 0;i<10;i++){
		//计算剩余位,直接输出
		while(a[i]!=0){
			printf("%d",i);
			a[i]--;
		}
	}
	return 0;
}

参考答案

#include <stdio.h>
 
int b[10];
 
int main()
{
	int i, j;
	for ( i=0; i<10; i++ )
	{
		b[i] = 0;
	}
	for ( i=0; i<10; i++ )
	{
		scanf("%d", &b[i]);
	}
	for ( i=1; i<10; i++ )
	{
		if (b[i])
		{
			printf("%d", i);
			b[i]--;
			break;
		}
	}
	for ( i=0; i<10; i++ )
	{
		for ( j=0; j<b[i]; j++ )
		{
			printf("%d", i);
		}
	}
	printf("\n");
	
	return 0;
}

9.1-1 取地址运算:&运算符取得变量的地址

运算符&

  • scanf(“%d”,&i);里的&
  • 获取变量的地址,它的操作数必须是变量
    • int i;
    • printf(“%x”,&i);
  • 地址的大小是否与int相同取决于编译器
    • int i;
    • printf(“%p”,&i);

&不能取得地址

  • 不能对没有地址的东西取地址
    • &(a+b)?
    • &(a++)?
    • &(++a)?

试试这些东西

  • 变量的地址
  • 相邻的变量的地址
  • &的结果的sizeof
  • 数组的地址
  • 数字单元的地址
  • 相邻的数组单元的地址

9.1-2 指针:指针变量就是记录地址的变量

scanf

  • 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量?
    • scanf(“%d”,&i);
  • scanf()的原型应该是怎样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?

指针

  • 就是保存地址的变量
    int i;
    int* p = &i;
    int* p ,q;
    int *p ,q;

指针变量

20220715154418

作为参数的指针

  • void f(int *p);
  • 在被调用的时候得到了某个变量的地址:
    • int i = 0;
    • f(&i);
  • 在函数里面可以通过这个指针访问外面的这个i

访问那个地址上的变量*

    • 是一个单目运算符,用来访问指针的值所表示的地址上的变量
  • 可以做右值也可以做左值
    • int k = *p;
    • *p = k+1;

* 左值之所以叫左值

  • 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果:
    • a[0] = 2;
    • *p = 3;
  • 是特殊的值,所以叫做左值

指针的运算符&*

  • 互相反作用
    • *&yptr -> *(&yptr) -> *(yptr的地址) ->得到那个地址上的变量 -> yptr
    • &*yptr -> &(*yptr) -> &(y) -> 得到y的地址,也就是yptr -> yptr

传入地址

  • 为什么
    • int i;
    • scanf(“%d”,i);
  • 编译没有报错?

9.1-3 指针的使用:指针有什么用呢?

指针应用场景一

  • 交换俩个变量的值
void swap(int *pa,int *pb){
	int t = *pa;
	*pa = *pb;
	*pb = t;    
}

指针应用场景二

  • 函数返回多个值,某些值就只能通过指针返回
    • 传入的参数实际上是需要保存带回的结果的变量

指针应用场景二b

  • 函数返回运算的状态,结果通过指针返回
  • 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:
    • -1或0(在文件操作会看到大量的例子)
  • 但是当任何数值都是有效的可能结果时,就得分开返回了
    • 后续的语言(C++、Java)采用了异常机制来解决这个问题

指针最常见的错误

  • 定义了指针变量,还没有指向任何变量,就开始使用指针

9.1-4 指针与数组:为什么数组传进函数后的sizeof不对了

传入函数的数组成了什么?

20220715163159

数组参数

  • 以下四种函数原型是等价的:
    • int sum(int *ar,int n);
    • int sum(int *,int );
    • int sum(int ar[],int n);
    • int sum(int [],int );

数组变量是特殊的指针

  • 数组变量本身表示地址,所以
    • int a[10];
    • int *p = a;//无需用&取地址
    • 但是数组的单元表达的是变量,需要用&取地址
    • a == &a[0]
  • []运算符可以对数组做,也可以对指针做:
    • p[0] <==> a[0]
  • *运算符可以对指针做,也可以对数组做:
    • *a = 25;
  • 数组变量是const的指针,所以不能被赋值
    • int a [] <==> int * const a = …

9.1-5 指针与const:指针本身和所指的变量都可能const

指针和const

20220715164418

指针是const

  • 表示一旦得到了某个变量的地址,不能再指向其他变量
    • int *const q = &i;//q 是 const
    • *q = 26; // 🆗
    • q++;//ERROR

所指是const

  • 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
    • const int *p = &i;
    • *p = 26;//ERROR! (*p)是const
    • i = 26; //OK
    • p = &j; //OK

这些是啥意思?

int i;
const int *p1 = &i;
int const *p2 = &i;
int *const p3 = &i;

判断那个被const了的标志是const在*的前面还是后面
const在前面表示变量的值不能被修改
const在后面表示指针的值不能被修改

转换

  • 总是可以把一个非const的值转换成const的

void f(const int *x);
int a = 15;
f(&a);//OK
const int b = a;

f(&b);//OK
b = a+1; // Error!

  • 当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改

const数组

  • const int a[] = {1,2,3,4,5,6,};
  • 数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
  • 所以必须通过初始化进行赋值

保护数组值

  • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
  • 为了保护数组不被函数破坏,可以设置参数为const
    • int sum(const int a[],int lengh);

9.2-1 指针运算

1+1=2?

  • 给一个指针加1表示要让指针指向下一个变量
    int a[10];
    int *p = a;
    *(p+1) —> a[1]
  • 如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义

指针计算

  • 这些算数运算可以对指针做:
    • 给指针加、减一个整数(+,-,+=,-=)
    • 递增递减(++/–)
    • 俩个指针相减

*p++

  • 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
  •   *  的优先级虽然高,但是没有++高
    
  • 常用于数组类的连续空间操作
  • 在某些CPU上,这可以直接被翻译成一条汇编指令

指针比较

  • <,<=,==,>,>=,!= 都可以对指针做
  • 比较他们在内存中的地址
  • 数组中的单元的地址肯定是线性递增的

0地址

  • 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  • 所以你的指针不应该具有0值
  • 因此可以用0地址来表示特殊的事情:
    • 返回的指针是无效的
    • 指针没有被真正初始化(先初始化为0)
  • NULL是一个预定定义的符号,表示0地址
    • 有的编译器不愿意你用0来表示0地址

指针的类型

  • 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
  • 但是指向不同类型的指针是不能直接互相赋值的
  • 这是为了避免用错指针

指针的类型转换

  • void* 表示不知道指向什么东西的指针
    • 计算时与char* 相同(但不相通)
  • 指针也可以转换类型
    • int *p = &i;
    • void q = (void)p;
  • 这并没有改变p所指向的变量的类型,而是让后人用不同的眼光通过p看它所指向的变量
    • 我不再当你是int啦,我认为你就是个void

用指针来做什么

  • 需要传入较大的数据时用作参数
  • 传入数组后对数组做操作
  • 函数返回不止一个结果
    • 需要用函数来修改不止一个变量
  • 动态申请的内存

9.2-2 动态内存分配

输入数据

  • 如果输入数据时,先告诉你个数,然后再输入,要记录每个数据
  • C99可以用变量做数组的定义的大小,C99之前呢?
  • int a =(int)malloc(n*sizeof(int));

malloc

#include <stdlib.h>
void* malloc(size_t size);

  • 向malloc申请的空间的大小是以字节为单位的
  • 返回的结果是void* ,需要类型转化为自己需要的类型
    • (int* )malloc(n*sizeof(int))

没空间了?

  • 如果申请失败则返回0,或者叫做NULL
  • 你的系统能给你多大的空间?

free()

  • 把申请得来的空间还给“系统”
  • 申请过的空间,最终都应该要还
    • 混出来的,迟早都是要还的
  • 只能还申请来的空间的首地址

常见问题

  • 申请了没free——> 长时间运行内存逐渐下降
    • 新手:忘了
    • 老手:找不到合适的free的时机
  • free过了再free
  • 地址变过了,直接去free

10.1-1 字符串

字符数组

  • char word[] = {‘H’,‘e’,‘l’,‘l’,‘o’,‘!’};
    | word[0] | H |
    | ------- | — |
    | word[1] | e |
    | word[2] | l |
    | word[3] | l |
    | word[4] | o |
    | word[5] | ! |

这不是C语言的字符串,因为不能用字符串的方式来计算

字符串

  • char word[] = {‘H’,‘e’,‘l’,‘l’,‘o’,‘!’,‘\0’};
    | word[0] | H |
    | ------- | — |
    | word[1] | e |
    | word[2] | l |
    | word[3] | l |
    | word[4] | o |
    | word[5] | ! |
    | word[6] | \0 |

  • 以0(整数0)结尾的一串字符

    • 0或者’\0’是一样的,但是和’0’不同
  • 0标志字符串的结束,但是它不是字符串的一部分

    • 计算字符串长度的时候不包括这个0
  • 字符串以数组的形式存在,以数组或指针的形式访问

    • 更多的是以指针的形式
  • string.h 里有很多处理字符串的函数

字符串变量

  • char *str = “Hello”;
  • char word[] = “Hello”;
  • char line[10] = “Hello”;

字符串常量 (字符串字面量)

  • “Hello”
  • “Hello”会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还会表示结束的0
  • 俩个相邻的字符串常量会被自动连接起来
    • printf(“请分别输入身高的英尺和英寸,”
    •   "如输入\"5 7\"表示5英尺7英寸")
      
    • 其中两行的"" ""字符串会自动连接起来
    • printf("请分别输入身高的英尺和英寸,\
    • 如输入"5 7"表示5英尺7英寸")
    • 效果一样

字符串

  • C语言的字符串是以字符数组的形式存在的
    • 不能用运算符对字符串做运算
    • 通过数组的方式可以遍历字符串
  • 唯一特殊的地方是字符串字面量可以用来初始化字符数组
  • 以及标准库提供了一系列字符串函数

10.1-2 字符串变量

字符串常量

char *s = “Hello,world!”;

  • s是一个指针,初始化为指向一个字符串常量
    • 由于这个常量所在的地方,所以实际上s是const char * s,但是由于历史的原因,编译器接受不带const的写法
    • 但是试图对s所指的字符串做写入会导致严重的后果
  • 如果需要修改字符串,应该用数组:
    char s[] = “Hello,world!”;

指针还是数组?

  • char *str= “Hello” ;
  • char word[] = “Hello”;
  • 数组:这个字符串在这里
    • 作为本地变量空间自动被回收
  • 指针:这个字符串不知道在哪里
    • 处理参数
    • 动态分配空间
  • 如果要构造一个字符串——>数组
  • 如果要处理一个字符串——>指针

char* 是字符串?

  • 字符串可以表达为char* 的形式
  • char* 不一定是字符串
    • 本意是指向字符的指针,可能指向的是字符的数组(就像int* 一样)
    • 只有它所指的字符数组有结尾的0,才能说它所指的是字符串

10.1-3 字符串输入输出

字符串赋值?

  • char *t = “title”;
  • cahr *s;
  • s = t;
  • 并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的如何操作就是对t做的

字符串输入输出

  • char string[8];

  • scanf(“%s”,string);

  • printf(“%s”,string);

  • scanf读入一个单词(到空格、tab或回车为止)

  • scanf是不安全的,因为不知道要读入的内容的长度

安全的输入

  • char string[8];

  • scanf(“%7s”,string);

  • 在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一

    • 下一次scanf从哪里开始?

常见错误

  • char *string ;
  • scanf(“%s”,string);
  • 以为char* 是字符串类型,定义了一个字符串类型的变量string就可以直接使用了
    • 由于没有对string初始化为0,所以不一定每次运行都出差

空字符串

  • char buffer[100] = “”;
    • 这是一个空的字符串,buffer[0]==”\0”;
  • char buffer[] = “”;
    • 这个数组的长度只有1!

10.1-4 字符串数组,以及程序参数

字符串数组

  • char a**
    • a是一个指针,指向另一个指针,那个指针指向一个字符(串)
    • 所以他不是我们要的字符串数组
  • char a[][]

程序参数

  • int main(int argc,char const *argv[])
  • argv[0]是命令本身
    • 当使用Unix的符号链接时,反映符号链接的名字

10.2-1 单字符输入输出,用putchar和getchar

putchar

  • int putchar(int c);
  • 向标准输出写一个字符
  • 返回写了几个字符,EOF(-1)表示失败

getchar

  • int getchar(void);
  • 从标准输入读入一个字符
  • 返回类型是int是为了返回EOF(-1)
    • Windows——>Ctrl-Z
    • Unix——>Ctrl-D

10.2-2 字符串函数strlen

string.h

  • strlen
  • strcmp
  • strcpy
  • strcat
  • strchr
  • strstr

#include <string.h>

strlen

  • size_t strlen(const char *s);
  • 返回s的字符串长度(不包括结尾的0)

10.2-3 字符串函数strcmp

strcmp

  • int strcmp(const char *s1,const char *s2);
  • 比较俩个字符串,返回:
    • 0 : s1 == s2
    • 1 : s1 > s2
    • -1: s1 < s2

10.2-4 字符串函数strcpy

strcpy

  • char * strcpy(char * restrict dst,const char * restrict src);
  • 把src的字符串拷贝到dst
    • restrict表明src和dst不重叠(C99)
  • 返回dst
    • 为了能链起代码来

复制一个字符串

char * dst = (char * )malloc(strlen(src)+1);
strcpy(dst,src);

10.2-5 字符串函数strcat

strcat

  • char * strcat(char * restrict s1, const char * restrict s2);
  • 把s2拷贝到s1的后面,接成一个长的字符串
  • 返回s1
  • s1i必须具有足够的空间

安全问题

  • strcpy和strcat都可能出现安全问题
    • 如果目的地没有足够的空间?

安全版本

  • char * strncpy(char * restrict dst,const char * restrict src,size_t n);
  • char * strncat(char * restrict s1,const char * restrict s2,size_t n);
  • int strncmp(const char * s1,const char * s2,size_t n);

10.2-6 字符串搜索函数

字符串中找字符

  • char * strchr(const char * s,int c);

  • char * strrchr(const char * s,int c);

  • 返回NULL表示没有找到

  • 如何寻找第2个?

字符串中找字符串

  • char * strstr(const char * s1,const char * s2);
  • char * strcasestr(const char * s1,const char * s2);

10.3 练习

10.3.0 说反话 (20)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
CHEN, Yue
给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出。

输入格式:测试输入包含一个测试用例,在一行内给出总长度不超过80的字符串。字符串由若干单词和若干空格组成,其中单词是由英文字母(大小写有区分)组成的字符串,单词之间用1个空格分开,输入保证句子末尾没有多余的空格。

输出格式:每个测试用例的输出占一行,输出倒序后的句子。

输入样例:
Hello World Here I Come
输出样例:
Come I Here World Hello

自己

#include<stdio.h>
#include<string.h>
int main(){
	//总长度不超过80的字符串
	char str[90];
	//获得数组
	gets(str);
	//定义一个二维数组来接收单词 	
	char a[90][90];
	//line是行号,column是列 
	int line = 0;
	int column = 0;
	//len是str的长度 
	int len = strlen(str);
	for(int i = 0;i<len;i++){
		if(str[i] != ' '){
			//把单词放在数组a里面,当str[i]不是空格时,令列++ 
			a[line][column++] = str[i]; 
		}else{
			//如果收到空格时,说明这个单词收入完成,
			//结束这一行,并进入下一行,让列初始化 
			a[line][column] = '\0';
			line++;
			column = 0;

		}
	}
	for(int i = line;i>=0;i--){
		//输出 
		printf("%s",a[i]);
		if(i>0){
			printf(" ");
		}

	}
	return 0;
}

参考答案

#include <stdio.h>
#include <string.h>
 
char str[81];
 
int main()
{
	int i, k;
	char *p;
	gets(str);
	k = strlen(str);
	p = str + k;
	while (1)
	{
		if ( p == str )
		{
			printf("%s\n", p);
			break;
		}
		if (*p == ' ' && *(p+1) != ' ')
		{
			*p = '\0';
			printf("%s ", p+1);
		}
		p--;
	}
	return 0;
}

10.3.1 在字符串中查找指定字符(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
白洪欢(浙江大学)
输入一个字符串S,再输入一个字符c,要求在字符串S中查找字符c。如果找不到则输出“Not found”;若找到则输出字符串S中从c开始的所有字符。

输入格式:

输入在第1行中给出一个不超过80个字符长度的、以回车结束的非空字符串;在第2行中给出一个字符。

输出格式:

在一行中按照题目要求输出结果。

输入样例1:
It is a black box
b
输出样例1:
black box
输入样例2:
It is a black box
B
输出样例2:
Not found

自己

#include <string.h>
#include <stdio.h>
	
int main(){
	//给出一个不超过80个字符长度的、以回车结束的非空字符串
	char str[180];
	//给出一个字符
	char ch;
	//len是str的长度 ,i是循环 ,p来判断是否找到 
	int len,i,p = 0; 	
	//输入数组 
	gets(str);
	len = strlen(str);
	//输入字符 
	scanf("%c",&ch);
	//遍历数组 
	for(i = 0;i < len;i++){
		//如果str数组的i个元素和ch字符相同 
		if(str[i] == ch){
			//使判断器p等于1 
			p = 1;
		}
		//如果判断器p等于1,那么打印出str数组剩余元素 
		if( p ){
			printf("%c",str[i]);
		}
	}
	//如果判断器为0,说明没找到,打印NOT FOUND,不然打印换行 
	if( !p ){
		printf("Not found\n");
	}else{
		printf("\n");
	}
	return 0;
}

参考答案

#include <stdio.h>
#include <string.h>
 
int main()
{
    char str[80];
    char chr;
    int flag = 0;
    
    gets(str);
    scanf("%c", &chr);
    
    int i = 0;
    while ( str[i] != '\0' ) {
        if ( str[i] == chr ) {
            while( str[i] != '\0' ) {
                printf("%c", str[i]);
                i++;
            }
            flag = 1;
            break;
        }
        i++;
    }
    
    if ( !flag ) {
        printf("Not found\n");
    }
    
    return 0;
}

10.3.2 删除字符串中的子串(20)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
白洪欢(浙江大学)
输入2个字符串S1和S2,要求删除字符串S1中出现的所有子串S2,即结果字符串中不能包含S2。

输入格式:

输入在2行中分别给出不超过80个字符长度的、以回车结束的2个非空字符串,对应S1和S2。

输出格式:

在一行中输出删除字符串S1中出现的所有子串S2后的结果字符串。

输入样例:
Tomcat is a male ccatat
cat
输出样例:
Tom is a male

自己

#include<stdio.h>
#include<string.h>
 
////Tomcat is a male ccatat
////cat

int main(void){
	char S1[81];
	gets(S1);
	char S2[81];
	gets(S2);
	
	char *p;
	char S3[81];
	//strstr 找不到返回 NULL
	while((p=strstr(S1,S2)) != NULL){
		// 指定连接下一步(连接函数)之前 S1 的终止位置; 
		*p = '\0';
		 // strcat 函数中的两个传入参数的内存地址不能重叠,所以这里用 S3 当作 temp 
		strcpy(S3,p+strlen(S2));
		strcat(S1,S3);
	}
	puts(S1);
	return 0;
}

参考答案

#include <stdio.h>
#include <string.h>
 
char s1[81], s2[81];
 
int main()
{
	int i, k;
	gets(s1);
	gets(s2);
	char *p = strstr(s1, s2);
	while(1)
	{
		char *p = strstr(s1, s2);
		if ( p )
		{
			for ( i=0; i<strlen(p) - strlen(s2); i++ )
			{
				p[i] = p[strlen(s2) + i];
			}
			p[i] = '\0';
	    }
	    else
		{
	    	puts(s1);
	    	break;
		}
	}
	
	return 0;
}

10.3.3 字符串逆序(15)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
白洪欢(浙江大学)
输入一个字符串,对该字符串进行逆序,输出逆序后的字符串。

输入格式:

输入在一行中给出一个不超过80个字符长度的、以回车结束的非空字符串。

输出格式:

在一行中输出逆序后的字符串。

输入样例:
Hello World!
输出样例:
!dlroW olleH

自己

#include <stdio.h>
#include <string.h>
//Hello World!
//!dlroW olleH
int main(){
	//不超过80个字符长度的、以回车结束的非空字符串。
	char str[81];
	//输入数组 
	gets(str);
	//获得数组长度 
	int len = strlen(str);
	//以为数组最后一个是'\0'所以 要len-1 获取输入的最后一个元素 
	len -= 1;
	//从字符串len-1个元素开始循环遍历 
	for(len;len>=0;len--){
		printf("%c",str[len]);
	}
	printf("\n");
	return 0;
}

参考答案

#include <stdio.h>
#include <string.h>
 
int main()
{
	char str[81];
	int k;
	gets(str);
	k = strlen(str);
	k -= 1;
	for ( k; k>=0; k-- )
	{
		printf("%c", str[k]);
	}
	printf("\n");
 
	return 0;
}

10.3.4 字符串循环左移(20)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
白洪欢(浙江大学)
输入一个字符串和一个非负整数N,要求将字符串循环左移N次。

输入格式:

输入在第1行中给出一个不超过100个字符长度的、以回车结束的非空字符串;第2行给出非负整数N。

输出格式:

在一行中输出循环左移N次后的字符串。

输入样例:
Hello World!
2
输出样例:
llo World!He

自己

#include <stdio.h>
#include <string.h>
//Hello World!
//2
//llo World!He
int main(){
	//给出一个不超过100个字符长度的、以回车结束的非空字符串
	char str[101];
	gets(str); 
	//其中i,j,k是循环变量,N是左移次数,len是字符串长度 
	int i,j,k,N,len;
	scanf("%d",&N);
	len = strlen(str);
	//临时变量 
	char temp; 
	//遍历 
	for(i = 1;i<=N;i++){
		//len-1去掉'\0' 
		for(j=0;j<len-1;j++){
			temp = str[j];
			str[j] = str[j+1];
			str[j+1] = temp;
		}
	}
	for(k=0;k<len;k++)
	{
		printf("%c",str[k]);
	}
	return 0;
}

参考答案

#include <stdio.h>
#include <string.h>
 
char str[103];
 
int main()
{
	int i, k, n;
	gets(str);
	k = strlen(str);
	scanf("%d", &n);
	if ( n % k == 0 )
		puts(str);
	else
	{
		for ( i=n%k; i<k; i++ )
		{
			printf("%c", str[i]);
		}
		for ( i=0; i<n%k; i++ )
		{
			printf("%c", str[i]);
		}
		printf("\n");
	}
	
	return 0;
}

11.1-1 枚举

常量符号化

20220801151354

枚举

20220801151415





  • 枚举是一种用户定义的数据类型,他用关键字enum以如下语法来声明:
    • enum枚举类型名字{名字0,…,名字n};
  • 枚举类型名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是常量符号,它们的类型是int,值则依次从0到n。如:
    • enum colors{red,yellow,green};
  • 就创建了三个常量,red的值是0,yellow的值是1,而green的值是2.
  • 当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值名字。
#include <stdio.h>

enum color{
	red,yellow,green
};
void f(enum color c);
int main(void){
	enum color t = red;
	scanf("%d",&t);
	f(t);
	return 0;
}
void f(enum color c){
	printf("%d\n",c);
}
  • 枚举量可以作为值
  • 枚举类型可以跟上enum作为类型
  • 但是实际上是以整数来做内部计算和外部输入输出的

小套路:自动计数的枚举

20220801153459

枚举量

  • 声明枚举量的时候可以指定值
  • enum COLOR{RED = 1, YELLOW , GREEN = 5};
#include <stdio.h>
enum COLOR{RED = 1, YELLOW , 	GREEN = 5};
int main(int argc,char const *argv[]){
	printf("code for GREEN is %d\n",GREEN);
	return 0;
}

枚举只是int

#include <stdio.h>
enum COLOR{RED = 1, YELLOW , 	GREEN = 5, NumCOLORs};
int main(int argc,char const *argv[]){
	enum COLOR color = 0;
	printf("code for GREEN is %d\n",GREEN);
	printf("and color is %d\n",color);
	return 0;
}

  • 即使给枚举类型的变量赋不存在的整数值也没有任何warning或者error

枚举

  • 虽然枚举类型可以当作类型使用,但是实际上很(bu)少(hao)用
  • 如果有意义上排比的名字,用枚举比const int方便
  • 枚举比宏(macro)好,因为枚举有int类型

11.2-1 结构类型

声明结构类型

#include <stdio.h>
int main(int argc,char const *argv[]){
	struct date{
		int month;
		int day;
		int year;
	};
	struct date today;
	today.month = 07;
	today.day = 31;
	today.year = 2014;
	printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day);
	return 0;	
}

初学者注意别忘记struct大括号后面的;号

在函数内/外?

#include <stdio.h>
struct date{
	int month;
	int day;
	int year;
};
int main(int argc,char const *argv[]){

	struct date today;
	today.month = 07;
	today.day = 31;
	today.year = 2014;
	printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day);
	return 0;	
}
  • 和本地变量一样,在函数内部声明的结构类型只能在函数内部使用
  • 所以通常在函数外部声明结构类型,这样就可以被多个函数所使用了

声明结构的形式

方法一

struct point{
int x;
int y;
};

struct point p1,p2;

p1和p2都是point
里面有x和y的值

方法二

struct {
int x;
int y;
}p1,p2;

p1 和p2都是一种无名结构,里面有x和y

方法三

struct point{
int x;
int y;
}p1,p2;

p1和p2都是point
里面有x和y的值

对于第一和第三种形式,都声明了结构point。但是第二种没有声明point,只是定义了俩个变量

结构变量

20220801160434

结构的初始化

#include <stdio.h>
struct date{
	int month;
	int day;
	int year;
};
int main(int argc,char const *argv[]){

	struct date today = {07,31,2014};
	struct date thismonth = {.month = 7,.year = 2014};
	printf("Today's date is %i-%i-%i.\n",today.year,today.month,today.day);
	printf("This month's date is %i-%i-%i.\n",thismonth.year,thismonth.month,thismonth.day);
	return 0;	
}

结构成员

  • 结构和数组有点像
  • 数组用[]运算符和下标访问其成员
    • a[0] = 10;
  • 结构用 . 运算符和名字访问其成员
    • today.day
    • student.firstName
    • p1.x
    • p1.y

结构运算

  • 要访问这个结构,直接用结构变量的名字
  • 对于整个结构,可以做赋值、取地址,也可以传递给函数参数

数组无法做这俩种运算

结构指针

  • 和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符
  • struct date *pDate = &today;

11.2-2 结构与函数

结构作为函数参数

int numberOfDays(struct date d)
  • 整个结构可以作为参数的值传入函数
  • 这时候是在函数内新建一个结构变量,并复制调用者的结构的值
  • 也可以返回一个结构
  • 这与数组完全不同

输入结构

  • 没有直接的方式可以一次scanf一个结构
  • 如果我们打算写一个函数来读入结构
    • ↓↓↓↓
      20220801163555
  • 但是读入的结构如何送回来呢?
  • 记住C在函数调用时是传值的
    • 所以函数中的p与main中的y是不同的
    • 在函数读入了p的数值之后,没有任何东西回到main,所以y还是

解决的方案

  • 之前的方案,把一个结构传入了函数,然后再函数中操作,但是没有返回回去
    • 问题在于传入函数的是外面那个结构的克隆体,而不是指针
    • 传入结构和传入数组是不同的
  • 在这个输入函数中,完全可以创造一个临时的结构变量,然后把这个结构返回给调用者
    20220801164350

结构指针作为参数

20220801164550

指向结构的指针

struct date{
	int month;
	int day;
	int year;
}myday;

struct date *p = &myday;

(*p).month = 12;
p->month = 12;
  • 用->表示指针所指的结构变量中的成员

结构指针参数

20220801165211

11.2-3 结构中的结构

结构数组

struct date dates[100];
struct date dates[] = {
	{4,5,2005},{2,4,2005}
};

结构中的结构

struct dateAndTime{
	struct date sdate;
	struct time stime;    
};

嵌套的结构

struct point{
	int x;
	int y;
};

struct rectangle{
	struct point pt1;
	struct point pt2
};

如果有变量
struct rectangle r;
就可以有:
r.pt1.x、r.pt1.y,
r.pt2.x和r.pt2.y


如果有变量定义
struct rectangle r,*rp;
rp = &r;

那么下面的四种形式是等价的:
r.pt1.x
rp->pt1.x
(r.pt1).x
(rp->pt1).x
但是没有rp->pt1->x(因为pt1不是指针)

结构中的结构的数组

20220802154854

11.3-1 类型定义

自定义数据类型(typedef)

  • C语言提供了一个叫做typedef的功能来声明一个已有的数据类型的新名字。比如:
    • typedef int Length;
    • 使得Length成为int类型的别名
  • 这样,Length这个名字就可以代替int出现在变量定义和参数声明的地方了:
    • Length a,b,len;
    • Length numbers[10];

Typedef

声明新的类型的名字

  • 新的名字是某种类型的别名
  • 改善了程序的可读性
typedef long int64_t;	//重载已有的类型名字 新名字的含义更清晰 具有可移植性
typedef struct ADate{
	int month;
	int day;
	int year;
}Date;			//简化了复杂的名字

int64_t i = 100000000000;
Date d = {9,1,2005};
typedef struct{
	int month;
	int day;
	int year;
}Date;
typedef int Length;	//Length就等价于int类型
typedef *char[10] Strings;	//Strings是10个字符串的数组的类型
typedef struct node{
	int data;
	struct node *next;
}aNode;
//或者
typedef struct node aNode;	//这样用aNode就可以代替struct node

11.3-2 联合

联合

20220802161108

union 的用处

#include <stdio.h>
typededf union{
	int i;
	char ch[sizeof(int)];
}CHI;
int main(int argc,char const *argv[]){
	CHI chi;
	int i;
	chi.i = 1234;
	for(i = 0; i<sizeof(int); i++){
		printf("%02hhX",chi.ch[i]);
	}   
	printf("\n");
    
	return 0;
}

11.4 练习

11.4-0 平面向量加法(10)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
乔林(清华大学)
本题要求编写程序,计算两个二维平面向量的和向量。

输入格式:

输入在一行中按照“x1 y1 x2 y2”的格式给出两个二维平面向量V1=(x1, y1)和V2=(x2, y2)的分量。

输出格式:

在一行中按照“(x, y)”的格式输出和向量,坐标输出小数点后1位(注意不能输出-0.0)。

输入样例:
3.5 -2.7 -13.9 8.7
输出样例:
(-10.4, 6.0)

自己

#include <stdio.h> 
struct xy{
	double x;
	double y;
};
int main(){
	struct xy a,b,c;
	scanf("%lf %lf %lf %lf",&a.x,&a.y,&b.x,&b.y);
	c.x = a.x + b.x;
	c.y = a.y + b.y;
	if(c.x<0&&c.x>-0.05){
		c.x=0.0;
	} 
	if(c.y<0&&c.y>-0.05){
		c.y=0.0;
	}
	printf("(%.1lf,%.1lf)",c.x,c.y);
	return 0;
}

参考答案

#include<stdio.h>
#include<math.h>
int main(void)
{
	double x1, y1, x2, y2;
	double x, y;

	scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);

	x = x1 + x2;
	y = y1 + y2;

	
	// 小数部分输出一位小数,进行四舍五入处理,若结果为-0.01至-0.04,则结果为-0.0,故需取其绝对值
	if (fabs(x) < 0.05)
	{
		x = fabs(x);   // 浮点数的绝对值函数为fabs()
	}

	if (fabs(y) < 0.05)
	{
		y = fabs(y);
	}

	printf("(%.1f, %.1f)\n", x, y);

	return 0;
}

11.4-1 通讯录的录入与显示(10)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
8000 B
判题程序
Standard
作者
乔林(清华大学)
通讯录中的一条记录包含下述基本信息:朋友的姓名、出生日期、性别、固定电话号码、移动电话号码。本题要求编写程序,录入N条记录,并且根据要求显示任意某条记录。

输入格式:

输入在第1行给出正整数N(<=10);随后N行,每行按照格式“姓名 生日 性别 固话 手机”给出一条记录。其中“姓名”是不超过10个字符、不包含空格的非空字符串;生日按“yyyy/mm/dd”的格式给出年月日;性别用“M”表示“男”、“F”表示“女”;“固话”和“手机”均为不超过15位的连续数字,前面有可能出现“+”。

在通讯录记录输入完成后,最后一行给出正整数K,并且随后给出K个整数,表示要查询的记录编号(从0到N-1顺序编号)。数字间以空格分隔。

输出格式:

对每一条要查询的记录编号,在一行中按照“姓名 固话 手机 性别 生日”的格式输出该记录。若要查询的记录不存在,则输出“Not Found”。

输入样例:
3
Chris 1984/03/10 F +86181779452 13707010007
LaoLao 1967/11/30 F 057187951100 +8618618623333
QiaoLin 1980/01/01 M 84172333 10086
2 1 7
输出样例:
LaoLao 057187951100 +8618618623333 F 1967/11/30
Not Found

自己

#include <stdio.h>
struct Contacts{
	char name[11];
    char dates[20];
    char sex[2];
    char landline[17];
    char mobile[17];
};
int main(void){
	int x;
	int y;
	int z;
	struct Contacts person[10];
	//输入有几条信息 
	scanf("%d",&x);
	//输入每一条person信息 
	for(int i=0;i<x;i++){
		scanf("%s %s %c %s %s",&person[i].name,&person[i].dates,&person[i].sex,&person[i].landline,&person[i].mobile);
	}
	//查找第几条信息 
	scanf("%d",&y);
	//遍历 
	for(int j=0;j<y;j++){
		scanf("%d",&z);
		if(z>=0&&z<x){
			printf("%s %s %s %c %s\n", person[z].name, person[z].landline, person[z].mobile, person[z].sex[0], person[z].dates);
		}else{
			printf("Not Found\n");
		}
	}
	return 0;
}

参考答案

#include <stdio.h>
#define N 10
 
struct Person {
	char name[11];
	char birthday[11];
	char sex;           
	char fixed[17];     
	char mobile[17];     
};
 
int main(void)
{
	struct Person p[N];
	int num[N];
	int i, n, k;
	scanf("%d", &n);
	for(i = 0; i < n; ++i)
		scanf("%s %s %c %s %s", p[i].name, p[i].birthday, 
		      &p[i].sex, p[i].fixed, p[i].mobile);
	scanf("%d", &k);
	for(i = 0; i < k; ++i)
		scanf("%d", &num[i]);
	for(i = 0; i < k; ++i) {
		if(num[i] >= 0 && num[i] < n)
			printf("%s %s %s %c %s\n", p[num[i]].name, 
			       p[num[i]].fixed, p[num[i]].mobile, p[num[i]].sex, p[num[i]].birthday);
		else
			printf("Not Found\n");
	}
	return 0;
}

12.1-1 全局变量:定义在函数之外的变量,全局的生存期和作用域

全局变量

  • 定义在函数外面的变量是全局变量
  • 全局变量具有全局的生存期和作用域
    • 它们与任何函数都无关
    • 在任何函数内部都可以使用它们

全局变量初始化

  • 没有做初始化的全局变量会得到0值
    • 指针会得到NULL值
  • 只能用编译时刻已知的值来初始化全局变量
  • 它们初始化发生在main函数之前

被隐藏的全局变量

  • 如果函数内部存在与全局变量同名的变量,则全局变量被隐藏

12.1-2 静态本地变量:能在函数结束后继续保有原值的本地变量

静态本地变量

  • 在本地变量定义时加上static修饰符就成为静态本地变量

  • 当函数离开的时候,静态本地变量会继续存在并保持其值

  • 静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值

  • 静态本地变量实际上是特殊的全局变量

  • 它们位于相同的内存区域

  • 静态本地变量具有全局的生存期,函数内的局部作用域

    • static在这里的意思是局部全局作用域(本地可访问)

12.1-3 全局变量贴士:返回指针的函数,使用全局变量的贴士

*返回指针的函数

  • 返回本地变量的地址是危险的
  • 返回全局变量或静态本地变量的地址是安全的
  • 返回在函数内malloc的内存是安全的,但是容易造成问题
  • 最好的做法是返回传入的指针

tips

  • 不要使用全局变量来在函数间传递参数和结果
  • 尽量避免使用全局变量
    • 丰田汽车的案子
    • 使用全局变量和静态本地变量的函数是线程不安全的

12.2-1 宏定义

编译预处理指令

  • #开头的是编译预处理指令
  • 它们不是C语言的成分,但是C语言程序离不开它们
  • #define用来定义一个宏

#define

  • #define <名字> <值>
  • 注意没有结尾的分号,因为不是C的语句
  • 名字必须是一个名词,值可以是各种东西
  • 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值
    • 完全的文本替换
  • gcc – save-temps

  • 如果一个宏的值中有其他的宏的名字,也是会被替换的
  • 如果一个宏的值超过一行,最后一行之前的行末需要加\
  • 宏的值后面出现的注释不会被当作宏的值的一部分

没有值的宏

  • #define _DEBUG
  • 这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了

预定义的宏

· __LINE__
· __FILE__
· __DATE__
· __TIME__
· __STOC__

12.2-2 带参数的宏

像函数的宏

  • #define cube(x) ((x) * (x) * (x))
  • 宏可以带参数

错误定义的宏

  • #define RADTODEG(x) (x * 57.29578)
  • #define RADTODEG(x) (x) * 57.29578

带参数的宏的原则

  • 一切都要括号
    • 整个值要括号
    • 参数出现的每个地方都要括号
  • #define RADTODEG(x) ((x) * 57.29578)

带参数的宏

  • 可以带多个参数
    • #define MIN(a,b) ((a) > (b) ? (b) : (a))
  • 也可以组合(嵌套)使用其他宏

分号?

#define PRETTY_PRINT(msg) printf(msg);
注意定义宏的手最后不需要分号

if(n < 10)
	PRETTY_PRINT("n is less than 10");
else
	PRETTY_PRINT("n is at least 10");

带参数的宏

  • 在大型程序的代码中使用非常普遍
  • 可以非常复杂,如“产生”函数
    • 在#和## 这俩个运算符的帮助下
  • 存在中西方文化差异
  • 部分宏会被inline函数替代

其他编译预处理指令

  • 条件编译
  • error
  • 。。。

12.3-1 大程序(多个源代码文件)

多个.c文件

  • main()里的代码太长了适合分成几个函数
  • 一个源代码文件太长了适合分成几个文件
  • 俩个独立的源代码文件不能编译形成可执行的程序

项目

  • 在Dev C++中新建一个项目,然后把几个源代码文件加入进去
  • 对于项目,Dev C++的编译会把一个项目中所有的源代码文件都编译后,链接起来
  • 有的IDE有分开的编译和构建俩个按钮,前者是对单个源代码文件编译,后者是对整个项目做链接(例如:Visual Studio)

编译单元

  • 一个.c文件是一个编译单元
  • 编译器每次编译只处理一个编译单元

12.3-2 头文件

头文件

  • 把函数原型放在一个头文件(以.h结尾)中,在需要调用这个函数的源代码(.c文件)中#include这个头文件,就能让编译器在编译的时候知道函数的原型

#include

  • #include是一个编译预处理指令,和宏一样,在编译之前就处理了
  • 它把那个文件的全部文本内容原封不动地插入到它所在的地方
  • 所以也不是一定要在.c文件的最前面#include

""还是<>

  • #include有两种形式来指出要插入的文件
    • ""要求编译器首先在当前目录(.c文件所在的目录)寻找这个文件,如果没有,到编译器指定的目录去找
    • <>让编译器只在指定的目录去找
  • 编译器自己知道自己的标准库的头文件在哪路
  • 环境变量和编译器命令行参数也可以指定寻找头文件的目录

#include的误区

  • #include不是用来引入库
  • stdio.h里只有printf的原型,printf的代码在另外的地方,某个.lib(Windows)或.a(Unix)中
  • 现在的C语言编译器默认会引入所有的标准库
  • #include<stdio.h>只是为了让编译器知道printf函数的原型,保证你调用时给出的参数是正确的类型

头文件

  • 在使用和定义这个函数的地方都应该#include这个头文件
  • 一般的做法就是任何.c都有对应的同名的.h,把所有对外公开的函数的原型和全局变量的声明都放进去

不对外公开的函数

  • 在函数前面加上static就使得它成为只能在所在的编译单元中被使用的函数
  • 在全局变量前面加上static就使得它成为只能在所在的编译单元中被使用的全局变量

12.3-3 声明

变量的声明

  • int i;是变量的定义
  • extern int i;是变量的声明

声明和定义

  • 声明是不产生代码的东西
    • 函数原型
    • 变量声明
    • 结构说明
    • 宏声明
    • 枚举声明
    • 类型声明
    • inline声明
  • 定义是产生代码的东西

头文件

  • 只有声明可以放在头文件中
    • 是规则不是法律
  • 否则会造成一个项目中多个编译单元里有重名的实体
    • 某些编译器允许几个编译单元中存在同名的函数,或者用weak修饰符来强调这种存在

重复声明

  • 同一个编译单元里,同名的结构不能被重复声明
  • 如果你的头文件里有结构的声明,很难这个头文件不会在一个编译单元里被#include多次
  • 所以需要“标准头文件结构”

标准头文件结构

20220806160028

13.1-1 格式化输入输出

格式化输入输出

  • printf
    • %[flags][width][.prec][hlL]type
  • scanf
    • %[flag]type

printf: %[flags][width][.prec][hlL]type

Flag 含义
- 左对齐
+ 在前面放+或-
(space) 正数留空
0 0填充
width或prec 含义
number 最小字符数
* 下一个参数是字符数
.number 小数点后的位数
.* 下一个参数是小数点后的位数
类型修饰 含义
hh 单个字符
h short
l long
ll long long
L long double
type 用于 type 用于
i或d int g float
u unsigned int G float
o 八进制 a或A 十六进制浮点
x 十六进制 c char
X 字母大写的十六进制 s 字符串
f或F float,6 p 指针
e或E 指数 n 读入/写出的个数

scanf: %[flag]type

flag 含义 flag 含义
* 跳过 l long,double
数字 最大字符数 ll long long
hh char L long double
h short
type 用于 type 用于
d int s 字符串(单词)
i 整数,可能为十六进制或八进制 […] 所允许的字符
u unsigned int p 指针
o 八进制
x 十六机制
a,e,f,g float
c char

[^.]

20220807153248

printf和scanf的返回值

  • 读入的项目数

  • 输出的字符数

  • 在要求严格的程序中,应该判断每次调用scanf或printf的返回值,从而了解程序运行中是否存在问题

13.1-2 文件输入输出

文件输入输出

  • 用>和<做重定向

FILE

20220807154007

打开文件的标准代码

FILE* fp = fopen("file","r");
if(fp){
	fscnaf(fp,...);
	fclose(fp);
}else{
	...
}

fopen

含义
r 打开只读
r+ 打开拼写,从文件头开始
w 打开只写。如果不存在则新建,如果存在则清空
w+ 打开读写。如果不存咋则新建,如果存在则清空
a 打开追加。如果不存在则新建,如果存在则从文件尾开始
…x 只新建,如果文件已存在则不能打开

13.1-3 二进制文件

二进制文件

  • 其实所有的文件最终都是二进制的
  • 文本文件无非是用最简单的方式可以读写的文件
    • more、tail
    • cat
    • vi
  • 而二进制文件是需要专门的程序来读写的文件
  • 文本文件的输入输出是格式化,可能经过转码

文本VS二进制

  • Unix喜欢用文本文件来做数据存储和程序配置

    • 交互式终端的出现使得人们喜欢用文本和计算机“talk”
    • Unix的shell提供了一些读写文本的小程序
  • Windows喜欢用二进制文件

    • DOS是草根文化,并不熟悉和继承Unix文化
    • PC刚开始的时候能力有限,DOS的能力更有限,二进制更接近底层
  • 文本的优势是方便人类读写,而且跨平台

  • 文本的缺点是程序输入输出要经过格式化,开销大

  • 二进制的缺点是人类读写困难,而且不跨平台

    • int的大小不一致,大小端的问题…
  • 二进制的优点是程序读写快

程序为什么要写文件

  • 配置
    • Unix用文本,Windows用注册表
  • 数据
    • 稍微有点量的数据都放数据库了
  • 媒体
    • 这个只能是二进制的
  • 现实是,程序通过第三方库来读写文件,很少直接读写二进制文件

二进制读写

20220807160508

为什么nitem?

  • 因为二进制文件的读写一般都是通过对一个结构变量的操作来进行的
  • 于是nitem就是用来说明这次读写几个结构变量!

在文件中定位

  • long ftell(FILE *stream);
  • int fseek(FILE *stream,long offset,int whence);
    • SEEK_SET:从头开始
    • SEEK_CUR:从当前位置开始
    • SEEK_END:从尾开始(倒过来)

可移植性

  • 这样的二进制文件不具有可移植性
    • 在int为32位的机器上写出的数据文件无法直接在int为64位的机器上正确读出
  • 解决方案之一是放弃使用int,而是typedef具有明确大小的类型
  • 更好的方案是用文本

13.2-1 按位运算

按位运算

  • C有这些按位运算的运算符
运算符 含义
·& 按位的与
·| 按位的或
·~ 按位取反
·^ 按位的异或
·<< 左移
·>> 右移

按位与&

  • 如果(x)i==1并且(y)i==1,那么(x & y)i=1
  • 否则的话(x & y)i = 0
  • 按位与常用于俩种应用:
    • 让某一位或某些位为0:x & 0xFE
    • 取一个数中的一段:x & 0xFE

按位或 |

  • 如果(x)i==1或(y)i==1,那么(x | y)i=1
  • 否则的话,(x | y)i==0
  • 按位或常用于俩种应用:
    • 使得一位或几个位为1:x | 0x01
    • 把两个数拼起来:0x00FF | 0xFF00

按位取反~

  • (~ x)i=1-(~ x)i
  • 把1位变0,0位变1
  • 想得到全部位为1的数:~0
  • 7的二进制是0111,x | 7使得低3位为1,而x & ~7,就使得低3位为0

逻辑运算vs按位运算

  • 对于逻辑运算,它只看到俩个值:0和1
  • 可以认为逻辑运算相当于把所有非0值都变成1,然后做按位运算
    • 5 & 4 ——> 4而 5 && 4 ——> 1 & 1 ——> 1
    • 5 | 4 ——> 5 而 5 || 4 ——> 1 | 1 ——> 1
    • ~4 ——> 3 而 ! 4 ——> ! 1 ——> 0

按位异或^

  • 如果(x)i==(y)i,那么(x^y)i=0
  • 否则的话,(x^y)i==1
  • 如果俩个位相等,那么结果为0;不相等,结果为1
  • 如果x和y相等,x^y的结果为0
  • 对一个变量用同一个值异或俩次,等于什么也没做
    • x ^ y ^ y ——> x

13.2-2 移位运算

左移<<

  • i << j
  • i 中所有的位向左移动 j 个位置,而右边填入0
  • 所有小于int的类型,移位以int的方式来做,结果是int
  • x <<= 1 等价于 x *= 2
  • x <<= n 等价于 x *= 2n

右移>>

  • i >> j
  • i 中所有的位向右移动 j 个位置
  • 所有小于int的类型,移位以int的方式来做,结果是int
  • 对于unsigned的类型,左边填入0
  • 对于signed的类型,左边填入原来的最高位(保持符号不变)
  • x >>= 1 等价于 x /= 2
  • x >>= n 等价于 x /= 2n

no zuo no die

  • 移位的位数不要用负数,这是没有定义的行为
    x <<-2 //! ! No ! !

13.2-3 位运算例子

输入一个数的二进制

#include <stdio.h>
int main(){
	int number;
	scanf("%d",&number);
	unsigned mask = 1u<<31;
	for(;mask;mask>>=1){
		printf("%d",number & mask ? 1:0);
	}
	printf("\n");
	return 0;
} 

MCU的SFR

20220809162115

  • const unsigned int SBS = 1u<<2;
  • const unsigned int PE = 1u<<3;
  • U0LCR |= SBS | PE;
  • U0LCR &= ~SBS;
  • U0LCR &= ~(SBS | PE);

13.2-4 位段

位段

  • 把一个int的若干位组成一个结构
struct{
	unsigned int leading : 3;
	unsigned int FLAG1: 1;
	unsigned int FLAG2: 1;
	int trailing: 11;
};
  • 可以直接用位段的成员名称来访问
    • 比移位、与、或还方便
  • 编译器会安排其中的位的排列,不具有可移植性
  • 当所需的位超过一个int时会采用多个int

14.1-1 可变数组

Resizable Array

  • Think about a set of functions that provide a mechanism of resizable array of int.
  • Gorwable
  • Get the current size
  • Access to the elements

the Interface

  • Array array_create(int init_size);
  • void array_free(Array *a);
  • int array_size(const Array *a);
  • int* array_at(Array *a,int index);
  • void array_inflate(Array *a,int more_size);

array_create()

Array array_create(int init_size){
	Array a;
	a.array = (int*)malloc(sizeof(int)*init_size);
	a.size = init_size;
	return a;
}

Why Array not Array*?

因为Array a是本地变量,如果返回指针就无效了

14.2-1 可变数组的缺陷、

issues

20220810153338

Q.E.D.