目录
- C++11 重要新特性
- 1. range-based for 语句
- 2. nullptr
- 3. auto 类型指示符
- 4. decltype 类型指示符
- 5. unifrom initialization
- 6. initializer_list
- 7. explicit(不是新特性)
- 8. 可变参数模板(Variadic templates)
- 9. default
- 10. delete
- 11. alias templates(模板别名)
- 12. Template Template Parameter(模板模板参数)
- 13. 类型别名(Type Alias)
- 14. noexcept
- 15. override
- 16. final
- 17. 一元谓词、二元谓词以及仿函数
- 18. lambda
- 19. 右值引用
C++11 重要新特性
1. range-based for 语句
用于更方便地遍历容器(例如数组、vector、list 等)中的元素。
语法格式:
for (range_declaration : range_expression) {
// 循环体
}
range_declaration 是一个变量,用于接收容器中的每个元素。
range_expression 是要遍历的容器,可以是数组、STL 容器或其他支持 begin() 和 end() 函数的对象。
示例:
std::vector<int> nums = {1, 2, 3, 4, 5};
for (const auto& num : nums) {
std::cout << num << " ";
}
2. nullptr
nullptr_t 是 C++11 标准引入的一个新类型,专门用于表示 nullptr。它是一种空指针常量的类型,是为了解决在类型推导过程中可能产生的二义性问题。
表示空指针,nullptr 是一个属于 nullptr_t 类型的字面常量,不同于 NULL 或 0,它没有整数类型。nullptr 被用来初始化指针、作为函数参数传递,并在条件判断语句中进行比较。使用 nullptr 有助于编写更安全、更明确的代码。
int* ptr1 = nullptr; // 使用 nullptr 初始化指针
int* ptr2 = 0; // 传统方式,但不推荐
3. auto 类型指示符
使用 auto 声明的变量可以根据其初始化表达式的类型来确定其类型,从而简化代码书写。
语法:
auto variable = expression;
//其中 expression 是变量的初始化表达式。编译器会根据初始化表达式的类型推导出变量的类型。
需要注意的是,auto 并不是一种动态类型,而是在编译期确定的静态类型。
4. decltype 类型指示符
它的作用是返回操作数的数据类型。编译器分析表达式并得到它的类型,但不实际计算表达式的值。
decltype(f()) sum = x; //sum的类型就是函数 f 返回类型
编译器并不实际调用函数f ,只是返回类型作为sum的类型。
切记: decltype((variable)) (注意是双层括号) 的结果永远是引用,而 decltype(variable) 结果只有当 variable 本身就是一个引用时才是引用。
1. 基本用法:
decltype(x + 1) z = 3; // z的类型为int,因为x + 1的类型为int
int a = 3, b = 4;
decltype(a) c = a;
decltype((b)) d = a;
cout << ++c << " " << ++d << endl; // 4 4
2. 结合模板:
template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
//这是一种使用尾置返回类型(trailing return type)语法的写法,主要用于指定函数返回类型。在这个写法中,返回类型写在 -> 后面。通常结合auto关键字使用。
return t + u;
}
int main() {
int a = 5;
double b = 3.14;
auto result = add(a, b); // result的类型为double,decltype(a + b)为double
return 0;
}
3. decltype(auto):
int x = 42;
// 使用decltype(auto)保留引用类型,因为 x 是一个 int 类型的变量,而 decltype(auto) 也会保留 x 的引用类型。
decltype(auto) y = x; // y的类型为int&,是x的引用
5. unifrom initialization
是 C++11 引入的一种初始化语法,它的目标是提供一种统一的初始化方式,适用于各种类型的对象。
// 直接初始化
int a{42};
// 拷贝初始化
double b = {3.14};
// 列表初始化数组
int arr[]{1, 2, 3, 4, 5};
// 列表初始化容器
std::vector<int> vec{1, 2, 3, 4, 5};
6. initializer_list
std::initializer_list 是 C++11 引入的一种新特性,它允许你使用花括号 {} 语法初始化对象和数组。
initializer_list 是一个轻量级的容器,它可以包含任何类型的值。你可以将 initializer_list 作为函数或构造函数的参数,以便接受花括号初始化列表。
#include<initializer_list>
class MyClass {
vector<int> data;
public:
MyClass(initializer_list<int> init) : data(init) {}
};
MyClass obj {1, 2, 3, 4, 5}; // 使用花括号初始化列表初始化 MyClass 对象
以下情况下使用:
- 当你在自己的类或函数中直接使用 initializer_list 类型时。例如,如果你想让你的类的构造函数接受一个初始化列表,或者你想在你的函数中处理一个初始化列表,你就需要包含 <initializer_list> 头文件。
MyClass(initializer_list<int> list) {// 处理初始化列表}
- 当你需要访问 initializer_list 类型的成员函数(如 begin()、end()、size())时。虽然你可以在不包含 <initializer_list> 头文件的情况下创建和传递初始化列表,但如果你需要访问这些函数,你就需要包含这个头文件。
#include <initializer_list>
void printSize(initializer_list<int> list) {
cout << "Size: " << list.size() << endl;
}
7. explicit(不是新特性)
explicit 关键字主要用于修饰单参数的构造函数,目的是防止隐式类型转换。
// 单参数构造函数声明为 explicit
explicit MyClass(int x) : value(x) {}
// 多参数构造函数
MyClass(int x, int y) : value(x + y) {} // 没有使用explicit
多参数构造函数通常不会导致隐式类型转换问题,因为需要提供足够的参数,这使得类型转换更为显式。
8. 可变参数模板(Variadic templates)
它允许你创建接受任意数量参数的模板函数或模板类。在此之前,模板的参数数量是固定的,但引入可变参数模板后,可以编写接受任意数量参数的模板。
可变参数模板主要使用在函数模板和类模板中,通过 … 语法来表示可变数量的参数。这种特性使得编写更加灵活的模板成为可能。允许处理不定数量的参数类型。
(1) 函数模板中的可变参数模板:
#include <iostream>
// 基本情况:递归终止
template <typename T>
void print(const T& value) {
std::cout << value << std::endl;
}
// 递归情况:打印第一个参数并递归调用打印其余参数
template <typename T, typename... Args>
void print(const T& first, const Args&... args) {
std::cout << first << ", ";
print(args...);
}
int main() {
print(1, 2.5, "Hello", 'c');
return 0;
}
在上面的示例中,print函数模板通过可变参数模板实现了接受任意数量参数的功能。递归地调用print函数,每次打印第一个参数,并递归调用以打印其余参数。
(2) 类模板中的可变参数模板:
#include <iostream>
// 类模板的基本情况:递归终止
template <typename T>
class Tuple {
public:
Tuple(const T& value) : data(value) {}
void print() const {
std::cout << data << std::endl;
}
private:
T data;
};
// 类模板的递归情况:包含一个元素和递归包含其余元素
template <typename T, typename... Args>
class Tuple {
public:
Tuple(const T& value, const Args&... args) : data(value), rest(args...) {}
void print() const {
std::cout << data << ", ";
rest.print();
}
private:
T data;
Tuple<Args...> rest;
};
int main() {
Tuple<int, double, std::string> myTuple(1, 2.5, "Hello");
myTuple.print();
return 0;
}
在上面的示例中,Tuple类模板使用可变参数模板实现了接受任意数量的参数。在递归情况下,每个实例都包含一个元素和递归的Tuple实例,这样就可以处理不同数量的模板参数。
9. default
如果类中有了自定义的构造函数,编译器就不会隐式生成默认构造函数。
default 关键字可以用于显式地要求编译器生成默认的版本。也可用于默认拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数。
MyClass() = default;
10. delete
主要用于删除特殊的成员函数,如拷贝构造函数和拷贝赋值运算符,以防止编译器自动生成它们。
MyClass(const MyClass&) = delete; // 禁止拷贝构造函数
MyClass& operator =(const MyClass&) = delete; // 禁止拷贝赋值运算符
11. alias templates(模板别名)
模板别名允许给现有的模板起一个新的名字,使代码更简洁易读。
语法:
template <typename T>
using NewName = OldTemplate<T>;
示例:
template <typename T>
using MyContainer = std::vector<T>; //MyContainer 是 std::vector 的别名
MyContainer<int> numbers = {1, 2, 3, 4, 5};
12. Template Template Parameter(模板模板参数)
允许你将模板作为另一个模板的参数。模板参数的推导并不会自动考虑默认模板参数。如果你传递的模板有默认参数,你仍然需要显式提供所有参数。
语法:
template <template <typename> class TemplateParameter>
class MyClass {
// 使用 TemplateParameter 进行操作
};
示例:
#include <iostream>
// 声明一个模板模板参数
template <template <typename> class Container>
class Wrapper {
public:
// 使用模板模板参数作为成员
Container<int> container;
// 构造函数初始化
Wrapper(const Container<int>& initialContainer) : container(initialContainer) {}
// 打印容器元素
void print() {
for (const auto& element : container) {
std::cout << element << " ";
}
std::cout << std::endl;
}
};
// 定义一个模板类作为模板模板参数
template <typename T>
class MyContainer {
public:
T value;
MyContainer(const T& val) : value(val) {}
// 添加打印功能
void print() const {
std::cout << value << std::endl;
}
};
int main() {
// 使用 Wrapper包装 MyContainer
Wrapper<MyContainer> wrapper(MyContainer<int>(42));
// 打印 Wrapper中的容器元素
wrapper.print();
return 0;
}
在这个示例中,Wrapper是一个使用模板模板参数的类模板,它接受一个模板类 Container 作为参数。MyContainer 是一个简单的模板类,包含一个值,并具有打印功能。在 main 函数中,我们创建了一个 Wrapper 对象,将 MyContainer
作为模板参数传递给 Wrapper。最后,我们调用 Wrapper 的 print 函数来打印容器元素。
13. 类型别名(Type Alias)
用来为现有类型定义一个新的名称。它可以提高代码的可读性和可维护性。在C++中,有两种方式定义类型别名:使用关键字typedef和使用关键字using。
在现代 C++ 中,using 更为推荐,因为它更灵活、语法更清晰。
示例:
使用 using 定义类型别名:
using Integer = int; // 将 int 类型定义为 Integer
using DoubleVector = std::vector; // 将 std::vector 定义为 DoubleVector
using SizeType = std::size_t; // 使用标准库定义的 std::size_t 作为类型别名
using Predicate = bool (*)(int); // 使用函数指针作为类型别名
template <typename T>
using MyVector = std::vector; // 使用模板别名
14. noexcept
当函数被声明为 noexcept 时,它表示该函数不会抛出异常。这有助于编译器进行一些优化,并使程序员能够更清晰地了解函数的异常行为。
语法:
- 函数声明中使用 noexcept 关键字的语法如下:
void myFunction() noexcept {
// 函数体
}
- 或者可以指定一个条件,如果该条件为 true,则函数不会抛出异常:
void myFunction() noexcept(condition) {
// 函数体
}
示例:
#include <iostream>
// 带有异常的函数
int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Division by zero");
}
return a / b;
}
// 带有 noexcept 的函数
int safe_divide(int a, int b) noexcept {
if (b == 0) {
// 在 noexcept 函数中抛出异常,会导致程序终止
throw std::runtime_error("Division by zero");
}
return a / b;
}
int main() {
try {
// 调用带有异常的函数
int result = divide(10, 2);
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << std::endl;
}
try {
// 调用带有 noexcept 的函数
int result = safe_divide(10, 0);
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << std::endl;
}
return 0;
}
在这个示例中,divide 函数可能抛出异常,而 safe_divide 函数使用 noexcept 表示它不会抛出异常。在 main 函数中,我们分别调用这两个函数,并使用 try 和 catch 块捕获异常。请注意,如果在 noexcept 函数中抛出异常,会导致程序终止。
15. override
override 关键字用于在派生类中显式指定对基类虚函数的覆盖。它有助于在编译时检测错误,例如拼写错误或者意外不匹配的函数签名。
如果在派生类中使用 override 关键字声明一个函数,但该函数并没有覆盖基类中的虚函数,编译器将生成一个错误。
#include <iostream>
class Base {
public:
virtual void print() const {
std::cout << "Base class" << std::endl;
}
};
class Derived : public Base {
public:
// 使用 override 关键字表明该函数覆盖了基类的虚函数
void print() const override {
std::cout << "Derived class" << std::endl;
}
// 拼写错误 编译时检测错误
//void prin() const override {
// std::cout << "Derived class" << std::endl;
//}
};
16. final
final 关键字用于在基类中阻止某个虚函数被进一步覆盖,或者在派生类中阻止某个类被继续派生。
在基类中使用 final 关键字,可以防止派生类再次覆盖该虚函数。
- 禁止继承的类示例:
class FinalClass final {
public:
// 类声明中使用 final,表示该类不能被继承
void someFunction() const {
// 类的成员函数实现
}
};
// 下面的派生类声明是非法的,会导致编译错误
// class Derived : public FinalClass {};
- 禁止覆盖的虚函数示例:
class Base {
public:
// 使用 final 关键字表示该虚函数不能被派生类覆盖
virtual void someFunction() const final {
// 虚函数的实现
}
};
class Derived : public Base {
// 下面的函数声明是非法的,会导致编译错误
// void someFunction() const override;
};
17. 一元谓词、二元谓词以及仿函数
(1)一元谓词(Unary Predicate):
一元谓词是一个接受一个参数的函数或函数对象。
它通常用于表示某种条件,例如判断元素是否满足某个条件。
一元谓词通常用于像 find_if、remove_if 等算法中,这些算法需要一个判断条件。
// 一元谓词的示例
bool isEven(int x) {
return x % 2 == 0;
}
(2)二元谓词(Binary Predicate):
二元谓词是一个接受两个参数的函数或函数对象。
它通常用于表示某种操作或比较两个元素。
二元谓词通常用于像 sort、max_element 等需要比较或操作两个元素的算法中。
// 二元谓词的示例
bool compare(int a, int b) {
return a < b;
}
(3)仿函数(函数对象):
仿函数是一个类或对象,它可以像函数一样被调用。
它可以重载 operator(),使得对象的行为类似于函数调用。
仿函数通常用于需要封装某种操作或条件的算法中,例如像 transform 中的转换操作。
// 仿函数的示例
struct Multiply {
int operator()(int a, int b) const {
return a * b;
}
};
18. lambda
一个lambda表达式表示一个可调用的代码单元。可以理解为一个未命名的内联函数。一个lamba具有一个返回类型,一个参数列表和一个函数体。但与函数不同,lambdas可能定义再函数内部。
lambda表达式具有如下形式:
[capture list](parameter list) -> return type { function body }
capture list(捕获列表)是一个lambda 所在函数中定义的局部变量的列表(通常为空);
return type , parameter list, function body与普通函数一样,分别表示返回类型,参数列表和函数体;
与普通函数不同,lambda必须使用尾置返回类型。即 -> return type ,可以使用decltype关键字来返回类型。
示例:
//空捕获列表表示此lambda 不使用它所在的函数中的任何局部变量。lambda 函数体比较两个参数的size() ,并根据两者的相对大小返回一个布尔值。
[ ](const string &a,const string &b){
return a.size() < b.size();
}
lambda 表达式可以当参数传入算法和函数当中。
// Lambda 表达式作为参数传递给 for_each 算法
for_each(numbers.begin(), numbers.end(), [](int x){cout << x << " ";});
//lambda表达式定义了一个匿名函数,用于输出容器中的每个元素。
19. 右值引用
右值引用就是必须绑定到右值的引用,通过&& 而不是 & 来获得右值引用。显然右值引用有一个重要的性质,只能绑定到一个将要销毁的对象。因此我们可以将一个右值引用的资源赋值到另一个对象中。
左值和右值的区别:
左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中的临时对象。
不能将一个右值引用绑定到一个右值引用类型的变量上:
int &&rr1 = 42; // 正确;字面量是右值
int &&rr2 = rr1; // 错误;表达式 rr1 是左值!
本文到此结束
欢迎关注🔎点赞👍收藏⭐️留言📝