GENIA

C语言学习笔记

2023-12-08

参考资料:翁恺c语言

1.1.2 程序的执行方式

可以分为解释执行和编译执行两种,各有优缺点
解释执行:通过特定的程序来理解你写的程序,赋予了一些特殊的计算性能
编译执行:将你写的程序翻译给电脑,具有确定的运算性能
语言本身没有解释语言或编译语言之分,只是常用的执行方式有所不同

1.2.1 C语言的发展

语言的能力/适用的领域通常是由库和传统所决定的

c语言的发展历史:
FORTRAN(1950s,first senior language)->BCPL->B->C
ANSI C->C89->C95->C99

C语言是一种工业语言 多用于处理一些基础性工作
IDE=编辑器+编译器

1.3.1/1.3.2 程序框架

1
2
3
4
5
6
#include<stdio.h>
int main()
{
printf(“Hello World\n!”); //直接输串字符串
return 0;
}

在c语言中,换行和空格不会有任何问题(不像python会通过缩进来表示逻辑关系)

1.3.3 输出

1
2
printf(“%d”,12+34);//%d在这里是占位符
printf(“12+34=%d”,12+34);

printf()函数中可以进行简单的数学运算:+、-、*、/、%

2.1.2 变量定义

1
2
int price = 0;//类型 名称(标识符) = 初始值;
int price,amount;//也可以一行定义多个变量

变量的作用:为保存数据留出足够的空间
其中标识符的规则与python的规则非常类似

2.1.3 赋值和初始化

1
int price = 0;//“=”为赋值运算符

在数学中,a=b与b=a完全等价,“=”表示的是一种静态的关系
在程序中,a=b将b的值赋给a,是一个动态的动作
C语言没有强制要求所有的变量都要在定义的地方进行初始化,但是在被使用之前(出现在赋值运算符的右边)应该被赋值一次,否则编译器会给出警告
给多个变量赋值:

1
int price = 0,amount = 100;

int price,amount=XX;
上面的这种表示方法是错误的,只能一个一个进行赋值
数据类型表示在变量中可以存放什么样的数据。
变量中只能存放指定类型的数据。
程序运行中也不能改变变量的类型。

1
int change = 100 - price;//C99标准:允许在程序的任何地方定义变量

2.1.4 变量输入

scanf()函数

1
scanf(“%d”,&price);//读取一个整数,将结果赋值给price

注意第二个参数传入的是地址!

2.1.5 常量与变量

1
2
3
4
5
int change = 100 - price;//这里的100是一个常数,在程序里也称为直接量(literal)
//定义一个常量:
const int AMOUNT = 100;
//const是一个修饰符,代表不可修改,单独定义可以方便日后修改
//同时,AMOUNT不能被再次赋值。

2.1.6 浮点数

在C语言中,两个整数的运算结果只能是整数。

比如:10/3*3=9,直接扔掉了小数部分,没有四舍五入。

浮点数是表示非整数(包括分数和无理数)的一种方式(C语言中没有定点数)。

当浮点数和整数放在一起运算时,C语言会将整数换为浮点数,然后进行浮点数计算。

1
2
3
//整数(int)                  //浮点数(用double比用float要多)
printf("%d",xxxx); printf("%f",xxxx);
scanf("%d",xxxx); scanf("%lf",xxxx);

2.2.1 表达式

一个表达式由运算符和算子两部分组成。

其中算子可以是常数,也可以变量或者返回值。

2.2.2 运算优先级

单目运算符(不变或取负)的优先级是最高的。

“=”为自右向左的运算:a=b=6 ——-> a=(b=6)

C语言支持嵌入式赋值:

1
2
3
int a = 6;
int b;
int c = 1 + (b = a);

但是此法可读性太差,不建议使用。

需要注意的是:C语言不能像python一样使用“**”来表示幂次,也不能用“%”来表示百分数。

2.2.3 交换变量

体现出人的思维与计算机运算方式的差别。

a=b; b=a; ———> t=a; a=b; b=t;

(好像也不能和python一样,通过a,b = b,a 来交换值)

