C++提高编程
泛型编程(函数模板和类模板)
一、模板
1.1模板的概念
- 模板就是建立通用的摸具,提高复用性,生活中也有很多模板,比如PPT模板。
- 编程也可以有模板,而STL叫做标准模板库,就是基于泛型编程实现的模板化。
- C++提供两种模板:函数模板和类模板,它们的实现是基于泛型编程这种编程思想。
1.2函数模板
1.2.1函数模板的语法
-
函数模板的作用:建立一个通用函数,它的返回值类型和形参类型不具体指定,用一个虚拟的类型来代表。
-
使用函数模板有两种方式确定泛型的类型:自动类型的推导、显示指定泛型类型
-
语法:
在函数声明前加一行声明:template<typename T>或者template<class T>
//使用模板后 template<class T> void mySwap(T& a, T& b)//交换两个值 { T temp = a; a = b; b = temp; } int main() { int a = 10; int b = 20; char c = 'c'; //c对应的ASCII码是 99 mySwap(a,b); cout << "a=" << a << "\tb=" << b << endl; cout<<a<<"+" <<c<< "=" << myAdd<int>(a, c) << endl; }
1.2.2函数模板注意事项
- 自动类型推导,必须推导出一致的数据类型T,才可以使用。
- 模板必须要确定出T的类型,才可以使用,如果不能自动推导,那就需要手动指定。
1.2.3普通函数和函数模板的区别:
-
普通函数调用时可以发生隐式类型转换
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。除非显示指定泛型的类型,才可以发生隐式类型转换。//普通函数调用时可以发生隐式类型转换 int add(int a, int b) { return a + b; } //模板函数 template<class T> T add_tpl(T a, T b) { return a + b; } //普通函数和函数模板的区别 int a = 10; char c = 'c'; cout << add(a, c) << endl;//这里发生隐式类型转换,c被转换成了int类型,即c的ascii码99 //add_tpl(a, c);//报错了,推导的T类型不一致,没有发生隐式类型转换 cout << add_tpl<int>(a, c) << endl;//这次显式指定了T的类型,就发生了隐式类型转换
1.2.4普通函数与函数模板的调用规则
-
如果普通函数和函数模板都可以实现,优先调用普通函数(具体的高于通用的)
-
可以通过空模板参数列表<>来强制调用函数模板
-
函数模板也可以重载
//普通函数与函数模板的调用规则 void myPrint(int a, int b) { cout << "a=" << a << ",b=" << b << ",调用普通函数"; }//普通函数 template<class T> void myPrint(T a, T b) { cout << "a=" << a << ",b=" << b << ",调用函数模板的重载之一:两个参数的"; }//函数模板 template<class T> void myPrint(T a, T b,T c) { cout << "a=" << a << ",b=" << b <<",c="<<c << ",调用函数模板的重载之一:三个参数的"; }//函数模板的重载 //普通函数与函数模板的调用规则 int a = 10; int b = 20; //myPrint(a, b);//如果普通函数和函数模板都可以实现,优先调用普通函数(具体的高于通用的) //myPrint<>(a, b);//可以通过空模板参数列表<>来强制调用函数模板,并且发生了重载 int c = 30; //myPrint(a, b, c);//调用了重载的函数模板,三个参数的 char c1 = 'a'; char c2 = 'b'; myPrint(c1, c2);//调用了重载的函数模板,两个参数的,因为普通函数类型不匹配,只有这个模板函数可以匹配
1.2.5特定的模板(解决模板的局限性)
普通函数模板的局限性:模板的通用性不是万能的,在遇到自定义类型的时候,就无法处理了。
为了解决这种问题,提供了特定模板:
利用特定模板,解决特定类型的通用问题
语法:在函数头的同一行,前面加template<>
特定模板的优先级高于普通模板
建议:使用函数模板,建议优先使用显式声明泛型类型的方式,当模板存在时,就不要再声明同名的普通函数了。
//普通函数模板,比较两个值是否相等
template<class T>
bool myCompare(T& a, T& b)
{
if (a==b)
{
return true;
}
else
{
return false;
}
}
//定义一个特定的模板,解决调用Student对象类型的问题
template<> bool myCompare(Student& s1, Student& s2)
{
//比较自定义类型的逻辑,需要我们自己指定
if (s1.m_Name==s2.m_Name and s1.m_Age==s2.m_Age)//对象的属性完全相等,那两个对象就是相等的
{
return true;
}
else
{
return false;
}
}
//特定的模板(解决模板的局限性)
int a = 10;
int b = 20;
cout <<"基本类型,可以调用普通模板实现,结果是:"<< myCompare(a, b) << endl;
Student s1("Tom", 20);
Student s2("Tom", 20);
cout << "自定义类型,结果是:" << myCompare(s1, s2) << endl;//自定义类型,无法调用普通模板实现
1.3类模板
1.3.1类模板的作用:
建立一个通用类,类中的成员的数据类型可以不具体指定,用虚拟的泛型来表示。
类模板和函数模板的主要区别是:类模板没有自动类型推导的使用方式,只能用显式指定泛型类型的方式。
语法:在类的声明之前加上模板的声明template<typename T>或者template<class T>
//类模板的定义
template<class NameType,class AgeType>
class Person
{
NameType m_Name;
AgeType m_Age;
public:
Person(NameType name, AgeType age)
{
m_Name = name;
m_Age = age;
}
void show()
{
cout << "name:" << m_Name << " " << "age:" << m_Age << endl;
}
};
//类模板的使用
Person<string, int> p1("孙悟空", 100);//显式指定泛型的类型
Person<string, string> p2("千年老妖", "一千岁");
p1.show();
p2.show();
1.3.2类模板结合函数模板的使用,将类模板对象当作函数参数
类模板实例化出的对象,向函数传参,有三种方式:
1)显式指定传入的类型(推荐的,易于理解,但是灵活度不高,需要传入对应类型的对象)
//1)显式指定传入的类型
void show_type(Person<string, int>& p)
{
p.show();
}
2)对象参数模板化(进一步使用函数模板把对象的模板参数模板化,灵活度高)
//2)对象参数模板化
template<class T1,class T2>
void show_tpl_para(Person<T1, T2>& p)
{
p.show();
cout << "T1的类型:" << typeid(T1).name() << endl;//查看T1的具体类型
cout << "T2的类型:" << typeid(T2).name() << endl;//查看T2的具体类型
}
3)整个类模板化(灵活度最高)
//3)整个类模板化
template<class T>
void show_tpl_class(T& p)
{
p.show();
cout << "T的类型:" << typeid(T).name() << endl;
}
//将类模板对象当作函数参数
//1)显式指定传入的类型
Person<string, int> p1("孙悟空", 100);
Person<string, string> p2("唐僧", "五百岁");
Person<string, int> p3("猪八戒", 800);
show_type(p1);//show_type只支持传入Person<string,int>类型的对象
//2)对象参数模板化
show_tpl_para(p1);//show_tpl_para支持任意类型的Person<T1,T2>对象
show_tpl_para(p2);
//3)整个类模板化
show_tpl_class(p1);//show_tpl_class支持任意类型的参数对象,因为T代表整个类对象
show_tpl_class(p2);
1.3.3类模板遇到继承
如果父类是类模板,子类继承父类,有几种情况:
1)如果父类是类模板,子类既可以是模板版,也可以是普通类。
2)如果父类子类都是模板类,子类和父类的泛型可以不同,各自指定各自的泛型类型。也可以相同,让子类和父类使用一个泛型类型。
//类模板遇到继承
template<class T>
class Fruit//基类,水果类,是个类模板
{
public:
Fruit();
};
template<class T>//如果在类外实现类中的成员函数,如果类是类模板,也需要在类外加上模板声明
Fruit<T>::Fruit(){ cout << "Fruit类在构造,它的类型是:" << typeid(T).name() << endl; }
//子类是普通类,继承一个模板类父类,需要确定父类的泛型类型
class Apple :public Fruit<int>
{
public:
Apple()
{
cout << "Apple类在构造,它是普通类" << endl;
}
};
//子类也是模板类,子类可以和父类各自拥有自己的泛型
template<class T>
class Banana :public Fruit<int>
{
public:
Banana(){ cout << "Banana类在构造,它的类型是:" << typeid(T).name() << endl; }
};
//子类也是模板类,子类和父类公用相同的泛型
template<class T>
class Orange :public Fruit<T>
{
public:
Orange(){ cout << "Orange类在构造,它的类型是:" << typeid(T).name() << endl; }
};
//类模板遇到继承
Apple a;//Apple类是普通类,继承了Fruit类,并且给Fruit类的泛型定义成int
Banana<double> b;//Banana类是模板类,同时指定了父类Fruit类的泛型是int
Orange<long> o;//Orange类是模板类,并且跟父类Fruit共用了long这个类型
二、STL概念
- 为了建立数据结构和算法的一套标准,提高复用性,诞生了STL(standard template library)标准模板库
- STL都采用了泛型编程实现,里面几乎都是类模板和函数模板。
- STL分为三大部分:容器(container)、算法(algorithm)、迭代器(iterator)。
**容器:**放数据的地方,STL容器实现了常见的数据结构,用来组织和存储数据。
**算法:**用于对数据进行运算的,用于解决问题的方法。比如排序、查找等等。
**迭代器:**用于指向容器中的每个元素,用来访问元素,本质上就是一个封装了指针的类。
三、STL中的常用容器
3.1string容器
3.1.1基本概念
string是STL里定义的字符串,本质是一个类,是一个特殊的容器,元素类型只能是char类型。
string和char*的区别:
char是一个指针,而string是一个类,类内部封装了char
string管理char*所分配的内存,而且不用担心越界的问题,由类来负责管理,string封装了很多成员方法,使用起来非常方便
3.1.2string构造
string();//创建一个空的字符串,无参构造
string(const char* s);//使用字符串s来初始化string对象
string(const string& str);//使用string对象str来初始化string对象,拷贝构造
string(int n,char c);//使用n个字符c来初始化string对象
//string构造
void test01()
{
string s1;//无参构造,s1是一个空字符串
cout << "s1=" << s1 << endl;
const char* str = "hello world";
string s2(str);//根据一个字符串常量构造string对象
cout << "s2=" << s2 << endl;
string s3(s2);//调用拷贝构造,构造s3
cout << "s3=" << s3 << endl;
string s4(10, 'A');//由10个A组成的字符串
cout << "s4=" << s4 << endl;
}
3.1.3string赋值操作
重载赋值运算符:
string& operator=(const char* s);//字符串常量赋值
string& operator=(const string& s);//string对象的赋值
string& operator=(char c);//单个字符赋值
赋值函数assign():
string& assign(const char* s);//字符串常量赋值
string& assign(const char* s,int n);//将字符串s的前n个字符赋值给string
string& assign(const string& s);//使用string对象完成赋值
string& assign(int n,char c);//使用n个字符c来赋值
//sting赋值
void test02String()
{
string str1;
str1 = "Hello C++";
cout << "str1=" << str1 << endl;
string str2 = str1;
cout << "str2=" << str2 << endl;
string str3;
str3= 'a';
cout << "str3=" << str3 << endl;
// 使用赋值函数赋值
string str4;
str4.assign("Hell0 107");
cout << "str4=" << str4 << endl;
string str5;
str5.assign("hello c++", 5);
cout << "str5=" << str5 << endl;
string str6;
str6.assign(str5);
cout << "str6=" << str6 << endl;
string str7;
str7.assign(5, 'm');
str7.append(str6);
cout << "str7=" << str7 << endl;
}
3.1.4string字符串拼接
在字符串末尾拼接字符串
重载运算符+=:
string& operator+=(const char* s);
string& operator+=(const string& str);
string& operator+=(char c);
append函数:
string& append(const char* s);
string& append(const char* s,int n);//把字符串s的前n个字符拼接到string字符串
string& append(const string& s);
string& append(const string& s,int pos,int n);//pos代表position位置,从0开始的下标。从pos位置开始,将字符串s后面的n个字符拼接到当前字符串
//字符串拼接操作
void test03String()
{
string str1 = "我爱";
str1 += "玩游戏";
cout << str1 << endl;
str1 += ':';
cout << str1 << endl;
string str2 = "Love ";
str1 += str2;
cout << str1 << endl;
str1.append("dsf");
cout << str1 << endl;
str1.append("C++", 2);
cout << str1 << endl;
str1.append("abcdefg", 2, 3); //cde 拼接到str1的后面
cout << str1 << endl;
}
3.1.5string查找和替换
查找:查找指定的字符串是否存在,找到会返回对应的位置,找不到返回-1(-1即string::npos),查找是常函数
从左往右查找:find
int find(const string& str,int pos=0) const;//查找字符串str,返回它第一次出现的位置,从pos位置开始找,pos默认值是0,即开头位置
int find(const char* s,int pos=0) const;
int find(const char* s,int pos=0,int n) const;//从pos位置开始查找字符串s的前n个字符第一次出现的位置
int find(char c,int pos=0) const;//从pos位置查找字符c第一次出现的位置
从右往左查找:rfind
int rfind(const string& str,int pos=npos) const;//从右往左查找str最后一次出现的位置,从pos位置开始查找,pos默认值是npos,npos是string类的静态常量,表示字符串结束
int rfind(const char* s,int pos=npos) const;
int rfind(const string& s,int pos=npos,int n) const;//从pos开始从右往左查找s字符串的前n个字符最后一次出现的位置
int rfind(char c,int pos=npos) const;
替换:在指定的位置替换字符串
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 test04String()
{
string str1 = "hc+elloc++,how are you";
// string 访问
cout << str1[4] << str1.at(4)<<endl;
// string 容量
cout << str1.size()<<endl;
//string 查找
string str2 = "c++";
cout << str1.find("c++", 0, 2) << endl;
cout << str1.rfind("c++", str1.size() - 1, 2) << endl;
//string 替换
str1.replace(7, 3, "java");
cout << str1 << endl;
}
3.1.6string字符串比较
比较方式:是按照字符串中每个字符的ASCII码逐个比较,如果比较出大小就返回结果,如果直到结束都没有比较出大小,证明两个字符串相等。
所以,比较方法主要的作用就是判断两个字符串是否相等,至于大小没实际意义。
返回值:相等的时候返回0,大于的时候返回1,小于返回-1
int compare(const string& str) const;//当前字符串跟字符串str比较
int compare(const char* s) const;//当前字符串跟字符串s比较
//比较两个字符串
string str1 = "hell";
string str2 = "hell";
cout << str1.compare(str2) << endl;
3.1.7string插入和删除
//插入字符串
str1.insert(4, "o");
str1.insert(5, 2, 'c');
cout << str1 << endl;*
* 删除:
string& erase(int pos,int n);//删除从pos开始的n个字符
// 删除字符串
str1.erase(5, 2);
cout << str1 << endl;
3.1.8string截取子字符串
从字符串中截取想要的子字符串
string& substr(int pos,int n) const;//返回从pos开始的n个字符组成的子字符串。
// 截取字符串
string str= str1.substr(1, 3);
cout << str << endl;
3.1.9获取长度
// string 容量
cout << str1.size()<<endl;
3.1.10string元素的存取
string中单个字符存取可以通过下面两个方式:
char& operatorint n;//运算符重载,通过[index]取值,index代表下标
char& at(int n);//通过at成员函数取值,n也是下标
string str1 = "hc+elloc++,how are you";
// string 访问
cout << str1[4] << str1.at(4)<<endl;
3.2vector动态数组
3.2.1vector的基本概念
vector的数据结构跟数组非常相似,也叫做单端数组。
**vector与数组的区别:**数组是静态的,一旦声明了数组的长度,是不可变的。而vector是动态的,它的长度可以动态拓展。
**拓展原理:**当空间不足的时候,再添加新元素,vector会重新申请一块更大的内存空间,并且将旧空间的数据拷贝到新空间,然后释放旧空间。
**涉及到两个概念:**容量capacity 和 大小size,容量指的是空间的总大小,size指的是实际存放的元素数。
//打印vector内部元素的方法
void printVector(vector<int>& v)
{
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
3.2.2vector构造函数
[vector v;//采用类模板实现,无参构造,构造了一个空的vector容器,就是v
vector vec(v.begin(),v.end());//参数是两个迭代器,两个位置,v是一个vector对象,将vbegin,end)区间中的元素拷贝给当前vector对象,完成构造。
vector v(const vector& vec);//拷贝构造
vector v(n,ele);//ele是element,即元素。使用n个ele元素完成当前vector对象的构造。
//vector构造函数
void test01()
{
vector<int> v1;//无参构造,构造了一个vector对象v1,目前是空的,里面可以存放int类型的元素
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
printVector(v1);
//区间元素完成构造
vector<int> v2(++v1.begin(), --v1.end());
printVector(v2);
//拷贝构造
vector<int> v3(v2);
printVector(v3);
//n个元素来构造
vector<int> v4(10, 100);
printVector(v4);
}
3.2.3vector赋值
重载赋值运算符:
vector& operator=(const vector& vec);
成员函数assign:
[assign(beg,end);//将vectorbeg,end)区间的元素赋值给当前的vector对象,beg和end是两个迭代器位置
assign(n,ele);//将n个ele元素赋值给当前
//vector赋值
void test02()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
//赋值运算符
vector<int> v2;
v2 = v1;
printVector(v2);
//赋值函数assign
vector<int> v3;
v3.assign(++v1.begin(), --v1.end());
printVector(v3);
vector<int> v4;
v4.assign(6, 6);
printVector(v4);
}
3.2.4vector容量和大小
//vector容量和大小
void test03()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
vector<int> v2;
cout << "v1:" << v1.empty() << endl;
cout << "v2:" << v2.empty() << endl;
cout << "v1的容量:" << v1.capacity() << endl;
cout << "v1的size:" << v1.size() << endl;
cout << "v2的容量:" << v2.capacity() << endl;
cout << "v2的size:" << v2.size() << endl;
//resize重新指定容器的size
v1.resize(15);
printVector(v1);
v1.resize(5);
printVector(v1);
v1.resize(10,100);
printVector(v1);
}
3.2.5vector插入和删除
尾部操作:
push_back(ele);//尾部推入一个元素ele
pop_back();//尾部最后一个元素被弹出
指定位置插入:insert
insert(iterator pos,ele);//迭代器指向位置pos插入元素ele,并且返回新插入元素的位置迭代器。
insert(iterator pos,int n,ele);//迭代器指向位置pos插入n个元素ele,并且返回新插入多个元素的第一个元素的位置迭代器。
删除:erase
erase(iterator pos);//删除迭代器所指向的元素,返回被删除元素的下一个迭代器位置。
erase(iterator beg,iterator end);//删除beg到end之间的元素,不包括end
clear();//清除容器中的所有元素
//vector插入和删除
void test04()
{
vector<int> v1;
//尾部操作
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);
v1.push_back(50);
printVector(v1);
v1.pop_back();
printVector(v1);
//指定位置插入:insert
v1.insert(v1.begin(), 100);
printVector(v1);
v1.insert(v1.begin(), 2, 1000);
printVector(v1);
//删除
v1.erase(v1.begin());
printVector(v1);
//v1.clear();//清空
//printVector(v1);
//插入insert函数会返回插入对象的迭代器位置,用迭代器来接收
vector<int>::iterator it = v1.insert(v1.begin() + 2, 88);
printVector(v1);
cout << *it << endl;//it迭代器指向了88
//插入多个元素,返回的是插入元素中第一个元素的位置
vector<int>::iterator it_n = v1.insert(v1.begin() + 3, 2, 6);
printVector(v1);
cout << *it_n << endl;
cout << *(it_n + 1) << endl;
//删除erase函数返回删除元素的下一个元素的位置迭代器
vector<int>::iterator it_erase = v1.erase(v1.begin() + 2);
printVector(v1);
cout << *it_erase << endl;
vector<int>::iterator it_erase_n = v1.erase(v1.begin() + 2, v1.begin() + 4);
printVector(v1);
cout << *it_erase_n << endl;
}
- 注意:删除或者插入操作都会导致删除和插入位置以及以后位置的迭代器改变,所以如果要继续使用迭代器来操作后面的元素,就必须更新迭代器。**
这个原则适用于连续空间的数据结构,包括我们马上要学习的另外一个容器deque。但是对于后面再学习的基于链表或者是树的容器,就不适用了。比如list、map、set等。
//注意:插入和删除操作会造成当前操作位置以及后面所有元素的迭代器发生改变
void test05()
{
vector<int> v1;
v1.push_back(10);
v1.push_back(20);
v1.push_back(30);
v1.push_back(40);
v1.push_back(50);
//循环遍历v1的元素,删除30这个元素
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
if (*it==30)
{
v1.erase(it);//报错了,错误信息是出现了无效的迭代器,原因是删除操作导致后面元素的迭代器改变,原来循环条件中的迭代器end()就失效了
}
}
//针对上述问题的解决办法
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
if (*it == 30)
{
it=v1.erase(it);//这里通过erase的返回值,更新了当前迭代器的最新值,此时就是正确的迭代器了
}
}
printVector(v1);
//插入操作也会导致迭代器失效的问题,也许更新迭代器
//我们遍历数组,在30之前插入一个值100
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
if (*it == 30)
{
v1.insert(it, 100);//这里的插入操作仍然导致迭代器失效,需要在下一行代码中更新it的值
it = find(v1.begin(), v1.end(), 30);//#include <algorithm>,使用其中的find查找算法,找到30的迭代器,给it重新赋值
}
}
printVector(v1);
}
3.2.6vector数据存取
//vector数据存取
void test06()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
printVector(v1);
cout << v1[3] << endl;
cout << v1.at(6) << endl;
cout << v1.front() << endl;
cout << v1.back() << endl;
}
3.2.7vector互换
可以实现两个vector容器元素的互换
v1.swap(v2);//v1和v2互换元素
//vector互换
void test07()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
printVector(v1);
vector<int> v2;
for (int i = 10; i > 0; i--)
{
v2.push_back(i);
}
printVector(v2);
cout << "互换后" << endl;
v1.swap(v2);
printVector(v1);
printVector(v2);
}
3.2.8vector预留空间
当vector存储的数据量很大时,由于vector的动态数组特性,会导致多次的动态扩展发生,这样会降低效率。
为了避免多次拓展的情况发生,可以在一开始就指定一些预留的空间,其实就是指定了capacity的值。
reserve(int len);//给容器预留len个元素的位置,预留的位置不可访问,因为没有被初始化。
void myVector08()
{
vector<int> v;
v.reserve(9);//预留10个位置
for (int i = 0; i < 10; i++)// 插入10个数据,容量是够的,所以不需要动态扩展
{
v.push_back(i);
// 通过观察某个值的地址,判断地址有没有改变,如果没有改变说明没有发生动态扩展
cout << &v[0] << endl;//第十次首地址发生了改变,说明发生了动态扩展
}
cout << "v的容量" << v.capacity() << endl;
cout << "v的大小" << v.size() << endl;
}
3.2.9几个常用的算法函数(需要引入头文件#include )
这是算法,适用于大部分容器的,不是vector的成员函数
排序:
sort(iterator beg,iterator end);//对区间内的元素进行排序,默认升序,从小到大
sort(iterator beg,iterator end,func);//对区间内的元素进行排序,使用func指定排序方式
反转:
reverse(iterator beg,iterator end);//对区间内元素反转倒置
复制:
copy(源容器起始位置,源容器结束位置,目标容器的起始位置);//将源容器的区间元素复制到目标容器指定位置,注意是替换,新复制进去的值替换了原来位置的值
查找:
find(iterator beg,iterator end,ele);//在区间内查找元素ele,找到返回所在位置的迭代器,找不到返回end迭代器。
遍历:
for_each(iterator beg,iterator end,func);//遍历容器区间内的元素,func是个函数或者仿函数,指明遍历时对元素的操作。
// 几个常用函数的用法(#include<algorithm>)
//定义一个排序函数
bool cap(int a,int b)
{
return a > b;
}
void test10()
{
//准备一个容器,使用数组转换到容器中
int arr[] = { 50,24,1,25,85,63,14,25,36,54 };
vector<int>v1;
v1.assign(arr, arr + 10);
myPrint(v1);
//接下来使用常用的函数进行排序
//排序默认是升序
sort(v1.begin(), v1.end());
cout << "默认升序:";
myPrint(v1);
//使用排序函数,制定排序函数为降序
sort(v1.begin(), v1.end(), cap);
cout << "使用降序函数:";
myPrint(v1);
reverse(v1.begin(),v1.end());
cout << "反转成升序:";
myPrint(v1);
//复制copy
vector<int>v2;
v2.assign(10, 100);
copy(v1.begin(), v1.begin() + 5, v2.begin());
cout << "复制v1前五个值到v2中:";
myPrint(v2);
//查找find;
vector<int>::iterator it=find(v1.begin(), v1.begin() + 5, 25);
if (it == v1.end())
{
cout << "没有找到" << endl;
}
else
{
cout << "下标为:" << it - v1.begin() << endl;
}
//遍历for_each
for_each(v1.begin(), v1.end(), print);
}
//vector容器的遍历方法
void test11()
{
vector<int>v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
//第一中方式遍历 :while(){}
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
//第二种方式遍历循环:for循环
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
// 第三种方式遍历循环: for_each()
for_each(v.begin(), v.end(), print);
}
//课堂练习:定义一个类Person,姓名和年龄两个属性,使用容器vector存放多个对象,并且遍历输出对象
class Person
{
public:
string m_Name;
int m_Age;
public:
Person(string name,int age):m_Name(name),m_Age(age){}
};
void test12()
{
//课堂练习:定义一个类Person,姓名和年龄两个属性,使用容器vector存放多个对象,并且遍历输出对象
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
vector<Person> v;
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
for (vector<Person>::iterator it = v.begin(); it !=v.end(); it++)
{
cout << (*it).m_Name <<","<< it->m_Age << endl; //it是迭代器
}
}
//课堂练习:容器嵌套容器,外层容器存放的元素是vector<int>类型的容器,做一下容器的嵌套并且遍历输出vector<int>容器中的值。
void test13()
{
//创建外层容器
vector<vector<int>> v;
//创建内层的容器
vector<int> v1;
vector<int> v2;
vector<int> v3;
vector<int> v4;
//给v1-v4插入数据
for (int i = 0; i < 10; i++)
{
v1.push_back(i+1);
v2.push_back(i+2);
v3.push_back(i+3);
v4.push_back(i+4);
}
//嵌套,将v1-v4插入到v中
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
v.push_back(v4);
//双层循环输出
for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++)
{
for (vector<int>::iterator vit = (*
it).begin(); vit < (*it).end(); vit++)
{
cout << *vit << " ";
}
cout << endl;
}
}
3.3deque双端队列
3.3.1概念
deque全程是double-ended-queue,也叫双端数组,可以对两端进行操作
deque和vector的区别:
vector是单向开口的连续空间,deque是双向开口的连续空间,可以高效地在头尾两端做元素的插入和删除操作。当然如果在中间insert操作,效率仍然是低的。
deque没有容量capacity的概念,它是以分段的连续空间组合而成的,随时可以增加一段新的空间连接起来,它不会像vector那样因为空间不足,重新申请一块空间的操作。
deque通过内部的中控器来维护这些分段的连续空间。中控器维护每个连续空间的地址,使它们连接起来。
deque的成员函数跟vector几乎一样,不再赘述。
主函数:
int main()
{
test01();
//课堂练习:deque<string> 转大写
string s1 = "abc";
string s2 = "Kawa";
string s3 = "Swith";
deque<string> d1;
d1.push_back(s1);
d1.push_back(s2);
d1.push_back(s3);
toUpper(d1);
for (deque<string>::iterator it = d1.begin(); it != d1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
//打印函数
void printDeque(deque<int>& d)
{
for (deque<int>::iterator it = d.begin(); it != d.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
//练习,使用算法操作deque
bool cap(int a, int b) { return a > b; }//倒叙排序的函数
void test01()
{
int a[] = { 5,2,63,36,27,42,45,3,54,88 };
deque<int> d(a, a + 10);
//排序
sort(d.begin(), d.end());
printDeque(d);
sort(d.begin(), d.end(), cap);
printDeque(d);
//倒置
reverse(d.begin(), d.end());
printDeque(d);
//复制
deque<int> d2(3, 100);
copy(d2.begin(), d2.end(), d.begin());
printDeque(d);
//头部操作
d.push_front(88);
printDeque(d);
d.pop_front();
printDeque(d);
cout << d.size() << endl;
}
//课堂练习:一个deque中存储了很多个string字符串,都是英文字母,写一个函数,将所有字符串转成大写。
void toUpper(deque<string>& d)
{
//toupper函数可以完成单个字符的大写转换
for (deque<string>::iterator it = d.begin(); it != d.end(); it++)
{
//继续对每个string做遍历,同时转换每个字符为大写
for (int i = 0; i < (*it).size(); i++)
{
(*it)[i] = toupper((*it)[i]);//单个字符转大写
}
}
}
3.4栈stack
3.4.1栈的基本概念
栈是一种数据结构,它最大的特点是先进后出,因为它只有一个出口。
栈只能在栈顶访问元素操作元素,比如说对元素进行新增和删除操作。除了栈顶之外,其他位置的元素都不可访问。
压入元素到栈顶,称为入栈,弹出栈顶的元素,称为出栈。
栈不具有遍历行为的,没有迭代器,也不支持随机访问(通过下标访问)。
3.4.2栈的常用函数
构造函数:
stack<T> stk;//无参构造
stack<T> stk(const stack& s);//拷贝构造
赋值:
赋值运算符重载
stack& operator=(const stack& s);
数据存取:
push(ele);//添加元素到栈顶
pop();//将栈顶元素删除
top();//返回栈顶元素
大小相关:
empty();//判断栈是否为空
size();//返回栈的大小
//栈
void test01()
{
stack<int> s;
//入栈
s.push(10);
s.push(20);
s.push(30);
cout << "栈顶元素:" << s.top() << endl;
cout << "栈的大小:" << s.size() << endl;
cout << "栈是否为空:" << s.empty() << endl;
s.pop();
s.pop();
s.pop();
cout << "栈是否为空:" << s.empty() << endl;
stack<int> s2(s);
cout << "s2的大小:" << s2.size() << endl;
stack<int> s3;
s3 = s2;
cout << "s3的大小:" << s3.size() << endl;
}
//如何访问栈中的所有元素?通过不断的出栈,改变top的值,直到栈为空
template<class T>
void printStack(stack<T>& stk)
{
while (!stk.empty())
{
cout << stk.top() << " ";
stk.pop();
}
cout << endl;
}
//课堂练习1:写一个函数,判断一个字符中的括号()是否匹配,是否成对出现。需要用到栈。
//举例:alk(2kg)2og(lg(l2),返回false,ogl(2gl)ljl(2k),返回true,(ga))((2lng),返回false,298)lk2j(lg(lqjlg),返回false
bool isMatch(string& str)
{
stack<char> stk;
for (int i = 0; i < str.size(); i++)
{
//思路:如果是左括号,那就入栈,等待匹配,如果是右括号,那就匹配一个左括号,让一个左括号出栈(还需要先判断栈是否为空,如果为空,右括号就会匹配失败)
//等到全部的括号遍历完之后,只需要判断栈是否为空即可,如果栈为空,说明全部匹配成功。如果栈不为空,说明没有全部匹配。
if (str[i]=='(')
{
stk.push('(');
}
else if (str[i]==')')
{
if (stk.empty())
{
return false;
}
else
{
stk.pop();
}
}
}
if (stk.empty())
{
return true;
}
else
{
return false;
}
}
3.5队列queue
3.5.1队列的概念
队列是一种先进先出的数据结构,它有队头和队尾,队尾只能进,叫入队,队头只能出,叫出队。其他位置的元素无法访问。
没有迭代器,没有遍历行为,不能随机存取(通过下标访问)。只能操作队头和队尾。
3.5.2queue的常用函数
构造函数:
queue<T> que;//无参构造
queue<T> que(const queue& q);//拷贝构造
赋值:
queue& operator=(const queue& q);
数据存取:
push(ele);//添加元素到队尾
pop();//将队头元素删除
back();//返回队尾元素
front();//返回队头元素
大小相关:
empty();//判断队列是否为空
size();//返回队列的大小
//队列
void test02()
{
queue<int> que;
for (int i = 0; i < 10; i++)
{
que.push(i);
}
cout << "队头:" << que.front() << endl;
cout << "队尾:" << que.back() << endl;
que.pop();
cout << "队头:" << que.front() << endl;
cout << "队尾:" << que.back() << endl;
cout << que.size() << endl;
}
//如何访问队列中的所有元素?通过不断的出队,改变队头的值,直到队列为空
template<class T>
void printQueue(queue<T>& que)
{
while (!que.empty())
{
cout << que.front() << " ";
que.pop();
}
cout << endl;
}
//课堂练习2:使用队列存储自定义类型对象Person(m_Name,m_Age),并且遍历输出
class Person
{
public:
string m_Name;
int m_Age;
public:
Person(string name, int age) :m_Name(name), m_Age(age) {}
};
void test03()
{
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
queue<Person> q;
q.push(p1);
q.push(p2);
q.push(p3);
while (!q.empty())
{
cout << q.front().m_Name << "," << q.front().m_Age << endl;
q.pop();
}
}
//课堂练习3:一个存储整数的队列,写个函数,对这些整数进行分类,奇数放入一个新队列中,偶数放入一个新队列中,打印输出,并且保持数据原来的先后顺序。
void numType(queue<int>& que)
{
queue<int> q1;
queue<int> q2;
while (!que.empty())
{
if (que.front()%2!=0)
{
q1.push(que.front());//奇数入q1
que.pop();
}
else
{
q2.push(que.front());//偶数入q2
que.pop();
}
}
cout << "奇数队列:" << endl;
printQueue(q1);
cout << "偶数队列:" << endl;
printQueue(q2);
}
//课堂练习3:奇数偶数分队
queue<int> q;
for (int i = 0; i < 10; i++)
{
q.push(i);
}
numType(q);
3.6链表list
3.6.1链表概念
链表是一种物理上不连续的存储结构,不支持随机存取(通过下标取值)。数据单元的逻辑顺序是通过链表中的指针链接实现的。
链表的组成单元:结点,结点又是由两部分构成:数据域和指针域。
STL中的list链表是一种双向链表(两个方向,可前可后)
链表list中有迭代器,支持遍历,但是迭代器只能单步位移,每次只能向前或向后移动一位。不支持指针运算。
链表的特点:
优点:
链表最大的优点是插入和删除操作十分高效,修改指针即可,不需要大量移动元素。同样不会造成迭代器的失效。
链表也是动态内存分配的,不会造成内存浪费也不会有内存溢出。它不像vector一样,需要实现固定一个容量的长度。
缺点:
遍历元素效率太低
3.6.2list构造
list<T> lst;
list<T> lst(beg,end);//将[beg,end)区间的元素拷贝给当前要构造的list,beg和end是迭代器
list(n,ele);//n个ele元素构造当前list
list(const list& lst);//拷贝构造
示例:
//构造
void test01()
{
list<int> L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
printList(L1);
list<int> L2(++L1.begin(), --L1.end());
printList(L2);
list<int> L3(L2);
printList(L3);
list<int> L4(8, 8);
printList(L4);
}
3.6.3list赋值和交换
赋值运算符重载=
assign(beg,end);//将[beg,end)区间的元素赋值给当前list
assign(n,ele);//将n个ele元素赋值给当前list
swap(lst);//两个list交换内部的元素
示例:
//赋值和交换
void test02()
{
list<int> L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
cout << "L1=";
printList(L1);
list<int> L2;
L2 = L1;
printList(L2);
list<int> L3;
L3.assign(L2.begin(), L2.end());
printList(L3);
list<int> L4;
L4.assign(6, 6);
cout << "L4=";
printList(L4);
cout << "交换后:" << endl;
L1.swap(L4);
cout << "L1=";
printList(L1);
cout << "L4=";
printList(L4);
}
3.6.4list大小相关
size();
empty();
resize(int n);
resize(int n,ele);
示例:
//大小相关
void test03()
{
list<int> L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
cout << "L1=";
printList(L1);
cout <<"是否为空:"<< L1.empty() << ",size:" << L1.size() << endl;
cout << "resize之后" << endl;
L1.resize(6);
printList(L1);
L1.resize(2);
printList(L1);
}
3.6.5list插入和删除
push_back(ele);
pop_back();
push_front(ele);
pop_front();
insert(pos,ele);//在pos指定的位置插入ele元素,返回插入元素的位置迭代器
insert(pos,n,ele);//在pos指定的位置插入n个ele元素
insert(pos,beg,end);//在pos指定的位置插入[beg,end)之间的元素
clear();
erase(pos);//删除pos位置的元素,返回下一个位置
erase(beg,end);//删除[beg,end)之间的元素,返回下一个位置
remove(ele);//删除list中所有的ele元素(ele有可能重复)
示例:
//插入和删除以及数据存取
void test04()
{
list<int> L;
L.push_back(10);
L.push_back(20);
L.push_back(30);
L.push_front(100);
L.push_front(200);
L.push_front(300);
printList(L);
L.pop_back();
L.pop_front();
printList(L);
L.insert(L.begin(), 1000);
list<int> L1(3, 6);
L.insert(L.begin(), 3, 8);
L.insert(L.begin(),L1.begin(), L1.end());
printList(L);
L.erase(L.begin());
printList(L);
L.erase(L.begin(), ++(++L.begin()));
printList(L);
L.remove(8);
printList(L);
cout << L.front() << "," << L.back() << endl;
}
3.6.6list数据存取
front();//取头部第一个元素
back();//取最后一个元素
3.6.7list反转和排序
reverse();//反转链表,注意这里不是算法
sort();//升序排序,不是算法sort()
sort(func);//按func规则排序
示例:
//反转和排序
void test05()
{
list<int> L;
L.push_back(3);
L.push_back(1);
L.push_back(6);
L.push_back(2);
printList(L);
//反转
L.reverse();
printList(L);
//排序
L.sort();//默认是升序
printList(L);
//指定规则排序
L.sort(myCompare);
printList(L);
}
//课堂练习:使用list存储自定义类型,Person(m_Name,m_Age),如果要排序,必须指定排序规则,才可以排序。
//规则如下:按照年龄进行升序,如果遇到年龄相同,再按照姓名降序排序
class Person
{
public:
string m_Name;
int m_Age;
Person(string name,int age):m_Name(name),m_Age(age){}
};
//Person的排序函数
bool comparePerson(Person& p1, Person& p2)
{
if (p1.m_Age==p2.m_Age)//年龄相等,按照姓名降序
{
return p1.m_Name > p2.m_Name;
}
else
{
return p1.m_Age < p2.m_Age;//年龄升序排序
}
}
//课堂练习,自定义数据类型排序
Person p1("abc", 6);
Person p2("bcd", 6);
Person p3("cde", 7);
list<Person> L;
L.push_back(p1);
L.push_back(p2);
L.push_back(p3);
L.sort(comparePerson);//按照指定规则排序
for (list<Person>::iterator it = L.begin(); it != L.end(); it++)
{
cout << "姓名:" << (*it).m_Name <<"," << it->m_Age << endl;
}
3.7集合set/multiset
3.7.1基本概念
set叫做集合,它的特点是所有的元素会在插入的时候自动完成排序。
set在物理空间上也不是连续的,所以也不支持随机存取(下标取值)。它的迭代器也不支持指针运算,只能++或者–
set的底层数据结构是红黑树。
二叉树:
排序二叉树:
平衡二叉树:
红黑树:
set和multiset的区别:
set不允许有重复的元素
multiset可以有重复的元素
除此之外,它们的成员函数基本是一样的。
集合是基于红黑树实现的,所以集合禁止修改元素,可以添加删除元素,因为修改会导致树的结构发生改变。
3.7.2set构造、插入、赋值、删除
构造:
set<T> st;
set(const set& st);
set<T,仿函数> st;//通过仿函数来指定排序规则,默认是升序
赋值:
重载赋值预算符=
插入、删除:
insert(ele);//在容器中插入元素
clear();
erase(pos);//删除pos迭代器指向的元素,返回下一个元素的迭代器
erase(beg,end);//删除区间[beg,end)内的元素,返回下一个迭代器
erase(ele);//删除容器中的ele元素
示例:
//set构造、插入、赋值、删除
void test01()
{
set<int> s1;//无参构造
//插入元素
s1.insert(10);
s1.insert(30);
s1.insert(20);
s1.insert(40);
printSet(s1);
//拷贝构造
set<int> s2(s1);
printSet(s2);
set<int> s3;
s3 = s2;//赋值
printSet(s3);
//删除
s1.erase(s1.begin());
printSet(s1);
s2.erase(s2.begin(), --s2.end());
printSet(s2);
s3.erase(40);
printSet(s3);
}
3.7.3set大小和交换
size();
empty();
swap(st);
示例:
//大小和交换
void test02()
{
set<int> s1;//无参构造
//插入元素
s1.insert(10);
s1.insert(30);
s1.insert(20);
s1.insert(40);
cout << s1.size() << "," << s1.empty() << endl;
set<int> s2(s1);
s2.erase(++s2.begin(), --s2.end());
s1.swap(s2);
printSet(s1);
printSet(s2);
}
3.7.4set查找和统计
find(ele);//找到返回迭代器位置,找不到返回end迭代器
count(ele);//统计ele元素的个数,对于set来说,只能是0或者1
multiset的成员函数跟set一样,区别就是它的元素可以重复。
示例:
//查找和统计
void test03()
{
set<int> s1;
s1.insert(10);
s1.insert(30);
s1.insert(20);
s1.insert(40);
set<int>::iterator pos = s1.find(30);
cout <<"找到了,它的值是:"<< * pos << endl;
cout << "统计30的数量:" << s1.count(30) << endl;
cout << "统计50的数量:" << s1.count(50) << endl;
}
//multiset
void test04()
{
multiset<int> ms;
ms.insert(10);
ms.insert(10);
ms.insert(40);
ms.insert(20);
ms.insert(30);
for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++)
{
cout << *it << " ";
}
cout << endl;
cout << "统计10的数量:" << ms.count(10) << endl;
}
3.7.5仿函数(函数对象)
-
仿函数本质是一个类对象,不是函数,它在类内部重载了运算符(),使用起来像函数。
-
使用仿函数,可以当作参数去指定容器的排序规则,尤其是自定义类型的数据,必须要在set初始化的时候就指定排序规则,否则无法插入元素。
-
返回值是bool类型的仿函数,叫做谓词。
//对于自定义类型,必须指定排序规则,才能插入set中 class Person { public: string m_Name; int m_Age; Person(string name,int age):m_Name(name),m_Age(age){} }; //仿函数,用于指定Person的排序规则 class ComparePerson { public: bool operator()(const Person& p1, const Person& p2) const//注意,这里的对象参数必须声明为常量 { //按照年龄降序 return p1.m_Age > p2.m_Age; } }; void printPersonSet(set<Person, ComparePerson>& st) { for (set<Person,ComparePerson>::iterator it = st.begin(); it != st.end(); it++) { cout << it->m_Name << "," << it->m_Age << endl; } }
//对于自定义类型,必须指定排序规则,才能插入set中 Person p1("aaa", 12); Person p2("bbb", 14); Person p3("ccc", 10); set<Person, ComparePerson> st; st.insert(p1); st.insert(p2); st.insert(p3); printPersonSet(st);
-
内置的仿函数,通过引入头文件#include
-
关系仿函数:
-
equal_to 等于
-
not_equal_to 不等于
-
greater 大于,如:set<int, greater> s1; 这些仿函数也是模板,使用的时候也要对泛型进行
-
greater_equal 大于等于
-
less 小于
-
less_equal 小于等于
-
-
算术仿函数:
-
plus 加法
-
minus 减法
-
multiplies 乘法
-
divides 除法
-
negate 取反
-
-
逻辑仿函数:
-
logical_and 逻辑与
-
logical_or 逻辑或
-
logical_not 逻辑非
-
-
示例:
//使用系统提供的默认仿函数
void test06()
{
set<int, greater<int>> s1;
s1.insert(10);
s1.insert(30);
s1.insert(20);
s1.insert(40);
for (set<int, greater<int>>::iterator it = s1.begin(); it != s1.end(); it++)
{
cout << *it << " ";
}
}
3.8 pair对组
- 这个数据结构是存储成对的数据
- pair中有两个数据,第一个叫做key(键值),起到了索引作用,第二个叫做value(实际的值),对组也叫键值对。
对组的创建方式:
pair<type,type> p(key,value);
pair<type,type> p=make_pair(key,value);
对组的访问方式:
- 可以通过队组的两个属性来分别访问两个值:first、second
//对组
void test01()
{
pair<string, int> p(string("tom"), 23);
cout << "姓名:" << p.first << ",年龄:" << p.second << endl;
pair<string, int> p1 = make_pair(string("jerry"), 21);
cout << "姓名:" << p1.first << ",年龄:" << p1.second << endl;
}
3.9 map\multimap字典
3.9.1 map概念
- map也是基于红黑树的数据结构,所以它的元素也会自动排序(默认升序,它是根据key来排序的),它也不能修改key的值。但是,我们可以修改value的值。
- 它也不支持随机存取,因为不是连续的物理空间,所以它的迭代器也不支持算术运算,只能++或者-
- 可以通过map的key拿到对应的value:如map[key]就能拿到这个key对应的value。也可以通过at(key)取
- map不允许key重复
- multimap则允许key重复。
3.9.2 map构造、赋值、取值、插入、删除
-
构造:
map<T1,T2> mp; map(const map& mp); map<T1,T2,仿函数> mp;//仿函数指定排序规则,对于自定义类型必须要有。
-
赋值:
重载赋值运算符=
-
取值:
map[key];//通过key得到对应的value,也可以通过at(key)取值。
-
插入:
insert(ele);//插入元素ele,ele是个对组
-
删除:
erase(pos);//删除迭代器所指元素,返回下一个迭代器 erase(beg,end);//删除迭代器区间的元素,返回下一个迭代器 erase(key);//删除索引为key的对组 clear();//清空
- 示例:
void test02()
{
map<int, int> m;
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(2, 20));
//printMap(m);
//map<int, int> m1(m);
//printMap(m1);
//map<int, int> m2;
//m2 = m1;
//printMap(m2);
//其他几种插入方式
m.emplace(3, 30);
m.insert(make_pair(4, 40));
m[5] = 50;//通过key取值的方式,如果当前key不存在,相当于插入
m.insert(map<int, int>::value_type(6, 60));
printMap(m);
取值,通过key取值
//cout <<"key=3:"<< m[3] << endl;
//m[3] = 300;
//cout <<"key=3:"<< m[3] << endl;
删除
//m.erase(m.begin());
//m.erase(m.begin(), ++m.begin());
//m.erase(5);
//printMap(m);
}
3.9.3map大小和交换
size();
empty();
swap(mp);
- 示例:
//大小和交换
void test03()
{
map<int, int> m;
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(2, 20));
m.insert(pair<int, int>(3, 30));
cout << "size:" << m.size() << ",empty:" << m.empty() << endl;
map<int, int> m1;
m1.insert(pair<int, int>(4, 40));
m1.insert(pair<int, int>(5, 50));
m1.insert(pair<int, int>(6, 60));
m.swap(m1);
printMap(m);
}
3.9.4查找和统计
find(key);//根据key查找,存在则返回迭代器位置,不存在返回end迭代器
count(key);//统计key的个数,对于map来说,只能是0或者1
-
multimap的成员函数跟map一样,区别是允许key重复。
-
multimap不能通过key直接取值,因为key有重复,可以通过count和find两个函数来依此取值,先统计相同的key有几个,然后再通过find找到第一个key,然后操作迭代器依此取后面的几个值。
-
示例:
//查找和统计
void test04()
{
map<int, int> m;
m.insert(pair<int, int>(1, 10));
m.insert(pair<int, int>(2, 20));
m.insert(pair<int, int>(3, 30));
//查找
map<int, int>::iterator pos = m.find(2);
cout << pos->first << ":" << pos->second << endl;
cout << m.count(1) << endl;
cout << m.at(3) << endl;
}
//multimap
//multimap中查找重复的多个key
void test05()
{
multimap<string, int> mp;
mp.insert(make_pair("tom", 10));
mp.insert(make_pair("tom", 10));
mp.insert(make_pair("tom", 10));
mp.insert(make_pair("jerry", 20));
mp.insert(make_pair("jerry", 20));
//查找多个tom
int count=mp.count("tom");//先查找有几个tom
//逐个输出每个tom
//先找到第一个tom的位置
multimap<string,int>::iterator it=mp.find("tom");
for (int i = 0; i < count; i++)
{
cout << it->first << ":" << it->second << endl;
it++;
}
}
*** 自定义排序规则**
//仿函数,实现降序
class MyCompare
{
public:
bool operator()(int v1, int v2) const
{
return v1 > v2;
}
};
void test06()
{
//改变key的排序规则为降序
map<int, int, MyCompare> m;
m.insert(make_pair(2, 20));
m.insert(make_pair(1, 10));
m.insert(make_pair(3, 30));
m.insert(make_pair(4, 40));
for (map<int,int,MyCompare>::iterator it = m.begin(); it != m.end(); it++)
{
cout << "key=" << it->first << ",value=" << it->second << endl;
}
}
void printMapString(map<string, int>& m)
{
for (map<string, int>::iterator it = m.begin(); it != m.end(); it++)
{
cout << "key=" << it->first << ",value=" << it->second << endl;
}
cout << endl;
}
//针对对组的排序方法,降序
bool pairSort(pair<string, int>& p1, pair<string, int>& p2)
{
return p1.second > p2.second;
}
//map根据value来排序
//直接使用map是无法实现根据value排序的,但我们可以这样做:将map转换为vector,然后通过vector的排序算法指定根据value排序即可
void test07()
{
map<string, int> mp;
mp.insert(make_pair("tom", 10));
mp.insert(make_pair("jerry", 12));
mp.insert(make_pair("lucy", 8));
printMapString(mp);
//根据value排序,需要先将map的元素放到vector中
vector<pair<string, int>> v(mp.begin(), mp.end());
//接下来使用sort算法排序,可以指定排序规则
sort(v.begin(), v.end(), pairSort);//根据对组中的value降序排序
for (vector<pair<string,int>>::iterator it = v.begin(); it != v.end(); it++)
{
cout << it->first << ":" << it->second << endl;
}
}
3.10容器总结:
3.10.1各容器共性:
- (1)STL容器提供的都是值拷贝,当容器插入元素的时候,容器内部实现了拷贝动作,将我们要插入的元素拷贝了一份放到容器中。
- (2)除了栈和队列之外,其他容器都提供了迭代器。
- (3)每个容器都提供了默认的构造函数和拷贝构造函数。
- (4)每个容器都提供了size和empty函数。
- (5)物理上连续的存储结构的容器,都支持随机存储,支持指针运算。非连续空间数据结构的容器,迭代器只能++或–
- (6)连续空间存储的容器,如vector和deque,插入和删除操作,会引起迭代器失效,如果要再次使用迭代器,需要做更新
- (7)基于红黑树的容器set和map,都会自动升序排序,如果要插入自定义类型,必须用仿函数指定排序规则。它们都不能修改排序的值。
3.10.2容器使用时机:
- (1)vector适合存储历史操作记录,比如说日志。因为历史记录一般不删除,只是新增或查看。
- (2)deque适合排队购票的场景,涉及的主要是头尾两头的操作,这时候用vector就不适合,因为头部操作效率很低。
- (3)list适合数据频繁的插入删除,并且不是在确定位置。但是list遍历效率太低,所以不适合频繁的查找需求。
- (4)set适合需要排序的数据,并且数据量较大,红黑树的查找和增删效率都不错。比如说游戏玩家的排行榜。
- (5)map适合存储成对的数据,并且数据较大,需要考虑查询效率。比如说用户的信息包含id和详情信息,
p<string,int>::iterator it=mp.find(“tom”);
for (int i = 0; i < count; i++)
{
cout << it->first << “:” << it->second << endl;
it++;
}
}
*** 自定义排序规则**
```c++
//仿函数,实现降序
class MyCompare
{
public:
bool operator()(int v1, int v2) const
{
return v1 > v2;
}
};
void test06()
{
//改变key的排序规则为降序
map<int, int, MyCompare> m;
m.insert(make_pair(2, 20));
m.insert(make_pair(1, 10));
m.insert(make_pair(3, 30));
m.insert(make_pair(4, 40));
for (map<int,int,MyCompare>::iterator it = m.begin(); it != m.end(); it++)
{
cout << "key=" << it->first << ",value=" << it->second << endl;
}
}
void printMapString(map<string, int>& m)
{
for (map<string, int>::iterator it = m.begin(); it != m.end(); it++)
{
cout << "key=" << it->first << ",value=" << it->second << endl;
}
cout << endl;
}
//针对对组的排序方法,降序
bool pairSort(pair<string, int>& p1, pair<string, int>& p2)
{
return p1.second > p2.second;
}
//map根据value来排序
//直接使用map是无法实现根据value排序的,但我们可以这样做:将map转换为vector,然后通过vector的排序算法指定根据value排序即可
void test07()
{
map<string, int> mp;
mp.insert(make_pair("tom", 10));
mp.insert(make_pair("jerry", 12));
mp.insert(make_pair("lucy", 8));
printMapString(mp);
//根据value排序,需要先将map的元素放到vector中
vector<pair<string, int>> v(mp.begin(), mp.end());
//接下来使用sort算法排序,可以指定排序规则
sort(v.begin(), v.end(), pairSort);//根据对组中的value降序排序
for (vector<pair<string,int>>::iterator it = v.begin(); it != v.end(); it++)
{
cout << it->first << ":" << it->second << endl;
}
}
3.10容器总结:
3.10.1各容器共性:
- (1)STL容器提供的都是值拷贝,当容器插入元素的时候,容器内部实现了拷贝动作,将我们要插入的元素拷贝了一份放到容器中。
- (2)除了栈和队列之外,其他容器都提供了迭代器。
- (3)每个容器都提供了默认的构造函数和拷贝构造函数。
- (4)每个容器都提供了size和empty函数。
- (5)物理上连续的存储结构的容器,都支持随机存储,支持指针运算。非连续空间数据结构的容器,迭代器只能++或–
- (6)连续空间存储的容器,如vector和deque,插入和删除操作,会引起迭代器失效,如果要再次使用迭代器,需要做更新
- (7)基于红黑树的容器set和map,都会自动升序排序,如果要插入自定义类型,必须用仿函数指定排序规则。它们都不能修改排序的值。
3.10.2容器使用时机:
- (1)vector适合存储历史操作记录,比如说日志。因为历史记录一般不删除,只是新增或查看。
- (2)deque适合排队购票的场景,涉及的主要是头尾两头的操作,这时候用vector就不适合,因为头部操作效率很低。
- (3)list适合数据频繁的插入删除,并且不是在确定位置。但是list遍历效率太低,所以不适合频繁的查找需求。
- (4)set适合需要排序的数据,并且数据量较大,红黑树的查找和增删效率都不错。比如说游戏玩家的排行榜。
- (5)map适合存储成对的数据,并且数据较大,需要考虑查询效率。比如说用户的信息包含id和详情信息,