标题
文章目录
面向对象基础知识
函数指针
函数在内存中也是占用一段内存的所以我们也可以使用指针指向函数,之后只用指针调用函数,这一点在Python中有较好的体现,在GAToolBox
的程序实现中,我曾直接将适应度函数直接传输给GAToolBox
类,之后传输给Individials
类,在个体类中直接调用适应度函数指针即可实现适应度的计算,因为Python中遵循一切皆指针的原则,所以直接以函数名传递即可实现。当然在C++函数名也是函数指针类型的可以直接按函数指针进行传递,在北大视频中以数组排序函数为例进行讲解,其中比较函数则使用的函数指针进行传递。
变参函数
C++允许定义形参个数和类型不确定的函数。例如,C语言中的标准函数printf便使用这种机制。在声明不确定形参的函数时,形参部分可以使用省略号“…”代替。“…”告诉编译器,在函数调用时不检查形参类型是否与实参类型相同,也不检查参数个数。
头文件 (stdarg.h)
类型
va_list : Type to hold information about variable arguments (type )
宏函数
函数 | 功能 |
---|---|
va_start | Initialize a variable argument list (macro) |
va_arg | Retrieve next argument (macro) |
va_end | End using variable argument list (macro) |
va_copy | Copy variable argument list (macro) |
示例 :
#include <stdio.h>
#include <stdarg.h>
void simple_printf(const char* fmt, ...){
va_list args;
va_start(args, fmt);
while (*fmt != '\0') {
if (*fmt == 'd') {
int i = va_arg(args, int);
printf("%d\n", i);
} else if (*fmt == 'c') {
// 将提升 'char' 类型值为 'int'
// C 中字符常量自身为 'int' 类型
int c = va_arg(args, int);
printf("%c\n", c);
} else if (*fmt == 'f') {
double d = va_arg(args, double);
printf("%f\n", d);
}
++fmt;
}
va_end(args);}
命令行参数
int main(int argc,char * argv[])
{
for(int i=0;i<argc;i++)
printf("%s\n",argv[i]);
return 0;
}
其中argc
为命令行参数的个数,argv
为参数的字符串,每个参数以空格为分割符,如果参数中间有空格通过引号括起来就好了,并且argc 和 argv 是包含程序名本身的。
位运算 可以提高运算效率
& //按位与(双目) 使得某些位变为0
| //按位或(双目) 使得某些位变为1
^ //按位异或(双目) 使得某些位取反
~ //按位取反(单目)
<< //左移(双目)
>> //右移(双目)
其中按位异或^
有特性a^b=c
, c^b=a
,c^a=b
可用于简单的加密和解密,例如以a为原文,b为秘钥,c则为密文。当我持有密文和秘钥的时候,便可以通过按位异或获取原文a。同时异或可用于两个整型变量进行交换。例如:
int a = 5,b = 7;
a = a ^ b;
b = b ^ a;
a = a ^ b;
类似于,因为可以通过运算将两个不想关的变量联系起来,类似于耦合解耦的过程:
int a = 5, b = 7;
a = a + b;
b = a - b;
a = a - b;
引用
引用就是别名,同时引用只能引用变量,且从一而终。const T &
和T &
不是同一种类型,可以理解为const &
为一种实现方式的权限设定,通过该引用无法修改引用的内容。则如果原变量为非const
类型则可以修改权限使得引用为只读引用,如果原变量为const
,则如果使用可修改引用,则会使得该方式的权限无法实现,出现编译错误。
const 用法
-
定义常量
-
定义常量指针:通过该指针无法修改指向的内容
const int * p1, int * p2;
p1 = p2; // ok
p2 = p1; // error 等于是提高权限,有不安全因素
p2 = (int *) p1; // 当然权限的修改也不是不可以,前提是常指针指向的
- 定义常引用:通过该引用无法修改引用的内容
动态分配内存 new
- 分配
new T
,new T[]
。 - 释放
delete T
,delete [] T
。
函数重载
同函数的参数个数和类型进行函数调用。在Python中我们也可以通过对参数的类型和个数进行不同功能的实现。
面向过程设计
实际上就是模块化编程,简单来说就是程序 = 数据结构 + 函数
,但是函数和变量是分开的也就是说property和behavior是分割的。
面向对象设计
类的定义
class 类名
{
访问范围说明符:
成员变量1
成员变量2
···
成员函数声明1
成员函数声明2
访问范围说明符:
更多成员变量1
更多成员变量2
···
更多成员函数声明1
更多成员函数声明2
};
复制构造函数
class X{};
X::X(X &);
X::X(const X &);
X::X(X); //该构造函数形式不允许存在
作用:
- 当用一个对象去初始化同类的另一个对象时。
- 如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用。
- 如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用
类型转换构造函数
目的:
- 实现类型的自动转换
- 特点
- 只有一个参数
- 不是复制构造函数
- 编译系统会自动调用
转换构造函数
建立一个临时对象/临时变量
Exsample :
class Complex{
public:
double real, imag;
Complex(int i){
cout << "IntConstructor called" <<endl;
real = i;
imag = 0;
}
Complex(double r,double i) {
real = r;
imag = i;
}
};
int main(){
Complex c1(7,8);
Complex c2=12;
c1=9;//9被自动转换成一个临时Complex对象
cout<< c1.real<<","<<c1.imag <<endl;
return 0;
}
静态成员变量与函数
- 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。
- 普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。
sizeof 不计算静态成员变量, 静态函数不可以访问非静态成员变量。
访问方法:
- 类名::成员名
CRectangle::PrintTotal();
- 对象名.成员名
CRectangle r; r.PrintTotal();
- 指针->成员名
CRectangle*p=&r; p->PrintTotal();
- 引用.成员名
CRectangle&ref=r; int n=ref.nTotalNumber;
友元
一个类的友元函数可以访问该类的私有成员, 但是友元类之间的关系,不能传递,不能继承
实现方法:
- 将一个类的成员函数(包括构造,析构函数)定义为另一个类的友元函数
class CCar;//提前声明CCar类,以便后面CDriver类使用
class CDriver{
pubic:
void ModifyCar(CCar*pCar);//改装汽车
};
class CCar{
private:
int price;
friend int MostExpensiveCar(CCar carsl,int total);//声明友元
friend void CDriver:ModifyCar(CCar*pCar); //声明友元
};
- 将一个类定义为另一个类的友元类
class CCar{
private:
int price;
friend class CDrpver/声明CDriver为友元类
};
class CDriver{
public:
CCar myCar;
void ModifyCar(){//改装汽车
myCar.price +=1000; //CDriver是CCar的友元类可以访问其私有成员
}
};
静态成员变量&函数
注意静态变量&函数可以直接通过类进行调用,同时静态成员变量需要在类外*.cpp文件中进行初始化
头文件中
class chart{
public:
static int chartCount;
static initChartCount(){
chartCount = 0;
}
};
源文件中
int chart::chartCount = 0;
void test(){
chart::initChartCount();
}
成员对象和封闭类
成员对象:一个类的成员变量是另一个类的对象
封闭类(Enclosing): 包含成员对象的类
当封闭类对象生成时
- 执行所有成员对象的构造函数
- 执行封闭类的构造函数
成员对象的构造函数调用顺序
- 和成员对象在类中的说明(定义)顺序一致
- 与在成员初始化列表中出现的顺序无关
当封闭类的对象消亡时
- 先执行封闭类的析构函数
- 执行成员对象的析构函数
构造函数和析构函数的调用顺序相反
运算符重载
- 对已有的运算符赋予多重的含义
- 使同一运算符作用于不同类型的数据时 --> 不同类型的行为
目的 : 扩展C++中提供的运算符的适用范围,以用于类所表示的抽象数据类型
在程序编译时:
- 把含运算符的表达式 --> 对
运算符函数
的调用 - 把运算符的操作数 --> 运算符函数的
参数
- 运算符被多次重载时,根据
实参的类型
决定调用哪个运算符函数 - 运算符可以被重载成
普通函数
- 也可以被重载成
类的成员函数
重载为普通函数时,参数个数为运算符目数
Complex operator+(const Complex & a,const Complex & b){
return Complex(a.real+b.real,a.imaginary+b.imaginary);
}//“类名(参数表)”就代表一个对象
重载为成员函数时,参数个数为运算符目数减一
Complex operator+(const Complex&);// addition
赋值运算符重载
赋值运算符两边的类型可以不匹配
赋值运算符"="只能重载为成员函数
浅拷贝和深拷贝
浅拷贝:将指针指向所需拷贝的内容所在的内存空间
深拷贝:将所需拷贝的内容复制到指定内存空间,并将指针指向该内存
深拷贝程序实现:
String & String::operator = (const String & s) {
if(str)==(s.str) return *this;
if(str) delete [] str;
if(s.str){ //s.str不为NULL才会执行拷贝
str = new char[strlen(s.str) + 1]; //这里的+1 是为存储'\0'
strcpy(str,s.str);
}
else str = NULL;
return *this;
注意:
1. 不要设置返回值为 void
2. 复制构造函数面临’='中的浅拷贝问题
字符串拷贝 strcpy(new,old)
内存拷贝 memcpy(new,old,sizeof(type)*size)
流运算符重载
istream & operator >> (istream&is,Complex&c){
string s;
is >>s; //将"a+bi"作为字符串读入,"a+bi"中间不能有空格
int pos=s.find("+",0);
string sTmp=s.substr(0,pos); //分离出代表实部的字符串
c.real=atof(sTmp.c str());
//atof库函数能将const char*指针指向的内容转
sTmp=s.substr(pos+1,s.length()-pos-2); //分离出代表虚部的字符串
c.imag=atof(sTmp.c_str());
return is;
}
ostream&operator<<(ostream&os,const Complex cc){
os<<c.real <<"+"<<c.imag<<"i"; //以"a+bi"的形式输出
return os;
}
自加自减运算符重载
前置运算符作为一元运算符重载
- 重载为成员函数:
T operator++();
T operator–(); - 重载为全局函数:
T operator++(T);
T operator–(T);
后置运算符作为二元运算符重载,多写一个参数,具体无意义,只为区别于前置运算符
- 重载为成员函数:
T operator ++ (int);
T operator – (int); - 重载为全局函数:
T operator ++ (T,int);
T operator – (T,int);
重载类型强制转换运算符
operator int() { return n; }
int作为一个类型强制转换运算符被重载,
Demo s;
int(s); //等效于s.int();
类型强制转换运算符重载时,
- 不能写返回值类型
- 实际上其返回值类型-类型强制转换运算符代表的类型
运算符重载注意事项
- C++不允许定义新的运算符
- 重载后运算符的含义应该符合日常习惯
- complex_a+complex_b
- word_a>word_b
- date_b=date_a+n
- 运算符重载不改变运算符的优先级
- 以下运算符不能被重载:".",".*","::","?:",sizeof
- 重载运算符(),[],->或者赋值运算符=时,重载函数必须声明为类的成员函数
派生类
class 派生类 : public 基类{}
构造函数以及析构函数调用顺序
创建派生类的对象时,执行派生类的构造函数之前:
- 调用基类的构造函数, 初始化派生类对象中从基类继承的成员
- 调用成员对象类的构造函数
初始化派生类对象中成员对象执行完派生类的析构函数后:
- 调用成员对象类的析构函数
- 调用基类的析构函数
析构函数的调用顺序与构造函数的调用顺序相反
public继承的赋值兼容规则
class base{};
class derived:public base{};
base b;
derived d;
- 派生类的对象可以赋值给基类对象
b=d
- 派生类对象可以初始化基类引用
base&br=d
- 派生类对象的地址可以赋值给基类指针
base*pb=&d
访问范围说明符
基类的private成员:可以被下列函数访问
- 基类的成员函数
- 基类的友员函数
基类的public成员:可以被下列函数访问
- 基类的成员函数
- 基类的友员函数
- 派生类的成员函数
- 派生类的友员函数
- 其他的函数
基类的protected成员:可以被下列函数访问
- 基类的成员函数
- 基类的友员函数
- 派生类的成员函数可以访问当前对象的基类的保护成员
注意:
- 如果在派生类中有基类一样的变量或函数,则使用域运算符进行区分
继承关系和复合关系
继承:“是”关系。
class CCircle : public CPoint {
double r;
}
- 基类A,B是基类A的派生类。
- 逻辑上要求:“一个B对象也是一个A对象”。
复合:“有”关系。
class CCircle {
double r;
CPoint center;
}
- 类C中“有”成员变量k,k是类D的对象,则C和D是复合关系
- 一般逻辑上要求:“D对象是C对象的固有属性或组成部分”。
复合关系的使用
正确写法:
- 为“狗”类设一个“业主”类的对象指针;
- 为“业主”类设一个“狗”类的对象指针数组。
class CMaster;//CMaster必须提前声明,不能先写CMaster类后写Cdog类
class CDog{
CMaster * pm;
};
class CMaster{
std::vector<CDog *> dogs
}
泛型程序设计(Generic Programming)
- 算法实现时不指定具体要操作的数据的类型
- 泛型 --> 算法实现一遍 --> 适用于多种数据结构
- 优势 : 减少重复代码的编写大量编写模板,使用模板的程序设计
函数模板
实现方式 :
template <class 类型参数1, class 类型参数2... ...>
返回值类型 模板名(形参表){
函数体
}
例子 :
template<typename T>
void selectionSort(T arr[], int n){
for(int i = 0 ; i < n ; i ++){
int minIndex = i;
for( int j = i + 1 ; j < n ; j ++ )
if( arr[j] < arr[minIndex] )
minIndex = j;
swap( arr[i] , arr[minIndex] );
}
}
template<class T1,class T2>
T2 print(T1 arg1,T2 arg2){
cout<< arg1<<""<< arg2<<endl;
return arg2;
}
C++编译器遵循以下优先顺序:
Step 1:先找参数完全匹配的普通函数(非由模板实例化而得的函数)
Step 2:再找参数完全匹配的模板函数
Step 3:再找实参经过自动类型转换后能够匹配的普通函数
Step 4:上面的都找不到,则报错
类模板
- 在定义类的时候给它一个/多个参数
- 这个/些参数表示不同的数据类型
在调用类模板时,指定参数,由编译系统根据参数提供的数据类型自动产生相应的模板类;
类模板定义
template <类型参数表>
class 类模板名{
成员函数和成员变量
}
template <型参数表>
返回值类型 类模板名<类型参数名列表> :: 成员函数名(参数表)
{
... ...
}
用类模板定义对象的写法如下:
类模板名<真实类型参数表>对象名(构造函数实际参数表);
如果类模板有无参构造函数,那么也可以只写:
类模板名<真实类型参数表>对象名;
类模板的参数声明中可以包括非类型参数
template <class T,int elementsNumber>
非类型参数:用来说明类模板中的属性
类型参数:用来说明类模板中的属性类型,成员操作的参数类型和返回值类型
template <class T,int size>
class CArray{
T array[size];
public:
void Print(){
for(int i=0;i<size;++i)
cout << array[i]<<endl;
}
};
类模板与继承
模板类 :即类模板中类型/非类型参数实例化后的类
- 类模板派生出类模板
template <class T1,class T2>
class A{
T1 V1;
T2 v2;
};
template <class T1,class T2>
class B : public A<T2,T1>{
T1 y3;
T2 y4;
};
template <class T>
class C : public B<T,T>{
T v5;
}
- 模板类派生出类模板
template <class T>
class B : public A<int,double>{ T v;};
- 普通类派生出类模板
classA{ intv1;}; template <class T>
class B:publicA{Tv;};
- 模板类派生出普通类
template <class T>
class A{T v1; int n;};
class B : public A<int>{ double v;};
虚函数
- 在类的定义中,前面有
virtual
关键字的成员函数就是虚函数。
class base {
virtual void func(){ }
};
int base::func() {}
- virtual 关键字只用在类定义的函数声明中,写函数体时不用
- 构造函数和静态成员函数不能是许函数
纯虚函数
抽象类:包含纯虚函数的类
- 只能作为基类来派生新类使用
- 不能创建抽象类的对象
- 抽象类的指针和引用 -> 抽象类派生出的类的对象
抽象类中
- 在成员函数中可以调用纯虚函数
- 在构造函数和析构函数中不可以调用纯虚函数
虚析构函数
将基类的析构函数声明为virtual后,可用于实现通过基类指针删除派生类对象时,首先调用派生类析构函数,然后调用基类的析构函数。如果
多态
表现形式一
- 派生类的指针可以赋值给基类指针
- 通过基类指针调用的基类和派生类中的同名虚函数时:
- 若该指针指向一个基类的对象,那么被调用的是基类的虚函数
- 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数
表现形式二
- 派生类的指针可以赋值给基类引用
- 通过基类引用调用的基类和派生类中的同名虚函数时:
- 若该引用引用的是一个基类的对象,那么被调用的是基类的虚函数
- 若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数
用基类指针数组存放指向各种派生类对象的指针,然后遍历该数组,就能对各个派生类对象做各种操作,是很常用的操作。
在非构造函数以及非析构函数的成员函数中调用虚函数就是多态。
多态的原理
多态的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的基类还是派生类的函数运行才确定这叫动态联编
虚函数表
每一个有虚函数的类(或有虚函数类的派生类)都有一个虚函数表,该类的任何对象都放着虚函数表的指针,虚函数表中列出该类的虚函数地址。该虚函数表的地址栈4个字节。
文件操作
顺序文件 : 一个有限字符构成的顺序字符流
C++标准库中:ifstream,ofstream和fstream共3个类用于文件操作一统称为文件流类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GUbwQJyX-1581386648372)(en-resource://database/2898:1)]
使用/创建文件的基本流程:
- 打开文件
- 通过指定文件名,建立文件和文件流对象的关联;
- 指明文件的使用方式
- 读写文件 :利用读/写指钍进行相应位置的操作
- 关闭文件
建立顺序文件
程序实现 :
#include <fstream>//包含头文件
ofstream outFile("clients.dat",ios:out|ios::binary); //打开文件
或
ofstream(fout; fout. open("test. out", ios:: outlios: binary);
打开并建立文件的选项
- ios::out 输出到文件,删除原有内容
- ios:app 输出到文件,保留原有内容,总是在尾部添加
- ios:binary 以二进制文件格式打开文件
文件读写指针
程序实现 :
ofstream fout("a1.out",ios::app);
long location=fout.tellp();//取得写指针的位置
location=10L;fout.seekp(location);//将写指针移动到第10个字节处
fout.seekp(location,ios:beg);//从头数location
fout.seekp(location,ios:cur);//从当前位置数location
fout.seekp(location,ios::end);//从尾部数location
输入输出
输入流对象:
- cin与标准输入设备相连
输出流对象:
- cout与标准输出设备相连 可以重定向(到文件)
- cerr与标准错误输出设备相连 不可以重定向 无缓冲区直接输出
- clog与标准错误输出设备相连 不可以重定向 有缓冲区
输入输出重定向
freopen("test.txt","w",stdout);//将标准输出重定向到test.txt文件
freopen("t.txt","r",stdin);//cin被改为从t.txt中读取数据
istream类的成员函数
bool eof();//判断输入流是否结束
int peek();//返由下一个字符,但不从流中去掉
istream & putback(charc);//将字符ch放回输入流
istream & ignore(int nCount=1,int delim=EOF);//从流中删掉最多nCount个字符,遇到EOF时结束.
标准模板库
泛型程序设计(generic programming)的思想 : 模板机制,以及标准模板库STL 。
标准模板库 (STL,Standard Template Libaray)
一些常用数据结构和算法的模板的集合。将一些常用的数据结构(比如链表,数组,二叉树)和算法(比如排序,查找)写成模板,以后则不论数据结构里放的是什么对象,算法针对什么样的对象,则都不必重新实现数据结构,重新编写算法。
容器概述
可以用于存放各种类型的数据(基本类型的变量,对象等)的数据结构,都是类模版,分为三种:
- 顺序容器
vector,deque,list
- 关联容器(排序的,查找速度更快)
set,multiset,map,multimap
- 容器适配器
stack,queue,priority_queue
对象被插入容器中时,被插入的是对象的一个复制品。许多算法,比如排序,查找,要求对容器中的元素进行比较,有的容器本身就是排序的,所以,放入容器的对象所属的类,往往还应该重载 ==
和 <
运算符。
顺序容器
容器并非排序的,元素的插入位置同元素的值无关。有 vector,deque,list
三种。
vector 头文件
动态数组。元素在内存连续存放。随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能(大部分情况下是常数时间)。
— | A0 | A1 | A2 | — | — | — | — | — | — | — | An | — |
---|---|---|---|---|---|---|---|---|---|---|---|---|
begin | end |
deque 头文件
双向队列。元素在内存连续存放。随机存取任何元素都能在常数时间完成(但次于vector)。在两端增删元素具有较佳的性能(大部分情况下是常数时间)。
— | — | A0 | A1 | A2 | A3 | A4 | A5 | — | — | — | — | — | — | — |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
begin | end |
list 头文件
双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ECWsq25Z-1581386648373)(en-resource://database/3584:1)]
关联容器
- 元素是排序的
- 插入任何元素,都按相应的排序规则来确定其位置
- 在查找时具有非常好的性能
- 通常以平衡二叉树方式实现,插入和检索的时间都是O(log(N))
set/multiset 头文件
set 即集合。set中不允许相同元素,multiset中允许存在相同的元素。
map/multimap 头文件
容器适配器
stack:头文件
栈。是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项(栈顶的项)。后进先出
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n3t3SQkP-1581386648374)(en-resource://database/3592:1)]
queue头文件
队列。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。先进先出
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HvapTrs0-1581386648374)(en-resource://database/3590:1)]
priority_queue 头文件
优先级队列。最高优先级元素总是第一个出列
顺序容器和关联容器中都有的成员函数
- begin 返回指向容器中第一个元素的迭代器
- end 返回指向容器中最后一个元素后面的位置的迭代器
- rbegin 返回指向容器中最后一个元素的迭代器
- rend 返回指向容器中第一个元素前面的位置的迭代器
- erase 从容器中删除一个或几个元素
- clear 从容器中删除所有元素
顺序容器常用函数
- front:返回容器中第一个元素的引用
- back:返回容器中最后一个元素的引用
- push_back:在容器末尾增加新元素
- pop_back:删除容器末尾的元素。
- erase:删除迭代器指向的元素(可能会使该迭代器失效),或删除一个区间,返回被删除元素后面的那个元素的迭代器
迭代器
定义一个容器类的迭代器的方法可以是:
容器类名::iterator 变量名;
容器类名::const_iterator 变量名;
容器类名::reverse_iterator 变量名;
容器类名::const_reverse_iterator 变量名;
访问一个迭代器指向的元素:
*选代器变量名。
- 用于指向顺序容器和关联容器中的元素
- 迭代器用法和指针类似
- 有const和非const两种
- 通过迭代器可以读取它指向的元素
- 通过非const选代器还能修改其指向的元素
迭代器上可以执行++操作,以使其指向容器中的下一个元素。如果迭代器到达了容器中的最后一个元素的后面,此时再使用它,就会出错,类似于使用NULL或未初始化的指针一样。
双向迭代器
- 若p和p1都是双向迭代器,则可对p、p1可进行以下操作:
- ++p,p++使p指向容器中下一个元素
- –p,p-使p指向容器中上一个元素
- *p取p指向的元素
- p=p1赋值
- p==p1,p!=p1判断是否相等、不等
随机访问迭代器
- 双向迭代器的所有操作
- p+=i 将p向后移动i个元素
- p-=i 将p向向前移动i个元素
- p+i 值为:指向p后面的第i个元素的迭代器
- p-i 值为:指向p前面的第i个元素的迭代器
- p[i] 值为:p后面的第i个元素的引用
- p<p1,p<=p1,p>p1,p>=p1
容器容器上的迭代器类别
- vector 随机访问
- deque 随机访问
- list 双向
- set/multiset 双向
- map/multimap 双向
- stack 不支持选代器
- queue 不支持迭代器
- nriority aueue不支持迭代器
有的算法,例如sort,binary_search需要通过随机访问迭代器来访问容器中的元素,那么1ist以及关联容器就不支持该算法!
算法
STL中的算法大致可以分为以下七类:
- 不变序列算法
- 变值算法
- 删除算法
- 变序算法
- 排序算法
- 有序区间算法
- 数值算法
特性 :
- 算法就是一个个函数模板,大多数在中定义
- STL中提供能在各种容器中通用的算法,比如查找,排序等
- 算法通过迭代器来操纵容器中的元素。许多算法可以对容器中的一个局部区间进行操作,因此需要两个参数,一个是起始元素的选代器,一个是终止元素的后面一个元素的迭代器。比如,排序和查找
- 有的算法返回一个迭代器。比如find()算法,在容器中查找一个元素,并返回一个指向该元素的迭代器
- 算法可以处理容器,也可以处理普通数组
不变序列算法
- 该类算法不会修改算法所作用的容器或对象
- 适用于顺序容器和关联容器
- 时间复杂度都是O(n)
算法 | 作用 |
---|---|
min | 求两个对象中较小的(可自定义比较器) |
max | 求两个对象中较大的(可自定义比较器) |
min_element | 求区间中的最小值(可自定义比较器) |
max_element | 求区间中的最大值(可自定义比较器) |
for_each | 对区间中的每个元素都做某种操作 |
count | 计算区间中等于某值的元素个数 |
count_if | 计算区间中符合某种条件的元素个数 |
find | 在区间中查找等于某值的元素 |
find_if | 在区间中查找符合某条件的元素 |
find_end | 在区间中查找另一个区间最后一次出现的位置(可自定义比较器) |
find_first_of | 在区间中查找第一个出现在另一个区间中的元素(可自定义比较器) |
adjacent_find | 在区间中寻找第一次出现连续两个相等元素 的位置(可自定义比较器) |
search | 在区间中查找另一个区间第一次出现的位置(可自定义比较器) |
search_n | 在区间中查找第一次出现等于某值的连续n个元素(可自定义比较器) |
equal | 判断两区间是否相等(可自定义比较器) |
mismatch | 逐个比较两个区间的元素,返回第一次发生不相等的两个元素的位置(可自定义比较器) |
lexicographical_compare | 按字典序比较两个区间的大小(可自定义比较器) |
算法示例 : find()
template<class Inlt,class T>
Inlt find(Inlt first,Inlt last,const T& val);
- first和last 这两个参数都是容器的迭代器,它们给出了容器中的查找区间起点和终点[first,last)。区间的起点是位于查找范围之中的,而终点不是。find在[first,last)查找等于val的元素
- 用==运算符判断相等
- 函数返回值是一个迭代器。如果找到,则该选代器指向被找到的元素。
- 如果找不到,则该迭代器等于last
Example:
p=find(v.begin),v.end(),3);
if(!p=v.end())
cout<<*p<<endl;
STL中"大""小"的概念
- 关联容器内部的元素是从小到大排序的
- 有些算法要求其操作的区间是从小到大排序的,称为"有序区间算法"
例:binary_search 二分法/折半查找 - 有些算法会对区间进行从小到大排序,称为"排序算法"
例:sort - 还有一些其他算法会用到"大","小"的概念使用STL时,在缺省的情况下,以下三个说法等价:
- x 比 y 小
- 表达式"x<y"为真
- y 比 x 大
变值算法
- 此类算法会修改源区间或目标区间元素的值
- 值被修改的那个区间,不可以是属于关联容器的
算法 | 作用 |
---|---|
for_each | 对区间中的每个元素都做某种操作 |
copy | 复制一个区间到别处 |
copy_backward | 复制一个区间到别处,但目标区前是从后往前被修改的 |
transform | 将一个区间的元素变形后拷贝到另一个区间 |
swap_ranges | 交换两个区间内容 |
fill | 用某个值填充区间 |
fill_n | 用某个值替换区间中的n个元素 |
generate | 用某个操作的结果填充区间 |
generate_n | 用某个操作的结果替换区间中的n个元素 |
replace | 将区间中的某个值替换为另一个值 |
replace_if | 将区间中符合某种条件的值替换成另一个值 |
删除算法
- 删除一个容器里的某些元素
- 删除–不会使容器里的元素减少
- 将所有应该被删除的元素看做空位子
- 用留下的元素从后往前移,依次去填空位子
- 元素往前移后,它原来的位置也就算是空位子
- 也应由后面的留下的元素来填上
- 最后,没有被填上的空位子,维持其原来的值不变删除算法不应作用于关联容器
- 删除算法不应作用于关联容器
- 算法复杂度都是O(n)的
算法 | 作用 |
---|---|
remove | 删除区间中等于某个值的元素 |
remove_if | 删除区间中满足某种条件的元素 |
remove_copy | 拷贝区间到另一个区间,等于某个值的元素不拷贝 |
remove_copy_if | 拷贝区间到另一个区间,符合某种条件的元素不拷贝 |
unique | 删除区间中连续相等的元素,只留下一个(可自定义比较器) |
unique_copy | 拷贝区间到另一个区间.连续相等的元素,只拷贝第一个到目标区间(可自定义比较器) |
变序算法
- 变序算法改变容器中元素的顺序
- 但是不改变元素的值
- 变序算法不适用于关联容器
- 算法复杂度都是O(n)的
算法 | 作用 |
---|---|
reverse | 颠倒区间的前后次序 |
reverse_Copy | 把一个区间颠倒后的结果拷贝到另一个区间,源区间不变 |
rotate | 将区间进行循环左移 |
rotate_copy | 将区间以首尾相接的形式进行旋转后的结果拷贝到另一个区间,源区间不变 |
next_permutation | 将区间改为下一个排列(可自定义比较器) |
prev_permutation | 将区间改为上一个排列(可自定义比较器) |
random_shuffle | 随机打乱区间内元素的顺序 |
partition | 把区间内满足某个条件的元素移到前面,不满足该条件的移到后面 |
排序算法
- 比前面的变序算法复杂度更高,一般是O(nlog(n))
- 排序算法需要随机访问迭代器的支持不适用于关联容器和list
算法 | 作用 |
---|---|
sort | 将区间从小到大排序(可自定义比较器)将区间从小到大排序 |
stable_sort | 并保持相等元素间的相对次序(可自定义比较器) |
partial_sort | 对区间部分排序,直到最小的n个元素就位(可自定义比较器) |
partial_sort_copy | 将区间前n个元素的排序结果拷贝到别处,源区间不变(可自定义比较器) |
nth_element | 对区间部分排序,使得第n小的元素(n从0开始算)就位,而且比它小的都在它前面,比它大的都在它后面(可自定义比较器) |
排序算法 复杂度分析
sort
- 实际上是快速排序,时间复杂度O(n*log(n))
- 平均性能最优
- 但是最坏的情况下,性能可能非常差,如果要保证"最坏情况下"的性能,那么可以使用stable_sort
stable_sort
- 实际上是归并排序,特点是能保持相等元素之间的先后次序
- 在有足够存储空间的情况下,复杂度为nlog(n),否则复杂度为nlog(n)*log(n)
- stable_sort用法和sort相同.
- 排序算法要求随机存取迭代器的支持,所以ist不能使用
有序区间算法
- 要求所操作的区间是已经从小到大排好序的
- 需要随机访问迭代器的支持
- 有序区间算法不能用于关联容器和list
算法 | 作用 |
---|---|
binary_search | 判断区间中是否包含某个元素 O(log(n)) |
includes | 判断是否一个区间中的每个元素,都在另一个区间中 |
lower_bound | 查找最后一个不小于某值的元素的位置 |
upper_bound | 查找第一个大于某值的元素的位置 |
equal_range | 同时获取lower_bound和upper_bound |
merge | 合并两个有序区间到第三个区间人 |
set_union | 将两个有序区间的并拷贝到第三个区间 |
set_intersection | 将两个有序区间的交拷贝到第三个区间 |
set difference | 将两个有序区间的差拷贝到第三个区间 |
set_symmetric_difference | 将两个有序区间的对称差拷贝到第三个区间 |
inplace_merge | 将两个连续的有序区间原地合并为一个有序区间 |
容器详解
顺序容器
Vector
- 可变长的动态数组
- 必须包含头文件 #include
- 支持随机访问迭代器
- 根据下标随机访问某个元素时间为常数
- 在尾部添加速度很快
- 在中间插入慢
成员函数:
成员函数 | 作用 |
---|---|
vector(); | 无参构造函数,将容器初始化成空的 |
vector(int n); | 将容器初始化成有n个元素 |
vector(int n,const T&val); | 假定元素类型是T,将容器初始化成有n个元素,每个元素的值都是Val |
vector(iterator first,iterator last); | 将容器初始化为与别的容器上区间[first,last)一致的内容 |
void pop_back(); | 删除容器末尾的元素 |
void push_back(const T&val); | 将val添加到容器末尾 |
int size(); | 返回容器中元素的个数 |
T&font(); | 返回容器中第一个元素的引用 |
T&back(); | 返回容器中最后一个元素的引用 |
List
- 在任何位置插入/删除都是常数时间
- 不支持根据下标随机存取元素
- 具有所有顺序容器都有的成员函数
成员函数:
成员函数 | 作用 |
---|---|
push_front | 在链表最前面插入 |
pop_front | 删除链表最前面的元素 |
sort | 排序(list不支持STL的算法sort) |
remove | 删除和指定值相等的所有元素 |
unique | 删除所有和前一个元素相同的元素 |
merge | 合并两个链表,并清空被合并的链表 |
reverse | 颠倒链表 |
splice | 在指定位置前面插入另一链表中的一个或多个元素,并在另一链表中删除被插入的元素 |
Deque
双向队列
必须包含头文件#include
成员函数:
- 所有适用于vector的操作>都适用于deque
- deque还有 push_front(将元素插入到容器的头部)和pop_front(删除头部的元素)操作
关联容器
set,multiset,map,multimap
- 内部元素有序排列,新元素插入的位置取决于它的值,查找速度快。
- 除了各容器都有的函数外,还支持以下成员函数:
- find:查找等于某个值的元素(x小于y和y小于x同时不成立即为相等)
- lower bound:查找某个下界
- upper_bound:查找某个上界
- equal_range:同时查找上界和下界
- count:计算等于某个值的元素个数(x小于y和y小于x同时不成立即为相等)
- insert:用以插入一个元素或一个区间
map/multimap容器里放着的都是pair模版类的对象,且按first从小到大排序
template<class _T1, class _T2>
struct pair{
typedef_T1 first type;
typedef_T2 second_type;
T1 first;
T2 second;
pair(): first(), second(){}
pair(const _T1&a, const_T2&b) : first(_a), second(b){}
template<class _U1, class _U2>
pair(const pair<_U1,_U2>&p) : first(p. first), second(p. second){}
Multiset
实现 :
template<class Key,class Pred=less<Key>,class A=allocator<Key>>
class multiset{.....};
-
Pred类型的变量决定了multiset中的元素,“一个比另一个小”是怎么定义的。
multiset运行过程中,比较两个元素x,y的大小的做法,就是生成一个Pred类型的变量,假定为op,若表达式op(x,y)返回值为true,则x比y小。
Pred的缺省类型是less。 -
其中less模板定义:
template<class T>
struct less:public binary_function<T,T,bool>
{bool operator()(const T& x,const T& y){returnx<y;}const;};
成员函数 :
成员函数 | 作用 |
---|---|
iterator find(const T&val); | 在容器中查找值为val的元素,返回其迭代器.如果找不到,返回end(). |
iterator insert(const T&val); | 将val插入到容器中并返回其迭代器. |
void insert(iterator first,iterator last); | 将区间[first,last)插入容器. |
int count(const T&val); | 统计有多少个元素的值和val相等. |
iterator lower bound(const T&val); | 查找一个最大的位置it,使得[begin(),it),中所有的元素都比val小. |
iterator upper bound(const T&val); | 查找一个最小的位置t,使得[t,end())中所有的元素都比val大. |
pair<iterator,iterator>equal_range(const T&val); | 同时求得lower_bound和upper_bound. |
iterator erase(iterator it); | 删除it指向的元素,返回其后面的元素的迭代器(Visual studio2010上如此,但是在C++标准和Dev C++中,返回值不是这样). |
Set
实现 :
template<class Key,class Pred=less<Key>,class A=allocator<Key>>
class set{...}
插入set中已有的元素时,忽略插入,重复的定义 : (a<b)||(b<a)=0
。
插入函数的不同
template<typename T>
pair<std::set<T>::iterator,bool> insert(const T&val); 将val插入到容器中并返回其迭代器。
Multimap
实现 :
template<class Key,class T,class Pred=less<Key>,classA=allocator<T>>
class multimap{
..
typedef pair<const Key,T> value_type;
...…
};//Key代表关键字的类型
- multimap中的元素由〈关键字,值〉组成,每个元素是一个pair对象,关键字就是first成员变量,其类型是Key
- multimap中允许多个元素的关键字相同.元素按照first成员变量从小到大排列,缺省情况下用less定义关键字的"小于"关系.
示例:
# include <iostream>
# include<map>
using namespace std;
int main(){
typedef multimap<int, double, less<int>>mmid;
mmid pairs;
pairs.insert(mmid: value_type(15,2.7);
cout<<"含有15元素个数:"<<pairs.count(15)<< endl;
Map
实现 :
template<class Key,class T,class Pred=less<Key>, class A=allocator<T>>
class map{
... ...
typedef pair<const Key,T> value_type;
... ...
};
- map中的元素都是pair模板类对象.关键字(first成员变量)各不相同.元素按照关键字从小到大排列,缺省情况下用less, 即"<“定义"小于”。
若pairs为map模版类的对象
- pairs[key]
返回对关键字等于key的元素的值(second成员变量)的引用。若没有关键字为key的元素,则会往pairs里插入一个关键字为key的元素,其值用无参构造函数初始化,并返回其值的引用。
如:
map<int,double>pairs;pairs[50]=5;会修改pairs中关键字为50的元素,使其值变成5。
若不存在关键字等于50的元素,则插入此元素,并使其值变为5。
实用类模板bitset
实现
bitset template<size_tN>
class bitset
{
......
};
特性
- 实际使用的时候,N是个整型常数如:
bitset<40>bst; - bst是一个由40位组成的对象
- 用bitset的函数可以方便地访问任何一位
成员函数
bitset&operator&=(const bitset&rhs);
bitset&operatorl=(const bitset&rhs);
bitset&operatorA=(const bitset&rhs);
bitset&operator<<=(size_tnum);
bitset&operator>>=(size_tnum);
bitset&set();/全部设成1
bitset&set(size_t pos,bool val=true);//设置某位
bitset&reset();//全部设成0
bitset&reset(size_t pos);/某位设成0
bitset&flip();//全部翻转
bitset&flip(size_t pos);//翻转某位
reference operator[](size_t pos);//返回对某位的引用。
bool operator[](size_t pos)const;//判断某位是否为1
reference at(size_t pos);
bool at(size_t pos)const;
unsigned long to_ulong()const;//转换成整数
string to_string()const;//转换成字符串
size_t count()const;//计算1的个数
size_t size()const;
bool operator==(const bitset&rhs)const;
bool operator!=(const bitset&rhs)const;
容器适配器详解
可以用某种顺序容器来实现
(让已有的顺序容器以栈/队列的方式工作)
- stack:头文件
栈–后进先出 - queue:头文件
队列–先进先出 - priority_queue:头文件
优先级队列–最高优先级元素总是第一个出列R
都有3个成员函数:
- push:添加一个元素;
- top:返回栈顶部或队头元素的引用
- pop:删除一个元素容器适配器上没有迭代器
STL中各种排序,查找,变序等算法都不适合容器适配器
Stack
实现 :
template<class T,class Cont=deque<T>>
class stack{
...
};
- stack 是后进先出的数据结构
- 只能插入,删除,访问栈顶的元素
- 可用 vector,,list,deque来实现
- 缺省情况下,用deque实现
- 用vector和deque实现,比用list实现性能好
- push,pop,top函数均在栈顶
Queue
实现 :
template<class T,class Cont=deque<T>>
class queue{
};
- 和stack 基本类似,可以用list和deque实现
- 缺省情况下用deque实现
- push在队尾,pop,top函数均在队头
Priority_queue
- 和queue类似,可以用vector和deque实现
- 缺省情况下用vector实现
- priority_queue 通常用堆排序技术实现,保证最大的元素总是在最前面
- 执行pop操作时,删除的是最大的元素
- 执行top操作时,返回的是最大元素的引用
- 默认的元素比较器是less
函数对象
头文件:
若一个类重载了运算符"()",则该类的对象就成为函数对象。
STL中的函数对象类模板以下模板可以用来生成函数对象。
- equal_to
- greater
- less
- … …
函数对象应用
list有两个sort成员函数
-
void sort();
将list中的元素按"<"规定的比较方法升序排列 -
template
void sort(Compare op);
将list中的元素按op规定的比较方法升序排列。即要比较x,y大小时,看op(x,y)的返回值,为true则认为x小于y
template <typename T,int size=3>
class Power {
public:
T operator()(T value) {
T count = T(1);
for (int i = 0; i < size; i++)
{
count *= value;
}
return count;
}
};
template <typename T, int size = 3>
T power(T value) {
T count = T(1);
for (int i = 0; i < size; i++)
{
count *= value;
}
return count;
}
template <typename T=int, int size = 3>
T sumPower(std::vector<T> list) {
T count = T(0);
for (auto temp : list)
{
count += power<T,size>(temp);
}
return count;
}
template <typename T,typename Fun>
T accumulate(std::vector<T> list) {
T count = T(0);
Fun fun;
for (auto temp : list)
{
count += fun(temp);
}
return count;
}
C++11新特性
auto关键字
template <class T1,class T2>
auto add(T1x,T2 y)->decltype(x+y){
return x+y;
}
Lambda 表达式
只使用一次的函数对象,能否不要专门为其编写一个类?
只调用一次的简单函数,能否在调用时才写出其函数体?
形式 :
[外部变量访问方式说明符](参数表)->返回值类型语句组
{
语句组
}
外部变量访问方式说明符形式 :
- [=] 以传值的形式使用所有外部变量
- [] 不使用任何外部变量
- [&] 以引用形式使用所有外部变量
- [x,&y] x以传值形式使用,y以引用形式使用
- [=,&x,&y] x,y以引用形式使用,其余变量以传值形式使用
- [&,x,y] x,y以传值的形式使用,其余变量以引用形式使用
//可以在函数里定义函数
auto ff=[=,&y,&z](int n){
cout<<x<<endl; y++;z++;
return n*n;
};
cout <<ff(15)<<endl;
//作为函数指针进行传递
int a[4]={4,2,11,33};
sort(a,a+4,[](int x,int y)->bool {return ×%10<y%10;});
for_each(a,a+4,[](int x){cout<<x<<"";});
for_each(a.begin(),a. end(),[&](int &x){ total+=x;x*=2;});
//实现递归求斐波那契数列第n项:
function<int(int)>fib=[&fib](int n)
{return n<=2?1:fib(n-1)+fib(n-2);};
//function<int(int)>表示返回值为int,有一个int参数的函数
异常处理
try{
.......
throw typename(variable) //抛出任意类型(自定义类)变量
}
catch(typename variable){
}
catch(...){ //...表示任何异常
}
如果一个函数在执行的过程中,抛出的异常在本函数内就被catch块捕获并处理了,那么该异常就不会抛给这个函数的调用者(也称“上一层的函数”);如果异常在本函数中没被处理,就会被抛给上一层的函数。
C++标准异常类
C++标准库中有一些类代表异常,这些类都是从exception类派生而来
需要头文件
- bad_cast 在用dynamic_cast进行从多态基类对象(或引用),到派生类的引用的强制类型转换时,如果转换是不安全的,则会抛出此异常。
- bad_alloc 在用new运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。
- out_of_range 用vector或string的at成员函数根据下标访问元素时,如果下标越界,就会抛出此异常。
以上类存在 what()
函数获得异常字符串
类型检查
C++运算符typeid是单目运算符,可以在程序运行过程中获取一个表达式的值的类型。typeid运算的返回值是一个type_info类的对象,里面包含了类型的信息。
需要头文件
使用方法
typeid(i).name(); //输出类型的字符串
强制类型转换类模板
static_cast
static_cast用来进用行比较“自然”和低风险的转换,比如整型和实数型、字符型之间互相转换。
static_cast不能来在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,也不能用于不同类型的引用之间的转换。
reinterpret_cast
reinterpret_cast用来进行各种不同类型的指针之间的转换、不同类型的引用之间转换、以及指针和能容纳得下指针的整数类型之间的转换。转换的时候,执行的是逐个比特拷贝的操作。
const_cast
用来进行去除const属性的转换。将const引用转换成同类型的非const引用,将const指针转换为同类型的非const指针时用它。
dynamic_cast
dynamic_cast专门用于将多态基类的指针或引用,强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回NULL指针。
ldynamic_cast不能用于将非多态基类的指针或引用,强制转换为派生类的指针或引用