突破编程_C++_C++11新特性(nullptr、constexpr与基于范围的 for 循环)
1 nullptr
C++11 引入了一个名为 nullptr 的新关键字,用于替换 C++98/03 中的 NULL 宏。nullptr 是空指针常量的一种类型安全的表示方法,它解决了使用 NULL 时可能遇到的一些问题和歧义。
1.1 NULL 的问题
在 C++98/03 中,NULL 通常被定义为 (void*)0 或 0,这取决于具体的实现。使用 NULL 有两个潜在的问题:
(1)类型不安全: NULL 可以隐式转换为任何指针类型,这可能导致一些类型混淆的错误。
(2)重载函数的歧义: 如果函数重载了接受 int 和指针参数的版本,使用 NULL 可能会导致调用错误版本的函数。
1.2 nullptr 的优势及示例
nullptr 的优势如下:
(1)类型安全: nullptr 是一个指针字面量,它只能转换为指针类型,不能转换为整数类型。
(2)消除重载函数的歧义: 使用 nullptr 可以确保调用正确版本的函数,因为它只能表示指针类型的空值。
nullptr 的示例如下:
示例 1:替换 NULL
#include <iostream> int* getPointer() { // ... return nullptr; // 返回空指针
} int main()
{ int* ptr = getPointer(); if (nullptr == ptr) { // 检查指针是否为空 std::cout << "Pointer is null." << std::endl; } else { std::cout << "Pointer is not null." << std::endl; } return 0;
}
示例 2:消除重载函数的歧义
#include <iostream> void foo(int x) { std::cout << "Called foo(int)" << std::endl;
} void foo(int* ptr) { if (nullptr == ptr) { std::cout << "Called foo(int*) with null pointer." << std::endl; } else { std::cout << "Called foo(int*) with non-null pointer." << std::endl; }
} int main()
{ foo(0); // 调用 foo(int) foo(nullptr); // 调用 foo(int*) return 0;
}
在这个例子中,如果使用 NULL 替换 nullptr,那么 foo(NULL) 的调用将变得不明确,因为编译器无法区分应该调用哪个版本的 foo 函数。但是使用 nullptr 可以消除这种歧义。
1.3 注意事项
使用 nullptr 的注意事项如下:
- 确保在编写新的 C++11 代码时使用 nullptr 替代 NULL。
- 当升级旧代码时,逐步替换 NULL 为 nullptr,确保代码的正确性和兼容性。
- 了解编译器和库是否支持 C++11,以确保 nullptr 可以被正确识别和使用。
- 通过使用 nullptr,可以提高代码的类型安全性,并消除由于使用 NULL 而可能引入的错误和歧义。
2 constexpr
C++11 引入了一个非常强大的关键字 constexpr,它允许在编译时计算表达式的值,并且这些值可以用于初始化常量表达式。这不仅可以提高代码的可读性和性能,还可以用于创建模板元编程中的常量值。
2.1 constexpr 的基本用法
constexpr可以用于变量、函数或类的构造函数。当用于变量时,它表示该变量的值是一个常量表达式,必须在编译时确定。当用于函数或构造函数时,它表示该函数或构造函数的返回值是一个常量表达式。
示例 1:constexpr变量
constexpr int a = 5; // a是一个编译时常量
constexpr int b = a * 2; // b的值在编译时确定为10
示例 2:constexpr函数
constexpr int square(int x) { return x * x;
} int main()
{ constexpr int c = square(3); // c的值在编译时确定为9 return 0;
}
2.2 constexpr 的限制和扩展
在 C++11 中,constexpr 函数有一些限制:
- 它们只能包含单个返回语句。
- 它们只能调用其他 constexpr 函数。
- 它们不能包含非 constexpr 的变量或函数调用。
注意:从 C++14 开始,这些限制得到了放宽,允许 constexpr 函数有更多的灵活性,包括循环、条件语句等。
示例 1:C++14 中的 constexpr 函数
constexpr int factorial(int n) { int result = 1; for (int i = 1; i <= n; ++i) { result *= i; } return result;
} int main()
{ constexpr int d = factorial(5); // d 的值在编译时确定为 120 return 0;
}
constexpr 也可以用于类的构造函数,允许在编译时创建类的常量对象。
示例 2:constexpr 构造函数
#include <iostream>
class Point {
public:constexpr Point(int x = 0, int y = 0) : m_x(x), m_y(y) {}constexpr int getX() const { return m_x; }constexpr int getY() const { return m_y; }private:int m_x, m_y;
};int main()
{constexpr Point p(1, 2); // p 是一个编译时常量对象 constexpr int e = p.getX(); // e 的值在编译时确定为 1 return 0;
}
2.3 使用 constexpr 的优势
使用 constexpr 的优势如下:
- 性能提升:由于 constexpr 的值在编译时确定,所以不需要在运行时进行计算,这可以提高代码的执行效率。
- 类型安全:使用 constexpr 可以确保常量表达式在编译时就是正确的,从而避免运行时错误。
- 模板元编程:constexpr 在模板元编程中特别有用,因为元编程需要在编译时计算值。
2.4 注意事项
使用 constexpr 的注意事项如下:
- 并不是所有的编译器都完全支持 C++11 和 C++14 中的 constexpr 特性,特别是在处理复杂表达式和函数时。因此,在使用 constexpr 时,最好检查编译器文档以确保兼容性。
- 过度使用 constexpr 可能会使代码难以阅读和维护。通常,只在确实需要编译时常量表达式的地方使用它。
- 通过掌握 constexpr 的用法,可以写出更加高效、安全的 C++ 代码,并利用编译时计算的优势来提升性能。
3 范围的 for 循环
基于范围的for循环(Range-based for loop)是 C++11 引入的一个新特性,它使得遍历容器或数组变得更加简单和直观。这种循环方式可以自动处理迭代器,使得代码更加简洁易读。
3.1 基本语法
基于范围的for循环的基本语法如下:
for (declaration : expression) { // 循环体
}
其中:
- declaration:定义循环变量的类型。
- expression:返回一个序列,通常是一个容器或数组。
3.2 示例
下面是一些使用基于范围的for循环的示例:
示例 1:遍历数组
#include <iostream> int main()
{ int arr[] = {1, 2, 3, 4, 5}; for (int num : arr) { std::cout << num << " "; } std::cout << std::endl; return 0;
}
输出:
1 2 3 4 5
示例 2:遍历 STL 容器
#include <iostream>
#include <vector>
#include <string> int main()
{ std::vector<std::string> vec = {"apple", "banana", "cherry"}; for (const std::string& fruit : vec) { std::cout << fruit << " "; } std::cout << std::endl; return 0;
}
输出:
apple banana cherry
示例 3:遍历自定义容器
只要自定义容器支持 begin() 和 end() 成员函数,并且这些函数返回的迭代器支持解引用操作,那么就可以使用基于范围的 for 循环来遍历它:
#include <iostream> class IntegerSequence {
public:IntegerSequence(int start, int end) : m_start(start), m_end(end) {}class iterator {public:iterator(int value) : m_current(value) {}bool operator==(const iterator& other) const {return m_current == other.m_current;}bool operator!=(const iterator& other) const {return !(*this == other);}int operator*() const {return m_current;}iterator& operator++() {++m_current;return *this;}private:int m_current;};iterator begin() const {return iterator(m_start);}iterator end() const {return iterator(m_end + 1);}private:int m_start;int m_end;
};int main()
{IntegerSequence seq(1, 5);// 使用基于范围的for循环遍历自定义类型 for (int num : seq) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
输出:
1 2 3 4 5
在上面的代码中,IntegerSequence 类包含了一个迭代器 iterator,该迭代器可以递增并返回当前值。IntegerSequence 的 begin 和 end 成员函数返回迭代器,分别指向序列的开始和结束位置。
在 main 函数中,创建了一个 IntegerSequence 对象,并使用基于范围的 for 循环遍历它。循环体会自动使用 begin 和 end 返回的迭代器,并在每次迭代时递增迭代器,直到它等于 end 返回的迭代器为止。
3.3 注意事项
如下是基于范围的 for 循环的一些注意事项:
- 基于范围的 for 循环只能用于支持迭代器的序列。
- 在循环体内,无法直接修改序列的大小(例如,在遍历 std::vector 时,不能添加或删除元素)。如果需要修改序列,则需要使用传统的迭代器循环。
- 对于引用类型的循环变量(如示例 2 中的 const std::string& fruit),实际上是在引用序列中的元素,而不是复制它们。这意味着对循环变量的修改会影响序列中的实际元素(如果没有使用 const 的话)。