6.26小学期基础语法记录:内建函数、vector清理、八进制
__builtin_popcount()与内建函数(builtin function)
__builtin_popcount() 是 GCC 提供的一个内建函数(builtin function),用于计算一个整数中二进制表示下 1 的个数,也叫“汉明重量”或“population count”。
一、__builtin_popcount()详解
✅ 函数原型(GCC/Clang)
int __builtin_popcount(unsigned int x);
int __builtin_popcountll(unsigned long long x); // 适用于64位
✅ 举例
#include <iostream>
using namespace std;
int main() {
unsigned int x = 29; // 二进制: 11101
cout << __builtin_popcount(x) << endl; // 输出: 4
return 0;
}
✅ 背后实现原理(硬件优化)
__builtin_popcount()实际会在编译时转为 CPU 指令(如 x86 架构的POPCNT指令),速度远远快于自己写的循环统计法。- 时间复杂度近似为 O(1),和你调用普通函数差不多快!
二、自己实现 popcount(了解原理)
如果你不能使用内建函数,可以用如下方法代替:
方法一:逐位判断
int popcount(int x) {
int cnt = 0;
while (x) {
cnt += x & 1;
x >>= 1;
}
return cnt;
}
方法二:Brian Kernighan算法(更快)
int popcount(int x) {
int cnt = 0;
while (x) {
x &= (x - 1); // 每次去掉最低位的1
cnt++;
}
return cnt;
}
三、其他高效的 GCC 内建函数(对竞赛有巨大提升)
| 内建函数 | 功能 | 举例说明 |
|---|---|---|
__builtin_clz(x) |
计算前导零个数(Count Leading Zeros) | __builtin_clz(16) 返回 27(32位int) |
__builtin_ctz(x) |
计算末尾零个数(Count Trailing Zeros) | __builtin_ctz(16) 返回 4 |
__builtin_parity(x) |
判断 1 的个数奇偶(偶返回0,奇返回1) | 可用于奇偶性判断 |
__builtin_ffs(x) |
返回最低位的1是第几位(1-based) | __builtin_ffs(16) 返回 5 |
__builtin_popcountll(x) |
64位版本 popcount | __builtin_popcountll(ULLONG_MAX) 返回64 |
__builtin_expect(x, val) |
分支预测优化 | 用于手动提示分支概率 |
四、在算法竞赛中的应用(非常实用)
-
子集枚举优化
for (int mask = 0; mask < (1 << n); ++mask) { if (__builtin_popcount(mask) == k) { // 枚举所有 k 个元素的子集 } } -
判断一个数是否为 2 的幂
if (__builtin_popcount(x) == 1) { // x 是 2 的幂 } -
快速找前导零、找首个 1
int highest_bit_pos = 31 - __builtin_clz(x); // log2(x) -
状态压缩DP、图论中位运算优化
五、小结(建议记住的内建函数)
| 建议记住的函数 | 用途简述 |
|---|---|
__builtin_popcount |
统计1的个数 |
__builtin_clz |
快速 log2 |
__builtin_ctz |
找最低的1 |
__builtin_ffs |
找第一个1的位置 |
__builtin_parity |
奇偶判断 |
__builtin_expect |
分支优化(进阶) |
六、拓展:__builtin_expect(x, val)分支优化
__builtin_expect(x, val) 是 GCC 提供的一个分支预测优化内建函数,它的作用是:
👉 告诉编译器:你“预计”某个条件表达式 x 更有可能为 val。
✅ 函数原型
long __builtin_expect(long x, long expected);
你可以把它看成这样写的:
if (__builtin_expect(x, 1)) {
// 这里我们告诉编译器:我们预计 x == 1 是更有可能的分支
}
✅ 举个例子(编译器层面优化)
if (__builtin_expect(a == 0, 0)) {
// 你告诉编译器:a==0 很少发生(不常见路径)
// 编译器会把这个分支安排到冷路径,提高指令执行效率
}
相当于你在用编程语言语法告诉 CPU:
“嘿,a 通常不是 0,不要专门为这个分支做指令预测和优化。”
✅ 为什么要用它?(现代 CPU 执行原理)
现代 CPU 有分支预测器,会根据之前执行的历史猜测分支走向,比如:
if (x > 0) {
// 热路径
} else {
// 冷路径
}
如果你知道某个条件绝大多数时候为真/假,就可以用 __builtin_expect() 告诉编译器,把最可能的分支放在更快的指令流水线位置。
✅ 它到底能提升多少性能?
- 一般情况下,对微秒级函数或循环中的判断才有显著影响。
- 在高性能代码、驱动、网络协议栈、游戏引擎、内核代码等频繁判断的地方用得较多。
在算法竞赛中,如果你要做超大规模循环+判断,可能带来 微小但实在的优化(约 1~3%)。
✅ 推荐用法(更简洁的宏)
大多数高性能代码会自定义宏来使用它:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
注意:这里的!!(x)意为把任意值的结果转换为布尔数。
这样就可以写成:
if (likely(x == 1)) {
// 预计这里是主路径
} else if (unlikely(x == 0)) {
// 少数情况
}
✅ 竞赛中是否需要?
一般不需要,除非你卡常数。
但你在做以下几类题时可以考虑加上:
- 大量状态转移的 DP(如状态压缩)
- bitmask 枚举中剪枝
- 模拟或计数类题中关键判断
- 尤其是 Hack 某些恶意测试数据(如 Codeforces 的卡常题)
✅ 小结
| 项目 | 内容 |
|---|---|
| 函数名 | __builtin_expect(x, val) |
| 功能 | 提示编译器 x 更可能为 val,用于分支预测优化 |
| 推荐宏 | likely(x)、unlikely(x) |
| 使用场景 | 高频判断语句、性能临界代码 |
| 竞赛用途 | 卡常/微优化用法,非必需 |
🧠 C++ vector 清空与内存管理
✅ 一、清空 vector 的常用方法
| 方法 | 是否清元素 | 是否释放内存 | 说明 |
|---|---|---|---|
v.clear() |
✅ | ❌ | 清空内容,保留容量(capacity) |
v.erase(v.begin(), v.end()) |
✅ | ❌ | 等价于 clear(),写法繁琐 |
vector<T>().swap(v) |
✅ | ✅ | 清空并释放内存,常用于彻底清理 |
v = vector<T>() |
✅ | ✅ | 与 swap 等价,语义清晰简洁 |
v.resize(0) |
✅ | ❌ | 把 size 设为 0,不释放容量 |
✅ 1. v.clear()
vector<int> v = {1, 2, 3};
v.clear(); // 清空所有元素
- ✅ 最常用
- ✅ 所有元素析构,
size()变成 0 - ⚠️ 不会释放内存容量(
capacity不变)
v.capacity(); // 还是原来的容量
✅ 2. v.erase(v.begin(), v.end())
v.erase(v.begin(), v.end()); // 等价于 clear()
- ✅ 和
clear()效果一样 - ❌ 语法冗长,不推荐
✅ 3. 释放内存:vector<T>().swap(v)
vector<int> v = {1, 2, 3};
// 清空并释放内存
vector<int>().swap(v);
- ✅ 真正把
capacity也清掉(释放内存) - ✅
v.size()为 0,v.capacity()也为 0 - ✅ 适合清空大容器、节省内存
- ⚠️ 容器会被置为空(不能用原来的内容)
✅ 4. 重新构造:v = vector<int>()
v = vector<int>();
- 和
swap方法本质一样(临时对象赋值) - 简洁直观,也会释放内存
✅ 5. resize(0)(仅清元素)
v.resize(0);
- ✅
size()变成 0,和clear()效果一致 - ⚠️ 同样不释放
capacity
✅ 二、size() 与 capacity() 的区别
| 属性 | size() |
capacity() |
|---|---|---|
| 含义 | 当前存了几个元素 | 最大可容纳元素数(不再重新分配内存) |
| 单位 | 元素个数 | 元素个数 |
是否随 push_back 变化 |
✅ | ✅,但增长策略是倍增 |
| 是否可手动控制 | ❌(用 resize()) |
✅(用 reserve()) |
🎯 示例:
vector<int> v;
v.reserve(10); // capacity = 10, size = 0
v.resize(5); // capacity = 10, size = 5
✅ 三、reserve() 与 resize() 的区别
| 方法 | 功能 | 是否构造元素 | 是否可用 v[i] |
|---|---|---|---|
reserve(n) |
只分配内存,不构造元素 | ❌ | ❌(不能访问) |
resize(n) |
分配并构造 n 个默认值元素 | ✅ | ✅(可访问) |
❌ 错误访问示例:
vector<int> v;
v.reserve(10); // 仅预留内存
v[3] = 42; // ❌ 未构造元素,越界访问,未定义行为
✅ 四、随机访问的正确姿势
如果你想使用 v[i] = x,必须保证该元素已存在,即:
- 使用
resize(n)构造; - 或者使用
push_back()填充至目标位置; - 或使用
emplace_back()构造更复杂对象。
✅ 正确示例:
vector<int> v;
v.resize(10); // 构造了 10 个元素
v[3] = 100; // 安全访问
✅ 五、扩展技巧
🎯 缩小容量以节省内存
v.shrink_to_fit(); // 请求释放多余内存,非强制
🎯 完全释放内存(推荐)
vector<T>().swap(v); // 清空 + 清 capacity(彻底清干净)
✅ 六、推荐写法总结
// 清空元素但保留内存
v.clear();
// 清空元素并释放内存
vector<int>().swap(v); // 推荐!
// 想随机访问 v[i]:必须保证 size 足够
v.resize(100);
v[99] = 5;
八进制(Octal)
在 C++ 中,八进制的使用非常简单明确,主要用于整数的表示、输入和输出等。下面我会从“如何定义八进制数”“如何输出为八进制”“如何输入八进制数”三个方面详细讲解,并补充一些应用细节与注意事项。
一、八进制数的定义(写法)
在 C++ 中,如果你在整数字面量前加上一个 0(不是 0x 也不是 0b),那么这个数就会被解释为八进制。
int a = 0123; // 这是一个八进制数,等价于十进制 83
解释:
0123= 1×8² + 2×8¹ + 3×8⁰ = 64 + 16 + 3 = 83
二、输出八进制数(std::oct)
使用 <iomanip> 里的 std::oct 可以将整数以八进制格式输出。
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
int x = 83;
cout << "十进制: " << x << endl;
cout << "八进制: " << oct << x << endl;
return 0;
}
输出结果:
十进制: 83
八进制: 123
三、输入八进制数
如果你从输入中读取八进制数,需要明确处理:
- 方式1:手动加前缀
0让cin识别。 - 方式2:先输入字符串,然后用
stoi(str, nullptr, 8)转换。
示例:
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s; // 假设输入为 "123"
int x = stoi(s, nullptr, 8); // 按八进制解析
cout << "你输入的是十进制: " << x << endl;
}
输入:
123
输出:
你输入的是十进制: 83
四、应用场景举例:权限位(如 Linux 文件权限)
Linux 的权限通常用八进制表示,如 chmod 755。这在 C++ 中可以模拟:
int permission = 0755;
cout << "权限值: " << oct << permission << endl;
五、注意事项
-
八进制只能用数字
0~7,否则会报错或得到错误结果:int x = 089; // 错误:不是有效的八进制数 -
0前缀表示八进制,不是十进制!很多初学者会误以为012是十二,其实是 十进制 10。 -
C++20 起,还可以使用
std::format("{:o}", x)来格式化输出八进制(需要<format>)。
总结:
| 任务 | 方法 |
|---|---|
| 定义八进制 | int x = 0123; |
| 输出八进制 | cout << oct << x; |
| 输入八进制 | stoi(s, nullptr, 8) 或手动加 0 前缀 |
| 输出回十进制 | cout << dec << x; |

浙公网安备 33010602011771号