C++11新特性

gcc版本要支持c++11 那么linux上要求gcc 版本 >= 4.8(4.7应该也能支持)

1字符字面量

string str = R"(D:\hello\world\test.text)“; //字符串里不存在转移等,原样赋值
string str = R”(hello world
test.text)“; //可以在其中换行,它会原样打印换行后的字符排版
string str = R"这是一个路径(D:\hello\world\test.text)这是一个路径”; //可以在括号外面加上注释,但两边注释必须相同,他们不会被编译器处理,不影响里面的字符串

2 nullptr

nullprt是一个指针类型,NULL其实是一个宏,它是int类型的0,#define NULL 0

3.constexpr

定义的常量表达式在编译阶段就会产生计算机结果,而变量表达式需要在运行阶段才能产生结果,因此常量表达式可以提示运行效率。但编译器并不知道哪些是常量表达式,因此需要constexpr来指定。
在定义基础类型是,constexpr和const是等价的,但推荐接受新的语法

const i = 10;
constexpr i = 10;

constexpr可以用来修饰函数,包括篇普通函数,类的成员和构造函数,模板函数;修饰之后这个函数就被成为常量表达式函数
修饰函数有要求,返回值必须是常量;一般函数可以先生命,调用,之后再给定义,但常量表达式函数,调用必须在定义之后;在函数内不能有非常量表达式,因为那些在编译阶段没法完成,得在运行后;

constexpr int func()
{
constexpt int a = 0;
return a;
}
//如果实现时,display内部和返回值是常量则,constexpr则生效,否则作普通函数处理
templete<typename T>
constexpr T display(T t)
{
	return T;
}

class Person
{
constexpr Person():a(100) //修饰构造函数时,函数体内必须是空的,成员函数可以在参数列表内初始化
{}
int a;
}

4.auto

atuo定义变量时必须初始化,因为就是对变量被赋的值推到处出变量的类型,因此它不能作函数形参,类的成员是const时,才能用auto,因为const变量必须初始化。定义数组时不能用auto;
补充:volatile就是告诉编译器这个就是变量,会经常变化,不要作任何优化

int a = 9;
auto& c = a;//等同于int& c = a;
auto* d = &a;//等同于auto d = &a; d都是指针
int tmp = 250;
const auto a1 = tmp;//const int a1 = tmp;
const int tmp2 = 250;
auto a2 = tmp2;//int a2 = tmp2 如果tmp2不是指针或者引用,则const和volatile关键字则不会保留
//因为auto定义的新变量如果指向原来的变量,那它们其实是共享,因此如果此内存保存的是const值,那么他们都不能被修改;
auto& a3 = tmp2; //const int a3 = tmp2;
auto* pt4 = &a1;//const int* pr4 = &a1;
*pt4 = 2;//error,此值不能被修改

5.decltype

declart type的缩写,它的用法如下,它根据表达时推导出类型,然后再定义一个变量;但它不会运行表达式
decltype(表达式)

int a = 10;
decltype(a) b = 10;
decltype(a + 3.14) c = 52.12;
const int b = 10;
decltype(b) a = b;//此时a也是const int类型
//但如果b是纯右值,则其中的const 和 volatile就不会被保留,如果这个纯右值是类成员则会被保留。纯右值就是字面量,没有内存

6.返回值后置

template <typename T, typename U>
auto add(T t, U u) -> decltype(t+u)
{
return t + u;
}

7.final和override

它用于修饰某个虚函数,使得这个函数不能在派生类中重写,写在函数后;
也用来修饰某个类,使得这个类不能被继承,写在类后;

class Father
{
public:
virtual void test(){}
}

class Child final : public Father
{
public:
virtual void test() final {}
}

override用来修饰子类中要重写的虚函数,用来给编译器提醒我要重写父类函数,记得帮我检查是否重写。

8.模板头的默认类型和模板函数的默认参数

