当前位置:首页 > 技术 > 正文内容

C++11 标准库新容器与核心语言特性详解

访客 技术 2026年9月4日 1

一、 单向链表 forward_list

C++11 引入了 std::forward_list,这是一个单向链表容器。与双向链表 std::list 相比,它由于不需要维护前向指针,在内存占用上更加精简,适用于对内存敏感且只需单向遍历的场景。

其核心接口可归纳为以下几类:

  • 迭代器与边界:提供 before_begin()begin()end() 及其 const 版本。注意 before_begin() 是单向链表特有的,用于支持在头部之前插入或删除元素。
  • 容量与访问:支持 empty()max_size()front()。由于是单向链表,不支持 size()back(),以保持 O(1) 的操作复杂度。
  • 元素修改:包括 push_front()pop_front()emplace_front()。对于非头部操作,提供 insert_after()erase_after()emplace_after()
  • 链表专属操作:如 splice_after()(拼接)、remove()remove_if()unique()merge()sort()reverse()

二、 容器元素的删除:Erase-Remove 惯用法

在 C++ 中,标准算法 std::remove_if 并不会真正改变容器的大小。它通过移动元素,将不需要删除的元素前置,并返回一个指向新逻辑结尾的迭代器。要真正释放内存并改变容器大小,必须结合容器自身的 erase 方法。

#include <iostream>
#include <vector>
#include <algorithm>

bool isEven(int val) {
    return val % 2 == 0;
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8};
    
    // remove_if 将偶数移到末尾,返回新逻辑结尾
    auto new_end = std::remove_if(numbers.begin(), numbers.end(), isEven);
    
    // 真正删除多余的元素
    numbers.erase(new_end, numbers.end());
    
    for (int n : numbers) {
        std::cout << n << " ";
    }
    return 0;
}

需要注意的是,这种惯用法不适用于关联容器(如 std::setstd::map),因为它们不允许通过算法随意修改元素位置,应直接使用其成员函数 erase

三、 容器交换操作 swap 的底层差异

C++11 提供了非成员函数 std::swap。对于大多数动态容器(如 std::vector),swap 仅交换内部指针和状态,时间复杂度为 O(1),且原有迭代器依然有效,只是它们现在指向了另一个容器的元素。然而,对于 std::array,由于其内存是连续且固定分配的,swap 会逐个交换元素,时间复杂度为 O(N),且迭代器依然指向原容器,只是元素值发生了改变。

#include <iostream>
#include <vector>
#include <array>

int main() {
    std::vector<int> vecA = {10, 20};
    std::vector<int> vecB = {30, 40};
    auto itA = vecA.begin();
    
    std::swap(vecA, vecB);
    // itA 仍然有效,但现在指向 vecB 中的 10
    std::cout << "Vector swap - itA points to: " << *itA << "\n";

    std::array<int, 2> arrX = {1, 2};
    std::array<int, 2> arrY = {3, 4};
    auto itX = arrX.begin();
    
    std::swap(arrX, arrY);
    // itX 仍然指向 arrX,但 arrX 的首元素变成了 3
    std::cout << "Array swap - itX points to: " << *itX << "\n";
    
    return 0;
}

四、 就地构造 emplace 系列函数

emplaceemplace_frontemplace_back 允许直接在容器的内存空间中构造对象,避免了 push_back 可能带来的临时对象创建和拷贝/移动开销。

#include <iostream>
#include <vector>
#include <string>

class Employee {
public:
    Employee(std::string n, int a) : name(std::move(n)), age(a) {
        std::cout << "Constructor called\n";
    }
private:
    std::string name;
    int age;
};

int main() {
    std::vector<Employee> staff;
    
    // 使用 emplace_back 直接传递构造参数,无需创建临时对象
    staff.emplace_back("Alice", 28);
    
    // push_back 需要先构造临时对象,再移动或拷贝进容器
    // staff.push_back(Employee("Bob", 30)); 
    
    return 0;
}

五、 内存容量优化 shrink_to_fit

