C++笔记目录
- 1 .头文件
- 2. vector
- 3. string
- 4. 一些有用的宏变量
- 5. 引用
- 6. 参数缺省值
- 7. 函数重载
- 8. 枚举
- 9. 函数指针
- 10. 类
- 11. bool
- 12. 建立自定义头文件
- 13.new与delete
- 14.const
- 15. 重载运算符
- 16. 模板函数/类
- 17. 宏函数/内联函数
- 18.异常
- 19.迭代器
- 20.算法常用函数
- 21.哈希表
- 22.set
- 23.pair
- 24. istringstream
- 25.优先队列
- 26. lambda表达式(匿名函数)
- 27.位运算常用函数
- 28.队列
- 29. C++ 11 特性
1 .头文件
C++标准程序库中的所有标识符都被定义于一个名为std的namespace中,使用的时候,该头文件没有定义全局命名空间,必须使用
using namespace std;
区别于C语言,C++头文件没有.h
#include<iostream> 标准输入输出(cin,cout)
#include<string> 字符串
#include<vector> vector动态数组
#include<stack> 栈
#include<unordered_map> 哈希表
#include<unordered_set> 哈希set
#include<queue> 队列
#include<algorithm> sort、max、min需要引入的头文件
#include <ctype.h> 判断字符类型
#include<sstream> 分隔字符串
#include<bitset> 位运算
#include<assert.h> 断言
#include<type_traits> 判断是否为POD类型
using namespace std;
注意得引入iostream 之后 std::cout才有效
#include<iostream>
std::cout<<"加油";
2. vector
动态数组,需要引入vector头文件
可以直接用==进行比较
注意:sizeof无法得出vector实际大小,只能得出vector类型大小为16
vector<int> a = {10,1,1,1,1,1};
vector<int> a(10); 创造大小为10的动态数组
vector<int> a(10,16); 创造大小为10且每个元素都为16的动态数组
vector<vector<int>> b; 二维数组
vector常用方法
1.a.push_back(10) 在数组的最后添加一个数据
2.a.pop_back() 去掉数组的最后一个数据
3.a.at() || a[10] 得到编号位置的数
4.a.front() 得到第一个元素
5.a.back() 得到数组的最后一个元素
6.a.size() 当前数组的大小
7.a.resize(10) 改变当前使用数据的大小,如果它比当前使用的大,则填充默认值0
8.a.erase(a.begin()+1); 删除指针指向的数据项(此处为第二个元素)
9.a.insert(a.begin(), 1); 插入1到第一个元素
10.a.clear() 清空当前的vector
11.a.empty() 判断vector是否为空,bool类型
12.reverse(res.begin(),res.end()); 反转数组
3. string
需引入string头文件,可以用==比较
注意:char*不能用等于号进行拷贝,要用strcpy(des,src)函数进行拷贝
字符串不能用strcpy进行拷贝,string是个类,里面有许多额外属性
string a = "asfdf";
1.a[0] || a.at(0);可以像数组一样访问
2.const char* str = a.c_str();
const char* str = a.data();兼容C语言,注意const不能省略
3.a.size();a.length(); 字符串长度
4.a.compare(str); bool类型,比谁ASII码大
5.a.empty() 判断是否为空,bool类型
6.s.erase(5, 4); 删除包括索引5在内的4个字符
s.erase(3);删除包括索引3在内之后的所有字符
s.erase();清空字符串
7.a.insert(1,str);插入str到索引位置
8.a.append() 追加字符,也可以用+=实现
9.str=a.substr(1); 删除包括索引1在内之后的所有字符
str=a.substr(1,3); 截取包括索引1在内的3个字符,a不变
10.to_string(i); 其他类型转化为字符串
4. 一些有用的宏变量
cout << __LINE__; 打印当前行数
cout << __FUNCTION__; 打印当前的函数名
cout << __STDC_HOSTED__; 是否包含完整的C库,值为0/1
cout << __FILE__; 打印物理文件路径
cout << __DATE__; 打印当前日期 Feb 19 2021
cout << __TIME__; 打印当前时间 16:52:08(预编译时的时间,不是动态变化的)
cout << __cplusplus;打印支持c++的版本,版本越高数越大
#if
#elif
#else
#endif
#define
#pragma once 防止头文件重定义
5. 引用
引用相当于给变量取别名,从此以后rb就是b
注意:临时变量传引用要加const
如path+d[i] 要用const string& str 接收
int& rb = b;
void swap(int& a,int& b){
int temp = a;
a = b;
b = temp;
}
swap(a,b);
- 定义必须初始化
- 引用不可改变指向
- 不存在空引用
6. 参数缺省值
1.注意:参数缺省值必须放在函数参数的最后
否则就没有意义了如func(int a = 1, int b),每次传参为了传入b必然要传两个参数,设置a的缺省就没有意义了
2.默认参数一般在函数声明中提供。如果程序中既有函数的声明又有函数的定义时,则定义函数时不允许再定义参数的默认值。
void fun(int x = 0,int y = 0);
void fun(int x = 0, int y = 0) { }
// error C2572: “fun”: 重定义默认参数 : 参数 2
// error C2572: “fun”: 重定义默认参数 : 参数 1
使用示范:
void func(int a, int b, int c = 24) {
//不传参c默认24
}
func(1,2);
7. 函数重载
c++支持函数重载,根据参数类型与数量来区分,而不是根据返回值
需要注意有函数缺省值时,函数的重载
void func(int a, int b, int c, int d = 24) {
//可以看作三个或四个int变量
}
void func(int a, int b, int c = 24) {
//可以看作二或三个int变量
}
func(1,2,3); //此时就无法区分两个函数
8. 枚举
枚举常量只能以标识符形式表示,而不能是整型、字符型等文字常量。
例如,以下定义非法:
enum letter_set {'a','d','F','s','T'};
枚举常量不能是字符常量
enum year_set{2000,2001,2002,2003,2004,2005};
枚举常量不能是整型常量
可改为以下形式则定义合法:
enum letter_set {a, d, F, s, T};
enum year_set{y2000, y2001, y2002, y2003, y2004, y2005};
亦可类型与变量同时定义(甚至类型名可省),格式如下:
enum week{Sun,Mon,Tue,Wed,Thu,Fri,Sat} weekday1, weekday2;
定义格式:定义枚举类型之后,就可以定义该枚举类型的变量:
color_set1 color1= RED;
枚举变量可以直接输出,输出的是变量的整数值:
cout<< Sun; 输出的是Sun的整数值,即0
从第一个元素依次加一,一个改变之后的索引也随之改变
不赋值默认为0
enum week { Sun, Mon = 9, Tue, Wed, Thu, Fri, Sat } weekday1, weekday2;
weekday1 = Sun; 0
weekday2 = Tue; 10
weekday3; 无论第一个元素如何都为0
可以在类中定义枚举类型
class A {
public:
enum week { Sun, Mon = 9, Tue, Wed, Thu, Fri, Sat } weekday1, weekday2;
};
a.weekday1 = A::week::Mon; 或者
a.weekday1 = A::Mon;
强枚举类型
c语言中枚举类型底层数据类型只能为int,且相同作用域下枚举值可能会冲突
c++ 11引入了枚举类
使用枚举值必须加作用域
enum class state : char { state1, state2, state3 };
state::state1;
9. 函数指针
指向函数的指针,可作为函数参数
函数名表示这个函数地址
int fun(int a,int b){
return 1;
}
void sun(int(*p)(int,int)) {
用函数指针调用函数
p(1,2);
}
int main() {
函数返回值类型 (* 指针变量名) (函数参数列表);
int(*p)(int, int) = &fun;
sun(p);
sun(fun);
p(1, 2); 调用指针p对应的函数fun
}
类中成员变量函数指针
class A {
public:
int happy(int a,int b) {
return 0;
}
static int s(int a) {
return 0;
}
};
int main() {
A a;
int(A::*p)(int ,int) = &A::happy;
(a.*p)(1,2);
int(*ss)(int) = &A::s;
ss(1);
}
10. 类
定义一个类,里面不写public:,默认为private
C++中类与结构体的唯一区别为默认权限不同
注意:计算类的大小时按照字节对齐的方式计算
class people {
public:
string name;
int age;
enum sex{ 男,女 }sex1;
void breathe() {
cout << "我在呼吸";
}
};
10.1 构造函数
10.1.1 初始化参数列表
引用类型必须赋初值,需要用到初始化参数列表,初始化参数列表只能在构造函数中使用,按照类中定义顺序赋值,而不是根据参数列表顺序。
注意:写有参构造函数后,系统不会默认生成无参构造函数了,此时调用无参构造函数会由于没有无参构造函数而报错。
class A {
public:
int a;
int b;
int& np;
A() :a(10),b(10),np(a){ 无参构造函数,初始化参数列表
}
A(int a):np(a) { 有参构造函数,初始化参数列表
}
};
10.1.2 拷贝构造函数
有两个构造会默认生成,一个是无参数的构造,无内容。一个是参数为本类对象const引用的构造,用来提供复制自身的功能。
值传递会用到拷贝构造函数
- 对象需要通过另外一个对象进行初始化 A a = b;
- 对象以值传递的方式传入函数参数void fun(Node n)
- 对象以值传递的方式从函数返回 return n;
拷贝方式:
4. 浅拷贝: 拷贝时简单的把所有成员的值拷贝一份,默认的拷贝构造就是浅拷贝。
注意:浅拷贝时,如果对象有指针,很容易出现多次释放同一个指针。
- 深拷贝: 重写拷贝构造函数,把会重复释放的部分让两个对象分别拥有自己的空间。
第一步开辟自己的空间,第二步把值赋给该空间
class A {
public:
int t;
int arr[20];
char* str;
string* s; 注意是*string类型
A() {
str = (char*)malloc(10);
}
A(const A& other):t(other.t) { //深拷贝构造函数
this->str = (char*)malloc(10);
memcpy(str,other.str,10); 复制字符串到新开辟的空间
或
str = new char[strlen(other.str)+1];
strcpy(this->str,other.str);
memcpy(arr,other.arr,sizeof(int)*20); 复制数组到新开辟的空间
s = new string; 复制字符串指针
*s = *(other.s);
或
s = new string(*(other.s));
}
~A() {
free(str);
}
};
void fun(A c) { //值引用
用浅拷贝c用完之后释放str空间,主函数结束后a又释放一次,会报重复释放错误
}
int main() {
A a;
fun(a);
}
阻止拷贝构造
7. 使用指针或引用传递。
8. 私有化拷贝构造。
10.1.3 隐式构造
当传入参数不符合时,会检查函数需要的对象的单参数构造函数,有没有符合传入参数的,如果有就隐式构造一个实例
不想被隐式构造的函数,可以在前面加 explicit 关键字
class A {
public:
int t;
A() {
}
explicit A(int t) { 无法被隐式构造的函数
this->t = t;
}
A(int a,int b=0) {
也可视为单参数的构造函数被隐式调用
}
A(double a) {
int参数甚至可以隐式转换到double来隐式构造这个函数
}
};
void fun(A c) {
参数需要一个A类的,通过隐式构造实例
}
int main() {
A b = 6; 右值需要一个A类的,通过隐式构造实例,传入6
fun(5);
}
10.2 析构函数
写法为 ~类名,不写返回值
注意:析构函数不能带有参数
完成一些厚实,不负责释放空间
构造函数与析构函数都必须为public
class A {
public:
~A() {
cout << this;
}
};
构造函数与析构函数顺序
构造父类到子类,析构子类到父类
子类可以选择调用父类的哪个构造函数,但只能调用public的父类构造,会默认调用父类的无参构造函数。
class A {
public:
A(){}
A(int a, int b) {}
};
class B :public A{
public:
B():A(3,2){
显示调用双参数构造函数
}
};
10.3 拷贝赋值
拷贝赋值,分为深拷贝和浅拷贝
为防止重复释放指针,带指针的应采取深拷贝
把自己的释放,然后开辟新空间,赋值字符串到新空间
不能忘记释放原指针,否则会内存泄漏
当 a = b时就会调用operator=函数
class A{
public:
int i;
char* str;
A& operator=(const A& other) { 重写=符号
free(str);
str = (char*)malloc(10);
memcpy(str,other.str,10);
return *this;
}
};
注意:
A a;
A b;
a = b; 调用的是operator=函数
A a = b; 调用的是拷贝构造函数
取决于有没有创建新实例
10.4 static
静态变量,所有对象实例共享,由类名或对象实例调用
注意:
- static修饰的函数内不能使用this指针
- 静态成员变量,不能在类中直接赋值,必须要初始化,要在类外初始化,相当于全局变量,所以不能在主函数中初始化
- 所有对象实例共享一个静态变量
- 静态变量只在类中声明,不占用类空间
class A {
public:
static int a;
static void happy () {
}
};
int A::a = 10; 初始化
int main() {
A::a = 6;
A::happy();
A c;
c.a = 8;
return 0;
}
10.5 继承
三种继承修饰符public、proctected、private
- public继承:继承到的父类属性权限不变
- protected继承:public变为protected,其他不变
- private:全变为private
class student :public people,protected singer{
public:
int num;
void study(){
cout << "我在努力学习";
}
};
c++支持多继承但不建议使用,如菱形继承
B、C都继承A,D同时继承B、C,会导致A的属性被继承了两次,浪费内存,可采用虚继承方式避免。
10.6 友元
提供一个机制,可以打破封装性。在特定的函数或类中,对象能访问私有的成员。
class A {
int a;
friend void fun(int b); 可以在fun函数中中使用A的private的变量或函数
friend class B; 可以在B中使用A的private的变量或函数
};
void fun(int b) {
A a;
a.a;
}
10.7 内部类
在类中定义一个类,也可以有权限修饰符,也可以继承。
内部类视为外部类的友元类,但外部类不是内部类的友元类。
class A {
private:
int c;
public:
class B :public C{
void fun() {
A a;
a.c;
}
};
};
int main() {
A::B b; 定义内部类对象
}
10.8 纯虚类(抽象类)
虚函数:
加 virtual,意为可以被子类重写
一个父类函数是虚函数,它的子类与它具有相同特征的函数也是虚函数
class animal {
public:
virtual void run() { 虚函数
cout << "动物跑";
}
};
纯虚函数:
class animal {
public:
virtual void run() = 0;
};
含有纯虚函数的类被称为纯虚类(抽象类),抽象类可以被继承,但不能生成对象实例,可以声明指针来进行多态
10.9 多态
有虚函数时,父类指针,子类对象,调用子类重载函数
不是虚函数将无法被重写,什么类型指针就调用什么类型的方法(所以父类要用虚构造函数,否则调用父类析构函数无法释放子类内存)
父类函数被子类同名函数隐藏
class animal {
public:
virtual void run() {
cout << "动物跑";
}
}; 也可以写为抽象类形式
class animal {
public:
virtual void run() = 0;
};
class cat :public animal {
public:
void run() {
cout << "喵喵喵";
}
};
class dog :public animal {
public:
void run() {
cout << "汪汪汪";
}
};
int main() {
animal* an = new cat;
an->run(); 喵喵喵
}
10.10 虚析构函数
如果不将析构函数写成虚函数形式,将只会调用A的虚构函数,导致内存没有被释放。所以在父类将析构函数写成虚函数,释放A* a = c; 时,也会调用 B /C 的构造函数。
class A {
public:
virtual ~A() {
cout << "A析构";
}
};
class B :public A {
public:
~B() {
cout << "B析构";
}
};
class C :public B {
public:
~C() {
cout << "C析构";
}
};
void del(A *a) {
delete a;
}
int main() {
C* c = new C();
c有A与B的构造函数,但不可以调用 c->a,会报错
del(c);
}
11. bool
除了0以外,都视为1
bool a = 5;
cout<<a; 1
cout << (a + 5); 6
12. 建立自定义头文件
- 在头文件文件夹中定义头文件,最好在.h文件中只声明函数,函数定义写在头文件对应的cpp文件中,头文件函数定义过多会导致编译缓慢,vs2017对应选项为,右键函数->快速操作和重构->创建声明/定义
- 头文件中不建议使用using namespace等等
- 尽量不要有#include<>包含其他的头文件
- 引用自定义头文件必须用双引号引入,别忘了带.h
#include<iostream>
#include"m_string.h"
防止重复引用,不通用,有些编译器不支持,但方便
#pragma once
通用,但要每个类都写一次
#ifndef _A_
#define _A_
class A {
};
#endif
#ifndef _B_
#define _B_
class B {
};
#endif
13.new与delete
new先申请空间后调用构造函数,delete先调用析构函数后释放空间,在自由存储区(一般是堆区)申请空间
注意:new[]申请的空间要用delete[]释放
string* str = new string("123"); new字符串
delete str;
student* stu = new student; new对象
delete stu;
int *a = new int[20]; new[]数组
delete[] a; delete[]数组
14.const
c语言中,const只读,不能通过当前变量来修改,但可以其他变量指向相同空间来修改。
什么类型的指针解引用后就是什么类型
const变量必须要初始化
const int d = 5;
int* p = &d;
1. const int* c1 = &d; 锁*c1,解引用后是const int类型
2. int const* c2 = &d; 与1一致,锁*c2,解引用后是const int类型
c1 = &a; c1、c2还可以指向其他变量
3. int* const c3 = &d; 锁c3,c3是一个只读的指针变量,指向一个int类型变量
*c3 = 6; *c3的值可以改变
c++觉得用非只读的引用或指针来引用只读的变量不安全,所以要加上强制类型转换
const int& a = pi;
int& a = (int &)d;
int* p = (int *)d;
c++觉得用非只读的引用或指针来引用只读的变量不安全,所以要加上强制类型转换
优化
const字面量优化:当const变量初始化为一个字面值时,c++会把变量当成宏展开,宏要快于使用变量
指针指向的位置改变了,但使用a时进行字面值替换,打印出的值不变
#define Pi 3;
const int d=5;
int main() {
const int a = 10;
int *p = (int*)&a;
*p = 100;
cout << *p; //100
cout << a; //10
return 0;
}
当定义全局常量时,给d赋值会视为5,5没有地址,所以解引用指针会报错
const int d=5;
int main() {
int * p = (int*)&d;
cout << pi; 报错
}
常函数
只有类中普通成员函数可以定义为常函数
常函数中不可对成员变量进行修改,原因为
普通函数内this指针为:T* const this
常函数内this指针为:const T* const this
要想修改成员变量:
方法一:加关键字mutable 修饰成员变量
方法二:将this指针转换为非只读形式
常对象只能调用常函数
函数名和形参表相同的常函数和非常函数构成重载关系,常对象调用常函数,非常对象调用非常函数
class A {
int a;
mutable int b;
void func() const {
A *p = (A*)this;
p->a = 10;
b = 100;
}
};
constexpr
c++ 11中引入的真正常量,不可以赋值变量给他
constexpr int c = 100;
int arr[c];
15. 重载运算符
给运算符赋予意义
此时ab就等同于operator(“aa”,5);
重载运算符函数参数,必须至少含有一个非基本类型参数,string不算基本类型
运算符有几目就需要几个参数,如+就是双目运算符,需要两个参数
全局重载运算符
string operator*(string a,int n) {
for (int i = 0; i < n;i++) {
a += a;
}
return a;
}
int main() {
operator*("aa",5);
string a = "aa";
cout<<a * 5; 相当于调用了*函数
}
成员重载运算符
- 定义在类中的重载运算符函数
- 第一个参数为this指向的对象
- 参数数量为运算数目数减1
class complex{ 复数
public:
double a;
double b;
complex():a(0),b(0) {}
complex(int a, int b):a(a),b(b) {
}
void operator+(complex& m) {
a += m.a;
b += m.b;
}
void toString() {
printf("%g+%gi",this->a,this->b);
}
};
int main() {
complex a(10,20);
complex b(5,6);
a + b;
a.toString();
}
仿函体(函数对象)
在类中进行()运算符重载,可用出函数的效果,但可以用类中的属性做函数做不到的事情
class func {
int count;
public:
void operator()(int a) {
count++;
}
void operator()(int a, int b) {
count++;
}
};
int main() {
func f;
f(10);
f(10,20);
}
sort(a.begin(),a.end(),greater); 中greater就是一个函数对象,我们也可以创造一个函数对象传入sort中
class cmp {
public:
bool operator() (int a, int b){
return a > b;
}
}
int main() {
vector<int> vec = { 5,9,6,3,2 };
cmp c;
sort(vec.begin(), vec.end(), c);}
return 0;
}
16. 模板函数/类
模板函数/类,通过一个模板,批量产生不同类型的函数/类
模板函数:
template<class T,class A> void mswap(T a,int c,A b=10){
T t = a;
a = b;
b = t;
};
template<class T,class A> void show(T a,A b) {
cout << a<< b;
};
模板类:
template<class T,class A> class student {
public:
T a;
student() {}
student(T a, A b) {}
void run(A a) {
cout << a;
}
};
int main() {
mswap<int,double>(10, 50);
student<double, string> stu;
show(10,10); 也可以这样调用模板函数
a.run("加油");
return 0;
}
17. 宏函数/内联函数
宏函数与内联函数可以避免调用函数带来的性能浪费
宏函数注意:
- 使用宏函数要在整个函数体和变量加括号,避免运算符优先级带来的问题
- 宏函数是文本传输,相当于将 i 替换成了a++文本,在调用和参数时不计算,只进行文本替换,在函数体计算a++
- 可用 / 连接
内联函数注意:
- 空间代价换时间,一般代码量小且调用频繁的函数会使用内联函数
- 系统会根据情况决定是否内联,不一定真的会起作用
- 在内联函数内不允许使用循环语句和switch语句
#define unsafe(i) ((i)>0? ((i)+1):((i)-1)) 宏函数
#define unsafe(i) ((i)>0?\ 等同上行
((i)+1):((i)-1) )
inline void fun() {
内联函数,加inline关键字
}
int main() {
int a = 5;
int b = unsafe(++a);
8,加了两次,返回值再+1
fun();
}
18.异常
可以抛 int、vector、const char*、字符串
catch(…)接收所有类型错误
void fun() {
string a = "asd";
throw "abc";
throw 9;
throw a;
vector<int> a = { 1,2,3 };
a.at(5);
}
int main() {
try {
fun();
}
catch (string a) {
cout << a;
}
catch (const char* a) {
cout << a;
}
catch (out_of_range o) { 数组越界错误
cout << o.what(); 别忘加括号
}
catch (...) {
cout << "接收所有类型错误";
}
}
19.迭代器
类似指针,迭代器*重载了,用 *迭代器取值
a.begin()指向第一个元素,a.end() 指向最后一个元素的下一个元素(方便遍历),左闭右开区间
很多函数的参数都采用迭代器传参,
template<class T> void printff(T& a) {
for (auto i = a.begin(); i != a.end(); i++) {
cout << *i << endl;
}
}
int main() {
vector<int> a = { 1,2,3,4,5 };
list<int> li = { 1,2,3,4,5,6 };
for (vector<int>::iterator i = a.begin(); i != a.end(); i++) {
cout << *i << endl;
}
printff(li);
}
20.算法常用函数
1.sort 需要引入algorithm头文件
在oj中比较函数需要设定为静态函数
#include<algorithm>
sort(arr.begin(),arr,end(),cmp);
小于号从小到大排序
static bool cmp(const vector<int> a,const vector<int> b){
return a[1]<b[1]; 根据每个数组第二个元素比较大小
}
也可以lambda达表达式
sort(points.begin(), points.end(), [](const vector<int>& u, const vector<int>& v) {
return u[1] < v[1];
});
2.判断字符
#include <ctype.h>
isalnum('a'); 字母和数字
isdigit(); 数字
ispunct(); 标点符号
isspace(); 空格字符
3.其他
swap(a,b); 交换两个元素
INT_MAX; int最大值
INT_MIN; int最小值
max(a,b); 返回a、b最大值
min(a,b); 返回a、b最小值
abs(a); 返回a的绝对值
const int MOD = 1e9 + 7; 科学计数法
int i = pow(2,j); 2的j次方
4.累计求和
放两个迭代器和初值
int sum = accumulate(vec.begin() , vec.end() , 42);
21.哈希表
哈希表key值唯一,根据key决定放到哪个桶里,桶内装的是<key,value>
要引入unordered_map头文件
unordered_map<string,double> hash;
1. 插入
hash.insert(make_pair<string,double>("eggs",6.0));新建pair插入
hash.insert({{"sugar",0.8},{"salt",0.1}}); 初始化数组插入
hash["coffee"] = 10.0; 数组形式插入
2. 查找
根据key查找,返回迭代器,找不到返回myrecipe.end()
unordered_map<string,double>::iterator got = hash.find("coffee");
got->first访问key值,got->second访问value
map[i]; 访问key为i的元素
3. 修改
hash.at("coffee") = 9.0;
hash["milk"] = 3.0;
4. 删除
hash.erase(hash.begin()); 通过迭代器删除
hash.erase("milk"); 通过key删除
hash.clear(); 清空
5. 计数
hash.size(); 不同key值数量
hash.count(1); key为1的个数,因为key是唯一的,所以值为0或者1
6.遍历
for(auto p:map){
p.first;
p.second;
}
for(auto it = map.begin(); it != map.end(); it++){
it->first;
it->second;
}
22.set
unordered_set<int> s
set1.find(2); //查找2,找到返回迭代器,失败返回end()
s.find(x) == s.end() 说明不存在这个元素
set1.count(2); //返回指2出现的次数,0或1
set1.insert(set1.end(), 4);//指定插入位置,如果位置正确会减少插入时间,返回指向插入元素的迭代器
set1.erase(1); //删除操作,成功返回1,失败返回0
set1.erase(set1.find(1)); //删除操作,成功返回下一个pair的迭代器
set1.erase(set1.begin(), set1.end()); //删除set1的所有元素,返回指向end的迭代器
set1.empty(); //是否为空
set1.size(); //大小
23.pair
pair是一种模板类型,将2个数据组合成一组数据,常用于函数返回值
在创建pair对象时,必须提供两个类型名,两个对应的类型名的类型不必相同
pair<T1, T2> p1; //创建一个空的pair对象(使用默认构造)
pair<T1, T2> p1(v1, v2); //创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2。
make_pair(v1, v2); 创造一个pair
p1.first; p1的第一个元素
p1.second; p1的第二个元素
24. istringstream
能根据空格分隔字符串,无论有多少空格,需引入sstream头文件
#include<sstream>
string s1 = "i am a boy";
istringstream ss(s1);
while (ss>> s1)
{
cout << s1 << endl;
}
25.优先队列
不传比较函默认大顶堆
第一个元素是元素类型,可以为自己定义的,或者基本数据类型。
第二个是容器数据类型,默认是vector。
第三个是比较函数,默认调用 < 。
常用函数:
priority_queue<int> q;
priority_queue<int,vector<int>,greater<int>> q; 最小优先队列
priority_queue<int, vector<int>, less<int>> q; 最大优先队列
q.push(3);
while (!q.empty()) {
cout << q.top();
q.pop();
}
自定义比较器,比较函数必须写在类内
自定义比较函数时,小顶堆要用大于号
1.写在类内,重载小于号运算符
struct node{
int value;
bool operator < (const node &a, const node &b) {
return a.value < b.value; // 按照value从大到小排列
}
};
priority_queue<node>q; 可以不传比较函数
小于号重载函数也可以写在他的友元类中
2.写在另外一个类内,重载()运算符
struct cmp{
bool operator ()(const node &a, const node &b)
{
return a.value>b.value;// 按照value从小到大排列
}
};
priority_queue<node, vector<node>, cmp>q; 需要传入比较函数
26. lambda表达式(匿名函数)
一般配合需要传函数参数使用
[捕获列表] (参数列表) {函数体}
[](int a){ }; 如果没有参数,括号可以省略
[]()->int { return 0;} 可以显示指定返回类型,不写也可以自动推导返回值类型
[](){cout<<"创建时直接调用"}(); 末尾加括号,创建时直接调用
[] 什么也没有捕获
[a, &b] 按值捕获a,按引用捕获b
[&] 按引用捕获任何用到的外部变量
[=] 按值捕获任何用到的外部变量
[a, &] 按值捕获a, 其它的变量按引用捕获
[&b, =] 按引用捕获b,其它的变量按值捕获
[this] 按值捕获this指针,引用捕获指针无意义
lambda表达式相当于创建了一个函数对象,值捕获的是只读的
struct lambda {
const int a;
const int b;
int& c;
lambda(int _a, int _b, int& _c) :a(_a), b(_b), c(_c) {
}
void operator()() {
cout << a + b;
c = 999;
}
};
int main() {
int a = 1, b = 2, c = 3;
auto f = [a, b, &c]() {cout << a + b; };
f();
return 0;
}
注意: 假如我们想修改按值传进来的形参,将会发生错误,因为按值捕获的对象不能被修改,如果想修改要加mutable关键字
这时我们需要假如一个mutable,如下
int n = 10;
[n](int a)mutable {
n += a; // 正确
return n;
};
27.位运算常用函数
unsigned int a; 无符号整数
#include<bitset> 需引入头文件bitset
bitset<32>(3).count(); 计算二进制中1的个数
bitset<32>(3).to_string(); 返回二进制的字符串表示
bitset<32>(3).flip(); 取反所有位
28.队列
队列取顶元素用front
queue<int> que;
que.front();获取首元素
que.back(); 获取尾元素
que.push(); 加入队列尾
que.pop(); 移除队列首元素
双向队列
deque<int> dque;
dque.front(); 获取首元素
dque.back(); 获取尾元素
dque.pop_back() 删除尾部的元素
dque.pop_front() 删除头部的元素
dque.push_back() 在尾部加入一个元素
dque.push_front() 在头部加入一个元素
29. C++ 11 特性
1. 类型名重定义
using it = int;
2. nullptr
nullptr表示各种类型的空指针
NULL在C++中实质上是int类型的0,在函数重载中有二义性
注意:重载时int*,double*都能接收nullptr,要注意这种情况下nullptr的使用
nullptr
3. C/C++ 混合编程
在编译时c和c++编译出的函数名不一致,所以统一按照 C 的命名方式编码
用extern “C”完成C语言格式的编码,但C语言不支持extern
所以用__cplusplus这个cpp内置宏变量判断处在cpp环境还是c环境
cpp环境extern有效就改为c命名方式
c环境屏蔽掉extern(因为c不支持该关键字),也完成了c的命名方式
这样c/cpp就能共用同一个头文件了
#pragma once
#ifdef __cplusplus 如果在cpp环境,就改为c格式的编码
extern "C" {
#endif
void fun();
#ifdef __cplusplus
}
#endif
4. 整形提升规则
所有表达式或变量计算时如果小于4个字节,就转换为4个字节来计算
如char 和 short 计算时会直接转化为 int 计算
原因:32位操作系统操作数据的基本单位是4个字节,无法单独操作char和short,所以需要转换为int
同类型的有符号和无符号运算会转换为无符号类型再进行运算
5. 原始字符串
里面的字符就不会进行转义之类的操作,为防止字符串中出现)",即该函数的结束符,我们可以自定义结束字符串,这样就不会提前结束
int main() {
string str = R"(""aefdg\\ft")";
string str1 = R"123456(safwea.'/)123456";
cout << str;
return 0;
}
6. 自定义字面量后缀
可以在使用后缀时,调用相应的函数,用重写“”符号来定义
参数可以是unsigned long long,char等,返回值可以直接作用到该数值上,返回值类型为该数的类型
例:制造该数的平方
int operator""sq(unsigned long long n) {
return n * n;
}
int main() {
int a = 12sq;
cout << a; //144
cout << 16sq; //256
return 0;
}
7. 可执行体
c++ 可执行体包括函数,模板函数,函数指针,仿函体,lambda对象,bind
()函数返回值等
可以用function储存对应的可执行体function<返回值<参数类型,参数类型>>
也可以作为函数参数、返回值类型等
#include<functional>
int main() {
function<void()> f = [a, b, &c]() {cout << a + b; };
f();
function<int(int, int)> f1 = [](int a, int b)->int { return 0; };
f1(1,2);
return 0;
}
8. bind函数适配器
bind函数可以对函数参数进行调整,但函数本身参数是不变的
placeholders::_1,表示传入的第一个参数
多参数变少参数:多的参数用占位符或其他变量、字面量代替
少参数转多参数:在传入的多个参数中挑选一个,或哪个都不选,设置一个默认值
转换后的参数可以直接加括号调用
void fun1(int a) {
cout << a << endl;
}
void fun2(int a, int b) {
cout << a << b << endl;
}
void fun3(int a, int b, int c) {
cout << a << b << c << endl;
}
void buck(function<void(int, int)> f) {
f(1,2);
}
int main() {
function<void(int, int)> new3 = bind(fun3,3,placeholders::_2,placeholders::_1);
function<void(int, int)> new1 = bind(fun1,placeholders::_2); //传两个参数,用占位决定用哪个
new1(1, 2); //2
buck(fun2); //12
buck(new3); //321
buck(new1); //2
buck(bind(fun3, 1000, 52, 521)); //100052521
buck(bind(fun3,1000, placeholders::_2, placeholders::_2)); //100022
return 0;
}
成员函数转全局函数,要有public权限
class A {
public:
void run(int a,int b) {
cout << this<<endl;
}
};
int main() {
A a;
function<void(int, int)> f = bind(&A::run,a,placeholders::_1, placeholders::_2);
f(1,2);
return 0;
}
默认参数变量传递时为值传递
void add(int& a, int& b) {
a++;
b++;
}
int main() {
int m = 1, n = 1;
function<void(int&)> f = bind(add, placeholders::_1, n);
f(m);
cout << m << n; //21
return 0;
}
9. 类型转换
1.静态类型转换
static_cast<int>:用于基本类型转换,派生类型向上转型,编译期间确定的指针类型转换
子指针转父指针安全,父指针转子指针不一定安全,不同类型指针转换不一定安全
class A {
};
class B: public A {
};
int main() {
char* p = (char*)malloc(10);
char* p1 = static_cast<char*>(malloc(10));
int i = static_cast<int>(5.4);
A *a;
B *b = new B();
a = static_cast<A*>(b);
return 0;
}
2.常量类型转换
const_cast<int*>:用于去掉指针的const修饰符
const int a = 10;
const int *p = &a;
int* b = const_cast<int*>(p);
3.动态类型转换
dynamic_cast<A*>:用于安全的在父子类间转换,类中有虚函数(根据虚指针判断是否安全)
转换指针时不安全返回空指针
转换引用时,不安全抛bad_cast异常
class A {
virtual void run() {}
};
class B: public A {};
int main() {
A* pa;
A a;
A& ra = a;
B* b = new B();
pa = dynamic_cast<A*>(b);
if (pa != nullptr) {
cout << "转换成功";
}
try{
B& rb = dynamic_cast<B&>(ra);
}
catch (bad_cast b) {
cout << b.what();
}
return 0;
}
4.重新解释转换
reinterpret_cast:不常用,只能用于转换指针,转换类型时指针二进制内容不改变
class A {
};
class B: public A {};
class C :public A {};
int main() {
A a;
A* pa = &a;;
B* b = reinterpret_cast<B*>(pa);
C* c = reinterpret_cast<C*>(pa);
cout << b << " " << c; //相同
return 0;
}
10. 断言
assert
当不满足assert中的条件时退出程序并报错,一个断言最好只写一种条件判断,否则无法准确判断出不满足哪个条件
当想让所有断言失效可以在#include<assert.h>头文件前面,定义NDEBUG宏
#define NDEBUG
#include<assert.h>
using namespace std;
void getAge(int age) {
assert(age >= 0 && age <= 150);
cout << age;
}
int main() {
getAge(-1);
return 0;
}
static_assert
静态断言
c++ 11引入的,在编译时就进行检查,不满足表达式就报错,第二个参数为提示错误信息
判断是不是64位机器
static_assert(sizeof(int) == 4, "no");
11. array
储存在栈中,不可以动态扩容
vector储存在堆中,可以动态扩容
array<int.20> arr;
12. set、map、unordered_set.map自定义类型
se、mapt底层实现为红黑树(高级平衡二叉树,最多旋转三次就可以把树变平衡),所以需要一个用于比较两个对象大小的函数,查找效率logn
方法一:传入可用于比较大小的仿函体
注意写友元才能直接访问私有元素,注意比较函数要是public函数
方法二:在类内重写<小于号
注意要为const函数,且访问权限也要为public
map与set操作一致,只是比较的为key值
class student {
string sno;
friend class cmpStudent;
public:
bool operator<(const student& stu) const{ 方法一
return this->sno < stu.sno; 从小到大
}
};
class cmpStudent { 方法二
public:
bool operator()(const student& a,const student& b) {
return a.sno < b.sno; 从小到大
}
};
int main() {
set<student, cmpStudent> s; 方法一
set<student> s1; 方法二
return 0;
}
unorxdered_set.map底层用哈希函数实现,所以要重写哈希函数和比较函数,哈希决定元素位置,比较决定是否插入(相同元素不会重复插入)
方法一:创建两个仿函体,一个用于哈希,一个比较是否相等,注意public和const
方法二:创建仿函体哈希函数,自身类内重写==符号,注意public和const
class student {
string sno;
friend class equalStudent;
friend class hashStudent;
public:
student(string sno) {
this->sno = sno;
}
string getSno() {
return sno;
}
bool operator==(const student& a) const{
return this->sno == a.sno;
}
};
class hashStudent {
public:
size_t operator()(const student& a) const{
hash<string> hs;
return hs(a.sno);
}
};
class equalStudent {
public:
bool operator()(const student& a,const student& b) const{
return a.sno == b.sno;
}
};
int main() {
unordered_set<student, hashStudent, equalStudent> s;
unordered_set<student, hashStudent> s1;
return 0;
}
13. initializer_list
c++11引入的,在c++98、03中无法使用vector vec = {1,2,3};方式初始化
void get(initializer_list<int> arr) {
arr.begin();
arr.size();
for (auto i : arr) {
cout << i;
}
}
int main() {
get({ 1,2,3,4 });
return 0;
}
14. typeid
可以获取变量类型名
当对象为空指针且类中有虚函数,会报bad_typeid错误
空指针但类中无虚函数,不报异常
class A {
virtual void fun(){}
};
int main() {
int a;
cout << typeid(a).name(); //int
A b;
cout << typeid(b).name(); //class A
A *pa = nullptr;
try {
cout << typeid(*pa).name();
}
catch (bad_typeid b) {
cout << b.what();
}
return 0;
}
15. 右值、右值引用
左值:能获取地址的值,如变量、引用
右值:c++ 11右值分为两种(新引入了将亡值)
1、纯右值:如匿名对象、字面量
2、将亡值:函数的返回值如果为右值,name返回值为将亡值
move函数返回值是将亡值
将左值强制转化为右值类型(move原理)
右值引用int&&表示
右值引用实际上是左值
不同引用可以指向的数据类型
右值引用和常量左值引用都可以引用右值,但常量左值引用不可以进行更改
class Node {
};
int main() {
const int a = 10;
const int& b = a;
int&& p = 3; //可以进行取地址操作
p = 5;
cout << p;
const int& c = 5; //右值引用和常量左值引用都可以引用右值,但常量左值引用不可以进行更改
int c = 3;
int &&pp = static_cast<int&&>(c); //可以用static_cast进行类型转换为右值
Node&& n = Node(); //匿名对象属于右值
const Node&& n1 = const Node(); //常量匿名对象只能用常量右值引用
return 0;
}
16. 移动构造、移动赋值
当传入值为右值时,会进行移动构造或拷贝
用于处理a初始化b后就将a析构的情况,让b指向相应成员变量,让a指针为空,所以析构a时不会影响b的,不会造成相同指针重复析构,同时又节省了空间
class Node {
public:
char* name;
Node(const char* name) {
this->name = (char*)malloc(strlen(name) + 1);
strcpy(this->name, name);
}
Node(Node&& o) {
cout << "移动构造";
name = o.name;
o.name = nullptr;
}
Node& operator=(Node&& o) {
cout << "移动赋值";
free(name);
name = o.name;
o.name = nullptr;
return *this;
}
};
Node getNode() {
Node a("小赵");
return a;
}
int main() {
Node rn = getNode(); //移动构造,小赵
Node n("迪迦");
Node n2(move(n)); //移动构造,迪迦
n = getNode(); //移动赋值,小赵
return 0;
}
swap底层实现为移动赋值,这样减少了三次复制字符串开辟空间和复制的操作,性能更加优秀
void mySwap(Node& a, Node& b) {
Node temp = move(a);
a = move(b);
b = move(temp);
}
17. 引用折叠
c++ 11中模板参数和形参都出现了引用,根据指定规则转化为左引用还是右引用
当出现一个左引用就会转化为左引用
当不出现左引用,且右引用出现至少一次就是右引用
所以参数类型最好设置为TR&& v,这样传来左引用就是左引用,传来右引用就是右引用
templatevoid run(TR v){ }
18. 完美转发
想在函数处理完参数时将参数传入其他函数,但左右值属性不变(如接受一个右值,但int&& a实质上已经是一个左值了),这时就会用到完美转发
otherfun(std::forward(a)); 用forward函数就可以实现上述要求,注意别忘写T
template<typename T>void fwd(T&& a) {
otherfun(std::forward<T>(a));
}
19. 新关键字
c++ 11引入了一些新语法和关键字
1.类中成员初始化
class T {
public:
int a = 10;
};
2.default
指示系统生成对应的默认函数,增强可读性
class T {
public:
T() = default;
T(const T&) = default;
};
3.delete
用于删除函数,可以让指定的的默认函数不再生成
会报尝试引用已删除函数的错误
class T {
public:
T() = delete;
T(const T&) = delete;
};
4.override
用于检查是否对父类函数进行了重写,没有进行重写会报错
class T {
public:
virtual void run(){}
};
class S :public T {
void run() override{};
};
5.final
使用final的类不可以被继承,使用final修饰的函数不可以被重写
注意:final只能修饰虚函数
class T final{ };
class S {
virtual void run() final{};
};
6.可以用{}初始化对象,但要写相应的构造函数
class So {
int a;
string str;
public:
So(int a,string str):a(a),str(str){}
};
int main() {
So so{ 1,"有点想你" };
return 0;
7.增强sizeof
可以直接用sizrof(A::val)的方式获得非静态成员的大小
cout << sizeof(So::a);
20. POD类型
plain old date 普通旧数据类型,即与C语言兼容的类型,可以用malloc创建对象,类中不能有自定义的构造函数、析构函数、没有虚指针(虚函数和虚继承)
类中成员不能进行初始化(malloc无法进行初始化)
系统默认的构造与析构可以,即使里面什么都不写,也不可以定义构造与析构函数
#include<type_traits>
is_pod模板类的静态属性value可以判断是否是POD函数
class So {
int a;
So() = default;
void da(){
}
};
int main() {
cout << is_pod<So>::value;
cout << is_pod<So>().value;
return 0;
}
21. 委托构造、继承构造
委托构造:
在一个构造函数中调用了另一个构造函数,然后再来执行自己
限制:使用委托构造,初始化参数列表不能写其他值了
优点:可以把代码都写在其中一个函数中,由其他构造函数调用,节省代码
class student {
int age;
double score;
string name;
public:
student(int age) :student(age, "默认名称") { cout << 1; }
student(int age,string name):student(age,name,0){ cout << 2; }
student(int age, string name, double score) :age(age), name(name), score(score) { cout << 3; }
};
int main() {
student stu(19); //运行顺序为321
return 0;
}
继承构造:
正常情况下,构造方法不会被继承,要在子类写相同参数的构造函数用它再调用父类的构造函数
子类没有新功能加入,想使用父类属性也要写很多构造函数,于是引入了继承构造
using T::T,可以继承所有父类构造方法
class stu :public student{
public:
using student::student;
stu(int age):student(age){} //没有继承构造就要这样写
};
int main() {
stu t(1);
stu tt(9, "s");
return 0;
}
22. 外部模板
每个文件使用一次模板实例化都会编译出一份,但连接时又要移除每个文件相同的实例化代码
于是引入了外部模板,代表这个类型的模板函数已经在别的文件实例化了,直接使用即可,提升了编译的效率
但如果该函数没在其他文件生成,同时由于使用外部模板,自己也不生成了,就会报找不到外部符号错误
extern template void run(string);
template<typename T>void run(T a) {
cout << a;
}
int main() {
run(1);
run("asdf"); 会报找不到外部符号错误
return 0;
}
23. 模板参数包
template<typename… T>void run(T… a),可接受0个或多个参数,但单独使用其中的参数需要使用递归打印
sizeof…(args)或sizeof…(T)可以打印参数个数
递归打印原理为每次剥离出第一个参数,然后递归调用剩余参数,继续剥离,注意要写一个无参的同名重载函数,如果不写传入空参数会出现无法接受而报错(打印至少接收一个参数)
void unPacking() {
cout << "打印结束";
}
template<typename T, typename... S>void unPacking(T a, S... args) {
int f = sizeof...(args);
unPacking(args...);
}
template<typename... T>void run(T... a) {
unPacking(a...);
}
int main() {
run(1,2,3,4,5); //12345答应结束
return 0;
}
24. 自动推导返回值类型
c++ 11可以用下面这种方式定义函数返回值
auto fun()->int {}
decltype()可以获取类型,二者结合可以得到自动推导返回值类型的模板函数
template<typename T1, typename T2>auto add(T1 a, T2 b)->decltype(a + b) {
return a + b;
}
25. 线程库
1.创建线程
#include<thread>
run为线程运行的函数
thread t(run);
调用它的线程会被block,直到线程的执行被完成
thread t(run);
t.join();
2.线程锁
#include<mutex>
mutex lk;
void run() {
for (int i = 0; i < 100000; i++) {
lk.lock();
timer++;
lk.unlock();
}
}
随着lg对象的销毁而解锁
int timer = 0;
mutex lk;
void run() {
for (int i = 0; i < 100000; i++) {
lock_guard<mutex> lg(lk);
timer++;
}
}
条件变量,当满足条件时才运行
void run() {
for (int i = 0; i < 100000; i++) {
超出作用域才进行解锁
unique_lock<mutex> lg(lk);
不满足条件就一直阻塞
cv.wait(lg, []() { return timer > -1; });
timer++;
}
}
原子变量,对该类型的变量的操作都是原子操作
atomic_类型名
#include<atomic>
atomic_int timer = 0;
void run() {
for (int i = 0; i < 100000; i++) {
timer++;
}
}
26. 四种智能指针
c++提供的系统自动管理内存的方法
1. auto_ptr
c++98引入的,实现了内存的自动释放
缺点:看似拷贝构造,实为移动拷贝,不符合常理,也无法应用到vector中(push_back中使用的是拷贝构造)
只触发了一次构造函数
class node {
public:
int a = 1;
node() { cout << "构造函数"; }
~node() { cout << "析构函数"; }
void run() { cout << "run"; }
};
int main() {
auto_ptr<node> ap1(new node);
auto_ptr<node> ap2 = ap1;
ap2->run();
(*ap2).a;
return 0;
}
2. unique_ptr
c++11引入的,同样的一个指针指向一块堆内存,禁用了拷贝构造(会报错),只能使用移动构造的形式,也可以用移动构造的方式使用push_back了
添加了删除器,可以用delete[]或其他方式释放空间了
struct array_delete {
void operator()(node* p) {
delete[] p;
}
};
int main() {
unique_ptr<node> ap1(new node(1,2));
unique_ptr<node> ap = make_unique<node>(1, 2); c++14引入
//unique_ptr<node> ap2 = ap1; 明确禁止,会报错
unique_ptr<node> ap2 = move(ap1);
vector<unique_ptr<node>> arr;
arr.push_back(move(ap1));
删除器
unique_ptr<node, array_delete> ap3(new node[5]);
return 0;
}
3. shared_ptr
多个shared_ptr指向同一个对象,多一个指向计数区加一,直到计数区为0才销毁对象
有真正的拷贝构造函数了
int main() {
shared_ptr<nodes> ap1(new nodes);
shared_ptr<nodes> ap2 = ap1;
shared_ptr<nodes> ap3 = ap2;
对象的被指向数
cout << ap2.use_count();
return 0;
}
shared_ptr删除器,第二个参数为lambda表达式
shared_ptr<nodes> arr(new nodes[5], [](nodes* o) {delete[] o; });
shared_ptr造成的循环引用问题,node1需要node2(node2->pre指向node1,使node1计数区无法为0)销毁才能释放,node2需要node1(node1->next指向node2)先销毁,造成两个变量都无法释放,此时就需要weak_ptr解决循环引用问题
struct nodes {
shared_ptr<nodes> next;
shared_ptr<nodes> pre;
nodes() {
cout << "构造函数" << endl;
}
~nodes() {
cout << "析构函数" << endl;
}
};
int main() {
shared_ptr<nodes> ap1(new nodes);
shared_ptr<nodes> ap2(new nodes);
ap1->next = ap2;
ap2->pre = ap1;
return 0;
}
4.weak_ptr
弱引用指针,专门用来解决shared_ptr的循环引用问题
资源不会在意weak_ptr的管理,即使weak_ptr存在资源也会被释放,所以资源不存在返回空
将next、pre定义为weak_ptr类型就可以解决循环引用的问题(node12的计数区都为1)
weak_ptr没有重写*,->运算符,只能通过lock函数转化为shared_ptr指针,如果weak_ptr指向的资源为空,将返回一个空的shared_ptr指针
struct nodes {
weak_ptr<nodes> next;
weak_ptr<nodes> pre;
int val;
nodes() {
cout << "构造函数" << endl;
}
~nodes() {
cout << "析构函数" << endl;
}
};
int main() {
shared_ptr<nodes> ap1(new nodes);
shared_ptr<nodes> ap2(new nodes);
ap1->next = ap2;
ap2->pre = ap1;
ap1->val = 10;
shared_ptr<nodes> ap3 = ap2->pre.lock();
cout << ap3->val;
return 0;
}