template<typename T = long, typename U = int>
void mytest(T t = 'A', U u = 'B')
{}
//调用
mytest();//会使用默认的类型和参数,此时,'A'和'B'会转换成数值,并且是long和int类型
mytest<char>('a');//没有指定的使用默认的

9.using新特性

using和typedef都可以给一个类型或者复合类型,定义别名,主要是为了方便

mytest(int a, string b){}
typedef int t1;
t1 a = 2;
using t2 = int;
t2 a1 = 2;
//定义函数指针 注意这里定义的只是一个类型,并不是实例化
typedef int(*func)(int, string);
using func1 = int(*)(int, sting);
func f = mytest;
func1 f1 = mytest;
f(10, "hello");
f1(10, "hello");
(*f)(10, "hello");
//定义容器
using MMap =map<int, long>
MMap a;
//using更加简洁,而且using可以给模板定义别名,typedef不行

在C++中,子类在继承父类时会获取父类的成员,但不包含构造函数和析构函数。子类的所有构造函数会默认调用父类的无参数构造函数。当父类只有带参数的构造函数时,子类必须在初始化列表中显式调用父类的构造函数并传入参数。否则,编译会出现错误。这种机制确保了对象构造的顺序,从基类到派生类,析构时则反过来。
但如果出现了父类指针指向子类/孙子对象,即多态时,父类对象必须是虚析构函数,不然delete 父类指针时,子类或者子类和孙子类的析构函数不会被调用。而子类和孙子类的析构函数不必是虚的。

10委托构造函数和继承构造函数

在c++11之前无法在构造函数中调用其他构造函数,以下是构造函数的相互调用

class Test
{
public:
    Test() {};
    Test(int max)
    {
        this->m_max = max > 0 ? max : 100;
    }

    Test(int max, int min):Test(max)
    {
        this->m_min = min > 0 && min < max ? min : 1;
    }

    Test(int max, int min, int mid):Test(max, min)
    {
        this->m_middle = mid < max && mid > min ? mid : 50;
    }
}
//作者: 苏丙榅
//链接: https://subingwen.cn/cpp/construct/#1-%E5%A7%94%E6%89%98%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0

继承构造函数
C++11中提供的继承构造函数可以让派生类直接使用基类的构造函数,而无需自己再写构造函数,尤其是在基类有很多构造函数的情况下,可以极大地简化派生类构造函数的编写。先来看没有继承构造函数之前的处理方式:

class Base
{
public:
    Base(int i) :m_i(i) {}
    Base(int i, double j) :m_i(i), m_j(j) {}
    Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}

    int m_i;
    double m_j;
    string m_k;
};
class Child : public Base
{
public:
    Child(int i) :Base(i) {}
    Child(int i, double j) :Base(i, j) {}
    Child(int i, double j, string k) :Base(i, j, k) {}
};


通过测试代码可以看出,在子类中初始化从基类继承的类成员,需要在子类中重新定义和基类一致的构造函数,这是非常繁琐的,C++11中通过添加继承构造函数这个新特性完美的解决了这个问题,使得代码更加精简。

class Child : public Base
{
public:
    using Base::Base;  //using 类名::构造函数名
};

int main()
{
    Child c1(520, 13.14);//如果在定义了Child::Child(int i, double j)构造函数,其优先级高于Base(int i, double j)被调用
    cout << "int: " << c1.m_i << ", double: " << c1.m_j << endl;
    Child c2(520, 13.14, "i love you");
    cout << "int: " << c2.m_i << ", double: " 
         << c2.m_j << ", string: " << c2.m_k << endl;
    return 0;
}

继承重载函数
另外如果在子类中隐藏了父类中的同名函数,也可以通过using的方式在子类中使用基类中的这些父类函数,也就是重载时调用父类被重载的函数。(重载函数是指参数数量或者参数类型,以及二者都不相同的同名函数,构成重载;函数返回值可以相同也可以不同)(重写时,返回值类型,函数名,参数列表必须全部相同)(覆盖是函数名字和参数列表都相同,但子类和父类的函数都不是虚函数;其返回值可以不同)
重载用以下using方法可以继承来,不用再使用Base::作用域限定来调用,而覆盖必须使用Base::来限定调用。