动态容器(如 std::vector)在扩容时通常会分配比实际需求更多的内存(即 capacity 大于 size)。如果后续不再添加元素,可以通过调用 shrink_to_fit() 请求编译器释放多余的内存,使 capacity 降至与 size 相等。请注意,这是一个非绑定请求,具体行为依赖于标准库实现。

#include <iostream>
#include <vector>

int main() {
    std::vector<double> data;
    for (int i = 0; i < 100; ++i) {
        data.push_back(i * 1.1);
    }
    std::cout << "Before: size=" << data.size() << ", capacity=" << data.capacity() << "\n";
    
    data.shrink_to_fit();
    std::cout << "After: size=" << data.size() << ", capacity=" << data.capacity() << "\n";
    
    return 0;
}

六、 无序关联容器 unordered_map

C++11 引入了基于哈希表实现的无序容器。std::unordered_map 提供平均 O(1) 的查找、插入和删除复杂度,而传统的 std::map 基于红黑树,复杂度为 O(log N),但能保持元素按 key 有序排列。

#include <iostream>
#include <unordered_map>
#include <map>

int main() {
    std::unordered_map<int, std::string> hash_map;
    std::map<int, std::string> tree_map;

    hash_map[3] = "Three";
    hash_map[1] = "One";
    hash_map[2] = "Two";

    tree_map[3] = "Three";
    tree_map[1] = "One";
    tree_map[2] = "Two";

    std::cout << "Unordered Map traversal:\n";
    for (const auto& pair : hash_map) {
        std::cout << pair.first << ": " << pair.second << "\n"; // 输出顺序不确定
    }

    std::cout << "Ordered Map traversal:\n";
    for (const auto& pair : tree_map) {
        std::cout << pair.first << ": " << pair.second << "\n"; // 严格按 1, 2, 3 输出
    }

    return 0;
}

七、 智能指针 shared_ptr

std::shared_ptr 通过引用计数机制实现共享所有权。当最后一个指向该对象的 shared_ptr 被销毁或重置时,对象内存会被自动释放。推荐使用 std::make_shared 进行初始化,它会将控制块和对象内存一次性分配,提升性能并增强异常安全性。

#include <iostream>
#include <memory>
#include <string>

int main() {
    auto ptr1 = std::make_shared<std::string>("Hello");
    std::cout << "ptr1 count: " << ptr1.use_count() << "\n"; // 1

    {
        auto ptr2 = ptr1; // 引用计数增加
        std::cout << "ptr2 count: " << ptr2.use_count() << "\n"; // 2
    } // ptr2 离开作用域,引用计数减 1

    std::cout << "ptr1 count after scope: " << ptr1.use_count() << "\n"; // 1
    
    ptr1.reset(); // 手动重置,引用计数归零,内存释放
    return 0;
}

八、 构造函数新特性:委托与默认

C++11 允许在一个构造函数中调用同一个类的另一个构造函数(委托构造),从而减少代码重复。同时,使用 = default 可以显式要求编译器生成默认的特殊成员函数(如默认构造函数、拷贝构造函数等),这在某些情况下比手动实现更高效且语义更明确。

#include <iostream>

class Sensor {
public:
    // 默认构造函数
    Sensor() = default;
    
    // 完整参数构造函数
    Sensor(int id, double val, bool active) 
        : sensor_id(id), value(val), is_active(active) {}
        
    // 委托构造:调用三参数构造函数
    Sensor(int id) : Sensor(id, 0.0, false) {}

    // 显式默认拷贝构造
    Sensor(const Sensor&) = default;

    void print() const {
        std::cout << "ID: " << sensor_id << ", Val: " << value << ", Active: " << is_active << "\n";
    }

private:
    int sensor_id = 0;
    double value = 0.0;
    bool is_active = false;
};

int main() {
    Sensor s1;
    Sensor s2(101, 25.5, true);
    Sensor s3(102); // 使用委托构造
    
    s1.print();
    s2.print();
    s3.print();
    
    return 0;
}
返回列表

上一篇:Python 文件系统操作与数据流控制指南

没有最新的文章了...

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。