本系列的目的
一方面,为考研的理论知识做准备,博主决定重新过一遍语法知识;另一方面,在多技术栈各种知识的学习过程中,深刻认识到C/C++基础的重要性,于是决定撰写本系列。
本系列特征:
- 相对冷门知识
- 相对基础知识
- 编程中容易犯的错
本系列基于:
- 海贼宝藏(https://www.haizeix.com/)课程
- 《C陷阱与缺陷》(C Traps and pitfalls)
- 个人OJ刷题总结
条件表达式
条件表达式的1和0不仅代表布尔值,也是可以用于运算的真实值
(小技巧:a += (a == b)可代替分支)
a += (a == b);//如果a == b,a += 1,否则 a += 0
printf("%d\n", a);
非0即为真值
用1表达真,用0表达假,但实际上程序中的仍何值只要非0都会被判定为真,包括负数等。
短路原则
对于逻辑表达式,若前面的表达式可确定整体的值,则不再计算后面的表达式
技巧:代替分支
a < b && printf("Yes\n");
!(a < b) && printf("No\n");
分支结构
初学者容易犯的错误
(表达式)不可连续判断
if (0 < x <= 60)
cout << "yes!" << endl;
当x = 50, 程序实际的执行是0 < x先判定,即得到1值,在1 < 60 判断。
switch-case中,进入case后将一直执行直至break或结束
所以有:
swtich(a){
case 1:
case 2:cout << "yes" << endl;
case 3:cout << "No" << endl;
}
无break时,例如从1进入,将依次执行2、3中的yes和no输出(如果有default也会执行)
当然,我们也可以利用这种特性:
swtich(a){
case 1:
case 2:cout << "yes" << endl;break;
case 3:
case 4:cout << "No" << endl;break;
}
如果我们此时的需求是1和2时有相同的输出,3和4时有相同的输出,那么这种特性利用可以使程序清晰而方便。
cpu的分支预测
当cpu以流水线形式执行时,分支语句可能会导致这样的问题:下一条语句的执行需要读取上一条语句的结果值,但此时往往上一条语句还未运行完成,于是cpu预测由此诞生。
Linux有宏如下:
#define likely(x) _builtin_expect(!!(x), 1)
#define unlikely(x) _builtin_expect(!!(x), 0)
//likely表示x经常成立
//unlikely表示宏不经常成立
- 两次!的意义:逻辑值的归1化(即将真值都归1)
- 该宏即协助cpu做分支预测
应用实例如下:(选择leetcode-9:回文数)
class Solution {
public:
bool isPalindrome(int x) {
// if (__builtin_expect(!!(x < 0), 0)) return false;
if (x < 0) return false;
long long y = x;
long long z = 0;
while (x){
z = z * 10 + x % 10;
x /= 10;
}
return z == y;
}
};
class Solution {
public:
bool isPalindrome(int x) {
if (__builtin_expect(!!(x < 0), 0)) return false;
// if (x < 0) return false;
long long y = x;
long long z = 0;
while (x){
z = z * 10 + x % 10;
x /= 10;
}
return z == y;
}
};
此处借用leetcode 的ai复杂度分析,优化前为log10(N),优化后为log2(N)
(由于优化有限,有时候该题跑出来的实际速度可能相差不大乃至相同,但是根据复杂度分析是能看出确实变快了)
循环结构
技巧:不断读入用户输入
int n;
while (scanf("%d", &n) != EOF){
printf("2 * x = %d\n", 2 * n);
}
EOF:
- linux/MacOs:Ctrl/Control + D
- Windows:Ctrl + Z
- 循环的充分利用
for (int i = 0, f1 = 1, f2 = 1; i < 20; i++, f2 += f1, f1 = f2 - f1){
printf("%d\n", f1);
}
并不提倡这种做法,但可以据此充分理解for的语法
但不允许这样初始化:
for (int i = 0, int f1 = 1, f2 = 1; i < 20; i++, f2 += f1, f1 = f2 - f1)
即连续使用int,因为for仅允许使用一个声明,而int i ,f1被视为一个声明,两次使用int则视为两个声明
被封印的goto语句
由于goto语法功能的可代替性,工程上也不常用(可能会破坏代码的结构)
- goto被用于将控制无条件转移到所想要的位置。
- 若跳过的代码中有变量定义,不影响编译和使用相关变量,但相关初始化不会生效。
- 基本使用示范:
#include<stdio.h>
int main(){
goto lab_1;
printf("Hello World!\n");
lab_1:
printf("Hello Hang Zhou!\n");
//----------------------------------------//
goto lab_2;
int a, b;
scanf("%d%d", &a, &b);
lab_2:
printf("%d\n", a * b);
return 0;
}
- goto模拟if-else:
#include<stdio.h>
int main(){
int n;
scanf("%d", &n);
n % 2 == 0 && ({goto if_stmt; 0;});//小括将大括号变为一个有返回值的表达式,且返回值等于大括号中最后一条表达式的值
!(n % 2 == 0) && ({goto else_stmt; 0;});//大括号的意义是将goto和后面一条语句合成为一个复合语句
if_stmt:
printf("%d is even\n", n);
goto if_end;
else_stmt:
printf("%d is odd\n", n);
if_end:
return 0;
}
- goto模拟while:
#include<stdio.h>
int main(){
int n, i = 1;
scanf("%d", &n);
judge:
if (i <= n) goto stmt;
else goto while_end;
stmt:
printf("%d ", i);
i++;
goto judge;
while_end:
printf("\n");
return 0;
}
- goto模拟for:
#include<stdio.h>
int main(){
int n;
scanf("%d", &n);
int i = 1;
for_1:
goto for_2;
for_2:
if (i <= n)
goto for_4;
else
goto for_end;
for_3:
i++;
goto for_2;
for_4:
if (i % 3 == 0) goto for_3;//不能使用continue,只能在循环中使用
else {
printf("%d ", i);
goto for_3;
}
for_end:
printf("\n");
return 0;
}
C中“一条语句”的理解
共五种类型:
- 复合语:
{staement};
——由大括号构成的语句 - 表达式语句:
expression;
——包含变量各种运算的式子 - 选择(分支)语句:
if_else和switch_case
- 循环语句:
while/do_while/for
- 跳转语句:
break/continue/return/goto
(第六种:空语句——仅由;组成,其实也称为一条语句)
if,while等控制的都是后面的一条语句,所以if后可以非大括号的形式直接接上一个for循环,for后也可以不需要大括号接上if_else
if(1){
cout << "1" << endl;
cout << "2" << endl;
}
//-----------------------------
for (int i = 0; i < 10; i++);{//初学者容易犯的错,此时for后的一条语句实际是空语句而非复合语句
cout << "1" << endl;
cout << "2" << endl;
cout << "3" << endl;
}
//------------------------------
int n = 5;
while(n--)
if (1){
cout << "1" << endl;
cout << "2" << endl;
}//此时的if整体也是一条语句,可以不需要大括号
//------------------------------
//循环嵌套打印一个三维矩阵
for(int i = 1; i <= 3; i++){
for (int j = 1; j <= 3; j++)
printf("(%d, %d) ",i, j);
printf("\n");
return 0;
}