1. C++基础入门
1.1 第一个C++程序
在VS studio中创建一个C++空项目,在“源文件”文件夹下创建一个.cpp文件,文件中编写C++代码即可运行。
#include<iostream>
using namespace std;
int main() {
cout << "hello world!" << endl;
system("pause");
return 0;
}
- main是一个程序的入口,每个程序都必须有这个函数,有且仅有一个
- 常量:用于记录程序中不可更改的数据,C++定义常量有两种方式:
- #define宏常量:#define 常量名 常量值
- const修饰的变量:const 数据类型 常量名 = 常量值
1.2 数据类型
C++规定在创建一个变量或常量时,必须要指定出相应的数据类型,否则无法给变量分配内存。
- 整型:C++中能表示整型的类型有以下几种方式,区别在于占用的内存空间不同:① short(短整型,2字节);② int(整型,4字节);③ long(长整型,Windows系统为4字节);④ long long(长长整型,8字节)
- sizeof关键字:可以统计数据类型所占内存的大小
语法:sizeof(数据类型 or 变量)
- 浮点型:浮点型变量分为两种:① float(单精度,4字节,7位有效数字);② double(双精度,8字节,15~16位有效数字)。C++默认定义小数为double类型,若要定义float类型,则在小数最后加“f”,否则会执行一次数据类型转换
- 字符型:字符型变量char用于显示单个字符,用单引号括起来,只占用一个字节,是将对应的ASCII码放入存储单元
- 转义字符:用于表示一些不能显示出来的ASCII字符,如\n,\t
- 字符串型:用于表示一串字符,用双引号括起来,有两种风格表示:
- C风格字符串:char 变量名[ ] = “字符串值”
- C++ 风格字符串 string 变量名 = “字符串值”【注意:使用C++风格字符串之前,要包含
#include<string>
头文件】
- 布尔类型:布尔类型bool代表真或假的值,true代表真(本质是1),false代表假(本质是0),占1个字节大小,非零数字都可以代表真
- 数据的输入:cin用于从键盘获取数据
cin >> 变量
1.3 运算符
- 算术运算符:用于处理四则运算
- 赋值运算符:用于将表达式的值赋值给变量
- 比较运算符:用于表达式的比较,并返回一个真值或假值
- 逻辑运算符:用于根据表达式的值返回真值或假值
1.4 程序流程结构
- 顺序结构:程序按顺序执行,不发生跳转
- 选择结构:依据条件是否满足,有选择的执行相应功能(switch判断中只能是整型或字符型)
- 循环结构:依据条件是否满足,循环多次执行某段代码
1.5 数组
- 数组就是一个集合,里面每个数据元素都是相同的数据类型,数组是由连续的内存位置组成的
- 一维数组有3种定义方式:
- 数据类型 数组名[ 数组长度 ];
- 数据类型 数组名[ 数组长度 ] = { 值1,值2,… };
- 数据类型 数组名[ ] = { 值1,值2,… };
- 一维数组名称的用途:① 可以统计整个数组在内存中的长度:
sizeof(arr)
(统计数组长度:sizeof(arr)/sizeof(arr[0])
);② 可以获取数组在内存中的首地址:(int)arr
(等于数组中第一个元素的内存地址:(int)&arr[0]
) - 数组名是常量,不能进行赋值操作
- 二维数组有4种定义方式:(在定义二维数组时,如果初始化了数据,可以省略行数,但列数不可以省略)
- 数据类型 数组名[ 行数 ][ 列数 ];
- 数据类型 数组名[ 行数 ][ 列数 ] = { { 值1,值2 },{ 值3,值4 } };
- 数据类型 数组名[ 行数 ][ 列数 ] = { { 值1,值2,值3,值4 } };
- 数据类型 数组名[ ][ 列数 ] = { { 值1,值2,值3,值4 } };
1.6 函数
- 值传递:指函数调用时实参将传入给形参,若形参发生改变,并不会影响实参
- 函数的声明:在函数定义之前先告诉编译器有这个函数存在,函数的实际主体可以单独定义。函数的声明可以多次,但是定义只能有一次
- 函数的分文件编写:一般.h头文件中编写函数声明,.cpp源文件中编写函数定义
1.7 指针
- 作用:可以通过指针间接访问内存。内存地址编号从0开始记录,用十六进制数表示,可以利用指针变量保存地址编号
- 定义:数据类型* 指针变量名
int* p
- 使用:① 让指针记录变量的地址:
p = &a
;② 指针前使用*解引用找到指向内存中的数据:cout << *p
- 指针所占内存空间:在32位操作系统下占用4个字节空间,64位占8个字节
- 空指针:指针变量指向内存中编号位0的空间
int* p = NULL
,用于初始化指针变量,空指针指向的内存是不可以访问的(编号0-255的内存空间由系统占用,不可访问) - 野指针:指针变量指向非法的内存空间
int* p = (int*)0x1100
,未申请的内存空间无法访问 - const修饰指针:共有3种情况:①
const int* p = &a
;②int* const p = &a
;③const int* const p = &a
- const修饰指针(常量指针):指针的指向可以改,指向的值不可以更改
- const修饰常量(指针常量):指针的指向不可以改,指向的值可以改
- const既修饰指针,又修饰常量:指针的指向和指向的值都不可以改
- 使用指针访问数组中元素:① 指向数组的指针:
int* p = arr
;② 指针访问第一个元素:cout << *p
;③ 利用指针遍历数组:cout << *p; p++;
- 函数的地址传递:① 定义:
void fun1(*p1,*p2)
;② 使用:fun1(&a,&b)
- 当定义一个函数中有数组类型的形参时,两种方法等价:①
void fun1(int* arr)
;②void fun1(int arr[])
1.8 结构体
- 概念:结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
- 结构体定义:
struct 结构体名{ 结构体成员列表 }
- 结构体类型变量的创建方式有3种:(创建结构体变量时struct关键字可以省略)
- struct 结构体名 变量名
- struct 结构体名 变量名 = { 成员1值,成员2值 }
- 定义结构体时顺便创建变量
- 通过结构体指针访问结构体成员时,使用 -> 操作符
- 在定义函数时,若将结构体指针作为形参,可在该形参前加const关键字,保证函数体中的该结构体不能被修改
2. C++面向对象编程
2.1 内存分区模型
2.1.1 程序执行时的内存划分
C++在程序执行时,将内存大方向划分位4个区域,不同区域存放的数据,拥有不同的生命周期:
- 代码区:存放函数体的二进制代码,由操作系统进行管理
- 全局区:存放全局变量、静态变量、字符串常量和全局常量
- 栈区:由编译器自动分配和释放,存放函数形参、局部变量、局部常量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
2.1.2 程序运行前的内存划分
在程序编译后运行前,生成了exe可执行程序,未执行该程序前分为两个区域:
- 代码区:存放CPU执行的机器指令。代码区是共享的,对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区是只读的,防止程序意外地修改指令
- 全局区:存放全局变量、静态变量。全局区中还包含常量区,存放字符串常量和全局常量(const修饰的全局变量)。该区域的数据在程序结束后由操作系统释放
2.1.3 程序运行后的内存划分:
- 栈区:不要返回局部变量的地址,因为栈区开辟的数据在函数执行完后自动释放
- 堆区:主要利用new在堆区中开辟内存。通过new会返回对应类型的地址,
int* p = new int(10)
可以返回函数内部的int型变量地址(直接定义局部变量地址无法返回)。堆区开辟的数据可用关键字delete释放delete p
,释放数组时加[]delete[] arr
2.2 引用
- 作用:给变量起别名:
数据类型 &别名 = 原名
- 注意事项:引用必须初始化,且在初始化后就不能更改
- 引用做函数参数:可以利用引用让形参修饰实参,产生和地址传递一样的效果
- 引用做函数返回值:
int& fun(){}
函数在调用时要用引用类型的变量接收返回值。注意:① 不要返回局部变量引用;② 函数的调用可以作为左值,此时引用的变量值会更改
int& fun(){
static int a = 10;
return a;
}
int& ref = fun();
fun() = 20;
- 引用的本质:引用在C++内部实现是一个指针常量,所有指针相关的操作都交给编译器完成,在编译器内部
int& ref = a
相当于int* const ref = &a
,ref = 10
相当于*ref = 10
- 常量引用:主要用来修饰形参,防止误操作
void fun(const int& a)
// int& ref = 10; 错误:引用本身需要一个合法的内存空间
const int& ref = 10; // 编译器将代码优化为:int temp = 10; const int& ref = temp;
2.3 函数提高
- 函数的默认参数:形参可以有默认值。① 如果某个位置参数有默认值,那么该位置往后都必须有默认值;② 如果函数声明有默认值,实现的时候就不能再有默认值(声明和实现只能有一个有默认值)
- 函数的占位参数:形参列表里可以有占位参数,只有数据类型没有参数,调用函数时必须填补该位置
返回值类型 函数名 (数据类型){}
。占位参数可以有默认参数 - 函数重载:函数名可以相同,提高复用性,函数重载满足的条件:① 同一个作用域下;② 函数名相同;③ 函数参数类型不同,或个数不同,或顺序不同
void fun(int& a){} // int a = 10; fun(a); 调用该重载
void fun(const int& a){} // fun(10); 调用该重载
void fun2(int a){}
void fun2(int a, int b = 10){} // 出现二义性,调用单个参数时报错
2.4 类和对象
2.4.1 封装
- C++中万事万物皆对象,对象有其属性和行为。面向对象的三大特征:封装、继承、多态
- 封装:在设计类的时候,将属性和行为写在一起,且加以权限控制
- C++中struct和class唯一的区别在于成员的默认访问权限不同。struct默认权限为public,class默认权限为private
2.4.2 对象的初始化和清理
- C++利用了构造函数和析构函数解决了对象的初始化和清理的问题,这两个函数会被编译器自动调用,若未手动提供,编译器会提供这两个函数的空实现。构造函数可以有参数,因此可以重载;析构函数不能有参数,因此不能重载
- 构造函数按参数分为无参构造和有参构造,按类型分为普通构造和拷贝构造(复制一份相同的对象:
Person(const Person& p){}
,构造函数体内需要自己复制属性) - 构造函数有3种调用方法:① 括号法:
Person p(10)
(无参构造函数不能加括号,会被编译器认为是函数的声明);② 显式法:Person p = Person(10)
(等号右侧称为匿名对象,不能用拷贝构造函数初始化匿名对象,即不能把拷贝构造函数写在等号左侧,编译器会认为是变量的声明);③ 隐式转换法:Person p = 10
(等同于上述显式法) - 拷贝构造函数的调用时机:① 使用一个已经创建完毕的对象来初始化一个新对象;② 以值传递的方式给函数参数传值;③ 值方式返回局部对象
- 默认情况下,C++编译器至少给一个类添加3个函数:默认无参构造函数、默认析构函数、默认拷贝构造函数(对属性值进行拷贝)。如果用户定义有参构造函数,就不再提供默认无参构造函数;如果用户定义拷贝构造函数,就不再提供默认构造函数。析构函数体中一般要将该对象在堆区开辟的数据进行释放
- 深拷贝和浅拷贝:浅拷贝是简单的赋值操作(默认拷贝构造函数中为浅拷贝),深拷贝在堆区重新申请空间进行赋值。浅拷贝带来的问题有析构函数中堆区内存重复释放,如果类中有属性在堆区开辟内存,一定要自己提供拷贝构造函数
- C++提供了初始化列表语法,用来初始化属性:
构造函数():属性1(值1),属性2(值2){}
- 静态成员:在成员变量和成员函数前加上关键字static。静态成员变量所有对象共享一份数据,在编译阶段分配内存,类内声明、类外初始化。静态成员函数所有对象共享同一个函数,只能访问静态成员变量。静态成员可直接通过类名访问:
类名::静态成员
2.4.3 对象模型和this指针
- C++编译器会给每个空对象分配一个字节的内存空间,为了区分空对象在内存中的位置
- C++中只有非静态成员变量才存储于类的对象中,静态成员变量存储于全局区中,非静态成员函数存储于代码区中(所有该类的对象共享)
- this指针是隐含在每个非静态成员函数内的一种指针,不需要定义,指向被调用的成员函数所属的对象。this指针的本质时指针常量,指针的指向不能修改
- C++允许空指针调用成员函数,但要注意此时通过this访问属性会报错
- 成员函数后加const称为常函数,常函数内不可以修改成员属性,成员属性声明时加关键字mutable后在常函数中仍可以修改。该const本质上修饰的是this指针,使指针指向的值也不能修改
- 声明对象前加const称为常对象,常对象只能调用常函数,只能修改mutable属性
2.4.4 友元
- 友元的目的是让一个函数或类访问另一个类中私有成员,关键字为friend,友元有3种实现:① 全局函数做友元;② 类做友元;③ 成员函数做友元
class ClassA{
friend void fun(); // 全局函数fun内可以访问ClassA的私有成员
friend class ClassB; // 类ClassB内可以访问ClassA的私有成员
friend void ClassB::funB(); // 类ClassB的funB方法可以访问ClassA的私有成员
}
void fun(){}
class ClassB{
void funB(){};
}
2.4.5 运算符重载
- 加号运算符重载:① 通过成员函数重载:
Person operator+ (Person& p){}
;② 通过全局函数重载:Person operator+ (Person& p1, Person& p2){}
。运算符重载也可以发生函数重载 - 左移运算符重载:不会通过成员函数重载左移运算符,因为无法实现cout在左侧。通过全局函数重载:
ostream& operator<< (ostream& cout, Person& p){return cout;}
,若类中成员私有化,需要将该全局函数设置为类的友元 - 递增运算符重载:自定义整型变量实现与int相同的前置递增和后置递增操作,前置递增返回引用,后置递增返回值
class MyInteger {
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}
//前置++重载
MyInteger& operator++() {
m_Num++;
return *this;
}
//后置++重载,int代表占位参数,用于区分后置递增
MyInteger operator++(int) {
MyInteger temp = *this;
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, MyInteger myint) {
cout << myint.m_Num;
return cout;
}
- 赋值运算符重载:C++编译器至少给一个类添加4个函数:3个默认构造函数和赋值运算符
Person& operator=(Person& p){}
,赋值运算符与默认拷贝构造函数一样是对属性进行值拷贝(浅拷贝) - 关系运算符重载:可以让两个自定义类型对象进行比较操作
bool operator==(Person& p){}
- 函数调用运算符重载:()也可以重载,由于重载后使用方式与函数调用相同,因此称为仿函数
void operator()(string text){}
,仿函数的参数个数不固定
2.4.6 继承
- 语法:
class 子类 : 继承方式 父类
- 继承方式一共有3种:公共继承、保护继承、私有继承
- 继承中的对象模型:父类中所有非静态成员属性都会被子类继承,私有属性访问不到但依然继承
在VS开发人员命令提示符下,转到cpp文件所在的目录,输入命令:cl /d1 reportSingleClassLayoutPeople HelloWorld.cpp,即可查看People类的内存结构
- 继承中的构造和析构顺序:父类先于子类构造,后于子类析构
- 继承同名成员:访问子类同名成员时直接访问即可,访问父类同名成员需要加作用域
son.Father::property
。如果子类出现和父类同名的成员函数,子类会隐藏掉父类中所有该名的成员函数(父类中所有重载的函数) - 继承同名静态成员:访问时同样是子类直接访问,父类加作用域
- 多继承语法:
class 子类 : 继承方式 父类1, 继承方式 父类2
,多继承可能会引发父类中有同名成员出现,需要加作用域区分,C++实际开发中不建议用多继承 - 菱形继承:两个类继承同一个基类,又有一个类同时继承于这两个类。菱形继承会产生二义性和资源浪费,利用虚继承可以解决,使用虚继承后两个类继承的是虚基类指针(vbptr),两个指针指向同一份数据,使得成员属性只剩一份
class Father{
public: int age;
};
// 继承前加virtual关键字后变为虚继承,此时的父类Father称为虚基类
class Son1 : virtual public Father{};
class Son2 : virtual public Father{};
class GrandSon : public Son1, public Son2{};
2.4.7 多态
- 多态分为静态多态和动态多态(运行时多态)。① 静态多态:函数重载和运算符重载,复用函数名,编译阶段确定函数地址;② 动态多态:由派生类和虚函数实现,运行阶段确定函数地址
- 动态多态满足条件:① 有继承关系;② 子类重写父类的虚函数;③ 父类的指针或引用指向子类对象
- 多态原理:父类内部会记录虚函数指针(vfptr),指向父类虚函数表,表内记录虚函数的地址。若子类重写父类的虚函数,则子类虚函数表内部会替换成子类函数地址。当父类指针或引用指向子类对象时,会从子类的虚函数表中寻找函数地址
- 纯虚函数和抽象类:在多态中,通常父类中虚函数的实现无意义,只调用子类重写的内容,因此将虚函数改为纯虚函数
virtual 返回值类型 函数名 ( 参数列表) = 0
,当类中有了纯虚函数,该类称为抽象类。抽象类无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类 - 虚析构和纯虚析构:使用多态时,若子类有属性开辟到堆区,则父类指针在释放时无法调用到子类的析构代码。此时要将父类中的析构函数改为虚析构或纯虚析构
virtual ~类名(){} = 0
(纯虚析构也必须实现),有纯虚析构的类是抽象类
2.5 文件操作
2.5.1 文本文件
- 程序运行时产生的数据都属于临时数据,一旦程序运行结束都会被释放,通过文件可以将数据持久化,C++中对文件操作需要包含头文件
<fstream>
- 文件类型分为两种:① 文本文件:以文本的ASCII码形式存储;② 二进制文件:以文本的二进制形式存储
- 操作文件的三大类:ofstream(写操作)、ifstream(读操作)、fstream(读写操作)
- 写文件步骤:① 包含头文件:
#include<fstream>
;② 创建流对象:ofstream ofs
;③ 打开文件:ofs.open("文件路径", 打开方式)
;④ 写数据:ofs << "写入的数据"
(endl
可以写入时换行);⑤ 关闭文件:ofs.close()
文件打开方式:(文件打开方式可以配合使用,利用
|
操作符)
ios::in
:为读文件而打开文件ios::out
:为写文件而打开文件ios::ate
:初始位置在文件尾ios::app
:追加方式写文件ios::trunc
:如果文件存在,先删除再创建ios::binary
:二进制方式
- 读文件步骤:① 包含头文件:
#include<fstream>
;② 创建流对象:ifstream ifs
;③ 打开文件并判断是否打开成功:ifs.open("文件路径", 打开方式)
;④ 读数据:四种方式读取;⑤ 关闭文件:ifs.close()
void test01() {
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "不存在文件" << endl;
return;
}
// 方式一
char buf[1024] = { 0 };
while (ifs >> buf) {
cout << buf << endl;
}
// 方式二
char buf[1024] = { 0 };
while (ifs.getline(buf,sizeof(buf))) {
cout << buf << endl;
}
// 方式三
string buf;
while (getline(ifs, buf)) {
cout << buf << endl;
}
// 方式四
char c;
while ((c = ifs.get()) != EOF) {
cout << c;
}
ifs.close();
}
2.5.2 二进制文件
- 二进制写文件利用流对象调用成员函数write:
ostream& write(const char* buffer, int len)
,其中字符指针buffer指向内存中一段存储空间,len是读写的字节数 - 二进制读文件利用流对象调用成员函数read:
istream& read(char* buffer, int len)
// 写文件
Person p = {"张三", 18};
ofs.write((cosnt char*)&p, sizeof(Person));
// 读文件
Person p;
ifs.read((char*)&p, sizeof(Person));
3. 泛型与STL
3.1 模板
3.1.1 函数模板
- C++另一种编程思想称为泛型编程,主要利用的技术是模板。C++中提供两种模板机制:函数模板和类模板
- 函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不确定,用一个虚拟的类型来代表
- 函数模板语法:template声明创建模板,typename表明后面的符号是一种数据类型(可用class关键字代替)。使用函数模板时,可用自动类型推导,或显式指定类型
template<typename T>
void fun(T& a, T& b){} //函数声明或定义
fun(a, b); //自动类型推导
fun<int>(a, b) //显式指定类型
- 普通函数调用时可以发生隐式类型转换。函数模板调用时,如果使用自动类型推导,不会发生隐式类型转换;如果显式指定类型,可以发生隐式类型转换
- 如果函数模板和普通函数都可以实现(非隐式转换),优先调用普通函数。可以通过空模板函数列表来强制调用函数模板
fun<>(a,b)
。函数模板也可以发生重载 - 模板并不是万能的,对于特定数据类型,需要用具体化方式做特殊实现
template<class T>
bool myCompare(T& a, T& b){
if(a == b) return true;
return false;
}
// 具体化数据类型为Person版本的函数实现
template<> bool myCompare(Person& a, Person& b){
if(a.name == b.name) return true;
return false;
}
3.1.2 类模板
- 类模板的语法与函数模板相同,将函数定义改为类定义即可
- 类模板没有自动类型推断的使用方式。类模板在模板参数列表中可以有默认参数
template<class NameType, class AgeType = int>
class Person{
publicL
NameType name;
AgeType age;
}
void test(){
Person<string, int> p1("Tom", 18);
Person<string> p2("Jack", 20);
}
- 普通类中的成员函数在一开始就创建,类模板中的成员函数在调用时才创建
- 类模板对象为函数参数时,有3中传入方式:① 直接显示对象的数据类型:
void fun(Person<string,int>& p)
;② 将对象中的参数作为模板进行传递:void fun(Person<T1,T2>& p)
;③ 将整个对象作为模板进行传递:void fun(T& p)
- 类模板与继承:当子类继承于一个类模板时,子类在声明时要指定父类中T的类型,否则编译器无法给子类分配内存,或者将子类也设定为类模板
template<class T>
class Father{
T m;
}
class Son:public Father<int>{}
template<class T1, class T2>
class Son:public Father<T2>{
T1 n;
}
- 类模板成员函数的类外实现:
Person<T1,T2>::Person(T1 name,T2 age){}
- 类模板分文件编写:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。解决方法:① 直接包含.cpp文件;② 将声明和实现写到同一个.hpp文件中(.hpp是约定的名称,一般.hpp中都是类模板)
- 类模板与友元:若一个全局函数为模板类的友元,优先选择直接在类内实现,在类外实现需要提前让编译器知道全局函数的存在以及模板类的存在(非常麻烦)
3.2 STL容器
3.2.1 STL介绍
- STL(Standard Template Library,标准模板库)是C++为了建立数据结构和算法的一套标准而诞生。STL在广义上分为:容器、算法、迭代器。STL几乎所有的代码都采用了模板类或模板函数
- STL六大组件:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
- 迭代器:容器和算法之间的粘合剂,提供一种方法,使之能够依序访问某个容器所含的各个元素,而又无需暴露该容器的内部表示方式,每个容器都有自己专属的迭代器
- 迭代器种类有:输入迭代器(只读)、输出迭代器(只写)、前向迭代器(读写)、双向迭代器、随机访问迭代器。常用的容器中迭代器都是后两种
3.2.2 string容器
- string是C++风格的字符串,本质上一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器。string类内部封装了很多成员方法。string管理char*所分配的内存,不用担心复制越界和取值越界等,使用string容器时需要包含头文件
#include<string>
- string构造函数:①
string()
默认构造,创建一个空字符串 ②string(const char* s)
使用字符串s初始化;③string(const string& str)
使用一个string对象初始化另一个string对象;④string(int n,char c)
使用n个字符c初始化 - string基本操作:对string的操作可通过运算符或成员函数来完成,可操作的类型包括:char*、string、char。① 使用
=
或assign()
为string赋值;② 使用+=
或append()
进行string拼接;③ 使用find()
和rfind()
进行查找,使用replace()
进行替换;④ 使用compare()
进行string比较;⑤ 使用[]
或at()
存取string中的单个字符;⑥ 使用insert()
进行插入,erase()
进行删除;⑦ 使用substr()
获取string中的子串
3.2.3 vector容器
- vector与数组非常相似,也称为单端数组。不同之处在于数组是静态空间,而vector可以动态扩展(并不是在原空间之后续接新空间,而是找更大的空间拷贝原数据,然后释放原空间),使用vector容器时需要包含头文件
#include<vector>
- vector容器的迭代器是支持随机访问的迭代器,
v.begin()
指向容器中的第一个元素所在位置,v.end()
指向容器中最后一个元素的下一个位置
// 也可使用只读迭代器const_iterator进行遍历,当通过方法传入的vector引用为只读时,必须使用只读迭代器
for(vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout << *it << " ";
}
- vector构造函数:①
vector<T> v
默认构造函数;②vector(v.begin(),v.end())
前闭后开区间;③vector(n,elem)
;④vector(const vector& vec)
拷贝构造函数 - vector赋值操作:使用
=
或assign()
为vector赋值 - vector容量和大小:
empty()
判断容器是否为空;capacity()
返回容器的容量;size()
返回容器中元素的个数;resize(int num)
重新指定容器的长度为num,若容器变长则以默认值填充新位置,若容器变短则删除末尾元素 - vector插入和删除:
push_back(ele)
尾部插入元素;pop_back()
删除最后一个元素;insert(const_iterator pos, ele)
迭代器指向位置处插入元素;earse(const_iterator pos)
删除迭代器指向的元素;clear()
删除容器中所有元素 - vector数据存取:
at(int idx)
和[]
返回索引处所指的数据;front()
返回容器中第一个数据元素;back()
返回容器中最后一个数据元素 - vector互换容器:
swap(vec)
将两个容器内元素进行互换。使用vector<int>(v).swap(v)
可以收缩内存,让vector的容量变得与元素个数相等 - vector预留空间:可以减少vector在动态扩展容量时的扩展次数,
reserve(itn len)
容器预留指定元素长度,预留位置不初始化,元素不可访问
3.2.4 deque容器
- deque双端数组,可以对头端进行插入删除操作。相对于vector,deque对头部的插入删除更快,vector访问元素的速度更快,使用deque容器时需要包含头文件
#include<deque>
- deque内部工作原理:deque内部有个中控器,维护每段缓冲区的地址,缓冲区中存放真实数据,使得使用deque时像一片连续的内存空间
- deque容器的迭代器也支持随机访问,构造函数与vector基本相同
- deque容器的赋值操作、大小操作、插入删除操作、数据存取操作与vector基本相同,不同在于deque容器没有容量概念,可以从头部插入删除元素
push_front(ele)
头部插入元素;pop_front)
删除第一个元素 - 排序:
sort(iterator beg, iterator end)
对区间内元素进行排序,使用排序算法时需要包含头文件#include<algorithm>
,对于支持随机访问迭代器的容器,都可以使用sort直接对其排序
3.2.5 stack容器和queue容器
- stack是一种先进后出的数据结构,只有顶端的元素可以被外界使用,因此栈不允许有遍历行为,使用stack容器时需要包含头文件
#include<stack>
- stack常用操作:① 构造函数:默认构造函数和拷贝构造函数;② 赋值操作:通过重载后的等号操作符;③ 数据存取:
push(elem)
向栈顶添加元素,pop()
从栈顶移除第一个元素,top()
返回栈顶元素;④ 大小操作:empty()
判断堆栈是否为空,size()
返回栈的大小 - queue是一种先进先出的数据结构,只有队头和队尾的元素可以被外界访问,因此队列不允许有遍历行为,使用queue容器时需要包含头文件
#include<queue>
- queue常用操作:① 构造函数:默认构造函数和拷贝构造函数;② 赋值操作:通过重载后的等号操作符;③ 数据存取:
push(elem)
向队尾添加元素,pop()
从队头移除第一个元素,back()
返回队尾元素,front()
返回队头元素;④ 大小操作:empty()
判断队列是否为空,size()
返回队列的大小
3.2.6 list容器
- list将数据进行链式存储,链表是一种物理存储单元上非连续的结构,数据元素的逻辑顺序是通过链表中的指针链接实现的。链表由一系列结点组成,结点中包含存储数据的数据域,和存储下一个结点地址的指针域。STL中的list是一个双向循环链表,使用list容器时需要包含头文件
#include<list>
- 由于链表的存储方式并不是连续的内存空间,因此list中的迭代器只支持前移和后移,属于双向迭代器。list的插入和删除操作不会导致原有迭代器失效,这在vector中不成立
- list构造函数和deque类似,同样是4种。list的赋值、交换、大小、插入删除操作所用到的方法都与deque基本相同,list同样没有容量的概念,
remove(elem)
删除list中所有等于elem的元素。list的数据存取操作与queue相同,只能访问头尾元素 - list的成员函数:
reverse()
反转,sort()
排序(不支持随机访问迭代器的容器,不能使用标准的排序算法,容器内部会提供相应的成员函数进行排序),排序算法默认升序,若想降序排序可传入回调函数作为参数,自定义数据类型可以通过回调函数指定排序规则
bool myCompare(int v1, int v2){
return v1 > v2;
}
L.sort(myCompare);
3.2.7 set / multiset容器
- set / multiset属于关联式容器,底层结构是用二叉树实现,所有元素在插入时自动排序,set中不允许有重复元素,multiset中可以有重复元素,使用set / multiset容器时需要包含头文件
#include<set>
- set常用操作:① 构造函数:默认构造函数和拷贝构造函数;② 赋值操作:通过重载后的等号操作符;③ 数据存取:
insert(elem)
在set中插入元素,erase()
从set中删除元素,clear()
清空所有元素;④ 大小操作:empty()
判断set是否为空,size()
返回set中元素个数;⑤swap(st)
交换两个set;⑥ 查找和统计:find(key)
查找key是否存在,若存在返回该元素的迭代器,若不存在返回set.end()迭代器,count(key)
统计key的元素个数 - set的插入操作会返回pair数据类型,从结果中可以得到插入是否成功,multiset插入操作仅返回迭代器,可以插入重复元素
set<int> s;
pair<set<int>::iterator, bool> ret = s.insert(10);
if (ret.second){
cout << "插入成功" << endl;
}
- pair对组创建:利用队组可以返回成对出现的两个数据,有两种创建方式:①
pair<type, type> p(v1, v2)
;②pair<type, type> p = make_pair(v1, v2)
- set容器插入数据的默认排序规则为从小到大,可以利用仿函数改变排序规则:
set<T,MyCompare> s
,仿函数对应的类中重载()
运算符,返回值为bool类型数据
3.2.8 map / multimap容器
- map / multimap属于关联式容器,底层结构是用二叉树实现。map中所有元素都是pair,pair中第一个元素为key,第二个元素为value,所有元素都会根据key自动排序,可以根据key值快速找到value值。map中不允许有重复key,multimap中可以有重复key。使用map / multimap容器时需要包含头文件
#include<map>
- map常用操作:① 构造函数:默认构造函数
map<T1,T2> m
和拷贝构造函数;② 赋值操作:通过重载后的等号操作符;③ 数据存取:insert(pair<T1,T2>(a,b))
在map中插入pair,[]
可用于按key插入或访问value,erase()
从map中删除元素,clear()
清空所有元素;④ 大小操作:empty()
判断map是否为空,size()
返回map中元素个数;⑤swap(st)
交换两个map;⑥ 查找和统计:find(key)
查找key是否存在,若存在返回该pair的迭代器,若不存在返回map.end()迭代器,count(key)
统计key的元素个数 - 可以利用仿函数改变key的排序规则:
map<T1,T2,MyCompare> m
3.3 STL函数对象
- 重载函数调用操作符
()
的类,其对象称为函数对象,也叫仿函数。函数对象(仿函数)是一个类,不是一个函数 - 函数对象在使用时,像普通函数一样调用,可以有参数和返回值。函数对象相较于普通函数而言,可以有自己的状态(如在类中加入属性记录函数调用次数),可以作为参数传递
- 返回bool类型的仿函数称为谓词,如果
operator()
接收一个参数称为一元谓词,接收两个参数称为二元谓词 - STL内建了一些函数对象,包括:算术仿函数、关系仿函数、逻辑仿函数,使用内建函数对象需要引入头文件
#include<functional>
- 算术仿函数:用于实现四则运算,其中negate(取反)是一元运算,其他都是二元运算。关系仿函数:用于实现关系对比。逻辑仿函数:实现逻辑运算
// 取反仿函数
negate<int> n;
cout << n(10) << endl;
// 加法仿函数
plus<int> p;
cout << p(1, 2) << endl;
// 大于仿函数
sort(v.begin(), v.end(), greater<int>());
// 逻辑非仿函数
transform(v1.begin(), v1.end(), v2.begin(), logical_not<bool>());
3.4 STL算法
算法主要由头文件<algorithm> <functional> <numeric>
组成
3.4.1 常用遍历算法
for_each(iterator beg,iterator end,_fun)
遍历容器,_fun为函数或仿函数,普通函数传函数名,仿函数还要加括号transform(iterator beg1,iterator end1,iterator beg2,_fun)
搬运容器到另一个容器中,beg2为目标容器开始迭代器,_fun为在搬运过程中的操作,目标容器的空间不能小于源容器的空间
3.4.2 常用查找算法
find(iterator beg,iterator end,value)
查找元素,找到返回指定元素的迭代器,找不到返回结束迭代器end(),自定义数据类型需要重载==操作符find_if(iterator beg,iterator end,_Pred)
按条件查找元素,_Pred为函数或谓词adjacent_find(iterator beg,iterator end)
查找相邻重复元素,找到返回相邻元素第一个位置的迭代器binary_search(iterator beg,iterator end,value)
二分查找法,查找指定元素是否存在,只能用于有序序列,返回值为bool类型count(iterator beg,iterator end,value)
统计元素个数,返回值为int类型count_if(iterator beg,iterator end,_Pred)
按条件统计元素个数
3.4.3 常用排序算法
sort(iterator beg,iterator end,_Pred)
对容器内元素进行排序,不传_Pred时默认从小到大排序random_shuffle(iterator beg,iterator end)
指定范围内元素随机调整次序merge(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest)
容器元素合并,并存储到另一容器中。两个容器必须是有序的,合并后依然有序。dest为目标容器开始迭代器,目标容器的空间必须不小于两个容器空间之和reverse(iterator beg,iterator end)
反转指定范围内元素
3.4.4 常用拷贝和替换算法
copy(iterator beg,iterator end,iterator dest)
将容器内的元素拷贝到另一容器中replace(iterator beg,iterator end,oldvalue,newvalue)
将容器内的旧元素替换为新元素replace_if(iterator beg,iterator end,_Pred,newvalue)
将容器内满足条件的旧元素替换为新元素swap(container c1,container c2)
互换两个容器的元素,两个容器必须是同种类型
3.4.5 常用算术生成算法
- 算术生成算法属于小型算法,使用时需包含头文件
#include<numeric>
accumulate(iterator beg,iterator end,value)
计算容器元素累计总和,value为起始累加值fill(iterator beg,iterator end,value)
向容器中添加元素,value为填充内容
3.4.6 常用集合算法
set_intersection(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest)
求两个容器的交集,两个容器内元素必须有序,返回交集最后一个元素的迭代器地址set_union(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest)
求两个容器的并集,两个容器内元素必须有序,返回并集最后一个元素的迭代器地址set_difference(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest)
求两个容器的差集,两个容器内元素必须有序,返回差集最后一个元素的迭代器地址