C++学习:六个月从基础到就业——C++11/14:其他语言特性

C++学习:六个月从基础到就业------C++11/14:其他语言特性

本文是我C++学习之旅系列的第四十四篇技术文章,也是第三阶段"现代C++特性"的第六篇,主要介绍C++11/14中引入的其他重要语言特性。查看完整系列目录了解更多内容。

引言

在前几篇文章中,我们已经详细介绍了C++11/14引入的一些主要特性,如右值引用与移动语义、lambda表达式、auto类型推导、decltype关键字以及列表初始化。然而,C++11/14标准还引入了许多其他重要的语言特性,这些特性虽然不像前面几个那样被广泛讨论,但在日常编程中同样非常有用。

本文将介绍这些"其他语言特性",包括nullptr、constexpr、范围for循环、默认函数控制、枚举类等。这些特性共同构成了现代C++的基础,掌握它们对于编写更简洁、更安全、更高效的C++代码至关重要。

目录

nullptr关键字

NULL的问题

在C++11之前,程序员通常使用NULL宏或直接使用0来表示空指针,这可能导致函数重载解析的歧义:

cpp 复制代码
void foo(int i) { std::cout << "整数版本" << std::endl; }
void foo(char* p) { std::cout << "指针版本" << std::endl; }

int main() {
    foo(0);       // 调用foo(int)
    foo(NULL);    // 在大多数实现中,也会调用foo(int)
}

nullptr的优势

C++11引入了nullptr关键字,它是一个特殊的字面量,可以隐式转换为任何指针类型,但不能转换为整数类型:

cpp 复制代码
void foo(int i) { std::cout << "整数版本" << std::endl; }
void foo(char* p) { std::cout << "指针版本" << std::endl; }

int main() {
    foo(nullptr);  // 明确调用foo(char*)
    
    // 可以隐式转换为任何指针类型
    int* p1 = nullptr;
    char* p2 = nullptr;
    
    // 但不能转换为整数
    // int n = nullptr;  // 编译错误
}

nullptr的类型是std::nullptr_t,可用于重载区分和模板参数匹配,提高了代码的类型安全性和可读性。

constexpr关键字

constexpr关键字允许在编译期计算表达式的值,提供了真正的常量表达式功能。

编译期常量

cpp 复制代码
// 编译期常量
constexpr int MAX_SIZE = 100;

// 可用于数组大小
constexpr int getSizeForArray() { return 42; }
int array[getSizeForArray()];  // 合法:在编译期求值

constexpr函数

C++11中的constexpr函数有很多限制,只能包含简单的代码:

cpp 复制代码
// C++11 constexpr函数
constexpr int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

// 编译期计算
constexpr int fact5 = factorial(5);  // 编译期计算为120

C++14中的增强

C++14大大放宽了对constexpr函数的限制,允许使用循环、条件语句等:

cpp 复制代码
// C++14 constexpr函数
constexpr int factorial(int n) {
    int result = 1;
    for (int i = 1; i <= n; ++i) {
        result *= i;
    }
    return result;
}

应用场景

constexpr的主要应用包括:

  1. 编译期数学计算

    cpp 复制代码
    constexpr double circleArea(double r) {
        return 3.14159 * r * r;
    }
  2. 类型特性和元编程

    cpp 复制代码
    template<typename T>
    constexpr bool is_power_of_two(T x) {
        return x > 0 && (x & (x - 1)) == 0;
    }
    static_assert(is_power_of_two(16), "16是2的幂");
  3. 提高性能:可编译期计算的内容不会增加运行时开销

范围for循环

基本语法

C++11引入了范围for循环,使迭代容器和数组更加方便:

cpp 复制代码
// 数组迭代
int arr[] = {1, 2, 3, 4, 5};
for (int value : arr) {
    std::cout << value << " ";
}

// 容器迭代
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
for (const auto& name : names) {  // 使用引用避免复制
    std::cout << name << " ";
}

// 修改元素
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto& num : numbers) {
    num *= 2;  // 每个元素乘以2
}

范围for循环的一般语法是:for (declaration : expression) { ... }

与迭代器的对比

相比传统迭代器方法,范围for循环更简洁,更不易出错:

cpp 复制代码
// 传统迭代器方法
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
    *it *= 2;
}

// 范围for循环
for (auto& elem : vec) {
    elem *= 2;
}

