目录
本阶段主要针对泛型编程和STL技术作详细讲解。
1 模板
1.1 模板的概念
C++提供一种泛型编程,主要利用模板的技术,模板的目的是提高代码复用性,将类型参数化。
C++提供两种模板机制:
- 函数模板
- 类模板
模板的特点:
- 模板不可用直接使用,它只是一个框架;
- 模板的通用表示万能的,也是由使用范围的。
1.2 函数模板
函数模板的作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,而是用一个虚拟的类型来代表。
语法:
template<typename/class T>
函数
template:声明创建模板
typename/class:表明其后面的符号是一种数据类型
T:通用的数据类型,该名称可以替换,通常为大写字母
1.2.1 函数模板的创建与使用
语法:
//声明一个模板,告诉编译器后面紧跟的代码中的T不要报错,T是一个通用类型
template<typename T>
//模板使用的两种方式:1.自动类型推导;2.显示指定类型
//1.自动类型推导
mySwap(a,b);//2.显示指定类型
mySwap<int>(a,b);
码如下:
#include <iostream>
using namespace std;
//函数模板
//交换两个整形的函数
void swapInt(int a,int b)
{
int temp=a;
a=b;
b=temp;
}
//交换两个浮点数的函数
void swapDouble(double a,double b)
{
double temp=a;
a=b;
b=temp;
}
//函数模板
template<typename T>//声明一个模板,告诉编译器后面紧跟的代码中的T不要报错,T是一个通用类型
void mySwap(T &a, T &b)
{
T temp=a;
a=b;
b=temp;
}
void test01()
{
int a=4;
int b=10;
//swapInt(a,b);
//利用模板交换,两种方式:1.自动类型推导;2.显示指定类型
//1.自动类型推导
///mySwap(a,b);
//2.显示指定类型
mySwap<int>(a,b);
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
double c=4.66;
double d=3.22;
swapDouble(c,d);
//mySwap(a,c);//错误示例1:自动类型推导,必须推导出一致的数据类型T才能使用
cout<<"c="<<c<<endl;
cout<<"d="<<d<<endl;
}
template<class T>
void func()
{
cout<<"this is func()"<<endl;
}
void test02()
{
//func();//错误示例2:模板必须确定T的数据类型才能使用
}
int main()
{
test01();
test02();
return 0;
}
注意事项:
- 自动类型推导,必须推导出一致的数据类型T才能使用
- 模板必须确定T的数据类型才能使用
错误示例1:
错误示例2:
1.2.2 函数模板案例
案例描述:
- 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
- 排序规则为从大到小,排序算法为选择排序
- 分别利用char数组和int数组进行测试
代码如下:
#include <iostream>
using namespace std;
//函数模板案例:对不同数据类型的数组进行从大到小的选择排序
template<class T>
void mySort(T buff[],int len)
{
for(int i=0;i<len;i++)
{
int max=i;
for(int j=i+1;j<len;j++)
{
if(buff[j]>buff[max])
{
max=j;
}
}
if(max!=i)
{
T temp=buff[i];
buff[i]=buff[max];
buff[max]=temp;
}
}
}
template<class T>
void printArry(T buff,int len)
{
for(int i=0;i<len;i++)
{
cout<<buff[i]<<endl;
}
}
//测试char数组
void test01()
{
char arr[]="abcdef";
int len=sizeof(arr)/sizeof(char);
mySort(arr,len);
printArry(arr,len);
}
//测试int数组
void test02()
{
int arr[]={2,4,6,8,3,1};
int len=sizeof(arr)/sizeof(int);
mySort(arr,len);
printArry(arr,len);
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
写错的地方:
1.选择排序比较两个数的大小是用第j个和最大的数比较if(buff[j]>buff[max]),而不是和第i个比较if(buff[j]>buff[i])。
2.计算数组长度时忘记除以数据类型的长度sizeof(arr)/sizeof(int)。
考究的地方:
向函数传递数组时似乎可以只写数组名void printArry(T buff,int len),而不一定要写中括号void mySort(T buff[ ],int len)。
1.2.3 普通函数与函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 函数模板调用时,如果利用显示指定类型,可以发生隐式类型转换
建议:使用显式指定类型调用函数模板
代码如下:
#include <iostream>
using namespace std;
//普通函数调用时可以发生自动类型转换(隐式类型转换)
//函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
//如果利用显示指定类型的方式,可以发生隐式类型转换
//普通函数
int myadd(int a,int b)
{
return a+b;
}
template<class T>
T myadd2(T a,T b)
{
return a+b;
}
void test01()
{
int a=3;
int b=4;
char c='c';
cout<<myadd(a,b)<<endl;
//普通函数调用时可以发生自动类型转换(隐式类型转换)
cout<<myadd(a,c)<<endl;//将字符C隐式地转换为了整形
cout<<myadd2(a,b)<<endl;
//函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
//cout<<myadd2(a,c)<<endl;
//如果利用显示指定类型的方式,可以发生隐式类型转换
cout<<myadd2<int>(a,c)<<endl;
}
int main()
{
test01();
return 0;
}
输出如下:
错误示例:调用函数模板时,使用自动类型推导不会发生隐式类型转换
1.2.4 普通函数与函数模板的调用规则
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
代码如下:
#include <iostream>
using namespace std;
//如果函数模板和普通函数都可以实现,优先调用普通函数
//可以通过空模板参数列表来强制调用函数模板
//函数模板可以发生重载
//如果函数模板可以产生更好的匹配,优先调用函数模板
//普通函数
void myprint(int a,int b)
{
cout<<"普通函数"<<endl;
}
template<class T>
void myprint(T a,T b)//函数重载
{
cout<<"函数模板"<<endl;
}
template<class T>
void myprint(T a,T b,T c)//函数模板重载
{
cout<<"函数模板重载"<<endl;
}
void test01()
{
int a=3;
int b=4;
//如果函数模板和普通函数都可以实现,优先调用普通函数
myprint(a,b);
//可以通过空模板参数列表来强制调用函数模板
myprint<>(a,b);
//函数模板可以发生重载
myprint(a,b,18);
//如果函数模板可以产生 更好的匹配 ,优先调用函数模板
char c1='a';
char c2='b';
myprint(c1,c2);
}
int main()
{
test01();
return 0;
}
输出如下:
错误示例:普通函数没有实现,只有定义,调用时仍然不会调用函数模板且报错。
1.2.5 模板的局限性
模板的通用性表示万能的。
template<class T>
void f(T a, T b)
{
a=b;}
例1:在上述代码中提供的赋值操作,如果传入的a和b是一个数组(数组之间无法赋值),就无法实现。
template<class T>
void f(T a, T b)
{
if(a>b){...}
}
例2:在上述代码中,如果T的数据类型传入的是像person这样自定义的数据类型(自定义的数据类型不知道如何比较),也无法正常运行。
解法1:运算符重载
解法2:对自定义的数据类型提供一个具体的版本的函数模板
因此,C++提供模板的重载,为特定的类型提供具体化的模板,来解决上述问题。
代码如下:
#include <iostream>
using namespace std;
#include <string>
//自定义数据类型
class Person
{
public:
Person(string name,int age)
{
this->m_Age=age;
this->m_Name=name;
}
string m_Name;
int m_Age;
};
//对比两个数是否相等的函数
template<class T>
bool mycompare(T &a,T &b)
{
if(a==b)
{
return true;
}
else
{
return false;
}
}
void test01()
{
int a=10;
int b=20;
bool ret=mycompare(a,b);
if(ret)
{
cout<<"a=b"<<endl;
}
else
{
cout<<"a!=b"<<endl;
}
}
//为person写一个具体的版本
template<> bool mycompare(Person &p1,Person &p2)
{
if(p1.m_Name==p2.m_Name && p1.m_Age==p2.m_Age)
{
return true;
}
else
{
return false;
}
}
void test02()
{
Person p1("tom",10);
Person p2("tom",10);
bool ret=mycompare(p1,p2);
if(ret)
{
cout<<"p1=p2"<<endl;
}
else
{
cout<<"p1!=p2"<<endl;
}
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
1.3 类模板
1.3.1 类模板基本语法
类模板作用:建立一个通用类,类中的成员 数据类型可以不具体指定,而是用一个虚拟的类型代表。
语法:
template<typename T>
类
代码如下:
#include <iostream>
using namespace std;
//类模板
template <class NameType,class AgeType>
class Person
{
public:
Person(NameType name,AgeType age)
{
this->m_Name=name;
this->m_Age=age;
}
void showPerson()
{
cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
Person<string,int> p1("ddd",22);
p1.showPerson();
}
int main()
{
test01();
return 0;
}
输出如下:
1.3.2 类模板成员函数创建时机
- 普通类:成员函数一开始就可以创建
- 类模板:成员函数在调用时创建
代码如下:
#include <iostream>
using namespace std;
class Person1
{
public:
void showPerson1()
{
cout<<"Person1"<<endl;
}
};
class Person2
{
public:
void showPerson2()
{
cout<<"Person2"<<endl;
}
};
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();//出错
}
int main()
{
test01();
return 0;
}
输出如下:
错误示例:
1.3.3 类模板对象做函数参数
类模板实例化出的对象,向函数传参的方式:
- 指定传入类型:直接显示对象的数据类型(最常用
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将对象模型,模板化 进行传递
代码如下:
#include <iostream>
using namespace std;
//类模板做函数参数
//类模板
template <class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
void showPerson()
{
cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}
T1 m_Name;
T2 m_Age;
};
//指定传入类型:直接显示对象的数据类型
void printPerson1(Person<string,int> &p)
{
p.showPerson();
}
void test01()
{
Person<string,int> p("ddd",23);
printPerson1(p);
}
//参数模板化:将对象中的参数变为模板进行传递
template <class T1,class T2>
void printPerson2(Person<T1,T2> &p)
{
p.showPerson();
cout<<"T1 type:"<<typeid(T1).name()<<endl;
cout<<"T2 type:"<<typeid(T2).name()<<endl;
}
void test02()
{
Person<string,int> p("qqq",24);
printPerson2(p);
}
// 整个类模板化:将对象模型,模板化 进行传递
template <class T>
void printPerson3(T &p)
{
p.showPerson();
cout<<"T type:"<<typeid(T).name()<<endl;
}
void test03()
{
Person<string,int> p("fff",25);
printPerson3(p);
}
int main()
{
test01();
test02();
test03();
return 0;
}
输出如下:T出数据类型名打印和学习视频中有些出入,应该识别出class Person,string和int。
1.3.4 类模板与继承
当类模板遇到继承时,需要注意以下几点:
- 当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型。
- 如果不指定类型,编译器无法为子类分配内存。
- 如果想灵活指出父类中的T的类型,子类也需要变为类模板。
代码如下:
#include <iostream>
using namespace std;
//类模板与继承
template<class T>
class Base
{
T m;
};
//class Son:public Base//错误示例:没有指定T的数据类型
class Son:public Base<int>
{
};
void test01()
{
Son s1;
}
template<class T1,class T2>
class Son2:public Base<T2>
{
public:
Son2()
{
cout<<"T1的数据类型为:"<<typeid(T1).name()<<endl;
cout<<"T2的数据类型为:"<<typeid(T2).name()<<endl;
}
T1 obj;
};
void test02()
{
Son2<int,char> s2;
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
错误示例:子类继承类模板父类时没有指定数据类型,所以也无法计算Son类占的内存空间大小
1.3.5 类模板成员函数类外实现
类模板中成员函数类外实现时,需要加上模板参数列表。
代码如下:
#include<iostream>
using namespace std;
//类模板成员函数类外实现
template<class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age);
// {
// this->m_Name=name;
// this->m_Age=age;
// }
void showPerson();
// {
// cout<<"name: "<<this->m_Name<<" age: "<<this->m_Age<<endl;
// }
T1 m_Name;
T2 m_Age;
};
//类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
cout<<"name: "<<this->m_Name<<" age: "<<this->m_Age<<endl;
}
void test()
{
Person<string,int> p("ddd",22);
p.showPerson();
}
int main()
{
test();
return 0;
}
输出如下:
1.3.6 类模板分文件编写
类模板中成员函数在调用阶段创建,导致分文件编写时链接不到。
解决方案1:直接包含.cpp源文件
解决方案2:将声明和实现写到同一个文件中,并更改后缀为.hpp,hpp是约定的名称,并不是强制。
解决方案1代码如下:
1-3-6-person.h
#pragma once
#include <iostream>
using namespace std;
template<class T1,class T2>
class Person
{
public:
Person(T1 name ,T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
1-3-6-person.cpp
#include "1-3-6-person.h"
template<class T1,class T2>
Person<T1,T2>::Person(T1 name ,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}
1-3-6.cpp
#include <iostream>
using namespace std;
#include "1-3-6-person.cpp"//解决方案1:成员函数调用时才创建,.h中没有成员函数实现的代码
//类模板分文件编写
void test01()
{
Person<string,int> p("fff",23);
p.showPerson();
}
int main()
{
test01();
return 0;
}
输出如下:
解决方案2代码如下:
1-3-6-person.hpp
#pragma once
#include <iostream>
using namespace std;
template<class T1,class T2>
class Person
{
public:
Person(T1 name ,T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
template<class T1,class T2>
Person<T1,T2>::Person(T1 name ,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}
1-3-6.cpp
#include <iostream>
using namespace std;
#include "1-3-6-person.hpp"//解决方案2:将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件
//类模板分文件编写
void test01()
{
Person<string,int> p("fff",23);
p.showPerson();
}
int main()
{
test01();
return 0;
}
输出如下:
1.3.7 类模板与友元
掌握类模板配合友元函数的类内和类外实现。
- 全局函数类内实现:直接在类内声明友元即可
- 全局函数类外实现:需要提前让编译器知道全局函数的存在
代码如下:
#include <iostream>
using namespace std;
//通过全局函数打印Person信息
//全局函数 类外实现:提前让编译器知道Person类的存在
template<class T1,class T2>
class Person;
//全局函数 类外实现
template<class T1,class T2>
void showPerson2(Person<T1,T2> p)
{
cout<<"类外 name:"<<p.m_Name<<" age:"<<p.m_Age<<endl;
}
template<class T1,class T2>
class Person
{
//全局函数 类内实现
friend void showPerson(Person<T1,T2> p)
{
cout<<"name:"<<p.m_Name<<" age:"<<p.m_Age<<endl;
}
//全局函数 类外实现
//加空模板参数列表<>(声明是普通函数的声明,实现是函数模板的实现,两套系统不一样,因此声明的时候 增加参数列表变成函数模板的函数声明)
friend void showPerson2<>(Person<T1,T2> p);
public:
Person(T1 name ,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
private:
T1 m_Name;
T2 m_Age;
};
//全局函数 类内实现
void test01()
{
Person<string,int> p("ddd",23);
showPerson(p);
}
void test02()
{
Person<string,int> p("fff",25);
showPerson2(p);
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
1.3.8 类模板案例
案例描述:实现一个通用的数组类,要求如下:
- 可以对内置数据类型以及自定义数据类型的数据进行存储·将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
代码如下:
1-3-8-MyArray.hpp
#pragma once
#include <iostream>
using namespace std;
template<class T>
class MyArray
{
public:
//有参构造
MyArray(int capacity)
{
this->m_Capacity=capacity;
this->m_Size=0;
this->pAddress=new T[this->m_Capacity];
cout<<"有参构造"<<endl;
}
//拷贝构造
MyArray(const MyArray& arr)
{
this->m_Capacity=arr.m_Capacity;
this->m_Size=arr.m_Size;
//深拷贝
this->pAddress=new T[arr.m_Capacity];
//将arr中的数据拷贝
for(int i=0;i<this->m_Size;i++)
{
this->pAddress[i]=arr.pAddress[i];
}
cout<<"拷贝构造"<<endl;
}
//=运算符重载 防止浅拷贝的问题(堆区重复释放)
MyArray& operator=(const MyArray& arr)
{
//先判断原堆区是否有数据,若有先释放
if (this->pAddress!=NULL)
{
delete [] this->pAddress;
this->pAddress=NULL;
this->m_Capacity=0;
this->m_Size=0;
}
this->m_Capacity=arr.m_Capacity;
this->m_Size=arr.m_Size;
//深拷贝
this->pAddress=new T[arr.m_Capacity];
//将arr中的数据拷贝
for(int i=0;i<this->m_Size;i++)
{
this->pAddress[i]=arr.pAddress[i];
}
cout<<"operator="<<endl;
return * this;
}
//尾插法
void PushBack(const T& val)
{
//判断容量
if(this->m_Capacity==this->m_Size)
{
return;
}
this->pAddress[this->m_Size]=val;
this->m_Size++;
}
//尾删法
void PopBack()
{
if(this->m_Size==0)
{
return;
}
this->m_Size--;
}
//通过下标访问:重载[]
T& operator[](int index)
{
return this->pAddress[index];
}
int getCap()
{
return this->m_Capacity;
}
int getSize()
{
return this->m_Size;
}
//析构函数
~MyArray()
{
if (this->pAddress!=NULL)
{
delete [] this->pAddress;
this->pAddress=NULL;
}
cout<<"析构函数"<<endl;
}
private:
T * pAddress;
int m_Capacity;
int m_Size;
};
1-3-8.cpp
#include <iostream>
using namespace std;
#include "1-3-8-MyArray.hpp"
void printArry(MyArray<int>& arr)
{
for(int i=0;i<arr.getSize();i++)
{
cout<<arr[i]<<endl;
}
}
void test()
{
//测试构造和析构
MyArray<int> ma1(12);
//测试拷贝构造
MyArray<int> ma2(ma1);
MyArray<int> ma3(100);
//测试operate=
ma3=ma1;
}
void test01()
{
MyArray<int> arr1(4);
for(int i=0;i<4;i++)
{
//插入数据
arr1.PushBack(i);
}
cout<<"数组arr1为:"<<endl;
printArry(arr1);
cout<<"数组arr1的容量为:"<<arr1.getCap()<<endl;
cout<<"数组arr1的大小为:"<<arr1.getSize()<<endl;
MyArray<int> arr2(arr1);
cout<<"数组arr2为:"<<endl;
printArry(arr2);
//删除数据
arr2.PopBack();
cout<<"数组arr2尾删后为:"<<endl;
printArry(arr2);
}
//自定义数据类型
class Person
{
public:
Person(){}
Person(string name ,int age)
{
this->m_Name=name;
this->m_Age=age;
}
string m_Name;
int m_Age;
};
void printPerson(MyArray<Person> & p)
{
for(int i=0;i<p.getSize();i++)
{
cout<<"name:"<<p[i].m_Name<<" age:"<<p[i].m_Age<<endl;
}
}
void test02()
{
MyArray<Person> buf1(4);
Person p1("aaa",11);
Person p2("bbb",31);
Person p3("ccc",22);
Person p4("ddd",12);
buf1.PushBack(p1);
buf1.PushBack(p2);
buf1.PushBack(p3);
buf1.PushBack(p4);
printPerson(buf1);
cout<<"数组buf1的容量为:"<<buf1.getCap()<<endl;
cout<<"数组buf1的大小为:"<<buf1.getSize()<<endl;
MyArray<Person> buf2(buf1);
cout<<"数组buf2为:"<<endl;
printPerson(buf2);
//删除数据
buf2.PopBack();
cout<<"数组buf2尾删后为:"<<endl;
printPerson(buf2);
}
int main()
{
test();
test01();
test02();
return 0;
}
输出如下:
1.4 类模板与函数模板的区别
- 类模板没有自动类型推导
- 类模板在模板参数列表中可以有默认参数
PS:据说版本问题,目前类模板也有自动类型推导,函数模板也有默认参数了。
代码如下:
#include <iostream>
using namespace std;
#include <string>
//类模板与函数模板的区别
template <class NameType,class AgeType=int>//设置参数列表中的AgeType默认数据类型为int
class Person
{
public:
Person(NameType name,AgeType age)
{
this->m_Name=name;
this->m_Age=age;
}
void showPerson()
{
cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
//Person p1("ddd",22);//类模板没有自动类型推导
Person<string,int> p1("ddd",22);
p1.showPerson();
Person<string> p2("fff",22);//类模板在模板参数列表中可以有默认参数
p2.showPerson();
}
int main()
{
test01();
return 0;
}
输出如下:
错误示例:类模板没有自动类型推导
2 STL初识
2.1 STL的基本概念
大多数情况下,数据结构和算法都没有一套标准,导致被迫从事大量重复工作,而C++的面向对象(封装、继承、多态)和泛型编程思想的目的就是提升复用性。因此,为建立数据结构和算法的一套标准,诞生了STL。
STL(standard template library)称为标准模板库,从广义上分为容器(container)、算法(algorithm)、迭代器(iterator)。容器和算法之间通过迭代器进行连接。STL中几乎所有的代码使用的都是模板类或模板函数。
STL六大组件:
- 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
- 算法:各种常用的算法,如sort、find、copy、for_each等。
- 迭代器:扮演容器与算法之间的胶合剂。
- 仿函数:行为类似函数,可以作为算法的某种策略。
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
- 空间配置器:负责空间的配置与管理。
容器:
STL容器将运用最广泛的一些数据结构实现出来
常用的数据结构:数组、链表、树、栈、队列、集合、映射表等
容器可分为序列式容器和关联式容器。
- 序列式容器:强调值的排序,容器中每个元素都有固定的位置。
- 关联式容器:二叉树结构,各元素之间没有严格物理上的顺序关系。
算法:
算法通过有限的步骤解决逻辑或数学上的问题。
算法分为质变算法和非质变算法:
质变算法:运算过程中会更改区间内元素的内容。如拷贝、替换、删除等。
非质变算法:运算过程中不会更改区间内元素的内容。如查找、计数、遍历、寻找极值等。
迭代器:
提供一种方法,使其能依序寻访某个容器所含的各元素(算法要通过迭代器才能访问容器中的元素),而无需暴露该容器的内部表示方式。每个容器都有自己专属的迭代器,迭代器的使用类似于指针。
迭代器种类:
常用的容器中狄俄代其种类为双向迭代器和随机访问迭代器。
2.2 vector存放内置数据类型
STL中最常用的容器为vector,可以理解为数组,下面学习如何向容器中插入数据并遍历容器。
容器:vector
算法:for_each
迭代器:vector<int>::iterator
代码如下:
#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>
void myPrint(int val)
{
cout<<val<<endl;
}
//vector容器存放内置数据类型并操作数据
void test01()
{
//创建一个vector容器/数组
vector<int> v;
//向容器中插入数据
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//通过迭代器访问容器中的数据
vector<int>::iterator itBegin=v.begin();//起始迭代器 指向容器中第一个元素
vector<int>::iterator itEnd=v.end();
//第一种遍历方式
while (itBegin!=itEnd)
{
cout<<*itBegin<<endl;
itBegin++;
}
//第二种遍历方式
for(vector<int>::iterator it=v.begin();it!=v.end();it++)
{
cout<<*it<<endl;
}
//第3种遍历方式
for_each(v.begin(),v.end(),myPrint);
}
int main()
{
test01();
return 0;
}
输出如下:
2.3 vector存放自定义数据类型
代码如下:
#include <iostream>
using namespace std;
#include <vector>
#include <string>
//vector容器存放自定义数据类型
class Person
{
public:
Person(string name,int age)
{
this->m_Name=name;
this->m_Age=age;
}
string m_Name;
int m_Age;
};
void test01()
{
vector<Person> v;
Person p1("aaa",11);
Person p2("bbb",12);
Person p3("ccc",14);
Person p4("ddd",15);
Person p5("eee",16);
Person p6("fff",17);
//向容器中添加数据
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
v.push_back(p6);
//遍历容器中的数据
for(vector<Person>::iterator it=v.begin();it!=v.end();it++)
{
cout<<"name:"<<(*it).m_Name<<" age:"<<(*it).m_Age<<endl;
}
}
//vector容器存放自定义数据类型 数组
void test02()
{
vector<Person*> v;
Person p1("aaa",11);
Person p2("bbb",12);
Person p3("ccc",14);
Person p4("ddd",15);
Person p5("eee",16);
Person p6("fff",17);
//向容器中添加数据
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
v.push_back(&p4);
v.push_back(&p5);
v.push_back(&p6);
//遍历容器中的数据
for(vector<Person*>::iterator it=v.begin();it!=v.end();it++)
{
cout<<"name:"<<(*it)->m_Name<<" age:"<<(*it)->m_Age<<endl;
}
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
2.4 容器嵌套容器
代码如下:
#include <iostream>
using namespace std;
#include <vector>
//vector容器嵌套容器
void test01()
{
vector<vector<int>> v;
//创建小容器
vector<int> v1;
vector<int> v2;
vector<int> v3;
vector<int> v4;
//向小容器添加数据
for(int i=0;i<4;i++)
{
v1.push_back(i+1);
v2.push_back(i+2);
v3.push_back(i+3);
v4.push_back(i+4);
}
//将小容器插入到大容器
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
v.push_back(v4);
//通过大容器遍历所有数据
for(vector<vector<int>>::iterator it=v.begin();it!=v.end();it++)
{
// *it是一个vector<int>类型的容器
for(vector<int>::iterator vit=(*it).begin();vit!=(*it).end();vit++)
{
cout<<*vit<<" ";
}
cout<<endl;
}
}
int main()
{
test01();
return 0;
}
输出如下:
3 容器
3.1 string容器
string是C++风格的字符串,而string本质上是一个类。
string和char*的区别:
- char*是一个指针。
- string是一个类,类内部封装了char*,管理整个字符串,是一个char*型的容器。
string的特点:
- string内部封装了很多成员方法,如查找find、删除delete、替换replace、插入insert。
- string管理char*所分配的内存,不用担心赋值越界和取值越界,由类内部进行负责。
3.1.1 构造函数
构造函数原型:
- string(); //创建一个空的字符串,如stringstr;
- string(const char* s); //使用字符串s初始化
- string(const string& str); //使用一个string对象初始化另一个string对象
- string(int n,char c); //使用n个字符c初始化
代码如下:
#include <iostream>
using namespace std;
#include <string>
//string容器的构造函数
// string(); //创建一个空的字符串,如stringstr;
// string(const char* s); //使用字符串s初始化
// string(const string& str); //使用一个string对象初始化另一个string对象
// string(int n,char c); //使用n个字符c初始化
void test01()
{
string s1;//默认构造
const char* str="hello C++";
string s2(str);
cout<<"s2="<<s2<<endl;
string s3(s2);
cout<<"s3="<<s3<<endl;
string s4(10,'d');
cout<<"s4="<<s4<<endl;
}
int main()
{
test01();
return 0;
}
输出如下:
3.1.2 赋值函数
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赋值给当前字符串
代码如下:
#include <iostream>
using namespace std;
#include <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 C++";
cout<<"str1="<<str1<<endl;
string str2;
str2=str1;
cout<<"str2="<<str2<<endl;
string str3;
str3='f';
cout<<"str3="<<str3<<endl;
string str4;
str4.assign("hello fff");
cout<<"str4="<<str4<<endl;
string str5;
str5.assign("hello hhh",4);
cout<<"str5="<<str5<<endl;
string str6;
str6.assign(str1);
cout<<"str6="<<str6<<endl;
string str7;
str7.assign(10,'w');
cout<<"str7="<<str7<<endl;
}
int main()
{
test01();
return 0;
}
输出如下:
3.1.3 字符串拼接
string& operator+=(const char* s); //+=运算符重载
string& operator+=(const string& s); //+=运算符重载
string& operato+r=(char c); //+=运算符重载
string& append(const char* s); //把字符串s拼接到当前字符串末尾
string& append(const char* s,int n); //把字符串s的前n个字符拼接到当前字符串末尾
string& append(const string& s); //把字符串s赋值给当前字符串
string& append(const string& s,int pos,int n); //把n个字符c赋值给当前字符串
代码如下:
#include <iostream>
using namespace std;
#include <string>
//string容器的字符串末尾拼接字符串的算法
// string& operator+=(const char* s); //+=运算符重载
// string& operator+=(const string& s); //+=运算符重载
// string& operato+r=(char c); //+=运算符重载
// string& append(const char* s); //把字符串s拼接到当前字符串末尾
// string& append(const char* s,int n); //把字符串s的前n个字符拼接到当前字符串末尾
// string& append(const string& s); //把字符串s赋值给当前字符串,同operator+=(const string& s);
// string& append(const string& s,int pos,int n); //把n个字符c赋值给当前字符串
void test01()
{
string str1="pretty ";
str1+="good ";
cout<<"str1="<<str1<<endl;
str1+='a';
cout<<"str1="<<str1<<endl;
string str2="fff ";
str1+=str2;
cout<<"str1="<<str1<<endl;
string str3="I ";
str3.append("love ");
cout<<"str3="<<str3<<endl;
str3.append("game abcde",8);
cout<<"str3="<<str3<<endl;
str3.append(str2);
cout<<"str3="<<str3<<endl;
str3.append("abcdefghijklmnopqrst",4,8);
cout<<"str3="<<str3<<endl;
}
int main()
{
test01();
return 0;
}
输出如下:
3.1.4 字符串查找和替换
代码如下:
#include <iostream>
using namespace std;
//string字符串查找和替换
//查找
void test01()
{
string str1="abcdefg";
int pos =str1.find("de",0);
cout<<"pos="<<pos<<endl;
pos=str1.rfind("de");
cout<<"pos="<<pos<<endl;
//find rfind区别:find从左往右 rfind从右往左找 只是查找方向不同!!!不是下标相反
}
//替换
void test02()
{
string str2="abcdefg";
str2.replace(1,3,"1111");
cout<<"str2:"<<str2<<endl;
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
3.1.5 字符串比较
按ASCII码进行对比:
=返回0
>返回1
<返回-1
代码如下:
#include <iostream>
using namespace std;
#include <string>
//字符串比较
void test01()
{
string str1="hello";
string str2="hedlo";
if(str1.compare(str2)==0)
{
cout<<"str1=str2"<<endl;
}
else if(str1.compare(str2)>0)
{
cout<<"str1>str2"<<endl;
}
else
{
cout<<"str1<str2"<<endl;
}
}
int main()
{
test01();
return 0;
}
3.1.6 字符存取
string中单个字符存取方式:
代码如下:
#include <iostream>
using namespace std;
#include <string>
//string字符存取
void test01()
{
string str="hello";
cout<<"str="<<str<<endl;
//访问字符
for(int i=0;i<str.size();i++)
{
cout<<str[i]<<" ";
}
cout<<endl;
for(int i=0;i<str.size();i++)
{
cout<<str.at(i)<<" ";
}
cout<<endl;
//修改字符
str[0]='x';
cout<<"str="<<str<<endl;
str.at(1)='x';
cout<<"str="<<str<<endl;
}
int main()
{
test01();
return 0;
}
输出如下:
3.1.7 字符串插入和删除
代码如下:
#include <iostream>
using namespace std;
#include <string>
//string字符的插入和删除
void test01()
{
string str="hello";
//插入
str.insert(1,"ddd");
cout<<"str="<<str<<endl;
//删除
str.erase(1,3);
cout<<"str="<<str<<endl;
}
int main()
{
test01();
return 0;
}
输出如下:
3.1.8 子串获取
代码如下:
#include <iostream>
using namespace std;
#include <string>
//string子串获取
void test01()
{
string str="hello";
string substr=str.substr(1,3);
cout<<"substr="<<substr<<endl;
}
void test02()
{
string email="zhangsan@sina.com";
int pos=email.find('@');
cout<<"pos of @ = "<<pos<<endl;
string userName=email.substr(0,pos);
cout<<"username="<<userName<<endl;
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
3.2 vector容器
vector容器和数组相似,称为单端数组。数组是静态的vector容器是可以动态扩展的,扩展的方式不是通过在原空间之后增加新空间,而是另外开辟更大的内存空间,拷贝原数据,并释放原空间。
PS:vector容器的迭代器支持随机访问
3.2.1 构造函数
代码如下:
#include<iostream>
using namespace std;
#include<vector>
//vector容器构造函数
void printVector(vector <int> &v)
{
for(vector<int>::iterator it =v.begin();it!=v.end();it++)
{
cout<<*it<<" ";
}
cout<<endl;
}
void test01()
{
vector<int> v1;
for(int i=0;i<10;i++)
{
v1.push_back(i);
}
printVector(v1);
//通过区间方式进行构造
vector<int> v2(v1.begin(),v1.end());
printVector(v2);
//n个elem方式构造
vector<int> v3(10,100);//10个100
printVector(v3);
vector<int> v4(v3);
printVector(v4);
}
int main()
{
test01();
return 0;
}
输出如下:
3.2.2 赋值操作
3.2.3 容量和大小
3.2.4 插入和删除
3.2.5 数据存取
3.2.6 互换容器
3.2.7 预留空间
3.3 deque容器
3.4 stack容器
。