C++11新特性总结

C++11新特性总结

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。

  1. weak_ptr指针通常不单独使用,只能和 shared_ptr 类型指针搭配使用。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。
  2. 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是划时代的版本,目前已经广泛应用,非常有学习的必要。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奥修的灵魂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值