类型推导
C++11引入了auto
和decltype
关键字,使用他们可以在编译期就推导出变量或者表达式的类型,方便开发者编码也简化了代码
-
auto
:让编译器在编译器就推导出变量的类型,可以通过=右边的类型推导出变量的类型1
auto a = 10; // 10是int型,可以自动推导出a是int
-
decltype
:相对于auto
用于推导变量类型,而decltype
则用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算1
2
3cont int &i = 1;
int a = 2;
decltype(i) b = 2; // b是const int&
右值引用
右值引用是 C++11 引入的一个重要特性,它允许程序员有效地处理临时(即将销毁的)对象,并支持移动语义,从而提高性能和资源利用率。右值引用通常与移动语义一起使用,用于优化对象的传递、复制和销毁操作
1 |
|
输出结果
1 | point 2 |
移动构造函数是一种特殊的构造函数,用于实现从一个对象(通常是临时对象或右值)移动数据到另一个对象,而不是进行深拷贝。移动构造函数的存在可以显著提高性能,特别是对于大型资源,如动态分配的内存
移动构造函数通常采用右值引用作为参数,即 Type&&
,其中 Type
是类的类型。在移动构造函数内部,我们可以执行数据指针的转移、资源的所有权转移等操作,以实现高效的移动语义
列表初始化
在C++11中可以直接在变量名后面加上初始化列表来进行对象的初始化
1 | Time::Time(int h, int m) |
封装
C++11 新增了 std::function
& std::bind
& Lambda表达式
std::function
std::function
是一种通用、多态的函数封装。可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等
1 | //代码出自链接:http://www.jellythink.com/archives/771 |
运行结果
1 | 普通函数:10 |
- 转换后的
std::function
对象的参数能转换为可调用实体的参数;可调用实体的返回值能转换为std::function
对象的返回值。 std::function
对象最大的用处就是在实现函数回调,但注意,它不能被用来检查相等或者不相等,但是可以与NULL
或者nullptr
进行比较
std::bind
std::bind
是 C++11 标准引入的函数模板,位于 <functional>
头文件中。它用于创建一个新的可调用对象(函数对象或函数指针),将一个函数与其参数绑定起来,可以实现参数的预绑定、参数重排序以及参数传递等功能
1 |
|
运行结果
1 | Hello, Alice! How are you?, Bye |
其中 placeholders::_1
表示参数被保留为占位符
Lambda 表达式
Lambda 表达式是 C++11 标准引入的一种匿名函数形式,它允许你在代码中内联定义一个简单的函数,无需显式命名。Lambda 表达式通常用于需要一个函数对象(函数符)的地方,例如作为函数参数、STL 算法的谓词、或者用于创建自定义的排序规则
Lambda 表达式的基本语法如下
1 | [capture](parameters) -> return_type { |
capture
是用于捕获外部变量的列表。它可以为空,表示不捕获任何变量,也可以是[&]
(捕获所有变量引用)、[=]
(捕获所有变量拷贝)、[var1, var2]
(捕获特定变量)等形式。parameters
是传递给 Lambda 表达式的参数列表。return_type
是 Lambda 表达式的返回类型。可以省略,编译器会自动推断。
1 |
|
另外 捕获引用[&]
与捕获复制[=]
的区别
1 |
|
这里不同的是,尝试对以值方式(copy captured)捕获的变量进行赋值操作。默认情况下,Lambda 表达式的参数和以值方式捕获的变量都是不可变的,即不能被修改
- 希望在
Lambda 表达式
内部修改这些变量,需要将Lambda 表达式
标记为可变mutable
- 另外已值方式捕获的变量不会影响
main
函数的m
和n
, 因为值捕获创建了变量的拷贝
模板改进
C++11关于模板有一些细节的改进:
- 模板的右尖括号
- 模板的别名
- 函数模板的默认模板参数
模板的右尖括号(Template Right Angle Brackets): 在 C11 之前,当使用嵌套的模板类型时,右尖括号 >>
可能会被解释为右移操作符。C11 引入了对 >>
在模板中的正确解析,以避免这种歧义。这样可以更容易地编写嵌套的模板类型,例如容器嵌套容器。
1 | std::vector<std::vector<int>> matrix; // 在 C++11 及以后版本中正确解析 |
模板的别名(Template Aliases): C++11 引入了模板的别名,可以使用 using
关键字来定义模板别名,从而为模板类型创建简洁易读的名称。这对于复杂的模板类型特别有用
1 | template <typename T> |
函数模板的默认模板参数(Default Template Arguments for Function Templates): C++11 允许在函数模板中为模板参数指定默认值,从而使调用函数模板时可以省略特定的模板参数。这可以简化模板函数的调用,并提高代码的可读性
1 | template <typename T = int> |
并发
有点多,后面单独弄一章单线程
智能指针
智能指针(Smart Pointers)用于管理动态分配的内存资源,有助于避免内存泄漏、多重释放等常见的内存错误
C++11引入了三种主要类型的智能指针:
-
std::shared_ptr: 共享指针,用于多个智能指针共享同一个资源。资源在最后一个
std::shared_ptr
离开其作用域时释放1
2
3
4
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::shared_ptr<int> anotherSharedPtr = sharedPtr; // 共享资源 -
std::unique_ptr: 独占指针,用于唯一拥有一个资源,保证了资源的独占性和自动释放
1
2
3
4
std::unique_ptr<int> uniquePtr = std::make_unique<int>(42);
// std::unique_ptr<int> anotherUniquePtr = uniquePtr; // 错误,无法复制,但可以通过std::move移动 -
std::weak_ptr: 弱指针,用于共享资源的控制,但不会增加资源的引用计数。通常与
std::shared_ptr
一起使用,以防止循环引用1
2
3
4
5
std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared; // 弱指针
std::shared_ptr<int> sharedAgain = weak.lock(); // 尝试获取shared_ptr
基于范围的for循环
1 | vector<int> vec; |
委托构造函数
委托构造函数允许在同一个类中一个构造函数调用另外一个构造函数,可以在变量初始化时简化操作,通过代码来感受下委托构造函数的妙处:
不使用委托构造函数:
1 | struct A { |
使用委托构造函数:
1 | struct A { |
继承构造函数
继承构造函数可以让派生类直接使用基类的构造函数,如果有一个派生类,希望派生类采用和基类一样的构造方式,可以直接使用基类的构造函数,而不是再重新写一遍构造函数,老规矩,看代码:
不使用继承构造函数:
1 | struct Base { |
使用继承构造函数
1 | struct Base { |
只需要使用using Base::Base继承构造函数,就免去了很多重写代码的麻烦
nullptr
nullptr
是c11用来表示空指针新引入的常量值,在c中如果表示空指针语义时建议使用nullptr
而不要使用NULL
,因为NULL本质上是个int型的0,其实不是个指针
1 | void func(void *ptr) { |
final & override
c++11关于继承新增了两个关键字,
final
用于修饰一个类,表示禁止该类进一步派生和虚函数的进一步重载,override
用于修饰派生类中的成员函数,标明该函数重写了基类函数,如果一个函数声明了override
但父类却没有这个虚函数,编译报错
示例代码1:
1 | struct Base { |
示例代码2:
1 | struct Base final { |
default
c++11引入default特性,多数时候用于声明构造函数为默认构造函数,如果类中有了自定义的构造函数,编译器就不会隐式生成默认构造函数
1 | struct A { |
上面代码编译出错,因为没有匹配的构造函数,因为编译器没有生成默认构造函数,而通过default,程序员只需在函数声明后加上“=default;”,就可将该函数声明为 defaulted
函数,编译器将为显式声明的 defaulted
函数自动生成函数体
1 | struct A { |
delete
c++中,如果开发人员没有定义特殊成员函数,那么编译器在需要特殊成员函数时候会隐式自动生成一个默认的特殊成员函数,例如拷贝构造函数或者拷贝赋值操作符,如下代码:
1 | struct A { |
有时候想禁止对象的拷贝与赋值,可以使用delete修饰,如下
1 | struct A { |
delele
函数在c++11中很常用,std::unique_ptr
就是通过delete
修饰来禁止对象的拷贝的
explicit
explicit专用于修饰构造函数,表示只能显式构造,不可以被隐式转换,根据代码看explicit的作用:
不用explicit:
1 | struct A { |
使用 explicit
1 | struct A { |
constexpr
首先说一下 const
-
const的修饰的变量不可更改
1
const int value = 5;
-
指针使用const,从右向左读,即可知道const究竟修饰的是指针还是指针所指向的内容
1
2char *const ptr; // 指针本身是常量
const char* ptr; // 指针指向的变量为常量 -
在函数参数中使用const,一般会传递类对象时会传递一个const的引用或者指针,这样可以避免对象的拷贝,也可以防止对象被修改
1
2class A{};
void func(const A& a); -
const修饰类的成员变量,表示是成员常量,不能被修改,可以在初始化列表中被赋值
1
2
3
4
5
6
7class A {
const int value = 5;
};
class B {
const int value;
B(int v) : value(v){}
}; -
修饰类成员函数,表示在该函数内不可以修改该类的成员变量
1
2
3class A{
void func() const;
}; -
修饰类对象,类对象只能调用该对象的const成员函数
1
2
3
4
5class A {
void func() const;
};
const A a;
a.func();
const
只表示read only的语义,只保证了运行时不可以被修改,但它修饰的仍然有可能是个动态变量
constexpr
修饰的才是真正的常量,它会在编译期间就会被计算出来,整个运行过程中都不可以被改变,constexpr
可以用于修饰函数,这个函数的返回值会尽可能在编译期间被计算出来当作一个常量,但是如果编译期间此函数不能被计算出来,那它就会当作一个普通函数被处理
1 |
|
enum class
不带作用域的枚举代码:
1 | enum AColor { |
不带作用域的枚举类型可以自动转换成整形,且不同的枚举可以相互比较,代码中的红色居然可以和白色比较,这都是潜在的难以调试的bug,而这种完全可以通过有作用域的枚举来规避
有作用域的枚举代码:
1 | enum class AColor { |
使用带有作用域的枚举类型后,对不同的枚举进行比较会导致编译失败,消除潜在bug,同时带作用域的枚举类型可以选择底层类型,默认是int,可以改成char等别的类型。
1 | enum class AColor : char { |
平时编程过程中使用枚举,一定要使用有作用域的枚举取代传统的枚举
非受限联合体
首先说明一下 POD类型
在C中,POD(Plain Old Data)类型没有用户自定义的构造函数、析构函数或虚函数,并且可以通过内存拷贝进行操作。C11引入了更严格的定义,将POD类型划分为三种:POD、POD Trivial、POD标准布局。这些类型通常适用于与C语言库交互、内存布局和二进制数据处理等情况
-
基本数据类型: 整数类型(如
int
、char
、long
)、浮点类型(如float
、double
)等是POD类型1
2int x = 42;
float y = 3.14; -
结构体和类: 简单的结构体和类,只包含POD类型的成员且没有自定义构造函数、析构函数和虚函数,也可以是POD类型
1
2
3
4
5
6
7
8
9
10struct Point {
int x;
int y;
};
class Rectangle {
public:
int width;
int height;
}; -
C数组: C风格的数组也是POD类型
1
int array[5] = {1, 2, 3, 4, 5};
std::vector
、std::array
和动态内存分配 就不是 -
联合体: 简单的联合体也可以是POD类型,但要注意成员之间的内存布局
1
2
3
4union Data {
int intValue;
float floatValue;
}; -
枚举: 枚举类型在某些情况下可以被视为POD类型,但也可能受到枚举的底层类型和使用方式的影响
1
enum Color { Red, Green, Blue };
比如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27//1 包含成员函数
enum class Color {
Red,
Green,
Blue
// 无法包含成员函数,否则不是POD类型
// void print() {}
};
//2. 包含虚函数
enum class Shape {
Circle,
Square
// 无法包含虚函数,否则不是POD类型
// virtual void draw() {}
};
//3. 继承
class Base {
public:
int x;
};
enum class Derived : int, Base { // 不是POD类型,因为继承了Base类
A,
B
};
大体上可以理解为对象可以直接memcpy
的类型
c11之前union中数据成员的类型不允许有非POD类型,而这个限制在c11被取消,允许数据成员类型有非POD类型
1 | struct A { |
sizeof
c++11中sizeof
可以用的类的数据成员上,看代码:
c++11前:
1 | struct A { |
c++11后:
1 | struct A { |
想知道类中数据成员的大小在c++11中是不是方便了许多,而不需要定义一个对象,在计算对象的成员大小
assertion
c++11引入static_assert
声明,在编译期间检查,如果第一个参数值为false,则打印message
,编译失败。
1 | static_assert(true/false, message); |
自定义字面量
c11可以自定义字面量,平时c中都或多或少使用过chrono
中的时间,例如:
1 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 100ms |
其实没必要这么麻烦,也可以这么写:
1 | std::this_thread::sleep_for(100ms); // c++14里可以这么使用,这里只是举个自定义字面量使用的例子 |
这就是自定义字面量的使用,示例如下:
1 | struct mytype { |
基础数值类型
c++11新增了几种数据类型:long long
、char16_t
、char32_t
等
long long
: 这是一种整数类型,用于表示更大范围的整数值。在某些平台上,long long
的范围要比传统的 int
或 long
类型更大,长度至少具有64位。
1 | long long bigNumber = 123456789012345LL; // 后缀 LL 表示 long long 类型 |
char16_t
和 char32_t
: 这些是字符类型,用于表示更宽字符集的字符。在国际化和 Unicode
支持方面,它们非常有用,可以用来存储更多种类的字符
1 | char16_t unicodeChar16 = u'\u03A9'; // 使用 u 前缀表示 char16_t 类型 至少具有16位 |
计算实际长度
1 |
|
不同的编译器和平台可能会有不同的结果
正则表达式
c++11引入了regex库更好的支持正则表达式
1 |
|
chrono
c++11关于时间引入了chrono
库,用于提供时间处理和时钟操作的支持
duration
用于表示时间间隔,例如秒、毫秒、微秒等time_point
用于表示特定时间点clocks
于获取时间信息
1 |
|