C++11新特性总结
文章目录
- C++11新特性总结
- 1.C++11简介
- 2.1 微小但重要的语法提升
- 2.2 以auto完成自动类型推导
- 2.3 一致性初始化(Uniform Initialization)
- 2.4 初值列表(Initializer List)
- 2.5 for循环(Range-Based )
- 2.6 Move语义和Rvalue Reference
- 2.7 新式的字符串字面常量(String Literal)
- 2.8 关键字noexcept
- 2.9 关键字constexpr
- 2.10 std::chrono时间库
- 2.11 正则表达式std::regex
- 2.12 可变参数模板(variadic template)
- 2.13 lambda表达式
- 2.14 关键字decltype
- 2.15 新的函数声明语法
- 2.16 带领域的(Scoped)Enum
- 2.17 新的基础类型
- 2.18 =default,=delete
- 2.19 Override
- 2.20 Final
- 2.21 模板别名(Alias Template)
- 2.22 std::array
- 2.23 std::forward_list
- 2.24 std::unordered_map
- 2.25 std::unordered_set
- 2.26 tuple多元数组
- 2.27 std::unique_ptr
- 2.28 std::shared_ptr
- 2.29 std::weak_ptr
- 2.30 std::thread
- 2.31 std::atomic<XXX>
- 2.32 std::condition_variable
- 3.总结
1.C++11简介
C++11引入了许多新特性,如auto关键字实现自动类型推导,nullptr代替NULL,智能指针如unique_ptr和shared_ptr更安全地管理内存,lambda表达式简化函数对象的书写,委托构造函数和默认删除函数增强了类的构造与析构控制,移动语义提高了效率,变长参数模板variadic templates支持可变数量的参数类型。这些特性大大提高了C++的易用性和性能,并使得代码更简洁、安全、高效。
2.1 微小但重要的语法提升
在两个Template表达式的闭符之间放一个空格的要求已经过时了。
#include <iostream>
#include <cwchar>
#include <vector>
#include <list>
int main()
{
std::vector<std::list<int> > nameList; // 早期版本要求的格式
std::vector<std::list<int>> idList; // 从C++11版本开始不需要添加空格
return 0;
}
2.2 以auto完成自动类型推导
C++11允许你声明一个变量或者对象(object)而不需要指明其类型,只需要说它是auto。
auto i = 42;
auto ret = function();
auto index; // 错误: 没有初值无法自动推导类型
static auto vat = 0.19;
std::vector<std::string> vec;
auto pos = vec.begin();
auto m = [] (int x) -> bool {
std::cout << "hello world!" << std::endl;
return true;
};
2.3 一致性初始化(Uniform Initialization)
在C++11之前,程序员,特别是初学者,很容易被这个问题混淆: 如何初始化一个变量或对象。初始化可因为小括号、大括号或赋值操作符的出现而发生。为此C++11引入了一致性初始化概念,意思是面对任何初始化动作,你可以使用相同的语法,也就是大括号。
int i; // i未被初始化
int j{}; // j被初始化未0
int* p; // p未被初始化
int* q{}; // q被初始化未 nullptr
2.4 初值列表(Initializer List)
int values[]{1, 2, 3};
std::vector<int> v{2, 3, 4, 5, 6, 7, 8, 9};
std::vector<std::string> cities{"Berlin", "New York", "London", "Braunschweig", "Cairo", "Cologne"};
2.5 for循环(Range-Based )
C++11引入了一种崭新的for循环形式,可以逐一迭代某个给定的区间、数组、集合内的每一个元素,其他编程语言可能称为foreach循环,其一般语法如下:
for ( decl: coll) { statement }
for (int i : {1, 2, 3, 4, 5, 6, 7, 8, 9})
{
std::cout << i << std::endl;
}
// 使用应用方式访问元素避免拷贝
std::vector<int> vec {10,20,30,70,90, 102, 104};
for (auto& item: vec )
{
item *= 3;
}
2.6 Move语义和Rvalue Reference
C++11的一个重要的特性就是,支持move(迁移语义)。这项特性更进一步进入C++主要设计目标内,用以避免非必要的拷贝(copy)和临时对象(temporary)
class Example
{
public:
Example(const Example& lvalue); // 拷贝构造函数
Example(Example&& lvalue); // move构造函数
Example& operator= (const Example& lvalue); // 赋值操作符
Example& operator= (Example&& rvalue); // move操作符
};
2.7 新式的字符串字面常量(String Literal)
原始字符串字面量允许你定义包含特殊字符(如换行符、制表符等)的字符串,而无需使用转义序列。这在处理正则表达式、HTML 片段或其他需要保留原始格式的内容时特别有用。原始字符串字面量使用前缀 R"(大写或小写的 ‘R’)来定义,后面跟着一对括号,括号内可以包含最多 16 个字符,用于定义分隔符。分隔符可以是任何非字母数字字符序列,用于包围原始字符串的内容。
const char* rawString = R"(This is a raw string
that can span multiple lines
without using escape sequences.)";
2.8 关键字noexcept
C++11提供了关键字noexcept,用来指明某个函数无法或不打算抛出异常。
void swap (Type& x, Type& y) noexcept (noexcept(x.swap(y)))
{
x.swap(y);
}
2.9 关键字constexpr
自C++11起,constexpr可用于让表达式核定于编译期。
constexpr int square(int x)
{
return x * x;
}
float a[(square(9)];
2.10 std::chrono时间库
chrono是一个time library, 源于boost,现在已经是C++标准。
std::chrono::duration<double> duration //时间间隔
std::this_thread::sleep_for(duration); //sleep
LOG(INFO) << "duration is " << duration.count() << std::endl;
std::chrono::microseconds(10) //微秒
std::chrono::seconds(100) //秒
end = std::chrono::system_clock::now(); //获取当前时间
2.11 正则表达式std::regex
C++ 标准库中常用正则表达式类有五个类std::regex_match、std::regex_search、 std::regex_replace、std::regex_iterator和std::regex_traits.
// 简单正则表达式匹配
std::string fnames[] = {"foo.txt", "bar.txt", "baz.dat", "zoidberg"};
std::regex txt_regex("[a-z]+\\.txt");
for (const auto &fname : fnames)
{
std::cout << fname << ": " << std::regex_match(fname, txt_regex) << '\n';
}
// 提取子匹配
std::regex base_regex("([a-z]+)\\.txt");
std::smatch base_match;
for (const auto &fname : fnames)
{
if (std::regex_match(fname, base_match, base_regex))
{
//首个 sub_match 是整个字符串;下个sub_match 是首个有括号表达式。
if (base_match.size() == 2)
{
std::ssub_match base_sub_match = base_match[1];
std::string base = base_sub_match.str();
}
}
}
2.12 可变参数模板(variadic template)
在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变得更加强大,而由此也带来了许多神奇的用法。
#include <iostream>
template <typename T>
T my_max(T value) {
return value;
}
template <typename T, typename... Types>
T my_max(T value, Types... args) {
return std::max(value, my_max(args...));
}
int main(int argc, char *argv[]) {
std::cout << my_max(1, 5, 8, 4, 6) << std::endl;
return 0;
}
2.13 lambda表达式
所谓Lambda是一份功能定义式,可被定义于语句或表达式内部。因此你可以拿lambda当作inline函数使用。
// 泛型 lambda , operator() 是有二个形参的模板
auto glambda = [](auto a, auto&& b) { return a < b; };
bool b = glambda(3, 3.14); // ok
// 泛型 lambda , operator() 是有一个形参的模板
auto vglambda = [](auto printer)
{
return [=](auto&&... ts) // 泛型 lambda , ts 是形参包
{
printer(std::forward<decltype(ts)>(ts)...);
return [=] { printer(ts...); }; // 空型 lambda (不接收参数)
};
};
auto p = vglambda([](auto v1, auto v2, auto v3)
{
std::cout << v1 << v2 << v3;
});
auto q = p(1, 'a', 3.14); // 输出 1a3.14
q(); // 输出 1a3.14
2.14 关键字decltype
decltype 是 C++11 引入的关键字,用于获取表达式的类型。当我们使用 decltype 时,它会根据提供的表达式来推导出一个类型。这个类型可以是变量、函数、表达式或任何合法的 C++ 实体。语法结构如下:
decltype(expression)
decltype(&HP_StartStorage) StartRecord = nullptr;
decltype(&HP_StopStorage) StopRecord = nullptr;
decltype(&HP_GetLastClipVideoFilePath) GetClipPath = nullptr;
decltype(&HP_GetLastStorageVideoFilePath) GetRecordPath = nullptr;
decltype(&HP_DrawGraph) DrawGraph = nullptr;
decltype(&HP_ModGraph) ModifyGraph = nullptr;
decltype(&HP_GetGraphNum) GetGraphCount = nullptr;
decltype(&HP_RenderPrivateData) SetPrivateDataVisible = nullptr;
decltype(&HP_ControlPrivateSubData) SetPrivateSubDataVisible = nullptr;
decltype(&HP_OpenRenderPrivateData) OpenRenderPrivateData = nullptr;
decltype(&HP_CloseRenderPrivateData) CloseRenderPrivateData = nullptr;
decltype(&HP_SetNetWorkPerfor_V20) SetNetworkPerformance = nullptr;
decltype(&HP_AddToSyncGroup) AddToSyncGroup = nullptr;
decltype(&HP_QuitSyncGroup) DeleteFromSyncGroup = nullptr;
decltype 的工作原理是对表达式进行分析,然后返回该表达式的类型,而不会对表达式进行求值。这意味着 decltype 在编译期间进行类型推导,而不会运行时产生任何额外的开销。
2.15 新的函数声明语法
在C++11中,你可以将一个函数的返回类型转而声明于参数列之后。
template <typename T1, typename T2>
decltype(x+y) add(T1 x, T2 y);
template <typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x+y);
2.16 带领域的(Scoped)Enum
和类类似,枚举定义了一种新的自定义类型,其将一组整形常量组织在一起。枚举属于字面值常量类型。根据作用域区分,枚举分为限定作用域(C++11引入)和不限定作用域两种
//限定作用域
enum class DAY
{
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
};
//限定作用域
enum class DAY : uint8_t
{
MONDAY, TUESDAY, WEDNESDAY, THURSDAY,FRIDAY, SATURDAY, SUNDAY
};
2.17 新的基础类型
2.17.1 long long
long long 64位有符号整数类型,对应的,unsigned long long 为无符号64位整数类型。c++ 标准为 long long 和unsigned long long 定义了两种字面量后缀,分别为 LL, ULL。字面量后缀在某些场景下需用到才能使代码逻辑准确。
long long t_lData1 = 65536 << 16;//左移16位
std::cout<<"t_lData1="<<t_lData1<<std::endl;//t_lData1值为0,因为默认65536被当作32位整型数据操作,
long long t_lData2 = 65536LL <<16;
std::cout<<"t_lData2="<<t_lData2<<std::endl;
2.17.1 char16_t & char32_t
c++11 新增:char16_t 和char32_t 两种字符类型,char16_t占2字节,char32_t 占4字节,分别用来对应Unicode字符集的UTF-16 和UTF-32。当前,Unicode字符集有UTF-8 ,UTF-16和UTF32这三种编码方法,三者区别是使用不同的内存空间进行编码。具体编码规则不在这里赘述。除此之外,c++11为三种编码提供了新前缀用于声明3种编码字符和字符串字面量,分别是:UTF-8使用前缀u8,UTF-16使用前缀u,UTF-32使用前缀U。
char8_t utf8[] = u8"你好";
char16_t utf16 = u'好';
char32_t utf32[] = U"你好";
2.17.1 char8_t
c++20 引入:char8_t字符类型,可以代替char作为UTF-8的字符类型。char8_t具有和unsigned char相同的符号属性、存储大小、对齐方式以及整数转换等级。
2.18 =default,=delete
default的默认构造方式可生成:默认无参数的构造函数、拷贝构造函数、赋值构造函数。析构函数同样可以由default默认构造。 delete关键字禁止拷贝构造、禁止赋值构造、禁止自定义参数的构造函数。注意析构函数不可由delete修饰。
class noncopyable
{
protected:
constexpr noncopyable() = default;
~noncopyable() = default;
noncopyable(const noncopyable &) = delete;
noncopyable &operator= (const noncopyable &) = delete;
};
2.19 Override
在成员函数声明或定义中, override 确保该函数为虚并覆写来自基类的虚函数。
class A
{
virtual void foo();
void bar();
};
class B : public A
{
void foo() const override; // 错误: B::foo 不覆写 A::foo
// (签名不匹配)
void foo() override; // OK : B::foo 覆写 A::foo
void bar() override; // 错误: A::bar 非虚
};
2.20 Final
指定派生类不能覆写虚函数,或类不能被继承。
class Base
{
virtual void foo();
};
class A : public Base
{
void foo() final; // A::foo 被覆写且是最终覆写
void bar() final; // 错误:非虚函数不能被覆写或是 final
};
class B final : public A // struct B 为 final
{
void foo() override; // 错误: foo 不能被覆写,因为它在 A 中是 final
};
class C : public B // 错误: B 为 final
{
};
2.21 模板别名(Alias Template)
自C++11起开始支持模板别名,然而由于关键字typename用于此处时总是出于某种原因而失败,所以这里引入using,并因此引入一个新术语Alias Template。
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>;
Vec<int> coll; // 等价于 std::vector<int, MyAlloc<int>> coll;
2.22 std::array
此容器是一个聚合类型,其语义等同于保有一个 C 风格数组 T[N] 作为其唯一非静态数据成员的结构体。不同于 C 风格数组,它不会自动退化成 T* 。作为聚合类型,它能聚合初始化,只要有至多 N 个能转换成 T 的初始化器: std::array<int, 3> a = {1,2,3}; 该结构体结合了 C 风格数组的性能和可访问性和容器的优点,譬如知晓其大小、支持赋值、随机访问等。
std::array<int, 10> arr = {1,2,3,4,5,6,7,8,9,0};
std::for_each(arr.begin(), arr.end(), [](int &i){i++;});
for(auto i : arr){std::cout << i << " ";}
2.23 std::forward_list
forward_list 容器以单链表的形式存储元素。forward_list 的模板定义在头文件 forward_list 中。fdrward_list 和 list 最主要的区别是:它不能反向遍历元素;只能从头到尾遍历。
std::forward_list<std::string> my_words {"three", "six", "eight"};
auto count = std::distance(std::begin(my_words),std::end(my_words));
// Result is 3
std::forward_list<int> data {10, 21, 43, 87, 175, 351};
auto iter = std::begin(data);
size_t n {3};
std::advance(iter, n);
std::cout << "The " << n+1 << "th element is n << *iter << std::endl;
2.24 std::unordered_map
unordered_map 是关联容器,含有带唯一键的键-值 pair 。搜索、插入和元素移除拥有平均常数时间复杂度。
元素在内部不以任何特定顺序排序,而是组织进桶中。元素放进哪个桶完全依赖于其键的哈希。这允许对单独元素的快速访问,因为一旦计算哈希,则它准确指代元素所放进的桶。
// 创建三个 string 的 unordered_map (映射到 string )
std::unordered_map<std::string, std::string> u = {
{"RED","#FF0000"},
{"GREEN","#00FF00"},
{"BLUE","#0000FF"}
};
// 迭代并打印 unordered_map 的关键和值
for( const auto& n : u ) {
std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n";
}
// 添加新入口到 unordered_map
u["BLACK"] = "#000000";
u["WHITE"] = "#FFFFFF";
// 用关键输出值
std::cout << "The HEX of color RED is:[" << u["RED"] << "]\n";
std::cout << "The HEX of color BLACK is:[" << u["BLACK"] << "]\n";
2.25 std::unordered_set
unordered_set is 是含有 Key 类型唯一对象集合的关联容器。搜索、插入和移除拥有平均常数时间复杂度。在内部,元素并不以任何特别顺序排序,而是组织进桶中。元素被放进哪个桶完全依赖其值的哈希。这允许对单独元素的快速访问,因为哈希一旦,就准确指代元素被放入的桶。
std::unordered_set<string> things {16}; // 16 buckets
std::unordered_set<string> words {"one", "two", "three", "four"};// Initializer list
std::unordered_set<string> some_words {++std::begin(words), std::end (words)}; // Range
std::unordered_set<string> copy_wrds {words}; // Copy constructor
std::unordered_set<Name, Hash_Name> names {8, Hash_Name()};//8 buckets & hash function
2.26 tuple多元数组
tuple是C++ 11新的标准库之一,其表示N元数组,它相当于有N个成员的结构体,只不过这个结构体的成员都是匿名的。tuple是类似于pair的模板,tuple像是pair的泛化版本,pair只能存放两个成员,而tuple则可以多个成员,相同的是,pair和tuple都允许其成员的类型不一样。
std::tuple<double, char, std::string> get_student(int *id*)
{
if (id == 0) return std::make_tuple(3.8, 'A', "Lisa Simpson");
if (id == 1) return std::make_tuple(2.9, 'C', "Milhouse Van Houten");
if (id == 2) return std::make_tuple(1.7, 'D', "Ralph Wiggum");
throw std::invalid_argument("id");
}
auto student0 = get_student(0);
std::cout << "ID: 0, " << "GPA: " << std::get<0>(student0) << ", " << "grade: "
<< std::get<1>(student0) << ", " << "name: " << std::get<2>(student0) << '\n';
double gpa1;
char grade1;
std::string name1;
std::tie(gpa1, grade1, name1) = get_student(1);
std::cout << "ID: 1, " << "GPA: " << gpa1 << ", "
<< "grade: " << grade1 << ", " << "name: " << name1 << '\n';
2.27 std::unique_ptr
std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。
//创建一个指向int的空指针
std::unique_ptr<int> fPtr1;
std::unique_ptr<int> fPtr2(new int(4));
auto fPtr3 = std::make_unique<int>();
//fPtr2释放指向对象的所有权,并且被置为nullptr
std::cout << "fPtr2 release before:" << fPtr2.get() << std::endl;
int *pF = fPtr2.release();
std::cout << "fPtr2 release before:" << fPtr2.get() << " and pF value:" << *pF << std::endl;
//所有权转移,转移后fPtr3变为空指针
std::cout << "move before fPtr1 address:" << fPtr1.get() << " fPtr3 address:" << fPtr3.get() << std::endl;
fPtr1 = std::move(fPtr3);
std::cout << "move after fPtr1 address:" << fPtr1.get() << " fPtr3 address:" << fPtr3.get() << std::endl;
std::cout << "move before fPtr1 address:" << fPtr1.get() << std::endl;
fPtr1.reset();
std::cout << "move after fPtr1 address:" << fPtr1.get() << std::endl;
2.28 std::shared_ptr
std::shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。
class test
{
int num;
string name;
};
test* pTest = new test();
std::shared_ptr<test> ptr_test = std::shared_ptr<test>(pTest); //普通指针转shared_ptr
std::shared_ptr<test> ptr_test2 = std::make_shared<test>();
test* pTest2 = ptr_test2.get(); //shared_ptr转普通指针
2.29 std::weak_ptr
weak_ptr是一种用于解决shared_ptr相互引用时产生死锁问题的智能指针。如果有两个shared_ptr相互引用,那么这两个shared_ptr指针的引用计数永远不会下降为0,资源永远不会释放。weak_ptr是对对象的一种弱引用,它不会增加对象的use_count,weak_ptr和shared_ptr可以相互转化,shared_ptr可以直接赋值给weak_ptr,weak_ptr也可以通过调用lock函数来获得shared_ptr。
- weak_ptr指针通常不单独使用,只能和 shared_ptr 类型指针搭配使用。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。
- weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class B;
class A
{
public:
weak_ptr<B> pb_weak;
~A()
{
cout << "A delete\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout << "B delete\n";
}
void print() {
cout << "This is B" << endl;
}
};
void fun() {
shared_ptr<B> pb(new B());
cout << "pb.use_count " << pb.use_count() << endl;//1
shared_ptr<A> pa(new A());
cout << "pa.use_count " << pa.use_count() << endl;//1
pb->pa_ = pa;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
pa->pb_weak = pb;
cout << "pb.use_count " << pb.use_count() << endl;//1:弱引用不会增加所指资源的引用计数use_count()的值
cout << "pa.use_count " << pa.use_count() << endl;//2
shared_ptr<B> p = pa->pb_weak.lock();
p->print();//不能通过weak_ptr直接访问对象的方法,须先转化为shared_ptr
cout << "pb.use_count " << pb.use_count() << endl;//2
cout << "pa.use_count " << pa.use_count() << endl;//2
}//函数结束时,调用A和B的析构函数
//资源B的引用计数一直就只有1,当pb析构时,B的计数减一,变为0,B得到释放,
//B释放的同时也会使A的计数减一,同时pa自己析构时也会使资源A的计数减一,那么A的计数为0,A得到释放。
int main()
{
fun();
system("pause");
return 0;
}
2.30 std::thread
类 thread 表示单个执行线程。线程允许多个函数并发执行。这是C++11引入的重大特性,从此以后C++在跨平台使用多线程时不在需要直接调用系统API来实现多线程。
void threadfun1()
{
std::cout << "threadfun1 - 1\r\n" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "threadfun1 - 2" << std::endl;
}
void threadfun2(int *iParam*, std::string *sParam*)
{
std::cout << "threadfun2 - 1" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "threadfun2 - 2" << std::endl;
}
std::thread t1(threadfun1);
std::thread t2(threadfun2, 10, "abc");
t1.join();
std::cout << "join" << std::endl;
t2.detach();
2.31 std::atomic
用于多线程资源互斥操作,属c++11重大提升,多线程原子操作简单了许多。
std::atomic<bool> readyFlag(false);
void threadBody1()
{
readyFlag.store(true);
}
void threadBody2()
{
while (!readyFlag.load())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
2.32 std::condition_variable
condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。
std::mutex m; std::condition_variable cv; std::string data;
bool ready = false; bool processed = false;
void worker_thread()
{
// 等待直至 main() 发送数据
std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{return ready;});// 等待后,我们占有锁。
std::cout << "Worker thread is processing data\n"; data
+= " after processing"; // 发送数据回 main()
processed = true; std::cout << "Worker thread signals data processing completed\n";
lk.unlock();
cv.notify_one();// 通知前完成手动解锁,以避免等待线程才被唤醒就阻塞(细节见 notify_one )
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";// 发送数据到 worker 线程
{
std::lock_guard<std::mutex> lk(m); ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
{ // 等候 worker
std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
3.总结
C++11特性还有很多没有全部介绍,本文只是挑了部分比较常用的特性介绍,想详细了解C++11的特性可上C++标准网址查看。C++11是划时代的版本,目前已经广泛应用,非常有学习的必要。