C++基础与STL

一、基础知识

1.1. 数据类型大小sizeof()

数据类型32位机大小(Byte)64为机大小(Byte)
char11
short22
unsigned short22
int44
unsigned int44
long48
unsigned long48
float44
double88
指针48

struct:内部最大数据类型 大小的整数倍;
union:内部最大数据类型 大小;
换算
1Byte = 8 bit
1KB = 1024 B = 2^10 B;

1.2. const 与define

用于定义一个常量,不可更改且必须定义时赋值。与#define相同。

const后面跟谁修饰谁
int * const a: 修饰指针,指向地址不可变
const int *a: 修饰int表示指针指向的变量为常量。
int const a与const int a相同。

1.3. #include

include标准库头文件既可以<>也可以“”,用户自定义头文件只能“”

1.4. static、extern与const

1.4.1. static

作用于局部变量时为 静态局部变量,变量作用域不变但只分配一块内存,具有记忆功能。

作用于全局变量或函数时,表明该变量或函数只可在当前文件中被调用(内部变量),不能被 被引用的文件 调用。

当static修饰类属性时,表示该属性所有对象共享一个(一份内存),并且在类创建时就创建了(先于对象创建)。但是必须在类内声明类外定义,并且定义之后才分配内存这是由于如果 类内就定义了,这时每次创建一个对象 都会做这样一次定义,显然与 共享变量一份内存 相违背。

static方法不能用this、只能访问类static属性、不可为虚函数、不能为const。

class human
{
public:
	static int fingers;
	string country;
	static void resolution(int n){fingers+=n;}	//只能访问static属性
	void deresolution(int n){fingers-=n;}	//非static函数可以访问
	human(string c){country = c;};
	virtual ~human(){};
};
int human::fingers = 10;		//类内声明类外定义,与类同级

void fun()
{
	static int x=2;
	x++;
};
int main()
{
	fun();		//x为3
	fun();		//x为4
	
	human h1 = human("China");
	human h2 = human("America");
	h1.resolution(2);
	cout<<h2.fingers;		//打印12
	return 0;
}

1.4.2. extern

可以跨文件使用的变量。

									a.h
int i = 10;
									b.h
#include"a.h"
int main(){
extern int i;		//系统将从a中寻找变量i
int x = i+1;		//x为11
return 0;
}
									c.h
#include"a.h"
int main(){
extern int i;
int y = i+2;		//y为12,extern不具有记忆性
return 0;
}

1.4.4. const

与static类似,但必须声明时定义,且之后不可再更改。
可以extern const int a = 10;声明定义可夸文件使用常量。

1.5. new与delete

new可以创建一个指定大小的变量,并传送一个指针,且可以在程序运行时选择对象,实现动态联编。
而delete则会释放使用new构造的指针指向的对象,而不是指针本身

而一个函数中,new一个指针。如果没有delete则只会释放该指针,加上delete就会释放指针指向的内存。

#include <iostream>
using namespace std;
void fun1(int * &a)
{
    int *tmp = new int(10);
    a = tmp;
};
void fun2(int * &a)
{
    int *tmp = new int(10);
    a = tmp;
    delete tmp;
};
void fun3(int * &a)
{
    int tmp = 10;
    a = &tmp;
};
int main() {
	int *a = new int(5);
	fun1(a);
    cout<<*a;			//局部变量*tmp被系统释放,但int(10)没有,打印10;
    fun2(a);
    cout<<*a;			//局部变量*tmp被系统释放,int(10)被delete,野指针;
    fun3(a);
    cout<<*a;			//局部变量tmp被系统释放,野指针;
    return 0;  
}

1.6. new与malloc的区别

int * a = (int *)malloc(sizeof(int));
free(a);

int * a = new int;
*a = 10;
delete a;

0.本身
new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件stdlib支持。

1.输入
使用new操作符 无须指定内存大小,编译器会自行计算。
malloc则需要显式指出所需内存的大小。

2.输出
new操作符内存分配成功时,返回对象类型的指针
malloc输出为(void *)类型,( void *表示可以指向任意类型的抽象指针,类似于抽象类的概念不能实例化但可以指向具体类型) 需要 强制类型转换 转换成 指向对象类型的指针。

3 内存分配失败
new内存分配失败时,会抛出bac_alloc异常
malloc分配内存失败时返回NULL。

4.自定义类型
new会先调用operator new函数,先申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)注意delete删除的不是指针而是对象!。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做类型对象构造函数和析构工作。

5.重载
允许重载new/delete操作符,指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化。
malloc不允许重载。

1.7. 引用与指针的区别

1、指针是一个实体,需要分配内存空间,存放对象地址
引用只是变量的别名,不需要分配内存空间。

2、指针定义 可以不指向(随机指而不是NULL),并且指向的空间可变,安全性差
引用定义 必须初始化,并且不能够改变,安全性高

3、sizeof 引用 得到 所指向的变量(对象)的大小,
sizeof 指针 得到 指针本身的大小(64位系统8字节,32位系统4字节)。

1.8. 函数重载(函数多态)

一个接口不同实现。允许同类型输出、函数名称相同 但 不同输入(类型、形参个数 之一有区别即可) 的多个函数同时存在

void a(int x){};
void a(int x,int y){};
void a(string s){};
void a(int y){};			//错误,与第一个重复

1.9. 随机数

