补充

本章对一些知识进行补充说明,这些知识在语法基础阶段应用场景不多,所以在前面为了降低理解难度并没进行讲解,在此处进行列举。

1.变量类型补充

整数类型在定义时,可以使用 signedunsigned 关键字进行修饰。

signed 表示带符号整数(默认),这个用的少,因为默认就是带符号的。

unsigned 表示无符号整数,这样定义出来的整数变量无法存储负数。

例如 unsigned int unsigned long long,相较于带符号整数,无符号整数腾出了一个符号位,所以能存更大的数字。

2.C风格的输入输出

scanfprintf 其实是C语言提供的函数,大部分情况下它们输入输出的速度快于 cincout

在使用时需要配合占位符来使用,先看例子:

#include <bits/stdc++.h>
using namespace std;

int main() {
	int x;
	double y;
	scanf("%d %lf", &x, &y);  // 输入
	printf("x: %d y: %lf", x, y);  // 输出
	return 0;
}

在这段代码中,%? 即是一个占位符,其意思是在此处会 输入/输出 一个对应类型的数据,根据 % 后面的内容不同,代表的数据类型也不同,下面进行了说明:

在输入输出文本中,除了占位符外,可以加入一些自定义的文本,在一些规定了格式的输入输出的场景下,scanfprintf 代码写起来会更简单。

scanf 中的 & 是什么?

在这里,& 是取址运算符,即获取变量在内存中的地址,不用过于深究,记住这个区别即可。

另外,占位符还可以控制输出的格式,例如保留小数位数,位宽等等,下面例子进行了一些常用的:

printf("%.3lf\n", 3.14159);  // 保留三位小数  \n为换行
printf("%3d", 12);  // 设置位宽为3,不足的会在前面补空格
printf("%03d", 12);  // 设置位宽为3,不足的会在前面补0

还有更多用法可以自行探索。

3.#define 命令

#define 是一种预处理命令,用于定义宏,本质其实是用一个文本替换另一个文本,例如:

#include <bits/stdc++.h>
#define qwq cout << "hello"
using namespace std;

// 这里相当于会把代码中所有独立的 qwq 文本替换为 cout << "hello"
// 但如果是非独立的文本则不受影响,例如 aqwq 是不会被影响的。

int aqwq = 5;

int main() {
    qwq;
    cout << '\n' << aqwq;
	return 0;
}

#define 使用起来是有风险的,因为其作用域是整个程序,可能会导致一些粗心的错误,较为常见的一个用法是:

#include <bits/stdc++.h>
#define int long long
using namespace std;

signed main() {
	...
	return 0;
}

因为大部分 oier 都习惯了 int,所以这样可以避免忘记开 long long 导致的错误。但随之而来的是可能会增大常数导致 TLE,或者因为爆空间而 MLE,所以使用时要自己能算清楚时间复杂度空间复杂度之类的。

4.三目运算符

三目运算符是 if else 的一种简写,常用于简单的条件判断,特别是需要根据条件给变量赋值时。

语法格式为: 判断条件 ? 成立的情况 : 不成立的情况

例如:

cout << (2 > 1 ? "yes" : "no") << '\n';  // 会输出 yes 

int x, y, z;
z = (x > y) ? y : x;  // 如果 x>y,那么 z=y,否则 z=x

初赛的程序阅读可能会出现,所以还是了解一下这个执行逻辑,自己写代码的时候没必要用。

5.加快输入输出

在数据量级较高的情况下(输入 105 个数据以上),scanfprintf 的效率要比 cincout 的效率高 10 倍不止,不过 scanf 读入字符数据时,会需要注意 空格 和 换行的影响,处理起来会比较麻烦,所以建议大家用以下方式:

int main() {
	// 这行代码作用是关闭输出流,取消c风格的输入输出和c++风格输入输出的
	// 同步,写了之后注意两者不要混用。
	ios::sync_with_stdio(false);
	// 这两行是把 cin 和 cout 取消关联,进行输入操作时不再强行刷新缓冲区。
	cin.tie(0);
	cout.tie(0);
	...
	后面正常用cin cout 读写数据
}

加上以上三行代码后,cincout 的效率和C风格的相差无几,甚至是快一些的,但使用后要注意以下几点:

  1. 使用后就不要用 scanfprintf 了,因为同步流已经关闭了,全用 cincout 即可。

  2. 换行时写 cout << '\n',不要用 endl,因为 endl 通常会刷新缓冲区,但使用上述代码后它不再具备刷新的功能。

  3. 这段代码的作用只是提高输入输出的效率,对于程序的结果不会任何影响。

6.初始化数组中的值

众所周知,数组定义在全局时,默认里面的元素都为 0

但如果在某些场景下,需要把数组中的初始值都设置为其它值的时候,怎么做呢?容易想到的是直接手写一个循环:

for (int i=1; i<=n; i++) a[i] = 1;  // 都设置成 1

利用 memset 函数可以达到相同的效果:

memset(a, 0, sizeof a);  // 批量设置为 0

不过 memset 并不能随意批量设置值,中间的数据在赋值时会被转为 unsigned char 类型,然后把这八位二进制数据一字节一字节的复制过去,所以常用的只有 0-10x3f,若是其他数据得到的结果和我们的预期会不一样。

值得一提的是 0x3f 被复制到 int 类型中得到的结果是 0x3f3f3f3f,实际值为 1061109567,即 10^9 级别。

另,memset 的时间复杂度和循环赋值是相同的 O(N) 级别,所以只使用循环赋值也没啥问题。

7.exit()

众所周知,在其他函数中使用 return 只能结束这次函数运行,没法结束程序运行。

但使用 exit(0) 可以结束程序运行,并返回 0 表示程序正常结束,在一些递归函数中使用会有奇效。

void func(int x) {
	if (x == 1) {
		exit(0);
	}
	cout << "函数调用前:" << x << "\n";
	func(x-1);
	cout << "函数调用后:" << x << "\n";  // 这段其实不会输出。
}

func(5);