第一节课 常见概念(1)
Q&A:
1. C语言发展史, 学习C语言的必要性?
C语言最初是作为Unix系统的开发工具而发明的;
2. 编译和链接是什么? 过程是什么?
编译就是将每个源文件(.c)通过编译器变成对应的目标文件(.obj), 链接是将这些文件与链接库通过连接器处理生成可执行程序(.exe);
3. 各个编译器我该使用什么? vs的优势?
使用VS2022, 因为它是一个主流的集成开发环境, 且在企业中较为普遍, 基本不用额外配置环境, 功能强大上手容易;
4. main函数究竟是啥?
main函数是程序的入口, 有且仅有一个, 在一个工程中有多个源文件, 但main函数只能有一个;
5. 关键字有哪些? 要背诵吗?
关键字是留给C语言使用的, 较为主流的关键字有32个, 无需背诵;
6. 字符? ASCII码? 字符计算?
在键盘上可以敲出各种字符, 如: a, q, @, #等, 这些符号被称为字符, 通常用单引号括起来, 用asc编码代表字符, 字符计算就是用ASCII码值进行计算;
7. C语言的字符串? 字符和字符串有何区别?
使用双引号括起来的一串字符被称为字符串, 字符串在结尾隐藏着一个\0, 它是字符串结束的标志, 如"abc", 它其实是一个4个元素的数组, \0也可自己添加, 表示这个数组的结束;
8. sizeof和strlen? 竟然有坑?
sizeof是统计类型或表达式的长度的, 若为表达式, 则表达式不计算, 且结果为size_t类型, 占位符为%zd;
strlen是统计字符串中\0之前的元素的个数, 如strlen("abc"), 它的值就为3, 若要使用则需头文件string.h;
9. 转义字符是什么? 平时怎么用呢?
转义字符可以转变字符的意思, 通常使用的就是
\n 换行 \t 制表 \某个字符(可以表示那个字符, 以免产生歧义);
第二节课 常见概念(2)+数据类型和变量(1)
Q&A:
1. 两种特殊的转义字符:\ddd 和 \xdd是什么?
分别是八进制和16进制, ''和\就类似于求导与积分的关系, 两者结合在一起就抵消了, 如'\130'表示的就是字符x, 即表示ASC码: 1*8^2+3*8+0=84的字符, 就是x;
2. 指出里面哪些是转义字符,并给出运行结果printf("%zd\n", strlen("c:\\test\128\abcd.c"));
\\斜杠号, \12, \a蜂鸣符; 结果是15;
3. C语言是结构化语言,他有哪几种结构?控制语句是什么?
三种结构: 顺序结构, 选择结构, 循环结构; 控制语句可以控制程序的流程,
条件判断语句: if, switch;
循环执行语句: do-while, while, for;
转向语句: break, goto, continue, return;
4. 为什么要写注释? 编译器是如何看待注释掉的代码的呢?VS2022如何批量注释代码呢?
注释是给程序员看的, 编译器会直接去除被注释掉的代码, \**\来批量注释多行代码;
5. C语言也有布尔类型?如何使用?
有, 得包含头文件stdbool.h, 类型是_Bool或bool, true就是1, false就是0;
6. sizeof不是函数? sizeof还有返回值?? sizeof是在编译器工作的哪个时期运算的?
是关键字, 操作符, 返回值是size_t类型, 是字节大小, 在编译期间就把sizeof处理掉了, 所以在运行期间就不会执行表达式, 即sizeof表达式, 表达式是不真实参与运算的,根据表达式的类型来得出⼤⼩;
7 .下面这段代码为什么不输出3 ?为什么?
#include <stdio.h>
int main()
{
int a = 2;
sizeof(++a);
printf("%d", a);
return 0;
}
sizeof后的表达式不参与运算, 即sizeof(++a)算出来为4(int), ++a不运算, a就是2, 所以输出2;
8. 了解一下头文件 limits.h 和 float.h
limits.h整数类型的取值范围;float.h浮点数类型的取值范围;
9. C语言的数据类型有哪四种? 不同的数据类型有不同取值范围? char类型是有符号还是无符号?
字符型, 整形, 浮点型, 布尔类型;char类型根据当前系统, 有可能有符号, 有可能没符号;
10. 变量创建的局部有限性原则究竟是什么?
在自己生存的大括号里面的;比如创建在main函数(main函数大括号内, 其他所有大括号外)里的变量, 在main函数里都可用;
11. 局部变量和全局变量分别存在内存中的哪个位置?堆内如何存储变量?
局部变量存在于栈区, 全局变量存在于静态区; 动态内存管理;
第三节课 数据类型和变量(2)
Q&A:
1. 除号(/)的玄机——浮点数除法和整型除法的注意事项?
浮点数除法: 两个运算数必须⾄少有⼀个浮点数, 返回结果为浮点数;
整型除法: 两个整数的除法, 只会返回整数部分,丢弃⼩数部分;
2. 前置后置自增自减究竟怎么算?
比如++a, 就是先自增1再使用a; a++, 就是先使用a再自增1;
#include <stdio.h>
int main()
{
int a = 10;
printf("%d\n", ++a);
printf("%d\n", a++);
return 0;
}
如图代码, 第一个打印出来的就是11, 第二个打印出来的是10;
3. 强制类型转化是什么?
语法形式是: (类型)
比如有一个float类型变量a = 20.0, 我们可以int b = (int) a; 这样当我们打印出b的值时, 就是20, 只取整数部分, 不会四舍五入;
【详解printf函数!】
4. 占位符是个啥?多个占位符怎么一起使用?
占位符就是一个占住位置的符号, 它可以被其他的值代入; 一个输出文本中可以放多个占位符, 它们与参数是一一对应的关系;
5. 如何限定输出最小宽度?
#include <stdio.h>
int main()
{
int a = 123;
printf("%5d", a);
printf("%-5d", a);
return 0;
}
如上面这段代码, 第一个输出的是" 123", 第二个输出的是"123 "; %5d表示占5个位置, 不足的使用空格补齐;
对于小数, 同样成立, 但是小数点.也占一个位置; 可以使用%.mf来限制小数点后的个数, m就是小数点位数;
对于字符串, 同样成立, 例如, %.5s, 表⽰只输出字符串“hello world”的前5个字符, 即“hello”;
若超过这个最小宽度, 还是会全部都输出;
6. printf输出还能控制左对齐或者右对齐?
默认右对齐, 在百分号%后面加一个-即可实现左对齐;
7. %*.*f 是个啥?
*可以代替最小宽度和小数位数, 如%6.2f就是表示最小宽度为6, 小数点位数为2的数, 若printf("%*.*f", 6, 2, 1.445); 打印的结果就是" 1.45", 对于小数位数, 会四舍五入;
【详解scanf!】
8. 什么?vs里不让用scanf?该怎么办!
vs认为scanf不安全, 需要在文件头处#define _CRT_SECURE_NO_WARNINGS 1;
9. 缓冲区是啥?竟和scanf有关系?
scanf() 处理数值占位符时, 会⾃动过滤空⽩字符, 包括空格、制表符、换⾏符等;
缓冲区是用户键盘输入存放的地方, scanf()处理用户输入的时, 当等到按下回⻋键后,按照占位符对缓存区进⾏解读; 解读⽤⼾输⼊时, 会从上⼀次解读遗留的第⼀个字符开始, 直到读完缓存, 或者遇到第⼀个不符合条件的字符为⽌;
#include <stdio.h>
int main()
{
int x;
float y;
scanf("%d", &x);
printf("%d\n", x);
scanf("%f", &y);
printf("%f\n", y);
return 0;
}
如若用户输入" -13.45e12# 0", 那么第一次会读取到-13, 第二次会读取到.45e12 , 这是采⽤科学计数法的浮点数格式; 后⾯的 # 不属于浮点数的有效字符, 所以会停在这⾥;
第一次会输出-13, 第二次会输出44999999880;
10. 浮点数怎么计算机还不能精确存储?
见上图代码, 第二次会输出44999999880, 因为0.45不能精确保存, 大概是0.44999999880;
11. scanf返回值表示什么?有什么含义?
scanf的返回值表⽰成功读取的变量个数;
如果没有读取任何项, 或者匹配失败, 则返回 0;
如果在成功读取任何数据之前, 发⽣了读取错误或者遇到读取到⽂件结尾, 则返回常量EOF(-1);
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
float f = 0.0f;
int r = scanf("%d %d %f", &a, &b, &f);
printf("a = %d b = %d f = %f\n", a, b, f);
printf("r = %d\n", r);
return 0;
}
情况一: 正常输入三个数, 如"1 2 3.14", 那么最后就会输出r = 3;
情况二: 正常输入两个数, 第三个数的时候按ctrl + z, 最后就会输出2;
情况三: 在vs环境中, 连续按三次ctrl + z, 最后就会输出-1;
12. 多组数据输入如何用scanf实现?
while (scanf("%d", &n) != EOF) 这种写法叫做多组输入;(在vs环境中, 只有连按三次ctrl + z才能输出EOF停止输入;)
13. scanf里使用占位符%c,要多留个心眼——小心空白字符被吃掉
空白字符也算一个字符, 若使用%c需要注意空白字符, 它会存储空白字符;
若要跳过字符前的空白字符, 可以使用scanf(" %c", &ch) , 即 %c 前加上⼀个空格, 表⽰跳过零个或多个空⽩字符;
14. scanf里使用%s,遇到空白字符会停止吗?——存储到变量里后会自动存储'\0'
scanf中使用%s, 从当前第⼀个⾮空⽩字符开始读起, 直到遇到空⽩字符(即空格、换⾏符、制表符等)为⽌;
另外, scanf()将字符串读⼊字符数组时, 不会检测字符串是否超过了数组⻓度; 所以, 储存字符串时 , 很可能会超过数组的边界, 导致预想不到的结果; 为了防⽌这种情况 ,, 使⽤读⼊字符串的最⻓⻓度, 即写成 %[m]s , 其中的 %s 占位符时, 应该指定 [m] 是⼀个整数, 表⽰读取字符串的最⼤⻓度, 后⾯的字符将被丢弃;
#include <stdio.h>
int main()
{
char name[11];
scanf("%10s", name);
return 0;
}
例如, 在上图中, 若我输入了"abcdefghijklmn", 数组中, 0-9这十个字符分别是abcdefghij, 第十一个字符是'\0';
15. 赋值忽略符有什么用?
只要把 * 加在任何占位符的百分号后⾯, 该占位符就不会返回值, 解析后将被丢弃。
#include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d%*c%d%*c%d", &year, &month, &day);
return 0;
}
例如, 在上图中, 我可以输入"2024 6 10"或者"2024-6-10", 都可以成功的将它们分别正确的读入;
第四节课 分支和循环(1)
Q&A:
【if语句】
1. 真假与01的关系?
0代表假, 非0即为真, 即1为真;
2. 这个写法正确吗:else (a < 10)
不正确, else后面不能跟条件;
3. if的分支有多条语句怎么办?
复合语句, 用一个大括号括起来;
4. 悬空else问题?
else总是跟最近的那个if匹配, 无关于缩进;
5. 要注意代码规范,善用tab键,增强代码可读性噢
对;
【关系操作符】 >、<、>=、<=、==、!=
6. 关系操作符的优先级和结合性?
关系运算符是从左到右计算; 优先级高的结合;
7. a = 5 和 a == 5 的区别?
a = 5是给a赋值为5, a == 5是判断a的值是否是5, 若为真返回1, 若为假返回0;
8. x < y < z和 x < y && y < z是等价的吗?为什么推荐 5 == a 这种写法?
不等价, x < y < z会从左往右计算, 左边计算结果为0或1, 通常不符合我们的意思; x < y && y < z才与数学上一致; 因为若写为5 = a, 编译器会检查出我们的错误, 若写为a = 5, 编译器会编译通过, 我们也很难查出来错误;
【条件操作符】
9. 三目运算符是什么?(可以用,但不要嵌套太多噢)
exp1 ? exp2 : exp3, 先判断exp1, 若成立, 则计算exp2, 且它的结果是整个表达式的值; 若不成立, 则计算exp3的值, 结果为整个表达式的值;
【逻辑运算符】
10. 有哪些逻辑操作符?你能画出他们的真值表吗?
与(&&), 或(||), 非(!);
与: 有假即假, 全真为真;
或: 有真即真, 全假为假;
非: 真变假, 假变真;
11. 短路是什么?有哪些情况会短路?
若左边的表达式满⾜逻辑运算符的条件, 就不再对右边的表达式求值, 这种情况称为“短路”。
在与操作符中, 左侧的表达式为假, 就不再进行右侧表达式的计算;
在或操作符中, 左侧的表达式为真, 就不再进行右侧表达式的计算;
12. C语言如何实现判断闰年呢?
有两种闰年, 1. 该年为4的倍数但不是100的倍数;
2. 该年是400的倍数;
#include <stdio.h>
int main()
{
int year = 0;
if ((year % 4 == 0 && year % 100 != 0)
|| year % 400 == 0)
{
printf("%d年为闰年", year);
}
return 0;
}
【switch语句】
13. 如何把if-else语句改写为switch语句?
switch语句是if-else语句的特殊形式, ⽤于判断条件有多个结果的情况;
switch(整形表达式)
{
case 整型常量表达式:
case 整型常量表达式:
default 整形常量表达式:
}
14. switch后的表达式类型可以是什么?
整型, 例如short, int, long, long long;
15. case后面值的类型可以是什么? (要注意case和值之间一定要有空格噢)
整型常量表达式,如1+2或者是整数;
16. default是什么? switch的运行顺序?(要理解switch的匹配逻辑)
若所有case情况都不满足, 就会执行default; swtich语句就像公交车, case就是很多个站口, 我们可以从某一站上车, 它会一直开下去, 若是不使用break语句, 我们就会从进入的那个case, 一直执行到最后的case, default就是没上车就会执行;
第五节课 分支和循环(2)
Q&A:
【while循环】
1. 区分if和while的异同
if和while都是判断条件满足, 但if只会执行一次, while是若条件满足就会一直循环;
2. 逆序打印一个正整数(进阶: 用函数递归实现)
#include <stdio.h>
// 逆序输出整数
int main()
{
int n = 0;
scanf("%d", &n);
do
{
printf("%d", n % 10);
n /= 10;
} while (n > 0);
return 0;
}
#include <stdio.h>
// 递归倒序输出整数
void rev(int n)
{
if (n > 0)
{
printf("%d", n % 10);
rev(n / 10);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
rev(n);
return 0;
}
【for循环】
3. 理解for循环的执行流程【重要】, 可以画图展示。
for (exp1; exp2; exp3)
初始化; 判断; 调整
4. while循环和for循环对比?如何选择使用哪个?(已知迭代次数或者需要遍历可以先选for。一般for和while可以转换,但主要原则是优先考虑代码可读性,且避免过于复杂的条件表达式)
已知迭代次数或者需要遍历可以先选for, 不知道要循环几次选择while, 如判断一个数的位数或者是逆序输出; while循环也能做到for循环的效果, 但是不如for循环简洁明了, 同时也方便代码的维护;
5. do-while和while的区别?如何选用?(最大的区别就是do-while一定会循环一次,如何选择就看是否需要保底先执行一次)
do-while是先执行一次代码块, 再判断是否满足条件, while是先判断是否满足条件再执行; 一般根据题目的边界情况来判断是否要用do-while, 如判定数的位数, 0这个边界情况用do-while判定;
6. break和continue如何理解?作用范围?
break是打破整个循环; continue是结束当次循环, 但是循环还会继续运行; 作用范围就是所在代码块(大括号)的循环;
7. 实战: 素数判断代码实现
#include <stdio.h>
#include <math.h>
int main()
{
int m = 0, n = 0; // 区间
int flag = 1; // 判定是否是素数, 假定是素数
scanf("%d%d", &m, &n);
if (m == 2)
{
printf("2是素数\n");
}
for (int i = (m % 2 == 0? m + 1: m); i <= n; i += 2) // 偶数不可能是素数
{
flag = 1; // 每个数都要重置flag, 确保一开始判定为素数
for (int j = 2; j < sqrt(i); j++)
{
if (i % j == 0)
{
flag = 0; // 不是素数
break;
}
}
if (flag == 1)
{
printf("%d是素数\n", i);
}
}
return 0;
}
8. goto语句的缺陷(大量使用goto语句,会降低代码可读性,加大维护成本,与结构化编程的原则相违背。大多数情况下,使用结构化编程是可以替代goto的)(还记得啥是结构化编程吗哈哈)
结构化编程: 顺序, 分支, 循环; 理论上仅使用这三种结构, 如果处理的好是可以解决所有问题的;
9. 实战:用goto语句实现一个关机小程序
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char input[20] = { 0 };
system("shutdown -s -t 60");
while (1)
{
printf("你的电脑将在1分钟后关机, 请输入我是猪来取消关机\n");
scanf("%s", input);
if (strcmp(input, "我是猪") == 0)
{
system("shutdown -a");
break;
}
}
return 0;
}
10. 实战: 最大公约数和最小公倍数代码实现
#include <stdio.h>
int main()
{
int a = 0, b = 0;
int rem = 0;
scanf("%d%d", &a, &b);
while(rem = a % b) // 辗转相除法
{
a = b;
b = rem;
}
printf("gcd = %d\n", b);
printf("lcm = %d\n", a * b / gcd);
return 0;
}