C++笔记

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()); 反转数组

别人写的vector教程

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(13); 截取包括索引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);
  1. 定义必须初始化
  2. 引用不可改变指向
  3. 不存在空引用

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引用的构造,用来提供复制自身的功能。

值传递会用到拷贝构造函数

  1. 对象需要通过另外一个对象进行初始化 A a = b;
  2. 对象以值传递的方式传入函数参数void fun(Node n)
  3. 对象以值传递的方式从函数返回 return n;

拷贝方式:
4. 浅拷贝: 拷贝时简单的把所有成员的值拷贝一份,默认的拷贝构造就是浅拷贝。
注意:浅拷贝时,如果对象有指针,很容易出现多次释放同一个指针。

  1. 深拷贝: 重写拷贝构造函数,把会重复释放的部分让两个对象分别拥有自己的空间。
    第一步开辟自己的空间,第二步把值赋给该空间
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

静态变量,所有对象实例共享,由类名或对象实例调用

注意:

  1. static修饰的函数内不能使用this指针
  2. 静态成员变量,不能在类中直接赋值,必须要初始化,要在类外初始化,相当于全局变量,所以不能在主函数中初始化
  3. 所有对象实例共享一个静态变量
  4. 静态变量只在类中声明,不占用类空间
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

  1. public继承:继承到的父类属性权限不变
  2. protected继承:public变为protected,其他不变
  3. private:全变为private
class student :public peopleprotected 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. 建立自定义头文件

  1. 在头文件文件夹中定义头文件,最好在.h文件中只声明函数,函数定义写在头文件对应的cpp文件中,头文件函数定义过多会导致编译缓慢,vs2017对应选项为,右键函数->快速操作和重构->创建声明/定义
  2. 头文件中不建议使用using namespace等等
  3. 尽量不要有#include<>包含其他的头文件
  4. 引用自定义头文件必须用双引号引入,别忘了带.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;	相当于调用了*函数
}

成员重载运算符

  1. 定义在类中的重载运算符函数
  2. 第一个参数为this指向的对象
  3. 参数数量为运算数目数减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. 宏函数/内联函数

宏函数与内联函数可以避免调用函数带来的性能浪费

宏函数注意:

  1. 使用宏函数要在整个函数体和变量加括号,避免运算符优先级带来的问题
  2. 宏函数是文本传输,相当于将 i 替换成了a++文本,在调用和参数时不计算,只进行文本替换,在函数体计算a++
  3. 可用 / 连接

内联函数注意:

  1. 空间代价换时间,一般代码量小且调用频繁的函数会使用内联函数
  2. 系统会根据情况决定是否内联,不一定真的会起作用
  3. 在内联函数内不允许使用循环语句和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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值