实现真正的随机数,包括种子更改 和 随机数生成。
使用时间作为种子。

  • 包含文件为stdlib.h和time.h
  • srand((unsigned)time(NULL)) 输入 种子
  • rand() 输出 0~INT_MAX 的随机整数
  • rand()%(b-a+1) + a 输出 [a,b] 的随机整数
  • rand()/double(RAND_MAX) 输出 0~1 随机浮点数
#include<stdlib.h>
#include<time.h>
int main()
{
	srand((unsigned)time(NULL));
	cout<< rand()/double(RAND_MAX);
	return 0;
}

1.10. Lambda函数

在 使用函数的位置 直接定义 一个匿名函数。
使用一个函数,需要先声明,再定义,再使用。而Lambda函数能够 不声明,原地定义再使用。即
[ 外部动态变量 ]( 变量类型1,形参1,变量类型2,形参2,…,变量类型n,形参n ){函数定义}
外部动态变量表示可以使用的外部变量,[=] 表示按值传递,加上mutable才可以函数内部改变值。
[&] 表示所有变量引用传递。 lambda函数不用写明返回类型,会根据return 自动判断。

int a = 1;
int b = 2
cout<<[a](int x){return x+a;}(3);
cout<<[&a](int x){a++;return x+a;}(3);
cout<<[](int x){return x+1;}(3);

cout<<[=](int x){return x+a+b;}(3);
cout<<[&](int x){a++;return x+a;}(3);
cout<<[=]mutable(int x){a++;b++;return x+a+b>0;}(3);
auto fun = [=]mutable(int x){a++;b++;return a+b+x;};		//最后的分号别忘了;
cout<<fun(1);

二、 面向对象设计技术

2.1. 对象的构造与析构

class student{
    public:
    int index;
    char name;
    student(int i,char n){this->index = i;this->name = n;}
	~student(){}		// 注意析构函数的写法,里面什么都没有
};						//分好 别忘了!!!


int main()
{
	student s = student(1,'a');		// 栈区 分配内存,系统自动执行析构,即删除对象
	student s(1,'a');		// 栈区 分配内存,系统自动执行析构,即删除对象
	
	student * s = new student(1,'a');		// 堆区 分配内存,需要手动delete执行析构
	delete s;
	
	return 0;}

2.2. 面向对象的设计特点:封装

封装:类的属性和方法实现细节无需知道
struct和class的区别
struct内容全部为public,而class(默认属性方法均为private)可以指定private、protected与public。
class还可以继承和派生,而struct不行。

类权限修饰符
private: 自身类和友元可以访问,方法和属性——用户不能访问,类——其他类不能访问
public: 均能访问
protected: 子类(继承类)可见,其他类、用户不可见
注:结构体默认为public,类默认为private

2.3. 面向对象的设计特点:继承

继承(派生):类属性和方法 按照一定方式 全部传递给 子类,子类有父类所有的 属性和方法(is-a关系),但 权限会参照继承方式。

2.3.1. 继承方式

继承方式指 父类属性和方法 传递至 子类属性和方法 时权限是否发生变化
公有继承public不改变基类权限,私有继承会降级(protected继承会使基类public→子类protected,private继承会使基类全部→子类private),默认为private继承

class plane
{
protected:
	double weight= 10;
	double height=10;
public: 
	plane(double w, double h) { weight = w; height = h; };		
	~plane() {};
};								//分号细节

class missile
{
public:
	double damage = 0.1;
	missile(double d) { damage = d; }
	~missile(){}
};

class flighter:public plane,private missile		//双继承
{
public:
	int ammo;
	flighter(int a, double w, double h,double d) :plane(w, h), missile(d){ ammo = a; };   //子类如何构造
	~flighter() {};
};
int main()
{
	plane p = plane(1, 2);
	flighter f = flighter(1, 2, 3,4);
	f.ammo;				//子类public属性,正确
	f.weight;				//父类protected属性,public继承,错误
	f.height;				//父类protected,public继承,错误
	f.damage;				//父类public,private继承,(protected继承也是)错误
	return 0;
}

2.3.2. 不可继承

父类构造函数、析构函数不可被继承

2.3.3. 继承顺序

构造父类->构造子类
析构子类->析构父类

2.4. 面向对象的设计特点:多态

多态:父类的方法 对不同的对象 有相同的接口但不同的实现。多态在继承的基础上实现的。

2.4.1. 虚函数、纯虚函数与抽象类

虚函数: 父类中的方法,在继承给 子类及孙类 之后,该方法 可以(非必须) 更改实现,但新的实现依旧为虚函数(即使不写virtual)。

纯虚函数:父类中没有任何实现(内容)的虚函数,但输入必须给定,在继承给 子类 之后,该方法可以(非必须) 被重新覆写。(虚函数就是有了内容的纯虚函数)

抽象类:含有 纯虚函数的类,不能创建对象,但可以创建指针指向子类对象。含义就是抽象的类型,例如动物、人、武器等等抽象的概念,而子类就是具象化例如狮子、学生、步枪等。

注意:如果父类是抽象类,子类如果不覆写纯虚函数,则子类也拥有纯虚函数而成为抽象类。
多态的意义在于,仅通过父类指针指向不同的子类对象 调用同一个接口却有着不同的实现(动态绑定),也可以将 父类引用为子类对象

class plane
{
public:
	int weight;
	plane(int w){weight = w;}
	~plane(){};
	virtual void show(string s) = 0;
	//纯虚函数构造,注意输入得定义,因此plane为抽象类
};

