c++11新特性
目录
1、关键字及新语法
1.1、auto 关键字及用法
1.2、nullptr 关键字及用法
1.3、for 循环语法
2、STL容器
2.1、std::array
2.2、std::forward_list
2.3、std::unordered_map
2.4、std::unordered_set
3、多线程
3.1、std::thread
3.2、st::atomic
3.3、std::condition_variable
4、智能指针内存管理
4.1、std::shared_ptr
4.2、std::weak_ptr
5、其他
5.1、std::function、std::bind封装可执行对象
5.2、lamda表达式
1、关键字及新语法
1.1、auto关键字及用法
A、auto关键字能做什么? 编译器根据上下文情况,确定auto变量的真正类型。
auto AddTest(int a, int b)
{
return a + b;
}
int main()
{
auto index = 10;
auto str = "abc";
auto ret = AddTest(1,2);
std::cout << "index:" << index << std::endl;
std::cout << "str:" << str << std::endl;
std::cout << "res:" << ret << std::endl;
}
// auto在C++14中可以作为函数的返回值
B、auto作为函数返回值时,只能用于定义函数,不能用于声明函数。
// 声明函数
#pragma once
class Test
{
public:
auto TestWork(int a ,int b);
};
// 定义函数
#pragma once
class Test
{
public:
auto TestWork(int a, int b)
{
return a + b;
}
};
1.2、nullptr关键字及用法
class Test
{
public:
void TestWork(int index)
{
std::cout << "TestWork 1" << std::endl;
}
void TestWork(int * index)
{
std::cout << "TestWork 2" << std::endl;
}
};
int main()
{
Test test;
test.TestWork(NULL); // NULL被翻译成0 >>> TestWork 1
test.TestWork(nullptr); // nullptr确定为 指针 >>> TestWork 2
}
1.3 范围for 配合auto
int main()
{
int numbers[] = { 1,2,3,4,5 };
std::cout << "numbers:" << std::endl;
for (auto number : numbers)
{
std::cout << number << std::endl;
}
}
2、STL容器
2.1、std::array 跟数组并没有太大区别 增加了迭代器等函数
#include <array>
int main()
{
std::array<int, 4> arrayDemo = { 1,2,3,4 };
std::cout << "arrayDemo:" << std::endl;
for (auto itor : arrayDemo)
{
std::cout << itor << std::endl;
}
int arrayDemoSize = sizeof(arrayDemo);
std::cout << "arrayDemo size:" << arrayDemoSize << std::endl; // 4*4 =16
return 0;
}
2.2、std::forward_list 新增的线性表,与list区别在于它是单向链表
链表在对数据进行插入和删除是比顺序存储的线性表有优势,
因此在插入和删除操作频繁的应用场景中,
使用list和forward_list比使用array、vector和deque效率要高很多。
#include <forward_list>
int main()
{
std::forward_list<int> numbers = {1,2,3,4,5,4,4};
std::cout << "numbers:" << std::endl;
for (auto number : numbers)
{
std::cout << number << std::endl;
}
numbers.remove(4);
std::cout << "numbers after remove:" << std::endl;
for (auto number : numbers)
{
std::cout << number << std::endl;
}
return 0;
}
2.3、std::unordered_map 哈希map 无序map
std::unordered_map与std::map用法基本差不多,但STL在内部实现上有很大不同,
std::map使用的数据结构为二叉树,
而std::unordered_map内部是哈希表的实现方式,哈希map理论上查找效率为O(1)。
但在存储效率上,哈希map需要增加哈希表的内存开销。
#include <iostream>
#include <string>
#include <unordered_map>
int main()
{
std::unordered_map<std::string, std::string> mymap =
{
{ "house","maison" },
{ "apple","pomme" },
{ "tree","arbre" },
{ "book","livre" },
{ "door","porte" },
{ "grapefruit","pamplemousse" }
};
unsigned n = mymap.bucket_count();// 使用的内存桶数量
std::cout << "mymap has " << n << " buckets.\n";
for (unsigned i = 0; i<n; ++i) // 遍历每一个桶,每个桶内可能存在多个数据
{
std::cout << "bucket #" << i << " contains: ";
for (auto it = mymap.begin(i); it != mymap.end(i); ++it)// 遍历每个桶内存储的数据
std::cout << "[" << it->first << ":" << it->second << "] ";
std::cout << "\n";
}
return 0;
}
2.4、std::unordered_set 无序集合
std::unordered_set的数据存储结构也是哈希表的方式结构,
除此之外,std::unordered_set在插入时不会自动排序,这都是std::set表现不同的地方。
#include <iostream>
#include <string>
#include <unordered_set>
#include <set>
int main()
{
std::unordered_set<int> unorder_set;
unorder_set.insert(7);
unorder_set.insert(5);
unorder_set.insert(3);
unorder_set.insert(4);
unorder_set.insert(6);
std::cout << "unorder_set:" << std::endl;
for (auto itor : unorder_set)
{
std::cout << itor << std::endl; // 无序,不会排序,按插入的循序打印 7,5,3,4,6
}
std::set<int> set; // 有序,在插入时会自动排序
set.insert(7);
set.insert(5);
set.insert(3);
set.insert(4);
set.insert(6);
std::cout << "set:" << std::endl;
for (auto itor : set)
{
std::cout << itor << std::endl;// 有序3,4,5,6,7
}
}
3、多线程
C++11中,引入了boost库中的多线程部分内容,形成C++标准,
形成标准后的boost多线程编程部分接口基本没有变化,
这样方便了以前使用boost接口开发的使用者切换使用C++标准接口,
把容易把boost接口升级为C++接口。
3.1、std::thread
std::thread为C++11的线程类,使用方法和boost接口一样,非常方便,
同时,C++11的std::thread解决了boost::thread中构成参数限制的问题。
#include <thread>
void threadfun1()
{
std::cout << "threadfun1 - 1\r\n" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));// 本线程休息1s
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));// 本线程休息5s
std::cout << "threadfun2 - 2" << std::endl;
}
int main()
{
std::thread t1(threadfun1);// 线程绑定 需要执行的函数 打印 threadfun1 - 1
std::thread t2(threadfun2, 10, "abc"); // 打印 threadfun2 - 1
t1.join();// t1.join()会等待t1线程退出后才继续往下执行
// 打印 threadfun1 - 2
// 往下执行
std::cout << "join" << std::endl;
t2.detach(); // 执行的线程从线程对象中被分离,已不再被一个线程对象所表达,可以
std::cout << "detach" << std::endl; // threadfun2 - 2 没有输出
// detach字符输出后,主函数退出,threadfun2还未执行完成,但是在主线程退出后,t2的线程也被已经被强退出
}
3.2、std::atomic 原子数据类型。
从功能上看,简单地说,原子数据类型不会发生数据竞争,
能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。
从实现上,可以理解为这些原子类型内部自己加了锁。
// 使用10个线程,把std::atomic_int类型的变量iCount从100减到1。
#include <thread>
#include <atomic>
#include <stdio.h>
std::atomic_bool bIsReady = false;
std::atomic_int iCount = 100;
void threadfun1()
{
if (!bIsReady) {
std::this_thread::yield();
}
while (iCount > 0)
{
printf("iCount:%d\r\n", iCount--);
}
}
int main()
{
std::atomic_bool b;
std::list<std::thread> lstThread;//线程列表 10个线程
for (int i = 0; i < 10; ++i)
{
lstThread.push_back(std::thread(threadfun1));
}
for (auto& th : lstThread)
{
th.join();
}
}
3.3、std::condition_variable
C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一样,
可以让线程休眠,直到别唤醒,现在在从新执行。
线程等待在多线程编程中使用非常频繁,经常需要等待一些异步执行的条件的返回结果。
// webset address: http://www.cplusplus.com/reference/condition_variable/condition_variable/%20condition_variable
// condition_variable example
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lock<std::mutex> lck(mtx);
while (!ready) cv.wait(lck);
// ...
std::cout << "thread " << id << '\n';
}
void go() {
std::unique_lock<std::mutex> lck(mtx);
ready = true;
cv.notify_all();
}
int main()
{
std::thread threads[10];
// spawn 10 threads:
for (int i = 0; i<10; ++i)
threads[i] = std::thread(print_id, i);
std::cout << "10 threads ready to race...\n";
go(); // go!
for (auto& th : threads) th.join();
return 0;
}
// 在14行中调用cv.wait(lck)的时候,线程将进入休眠,在调用33行的go函数之前,
// 10个线程都处于休眠状态,当22行的cv.notify_all()运行后,14行的休眠将结束,继续往下运行
4、智能指针内存管理
在内存管理方面,C++11的std::auto_ptr基础上,移植了boost库中的智能指针的部分实现,
如std::shared_ptr、std::weak_ptr等,当然,想boost::thread一样,
C++11也修复了boost::make_shared中构造参数的限制问题。
把智能指针添加为标准,个人觉得真的非常方便,毕竟在C++中,智能指针在编程设计中使用的还是非常广泛。
简单地说,智能指针只是用对象去管理一个资源指针,同时用一个计数器计算当前指针引用对象的个数,
当管理指针的对象增加或减少时,计数器也相应加1或减1,当最后一个指针管理对象销毁时,
计数器为1,此时在销毁指针管理对象的同时,也把指针管理对象所管理的指针进行delete操作。
如下图所示,简单话了一下指针、智能指针对象和计数器之间的关系:
#include <memory>
class Test
{
public:
Test()
{
std::cout << "Test()" << std::endl;
}
~Test()
{
std::cout << "~Test()" << std::endl;
}
};
int main()
{
std::shared_ptr<Test> p1 = std::make_shared<Test>();
std::cout << "1 ref:" << p1.use_count() << std::endl;
{
std::shared_ptr<Test> p2 = p1;
std::cout << "2 ref:" << p1.use_count() << std::endl;
}
std::cout << "3 ref:" << p1.use_count() << std::endl;
return 0;
}
1、std::make_shared封装了new方法,boost::make_shared之前的原则是既然释放资源delete由智能指针负责,
那么应该把new封装起来,否则会让人觉得自己调用了new,但没有调用delete,
似乎与谁申请,谁释放的原则不符。C++也沿用了这一做法。
2、随着引用对象的增加std::shared_ptr<Test> p2 = p1,指针的引用计数有1变为2,
当p2退出作用域后,p1的引用计数变回1,当main函数退出后,p1离开main函数的作用域,
此时p1被销毁,当p1销毁时,检测到引用计数已经为1,就会在p1的析构函数中调用delete之前std::make_shared创建的指针。
4.2、std::weak_ptr
std::weak_ptr网上很多人说其实是为了解决std::shared_ptr在相互引用的情况下出现的问题而存在的.
// A、std::shared_ptr相互引用的问题示例:=====
#include <memory>
class TestB;
class TestA
{
public:
TestA()
{
std::cout << "TestA()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestB> test_ptr)
{
m_TestB_Ptr = test_ptr;
}
~TestA()
{
std::cout << "~TestA()" << std::endl;
}
private:
std::shared_ptr<TestB> m_TestB_Ptr; //TestB的智能指针
};
class TestB
{
public:
TestB()
{
std::cout << "TestB()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestA> test_ptr)
{
m_TestA_Ptr = test_ptr;
}
~TestB()
{
std::cout << "~TestB()" << std::endl;
}
std::shared_ptr<TestA> m_TestA_Ptr; //TestA的智能指针
};
int main()
{
std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();
std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();
ptr_a->ReferTestB(ptr_b);
ptr_b->ReferTestB(ptr_a);
return 0;
}
/*我们创建了一个TestA和一个TestB的对象,但在整个main函数都运行完后,都没看到两个对象被析构,这是什么问题呢?
原来,智能指针ptr_a中引用了ptr_b,同样ptr_b中也引用了ptr_a,在main函数退出前,
ptr_a和ptr_b的引用计数均为2,退出main函数后,引用计数均变为1,也就是相互引用。
这等效于说:
ptr_a对ptr_b说,哎,我说ptr_b,我现在的条件是,你先释放我,我才能释放你,这是天生的,造物者决定的,改不了。
ptr_b也对ptr_a说,我的条件也是一样,你先释放我,我才能释放你,怎么办?
是吧,大家都没错,相互引用导致的问题就是释放条件的冲突,最终也可能导致内存泄漏。
*/
B、std::weak_ptr如何解决相互引用的问题
我们在上面的代码基础上使用std::weak_ptr进行修改:
#include <memory>
class TestB;
class TestA
{
public:
TestA()
{
std::cout << "TestA()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestB> test_ptr)
{
m_TestB_Ptr = test_ptr;
}
void TestWork()
{
std::cout << "~TestA::TestWork()" << std::endl;
}
~TestA()
{
std::cout << "~TestA()" << std::endl;
}
private:
std::weak_ptr<TestB> m_TestB_Ptr;
};
class TestB
{
public:
TestB()
{
std::cout << "TestB()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestA> test_ptr)
{
m_TestA_Ptr = test_ptr;
}
void TestWork()
{
std::cout << "~TestB::TestWork()" << std::endl;
}
~TestB()
{
std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock();
tmp->TestWork();
std::cout << "2 ref a:" << tmp.use_count() << std::endl;
std::cout << "~TestB()" << std::endl;
}
std::weak_ptr<TestA> m_TestA_Ptr;
};
int main()
{
std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();
std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();
ptr_a->ReferTestB(ptr_b);
ptr_b->ReferTestB(ptr_a);
std::cout << "1 ref a:" << ptr_a.use_count() << std::endl;
std::cout << "1 ref b:" << ptr_a.use_count() << std::endl;
return 0;
}
1、所有的对象最后都能正常释放,不会存在上一个例子中的内存没有释放的问题。
2、ptr_a 和ptr_b在main函数中退出前,引用计数均为1,
也就是说,在TestA和TestB中对std::weak_ptr的相互引用,不会导致计数的增加。
在TestB析构函数中,调用std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock(),
把std::weak_ptr类型转换成std::shared_ptr类型,然后对TestA对象进行调用。