2.2.4 复合赋值、递增递减

和python是一样的,很好理解:

1
2
3
total += (sum + 100)/2;  ------> total = total + (sum + 100)/2;
total *= sum + 12; -------> total = total*(sum + 12);
total /= 12 + 6; --------> total = total/(12 + 6);

“++”和”–”属于单目运算符,算子必须是变量。

count++; --------> count += 1; ---------> count = count+1;

前后缀问题:

a++是 a+1 以前的值

++a是 a+1 之后的值

但无论后缀如何,经过一次运算,变量a的值已经发生了变化。

3.1.1 做判断

1
2
3
if(条件){
若满足条件则执行的命令;
}

3.1.2 判断的条件

== != > < >= <=

条件成立则值为1,条件不成立则值为0。

优先级:赋值运算符 < 关系运算符 < 算数运算符

在关系运算符中,又有 == 和 != 的优先级较低一些。

连续的关系运算总是从左往右进行。

1
2
 6 > 5    >4  -------> False = 0 
True=1

3.1.3 注释

1
2
3
4
// C99支持的单行注释
/*
通用的多行注释
*/

3.1.4 否则的话

1
2
}//if或者else if的第二个花括号
else {否则的话……};

3.1.5 单独的if语句

可以没有大括号,else也一样,但是只能写一句话。不建议使用。

1
2
if(total > amount)
total += amount + 10;

3.2.1 嵌套的if-else

不是和python一样用缩进表示逻辑关系

用{}来表示清晰的逻辑关系。

小括号后不加 “;” ,表示语句还未结束。

3.2.2 级联的if-else if

1
2
3
4
5
6
7
8
9
if(x<0){
f=-1;
}
else if(x==0){ //else if就像python中的elif
f=0;
}
else{ //若没有大括号,else总是和离它最近的if匹配
f=2*x; //缩进不能暗示else的匹配
}

3.2.3 几个非常常见的错误

1.忘了加大括号——>养成加大括号的习惯

2.if 条件句后加了 “ ; ”

3.错误的使用 == 和 = ,if只判断()中的值是0或者非0,所以一般这种错误很难在编译过程中发现

4.要统一代码风格

在 if 和 else 之后尽量都加上大括号,哪怕只有一条代码。

大括号里的内容要缩进一个Tab的位置,整齐便于阅读。

3.2.4 多路分支

switch-case的使用:

1
2
3
4
5
6
7
8
9
10
11
12
switch(type){
//type得是int类型的
case 1: //case后面跟的必须是常数或者有确定结果的表达式
printf("xxxxx");
break; //直接离开switch-case段落
case 2: //分支标号是switch内部位置的路标
xxxxx; //执行完分支中的最后一条语句后,如果后面没有break,会执行下一个case
case 3:
xxxxx;
default:
xxxxx;
}

4.1.1 循环

例子:计算整数所含的位数