class helicopter:public plane
{
public:
	helicopter(int w):plane(w){};
	~helicopter(){};
	void show(string s){cout<<"This is a helicopter: "<<s<<endl;
	//覆写父类纯虚函数,依然为虚函数
};

class uav:public plane
{
public:
	uav(int w):plane(w){};
	~uav(){};
	void attack(){cout<<"attacking!"<<endl;}
	void show(string s){cout<<"This is an uav:"<<s<<endl;
};

class flighter:public plane
{
public:
	flighter(int w):plane(w){};
	~flighter(){};
	//未覆写父类纯虚函数,因此flighter依然为抽象类
};

int main()
{
	plane p = plane(0);	//抽象类不可实例化,错误
	helicopter h = helicopter(1);
	uav u = uav(2);
	flighter f = flighter(3);//抽象类不可实例化,错误

	plane * ph = &h; //父类指针,指向子类,正确
	plane * pu = &u; //父类指针,指向子类,正确
	plane * pf = &f; //父类指针,指向子类,正确
	plane & pp = h;	//父类引用子类,正确

	ph->show("dark eagle"); 
	pu->show("rainbow");
	pu->attack(); //父类中不含有attack()方法,错误
	pf->show("J20");//抽象类纯虚函数无定义,因此不能使用,错误
	return 0;
}

2.4.2. 静态联编与动态联编

联编:将各函数调用联系起来。

静态联编(默认,效率高):程序运行之前,编译阶段就完成了联编。

动态联编:编译阶段并不能知道应该调用的函数,只有在程序执行过程中才知晓。(只知道调用的是父类虚函数,但到底是哪个函数,需要程序运行 追踪 父类指针判定)

当子类和父类的 非虚函数 重名时,子类对象 调用的是 子类定义的函数,父类指针 调用的是 父类定义的函数。
但如果有了virtual,子类对象和父类指针 都将调用子类定义的函数。

class grandfather
{
	grandfather(){}
	void show(){cout<<"grandfather show"<<endl;}
	virtual void present(){cout<<"grandfather present"<<endl;}
};
class father:public grandfather
{
	father(){}
	void show(){cout<<"father show"<<endl;}
	void present(){cout<<"father present"<<endl;}
};
int main()
{
	grandfather * gf = new father();
	father f = father();
	gf->show();		//show函数非虚函数,打印"grandfather show"
	gf->present():		//present函数为虚函数,打印"father present"
	f.show();		//show函数子类父类同名,运行子类打印"father show"
	f.present();		//打印"father present"
	return 0;
}

这实际上是由指针类型决定的,指针若是 父类指针,则实际上该指针只能访问 子类对象中的父类对象那部分
因此执行gf->present();时 ,先寻找 new father()创建的子类对象,
之后由于present()为虚函数,因此通过 虚函数指针 索引至 虚函数表,再遍历找到实际应该执行的present();

2.4.3. 虚函数工作原理

类中存放一个隐藏成员——虚函数表指针,指向一块数组——虚函数表,存放该类虚函数的地址。如果虚函数 被 重新定义,则存放 新函数 的地址。

因此每个对象都会存放一个虚函数表指针。在执行gf->present();时,会跟踪虚函数表指针,找到虚函数表,再遍历找到确要执行的函数。

2.4.4. 构造函数和析构函数可以为虚函数吗?

构造函数不能为虚函数。因为即使 构造函数为虚函数,创建 子类对象时, 也将分别调用 父类构造函数 和 子类构造函数,二者之间没有继承关系,也不会体现出多态性。

构造函数可以包含虚函数。父类构造函数 只能包含父类虚函数,因此virtual可以去掉。子类构造函数 如果是包含子类虚函数 则是简单调用,如果是包含父类虚函数,则是继承,都没有体现出 多态性

析构函数为虚函数防止内存泄漏。当 父类指针 指向 子类对象时,在执行delete 操作会 释放子类对象中 父类的那部分内存(delete的是父类指针),如果析构函数不是virtual则对象的子类部分不被清理,造成内存泄露。

2.5. 多重继承与虚继承

多重继承是指一个类继承多个类。
但当所继承的多个类,有相同的成员,则调用的时候可能会出现二义性。

2.5.1. 钻石(菱形)继承

如果两个父类继承自同一个爷类,子类对象不知调用的是 哪个子对象 的爷类对象,依然会出现二义性。

class GrandFather { //第一层基类GrandFather
public:
	int value = 10;
	GrandFather() {}
};

class Father1:public GrandFather
{ 
public:
Father1() {}
void show(){}
};

class Father2 :public GrandFather
{ 
public:
Father2() {}
void show(){}
};

class Son : public Father1,  public Father2
{ 
public:
Son() {}
};
int main() {
	Son s =  Son();
	s.show();	//多重继承,Father1和Father2的show()不确定,错误
	s.Father1::show();	//声明所属,正确
	cout<<s.value;	//钻石继承,Father1和Father2的value不确定,错误
	cout<<s.Father1::value;	//调用Father1 的value,正确
	return 0;
}

Father1和Father2都继承了一次GrandFather类,因此Father1和Father2各自都有value。
当创建一个Son对象时,会分别创建 Father1 和 Father2 两个子对象,然后创建 Father1子对象 和 Father子对象 时 会先 分别创建一个GrandFather子对象,各子对象之间的关系如下图:

在这里插入图片描述
因此即使是调用GrandFather的成员value也会出现二义性。

解决方法是虚继承。表示Father1和Father2对GrandFather虚继承,只保留一个爷类实例。(虚继承仅仅为了解决钻石继承问题而已)

class Father1: virtual public GrandFather
class Father2: virtual public GrandFather
class Son: public Father1,public Father2

2.6. 运算符重载

例如加法,和为新的student对象
student operator+(student st){…}

三、设计模式

四、 内存管理

4.1. 内存分区

