c++11新特性学习

c++版本引入学习(98/11/14/17)

1.语言上的强化


1.1变量
1.1.1 nullptr

c++11之前:c++会使得NULL,0,两个变量是一个东西,从而无法区分;(有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0);

弊端:
c++不允许直接将 void * 隐式转换到其他类型;

void foo(int);
void foo(char*);

转换不了void*,编译器只能转换成0,导致 C++ 中重载特性发生混乱;(这里会调用foo(int));

c++11之后:
引入了 nullptr 关键字,专门用来区分空指针、0。而 nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较
nullpter-》char*
o->int

1.1.2 constexpr

c++11之前:
缘由:编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,增加程序的性能
const关键字;
弊端:数组的长度必须是一个常量表达式

#include <iostream>
//常量
int len_foo() {
    int i = 2;
    return i;
}
//常量表达式
constexpr int len_foo_constexpr() {
    return 5;
}

int main() {
    int len = 10;
    // char arr_3[len];                  // len是常量

    const int len_2 = len + 1;
    constexpr int len_2_constexpr = 1 + 2 + 3;
    // char arr_4[len_2];                // 非法 这是一个 const 常数,而不是一个常量表达式
    char arr_4[len_2_constexpr];         // 合法

    char arr_5[len_foo()+5];          // 非法 len_fo会返回一个常量
    char arr_6[len_foo_constexpr() + 1]; // 合法

    return 0;
}

c++11之后:constexpr 让用户显式的声明函数或对象构造函数在编译期会成为常量表达式;

1.1.3 元组 std::tuple

C++11 新增了 std::tuple 容器用于构造一个元组,进而囊括多个返回值

std::tuple<int, double, std::string> f() {
   return std::make_tuple(1, 2.3, "456");
}

int main() {
   auto [x, y, z] = f();
   std::cout << x << ", " << y << ", " << z << std::endl;
   return 0;
}

弊端:11-14无法使用简单的方法取出特定的元素,目前使用std::tie拆包;

1.2 类型推导
1.2.1 auto (自动类型推导)
for(vector<int>::const_iterator it = vec.cbegin(); itr != vec.cend(); ++it)
->
for(auto it = vec.bengin();it!=vec.end;++it)

auto i = 5;              // i 被推导为 int
auto arr = new auto(10); // arr 被推导为 int *

注意:
auto auto_arr2[10] = {arr}; // 错误, 无法推导数组元素类型
报错信息:
2.6.auto.cpp:30:19: error: 'auto_arr2' declared as array of 'auto'
    auto auto_arr2[10] = {arr};
1.2.2 decltype(与auto一样)

decltype的类型推导并不是像auto一样是从变量声明的初始化表达式获得变量的类型,而是总是以一个普通表达式作为参数,返回该表达式的类型,而且decltype并不会对表达式进行求值

 int i = 4;
 decltype(i) a; //推导结果为int。a的类型为int。

auto x = 1;
auto y = 2;
decltype(x+y) z; 

auto+decltype一起使用,推导出返回值的类型;在c++14被摒弃;
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y){
    return x + y;
}
->合法化: 普通函数也具有返回值类型的推导;
template<typename T, typename U>
auto add(T x, U y){
    return x + y;
}
1.2.3 区间for迭代
int main() {
    std::vector<int> vec = {1, 2, 3, 4};
    if (auto itr = std::find(vec.begin(), vec.end(), 3); 
    	itr != vec.end()) 
    		*itr = 4;
    		
    for (auto element : vec)
        std::cout << element << std::endl; // read only
    for (auto &element : vec) {
        element += 1;                      // writeable
    }
    for (auto element : vec)
        std::cout << element << std::endl; // read only
}
1.2.4 > 括号
在传统 C++ 的编译器中,>>一律被当做右移运算符来进行处理。
std::vector<std::vector<int>> matrix;//c++11之前非法
c++11之后合法
1.3 初始化列表

C++11 把初始化列表的概念绑定到了类型上,并将其称之为 std::initializer_list,允许构造函数或其他函数像参数一样使用初始化列表

class MagicFoo {
public:
    std::vector<int> vec;
    MagicFoo(std::initializer_list<int> list) {
        for (std::initializer_list<int>::iterator it = list.begin();
             it != list.end(); ++it)
            vec.push_back(*it);
    }
};
int main() {
 
    MagicFoo magicFoo = {1, 2, 3, 4, 5};
	for (std::vector<int>::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) 
		std::cout << *it << std::endl;
}
1.4 变长参数模版

递归表达:

template<typename T, typename... Ts>
void printf1(T value, Ts... args) {
    std::cout << value << std::endl;
    printf1(args...);
}
int main() {
    printf1(1, 2, "123", 1.1);
    return 0;
}

弊端:
虽然使用了变参模板,却不一定需要对参数做逐个遍历,我们可以利用 std::bind 及完美转发等特性实现对函数和参数的绑定