1
2
3
4
5
6
7
8
9
10
11
12
13
{
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和do-while循环

do-while循环:进入循环时不做检查,而是在执行完一轮循环体的代码之后,再来检查循环的条件是否满足,满足则进入下一轮循环,不满足则结束循环。

1
2
3
do{
<循环语句> //无论是否满足条件,循环都至少执行一遍
}while(<循环条件>);

对于while循环:循环体内要有改变条件的机会,否则会陷入死循环。

程序的验证:常用数据边界,如有效范围两端的数,特殊的倍数等

0、负数、10、个位数等等

可以在程序适当的地方插入printf()检查输出。

4.2.1 循环计算

计算之前先保存原始的值,后面可能会有用

如果要模拟一个次数很大的循环,可以模拟较少次数得到结论

4.2.2 猜数游戏

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

1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<time.h>
int main(){
srand(time(0));
int a=rand(); //得到一个随机的整数
printf("%d\n",a%100); //x%n的结果是[0,n-1]的一个整数
//也可以简写为 int number = rand()%100 + 1;
return 0;
}

4.2.4 整数逆序

一个很经典的方法:

1
2
3
4
5
6
7
8
9
10
11
……
int x;
int digit; int ret = 0;
while(x>0){
digit=x%10; //得到整数的末位
ret=ret*10+digit;
printf("x=%d,digit=%d\n",x,digit,ret);
x/=10; //去掉整数的末位
}
printf("%d",ret);
return 0;

5.1.1 for循环

例子:计算阶乘n!

1
2
3
4
5
……
for( i = 1 ; i <= n ; i ++ ){
//初始条件|循环继续的条件|循环每轮要做的动作
fact*=i;
}

注意:

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

可以把 i 的定义移到 for 循环里(C99)

省去 i 也行:

1
2
3
4
5
6
7
8
……
int i = n;
for( n = n ; n > 1 ; n -- ){
//这里可以直接省略了
fact*=n;
}
printf("%d!=%d/n",i,fact);
……

5.1.2 循环的计算与选择

for(i=0;i<n;i++)for(i=1;i<=n;i++)循环的次数都是n次,不同的是 i 的初始值和最终值。

for循环完全可以用while来实现,按需求进行选择。

一般:1.有固定次数,用for

​ 2.必须执行一次,用do-while

​ 3.其它情况,用while

5.2.1 循环控制

break:跳出循环

continue:跳过本轮,进入下一轮

例子:判断素数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
int main(){
int x;
scanf("%d",&x);
int i;
int isPrime=1;
for(i=2;i<x;i++){
if(x%i == 0){
isPrime =0;
break;
}
}
if(isPrime==1){
printf("是素数\n");
}else{
printf("不是素数\n");
}
return 0;
}

5.2.3 从嵌套的循环中跳出

break和continue都只能对它们所在的那层循环生效。

1.若要跳出多层循环,使用接力break方法,为外层break设定一个可行条件。

2.goto 标号(不建议使用)

5.2.3 整数分解

逆序整数(扫描填充法,这里用do-while实现)

1
2
3
4
5
6
7
8
9
……
int t=0;
do{
int d= x%10;
t=t*10+d;
x/=10;
} while(x>0);
printf("x=%d,t=%d\n",x,t);
……

5.3.3 求最大公约数和公倍数

1.枚举法

2.辗转相除法:若b=0,计算结束,a就是最大公约数。否则,计算a除以b的余数,让a等于b,b等于余数。回到第一步。

1
2
3
4
5
6
7
8
9
10
11
12
13
……
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;
……

而最小公倍数等于输入的两个数之积除以它们的最大公约数。

6.1.1 数据类型

C语言的变量必须在使用前定义并确定类型

整数:char , short , int , long , long long

浮点数:float ,double , long double

其中 long long 和 long double 均为C99类型

逻辑:bool

指针类型、自定义类型(结构体)

size of 能给出某个类型在内存中所占的字符数

它是一个静态运算符,它的结果在编译时刻就决定了。

不要在 size of 的括号里做运算(这些运算是不会做的)

6.1.2 整数类型

字长(32bit/64bit)往往代表着寄存器(Reg)的宽度,即一次可处理的数据量。

int 代表的就是一个寄存器的大小,所以整形往往有着特殊的含义。

6.1.3 整数的内部表达方式

补码——> 负整数内部的表达方式

6.1.4/6.1.5 整数的范围和格式化

一个字节可以表示数的大小: -128 ~127

一个 int 类型:-2^(32-1) ~ -2^(32-1)-1

unsigned ——>用于扩大正数范围,不能表示负数。用于做纯二进制运算,主要是用于移位

用u或者U来表示占位符

%o用于输出8进制,%x用于输出16进制

6.1.6 选择整数类型

没有特殊要求直接用int

现代编译器一般会设计内存对齐,所以更短的类型实际上在内存中也可能占据一个int的大小。

6.1.7 浮点类型

1
2
3
        字长         有效数字位数          scanf()        printf()
float 32 7 %f %f,%e ---> 科学计数法
double 64 15 %lf %f,%e

6.1.8 浮点数的范围和精度浮点数能表示那些数

+(-)inf ——> 正负无穷(不能用整形表示,但是以特殊值的形式定义于浮点类中)

nan ——> 不存在的数

带小数点的字面量默认为double而非float,因而float需要用f或者F后缀来表明身份。

直接判断两个浮点数是否相等可能会失败,可以用 fabs(f1 - f2) < 1e-12 来判断,不要用浮点数来做精确运算

没有特别要求,使用double

6.1.9 字符类型

char也可以说是整数

在ASCII码中,‘1’ =49

当使用混合输入时——> 尽可能加入空格,以防读取空格

大小写转化:

字母在ASCII表中是按顺序排列的,大小写分开但是各自连续

a+’a’-‘A’——>大写变小写

a+’A’-‘a’——>小写变大写

6.1.10 逃逸字符

\b 回退一格,仅限于输入下一个输出时,从回退到的位置开始,否则不变。

\t 制表符,使上下行对齐。

\"双引号 \'单引号 \n换行 \\反斜杠 \r回车

6.1.11 类型转化

运算符两边类型不同时,自动转化为较大的类型。

char -> short -> int -> long -> long long

int -> float -> double

printfscanf 的区别:

对于 printf ,任何 小于 int 的类型 —–> int

float —–> double

对于 scanf ,类型是严格规定的,必须按照要求输入

强制类型转化:(类型)值 ——> int(10.2)

强制类型转化运算的优先级要高于四则运算,但是不会改变原有的类型与值

6.2.1 bool类型

#include<stdbool.h>

之后即可以使用 booltruefalse,但它们依旧是作为 int 类型存在的。

6.2.2 逻辑运算

!非 &&与 ||或

不要和python一样写出4<x<6这种式子,编译器无法按照你的预期来理解。

例如:判断一个字母是否为大写字母 c >= 'A' && c <= 'Z'

优先级:! > && > ||

短路原则:运算从左到右进行,不满足则即刻停止。

例如:

1
2
a==6 && b==1
a==6 && b+=1 //第二种情况中,如果a!=6,则后面对b的运算不会执行

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

6.2.3 条件运算与逗号运算

如:a++ >= 1 && b -----> 2 ? a : b 脑子有病才会这么写

逗号运算:常用于 for 运算,以其右边的表达式的值作为结果,优先级最低

7.1.2 函数的定义与调用

1
2
3
4
5
6
7
8
9
//定义:
返回类型 函数名(参数类型 参数名称,……){

函数体

}
//调用:
函数名(参数值)
//其中()表示函数调用的重要作用,即使没有参数也需要()

给出的正确数量和顺序的参数会被按照顺序依次用来初始化函数中的参数。

7.1.3 从函数中返回

return 停止函数的执行,并可以返回一个值

一个函数里可以出现多个 return

函数返回值可以赋值给变量,也可以再传递给函数,甚至可以丢弃

没有返回值的函数调用时不能给变量赋值,而且必须是 void 类型

7.2.1 函数原型

用来告诉编译器函数长啥样。

因为C编译器从上往下分析代码,如果不把自定义函数体放在前面则需要先进行声明。

main()之前加一个函数头,此即为函数原型。函数原型里可以不写参数名称。

7.2.2 参数传递

调用函数时,用表达式的值初始化函数的参数。

由于编译器会自动进行类型转化,使得调用函数时给的值与参数类型不匹配成为了C语言传统上的最大的漏洞。

(C++和Java在这方面会很严格)

7.2.3 本地变量

定义在函数内部的变量和参数均为本地变量(局部变量),由大括号来界定。本地变量也可以存在于语句块中,甚至可以随便拉一个大括号来存放。

在快外定义的变量,在块内依旧可以使用;但当内外存在同名变量时,内部运行时会把外部盖住。

不能在一个块中定义同名的变量。

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

7.2.4 关于函数的一些细节

没有参数在括号里时,编译器会进行不一定正确的猜测,故不要写出这样的函数原型。

如果确实没有参数,记得加上void,对于main函数来说也是如此。

返回0说明程序正常结束,都则说明在运行时发生了错误。

Tags: 语言