这一系列的C语言笔记是取材于慕课翁恺老师的课程《C语言程序设计》
First Week 程序设计与C语言
1.1 计算机和编程语言
1.1.1计算最大公约数:
- 如果v等于0,计算结束,u就是最大公约数;
- 如果v不等于0,那么计算u除以v的余数,让u等于v,而v等于那个余数;
- 回到第一步。
#include <stdio.h>
int main(int argc, char const *argv[]){
int u = 32;
int v = 26;
while (v!=0) {
int temp = u%v;
u = v;
v = temp;
}
printf("%d", u);
return 0;
}
1.1.2 程序的执行
- 解释:借助一个程序,那个程序能试图理解你的程序,然后按照你的要求执行。
- 编译:借助一个程序,就像一个翻译,把你的程序翻译成计算机真正能懂得语言——机器语言——写的程序,然后,这个机器语言写的程序就能直接执行了。
对于计算机编程语言来说,语言本身是没有解释和编译得区分的,任何语言都可以解释执行或编译执行,只是具体执行方式不同。
好处:解释型方式或许可以在程序运行过程中更改源代码。编译型方式是有一个确定的运算性能。
两种执行方式没有非常大的本质的区别,只是传统和喜好的问题。
1.2 C语言
- C语言是从B语言发展而来的,B语言是从BCPL发展而来的,BCPL是从FORTRAN发展而来的
- BCPL和B都支持指针间接方式,所以C也支持了
- C语言还受到了PL/I的影响,还和PDP-11的机器语言有很大的关系
- 1973年3月,第三版的Unix上出现了C语言的编译器
- 1973年11月,第四版的Unix发布了,这个版本时完全用C语言重新写的
- C语言的创造者,最开始发布的C版本是K&R标准
- 1989年ANSI发布了一个标准——ANSI C
- 1990年ISO接受了ANSI的标准——C89
- C的标准在1995和1999年两次更新——C95和C99
C语言是一种工业语言
- 操作系统
- 嵌入式系统
- 驱动程序
- 底层驱动
- 图形引擎、图像处理、声音效果
- 开发效率>>学习过程
- 开发效率>>开发乐趣
- 日常应用很少直接用c语言编写
- 学习c的过程主要是写练习代码
- 而非真是软件
1.3 第一个程序
四则运算 | C符号 | 意义 |
---|---|---|
+ | + | 加 |
- | - | 减 |
x | * | 乘 |
÷ | / | 除 |
% | 取余(两个数相除后的余数) | |
() | () | 括号 |
Second Week 计算
1. 变量
所有的变量在第一次被使用之前应该有过初始化。
ANSI C标准只能在代码开头的地方定义变量。C99则是只要在使用前定义就可以。
可以有别的办法知道C语言的输入是否为整数
scanf 函数是有返回值的,它的返回值可以分成三种情况:
- 正整数,表示正确输入参数的个数。例如执行 scanf(“%d %d”, &a, &b);
如果用户输入"3 4",可以正确输入,返回2(正确输入了两个变量);
如果用户输入"3,4",可以正确输入a,无法输入b,返回1(正确输入了一个变量)。- 返回0,表示用户的输入不匹配,无法正确输入任何值。如上例,用户如果输入",3 4",返回0。
- EOF,这是在stdio.h里面定义的常量(通常值为-1),表示输入流已经结束。在Windows下,用户按下CTRL+Z(会看到一个^Z字符)再按下回车(可能需要重复2次),就表示输入结束;Linux/Unix下使用CTRL+D表示输入结束。
C99才可以const常量,一般用全大写
1.1 英制计量单位转换
#include <stdio.h>
int main(int argc, char const *argv[]){
printf("请分别输入身高的英尺和英寸,"
"如输入\"5 7\"表示5英尺7英寸:");
int foot;
int inch;
scanf("%d %d", &foot, &inch);
printf("身高是%f米。\n", ((foot + inch/12.0) * 0.3048));
return 0;
}
当浮点数和整数一起运算时,整数会自动改为浮点数。
整数在计算机里运算比浮点数快。
2. 表达式
C语言的整个一行都是表达式,由运算符和算子构成,故C的赋值左侧算子也可以是一个复杂的运算。
2.1 计算时间差
#include <stdio.h>
int main(int argc, char const *argv[]){
int hour1, minute1;
int hour2, minute2;
scanf("%d %d", &hour1, &minute1);
scanf("%d %d", &hour2, &minute2);
int t1 = hour1*60 + minute1;
int t2 = hour2*60 + minute2;
int t = t2 - t1;
printf("时间差是%d小时%d分。\n", t/60, t%60);
return 0;
}
2.2 运算符优先级
优先级 | 运算符 | 运算 | 结合关系 | 举例 |
---|---|---|---|---|
1 | + | 单目不变 | 自右向左 | a*+b |
1 | - | 单目取负 | 自右向左 | a*-b |
2 | * | 乘 | 自左向右 | a*b |
2 | / | 除 | 自左向右 | a/b |
2 | % | 取余 | 自左向右 | a%b |
3 | + | 加 | 自左向右 | a+b |
3 | - | 减 | 自左向右 | a-b |
4 | = | 赋值 | 自右向左 | a=b |
2.3 赋值运算符
- 赋值也是运算,也有结果
- a=6的结果是a被赋予的值,也就是6
- a=b=6 ——> a=(b=6)
2.4 “嵌入式赋值”(不推荐使用)
int a = 6;
int b;c
int c= 1+(b=a);
- 不利于阅读
- 容易产生错误
2.5 交换两个变量(a和b)的值(小套路之一)
int a=5, b=6, t;
t = a;
a = b;
b = t;
2.6 复合赋值
“+=”、“-=”、“*=”、“/=”、“%=”
两个运算符中间不要由空格。
total /= 12 + 6;
total = total / (12+6); // 两者等效
2.7 递增递减运算符
它们的算子必须是变量。
其前缀后缀:
表达式 | 运算 | 表达式的值 |
---|---|---|
count++ | 给count加1 | count原来的值 |
++count | 给count加1 | count+1以后的值 |
count– | 给count减1 | count原来的值 |
–count | 给count减1 | count-1以后的值 |
历史来源:
当年在PDP11上有两条特殊的指令 INC=递增,DEC=递减。
有了++、–,则C语言编译器可方便的将其编译为这两条特殊指令,运算可以加快。
可是如今,这个的意义已不太大了,因为现在的编译器即便你不写的是递增递减运算符,而只是a=a+1它也会产生相同的指令,并且有些CPU或许并没有INC、DEC指令,没法加快。
如今还在使用仅是因为习惯和方便。
Third Week 判断
1. 判断
1.1关系运算:
运算符 | 意义 |
---|---|
== | 相等 |
!= | 不相等 |
> | 大于 |
>= | 大于或等于 |
< | 小于 |
<= | 小于或等于 |
-
运算结果为0或1
-
所有的关系运算符的优先级比算术运算的低,但是比赋值运算的高
-
==和!=的优先级比其他的关系运算低,连续的关系运算是从左到右的
1.2 注释
/* */
-
其内包含的都是注释
-
而注释方式
//
仅在C99有用,代表行注释。 -
注释在编译中会被替换为一个空格
1.3 IF语句
- 一个基本的if语句由一个关键字if开头,跟上在括号里的一个表示条件的逻辑表达式,然后是一对大括号“{}”之间的若干条语句。如果表达条件的逻辑表达式的结果不是零,那么就执行后面跟着的这对大括号中的语句,否则就跳过这些语句不执行,而继续下面的其他语句。
- if语句这一行结束的时候并没有表示语句结束的“;”,而后面的赋值语句写在if的下一行,并且缩进了,在这一行结束的时候有一个表示语句结束的“;”。这表明这条赋值语句是if语句的一部分,if语句拥有和控制这条赋值语句,决定它是否要被执行。
if(total>amount)
total += amount + 10;
- 为了方便阅读,一般都带{}
2. 分支
// 非单一出口
if(x<0){
printf("%d", -1);
}else if(x==0){
printf("%d", 0);
}else{
printf("%d", 2*x);
}
// 单一出口
int f;
if(x<0){
f = -1;
}else if(x==0){
f = 0;
}else{
f = 2*x;
}
printf("%d", f);
- 更支持单一出口方式
// 错误使用==和=
// if只要求()里的值是零或非零
if(a=b){
printf("A=B");
}
2.1 switch-case
- 控制表达式只能是整数型的结果
- 常量可以是常数,也可以是常数计算的表达式(c99)
switch (控制表达式) {
case 常量:
语句
...
case 常量:
语句
...
default:
语句
...
}
- break:switch语句可以看作是一种基于计算的跳转,计算控制表达式的值后,程序会跳转到相匹配的case(分支标号)处。分支标号只是说明switch内部位置的路标,在执行完分支中的最后一条语句后,如果后面没有break,就会顺序执行到下面的case里去,直到遇到一个break,或者switch结束为止。
switch (type) {
case 1:
case 2:
printf("你好\n");
break;
case 3:
printf("晚上好\n");
case 4:
printf("再见\n");
break;
default:
printf("啊,什么啊?\n");
break;
}
- C99支持case后是const常量
用switch-case来实现分段函数: y = { − 1 x < 0 0 x = 0 2 ∗ x x > 0 y= \begin{cases} -1 & x<0 \\ 0 & x=0 \\ 2*x & x>0 \end{cases} y=⎩ ⎨ ⎧−102∗xx<0x=0x>0
#include <stdio.h>
int main(int argc, char const *argv[]){
int x;
int y;
scanf("%d", &x);
switch (x<0) {
case 1: y=-1;break;
case 0: y=2*x;break;
}
printf("y=%d", y);
return 0;
}
- 虽然可以实现,但是这种情况尽量使用IF判断
Fourth Week 循环
1. 循环
1.1 数位数
#include <stdio.h>
int main(int argc, char const *argv[]){
int x;
scanf("%d", &x);
int n = 0;
do {
x /= 10;
n++;
} while (x >0);
printf("%d", n);
return 0;
}
1.2 猜大小
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char const *argv[]){
srand(time(0));
int number = rand()%100 + 1;
int count = 0;
int a = 0;
printf("我已经想好了一个1到100之间的数");
do {
printf("请猜测:");
scanf("%d", &a);
count++;
if (a > number) {
printf("你猜的数大了。");
} else if (a < number) {
printf("你猜的数小了。");
}
} while (a != number);
printf("太好了!你用了%d次猜到了答案。\n", count);
return 0;
}
1.3 求平均数
#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);
}
/*do {
scanf("%d", &number);
if (number!=-1) {
sum += number;
count++;
}
} while (number!=-1);*/
printf("%f\n", 1.0*sum/count);
return 0;
}
- 这个程序的while比do-while好,因为少了一次IF判断。
1.4 任意位数的逆序
整数的分解
- 一个整数是由I至多位数字组成的,如何分解出整数的各个位上的数字,然后加以计算
- 对一个整数做%10的操作,就得到它的个位数
- 对一个整数做/10的操作,就去掉了它的个位数
- 然后再对2的结果做%10,就得到原来数的十位数了
- 依此类推
#include <stdio.h>
int main(){
int x;
// scanf("%d", &x);
x = 700;
int digit;
int ret = 0;
while (x>0) {
digit = x%10;
// printf("%d\n", digit);
ret = ret*10 + digit;
printf("x=%d,digit=%d,ret=%d\n", x, digit, ret);
x /= 10;
}
printf("%d", ret);
return 0;
}
//结果
//x=700,digit=0,ret=0
//x=70,digit=0,ret=0
//x=7,digit=7,ret=7
//7
Fifth Week 循环控制
1. 第三种循环
1.1 For循环
for (count=10;count>0;count--)
// 对于一开始的count=10,当count>0时,重复做循环体,每一轮循环在做完循环体内语句后,使得count--。
// 循环控制变量i只在循环里被使用了,在循环外面它没有任何用处。因此我们可以把变量i的定义写到for语句里面去
// 仅支持C99
int n;
scanf("%d", &n);
int fact = 1;
for (int i=1; i<=n; i++) {
fact *= i;
}
printf("%d!=%d\n", n, fact);
阶乘:
#include <stdio.h>
int main(int argc, char const *argv[]){
int n;
scanf("%d", &n);
int fact = 1;
int i = n;
for ( ; n>1; n--) {
fact *= n;
}
printf("%d!=%d\n", i, fact);
return 0;
}
一个需求:使用for循环和while循环都可以去实现,那么到底两者之间有什么区别?
从内存角度考虑:
- 局部变量在栈内存中存在,当for循环语句结束,那么变量会及时被gc(垃圾回收器)及时的释放掉,不浪费空间
- 如果使用循环之后还想去访问循环语句中控制那个变量,使用while循环
从应用场景角度考虑:
- 如果一个需求明确循环的次数,那么使用for循环(开发中使用for循环的几率大于while循环)
- 如果一个需求,不知道循环了多少次,使用while循环
for循环像一个计数循环:设定一个计数器,初始化它,然后在计数器到达某值之前,重复执行循环体,而每执行一轮循环,计数器值以一定步进进行调整,比如加1或者减1。
for (i=0; i<5; i++) {
printf("%d", i);
}
for (初始动作; 条件; 每轮的动作) {
}
// for中的每一个表达式都是可以省略的
for (; 条件;) == while (条件)
Tips for loops:
- 如果有固定次数,用for
- 如果必须执行一次,用do_while
- 其他情况用while
2. 循环控制
2.1 goto
// 接力break
int x;
int one, two, five;
int exit = 0;
scanf("%d", &x);
for (one=1; one<x*10; one++) {
for (two=1; two<x*10/2; two++) {
for (five=1; five<x*10/5; five++) {
if (one+two*2+five*5==x*10)) {
printf("可以用%d个1角加%d个2角加%d个5角得到%d元\n", one, two, five, x);
exit = 1;
break;
}
}
if (exit==1) break;
}
if (exit==1) break;
}
// goto
int x;
int one, two, five;
scanf("%d", &x);
for (one=1; one<x*10; one++) {
for (two=1; two<x*10/2; two++) {
for (five=1; five<x*10/5; five++) {
if (one+two*2+five*5==x*10)) {
printf("可以用%d个1角加%d个2角加%d个5角得到%d元\n", one, two, five, x);
goto out;
}
}
}
}
out:
return 0;
- out:写到前面或许只是格式习惯,方便阅读,并不影响后面的程序。
3. 循环应用
3.1 正序分解整数
#include <stdio.h>
int main(int argc, char const *argv[]){
int x;
scanf("%d", &x);
int mask = 1;
int t = x;
while (t > 9) {
t /=10;
mask *= 10;
} // 以上目的在于得到x的位数mask
printf("x=%d, mask=%d\n", x, mask);
do{
int d = x/ mask;
printf("%d", d);
if (mask > 9) {
printf(" ");
}
x %= mask;
mask /= 10;
} while (mask > 0);
printf("\n");
return 0;
}
3.2 求最大公约数
3.2.1 方法一:枚举
- 设t为2;
- 如果u和v都能被t整除,则记下这个t;
- t加1后重复第二步,直到t等于u或v;
- 那么,曾经记下的最大的可以同时整除u和v的t就是gcd
int a, b;
int min;
scanf("%d %d", &a, &b);
if (a<b) {
min = a;
} else {
min = b;
}
int ret = 0;
int i;
for (i=1; i<min; i++) {
if (a%i==0) {
if (b%i==0) {
ret = i;
}
}
}
printf("%d和%d的最大公约数是%d。\n", a, b, ret);
3.2.2 方法二:辗转相除法
- 如果b等于0,计算结果,a就是最大公约数;
- 否则,计算a除以b的余数,让a等于b,而b等于那个余数;
- 回到第一步。
#include <stdio.h>
int main(int argc, char const *argv[]){
int a, b;
int t;
scanf("%d %d", &a, &b);
while (b!=0) {
t = a % b;
a = b;
b = t;
printf("a=%d,b=%d,t=%d\n", a, b, t);
}
printf("gcd=%d\n", a);
return 0;
}