虚函数是虚构函数
explicit加在构造函数前
shared_ptr<element_type> lock() const noexcept;
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不行
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);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的方式在子类中使用基类中的这些父类函数,也就是覆盖时调用父类被覆盖的函数
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.func(19, "luffy");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表达式
格式:opt->ret{}
[]不捕获任何变量
[&]以引用的方式捕获所有变量
[=]以赋值的方式捕获,以这种方式传递进来的变量都只读,不可改变
[=, &a]a变量是引用捕获,其他赋值捕获
[bar]赋值捕获bar变量,其他都不捕获
[&bar]引用捕获bar变量,其他都不捕获
[this]捕获this指针,因此可以用类的成员变量和成员函数
opt选项,可以省略
mutable:把按值传递进来的只读变量修改为可读可写
exception:指定函数抛出的一场,可以用throw()
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,当捕获了外部变量,就无法作为函数指针进行使用,因为此时它已经关联了上下文变量,而这个函数指针可能在不同的上下文环境中调用
}
}
}
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
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();
ptr3->myfunc();//调用智能指针的成员用.,调用其管理指针的成员用->。//shared_ptr可以自定义删除器,在初始化的最后一个参数
shared_ptr<Test> p6(new Test[5], [](Test* t){delete [] t;
});
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();
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 和上面错误相同//如果想用函数返回智能指针,可以借助weak_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();//共享智能指针不能互相引用
//如果互相引用的话,会增加引用计数
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_;
}