本阶段主要针对 C++泛型编程 和STD技术做详细学习,探讨 C++ 更深层次的使用。
一、 模板
模板就是建立通用的模具,大大提高复用性。学习模板是为了会用别人提供的模板。
- C++ 另一种编程思想称为泛型编程,主要利用的技术就是模板
- C++ 提高两种模板机制:函数模板和类模板
1.1 函数模板
1.1.1 函数模板定义
函数模板:建立一个通用函数,其函数返回类型和形参类型可以不具体制定,用一个虚拟的类型来代表
语法:
template<typename T> // 注意这里没有分号;
void func(T &a){...} // 函数声明或定义
解释:
- template — 声明创建模板
- typename — 表明其后面的符号是一种数据类型,可以用 class 代替
- T — 通用的数据类型,名称可以替换,通常为大写字母
template<typename T> // 声明一个模板,告诉编译器 T 为通用数据类型,不要报错
void mySwap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
int main() {
float a = 1.1;
float b = 2.2;
mySwap(a, b); // 第一种调用:自动类型推导
cout << a << endl;
cout << b << endl;
int c = 10;
int d = 20;
mySwap<int>(c, d); // 第二种调用:显示指定类型
cout << c << endl;
cout << d << endl;
system("pause");
}
总结:
- 函数模板用关键字 template
- 使用函数模板有两种方式:自动类型推导、显示指定类型
- 模板的目的时为了提高函数复用性,将类型参数化。
1.1.2 函数模板注意事项
注意事项:
自动类型推导,必须推导出一致的数据类型 T,才可使用
模板必须要确定出 T 的数据类型,才可以使用
template<typename T> // 声明了 T, 紧接着的 test()函数却没有使用 void test() { cout << "test()调用 " << endl; } void mySwap(T &a, T &b){ // 这个会报错 T, 上面的 T 声明仅对 T 声明下面的第一个函数有效 cout << " 交换 " << endl; } int main() { test(); // 这里调用就会报错 test<int>() // 这随意指定一个 int,就没问题了 system("pause"); }
1.1.3 函数模板案例
案例描述:
- 利用函数模板封装一个排序的函数,可以对不同的数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
- 分别利用 char 数组和 int 数组进行测试
template<typename T>
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
template<typename T>
void mySort(T arr[], int len) {
for (int i = 0; i < len; ++i) {
int max = i;
for (int j = i + 1; j < len; ++j) {
if (arr[max] < arr[j]) {
max = j;
}
}
if (max != i) {
mySwap(arr[max], arr[i]);
}
}
}
void test() {
char cha[] = "dhsjas";
//int cha[] = { 3,4,2,5,1,6,8,7 };
int len = sizeof(cha) / sizeof(cha[0]);
mySort(cha, len);
for (int i = 0; i < len; ++i) {
cout << cha[i] << " ";
}
}
int main() {
test();
system("pause");
}
1.1.4 普通函数与函数模板的区别
普通函数与函数模板区别(主要是是否可以发生隐式类型转换):
- 普通函数调用时,可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
int addtest1(int a, int b) {
return a + b;
}
template<typename T>
T addtest2(T a, T b) {
return a + b;
}
int main() {
int a = 10;
char c = 'c'; //(int)c = 99
cout << addtest1(a, c) << endl;
// 自动类型推导
//cout << addtest2(a, c) << endl; 报错
// 显示指定类型
cout << addtest2<int>(a, c) << endl;
system("pause");
}
1.1.5 普通函数与函数模板的调用规则
- 如果函数模板和普通函数都可以实现,优先使用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
int test1(int a, int b);
template<typename T>
void test1(T a, T b) {
cout << " 调用模板函数 " << endl;
}
int main() {
int a = 10;
int b = 20;
// 报错,调用普通函数,然而普通函数只有声明没有实现
//test1(a, b);
// 通过空模板参数列表, 强制调用函数模板
test1<>(a, b) ;
// 函数模板可以产生更好的匹配,优先调用函数模板
char c1 = 'a';
char c2 = 'b';
test1(c1, c2);
system("pause");
}
1.1.6 模板的局限性 / 具体化的模板
有时候通用模板并不能解决所有问题,这时候可以利用具体化的模板,去解决自定义类型的通用化。
class Person {
public:
Person(string name, int age) {
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
template<typename T>
bool compare(T a, T b) {
if (a == b) {
return true;
}
else {
return false;
}
};
// 具体化模板
template<> bool compare(Person &p1, Person &p2) {
if (p1.m_name == p2.m_name && p1.m_age == p2.m_age) {
return true;
}
else {
return false;
}
}
int main() {
Person p1(" 张三 ", 25);
Person p2(" 张三 ", 21);
bool ret = compare(p1, p2);
system("pause");
}
1.2 类模板
1.2.1 类模板语法
类模板作用:建立一个通用类,类中的成员、数据类型可以不具体制定,用一个虚函数类型来代表。
语法:
template<typename T>
class A{...} // 类的声明或定义
尖括号 <>
里可以定义多个通用数据类型,以逗号隔开。
template<class NameType,class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_name = name;
this->m_age = age;
}
void show_Person() {
cout << this->m_name << endl;
cout << this->m_age << endl;
}
NameType m_name;
AgeType m_age;
};
void test() {
Person<string, int> p1("Tom", 23); //<string,int> 叫模板的参数列表,必须指定
p1.show_Person();
}
int main() {
test();
system("pause");
}
1.2.2 类模板与函数模板区别
类模板与函数模板区别:
- 类模板没有自动类型推导的使用方式。
- 类模板在模板参数列表中可以有默认参数。
template<class NameType, class AgeType=int>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_name = name;
this->m_age = age;
}
NameType m_name;
AgeType m_age;
};
// 类模板没有自动类型推导的使用方式
void test1() {
Person p1("Tom", 23); // 报错,必须指定类型
}
// 类模板在模板参数列表中可以有默认参数
void test2() {
Person<string> p2("Tomo", 22); //template 中已经指定了第二个参数的默认类型
}
1.2.3 类模板中的成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以创建。
- 类模板中的成员函数在调用时才创建。
class Person1 {
public:
void show1() {
cout << "Person1 成员函数的调用 " << endl;
}
};
class Person2 {
public:
void show2() {
cout << "Person2 成员函数的调用 " << endl;
}
};
template<class T>
class Person {
public:
T obj;
// 类模板中的成员函数
void func1() {
obj.show1();
}
void func2() {
obj.show2();
}
};
void test() {
Person<Person1> p;
p.func1(); // 这里并不会报错,调用 func1()只会创建 func1()
p.func2(); // 报错
}
int main() {
test();
system("pause");
}
1.2.4 类模板对象做函数参数
一共有三种传入方式:
- 指定传入的类型:直接显示对象的数据类型
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将这个对象类型模板化进行传递
template <class T1,class T2>
class Person {
public:
Person(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
void show() {
cout << m_name << endl;
cout << m_age << endl;
}
T1 m_name;
T2 m_age;
};
// 指定传入的类型
void test1(Person<string, int>&p) {
p.show();
}
// 参数模板化
template<class T1, class T2>
void test2(Person<T1, T2>& p) {
p.show();
}
// 整个类模板化
template<class T>
void test3(T &p) {
p.show();
cout << typeid(T).name() << endl; // 输出
}
int main() {
Person<string, int>p("Tom", 200);
test1(p);
Person<string, int>p2("Tomo", 20);
test2(p2);
Person<string, int>p3("July", 50);
test3(p3);
system("pause");
}
使用最广泛的是第一种:指定传入的类型。
1.2.5 类模板与继承
当类模板碰到继承时,需要注意以下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定父类中 T 的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定父类中 T 的类型,子类也需要变为类模板
template <class T>
class Base {
T m;
};
// 在继承父类时,父类模板必须要指定类型
class Son1 :public Base<int> {...};
// 也可以灵活指定,子类也需要变为类模板
template<class T1,class T2>
class Son2 :public Base<T1> {
public:
T2 obj;
Son2() {
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
}
};
void test() {
Son1 s1;
Son2<int, char> s2;
}
int main() {
test();
system("pause");
}
1.2.6 类模板成员函数类外实现
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 << this->m_name << endl;
cout << this->m_age << endl;
}
1.2.7 类模板成员函数类外实现
当我们写类模板时,成员函数的调用时机是在调用阶段,导致文件编写时链接不到
有两种解决方式:
- 解决方式 1:直接包含.cpp 源文件
- 解决方式 2:将声明和实现写到同一个文件中,并更改后缀名为.hpp。hpp 是约定的名称,并不是强制
1.2.8 类模板与友元
全局函数类内实现:直接在类内声明友元即可。
全局函数类外实现:需要提前让编译器知道全局函数的存在。
template <class T1, class T2>
class Person;
// 类外实现
template <class T1, class T2>
void showPerson2(Person<T1, T2> p) {
cout << p.m_name << endl;
cout << p.m_age << endl;
}
template <class T1, class T2>
class Person {
// 全局函数在类内实现
friend void showPerson(Person<T1, T2> p) {
cout << p.m_name << endl;
cout << 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 test() {
Person<string, int> p1("Tom", 25);
showPerson(p1); // 全局函数在类内实现
showPerson2(p1); // 全局函数在类外实现
}
int main() {
test();
system("pause");
}
一般建议全局函数模板类内实现,类外实现太复杂了。
1.2.9 类模板案例
实现一个通用的数组类,要求如下:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及 operator= 防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
首先定义一个数组模板类,文件命名为 Array.hpp:
#include<iostream>
#include<string>
using namespace std;
template<class T>
class Array {
public:
// 有参构造函数,用于初始化数组容量
Array(int capacity) {
this->mSize = 0;
this->mCapacity = capacity;
this->mAddress = new T[this->mCapacity];
}
// 拷贝构造函数
Array(const Array& arr) {
this->mCapacity = arr.mCapacity;
this->mSize = arr.mSize;
this->mAddress = new T[this->mCapacity];
for (int i = 0; i < this->mSize; ++i) {
this->mAddress[i] = arr.mAddress[i];
}
}
// 重载 = 操作符,深拷贝
Array& operator=(const Array& arr) {
if (this->mAddress != NULL) {
delete[] this->mAddress;
this->mSize = 0;
this->mCapacity = 0;
}
this->mCapacity = arr.mCapacity;
this->mSize = arr.mSize;
this->mAddress = new T[this->mCapacity];
for (int i = 0; i < this->mSize; ++i) {
this->mAddress[i] = arr.mAddress[i];
}
return *this;
}
// 尾部插入数据
void Push_Back(const T& val) {
if (this->mCapacity == this->mSize) {
return;
}
this->mAddress[this->mSize] = val;
this->mSize++;
}
// 从尾部删除数据
void Pop_Back() {
if (this->mSize == 0) {
return;
}
this->mSize--; // 逻辑删除,让用户访问不到
}
T& operator[](int index) {
return this->mAddress[index];// 暂未考虑越界的问题
}
int getCapacity() {
return this->mCapacity;
}
int getSize() {
return this->mSize;
}
~Array() {
if (this->mAddress != NULL) {
delete[] this->mAddress;
this->mAddress = NULL;
this->mCapacity = 0;
this->mSize = 0;
}
}
private:
T* mAddress; // 指针指向堆区开辟的真实数组
int mCapacity; // 数组容量
int mSize; // 数组当前的大小
};
测试案例,命名为 ArrayTest.cpp:
#include<iostream>
#include<string>
#include"Array.hpp"
using namespace std;
void printIntArray(Array<int>& arr) {
for (int i = 0; i < arr.getSize(); ++i) {
cout << arr[i] << " ";
}
cout << endl;
}
// 测试 1
void test1() {
Array<int> arr1(6);
Array<int> arr3(100);
arr1 = arr3;
for (int i = 0; i < 6; ++i) {
arr1.Push_Back(i);
}
printIntArray(arr1);
cout << "arr1 的大小:" << arr1.getSize() << endl;
cout << "arr1 的容量:" << arr1.getCapacity() << endl;
Array<int> arr2(arr1);
printIntArray(arr2);
cout << "arr2 的大小:" << arr2.getSize() << endl;
cout << "arr2 的容量:" << arr2.getCapacity() << endl;
arr2.Pop_Back();
printIntArray(arr2);
cout << "arr2 的大小:" << arr2.getSize() << endl;
cout << "arr2 的容量:" << arr2.getCapacity() << endl;
}
// 测试自定义数据类型
class Person {
public:
Person() {};
Person(string name, int age) {
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
void printPersonArray(Array<Person>& arr) {
for (int i = 0; i < arr.getSize(); ++i) {
cout << arr[i].m_name + " " << arr[i].m_age << endl;
}
cout << endl;
}
// 测试自定义的数据类型
void test2() {
cout << "\n--------------test2------------" << endl;
Array<Person> arr(10);
Person p1(" 孙悟空 ", 999);
Person p2(" 韩信 ", 24);
Person p3(" 妲己 ", 23);
Person p4(" 赵云 ", 26);
Person p5(" 鲁班 ", 12);
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
arr.Push_Back(p5);
printPersonArray(arr);
cout << "arr 的大小:" << arr.getSize() << endl;
cout << "arr 的容量:" << arr.getCapacity() << endl;
}
int main() {
test1();
test2();
system("pause");
}
1.3 写模板的坑
推荐把模板的实现和声明都写在头文件!否则编译会报错。
还有一种方式,可以分离声明与实现:
h 头文件:
template <typename T>
T add(const T &a, const T &b);
cpp 实现:
#include "add.h"
template <typename T>
T add(const T &a, const T &b)
{
return a + b;
}
template int add(const int &a, const int &b);
最后一行要指定模板的实际类型。
二、STL 初识
2.1 STL 的诞生
- 长久以来,人们一直希望建立一种可以重复利用的东西
- C++ 面向对象编程和泛型编程思想,目的就是复用性的提升
- 大多情况下,数据结构和算法都未有一套标准,导致被迫从事大量重复工作
- 为了建立数据结构和算法的一套标准,诞生了 STL(Standard Template Library, 标准模板库)
2.2 STL 的介绍
- STL 从广义上分为:容器 (container), 算法 (algorithm), 迭代器(iterator)
- 容器 和算法 之间通过 迭代器 进行无缝链接
- STL 几乎所有的代码都采用了模板类或者模板函数
2.3 STL 六大组件
STL 大体分为六大组件,分别是容器,算法,迭代器,仿函数,适配器(配接器),空间配置器
- 容器:各种数据结构,如 vector, list, deque, set, map 等,用来存放数据
- 算法:各种常用的算法,如 sort, find, copy, for_each 等。
- 迭代器:扮演了容器与算法之间的胶合剂
- 仿函数,行为类似函数,可作为算法的某种策略
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
- 空间配置器:负责用来空间的配置与管理
2.4 STL 中的容器、算法、迭代器
容器:置物之所也。
STL 容器就是将运用最广泛的一些数据结构实现出来,常用的数据结构:数组,链表,树,栈,队列,集合,映射表等。
这些容器分为序列式容器和关联式容器两种:
- 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。
- 关联式容器:二叉树结构,各元素之间没有严格的物理意义上的顺序关系。只能一步步偏移取值,不能跳转。
算法:问题值解法也。
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)。
算法分为:质变算法和非质变算法。
- 质变算法:是指运算过程中,会更改区间元素的内容,例如拷贝,替换,删除等。
- 非质变算法:是指运算过程中,不会更改区间内的元素内容,例如查找,计数,遍历,寻找极值等等。
迭代器:容器和算法之间的粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器。
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针。
迭代器种类:
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据的只读访问 | 只读,支持 ++,==,!= |
输出迭代器 | 对数据的只写访问 | 只写,支持 ++ |
前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持 ++,==,!= |
双向迭代器 | 读写操作,并能向前向后操作 | 读写,支持 ++,– |
随机访问迭代器 | 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 | 读写,支持 ++,–,[n],-n,<,<=, >,>= |
常用的容器迭代器种类为 双向迭代器 和随机访问迭代器
2.5 容器算法迭代器初识
STL 中最常用的容器为 Vector,可以理解为数组。
2.5.1 vector 存放内置数据类型
容器:vector
算法:for_each
迭代器:vector<int>::interator
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void Mprint(int val) {
cout << val << endl;
}
void test() {
vector<int> v;
v.push_back(10); // 插入数据
v.push_back(20);
v.push_back(30);
v.push_back(40);
// 通过迭代器访问容器中的数据
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;
}
// 第三种遍历方式,利用 STL 提供的遍历算法
for_each(v.begin(), v.end(), Mprint);
}
int main() {
test();
system("pause");
}
2.5.2 vector 存放自定义数据类型
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
class Person {
public:
Person(string name, int age) {
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
// 传入自定义的数据类型
void test1() {
vector<Person> v;
Person p1("Tom", 23);
Person p2("Jack", 18);
Person p3("Judy", 23);
Person p4("Mark", 23);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
for (vector<Person>::iterator it = v.begin(); it != v.end(); ++it) {
//cout << (*it).m_name << " " << (*it).m_age << endl; // *it 是 Perosn 类型的数据
cout << it->m_name << " " << it->m_age << endl; // 推荐这种
}
}
// 传入自定义的数据类型的指针
void test2() {
vector<Person*> v; //v 用来储存指针(指针指向 person 类型)
Person p1("Tom", 23);
Person p2("Jack", 18);
Person p3("Judy", 23);
Person p4("Mark", 23);
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
v.push_back(&p4);
for (vector<Person*>::iterator it = v.begin(); it != v.end(); ++it) {
cout << (*it)->m_name << " " << (*it)->m_age << endl; // *it 是 Perosn 类型的数据
}
}
int main() {
test1();
test2();
system("pause");
}
2.5.3 vector 容器嵌套容器
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
void test1() {
vector<vector<int>> v;
// 创建小容器
vector<int> v1;
vector<int> v2;
vector<int> v3;
// 向小容器添加数据
for (int i = 0; i < 3; ++i) {
v1.push_back(i + 1);
v2.push_back(i + 2);
v3.push_back(i + 3);
}
// 将小容器插入到大容器中
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
// 通过大容器,把所有数据遍历一遍
for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); ++it) {
for (vector<int>::iterator jt = (*it).begin(); jt != (*it).end(); ++jt) {
cout << *jt << " ";
}
cout << endl;
}
}
int main() {
test1();
system("pause");
}
三、STL 常用容器
STL 中list
、vector
、map
是三个最常被使用的容器。
STL 中,所有容器拷贝均为 深拷贝。没有浅拷贝的概念。
3.1 string 容器
3.1.1 string 基本概念
本质:string 是 C++ 风格的字符串,而 string 本质是一个类
string 和 char 区别:
- char * 是一个指针
- string 是一个类,类内部封装了
char*
,管理这个字符串,是一个char*
类型的容量
特点:
- string 类内部封装了很多成员方法,如查找 find,拷贝 copy,删除 delete,替换 replace,插入 insert
- string 管理
char*
所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
3.1.2 string 构造函数
构造函数原型:
string();
// 创建一个空的字符串,例如 string str;string(const char* s);
// 使用字符串 s 初始化string(const string& str);
// 使用一个 string 对象初始化另一个 string 对象string(int n, char c);
// 使用 n 个字符 c 初始化
3.1.3 string 赋值操作
string 赋值操作:即给 string 字符串进行赋值
赋值的函数原型:
string& operator=(const char* s);
//char* 类型字符串,赋值给当前的字符串string& operator=(const string& s);
// 把字符串 s 赋值给当前的字符串string& operator=(char s);
// 把字符赋值给当前的字符串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 s);
// 用 n 个字符 s 赋值给当前的字符串
3.1.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 *s, int n);
// 把字符串 s 的前 n 个字符连接到当前字符串结尾string& append(const string &s);
// 同 operator+=(const string& str)string& append(const string &s, int pos, int n);
// 字符串 s 中从 pos 开始的 n 个字符连接到字符串结尾string& append(int n, char c);
// 在字符串结尾拼接 n 个 c
// 字符串拼接
void test01(){
string str1 = " 我 ";
// += 拼接
str1 += " 爱玩游戏 ";
cout << "str1 = " << str1 << endl;
string str2 = "LOL DNF";
str1 += str2;
cout << "str1 = " << str1 << endl;
string str3 = "I";
// append 拼接
str3.append(" love ");
str3.append("game abcde", 4);
//append 指定位置拼接
str3.append(str2, 4, 3); // 从下标 4 位置开始 ,截取 3 个字符,拼接到字符串末尾
cout << "str3 = " << str3 << endl;
}
3.1.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 第一次出现位置int rfind(const string& str, int pos = npos) const;
// 查找 str 最后一次位置, 从 pos 开始查找int rfind(const char* s, int pos = npos) const;
// 查找 s 最后一次出现位置, 从 pos 开始查找int rfind(const char* s, int pos, int n) const;
// 从 pos 查找 s 的前 n 个字符最后一次位置int rfind(const char c, int pos = 0) const;
// 查找字符 c 最后一次出现位置string& replace(int pos, int n, const string& str);
// 替换从 pos 开始 n 个字符为字符串 strstring& replace(int pos, int n,const char* s);
// 替换从 pos 开始的 n 个字符为字符串 s
// 查找
void test01(){
string str1 = "abcdefgde";
//find 找第一次出现的位置
int pos = str1.find("de");
if (pos == -1){
cout << " 未找到 " << endl;
}
else{
cout << "pos = " << pos << endl;
}
//rfind 找最后一次出现的位置
pos = str1.rfind("de");
cout << "pos = " << pos << endl;
}
// 替换
void test02(){
string str1 = "abcdefgde";
str1.replace(1, 3, "1111"); // 从位置 1 开始的共 3 个字符替换为 1111
cout << "str1 = " << str1 << endl;
}
3.1.6 string 字符串比较
功能描述:
- 字符串之间的比较
比较方式:
- 字符串比较是按字符的 ASCII 码进行对比
= 返回 0
> 返回 1
< 返回 -1
函数原型:
int compare(const string &s) const;
// 与字符串 s 比较int compare(const char *s) const;
// 与字符串 s 比较
void test01(){
string s1 = "hello";
string s2 = "aello";
int ret = s1.compare(s2);
if (ret == 0) {
cout << "s1 等于 s2" << endl;
}
else if (ret > 0)
{
cout << "s1 大于 s2" << endl;
}
else
{
cout << "s1 小于 s2" << endl;
}
}
3.1.7 string 字符存取
string 中单个字符存取方式有两种
char& operator[](int n);
// 通过 [] 方式取字符char& at(int n);
// 通过 at 方法获取字符
void test01(){
string str = "hello world";
// 获取字符
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';
str.at(1) = 'x';
cout << str << endl;
}
[]:下标操作符 [] 在使用时不检查索引的有效性,如果下标超出字符的长度范围,会示导致未定义行为。
at:函数 at() 在使用时会检查下标是否有效。如果给定的下标超出字符的长度范围,系统会抛出 out_of_range 异常。
3.1.8 string 插入和删除
功能描述:
- 对 string 字符串进行插入和删除字符操作
函数原型:
string& insert(int pos, const char* s);
// 插入字符串string& insert(int pos, const string& str);
// 插入字符串string& insert(int pos, int n, char c);
// 在指定位置插入 n 个字符 cstring& erase(int pos, int n = npos);
// 删除从 Pos 开始的 n 个字符
// 字符串插入和删除
void test01()
{
string str = "hello";
//insert 插入
str.insert(1, "111");
cout << str << endl; //
//erase 删除
str.erase(1, 3); // 从 1 号位置开始 3 个字符, 第二个数字可以不写,意为删除从第 1 个到最后一个
cout << str << endl;
}
3.1.9 string 子串
功能描述:
- 从字符串中获取想要的子串
函数原型:
string substr(int pos = 0, int n = npos) const;
// 返回由 pos 开始的 n 个字符组成的字符串
void test01(){
string str = "abcdefg";
// 获取子串
string subStr = str.substr(1, 3); // 若第二个数字可以不写,意为取从第 1 个到最后一个
cout << "subStr = " << subStr << endl;
string email = "hello@sina.com";
int pos = email.find("@");
string username = email.substr(0, pos);
cout << "username: " << username << endl;
}
3.2 vector 容器
3.2.1 vector 基本概念
vector 数据结构和数组非常相似,也称为单端数组。
vector 与普通数组区别:数组是静态空间,而 vector 可以动态扩展。数组 array 在定义时必须显式或隐式指定其 size;vector 则不用。
动态扩展:并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝至新空间,释放原空间。
vector 容器的迭代器是支持随机访问的迭代器。
3.2.2 vector 构造函数
函数原型:
vector<T> v;
// 采用模板实现类实现,默认构造函数vector(v.begin(), v.end());
// 将 v[begin(), end()]区间中的元素拷贝给本身。vector(n, elem);
// 构造函数将 n 个 elem 拷贝给本身。vector(const vector &vec);
// 拷贝构造函数。
#include <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()); // 将 v1 的所有数据传给 v2
printVector(v2);
vector<int> v3(10, 100); // 传入 10 个 100 给 V3
printVector(v3);
vector<int> v4(v3); // 拷贝构造
printVector(v4);
}
vector 数组快速声明与初始化:
// 初始化数组长度
vector<int> v1(10);
// 初始化数组大小和值
vector<int> v2(10, 100);
// 直接赋值
vector<int> v3 = {1,2,3,4,5};
// 使用数组初始化
int arr[] = {1,2,3,4};
vector<int> v4(arr, arr+4);
vector<int> v5(std::begin(arr), std::end(arr));
3.2.3 vector 赋值操作
函数原型:
vector& operator=(const vector &vec);
// 重载等号操作符
assign(beg, end);
// 将 [beg, end) 区间中的数据拷贝赋值给本身。assign(n, elem);
// 将 n 个 elem 拷贝赋值给本身。
#include <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;
v2 = v1; // = 号赋值
printVector(v2);
vector<int>v3;
v3.assign(v1.begin(), v1.end()); //assign 赋值
printVector(v3);
vector<int>v4;
v4.assign(10, 100); //assign 赋数值
printVector(v4);
}
3.2.4 vector 容量和大小
函数原型:
empty();
// 判断容器是否为空capacity();
// 容器的 容量size();
// 返回容器中 元素的个数resize(int num);
// 重新指定容器的长度为 num,若容器变长,则以默认值 0 填充新位置。 // 如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);
// 重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。 // 如果容器变短,则末尾超出容器长度的元素被删除
void test01()
{
vector<int> v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
//printVector(v1);
//empty 使用
if (v1.empty())
{
cout << "v1 为空 " << endl;
}
else
{
cout << "v1 不为空 " << endl;
cout << "v1 的容量 = " << v1.capacity() << endl;
cout << "v1 的大小 = " << v1.size() << endl;
}
//resize 重新指定大小为 15,用 10 补齐
v1.resize(15, 10);
//printVector(v1);
//resize 重新指定大小,超出 5 个元素部分删除
v1.resize(5);
//printVector(v1);
}
3.2.5 vector 插入和删除
函数原型:
push_back(ele);
// 尾部插入元素 elepop_back();
// 删除最后一个元素insert(const_iterator pos, ele);
// 迭代器指向位置 pos 插入元素 eleinsert(const_iterator pos, int count,ele);
// 迭代器指向位置 pos 插入 count 个元素 eleerase(const_iterator pos);
// 删除迭代器指向的元素erase(const_iterator start, const_iterator end);
// 删除迭代器从 start 到 end 之间的元素clear();
// 删除容器中所有元素
#include <vector>
void test01()
{
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);
// 插入
v1.insert(v1.begin(), 100);
//printVector(v1); //100 10 20 30 40 50
v1.insert(v1.begin(), 2, 1000);
//printVector(v1);
// 删除
v1.erase(v1.begin());
printVector(v1);
// 清空
v1.erase(v1.begin(), v1.end());
v1.clear();
//printVector(v1);
}
当 vector 容器为空时,必须采用 push_back()方法来插入元素。但如果 vector 容器初始化有值,可以用随机访问的方法重新赋值:
void test() {
vector<int> m(10,0); // 初始化 m 容器为 10 个 0
// 修改元素
for (int i = 0; i < 10; ++i) {
m[i] = i;
}
// 查看元素
for (int num : m) {
cout << num;
}
}
3.2.6 vector 数据存取
函数原型:
at(int idx);
// 返回索引 idx 所指的数据operator[idx];
// 返回索引 idx 所指的数据front();
// 返回容器中第一个数据元素back();
// 返回容器中最后一个数据元素
#include <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] << " ";
}
cout << endl;
for (int i = 0; i < v1.size(); i++)
{
cout << v1.at(i) << " ";
}
cout << endl;
cout << "v1 的第一个元素为: " << v1.front() << endl;
cout << "v1 的最后一个元素为: " << v1.back() << endl;
}
3.2.7 vector 互换容器
函数原型:
swap(vec);
// 将 vec 与本身的元素互换
void test01()
{
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);
}
当然 swap 函数更重要的作用是:收缩容量,节省空间
void test02()
{
vector<int> v;
for (int i = 0; i < 10000; i++) {
v.push_back(i);
}
cout << "v 的容量为:" << v.capacity() << endl; // 输出 12138
cout << "v 的大小为:" << v.size() << endl; // 输出 10000
v.resize(3);
cout << "v 的容量为:" << v.capacity() << endl; // 输出 12138
cout << "v 的大小为:" << v.size() << endl; // 输出 3
// 收缩容量(内存)
vector<int>(v).swap(v); //vector<int>(v)匿名对象
cout << "v 的容量为:" << v.capacity() << endl; // 输出 3
cout << "v 的大小为:" << v.size() << endl; // 输出 3
}
3.2.8 vector 预留空间
减少 vector 在动态扩展容量时的扩展次数。
函数原型:
reserve(int len);
// 容器预留 len 个元素长度,预留位置不初始化,元素不可访问。
void test01()
{
vector<int> v;
// 预留空间
v.reserve(100000); // 加与不加这行,后面计算的 num 有差别
int num = 0;
int* p = NULL;
for (int i = 0; i < 100000; i++) {
v.push_back(i);
if (p != &v[0]) {
p = &v[0];
num++;
}
}
cout << "num:" << num << endl; // 输出:1
}
3.3 Deque 容器
3.3.1 deque 容器基本概念
deque(音 /dɛk/)容器是一种双端数组,可以对头部和尾部进行插入删除操作。
deque 与 vector 区别:
- vector 对于头部的插入和删除效率低下,数据量越大,效率越低
- deque 相对而言,对头部的插入删除速度会比 vector 快
- vector 访问元素时的速度会比 deque 快,这和内部实现有关。
deque 内部工作原理:
deuqe 内部有个中控器,维护每段缓冲区中的内容,缓冲区存放真实数据。
中控器维护的是每个缓冲区的地址,使得使用 deque 时像一片连续的内存空间。
deque 容器的迭代器是支持随机访问的迭代器。
3.3.2 deque 构造函数
函数原型:
deque<T>
deqT; // 默认构造形式deque(beg, end);
// 构造函数将 [beg, end) 区间中的元素拷贝给本身。deque(n, elem);
// 构造函数将 n 个 elem 拷贝给本身。deque(const deque &deq);
// 拷贝构造函数
void printDeque(const deque<int>& d)
{
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { // 注意这里的 const_iterator,只读迭代器
cout << *it << " ";
}
cout << endl;
}
void test01() {
deque<int> d1;
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);
deque<int> d2(d1.begin(), d1.end());
printDeque(d2);
deque<int>d3(10, 100);
printDeque(d3);
deque<int>d4 = d3;
printDeque(d4);
}
总结:deque 容器和 vector 容器的构造方式几乎一致,灵活使用即可
3.3.3 deque 赋值操作
函数原型:
deque& operator=(const deque &deq);
// 重载等号操作符
assign(beg, end);
// 将 [beg, end) 区间中的数据拷贝赋值给本身。assign(n, elem);
// 将 n 个 elem 拷贝赋值给本身。
void test01()
{
deque<int> d1;
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);
deque<int>d2;
d2 = d1; //= 号赋值
printDeque(d2);
deque<int>d3;
d3.assign(d1.begin(), d1.end()); //assign 赋值
printDeque(d3);
deque<int>d4;
d4.assign(10, 100);
printDeque(d4);
}
3.3.4 deque 大小操作
函数原型:
deque.empty();
// 判断容器是否为空deque.size();
// 返回容器中元素的个数deque.resize(num);
// 重新指定容器的长度为 num, 若容器变长,则以默认值填充新位置。 // 如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem);
// 重新指定容器的长度为 num, 若容器变长,则以 elem 值填充新位置。 // 如果容器变短,则末尾超出容器长度的元素被删除。
其用法与 vector 完全一样。
3.3.5 deque 插入和删除
函数原型:
两端插入操作:
push_back(elem);
// 在容器尾部添加一个数据push_front(elem);
// 在容器头部插入一个数据pop_back();
// 删除容器最后一个数据pop_front();
// 删除容器第一个数据
指定位置操作:
insert(pos,elem);
// 在 pos 位置插入一个 elem 元素的拷贝,返回新数据的位置。insert(pos,n,elem);
// 在 pos 位置插入 n 个 elem 数据,无返回值。insert(pos,beg,end);
// 在 pos 位置插入 [beg,end) 区间的数据,无返回值。clear();
// 清空容器的所有数据erase(beg,end);
// 删除 [beg,end) 区间的数据,返回下一个数据的位置。erase(pos);
// 删除 pos 位置的数据,返回下一个数据的位置。
用法类似 vector。比 vector 多的是可以进行头部插入和删除。
3.3.6 deque 数据存取
函数原型:
at(int idx);
// 返回索引 idx 所指的数据operator[];
// 返回索引 idx 所指的数据front();
// 返回容器中第一个数据元素back();
// 返回容器中最后一个数据元素
其用法与 vector 完全一样。
3.3.7 deque 排序
算法:
sort(iterator beg, iterator end)
// 对 beg 和 end 区间内元素进行排序
#include <deque>
#include <algorithm> // 标准算法库
void test01(){
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
printDeque(d);
// 排序
sort(d.begin(), d.end());
printDeque(d);
}
vector 也支持用 sort 进行排序。总结一句话:只要是支持随机访问的迭代器容器都可以用 sort 进行排序。
3.4 案例 - 评委打分
3.4.1 案例描述
有 5 名选手,选手 ABCDE,10 个评委分别对每一位选手打分,去除最高分,去除评委中最低分,取平均分。
实现步骤:
- 创建五名选手,放到 vector 容器中
- 遍历 vector 容器,取出来每一位选手,执行 for 循环,可以把 10 个评分打分存到 deque 容器中
- sort 算法对 deque 容器中分数排序,去除最高分和最低分
- deque 容器遍历一遍,累加总分
- 获取平均分
3.4.2 代码实现
#pragma once
#include<iostream>
#include<string>
using namespace std;
#include<vector>
#include<algorithm>
#include <deque>
class Person {
public:
Person(string name, int score) {
this->m_Name = name;
this->m_score = score;
}
string m_Name;
int m_score;
};
void creatPerson(vector<Person>& v) {
string seed = "ABCDE";
for (int i = 0; i < seed.size(); ++i) {
string name = " 选手 " ;
name += +seed[i];
int score = 0;
Person p(name, score);
v.push_back(p);
}
}
void setScore(vector<Person>& v) {
for (vector<Person>::iterator it = v.begin(); it != v.end(); ++it) {
deque<int> d; // 用来存放 10 个分数
for (int i = 0; i < 10; ++i) {
int score = rand() % 40 + 61;
d.push_back(score);
}
sort(d.begin(), d.end());
d.pop_back();
d.pop_front();
// 计算平均分
int sum = 0;
for (deque<int>::iterator dit = d.begin(); dit != d.end(); ++dit) {
sum += *dit;
}
int avg = sum / d.size();
it->m_score = avg;
}
}
int main() {
srand((unsigned int)time(NULL)); // 随机数种子
vector<Person> v;
creatPerson(v);
setScore(v);
for (vector<Person>::iterator it = v.begin();it!=v.end();++it) {
cout << it->m_Name << " " << it->m_score << endl;
}
system("pause");
return 0;
}
3.5 stack 容器
3.5.1 基本概念
stack(栈)容器是一种先进后出的数据结构容器,它只有一个出口。
栈内元素只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为。
栈中进入数据称为—入栈push
栈中弹出数据称为—出栈pop
3.5.2 stack 常用接口
构造函数:
stack<T> stk
// 采用模板类实现,stack 对象的默认构造形式stack(const stack &stk)
// 拷贝构造函数
赋值操作:
stack& operator=(const stack &stk)
// 重载等号操作符
数据存取:
push(elem);
// 向栈顶添加元素pop();
// 从栈顶移除元素top();
// 返回栈顶元素
大小操作:
empty();
// 判断栈是否为空size();
// 返回栈的大小
3.6 queue 容器
3.6.1 基本概念
queue 是一种先进先出的数据结构,它有两个出口:
队列容器允许从一端新增元素,从另一端移除元素。犹如人,嘴巴进,屁股出。
队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为
队列中进数据称为—入队push
队列中出数据称为—出队pop
3.6.2 常用接口
构造函数:
queue<T> que;
//queue 采用模板类实现,queue 对象默认构造形式queue(const queue &que);
// 拷贝构造函数
赋值操作:
queue& operator=(const queue &que);
// 重载等号操作符
数据存取:
push(elem);
// 往队尾添加元素pop();
// 从队头移除第一个元素back();
// 返回最后一个元素front();
// 返回第一个元素
大小操作:
empty();
// 判断容器是否为空size();
// 返回容器的大小
3.7 list 容器
3.7.1 基本概念
list 容器的存储方式为链式存储。
链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的。
链表的组成:链表由一系列结点组成。
结点的组成:一个是存储数据元素的数据域,另一个是储存下一个结点地址的指针域。
STL 中的链表是一个 双向循环 链表。
- 双向 是指每一个结点有两个指针,一个指针 next 指向下一个结点的地址,一个指针 prev 指向上一个结点的地址;
- 循环 是指第一个结点 prev 指向最后一个结点的地址,最后一个结点 next 指向第一个结点的地址。
由于链表的存储方式并不是连续的内存空间,因此链表 list 中的迭代器只支持前移和后移,属于 双向迭代器。
list 的优点:
- 采用动态存储分配,不会造成内存浪费和溢出
- 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
list 的缺点:
- 链表灵活,但是空间(指针域)和时间(遍历)额外耗费较大。
List 有一个重要的性质,插入和删除操作都不会造成原有 list 迭代器的失效,这在 vector 是不成立的。
3.7.2 list 构造函数
函数原型:
list<T> lst;
// list 采用采用模板类实现, 对象的默认构造形式:list(beg,end);
// 构造函数将 [beg, end) 区间中的元素拷贝给本身。list(n,elem);
// 构造函数将 n 个 elem 拷贝给本身。list(const list &lst);
// 拷贝构造函数。
#include<list>
void printList(const list<int>&L) {
for (list<int>::const_iterator it = L.begin(); it != L.end(); ++it) {
cout << *it << " ";
}
cout << endl;
}
void test1() {
// 创建 list 容器 --- 默认构造
list<int> L1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);
printList(L1);
// 创建 list 容器 --- 区间方式构造
list<int> L2(L1.begin(),L1.end());
// 创建 list 容器 --- 拷贝构造
list<int> L3(L2);
// 创建 list 容器 ---n 个 elem
list<int> L4(10,100);
printList(L4);
}
int main() {
test1();
system("pause");
return 0;
}
3.7.3 list 赋值和交换
函数原型:
assign(beg, end);
// 将 [beg, end) 区间中的数据拷贝赋值给本身。assign(n, elem);
// 将 n 个 elem 拷贝赋值给本身。list& operator=(const list &lst);
// 重载等号操作符swap(lst);
// 将 lst 与本身的元素互换。
3.7.4 list 大小操作
函数原型:
size();
// 返回容器中元素的个数empty();
// 判断容器是否为空resize(num);
// 重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。 // 如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem);
// 重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。变短则删除。
3.7.5 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 值匹配的元素。
3.7.6 list 数据存取
函数原型:
front();
// 返回第一个元素。back();
// 返回最后一个元素。
3.7.7 list 反转和排序
函数原型:
reverse();
// 反转链表sort();
// 链表排序,升序
#include<list>
void printList(const list<int>&L) {
for (list<int>::const_iterator it = L.begin(); it != L.end(); ++it) {
cout << *it << " ";
}
cout << endl;
}
bool myCompare(int v1, int v2) {
return v1 > v2;
}
void test1() {
list<int> L1;
L1.push_back(20);
L1.push_back(10);
L1.push_back(40);
L1.push_back(30);
L1.sort(); // 默认升序排列
printList(L1);
L1.sort(myCompare); // 降序排列
printList(L1);
}
int main() {
test1();
system("pause");
return 0;
}
3.7.8 排序案例
#include<list>
class Person {
public:
Person(string name, int age, int height) {
this->m_Name = name;
this->m_Age = age;
this->m_Height = height;
}
string m_Name;
int m_Age;
int m_Height;
};
void printList(const list<Person>&L) {
for (list<Person>::const_iterator it = L.begin(); it != L.end(); ++it) {
cout << it->m_Name << " ";
cout << it->m_Age << " ";
cout << it->m_Height << endl;
}
}
bool sortRule(Person &p1, Person &p2) {
if (p1.m_Age == p2.m_Age) {
return p1.m_Height > p2.m_Height;
}
return p1.m_Age < p2.m_Age;
}
void test1() {
list<Person> L1;
Person p1(" 张飞 ", 23, 178);
Person p2(" 周瑜 ", 28, 175);
Person p3(" 孙权 ", 12, 132);
Person p4(" 曹操 ", 34, 175);
Person p5(" 关羽 ", 25, 182);
Person p6(" 刘备 ", 28, 180);
L1.push_back(p1);
L1.push_back(p2);
L1.push_back(p3);
L1.push_back(p4);
L1.push_back(p5);
L1.push_back(p6);
printList(L1);
cout << endl;
L1.sort(sortRule);
printList(L1);
}
对于自定义数据类型,必须要指定排序规则,否则编译器不知道如何进行排序。
高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂。
3.8 set/multiset 容器
3.8.1 基本概念
集合容器,所有元素在插入时会自动被排序。set/multiset 属于 关联式容器,底层结构用二叉树实现。
set 和 multiset 区别:
- set 不允许容器中有重复的元素,multiset 允许容器中有重复的元素
- set 插入数据的同时会返回插入结果,表示插入是否成功;multiset 无返回值
set 和 multiset 的头文件都是#include<set>
构造函数
set<T> st;
// 默认构造函数:set(const set &st);
// 拷贝构造函数
赋值
set& operator=(const set &st);
// 重载等号操作符
3.8.2 set 大小和交换
函数原型:
size();
// 返回容器中元素的数目empty();
// 判断容器是否为空swap(st);
// 交换两个集合容器
3.8.3 set 插入和删除
函数原型:
insert(elem);
// 在容器中插入元素。clear();
// 清除所有元素erase(pos);
// 删除 pos 迭代器所指的元素,返回下一个元素的迭代器。erase(beg, end);
// 删除区间 [beg,end) 的所有元素 ,返回下一个元素的迭代器。erase(elem);
// 删除容器中值为 elem 的元素。
3.8.5 set 查找和统计
函数原型:
find(key);
// 查找 key 是否存在, 若存在,返回该键的元素的迭代器;若不存在,返回 set.end();count(key);
// 统计 key 的元素个数(整型 int)
#include<set>
void test1() {
set<int> s1;
s1.insert(20);
s1.insert(50);
s1.insert(10);
s1.insert(40);
s1.insert(30);
set<int>::iterator pos = s1.find(30);
if (pos != s1.end()) {
cout << " 找到 " << endl;
}
else {
cout << " 没找到 " << endl;
}
int num = s1.count(10);
}
3.8.6 set 返回值
set 插入数据的同时会返回插入结果,表示插入是否成功。返回值是一个 pair 类型的数据
#include<set>
void test1() {
set<int> s1;
pair<set<int>::iterator, bool> ret = s1.insert(20);
if (ret.second) {
cout << " 插入成功!" << endl;
}
else {
cout << " 插入失败!" << endl;
}
pair<set<int>::iterator, bool> ret2 = s1.insert(20);
if (ret2.second) {
cout << " 插入成功!" << endl;
}
else {
cout << " 插入失败!" << endl;
}
}
3.8.7 pair 对组创建
成对出现的数据,利用对组可以返回两个数据。
两种创建方式:
pair<type, type> p (value1, value2);
pair<type, type> p = make_pair(value1, value2);
pair<string, int> p("Tom", 20);
cout << " 姓名: " << p.first << " 年龄: " << p.second << endl;
3.8.8 set 容器排序
学习目标:
- set 容器默认排序规则为从小到大,掌握如何改变排序规则
主要技术点:
- 利用仿函数,可以改变排序规则
示例一:内置的数据类型排序
#include<set>
class SortRule {
public:
bool operator()(int v1, int v2) const { // 这里加个 const,不然 vs2019 会报错
return v1 > v2;
}
};
void printSet(set<int, SortRule>&s) {
for (set<int, SortRule>::iterator it = s.begin(); it != s.end(); ++it) {
cout << *it << " ";
}
cout << endl;
}
void test1() {
set<int, SortRule> s1;
s1.insert(20);
s1.insert(30);
s1.insert(10);
s1.insert(50);
s1.insert(40);
printSet(s1);
}
示例二:自定义数据类型排序
#include<set>
class Person {
public:
Person(string name, int age, int height) {
this->m_Name = name;
this->m_Age = age;
this->m_Height = height;
}
string m_Name;
int m_Age;
int m_Height;
};
class SortRule {
public:
bool operator()(const Person& p1, const Person& p2) const{
return p1.m_Age > p2.m_Age;
}
};
void printSet(set<Person, SortRule>&s) {
for (set<Person, SortRule>::iterator it = s.begin(); it != s.end(); ++it) {
cout << it->m_Name << " ";
cout << it->m_Age << " ";
cout << it->m_Height << endl;
}
}
void test1() {
set<Person, SortRule> s1;
Person p1(" 张飞 ", 25, 180);
Person p2(" 周瑜 ", 26, 175);
Person p3(" 刘备 ", 28, 179);
Person p4(" 孙权 ", 16, 158);
Person p5(" 关羽 ", 27, 182);
s1.insert(p1);
s1.insert(p2);
s1.insert(p3);
s1.insert(p4);
s1.insert(p5);
printSet(s1);
}
3.9 map/multimap 容器
3.9.1 map 基本概念
map/multimap 属于关联式容器,底层结构是用二叉树实现。
- map 中所有元素都是 pair
- pair 中第一个元素为 key(键值),起索引作用,第二个元素为 value(实值)
- 所有元素都会根据元素的键值自动排序
优点:
- 可以根据 key 值快速找到 value 值
map/multimap 区别:
- map 不允许容器中有重复 key 值元素。
- multimap 允许容器中有重复 key 值元素。
3.9.2 map 构造和赋值
函数原型:
构造:
map<T1, T2> mp;
//map 默认构造函数:map(const map &mp);
// 拷贝构造函数
赋值:
map& operator=(const map &mp);
// 重载等号操作符
#include<map>
void printMap(map<int, int>& m) {
for (map<int, int>::iterator it = m.begin(); it != m.end(); ++it) {
cout << (*it).first << " " << (*it).second << endl;
}
}
void test1() {
// 默认构造
map<int, int>m;
m.insert(pair<int,int>(1, 10));
m.insert(pair<int, int>(4, 40));
m.insert(pair<int, int>(3, 30));
m.insert(pair<int, int>(2, 200));
printMap(m);
// 拷贝构造
map<int, int>m2(m);
printMap(m2);
// 赋值
map<int, int>m3 = m;
printMap(m3);
// 按照 key 取值
cout << m[2] << endl;
// 多层 map
map<string, map<int, int>> mm;
mm.insert(make_pair("No1", m)); //mm["No1"]=m
}
map 取值有三种:[],find,at
find 有就是有,没有就是没有,需要判断 find 的返回结果,才知道有没有。
[] 不管有没有,都是有。因为没有就是 0,字符串就是空。反正就是给它是初始值。如果原先不存在该 key,则插入,如果存在,则覆盖插入。
at 用于取值,但它是进行越界检测,这会损失效率。
相比于 find,能少敲键盘
相比于[],更安全
性能会有损失
所以常用于待查找的 key 都应该存在的情况,如果查找的 key 不存在,则说明程序有问题了。
以上三种虽然都能达到取值的目的,但用法上的些微差别可能导致使用上的 bug,所以一般性的建议如下:
如果纯粹是查找取值,不要用 [] 操作符,因为它会自动创建不存在的元素
正因为上面的原因,[]仅用于非 const 的情形下。在 const map 下使用 [] 会导致编译期错误
如果不确定待查找的值是否存在,且不存在也是合理的,则使用 find,判断返回值是否是 end()
如果待查找的值肯定存在,如果不存在就是逻辑上的错误,则使用 at,处理 out_of_range 异常
所以,对 map 根据 key 取值,不要用[]!!!
3.9.3 map 大小和交换
函数原型:
size();
// 返回容器中元素的数目empty();
// 判断容器是否为空swap(st);
// 交换两个集合容器
3.9.4 map 插入和删除
函数原型:
insert(elem);
// 在容器中插入元素。clear();
// 清除所有元素erase(pos);
// 删除 pos 迭代器所指的元素,返回下一个元素的迭代器。erase(beg, end);
// 删除区间 [beg,end) 的所有元素 ,返回下一个元素的迭代器。erase(key);
// 删除容器中值为 key 的元素。
3.9.5 map 查找和统计
函数原型:
find(key);
// 查找 key 是否存在,若存在,返回该键的元素的迭代器;若不存在,返回 map.end();count(key);
// 统计 key 的元素个数,返回 int 整型数据
#include<map>
void test1() {
map<int, int>m;
m.insert(make_pair(1, 10));
m.insert(make_pair(2, 20));
m.insert(make_pair(3, 30));
m.insert(make_pair(4, 40));
map<int, int>::iterator pos = m.find(2);
cout << pos->second << endl;
}
3.9.6 map 容器排序
map 排序函数与 set 类似。
class SortRule {
public:
bool operator()(int v1, int v2) const { // 这里加个 const,不然 vs2019 会报错
return v1 > v2;
}
};
void test1() {
map<int, int, SortRule> m; //SortRule 指明按照从大到小排列
m.insert(make_pair(1, 10));
m.insert(make_pair(2, 20));
m.insert(make_pair(3, 30));
m.insert(make_pair(4, 40));
m.insert(make_pair(5, 50));
}
3.10 案例 - 员工分组
3.10.1 案例描述
- 公司今天招聘了 10 个员工(ABCDEFGHIJ),10 名员工进入公司之后,需要指派员工在那个部门工作
- 员工信息有: 姓名 工资组成;部门分为:策划、美术、研发
- 随机给 10 名员工分配部门和工资
- 通过 multimap 进行信息的插入 key(部门编号) value(员工)
- 分部门显示员工信息
3.10.2 代码实现
#include<vector>
#include<map>
class Worker {
public:
string m_Name;
int m_Salary;
};
void createWorker(vector<Worker>& v) {
string nameseed = "ABCDEFGH";
for (int i = 0; i < nameseed.size(); ++i) {
Worker worker;
worker.m_Name = " 员工 ";
worker.m_Name += nameseed[i];
worker.m_Salary = rand() % 10000 + 10000;
v.push_back(worker);
}
}
void setGroup(vector<Worker>& v, multimap<int, Worker>& m) {
for (vector<Worker>::iterator it = v.begin(); it != v.end(); ++it) {
int dpart = rand() % 3 + 1;
m.insert(pair<int, Worker>(dpart, *it));
}
}
void showWorkerByGroup(multimap<int, Worker>& m) {
for (int i = 1; i < 4; ++i) {
switch (i)
{
case 1:
cout << " 策划部门员工:" << endl;
break;
case 2:
cout << " 美术部门员工:" << endl;
break;
case 3:
cout << " 研发部门员工:" << endl;
break;
default:
break;
}
multimap<int, Worker>::iterator pos = m.find(i);
int num = m.count(i);
int index = 0;
for (; pos != m.end() && index < num; ++pos, index++) {
cout << pos->second.m_Name << " " << pos->second.m_Salary << endl;
}
cout << endl;
}
}
void test1() {
srand((unsigned int)time(NULL));
vector<Worker> vWorker;
createWorker(vWorker);
multimap<int, Worker>mWorker;
setGroup(vWorker, mWorker);
showWorkerByGroup(mWorker);
/*for (vector<Worker>::iterator it = vWorker.begin(); it != vWorker.end(); ++it) {
cout << it->m_Name << " " << it->m_Salary << endl;
}*/
}
四、STL- 函数对象
4.1 函数对象
4.1.1 函数对象概念
概念:
- 重载函数调用操作符的类,其对象常称为函数对象。
- 函数对象使用重载的 () 时,行为类似函数调用,也叫仿函数。
本质:函数对象(仿函数)是一个类,不是一个函数。
4.1.2 函数对象使用
特点:
函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
// 函数对象(仿函数) class MyAdd { public: int operator()(int v1, int v2) { return v1 + v2; } }; void test1() { MyAdd add; int ret = add(10, 20); // 类对象的调用类似函数 cout << ret << endl; }
函数对象超出普通函数的概念,函数对象可以有自己的状态
class MyPrint { public: MyPrint() { this->count = 0; } void operator()(string test) { cout << test << endl; this->count++; } int count; }; void test1() { MyPrint print; print("hello world"); print("hello world"); print("hello world"); cout << "print 调用次数:" << print.count << endl; }
这里仿函数可以记录自己调用的次数
函数对象可以作为参数传递
class MyPrint { public: MyPrint() { this->count = 0; } void operator()(string test) { cout << test << endl; this->count++; } int count; }; void doPrint(MyPrint& mp, string test) { mp(test); } void test1() { MyPrint print; doPrint(print, "Hello C++"); //here }
4.2 谓词 Pred
返回 bool 类型的仿函数称为谓词:
- 如果 operator()接受一个参数,那么就叫一元谓词
- 如果 operator()接受两个参数,那么就叫二元谓词
#include <vector>
#include <algorithm>
//1. 一元谓词
struct GreaterFive{
bool operator()(int val) {
return val > 5;
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
// 利用 find_if 标准算法
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
if (it == v.end()) {
cout << " 没找到!" << endl;
}
else {
cout << " 找到:" << *it << endl;
}
}
// 二元谓词
class MyCompare
{
public:
bool operator()(int num1, int num2)
{
return num1 > num2;
}
};
void test(){
vector<int> v;
v.push_back(10);
v.push_back(20);
sort(v.begin(), v.end(), MyCompare);
}
4.3 内建函数对象
4.3.1 基本概念
STL 内建了一些函数对象,其中包括:
- 算术仿函数
- 关系仿函数
- 逻辑仿函数
用法:
- 这些仿函数所产生的对象,用法和一般函数完全相同
- 使用内建函数对象,需要引入头文件
#include<functional>
4.3.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>
// 取反仿函数
#include<functional>
void test1() {
negate<int> ne; // 取反
cout << ne(50) << endl;
plus<int> p; // 加法
cout << p(10, 20) << endl;
}
4.3.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>
// 小于等于
#include<algorithm>
#include<vector>
#include<functional>
void test1() {
vector<int> v;
v.push_back(10);
v.push_back(30);
v.push_back(20);
sort(v.begin(), v.end(), greater<int>()); // 内建的 greater<int>()函数对象,改变默认的排序方式
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
cout << *it << endl;
}
}
4.3.4 逻辑仿函数
功能描述:
- 实现逻辑运算
函数原型:
template<class T> bool logical_and<T>
// 逻辑与template<class T> bool logical_or<T>
// 逻辑或template<class T> bool logical_not<T>
// 逻辑非
#include<algorithm>
#include<vector>
#include<functional>
void test1() {
vector<bool> v;
v.push_back(true);
v.push_back(false);
v.push_back(true);
// 利用逻辑非,将容器 v 搬运到容器 v2 中,并执行取反操作
vector<bool> v2;
v2.resize(v.size());
transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());
for (vector<bool>::iterator it = v2.begin(); it != v2.end(); ++it) {
cout << *it << endl;
}
}
此部分内容仅做了解即可,实际使用较少
五、STL 常用算法
概述:
算法主要是由头文件
<algorithm>
<functional>
<numeric>
组成。<algorithm>
是所有 STL 头文件中最大的一个,范围涉及到比较、 交换、查找、遍历操作、复制、修改等等<numeric>
体积很小,只包括几个在序列上面进行简单数学运算的模板函数<functional>
定义了一些模板类, 用以声明函数对象。
5.1 常用遍历算法
算法简介:
for_each
// 遍历容器transform
// 搬运容器到另一个容器中
这两个算法包含在 <algorithm>
中。
5.1.1 for_each
功能描述:
- 实现遍历容器
函数原型:
for_each(iterator_beg, iterator_end, _func);
// 遍历算法 遍历容器元素
// beg 开始迭代器
// end 结束迭代器
// _func 函数或者函数对象
#include<algorithm>
#include<vector>
// 普通函数
void print1(int val) {
cout << val << " ";
}
// 仿函数
class print2 {
public:
void operator()(int val) {
cout << val << " ";
}
};
void test1() {
vector<int> v;
v.push_back(10);
v.push_back(30);
v.push_back(20);
v.push_back(40);
for_each(v.begin(), v.end(), print1); // 传入普通函数,遍历出每个元素
cout << endl;
for_each(v.begin(), v.end(), print2()); // 传入仿函数(),遍历出每个元素
}
5.1.2 transform
功能描述:
- 搬运容器到另一个容器中
函数原型:
transform(iterator_beg1, iterator_end1, iterator_beg2, _func);
//beg1 源容器开始迭代器
//end1 源容器结束迭代器
//beg2 目标容器开始迭代器
//_func 函数或者函数对象
#include<algorithm>
#include<vector>
void print1(int val) {
cout << val << " ";
}
int trans(int v) {
return v;
}
void test1() {
vector<int> v;
v.push_back(10);
v.push_back(30);
v.push_back(20);
v.push_back(40);
vector<int> v2;
v2.resize(v.size()); // 先要开辟空间
transform(v.begin(), v.end(), v2.begin(), trans);
for_each(v2.begin(), v2.end(), print1);
}
5.2 常用查找算法
算法简介:
find(iterator beg, iterator end, value);
// 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
// beg 开始迭代器
// end 结束迭代器
// value 查找的元素
find_if(iterator beg, iterator end, _Pred);
// 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
// beg 开始迭代器
// end 结束迭代器
// _Pred 函数或者谓词(返回 bool 类型的仿函数)
adjacent_find(iterator beg, iterator end);
// 查找相邻重复元素, 返回相邻元素的第一个位置的迭代器
// beg 开始迭代器
// end 结束迭代器
bool binary_search(iterator beg, iterator end, value);
// 查找指定的元素,查到 返回 true 否则 false
// 注意: 在 无序序列中不可用
// beg 开始迭代器
// end 结束迭代器
// value 查找的元素
count(iterator beg, iterator end, value);
// 统计元素出现次数,返回 int 类型数据
// beg 开始迭代器
// end 结束迭代器
// value 统计的元素
count_if(iterator beg, iterator end, _Pred);
// 按条件统计元素出现次数,返回 int 类型数据
// beg 开始迭代器
// end 结束迭代器
// _Pred 谓词
一个 find_if
的例子:
class Greater30 {
public:
bool operator()(int val) {
return val > 30;
}
};
void test1() {
vector<int> v;
v.push_back(10);
v.push_back(30);
v.push_back(20);
v.push_back(40);
vector<int>::iterator it=find_if(v.begin(), v.end(), Greater30());
if (it == v.end()) {
cout << " 没找到 " << endl;
}
else {
cout << " 找到 " << endl;
}
cout << *it << endl; // 输出 30,找到第一个满足条件的即停止查找
}
5.3 常用排序算法
算法简介:
sort(iterator beg, iterator end, _Pred);
// 排序
// beg 开始迭代器
// end 结束迭代器
// _Pred 谓词
random_shuffle(iterator beg, iterator end);
// 指定范围内的元素随机调整次序
// beg 开始迭代器
// end 结束迭代器
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
// 容器元素合并,并存储到另一容器中
// 注意: 两个容器必须是 有序的
// beg1 容器 1 开始迭代器
// end1 容器 1 结束迭代器
// beg2 容器 2 开始迭代器
// end2 容器 2 结束迭代器
// dest 目标容器开始迭代器reverse(iterator beg, iterator end);
// 反转指定范围的元素
// beg 开始迭代器
// end 结束迭代器
5.4 常用拷贝和替换算法
算法简介:
copy(iterator beg, iterator end, iterator dest);
// 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
// beg 开始迭代器
// end 结束迭代器
// dest 目标起始迭代器
replace(iterator beg, iterator end, oldvalue, newvalue);
// 将区间内旧元素 替换成 新元素
// beg 开始迭代器
// end 结束迭代器
// oldvalue 旧元素
// newvalue 新元素
replace_if(iterator beg, iterator end, _pred, newvalue);
// 按条件替换元素,满足条件的替换成指定元素
// beg 开始迭代器
// end 结束迭代器
// _pred 谓词
// newvalue 替换的新元素
swap(container c1, container c2);
// 互换两个容器的元素
// c1 容器 1
// c2 容器 2
5.5 常用算术生成算法
注意:
- 算术生成算法属于小型算法,使用时包含的头文件为
#include <numeric>
算法简介:
accumulate(iterator beg, iterator end, value);
// 计算容器元素累计总和,返回总和
// beg 开始迭代器
// end 结束迭代器
// value 起始值
fill(iterator beg, iterator end, value);
// 向容器中填充元素
// beg 开始迭代器
// end 结束迭代器
// value 填充的值
5.6 常用集合算法
算法简介:
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
// 求两个集合的交集
// 注意: 两个集合必须是有序序列
// beg1 容器 1 开始迭代器
// end1 容器 1 结束迭代器
// beg2 容器 2 开始迭代器
// end2 容器 2 结束迭代器
// dest 目标容器开始迭代器,目标容器开辟空间需要从 两个容器中取小值set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
// 求两个集合的并集
// 注意: 两个集合必须是有序序列
// beg1 容器 1 开始迭代器
// end1 容器 1 结束迭代器
// beg2 容器 2 开始迭代器
// end2 容器 2 结束迭代器
// dest 目标容器开始迭代器,目标容器开辟空间需要 两个容器相加set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
// 求两个集合的差集
// 注意: 两个集合必须是有序序列
// beg1 容器 1 开始迭代器
// end1 容器 1 结束迭代器
// beg2 容器 2 开始迭代器
// end2 容器 2 结束迭代器
// dest 目标容器开始迭代器,目标容器开辟空间需要从 两个容器取较大值
六、异常处理
程序有时会遇到运行阶段错误,导致程序无法正常地运行下去,C++ 为此提供了异常处理工具。其基本思想是:函数 A 在执行过程中发现异常时可以不加处理,而只是”拋出一个异常”给 A 的调用者,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果 main 函数也不处理异常,那么程序就会立即异常地中止。
6.1 基本的异常处理
1、调用 std::abort() 函数
using namespace std;
double cal(double a, double b) {
if (a == -b) {
cout << " 两数之和不可以等于 0" << endl;
abort(); // 直接调用
}
else {
return 1 / (a + b);
}
}
当运行到 abort(),程序会终止,避免崩溃。
2、返回错误码
可以使用指针 / 引用参数来将值返回给调用程序,并使用函数的返回值来指出成功还是失败。
bool cal(double a, double b, double * ans) {
if (a == -b) {
*ans = 0.0;
return false;
}
else {
*ans = 1 / (a + b);
return true;
}
}
void test1() {
double x, y, ans;
cout << " 请输入 x,y 的值 " << endl;
cin >> x >> y;
if (cal(x, y, &ans)) {
cout << ans << endl;
}
else {
cout << "error code 1" << endl;
}
}
6.2 异常机制
C++ 异常是对程序运行过程中发生异常情况的一种响应,对异常的处理分为 3 个组成部分:
- 引发异常
- 使用处理程序捕获异常
- 使用 try 块
C++ 异常处理涉及到三个关键字:try、catch、throw。
如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:
捕获异常
// 捕获异常
try {
// 保护代码
}
catch(ExceptionName e1)
{
// 处理 ExceptionName 异常的代码
}
catch(ExceptionName e2)
{
// 处理 ExceptionName 异常的代码
}
如果您想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 …,如下所示:
try {}
catch (...){} // 捕获任何异常
抛出异常
我们可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
double division(int a, int b)
{
if(b == 0)
{
throw "Division by zero condition!"; // 抛出异常
}
return (a/b);
}
6.3 案例
#include <iostream>
using namespace std;
double division(int a, int b)
{
if(b == 0)
{
throw "Division by zero condition!";
}
return (a/b);
}
int main ()
{
int x = 50;
int y = 0;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
}catch (const char* msg) {
cerr << msg << endl;
}
return 0;
}
由于我们抛出了一个类型为字符串数组(”Division by zero condition!”)的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char* 接收。
6.4 异常类型
6.4.1 标准异常
C++ 提供了一系列标准的异常,定义在 <exception>
中。
我们可以在程序中使用这些标准的异常。
下面举一个 std::bad_alloc
例子:
#include<iostream>
using namespace std;
int main()
{
try {
char* p = new char[0x7fffffff]; // 无法分配这么多空间,会抛出异常
}
catch (exception& e) {
cout << e.what() << endl;
}
catch (bad_alloc& e) {
cout << e.what() << endl;
}
return 0;
}
// 第 8 行的 catch 捕获到了 bad allocation 输出,第二次 catch 为空(即使就是 bad_alloc 类型的异常)
what() 是异常类提供的一个公共方法,将返回异常产生的原因
异常只捕获一次
6.4.2 自定义异常
可以通过继承和重载 exception 类来定义新的异常。
#include <iostream>
#include <exception>
using namespace std;
class MyException : public exception
{
public:
const char* what() const throw ()
{
return "C++ Exception";
}
};
int main()
{
try
{
throw MyException();
cout << "look here" << endl;
}
catch (MyException& e)
{
cout << "MyException caught" << endl;
cout << e.what() << endl;
}
catch (exception& e)
{
// 其他的错误
}
}
/* 输出
MyException caught
C++ Exception
*/
欢迎各位看官及技术大佬前来交流指导呀,可以邮件至 jqiange@yeah.net