class Base
{
public:
    void func(int i)
    {
        cout << "base class: i = " << i << endl;
    }
    
    void func(int i, string str)
    {
        cout << "base class: i = " << i << ", str = " << str << endl;
    }

};
class Child : public Base
{
public:
    using Base::func;
    void func()
    {
        cout << "child class: i'am luffy!!!" << endl;
    }
};
int main()
{
    Child c;
    c.func();
    c.func(19);//c.Base::func(19); 否则需要这样显式调用,其实Base中的继承来的成员都可以显式调用
    c.func(19, "luffy");//c.Base::func(19, "luffy");否则需要这样显式调用,其实Base中的继承来的成员都可以显式调用
    return 0;
}

11.统一的初始化

//类的初始化
Person t1(520);
Person t2 = 520;
Person t3 = {520};
Person t4{520};
int a1 = {1314};
int a2{1314};
int a3[] = {1,2,3,4};
int a4[]{1,2,3,4};

聚合对象和非聚合对象
像数组,基本变量如int都是聚合对象,他们可以直接使用统一初始化。默认构造函数的形参列表为空,因此统一初始化其实并不是调用它的默认构造函数。
如果是数组,类,就不一定是聚合对象,比如它有私有的或受保护的成员变量,或者有自定义的构造函数。这个时候就不能直接使用,必须对应着构造函数使用。

12.initializer_list

不确定多个同类型参数,作为函数形参使用
它是一个轻量型容器类型,内部定义了iterator等容器必须概念,遍历时得到的迭代器是只读的,可以接受不确定多个参数,但参数必须是同类型。它内部有三个接口,size(), begin(), end()。

void func(initialize_list<int> ls)
{
	auto it = ls.begin();
	for(; it != ls.end(); it++)
	{
		cout << *it << endl;
	}
	cout << endl;
}
//调用
func({2, 3, 6, 7, 9})//对initializer_list<T>的赋值必须是统一初始化列表。

13.基于范围的for循环

for(declaration : expression)
{
//循环体
}

declaration表示遍历声明,当前遍历到的元素会被存储到声明的变量中,expression是要遍历的对象,可以是表达式,容器,数组,初始化列表。

vector<int> v{1, 3, 4, 5, 6};
vector<int>& getRange()
{
cout << "get vector range" << endl;
return v;
}
for(auto val : getRange())
{
	cout << val << " " ;
}
cout << endl;

以上结果是只会有一次打印,因为getRange()只被调用一次;基于范围的for只会访问expression一次,然后确定begin和end,之后就开始遍历,因此此时如果删除某些成员,导致没有那么多元素,但是依然会遍历那么多个,就会出错误;正常for循环就不会出这个错误。

set<int> st{1, 2, 3, 4, 5};
for(auto& it : st)
{
cout << it++ << endl;//这里会出错,因为set是只读的容器,因此auto& it其实会推导成const类型。
}

map<int, string> m{{1, "lucy"}, {2, "lily"}, {3, "tom"}};
for(auto& it : m)
{
	cout << "id:" << it.first++ << ", name:" << it.second << endl;//错误,first是key值,也是不可修改的
}

14.lambda表达式

从c++11开始,除了函数外,还有四种可以被调用的对象,函数指针,仿函数,lambda,bind创建的对象
函数指针:从c语言继承来的
int (*)(int, int);
通常我们用 typedef 来定义函数指针类型的别名方便使用
typedef int (Plus)(int, int);
从 C++11 开始,更推荐使用 using 来定义别名
using Plus = int (
)(int, int);
仿函数:
仿函数其实就是让一个类(class/struct)的对象的使用看上去像一个函数,具体实现就是在类中实现 operator()。
class Plus {
public:
int operator()(int a, int b) {
return a + b;
}
};
Plus plus;
std::cout << plus(11, 22) << std::endl; // 输出 33