  • 栈区:内存地址连续,系统自动分配自动释放,无法控制但速度快,主要用于存放局部变量、函数参数等。
  • 堆区:内存地址随机分配,由程序员手动分配手动释放(容易产生内存泄漏),使用方便但速度慢,new分配delete回收。
  • 全局/静态存储区:全局变量、静态变量、虚函数表
  • 常量存储区:存储常量,不允许被修改
  • 代码区:存放代码

4.2. 内存泄露与内存溢出

内存泄漏:指程序中堆 内存由于某种原因 程序未释放,造成系统内存的浪费。
内存溢出:程序运行所需内存 大于 提供的最大内存。

X、标准模板库(Standard Template Library, STL)

X.1. vector

需要包含头文件#include< vector >
1.特点:
(1) 一个动态分配的数组(自动管理内存、动态改变长度并随着元素的增减而增大或缩小);
(2) 当删除元素时,不会释放限制的空间,所以向量容器的容量(capacity)大于向量容器的大小(size);
(3) 对于删除或插入操作,执行效率不高,访问时更加高效

2.创建vector对象:

vector<int> vec1;    //默认初始化,vec1为空

vector<int> vec1{1,2,3,4}; //初始化vec1其元素为1,2,3,4
		
vector<int> {1,2,3,4}; 				//返回一个vector其元素为1,2,3,4

vector<int> vec2(vec1);  				 //使用vec1初始化vec2

vector<int> vec4(10);    				 //初始大小为10,元素全为0

vector<vector<int>> vec(10,vector<int>(20));	 				//创建二维数组大小分别为10和20

vector<int> vec5(10,4);  				 //10个值为4的元素

vector<string> vec7(10,"hello");   				//10个值为hello的元素

所有容器定义的操作:

vector<int>::iterator ite=v.begin();     //头元素的迭代器
vector<int>::iterator ite=v.end();       //尾部元素的迭代器 
*ite;      		//返回迭代器所指的元素
int i=v.size();      //容器大小
bool s=v.empty(); //是否为空
v.swap(a); //将v与a内容交换

所有序列容器定义的操作:

v.insert(pos,elem); 	 				//迭代器pos处插入elem
v.insert(pos, n, elem) 	 				//在位置迭代器pos上插入n个元素elem
v.insert(pos, v1.begin(), v1.end());  					//将v1的[begin, end)区间的数据插入到迭代器pos前
v.resize(n); 	 				//将v变成容量为n
v.assign(ita,itb);  				 //将ita到itb的元素替换v
v.assign(n,t);		//将n个t替换v
v.erase(pos);   	//移除pos位置上的元素,返回下一个数据的位置
v.erase(v.begin(), v.end());   //移除[begin, end)区间数据,返回下一个元素的位置
v.clear();	//清空
v.front();     //获取头部元素

某些序列容器定义的操作:

 v.at(1); //用法和[]运算符相同,v[1]
 v.push_back(100); //尾部插入100
 v.pop_back();  //尾部删除
 v.back();      //获取尾部元素

仅为vector定义的操作:

 int i=v.capacity();  //容器容量

X.2. list

list表示双向链表。需要包含头文件#include < list >
list地址不连续,不支持数组表示法,因此访问元素只能按逻辑查找时间复杂度为O(n),但可以快速插入删除为O(1) ,并且删除链表中的元素并不改变其它元素位置。
提醒:序列容器都是线性排序,因此list首尾不会相连。

list<int> lst1;          //创建空list
list<int> lst1{1,2,3,4}; //初始化lst1其元素为1,2,3,4
list<int> lst2(3);       //创建含有三个元素的list
list<int> lst3(3,2); //创建含有三个元素的值为2的list
list<int> lst4(lst2);    //使用lst2初始化lst4
list<int> lst5(lst2.begin(),lst2.end());  //同lst4

 所有容器定义的操作:
list<int>::iterator ite=lst.begin();     //头元素的迭代器
list<int>::iterator ite=lst.end();       //尾部元素的迭代器  
*ite;      		//返回迭代器所指的元素
int i=lst.size();      //容器大小
bool s=lst.empty(); //是否为空
lst.swap(a); //将lst与a内容交换

 所有序列容器定义的操作:
lst.insert(pos,elem); 	//迭代器pos处插入elem
lst.insert(pos, n, elem) 	//在位置迭代器pos上插入n个元素elem
lst.insert(pos, lst.begin(), lst.end()); 	//将[begin, end)区间的数据插入到迭代器pos前
lst.resize(n); 	//将lst变成容量为n
lst.assign(ita,itb);  //将ita到itb的元素替换lst
lst.assign(n,t);		//将n个t替换lst
lst.erase(pos);   	//移除pos位置上的元素,返回下一个数据的位置
lst.erase(lst.begin(), lst.end());   //移除[begin, end)区间数据,返回下一个元素的位置
lst.clear();	//清空
lst.front();     //获取头部元素

 某些序列容器定义的操作:
lst.push_back(100); //尾部插入100
lst.push_front(100);	//前插100
lst.pop_back();  //尾部删除
lst.pop_front();	//前删
lst.back();      //获取尾部元素

