学习路线
基本语法 通常头文件我们都选择使用,虽然<bits/stdc++.h>包含c++所有的头文件,但是会产生很多我们不使用的头文件,导致编译时间增长。个人认为输入输出还是使用c的函数为好(c的输入输出更快)。
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> #include <bits/stdc++.h> #include <cstdio> using namespace std; int main () { int n; cin>>n; cout<<n<<endl; return 0 ; }
变量的定义
相比c语言,c++多了string字符串类型的定义
宏常量
一般在main函数的上面声明,用大写命名.
命名规则 c++规定给标识符(变量、常量、函数、结构体、类等)命名时,必须遵守以下规则。
在名称中只能使用字母字符、数字和下划线
名称的第一个字符不能是数字
区分大写字符与小写字符
不能将C++关键词用作名称
以下划线和大写字母打头的名称被保留给编译器机器使用的资源使用,如果违反了这一规则,会导致行为的不确定性
C++对名称的长度没有限制,但有些平台可能有长度限制
算术运算 注意:
整数进行除法运算时,如果分母为0,程序将异常退出
浮点数进行除法运算时,如果分母为0.0,将得到inf(nfinite,无穷大)
两个整数进行除法运算时,将舍去小数部分,得到一个整数
整数与浮点数进行除法运算时,得到的结果是浮点数
在整数前面加(float)或(double)可以将整数转换为float或double类型
取模运算只能用于整数(分母也不能为0)
赋值运算 注意
字符串(string)只能使用等号赋值,不能使用其他的赋值运算符
浮点数不能使用%=运算符
C++初始化
把值写在小括号中,等于号可以省略(C++标准)
把值写在花括号中,等于号也可以省略(C++11标准)
注意:在Linux平台下,编译需要加-std=c++11 参数。
C++类型转换 功能 :
C++类型转换符 :
static_cast
dynamic_cast
reinterpret_cast
const_cast
使用方式 :xx_cast<type>(expression)
C语言的风格:
C++语言风格:
1 xx_cast <type>(expression);
const_cast转换符
读写优化 1 2 ios::sync_with_stdio (false ); cin.tie (0 ),cout.tie (0 );
命名空间 用于解决名字冲突
使用关键字namespace,控制名称的作用域
命名空间的本质 :对符号常量、变量、函数、结构、枚举、类和对象等等进行封装
命名空间语法 :namespace A{}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 namespace A{ int data=10 ; namespace C{ int data=30 ; } }namespace B{ int data=20 ; }namespace D{ int data=10 ; }namespace D{ void func () { cout<<"func函数" <<endl; } }void test01 () { using A::a; cout<<"a=" <<a<<endl; cout<<"A::data= " <<A::data<<endl; cout<<"A::B::data= " <<A::B::data<<endl; }
注意事项 :
命名空间只能定义在全局中
命名空间可以嵌套
命名空间是开放的,可以随时将新的成员加入命名空间
命名空间可以声明和实现分离(外部实现时要加作用域)
无名空间(namespace{}
)只能在本源文件使用,里面的成员相当于静态全局成员一样
命名空间起别名(namespace 别名=要取别名的空间名
)
使用using声明命名空间中的某几个成员可用,容易造成名字冲突(会合普通变量名字冲突)
using使用命名空间中的函数重载过的函数,函数重载,命名空间的所有同名函数都被声明
using编译指令 :
using声明整个命名空间,可以直接通过成员名使用
语法:using namespace 空间名
注意事项 :如果外部有定义空间中的变量,调用时则会先找普通变量,再找命名空间的,全局范围的变量会与局部的命名空间的变量产生冲突
内存分区模型 C++程序运行时,将内存大方向划分为4个区域
代码区:存放函数体的二进制代码,由操作系统进行管理的
全局区:存放全局变量和静态变量以及常量
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义 :
不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
程序运行前 在程序编译后,生成了exe可执行程序,为执行该程序前分为两个区域
代码区 :
存放cpu执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程 序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修 改了它的指令
全局区 :
全局变量和静态变量存放在此
全局区还包含了常量区,字符串常量和其他常量也存放 在此
该区域的数据在程序结束后由操作系统释放
总结 :
C++中在程序运行前分为全局区和代码区
代码区特点是共享和只读
全局区存放全局变量、静态变量、常量
常量区中存放const修饰的全局常量和字符串常量
程序运行后 栈区 :
由编译器自动分配释放,存放函数的参数值,局部变量 等
注意事项:不要返回局部变量的地址,栈区开辟的数据 由编译器自动释放
堆区 :
由程序员分配释放,若程序员不释放,程序结束时由操作 系统回收
在C++中主要利用new在堆区开辟内存
总结 :
堆区数据由程序员管理开辟和释放
堆区数据利用new关键字进行开辟内存
new操作符 C++中利用new 操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用 delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> using namespace std;int *func () { int *p=new int (10 ); return p; }void test01 () { int *p=func (); cout<<*p<<endl; cout<<*p<<endl; cout<<*p<<endl; delete p; cout<<*p<<endl; }void test02 () { int *arr=new int [10 ]; for (int i=0 ;i<10 ;i++){ arr[i]=i+100 ; } for (int i=0 ;i<10 ;i++){ cout<<arr[i]<<endl; } delete [] arr; }int main () { test01 (); test02 (); return 0 ; }
引用 引用的基本使用 作用:给变量起别名
语法:数据类型 &别名 = 原名
1 2 3 4 5 6 7 8 9 10 #include <iostream> using namespace std;int main () { int a=10 ; int &b=a; cout<<"a=" <<a<<endl; cout<<"b=" <<b<<endl; return 0 ; }
引用注意事项
引用做函数参数 作用 :函数传参时,可以利用引用的技术让形参修饰实参
优点 :可以简化指针修改实参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;void myswap (int &a,int &b) { int temp=a; a=b; b=temp; }int main () { int a=10 ; int b=20 ; myswap (a,b); cout<<"a= " <<a<<endl; cout<<"b= " <<b<<endl; return 0 ; }
总结 :通过引用参数产生的效果同地址传递是一样的,引用的语法更清楚更简单
引用做函数返回值 作用 :引用是可以作为函数的返回值存在的
注意 :不要返回局部变量引用
用法 :函数调用作为左值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;int & test01 () { int a=10 ; return a; }int & test02 () { static int a=10 ; return a; }int main () { int &ref=test01 (); cout<<"ref= " <<ref<<endl; cout<<"ref= " <<ref<<endl; int &ref2=test02 (); cout<<"ref2= " <<ref2<<endl; cout<<"ref2= " <<ref2<<endl; test02 ()=1000 ; cout<<"ref2= " <<ref2<<endl; cout<<"ref2= " <<ref2<<endl; return 0 ; }
引用的本质 本质 :引用的本质在C++内部实现是一个指针常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;void func (int & ref) { ref=100 ; }int main () { int a=10 ; int & ref=a; ref=20 ; cout<<"a:" <<a<<endl; cout<<"ref:" <<ref<<endl; func (a); return 0 ; }
总结 :C++推荐使用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
常量引用 作用 :常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> using namespace std;void showValue (const int &val) { cout<<"val= " <<val<<endl; }int main () { int &ref=10 ; const int &ref=10 ; int a=100 ; showValue (a); cout<<"a= " <<a<<endl; return 0 ; }
函数提高 函数默认参数 在C++中,函数的形参列表中的形参是可以有默认值的。
语法 :返回值类型 函数名 (参数 = 默认值){}
注意事项 :
如果某个位置已经有了默认参数,那么从这个位置往后,从左向右的形参都必须要有默认参数
如果函数声明有默认参数,函数实现就不能有默认参数
声明和实现只能有一个有默认参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> using namespace std;int func (int a,int b=20 ,int c=30 ) { return a+b+c; }int main () { cout<<func (10 )<<endl; cout<<func (10 ,30 )<<endl; return 0 ; }
函数占位参数 C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法 :返回值类型 函数名(数据类型){}
在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该技术
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> using namespace std;void func (int a,int ) { cout<<"this is func" <<endl; }void func1 (int a,int =10 ) { cout<<"this is func1" <<endl; }int main () { func (10 ,10 ); func1 (10 ); return 0 ; }
函数重载 概述 作用 :函数名可以相同,提高复用性
函数重载满足条件 :
同一个作用域下
函数名称相同
函数参数类型不同 或者个数不同 或者顺序不同
注意 :函数的返回值类型不可以作为函数重载的条件
例如:函数func返回值类型为int,而还有一个func函数返回值为void,这个时候编译器会报错!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> using namespace std;void func () { cout<<"func的调用" <<endl; }void func (int a) { cout<<"func(int a)的调用" <<endl; }void func (double a) { cout<<"func(double a)的调用" <<endl; }void func (int a,double b) { cout<<"func(int a,double b)的调用" <<endl; }void func (double a,int b) { cout<<"func(double a,int b)的调用" <<endl; }int main () { func (); func (10 ); func (0.1 ); func (10 ,0.1 ); func (0.1 ,10 ); return 0 ; }
函数重载注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> using namespace std;void func (int &a) { cout<<"func(int &a)的调用" <<endl; }void func (const int &a) { cout<<"func(const int &a)的调用" <<endl; }void func2 (int a,int b=10 ) { cout<<"func2(int a,int b)的调用" <<endl; }void func2 (int a) { cout<<"func2(int a)的调用" <<endl; }int main () { int a=10 ; func (a); func (10 ); func2 (10 ); return 0 ; }
内联函数 概念 :
内联函数
:内联函数其实就是在程序编译时,会将该函数的代码副本放置在每个调用该函数的地方,省去了函数调用的开销。
使用场景
:对于代码量少且函数执行的操作简单,以及调用函数开销要大于执行函数体所需要的开销 ,我们可以考虑将其变为内联函数,提高函数的执行效率
注:
内联是以代码膨胀(拷贝)为代价
,仅仅省去了函数调用的开销,并不包括执行函数体所需要的开销,而是仅指参数压栈、跳转、退栈和返回等操作。
如果执行函数体内代码的时间比函数调用的开销大 ,则inline 也就是内联的效率收益会很小
每一处内联函数的调用都要拷贝代码,将使程序的总代码量增大,消耗更多的内存空间
使用方法
:**使用inline关键字放在函数定义(不是声明,而是函数实现)**,将函数变为内联函数来提高函数的执行效率
inline是一种”用于实现的关键字”,也就是应在函数定义使用,而不是声明
类中的成员函数自动内联
使用限制
:只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句,例如while、switch,并且内联函数本身不能是直接递归函数 (即自己内部还调用自己)
缺点 :
消耗更多的内存空间
对内联函数进行任何修改,都需要重新编译函数的所有客户端 ,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数
使用示例 :
1 2 3 4 5 6 7 8 9 10 11 12 inline int Foo (int a,int b) ;int Foo (int a,int b) { return a+b; }int Foo (int a,int b) ;inline int Foo (int a,int b) { return a+b; }
类和对象 C++面向对象的三大特性为:封装 、继承 、多态
C++认为万事万物都皆为对象,对象上有其属性和行为
封装 封装的意义 封装是C++面向对象三大特性之一
封装的意义:
将属性和行为作为一个整体,表现生活中的事物
将属性和行为加以权限控制
封装意义一 :
在设计类的时候,属性和行为写在一起,表现事物
语法 :class 类名{ 访问权限:属性 / 行为 };
类中的属性和行为,我们统一称为成员
属性:成员属性 成员变量
行为:成员函数 成员方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;class Circle { public : int m_r; double calculateZC () { return 2 *PI*m_r; } };int main () { Circle c1; c1.m_r=10 ; cout<<"圆的周长为:" <<c1.calculateZC ()<<endl; return 0 ; }
封装的意义二 :
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
public 公共权限,成员类内外可以访问
protected 保护权限,成员类内可以访问,类外不可以访问,在继承中,父类的保护权限的内容,子类也可以访问
private 私有权限,成员类内可以访问,类外不可以访问,父类的私有权限的内容,子类不可以访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std;class Person { public : string m_Name; protected : string m_car; private : int m_password;public : void func () { m_Name="张三" ; m_car="拖拉机" ; m_password=123456 ; } };int main () { Person p1; p1.m_Name="李四" ; p1.m_car="奔驰" ; p1.m_password=12 ; p1.func (); return 0 ; }
struct和class区别 在C++中struct和class唯一的区别就在于默认的访问权限不同
区别:
struct 默认权限为公共
class 默认权限为私有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;class C1 { int m_A; };struct C2 { int m_A; }int main () { C1 c1; c1.m_A=100 ; C2 c2; c2.m_A=100 ; return 0 ; }
成员属性设置为私有 优点1 :将所有成员属性设置为私有,可以自己控制读写权限
优点2 :对于写权限,我们可以监测数据的有效性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <string> using namespace std;class Person {public : void setName (string name) { m_Name=name; } string getName () { return m_Name; }private : string m_Name; int m_Age; string m_lover; }int main () { Person p1; p1.setName ("张三" ); cout<<"姓名:" <<p1.getName ()<<endl; return 0 ; }
对象的初始化和清理 C++中的面向对象来源于生活,每个对象也会有初始化设置以及对象销毁前的清理数据的设置
构造函数和析构函数 对象的初始化和清理 也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知的
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数 和析构函数 解决上述问题,这两个函数将会被编译器自动调用,完成对象的初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供
编译器提供的构造函数和析构函数是空实现
构造函数 :主要用于创建的对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
析构函数 :主要作用在于对象销毁前 系统自动调用,执行一些清理工作。
构造函数 :
语法:类名(){}
构造函数,没有返回值也不写void
函数名称与类名相同
构造函数可以有参数,因此可以发生重载
程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数 :
语法:*~类名(){}*
析构函数,没有返回值也不写void
函数名称与类名相同,在名称前加上符号~
析构函数不可以有参数,因此不可以发生重载
程序在对象销毁前会自动化调用,无须手动调用,而且只会调用一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;class Person {public : Person (){ cout<<"Person 构造函数的调用" <<endl; } ~Person (){ cout<<"Person 析构函数调用" <<endl; } };void test01 () { Person p; }int main () { Person p; return 0 ; }
析构函数的分类及调用 两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
注意事项:
调用默认构造函数的时候不用加(),加入了括号,编译器会认为 是一个函数的声明,不会认为在创建对象
不要利用拷贝构造函数初始化匿名对象,编译器会认为Person(p3)==Person p3; 造成重定义报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> using namespace std;class Person {public : Person (){ cout<<"Person的无参构造函数的调用" <<endl; } Person (int a){ age=a; cout<<"Person的有参构造函数的调用" <<endl; } Person (const Person &p){ age=p.age; } ~Person (){ cout<<"Person 析构函数调用" <<endl; } int age; };void test01 () { Person p; Person p2 (10 ) ; Person p3 (p2) ; Person p1; Person p2=Person (10 ); Person p3=Person (p2); Person (10 ); Person p4=10 ; Person p5=p4; }int main () { test01 (); return 0 ; }
拷贝构造函数调用时机 C++中拷贝构造函数调用时机通常有三种情况:
使用一个已经创建完毕的对象来初始化一个新对象
值传递的方式给函数参数传值
以值方式返回局部对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> using namespace std;class Person {public : Person (){ cout<<"Person默认构造函数调用" <<endl; } Person (int age){ cout<<"Person有参构造函数调用" <<endl; m_Age=age; } Person (const Person &p){ cout<<"Person拷贝构造函数调用" <<endl; m_Aoge=p.m_Age; } };void test01 () { Person p1 (20 ) ; Person p2 (p1) ; }void doWork (Person p) { }void test02 () { Person p; doWotk (p); }Person doWork2 () { Person p1; cout<<(int *)&p1<<endl; return p1; }void test03 () { Person p=doWork2 (); cout<<(int *)&p<<endl; }int main () { test01 (); test02 (); test03 (); return 0 ; }
构造函数调用规则 默认情况下,C++编译器至少给一个类添加3个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,C++不会再提供其他构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> using namespace std;class Person {public : Person (){ cout<<"Person默认构造函数调用" <<endl; } Person (int age){ cout<<"Person有参构造函数调用" <<endl; m_Age=age; } Person (const Person &p){ cout<<"Person拷贝构造函数调用" <<endl; m_Age=p.m_Age; } ~Person (){ cout<<"Person析构函数调用" <<endl; } int m_Age; };void test01 () { Person p; p.m_Age=18 ; Person p2 (p) ; cout<<"p2的年龄为:" <<p2.m_Age<<endl; }int main () { return 0 ; }
深拷贝与浅拷贝 深浅拷贝是面试的经典问题,也是一个常见的坑
浅拷贝 :简单的赋值拷贝操作
深拷贝 :在堆区重新申请空间,进行拷贝操作
浅拷贝带来的问题就是堆区的内存重复释放
浅拷贝的问题要利用深拷贝进行解决,自己写一个拷贝函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> using namespace std;class Person {public : Person (){ cout<<"Person的默认构造函数调用" <<endl; } Person (int age,int height){ m_Age=age; m_Height=new int (height); cout<<"Person有参构造函数调用" <<endl; } Person (const Person &p){ cout<<"拷贝构造函数的调用" <<endl; m_Age=p.m_Age; m_Height=new int (*p.m_Height); } ~Person (){ if (m_Height!=NULL ){ delete m_Height; m_Height=NULL ; } cout<<"Person析构函数调用" <<endl; } int m_Age; int *m_Height };void test01 () { Person p1 (18 ,160 ) ; cout<<"p1的年龄为: " <<p1.m_Age<<"p1的身高为:" <<*p1.m_Height<<endl; Person p2 (p1) ; cout<<"p2的年龄为:" <<p2.m_Age<<"p2的身高为:" <<*p2.m_Height<<endl; }int main () { test01 (); return 0 ; }
总结 :如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
初始化列表 作用 :
C++提供了初始化列表语法,用来初始化属性
语法 :构造函数():属性1(值1),属性2(值2) …{}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std;class Person {public : Person (int a,int b,int c):m_A (a),m_B (b),m_C (c){ } int m_A; int m_B; int m_C; }void test01 () { Person p (10 ,20 ,30 ) ; Person p1 (30 ,20 ,10 ) ; }int main () { return 0 ; }
类对象作为类成员 C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
例如:
1 2 3 4 class A {};class B { A a; };
B类中有对象作为一个成员,A为对象成员
那么创建B对象时,A与B的构造和析构的顺序是谁先谁后?
先构造A对象再构造B对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Phone {public : Phone (string PName){ m_PName=PName; cout<<"Phone的构造函数调用" <<endl; } ~Phone (){ cout<<"Phone的析构函数调用" <<endl; } string m_PName; };class Person {public : Person (string name,string pName):m_Name (name),m_Phone (pName){ cout<<"Person构造函数调用" <<endl; } ~Person (){ cout<<"Person析构函数调用" <<endl; } string m_Name; Phone m_Phone; };void test01 () { Person p ("张三" ,"苹果MAX" ) ; }
总结 :当其他类对象作为本类成员,我们称该成员为对象成员 ,构造顺序是:先调用对象成员的构造,再调用本类构造,析构顺序与构造相反。
静态成员 静态成员就是在成员变量和成员函数前加上关键词static,称为静态成员
静态成员分为:
静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化(必须的操作)
静态成员函数
所有成员共享同一个函数
静态成员函数只能访问静态成员变量
静态成员变量:
注意事项 :静态成员变量也是有访问权限的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Person {public : static int m_A; };int Person::m_A=100 ; void test01 () { Person p; cout<<p.m_A<<endl; Person p2; p2.m_A=200 ; cout<<p.m_A<<endl; }void test02 () { Person p; cout<<p.m_A<<endl; cout<<Person::m_A<<endl; }
静态成员函数:
注意事项 :静态成员函数也是有访问权限的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Person {public : static void func () { m_A=100 ; m_B=200 ; cout<<"static void func函数的调用" <<endl; } static int m_A; int m_B; };int Person::m_A=0 ;void test01 () { Person p; p.func (); Person::func (); }
C++对象模型和this指针 成员变量和成员函数分开存储 在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Person {public : int m_A; static int m_B; void func () {} static void func2 () {} };int Person::m_B=0 ;void test01 () { Person p; cout<<"size of p= " <<sizeof (p)<<endl; }void test02 () { Person p; cout<<"size of p= " <<sizeof (p)<<endl; }
this指针 通过上一个章节我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
概念 :
C++通过提供特殊的对象指针—-this指针,来进行区分哪个对象调用哪一块代码,this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途 :
解决名称冲突 :当形参和成员变量同名时,可用this指针来区分
返回对象本身: 在类的非静态成员函数中返回对象本身,可使用*return this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Person {public : Person (int age){ this ->age=age; } Person& PersonAddAge (Person &p) { this ->age+=p.age; return *this ; } int age; };void test01 () { Person p1 (18 ) ; cout<<"p1的年龄为:" <<p1.age<<endl; }void test02 () { Person p1 (10 ) ; Person p2 (10 ) ; p2.PersonAddAge (p1).PersonAddAge (p1).PersonAddAge (p1); cout<<"p2的年龄为:" <<p2.age<<endl; }
空指针访问成员函数 C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Person {public : void showClassName () { cout<<"this is Person class" <<endl; } void showPersonAge () { if (this ==NULL ){ return ; } cout<<"age = " <<m_Age<<endl; } int m_Age; };void test01 () { Person *p=NULL ; p->showClassName (); p->showPersonAge (); }
const修饰成员函数 常函数 :
成员函数后 加入const后我们称这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键词mutable 时,在常函数和常对象中依然可以修改
常对象 :
声明对象前加const后称该对象为常对象
常对象只能调用常函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Person {public : void showPerson () const { this ->m_B=100 ; } void func () { m_A=100 ; } int m_A; mutable int m_B; };void test01 () { Person p; p.showPerson (); }void test02 () { const Person p; p.m_A=100 ; p.m_B=100 ; p.showPerson (); p.func (); }
友元 在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键词为 friend
友元的三种实现:
全局函数做友元 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Building { friend void test01 (Building *building) ;public : Building (){ m_SittingRoom="客厅" ; m_BedRoom="卧室" ; } string m_SittingRoom;private : string m_BedRoom; };void test01 (Building *building) { cout<<"全局函数正在访问:" <<building->m_SittingRoom<<endl; cout<<"全局函数正在访问:" <<building->m_BedRoom<<endl; }void test02 () { Building building; test01 (&building); }
类做友元 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Building ;class gy {public : gy (); void visit () ; Building *building; };class Building { friend class gy ; public : Building (); string m_SittingRoom;private : string m_BedRoom; }; Building::Building (){ m_SttingRoom="客厅" ; m_BedRoom="卧室" ; } gy::gy (){ building=new Building; }void gy::visit () { cout<<"gy类正在访问:" <<building->m_SttingRoom<<endl; cout<<"gy类正在访问:" <<building->m_BedRoom<<endl; }void test01 () { gy gg; gg.visit (); }
成员函数做友元 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Building ;class gy {public : gy (); void visit () ; void visit1 () ; Building *building; };class Building { friend void gy::visit () ;public : Building (); string m_SttingRoom;private : string m_BedRoom; }; Building::Building (){ m_SttingRoom="客厅" ; m_BedRoom="卧室" ; } gy::gy (){ building=new Building; }void gy::visit () { cout<<"visit函数正在访问:" <<building->m_SttingRoom<<endl; cout<<"visit函数正在访问:" <<building->m_BedRoom<<endl; }void gy::visit1 () { cout<<"visit1函数正在访问:" <<building->m_SttingRoom<<endl; cout<<"visit函数正在访问:" <<building->m_BedRoom<<endl; }void test01 () { gy gg; gg.visit (); gg.visit1 (); }
运算符重载 运算符重载概念 :对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型
注意事项 :运算符重载也可以发生函数重载
用operator运算符 作为运算符的重载的运算符标记
加号运算符重载 作用 :实现两个自定义数据类型相加的运算
重载实现方法:
用**operator+**作为加号运算符的重载的运算符标记
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Person {public : Person operator +(Person &p){ Person temp; temp.m_A=this ->m_A+p.m_A; temp.m_B=this ->m_B+p.m_B; return temp; }; int m_A; int m_B; }; Person operator +(Person &p1,Person &p2){ Person temp; temp.m_A=p1.m_A+p2.m_A; temp.m_B=p1.m_B+p2.m_B; return temp; } Person operator +(Person &p1,int num){ Person temp; temp.m_A=p1.m_A+num; temp.m_B=p1.m_B+num; return temp; }void test01 () { Person p1; p1.m_A=10 ; p1.m_B=10 ; Person p2; p2.m_A=10 ; p2.m_B=10 ; Person p3=p1.operator +(p2); Person p3=p1+p2; Person p4=p1+10 ; cout<<"p3.m_A= " <<p3.m_A<<endl; cout<<"p3.m_B= " <<p3.m_B<<endl; }
总结 :
对于内置的数据类型的表达式的运算符是不可能改变的
不要滥用运算符重载
左移运算符重载 作用 :可以输出自定义数据类型
重载实现方法:
注意事项 :重载的成员函数要想访问类的私有成员,就得利用友元技术
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Person { friend ostream &operator <<(ostream &cout,Person &p);public : Person (int a,int b){ m_A=a; m_B=b; }private : int m_A: int m_B; }; ostream &operator <<(ostream &cout,Person &p){ cout<<"m_A= " <<p.m_A<<" m_B= " <<p.m_B; return cout; }void test01 () { Person p (10 ,10 ) ; cout<<p<<endl; }
总结 :重载左移运算符配合友元可以实现输出自定义数据类型
递增运算符重载 作用 :通过重载递增运算符,实现自己的整型数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class MyInteger { friend ostream operator <<(ostream &cout,MyInteger myint)public : MyInteger (){ m_Num=0 ; } MyInteger& operator ++(){ m_Num++; return *this } 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; }void test01 () { MyInteger myint; cout<<myint<<endl; }void test02 () { MyInteger myint; cout<<myint++<<endl; cout<<myint<<endl; }
总结 :前置递增返回引用,后置递增返回值
赋值运算符重载 C++编译器至少给一个类添加4个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题(堆区内存重复释放,导致程序崩溃)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Person {public : Person (int age){ m_Age=new int (age); } ~Person (){ if (m_Age!=NULL ){ delete m_Age; m_Age=NULL ; } } Person& operator =(Person &p){ if (m_Age!=NULL ){ delete m_Age; m_Age=NULL ; } m_Age=new int (*p.m_Age); return *this ; } int *m_Age; };void test01 () { Person p1 (18 ) ; Person p2 (20 ) ; Person p3 (30 ) ; p3=p2=p1; cout<<"p1的年龄:" <<*p1.m_Age<<endl; cout<<"p2的年龄:" <<*p2.m_Age<<endl; cout<<"p3的年龄:" <<*p3.m_Age<<endl; }
关系运算符重载 作用 :重载关系运算符,可以让两个自定义类型对象进行对比操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Person {public : Person (string name,int age){ m_Name=name; m_Age=age; } bool operator ==(Person &p){ if (this ->m_Name==p.m_Name&&this ->m_Age==p.m_Age){ return true ; } return false ; } bool operator !=(Person &p){ if (this ->m_Name!=p.m_Name&&this ->m_Age!=p.m_Age){ return false ; } return true ; } string m_Name; int m_Age; };void test01 () { Person p1 ("Tom" ,18 ) ; Person p2 ("Jerry" ,18 ) ; if (p1==p2){ cout<<"p1和p2是相等的" <<endl; }else { cout<<"p1和p2不相等" <<endl; } if (p1!=p2){ cout<<"p1和p2不相等" <<endl; }else { cout<<"p1和p2是相等的" <<endl; } }
函数调用运算符重载(仿函数)
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class MyPrint {public : void operator () (string test) { cout<<test<<endl; } };void test01 () { MyPrint myprint; myprint ("hello world" ); }class MyAdd {public : int operator () (int num1,int num2) { return num1+num2; } };void test02 () { MyAdd myadd; int ret=myadd (100 ,100 ); cout<<"ret= " <<ret<<endl; cout<<MyAdd ()(100 ,100 )<<endl; }
继承 继承是面向对象的三大特性之一
我们发现,定义一些类时,下级别的成员除了拥有上一级的共性,还有自己的特性
这个时候我们就考虑利用继承的技术,减少重复代码
继承的基本语法 语法 :class 子类:继承方式 父类类名
子类 也成为派生类
父类 也称为基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class BasePage {public : void header () { cout<<"首页、公开课(公共头部)" <<endl; } void footer () { cout<<"帮助中心...(公共脚部)" <<endl; } };class Java :public BasePage{public : void content () { cout<<"Java学科视频" <<endl; } };class Python :public BasePage{public : void content () { cout<<"python学科视频" <<endl; } };class Cpp :public BasePage{public : void content () { cout<<"C++学科视频" <<endl; } };void test01 () { cout<<"Java下载视频页面如下:" <<endl; Java ja; ja.header (); ja.footer (); ja.content (); cout<<"python下载视频页面如下:" <<endl; Python py; py.header (); py.footer (); py.content (); cout<<"C++下载视频页面如下:" <<endl; Cpp cpp; cpp.header (); cpp.footer (); cpp.content (); }
总结 :
继承的好处:可以减少重复的代码
派生类中的成员,包含两大部分:
一类是从基类继承过来的,从基类继承过来的表现其共性
一类是自己增加的成员,新增的成员体现其个性
继承方式 继承方式一共有三种 :
公共继承
父类的公共权限到子类依然是公共权限
父类的保护权限到子类中依然是保护权限
父类中的私有权限成员子类访问不到
保护继承
父类中公共成员到子类中,变为保护权限
父类中保护成员到子类中,依然为保护权限
父类中的私有权限成员子类访问不到
私有继承
父类的公共成员到子类变为私有成员
父类的保护成员到子类变为私有成员
父类中的私有权限成员子类访问不到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Base1 {public : int m_A;protected : int m_B;private : int m_C; };class Son1 :public Base1{public : void func () { m_A=10 ; m_B=10 ; } };class Son2 :protected Base1{public : void func () { m_A=100 ; m_B=100 ; } };class Son3 :private Base1{public : m_A=100 ; m_B=100 ; };void test01 () { Son1 s1; Son2 s2; Son3 s3; s1.m_A=100 ; }
继承中的对象模型 父类的所有非静态成员属性,子类都继承下来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Base {public : int m_A;protected : int m_B;private : int m_C; };class Son :public Base{public : int m_D; };void test01 () { cout<<"size of Son = " <<sizeof (Son)<<endl; }
总结 :父类的私有属性也被子类继承下来了,只是访问不到,但是依然存在于子类中
继承中的构造和析构顺序 子类继承父类后,当创建子类对象,也会调用父类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Base {public : Base (){ cout<<"Base构造函数" <<endl; } ~Base (){ cout<<"Base析构函数" <<endl; } };class Son :public Base{public : Son (){ cout<<"Base构造函数" <<endl; } ~Son (){ cout<<"Base析构函数" <<endl; } };void test01 () { Son s; }
总结 :
先构造父类,再构造子类,析构顺序与构造顺序相反
继承同名成员处理方式
访问子类同名成员,直接访问即可
访问父类同名成员,需要加作用域
访问父类同名成员语法:
子类实例对象.父类名称::要调用的父类同名成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Base {public : Base (){ m_A=100 ; } void func () { cout<<"Base-func函数调用" <<endl; } void func (int a) { cout<<"Base-func(int)函数调用" <<endl; } int m_A; };class Son :public Base{public : Son (){ m_A=200 ; } void func () { cout<<"Son-func函数调用" <<endl; } void func (int a) { cout<<"Son-func(int)函数调用" <<endl; } int m_A; };void test01 () { Son s; cout<<"Son 下m_A" <<s.m_A<<endl; cout<<"Son 下m_A" <<s.Base::m_A<<endl; }void test02 () { Son s; s.func (); s.Base::func (); }
总结 :
子类对象可以直接访问到子类中同名成员
子类对象加作用域可以访问到父类同名成员
当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
继承同名静态成员处理方式 静态成员和非静态成员出现同名,处理方式一致
访问子类同名成员,直接访问即可
访问父类同名成员,需要加作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Base {public : static int m_A; static void func () { cout<<"Base-static func的调用" <<endl; } };int Base::m_A=100 ;class Son :public Base{public : static int m_A; static void func () { cout<<"Son-static func的调用" <<endl; } };int Son::m_A=200 ;void test01 () { Son s; cout<<"Son m_A=" <<s.m_A<<endl; cout<<"Base m_A=" <<s.Base::m_A<<endl; cout<<"通过类名访问静态成员" <<endl; cout<<"Son m_A=" <<Son::m_A<<endl; cout<<"Base m_A=" <<Son::Base::m_A<<endl; }void test02 () { Son s; s.func (); s.Base::func (); Son::func (); Son::Base::func (); }
总结 :
同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类名)
多继承语法 C++中允许一个类继承多个类
语法 :
class 子类:继承方式 父类1,继承方式 父类2…
注意事项 :
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Base1 {public : Base (){ m_A=100 ; } int m_A; };class Base2 {public : Base2 (){ m_A=200 ; } int m_A; };class Son :public Base1,public Base2{public : Son (){ m_C=300 ; m_D=400 ; } int m_C; int m_D; };void test01 () { Son s; cout<<"sizeof Son=" <<sizeof (s)<<endl; cout<<"m_A=" <<s.Base1::m_A<<endl; }
菱形继承 概念 :
两个派生类继承同一个基类,又有某个类同时继承着两个派生类,这种继承被称为菱形继承,或者钻石继承
问题 :
同名成员产生二义性,并且继承两个派生类的类会继承两份来自同一个基类的数据(资源浪费),但是实际只需要一份就行了
解决方法 :
利用虚继承解决菱形继承的问题,语法 :class 子类:virtual public 父类{}; ,则父类被称为虚基类,进行了虚继承(底层利用虚基类指针,让两个类都指向同一份数据),则继承的数据是共享的,更改两份继承下来的数据就是更改一份数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Animal {public : int m_Age; };class Sheep :virtual public Animal{ };class Tuo :virtual public Animal{ };class SheepTuo :public Sheep,public Tuo{ };void test01 () { SheepTuo st; st.Sheep::m_Age=18 ; st.Tuo::m_Age=28 ; cout<<"st.Sheep::m_Age=" <<st.Sheep::m_Age<<endl; cout<<"st.Tuo::m_Age= " <<st.Tuo::m_Age<<endl; }
多态 多态的基本概念 多态是C++面向对象的三大特性之一
多态分为两类 :
静态多态 :函数重载和运算符重载属于静态多态,复用函数名
动态多态 :派生类和虚函数(加virtual关键词)实现运行时多态
静态多态和动态多态的区别 :
静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Animal {public : virtual void speak () { cout<<"动物在说话" <<endl; } };class Cat :public Animal{public : void speak () { cout<<"小猫在说话" <<endl; } };void doSpeak (Animal &animal) { animal.speak (); }void test01 () { Cat cat; doSpeak (cat); }
总结 :
动态多态满足条件:
有继承关系
子类有重写父类的虚函数
动态多态的使用:父类的指针或引用指向子类对象
原理剖析 :
多态的优点 :
代码组织结构清晰
可读性强
利于前期和后期扩展以及维护
总结 :C++开发提倡利用多态设计程序架构,因为多态的优点很多
纯虚函数和抽象类 在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
语法 :virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也成为抽象类
抽象类特点 :
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Base {public : virtual void func () =0 ; };class Son :public Base{public : void func () { cout<<"Son func函数调用" <<endl; } };void test01 () { Son s; }
虚析构和纯虚析构 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式 :将父类中的析构函数改为虚析构 或者纯虚析构
虚析构和纯虚析构共性 :
可以解决父类指针释放子类对象的问题
都需要有具体的函数实现
虚析构和纯虚析构区别 :
虚析构语法 : virtual ~类名(){}
纯虚析构语法 :
virtual ~类名()=0; 类外:类名::~类名(){}
注意事项 :需要声明,也需要具体的实现代码(在类外实现),有了纯虚析构函数之后,这个类也属于抽象类,无法实例化对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Animal {public : Animal (){ cout<<"Animal构造函数调用" <<endl; } virtual ~Animal (){ cout<<"Animal析构函数调用" <<endl; } virtual ~Animal ()=0 ; virtual void speak () =0 ; }; Animal::~Animal (){ cout<<"Animal纯虚析构函数调用" <<endl; }class Cat :public Animal{public : virtual void speak () { cout<<*m_Name<<"小猫在说话" <<endl; } Cat (string name){ cout<<"cat构造函数调用" <<endl; m_Name=new string (name); } ~Cat (){ if (m_Name!=NULL ){ cout<<"cat析构函数调用" <<endl; delete m_Name; m_Name=NULL ; } } string *m_Name; };void test01 () { Animal *animal new Cat ("Tom" ) ; animal->speak (); delete animal; }
总结 :
虚析构或纯虚析构就是用来解决通过父类指针释放的子类对象
如果子类中没有堆区数据,可以不写虚析构或纯虚析构函数
拥有纯虚析构函数的类也属于抽象类
文件操作 程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件****
文件类型分为两种:
文本文件 - 文件以文本的ASCII码 形式存储在计算机中
二进制文件 - 文件以文本的二进制形式 存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
ofstream :写操作
ifstream :读操作
fstream :读写操作
文本文件 写文件 写文件步骤如下:
包含头文件
创建流对象
打开文件
写数据
关闭文件
文件打开方式 :
打开方式
解释
ios::in
为读文件而打开文件
ios::out
为写文件而打开文件
ios::ate
初始位置:文件尾
ios::app
追加方式写文件
ios::trunc
如果文件存在先删除,再创建
ios::binary
二进制方式
注意 :文件打开方式可以配合使用,利用*|*操作符
例如:用二进制方式写文件ios::binary|ios::out
读文件 读文件与写文件步骤相似,但是读取方式相对于比较多
读文件步骤如下 :
包含头文件
创建流对象 :
打开文件并判断文件是否打开成功
读数据
四种方式读取
关闭文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <fstream> #include <string> using namespace std;int main () { ifstream ifs; ifs.open ("test.txt" ,ios::in); if (!ifs.is_open ()){ cout<<"文件打开失败" <<endl; return 0 ; } char c; while ((c=ifs.get ())!=EOF){ cout<<c; } ifs.close (); return 0 ; }
总结 :
读文件可以利用ifstream,或者fstream类
利用is_open函数可以判断文件是否打开成功
close关闭文件
二进制文件 以二进制的方式对文件进行读写操作
打开方式要指定为iso::binary
写文件 二进制方式写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <fstream> using namespace std;class Person {public : char m_Name[64 ]; int m_Age; };void test01 () { ofstream ofs ("test1.txt" ,ios::out|ios::binary) ; Person p={"张三" ,18 }; ofs.write ((const char *)&p,sizeof (Person)); ofs.close (); }int main () { test01 (); return 0 ; }
读文件 二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <fstream> using namespace std;class Person {public : char m_Name[64 ]; int m_Age; };void test01 () { ifstream ifs ("test1.txt" ,ios::in|ios::binary) ; if (!ifs.is_open ()){ cout<<"文件打开失败" <<endl; return ; } Person p; ifs.read ((char *)&p, sizeof (Person)); cout<<p.m_Name<<" " <<p.m_Age<<endl; ifs.close (); }int main () { test01 (); return 0 ; }
模板 概念 :
模板就是建立通用的模具,大大提高复用性
特点 :
模板不可以直接使用,它只是一个框架
模板的通用并不是万能的
函数模板
C++另一种编程思想称为泛型编程 ,主要利用的技术就是模板
C++提供两种模板机制:函数模板 和类模板
函数模板语法 作用 :
建立一个通用函数,其返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
语法 :
1 2 template <typename T> 函数声明或定义
解释 :
template — 声明创建模板
typename — 表现其后面的符号是一种数据类型,可以用class代替
T — 通用的数据类型,名称可以替换,通常为大写字母
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 template <typename T> void myswap (T &a,T &b) { T temp=a; a=b; b=a; }void swapInt (int &a,int &b) { int temp=a; a=b; b=temp; }void swapDouble (double &a,double &b) { double temp=a; a=b; b=temp; }void test01 () { int a=10 ; int b=20 ; myswap (a,b); myswap <int >(a,b); cout<<"a= " <<a<<endl; cout<<"b= " <<b<<endl; double c=1.1 ; double d=2.2 ; cout<<"c= " <<c<<endl; cout<<"d= " <<d<<endl; }
总结 :
函数模板利用关键词template
使用函数模板有两种方式:自动类型推导、显示指定类型
模板的目的是为了提高复用性,将类型参数化
函数模板注意事项 注意事项 :
自动类型推导,必须推到出一致的数据类型T,才可以使用
模板必须要确定出T的数据类型,才可以使用
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 template <class T>void myswap (T&a,T&b) { T temp=a; a=b; b=temp; }template <class T>void func () { cout<<"func调用" <<endl; }void test01 () { int a=10 ; int b=20 ; char c='c' ; myswap (a,b); myswap (a,c); func <int >(); }
普通函数与函数模板的区别 普通函数和函数模板的区别 :
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
如果利用显示指定类型的方式,可以发生隐式类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int myAdd01 (int a,int b) { return a+b; }template <class T>T myAdd02 (T a,T b) { return a+b; } void test01 () { int a=10 ; int b=20 ; char c='c' ; myAdd02 (a,c); myAdd02 <int >(a,c); cout<<myAdd01 (a,c)<<endl; }
总结 :
建议使用指定类型的方,调用函数模板,因为可以自己确定通用类型T
普通函数和函数模板的调用规则 调用规则如下 :
如果函数模板和普通函数都可以实现,优先调用普通函数
可以通过空模板参数列表来强制调用函数模板
函数模板也可以发生重载
如果函数模板可以产生更好的匹配,优先调用函数模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void myprint (int a,int b) { cout<<"调用的普通函数" <<endl; }template <class T>void myprint (T a,T b) { cout<<"调用的模板" <<endl; }template <class T>void myprint (T a,T b,T c) { cout<<"调用的重载模板" <<endl; }void test01 () { int a=10 ; b=20 ; myprint (a,b); myprint<>(a,b); myprint (a,b,100 ); char c1='a' ; char c2='b' ; myprint (c1,c2); }
总结 :
既然有了函数模板,最好就不要提供同名普通函数,否则容易出现二义性
模板的局限性以及具体化 局限性 :
1 2 3 4 template <class T>void f (T a,T b) { a=b; }
在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了
再例如:
1 2 3 4 5 6 template <class T>void f (T a,T b) { if (a>b){ .... } }
在上述代码中,如果T的数据类型传入是像Person这样自定义的数据类型,也无法正常运行
因此C++为了解决这种问题,提供了模板的重载,可以为这些特定的类型 提供具体化的模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class Person {public : Person (string name,int age){ this ->m_name=name; this ->m_age=age; } string m_name; int m_age; };template <class T>bool mycompare (T &a,T &b) { if (a==b){ return true ; }else { return false ; } }template <>bool mycompare (Person &p1,Person &p2) { if (p1.m_name==p2.m_name&&p1.m_age==p2.m_age){ return true ; }else { return false ; } }void test01 () { int a=10 ; int b=20 ; bool ret=mycompare (a,b); if (ret){ cout<<"a==b" <<endl; }else { cout<<"a!=b" <<endl; } }void test02 () { Person p1 ("Tom" ,10 ) ; Person p2 ("Tom" ,10 ) ; bool ret=mycompare (p1,p2); if (ret){ cout<<"p1==p2" <<endl; }else { cout<<"p1!=p2" <<endl; } }
总结 :
利用具体化的模板,可以解决自定义类型的通用化
学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
类模板 类模板语法 作用 :
建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表
语法 :template<class T>
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 template <class nametype ,class agetype >class Person {public : Person (nametype name,agetype age){ this ->m_name=name; this ->m_age=age; } void show () { cout<<"name: " <<this ->m_name<<"age= " <<this ->m_age<<endl; } nametype m_name; agetype m_age; };void test01 () { Person<string,int > p1 ("孙悟空" ,999 ) ; }
类模板与函数模板区别 区别 :
类模板没有自动类型推导的使用方式
类模板在模板参数列表中可以有默认参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 template <class nametype ,class agetype =int >class Person{public : Person (nametype name,agetype age){ this ->m_name=name; this ->m_age=age; } void show () { cout<<"name: " <<this ->m_name<<"age= " <<this ->m_age<<endl; } nametype m_name; agetype m_age; };void test01 () { Person p ("孙悟空" ,999 ) ; Person p1<string,int >p1 ("孙悟空" ,999 ); Person p1<string>p1 ("孙悟空" ,999 ); p1.show (); }
总结 :
类模板使用只能用显示指定类型方式
类模板中的模板参数列表可以有默认参数
类模板中成员函数创建时机 类模板中成员函数和普通类中成员函数创建时机是有区别的:
普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Person1 {public : void show1 () { cout<<"person1show" <<endl; } };class Person2 {public : void show2 () { cout<<"person2show" <<endl; } };template <class T >class myclass {public : T obj; void func1 () { obj.show1 (); } void func2 () { obj.show2 (); } };void test01 () { myclass<Person1>m; m.func1 (); }
总结 :类模板中的成员函数并不是一开始就创建的,在调用时才去创建
类模板对象做函数参数 类模板实例化出的对象,向函数传参的方式
三种传参方式:
指定传入的类型 — 直接显示对象的数据类型
参数模板化 — 将对象中的参数变为模板进行传递
整个类模板化 — 将这个对象类型模板化进行传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 template <class T1 ,class T2 =int >class Person{public : Person (T1 name,T2 age){ this ->m_name=name; this ->m_age=age; } void show () { cout<<"name: " <<this ->m_name<<"age= " <<this ->m_age<<endl; } T1 m_name; T2 m_age; };void test01 () { Person<string,int >p ("孙悟空" ,100 ); Person<string,int >p1 ("孙悟空" ,100 ); Person<string,int >p2 ("孙悟空" ,100 ); printp1 (p); printp2 (p1); printp3 (p2); }void printp1 (Person<string,int >&p) { p.show (); }template <class T1,class T2>void printp2 (Person<T1,T2>&p) { p.show (); cout<<"T1的类型为:" <<typeid (T1).name ()<<endl; }template <class T>void printp3 (T &p) { p.show (); }
总结 :
通过类模板创建的对象,可以有三种方式向函数中进行传参
使用比较广泛的是第一种:指定传入的类型
类模板与继承 当类模板碰到继承时,需要注意一下几点:
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
如果不指定,编译器无法给子类分配内存
如果想灵活指定出父类中T的类型,子类也需要变为类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 template <class T >class Base {public : T m; };class Son :public Base<int >{};template <class T1 ,class T2 >class Son2 :public Base<T2>{public : Son2 (){ cout<<"T1的类型为:" <<typeid (T1).name ()<<endl; cout<<"T2的类型为:" <<typeid (T2).name ()<<endl; } T1 obj; };void test01 () { Son s1; Son2<int ,char >s2; }
类模板成员函数类外实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 template <class T1 ,class T2 >class Person {public : Person (T1 name,T2 age); void show () ; T1 m_name; T2 m_age; };template <class T1 ,class T2 > Person<T1,T2>::Person (T1 name,T2 age){ this ->m_name=name; this ->m_age=age; }template <class T1 ,class T2 >void Person<T1,T2>::show (){ cout<<"name: " <<this ->m_name<<"age= " <<this ->m_age<<endl; }void test01 () { Person<string,int >P ("tom" ,20 ); p.show (); }
总结 :类模板中成员函数类外实现时,需要加入模板的参数列表
类模板分文件编写 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决方法 :
直接包含.cpp源文件
将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
分文件编写的.h写法 :
1 2 3 #pragma once #include <iostream> using namespace std;
示例:
//第一种解决方法
Person.h代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 #pragma once #include <iostream> using namespace std;#include <string> template <class T1 ,class T2 >class Person {public : Person (T1 name,T2 age); void show () ; T1 m_name; T2 m_age; };
Person.cpp代码:
1 2 3 4 5 6 7 8 9 10 11 12 #include "Person.h" template <class T1 ,class T2 > Person<T1,T2>::Person (T1 name,T2 age){ this ->m_name=name; this ->m_age=age; }template <class T1 ,class T2 >void Person<T1,T2>::show (){ cout<<"name: " <<this ->m_name<<"age= " <<this ->m_age<<endl; }
main.cpp代码:
1 2 3 4 5 6 7 8 9 10 11 #include "Person.cpp" void test01 () { Person<string,int >P ("tom" ,20 ); P.show (); }int main () { test01 (); return 0 ; }
//第二种解决方法
将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件
.hpp被称为类模板
Person.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #pragma once #include <iostream> using namespace std;#include <string> template <class T1 ,class T2 >class Person {public : Person (T1 name,T2 age); void show () ; T1 m_name; T2 m_age; };template <class T1 ,class T2 > Person<T1,T2>::Person (T1 name,T2 age){ this ->m_name=name; this ->m_age=age; }template <class T1 ,class T2 >void Person<T1,T2>::show (){ cout<<"name: " <<this ->m_name<<"age= " <<this ->m_age<<endl; }
类模板与友元 类模板模板配合友元函数的类内和类外实现
全局函数类外实现 - 直接在类内声明友元即可
全局函数类外实现 - 需要提前让编译器知道全局函数的存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 template <class T1 ,class T2 >class Person ;template <class T1 ,class T2 >class Person { friend void print (Person<T1,T2>p) { cout<<"name: " <<p.m_name<<"age= " <<p.m_age<<endl; } friend void print1<>(Person<T1,T2>p);public : Person (T1 name,T2 age){ this ->m_name=name; this ->m_age=age; }private : T1 m_name; T2 m_age; };template <class T1,class T2>void print (Person<T1,T2> p) { cout<<"类外实现name: " <<p.m_name<<"age= " <<p.m_age<<endl; }void test01 () { Person<string,int >p ("tom" ,20 ); print (p); print1 (p); }
总结 :建议全局函数做类内实现,用法简单,而且编译器可以直接识别
类模板案例 案例描述:实现一个通用的数组类,要求如下:
可以对内置数据类型以及自定义数据类型的数据进行存储
将数组中的数据存储在堆区
构造函数中可以传入数组的容量
提供对应的拷贝构造函数以及operator=防止浅拷贝问题
提供尾插法和尾删法对数组中的数据进行增加和删除
可以通过下标的方式访问数组中的元素
可以获取数组中当前元素个数和数组的容量
myArray.hpp代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #pragma once #include <iostream> using namespace std;template <class T >class myArray {public : myArray (int capacity){ this ->m_capacity=capacity; this ->m_size=0 ; this ->paddress=new T[this ->m_capacity]; } myArray (const myArray&arr){ this ->m_capacity=arr.m_capacity; this ->m_size=arr.m_size; this ->paddress=new T[arr.m_capacity]; for (int i=0 ;i< this ->m_size;i++){ this ->paddress[i]=arr.paddress[i]; } } myArray& operator =(const myArray& arr){ if (this ->paddress!=NULL ){ delete [] this ->paddress; this ->paddress=NULL ; this ->m_capacity=0 ; this ->m_size=0 ; } this ->m_capacity=arr.m_capacity; this ->m_size=arr.m_size; this ->paddress=new T[arr.m_capacity]; for (int i=0 ;i< this ->m_size;i++){ this ->paddress[i]=arr.paddress[i]; } return *this ; } void push_back (const T& val) { if (this ->m_capacity== this ->m_size){ return ; } this ->paddress[this ->m_size]=val; this ->m_size++; } void pop_back () { if (this ->m_size==0 ){ return ; } this ->m_size--; } T& operator [](int index){ return this ->paddress[index]; } int get_capacity () { return this ->m_capacity; } int get_size () { return this ->m_size; } ~myArray (){ if (this ->paddress!=NULL ){ delete [] this ->paddress; this ->paddress=NULL ; } }private : T * paddress; int m_capacity; int m_size; };
myArray.cpp代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include "myArray.hpp" using namespace std;class Person { public : Person () {}; Person (string name, int age) { this ->m_name = name; this ->m_age = age; } string m_name; int m_age; };void printarr (myArray<Person> &arr) { for (int i = 0 ; i < arr.get_size (); i++) { cout << "姓名:" << arr[i].m_name << "年龄:" << arr[i].m_age << endl; } }void print (myArray<int > &arr) { for (int i = 0 ; i < arr.get_size (); i++) { cout << arr[i] << endl; } }void test01 () { myArray<int >arr (5 ); for (int i = 0 ; i < 5 ; i++) { arr.push_back (i); } cout << "arr的打印输出:" << endl; print (arr); cout << "arr的容量:" << arr.get_capacity () << endl; cout << "arr的大小:" << arr.get_size () << endl; myArray<int >arr1 (arr); cout << "arr1的打印输出:" << endl; print (arr1); arr1.pop_back (); cout << "arr1的容量:" << arr1.get_capacity () << endl; cout << "arr1的大小:" << arr1.get_size () << endl; }void test02 () { myArray<Person>arr (10 ); Person p1 ("孙悟空" , 999 ) ; Person p2 ("韩信" , 20 ) ; Person p3 ("妲己" , 10 ) ; Person p4 ("安其拉" , 27 ) ; Person p5 ("赵云" , 28 ) ; arr.push_back (p1); arr.push_back (p2); arr.push_back (p3); arr.push_back (p4); arr.push_back (p5); printarr (arr); cout << "arr的容量:" << arr.get_capacity () << endl; cout << "arr的大小:" << arr.get_size () << endl; }int main () { test02 (); return 0 ; }
STL初识 STL的诞生
长久以来,软件界一直希望建立一种可重复利用的东西
C++的面向对象 和泛型编程 思想,目的就是复用性的提升
大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作
为了建立数据结构和算法的一套标准,诞生了STL
STL基本概念
STL(Standard Template Library,标准模板库 )
STL从广义上分为:容器(container) 算法(algorithm) 迭代器(iterator)
容器 和算法 之间通过迭代器 进行无缝连接
STL几乎所有代码都采用了模板类或者模板函数
STL六大组件 STL大体分为六大组件,分别为:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
容器 :各种数据结构,如:vector、list、deque、set、map等,用来存放数据
算法 :各种常用的算法,如:sort、find、copy、for_each等
迭代器 :扮演了容器与算法之间的胶合剂
仿函数 :行为类似函数,可作为算法的某种策略
适配器 :一种用来修饰容器或者仿函数或者迭代器接口的东西
空间配置器 :负责空间的配置与管理。
STL中容器、算法、迭代器 容器 :置物之所也
STL容器就是将运用最广泛的一些数据结构闪现出来
常用的数据结构:数组,链表,树,栈,队列,集合,映射表等
这些容器分为序列式容器 和关联式容器 两种:
序列式容器 :强调值的排序,序列式容器中的每个元素均有固定的位置
关联式容器 :二叉树结构,各元素之间没有严格的物理上的顺序关系
算法 :问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们称为算法(Algorithms)
算法分为质变算法 和非质变算法 :
质变算法 :是指运算过程中会更改区间内的元素的内容,例如:拷贝,替换,删除等等
非质变算法 :是指运算过程中不会更改区间内的元素内容,例如:查找,计数,遍历,寻找极值等等
迭代器 :容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针
迭代器种类 :
种类
功能
支持运算
输入迭代器
对数据的只读访问
只读,支持++、==、!=
输出迭代器
对数据的只写访问
只写,支持++
前向迭代器
读写操作,并能向前推进迭代器
读写,支持++、==、!=
双向迭代器
读写操作,并能向前和向后操作
读写,支持++、–
随机访问迭代器
读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器
读写操作,支持++、–、[n]、-n、<、<=、>、>=
常用的容器中迭代器种类为双向迭代器,和随机访问迭代器
容器算法迭代器初识 STL中最常用的容器为Vector,可以理解为数组,下面我们将学习如何向这个容器插入数据,并遍历这个容器
vector存放内置数据类型 容器 :vector
算法 :for_each
迭代器:vector<int>::iterator
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <vector> #include <alogrithm> using namespace std;void myprint (int val) { cout<<val<<endl; }void test01 () { vector<int > v; v.push_back (10 ); v.push_back (20 ); v.push_back (30 ); vector<int >::iterator itBegin = v.begin (); vector<int >::iterator itEnd = v.end (); while (itBegin != itEnd) { cout << *itBegin << endl; itBegin++; } for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<endl; } for_each(v.begin (),v.end (),myprint); }int main () { test01 (); return 0 ; }
vector容器中存放自定义数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <vector> using namespace std;class Person {public : Person (string name,int age){ this ->m_name=name; this ->m_age=age; } string m_name; int m_age; };void test01 () { vector<Person> v; Person p1 ("aaa" ,10 ) ; Person p2 ("bbb" ,20 ) ; Person p3 ("ccc" ,30 ) ; Person p4 ("ddd" ,40 ) ; Person p5 ("eee" ,50 ) ; v.push_back (p1); v.push_back (p2); v.push_back (p3); v.push_back (p4); v.push_back (p5); for (vector<Person>::iterator it=v.begin ();it!=v.end ();it++){ cout<<"name:" <<it->m_name<<" age:" <<it->m_age<<endl; } }void test02 () { vector<*Person> v; Person p1 ("aaa" ,10 ) ; Person p2 ("bbb" ,20 ) ; Person p3 ("ccc" ,30 ) ; Person p4 ("ddd" ,40 ) ; Person p5 ("eee" ,50 ) ; v.push_back (&p1); v.push_back (&p2); v.push_back (&p3); v.push_back (&p4); v.push_back (&p5); for (vector<Person*>::iterator it=v.begin ();it!=v.end ();it++){ cout<<"name:" <<(*it)->m_name<<" age:" <<(*it)->m_age<<endl; } }
vector容器嵌套容器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <vector> using namespace std;void test01 () { vector<vector<int >>v; vector<int >v1; vector<int >v2; vector<int >v3; vector<int >v4; for (int i=0 ;i<4 ;i++){ v1.push_back (i+1 ); v2.push_back (i+2 ); v3.push_back (i+3 ); v4.push_back (i+4 ); } v.push_back (v1); v.push_back (v2); v.push_back (v3); v.push_back (v4); for (vector<vector<int >>::iterator it=v.begin ();it!=v.end ();it++){ for (vector<int >::iterator vit=(*it).begin ();vit!=(*it).end ();vit++){ cout<<*vit<<" " ; } cout<<endl; } }
STL –常用容器 string容器 string基本概念 本质 :
string是C++风格的字符串,而string本质上是一个类
string和char*的区别 :
char*是一个指针
string是一个类,类内部封装了char*,管理这个字符串,是一个char指针型容器
特点 :
string类内部封装了很多成员的方法
例如:查找find,拷贝copy,删除delete,替换replace,插入insert
string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
string构造函数 构造函数原型:
string();
//创建一个空的字符串,例如:string str;
string(const char* s);
//使用字符串s初始化
string(const string& str);
//使用一个string对象初始化另一个string对象
string(int n,char c);
//使用n个字符c初始化
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <string> using namespace std;void test01 () { string s1; const char *str="hello world" ; string s2 (str) ; cout<<"s2= " <<s2<<endl; string s3 (s2) ; cout<<"s3= " <<s3<<endl; string s4 (10 ,'a' ) ; cout<<"s4= " <<s4<<endl; }
string赋值操作 功能描述:
赋值的函数原型:
string& operator=(const char* s);
//char*类型字符串赋值给当前的字符串
string& operator=(const string &s);
//把字符串s赋给当前的字符串
string& operator=(char c);
//字符赋值给当前的字符串
string& assign(const char *s);
//把字符串s赋给当前的字符串
string& assign(const char *s,int n);
//把字符串s的前n个字符赋给当前的字符串
string& assign(const string &s);
//把字符串s赋给当前字符串
string& assign(int n,char c);
//把n个字符c赋给当前字符串
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <string> using namespace std;void test01 () { string str1; str1="hello world" ; cout<<"str1= " <<str1<<endl; string str2; str2=str1; cout<<"str2= " <<str2<<endl; string str3; str3='a' ; cout<<"str3= " <<str3<<endl; string str4; str4.assgin ("hello c++" ); cout<<"str4= " <<str4<<endl; string str5; str5.assign ("hello c++" ,5 ); cout<<"str5= " <<str5<<endl; string str6; str6.assign (str5); cout<<"str6= " <<str6<<endl; string str7; str7.assign (10 ,'w' ); cout<<"str7= " <<str7<<endl; }
总结 :string的赋值方式狠多,operator=
这种方式是比较实用的
string字符串拼接 功能描述:
函数原型 :
string& operator+=(const char* str);
//重载+=操作符
string& operator+=(const char c);
//重载+=操作符
string& operator+=(const string& str);
//重载+=操作符
string& append(const char *s);
//把字符s连接到当前字符串末尾
string& append(const char *s,int n);
//把字符串s的前n个字符连接到当前字符串结尾
string& append(const string &s);
//同operator+=(const string& str);
string& append(const string &s,int pos,int n);
//字符串s中从pos开始的n个字符连接到字符串末尾
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <string> using namespace std;void test01 () { string str1="我" ; str1+="爱玩游戏" ; cout<<"str1= " <<str1<<endl; str1+=':' ; cout<<"str1= " <<str1<<endl; string str2="LOL DNF" ; str1+=str2; cout<<"str1= " <<str1<<endl; string str3="I" ; str3.append ("love" ); cout<<"str3= " <<str3<<endl; str3.append ("game abcde" ,4 ); cout<<"str3= " <<str3<<endl; str3.append (str2); cout<<"str3= " <<str3<<endl; str3.append (str2,0 ,3 ); cout<<"str3= " <<str3<<endl; }
string查找和替换 功能描述 :
查找:查找指定字符串是否存放
替换:在指定的位置替换字符串
函数原型 :
int find(const string& str,int pos=0) const;
//查找str第一次出现位置,从pos开始查找
int find(const char* s,int pos=0) const;
//查找s第一次出现的位置,从pos开始查找
int find(const char* s,int pos,itn n) const;
//从pos位置查找s的前n个字符第一次位置
int find(const char c,int pos=0) const;
//查找字符c第一次出现位置
int rfind(const string& str,int pos=npos) const;
//查找str最后一次出现位置,从pos开始查找
int rfind(const char* s,int pos=npos) const;
//查找s最后一次出现位置,从pos开始查找
int rfind(const char* s,int pos,int n) const;
//从pos查找s的前n个字符最后一次位置
int rfind(const char c,int pos=0) const;
//查找字符c最后一次出现位置
string& replace(int pos,int n,const string string& str);
//替换从pos开始n个字符为字符串str
string& replace(int pos,int n,const char* s);
//替换从pos开始的n个字符为字符串
find和rfind的区别 :
find是从左往右查找
rfind是从右往左查找,但下标返回值是从左往右数的
注意事项 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <string> using namespace std;void test01 () { string str1="abcdefgede" ; int pos=str1.find ("de" ); cout<<"pos= " <<pos<<endl; pos=str1.rfind ("de" ); cout<<"pos= " <<pos<<endl; }void test02 () { string str1="abcdefg" ; str1.replace (1 ,3 ,"1111" ); cout<<"str1= " <<str1<<endl; }
string字符串比较 功能描述 :
比较方式 :
字符串比较是按字符的ASCII码进行对比
函数原型 :
int compare(const string &s) const;
//与字符串s比较
int compare(const char *s) const;
//与字符串s比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <string> using namespace std;void test01 () { string str1="hello" ; string str2="hello" ; if (str1.compare (str2)==0 ){ cout<<"str1=str2" <<endl; }else if (str1.compare (str2)>0 ){ cout<<"str1>str2" <<endl; }else if (str1.compare (str2)<0 ){ cout<<"str1<str2" <<endl; } }
string字符存取 string中单个字符存取方式有两种:
char& operator[](int n);
//通过[]方式取字符
char& at(int n);
//通过at方法获取字符
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <string> using namespace std;void test01 () { string sr1="hello" ; for (int i=0 ;i<str1.size ();i++){ cout<<str1[i]<<" " ; } cout<<endl; for (int i=0 ;i<str1.size ();i++){ cout<<str1.at (i)<<" " ; } cout<<endl; str1[0 ]='x' ; cout<<"str1= " <<str1<<endl; str1.at (1 )='x' ; cout<<"str1= " <<str1<<endl; }
string插入和删除 功能描述 :
函数原型 :
string& insert(int pos,const char* s);
//插入字符串
string& insert(int pos,const string& str);
//插入字符串
string& insert(int pos,int n,char c);
//在指定位置插入n个字符c
string& erase(int pos,int n=npos);
//删除从pos开始的n个字符
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <string> using namespace std;void test01 () { string str="hello" ; str.insert (1 ,"111" ); cout<<"str= " <<str<<endl; str.erase (1 ,3 ); cout<<"str= " <<str<<endl; }
string子串 功能描述 :
函数原型 :
string substr(int pos=0,int n=npos) const;
//返回由pos开始的n个字符组成的字符串
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <string> using namespace std;void test01 () { string str="abcedf" ; string subStr=str.substr (1 ,3 ); cout<<"subStr= " <<subStr<<endl; }void test02 () { string email="zhangsan@sina.com" ; int pos=email.find ("@" ); string username=email.substr (0 ,pos); cout<<"username= " <<username<<endl; }
vector容器 vector基本概念 功能 :
vector数据结构和数组非常相似,也成为单端数组
vector与普通数组区别 :
不同之处在于数组是静态空间,而vector可以动态扩展
动态扩展 :
并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间
vector容器的迭代器是支持随机访问的迭代器
vector构造函数 功能描述 :
函数原型 :
vector<T> v;
//采用模块实现类实现,默认构造函数
vector(v.begin(),v.end());
//将v[begin(),end())区间中的元素拷贝给本身
vector(n,elem);
//构造函数将n个elem拷贝给本身
vector(const vector &vec);
//拷贝构造函数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <vector> using namespace std;void print (vector<int >&v) { for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } print (v); vector<int >v1 (v.begin (),v.end ()); print (v1); vector<int >v2 (10 ,100 ); print (v2); vector<int >v3 (v2); print (v3); }
vector赋值操作 功能描述 :
函数原型 :
vector& operator=(const vector &vec);
//重载等号操作符
assign(beg,end);
//将[beg,end)区间中的数据拷贝赋值给本身
assign(n,elem);
//将n个elem拷贝赋值给本身
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <vector> using namespace std;void print (vector<int >&v) { for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } print (v); vector<int >v1; v1=v; print (v1); vector<int >v2; v2.assign (v1.begin (),v1.end ()); print (v2); vector<int >v3; v3.assign (10 ,100 ); print (v3); }
vector容量和大小 功能描述 :
函数原型 :
empty();
//判断容器是否为空
capacity();
//容器的容量
size();
//返回容器中元素的个数
resize(int num);
//重新指定容器的长度为num,若容器变长,则以默认值(0)填充新位置,如果容器变短,则末尾超出容器长度的元素被删除
resize(int num,elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <vector> using namespace std;void print (vector<int >&v) { for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } print (v); if (v.empty ()){ cout<<"v为空" <<endl; }else { cout<<"v不为空" <<endl; cout<<"v的容量为:" <<v.capacity ()<<endl; cout<<"v的大小为:" <<v.size ()<<endl; } v.resize (15 ,100 ); print (v); v.resize (5 ); print (v); }
vector插入和删除 功能描述 :
函数原型 :
push_back(ele);
//尾部插入元素ele
pop_back();
//删除最后一个元素
insert(const_iterator pos,ele);
//迭代器指向位置pos插入元素ele
insert(const_iterator pos,int count,ele);
//迭代器指向位置pos插入count个元素ele
erase(const_iterator pos);
//删除迭代器指向的元素
erase(const_iterator start,const_iterator end);
//删除迭代器从start到end之间的元素
clear();
//删除容器中所有元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <vector> using namespace std;void print (vector<int >&v) { for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { vector<int >v; for (int i=1 ;i<=5 ;i++){ v.push_back (i*10 ); } print (v); v.pop_back (); print (v); v.insert (v.begin (),100 ); print (v); v.insert (v.begin (),2 ,1000 ); print (v); v.erase (v.begin ()); print (v); v.clear (); print (v); }
vector数据存取 功能描述 :
函数原型 :
at(int idx);
//返回索引idx所指的数据
operator[];
//返回索引idx所指的数据
front();
//返回容器中第一个数据元素
back();
//返回容器中最后一个数据元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <vector> using namespace std;void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } for (int i=0 ;i<v.size ();i++){ cout<<v[i]<<" " ; } cout<<endl; for (int i=0 ;i<v.size ();i++){ cout<<v.at (i)<<" " ; } cout<<endl; cout<<"第一个元素为:" <<v.front ()<<endl; cout<<"最后一个元素为:" <<v.back ()<<endl; }
vector互换容器 功能描述 :
函数原型 :
swap(vec);
//将vec与本身大的元素互换
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <vector> using namespace std;void print (vector<int >&v) { for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } print (v); vector<int >v1; for (int i=10 ;i>0 ;i--){ v1.push_back (i); } print (v1); cout<<"交换后:" <<endl; v1.swap (v); print (v); print (v1); }void test02 () { vector<int >v; for (int i=0 ;i<10000 ;i++){ v.push_back (i); } cout<<"v的容量:" <<v.capacity ()<<endl; cout<<"v的大小为:" <<v.size ()<<endl; v.resize (3 ); cout<<"v的容量:" <<v.capacity ()<<endl; cout<<"v的大小为:" <<v.size ()<<endl; vector <int >(v).swap (v); cout<<"v的容量:" <<v.capacity ()<<endl; cout<<"v的大小为:" <<v.size ()<<endl; }
vector预留空间 功能描述 :
函数原型 :
reserve(int len);
//容器预留len个元素长度,预留位置不初始化,元素不可访问
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <vector> using namespace std;void test01 () { vector<int >v; v.reserve (100000 ); int num=0 ; int *p=NULL ; for (int i=0 ;i<100000 ;i++){ v.push_back (i); if (p!=&v[0 ]){ p=&v[0 ]; num++; } } cout<<"num= " <<num<<endl; }
deque容器 基本概念 功能 :
deque与vector区别 :
vector对于头部的插入删除效率低,数据量越大,效率越低
deque相对而言,对头部的插入删除速度会比vector快
vector访问元素时速度会比deque快,这和两者内部实现有关
deque内部工作原理 :
deque内部有一个中控器 ,维护每段缓冲区中的内容,缓冲区中存放真实数据
中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间
deque构造函数 功能描述 :
函数原型 :
deque<T> deqT;
//默认构造形式
deque(beg,end);
//构造函数将[beg,end)区间中的元素拷贝给本身
deque(n,elem);
//构造函数将n个elem拷贝给本身
deque(const deque &deq);
//拷贝构造函数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <deque> using namespace std;void print (const deque<int > &d) { for (deque<int >:: const_iterator it=d.begin ();it!=d.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { deque<int >d1; for (int i=0 ;i<10 ;i++){ d1.push_back (i); } print (d1); deque<int >d2 (d1.begin (),d1.end ()); print (d2); deque<int >d3 (10 ,100 ); print (d3); deque<int >d4 (d3); print (d4); }
deque赋值操作 功能描述 :
函数原型 :
deque& operator=(const deque &deq);
//重载等号操作符
assign(beg,end);
//将[beg,end)区间中的数据拷贝赋值给本身
assign(n,elem);
//将n个elem赋值给本身
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <deque> using namespace std;void print (const deque<int > &d) { for (deque<int >:: const_iterator it=d.begin ();it!=d.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { deque<int >d1; for (int i=0 ;i<10 ;i++){ d1.push_back (i); } print (d1); deque<int >d2; d2=d1; print (d2); deque<int >d3; d3.assign (d1.begin (),d1.end ()); print (d3); deque<int >d4; d4.assign (10 ,100 ); print (d4); }
deque大小操作 功能描述 :
函数原型 :
deque.empty();
//判断容器是否为空
deque.size();
//返回容器中的元素个数
deque.resize(num);
//重新指定容器的长度num,若容器变长,则以默认值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除
deque.resize(num,elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <deque> using namespace std;void print (const deque<int >&d) { for (deque<int >::const_iterator it=d.begin ();it!=d.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { deque<int >d1; for (int i=0 ;i<10 ;i++){ d1.push_back (i); } print (d1); if (d1.empty ()){ cout<<"d1为空" <<endl; }else { cout<<"d1不为空" <<endl; cout<<"d1的大小为:" <<d1.size ()<<endl; } d1.resize (15 ); print (d1); d1.resize (15 ,1 ); print (d1); d1.resize (5 ); print (d1); }
总结 :deque容器是没有容量的概念的。
deque插入和删除 功能描述 :
函数原型 :
两端插入删除操作:
push_back(elem);
//在容器尾部添加一个数据
push_front(elem);
//在容器的头部插入一个数据
pop_back();
//删除容器最后一个数据
pop_front();
//删除容器第一个数据
指定位置操作:
insert(pos,elem);
//在pos位置插入一个elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);
//在pos位置插入n个elem数据,无返回值
insert(pos,beg,end);
//在pos位置插入[beg,end)区间的数据,无返回值
clear();
//清空容器的所有数据
erase(beg,end);
//删除[beg,end)区间的数据,返回下一个数据的位置
erase(pos);
//删除pos位置的数据,返回下一个数据的位置
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <iostream> #include <deque> using namespace std;void print (const deque<int >&d) { for (deque<int >::const_iterator it=d.begin ();it!=d.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { deque<int >d1; d1.push_back (10 ); d1.push_back (20 ); d1.push_front (100 ); d1.push_front (200 ); print (d1); d1.pop_back (); print (d1); d1.pop_front (); print (d1); }void test02 () { deque<int >d1; d1.push_back (10 ); d1.push_back (20 ); d1.push_front (100 ); d1.push_front (200 ); print (d1); d1.insert (d1.begin (),1000 ); print (d1); d1.insert (d1.begin (),2 ,10000 ); print (d1); deque<int >d2; d2.push_back (1 ); d2.push_back (2 ); d2.push_back (3 ); d1.insert (d1.begin (),d2.begin (),d2.end ()); print (d1); }void test03 () { deque<int >d1; d1.push_back (10 ); d1.push_back (20 ); d1.push_front (100 ); d1.push_front (200 ); deque<int >::iterator it=d1.begin (); it++; d1.erase (it); print (d1); d1.erase (d1.begin (),d1.end ()); d1.clear (); print (d1); }
总结 :
deque数据存取 功能描述 :
函数原型 :
at(int idx);
//返回索引idx所指数据
operator[];
//返回索引idx所指数据
front();
//返回容器中第一个数据元素
back();
//返回容器中最后一个数据元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <deque> using namespace std;void test01 () { deque<int >d; d.push_back (10 ); d.push_back (20 ); d.push_back (30 ); d.push_front (100 ); d.push_front (200 ); d.push_front (300 ); for (int i=0 ;i<d.size ();i++){ cout<<d[i]<<" " ; } cout<<endl; for (int i=0 ;i<d.size ();i++){ cout<<d.at (i)<<" " ; } cout<<endl; cout<<"第一个元素为:" <<d.front ()<<endl; cout<<"最后一个元素为:" <<d.back ()<<endl; }
deque排序 功能描述 :
算法 :
sort(iterator beg,iterator end)
//对beg和end区间内元素进行排序
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <deque> #include <algorithm> using namespace std;void print (const deque<int >&d) { for (deque<int >::const_iterator it=d.begin ();it!=d.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { deque<int >d; d.push_back (10 ); d.push_back (20 ); d.push_back (30 ); d.push_front (100 ); d.push_front (200 ); d.push_front (300 ); sort (d.begin (),a.end ()); cout<<"排序后结果:" <<endl; print (d); }
stack(栈)容器 基本概念 :
概念 :stack是一种先进后出 (First In Last Out,FILO)的数据结构,它只有一个出口
栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为
栈中进入数据称为 — 入栈 push
栈中弹出数据称为 — 出栈 pop
stack常用接口 功能描述 :
构造函数 :
stack<T> stk;
//stack采用模板类实现,stack对象的默认构造形式
stack(const stack &stk);
//拷贝构造函数
赋值操作 :
stack& operator=(const stack &stk);
//重载等号操作符
数据存取 :
push(elem);
//向栈顶添加元素
pop();
//从栈顶移除第一个元素
top();
//返回栈顶元素
大小操作 :
empty();
//判断堆栈是否为空
size();
//返回栈的大小
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <stack> using namespace std;void test01 () { stack<int >s; s.push (10 ); s.push (20 ); s.push (30 ); s.push (40 ); cout<<"栈的大小:" <<s.size ()<<endl; while (!s.empty ()){ cout<<"栈顶元素为:" <<s.top ()<<endl; s.pop (); } cout<<"栈的大小为:" <<s.size ()<<endl; }
queue(队列)容器 基本概念 概念 :Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口
队列容器允许从一端新增元素,从另一端移除元素
队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为
队列中进数据称为 — 入队 push
队列中出数据称为 — 出队 pop
queue常用接口 功能描述 :
构造函数 :
queue<T> que;
//queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que);
//拷贝构造函数
赋值操作 :
queue& operator=(const queue &que);
//重载等号操作符
数据存取 :
push(elem);
//往队尾添加元素
pop();
//从队头移除第一个元素
back();
//返回最后一个元素
front();
//返回第一个元素
大小操作 :
empty();
//判断堆栈是否为空
size();
//返回栈的大小
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <queue> #include <string> using namespace std;class Person {public : Person (string name,int age){ this ->m_name=name; this ->m_age=age; } string m_name; int m_age; };void test01 () { queue<Person>q; Person p1 ("p1" ,20 ) ; Person p2 ("p2" ,30 ) ; Person p3 ("p3" ,40 ) ; Person p4 ("p4" ,50 ) ; q.push (p1); q.push (p2); q.push (p3); q.push (p4); cout<<"队列大小:" <<q.size ()<<endl; while (!q.empty ()){ cout<<"队头元素:" <<q.front ().m_name<<" " <<q.front ().m_age<<endl; cout<<"队尾元素:" <<q.back ().m_name<<" " <<q.back ().m_age<<endl; q.pop (); } cout<<"队列大小:" <<q.size ()<<endl; }
priority_queue(优先队列)容器 优先队列定义 :
使用优先队列,**定义参数<数据类型,容器类型,比较方法>**,默认是大根堆 例子: priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q
;
数据存取操作 :
push(elem);
//添加元素
pop();
//从队列中删除最高优先级的元素
top();
//获取队列中最高优先级的元素
大小操作 :
empty();
//判断是否为空
size();
//获取队列大小
list(链表)容器 功能 :将数据进行链式存储
链表 (list):是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
链表的组成:链表由一系列结点 组成
结点的组成:一个是存储数据元素的数据域 ,另一个是存储下一个结点地址的指针域
STL的链表是一个双向循环链表
由于链表的存储方式不是连续的内存内存空间,因此链表list中的迭代器只支持前移和后移属于双向迭代器
优点 :
采用动态存储分配,不会造成内存浪费和溢出
链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
缺点 :
链表灵活,但是空间(指针域)和时间(遍历)额外耗费较大
List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的
总结 :STL中的List和vector是两个最常被使用的容器 ,各有优缺点
list构造函数 功能描述 :
函数原型 :
list<T> lst;
//list采用模板类实现,对象的默认构造形式
list(beg,end);
//构造函数将[beg,end)区间中的元素拷贝给本身
list(n,elem);
//构造函数将n个elem拷贝给本身
list(const list &lst);
//拷贝构造函数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <list> using namespace std;void print (const list<int >&l) { for (list<int >::const_iterator it=l.begin ();it!=l.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { list<int >l; l.push_back (10 ); l.push_back (20 ); l.push_back (30 ); l.push_back (40 ); print (l); list<int >l1 (l.begin (),l.end ()); print (l1); list<int >l2 (l1); print (l2); list<int >l3 (10 ,1000 ); print (l3); }
list赋值和交换 功能描述 :
函数原型 :
assign(beg,end);
//将[beg,end)区间中的数据拷贝赋值给本身
assign(n,elem);
//将n个elem拷贝赋值给本身
list& operator=(const list &lst);
//重载等号操作符
swap(lst);
//将lst与本身的元素互换
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> #include <list> using namespace std;void print (const list<int >&l) { for (list<int >::const_iterator it=l.begin ();it!=l.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { list<int >l; l.push_back (10 ); l.push_back (20 ); l.push_back (30 ); l.push_back (40 ); print (l); list<int >l1; l1=l; print (l1); list<int >l2; l2.assign (l1.begin (),l1.end ()); print (l2); list<int >l3; l3.assign (10 ,100 ); print (l3); }void test02 () { list<int >l; l.push_back (10 ); l.push_back (20 ); l.push_back (30 ); l.push_back (40 ); list<int >l1; l1.assign (10 ,100 ); cout<<"交换前:" <<endl; print (l); print (l1); cout<<"交换后:" <<endl; l.swap (l1); print (l); print (l1); }
list大小操作 功能描述 :
函数原型 :
size();
//返回容器中元素的个数
empty();
//判断容器是否为空
resize(num);
//重新指定容器的长度为num,若容器变长,则以默认值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除
resize(num,elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <list> using namespace std;void print (const list<int >&l) { for (list<int >::const_iterator it=l.begin ();it!=l.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { list<int >l; l.push_back (10 ); l.push_back (20 ); l.push_back (30 ); l.push_back (40 ); print (l); if (l.empty ()){ cout<<"l为空" <<endl; }else { cout<<"l不为空" <<endl; cout<<"l的大小为:" <<l.size ()<<endl; } l.resize (10 ); print (l); l.resize (11 ,10000 ); print (l); l.resize (2 ); print (l); }
list插入和删除 功能描述 :
函数原型 :
push_back(elem);
//在容器尾部加入一个元素
pop_back();
//删除容器中最后一个元素
push_front(elem);
//在容器开头插入一个元素
pop_front();
//在容器开头移除第一个元素
insert(pos,elem);
//在pos位置插入elem元素的拷贝,返回新数据的位置
insert(pos,n,elem);
//在pos位置插入n个elem数据,无返回值
insert(pos,beg,end);
//在pos位置插入[beg,end)区间的数据,无返回值
clear();
//移除容器的所有数据
erase(beg,end);
//删除[beg,end)区间的数据,返回下一个数据的位置
erase(pos);
//删除pos位置的数据,返回下一个数据的位置
remove(elem);
//删除容器中所有与elem值匹配的元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <list> using namespace std;void print (const list<int >l) { for (list<int >::const_iterator it=l.begin ();it!=l.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { list<int >l; l.push_back (10 ); l.push_back (20 ); l.push_back (30 ); l.push_front (100 ); l.push_front (200 ); l.push_front (300 ); print (l); l.pop_back (); print (l); l.pop_front (); print (l); list<int >::iterator it=l.begin (); l.insert (++it,1000 ); print (l); it=l.begin (); l.erase (++it); print (l); l.push_back (10000 ); l.push_back (10000 ); l.push_back (10000 ); print (l); l.remove (10000 ); print (l); l.clear (); print (l); }
list数据存取 功能·描述 :
函数原型 :
front();
//返回第一个元素
back();
//返回最后一个元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <list> using namespace std;void print (const list<int >l) { for (list<int >::const_iterator it=l.begin ();it!=l.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { list<int >l; l.push_back (10 ); l.push_back (20 ); l.push_back (30 ); l.push_back (40 ); cout<<"第一个元素为:" <<l.front ()<<endl; cout<<"最后一个元素为:" <<l.back ()<<endl; list<int >::iterator it=l.begin (); it++; it--; }
总结 :list容器中不可以通过[]或者at方式访问数据
list反转和排序 功能描述 :
函数原型 :
reverse();
//反转链表
sort();
//链表排序
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <list> using namespace std;void print (const list<int >l) { for (list<int >::const_iterator it=l.begin ();it!=l.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { list<int >l; l.push_back (10 ); l.push_back (20 ); l.push_back (30 ); l.push_back (40 ); l.push_back (50 ); print (l); cout<<"反转后:" <<endl; l.reverse (); print (l); }bool mycompare (int v1,int v2) { return v1>v2; }void test02 () { list<int >l; l.push_back (60 ); l.push_back (10 ); l.push_back (5 ); l.push_back (40 ); l.push_back (50 ); cout<<"排序前:" <<endl; print (l); cout<<"排序后:" <<endl; l.sort (); print (l); l.sort (mycompare); print (l); }
set/multiset(集合)容器 基本概念 简介 :
本质 :
set/multiset属于关联式容器 ,底层结构是用二叉树 实现
set和multiset区别 :
set不允许容器中有重复的元素
multiset允许容器中有重复的元素
set构造和赋值 功能描述 :
构造 :
set<T>st;
//默认构造函数
set(const set &st);
//拷贝构造函数
赋值 :
set& operator=(const set &st);
//重载等号操作符
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <set> using namespace std;void print (const set<int >s) { for (set<int >::const_iterator it=s.begin ();it!=s.end ();it++){ cout<<*it<<" " ; } cout<<endl; } void test01 () { set<int >s1; s1.insert (10 ); s1.insert (30 ); s1.insert (40 ); s1.insert (20 ); s1.insert (30 ); print (s1); set<int >s2 (s1); print (s2); set<int >s3; s3=s2; print (s3); }
set大小和交换 功能描述 :
函数原型 :
size();
//返回容器中元素的数目
empty();
//判断容器是否为空
swap(st);
//交换两个集合容器
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include <set> using namespace std;void print (const set<int >s) { for (set<int >::const_iterator it=s.begin ();it!=s.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { set<int >s1; s1.insert (10 ); s1.insert (20 ); s1.insert (30 ); s1.insert (40 ); print (s1); if (s1.empty ()){ cout<<"s1为空" <<endl; }else { cout<<"s1不为空" <<endl; cout<<"s1的大小为:" <<s1.size ()<<endl; } }void test02 () { set<int >s1; s1.insert (10 ); s1.insert (20 ); s1.insert (30 ); s1.insert (40 ); set<int >s2; s2.insert (100 ); s2.insert (200 ); s2.insert (300 ); s2.insert (400 ); cout<<"交换前:" <<endl; print (s1); print (s2); cout<<"交换后:" <<endl; s1.swap (s2); print (s1); print (s2); }
set插入和删除 功能描述 :
函数原型 :
insert(elem);
//在容器中插入元素
clear();
//清除所有元素
erase(pos);
//删除pos迭代器所指的元素,返回下一个元素的迭代器
erase(beg,end);
//删除区间[beg,end)的所有元素,返回下一个元素的迭代器
erase(elem);
//删除容器中值为elem的元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <set> using namespace std;void print (const set<int >s) { for (set<int >::const_iterator it=s.begin ();it!=s.end ();it++){ cout<<*it<<" " ; } cout<<endl; }void test01 () { set<int >s1; s1.insert (10 ); s1.insert (30 ); s1.insert (20 ); s1.insert (40 ); print (s1); s1.erase (s1.begin ()); print (s1); s1.erase (30 ); print (s1); s1.clear (); print (s1); }
set查找和统计 功能描述 :
函数原型 :
find(key);
//查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
count(key);
//统计key的元素个数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <set> using namespace std;void test01 () { set<int >s1; s1.insert (10 ); s1.insert (20 ); s1.insert (30 ); s1.insert (40 ); set<int >::iterator pos=s1.find (30 ); if (pos!=s1.end ()){ cout<<"找到元素:" <<*pos<<endl; }else { cout<<"未找到元素" <<endl; } }void test02 () { set<int >s1; s1.insert (10 ); s1.insert (20 ); s1.insert (30 ); s1.insert (40 ); s1.insert (30 ); s1.insert (30 ); int num=s1.count (30 ); cout<<"num= " <<num<<endl; }
set和multiset区别 区别 :
set不可以插入重复数据,而multiset可以
set插入数据的同时会返回插入结果,表示插入是否成功
multiset不会检测数据,因此可以插入重复数据
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <set> using namespace std;void test01 () { set<int >s; pair<set<int >::iterator,bool > ret=s.insert (10 ); if (ret.second){ cout<<"第一次插入成功" <<endl; }else { cout<<"第一次插入失败" <<endl; } ret=s.insert (10 ); if (ret.second){ cout<<"第二次插入成功" <<endl; }else { cout<<"第二次插入失败" <<endl; } multiset<int >m; m.insert (10 ); m.insert (10 ); for (multiset<int >::iterator it=m.begin ();it!=m.end ();it++){ cout<<*it<<" " ; } cout<<endl; }
总结 :
如果不允许插入重复数据可以利用set
如果需要插入重复数据利用multiset
pair队组创建 功能描述 :
两种创建方式 :
pair<type,type> p(value1,value2);
pair<type,type> p=make_pair(value1,value2);
注意事项 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <string> using namespace std;void test01 () { pair<string,int >p ("Tom" ,20 ); cout<<"姓名:" <<p.first<<"年龄:" <<p.second<<endl; pair<string,int >p2=make_pair ("jerry" ,30 ); cout<<"姓名:" <<p2.first<<"年龄:" <<p2.second<<endl; }
set容器排序 学习目标:
set容器默认排序规则是从小到大,掌握如何改变排序规则
主要技术点:
示例一 set存放内置数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <set> using namespace std;class mycompare {public : bool operator () (int v1,int v2) { return v1>v2; } };void test01 () { set<int >s1; s1.insert (10 ); s1.insert (20 ); s1.insert (30 ); s1.insert (40 ); s1.insert (50 ); for (set<int >::iterator it=s1.begin ();it!=s1.end ();it++){ cout<<*it<<" " ; } cout<<endl; set<int ,mycompare>s2; s2.insert (10 ); s2.insert (20 ); s2.insert (30 ); s2.insert (40 ); s2.insert (50 ); for (set<int ,mycompare>::iterator it=s2.begin ();it!=s2.end ();it++){ cout<<*it<<" " ; } cout<<endl; }
示例2 set存放自定义数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <set> using namespace std;class Person {public : Person (string name,int age){ this ->m_name=name; this ->m_age=age; } string m_name; int m_age; };class mycompare {public : bool operator () (const Person&p1,const Person&p2) { return p1.m_age>p2.m_age; } };void test01 () { set<Person,mycompare>s; Person p1 ("张1" ,24 ) ; Person p1 ("张2" ,28 ) ; Person p1 ("张3" ,25 ) ; Person p1 ("张4" ,21 ) ; s.insert (p1); s.insert (p2); s.insert (p3); s.insert (p4); for (set<Person>::iterator it=s.begin ();it!=s.end ();it++){ cout<<"姓名:" <<it->m_name<<"年龄:" <<it->m_age<<endl; } }
map/multimap容器 map基本概念 简介 :
map中所有元素都是pair(队组)
pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
所有元素都会根据元素的键值自动排序
本质 :
map/multimap属于关联式容器 ,底层使用二叉树实现
优点 :
map和multimap区别 :
map不允许容器中有重复key值元素
multimap允许容器中有重复key值元素
map构造和赋值 功能描述 :
函数原型 :
构造 :
map<T1,T2>mp;
//map默认构造函数
map(const map &mp);
//拷贝构造函数
赋值 :
map& operator=(const map &mp);
//重载等号操作符
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <map> using namespace std;void print (const map<int ,int >&m) { for (map<int ,int >::const_iterator it=m.begin ();it!=m.end ();it++){ cout<<"key= " <<(*it).first<<"value= " <<(*it).second<<endl; } cout<<endl; }void test01 () { map<int ,int >m; m.insert (pair <int ,int >(1 ,10 )); m.insert (pair <int ,int >(3 ,30 )); m.insert (pair <int ,int >(2 ,20 )); m.insert (pair <int ,int >(4 ,40 )); print (m); map<int ,int >m2 (m); print (m2); map<int ,int >m3; m3=m2; print (m3); }
map大小和交换 功能描述 :
函数原型 :
size();
//返回容器中元素的数目
empty();
//判断容器是否为空
swap(st);
//交换两个集合容器
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <map> using namespace std;void print (const map<int ,int >&m) { for (map<int ,int >::const_iterator it=m.begin ();it!=m.end ();it++){ cout<<"key= " <<(*it).first<<"value= " <<(*it).second<<endl; } cout<<endl; }void test01 () { map<int ,int >m; m.insert (pair <int ,int >(1 ,10 )); m.insert (pair <int ,int >(2 ,20 )); m.insert (pair <int ,int >(3 ,30 )); if (m.empty ()){ cout<<"m为空" <<endl; }else { cout<<"m不为空" <<endl; cout<<"m的大小为:" <<m.size ()<<endl; } }void test02 () { map<int ,int >m; m.insert (pair <int ,int >(1 ,10 )); m.insert (pair <int ,int >(2 ,20 )); m.insert (pair <int ,int >(3 ,30 )); map<int ,int >m2; m2.insert (pair <int ,int >(4 ,100 )); m2.insert (pair <int ,int >(5 ,200 )); m2.insert (pair <int ,int >(6 ,300 )); cout<<"交换前:" <<endl; print (m); print (m2); cout<<"交换后:" <<endl; m.swap (m2); print (m); print (m2); }
map插入和删除 功能描述 :
函数原型 :
insert(elem);
//在容器中插入元素
clear();
//清除所有元素
erase(pos);
//删除pos迭代器所指的元素,返回下一个元素的迭代器
erase(beg,end);
//删除区间[beg,end)的所有有元素,返回下一个元素的迭代器
erase(key);
//删除容器中值为key的元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <map> using namespace std;void print (const map<int ,int >&m) { for (map<int ,int >::const_iterator it=m.begin ();it!=m.end ();it++){ cout<<"key= " <<it->first<<" value= " <<it->second<<endl; } cout<<endl; }void test01 () { map<int ,int >m; m.insert (pair <int ,int >(1 ,10 )); m.insert (make_pair (2 ,20 )); m.insert (map<int ,int >::value_type (3 ,30 )); m[4 ]=40 ; cout<<m[5 ]<<endl; print (m); m.erase (m.begin ()); print (m); m.erase (3 ); print (m); m.clear (); print (m); }
map查找和统计 功能描述 :
函数原型 :
find(key);
//查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
count(key);
//统计key的元素个数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <map> using namespace std;void test01 () { map<int ,int >m; m.insert (make_pair (1 ,10 )); m.insert (make_pair (2 ,20 )); m.insert (make_pair (3 ,30 )); m.insert (make_pair (4 ,40 )); map<int ,int >::iterator pos=m.find (3 ); if (pos!=m.end ()){ cout<<"查到了元素key= " <<pos->first<<" value= " <<pos->second<<endl; }else { cout<<"未找到元素" <<endl; } int num=m.count (3 ); cout<<"num= " <<num<<endl; }
map容器排序 学习目标 :
map容器默认排序规则为按照key值进行从小到大排序,掌握如何改变排序规则
主要技术点 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <map> using namespace std;class mycompare {public : bool operator () (int v1,int v2) { return v1>v2; } };void test01 () { map<int ,int ,mycompare>m; m.insert (make_pair (1 ,10 )); m.insert (make_pair (2 ,30 )); m.insert (make_pair (3 ,30 )); m.insert (make_pair (4 ,40 )); m.insert (make_pair (5 ,50 )); for (map<int ,int >::iterator it=m.begin ();it!=m.end ();it++){ cout<<"key= " <<it->first<<" value= " <<it->second<<endl; } }
unordered_map(哈希)容器 功能描述 :
是一个底层由哈希表实现的无序容器,可存储若干个键值对,键有唯一性。
常用接口 构造函数 :
unordered_map<key_type,val_type> um
//key_type为键的类型一般为int型,val_type为存储数据类型
赋值操作 :
unordered_map& operator=(const unordered_map &um);
//重载等号操作符
数据存取 :
insert(make_pair(key,val))
//向哈希表中插入键值对
数据查找/修改 :
count(const key_type& key);
//返回键为key的元素个数
find(const key_type& key);
//返回键为key的第一个元素,返回迭代器,没找到返回end()
emplace(const key_type& key,const val_type& val);
//如果容器中没有对应的键值,则插入一个新元素,如果有,就修改该键值对应的value值
clear();
//清除容器中所有元素
iterator erase ( const_iterator position );
//删除指定迭代器的元素
size_type erase ( const key_type& k );
//删除指定键值的元素
iterator erase ( const_iterator first, const_iterator last );
//删除指定迭代器范围内的元素
swap(unordered_map& ump);
//互相交换两个容器的内容,容器类型必须一致,但大小可以不同
大小操作 :
empty();
//判断哈希表是否为空
size();
//返回哈希表的大小
max_size();
//返回哈希容器最大容纳数量
STL –函数对象 函数对象 函数对象概念 概念 :
重载函数调用操作符 的类,其对象常称为函数对象
函数对象 使用重载的()时,行为类似函数调用,也叫仿函数
本质 :
函数对象(仿函数)是一个类 ,不是一个函数
函数对象使用 特点 :
函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
函数对象超出普通函数的概念,函数对象可以有自己的状态
函数对象可以作为参数传递
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <string> using namespace std;class myadd {public : int operator () (int v1,int v2) { return v1+v2; } };void test01 () { myadd a; cout<<a (10 ,10 )<<endl; }class myprint {public : myprint (){ this ->cnt=0 ; } void operator () (string test) { cout<<test<<endl; cnt++; } int cnt; };void test02 () { myprint p; p ("hello world" ); cout<<"myprint调用次数为:" <<p.cnt<<endl; }void doprint (myprint &mp,string test) { mp (test); }void test03 () { myprint p; doprint (p,"hello C++" ); }
总结 :
谓词 谓词概念 概念 :
返回bool类型的仿函数称为谓词
如果operator()接收一个参数,那么叫做一元谓词
如果operator()接收两个参数,那么叫做二元谓词
一元谓词 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <vector> #include <algorithm> using namespace std;class greaterfive {public : bool operator () (int val) { return val>5 ; } };void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } vector<int >::iterator it=find_if (v.begin (),v.end (),greaterfive ()); if (it==v.end ()){ cout<<"未找到" <<endl; }else { cout<<"找到了大于5的数字为:" <<*it<<endl; } }
二元谓词 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <vector> #include <algorithm> using namespace std;class mycompare {public : bool operator () (int val1,int val2) { return val1>val2; } };void test01 () { vector<int >v; v.push_back (10 ); v.push_back (40 ); v.push_back (20 ); v.push_back (30 ); v.push_back (50 ); sort (v.begin (),v.end ()); for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; sort (v.begin (),v.end (),mycompare ()); cout<<"---------------------" <<endl; for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; }
内建函数对象 内建函数对象意义 概念 :
分类 :
用法 :
这些仿函数所产生的对象,用法和一般函数完全相同
使用内建函数对象,需要引入头文件#include <functional>
算术仿函数 功能描述 :
实现四则运算
其中negate是一元运算,其他都是二元运算
仿函数原型 :
template<class T> T plus<T>
//加法仿函数
template<class T> T minus<T>
//减法仿函数
template<class T> T multiplies<T>
//乘法仿函数
template<class T> T divides<T>
//除法仿函数
template<class T> T modulus<T>
//取模仿函数
template<class T> T negate<T>
//取反仿函数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> #include <functional> using namespace std;void test01 () { negate<int >n; cout<<n (50 )<<endl; }void test02 () { plus<int >p; cout<<p (10 ,10 )<<endl; }
关系仿函数 功能描述 :
仿函数原型 :
template<class T> bool equal_to<T>
//等于
template<class T> bool not_equal_to<T>
//不等于
template<class T> bool greater<T>
//大于
template<class T> bool greater_equal<T>
//大于等于
template<class T> bool less<T>
//小于
template<class T> bool less_equal<T>
//小于等于
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <functional> #include <vector> #include <algorithm> using namespace std;class mycompare {public : bool operator () (int v1,int v2) { return v1>v2; } };void test01 () { vector<int >v; v.push_back (10 ); v.push_back (30 ); v.push_back (40 ); v.push_back (20 ); v.push_back (50 ); for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; sort (v.begin (),v.end (),greater <int >()); for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; }
逻辑仿函数 功能描述 :
函数原型 :
template<class T> bool logical_and<T>
//逻辑与
template<class T> bool logical_or<T>
//逻辑或
template<class T> bool logical_not<T>
//逻辑非
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <functional> #include <algorithm> #include <vector> using namespace std;void test01 () { vector<bool >v; v.push_back (true ); v.push_back (false ); v.push_back (true ); v.push_back (false ); for (vector<bool >::iterator it=v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; vector<bool >v2; v2.resize (v.size ()); transform (v.begin (),v.end (),v2.begin (),logical_not <bool >()); cout<<"--------------------" <<endl; for (vector<bool >::iterator it=v2.begin ();it!=v2.end ();it++){ cout<<*it<<" " ; } cout<<endl; }
总结 :逻辑仿函数实际应用较少,了解即可
STL –常用算法 概述 :
算法主要是由头文件<algorithm> <functional> <numeric>
组成
<algorithm>
是所有STL头文件中最大的一个,范围涉及到比较、交换、查找、遍历操作、复制、修改等等
<numeric>
体积很小,只包括几个在序列上面进行简单数学运算的模板函数
<functional>
定义了一些模板类,用以声明函数对象
常用遍历算法 学习目标 :
算法简介 :
for_each
//遍历容器
transform
//搬运容器到另一个容器中
for_each算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <algorithm> #incude <vector> using namespace std;void print01 (int val) { cout<<val<<" " ; }class print02 {public : void operator () (int val) { cout<<val<<" " ; } };void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } for_each(v.begin (),v.end (),print01); cout<<endl; for_each(v.begin (),v.end (),print02 ()); cout<<endl; }
总结 :for_each在实际开发中是最常用遍历算法,需要熟练掌握
功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <vector> #include <algorithm> using namespace std;class Transform {public : int operator () (int v) { return v+1000 ; } };class myprint {public : void operator () (int val) { cout<<val<<" " ; } };void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } vector<int >vt; vt.resize (v.size ()); transform (v.begin (),v.end (),vt.begin (),Transform ()); for_each(vt.begin (),vt.end (),myprint ()); cout<<endl; }
总结 :搬运的目标容器必须要提前开辟空间,否则无法正常搬运
常用查找算法 学习目标 :
算法简介 :
find
//查找元素
find_if
//按条件查找元素
abjacent_find
//查找相邻重复元素
binary_search
//二分查找法
count
//统计元素个数
count_if
//按条件统计元素个数
find 算法 功能描述 :
查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()
函数原型 :
find(iterator beg,iterator end,value);
//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
//beg 开始迭代器
//end 结束迭代器
//value 查找的元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <iostream> #include <algorithm> #include <vector> #include <string> using namespace std;void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } vector<int >::iterator it=find (v.begin (),v.end (),5 ); if (it==v.end ()){ cout<<"未找到" <<endl; }else { cout<<"找到:" <<*it<<endl; } }class Person {public : Person (string name,int age){ this ->m_name=name; this ->m_age=age; } bool operator ==(const Person &p){ if (this ->m_name==p.m_name&&this ->m_age==p.m_age){ return true ; }else { return false ; } } string m_name; int m_age; };void test02 () { vector<Person>v; Person p1 ("aaa" ,10 ) ; Person p2 ("bbb" ,20 ) ; Person p3 ("ccc" ,30 ) ; Person p4 ("ddd" ,40 ) ; v.push_back (p1); v.push_back (p2); v.push_back (p3); v.push_back (p4); vector<Person>::iterator it=find (v.begin (),v.end (),p2); if (it==v.end ()){ cout<<"未找到" <<endl; }else { cout<<"找到元素 姓名:" <<it->m_name<<" 年龄:" <<it->m_age<<endl; } }
find_if 算法 功能描述 :
函数原型 :
find_if(iterator beg,iterator end,_Pred);
//按值查找元素,找到返回指定位置的迭代器,找不到返回结束迭代器位置
//beg 开始迭代器
//end 结束迭代器
//_Pred 函数或者谓词(返回bool类型的仿函数)
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <iostream> #include <vector> #include <string> #include <algorithm> using namespace std;class greaterfive {public : bool operator () (int val) { return val>5 ; } };void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } vector<int >::iterator it=find_if (v.begin (),v.end (),greaterfive ()); if (it==v.end ()){ cout<<"没有找到" <<endl; }else { cout<<"找到大于5的数字为:" <<*it<<endl; } }class Person {public : Person (string name,int age){ this ->m_name=name; this ->m_age=age; } string m_name; int m_age; };class greater20 {public : bool operator () (Person &p) { return p.m_age>20 ; } };void test02 () { vector<Person>v; Person p1 ("a" ,10 ) ; Person p2 ("b" ,20 ) ; Person p3 ("c" ,30 ) ; Person p4 ("d" ,40 ) ; v.push_back (p1); v.push_back (p2); v.push_back (p3); v.push_back (p4); vector<Person>::iterator it=find_if (v.begin (),v.end (),greater20 ()); if (it==v.end ()){ cout<<"未找到" <<endl; }else { cout<<"找到姓名:" <<it->m_name<<" 年龄:" <<it->m_age<<endl; } }
adjacent_find 算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <vector> #include <algorithm> using namespace std;void test01 () { vector<int >v; v.push_back (0 ); v.push_back (2 ); v.push_back (0 ); v.push_back (3 ); v.push_back (1 ); v.push_back (4 ); v.push_back (3 ); v.push_back (3 ); vector<int >::iterator pos=adjacent_find (v.begin (),v.end ()); if (pos==v.end ()){ cout<<"未找到相邻重复元素" <<endl; }else { cout<<"找到相邻重复元素:" <<*pos<<endl; } }
总结 :面试题中如果出现查找相邻重复元素,记得用STL中的adjacemt_find算法
binary_search 算法
功能描述 :
函数原型 :
bool binary_search(iterator beg,iterator end,value);
//查找指定的元素,查到返回true,否则返回false
//注意 :在无序序列中不可用
//beg 开始迭代器
//end 结束迭代器
//value 查找的元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <vector> #include <algorithm> using namespace std;void test01 () { vector<int >v; for (int i=0 ;i<10 ;i++){ v.push_back (i); } bool ret=binary_search (v.begin (),v.end (),9 ); if (ret){ cout<<"找到了元素" <<endl; }else { cout<<"未找到" <<endl; } }
binary_search 算法 功能描述 :
函数原型 :
bool binary_search(iterator beg,iterator end,value);
//查找指定的元素,查到返回true,否则false
//注意 :在无序序列中不可用
//beg 开始迭代器
//end 结束迭代器
//value 查找的元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <vector> #include <algorithm> using namespace std;void test01 () { vector<int >v; for (int i = 0 ; i < 10 ; i++) { v.push_back (i); } bool ret = binary_search (v.begin (), v.end (), 9 ); if (ret) { cout << "找到了元素" << endl; } else { cout << "未找到" << endl; } }
总结 :二分查找法查找效率很高,值得注意的是查找的容器中必须是有序序列
count 算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> #include <vector> #include <algorithm> using namespace std;void test01 () { vector<int >v; v.push_back (10 ); v.push_back (40 ); v.push_back (30 ); v.push_back (40 ); v.push_back (40 ); int num = count (v.begin (), v.end (), 40 ); cout << "40的元素个数为:" << num << endl; }class Person { public : Person (string name, int age) { this ->m_name = name; this ->m_age = age; } bool operator ==(const Person &p) { if (this ->m_age == p.m_age) { return true ; } else { return false ; } } string m_name; int m_age; };void test02 () { vector<Person>v; Person p1 ("a" , 35 ) ; Person p2 ("b" , 35 ) ; Person p3 ("c" , 35 ) ; Person p4 ("d" , 40 ) ; Person p5 ("e" , 40 ) ; Person p6 ("f" , 35 ) ; v.push_back (p1); v.push_back (p2); v.push_back (p3); v.push_back (p4); v.push_back (p5); int num = count (v.begin (), v.end (), p6); cout << "与f同岁数的人员个数为:" << num << endl; }
总结 :统计自定义数据类型时候,需要配合重载operator==
count_if 算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream> #include <vector> #include <algorithm> using namespace std;class greater20 { public : bool operator () (int val) { return val > 20 ; } };void test01 () { vector<int >v; v.push_back (10 ); v.push_back (40 ); v.push_back (30 ); v.push_back (20 ); v.push_back (40 ); int num = count_if (v.begin (), v.end (), greater20 ()); cout << "大于20的元素个数为:" << num << endl; }class Person { public : Person (string name, int age) { this ->m_name = name; this ->m_age = age; } string m_name; int m_age; };class agegreater { public : bool operator () (const Person &p) { return p.m_age > 20 ; } };void test02 () { vector<Person>v; Person p1 ("a" , 35 ) ; Person p2 ("b" , 35 ) ; Person p3 ("c" , 35 ) ; Person p4 ("d" , 40 ) ; Person p5 ("e" , 20 ) ; v.push_back (p1); v.push_back (p2); v.push_back (p3); v.push_back (p4); v.push_back (p5); int num = count_if (v.begin (), v.end (), agegreater ()); cout << "大于20岁的人员个数:" << num << endl; }
常用排序算法 算法简介 :
sort
//对容器内元素进行排序
random_shuffle
//洗牌 指定范围内的元素随机调整次序
merge
//容器元素合并,并存储到另一个容器
reverse
//反转指定范围的元素
sort 算法 功能描述 :
函数原型 :
sort(iterator beg,iterator end,_Pred);
//按值查找元素,找到返回指定值位置迭代器,找不到返回结束迭代器位置
//beg 开始迭代器
//end 结束迭代器
//_Pred 谓词
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <algorithm> #include <vector> #include <functional> using namespace std;void myprint (int val) { cout << val << " " ; }void test01 () { vector<int >v; v.push_back (10 ); v.push_back (30 ); v.push_back (50 ); v.push_back (20 ); v.push_back (40 ); sort (v.begin (), v.end ()); for_each(v.begin (), v.end (), myprint); cout << endl; sort (v.begin (), v.end (), greater <int >()); for_each(v.begin (), v.end (), myprint); }
random_shuffle 算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <vector> #include <algorithm> #include <ctime> using namespace std;class print { public : void operator () (int val) { cout << val << " " ; } };void test01 () { srand ((unsigned int )time (NULL )); vector<int >v; for (int i = 0 ; i < 10 ; i++) { v.push_back (i); } random_shuffle (v.begin (), v.end ()); for_each(v.begin (), v.end (), print ()); cout << endl; }
总结 :random_shuffle算法使用时记得加随机数种子
merge 算法 功能描述 :
函数原型 :
merge(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
//容器元素合并,并存储到另一容器中
//注意 :两个容器必须是有序的
//beg1 容器1开始迭代器
//end1 容器1结束迭代器
//beg2 容器2开始迭代器
//end2 容器2结束迭代器
//dest 目标容器开始迭代器
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <vector> #include <algorithm> using namespace std;void print (int val) { cout<<val<<" " ; }void test01 () { vector<int >v1; vector<int >v2; for (int i=0 ;i<10 ;i++){ v1.push_back (i); v2.push_back (i+1 ); } vector<int >v; v.resize (v1.size ()+v2.size ()); merge (v1.begin (),v1.end (),v2.begin (),v2.end (),v.begin ()); for_each(v.begin (),v.end (),print); cout<<endl; }
总结 :merge合并的两个容器必须的有序序列
reverse 算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <vector> #include <algorithm> using namespace std;void print (int val) { cout<<val<<" " ; }void test01 () { vector<int >v; v.push_back (10 ); v.push_back (30 ); v.push_back (50 ); v.push_back (20 ); v.push_back (40 ); cout<<"反转前:" <<endl; for_each(v.begin (),v.end (),print); cout<<endl; cout<<"反转后:" <<endl; reverse (v.begin (),v.end ()); for_each(v.begin (),v.end (),print); }
常用拷贝和替换算法 算法简介 :
copy
//容器内指定范围的元素拷贝到另一容器中
replace
//将容器内指定范围的旧元素修改为新元素
replace_if
//容器内指定范围满足条件的元素替换为新元素
swap
//互换两个容器的元素
copy 算法 功能描述 :
函数原型 :
copy(iterator beg,iterator end,iterator dest);
//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
//beg 开始迭代器
//end 结束迭代器
//dest 目标容器开始迭代器
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <vector> #include <algorithm> using namespace std;void print (int val) { cout<<val<<" " ; }void test01 () { vector<int >v1; for (int i=0 ;i<10 ;i++){ v1.push_back (i); } vector<int >v2; v2.resize (v1.size ()); copy (v1.begin (),v1.end (),v2.begin ()); for_each(v2.begin (),v2.end (),print); cout<<endl; }
replace 算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <vector> #include <algorithm> using namespace std;class print {public : void operator () (int val) { cout<<val<<" " ; } };void test01 () { vector<int >v; v.push_back (20 ); v.push_back (30 ); v.push_back (50 ); v.push_back (20 ); v.push_back (10 ); v.push_back (20 ); cout<<"替换前:" <<endl; for_each(v.begin (),v.end (),print ()); cout<<endl; replace (v.begin (),v.end (),20 ,2000 ); cout<<"替换后:" <<endl; for_each(v.begin (),v.end (),print ()); cout<<endl; }
replace_if 算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <vector> #include <algorithm> using namespace std;void print (int val) { cout<<val<<" " ; }class greater30 {public : bool operator () (int val) { return val>=30 ; } };void test01 () { vector<int >v; v.push_back (10 ); v.push_back (40 ); v.push_back (20 ); v.push_back (30 ); v.push_back (40 ); v.push_back (50 ); cout<<"替换前:" <<endl; for_each(v.begin (),v.end (),print); cout<<endl; replace_if (v.begin (),v.end (),greater30 (),3000 ); cout<<"替换后:" <<endl; for_each(v.begin (),v.end (),print); cout<<endl; }
swap 算法 功能描述 :
函数原型 :
注意 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <vector> #include <algorithm> using namespace std;void print (int val) { cout<<val<<" " ; }void test01 () { vector<int >v1; vector<int >v2; for (int i=0 ;i<10 ;i++){ v1.push_back (i); v2.push_back (i+100 ); } cout<<"交换前:" <<endl; for_each(v1.begin (),v1.end (),print); cout<<endl; for_each(v2.begin (),v2.end (),print); cout<<endl; cout<<endl; cout<<"交换后:" <<endl; swap (v1,v2); for_each(v1.begin (),v1.end (),print); cout<<endl; for_each(v2.begin (),v2.end (),print); cout<<endl; }
常用算术生成算法 注意 :
算术生成算法属于小型算法,使用时包含头文件为#include <numeric>
算法简介 :
accumulate
//计算容器元素累计总和
fill
//向容器种添加元素
accumulate 算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <numeric> #include <vector> using namespace std;void test01 () { vector<int >v; for (int i=0 ;i<100 ;i++){ v.push_back (i); } int total=accumulate (v.begin (),v.end (),0 ); cout<<"total = " <<total<<endl; }
fill 算法 功能描述 :
函数原型 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <vector> #include <numeric> #include <algorithm> using namespace std;void print (int val) { cout<<val<<" " ; }void test01 () { vector<int >v; v.resize (10 ); fill (v.begin (),v.end (),100 ); for_each(v.begin (),v.end (),print); cout<<endl; }
常用集合算法 算法简介 :
set_intersection
//求两个容器的并集
set_union
//求两个容器的并集
set_difference
//求两个容器的差集
set_intersection 算法 功能描述 :
函数原型 :
set_intersection(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
//求两个集合的交集
//beg1 容器1开始迭代器
//end1 容器1结束迭代器
//beg2 容器2开始迭代器
//end2 容器2结束迭代器
//dest 目标容器开始迭代器
注意事项 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <algorithm> #include <vector> using namespace std;void print (int val) { cout<<val<<" " ; }void test01 () { vector<int >v1; vector<int >v2; for (int i=0 ;i<10 ;i++){ v1.push_back (i); v2.push_back (i+5 ); } vector<int >v; v.resize (min (v1.size (),v2.size ())); vector<int >::iterator it=set_intersection (v1.begin (),v1.end (),v2.begin (),v2.end (),v.begin ()); for_each(v.begin (),it,print); cout<<endl; }
总结 :
求交集的两个集合必须是有序序列
目标容器开辟空间需要从两个容器中取小值
set_intersection返回值即是交集中最后一个元素的位置
set_union 算法 功能描述 :
函数原型 :
set_union(iterator beg1,itertaor end1,iterator beg2,iterator end2,iterator dest);
//求两个集合的并集
//beg1 容器1开始迭代器
//end1 容器1结束迭代器
//beg2 容器2开始迭代器
//end2 容器2结束迭代器
//dest 目标容器开始迭代
注意事项 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <vector> #include <algorithm> using namespace std;void print (int val) { cout<<val<<" " ; }void test01 () { vector<int >v1; vector<int >v2; vector<int >v; for (int i=0 ;i<10 ;i++){ v1.push_back (i); v2.push_back (i+5 ); } v.resize (v1.size ()+v2.size ()); vector<int >::iterator it=set_union (v1.begin (),v1.end (),v2.begin (),v2.end (),v.begin ()); for_each(v.begin (),it,print); cout<<endl; }
总结 :
求并集的两个集合必须是有序序列
目标容器开辟空间需要从两个容器大小相加
set_union返回值即是并集中最后一个元素的位置
set_difference 算法 功能描述 :
函数原型 :
set_difference(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);
//求两个集合的差集
//beg1 容器1开始迭代器
//end1 容器1结束迭代器
//beg2 容器2开始迭代器
//end2 容器2结束迭代器
//dest 目标容器开始迭代器
注意事项 :
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <vector> #include <algorithm> using namespace std;void print (int val) { cout<<val<<" " ; }void test01 () { vector<int >v1; vector<int >v2; vector<int >v; for (int i=0 ;i<10 ;i++){ v1.push_back (i); v2.push_back (i+5 ); } v.resize (max (v1.size (),v2.size ())); vector<int >::iterator it=set_difference (v1.begin (),v1.end (),v2.begin (),v2.end (),v.begin ()); for_each(v.begin (),it,print); cout<<endl; }
总结 :
求差集的两个集合必须的有序序列
目标容器开辟空间需要从两个容器大小取大值
set_difference返回值即是差集中最后一个元素的位置
异常处理 概念 :
异常就是错误,而在程序中难免会有异常出现,但为了让程序正常运行而不是崩溃,则需要使用到异常处理,来解决
传统的异常处理是通过判断返回值来判断程序是否异常。
c++中提供了try、catch、throw的异常处理表达式。
函数 :
throw 表达式
throw语句用来抛出异常 ,当有异常发生时,可以通过throw创建一个异常对象并抛掷 。
try{复合语句} catch(异常声明){异常处理程序}
try内部的异常语句是保护段语句,当try内部的程序出现异常,则catch子句就会根据异常不同进行捕获异常,然后执行相对应的异常处理程序 。如果出现异常但是catch子句未匹配到异常,则库函数terminate将被自动调用,默认是调用abort终止程序 。当捕获异常并执行catch子句的时候则会自动帮你将try语块中的执行了尚未析构的语句析构了 。
异常声明:
异常类 :
智能指针 概念 :
智能指针是原始指针的封装,其优点是会自动分配内存 ,不用担心潜在的内存泄露
普通指针的不足 :
new和new[]的内存需要用delete和delete[]释放
程序员的主观失误,忘了或漏了释放
程序员也不确定何时释放
智能指针的设计思路 :
智能指针是类模板,在栈上创建智能指针对象
把普通指针交给智能指针对象
智能指针对象过期时,调用析构函数释放普通指针的内存
独占指针 unique_ptr 概念 :
unique_ptr
**独享一个原始指针(也就是unique_ptr指向的对象)**,也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁
源码简介 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <typename T,typename D =default_delete<T> >class unique_ptr{public : explicit unique_ptr (pointer p) noexcept ; ~unique_ptr () noexcept ; T& operator *() const ; T* operator ->() const noexcept ; unique_ptr (const unique_ptr&)=delete ; unique_ptr& operator =(const unique_ptr &)=delete ; unique_ptr (unique_ptr &&) noexcept ; unique_ptr& operator =(unique_ptr&&) noexcept ; private : pointer ptr; }
功能描述 :
在任何给定时候,只能有一个指针管理内存(也就是独占指针对象只管理一个指针资源)
当指针超出作用域时,内存将自动释放
unique_ptr指针不可Copy,也不可以进行赋值,只可以Move
可以通过get()获取裸指针地址 (&unique_ptr对象得到的是对象指针地址 不是裸指针)
可以通过->调用成员函数
可以通过*调用dereferencing
作为函数的参数 :
传引用 (不能传值 ,因为unique_ptr没有拷贝构造函数)
裸指针
注意 :
不要用同一个裸指针初始化多个unique_ptr对象
不要用unique_ptr管理不是new分配的内存
不支持指针的运算(+、-、++、–)
**创建方式(初始化)**:
通过已有裸指针 创建
1 2 3 4 5 6 7 8 9 class AA { .... };int main () { AA* p=new AA ("小明" ); unique_ptr<AA> pu1=p; return 0 ; }
通过new来创建
1 unique_ptr<AA> pu1 (new AA("小明" )) ;
通过std::make_unique创建 (推荐)
1 unique_ptr<AA> pu1=make_unique <AA>("小明" );
内置函数和使用方法 :
get()
:获取裸指针的地址
1 2 unique_ptr<int > p1=make_unique <int >(10 ); cout<<p1.get ()<<endl;
release()
:释放对原始指针的控制权 ,将unique_ptr对象置为空,返回裸指针地址 (之前管理的指针地址)。可用于把unique_ptr传递给子函数,子函数将负责释放对象
1 2 3 unique_ptr<int > p1=make_unique <int >(10 ); cout<<p1.release ()<<endl; if (p1==nullptr ) cout<<"p1为空" <<endl;
std::move()
:转移对原始指针的控制权 。(可用于把unique_ptr传递给子函数,子函数形参也是unique_ptr,传值的方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void func4 (unique_ptr<AA> a) { cout<<a->m_name<<endl; }int main (int argc, char *argv[]) { unique_ptr<AA> p (new AA("小明" )) ; cout<<"开始调用函数.\n" ; func4 (std::move (p)); cout<<"调用函数完成.\n" ; return 0 ; }
reset()
:释放unique_ptr指向资源,以及更改指向
1 2 3 4 5 6 void reset (T* _ptr=(T*)nullptr ) ; unique_ptr<AA>p (new AA ("小明" )); p.reset (); p.reset (nullptr ); p.reset (new AA ("小蓝" ));
swap()
:交换两个unique_ptr所指向的对象的控制权
1 2 3 4 5 6 7 8 9 10 11 void swap (unique_ptr<T>&_Right) ; unique_ptr<AA>p1 (new AA ("小明" )); unique_ptr<AA>p2 (new AA ("小红" )); p1.swap (p2); cout<<p1->m_name<<endl; cout<<p2->m_name<<endl;
[]
:unique_ptr重载了操作符[],提供了支持数组的具体化版本 ,操作符[]返回的是引用,可以作为左值
使用
1 2 3 4 5 unique_ptr<int []> parr1 (new int [3 ](33 ,22 ,11 )) ; cout<<"parr1[0]=" <<parr1[0 ]<<endl; cout<<"parr1[1]=" <<parr1[1 ]<<endl; cout<<"parr1[2]=" <<parr1[2 ]<<endl;
使用技巧 :
如果源unique_ptr对象是一个临时右值(一般是函数的返回值),编译器允许将这个unique_ptr赋给另一个unique_ptr对象 。如果源unique_ptr将存在一段时间,则编译器不允许赋值!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <memory> using namespace std; unique_ptr<int >func (){ unique_ptr<int > pp=make_unique <int >(100 ); return pp; }int main (int argc, char *argv[]) { unique_ptr<int > p1=make_unique <int >(10 ); unique_ptr<int > p2; p2=p1; p2=unique_ptr <int >(new int (1 )); p1=func (); std::cout<<*p1<<std::endl; return 0 ; }
用nullptr给unique_ptr赋值将会释放对象,空的unique_ptr==nullptr
一些情况使用技巧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> #include <memory> using namespace std;class AA {public : string m_name; AA (){ cout<<m_name<<"调用构造函数AA().\n" ; } AA (const string &name):m_name (name){ cout<<"调用构造函数AA(" <<m_name<<").\n" ; } ~AA (){ cout<<"调用了析构函数~AA(" <<m_name<<").\n" ; } };void func1 (const AA* a) { cout<<a->m_name<<endl; }void func2 (AA* a) { cout<<a->m_name<<endl; delete a; }void func3 (const unique_ptr<AA> &a) { cout<<a->m_name<<endl; }void func4 (unique_ptr<AA> a) { cout<<a->m_name<<endl; }int main (int argc, char *argv[]) { unique_ptr<AA> p (new AA("小明" )) ; cout<<"开始调用函数.\n" ; func1 (p.get ()); func2 (p.release ()); func3 (p); func4 (std::move (p)); cout<<"调用函数完成.\n" ; return 0 ; }
unique_ptr 也可以像普通指针一样,当指向一个类继承体系的基类对象时,也具有多态性质 ,如同使用裸指针管理基类对象和派生类对象那样
unique_ptr不是绝对安全 ,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <memory> using namespace std;class AA {public : string m_name; AA (){ cout<<m_name<<"调用构造函数AA().\n" ; } AA (const string &name):m_name (name){ cout<<"调用构造函数AA(" <<m_name<<").\n" ; } ~AA (){ cout<<"调用了析构函数~AA(" <<m_name<<").\n" ; } };unique_ptr<AA> p1 (new AA("小明全局" )) ;int main (int argc, char *argv[]) { unique_ptr<AA> p (new AA("小明局部" )) ; exit (0 ); } || 调用构造函数AA (小明全局). || 调用构造函数AA (小明局部). || 调用了析构函数~AA (小明全局). || Exited with code 0
共享指针 shared_ptr 概念 :
shared_ptr
共享它指向的对象,多个shared_ptr可以指向(关联)相同的对象,在内部采用计数机制来实现 ,共享不是复制,资源只有一个
当新的shared_ptr与对象关联 时,引用计数增加1
当shared_ptr超出作用域 时,引用计数减1 。
当引用计数为0 时,则表示没有任何shared_ptr与对象相联,则释放该对象
基本用法:
注:shared_ptr的构造函数也是explicit,但是,没有删除拷贝构造函数和赋值函数
初始化
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 shared_ptr p (new AA("小明" )) ; shared_ptr p=make_shared <AA>("小明" ); AA* p=new AA ("小明" );shared_ptr<AA> p0 (p) ; shared_ptr<AA> p0 (new AA("小明" )) ;shared_ptr<AA> p1 (p0) ; shared_ptr<AA> p2=p0;
use_count()
:显示shared_ptr所指向的资源引用次数
1 2 3 4 shared_ptr<AA> p0=make_shared <AA>("小明" );shared_ptr<AA> p1 (p0) ; shared_ptr<AA> p2=p0; cout<<p0.use_count ()<<"\n" ;
get()
:显示shared_ptr所指向的资源地址
unique()
:判断shared_ptr所指向的资源是否被其他shared_ptr对象所共享 ,如果use.count()为1,返回true,否则返回false
1 2 3 4 5 6 shared_ptr<AA> p0=make_shared <AA>("小明" );shared_ptr<AA> p1 (p0) ; shared_ptr<AA> p2=p0; cout<<p0.unique ()<<"\n" ; shared_ptr<AA> p3=make_shared <AA>("111" ); cout<<p3.unique ()<<"\n" ;
=
:shared_ptr重载了=操作符,支持赋值, 因为将右值shared_ptr赋值给了左值shared_ptr,因此右值shared_ptr所指向资源计数+1,则左值shared_ptr所指向资源计数-1
1 2 3 4 5 6 7 8 9 10 11 12 13 int main (int argc, char *argv[]) { shared_ptr<AA> p0=make_shared <AA>("小明" ); shared_ptr<AA> p1 (p0) ; shared_ptr<AA> p2=p0; shared_ptr<AA> p3=make_shared <AA>("111" ); shared_ptr<AA> p4=p3; shared_ptr<AA> p5=p3; p2=p5; cout<<p0.use_count ()<<"\n" ; cout<<p3.use_count ()<<"\n" ; return 0 ; }
[]
:shared_ptr重载了操作符[],提供了支持数组的具体化版本 ,操作符[]返回的是引用,可以作为左值
使用
std::move()
:可以将转移对原始指针的控制权,还可以将unique_ptr转移成shared_ptr(反过来不行)
reset()
:改变与资源的关联关系,以及指向
1 2 3 shared_ptr<AA> p=make_shared <AA>("小明" ); p.reset (); p.reset (new AA ("111" ));
swap()
:交换两个shared_ptr的控制权
1 2 void swap (shared_ptr<T> &_Right) ;
作为函数的参数 :
传引用 (不能传值 ,因为unique_ptr没有拷贝构造函数)
裸指针
注意 :
不要用同一个裸指针初始化多个shared_ptr对象
不要用shared_ptr管理不是new分配的内存
不支持指针的运算(+、-、++、–)
使用技巧 :
用nullptr给shared_ptr赋值时将计数减1 ,如果计数为0,将释放对象,空的shared_ptr==nullptr
shared_ptr 也可以像普通指针一样,当指向一个类继承体系的基类对象时,也具有多态性质 ,如同使用裸指针管理基类对象和派生类对象那样
shared_ptr不是绝对安全 ,如果程序中调用exit()退出,全局的shared_ptr可以自动释放,但局部的shared_ptr无法释放
如果unique_ptr能解决问题,就不要在用shared_ptr,unique_ptr的效率更高,占用的资源更少
shared_ptr的线程安全性 :
shared_ptr的引用计数本身是线程安全 (引用计数是原子操作)
多个线程同时读同一个shared_ptr对象是线程安全的
如果多个线程对同一个shared_ptr对象进行读和写,则需要加锁
多线程读写shared_ptr所指向的同一个对象 ,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护
shared_ptr存在问题 :
shared_ptr内部维护一个共享的引用计数器,多个shared_ptr可以指向同一个资源
如果出现了循环引用 的情况,引用计数永远无法归0,资源不会被释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <iostream> #include <memory> using namespace std;class BB ;class AA {public : string m_name; AA (){ cout<<m_name<<"调用构造函数AA().\n" ; } AA (const string &name):m_name (name){ cout<<"调用构造函数AA(" <<m_name<<").\n" ; } ~AA (){ cout<<"调用了析构函数~AA(" <<m_name<<").\n" ; } shared_ptr<BB> m_p; };class BB {public : string m_name; BB (){ cout<<m_name<<"调用构造函数BB().\n" ; } BB (const string &name):m_name (name){ cout<<"调用构造函数BB(" <<m_name<<").\n" ; } ~BB (){ cout<<"调用了析构函数~BB(" <<m_name<<").\n" ; } shared_ptr<AA> m_p; };int main (int argc, char *argv[]) { shared_ptr<AA> p0=make_shared <AA>("小明1" ); shared_ptr<BB> p1=make_shared <BB>("小明2" ); p0->m_p=p1; p1->m_p=p0; return 0 ; } || 调用构造函数AA (小明1 ). || 调用构造函数BB (小明2 ). || Exited with code 0
智能指针删除器 概念 :
在默认情况下,智能指针过期时,它会自动调用缺省的删除器来删除资源
程序员可以自定义删除器 ,改变智能指针释放资源的行为
删除器可以是全局函数、仿函数和Lambda表达式,形参为原始指针
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <iostream> #include <memory> using namespace std;class AA {public : string m_name; AA (){ cout<<m_name<<"调用构造函数AA().\n" ; } AA (const string &name):m_name (name){ cout<<"调用构造函数AA(" <<m_name<<").\n" ; } ~AA (){ cout<<"调用了析构函数~AA(" <<m_name<<").\n" ; } };void deletefunc (AA* a) { cout<<"自定义删除器(全局函数)。\n" ; delete a; }struct deleteclass { void operator () (AA* a) { cout<<"自定义删除器(仿函数).\n" ; delete a; } };auto deletelamb=[](AA* a){ cout<<"自定义删除器(Lambda).\n" ; delete a; };int main (int argc, char *argv[]) { shared_ptr<AA> p0=make_shared <AA>("小明" ); shared_ptr<AA> p1 (new AA("111" ),deletefunc) ; shared_ptr<AA> p2 (new AA("111" ),deleteclass()) ; shared_ptr<AA> p3 (new AA("111" ),deletelamb) ; unique_ptr<AA,decltype (deletefunc) *> pu1 (new AA("111" ),deletefunc) ; unique_ptr<AA,deleteclass> pu2 (new AA("111" ),deleteclass()) ; unique_ptr<AA,decltype (deletelamb) > pu3 (new AA("111" ),deletelamb) ; return 0 ; }
弱智能指针 weak_ptr 引言 :为了解决shared_ptr循环引用带来的未自动清理的情况,C++还引入了weak_ptr指针来帮助shared_ptr解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream> #include <memory> using namespace std;class BB ;class AA {public : string m_name; AA (){ cout<<m_name<<"调用构造函数AA().\n" ; } AA (const string &name):m_name (name){ cout<<"调用构造函数AA(" <<m_name<<").\n" ; } ~AA (){ cout<<"调用了析构函数~AA(" <<m_name<<").\n" ; } weak_ptr<BB> m_p; };class BB {public : string m_name; BB (){ cout<<m_name<<"调用构造函数BB().\n" ; } BB (const string &name):m_name (name){ cout<<"调用构造函数BB(" <<m_name<<").\n" ; } ~BB (){ cout<<"调用了析构函数~BB(" <<m_name<<").\n" ; } weak_ptr<AA> m_p; };int main (int argc, char *argv[]) { shared_ptr<AA> p0=make_shared <AA>("小明1" ); shared_ptr<BB> p1=make_shared <BB>("小明2" ); p0->m_p=p1; p1->m_p=p0; return 0 ; } || 调用构造函数AA (小明1 ). || 调用构造函数BB (小明2 ). || 调用了析构函数~BB (小明2 ). || 调用了析构函数~AA (小明1 ). || Exited with code 0
概念 :
weak_ptr
是为了配合shared_ptr而引入的 ,它指向一个由shared_ptr管理的资源但不影响资源的生命周期,也就是说,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数
不论是否有weak_ptr指向,如果最后一个指向资源的shared_ptr被销毁,资源就会被释放
weak_ptr更像是shared_ptr的助手而不是智能指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <iostream> #include <memory> using namespace std;class BB ;class AA {public : string m_name; AA (){ cout<<m_name<<"调用构造函数AA().\n" ; } AA (const string &name):m_name (name){ cout<<"调用构造函数AA(" <<m_name<<").\n" ; } ~AA (){ cout<<"调用了析构函数~AA(" <<m_name<<").\n" ; } weak_ptr<BB> m_p; };class BB {public : string m_name; BB (){ cout<<m_name<<"调用构造函数BB().\n" ; } BB (const string &name):m_name (name){ cout<<"调用构造函数BB(" <<m_name<<").\n" ; } ~BB (){ cout<<"调用了析构函数~BB(" <<m_name<<").\n" ; } weak_ptr<AA> m_p; };int main (int argc, char *argv[]) { shared_ptr<AA> p0=make_shared <AA>("小明1" ); shared_ptr<BB> p1=make_shared <BB>("小明2" ); cout<<p0.use_count ()<<endl; cout<<p1.use_count ()<<endl; p0->m_p=p1; p1->m_p=p0; cout<<p0.use_count ()<<endl; cout<<p1.use_count ()<<endl; return 0 ; } || 调用构造函数AA (小明1 ). || 调用构造函数BB (小明2 ). || 1 || 1 || 1 || 1 || 调用了析构函数~BB (小明2 ). || 调用了析构函数~AA (小明1 ). || Exited with code 0
基本用法 :
operator=();
:重载了=操作符,可以将shared_ptr或weak_ptr赋值给weak_ptr
expired()
:判断weak_ptr所指资源是否过期 (是否被销毁)
lock()
:将weak_ptr升级成shared_ptr ,如果指向资源存在 ,将weak_ptr升级成shared_ptr,返回shared_ptr ;如果资源已过期 ,返回空的shared_ptr
reset()
:将当前weak_ptr指针置为空
swap()
:交换两个weak_ptr控制权
注意 :
weak_ptr不控制对象的生命周期,但是它可以知道对象是否还活着
lock()函数是线程安全的,因为它是一个原子操作的 (对于多线程最好使用lock而不是expired)
(多线程)错误做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main (int argc, char *argv[]) { shared_ptr<AA> p0=make_shared <AA>("小明1" ); { shared_ptr<BB> p1=make_shared <BB>("小明2" ); p0->m_p=p1; p1->m_p=p0; if (p0->m_p.expired ()) cout<<"语句块内部:p0->m_p已过期.\n" ; else cout<<"语句块内部:p0->m_p.lock()->m_name" <<p0->m_p.lock ()->m_name<<endl; } if (p0->m_p.expired ()) cout<<"语句块外部:p0->m_p已过期.\n" ; else cout<<"语句块外部:p0->m_p.lock()->m_name" <<p0->m_p.lock ()->m_name<<endl; return 0 ; }
(多线程)正确做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int main (int argc, char *argv[]) { shared_ptr<AA> p0=make_shared <AA>("小明1" ); { shared_ptr<BB> p1=make_shared <BB>("小明2" ); p0->m_p=p1; p1->m_p=p0; shared_ptr<BB> pp=p0->m_p.lock (); if (pp==nullptr ) cout<<"语句块内部:p0->m_p已过期.\n" ; else cout<<"语句块内部:pp->m_name=" <<pp->m_name<<endl; } shared_ptr<BB> pp=p0->m_p.lock (); if (pp==nullptr ) cout<<"语句块外部:p0->m_p已过期.\n" ; else cout<<"语句块外部:pp->m_name=" <<pp->m_name<<endl; return 0 ; }
Lambda函数 概念 :
Lambda表达式
是用于创建和定义匿名函数
匿名函数
:顾名思义也就是可以没有名字的函数 ,可以看成是一个内联函数 ,在python、C#等语言都有这种函数表达式的实现。
通常适用于 创建一些代码量少 的函数以及在当前所处函数中调用次数少 的函数
声明Lambda表达式 :
1 2 3 4 [capture list](param list) mutable exception->return_type{ funtion body };
capture list
:捕获外部变量列表
Lambda捕获列表:
[]
:空捕获列表,lambda不能使用所在函数中的变量
[=]
:函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this指针),并且 使用值传递的方式传递 (编译器自动做的)
[&]
:函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this指针),并且 使用引用传递方式传递 (编译器自动做的)
[this]
:函数体内可以使用Lambda所在类中的所有成员变量
[x]
:将外部变量x按值进行传递,不能修改x值 ,可以添加mutable修饰符允许修改
[&x]
:将外部变量x按引用方式进行传递
[=,&x,&y]
:除外部变量x和y按引用方式传递,其他参数都按值传递方式传递
[&,x,y]
:除外部变量x和y按值进行传递外,其他参数都按引用方式进行传递
param list
:匿名函数形参列表
mutable(可选)
:是一种修饰符,用来声明Lambda函数体能否修改以值传递的捕获变量的值(值传递是会在Lambda体中复制形参值形成新对象,所以在Lambda的操作并不会修改Lambda外部变量的值,以引用形式传递的可以),当需要Lambda函数体能够修改以值传递的捕获变量值,需要在声明时加上该规范 。
exception(可选)
:**异常规范(设定)**,可以使用noexcept或者throw()设定函数不抛出异常
return_type
:返回类型
function body
:函数体
一些格式 :
Lambda表达式是可以省略某些成分来进行声明,常用的有如下几种:
**声明了const类型的表达式(没有加mutable修饰符,默认都是const类型)**,这种类型的表达式不能修改捕获列表中的值
1 [capture list](param list)->return_type {function body};
省略了返回值类型,编译器会根据Lambda体自动推导返回值类型 ,如果有返回值则编译器会自动推导返回值类型,如果没有返回值则默认为void类型
1 [capture list](param list){function body};
省略了参数列表,则这个函数就是无参函数
1 [capture list]{function body};
悬垂引用:
若以引用隐式或显式捕获非引用实体,而在该实体的生存期结束之后调用lambda对象的函数调用运算符,则发生未定义行为 。C++ 的闭包并不延长被捕获的引用的生存期。这同样适用于被捕获的this指针所指向的对象的生存期
简单来说就是在Lambda表达式捕获的实体生命周期结束后再调用Lambda函数则会发生找不到对应实体,导致发生未定义错误
基本使用 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <functional> int main (int argc, char *argv[]) { int c=2 ; auto foo=[&](int a,int b)->void { std::cout<<a+b+c<<std::endl; }; std::function<void (int ,int )>f=[=](int a,int b){ std::cout<<a+b+c<<std::endl; }; f (1 ,2 ); foo (1 ,2 ); return 0 ; }
完美转发 左值引用和右值引用 左值
:非临时,具有持续性,可取地址的对象 (常量、变量或表达式),例如int a=1,a就为左值
右值
:临时的对象,不能取地址 ,例如int a=1,a+1,则a+1其实就是一个临时生成的对象和值或者1就是右值
左值引用
:**对左值的引用就是左值引用(由单个&组成)**,例如int& a,就是一个左值引用
右值引用
:**对右值的引用就是右值引用(由一对&组成)**,例如int a=1,int&& x=a+1,int&& x就是一个右值引用
注意:
前缀自增/自减表达式的返回值为左值,后缀自增/自减表达式的返回值为右值
常量左值引用是一个可以绑定到任何类型的引用 ,左值,右值都可以,例如int& a=1
非常量左值引用不能绑定到右值上
万能引用和引用折叠 万能引用
:不是一种引用类型,实现了传入左值或左值引用推导出左值引用,传入右值或右值引用推导出右值引用,形式与右值引用一样(一对&)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <type_traits> template <typename T>void foo (T&& x) { if (std::is_same_v<T,int &>) printf ("x=%d,是左值引用\n" ,x); else if (std::is_same_v<T,int >) printf ("x=%d,是右值引用\n" ,x); }int main (int argc, char *argv[]) { int a=1 ; foo (a); foo (2 ); return 0 ; } x=1 ,是左值引用 x=2 ,是右值引用
注意
:
只出现在模板类型推导或者auto中
const关键字会剥夺万能引用资格 ,例如void fun(const T&&)
引用折叠
:是C++模板推导的一种规则,当类型出现左值引用折叠到另一个任意类型引用时则折叠成左值引用 ,当出现右值引用折叠到右值引用时则折叠成右值引用
引用1
引用2
引用拆分
引用折叠
左值引用
左值引用
T& &
左值引用 &
左值引用
右值引用
T& &&
左值引用 &
右值引用
左值引用
T&& &
左值引用 &
右值引用
右值引用
T&& &&
右值引用 &&
注意:我们并不能在代码中显示写出”引用的引用”这样的代码来表示引用折叠 ,引用折叠只出现在模板推导中
forward 概念 :
完美转发
:是用于泛型编程中,利用引用折叠,将参数类型以及属性性质原封不懂的转发传入到函数中,例如左值,右值,const或者volatile参数 ,是为了解决传递参数时的临时对象(右值)被强制转换为左值的问题
在C++中可以使用std::forward
来实现完美转发
函数原型 :
**自定义实现(引用折叠)**:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> template <typename T>T&& forward (T&& x) { return static_cast <T&&>(x); }template <typename T>T&& forward (T& x) { return static_cast <T&&>(x); }void foo1 (int & x) { printf ("左值引用:x=%d\n" ,x); }void foo1 (int && x) { printf ("右值引用:x=%d\n" ,x); }template <typename T>void foo (T&& x) { foo1 (forward<T>(x)); }int main (int argc, char *argv[]) { int a=1 ; foo (a); foo (2 ); return 0 ; } 左值引用:x=1 右值引用:x=2
问题抛出 :
问题
:我有以下代码,当我利用万能引用时正确的推导出了x的类型以及属性,但是x的类型以及属性却不能正确的传入到foo1函数,传入右值后,尽管在模板推导正确成为右值引用,但是进入函数内部时,右值引用变为了左值 (因为x作为持续性变量),导致输出结果并不是正确的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> void foo1 (int & x) { printf ("左值引用:x=%d\n" ,x); }void foo1 (int && x) { printf ("右值引用:x=%d\n" ,x); }template <typename T>void foo (T&& x) { foo1 (x); }int main (int argc, char *argv[]) { int a=1 ; foo (a); foo (2 ); return 0 ; } 左值引用:x=1 左值引用:x=2
利用引用折叠解决 :
因为模板推导的万能引用的作用导致最终结果只能为左值引用和进入函数内部转为左值的右值引用,因此我们可以利用引用折叠特性,将x强转成T&&类型,当x为左值则会直接转成右值引用,另一情况就是x为左值引用,根据引用折叠依然为左值引用,来实现完美转发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h> void foo1 (int & x) { printf ("左值引用:x=%d\n" ,x); }void foo1 (int && x) { printf ("右值引用:x=%d\n" ,x); }template <typename T>void foo (T&& x) { foo1 (static_cast <T&&>(x)); }int main (int argc, char *argv[]) { int a=1 ; foo (a); foo (2 ); return 0 ; } 左值引用:x=1 右值引用:x=2
使用std::forward解决 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h> #include <utility> void foo1 (int & x) { printf ("左值引用:x=%d\n" ,x); }void foo1 (int && x) { printf ("右值引用:x=%d\n" ,x); }template <typename T>void foo (T&& x) { foo1 (std::forward<T>(x)); }int main (int argc, char *argv[]) { int a=1 ; foo (a); foo (2 ); return 0 ; } 左值引用:x=1 右值引用:x=2