自定义类型支持

要使自定义类型支持范围for循环,需要提供begin()end()函数:

cpp 复制代码
class IntegerRange {
private:
    int start;
    int end;
    
    class Iterator {
        // 实现迭代器接口...
    };
    
public:
    IntegerRange(int s, int e) : start(s), end(e) {}
    
    Iterator begin() const { return Iterator(start); }
    Iterator end() const { return Iterator(end); }
};

// 使用
for (int i : IntegerRange(1, 6)) {
    std::cout << i << " ";  // 输出: 1 2 3 4 5
}

默认函数控制

C++11引入了=default=delete来更精确地控制特殊成员函数的生成。

default关键字

显式要求编译器生成默认版本的特殊成员函数:

cpp 复制代码
class Example {
public:
    // 显式要求生成默认构造函数
    Example() = default;
    
    // 自定义构造函数
    Example(int val) : value(val) {}
    
    // 显式要求生成默认析构函数和拷贝操作
    ~Example() = default;
    Example(const Example&) = default;
    Example& operator=(const Example&) = default;
    
private:
    int value = 0;
};

delete关键字

禁止使用特定函数:

cpp 复制代码
class NonCopyable {
public:
    NonCopyable() = default;
    
    // 禁止拷贝
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    
    // 允许移动
    NonCopyable(NonCopyable&&) = default;
    NonCopyable& operator=(NonCopyable&&) = default;
};

// 禁止特定类型的隐式转换
class NoInt {
public:
    NoInt(double d) {}      // 允许从double构造
    NoInt(int) = delete;    // 禁止从int构造
};

实际使用场景

这些关键字在以下场景非常有用:

  1. 实现单例模式
  2. RAII资源包装器
  3. 禁止特定用法:如禁止在堆上创建对象
cpp 复制代码
// 禁止在堆上创建对象
class NoHeapAllocation {
public:
    void* operator new(size_t) = delete;
    void* operator new[](size_t) = delete;
};

override和final关键字

override关键字

override关键字用于显式标记覆盖基类的虚函数,帮助避免常见错误:

cpp 复制代码
class Base {
public:
    virtual void foo() { std::cout << "Base::foo()" << std::endl; }
    virtual void bar(int x) { std::cout << "Base::bar()" << std::endl; }
};

class Derived : public Base {
public:
    // 明确标记为覆盖,编译器会检查
    void foo() override { std::cout << "Derived::foo()" << std::endl; }
    
    // 如果参数不匹配,编译器会报错
    // void bar(double x) override { /* 错误:没有覆盖基类函数 */ }
};

final关键字

final关键字应用于类或虚函数,禁止进一步继承或覆盖:

cpp 复制代码
class Base {
public:
    virtual void foo() { std::cout << "Base::foo()" << std::endl; }
};

class Derived : public Base {
public:
    // 禁止进一步覆盖此函数
    void foo() override final { std::cout << "Derived::foo()" << std::endl; }
};

// 定义为final的类不能被继承
class FinalClass final {
    // ...
};

强类型枚举

传统枚举的问题

C++98/03中的传统枚举存在作用域污染、类型不安全等问题:

cpp 复制代码
enum Color { RED, GREEN, BLUE };
enum Fruit { APPLE, BANANA, ORANGE };

// 问题:枚举值会泄漏到全局命名空间
bool comparison = (RED == APPLE);  // 允许比较,都是0

enum class的优势

C++11引入的强类型枚举解决了这些问题:

cpp 复制代码
enum class Color { RED, GREEN, BLUE };
enum class Fruit { APPLE, BANANA, ORANGE };

// 必须使用作用域操作符
Color c = Color::RED;

// 不同枚举类型不能直接比较
// bool comparison = (Color::RED == Fruit::APPLE);  // 编译错误

// 不会隐式转换为整数
// int n = Color::GREEN;  // 编译错误
int n = static_cast<int>(Color::GREEN);  // 正确,显式转换

底层类型与前向声明

可以指定枚举的底层类型,并支持前向声明:

cpp 复制代码
// 指定底层类型
enum class SmallEnum : uint8_t { A, B, C };

// 前向声明
enum class Status : int;  // 必须指定底层类型

用户定义字面量

C++11允许为自定义类型创建字面量,通过定义特殊的字面量运算符:

