C++语言新特性2
第009讲 关键字:nullptr/constexpr
1. nullptr
nullptr 是 C++11 引入的一个关键字,用于表示空指针。它的主要作用是替代传统的 NULL 宏和 0,提供了更强的类型安全性。
特点:
- 类型安全:nullptr 是一个指针类型,具体的类型是std::nullptr_t。因此,它可以隐式转换为任何类型的指针,但不能转换为整数类型,这样可以避免一些常见的错误。
- 避免二义性:在函数重载时,如果使用 NULL 或 0,可能会导致二义性问题。而 nullptr 由于其明确的类型,可以消除这种困扰。
案例:
#include <iostream>
void func(int) {
std::cout << "Called func with int" << std::endl;
}
void func(char*) {
std::cout << "Called func with char*" << std::endl;
}
int main() {
func(0); // 可能会调用 func(int)
func(NULL); // 可能会调用 func(int) 或 func(char*)
func(nullptr); // 明确调用 func(char*)
return 0;
}
2. constexpr
constexpr 关键字也是在 C++11 中引入的,用于指示一个变量或函数可以在编译时求值。这意味着可以在编译时计算其值,从而优化性能。
特点:
- 编译时常量:使用 constexpr 声明的变量或函数,其值在编译时确定。这样可以提高性能,尤其是在常量表达式的情况下。
- 函数:constexpr 函数可以在编译时求值,但并非所有 constexpr函数都必须在编译时执行。只有当它们的参数也是常量表达式时,才能在编译时求值。
- C++14 和 C++17 的扩展:C++14 进一步扩展了constexpr 的能力,允许在 constexpr 函数中使用更复杂的语句(如条件语句和循环)。C++17 又引入了一些新的特性,使得 constexpr 更加灵活。
示例:
#include <iostream>
// constexpr 函数
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int value = factorial(5); // 在编译时计算
std::cout << "Factorial of 5 is: " << value << std::endl; // 输出 120
return 0;
}
第010讲 共享内存
缺:
第011讲 std::unordered_set
std::unordered_set 是 C++ 标准库中提供的一种关联容器,用于存储不重复的元素,它的底层实现通常使用哈希表。与 std::set 不同,std::unordered_set 不保证元素的顺序,因此在插入、删除和查找元素时具有平均常数时间复杂度 O(1)。
主要特点:
- 无序性:元素的存储顺序与插入顺序无关。
- 唯一性:不允许重复元素。
- 快速查找:使用哈希表实现,平均查找时间复杂度为 O(1)。
- 动态大小:根据需要自动扩展,管理内存。
包含的头文件
使用 std::unordered_set 时需要包含以下头文件:
#include <unordered_set>
基本用法
以下是 std::unordered_set 的一些基本用法示例:
#include <iostream>
#include <unordered_set>
int main() {
// 创建一个 unordered_set
std::unordered_set<int> mySet;
// 插入元素
mySet.insert(1);
mySet.insert(2);
mySet.insert(3);
mySet.insert(2); // 2 将不会被插入,因为它是重复的
// 输出集合元素
std::cout << "Elements in mySet: ";
for (const auto& elem : mySet) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 查找元素
if (mySet.find(2) != mySet.end()) {
std::cout << "2 is found in mySet." << std::endl;
} else {
std::cout << "2 is not found in mySet." << std::endl;
}
// 删除元素
mySet.erase(2);
// 检查元素是否存在
if (mySet.find(2) == mySet.end()) {
std::cout << "2 has been removed from mySet." << std::endl;
}
return 0;
}
主要成员函数:
insert():添加元素,若元素已存在则不会插入。
erase():删除指定元素。
find():查找元素,返回一个迭代器指向该元素,若未找到则返回 end()。
count():返回指定元素的个数(对于
unordered_set 来说,最大为 1)。
size():返回集合中元素的数量。
clear():清空集合。 begin() 和
end():返回迭代器,用于遍历集合
示例:
#include <iostream>
#include <unordered_set>
struct Point {
int x, y;
// 重载比较运算符
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
// 自定义哈希函数
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash<int>()(p.x) ^ std::hash<int>()(p.y);
}
};
int main() {
// 创建一个 unordered_set 存储 Point
std::unordered_set<Point, PointHash> pointSet;
// 插入元素
pointSet.insert({1, 2});
pointSet.insert({3, 4});
pointSet.insert({1, 2}); // 重复的元素不会被插入
// 输出集合元素
for (const auto& point : pointSet) {
std::cout << "(" << point.x << ", " << point.y << ")" << std::endl;
}
return 0;
}
其中:
1.结构体 PointHash:
这是一个自定义的哈希函数结构体,通常与 std::unordered_map 或 std::unordered_set 一起使用。
2.重载 operator():
通过重载 operator(),PointHash 可以被当作函数调用。这种设计使得用户可以直接调用 PointHash 的实例,就像调用函数一样。
3.参数 const Point& p:
这个参数是一个常量引用,表示将要计算哈希值的 Point 对象。这种方式有效地避免了不必要的拷贝,提高了效率。
4.哈希计算:
- std::hash()(p.x) 和 std::hash()(p.y):分别计算 Point 对象的 x 和 y坐标的哈希值。
- ^运算符:这是按位异或运算符,将两个哈希值结合在一起。异或运算可以在一定程度上减少哈希冲突,因为它将两个不同的值混合,产生一个新的哈希值。
注意事项:
- 由于使用哈希表实现,std::unordered_set 对元素类型有特殊要求,元素类型必须支持哈希函数和相等比较。
- 可以通过自定义哈希函数和比较函数来使用自定义类型作为 unordered_set 的元素。
总结
std::unordered_set 是一个高效且灵活的容器,适合需要快速查找、插入和删除操作的场景。通过合理的哈希函数和比较操作,可以方便地存储自定义类型的元素。
第012讲 关联容器:unordered_map
unordered_map 是 C++ 标准库中提供的一种关联容器,属于 STL(标准模板库)的一部分。它是一个基于哈希表的实现,用于存储键值对数据。与 map 不同,unordered_map 不会保持元素的顺序,插入和查找的性能通常更快,但其内部元素的顺序是无序的。
特性:
- 底层实现:unordered_map 使用哈希表来存储数据,因此查找、插入和删除操作的平均时间复杂度为 O(1)。
- 无序性:与map(基于红黑树实现)不同,unordered_map 不会保持元素的顺序。元素的遍历顺序是无序的。
- 键的唯一性:每个键只能出现一次,如果重复插入相同的键,则会更新其对应的值。
- 自定义哈希:可以自定义哈希函数和比较函数,以支持自定义类型。
基本用法
以下是 unordered_map 的一些基本用法示例:
#include <iostream>
#include <unordered_map>
#include <string>
int main() {
// 创建一个 unordered_map
std::unordered_map<std::string, int> umap;
// 插入元素
umap["apple"] = 1;
umap["banana"] = 2;
umap["orange"] = 3;
// 访问元素
std::cout << "apple: " << umap["apple"] << std::endl; // 输出: apple: 1
// 检查键是否存在
if (umap.find("banana") != umap.end()) {
std::cout << "banana is found with value: " << umap["banana"] << std::endl; // 输出: banana is found with value: 2
}
// 删除元素
umap.erase("orange");
// 遍历元素
for (const auto& pair : umap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
注意事项
- 在使用 unordered_map 之前,确保你对哈希函数和哈希冲突有基本的了解。C++ 默认提供了常见类型(如整数、字符串)的哈希函数。
- 对于自定义类型,需要提供自定义的哈希函数和比较函数。
- 由于 unordered_map的实现是基于哈希表,因此在元素个数较少时,可能会造成内存空间的浪费。
第013讲 function函数对象
在C++中,函数对象(Function Objects)是一个非常强大的特性。它们是可以被调用的对象,通常是通过重载运算符 () 来实现的。函数对象与普通函数相比,有几个优点,例如可以存储状态、参数化行为等。
创建函数对象的基本步骤:
- 定义一个类:创建一个类并重载 () 运算符。
- 在类中添加成员变量(可选):可以在类中添加成员变量,用于存储状态。
- 实现operator():实现重载的 () 运算符,用于函数调用的具体实现。
c++函数对象:函数对象指定义operator()的对象,语法形式如下:
Class FunctionObjectType
{
Public:
Void operator()(){
操作语句
}
};
示例:
#include <iostream>
class Adder {
public:
// 构造函数,接受一个整数值
Adder(int value) : value(value) {}
// 重载()运算符
int operator()(int x) const {
return x + value; // 返回 x 和 value 的和
}
private:
int value; // 存储的状态
};
int main() {
Adder addFive(5); // 创建一个函数对象,添加 5
std::cout << "Adding 10: " << addFive(10) << std::endl; // 输出 15
Adder addTen(10); // 创建另一个函数对象,添加 10
std::cout << "Adding 20: " << addTen(20) << std::endl; // 输出 30
return 0;
}
函数对象的优点:
- 状态存储:函数对象可以存储状态,而普通函数不能。通过类的成员变量,你可以在不同的调用之间保持状态。
- 灵活性:可以通过构造函数传递参数来调整函数对象的行为。
- 可以与 STL容器和算法结合使用:函数对象可以与标准模板库(STL)的容器和算法紧密结合使用,例如 std::sort,std::for_each 等。
使用函数对象的场景:
- 算法和排序:可以使用自定义的比较器函数对象来控制 STL 算法的行为。
- 回调:函数对象可以用作回调机制,在需要时将其传递给其它函数。
- 延迟计算:通过将函数对象存储在数据结构中,可以实现延迟计算的功能。
示例使用函数对象与 STL 算法:
#include <iostream>
#include <vector>
#include <algorithm>
class Compare {
public:
bool operator()(int a, int b) const {
return a > b; // 从大到小排序
}
};
int main() {
std::vector<int> vec = {5, 2, 8, 1, 3};
std::sort(vec.begin(), vec.end(), Compare()); // 使用函数对象进行排序
std::cout << "Sorted vector: ";
for (int num : vec) {
std::cout << num << " "; // 输出排序后的数组
}
std::cout << std::endl;
return 0;
}