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) 分支预测优化 用于手动提示分支概率

四、在算法竞赛中的应用(非常实用)

  1. 子集枚举优化

    for (int mask = 0; mask < (1 << n); ++mask) {
        if (__builtin_popcount(mask) == k) {
            // 枚举所有 k 个元素的子集
        }
    }
    
  2. 判断一个数是否为 2 的幂

    if (__builtin_popcount(x) == 1) {
        // x 是 2 的幂
    }
    
  3. 快速找前导零、找首个 1

    int highest_bit_pos = 31 - __builtin_clz(x); // log2(x)
    
  4. 状态压缩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:手动加前缀 0cin 识别。
  • 方式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;

五、注意事项

  1. 八进制只能用数字 0~7,否则会报错或得到错误结果:

    int x = 089;  // 错误:不是有效的八进制数
    
  2. 0 前缀表示八进制,不是十进制!很多初学者会误以为 012 是十二,其实是 十进制 10

  3. C++20 起,还可以使用 std::format("{:o}", x) 来格式化输出八进制(需要 <format>)。


总结:

任务 方法
定义八进制 int x = 0123;
输出八进制 cout << oct << x;
输入八进制 stoi(s, nullptr, 8) 或手动加 0 前缀
输出回十进制 cout << dec << x;
posted @ 2025-06-26 21:52  十八Eigh18n  阅读(148)  评论(0)    收藏  举报