1. C++ bitset 基本用法
bitset
是 C++ 标准库中的一个类,用于处理固定大小的二进制位序列。
1.1 引入头文件使用 #include <bitset>
。
1.2 初始化
- 默认初始化:所有位为
0
std::bitset<8> b1; // 00000000
- 从整数初始化
std::bitset<8> b2(42); // 00101010
- 从字符串初始化
std::bitset<8> b3("101010"); // 00101010
1.3 常用操作
- 访问位
b1[0] = 1; // 设置第0位为1 bool bit = b1[0]; // 获取第0位的值
- 设置/重置位
b1.set(); // 所有位设置为1 b1.reset(); // 所有位重置为0 b1.flip(); // 所有位取反 b1.set(pos); // 设置pos位为1 b1.reset(pos); // 设置pos位为0 b1.flip(pos); // 反转pos位
- 统计
int count = b1.count(); // 统计1的个数 int size = b1.size(); // 获取位总数 bool any = b1.any(); // 是否存在1 bool none = b1.none(); // 是否全为0
1.4 转换
- 转换为整数
unsigned long val = b1.to_ulong();
- 转换为字符串
std::string s = b1.to_string();
1.5 位运算
支持位运算:
std::bitset<8> b4 = b1 & b2; // 与
std::bitset<8> b5 = b1 | b2; // 或
std::bitset<8> b6 = b1 ^ b2; // 异或
std::bitset<8> b7 = ~b1; // 取反
b1 <<= 2; // 左移
b1 >>= 2; // 右移
1.6 注意事项
bitset
的大小在编译时确定。- 访问越界会导致未定义行为。
to_ulong()
在bitset
表示的数值超出unsigned long
的范围时会抛出异常。
2. 快速统计容器内元素出现次数 (C++20)
C++20 引入了 std::ranges::count
和 std::ranges::count_if
,可以更方便地统计容器中元素的出现次数。
2.1 示例
#include <iostream>
#include <vector>
#include <algorithm> // C++20 后可能不需要单独包含,取决于具体实现
int main() {
std::vector<int> nums = {1, 2, 2, 3, 3, 3, 4, 4, 4, 4};
// 统计值为3的元素个数
size_t count_3 = std::ranges::count(nums, 3);
std::cout << "Number of 3s: " << count_3 << std::endl;
// 统计大于2的元素个数
size_t count_greater_than_2 = std::ranges::count_if(nums, [](int x){ return x > 2; });
std::cout << "Number of elements greater than 2: " << count_greater_than_2 << std::endl;
return 0;
}
2.2 说明
std::ranges::count(range, value)
: 统计range
中等于value
的元素个数。std::ranges::count_if(range, predicate)
: 统计range
中满足predicate
的元素个数。range
可以是任何支持迭代器的范围,例如std::vector
,std::array
, 甚至 C 数组。predicate
是一个可调用对象(函数、函数对象、Lambda 表达式),接受一个元素并返回true
或false
。
2.3 优点
- 简洁易用,代码可读性高。
- 避免了手动编写循环的繁琐。
3. 移动字符串(避免复制)
在 C++ 中,移动字符串以避免复制可以使用 std::move
。这会将字符串的所有权转移到另一个变量,而无需复制底层数据。
3.1 示例
#include <iostream>
#include <string>
#include <utility> // std::move
int main() {
std::string str1 = "Hello, world!";
std::string str2 = std::move(str1);
std::cout << "str1: " << str1 << std::endl;
std::cout << "str2: " << str2 << std::endl;
return 0;
}
3.2 说明
std::move(str1)
将str1
转换为右值引用,表示可以安全地将资源(字符串的底层数据)转移到另一个对象。- 移动后,
str1
的状态变为 “moved-from”,它的值是不确定的,但仍然是一个有效的std::string
对象。通常,它会变成空字符串,但不要依赖这种行为。 str2
现在拥有了原来str1
的字符串数据,避免了复制操作。
3.3 适用场景
- 当你需要将字符串从一个函数返回,或者赋值给另一个变量,但不再需要原来的字符串时,可以使用
std::move
来提高效率。 - 特别是在处理大型字符串时,移动操作可以显著减少性能开销。
4. 返回最大值
4.1 C++20
C++20 引入了 std::max
的 constexpr
版本,以及 std::ranges::max
,可以更方便地返回最大值。
4.1.1 示例
#include <iostream>
#include <algorithm> // std::max
#include <vector>
#include <ranges> // std::ranges::max
int main() {
// 使用 std::max
constexpr int a = 10;
constexpr int b = 20;
constexpr int max_value = std::max(a, b); // constexpr 保证编译时求值
std::cout << "Max of a and b: " << max_value << std::endl;
// 使用 std::ranges::max
std::vector<int> nums = {5, 2, 8, 1, 9};
auto max_element = std::ranges::max(nums);
std::cout << "Max element in nums: " << max_element << std::endl;
return 0;
}
4.1.2 说明
std::max(a, b)
: 返回a
和b
中的较大值。std::ranges::max(range)
: 返回range
中的最大值。range
可以是任何支持迭代器的范围。- 如果
range
为空,则行为未定义。
4.2 C++11
在 C++11 中,可以使用 std::max
返回两个值中的最大值,或者结合 std::max_element
和容器的迭代器来找到容器中的最大值。
4.2.1 示例
#include <iostream>
#include <algorithm> // std::max, std::max_element
#include <vector>
int main() {
// 使用 std::max
int a = 10;
int b = 20;
int max_value = std::max(a, b);
std::cout << "Max of a and b: " << max_value << std::endl;
// 使用 std::max_element
std::vector<int> nums = {5, 2, 8, 1, 9};
auto max_element_it = std::max_element(nums.begin(), nums.end());
if (max_element_it != nums.end()) {
std::cout << "Max element in nums: " << *max_element_it << std::endl;
}
return 0;
}
4.2.2 说明
std::max(a, b)
: 返回a
和b
中的较大值。std::max_element(begin, end)
: 返回指向[begin, end)
范围内最大元素的迭代器。- 如果范围为空,则返回
end
。 - 需要通过解引用迭代器来获取最大值。
- 如果范围为空,则返回
5. C++ 中 Lambda 表达式
Lambda 表达式是一种在 C++ 中定义匿名函数的简洁方式。它们可以捕获局部变量,并在需要函数对象的地方使用。
5.1 语法
[capture-list](parameter-list) -> return-type {
// 函数体
}
capture-list
: 捕获列表,指定哪些外部变量可以被 Lambda 表达式访问,以及如何访问(值捕获或引用捕获)。parameter-list
: 参数列表,与普通函数的参数列表类似。return-type
: 返回类型,可以省略,由编译器自动推导。function-body
: 函数体,包含 Lambda 表达式的执行代码。
5.2 示例
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
int x = 10;
int y = 5;
// 值捕获 x 和 y
auto add = [x, y]() { return x + y; };
std::cout << "x + y = " << add() << std::endl;
// 引用捕获 x
auto increment_x = [&x]() { x++; };
increment_x();
std::cout << "x = " << x << std::endl;
// 泛型 Lambda (C++14)
auto multiply = [](auto a, auto b) { return a * b; };
std::cout << "2 * 3 = " << multiply(2, 3) << std::endl;
std::cout << "2.5 * 4 = " << multiply(2.5, 4) << std::endl;
// 在算法中使用 Lambda
std::vector<int> nums = {1, 2, 3, 4, 5};
std::transform(nums.begin(), nums.end(), nums.begin(), [](int n) { return n * 2; });
for (int num : nums) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
5.3 捕获模式
[]
: 不捕获任何外部变量。[x, y]
: 值捕获x
和y
。Lambda 表达式内部会创建x
和y
的副本。[&x, &y]
: 引用捕获x
和y
。Lambda 表达式内部可以直接修改x
和y
的值。[=]
: 值捕获所有外部变量。[&]
: 引用捕获所有外部变量。[=, &x]
: 值捕获所有外部变量,但引用捕获x
。[&, x]
: 引用捕获所有外部变量,但值捕获x
。
5.4 优点
- 简洁,可以在需要函数对象的地方直接定义函数。
- 灵活,可以捕获外部变量,方便访问局部状态。
- 可读性好,可以使代码更易于理解。
6. 避免外部定义可能带来的函数调用复杂性
在 C++ 中,为了避免外部定义可能带来的函数调用复杂性,可以使用以下方法:
- 使用命名空间 (Namespace): 将相关的函数和类放在同一个命名空间中,可以避免命名冲突,提高代码的可读性和可维护性。
- 使用静态函数 (Static Function): 如果函数只在当前文件中使用,可以将其声明为静态函数,限制其作用域,避免被其他文件调用。
- 使用匿名命名空间 (Unnamed Namespace): 在源文件中使用匿名命名空间,可以使其中的函数和变量只在当前文件中可见,相当于静态函数和变量。
- 使用 Lambda 表达式 (Lambda Expression): 对于简单的函数,可以使用 Lambda 表达式直接在调用处定义,避免单独定义函数。
- 使用内联函数 (Inline Function): 对于频繁调用的短小函数,可以将其声明为内联函数,减少函数调用的开销。
6.1 示例
#include <iostream>
// 1. 使用命名空间
namespace MyNamespace {
void MyFunction() {
std::cout << "MyFunction in MyNamespace" << std::endl;
}
}
// 2. 使用静态函数
static void MyStaticFunction() {
std::cout << "MyStaticFunction" << std::endl;
}
// 3. 使用匿名命名空间
namespace {
void MyAnonymousFunction() {
std::cout << "MyAnonymousFunction" << std::endl;
}
}
int main() {
// 调用命名空间中的函数
MyNamespace::MyFunction();
// 调用静态函数
MyStaticFunction();
// 调用匿名命名空间中的函数
MyAnonymousFunction();
// 4. 使用 Lambda 表达式
auto myLambda = []() { std::cout << "Lambda Expression" << std::endl; };
myLambda();
return 0;
}
6.2 说明
- 命名空间: 可以将相关的函数和类组织在一起,避免命名冲突。
- 静态函数: 限制函数的作用域,使其只在当前文件中可见。
- 匿名命名空间: 类似于静态函数,但可以包含多个函数和变量。
- Lambda 表达式: 可以在调用处直接定义函数,避免单独定义函数。
- 内联函数: 减少函数调用的开销,提高程序的执行效率。
7. 只需 init 一次的写法
在 C++ 中,有几种方法可以实现只需初始化一次的变量或对象:
- 静态局部变量 (Static Local Variable): 静态局部变量在函数第一次被调用时初始化,之后每次调用函数都会保持其值不变。
- 单例模式 (Singleton Pattern): 单例模式确保一个类只有一个实例,并提供一个全局访问点。
std::call_once
(C++11):std::call_once
确保一个函数只被调用一次,即使在多线程环境下也是如此。
7.1 示例
#include <iostream>
#include <mutex>
#include <thread>
// 1. 静态局部变量
void MyFunction() {
static int count = 0; // 只初始化一次
count++;
std::cout << "MyFunction called " << count << " times" << std::endl;
}
// 2. 单例模式
class Singleton {
private:
Singleton() {}
static Singleton* instance;
static std::mutex mutex_;
public:
static Singleton* GetInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
void DoSomething() {
std::cout << "Singleton::DoSomething" << std::endl;
}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;
// 3. std::call_once
std::once_flag flag;
void Init() {
std::cout << "Init called" << std::endl;
}
void MyThreadFunction() {
std::call_once(flag, Init);
}
int main() {
// 静态局部变量
MyFunction();
MyFunction();
// 单例模式
Singleton* singleton = Singleton::GetInstance();
singleton->DoSomething();
// std::call_once
std::thread t1(MyThreadFunction);
std::thread t2(MyThreadFunction);
t1.join();
t2.join(); // Init 只会被调用一次
return 0;
}
7.2 说明
- 静态局部变量: 简单易用,适用于简单的初始化场景。
- 单例模式: 适用于需要全局唯一实例的场景。
std::call_once
: 线程安全,适用于多线程环境下的初始化。
8. 科学计数法为 Double 类型
在 C++ 中,科学计数法可以用于表示 double
类型的数值。科学计数法使用 e
或 E
来表示 10 的幂。
8.1 示例
#include <iostream>
#include <iomanip> // std::setprecision
int main() {
double num1 = 1.2345e6; // 1.2345 * 10^6 = 1234500
double num2 = 6.789e-3; // 6.789 * 10^-3 = 0.006789
double num3 = 1e9; // 1.0 * 10^9 = 1000000000
std::cout << "num1 = " << num1 << std::endl;
std::cout << "num2 = " << num2 << std::endl;
std::cout << "num3 = " << num3 << std::endl;
// 使用 std::setprecision 控制输出精度
std::cout << std::setprecision(10) << "num3 = " << num3 << std::endl;
return 0;
}
8.2 说明
- 科学计数法可以用于表示非常大或非常小的
double
类型数值。 e
或E
后面的数字表示 10 的幂。- 可以使用
std::setprecision
控制输出精度。
9. lower_bound
的查找范围和 make_pair()
匹配查找数据类型
9.1 lower_bound
的查找范围
std::lower_bound
在已排序的序列中查找第一个大于或等于给定值的元素。其查找范围是 [first, last),即包含 first
,但不包含 last
。
9.2 make_pair()
匹配查找数据类型
在使用 std::lower_bound
查找 std::pair
类型的元素时,需要确保 make_pair()
创建的 pair
的数据类型与容器中的元素类型匹配。
9.3 示例
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<std::pair<int, std::string>> vec = {
{1, "apple"},
{2, "banana"},
{3, "cherry"},
{4, "date"}
};
// 查找第一个大于等于 (2, "") 的元素
auto it = std::lower_bound(vec.begin(), vec.end(), std::make_pair(2, std::string("")));
if (it != vec.end()) {
std::cout << "Found: " << it->first << ", " << it->second << std::endl; // 输出: Found: 2, banana
} else {
std::cout << "Not found" << std::endl;
}
return 0;
}
9.4 说明
std::lower_bound
返回一个迭代器,指向查找范围内第一个大于或等于给定值的元素。- 如果查找失败,返回
last
。 make_pair(2, std::string(""))
创建一个pair
,其first
成员为 2,second
成员为空字符串。- 需要确保
pair
的类型与容器中的元素类型匹配。
10. 计数排序
计数排序 (Counting Sort) 是一种非常高效的排序算法,特别适用于处理范围较小的整数数据。它是通过统计每个元素出现的次数,并根据这些统计信息来进行排序,而不是像传统的比较排序算法那样进行元素之间的比较。
10.1 计数排序的基本思路
- 找出输入数据的范围:找出输入数据中的最大值和最小值。
- 建立计数数组:根据数据范围建立一个计数数组,记录每个元素出现的次数。
- 累加计数数组:将计数数组中的值累加,这样每个元素就能知道它的正确位置。
- 输出排序结果:根据累加后的计数数组将数据重新放回到原数组或新数组中。
10.2 计数排序的时间复杂度
- 时间复杂度:O(n + k),其中 n 是输入数组的大小,k 是数据的范围(即最大值和最小值之间的差值)。
- 空间复杂度:O(k),需要额外的空间来存储计数数组。
10.3 计数排序的优缺点
10.3.1 优点
- 时间复杂度较低,特别适合范围较小的整数排序。
- 对于元素个数很大,但数据范围有限的情况,效率特别高。
10.3.2 缺点
- 不适用于数据范围过大或者有浮动的小数。
- 空间复杂度较高,尤其是在数据范围 k 非常大时。
10.4 计数排序的实现
#include <iostream>
#include <vector>
#include <algorithm>
void countingSort(std::vector<int>& arr) {
if (arr.empty()) return;
// 找到最大值和最小值
int max_val = *std::max_element(arr.begin(), arr.end());
int min_val = *std::min_element(arr.begin(), arr.end());
// 创建计数数组,大小为数据范围的大小
std::vector<int> count(max_val - min_val + 1, 0);
// 统计每个元素出现的次数
for (int num : arr) {
count[num - min_val]++;
}
// 通过计数数组将原数组按顺序重新排列
int index = 0;
for (int i = 0; i < count.size(); i++) {
while (count[i] > 0) {
arr[index++] = i + min_val;
count[i]--;
}
}
}
int main() {
std::vector<int> arr = {4, 2, 2, 8, 3, 3, 1};
countingSort(arr);
// 输出排序后的数组
for (int num : arr) {
std::cout << num << " ";
}
return 0;
}
10.5 代码解析
- 找最大最小值:首先,程序找出输入数组中的最大值和最小值,用来计算计数数组的范围。
- 创建计数数组:然后,程序创建一个大小为 max_val - min_val + 1 的计数数组,用于记录每个元素出现的次数。
- 统计次数:遍历输入数组,更新计数数组。每次遇到一个元素,就增加它在计数数组中的计数。
- 生成排序结果:根据计数数组的值,按照每个元素的出现次数填充回原数组。
10.6 总结
计数排序是一个适用于范围小且整数数据的排序算法。通过统计每个元素的出现次数并根据这些统计信息将数组排序,可以达到非常高的排序效率。
它的时间复杂度为 O(n + k),其中 n 是数组大小,k 是数据范围,适用于数据范围较小的情况。
11. iota
- 头文件:
#include <numeric>
- 作用:用于填充连续递增的值到容器。
- 语法:
iota(begin, end, start_value);
11.1 示例
#include <iostream>
#include <numeric>
#include <array>
int main() {
std::array<int, 5> arr;
std::iota(arr.begin(), arr.end(), 100); // arr = {100, 101, 102, 103, 104}
for (int num : arr) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
12. array
vs int arr[N]
12.1 std::array<T, N>
- 固定大小,不能动态调整。
- 支持 STL 操作,如
.size()
、.at()
(带边界检查)。 - 支持整体赋值,
arr1 = arr2;
- 可返回整个数组,不像 C 数组返回指针会导致 UB。
12.2 int arr[N]
- C 风格数组,大小在编译时确定。
- 大小管理:
sizeof(arr)/sizeof(arr[0])
- 边界检查:可能 UB
- STL 兼容:需传指针
- 赋值:不支持整体赋值
- 返回数组:不能返回局部数组
12.3 建议
- 固定大小 + 需要安全性 → 用
std::array
- 极限性能 / 兼容 C 代码 → 用 C 数组
13. reduce
vs accumulate
特性 | std::accumulate() | std::reduce() (C++17+) |
---|---|---|
计算顺序 | 按照 严格顺序 计算 | 可能无序(可并行执行) |
默认操作 | +(可自定义) | +(可自定义) |
初始值 | 必须提供 | 可选(无初始值时默认第一个元素) |
是否可并行 | ❌ 不支持 并行 | ✅ 支持 并行计算(std::execution::par ) |
适用范围 | 适用于 小规模数据 | 适用于 大规模数据(并行更快) |
头文件 | <numeric> | <numeric> + <execution> |
13.1 推荐使用
- C++98 及以上:用
std::accumulate()
- C++17 及以上:
- 计算 小数据(或顺序计算):
std::accumulate()
- 计算 大数据(并行优化):
std::reduce()
- 计算 小数据(或顺序计算):
13.2 并行优化示例
- 依次相加:
(((1 + 2) + 3) + 4) + 5
- 可能拆分计算:
(1+2)
和(3+4)
同时计算,最后合并
明白了! 按照您的要求,我将把您提供的序号1到10的内容,依次修改为序号14到23,并整理成Markdown笔记格式。
14. printf
输出格式
%d
: 输出十进制整数。%f
: 输出浮点数。%c
: 输出字符。%s
: 输出字符串。%x
: 输出十六进制整数。%o
: 输出八进制整数。%p
: 输出指针地址。%%
: 输出百分号%
。%.nf
: 输出浮点数,保留 n 位小数。
15. set
set
是 C++ STL 中的一个关联容器,用于存储唯一的元素,并自动排序。- 常用操作:
insert(element)
: 插入元素。erase(element)
: 删除元素。find(element)
: 查找元素,返回迭代器,如果没找到则返回set::end()
。count(element)
: 返回元素个数(set
中只能是 0 或 1)。size()
: 返回元素个数。empty()
: 判断是否为空。
16. ceil()
ceil(x)
是 C++<cmath>
库中的函数,返回大于或等于x
的最小整数(向上取整)。- 返回值类型为
double
。
17. string
的 find()
函数
string::find(str, pos)
: 从字符串的pos
位置开始查找子串str
,返回子串第一次出现的位置索引。- 如果未找到子串,则返回
string::npos
。
18. scanf
&& cin
scanf
是 C 语言中的输入函数,需要指定输入格式。cin
是 C++ 中的输入流对象,可以自动识别输入类型,更方便。- 区别:
scanf
需要指定格式,例如%d
,%f
,%s
等。cin
不需要指定格式,使用>>
运算符。scanf
通常比cin
更快,但在 C++ 中通常推荐使用cin
,因为它更安全且易于使用。scanf
在读取字符串时,遇到空格会停止,cin
默认也会。可以使用getline(cin, str)
读取一行字符串(包括空格)。
19. 将 string
转化为 C 风格输出
-
使用
string::c_str()
方法可以将 C++string
对象转换为 C 风格的字符串 (const char*
)。 -
例如:
#include <iostream> #include <string> int main() { std::string str = "Hello, world!"; printf("%s\n", str.c_str()); return 0; }
20. 手动进制转化
-
其他进制转十进制: 每一位上的数字乘以对应进制的幂次方,然后求和。
- 例如,二进制
1011
转十进制:[1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0 = 8 + 0 + 2 + 1 = 11]
- 例如,二进制
-
十进制转其他进制: 除以目标进制,取余数,然后将余数倒序排列。
-
公式:设十进制数为 (N),目标进制为 (R),则转换过程如下:
- (N) 除以 (R),得到商 (Q) 和余数 (M)。
- 将余数 (M) 记录下来。
- 如果 (Q) 不为 0,则将 (Q) 作为新的 (N),重复步骤 1 和 2。
- 将所有余数倒序排列,即为 (N) 对应的 (R) 进制数。
-
例如,十进制
11
转二进制:- 11 / 2 = 5 余 1
- 5 / 2 = 2 余 1
- 2 / 2 = 1 余 0
- 1 / 2 = 0 余 1
- 所以二进制为
1011
-
C++ 代码示例:
#include <iostream> #include <string> #include <algorithm> // 用于 reverse 函数 std::string decimalToOther(int decimal, int base) { if (decimal == 0) { return "0"; } std::string result = ""; while (decimal > 0) { int remainder = decimal % base; char digit; if (remainder < 10) { digit = remainder + '0'; // 0-9 } else { digit = remainder - 10 + 'A'; // A-Z (for bases > 10) } result += digit; decimal /= base; } std::reverse(result.begin(), result.end()); // 反转字符串 return result; } int main() { int decimalNumber = 27; int base = 16; std::string result = decimalToOther(decimalNumber, base); std::cout << "Decimal " << decimalNumber << " in base " << base << " is: " << result << std::endl; return 0; }
代码解释:
decimalToOther(int decimal, int base)
函数接收十进制数decimal
和目标进制base
作为输入。- 如果
decimal
为 0,直接返回 “0”。 - 使用
while
循环,每次计算decimal
除以base
的余数remainder
。 - 如果
remainder
小于 10,则将其转换为字符 ‘0’ 到 ‘9’;否则,将其转换为字符 ‘A’ 到 ‘Z’(用于表示 10 到 35)。 - 将字符
digit
添加到结果字符串result
中。 - 将
decimal
除以base
,更新decimal
的值。 - 循环结束后,使用
std::reverse
函数将结果字符串result
反转,因为余数是倒序排列的。 - 返回结果字符串
result
。
-
希望这个更新对您有帮助!
21. substr()
string::substr(pos, len)
: 从字符串的pos
位置开始,提取长度为len
的子串。- 如果省略
len
,则提取从pos
位置到字符串末尾的子串。
22. s
与 ""
s
通常表示一个string
类型的变量。""
表示一个空的 C 风格字符串 (const char*)。std::string s = "";
表示创建一个空的 C++ 字符串对象。char str[] = "";
表示创建一个空的 C 风格字符串数组。
希望这些笔记对您有所帮助!