cpp 复制代码
// 距离字面量
Distance operator"" _km(long double km) {
    return Distance(km * 1000.0);  // 转换为米
}

// 使用
auto marathon = 42.195_km;  // 创建Distance对象

C++14标准库添加了多个字面量:

cpp 复制代码
using namespace std::literals;

// 字符串字面量
auto str = "hello"s;  // std::string,不是C风格字符串

// 时间字面量
auto day = 24h;
auto minute = 1min;

// 复数字面量
auto c = 3.0 + 4.0i;  // 复数(3, 4)

noexcept说明符

noexcept说明符指定函数不会抛出异常:

cpp 复制代码
// 保证不抛出异常的函数
void safeOperation() noexcept {
    // 如果这里抛出异常,程序会立即终止
}

// 条件noexcept
template<typename T>
void swap(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

noexcept在移动语义中特别重要,标准库容器使用它来决定是否使用移动操作:

cpp 复制代码
// 移动构造函数标记为noexcept
Widget(Widget&& other) noexcept
    : name_(std::move(other.name_)),
      data_(std::move(other.data_)) {
}

thread_local存储类型

thread_local指定变量的存储持续性为线程生命周期,每个线程有自己的副本:

cpp 复制代码
// 每个线程都有独立副本
thread_local int counter = 0;

void threadFunction() {
    ++counter;  // 只影响当前线程的counter
    std::cout << "Thread " << std::this_thread::get_id() 
              << ": " << counter << std::endl;
}

应用场景包括:

  1. 线程安全的单例模式
  2. 线程专用缓存
  3. 无锁数据结构

内联命名空间

C++11引入了内联命名空间,简化版本管理和符号导出:

cpp 复制代码
namespace MyLib {
    // v1命名空间内的符号直接可见
    inline namespace v1 {
        void func() { /* v1实现 */ }
    }
    
    // v2命名空间需要显式访问
    namespace v2 {
        void func() { /* v2实现 */ }
    }
}

// 直接使用v1版本
MyLib::func();  // 调用MyLib::v1::func

// 显式使用v2版本
MyLib::v2::func();

应用场景包括:

  1. 库版本管理
  2. 特性切换
  3. 平台特定实现

总结

C++11/14引入的这些"其他语言特性"极大地提高了C++语言的表达能力和安全性:

  1. nullptr:提供类型安全的空指针字面量
  2. constexpr:允许编译期计算表达式,增强性能和元编程能力
  3. 范围for循环:简化容器迭代,提高代码可读性
  4. 默认函数控制 :通过defaultdelete精确控制特殊成员函数
  5. override和final:增强类继承体系的安全性和表达能力
  6. 强类型枚举:提供更类型安全的枚举类型
  7. 用户定义字面量:为自定义类型创建直观的字面量表示法
  8. noexcept:明确函数的异常保证,优化移动语义
  9. thread_local:提供线程专用存储,简化多线程编程
  10. 内联命名空间:简化库版本管理和符号导出

掌握这些新特性,能够帮助我们编写更加现代化、高效、安全的C++代码。它们虽然各自看起来只是小改进,但共同提供了更强大、更具表达力的语言工具集。


这是我C++学习之旅系列的第四十四篇技术文章。查看完整系列目录了解更多内容。

相关推荐
LaoWaiHang1 小时前
MFC 捕捉桌面存成jpg案例代码
c++·mfc
mxway1 小时前
八、实现编辑框控件
c++·x11·linux gui·自绘控件·自绘制编辑框控件·utf8字符集编码·文本输入
liuzhangfeiabc5 小时前
[luogu12541] [APIO2025] Hack! - 交互 - 构造 - 数论 - BSGS
c++·算法·题解
学习使我变快乐6 小时前
C++:迭代器
开发语言·c++·windows
好想有猫猫6 小时前
【Redis】List 列表
数据库·c++·redis·分布式·缓存·list
天堂的恶魔9468 小时前
C++ - 仿 RabbitMQ 实现消息队列(2)(Protobuf 和 Muduo 初识)
c++·rabbitmq·ruby
休息一下接着来8 小时前
进程间通信(IPC)常用方式对比
linux·c++·进程间通讯
虾球xz8 小时前
游戏引擎学习第288天:继续完成Brains
c++·学习·游戏引擎
Y3174298 小时前
Python Day27 学习
python·学习·机器学习