array
array是C++11新引入的容器类型,与内置数组相比,array是一种更容易使用,更加安全的数组类型,可替代内置数组,作为数组升级版,继承数组最本特性,同时融入部分操作。
#include<array>
int main()
{
array<int, 4> arr{1,2,3,4};
int len = 4;
array<int, len> arr = {1,2,3,4}; // 非法, 数组大小参数必须是常量表达式
}
下标访问
for(std::size_t i=0;i<array.size();i++)
{
std::cout << array[i] << " ";
}
at访问
尽量使用at方法来访问元素,因为运算符[]不会对索引值进行检查,像array[-1]是不会报错的。
使用at()将在运行期间捕获非法索引的,默认将程序中断。
for(std::size_t i=0;i<array.size();i++)
{
std::cout << array.at(i) << " ";
}
forward_list
forward_list 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问,也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比list 更高的空间利用率。
函数名 | 作用 |
---|---|
before_begin | 返回一个迭代器,指向forwar_list容器第一个元素的前一个位置 |
empty | 测试forward_list容器是否为空 |
front | 返回forward_list容器第一个元素的引用 |
push_front | 在forward_list容器第一个元素前插入一个元素 |
pop_front | 将forward_list容器的第一个元素删除,size减1 |
insert_after | 在指定位置后面插入一个或一组元素 |
remove | 删除容器中所有等于指定值的元素 |
merge | 将两个forward_list容器合并,调用该函数前需对容器进行排序 |
sort | 排序,改变元素在容器中的存储位置 |
example
#include< iostream >
#include< forward_list >
using namespace std;
int main()
{
forward_list< int > fl1;
forward_list< int > fl2;
forward_list< int >::iterator it1 = fl1.before_begin();
forward_list< int >::iterator it2 = fl2.before_begin();
for(int i=1;i<10;i++)
{
fl1.insert_after(it1,i);
fl2.insert_after(it2,i*i);
it2++;
it1++;
}
for(int i=5;i<15;i++)
{
fl1.insert_after(it1,i);
fl2.insert_after(it2,i*i);
it2++;
it1++;
}
fl1.merge(fl2);
//fl1.reverse();
cout<<"fl1 data:";
while(!fl1.empty())
{
cout<<fl1.front()<<" ";
fl2.push_front(fl1.front());
fl1.pop_front();
}
cout<<endl;
cout<<"-----------"<<endl;
//fl2.sort();
cout<<"fl2 data:";
while(!fl2.empty())
{
cout<<fl2.front()<<" ";
fl2.pop_front();
}
cout<<endl;
return 0;
}
tuple
tuple是一个可以容纳不同类型元素的容器 。
定义tuple
tuple<int,double,string> testtuple1;//新建tuple类型
tuple<int,double,string> testtuple2(1,2.0,"test");//新建tuple类型的变量,其类型为tuple<int,double,string>
auto testtuple3=make_tuple("test2",123,3.14);//通过初值来初始化tuple,其类型从初值的类型中推断。
访问tuple值
auto str=get<0>(testtuple3);//访问第一个元素,注意从0开始,
auto int_v=get<1>(testtuple3);//访问第二个元素。
get<2>(testtuple3)=2.0;//更改第三个元素的值。
获取tuple的元素个数
size_t sz=tupe_size<testtuple3>::value;//返回3
example
#include <tuple>
#include <iostream>
using namespace std;
int main()
{
// 元组访问
tuple<int, int, vector<int>> tupleTest(1, 4, { 5,6,7,8 });
// 元组个数
int tupleTestSize = tuple_size<decltype(tupleTest)>::value;
cout << "tuple size is : " << tupleTestSize << endl;
auto first = get<0>(tupleTest);
cout << "first value is : " << first << endl;
auto second = get<1>(tupleTest);
cout << "second value is : " << second << endl;
auto third = get<2>(tupleTest);
for (const auto & iter : third)
{
cout << "third value is : " << iter << endl;
}
auto catTupleTest = make_tuple(1, 4);
// 元组合并
auto newTuple = tuple_cat(catTupleTest, tupleTest);
int newTupleSize = tuple_size<decltype(newTuple)>::value;
cout << "after cat, new tuple size is : " << newTupleSize << endl;
}
auto自动类型推导
注意点:
auto声明的变量必须要初始化,否则编译器不能判断变量的类型。
auto不能被声明为返回值,auto不能作为形参,auto不能被修饰为模板参数
decltype
decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 sizeof 很相似:
decltype(表达式)
在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
for循环
for(x:range)
// & 启用了引用
for(auto &i : arr) {
std::cout << i << std::endl;
}
lambda表达式
[捕获区] (参数区) {代码区};
如
auto add = [](int a, int b) {return a + b};
[a,&b] 其中 a 以复制捕获而 b 以引用捕获。
[this] 以引用捕获当前对象( *this )
[&] 以引用捕获所有用于 lambda 体内的自动变量,并以引用捕获当前对象,若存在
[=] 以复制捕获所有用于 lambda 体内的自动变量,并以引用捕获当前对象,若存在
[] 不捕获,大部分情况下不捕获就可以了
一般使用场景:sort等自定义比较函数、用thread起简单的线程。
右值引用
C++中,左值通常指可以取地址,有名字的值就是左值,而不能取地址,没有名字的就是右值。
右值引用就是对一个右值进行引用的类型。
智能指针
概念:C++中堆内存的申请和释放都由程序员自己管理,这样可以提高了程序的效率,但是如果申请的空间在函数结束时忘记释放,会造成内存泄漏。智能指针能在函数结束时自动释放内存空间,不需要手动释放内存空间,避免内存泄漏;
c++11支持三个智能指针:
- shared_ptr
多个智能指针可以指向同一个对象,对象会在最后一个引用被销毁时候释放。
它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。
当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。- weak_ptr
weak_ptr是为了配合shared_ptr而引入的一种智能指针,它可以从一个 shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权
它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数- unique_ptr
保证同一时间内只有一个智能指针可以指向该对象。
多线程
#include <thread>
using namespace std;
void func() {
// do some work here
}
int main() { //主线程
thread t(func); //子线程
t.join(); //等待子线程结束后才进入主线程
return 0;
}
上面就是一个最简单的使用std::thread的例子,函数func()在新起的线程中执行。调用join()函数是为了阻塞主线程,直到这个新起的线程执行完毕。线程函数的返回值都会被忽略,但线程函数可以接受任意数目的输入参数。
除了std::thread的成员函数外,在std::this_thread命名空间也定义了一系列函数用于管理当前线程。
函数名 | 作用 |
---|---|
get_id | 返回当前线程的id |
yield | 告知调度器运行其他线程,可用于当前处于繁忙的等待状态。相当于主动让出剩下的执行时间,具体的调度算法取决于实现 |
sleep_for | 指定的一段时间内停止当前线程的执行 |
sleep_until | 停止当前线程的执行直到指定的时间点 |
mutex
函数名 | 函数功能 |
---|---|
lock() | 上锁:锁住互斥量 |
unlock() | 解锁:释放对互斥量的所有权 |
try_lock() | 尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞 |
线程函数调用lock()时,可能会发生以下三种情况:
- 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁;
- 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住;
- 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock);
线程函数调用try_lock()时,可能会发生以下三种情况:
- 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量;
- 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉;
- 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock);
example
#include <iostream>
using namespace std;
#include <thread>
#include <mutex>
std::mutex m;
unsigned long sum = 0L;
void fun(size_t num)
{
for (size_t i = 0; i < num; ++i)
{
m.lock();
sum++;
m.unlock();
}
}
int main()
{
cout << "Before joining,sum = " << sum << std::endl;
thread t1(fun, 10000000);
thread t2(fun, 10000000);
t1.join();
t2.join();
cout << "After joining,sum = " << sum << std::endl;
return 0;
}
原子操作
在C++11中,程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的访问。
#include<iostream>
#include <atomic>
using namespace std;
atomic_long sum{ 0 };
void fun(size_t num)
{
for (size_t i = 0; i < num; ++i)
sum++; // 原子操作
}
int main()
{
cout << "Before joining, sum = " << sum << std::endl;
thread t1(fun, 1000000);
thread t2(fun, 1000000);
t1.join();
t2.join();
cout << "After joining, sum = " << sum << std::endl;
return 0;
}
更为普遍的,程序员可以使用atomic类模板,定义出需要的任意原子类型
atmoic<T> t; // 声明一个类型为T的原子类型变量t
标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。
#include <atomic>
int main()
{
atomic<int> a1(0);
//atomic<int> a2(a1); // 编译失败
atomic<int> a2(0);
//a2 = a1; // 编译失败
return 0;
}
委托构造
- 如果一个类包含多个构造函数,C++ 11允许在一个构造函数中的定义中使用另一个构造函数,但这必须通过初始化列表进行操作;
- 在一个构造函数中调用另外一个构造函数,这就是委托的意味,不同的构造函数自己负责处理自己的不同情况,把最基本的构造工作委托给某个基础构造函数完成,实现分工协作;
- 委托构造函数体中的语句在目标构造函数完全执行后才被执行。目标构造函数体中的局部变量不在委托构造函数体中起作用;
class X{
private:
int a, b;
public:
X(int x, int y) :a(x), b(y){ }
//委托构造函数,委托带参数的构造函数完成数据成员初始化
X():X(5, 10){ }
X(int x):X(x, 10){}
};
在上述代码中不带参数和带有一个整型参数的构造函数是委托构造函数,它们将数据成员的初始化操作都委托给了带有两个整型参数的构造函数,被委托的构造函数称为目标构造函数。目标构造函数还可以再委托给另一个构造函数。
继承构造
在继承体系中,如果派生类想要使用基类的构造函数,需要在构造函数中显式声明;
如果一个继承构造函数不被相关的代码使用,编译器不会为之产生真正的函数代码;
当使用using语句继承基类构造函数时。派生类无法对类自身定义的新的类成员进行初始化,我们可使用类成员的初始化表达式,为派生类成员设定一个默认初始值;
struct A
{
A(int i) {}
A(double d,int i){}
A(float f,int i,const char* c){}
//...等等系列的构造函数版本号
};
struct B:A
{
using A::A;//关于基类各构造函数的继承一句话搞定
int d{0};
};
多继承构造冲突:
- 当派生类拥有多个基类时,多个基类中的部分构造函数可能导致派生类中的继承构造函数的函数名。
- 一个解决的办法就是显式的继承类的冲突构造函数,阻止隐式生成对应的继承构造函数,以免发生冲突。
struct A
{
A(int){}
};
struct B
{
B(int){}
};
struct C:A,B
{
using A::A;
using B::B;
C(int){}//显示声明,阻止冲突
};
[1].C++11新特性梳理
[2]: C++11常用新特性快速一览
[3]: 牛客-C++工程师面试宝典
[4]:C++11知识点总结(全面解析C++11经常考到的知识点)
[5]:C++的forward_list最常用函数