格式:opt->ret{}
[]不捕获任何变量
[&]以引用的方式捕获所有变量
[=]以赋值的方式捕获(其实就是复制的方式获取外部变量),以这种方式传递进来的变量都只读,不可改变。
[=, &a]a变量是引用捕获,其他赋值捕获
[bar]赋值捕获bar变量,其他都不捕获
[&bar]引用捕获bar变量,其他都不捕获
[=, &x, &y]表示外部变量 x、y 的值可以被修改,其余外部变量不能被修改;
[&, x, y]表示除 x、y 以外的外部变量,值都可以被修改。
[this]捕获this指针,因此可以用类的成员变量和成员函数
opt选项,可以省略
mutable:把按值传递进来的只读变量修改为可读可写
exception:指定函数抛出的一场,可以用throw()
mutable 说明 lambda 表达式体内的代码可以修改被捕获的变量(因此按值捕获的变量,也可以被修改了),并且可以访问被捕获的对
象的 non-const 方法;被它修饰后,即使没有参数,也不能省略参数列表。
exception 说明 lambda 表达式是否抛出异常以及何种异常。attribute 用来声明属性。
ret返回值,也可以省略
()参数列表也可以省略,就变成[]{}

void func(int x, int y)
{
int a = 0;
int b = 0;
[=,&x](int z)mutable ->int
{
int c = a;
int d = x;
b++;//如果不加mutable 则error,因为按值捕获的变量都只读
return 0;//如果这个返回值类型可以被推导出,那么->int也可以省略,如果返回的是{1,3,3}这样的初始化列表那么就不能省略,因为它可以给数组,结构体,类等初始化,无法推导类型。//lambda用法一般不用返回
}(0)//进行调用,并传递0赋值给形参z
cout << "b:" << b << endl;//b为0,并没有改变
}

void func(int x, int y)
{
int a;
int b;
using prt = void(*)(int);
prt p1 = [](int z){
cout << "z:" << z << endl;
p1(11);

ptr p2 = [=](int z){
cout << "z:" << z << endl;
p2(11);//error,当捕获了外部变量,就无法作为函数指针进行使用,因为此时它已经关联了上下文变量,而这个函数指针可能在不同的上下文环境中调用
}
}
}

auto x2 = [=]() { return i_ + x + y; };
x2();

15.右值引用

左值是有内存的值,右值是没有内存的值

int num = 9;
int& a = num;//左值引用
int&& b = 8;//右值引用

16.move()转移

list<string> ls1 {"hello", "world", "aac"};
list<string> ls2 = ls1;//这样会copy一次
list<string> ls3 = move(ls1);//不会copy,而且ls1还不用释放了。

17.shared_ptr智能指针

智能指针内维护的是指向一个对象的指针。
c++11提供了三个智能指针,使用它们需要

#include <memory>

std::shared_ptr:共享智能指针
std::unique_prt:独占智能指针
std::weak_prt:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr的。

//std::shared_prt的四种初始化方式
//通过构造函数
shared_ptr<int> ptr1(new int(520));
cout << "引用计数个数:" << ptr1.use_count() << endl;

//move()移动赋值和拷贝赋值
shared_ptr<int> ptr2 = move(ptr1);//资源的转移,总的引用计数并没有变
shared_ptr<int> ptr4 = ptr1;
//make_shared
shared_ptr<Test> ptr3 = make_shared<Test>;
//reset()方法
//reset()方法有两个作用,首选如果它原来管理了一块内存,那么会断开与这块内存的链接,也就是这块内存的引用计数会减一;然后再重新与新放进来的内存建立连接
ptr3.reset();
ptr3.reset(new Test);
//get函数
//get()获取到的是被管理的对象的指针。
Test* t = ptr3.get();//get()并不会减少引用计数
ptr3->myfunc();//调用智能指针的成员用.,调用其管理指针的成员用->。

//shared_ptr可以自定义删除器,在初始化的最后一个参数
shared_ptr<Test> p6(new Test[5], [](Test* t){
	delete [] t;
});