 仅为list定义的操作:
lst.splice(ita,b);	//将链表b中的内容移动到ita前,b为空,O(1)
lst.splice(ita,b,itb,itc); 	//将链表b中[itb,itc)内容移动到ita前,b中消除,O(1)
lst.remove(t); 	//删除值为t的所有元素, O(1)
lst.unique(); 	//在连续的元素中剔出重复的元素, O(1)
lst.merge(b); 	//(lst和b必须有序)将链表b元素与lst有序融合, O(1)
lst.sort();	//排序
lst.reverse();	//顺序翻转
 单向链表节点的定义:
struct ListNode {
     int val;
     ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
};

X.3. deque

deque(double-ended queue 双端队列);
(2) 具有分段数组、索引数组, 分段数组是存储数据的,索引数组是存储每段数组的首地址;
(3) 向两端插入元素效率较高!中间插入元素效率较低!
(若向两端插入元素,如果两端的分段数组未满,既可插入;如果两端的分段数组已满,则创建新的分段函数,并把分段数组的首地址存储到deque容器中即可)。

deque<int> dq1;          //创建空deque
deque<int> dq1{1,2,3,4}; //初始化dq1其元素为1,2,3,4
deque<int> dq2(3);       //创建含有三个元素的deque
deque<int> dq3(3,2); //创建含有三个元素的值为2的deque
deque<int> dq4(dq2);    //使用dq2初始化dq4
deque<int> dq5(dq2.begin(),dq2.end());  //同dq4

 所有容器定义的操作:
deque<int>::iterator ite=dq.begin();     //头元素的迭代器
deque<int>::iterator ite=dq.end();       //尾部元素的迭代器 
*ite;      		//返回迭代器所指的元素 
int i=dq.size();      //容器大小
bool s=dq.empty(); //是否为空
dq.swap(a); //将dq与a内容交换

 所有序列容器定义的操作:
dq.insert(pos,elem); 	//迭代器pos处插入elem
dq.insert(pos, n, elem) 	//在位置迭代器pos上插入n个元素elem
dq.insert(pos, dq.begin(), dq.end()); 	//将[begin, end)区间的数据插入到迭代器pos前
dq.resize(n); 	//将dq变成容量为n
dq.assign(ita,itb);  //将ita到itb的元素替换dq
dq.assign(n,t);		//将n个t替换dq
dq.erase(pos);   	//移除pos位置上的元素,返回下一个数据的位置
dq.erase(dq.begin(), dq.end());   //移除[begin, end)区间数据,返回下一个元素的位置
dq.clear();	//清空
dq.front();     //获取头部元素

 某些序列容器定义的操作:
dq.back();      //获取尾部元素
dq.push_back(100); //尾部插入100
dq.pop_back();  //尾部删除
dq.push_front(100);	//前插100
dq.pop_front();	//前删
dq.at(n);	 		//dq[n]

X.4. queue

包含头文件 < queue >
queue不允许随机访问队列元素,不允许遍历队列,可以进行队列基本操作
元素先进先出,后进后出,可以查看队尾和队首的值,检查元素数目和测试队列是否为空

queue<int> qe;          //创建空
queue <int> qe(3);       //创建含有三个元素的
queue <int> qe(3,2); //创建含有三个元素的值为2的
queue <int> qe(qe2);    //使用qe2初始化qe
queue <int> qe(qe2.begin(),qe2.end());  //同lst4

 所有容器定义的操作:
queue<int>::iterator ite=qe.begin();     //头元素的迭代器
queue<int>::iterator ite=qe.end();       //尾部元素的迭代器
*ite;      		//返回迭代器所指的元素  
int i=qe.size();      //容器大小
bool s=qe.empty(); //是否为空
qe.swap(a); //将qe与a内容交换

 仅为queue定义的操作:
qe.push(100); //尾部插入100
qe.pop();	//前删
qe.front();      //获取首部元素
qe.back();      //获取尾部元素

X.5. priority_queue

包含头文件
priority_queue是优先队列,可以进行队列基本操作,加入元素是从队列尾部加入。但与queue不同的是,弹出的元素不是队列中最先进入的元素,而是优先级最大的元素。默认的底层实现为vector构成的大顶堆。

priority_queue<int> pq;          //创建空大顶堆
priority_queue<int,vector<int>,greater<int>> pq;         //创建底层为vector的小顶堆
priority_queue<int,vector<int>,less<int>> pq;         //创建底层为vector的大顶堆
priority_queue<node*,vector<node*>,cmp> pq;      //创建比较函数重载为cmp的堆

 所有容器定义的操作:
int i=pq.size();      //容器大小
bool s=pq.empty(); 	//是否为空
pq.swap(a); 		//将pq与a内容交换

 仅为priority_queue定义的操作:
pq.push(100);	 //插入100,使用堆排序,复杂度为O(lgn)
pq.pop();		//删除最大优先级元素,复杂度为O(lgn)
pq.top();      	//获取首部元素

 priority_queue比较函数的重载方法:
构造一个包含重载()的返回值为bool函数的结构体即可,priority_queue将堆中所有堆底元素代入a,堆顶元素代入b,下滤使每个元素代入函数的结果都为True,即



struct cmp {
bool operator()(typename a, typname b){
    	// 定义的比较方法
}
};

X.6. stack

包含头文件 < stack >
stack不允许随机访问栈元素,不允许遍历栈,先进后出,后进先出。
可以将值压入栈顶,从栈顶弹出元素,查看栈顶的值,检查元素数目,测试栈是否为空

stack<int> sta;          //创建空
stack <int> sta(3);       //创建含有三个元素的
stack <int> sta(3,2); //创建含有三个元素的值为2的
stack <int> sta(sta2);    //使用sta2初始化sta
stack <int> sta(sta2.begin(),sta2.end());  //同lst4

