c++笔记

1
2
3
4
5
6
7
8
9
10
11
# ASCII码表

## 基础代码
```cpp
#include <iostream>
using namespace std;

int main()
{
cout << "Hello World!";
}

开平方

sqrt(数字)
需包含数学库:#include <cmath>
参考链接:C++ cmath库

格式化输出

在C语言中,使用 printf函数可以格式化输出浮点数,保留指定的小数位数。如果要保留两位小数,可以使用 %.2f格式说明符。

1
2
3
4
5
6
7
#include <stdio.h>

int main() {
double num = 3.14159;
printf("%.2f\n", num); // 输出: 3.14
return 0;
}

注意事项

  • printf函数中的 %.2f表示输出的浮点数将保留两位小数。
  • 如果小数部分第三位数字大于或等于5,则会进行四舍五入。

示例代码

1
2
3
4
5
6
7
8
#include <stdio.h>

int main() {
double value = 123.4567;
printf("原始值: %f\n", value); // 输出: 原始值: 123.456700
printf("保留两位小数: %.2f\n", value); // 输出: 保留两位小数: 123.46
return 0;
}

一维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
int a[5] = { 0 }; // 将所有元素赋值为0
int b[5] = { 0 }; // 将第一个元素赋值为1,其余值为0(注释可能有误,实际第一个元素为1?)
int c[5] = { 0,1,2,3,4 }; // 将数组中的每个元素分别赋值
int c[] = { 0,1,2,3,4 }; // 将数组中的每个元素分别赋值
for (int i = 0; i < 5; i++) { // 循环将每个数据赋值为0
a[i] = -1;
}
for (int i = 0; i < 5; i++) { // 循环输出
printf("%d", a[i]);
}
}

快速排序

1
2
#include<algorithm>
sort(a + 1, a + 1 + n); // 从下标为1开始快速排序

二维数组

1
2
3
4
5
int a[2][3] = {0};                   // 对全部元素初始化为0
int b[2][3] = {1,2,3,4,5,6}; // 对全部数组元素初始化为1至6
int c[2][3] = {{1},{4}}; // 对部分元素初始化,未初始化的元素为0
int d[2][3] = {{1,2},{5}}; // 对数组第一维的前两个元素初始化为1和2
// 第二维的前一个元素初始化为5,其他为0

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;
int main(){
int a[105][105], n, m;
cin >> n >> m; // 输入n行m列
for (int i = 1; i <= n; i++) { // 循环第i行
for(int j = 1; j <= m; j++) { // 循环第j列
cin >> a[i][j];
}
}
for (int i = 1; i <= n; i++) { // 循环第i行
for(int j = 1; j <= m; j++) { // 循环第j列
cout << a[i][j] << " ";
}
cout << endl; // 一行结束后换行
}
}

字符数组

在初始化时可以直接将整个字符串初始化给字符数组。但是不能用 =赋值语句字符串赋值给字符数组。(声明时 =为初始化,其他时候为赋值)

1
2
3
4
5
6
7
8
9
10
11
#include <cstdio>
using namespace std;
int main()
{
char str1[20] = "34225553w5643"; // 将str1[]初始化为对应的字符串
char str2[20] = { 0 }; // 将str2[]中所有元素初始化为0
char str3[20];
for(int i = 0; i < 10; i++) {
str3[i] = 'a'; // 将str3[]中前10个元素初始化为字符'a'
}
}

字符数组和字符串的主要区别在于是否使用了字符串结束标志 \0。字符数组中的元素可以是任意字符,并不要求最后一个元素是 \0。但是,当字符数组被用作字符串时,就必须以 \0结尾。

字符数组输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <bits/stdc++.h>
using namespace std;
int main()
{
char str[105];
int n;
cin >> n;
for (int i = 0; i < n; i++) { // 循环输入n个字符
cin >> str; // scanf("%c", &str[i]);
}
for (int i = 0; i < n; i++) { // 循环输出n个字符
cout << str[i]; // printf("%c", str[i]);
}
}
  • cin >> str[i]; // 输入单个字符时会忽略空格和换行符
  • scanf("%c", &str[i]); // 输入单个字符时不会忽略空格和换行符
  • str[i] = getchar(); // 输入单个字符时不会忽略空格和换行符

字符数组 cin 和 cout

第一种

1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;
int main()
{
char str[105];
cin >> str;
cout << str; // 输出遇到'\0'停止
return 0;
}

第二种

1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;
int main()
{
char str[105];
cin.getline(str, 100);
cout << str; // 输出遇到'\0'停止
return 0;
}

对两个程序都输入 I love c++

  • cin >> str; // 输入字符串时读取到空格或换行符为止
  • cin.getline(str, 100); // 输入100个字符或遇到换行符为止
  • cin.getline(str, 100, 'a'); // 输入100个字符或遇到指定字符(‘a’)为止(换行符不会停止输入)

因此左侧程序输出 I,右侧程序输出 I love c++

字符串相关函数

(此处内容缺失)

字符串

1
2
3
4
5
6
7
8
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1; // 声明一条空字符串
string str2[105]; // 声明一个字符串数组(声明105条字符串str2[])
}

函数定义

1
2
3
4
5
6
返回类型 函数名(参数表){
语句a;
语句b;
…………
语句n;
}

cin 和 cout 优化

1
2
3
// cin和cout优化
ios::sync_with_stdio(0); // 取消同步
cin.tie(0), cout.tie(0); // 加速输入输出

快读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <ctype.h>   // 需要包含此头文件以使用 isdigit() 函数

/**
* 快速读入一个 long long 整数。
* 自动跳过前导空白字符(空格、换行等),正确处理负号,
* 遇到非数字字符时停止读取。
*
* 返回值:读取到的整数(long long 类型)。
*/
long long read() {
long long x = 0; // 用于累加数字部分的值
short f = 1; // 符号标志,1 表示正数,-1 表示负数
char ch; // 当前读取的字符

ch = getchar(); // 读取第一个字符

// 跳过所有非数字字符(如空格、换行、字母等),同时检测负号
while (!isdigit(ch)) {
if (ch == '-') { // 若遇到负号,标记为负数
f = -1;
}
ch = getchar(); // 继续读取下一个字符
}

// 此时 ch 已经是数字字符,开始将连续的数字字符转换为整数
while (isdigit(ch)) {
// 将当前数字字符('0'~'9')转换为数值并累加
// ch - '0' 得到字符对应的整数值
x = x * 10 + (ch - '0');
ch = getchar(); // 读取下一个字符
}

// 返回带符号的结果
return x * f;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
# 快写

```cpp
void write(long long x) {
if (x < 0) { // 如果是负数,先输出负号
putchar('-');
x = -x; // 将 x 转为正数以便后续处理
}
if (x > 9) { // 如果 x 大于 9,递归调用 write 输出高位数字
write(x / 10);
}
putchar(x % 10 + '0'); // 输出当前最低位数字
}