//shared_prt的 double free大坑
Test p = new Test;
shared_ptr p1(p);
shared_ptr p2(p);//此时p1和p2互相不关联,他们的引用计数都是1,因此会造成p被释放两次
因此我们才会使用make_shared<>,因为它返回的就是shartd_ptr类新,避免了程序中出现new的裸指针。
shared_ptr<Test> p3 = make_shared<Test>;//此时重新构建了一个Test类
shared_ptr<Test> p4 = p3;

//我们有时候需要通过类内的成员函数,返回管理此对象的shared_ptr,这时可以用以下方法
struct Test : public enable_shared_from_this<Test>
{
	share_ptr<Test> getSharedPtr()
	{
		return shared_from_this()://这个方法是继承来的
	}
}
shared_ptr<Test> ptr1(new Test);
shared_ptr<Test> ptr2 = ptr1->getSharedPtr();

18.unique_ptr

它是独占的智能指针,一块内存对象被一个unique_ptr管理之后,它就不能再被其他智能指针管理了。因此unique_ptr不能像shared_ptr

//初始化
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = ptr1;//error 独占无法共同管理
//move()
unique_ptr<int> ptr3 = move(ptr1);
//通过函数返回值方式
unique_ptr<int> func()
{
	return unique_ptr<int>(new int(520));//调用unique_ptr的构造函数
}
unique_ptr<int> ptr4 = func();//并没有共享,因为返回值是将亡值,所有可以赋值
//reset()
ptr3.reset();//释放对原内存的管理
ptr1.reset(new int(520));//释放对原内存的管理,并接管新的内存对象
//get()
unique_ptr<Test> ptr5(new Test);
Test* pt = ptr5.get(); //get()并不会减少引用计数
ptr5->m_func();//调用其成员函数
pt->m_func();//调用其成员函数

//删除器,shared_ptr也可以指定删除器
shared_ptr<int> ptr6(new int(10), [](int* p){
delete p;
});
//对于数组类型的内存管理
unique_ptr<Test[]> ptr7(new Test[3]); //c++11可以自动释放内存
shared_ptr<Test[]> ptr8(new Test[3]); //c++11,需要自定义删除器,不然无法释放内存,c++11以后可以自动释放内存

## 19.weak_ptr

//它是帮shared_ptr管理智能指针的,也就是用shared对象可以初始化它。它管理后引用计数并不加1,它不管理后,引用计数也不会减1。为什么需要它呢,1.需要它解决循环引用问题。2.返回值为shared_ptr时,出现多次释放问题。
//如:
struct Test
{
	shared_ptr<Test> getSharedPtr()
	{
		return shared_ptr<Test>(this);
	}
};
//初始化
shared_ptr<int> sp(new int);
weak_ptr<int> wp1;
weak_ptr<int> wp2(sp1);
weak_ptr<int> wp3(sp);
weak_ptr<int> wp4;
wp4 = sp;
weak_ptr<int> wp5;
wp5 = wp3;

//方法
use_count();//它虽然不能增加或减少引用计数,但可以查看
expired();//判断是否还在监管资源,不监管不代表资源已经不存在,原型如下:
expired();
lock();//调用它来获取监管的shared_ptr对象原型如下:
shared_ptr<element_type> lock() const noexcept;
shared_ptr<int> sp1(new int);
weak_ptr<int> wk1(p1);
shared_ptr<int> sp2;
sp2 = wk1.lock();
reset();//weak_ptr调用这函数后,就不帮助监管资源了。
wk1.reset();


20.注意事项

//不能使用一个原始对象,初始化多个共享智能指针
//函数不能返回管理了this的共享智能指针对象
//共享智能指针不能循环引用

Test* t = new Test;
shared_ptr<Test> ptr1(t);
shared_ptr<Test> ptr2(t);//error 这样的话,ptr1和2的引用记数都为1,他们互相不知道对方的存在,因此他俩释放时,t指向的内存会被释放两次,就会报错
shared_ptr<Test> ptr2 = ptr1;

