前置说明:此文章仅为个人学习中的周记,如有错误敬请谅解。主要内容为郝斌老师的B站课程视频,以及学习中遇到的各种问题的补充知识。
一、基本的输入和输出函数
1.printf—将变量内容输出到屏幕上
printf的4种用法:
1.printf("字符串");
2.printf("输出控制符",输出参数);
3.printf("输出控制符1 输出控制符2 .......",输出参数1 输出参数2 .......);
输出控制符和输出参数的个数必须一一对应
4.printf("输出控制符 非输出控制符",输出参数);
常用输出控制符
| 控制符 | 含义 | 对应类型 |
|---|---|---|
%d | 有符号十进制整数 | int |
%o | 无符号八进制整数 | unsigned int |
%x | 无符号十六进制整数,小写 | unsigned int |
%X | 无符号十六进制整数,大写 | unsigned int |
%f | 十进制浮点数 | double |
%c | 单个字符 | int,通常传char |
%s | 字符串 | char * |
%p | 指针地址 | void * |
%% | 输出一个百分号 | 无参数 |
长度修饰符
长度修饰符放在转换字符前面,用来匹配更宽或更窄的整数类型。
| 修饰符 | 含义 | 示例 |
|---|---|---|
hh | char级别 | %hhd、%hhu |
h | short级别 | %hd、%hu |
l | long或宽字符 | %ld、%lu、%lc |
ll | long long | %lld、%llu |
z | size_t | %zu |
t | ptrdiff_t | %td |
j | intmax_t | %jd |
L | long double | %Lf |
例:
c long a = 123456789L; printf("%ld\n", a); unsigned long long b = 123456789012345ULL; printf("%llu\n", b); long double pi = 3.1415926535L; printf("%Lf\n", pi);注意:float传给printf时会自动提升为double,所以输出float用%f即可。但scanf读float必须用%f,读double必须用%lf。
宽度、精度和标志
1. 宽度
宽度指定最少输出多少个字符:
c printf("%5d\n", 42); // " 42" printf("%-5d|\n", 42); // "42 |"2. 精度
对整数,精度表示最少数字位数;对字符串,表示最多输出多少个字符;对浮点数,表示小数点后位数。
c printf("%.3d\n", 42); // 042 printf("%.2f\n", 3.14159); // 3.14 printf("%.5s\n", "hello world"); // hello3. 常用标志
| 标志 | 含义 |
|---|---|
- | 左对齐,默认右对齐 |
+ | 正数也显示+ |
| 空格 | 正数前加空格,负数前加- |
# | 八进制加0,十六进制加0x |
0 | 用 0 填充宽度 |
例子:
c printf("%+d\n", 42); // +42 printf("% d\n", 42); // 42 printf("%#x\n", 255); // 0xff printf("%08d\n", 42); // 000000422.scanf——通过键盘将数据输入变量中
scanf的两种用法:
1.scanf("输入控制符",输入参数);
将键盘输入的字符转换为输入控制符所规定格式的数据,然后存入以输出参数的值为地址的变量中。
2.scanf("非输入控制符 输入控制符",输入参数);
将键盘输入的字符转换为输入控制符所规定格式的数据,然后存入以输出参数的值为地址的变量中,非输入控制符必须要原样输入。
如果格式串里写了逗号,输入时也必须写逗号。
c scanf("%d,%d", &a, &b);正确输入:
text 10,20
错误输入:
text 10 20
因为格式串要求中间必须有一个逗号。
再比如:
c scanf("a=%d", &a);输入必须是:
text a=100否则读取失败。
常见注意事项
类型必须匹配
%d对应int,%f对应double,%s对应char *。类型不匹配是未定义行为,可能输出乱码甚至崩溃。printf和scanf的%f不同printf:float和double都用%f;scanf:float用%f,double用%lf。
%s遇到'\0'停止
字符串必须以'\0'结尾,否则会越界读取。%p建议先转void *c printf("%p\n", (void *)p);
二、运算符
C 语言运算符,简单说就是对数据进行运算的符号。它决定了对几个操作数做什么操作,比如加减、比较、赋值、取地址、逻辑判断等。C 的运算符很丰富,按功能大致可以分为以下几类。
1、算术运算符
用于数值计算。
| 运算符 | 含义 | 示例 |
|---|---|---|
+ | 加法 | a + b |
- | 减法 | a - b |
* | 乘法 | a * b |
/ | 除法 | a / b |
% | 取模,求余数 | a % b |
++ | 自增 | a++、++a |
-- | 自减 | a--、--a |
注意:
整数除法会截断小数:
5 / 2 == 2。要得到小数,至少一个操作数要是浮点数:
5.0 / 2 == 2.5。
%只能用于整数
++、--有前置和后置区别:
++a:先加,再使用;
a++:先使用,再加。
c int a = 5; int b = a++; // b = 5, a = 6 int c = ++a; // a = 7, c = 72、关系运算符
用于比较,结果是0或1。C 里没有专门的布尔类型时,0表示假,非0表示真。
| 运算符 | 含义 |
|---|---|
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
c int a = 3, b = 5; printf("%d\n", a < b); // 1 printf("%d\n", a == b); // 0常见坑:=是赋值,==才是判断相等。
c if (a = 5) { } // 错误意图:把 5 赋给 a,条件为真 if (a == 5) { } // 正确:判断 a 是否等于 53、逻辑运算符
用于逻辑判断。
| 运算符 | 含义 | 说明 |
|---|---|---|
&& | 逻辑与 | 两边都为真才为真 |
|| | 逻辑或 | 一边为真就为真 |
! | 逻辑非 | 真变假,假变真 |
c int a = 1, b = 0; printf("%d\n", a && b); // 0 printf("%d\n", a || b); // 1 printf("%d\n", !a); // 0短路特性:
&&左边为假,右边不再计算;||左边为真,右边不再计算。
c if (p != NULL && *p == 10) { } // 安全:p 为空时不会解引用4、赋值运算符
| 运算符 | 含义 | 等价于 |
|---|---|---|
= | 赋值 | |
+= | 加后赋值 | a = a + b |
-= | 减后赋值 | a = a - b |
*= | 乘后赋值 | a = a * b |
/= | 除后赋值 | a = a / b |
%= | 取模后赋值 | a = a % b |
&= | 按位与后赋值 | a = a & b |
|= | 按位或后赋值 | a = a | b |
^= | 按位异或后赋值 | a = a ^ b |
<<= | 左移后赋值 | a = a << b |
>>= | 右移后赋值 | a = a >> b |
c int a = 10; a += 5; // a = 15 a *= 2; // a = 30编程技巧:
c x |= (1 << n); // 第 n 位置一 x &= ~(1 << n); // 第 n 位清零 x ^= (1 << n); // 第 n 位翻转 int bit = (x >> n) & 1;//读取第N位置一用或,清零用与取反,翻转用异或,读取用与加移位。
5、位运算符
直接对二进制位操作,只能用于整数。
| 运算符 | 含义 | 示例 |
|---|---|---|
& | 按位与 | a & b |
| | 按位或 | a | b |
^ | 按位异或 | a ^ b |
~ | 按位取反 | ~a |
<< | 左移 | a << 1 |
>> | 右移 | a >> 1 |
例子:
c int a = 5; // 0101 int b = 3; // 0011 printf("%d\n", a & b); // 0001 = 1 printf("%d\n", a | b); // 0111 = 7 printf("%d\n", a ^ b); // 0110 = 6 printf("%d\n", a << 1); // 1010 = 10 printf("%d\n", a >> 1); // 0010 = 2常用技巧:
x & 1判断奇偶;
x << n相当于乘2^n;
x >> n相当于除2^n;
x ^= y可用于交换两个数。
6、条件运算符
C 里唯一的三目运算符:
c 条件 ? 表达式1 : 表达式2条件为真取表达式1,否则取表达式2。
c int a = 10, b = 20; int max = (a > b) ? a : b; // max = 207、逗号运算符
逗号表达式从左到右依次计算,整个表达式的值是最后一个表达式的值。
c int a = (1, 2, 3); // a = 3常见于for循环:
c for (int i = 0, j = 10; i < j; i++, j--) { // ... }8、指针与地址运算符
| 运算符 | 含义 |
|---|---|
& | 取地址 |
* | 解引用 |
[] | 数组下标 |
. | 结构体成员访问 |
-> | 结构体指针成员访问 |
c int a = 10; int *p = &a; printf("%d\n", *p); // 10 struct Point { int x, y; }; struct Point pt = {1, 2}; struct Point *q = &pt; printf("%d\n", q->x); // 19、其他运算符
| 运算符 | 含义 |
|---|---|
sizeof | 求类型或变量占多少字节 |
(type) | 强制类型转换 |
?: | 条件运算符 |
, | 逗号运算符 |
*、& | 指针相关 |
->、. | 成员访问 |
c printf("%zu\n", sizeof(int)); // 通常 4 double d = (double)5 / 2; // 2.5运算符的优先级:
算术>关系>逻辑>赋值
三、流程控制
if语句
1.最简单用法
if(表达式)
{
语句
};
如果表达式为真则执行语句。
条件运算符替代简单 if
简单的二选一可以用三目运算符:
c int max = (a > b) ? a : b;等价于:
c int max; if (a > b) { max = a; } else { max = b; }2.if的范围问题
(1)
if(表达式)
语句A;
语句B;
if默认只能控制语句A是否执行,语句B一定会执行。
(2)
if(表达式)
{
语句A;
语句B;
};
此时if可以控制A和B是否执行。
(3)if ...else...
c if (条件) { // 条件为真 } else { // 条件为假 }例子:
c int a = 3; if (a % 2 == 0) { printf("偶数\n"); } else { printf("奇数\n"); }(4)if...else if...else
c if (条件1) { // 条件1成立 } else if (条件2) { // 条件2成立 } else if (条件3) { // 条件3成立 } else { // 以上都不成立 }例子:
c int score = 85; if (score >= 90) { printf("优秀\n"); } else if (score >= 80) { printf("良好\n"); } else if (score >= 60) { printf("及格\n"); } else { printf("不及格\n"); }注意:else if是依次判断的,一旦某个条件成立,后面的分支就不再判断。
条件写法
条件可以是:
c if (a) // a 非 0 为真 if (!a) // a 为 0 为真 if (a > b) // 比较 if (a && b) // 逻辑与 if (a || b) // 逻辑或 if (a == 1 && b != 0)
表达式常见错误:
c if (a = 5) // 错误:这是赋值,不是比较 if (a == 5) // 正确:判断相等为了避免这种错误,可以把常量写左边:
c if (5 == a) // 如果少写一个 =,编译会报错
总结
if和else的要点:
if (条件)条件为真执行,为假跳过;else处理条件为假的情况;else if处理多个分支,按顺序判断;可以嵌套,但要注意可读性;
C 里
0为假,非0为真;=是赋值,==是判断相等,不要写错;建议始终写大括号,避免悬空
else问题
switch语句
switch是 C 语言里另一种分支控制结构,适合一个表达式对多个固定值进行判断的场景。它和if...else if功能上有重叠,但当分支条件是离散的整数值时,switch通常更清晰、更直观。
1、基本形式
c switch (表达式) { case 常量1: 语句; break; case 常量2: 语句; break; ... default: 语句; break; }例子:
c int day = 3; switch (day) { case 1: printf("星期一\n"); break; case 2: printf("星期二\n"); break; case 3: printf("星期三\n"); break; default: printf("其他\n"); break; }2、执行流程
计算
switch后面表达式的值;和各个
case后的常量比较;匹配到哪个
case,就从那里开始往下执行;遇到
break就跳出switch;如果都没有匹配,执行
default分支。
注意:default不是必须的,但建议写上,用来处理意外情况。
3、break 的作用
break用来跳出switch。如果没有break,程序会继续执行下一个case,这叫穿透。同时break也能跳出上一层循环。
c int a = 1; switch (a) { case 1: printf("one\n"); case 2: printf("two\n"); case 3: printf("three\n"); break; default: printf("other\n"); }输出:
text one two three
因为case 1没有break,会一直往下执行到break为止。
4、故意利用穿透
有时候穿透是故意用的,比如多个case共用一段代码:
c char ch = 'a'; switch (ch) { case 'a': case 'e': case 'i': case 'o': case 'u': printf("元音\n"); break; default: printf("辅音\n"); break; }这种写法是合法的,也很常见。
5、default 的位置
default可以放在任意位置,但通常放最后。它的执行时机是:所有case都不匹配时。
c switch (x) { default: printf("未知\n"); break; case 1: printf("一\n"); break; }虽然语法允许,但为了可读性,一般还是放最后。
6、case 的要求
case后面必须是整型常量表达式,不能是变量,也不能是浮点数。
合法:
c case 1: case 'A': case 1 + 2: case sizeof(int):非法:
c int n = 3; case n: // 错误,n 是变量 case 1.5: // 错误,不是整型 case "abc": // 错误,不是整型switch表达式也必须是整型或枚举类型,不能是浮点、字符串。
7、switch 与 if 的对比
适合用switch:
c switch (choice) { case 1: ... break; case 2: ... break; case 3: ... break; default: ... break; }适合用if:
c if (score >= 90) { ... } else if (score >= 80) { ... } else if (score >= 60) { ... } else { ... }区别:
switch适合等值判断,且值是离散常量;if适合范围判断和复杂条件;switch通常比一长串else if更清晰;switch只能判断整型、字符、枚举,不能判断浮点和字符串。
8、常见坑
忘记 break
导致意外穿透,执行了不该执行的分支。case 后是变量
c
case n: // 错误
在 case 中定义变量
C 语言里,case后面直接定义变量可能出问题,最好用大括号包起来:c
case 1: { int x = 10; printf("%d\n", x); break; }default 忘记 break
虽然放最后时通常不影响,但养成加break的习惯更安全。switch 表达式类型
不能用float、double、字符串。
9、总结
switch的要点:
语法:
switch (整型表达式) { case 常量: ... break; default: ... }适合对离散整数值做多分支判断;
break用来跳出,不加会穿透;穿透可以故意利用,让多个
case共用代码;case后必须是整型常量表达式;default处理所有不匹配的情况;不能用于浮点数、字符串和范围判断。
switch是“对号入座”式的分支,if是“条件判断”式的分支。值离散、分支多时用switch,条件复杂、范围判断时用if。
循环语句
1.for循环
c for (初始化表达式; 条件表达式; 更新表达式) { 循环体; }执行流程:
执行初始化表达式,只执行一次;
判断条件表达式,为真则执行循环体,为假则结束循环;
执行循环体;
执行更新表达式;
回到第 2 步,继续判断。
c for (int i = 0; i < 5; i++) { printf("%d\n", i); }int i = 0:初始化,只执行一次;i < 5:循环条件,每次循环前判断;i++:更新,每次循环体执行完后执行。
这三个表达式都可以省略,此时等价于while(1),但分号不能省。
for循环嵌套:
1.
c for(1;2;3)//1 for(4;5;6)//2 A;//3 B;//4整体是两个语句,1、2、3是第一个语句,4是第二个语句。
2.
for(1;2;3)//1 for(4;5;6)//2 { A;//3 B;//4 };此时是一个语句。
例题:输出九九乘法表
for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { printf("%d*%d=%d ", j, i, i * j); } printf("\n"); }2.while循环
c while (条件表达式) { 循环体; }执行流程:
判断条件表达式;
为真,执行循环体;
回到第 1 步,继续判断;
为假,结束循环。
while 和 for 的关系
for可以看成while的紧凑写法:c for (int i = 0; i < 5; i++) { printf("%d\n", i); }等价于:
c int i = 0; while (i < 5) { printf("%d\n", i); i++; }区别:
for适合次数明确的计数循环;
while适合条件驱动、次数不确定的循环;
while的初始化通常在循环外,更新在循环体内,容易忘记写更新导致死循环。
break 和 continue
break:如果用于循环则终止整个循环;如果用于switch则是用于终止switch;break不能直接用于if,除非if属于循环内部一个语句。
例:
c for(i=0;i<3;++i) { if(3>2) break; printf("嘿嘿!\n");//永远不会输出 }break虽然是if内部语句,但是终止的是外面的for循环。在多层嵌套中,break只能终止最里面包裹它的那个循环。在多层 switch中终止距离最近的switch。
continue:用于跳过本次循环余下的语句,转去判断是否需要执行下次循环。
例:
c while(1) { A; B; continue; C; D; }C和D会被跳过不执行。