原理

这是一个用于输出整数的递归函数,其核心思想是通过不断分解数字,从高位到低位依次输出字符。下面分步骤解析其原理:

  1. 处理负数
    如果输入 x 小于 0,先输出一个负号 '-',然后将 x 取反转为正数。这样后续只需处理正数部分。

  2. 递归分离高位数字
    如果 x 大于 9(即至少两位数),则递归调用 write(x / 10)

    • x / 10 会去掉当前数字的最低位(例如 123 / 10 = 12),递归继续处理剩下的高位。
    • 递归会一直深入到最高位(当数字变成个位数时停止递归)。
  3. 输出当前位
    递归返回后,执行 putchar(x % 10 + '0')

    • x % 10 取出当前数字的最低位(例如 123 % 10 = 3)。
    • 加上字符 '0' 将其转换为对应的数字字符(如 3 + '0' 得到 '3')。
    • 由于递归是“先递归、后输出”,所以输出的顺序正好是从最高位到最低位。

举例说明:以 write(123) 为例

  • 第一次调用:x=123x>9,递归调用 write(12)
    • 第二次调用:x=12x>9,递归调用 write(1)
      • 第三次调用:x=1x≤9,不继续递归,直接执行 putchar(1%10+'0') → 输出 '1',返回
    • 第二次调用继续:执行 putchar(12%10+'0') → 输出 '2',返回
  • 第一次调用继续:执行 putchar(123%10+'0') → 输出 '3',结束
    最终输出 "123"

如果输入负数如 write(-456)

  • 先输出 '-'x 变为 456,然后按正数逻辑输出 "456",最终得到 "-456"

