目录
STL(Standard Template Library,标准模板库):
C++核心编程
-
程序的内存模型:
意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。
c++在程序运行前分为全局区和代码区。
代码区:
1.存放cpu执行的机器指令。
2.代码区是共享的,共享的意义是对于频繁被执行的程序,只需要在内存中有一份代码即可。
3.代码区是只读的,以防止程序意外的修改了它的指令。
全局区:
1.全局变量和静态变量存放在此。
2.全局区还包含了常量区,字符串常量和其他常量(const 修饰的全局变量)也存放在此。
3.该区域的数据在程序结束后由操作系统释放。
栈区:
1.由编译器自动分配释放,存放函数的参数值(形参),局部变量等。
2.注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。
堆区:
1.由程序员分配释放,若程序员不释放,程序结束时由操作系统回收。
2.在c++中主要利用new在堆区开辟内存,由delete释放。
-
new与delete运算符:
int *a = new int(10);
delete a;//释放内存
int *arr = new int[10];
delete[] arr;
-
c++引用:引用的本质在c++内部实现是一个指针常量。
1.引用必须初始化。int &b;//错误的
2.引用在初始化后不能发生改变。
3.注意:不要返回局部变量的引用。
4.如果函数的返回值是引用,这个函数调用可以作为左值。
int a = 10;
int d = 30;
int &b = a;//引用
b=d;//赋值操作,而不是更改引用,此时b=30,a=30
int &c;//错误的
//引用作为函数参数
int swap(int& a,int& b)
{
int temp=a;
a=b;
b=temp;
}
int& test()//不要这样做!!!
{
int a=10;//局部变量存放在栈区
return a;
}
int &ref = test();
cout<<ref;//第一次结果正确,因为编译器做了保留
cout<<ref;//第二次结果错误,因为a的内存已经释放
int& testt()//这样可以
{
static int a = 10;//静态变量存放在全局区
return a;
}
int &ref2=testt();
cout<<ref2;
cout<<ref2;//均正确
testt() = 1000;//如果函数的返回值是引用,这个函数调用可以作为左值。
cout<<ref2;//输出结果为1000
int a = 10;
int& ref = a;//自动转换为int* const ref = &a; 指针常量指向不可改,这也说明了为什么引用不可更改。
ref = 20;//内部发现ref是引用,自动帮我们转换为:*ref = 20;
//常量引用
//使用场景:用来修饰形参,防止误操作
int a = 10;
int& ref = 10;//错误的,引用必须引一块合法的内存空间。
const int& ref = 10;//正确的
//加上const以后,编译器内部将代码修改为 int temp = 10; const int& ref = temp;
ref = 20;//错误的,加上const之后变为只读,不可修改
//防止误操作
void showVal(int& val)
{
val=1000;//原本只想打印val,却不小心修改了数据。
cout<<val;
}
void showVal(const int& val)
{
val=1000;//const修饰之后,函数内无法修改val值。
cout<<val;
}
-
函数的默认参数:
1.如果某个位置已经有了默认参数,那么从这个位置往后的参数也必须要有默认参数。
2.如果函数声明有默认参数,函数实现就不能有默认参数,即声明与实现只能有一个有默认参数
int fuc(int a,int b,int c)
{
return a+b+c;
}
int func_1(int a = 10, int b = 10, int c = 10)
{
return a+b+c;
}
//如果某个位置已经有了默认参数,那么从这个位置往后的参数也必须要有默认参数
int func_2(int a, int b = 10,int c)//不行
{
return a+b+c;
}
int func_3(int a , int b = 10,int c = 10)//可以
{
return a+b+c;
}
//如果函数声明有默认参数,函数实现就不能有默认参数,即声明与实现只能有一个有默认参数
int func_4(int a,int b,int c);
int func_4(int a , int b = 10,int c = 10)
{
return a+b+c;
}
-
函数占位参数:
1.c++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置。
//占位参数
void func(int a)
{
cout<<"this is func";
}
func(10);
void func_1(int a,int)//第二个参数即为占位参数
{
cout<<"this is func_1";
}
func_1(1,2);//必须要有两个参数才能调用
//占位参数还可以有默认参数
void func_2(int a,int = 100)
{
cout<<"this is func_2";
}
func_2(2);
func_2(2,3);//都可调用
-
函数重载:
函数名可以相同,提高复用性。
函数重载满足条件:
1.同一个作用域下。
2.函数名称相同。
3.函数参数类型不同 或者 个数不同 或者 顺序不同。
//函数重载:返回值必须相同
void func()
{
cout<<"func 的调用";
}
void func(int a)
{
cout<<"func 的调用";
}
void func(double a)
{
cout<<"func 的调用";
}
void func(int a,double b)
{
cout<<"func 的调用";
}
void func(double a,int b)
{
cout<<"func 的调用";
}
注意事项:
1.引用作为重载条件
2.函数重载碰到默认参数。
//函数重载的注意事项
//1.引用作为重载的条件
void func(int &a)
{
cout<<"func 的调用";
}
void func(const int &a)
{
cout<<"func 的调用";
}
func(a);//调用第一个
func(10);//调用第二个
//2/函数重载碰到默认参数
void func2(int a,int b = 10)
{
cout<<"func 的调用";
}
void func2(int a)
{
cout<<"func 的调用";
}
func2(10);//当函数重载碰到默认函数,出现二义性,报错,尽量避免
类和对象:
-
封装:封装时C++面向对象三大特性之一
封装的意义:
1.将属性和行为作为一个整体,表现生活中的事物。
2.将属性和行为加以权限控制。
//设计一个圆类,求圆的周长
# define pi 3.15
//类中的属性和行为 我们统一称为 成员
//属性 成员属性 成员变量
//行为 成员函数 成员方法
class Circle
{
//访问权限:
//公共权限 public 类内可以访问,类外也可访问
//保护权限 protected 类内可以访问,类外不可访问 子类可以访问父类中的保护内容
//私有权限 private 类内可以访问,类外不可访问 子类不可访问父类中的私有内容
public:
//行为:获取圆的周长
double calculateZC()
{
return 2*pi*r;
}
//set方法
void setR(double a)
{
r=a;
}
//get方法
double getR()
{
return r;
}
private:
//属性:半径
double r;
};
int main()
{
Circle c;//实例化
}
struct和class的区别:
1.struct默认权限为公共
2.class默认权限为私有
class c1
{
int a;//默认为私有
};
struct c2
{
int a2;//默认为公共
};
//封装:
point.h 头文件
#pragma once
#include<iostream>
using namespace std;
class Point
{
public:
void setX(int a);//只需要声明,加分号补全
int getX();
...
private:
int x;
int y;
};
point.cpp 源文件//源文件中只需要函数实现
#include "point.h"
void Point::setX(int a)//添加作用域
{
x=a;
}
int Point::getX()
{
return x;
}
//使用时:只需包含头文件
include<bits/stdc++.h>
include "point.h"
using namespace std;
...
-
对象特性:
构造函数和析构函数:
1.构造函数:主要作用在于创建对象时为对象成员属性赋值。
2.析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理操作。
3.如果我们不提供,编译器会自动提供这两个函数。
4.编译器提供的是空实现。
创建一个类,C++编译器会给每个类都添加至少4个函数:
1.默认构造函数(空实现)
2.默认析构函数(空实现)
3.默认拷贝构造(值拷贝)
4.赋值运算符 operator= 对属性进行值拷贝
构造函数调用规则:
1.如果用户定义有参构造函数,C++不再提供默认无参构造,但会提供默认拷贝构造。
2.如果用户定义拷贝构造函数,c++不会再提供其他构造函数。
拷贝构造函数:
用处:
1.把对象作为实参进行函数调用时,系统自动调用拷贝构造函数实现把对象值传递给形参对象。
2.当函数的返回值为对象时,系统自动调用拷贝构造函数对返回对象值创建一个临时对象,然后再将这个临时对象值赋给接收函数返回值的对象。
class Point
{
public:
//构造函数:1.无返回值,无需写void . 2.函数名与类名相同。 3. 可以有参数,可以发生重载。 4.创建对象时,系统会自动调用一次.
Point(){}
Point(int ix,int iy)
{
x=ix;
y=iy;
}
//拷贝构造函数:
//1.名字与类名相同
//2.只有一个形参数,该参数是该类对象的引用
Point(const Point& p)
{
x=p.x;
y=p.y;
}
//析构函数:1.无返回值,无需写void . 2.函数名与类名相同,在前面加 ~ 。 3. 不可以有参数,不可以发生重载。 4.销毁对象时,系统会自动调用一次.
~Point()
{
cout<<"析构函数的调用";
}
private:
int x;
int y;
}
//构造调用的三种方法:
//1.括号法:
Point p1();//这样编译器会认为这是一个函数的声明
Point p2;//实例化对象
//2.显示法:
Point(2,3);//匿名对象 对象声明
// ^
Point(p2);//错误的 ||
//不要利用拷贝构造函数初始化匿名对象,编译器会认为 Point(p2) === Point p2 重定义了
Point p3 = Point(2,3);有参构造
Point p4 = Point(p3);//拷贝构造
//3.隐式转换法:
Point p5 = 10; 相当于 Point p5 = Point(10);
Point p6 = p5;拷贝构造 相当于 Point p6 = Point(p5);
深拷贝和浅拷贝:
浅拷贝:简单的赋值操作。
深拷贝:在堆区重新申请空间,进行拷贝操作。
如果属性有在堆区开辟的:
1.在析构函数中我们要自己释放掉这块内存空间(delete),默认析构函数只会释放掉栈区的空间
2.一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
class Person
{
public:
Person(){}
person(int age, int h)
{
m_Age=age;
m_Height = new int(h);
}
//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person& p)
{
m_Age = p.m_Age;
//m_Height = p.m_Height;//编译器默认实现的代码,会带来问题
//深拷贝操作
m_Height = new int(*p.m_Height);
}
~Person()
{
if(m_Height!=nullptr)
{
delete m_Height;
m_Height=nullptr;
}
}
int m_Age;
int *m_Height;
};
Person p1(18,180);
Person p2(p1);//此时如果调用浅拷贝会出现堆区内存重复释放的问题,因为浅拷贝会将p1在堆区存放身高数据的地址直接拷贝至p2,析构时p2会释放掉这块堆区的内存,那么等p1析构时其实这块内存已经在p2析构时被释放了,但p1并不知道,会再次释放这块内存,导致程序异常。
初始化列表:C++提供了初始化列表语法,用来初始化属性。
语法:构造函数():属性1(值1),属性2(值2)...{}
class Cla
{
public:
//传统初始值操作
// Cla(int a,int b)
// {
// m_a=a;
// m_b=b;
// }
//初始化列表初始化属性
Cla(int a,int b):m_a(a),m_b(b){}//可以
Cla(int a,int b):m_a(a),m_b(b)//可以
{
//也可以写他的的东西
}
int m_a;
int m_b;
};
类对象作为类成员:
1.C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。
2.构造的顺序是: 先调用对象成员的构造,再调用本类的构造
3.析构顺序与构造顺序相反。
class A{};
class B
{
public:
A a;
};
//先构造A对象,再构造B对象。
//析构相反。
静态成员:
静态成员变量:
1.所有对象共享同一份数据。
2.在编译阶段分配内存。
3.类内声明,类外初始化。
class A
{
public:
staric int x;
private://静态成员变量也有访问权限
static int y;
};
int A::x = 100;
int A::y = 500;
//A a1;
//cout<<a1.x;//100
//A a2;
//a2.x = 200;
//cout<<a1.x;//200
//静态成员变量 不属于某个对象,所有对象都共享同一份数据
//因此静态成员变量有两种访问方式
//1.通过对象进行访问
A a;
cout<<a.x;//100
//2.通过类名进行访问
cout<<A::x;//100
cout<<A::y;//类外访问不到私有静态成员变量
静态成员函数:
1.所有对象共享同一个函数。
2.静态成员函数只能访问静态成员变量。
class A
{
public:
//静态成员函数
static void func()
{
x = 300;//静态成员函数 可以访问 静态成员变量
y = 10;//不行,静态成员函数 不能访问 非静态成员变量, 无法区分这个y属于哪个对象
}
//静态成员变量
static int x;
//非静态成员变量
int y;
private:
//静态成员函数也有访问权限
static void func2(){}
};
int A::x = 100;
//访问:
//1.通过对象访问
A a;
a.func();
//2.通过类名访问
A::func();
A::func2();//错误的,类外访问不到私有静态成员函数
C++对象模型和this指针:
成员变量和成员函数分开存储:
1.在C++中,类内的成员变量和成员函数分开存储。
2.只有非静态成员变量采属于类的对象上。
3.每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。
class A
{
};
A a;
//空对象占用的内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间, 是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout<<sizeof(a);//1
//成员变量 和 成员函数 是分开存储的
class B
{
int x;//非静态成员变量, 属于类的对象上
static int y;//静态成员变量, 不属于类的对象上
void func(){} //非静态成员函数, 不属于类的对象上
static void func2(){} //静态成员函数, 不属于类的对象上
};
int B::y = 5;
B b;
//占用4个字节,因为有个非静态成员变量,类型为int
cout<<sizeof(b);//4
this指针:
前言: 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。那么问题是: 这一块代码如何区分哪个对象调用自己的呢?
C++通过特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。
1.this指针是隐含每一个非静态成员函数内的一种指针。
2.this指针不需要定义,直接使用即可。
this指针的用途:
1.当形参和成员变量同名时,可用this指针来区分。
2.在类的非静态成员函数中返回对象本身,可用 return *this;
//解决名称冲突
class A
{
public:
A(int x)
{
x=x;
}
int x;
};
A a(18);
cout<<a.x;//此时值不为18,而是一个乱值。
//********************************************
//解决名称冲突
class A
{
public:
A(int x)
{ //this指针指向被调用的成员函数所属的对象。
this->x=x;
}
int x;
};
A a(18);
cout<<a.x;//此时值为18
//返回对象本身用*this
class Person
{
public:
Person(int age)
{
this->age=age;
}
void PersonAddAge(Person& p)
{
this->age += p.age;
}
int age;
};
Person p1(10);
Person p2(20);
p2.PersonAddAge(p1);
cout<<p2.age;//30
//***********************************
class Person
{
public:
Person(int age)
{
this->age=age;
}
//如果是以值的方式返回,会调用拷贝构造函数创建一个新的对象
//引用的方式返回:不会创建新的对象,而是返回该对象
Person& PersonAddAge(Person& p)
{
this->age += p.age;
return *this;//返回对象本身用*this
}
int age;
};
Person p1(10);
Person p2(20);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
//如果是以值的方式返回,结果为30
//以引用的方式返回,结果为50
cout<<p2.age;
空指针访问成员函数:
1.C++中空指针也是可以调用成员函数的,但是也要主义有没有用到this指针。
2.如果用到this指针,需要加以判断保证代码的健壮性。
//空指针调用成员函数
class Person
{
public:
void showClassName()
{
cout<<"this is Person class ";
}
// void showAge()
// {
// //报错原因时因为传入的指针为nullptr,导致this->m_Age错误。
// cout<<"age = "<<m_Age;
// }
void showAge()
{
if(this == nullptr) return ;//提高代码的健壮性
cout<<"age = "<<m_Age;
}
int m_Age;
};
Person* p = nullptr;
p->showClassName();//可以正常调用,没有问题
p->showAge();
const修饰成员函数:
常函数:
1.成员函数后加const后我们称这个函数为常函数。
2.常函数内不可修改成员属性。
3.成员属性声明时加关键字mutable后,在常函数中依然可以修改。
常对象:
1.声明对象前加const称该对象为常对象。
2.常对象只能调用常函数。
//常函数
class Person
{
public:
//this的本质 是指针常量 指针的指向是不能修改的
//Person * const this
//在成员函数后加const,修饰的是this指针, 让指针指向的值也不可修改
void showPerson() const //相当于 const Person * const this
{
//this->m_A = 100 ;//函数后加const之后属性就无法修改了
this->m_B = 100;
}
void func(){}
int m_A;
mutable int m_B;//特殊变量,即使在常函数中,也可修改这个值,加关键字mutable
};
//常对象
const Person p;//在对象前加const, 变为常对象
p.m_A = 100;//报错,不能修改
p.m_B = 100;//没错,可以修改。m_B是特殊值, 在常对象下也可以修改
//常对象只能调用常函数
p.showPerson();
p.func();//报错, 常对象不可以调用普通成员函数, 因为普通成员函数可以修改属性
友元:关键字:friend
友元的目的是让一个函数或者类访问另一个类中私有友元。
友元的三种实现:
1.全局函数做友元。
2.类做友元。
3.成员函数做友元。
//全局函数做友元
class Building
{
//goodGay全局函数是Building类的友元,可以访问Building中的私有成员。
friend void goodGay(Building *building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//全局函数:
void goodGay(Building *building)
{
cout<<building->string m_SittingRoom;
cout<<building->string m_SittingRoom;
}
//类做友元
class Building;
///
class GoodGay
{
public:
GoodGay();
void visit();//参观函数, 访问Building中的属性
Building* building;
};
//类外写成员函数
GoodGay::GoodGay()
{
//创建建筑物对象
building = new Building;
}
void GoodGay::visit()
{
cout<<building->m_SittingRoom;//GoodGay类访问 客厅
cout<<building->m_BedRoom;GoodGay类访问 卧室
}
///
class Building
{
//GoodGay类Building类的友元,可以访问Building中的私有成员。
friend class GoodGay;
public:
Building();
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
//类外写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay gg;
gg.visit();
//成员函数做友元
class Building;
///
class GoodGay
{
public:
GoodGay();
void visit();//让visit函数 可以访问B uilding中私有成员
void visit2();//让visit2函数 不可以访问 Building中私有成员
Building* building;
};
//类外写成员函数
GoodGay::GoodGay()
{
//创建建筑物对象
building = new Building;
}
void GoodGay::visit()
{
cout<<building->m_SittingRoom;//visit() 可以 访问 客厅
cout<<building->m_BedRoom;//visit() 可以 访问 卧室
}
void GoodGay::visit2()
{
cout<<building->m_SittingRoom;//visit2() 可以 访问 客厅
//cout<<building->m_BedRoom;//visit2() 不可以 访问 卧室
}
///
class Building
{
//GoodGay类中的成员函数visit() 为Building类的友元,可以访问Building中的私有成员。
friend void GoodGay::visit();
public:
Building();
string m_SittingRoom;//客厅
private:
string m_BedRoom;//卧室
};
/
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
-
运算符重载:
概念:对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型。
注意:
1.对于内置的数据类型的表达式的运算符是不可能改变的,即你不能通过运算符重载让 1 + 1 = 0。
2.不要滥用运算符重载。
加号运算符重载:
实现两个自定义数据类型相加的运算。
class Person
{
public:
//1.成员函数重载 + 号
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;
}
Person operator+(int num)
{
Person temp;
temp.m_A = this->m_A + num;
temp.m_B = this->m_B + num;
return temp;
}
int m_A;
int m_B;
};
//2.全局函数重载 + 号
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 p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
//成员函数重载本质调用
Person p3 = p1.operator+(p2);
//全局函数重载本质调用
Person p3 = operator+(p1,p2);
//运算符重载 也可以发生函数重载
Person p4 = p1 + 10;
左移运算符重载:
作用:可以输出自定义数据类型
//左移运算符重载
class Person
{
friend ostream& operator<<( ostream &cout ,Person &p );
public:
//1.利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本 p<<cout
//不会利用成员函数重载 << 运算符,因为无法实现 cout 在左侧
// void operator<<( cout )
// {
//
// }
int m_A;
int m_B;
};
//只能利用全局运算符重载左移运算符
ostream& operator<<( ostream &cout ,Person &p )//本质 operator<<(cout,p) 简化 cout<<p;
{
cout<<"m_A = "<<p.m_A<<", m_B = "<<p.m_B;
return cout;
}
Person p;
p.m_A = 10;
p.m_B = 20;
cout<<p;
递增运算符重载:
作用:通过重载递增运算符,实现自己的整型数据。
//重载递增运算符
//自定义整型
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger& myint);
// friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//重载前置++运算符
MyInteger& operator++()
{
m_Num++;//先自增运算
return *this;//在返回
}
//重载后置++运算符
MyInteger* operator++(int)
{
MyInteger* temp = new MyInteger(*this);
m_Num++;
return temp;
}
// 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;
}
// ostream& operator<<(ostream& cout, MyInteger myint)
// {
// cout << myint.m_Num;
// return cout;
// }
MyInteger myint;
cout << ++myint << endl;
cout << *(myint++) << endl;
// cout << myint++ << endl;
cout << myint;
赋值运算符重载:
如果类中有属性指向堆区,做赋值操作时也会出现浅拷贝问题。
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age != nullptr)
{
delete m_Age;
m_Age = nullptr;
}
}
//重载 赋值运算符
Person& operator=(Person& p)
{
//编译器是提供浅拷贝
//m_Age = p.m_Age;//浅拷贝析构时会发生堆区重复释放的问题
//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != nullptr)
{
delete m_Age;
m_Age = nullptr;
}
//深拷贝
m_Age = new int(*p.m_Age);
return *this;
}
int* m_Age;
};
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1;
cout << *p1.m_Age << endl;//18
cout << *p2.m_Age << endl;//18
cout << *p3.m_Age;//18
关系运算符重载:
作用:重载关系运算符,可以让两个自定义类型进行对此操作。
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
//重载 == 号
bool operator==(Person& p)
{
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
{
return true;
}
return false;
}
//重载 != 号
bool operator!=(Person& p)
{
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
{
return false;
}
return true;
}
string m_Name;
int m_Age;
};
Person p1("tom", 18);
Person p2("mary", 18);
if (p1 == p2)
{
cout << "相等";
}
else
{
cout << "不等";
}
if (p1 != p2)
{
cout << "不等";
}
else
{
cout << "相等";
}
函数调用运算符重载:
1.函数调用运算符()也可以重载。
2.由于重载后使用的方式非常像函数的调用,因此被称为伪函数。
3.仿函数没有固定写法,非常灵活。
//函数调用运算符重载
//打印输出类
class MyPrint
{
public:
//函数调用运算符重载
void operator()(string test)
{
cout << test << endl;
}
};
void FuncPrint(string test)
{
cout << test << endl;
}
//仿函数非常灵活,没有固定的写法
//加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test01()
{
MyPrint myprint;
myprint("hello world");//由于使用起来非常类似于函数调用,因此被称为仿函数。
FuncPrint("hello world");
}
void test02()
{
MyAdd myadd;
int ret = myadd(100, 200);
cout << "ret = " << ret << endl;
//匿名函数对象
cout << MyAdd()(100, 100) << endl;
}
-
继承:
继承是面向对象三大特性之一
有些类与类之间存在特殊关系,例如下图中:
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码。
继承的基本语法:
语法: class 子类 : 继承方式 父类
子类 也称为 派生类
父类 也称为 基类
派生类中的成员,包含两大部分:
1.一类是从父类继承过来的,一类是自己增加的成员。
2.从父类继承过来的表现其共性,而新增的成员体现了其个性。
普通实现页面如下:无意义的重复的代码太多
//普通实现页面
//Java页面
class Java
{
public:
void header()
{
cout<<"首页、公开课、登录、注册。。。(公共头部)"<<endl;
}
void footer()
{
cout<<"帮助中心、 交流合作、站内地图。。。(公共底部)"<<endl;
}
void left()
{
cout<<"Java、 Python、 C++。。。(公共分类列表)"<<endl;
}
void content()
{
cout<<"Java 学科视频"<<endl;
}
};
//Python页面
class Python
{
public:
void header()
{
cout<<"首页、公开课、登录、注册。。。(公共头部)"<<endl;
}
void footer()
{
cout<<"帮助中心、 交流合作、站内地图。。。(公共底部)"<<endl;
}
void left()
{
cout<<"Java、 Python、 C++。。。(公共分类列表)"<<endl;
}
void content()
{
cout<<"Python 学科视频"<<endl;
}
};
//c++页面
class CPP
{
public:
void header()
{
cout<<"首页、公开课、登录、注册。。。(公共头部)"<<endl;
}
void footer()
{
cout<<"帮助中心、 交流合作、站内地图。。。(公共底部)"<<endl;
}
void left()
{
cout<<"Java、 Python、 C++。。。(公共分类列表)"<<endl;
}
void content()
{
cout<<"C++ 学科视频"<<endl;
}
};
void test01()
{
cout<<"Java 下载视频页面如下: "<<endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout<<"****************************************"<<endl;
cout<<"Python 下载视频页面如下: "<<endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout<<"****************************************"<<endl;
cout<<"C++ 下载视频页面如下: "<<endl;
Cpp cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
继承实现页面如下:
//继承实现页面
//继承的好处: 减少重复代码
//语法: class 子类 : 继承方式 父类
//子类 也称为 派生类
//父类 也称为 基类
class BasePage
{
public:
void header()
{
cout<<"首页、公开课、登录、注册。。。(公共头部)"<<endl;
}
void footer()
{
cout<<"帮助中心、 交流合作、站内地图。。。(公共底部)"<<endl;
}
void left()
{
cout<<"Java、 Python、 C++。。。(公共分类列表)"<<endl;
}
void content()
{
cout<<"Java 学科视频"<<endl;
}
};
//java页面
class Java : public BasePage
{
public:
void content()
{
cout<<"Java 学科视频"<<endl;
}
};
//python页面
class Python : public BasePage
{
public:
void content()
{
cout<<"Python 学科视频"<<endl;
}
};
//C++页面
class CPP : public BasePage
{
public:
void content()
{
cout<<"C++ 学科视频"<<endl;
}
};
继承方式:
继承方式一共有三种:
1.公共继承。
2.保护继承。
3.私有继承。
//继承方式
//父类
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son1 : public Base
{
public:
void func()
{
m_A = 10;//父类中的 公共权限成员 到子类中依然是 公共权限
m_B = 10;//父类中的 保护权限成员 到子类中依然是 保护权限
//m_C = 10;//父类中的 私有权限成员 子类访问不到
}
};
void test01()
{
Son s1;
s1.m_A = 100;
//s1.m_B = 100;//到Son1中 m_B 是 保护权限 类外访问不到
}
//保护继承
class Son2 : protected Base
{
public:
void func()
{
m_A = 10;//父类中的 公共权限成员 到子类中变为了 保护权限
m_B = 10;//父类中的 保护权限成员 到子类中变为了 保护权限
//m_C = 10;//父类中的 私有权限成员 子类访问不到
}
};
void test02()
{
Son s1;
//s1.m_A = 100;//到Son2中 m_B 是 保护权限 类外访问不到
//s1.m_B = 100;//到Son2中 m_B 是 保护权限 类外访问不到
}
//私有继承
class Son3 : protected Base
{
public:
void func()
{
m_A = 10;//父类中的 公共权限成员 到子类中变为了 私有权限成员
m_B = 10;//父类中的 保护权限成员 到子类中变为了 私有权限成员
//m_C = 10;//父类中的 私有权限成员 子类访问不到
}
};
class GrandSon3 : protected Son3
{
public:
void func()
{
// m_A = 10;//祖父类Base中的 公共权限成员, 到了以私有方式继承父类Son3中变为了私有成员,孙子类访问不到。
// m_B = 10;//祖父类Base中的 保护权限成员, 到了以私有方式继承父类Son3中变为了私有成员,孙子类访问不到。
//m_C = 10;//祖父类Base中的 私有权限成员, 子类 与 孙子类 都访问不到。
}
};
void test03()
{
Son s1;
//s1.m_A = 100;//到Son2中 m_B 是 私有权限 类外访问不到
//s1.m_B = 100;//到Son2中 m_B 是 私有权限 类外访问不到
}
继承中的对象模型:
问题:从父类继承过来的成员, 哪些属于子类对象中
//继承中的对象模型
//父类
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;//16
}
//利用开发人员命令提示工具查看对象模型
//跳转盘符 D:
//跳转文件路径 cd 具体路径下
//dir
//查看命令 cl /d1 reportSingleClassLayout 类名 文件名 (文件名可按tab键自动补齐)
//黑马程序员 C++ P129
继承中的构造和析构顺序:
子类继承父类后,当创建子类对象时,也会调用父类的构造函数。
问题:父类与子类的构造和析构顺序是谁前谁后?
1.先构造父类,再构造子类。
2.析构与构造顺序相反。
//继承中的构造和析构顺序
class Base
{
public:
Base()
{
cout<<"Base构造函数"<<endl;
}
~Base()
{
cout<<"Base析构函数"<<endl;
}
};
class Son : public Base
{
public:
Son()
{
cout<<"Son构造函数"<<endl;
}
~Son()
{
cout<<"Son析构函数"<<endl;
}
}
void test01()
{
//Base b;
//先构造父类,再构造子类
//析构与构造顺序相反
Son s;
}
继承中同名成员处理方式:
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
1.访问子类同名对象,直接访问即可。
2.访问父类同名对象,需要加作用域。
3.如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数。
//继承同名成员处理方式
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout<<"Base - func() 调用"<<endl;
}
void func(int a)
{
cout<<"Base - func(int a) 调用"<<endl;
}
int m_A;
};
class Son : public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout<<"Son - func() 调用"<<endl;
}
int m_A;
};
//同名成员属性处理方式
void test01()
{
Son s;
cout<<"Son 下 m_A = "<<s.m_A;
//如果通过子类对象 访问到父类中同名成员, 需要加作用域
cout<<"Base 下 m_A = "<<s.Base::m_A;
}
//同名成员函数处理方式
void test02()
{
Son s;
s.func();//直接调用 ,如果子类中没有同名成员函数,则直接调用父类中的函数,如果子类中有,调用的是子类中的同名成员
s.Base::func();
//如果 子类 中出现和 父类 同名的 成员函数,子类的同名成员会隐藏掉 父类中所有同名成员函数(包括重载的)
//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
s.func(100);//报错
s.Base::func(100);//正确的
}
继承同名静态成员的处理方式:
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
1.访问子类同名对象,直接访问即可。
2.访问父类同名对象,需要加作用域。
3.同名静态成员处理方式和非静态成员处理方式一致,只不过有两种访问的方式(通过对象 和类名)
//继承同名静态成员处理方式
class Base
{
public:
Base()
{
m_A = 100;
}
static void func()
{
cout<<"Base - static void func() 调用"<<endl;
}
static void func(int a)
{
cout<<"Base - static void func(int a) 调用"<<endl;
}
static int m_A;
};
int Base::n_A = 100;
class Son : public Base
{
public:
Son()
{
m_A = 200;
}
static void func()
{
cout<<"Son - static void func() 调用"<<endl;
}
static int m_A;
};
int Son::n_A = 200;
//同名静态成员属性处理方式
void test01()
{
//1.通过对象访问
Son s;
cout<<"Son 下 m_A = "<<s.m_A;
//如果通过子类对象 访问到父类中静态同名成员, 需要加作用域
cout<<"Base 下 m_A = "<<s.Base::m_A;
//2.通过类名访问
cout<<"Son 下 m_A = "<<Son::m_A;
cout<<"Base 下 m_A = "<<Base::m_A;//直接通过父类访问
cout<<"Base 下 m_A = "<<Son::Base::m_A; //第一个:: 代表通过类名方式访问, 第二个:: 代表访问父类作用域下
}
//同名成员函数处理方式
void test02()
{
//1.通过对象访问
Son s;
s.func();
s.Base::func();
//2.通过类名访问
Son::func();
Son::Base::func();
//如果子类中出现和父类同名的静态成员函数,也会隐藏掉父类中所有同名成员函数
//如果想访问父类中被隐藏的静态同名成员函数,需要加作用域
Son::Base::func(100);
}
多继承语法:
语法:class 子类 : 继承方式 父类1 , 继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分。
C++实际开发中不建议使用多继承。
//多继承语法
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 100;
//m_B = 100;
}
int m_A;
//int m_B;
};
//子类 需要继承Base1 和 Base2
//语法:class 子类 : 继承方式 父类1 , 继承方式 父类2...
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(s);//16
//cout<<s.Base2::m_B;
//当父类中出现同名成员, 需要加作用域区分
cout<<s.Base1::m_A;
cout<<s.Base2::m_A;
}
菱形继承:
概念:
1.两个派生类继承同一个基类。
2.又有某个类同时继承两个派生类。
3.这种继承被称为菱形继承,又或者钻石继承。
4.利用虚继承可以解决菱形继承问题。
菱形继承问题:
1.羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。
2.羊驼继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
3.利用虚继承可以解决菱形继承问题。
//动物类
class Animal
{
public:
int m_Age;
};
//利用虚继承 解决菱形继承的问题
//继承之前,加上关键字 virtual 变为虚继承
//Animal 类被称为 虚基类
//羊类
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;
//这份数据我们只需要一份就可以,菱形继承导致数据有两份,浪费资源。
}
//当使用虚继承之后,st.Sheep::m_Age,st.Tuo::m_Age,st.m_Age三个都是同一份数据。
void test02()
{
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
cout<<st.Sheep::m_Age;//28
cout<<st.Tuo::m_Age;//28
cout<<st.m_Age;//28
}
-
多态:
多态是C++面向对象三大特性之一。
多态分为两类:
1.静态多态:函数重载和运算符重载属于静态多态,复用函数名。
2.动态多态:派生类和虚函数实现运行时多态。
静态多态和动态多态的区别:
1.静态多态的函数地址早绑定 - 编译阶段确定函数地址。
2.动态多态的函数地址晚绑定 - 运行阶段确定函数地址。
//多态
//动物类
class Animal
{
public:
//虚函数 函数前面加virtual
virtual void speak()
{
cout<<"动物在说话";
}
};
//猫类
class Cat : public Animal
{
public:
//函数重写 函数返回值类型 函数名 参数列表 完全相同
void speak()
{
cout<<"小猫在说话";
}
};
//狗类
class Dog:public Animal
{
public:
void speak()
{
cout<<"小狗在说话";
}
};
//执行说话的函数
//地址早绑定, 在编译阶段确定函数地址
//如果想执行小猫在说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
//动态多态满足条件
//1.有继承关系
//2.子类要重写父类的虚函数,子类重写的函数前virtual关键字可写可不写
//动态多态的使用
//父类的指针或者引用 指向子类对象
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
//Animal & animal = cat;
doSpeak(cat);//在Animal类的speak()函数前面加virtual之前,执行结果为动物在说话
Dog dog;
doSpeak(dog);//在Animal类的speak()函数前面加virtual之后,执行结果为小狗在说话
}
多态的原理剖析:
//多态
//动物类
class Animal
{
public:
//虚函数 函数前面加virtual
virtual void speak()
{
cout<<"动物在说话";
}
};
//猫类
class Cat : public Animal
{
public:
//函数重写 函数返回值类型 函数名 参数列表 完全相同
void speak()
{
cout<<"小猫在说话";
}
};
//狗类
class Dog:public Animal
{
public:
void speak()
{
cout<<"小狗在说话";
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()//speak()没加virtual之前
{
cout<<sizeof(Animal);//1
}
void test02()//speak()加virtual之后
{
cout<<sizeof(Animal);//4 (32位) 8(64位) 说明类的内部多了个指针(虚函数(表)指针 )
}
多态的优点:
1.代码组织结构清晰。
2.可读性强。
3.利于前期和后期的扩展以及维护。
4.C++开发提倡利用多态设计程序框架,因为多态优点很多。
纯虚函数与抽象类:
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写函数,因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也被称为抽象类。
抽象类特点:
1.无法实例化对象。
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
class Base
{
public:
//纯虚函数。
//只要有一个纯虚函数,那么这个类就被称为抽象类。
//抽象类特点:
//无法实例化对象。
//抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};
class Son:public Base
{
public:
virtual void func()
{
cout<<"func() 函数调用 ";
}
};
void test01()
{
//Base b;//抽象类无法实例化对象
//new Base;//抽象类无法实例化对象
//Son s;//当子类中没有重写父类中的纯虚函数时,也无法实例化对象。
Son s;//当子类中 重写了 父类中的纯虚函数时,可以正常实例化对象了。
Base* base = new Son;
base->func();
}
虚析构和纯虚析构:
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方式: 将父类中的析构函数改为虚析构或者纯虚析构。
虚析构和纯虚析构共性;
1.可以解决父类指针释放子类对象。
2.都需要有具体的函数实现。
虚析构和纯虚析构区别:
1.如果是纯虚析构,该类属于抽象类,无法实例化对象。
虚析构语法: virtual ~类名(){ }
纯虚析构语法:virtual ~类名() = 0;类外代码实现:类名::~类名(){ }
//虚析构和纯虚析构
class Animal
{
public:
Animal(){}
//虚析构
// virtual ~Animal()
{
cout<<"Animal 虚析构函数调用";
}
//纯虚析构 需要声明也需要实现
//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
//纯虚函数
virtual void speak() = 0;
};
Animal::~Animal()
{
cout<<"Animal 纯虚析构函数调用";
}
class Cat:public Animal
{
public:
Cat(string name)
{
m_Name = new stirng(name);
}
~Cat()
{
if(m_Name != nullptr)
{
delete m_Name;
m_Name = nullptr;
}
}
void speak()
{
cout<<*m_Name<<"在猫叫"<<endl;
}
string *m_Name;
};
void test01()
{
Animal* a = new Cat("Tom");
a->speak();
//父类指针在析构时 不会调用子类中析构函数,导致子类如果有堆区属性时,会发生堆区内存泄漏
delete a; //解决方法,在父类析构函数前加virtual,改为虚析构
}
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象。
2.如果子类中没有堆区数据,可以不写虚析构或纯虚析构。
3.拥有纯虚析构的类也属于抽象类。
-
C++文件操作:
程序运行时产生的数据都属于临时数据,程序一旦结束都会被释放。通过文件可以将数据持久化。
C++中文件操作需要包含头文件<fstream>
文件类型分为两种:
1.文本文件 - 文件以文本的ASCII码形式存储在计算机中。
2.二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们。
操作文件的三大类:
1.ofstream:写操作
2.ifstream:读操作
3.fstream:读写操作
文本文件:
写文件:
写文件步骤如下:
1.包含头文件: #include<fstream>
2.创建流对象: ofstream ofs;
3.打开文件 : ofs.open("文件路径",打开方式);
4.写数据 : ofs<<"写入的数据";
5.关闭文件 : ofs.close();
文件打开方式:
注意:文件打开方式可以配合使用,利用|操作符。
例如:用二进制方式写文件: ios::binary | ios::out
//文本文件 写文件
//1.包含头文件 fstream
void test01()
{
//2.创建流对象
ofstream ofs;
//3.指定打开方式
ofs.open("文件路径",ios::out);
//4.写内容
ofs<<"姓名:张三"<<endl;
ofs<<"性别:男"<<endl;
ofs<<"年龄:18"<<endl;
//5.关闭文件
ofs.close();
}
读文件:
读文件步骤如下:
1.包含头文件: #include<fstream>
2.创建流对象: ifstream ifs;
3.打开文件并判断文件是否打开成功: ifs.open("文件路径",打开方式);
4.读数据 : 四种方式读取
5.关闭文件 : ifs.close();
//文本文件 读文件
void test01()
{
//1.包含头文件
//2.创建文件流
ifstream ifs;
//3.打开文件 并且判断是否打开成功
ifs.open("D:\\cppvscode\\study\\in.txt",ios::in);
if(!ifs.is_open())
{
cout<<"文件打开失败"<<endl;
return;
}
//4.读数据
//四种方法
//第一种:
// char buf[1024] = {0};
// while(ifs>>buf)
// {
// cout<<buf<<endl;
// }
//第二种
// char buf[1024] = { 0 };
// while (ifs.getline(buf, sizeof(buf)))
// {
// cout << buf << endl;
// }
//第三种
string buf;
while (getline(ifs, buf))
{
cout << buf << endl;
}
//第四种 不推荐用
// char c;
// while ((c = ifs.get()) != EOF)//EOF end of file
// {
// cout << c;
// }
//5.关闭文件
ifs.close();
}
二进制文件:
以二进制的方式对文件进行读写操作
打开方式要指定为 ios::binary
写文件:
二进制方式写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数。
//二进制文件 写文件
class Person
{
public:
//写字符串的时候,最好不要用C++的string,因为会出现一些问题,对文件直进行操作时,直接用C风格的字符串,因为底层使用C写的
char m_Name[64];//姓名
int m_Age;//年龄
};
//1.包含头文件 fstream
void test01()
{
//2.创建流对象
//ofstream ofs("person.txt", ios::out | ios::binary);
ofstream ofs;
//3.打开文件
ofs.open("person.txt", ios::out | ios::binary);
//4.写文件
Person p = { "张三",18 };
ofs.write((const char*)&p, sizeof(Person));
//5.关闭文件
ofs.close();
}
读文件:
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char* buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数。
//二进制 读文件
class Person
{
public:
char m_Name[64];//姓名
int m_Age;//年龄
};
void test01()
{
//1.包含头文件
//2.创建流对象
ifstream ifs;
//3.打开文件 并判断是否成功打开
ifs.open("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败";
return;
}
//4.读文件
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age;
//5.关闭文件
ifs.close();
}
C++提高编程:
本阶段主要针对C++泛型编程与STL技术。
-
模板:
模板的概念:
模板就是建立通用的模具,大大提高复用性。
模板的特点:
1.模板并不能直接使用,它只是一个框架。
2.模板的通用并不是万能的。
函数模板:
C++另一种编程思想成为泛型编程,主要利用的技术就是模板。
C++提供两种模板机制:函数模板和类模板。
函数模板语法:
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
语法:
template<typename T>//template - 声明创建模板
函数声明或定义 //typename - 表明其后面的符号是一种数据类型,可以用class代替
//T - 通用的数据类型,名称可以替换,通常为大写字母
//函数模板
//交换两个整型函数
void swapInt(int &a,int &b)
{
int temp = a;
a = b;
b = temp;
}
//交换两个浮点型函数
void swapFloat(float &a,float &b)
{
float temp = a;
a = b;
b = temp;
}
//函数模板
template<typename T>//声明一个模板,告诉编译器后面代码中紧跟着的 T 不要报错,T 是一个通用数据类型
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10;
int b = 20;
//swapInt(a,b);
//利用函数模板交换
//两种方式使用函数模板
1.自动类型推导
mySwap(a,b);
//2.显示制定类型
mySwap<int>(a,b);
}
总结:
1.函数模板利用关键字template
2.使用函数模板有两种方式:自动类型推导、显示指定类型。
3.模板的目的是为了提高复用性,将类型参数化。
函数模板注意事项:
注意事项:
1.自动类型推导,必须推到住一致的数据类型T,才可以使用。
2.模板必须确定出T的数据类型,才可以使用。
//函数模板注意事项
template<class T>//typename可以替换成class
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10;
int b = 20;
char c = 'x';
//1.自动类型推导,必须推到住一致的数据类型T,才可以使用。
mySwap(a,b);//正确的
mySwap(a,c);//错误的
}
//2.模板必须确定出T的数据类型,才可以使用。
template<typename T>
void func()//函数体中没有用到T时,编译器无法推导出T的数据类型
{
cout<<"func 调用";
}
void test02()
{
func();//错误的,函数体中没有用到T时,编译器无法推导出T的数据类型
func<int>();//正确的,此时随便给他一个数据类型,让它可以确定T的数据类型。
}
普通函数和函数模板的区别:
1.普通函数调用时可以发生自动类型转换(隐式类型转换)。
2.函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。
3.如果利用显示指定类型的方式,可以发生隐式类型转换。
//普通函数和函数模板的区别:
//1.普通函数调用时可以发生自动类型转换(隐式类型转换)。
//2.函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。
//3.如果利用显示指定类型的方式,可以发生隐式类型转换。
//普通函数
int myAdd01(int a,int b)
{
return a + b;
}
//函数模板
template<typename T>
T myAdd02(T a,T b)
{
return a + b;
}
void test01()
{
int a = 10;
int b = 20;
char c = 'c';//a - 97 c - 99
cout<<myAdd01(a,b)<<endl;//109
//自动类型推导
cout<<myAdd02(a,c)<<endl;//错位的 利用自动类型推导,不会发生隐式类型转换。
//显式指定类型
cout<<myAdd02<int>(a,c)<<endl;//109 利用显示指定类型的方式,可以发生隐式类型转换。
}
总结:建议使用显式指定类型的方式调用函数模板,因为可以自己确定通用类型T,以减少错误和不确定的结果。
普通函数与函数模板的调用规则:
调用规则如下:
1.如果函数模板和普通函数都可以实现,优先使用普通函数。
2.可以通过空模板参数列表来强制调用函数模板。
3.函数模板也可以发生重载。
4.如果函数模板可以产生更好的匹配,优先调用函数模板。
//普通函数与函数模板的调用规则:
//1.如果函数模板和普通函数都可以实现,优先使用普通函数。
//2.可以通过空模板参数列表来强制调用函数模板。
//3.函数模板也可以发生重载。
//4.如果函数模板可以产生更好的匹配,优先调用函数模板。
void myPrint(int a, int b)
{
cout<<"调用普通函数";
}
template<typename T>
void myPrint(T a, T b)
{
cout<<"调用函数模板";
}
template<typename T>
void myPrint(T a,T b,T c)
{
cout<<"调用重载的函数模板";
}
void test01()
{
int a = 10;
int b = 20;
myPrint(a,b);//调用普通函数,如果此时普通函数只有声明没有实现,依然会调用普通函数,只是没找到普通函数的实现,编译器会报错。
//通过空模板参数列表来强制调用函数模板
myPrint<>(a,b);//调用函数模板
myPrint(a,b,100);//调用重载的函数模板
//如果函数模板可以产生更好的匹配,优先调用函数模板
char c1 = 'a';
char c2 = 'b';
myPrint(c1,c2);//调用函数模板
}
总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。
模板的局限性:
局限性:模板的通用性并不是万能的。
//模板的局限性:
//模板并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现。
class Person
{
public:
string m_Name;
int m_Age;
Person(stirng name,int age)
{
m_Name = name;
m_Age = age;
}
};
//对比两个数据是否相等
template<typename T>
bool myCompare(T &a,T &b)
{
if(a==b)
{
return true;
}
return false;
}
//利用具体化Person的版本实现代码,具体化优先调用
template<> bool myCompare(Person &a,Person &b)
{
if(a.m_Name == b.m_Name && a.m_Age == b.m_Age)
{
return true;
}
return false;
}
void test01()
{
int a=10;
int b=20;
cout<<myCompare(a,b);//false
}
void test02()
{
Person p1("Tom",10);
Person p2("Tom",10);
cout<<myCompare(p1,p2);//异常, 利用具体化之后可以运行正确
}
总结:
1.利用具体化的模板,可以解决自定义类型的通用化。
2.学习模板并不是为了写模板,而是在STL中能够运用系统提供的模板。
类模板:
类模板的作用:
建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
语法:
template<typename T>//template - 声明创建模板
类 //typename - 表明其后面的符号是一种数据类型,可以用class 代替
//T - 通用的数据类型,名称可以替换,通常为大写字母。
//类模板
template<class NameType,class AgeType>
class Person
{
public:
NameType m_Name;
AgeType m_Age;
Person(NameType name,AgeType age)
{
m_Name = name;
m_Age = age;
}
void showInfo()
{
cout<<"m_Name="<<m_Name<<",m_Age"<<m_Age;
}
};
void test01()
{
Person<string,int> p1("孙悟空",20);
p1.showInfo();
}
总结:类模板与函数模板语法相似,在声明替template后面加类,此类称为类模板。
类模板与函数模板的区别:
类模板与函数模板区别主要有两点:
1.类模板没有自动类型推导。
2.类模板在模板参数列表中可以有默认参数。
//类模板与函数模板的区别:
template<class NameType,class AgeType = int>
class Person
{
public:
NameType m_Name;
AgeType m_Age;
Person(NameType name,AgeType age)
{
m_Name = name;
m_Age = age;
}
void showInfo()
{
cout<<"name:"<<m_Name<<",age:"<<m_Age;
}
};
//1.类模板没有自动类型推导的使用方式。
void test01()
{
//Person p("孙悟空",1000);//错误的,无法用自动类型推导
Person<string,int>p("孙悟空",1000);//正确的,只能用显示指定类型
p.showInfo();
}
//2.类模板在模板参数列表中可以有默认参数。
void test02()
{
//template<class NameType,class AgeType = int>
Person<string> p1("猪八戒",999);
p1.showInfo();
}
总结:
1.类模板使用只能用显式指定类型方式。
2.类模板中的模板参数列表中可以有默认参数。
类模板中成员函数的创建时机:
类模板中 成员函数 和 普通类中的 成员函数 创建时机 是有区别的:
1.普通类中的 成员函数 一开始 就可以创建。
2.类模板中的 成员函数 在调用时 才创建。
//类模板中 成员函数 和 普通类中的 成员函数 创建时机 是有区别的:
//1.普通类中的 成员函数 一开始 就可以创建。
//2.类模板中的 成员函数 在调用时 才创建。
//类模板中的成员函数: 只要没调用就不会创建
//类模板中的成员函数: 只要没调用就不会创建
//类模板中的成员函数: 只要没调用就不会创建
class Person1
{
public:
void showPerson1()
{
cout<<"Person1 show";
}
};
class Person2
{
public:
void showPerson2()
{
cout<<"Person2 show";
}
};
template<class T>
class MyClass
{
public:
T obj;
//类模板中的成员函数: 只要没调用就不会创建
void func1()
{
obj.showPerson1();
}
void func2()
{
obj.showPerson2();
}
};
void test01()
{
MyClass<Person1>m;
m.func1();
m.func2();//错误,showPerson2 不是 Person1 的成员函数
}
总结:类模板中的成员函数并不是一开始就创建的,在调用时才去创建。
类模板对象做函数参数:
学习目标:
类模板实例化出的对象,像函数传参的方式。
一共有三种传入方式:
1.指定传入的类型 --- 直接显示对象的数据类型。
2.参数模板化 --- 将对象中的参数变为模板进行传递。
3.整个类模板化 --- 将这个对象类型 模板化进行传递。
//类模板对象做函数参数
template<class T1,class T2>
class Person
{
public:
T1 m_Name;
T2 m_Age;
Person(T1 name, T2 age)
{
m_Name = name;
m_Age = age;
}
void showInfo()
{
cout<<"name:"<<m_Name<<",age:"<<m_Age;
}
};
//1.指定传入的类型 --- 直接显示对象的数据类型。
void printPerson1(Person<string,int>&p)
{
p.showInfo();
}
void test01()
{
Person<string,int> p("孙悟空",100);
printPerson1(p);
}
//2.参数模板化 --- 将对象中的参数变为模板进行传递。
template<class T1,class T2>//这里的T1与T2与 class Person 中的T1与T2没有关联
void printPerson2(Person<T1,T2>&p)
{
p.showInfo();
cout<<"T1的类型为:"<<typeid(T1).name<<endl;
cout<<"T2的类型为:"<<typeid(T2).name<<endl;
}
void test02()
{
Person<string,int> p1("猪八戒",90);
printPerson1(p1);
}
//3.整个类模板化 --- 将这个对象类型 模板化进行传递。
template<class T>
void printPerson3(T &p)
{
p.showInfo();
//查看T的数据类型名称
cout<<"T的数据类型为:"<<typeid(T).name<<endl;
}
void test03()
{
Person<string,int> p3("唐僧",30);
printPerson1(p3);
}
总结:
1.通过类模板创建的对象,可以有三种方式向函数中进行传参。
2.使用比较广泛的是第一种:指定传入的类型。
类模板与继承:
当类模板碰到继承时,需要注意以下几点:
1.当子类继承的父类是一个类模板时,子类在声明的时候,需要指出父类中T的类型。
2.如果不指定,编译器无法给子类分配内存。
3.如果想灵活制定出父类中T的类型,子类也需要变为类模板。
//类模板与继承
template<class T>
class Base
{
T m;
};
//class Son:punlic Base//错误,必须要知道父类中T的数据类型,才能继承给子类。因为不知道T的类型,无法计算要占多少内存空间
class Son:punlic Base<int>//正确的
{
public:
};
void test01()
{
Son s1;
}
//如果想灵活制定出父类中T的类型,子类也需要变为类模板。
template<class T1,class T2>//在这里 T1 为子类自己的成员变量 obj 的数据类型
class Son2:public Base<T2>//T2 为从父类中成员变量 m 的数据类型
{
public:
T1 obj;
Son2()
{
cout<<"T1的类型为:"<<typeid(T1).name()<<endl;
cout<<"T2的类型为:"<<typeid(T2).name()<<endl;
}
};
void test01()
{
Son2<int,char> s2;
}
总结:如果父类是类模板,子类需要指定出父类中T的数据类型。
类模板成员函数类外实现:
学习目标:能够掌握类模板中的成员函数类外实现
//类模板成员函数类外实现
template<class T1,class T2>
class Person
{
public:
T1 m_Name;
T2 m_Age;
Person(T1 name,T2 age);
// {
// m_Name = name;
// m_Age = age;
// }
void showInfo();
// {
// cout<<"name:"<<m_Name<<",age:"<<m_Age;
// }
};
//构造函数的类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
m_Name = name;
m_Age = age;
}
//成员函数的类外实现
template<class T1,class T2>
void Person<T1,T2>::showInfo()
{
cout<<"name:"<<m_Name<<",age:"<<m_Age;
}
void test01()
{
Person<string,int>p("tom",20);
p.showInfo();
}
总结: 类模板中成员函数类外实现时,需要加上模板参数列表。
类模板分文件编写:
学习目标:掌握类模板成员函数分文件编写产生的问题以及解决方式。
问题:
类模板中成员函数创建时机是在调用阶段,导致份文件编写时链接不到。
解决:
解决方式1:直接包含cpp源文件。
解决方式2:将声明和实现写到同一文件中,并更改后缀名为.hpp,hpp时约定的名称,并不是强制。
//类模板分文件编写问题及解决
/
//person.h 文件
#pragma once
#include<iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Person
{
public:
T1 m_Name;
T2 m_Age;
Person(T1 name,T2 age);
void showInfo();
};
///
///
//person.cpp 文件
#include"person.h"
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
m_Name = name;
m_Age = age;
}
template<class T1,class T2>
void Person<T1,T2>::showInfo()
{
cout<<"name:"<<m_Name<<",age:"<<m_Age;
}
///
///
//main 文件
#include<iostream>
using namespace std;
//#include"person.h"//法一:直接包含源文件 即改为#include"person.cpp"
#include"person.cpp" //法二:将.h和.cpp写到一起,并将后缀名更改为.hpp
void test01()
{
Person<string,int>p("tom",20);
p.showInfo();
}
///
///
#pragma once
#include<iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Person
{
public:
T1 m_Name;
T2 m_Age;
Person(T1 name,T2 age);
void showInfo();
};
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
m_Name = name;
m_Age = age;
}
template<class T1,class T2>
void Person<T1,T2>::showInfo()
{
cout<<"name:"<<m_Name<<",age:"<<m_Age;
}
///
///
//main 文件
#include<iostream>
using namespace std;
#include"person.hpp"//法一:直接包含源文件 即改为#include"person.cpp"
//法二:将.h和.cpp写到一起,并将后缀名更改为.hpp
void test01()
{
Person<string,int>p("tom",20);
p.showInfo();
}
///
总结:主流的解决方法是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp
类模板与友元:
学习目标:
掌握类模板配合友元函数的类内和类外实现。
1.全局函数内实现 - 直接在类内声明即可。
2.全局函数外实现 - 需要提前让编译器知道全局函数的存在。
//类模板与友元
//1.全局函数内实现 - 直接在类内声明即可。
//2.全局函数外实现 - 需要提前让编译器知道全局函数的存在。
//通过全局函数 打印Person信息
//提前让编译器知道Person类存在,且为模板类
template<class T1,class T2>
class Person;
//类外实现 需要让编译器提前知道这个函数的存在
template<class T1,class T2>
void showInfo2(Person<T1,T2> p)
{
cout<<"name:"<<p.m_Name<<",age:"<<p.m_Age;
}
template<class T1,class T2>
class Person
{
//全局函数 类内实现
friend void showInfo(Person<T1,T2> p)
{
cout<<"name:"<<p.m_Name<<",age:"<<p.m_Age;
}
//全局函数 类外实现
//加空模板参数列表 <>
//如果全局函数 是类外实现,需要让编译器提前知道这个函数的存在
friend void showInfo2<>(Person<T1,T2> p);
public:
T1 m_Name;
T2 m_Age;
Person(T1 name,T2 age);
{
m_Name = name;
m_Age = age;
}
};
//类外实现 需要在上方,需要让编译器提前知道这个函数的存在
//template<class T1,class T2>
//void showInfo2(Person<T1,T2> p)
//{
// cout<<"name:"<<p.m_Name<<",age:"<<p.m_Age;
//}
//类内实现测试
void test01()
{
Person<string,int>p("tom",20);
showInfo(p);
}
//类外实现测试
void test02()
{
Person<string,int>p2("jerry",20);
showInfo2(p2);
}
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。
-
STL(Standard Template Library,标准模板库):
诞生:长久以来,软件界一直希望建立一种可重复利用的东西->c++面向对象和泛型编程思想,目的就是复用性的提升->大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作->为了建立数据结构和算法的一套标准,诞生了STL。
基本概念:
1. STL从广义上分为:容器(container)算法(algorithm)迭代器(iterator)
2.容器和算法之间通过迭代器进行无缝连接
3.STL几乎所有的代码都采用了模板类或模板函数
STL六大组件:
大体分为六大组件:分别为:容器、算法、迭代器、仿函数、适配器、空间配置器
1.容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
2.算法:各种常用的算法,如sort、find、copy、for_each等
3.迭代器:扮演了容器与算法之间的胶合剂
4.仿函数:行为类似函数,可作为算法的某种策略
5.适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
6.空间配置器:负责空间的配置与管理
STL中容器、算法、迭代器:
容器:置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组、链表、树、栈、队列、集合、映射表 等
这些容器分为序列式容器和关联式容器两种:
序列式容器:强调值的排序,每个元素均有固定的位置
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
算法:问题之解法也
有限的步骤,解决逻辑或数学上的问题
算法分为:质变算法和非质变算法
质变算法:运算过程中会更改区间内的元素的内容。例如拷贝、替换、删除 等
非质变算法:运算过程中不会更改区间内的元素的内容。例如查找、计数、遍历、寻找极值 等
迭代器:容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针
迭代器种类:
常用的迭代器种类为双向迭代器和随机访问迭代器
容器算法迭代器初识:
容器:vector 算法:for_each 迭代器:vector<int>::iterator
#include<vector>
#include<algorithm>//标准算法头文件
//vector容器存放内置数据类型
void test01()
{
vector<int> v;//创建vector容器,数组
v.push_back(10);//向容器内尾插数据
v.push_back(20);
v.push_back(30);
v.push_back(40);
vector<int>::iterator it_begin = v.begin();//起始迭代器,指向容器内第一个元素
vector<int>::iterator it_end = v.end();//结束迭代器,指向容器中最后一个元素的下一个位置
//第一种遍历方式
while(it_begin != it_end)
{
cout<<*it_begin<<endl;
it_begin++;
}
//
//第二种遍历方式
for(vector<int>::iterator it = v.begin(); it != v.end(); ++it)
{
cout<<*it<<endl;
}
//第三种遍历方式,利用STL中的遍历算法
void func(int val)
{
cout<<val<<endl;
}
for_each(v.begin(), v.end(), func);
}
//vector容器存放自定义数据类型
class Person
{
public:
Person(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
void test02()
{
vector<Person> vp;
Person p1("zs", 12);
Person p2("za", 13);
Person p3("zb", 14);
Person p4("zc", 15);
Person p4("zd", 16);
//向容器中添加数据
vp.push_back(p1);
vp.push_back(p2);
vp.push_back(p3);
vp.push_back(p4);
vp.push_back(p5);
//遍历容器
for(vector<Person>::iterator it = vp.begin(); it != vp.end(); ++it)
{
cout<< (*it).m_name <<" "<< (*it).m_age <<endl;//法一
cout<< it->m_name <<" "<< it->m_age <<endl;//法二
}
}
///
//存放自定义数据类型 指针
void test03()
{
vector<Person*> vp;
Person p1("zs", 12);
Person p2("za", 13);
Person p3("zb", 14);
Person p4("zc", 15);
Person p4("zd", 16);
//向容器中添加数据
vp.push_back(&p1);
vp.push_back(&p2);
vp.push_back(&p3);
vp.push_back(&p4);
vp.push_back(&p5);
//遍历容器
for(vector<Person*>::iterator it = vp.begin(); it != vp.end(); ++it)
{
cout<< (*it)->m_name <<" "<< (*it)->m_age <<endl;//法一
}
}
容器嵌套容器:
//容器嵌套容器
void test01()
{
vector<vector<int>> v;
//创建小容器
vector<int> v1;
vector<int> v2;
vector<int> v3;
vector<int> v4;
//向小容器中添加数据
for(int i = 1; i < 4; ++i)
{
v1.push_back(i);
v2.push_back(i*10);
v3.push_back(i*20);
v4.push_back(i*30);
}
//将小容器插入到大容器中
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
v.push_back(v4);
//通过大容器遍历所有数据
for( vector<vector<int>>::iterator itv = v.begin(); itv != v.end(); ++itv)
{
//(*itv)------>容器
for(vector<int>::iterator it = (*itv).begin(); it != (*itv).end(); ++it)
{
cout<<*it<<endl;
}
}
}
string容器:
1.string基本概念:
本质:string是C++风格的字符串,本质是一个类
string和char*的区别:
char*是一个指针
string是一个类,类内部封装了一个char*,管理这个字符串,是一个char*型的容器
特点:
1.string类内部封装了很多成员方法,如查找find、拷贝copy、删除delete、替换replace、插入insert
2.string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
2.string构造函数:
构造函数原型:
//构造函数原型:
string(); //创建一个新的字符串,如string str;
string(const char* s); //使用字符串s初始化
string(const string& str); //使用一个string对象初始化另一个string对象
string(int n, char c); //使用n个字符c初始化
3.string赋值操作:
功能描述:给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赋给当前的字符串
样例:
void test01()
{
string str1;
str1 = "hello world";//1
string str2;
str2 = str1;//2
string str3;
str3 = 'a';//3
string str4;
str4.assign("hello C++");//4
string str5;
str5.assign("hello c++",7);//5 结果输出 hello c
string str6;
str6.assign(str5);//6
string str7;
str7.assign(10,'w');//7
}
总结:一般用 = 方式。
4.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* str, 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个字符串连接到当前字符串末尾
样例:
void test01()
{
string str1 = "我";
str1 += "爱玩游戏";//1 我爱玩游戏
str1 += ':';//2 我爱玩游戏:
string str2 = " LOL DNF";
str1 += str2;//3 我爱玩游戏: LOL DNF
string str3 = "I";
str3.append(" love");//4 I love
str3.append(" game abcde", 5);//5 I love game
str3.append(str2);//6 I love game LOL DNF
string str4 = "I love game";
str4.append(str2, 5, 3);//7 I love gameDNF
}
5.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, int n) const; //从pos位置查找s的前n个字符第一次出现位置
int find(const char c, int pos = 0) const; //查找字符c第一次出现位置,从pos开始找
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最后一次出现位置,从pos开始找
string& replace(int pos, int n, const string& str); //替换从pos开始的n个字符为str
string& replace(int pos, int n, const char* s); //替换从pos开始的n个字符为s
样例:
//字符串的查找和替换
//查找
void test01()
{
string str1 = "abcdefgde";
//find
int pos = str1.find("de");// 3
pos = str1.find("df"); // -1
//rfind
//find是从左往右查找,rfind是从右往左查找
pos = str1.rfind("de"); // 7 返回值依然是左边第一个字符的位置下标
}
//替换
void test02()
{
string str1 = "abcdefg";
//从 1号位置起 3个字符 替换为 “1111”
str1.replace(1,3,"1111");// a1111efg
}
6.字符串比较:
功能描述:字符串之间的比较
比较方式:
字符串比较是以字符的ASCII码进行对比
> 返回 1 = 返回 0 < 返回 -1
函数原型:
//函数原型:
int compare(const string& s) const; //与字符串s比较
int compare(const char* s) const; //与字符串s比较
样例:
//字符串比较
void test01()
{
string str1 = "hello";
string str2 = "hello";
string str3 = "xello";
int res = str1.compare(str2);// 0
res = str3.compare(str2);// 1
res = str2.compare(str3);// -1
}
总结:compare主要用于比较两个字符串是否相等,比较谁大谁小意义不是很大。
7.string字符存取:
string中单个字符存取方式有两种
char& operator[](int n); //通过[]方式取字符
char& at[](int n); //通过at方法获取字符
样例:
//string 字符存取
void test01()
{
string str = "hello";
//通过[]方式访问单个字符
for(int i = 0; i < str.size(); ++i)
{
cout<<str[i]<<" ";
}
//通过at方式访问单个字符
for(int i = 0; i < str.size(); ++i)
{
cout<<str.at(i)<<" ";
}
//修改
str[0] = 'x';// xello
str.at(1) = 'x';// xxllo
}
8.string插入和删除:
功能描述:对string字符串进行插入和删除字符操作
函数原型:
//函数原型:
string& insert(int pos, const char* s); //在指定位置插入字符串s
string& insert(int pos, const string& s); //在指定位置插入字符串s
string& insert(int pos, int n, char c); //在指定位置插入n个字符c
string& erase(int pos, int n = npos); //删除从pos开始的n个字符
样例:
//string的插入和删除
void test01()
{
string str = "hello";
//插入
str.insert(1,"111");// h111ello
//删除
str.erase(1,3);// hello
}
总结:插入和删除起始下标都是从0开始
9.string子串:
功能描述:从字符串中获取想要的子串
函数原型:
//函数原型:
string substr(int pos = 0, int n = npos) const;//返回由pos开始的n个字符组成的字符串
样例:
//string子串
void test01()
{
string str = "abcdefg";
string s1 = str.substr(1, 3);// bcd
}
vector容器:
1.vector基本概念:
功能:vector的数据结构与数组非常相似,也称为单端数组。
vector与普通数组区别:
不同之处在于数组是静态空间,而vector可以动态扩展。
动态扩展:
并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝至新空间,释放原空间。
vector的迭代器是支持随机访问的迭代器 。
2.vector构造函数:
功能和描述:创建vector容器。
函数原型:
//函数原型
vector<T> v; //采用模板类实现,默认构造函数
vector(v.begin(), v.end()); //将 v中的 [ begin(), end() ) 区间中的元素拷贝给本身,区间前闭后开
vector(n, elem); //构造函数将n个elem拷贝给本身
vector(const vector &vec); //拷贝构造函数
样例:
//vector容器构造
void p_v(vector<int> &v)
{
for(vector<int>::iterator it = v.begin(); it != v.end(); ++it)
cout << *it << " ";
}
void test01()
{
vector<int> v1;//默认构造 无参构造
for(int i = 0; i < 10; ++i)
v1.push_back(i);
p_v(v1);
//通过区间方式进行构造
vector<int> v2(v1.begin(), v1.end());
p_v(v2);
//n个elem方式
vector<int> v3(10,1);//10个1
p_v(v3);
//拷贝构造
vector<int> v4(v3);
p_v(v4);
}
3.vector容器赋值操作:
功能描述:给vector容器进行赋值
函数原型:
//函数原型
vector& operator=(const vector &vec); //重载 = 操作符
assign(v.begin(), v.end()); //将[ v.begin(), v.end() ) 区间中的值赋值给本身
assign(n, elem); //将n个elem拷贝赋值给本身
样例:
//vector赋值
void test01()
{
vector<int> v1;
for(int i = 0; i < 10; ++i)
v1.push_back(i);
//赋值
vector<int> v2 = v1;//等号赋值
//assign
vector<int> v3;
v3.assign(v1.begin(), v1.end());
vector<int> v4;
v4.assign(10,1);//10个1
}
4.vector容器的容量和大小:
函数原型:
//函数原型
empty(); //判断是否为空
capacity(); //容器的容量
size(); //返回容器中元素的个数
resize(int num); //重新指定容器的长度为num, 若容器变长,则以默认值填充新位置
//若变短,则末尾超出容器长度的元素被删除
resize(int num, elem); //重新指定容器的长度为num, 若容器变长,则以elem填充新位置
//若变短,则末尾超出容器长度的元素被删除
样例:
//vector容器容量和大小
void test01()
{
vector<int> v1;
for(int i = 0; i < 10; ++i)
v1.push_back(i);
bool res = v1.empty();// false
int cap = v1.capacity();//
int sz = v1.size();// 10
//重新指定大小
v1.resize(15);//如果第二个参数缺省,默认用0来填充(重新指定大小与比原来大)
v1.resize(15,100);//可以指定填充值
v1.resize(5);//重新指定大小与比原来小,超出部分被删除
}
5.vector插入和删除:
功能描述:对vector进行插入和删除操作
函数原型:
//函数原型
push_back(elem); //尾部插入元素elem
pop_back(); //删除最后一个元素
insert(const_iterator pos, elem); //迭代器指向位置pos插入元素elem
insert(const_iterator pos, int count, elem); //迭代器指向位置pos插入count个元素elem
erase(const_iterator pos); //删除迭代器指向的元素
erase(const_iterator start, const_iterator end); //删除迭代器从start到end之间的元素
clear(); //删除容器中所有元素
样例:
//vector的插入和删除
void test01()
{
vector<int> v1;
//尾插
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
//尾删
v1.pop_back();//删除末尾元素30
//插入
v1.insert(v1.begin(), 100);在头部插入100
v1.insert(v1.begin(), 2, 1000);//在头部插入2个1000
//删除
v1.erase(v1.begin());删除头部元素1000
v1.erase(v1.begin(), v1.end());//删除了全部元素
//清空
v1.clear();
}
6.vector数据存取:
功能描述:对vector容器中的数据进行存取操作
函数原型:
//函数原型
at(int idx); //返回索引index所指的数据
operator[idx]; //返回索引index所指的数据
front(); //返回容器中的第一个数据
back(); //返回容器中的最后一个数据
样例:
//vector数据存取
void test01()
{
vector<int> v1;
for(int i = 0; i < 10; ++i)
v1.push_back(i);
//利用[]方式访问数组中元素
for(int i = 0; i < v1.size(); ++i)
cout<< v1[i] <<" ";
//利用at方式访问数组中元素
for(int i = 0; i < v1.size(); ++i)
cout<< v1.at(i) <<" ";
//获取第一个元素
cout<< v1.front() <<endl;
//获取最后一个元素
cout<< v1.back() <<endl;
}
7.vector互换容器:
功能描述:实现两个容器内元素进行互换
函数原型:
//函数原型
swap(vec); //将vec与本身元素进行互换
样例:
//vector互换容器
//1.基本使用
void test01()
{
vector<int> v1;
for(int i = 0; i < 10; ++i)
v1.push_back(i);// 0到9
vector<int>v2;
for(int i = 10; i; --i)
v2.push_back(i);// 10到1
v1.swap(v2);//互换容器内元素
}
//2.实际用途
//巧用swap可以收缩内存空间
void test02()
{
vector<int> v;
for(int i = 0; i < 100000; ++i)
v1.push_back(i);
cout<<"容量"<<v.capacity()<<endl;
cout<<"大小"<<v.size()<<endl;
v.resize(3);//重新指定大小,容量没变,大小减小到3
cout<<"容量"<<v.capacity()<<endl;//不变
cout<<"大小"<<v.size()<<endl;//变为 3
//巧用swap收缩内存
vector<int> (v).swap(v);
cout<<"容量"<<v.capacity()<<endl;//变为3
cout<<"大小"<<v.size()<<endl;//变为3
}
8. vector预留空间:
功能描述:减少vector在动态扩展容量时的扩展次数
函数原型:
//函数原型
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问
样例:
//vector预留空间
void test01()
{
vector<int> v;
//利用reserve预留空间
v.reserve(100000);
int cnt = 0;//统计开辟次数
int *p = nullptr;
for(int i = 0; i < 100000; ++i)
{
v1.push_back(i);
if(p != &v[0])
{
p = &v[0];
cnt++;
}
}
cout<<cnt<<endl;// = 30 ,预留空间后 = 1
}
deque容器:
1.deque容器基本概念:
功能:双端数组,可以对两端进行插入删除操作。
deque与vector的区别:
1.vector对头部的插入和删除效率低,数据量越大,效率越低
2.deque相对vector而言,对头部的插入删除效率更高
3.vector访问元素的会比deque效率高,这与二者的内部实现有关
deque内部工作原理:
1.deque内部有个中控器,维护每段缓冲区的内容,缓冲区中存放真实数据。
2.中控器维护的是每段缓冲区的地址, 使得使用deque时像一片连续的内存空间。
deque容器的迭代器也是支持随机访问的。
2.deque构造函数:
函数原型:
//函数原型:
deque<T> deqT; //默认构造形式
deque(d.begin(), d.end()); //构造函数将 [ begin(), end() ) 区间内的元素拷贝给本身
deque(int n, elem); //构造函数将n个elem拷贝给本身
deque(const deque& deq); //拷贝构造函数
样例:
//deque构造函数
//额外知识:只读迭代器:const_iterator
void test01()
{
deque<int> d1;
for(int i = 0; i < 10; ++i)
d1.push_back(i);
deque<int> d2(d1.begin(),d1.end());
deque<int> d3(10,1);
deque<int> d4(d3);
}
3.deque赋值操作:
函数原型:
//函数原型:
deque& operator=(const deque& deq); //重载等号操作符
assign(begin(), end()); //将 [ begin(), end() ) 区间的数据拷贝赋值到本身
assign(int n, elem); //将n个elem拷贝赋值到本身
样例:
//deque容器赋值操作
void test01()
{
deque<int> d1;
for(int i = 0; i < 10; ++i)
d1.push_back(i);
deque<int> d2 = d1;
deque<int> d3;
d3.assgin(d1.begin(), d1.end());
deque<int> d4;
d4.assgin(10,1);
}
4.deque容器大小操作:
函数原型:
//函数原型:
deque.empty(); //判断是否为空
deque.size(); //返回容器中元素的个数
deque.resize(num); //重新指定容器的长度为num, 若容器变长,则以默认值填充新位置
//若变短,则末尾超出容器长度的元素被删除
deque.resize(num, elem); //重新指定容器的长度为num, 若容器变长,则以elem填充新位置
//若变短,则末尾超出容器长度的元素被删除
样例:
//deque容器大小操作
void test01()
{
deque<int> d1;
for(int i = 0; i < 10; ++i)
v1.push_back(i);
bool b = di.empty();
int sz = d1.size();
//deque容器中没有容量的概念,可以一直装,只需在中控器中加上新开辟的缓冲区首地址就行
//重新指定大小
d1.resize(15);
d1.resize(15,100);
d1.resize(5);
}
5.deque容器插入和删除:插入和删除提供的位置pos为迭代器
函数原型:
//函数原型
//两端插入操作
push_back(elem); //在容器尾部添加一个数据
push_front(elem); //在容器头部插入一个数据
pop_back(); //删除容器最后一个数据
pop_front(); //删除容器第一个数据
//指定位置操作:插入和删除提供的位置pos为迭代器
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位置的数据,返回下一个数据的位置
样例:
//deque容器插入和删除
void printDeque(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_front(100);
d.push_front(200);
printDeque(d);
//尾删
d.pop_back();
//头删
d.pop_front();
printDeque(d);
}
//插入
void test02()
{
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
printDeque(d);
d.insert(d.begin(), 1000);
printDeque(d);
d.insert(d.begin(), 2,10000);
printDeque(d);
//按区间进行插入
deque<int>d2;
d2.push_back(1);
d2.push_back(2);
d2.push_back(3);
d.insert(d.begin(), d2.begin(), d2.end());
printDeque(d);
}
//删除
void test03()
{
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
printDeque(d);
d.erase(d.begin());
printDeque(d);
d.erase(d.begin(), d.end());
d.clear();
printDeque(d);
总结:插入和删除提供的位置pos为迭代器
6.deque数据存储:
函数原型:
//函数原型
at(int idx); //返回索引idx所指的数据
operator[]; //返回索引idx所指的数据
front(); //返回容器中第一个数据元素
back(); //返回容器中最后一个数据元素
样例:
//deque数据存取
void test01()
{
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
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 << "front:" << d.front() << endl;
cout << "back:" << d.back() << endl;
}
7.deque排序:
算法:
sort(iterator beg, iterator end) //对beg和end区间内元素进行排序
样例:
//deque排序
void printDeque(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(200);
d1.push_back(5);
d1.push_back(30);
d1.push_back(0);
cout << "排序前:" << endl;
printDeque(d1);
// 注意不是d1.sort
// sort函数不属于任何容器,它是STL标准模板库提供的
sort(d1.begin(), d1.end());
cout << "排序后:" << endl;
printDeque(d1);
}
stack容器:
1.stack基本概念:
概念:stack(栈)是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个进出口,即栈顶。所以栈不能进行遍历操作
入栈--数据放入栈中 push
出栈--数据从栈顶取出 pop
stack常用接口:
//常用接口
//构造函数:
stack<T> stk; //stack采用模板类实现,默认构造 stack<int> s;
stack(const stack &stk); //拷贝构造函数
//赋值操作:
stack& operator=(const stack &stk); //重载等号操作符
//数据存取:
push(elem); //向栈顶添加元素
pop(); //从栈顶移除第一个元素
top(); //返回栈顶元素
//大小操作
empty(); //判断堆栈是否为空
size(); //返回栈的大小
样例:
#include <stack>
//栈容器常用接口
void teste1()
{
//创建栈容器 特点:符合先进后出
stack<int> s;
//向栈中添加元素,叫做压栈入栈
s. push(10);
s. push(20);
s. push(30);
//只要栈不为空,查看栈顶,并且执行出栈操作
while (!s. empty())
{
//输出栈顶元素
cout << "栈顶元素为:"<< s.top() << endl;
//弹出栈顶元素
s.pop();
}
cout <<“栈的大小为:”<< s.size() << endl;
}
queue容器:
概念:
1.Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口:队头和队尾。
2.从一端新增元素,只能另一端移除元素。
3.只有队头和队尾才可以被外界访问,因此队列不允许有遍历行为。
queue常见操作:
//queue常见操作
//构造函数:
queue<T> que; //默认构造函数
queue(const queue &que); //拷贝构造函数
//赋值操作:
queue& operator=(const queue &que); //重载等号操作符
数据存取:
push(elem); //往队尾添加元素
pop(); //从队头移除第一个元素
back(); //返回最后一个元素
front(); //返回第一个元素
//大小操作:
empty(); //判断堆栈是否为空
size(); //返回栈的大小
list容器:
1.list基本概念:
功能:将数据进行链式存储
链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
链表的组成:链表由一系列结点组成
结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域
STL中的链表是一个双向循环链表
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,不支持跳跃式访问。属于双向迭代器
list的优点:
1.采用动态存储分配,不会造成内存浪费和溢出
2.链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
list的缺点:
1.链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大
List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。
2.list构造函数:
功能描述:创建list容器
函数原型:
//函数原型
list<T> lst; //list采用采用模板类实现,对象的默认构造形式。
list(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem); //构造函数将n个elem拷贝给本身。
list(const list &lst); //拷贝构造函数。
样例:
//list构造函数
// 遍历函数
void printList(const list<int>& L) {
for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
void test01() {
//1.第一种方式,默认构造
list<int> lis1;
// 添加数据
lis1.push_back(10);
lis1.push_back(20);
lis1.push_back(30);
lis1.push_back(40);
//遍历容器
printList(lis1);
cout << endl;
// 2.第二种方式,n个元素
list<int> lis2(10, 20);
printList(lis2);
cout << endl;
// 3.第三种方式,利用区间
list<int> lis3(lis2.begin(), lis2.end());
printList(lis3);
cout << endl;
// 4.第四种方式,拷贝构造
list<int> lis4(list2);
printList(lis4);
cout << endl;
}
3.list赋值和交换:
功能描述:给list容器进行赋值,以及交换list容器
函数原型:
//函数原型
assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem); //将n个elem拷贝赋值给本身。
list& operator=(const list &lst); //重载等号操作符
swap(lst); //将lst与本身的元素互换。
样例:
//list赋值和交换
// 打印函数
void PrintList(const list<int>& lis)
{
for (list<int>::const_iterator it = lis.begin(); it != lis.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
void test01()
{
list<int> lis1;
list1.push_back(10);
list1.push_back(20);
list1.push_back(30);
list1.push_back(40);
PrintList(lis1);
// 第一种方式,利用assign函数将10个100赋给容器
list<int> lis2;
lis2.assign(10, 100);
PrintList(lis2);
// 第二种方式,利用assign函数将一个区间的值赋给容器
lis2.assign(lis1.begin(), lis1.end());
PrintList(lis2);
// 第三种方式,利用重载等号赋值操作
list<int> lis3 = lis1;
PrintList(lis3);
}
// 交换函数
void test02()
{
list<int> lis1;
list1.push_back(10);
list1.push_back(20);
list1.push_back(30);
list1.push_back(40);
lis2.assign(10, 100);
cout << "lis2容器交换前的元素值为:" << endl;
PrintList(lis2);
cout << "lis1容器交换前的元素值为:" << endl;
PrintList(lis1);
lis2.swap(lis1);
cout << "lis2容器与lis1容器交换后的元素值为:" << endl;
PrintList(lis2);
cout << "lis1容器与lis2容器交换后的元素值为:" << endl;
PrintList(lis1);
}
4.list 大小操作:
函数原型:
//函数原型
size(); //返回容器中元素的个数
empty(); //判断容器是否为空
resize(num); //重新指定容器的长度为num,若容器变长,则以默认值0来填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除
样例:
//list 大小操作
void test01()
{
list<int> lis1;
list1.push_back(10);
list1.push_back(20);
list1.push_back(30);
list1.push_back(40);
bool is_emp = list1.empty();
int sz = list1.size();
// 重新指定大小
lis1.resize(10, 100);
lis1.resize(2);
}
5.list 插入和删除:
函数原型:
//函数原型
//注意插入函数中的pos得用迭代器,而且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值匹配的元素。
样例:
//list 插入和删除
void test01()
{
list<int> list1;
//尾插
list1.push_back(10);
list1.push_back(20);
list1.push_back(30);
//头插
list1.push_front(100);
list1.push_front(200);
list1.push_front(300);
//尾删
list1.pop_back();
//头删
list1.pop_front();
//insert插入
list<int>::iterator it = list1.begin();
list1.insert(++it, 1000);
//删除
it = list1.begin();
list1.erase(++it);
//移除 按值删除 删除list中所有与传入的参数相同的值
list1.push_back(10000);
list1.push_back(10000);
list1.push_back(10000);
list1.remove(10000);
//清空
list1.clear();
}
6.list 数据存取:
函数原型:
//函数原型
front(); //返回第一个元素。
back(); //返回最后一个元素。
样例:
//list 数据存取
void test01()
{
list<int> list1;
list1.push_back(10);
list1.push_back(20);
list1.push_back(30);
int fro_data = list1.front();
int bac_data = list1.back();
//迭代器不支持随机访问
list<int>::iterator = list1.begin();
it++;//没问题
//it = it + 1;//报错,因为list的迭代器不支持随机访问
it--;//没问题,因为是双向迭代器
}
总结:list容器中不可以通过[]或者at方式访问数据,因为数据结构为链表。
7.list 反转和排序:
注意:
1.List不支持algorithm库中的排序算法,因为List容器的迭代器不能跳跃式访问
2.list容器的公共接口里有排序函数
3.所有不支持随机访问迭代器的容器,不可以用标准算法,不支持随机访问迭代器的容器,内部会提供对应一些算法
功能描述:将容器中的元素反转,以及将容器中的数据进行排序
函数原型:
//函数原型
list.reverse(); //反转链表
list.sort(); //链表排序,默认排序规则从小到大,升序
样例:
//list反转和排序
bool cmp(const int a, const int b)
{
return a > b;
}
void test01()
{
list<int> lst;
lst.push_back(50);
lst.push_back(20);
lst.push_back(40);
lst.push_back(10);
lst.push_back(30);
//反转
lst.reverse();
//排序
//所有不支持随机访问迭代器的容器,不可以用标准算法
//sort(lst.begin(), lst.end());//发生异常
//不支持随机访问迭代器的容器,内部会提供对应一些算法
//如果是自定义数据类型,使用排序算法时一定要提供自定义排序方式cmp
lst.sort();//默认升序
lst.sort(cmp);//自定义排序方式cmp, 此例中是 降序
}
set/multiset容器:
1.概念:
简介:插入数据的同时自动排好顺序
本质:set/multiset属于关联式容器。底层结构是二叉树实现。
set和multiset区别:
1.set不允许容器中有重复的元素。
2.multiset允许容器中有重复的元素。
2.set构造和赋值
函数原型:
//函数原型
//构造:
set<T> st; //默认构造函数:set<int>s3;
set(const set &st); //拷贝构造函数set<int>s2(s1);
//赋值:
set& operator=(const set &st); //重载等号操作符s3 = s2;
//插入数据:
insert(elem); //插入数据只有insert方法 set.insert(10);
3.set大小和交换:
函数原型:
//函数原型
//无法重新指定大小,没有resize()方法
size(); //返回容器中元素的数目
empty(); //判断容器是否为空
swap(st); //交换两个集合容器
4.插入和删除:
函数原型:
//函数原型:
insert(elem); //在容器中插入元素。
clear(); //清除所有元素
erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem); //删除容器中值为elem的元素。
5.查找和统计:
函数原型:
//函数原型
find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end()
count(key); //统计key的元素个数,set不允许重复,结果只能为0或1
6.set和multiset的区别:
区别:
1.set不可以插入重复数据,而multiset可以
2.set插入数据的同时会返回插入结果,表示插入是否成功
3.multiset不会检测数据,因此可以插入重复数据
//
set<int> s;
//set.insert()返回的类型为 pair< set<int>::iterator , bool > ,前值表示插入的位置,是一个迭代器类型;后值表示插入是否成功
pair<set<int>::iterator, bool> res = s.insert(10);
//multiset.insert() 返回的类型为 multiset<int>::iterator
multiset<int> ms;
multiset<int>::iterator m_res = ms.insert(10);
set排序:
规则:默认从小到大
主要技术点:利用仿函数,可以改变排序规则
内置数据类型:
//内置数据类型
class my_cmp
{
public:
bool operator()(int v1, int v2)
{
return v1 > v2;
}
};
void test01()
{
set<int> s1;
s1.insert(10);
s1.insert(50);
s1.insert(60);
s1.insert(30);
s1.insert(20);
//打印
print_set(s1);
//指定排序规则 为 从大到小
//指定排序规则需要在插入数据之前
set<int, my_cmp> s2;
s2.insert(10);
s2.insert(50);
s2.insert(60);
s2.insert(30);
s2.insert(20);
//遍历时 迭代器也要变为 set<int, my_cmp>::iterator 输出数据仍然为 cout<<*it<<" ";
}
自定义数据类型:
//自定义数据类型
class Person
{
public:
Person(string name, int age)
{
p_name = name;
p_age = age;
}
stirng p_name;
int p_age;
};
class my_cmp
{
public:
bool operator()(const Person& p1, const Person& p2)
{
//按年龄升序
return p1.p_age < p2.p_age;
}
};
void test01()
{
//自定义数据类型,都要定义排序规则
set<Person, my_cmp> s;
Person p1("刘备",20);
Person p2("关羽",28);
Person p3("张飞",26);
Person p4("赵云",21);
s.insert(p1);
s.insert(p2);
s.insert(p3);
s.insert(p4);
//未定义排序规则之前会导致异常,无法输出
for(set<Person, my_cmp>::iterator it = s.begin(); s != s.end(); it++)
cout<<"姓名:"<<(*it).p_name<<"\t"<<"年龄:"<<(*it).p_age<<endl;
}
pair对组:
函数原型:
//函数原型:
//创建
pair<type, type> p ( value1, value2 ); //默认构造 value1 value2赋初值
pair<type, type> p = make_pair( value1, value2 ); //利用make_pair
//访问
p.first 和 p.second; //如cout<<p.first;
//赋值:
pair<type, type> p = p1; //operator=
map/multimap容器:
1.基本概念:
简介:
1.map中所有元素都是pair,数据成对出现
2.pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
3.所有元素都会根据元素的键值自动排序
本质:
map/multimap属于关联式容器,底层结构是用二叉树实现。
优点:
可以根据key值快速找到value值
map与multimap区别:
map与multimap使用方法一致,但是map和multimap也有区别:
1.map不允许容器中有重复key值元素,但是value值可以重复
2.multimap允许容器中有重复key值元素,也允许value值重复
map/multimap容器的迭代器也不支持随机访问
2.map构造和赋值:
函数原型:
//函数原型
//构造:
map<T1, T2> mp; //map默认构造函数:
map(const map &mp); //拷贝构造函数
//赋值:
map& operator=(const map &mp); //重载等号操作符
样例:
//map构造和赋值
// 打印函数
void printMap(map<int,int> &m)
{
for(map<int,int>::iterator it=m.begin();it!=m.end();it++)
{
cout << "Key值:" << it->first << " " << "数值:" << it->second << endl;
}
cout<<endl;
}
void test01() {
// 默认构造
map<int, int> m1;
// 注意插入的时候一定要用对组
//map容器不允许有重复的Key值元素!和set容器一样编译器并不会报错,但最后还是相当于没插这个数.
m1.insert(pair<int, int>(1, 10));
m1.insert(pair<int, int>(4, 40));
m1.insert(pair<int, int>(2, 20));
m1.insert(pair<int, int>(3, 30));
printMap(m1);
// 拷贝构造
map<int, int> m2(m1);
printMap(m2);
// 等号赋值
map<int, int>m3;
m3 = m2;
printMap(m3);
}
3.map大小和交换:
函数原型:
//函数原型
size(); //返回容器中元素的数目
empty(); //判断容器是否为空
swap(st); //交换两个集合容器
4.map插入和删除:
函数原型:
//函数原型:
insert(elem); //在容器中插入元素。
clear(); //清除所有元素
erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end); //删除迭代器区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(key); //删除容器中键值为key的元素。
样例:
//map插入和删除
void test01()
{
map<int, int> m;
// 插入方法,第一种,利用对组
m.insert(pair<int, int>(1, 10));
// 第二种插入方法,make_pair
m.insert(make_pair(2, 20));
// 第三种插入方法,::value_type,不推荐
m.insert(map<int, int>::value_type(3, 30));
// 第四种
m[4] = 40;
// 直接打印cout<<m[5],会为容器创建一个键值为5 value为0的元素
cout<<m[5]<<endl;
// 删除
m1.erase(6);//按key值来删除
m1.erase(m1.begin());//迭代器
m1.erase(m1.begin(),m1.end());//迭代器
// 清空
m2.clear();
}
5.map查找和统计:
函数原型:
//函数原型
find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
//map<int, int>::iterator pos = map.find(key);
//key: (*pos).first, value: (*pos).second
count(key); //统计key的元素个数,对于map容器。元素个数要么为0,要么为1
6.map容器排序:
因为map是根据key值进行排序的,所以更改的排序规则针对的是key值。必须在容器插入数据前就给出排序规则。
主要技术点:利用仿函数,可以改变排序规则
样例:
//map排序
class my_cmp
{
public:
// 修改内置数据类型排序为降序
bool operator()(int v1, int v2)
{
return v1 > v2;
}
};
void test01()
{
// 创建map容器
map<int, int, my_cmp> 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));
}
函数对象:
1.函数对象概念:
概念:
1.重载函数调用操作符的类,其对象也称为函数对象
2.函数对象使用重载()时,行为类似函数调用,也叫仿函数
本质:函数对象(仿函数)是一个类,不是一个函数。
2.函数对象使用:
1.函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
2.函数对象超出普通函数的概念,函数对象可以有自己的状态
3.函数对象可以作为参数传递
样例:
// 函数对象(仿函数)
class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};
//1.函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
void test01()
{
MyAdd myadd;
cout << myadd(10, 10) << endl;
}
//2.函数对象超出普通函数的概念,函数对象可以有自己的状态
class MyPrint
{
public:
MyPrint()
{
this->count = 0;
}
void operator()(string test)
{
cout << test << endl;
this->count++;
}
int count;//内部自己状态
};
void test02()
{
MyPrint myprint;
myprint("hello world");
myprint("hello world");
myprint("hello world");
myprint("hello world");
myprint("hello world");
myprint("hello world");
cout << "MyPrint调用次数为:" << myprint.count << endl;
}
//3.函数对象可以作为参数传递
void doPrint(MyPrint& mp, string test)
{
mp(test);
}
void test03()
{
MyPrint myPrint;
doPrint(myPrint, "hello c++");
}
谓词:
谓词概念:
1.返回bool类型的仿函数称为谓词
2.如果operator()接受一个参数,那么叫做一元谓词
3.如果operator()接收两个参数,那么叫做二元谓词
一元谓词:
//一元谓词
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);
}
//查找容器中,有没有大于5的数字
//GreaterFive()匿名函数对象
vector<int>::iterator pos = find_if(v.begin(), v.end(), CreaterFive());
if (pos == v.end())
{
cout << "未找到" << endl;
}
else
{
cout << "找到了,大于5的数字为:" << *pos << endl;
}
}
二元谓词:
//二元谓词
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());
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
内建函数对象:
1.内建函数对象意义:
概念:STL内建了一些函数对象
分类:
1.算数仿函数
2.关系仿函数
3.逻辑仿函数
用法:
1.这些仿函数所产生的对象,用法和一般函数完全相同
2.使用内建函数对象,需要引入头文件#include< functional>
2.算数仿函数:
功能描述:
1.实现四则运算
2.其中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> // 取反仿函数
样例:
//算数仿函数
void test01()
{
// negate 一元仿函数 取反函数
negate<int> n;
cout << n(50) << endl;// -50
// plus 二元仿函数 加法
plus<int> p;
cout << p(10, 20) << endl;// 30
}
3.关系仿函数:
功能描述:实现关系对比
仿函数原型:
//仿函数原型
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> // 小于等于
样例:
//关系仿函数
//大于
class MyCompare
{
public:
bool operator()(int val1, int val2)
{
return val1 > val2;
}
};
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 << endl;
//降序
// sort(v.begin(), v.end(), MyCompare());
//利用 greater<type>() 内建函数对象 大于仿函数
sort(v.begin(), v.end(), greater<int>());
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
4.逻辑仿函数:
功能描述:实现逻辑运算
仿函数原型:
//仿函数原型
template<class T> bool logical_ and<T> // 逻辑与
template<class T> bool logical_or<T> // 逻辑或
template<class T> bool logical_not<T> // 逻辑非
样例:
//逻辑仿函数
逻辑非 logical_not
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 << endl;
// 利用逻辑非 将容器 v 搬运到 v2 中,并且执行取反操作
vector<bool> v2;
v2.resize(v.size());
//transform 搬运函数,应用时目标容器一定要有足够的空间,前两个参数为原容器的迭代器,第三个参数为目标容器的迭代器,最后一个参数为仿函数,即需要执行的操作。
transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());
for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
STL常用算法:
概述:
1.算法主要是由头文件< algorithm >< functional > < numeric >组成
2.< algorithm >是所有STL头文件中最大的一个,范围涉及到比较、交换、查找、遍历操作、复制、修改等等。
3.< functional >定义了一些模板类,用以声明函数对象
4.< numeric >体积很小,只包括几个在序列上面进行简单数据运算的模板函数
1.常用遍历算法:
算法简写:
//常用遍历算法
for_each //遍历容器
transform //搬运容器中的数据至另一个容器中
for_each:
功能描述:实现遍历容器
函数原型:
//函数原型:
for_each(iterator beg, iterator end, _func);
//beg:开始迭代器
//end:结束迭代器
//_func:普通函数或者函数对象(仿函数)
样例:
//for_each
//普通函数
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;
}
transform:
功能描述:搬运容器中的数据至另一个容器中
函数原型:
//函数原型:
transform(iterator beg1, iterator end1, iterator beg2, _func);
//beg1:源容器开始迭代器
//end1:源容器结束迭代器
//beg2:目标容器开始迭代器
//_func:函数或者函数对象,在搬运过程中对数据做的操作。
样例:
//transform
//普通函数,将元素搬运过去并+100
int Transform(int val)
{
return val + 100;
}
//仿函数
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(), print02());
cout << endl;
vector<int> v2;//目标容器
v2.resize(v.size()); //目标容器必须先开辟空间
transform(v.begin(), v.end(), v2.begin(), Transform);
for_each(v2.begin(), v2.end(), print02());
cout << endl;
}
2.常用查找算法:
学习目标:掌握常用的查找算法
算法简介:
//算法简介:
find //查找元素
find_if //按条件查找元素
adjacent_find //查找相邻重复元素
binary_search //二分查找法
count //统计元素个数
count_if //按条件统计元素个数
find:
功能描述: 查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()
函数原型:
//函数原型
find(iterator beg, iterator end, value);
//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
//beg:开始迭代器
//end:结束迭代器
//value:查找的元素
样例:
//find
//使用find查找内置数据类型
void test01()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
//查找容器中 5 是否存在
vector<int>::iterator it = find(v1.begin(), v1.end(), 5);
if (it == v1.end())
{
cout << "没找到" << endl;
}
else
{
cout << "找到 值为:" << *it << endl;
}
}
//使用find查找自定义数据类型
class Person
{
public:
Person(string name, int age)
{
this->m_Age = age;
this->m_Name = name;
}
//重载 == ,让底层find知道如何对比Person数据类型
bool operator==(const Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
return true;
else
return false;
}
int m_Age;
string m_Name;
};
void test01()
{
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
Person p4("ddd", 40);
Person p5("eee", 50);
vector<Person> v;
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
//若为自定义数据类型,必须重载 == 让底层find知道如何对比自定义的数据类型
vector<Person>::iterator it = find(v.begin(), v.end(), p3);
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类型的仿函数)
样例:
//find_if
//测试内置数据类型和自定义数据类型的find_if功能
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 << "找到了" << *it << endl;
}
}
//自定义数据类型
class Person
{
public:
Person(string name,int age)
{
this->m_Age = age;
this->m_Name = name;
}
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("1",11);
Person p2("2",22);
Person p3("3",33);
Person p4("4",44);
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:
功能描述: 查找相邻重复元素
函数原型:
//函数原型:
adjacent_find(iterator beg, iterator end); //查找相邻重复元素,返回相邻元素的第一个位置的迭代器
//beg:开始迭代器
//end:结束迭代器
样例:
//adjancent_find
void test01()
{
vector<int> v1;
v1.push_back(0);
v1.push_back(1);
v1.push_back(3);
v1.push_back(0);
v1.push_back(4);
v1.push_back(4);
v1.push_back(6);
v1.push_back(7);
vector<int>::iterator it = adjacent_find(v1.begin(), v1.end());
if (it == v1.end())
{
cout << "没找到" << endl;
}
else
{
cout << "找到 元素为:" << *it << endl;
}
}
binary_search:
功能描述: 查找指定元素是否存在
函数原型:
//函数原型
//注意: 在无序序列中不可用
bool binary_search(iterator beg, iterator end, value);
//查找指定的元素,查到 返回true 否则false
//beg:开始迭代器
//end:结束迭代器
//value:查找的元素
样例:
// binary_search
void test01()
{
vector<int> v;
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
bool ret = binary_search(v.begin(), v.end(), 2);
if (ret == 0)
{
cout << "没找到" << endl;
}
else
{
cout << "找到" << endl;
}
}
count:
功能描述: 统计元素个数
函数原型:
//函数原型:
count(iterator beg, iterator end, value); //统计元素出现次数
//beg:开始迭代器
//end:结束迭代器
//value:统计的元素
样例:
//count
//内置数据类型
void test01()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(4);
v.push_back(5);
v.push_back(3);
v.push_back(4);
v.push_back(4);
int num = count(v.begin(), v.end(), 4);
cout << "4的个数为: " << 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("刘备", 35);
Person p2("关羽", 35);
Person p3("张飞", 35);
Person p4("赵云", 30);
Person p5("曹操", 25);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
Person p("诸葛亮", 35);
int num = count(v.begin(), v.end(), p);
cout << "和诸葛亮同岁的人数为:" << num << endl;
}
count_if:
功能描述: 按条件统计元素个数
函数原型:
//函数原型:
count_if(iterator beg, iterator end, _Pred); //按条件统计元素出现次数
//beg:开始迭代器
//end:结束迭代器
//_Pred:谓词
样例:
//count_if
class Greater4
{
public:
bool operator()(int val)
{
return val >= 4;
}
};
//内置数据类型
void test01()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(4);
v.push_back(5);
v.push_back(3);
v.push_back(4);
v.push_back(4);
int num = count_if(v.begin(), v.end(), Greater4());
cout << "大于4的个数为: " << 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 AgeLess35
{
public:
bool operator()(const Person& p)
{
return p.m_Age < 35;
}
};
void test02()
{
vector<Person> v;
Person p1("刘备", 35);
Person p2("关羽", 35);
Person p3("张飞", 35);
Person p4("赵云", 30);
Person p5("曹操", 25);
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(), AgeLess35());
cout << "小于35岁的个数:" << num << endl;
}
3.常用排序算法:
学习目标: 掌握常用的排序算法
算法简介:
//算法简介:
sort() //对容器内元素进行排序
random_shuffle() //洗牌 指定范围内的元素随机调整次序
merge() // 容器元素合并,并存储到另一容器中
reverse() // 反转指定范围的元素
sort:
功能描述: 对容器内元素进行排序
函数原型:
//函数原型:
sort(iterator beg, iterator end, _Pred);
//对容器内数据进行排序,第三个参数缺省时默认为升序
//beg:开始迭代器
//end:结束迭代器
//_Pred:谓词
样例:
//sort
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默认从小到大排序
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);
cout << endl;
}
random_shuffle:
功能描述: 洗牌 指定范围内的元素随机调整次序
函数原型:
//函数原型:
random_shuffle(iterator beg, iterator end); //指定范围内的元素随机调整次序
//beg:开始迭代器
//end:结束迭代器
样例:
//random_shuffle:
class myPrint
{
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);
}
for_each(v.begin(), v.end(), myPrint());
cout << endl;
//打乱顺序
random_shuffle(v.begin(), v.end());
for_each(v.begin(), v.end(), myPrint());
cout << endl;
}
merge:
功能描述: 两个容器元素合并,并存储到另一容器中。
函数原型:
//函数原型:
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
//容器元素合并,并存储到另一容器中
//注意: 两个容器必须是有序的
//beg1:容器1开始迭代器
//end1:容器1结束迭代器
//beg2:容器2开始迭代器
//end2:容器2结束迭代器
//dest:目标容器开始迭代器
样例:
//merge:
//merge合并的两个容器必须是有序序列
class myPrint
{
public:
void operator()(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> vtarget;
//目标容器需要提前开辟空间
vtarget.resize(v1.size() + v2.size());
//合并 需要两个有序序列
merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vtarget.begin());
for_each(vtarget.begin(), vtarget.end(), myPrint());
cout << endl;
}
总结:merge合并的两个容器必须是有序序列
reverse:
功能描述: 将容器内元素进行反转
函数原型:
//函数原型:
reverse(iterator beg, iterator end); //反转指定范围的元素
//beg:开始迭代器
//end:结束迭代器
样例:
//reserve
class myPrint
{
public:
void operator()(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(), myPrint());
cout << endl;
cout << "反转后: " << endl;
reverse(v.begin(), v.end());
for_each(v.begin(), v.end(), myPrint());
cout << endl;
}
4.常用拷贝和替换算法:
学习目标: 掌握常用的拷贝和替换算法
算法简介:
//算法简介:
copy() // 容器内指定范围的元素拷贝到另一容器中
replace() // 将容器内指定范围的旧元素修改为新元素
replace_if() // 容器内指定范围满足条件的元素替换为新元素
swap() // 互换两个容器的元素
copy:
功能描述: 将容器内指定范围的元素拷贝到另一容器中
函数原型:
//函数原型:
copy(iterator beg, iterator end, iterator dest);
//将容器内指定范围的元素拷贝到另一容器中
//beg:开始迭代器
//end:结束迭代器
//dest:目标起始迭代器
样例:
//copy
class myPrints
{
public:
void operator()(int val)
{
cout << val << " ";
}
};
void test01()
{
vector<int> v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i + 1);
}
vector<int> v2;
v2.resize(v1.size());
copy(v1.begin(), v1.end(), v2.begin());
for_each(v2.begin(), v2.end(), myPrints());
cout << endl;
}
replace:
功能描述: 将容器内指定范围的旧元素修改为新元素
函数原型:
//函数原型
replace(iterator beg, iterator end, oldvalue, newvalue);
//将区间内旧元素 替换成 新元素
//beg 开始迭代器
//end结束迭代器
//oldvalue旧元素
//newvalue新元素
样例:
//replace
class myPrint
{
public:
void operator()(int val)
{
cout << val << " ";
}
};
void test01()
{
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(50);
v.push_back(20);
v.push_back(40);
v.push_back(70);
for_each(v.begin(), v.end(), myPrint());
cout << endl;
//替换
replace(v.begin(), v.end(), 20, 2000);
for_each(v.begin(), v.end(), myPrint());
}
replace_if:
功能描述: 将区间内满足条件的元素,替换成指定元素
函数原型:
//函数原型:
replace_if(iterator beg, iterator end, _pred, newvalue);
//按条件替换元素,满足条件的替换成指定元素
//beg:开始迭代器
//end:结束迭代器
//_pred:谓词
//newvalue:替换的新元素
样例:
//replace_if:
class myPrint
{
public:
void operator()(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(20);
v.push_back(50);
v.push_back(20);
v.push_back(40);
v.push_back(70);
for_each(v.begin(), v.end(), myPrint());
cout << endl;
//把大于等于30的替换为3000
replace_if(v.begin(), v.end(), Greater30(), 3000);
for_each(v.begin(), v.end(), myPrint());
}
swap:
功能描述: 互换两个容器的元素
函数原型:
//函数原型:
swap(container c1, container c2); //互换两个容器的元素
//c1:容器1
//c2:容器2
样例:
//swap:
class myPrint
{
public:
void operator()(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(), myPrint());
cout << endl;
for_each(v2.begin(), v2.end(), myPrint());
cout << endl;
cout << "交换后: " << endl;
swap(v1, v2);
for_each(v1.begin(), v1.end(), myPrint());
cout << endl;
for_each(v2.begin(), v2.end(), myPrint());
cout << endl;
}
5.常用算术生成算法:
学习目标: 掌握常用的算术生成算法
注意: 算术生成算法属于小型算法,使用时包含的头文件为#include<numeric>
算法简介:
//算法简介:
accumulate(); // 计算容器元素累计总和
fill(); // 向容器中添加元素
accumulate:
功能描述: 计算区间内容器元素累计总和
函数原型:
//函数原型:
accumulate(iterator beg, iterator end, value); //计算容器元素累计总和
//beg:开始迭代器
//end:结束迭代器
//value:起始值(起始的累加值,将后续所有的值都加到这里)
样例:
//accumulate:
void test01()
{
vector<int> v;
for (int i = 0; i <= 100; i++) {
v.push_back(i);
}
int a = accumulate(v.begin(), v.end(), 0);
//参数3:起始累加值
int b = accumulate(v.begin(), v.end(), 1000);
cout << "a = " << a << ", b = " << b << endl;
}
fill:
功能描述: 向容器中填充指定的元素
函数原型:
//函数原型
fill(iterator beg, iterator end, value); //向容器中填充元素
//beg:开始迭代器
//end:结束迭代器
//value:填充的值
样例:
//fill
class myPrint
{
public:
void operator()(int val)
{
cout << val << " ";
}
};
void test01()
{
vector<int> v;
v.resize(10);
//填充
fill(v.begin(), v.end(), 100);
for_each(v.begin(), v.end(), myPrint());
cout << endl;
}
6.常用集合算法:
学习目标: 掌握常用的集合算法
算法简介:
//算法简介:
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:目标容器开始迭代器
样例:
//set_intersection:
void myPrint(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);
}
for_each(v1.begin(), v1.end(), myPrint);
cout << endl;
for_each(v2.begin(), v2.end(), myPrint);
cout << endl;
vector<int> v3;
//容器大小为这两个容器中最小的那个
v3.resize(min(v1.size(), v2.size()));
//获取交集 返回值为交集中最后一个元素的位置
vector<int>::iterator End = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
//注意第二个参数为 End,因为目标容器的 size() 很有可能比交集大,如果为 v3.end(),后面会输出0
for_each(v3.begin(), End, myPrint);
cout << endl;
}
set_union:
功能描述: 求两个集合的并集
函数原型:
//函数原型:
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
//求两个集合的并集,注意:两个集合必须是有序序列。
//beg1:容器1开始迭代器
//end1:容器1结束迭代器
//beg2:容器2开始迭代器
//end2:容器2结束迭代器
//dest:目标容器开始迭代器
样例:
void myPrint(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);
}
for_each(v1.begin(), v1.end(), myPrint);
cout << endl;
for_each(v2.begin(), v2.end(), myPrint);
cout << endl;
vector<int> v3;
//新容器大小为之前两个容器大小之和
v3.resize(v1.size() + v2.size());
vector<int>::iterator End = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
for_each(v3.begin(), End, myPrint);
cout << endl;
}
set_difference:
功能描述: 求两个集合的差集
函数原型:
//函数原型:
//求两个集合的差集,注意:两个集合必须是有序序列。
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
//得到的结果为 容器1 - 容器2 , 即容器1中有而容器2中没有的数据
//beg1:容器1开始迭代器
//end1:容器1结束迭代器
//beg2:容器2开始迭代器
//end2:容器2结束迭代器
//dest:目标容器开始迭代器
样例:
//set_difference:
void myPrint(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);
}
for_each(v1.begin(), v1.end(), myPrint);
cout << endl;
for_each(v2.begin(), v2.end(), myPrint);
cout << endl;
vector<int> v3;
v3.resize(max(v1.size(),v2.size()));
//v1和v2的差集
vector<int>::iterator End = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
for_each(v3.begin(), End, myPrint);
cout << endl;
//v2和v1的差集
End = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), v3.begin());
for_each(v3.begin(), End, myPrint);
cout << endl;
}