1.5 lambda表达式

lambda也分为值拷贝,引用拷贝,在使用lambda表达式需要注意;

[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
// 函数体
}

排序例子;

int main()
{
    vector<int> myvec{ 3, 2, 5, 7, 3, 2 };
	vector<int> lbvec(myvec);
    sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; });   // Lambda表达式
    cout << "lambda expression:" << endl;
    for (int it : lbvec)
        cout << it << ' ';
}

外部变量:

int main()
{
    int a = 123;
    auto f = [a] { cout << a << endl; }; // 捕获外部变量a

    f(); // 输出:123

    auto x = [](int a){cout << a << endl;}(123); //输入参数
    
}

请添加图片描述
mutable关键字,该关键字用以说明表达式体内的代码可以修改值捕获的变量`

int main()
{
    int a = 123;
    auto f = [a]()mutable { cout << ++a; }; // 不会报错
    cout << a << endl; // 输出:123
    f(); // 输出:124
}
1.6 函数对象包装器 std::function

std::function 是一种通用、多态的函数封装, 它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作(函数的容器)-》能够更加方便的将函数、函数指针作为对象进行处理

std::bind
std::placeholder

int foo(int a,int b,int c) {
}
int main()
{
	//将参数1,2绑定到函数 foo 上,但是使用 std::placeholders::_1 来对第一个参数进行占位
	auto bindFoo =  std::bind(foo,std::placeholders::_1,1,2);
	bindFoo(1);
}
#include <iostream>
#include <functional>
 
void fn(int n1, int n2, int n3) {
	std::cout << n1 << " " << n2 << " " << n3 << std::endl;
}
 
int fn2() {
	std::cout << "fn2 has called.\n";
	return -1;
}
 
int main()
{
	using namespace std::placeholders;
	auto bind_test1 = std::bind(fn, 1, 2, 3);
	auto bind_test2 = std::bind(fn, _1, _2, _3);
	auto bind_test3 = std::bind(fn, 0, _1, _2);
	auto bind_test4 = std::bind(fn, _2, 0, _1);
 
	bind_test1();//1 2 3
	bind_test2(3, 8, 24);//3 8 24
	bind_test2(1, 2, 3, 4, 5);//1 2 3
	bind_test3(10, 24);//0 10 24
	bind_test3(10, fn2());//0 10 -1
	bind_test3(10, 24, fn2());//0 10 24
	bind_test4(10, 24);//24 0 10
	return 0;
}
1.7 右值引用

左值:表达式完成后依旧存在的对象,会维持到该作用域结束
右值:表达式结束后,不再存在

纯右值(prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 10, true; 要么是求值结果相当于字面量或匿名临时对象,例如 1+2。非引用返回的临时变量、运算表达式产生的临时变量、 原始字面量、Lambda 表达式都属于纯右值;(字符串字面量只有在类中才是右值,当其位于普通函数中是左值。)

将亡值(xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念(因此在传统 C++ 中, 纯右值和右值是同一个概念),也就是即将被销毁、却能够被移动的值。


2.容器

2.1 线性容器array

std::array
原因:array是有固定大小的容器,std::vector 是自动扩容的,当存入大量的数据后,并且对容器进行了删除操作, 容器并不会自动归还被删除元素相应的内存,这时候就需要手动运行 shrink_to_fit() 释放这部分内存

std::vector<int> v;
std::cout << "size:" << v.size() << std::endl;         // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0

// 如下可看出 std::vector 的存储是自动管理的,按需自动扩张
// 但是如果空间不足,需要重新分配更多内存,而重分配内存通常是性能上有开销的操作
v.push_back(1);
v.push_back(2);
v.push_back(3);
std::cout << "size:" << v.size() << std::endl;         // 输出 3
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 4

// 这里的自动扩张逻辑与 Golang 的 slice 很像
v.push_back(4);
v.push_back(5);
std::cout << "size:" << v.size() << std::endl;         // 输出 5
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8

// 如下可看出容器虽然清空了元素,但是被清空元素的内存并没有归还
v.clear();                                             
std::cout << "size:" << v.size() << std::endl;         // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8

// 额外内存可通过 shrink_to_fit() 调用返回给系统
v.shrink_to_fit();
std::cout << "size:" << v.size() << std::endl;         // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0
2.1 元组

std::make_tuple: 构造元组
std::get: 获得元组某个位置的值
std::tie: 元组拆包

#include <tuple>
#include <iostream>

auto get_student(int id)
{
    // std::tuple<double, char, std::string>
    if (id == 0){
        return std::make_tuple(3.8, 'A', "张三");
    }
    if (id == 1){
        return std::make_tuple(2.9, 'C', "李四");
    }
    if (id == 2) {
        return std::make_tuple(1.7, 'D', "王五");
    }
    return std::make_tuple(0.0, 'D', "null");
}

int main()
{
    auto student = get_student(0);
    std::cout << "ID: 0, "
    << "GPA: " << std::get<0>(student) << ", "
    << "成绩: " << std::get<1>(student) << ", "
    << "姓名: " << std::get<2>(student) << '\n';

    double gpa;
    char grade;
    std::string name;
    // 元组进行拆包
    std::tie(gpa, grade, name) = get_student(1);
    std::cout << "ID: 1, "
    << "GPA: " << gpa << ", "
    << "成绩: " << grade << ", "
    << "姓名: " << name << '\n';
}

获取元素可以通过:std::get<属性>();
std::tuple<std::string, double, double, int> t(“123”, 4.5, 6.7, 8);
std::cout << std::getstd::string(t) << std::endl;

弊端:如果元素一样呢?
可以通过下标的方式:
std::cout << std::get<3>(t) << std::endl;

元祖进行合并:std::tuple_cat(tup1,tup2);
auto new_tuple = std::tuple_cat(get_student(1), std::move(t));

遍历元祖:

3.智能指针

std::shared_ptr/std::unique_ptr/std::weak_ptr

3.1 shard_ptr

记录多少个 shared_ptr 共同指向一个对象,std::make_shared 会分配创建传入参数中的对象;
std::make_shared()并返回这个对象类型的std::shared_ptr指针当引用计数变为零的时候就会将对象自动删除

#include <iostream>
#include <memory>
void foo(std::shared_ptr<int> i) {
    (*i)++;
}
int main() {
    auto pointer = std::make_shared<int>(10);
    foo(pointer);
    std::cout << *pointer << std::endl; // 11
    return 0;
}

std::shared_ptr 可以通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数, 并通过use_count()来查看一个对象的引用计数

auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3

pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已 reset
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 已 reset

3.1 unique_ptr

是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:
注意:C++11 没有提供 std::make_unique
实现make_unique:

template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
  return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}

unique_ptr只允许一个指针指向他
但是可以利用std::move转移他的权限:

#include <iostream>
#include <memory>

struct Foo {
    Foo() { std::cout << "Foo::Foo" << std::endl; }
    ~Foo() { std::cout << "Foo::~Foo" << std::endl; }
    void foo() { std::cout << "Foo::foo" << std::endl; }
};

void f(const Foo &) {
    std::cout << "f(const Foo&)" << std::endl;
}

int main() {
    std::unique_ptr<Foo> p1(std::make_unique<Foo>());
    if (p1) p1->foo();//打印
    {
        std::unique_ptr<Foo> p2(std::move(p1));
        f(*p2);//打印
        if(p2) p2->foo();//打印
        if(p1) p1->foo();//不打印
        p1 = std::move(p2);
        if(p2) p2->foo();//不打印 作用yu结束 p2销毁
    }
    if (p1) p1->foo();//打印
}

之后出现了一种特殊情况,引出weak_ptr(两个shared_ptr相互指)

struct A;
struct B;

struct A {
    std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};
struct B {
    std::shared_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};
int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;
}

请添加图片描述
弱引用不会引起引用计数增加;
std::weak_ptr 没有 * 运算符和 -> 运算符,所以不能够对资源进行操作,它的唯一作用就是用于检查 std::shared_ptr 是否存在,其 expired() 方法能在资源未被释放时,会返回 false,否则返回 true

4.线程并发

std::thread 用于创建一个执行的线程实例,所以它是一切并发编程的基础,使用时需要包含 头文件, 它提供了很多基本的线程操作,例如 get_id() 来获取所创建线程的线程 ID,使用 join() 来加入一个线程等等

引入互斥量
std::mutex 是 C++11 中最基本的 mutex 类,通过实例化 std::mutex 可以创建互斥量, 而通过其成员函数 lock() 可以进行上锁,unlock() 可以进行解锁

注意:std::unique_lock 与std::lock_guard都能实现自动加锁与解锁功能,但是std::unique_lock要比std::lock_guard更灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点。
区别:std::lock_guard 不能显式的调用 lock 和 unlock, 而 std::unique_lock 可以在声明后的任意位置调用, 可以缩小锁的作用范围,提供更高的并发度。

#include <iostream>
#include <thread>

int v = 1;

void critical_section(int change_v) {
    static std::mutex mtx;
    std::unique_lock<std::mutex> lock(mtx);
    // 执行竞争操作
    v = change_v;
    std::cout << v << std::endl;
    // 将锁进行释放
    lock.unlock();

    // 在此期间,任何人都可以抢夺 v 的持有权

    // 开始另一组竞争操作,再次加锁
    lock.lock();
    v += 1;
    std::cout << v << std::endl;
}

int main() {
    std::thread t1(critical_section, 2), t2(critical_section, 3);
    t1.join();
    t2.join();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值