一.nullptr 代替 NULL
在C++中,nullptr
是一个空指针常量,用于表示空指针。它是C++11引入的新特性,旨在替代以前使用的NULL
宏。
使用nullptr
可以带来一些优势:
-
类型安全:
nullptr
是一个特殊的空指针类型,不会和其他类型发生混淆。而NULL
通常被定义为0或(void*)0,可能与整数类型发生重载歧义。 -
可读性:
nullptr
更加直观和明确,表达了“空指针”的含义,而NULL
相对较为模糊。 -
编译器检查:使用
nullptr
可以让编译器在某些情况下进行更好的类型检查和警告,帮助发现潜在的错误。
总而言之,如果你在C++11或更新版本的项目中工作,建议使用nullptr
来表示空指针,以取代以前的NULL
宏。这样可以提高代码的可读性和类型安全性。
二.引入 auto 和 decltype 关键字
在C++11中引入了auto
和decltype
两个关键字,用于简化类型推导和声明。
-
auto
关键字:auto
关键字可以用于自动推导变量的类型。通过使用auto
,编译器可以根据变量的初始化表达式来推断出变量的类型,从而省略显式地指定类型。- 例如,
auto x = 10;
会将变量x
的类型推断为int
,而auto y = 3.14;
会将变量y
的类型推断为double
。 auto
还可以与范围-based for循环一起使用,简化迭代变量的类型声明。- 注意:
auto
不是动态类型,而是在编译时进行类型推导。
-
decltype
关键字:decltype
关键字用于推导表达式的类型。它可以根据给定表达式的类型推导出一个变量或表达式的准确类型,并返回该类型。- 例如,
decltype(x + y)
会推导出x
和y
相加后的结果类型,可以用于声明一个新的变量。 decltype
还可以用于推导函数返回值的类型,通过结合auto
和decltype
,可以实现更灵活的类型推导。- 注意:
decltype
在推导过程中会保留表达式的const和引用特性。
这两个关键字的引入使得C++的类型推导更加灵活和方便,可以简化代码编写,并提高代码的可读性。使用auto
可以避免在变量声明时重复指定类型,而使用decltype
可以方便地获取表达式的准确类型。
三.基于范围的 for 循环 (for(auto &it:res){})
基于范围的for循环(Range-based for loop)是C++11引入的一种便捷的迭代方式,可以遍历容器或者其他支持迭代的数据结构中的元素。
语法形式为:for (auto &it : container) { }
其中,auto &it
是用来定义迭代变量的,它会自动推导出容器中元素的类型,并且通过引用方式进行访问,以便可以修改容器中的元素。
container
是要进行遍历的容器或者其他支持迭代的数据结构,比如数组、std::vector
等等。
在每次循环迭代时,迭代变量it
会按序访问container
中的每个元素,可以直接对元素进行操作,无需使用索引或迭代器。
例如,假设有一个std::vector<int>
的容器numbers
,我们可以使用基于范围的for循环遍历并打印所有元素:
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto &num : numbers) {
std::cout << num << " ";
}
输出结果为:1 2 3 4 5
基于范围的for循环简化了迭代过程,不需要手动管理迭代器或者使用索引进行访问。它提供了一种更加清晰和简洁的方式来遍历容器中的元素。同时,通过使用引用方式进行迭代,还可以方便地修改容器中的元素。
四.类和结构体中的初始化列表
在C++中,类和结构体可以使用初始化列表(Initialization List)来对成员变量进行初始化。初始化列表是在构造函数的参数列表后面使用冒号(:)表示的。
通过初始化列表,可以在对象创建时对成员变量进行赋值,而不是在构造函数的函数体内部进行赋值操作。这样可以提高代码的效率,并且避免一些潜在的问题。
以下是一个示例,展示了如何在类的构造函数中使用初始化列表:
class MyClass {
public:
int x;
double y;
// 构造函数使用初始化列表
MyClass(int a, double b) : x(a), y(b) {
// 构造函数的函数体
}
};
在上面的例子中,MyClass
类具有两个成员变量:x
和y
。构造函数中的初始化列表 : x(a), y(b)
将参数 a
赋值给成员变量 x
,将参数 b
赋值给成员变量 y
。
使用初始化列表的好处包括:
-
效率:通过初始化列表,在对象创建时就进行成员变量的赋值操作,避免了多余的拷贝构造或赋值操作,提高了效率。
-
成员变量常量或引用类型的初始化:对于某些成员变量是常量或引用类型的情况,只能通过初始化列表来进行初始化。
-
初始化顺序控制:通过初始化列表,可以显式地指定成员变量的初始化顺序,避免由于不同编译器对成员变量初始化顺序的不同处理而导致的问题。
总结来说,使用初始化列表可以方便地对类和结构体中的成员变量进行初始化,并具有提高效率和灵活性的优势。它是C++中常用的一种初始化方式。
五.std::forward_list(单向链表)
std::forward_list
是C++标准库中提供的一种单向链表容器。它是C++11引入的,在头文件<forward_list>
中定义。
单向链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。与std::list
(双向链表)相比,std::forward_list
只允许单向遍历,每个节点只有一个指向下一个节点的指针,因此空间上更加高效。
使用std::forward_list
可以实现对数据进行插入、删除和查找等基本操作,具有以下特点:
-
迭代器:类似于其他STL容器,
std::forward_list
提供了迭代器来遍历链表中的元素。可以使用前向迭代器(iterator
)或常量前向迭代器(const_iterator
)。 -
动态大小:
std::forward_list
的大小在运行时可以动态改变,不需要预先指定容量。这使得添加或删除元素非常高效。 -
单向遍历:由于每个节点只有一个指针指向下一个节点,因此
std::forward_list
只支持单向遍历,而无法进行逆向遍历。 -
删除和插入操作:除了提供常规的插入和删除操作外,
std::forward_list
还提供了erase_after
函数,用于删除指定位置后的节点,以及insert_after
函数,用于在指定位置后插入节点。
以下是一个示例代码,展示了如何使用std::forward_list
:
#include <iostream>
#include <forward_list>
int main() {
std::forward_list<int> numbers;
// 在链表头部插入元素
numbers.push_front(3);
numbers.push_front(2);
numbers.push_front(1);
// 遍历链表并打印元素
for (const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 删除第一个元素
numbers.pop_front();
// 在指定位置后插入元素
auto it = numbers.insert_after(numbers.begin(), 5);
// 删除指定位置后的元素
numbers.erase_after(it);
// 再次遍历链表并打印元素
for (const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
六.右值引用和move语义
右值引用和移动语义是C++11引入的重要特性,旨在提高内存管理的效率和性能。
-
右值引用(Rvalue Reference):
- 右值引用是一种新的引用类型,通过使用双引号&&来声明。例如,
int&&
表示一个右值引用。 - 右值引用主要用于绑定临时对象、表达式或将要销毁的对象等“右值”上。
- 与左值引用不同,右值引用可以绑定到临时对象,而左值引用只能绑定到具有名称的左值对象。
- 右值引用还能够通过
std::move
函数将左值转换为右值引用,用于启用移动语义。
- 右值引用是一种新的引用类型,通过使用双引号&&来声明。例如,
-
移动语义(Move Semantics):
- 移动语义是利用右值引用实现的一种资源管理方式,用于避免不必要的复制操作,提高性能。
- 通过移动语义,可以将资源(如动态分配的内存或IO句柄)从一个对象转移到另一个对象,而无需进行深拷贝。
- 移动语义通过使用移动构造函数(Move Constructor)和移动赋值运算符(Move Assignment Operator)来实现。
使用移动语义的常见场景包括:
- 在容器类中对元素进行插入或删除操作时,通过移动语义可以避免不必要的数据复制,提高性能。
- 在使用动态内存管理的对象中,通过移动资源实现高效的资源传递和释放。
以下是一个示例代码,展示了如何使用右值引用和移动语义:
#include <iostream>
#include <vector>
class MyObject {
public:
int* data;
// 移动构造函数
MyObject(MyObject&& other) noexcept : data(other.data) {
other.data = nullptr;
}
// 移动赋值运算符
MyObject& operator=(MyObject&& other) noexcept {
if (this != &other) {
delete data;
data = other.data;
other.data = nullptr;
}
return *this;
}
// 构造函数和析构函数等其他函数...
};
int main() {
std::vector<MyObject> vec;
// 添加临时创建的对象到容器中
vec.push_back(MyObject());
return 0;
}
在上述示例中,MyObject
类实现了移动构造函数和移动赋值运算符来管理资源的转移。当将临时创建的对象添加到容器中时,会使用移动语义而不是复制构造函数,从而提高运行效率。
总结来说,右值引用和移动语义是C++11引入的重要特性,通过使用右值引用和移动构造函数、移动赋值运算符,可以避免不必要的数据复制和资源管理开销,提高程序的性能和效率。它们在现代C++编程中被广泛应用。
七.无序容器和正则表达式
无序容器(Unordered Containers)是C++标准库提供的一组数据结构,包括std::unordered_set
、std::unordered_map
、std::unordered_multiset
和std::unordered_multimap
。它们在C++11引入,以哈希表(Hash Table)为基础实现,用于支持高效的快速查找、插入和删除操作。
无序容器的特点如下:
- 插入和查找效率高:无序容器使用哈希函数来将元素映射到存储桶中,使得插入和查找操作具有常数平均时间复杂度。
- 无序性:无序容器中的元素没有特定的顺序,与插入的顺序无关。
- 哈希冲突:由于哈希函数的限制,可能会出现不同元素映射到同一个存储桶的情况,称为哈希冲突。无序容器使用链地址法或开放定址法解决哈希冲突。
- 迭代器失效:与有序容器相比,无序容器的插入和删除操作可能导致迭代器失效,因为元素的重新哈希分布可能导致存储位置的改变。
以下是无序容器的示例代码:
#include <iostream>
#include <unordered_set>
#include <unordered_map>
int main() {
// 无序集合
std::unordered_set<int> set = {3, 1, 2, 4, 5};
// 插入元素
set.insert(6);
// 遍历集合
for (const auto& num : set) {
std::cout << num << " ";
}
std::cout << std::endl;
// 无序映射
std::unordered_map<std::string, int> map = {{"apple", 3}, {"banana", 2}, {"orange", 4}};
// 插入键值对
map["grape"] = 5;
// 查找元素
if (map.find("banana") != map.end()) {
std::cout << "Found banana with value: " << map["banana"] << std::endl;
}
return 0;
}
输出结果可能是:
6 5 4 3 2 1
Found banana with value: 2
正则表达式(Regular Expression)是一种描述和匹配字符串模式的工具,在C++中通过<regex>
头文件提供了正则表达式库。使用正则表达式可以进行字符串的模式匹配、搜索和替换等操作。
C++标准库中的正则表达式支持以下基本功能:
- 正则表达式语法:通过正则表达式语法,可以定义要匹配的模式。
- 匹配操作:可以使用正则表达式进行字符串匹配,判断是否符合特定的模式。
- 搜索和迭代:可以搜索符合正则表达式模式的字符串,并进行迭代操作。
- 替换操作:可以使用正则表达式进行字符串替换,将匹配到的部分替换为新的内容。
以下是正则表达式的示例代码:
#include <iostream>
#include <regex>
int main() {
std::string text = "Hello, 2023!";
// 匹配正则表达式
std::regex pattern("\\d+");
if (std::regex_search(text, pattern)) {
std::cout << "Match found!" << std::endl;
}
// 迭代匹配结果
std::sregex_iterator it(text.begin(), text.end(), pattern);
std::sregex_iterator end;
while (it != end) {
std::smatch match = *it;
std::cout << "Matched: " << match.str() << std::endl;
++it;
}
// 替换匹配结果```cpp
std::string replaced = std::regex_replace(text, pattern, "2024");
std::cout << "Replaced: " << replaced << std::endl;
return 0;
}
输出结果可能是:
Match found!
Matched: 2023
Replaced: Hello, 2024!
总结来说,无序容器提供了基于哈希表的高效数据存储和查找机制,适用于需要快速插入、查找和删除操作的场景。而正则表达式库则可以进行字符串的模式匹配、搜索和替换等操作,在处理文本的复杂操作中非常有用。它们都是C++标准库提供的重要功能,能够方便地处理相关的问题。
八.成员变量默认初始化
在C++中,成员变量的默认初始化可以通过以下几种方式实现:
1.默认构造函数初始化:如果类定义了一个默认构造函数(无参数或所有参数都有默认值),则在创建对象时,成员变量会通过默认构造函数进行初始化。
class MyClass {
public:
int num; // 默认构造函数将num初始化为0
std::string str; // 默认构造函数将str初始化为空字符串
};
int main() {
MyClass obj;
cout << obj.num << endl; // 输出:0
cout << obj.str << endl; // 输出:""
return 0;
}
2.成员变量初始值列表:在类的构造函数中,可以使用成员变量的初始值列表来显式地初始化成员变量。
class MyClass {
public:
int num;
std::string str;
MyClass() : num(42), str("Hello") {} // 通过初始值列表初始化num和str
};
int main() {
MyClass obj;
cout << obj.num << endl; // 输出:42
cout << obj.str << endl; // 输出:"Hello"
return 0;
}
3.类内静态成员变量初始化:类内静态成员变量可以在声明时直接初始化,或在类外部进行定义并初始化。静态成员变量只会被初始化一次。
class MyClass {
public:
static int count; // 声明静态成员变量
MyClass() {
count++; // 在构造函数中对静态成员变量进行操作
}
};
int MyClass::count = 0; // 在类外部进行定义和初始化
int main() {
MyClass obj1;
MyClass obj2;
cout << MyClass::count << endl; // 输出:2
return 0;
}
总结来说,在C++中,成员变量的默认初始化可以通过默认构造函数、成员变量初始值列表和类内静态成员变量初始化来实现。选择合适的方式取决于具体的需求和设计。