 所有容器定义的操作:
stack<int>::iterator ite=sta.begin();     //头元素的迭代器
stack<int>::iterator ite=sta.end();       //尾部元素的迭代器
*ite;      		//返回迭代器所指的元素  
int i=sta.size();      //容器大小
bool s=sta.empty(); //是否为空
sta.swap(a); //将sta与a内容交换

 仅为stack定义的操作:
sta.push(100); //栈顶压入100
sta.pop();	//前删
sta.top();      //获取栈顶元素

X.7. set与multiset

set的含义是集合,它是一个有序的容器,元素都是排序好的,支持插入,删除,查找等操作,键与值相同。所有的操作的都是严格在O(logn)时间之内完成,效率非常高。set和multiset的区别是:set插入的元素不能相同,但是multiset可以相同,均包含在头文件< set >中。

set<int> se1;          //创建空set
multiset<int> se1{1,2,2,4}; //初始化se1其元素为1,2,2,4
set<int> se1{1,2,2,4}; //初始化se1其元素为1,2,4
set<int> se4(se2);    //使用se2初始化se4
set<int> se5(se2.begin(),se2.end());    //使用se2初始化se4

所有容器定义的操作:
set<int>::iterator ite=se.begin();     //头元素的迭代器
set<int>::iterator ite=se.end();       //尾部元素的迭代器
*ite;      		//返回迭代器所指的元素  
int i=se.size();      //容器大小
bool s=se.empty(); //是否为空
se.swap(a); //将se与a内容交换

所有关联容器定义的操作:
se.insert(x); 	//插入int型常量x
se.insert(pos, n, elem) 	//在位置迭代器pos上插入n个元素elem
se.insert(se1.begin(), se1.end()); 	//将集合se1的[begin, end)区间的数据插入se中
se.erase(x);   	//移除键值为x的元素
se.erase(ite);   //移除迭代器ite指向的数据
se.erase(se.begin(), se.end());   //移除[begin, end)区间数据
se.clear();	//清空
se.lower_bound(k); 	//返回第一个≥k的迭代器
se.upper_bound(k); 	//返回第一个>k的迭代器
se.equal_range(k);	//返回一个pair容器,键值对为{≥k,>k}的两个迭代器
se.find(k); 	//返回内容为k的迭代器
se.count(k);	 	//返回元素为k的数量(注意set元素不相同,multiset可以相同)

X.8. map与multimap

Map底层结构为红黑树,键与值不相同且会根据键自动排序,每个键(key)对应一个值(value)。而map的键不可重复,multimap可以存在相同的键。二者均包含在头文件 < map > 中。

判断某元素是否在map中使用count()方法,而不是直接访问m[~],直接访问会使系统自动赋值而占用内存,即 size()+1

map<string ,int> m1;          //创建空map其键为string型,值为int型
map<string ,int> m1{{“one”,1},{“second”,2}};          //创建map及其键值对
map<string ,int> m4(m2);    //使用m2初始化m4,二者必须有相同类型的键值
map<string ,int> m5(m2.begin(),m2.end());    //使用m2初始化m4,键值类型相同
pair<string ,int> p(“one”,1);	//创建一个键值对

所有容器定义的操作:
map<string,int>::iterator ite=m.begin();     //头元素的迭代器
map<string,int>::iterator ite=m.end();       //尾部元素的迭代器
*ite;      		//返回迭代器所指的元素  
int i=m.size();      //元素个数
bool s=m.empty(); //是否为空
m.swap(a); //将m与a内容交换

所有关联容器定义的操作:
m.begin()->first;	//返回迭代器所指的键
m.begin()->second;	//返回迭代器所指的值
p.first;		//返回pair容器所指的键
p.second;		//返回pair容器所指的值
p=make_pair(“two”,1);		//创建新的键值对
m.insert({{“second”,2},{“third”,3}}); 	//插入两个键值对
m.insert(pair<string ,int> (“one”,1)); 	//创建或者修改键为one的值为1
m.erase(x);   	//移除键为x的所有元素
m.erase(ite);   //移除迭代器ite指向的值
m.erase(m.begin(), m.end());   //移除[begin, end)区间的值
m.clear();	//清空
m.lower_bound(k); 	//返回第一个键≥k的迭代器
m.upper_bound(k); 	//返回第一个键>k的迭代器
m.equal_range(k);	//返回一个pair容器,键值对为{≥k,>k}的两个迭代器
m.find(k); 	//返回键为k的迭代器
m.count(k);	 	//返回键为k的值个数

仅为map定义的操作:
m[“one”]; 	//返回键为one的值,初始为0
m[“one”]=1;		//创建或者修改键为one的值为1

X.9. unordered_set

set的含义是集合,哈希表中元素按照哈希函数使用拉链法存储。支持插入,删除,查找等操作,键与值相同。所有的操作的都是严格在O(logn)时间之内完成,效率非常高。包含在头文件< unordered_set >中。

unordered_set<int> se1;          //创建空
unordered_set<int> s(n,hf,eq);          //桶数为n,哈希函数为hf,相等谓词为eq
multiunordered_set<int> se1{1,2,2,4}; //初始化se1其元素为1,2,2,4
unordered_set<int> se1{1,2,2,4}; //初始化se1其元素为1,2,4
unordered_set<int> se4(se2);    //使用se2初始化se4
unordered_set<int> se5(se2.begin(),se2.end());    //使用se2初始化se4
unordered_set<int>::hasher ;    //Hash类
unordered_set<int>::key_equal ;    //二元谓词类,检查键的参数是否相等


所有容器定义的操作:
unordered_set<int>::iterator ite=se.begin(n);     //秩为n的桶第一个元素的迭代器
unordered_set<int>::iterator ite=se.end(n);       //秩为n的桶最后元素的迭代器
*ite;      		//返回迭代器所指的元素  
int i=se.size();      //容器元素个数
bool s=se.empty(); //是否为空
se.swap(a); //将se与a内容交换

所有无序关联容器定义的操作:
se.hash_function();	//返回se使用的哈希函数,类型为Hash
se.hash_function()(n); 	//返回自变量为n时哈希函数的输出
se.key_eq();		//返回se使用的二元谓词,类型为key_equal
se.bucket_count();	//返回se的桶数
se.max_bucket_count();	//返回se的桶数的上限
se.bucket(k);			//返回键为k的桶秩
se.load_factor();		//返回平均每个桶的元素数(载入因子=元素总数/桶数)
se.rehash(n);			//使se桶个数≥n
se.insert(i);		//插入i
se.erase(x);   	//移除键值为x的元素
se.erase(ite);   //移除迭代器ite指向的数据
se.erase(se.begin(n), se.end(n));   //移除[begin, end)区间数据
se.clear();	//清空
se.find(k); 	//返回内容为k的迭代器
se.count(k);	 	//返回元素为k的数量(注意unordered_set元素不相同, unordered_multiset可以相同)

X.10. bitset

包含头文件< bitset >
类似于数组,只能存储0或1,因此每个元素只占1bit,可以实现整体的位运算,但是必须提前定义大小。