shared_ptr<Test> Test::getSharedPtr()
{
	return shared_ptr<Test>(this);
}

shared_ptr<Test> ptr1(new Test);
shared_ptr<Test> ptr2 = ptr1->getSharedPtr();//error 和上面错误相同

//共享智能指针不能互相引用
//如果互相引用的话,会增加引用计数
struct TestA
{
shared_ptr<TestB> pb_;
}
struct TestB
{
shared_ptr<TestA> pa_;
}

shared_ptr<TestA> pa(new TestA);
shared_ptr<TestB> pb(new TestB);
pb->pa_ = pb;//这样之后,引用计数都会变成2
pa->pb_ = pa;//TestA在释放时,如果不对内部共享指针.reset()释放操作,引用计数就无法减为0
//解决这个问题可以改为weak_ptr,他可以管理shared_ptr的对象,但是又不增加引用计数。
//如下:
struct TestA
{
weak_ptr<TestB> pb_;
}
struct TestB
{
weak_ptr<TestA> pa_;
}

21.emplace

它是c++11后,容器增加元素时的高性能方法,对应之前的inset(),push_back()等,这些会把对象copy到容器中,而emplace则是根据传进来的对象,在容器中构造一个对象,而不进行copy操作。个人理解这个就像在类构造函数参数列表中构造;而inset()等就像在构造函数内赋值。它有emplace_front和emplace_back。

22.bind和function机制

使用时需包含

#include <functional>

function模板库:用于替代C语言中的函数指针,它允许用户在目标的实现上有更大的弹性,即目标不仅仅是普通函数,包括以下所有函数。
• 普通函数
• 函数指针
• lambda 表达式
• 重载了函数调用运算符的类(仿函数)
• bind 创建的对象
对std::function最简单的理解就是:std::function实现了函数的存储,即先将可调用实体保存起来,在需要的时候再调用。有一点,绑定类的成员函数时,必须要借助std::bind的帮忙。
std::function的使用有多态和万总归一的感觉,示例如下:

#include <iostream>
#include <functional>

// 普通函数
int testFun(int a, int b) {
	return a + b;
}

// Lambda表达式
auto lamdaExps = [](int a, int b) {
	return a + b;
};

// 仿函数
class Functor {
public:
	int operator()(int a, int b) {
		return a + b;
	}
};

// 1.类成员函数
// 2.类静态成员函数
class TestClass
{
public:
	int classMemberFun(int a, int b) { return a + b; }
	static int staticMemberFun(int a, int b) { return a + b; }
};

int main() {
	// 普通函数
	std::function<int(int, int)> functional = testFun; //需要先定义一个返回值和形参表,与将要被保存的函数相同的对象。
	int ret = functional(10, 10);
	std::cout << "普通函数:" << ret << std::endl;

	// 普通函数指针
	functional = &testFun;
	ret = functional(10 ,20);
	std::cout << "普通函数指针:" << ret << std::endl;

	// Lambda表达式
	functional = lamdaExps;
	ret = functional(10, 30);
	std::cout << "Lambda表达式:" << ret << std::endl;

	// 仿函数
	Functor testFunctor;
	functional = testFunctor;
	ret = functional(10, 40);
	std::cout << "仿函数:" << ret << std::endl;

	// 但是:::类成员函数(使用std::bind绑定类成员函数)
	TestClass testObj;
	functional = std::bind(&TestClass::classMemberFun, testObj, std::placeholders::_1, std::placeholders::_2);//其中最后两个参数是占位符,表示有两个参数,依次固定写法
	ret = functional(10, 50);
	std::cout << "类成员函数:" << ret << std::endl;
	// 类静态成员函数
	functional = TestClass::staticMemberFun;
	ret = functional(10, 60);
	std::cout << "类静态成员函数:" << ret << std::endl;

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

、、、、南山小雨、、、、

分享对你有帮助,打赏一下吧!

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

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

打赏作者

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

抵扣说明:

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

余额充值