该函数巧妙地利用递归实现了数字的逐位输出,避免了使用循环或字符串转换,且能正确处理负号。


inline 关键字

inline 是 C 和 C++ 中的一个关键字,它的核心含义是建议编译器将函数体在调用点“展开”,以避免函数调用的开销。

然而,在现代编译器和语言标准中,inline 的实际作用已经从“优化建议”演变为解决“单一定义规则”的链接问题

1. 历史作用:优化建议

在早期的 C/C++ 中,函数调用是有开销的(压栈、跳转、返回等)。使用 inline 相当于告诉编译器:

“这个函数很小,被调用的很频繁,请直接把代码复制到调用处,别走 call 指令。”

但这只是建议,编译器有权忽略它(例如递归函数或函数体过大时,编译器会无视 inline,依然按普通函数处理)。

2. 现代核心作用:解决链接冲突

这是目前 inline 最重要的意义。C++ 遵循 ODR

  • 普通函数:如果在头文件中定义,被多个 .cpp 文件包含,链接时会出现“重复定义”错误。
  • inline 函数:允许在头文件中定义。编译器会将该函数标记为“弱符号”。链接器在合并目标文件时,如果发现多个相同的 inline 函数定义,会保留一份,丢弃其他的,从而避免报错。

因此,inline 的主要用途现在变成了:
允许将函数的定义放在头文件中,供多个源文件包含使用。

3. 典型使用场景

场景一:定义在类内部的成员函数

在 C++ 中,在类内部直接定义的成员函数,默认就是 inline 的。

1
2
3
4
5
6
7
class MyClass {
public:
// 默认就是 inline 的
int getValue() const { return value_; }
private:
int value_;
};

场景二:定义在头文件中的全局函数或静态成员

如果你需要在头文件中写一个通用函数,必须加上 inline,否则包含该头文件的多个 .cpp 文件就会链接失败。

1
2
3
4
5
6
7
// utils.h
#pragma once

// 如果不加 inline,包含两个 cpp 文件就会报 "multiple definition"
inline int add(int a, int b) {
return a + b;
}

场景三:模板函数

注意: C++ 中的模板函数(如 template <typename T> void foo() {})默认具有类似 inline 的链接属性。因此,模板通常不需要显式加 inline(除非你希望强制展开或特化版本)。

4. inline 与编译器优化(现代视角)

在今天的编译器(GCC、Clang、MSVC)开启优化(如 -O2)时:

  • 编译器会忽略你的 inline 建议。它有一套复杂的成本模型,决定哪些函数内联,哪些不内联。
  • 即使你没写 inline,编译器也可能自动内联(如非常简单的函数)。

5. 常见误区

误解 真相
inline 能强制内联 不能。它只是建议。强制内联通常使用编译器扩展,如 __attribute__((always_inline)) (GCC) 或 __forceinline (MSVC)。
inline 能让代码更快 不一定。内联会增加二进制文件大小。如果滥用,会导致指令缓存命中率下降,反而变慢。
inline 函数不能递归 可以递归,但编译器通常不会内联递归函数(除非在编译时能展开的尾递归)。
inline 影响调试 是的。开启内联后,调试时可能无法在函数内部设置断点,或变量看不全。Debug 模式通常禁用内联。

6. C++17 的 inline 变量

C++17 扩展了 inline 的能力,允许定义内联变量。这对于头文件中定义全局变量非常有用,解决了静态成员变量需要在 .cpp 文件单独定义的繁琐问题。

传统方式:

1
2
3
4
5
6
// .h
class MyClass {
static int s_value; // 声明
};
// .cpp
int MyClass::s_value = 10; // 定义

C++17 方式:

1
2
3
class MyClass {
static inline int s_value = 10; // 直接在头文件定义,无需 .cpp
};

总结

当你使用 inline 时,可以这样理解:

  1. 在链接层面:告诉编译器“这个函数可能在多个编译单元中出现,请允许它们在头文件中定义,并在链接时去重”。
  2. 在优化层面:把决定权交给编译器,它通常会根据优化级别和函数大小来决定是否真正展开。

超级快读

1
2
3
4
const int maxn = 1e6;
char* p1, * p2;
char buf[maxn];
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, maxn, stdin), p1 == p2) ? EOF : *p1++)
1