         bitset<k> b1;    //初始化,b1是长度为k的bitset,值全为0
         bitset<k> b1(12);  //使用12的二进制初始化b1,最低位对应b[0]。若k<12二进制有效位,从小向大取,若k>12二进制有效位,则补0
         bitset<k> b1(1100);    //使用1100初始化b1,该二进制数从左到右位权降低。
       	bitset<k>b1(s,i,l);		//使用s从下标i开始,长度为l的字符串初始化b1

int i=b.size();      //大小
cout<<b;	//以二进制形式输出b
 b[1];	 //访问b[1]

位运算:
b1 & b2;	//按位与,b1与b2必须位数相同否则报错
b1 | b2;		//按位或,b1与b2必须位数相同否则报错
b1 ^ b2;		//按位异或,b1与b2必须位数相同否则报错
~b1;	//将b1按位取反
~b1[i];	//将b1[i]取反
b<<1;	//左移1位,即向高位移动1位,b[0]为0
b>>1;	//右移1位,即向低位移动1位,最高位补0


b.count(); 	//返回1的个数
b.any();		//判断b是否存在1
b.all();		//判断b是否全部为1
b.flip(i);		//将b[i]取反
b.flip();		//将b按位取反
b.set();	//b全部置位为1
b.set(i,j);	//将b[j]赋给b[i]
b.reset();	//b全部复位为0

string s = b.to_string();  //将b以 string类型输出
unsigned long a = b.to_ulong();  //将b以unsigned long类型输出


32位以内位向量直接使用int visit做标记:
位运算
 visit & (1<<i);	 //访问第i二进制位
 visit | (1<<i);	 //第i二进制位置1
 visit |( (1<<i)-1);	 //第i二进制位置0

X.11. string

包含头文件< string >

string s;    	//默认初始化,s为空
string s(s1); 	//初始化s为s1的复制
string s=”Hello”; 	 //使用s为”Hello”
string s(s1,i);    	//初始化s为s1从下标i开始到尾部的字符串
string s(s1,i,j);    	//初始化s为s1从下标i开始,长度为j的字符串

string::iterator ite=s.begin();     //头元素的迭代器
string::iterator ite=s.end();       //尾部元素的迭代器 
*ite;      		//返回迭代器所指的元素
int i=s.size();      //容器大小
bool s=s.empty(); //是否为空
s.swap(a); //将s与a内容交换

s==s1;      //判断两string是否相等,若是返回True,否则为False
int a=s.compare(i,j,s1,k,l);	//将s下标为i到j部分与si下标为k到l部分比较。若相等则返回0,大于返回1,小于返回-1
string s=s2+s+s1;	//字符串拼接或字符拼接

char c=s[i];			//访问字符串s下标为i的字符
 s.push_back(cs); 	//尾部插入字符cs或尾部插入字符串cs
 s.pop_back();  //尾部删除
s.insert(i,s1);		//下标i处插入字符串s1
s.insert(i,j,c1);		//下标i处插入j个字符c1
s.insert(s.begin()+i,c1); 	//下标i处插入字符c1
s.insert(pos, n, elem) 	//在位置迭代器pos上插入n个字符elem
s.insert(pos, s1.begin(), s1.end()); 	//将s1的[begin, end)区间的数据插入到迭代器pos前
s.erase(pos);   	//移除位置为pos的字符
s.erase(i,j);   //移除下标i开始长度为j的字符串
s.replace(i,j,s1);		//将s下标为i开始,长度为j字符串替换成s1
char c=tolower(c);		//将字符c转换成小写字母
char c=toupperer(c);		//将字符c转换成大写字母
int a=s.find(s1);		//在s中查找字符串s1,返回下标,返回-1表示未找到
int a=s.find(s1,i);		//在s中从下标i开始查找字符串s1,返回下标,返回-1表示未找到
int a=s.find(c1);		//在s中查找字符c1,返回下标,返回-1表示未找到
int a=s.find(c1,i);		//在s中从下标i开始查找字符c1,返回下标,返回-1表示未找到
int a=s.rfind(s1);		//在s中反向查找字符串s1,返回下标,返回-1表示未找到
string s=s1.substr(i,j);		//截取字符串s1下标i开始,长度为j的字符串

s.resize(n); 	//将s变成容量为n
s.clear();	//清空

X.12. 非修改序列操作

包含头文件< algorithm >

所需变量:
一般STL函数需要将迭代器作为函数参数。

X<typename>:: iterator first , first1;
X<typename>:: iterator last , last1;  	//定义迭代器指向容器中的元素
X<typename>:: iterator ite;

ite=find(first,last,k);      //在区间[first,last)中寻找第一个值为k的迭代器
int a=count(first,last,k);       //在区间[first,last)中寻找值为k的元素个数
bool a=equal(first,last,first1);       //判断区间[first,last)中的元素与first1开始的序列的元素是否相等

X.13. 修改序列操作

包含头文件< algorithm >

所需变量:
一般STL函数需要将迭代器作为函数参数。

X<typename>:: iterator first , first1;
X<typename>:: iterator last , last1;  	//定义迭代器指向容器中的元素
X<typename>:: iterator ite;

copy(first,last,first1);      //将区间[first,last)中的元素复制到first1开始的序列中
swap(a,b);	//交换a和b的值
replace(first,last,i,j);		//将[first,last]区间中的值i全部替换为j
fill(first,last,k);		//将[first,last]区间中的全部值替换为k
remove(first,last,k);		//移除区间[first,last)中值为k的元素
unique(first,last);			//将区间[first,last)中重复且连续的元素合并
reverse(first,last);		//将[first,last]区间中的元素翻转
random_shuffle(first,last);		//将区间[first,last)中的元素打乱

X.14. 排序相关

包含头文件< algorithm >

所需变量:
一般STL函数需要将迭代器作为函数参数。

X<typename>:: iterator first , first1;
X<typename>:: iterator last , last1;  	//定义迭代器指向容器中的元素
X<typename>:: iterator ite;

sort(first,last);		//将[first,last)区间中的元素升序排序
sort(first,last,cmp);		//将[first,last)区间中的元素按cmp的定义排序
nth_element(first,first1,last);		//将[first,last)区间中第first1小的元素放在first1的位置上
ite=lower_bound(first,last,k);		//在递增区间[first,last)中寻找第一个≥k的位置
ite=upper_bound(first,last,k);		//在递增区间[first,last)中寻找第一个>k的位置
merge(first,last,first1,last1,ite);	//将递增区间[first,last]与递增区间[first1,last1]元素合并,存于ite起始的序列中,该序列不可与被合并区间有重叠

bool a=includes(first,last,first1,last1);		//判断[first1,last1)的元素∈[first,last)
set_union(first,last,first1,last1,ite); 	//将[first,last]与[first1,last1]区间元素取并集至ite起始的序列中,该序列不可与被合并区间重叠
set_intersection(first,last,first1,last1,ite); 	//将[first,last)与[first1,last1)区间元素取交集至ite起始的序列中,该序列不可与被合并区间重叠
set_difference(first,last,first1,last1,ite); 	//将[first,last)与[first1,last1)区间元素取差集至ite起始的序列中,该序列不可与被合并区间重叠

make_heap(first,last);		//将区间[first,last)的元素构造成大顶堆
make_heap(first,last,cmp);	//将区间[first,last)的元素构造成按照cmp定义的堆
push_heap(first,last);		//将last处的值加入到[first,last-1)的堆中成为新堆
pop_heap(first,last);	//将[first,last)的堆中last-1值与first的值交换,并将[first,last-1)构成新堆
bool a=is_heap(first,last);	 //判断[first,last]是否是大顶堆
bool a=is_heap(first,last,cmp);	 //判断[first,last]是否是cmp定义的堆

x=min(a,b);	//返回a和b中最小的元素
x=max(a,b);	//返回a和b中最大的元素
pair<int,int> p=minmax(a,b);	 //将a和b按(min(a,b),max(a,b))构成一个pair
pair<int,int> p=maxmin(a,b);	 //将a和b按(max(a,b),min(a,b))构成一个pair
ite=min_element(first,last);	//返回[first,last)中最小元素的位置
ite=max_element(first,last);	//返回[first,last)中最大元素的位置
pair<first1,last1> p=minmax_element(first,last);		//分别将[first,last)中最小元素位置和最大元素位置构成一个pair

X.15. 数值运算

包含头文件< numeric >

所需变量:
一般STL函数需要将迭代器作为函数参数。

X<typename>:: iterator first , first1;
X<typename>:: iterator last , last1;  	//定义迭代器指向容器中的元素
X<typename>:: iterator ite;

x=accumulate(first,last,n);		//返回[first,last]元素值的总和,并加上n
sqrt(number);		//开方

isalnum(c) ;  	//判断字符c是否为字母和数字
char littlec = tolower(c);		//若c有小写字母则输出小写字母,否则保持不变
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Starry丶

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值