C++ 是在C语言基础上开发的,一种面向对象编程的语言。C++在C语言基础上封装了类实现了面向对象的功能,封装了函数实现了泛型编程的功能。与C相比具有安全性更高、功能更强的特点。本文以wiki知识库的形式,介绍C++的基本概念和语法规则,并在每个术语旁边配有对应的英文,为初学C++的人提供一份快速查阅的手册。旨在用最简短的文字,介绍清概念以及用法。
基本概念
面向对象编程 Object Oriented Programming(OOP)
编译过程:编译+连接
计算机语言的分类
- 机器语言:二进制代码
- 汇编语言:机器语言的简化,直接对地址、内存、寄存器进行操作,汇编代码与机器语言是一一对应的
- 高级语言:抽象程度更高,需要编译成机器语言
- 脚本语言:不需要编译
分类
- 关键字
- 标识符
基本数据类型与运算
数据类型
- 基本数据类型:整型int,浮点型float,双精度浮点double,字符char,布尔bool
- 自定义数据类型:枚举类型,结构体类型,联合体类型,类类型
流程控制
- 条件判断:if,while,switch
C语言和C++中,所有非零的值都被判定为逻辑真,为零的值才会被判定为逻辑假。
if、while语句中的逻辑判断是分步进行的,比如与运算中,只要前一项为非,不会对后一项进行判断,而是直接跳出判断。
数据的存储与操作
每个数据都会单独分配内存空间来处理
CPU只是对内存数据进行操作,而不会区分不同的数据类型。区分数据类型并选择不同的操作,是编译器需要完成的工作。
函数 Function
函数由参数表和函数体组成,函数在使用前必须先声明,可以之后再定义
参数传递
函数在调用时才分配存储单元
值传递
语法规定:传递给函数的实参,与函数形参表中的数据类型一致
//函数声明,此处省略函数定义
void swap(int a, int b);
int main(){
int a, b;
swap(a, b); //函数引用,因为传递的是值,此处a, b没有被交换
return 0;
}
引用传递
语法规定:使用“&”符号定义一个引用,引用必须指向已经存在的对象。函数的形参是一个引用,传递给函数的实参是被引用对象
int i;
int& ri = i; //ri是i的一个引用
函数实例:
//函数声明,此处省略函数定义
void swap(int& a, int& b);
int main(){
int a, b;
swap(a ,b); //函数引用,因为传递的是引用,此处a, b被交换
return 0;
}
指针传递
语法定义:传递实参的地址给子函数,子函数的形参表中定义形参为指针类型
//函数声明,此处省略函数定义
void swap(int* a, int* b);
int main(){
int a, b;
swap(&a, &b); //函数引用,传递指针给子函数
return 0;
}
函数的调用
运行栈
最后调用的函数总是最先返回,因此可以用栈这种数据结构来保存。运行栈就是专为函数调用设计的数据结构,
指针
寄存器中通常会保存两个指针,分别指向栈顶和函数调用发生的位置,来进行函数的调用和返回。
- 栈顶指针:指向栈顶的地址
- 帧指针:指向函数调用时的地址
函数的声明与安全性
C++ 要求函数在使用时先声明返回值类型和参数表,这样可以在编译过程中发现错误,是设计更为合理的编程语言。如果不做声明,在参数传递和参数返回两个过程中,都可能发生错误。
- 参数传递中,有可能把参数类型传错,将整型变量传递给浮点变量,而不做类型转换。
- 参数返回中,不做声明,有可能用整型方法去获取void类型的函数值,就会读取垃圾数据。
类与对象 Class & Object
构造函数与析构函数 Constructor and Destructor
构造函数
语法定义:函数名没有返回值类型,函数名和类名一致,不允许有返回值列席,不允许有return语句。当没有声明构造函数时,编译器会自动生成默认的构造函数,用于分配类成员的空间。
//类定义
class Point{
double x,y;
public:
Point(){}; //构造函数声明
}
初始化列表
除参数表和函数体以外,构造还可以拥有初始化列表,效率比在函数体中赋值要高一些。因为当传递参数是类时,在初始化列表中进行初始化,只需要执行一次复制构造函数。若在函数体中进行初始化,首先需要调用构造函数构造对象,再调用构造函数的赋值运算符进行赋值运算。
语法定义:初始化列表位于参数表和函数体之间,用冒号跟参数表隔开,初始化对象之间用逗号分隔。
//类定义
class Point{
double x,y;
public:
Point(double init_x, double init_y):x(init_x),y(init_y){} //含初始化列表的构造函数
}
委托构造函数
使用委托构造函数,可以保证代码的一致性,提高代码的复用率,降低修改成本
语法定义:定义构造函数时,可以在初始化列表中使用其他已经声明的构造函数
//类定义
class Point{
double x,y;
public:
Point(double init_x, double init_y):x(init_x),y(init_y){} //含初始化列表的构造函数
Point():Point(0.0,0.0){} //使用委托构造函数来实现默认构造函数
}
复制构造函数
语法定义:函数名与类名一致,形参表为const定义的类对象的引用。可以用"=delete"指令隐藏复制构造函数。有两个要点:复制和构造。
//类定义
class Point{
double x,y;
public:
Point(double init_x, double init_y):x(init_x),y(init_y){} //含初始化列表的构造函数
Point():Point(0.0,0.0){} //委托构造函数
Point(const Point &p){} //复制构造函数
}
//构造函数在主程序中的使用
int main(){
Point a; //默认构造函数被调用
Point b(0.0, 0.0); //含参数表的构造函数被调用
Point c(a); //复制构造函数被调用
}
析构函数
语法定义:函数名在类名前加"~",不允许有参数表,不允许有返回值列席,不允许有return语句。不定义时,编译器会自动生成析构函数。在对象消亡时,析构函数会被自动调用。
//类定义
class Point{
double x,y;
public:
Point(double init_x, double init_y):x(init_x),y(init_y){} //含初始化列表的构造函数
Point():Point(0.0,0.0){} //委托构造函数
Point(const Point &p){} //复制构造函数
~Point(){} //析构函数
}
组合类
-
组合类的构造函数:初始化的顺序与类中声名的顺序相同,与初始化列表中的顺序不同,组合类初始化时,会调用两次初始化构造函数,第一次是将实参赋值给形参,第二次是把形参值赋值给组合类中的对象。
-
前向引用声名:只声名类的名字而不包含任何细节。
UML
- 事物:
- 关系:依赖,重数,聚集(组合),泛化
- 图:
特殊类
- 结构体:在C++中是特殊的类,成员默认是public
- 联合体:成员共用存储空间,只有一个成员有效,可以使用无名的联合体。
- 枚举类:类型控制更严格,无法比较不同类的枚举类型;作用域限制在类中,可以使用同样的名字。
数据的共享与保护
作用域
作用域由小到大可以分为以下几种:
- 函数原型作用域:只在形参表中存在
- 局部作用域:又称为块作用域,变量定义所在的最小的一对大括号内
- 类作用域:
- 文件作用域:又称为静态作用域
- 命名空间作用域:
可见性
作用域和可见性可能不一致:当内层作用域与外层作用域出现变量重名
生存期
- 静态生存期:文件作用域中定义的变量,或用static声明的变量;如果在函数体内声明静态变量,那么该变量只会初始化一次,在之后对函数的调用过程中,访问的是同一个变量。
- 动态生存期:与作用域一致,同时消亡;在函数中调用的会随着函数运行栈的消亡而消亡
类的静态对象
- 为所有类的对象共有,可以使用类名直接调用
- 静态数据:声明在类体重,初始化和定义必须在类体之外
- 静态函数:无法确定调用对象,不可直接访问对象的非静态变量
友元: freind
- 友元函数:在类中声明,在类外定义,需要把对象当做实参传递给函数
- 友元类:友元关系式单向的
常类型:const
-
常对象:必须初始化,不能被更新
-
常成员:
a. 常数据成员
b. 常函数成员(一种函数重载的判断条件):承诺不改变数据状态,并由编译器进行检验(bitwise检验)
注一:可使用“mutable” 摆动场,释放非静态成员的bitwise constness 约束,从而使得常函数可以修改这些非静态成员的值。
注二:可使用转型“casting”方法,用non-const函数调用const函数,从而实现代码复用。 -
常引用:提高参数传递效率的同时,满足安全性的条件
-
常指针
const int* p1; //指向整型常量的指针
int const * p2; //指向整型常量的指针
int* const p3; //指向整型变量的常量指针
多文件结构
C++工程结构:声明文件、定义文件、使用文件
- 外部变量:文件作用域中定义的变量,默认都是外部变量。使用时需要用extern关键字声明之后,才可以使用
- 外部函数:
- 编译预处理:#include, #define, #if #endif #else #elif, #ifdef
标准C++库
- 输入输出类iostream:
- 容器类与抽象数据类型stl:
- 存储管理类
- 算法algorithms:
- 错误处理
- 运行环境支持
指针和数组 Pointers & Array
&: 取数据对应的地址,*: 取地址对应的数据
指针的应用场景
- 动态内存分配,返回值只能是指针
- 深拷贝:自定义构造函数,为类内的指针属性申请内存空间。
指针 Pointers
- 定义:用于存放地址类型的变量
- 初始化:指针变量的赋值必须是合法获得的地址,不可用非静态变量去初始化静态指针,空指针:nullptr
- 指向常量的指针,指针类型的常量
- 指针的算术运算:+n指向第n个数据的起始位置
- 指针的关系运算:指向相同类型的指针可以进行关系运算,也可以跟0进行关系运算
数组 Array
普通数组
语法定义:类型说明符 + 数组名 + [常量表达式][常量表达式]
int array_1[3]; //一维数组
int array_2[3][4]; //二维数组
注意:数组名后接的一定是常量表达式,不可以是变量
- 初始化:可以只给一部分元素初始值,数组名保存首元素的内存地址,为常量
int array_1[10] = {0,1,2,3,4,5,6,7,8,9}; //全部初始化
int array_2[10] = {0,1,2,3}; //部分初始化,未初始化部分为0
int array_3[] = {0,1,2,3,4,5,6,7,8,9}; //全部初始化可以不声明数组长度
int array_4[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; //二维数组初始化可以用大括号隔开
- 传递:数组作为函数参数传递时,直接传递指针
指针数组
语法定义:类型说明符 + 指针运算符 + 数组名 + [常量表达式][常量表达式]
代码实例
int *pa[3];
指针数组的用法跟多维数组类似,但是多维数组可以按照一维数组来使用,而指针数组不行。
函数与指针
指针类型的函数
语法定义:存储类型 + 数据类型 + *函数名(形参表){函数体}
代码实例
int* func_1(){}
注意:返回值必须是在主调函数中合法有效的地址,合法的地址包括
- 数组作为参数传递给函数
- 子函数中动态分配的地址
指向函数的指针
语法定义:存储类型 + 数据类型 + (*函数名)(形参表){函数体}
典型用途:函数回调
代码实例
//定义主调函数
int compute(int a, int b, int(*func)(int, int))
{return func(a,b);}
//定义求最值的函数
int max(int a, int b)
{return ((a>b)?a:b);}
//定义求和函数
int sum(int a, int b)
{return a + b;}
int main(){
int a, b;
compute(a, b, &max); //求最大值
compute(a, b, &sum); //求和
}
对象与指针
- 使用语法:对象指针 + “->” + 成员
Point p;
Point* ptr; //定义对象指针
ptr = &p;
p.getx(); //使用对象调用成员函数
ptr->getx(); //使用对象指针调用成员函数
- this指针:类对象隐含的指针
动态内存分配
- 使用语法:new 类型名T(初始化参数列表);delete 指针标识符;delet[] 数组标识符
Point *ptr = new Point(0.0,0.0);
delete ptr; //删除对象,但不删除指针
ptr = new Point(1, 2);
delete ptr;
- 注意事项:分配和释放一定要配合使用
智能指针
- unique_ptr :不允许多个指针共享资源,可以用标准库中的move函数转移指针
- shared_ptr :多个指针共享资源
- weak_ptr :可复制shared_ptr,但其构造或者释放对资源不产生影响
对象的复制与移动
复制
- 浅层复制:数据成员的一一对应
- 深层复制:在复制对象数据成员的基础上,复制指针指向的动态内存空间
移动构造
语法定义:函数名与类名一致,参数表为类对象的右值引用
代码实例
class IntNum{
public:
IntNum(IntNum && n): xptr(n.xptr){
n.xptr = nullptr; //将原始值赋值为nullptr
}
字符串
- C风格字符串:字符串数组,最后一位用"\0"结尾
- C++风格:string 类
继承 Inherit
基本概念和语法
语法定义: class + 派生类名 + : + 继承方式 + 基类名
class DerivedPublic : public Base {} //基类Base的派生类(公有继承)
class DerivedPrivate : private Base {} //基类Base的派生类(私有继承)
class DerivedProtected : protected Base {} //基类Base的派生类(保护继承)
继承方式
类成员的三种访问权限与三种继承方式是对应的,分别限定了类成员对于类对象、子类成员和子类对象的可见性。如果需要基类的某个成员对其派生类可见,但是对其对象不可见,那么就采用保护的可见性。
公有继承
公有继承不改变基类成员的可见性
成员类型 | 基类对象 | 派生类成员 | 派生类对象 |
---|---|---|---|
public | 可见 | 可见 | 可见 |
protected | 不可见 | 可见 | 不可见 |
private | 不可见 | 不可见 | 不可见 |
私有继承
私有继承将基类成员全部转变为派生类的私有成员
成员类型 | 基类对象 | 派生类成员 | 派生类对象 |
---|---|---|---|
public | 可见 | 可见 | 不可见 |
protected | 不可见 | 可见 | 不可见 |
private | 不可见 | 不可见 | 不可见 |
保护继承
保护继承将基类的共有成员转变为保护成员,其他成员的可见性不变
成员类型 | 基类对象 | 派生类成员 | 派生类对象 |
---|---|---|---|
public | 可见 | 可见 | 不可见 |
protected | 不可见 | 可见 | 不可见 |
private | 不可见 | 不可见 | 不可见 |
类型转换
派生类对象可以转换为基类对象。
注意:不要定义继承来的非虚函数
派生类的构造和析构函数
构造函数
语法定义:派生类名::派生类名(基类所需的形参,本类成员所需的形参):
基类名(参数表), 本类成员初始化列表
可以用using直接使用基类的构造函数。
代码示例
class B; //基类B
class C: public B{ //派生类C
public:
C(int i, int j);
}
//派生类的构造函数
C::C(int i,int j): B(i), c(j){
}
复制构造函数
语法定义:派生类名::派生类名(const 派生类名 &派生对象):
基类名(派生对象)
原理在于派生对象可以直接转换为基类对象
程序实例
C::C(const &c1):B(c1){}
析构函数
语法定义:派生类名::~派生类名()
并不需要显式地调用基类的析构函数
程序实例
C::~C(){}
访问基类的成员
二义性
当同名成员被多次定义的时候,出现了二义性,可以使用类名来限定。
当派生类从多个基类派生,而这些基类又有共同基类时,二义性会导致程序的冗余,甚至引起混淆或错误。
虚基类
慎用虚基类
语法定义:使用virtual调用
代码实例
class C: virtual public B{}
构造函数:最远派生类(建立对象时使用的类)给虚基类传递函数。
多态性 Polymorphism
- 编译多态性:重载运算符, 重载函数
- 运行多态性:虚函数
运算符重载
重载规则
运算符的重载不改变运算符的优先级,并且只能重载C++中已经包含的运算符
不能够重载的运算符: “.”, “.*”, “::”, “?:”
双目运算符重载为成员函数
语法定义:函数类型 operator 运算符(形参)
参数个数 = 原操作数 - 1
代码实例
class Complex{
public:
Complex operator+(const Complex &c2)const {
return Complex(real+c2.real, img+c2.img);
}
}
单目运算符重载为成员函数
语法定义:
-
前置:函数类型 &operator 运算符()
-
后置:函数类型 operator 运算符(int)
代码实例
Clock& operator ++ (); //前置运算符
Clock operator ++ (0); //后置运算符
重载操作符为非成员函数
语法定义:需要列出所有的操作数,前置、后置单目运算符需要添加一个int
代码实例
Complex operator+(const Complex &c1, const Complex &c2){
return Complex(c1.real+c2.real, c1.imag+c2.imag);
}
Complex operator-(const Complex &c1, const Complex &c2){
return Complex(c1.real-c2.real, c1.imag-c2.imag);
}
ostream & operator<<(ostream &out, const Complex &c){
out << "(" << c.real << ", " << c.imag << ")";
return out;
}
虚函数
虚函数
语法定义:virtual 函数类型 函数名(形参表)
虚函数不可以是静态,构造函数不可以是虚函数
代码实例
vitual void display() const;
虚析构函数
语法定义:与虚函数的定义一样
使用虚析构函数,可以保证动态申请的内存空间能够被释放。否则,基类指针指向的派生类对象消亡时,只会调用基类的析构函数。
虚表与动态绑定
每个多态类有一个虚表,虚表中有当前各个虚函数的入口地址
每个对象有一个指向当前虚表的指针
抽象类
纯虚函数: virtual 函数类型 函数名(参数表) = 0
包含纯虚函数的类为抽象类,不能够构造对象
显示函数覆盖 overriide
不允许被继承 final
语法定义: class 对象名 final;
clase Base1 final {}
模板 Template
模板
函数模板
语法定义:template<模板参数表> 函数定义
模板参数表:typename 或者 class
template<typename T>
T abs(T x){
return x < 0? -x:x;
}
类模板
语法定义:template<模板参数表> 类定义
template<class T>
class Test{
private:
T x;
public:
Test(T n);
}
线性群体
直接访问
数组
数组类模板特点:
- 存储空间在内存上连续,可以直接访问数组成员,访问开销为O(1)
数组基本功能:
- 构造函数:构造函数、复制构造函数、析构函数
- 重载操作符:[], *, =
- 数组操作:修改数组大小、取数组大小
代码实现
template <class T> //数组类模板定义
class Array {
private:
T * list; //用于存放动态分配的数组内存首地址
int size; //数组大小(元素个数)
public:
Array(int sz = 50); //构造函数
Array(const Array<T> &a); //复制构造函数
~Array(); //析构函数
Array<T> & operator = (const Array<T> &rhs); //重载"=",赋值运算符的返回值为数组的引用
T & operator [] (int i); //重载"[]"
const T & operator [] (int i) const; //重载"[]"常函数
operator T * (); //重载到T*类型的转换,指针函数不要求声明返回值类型
operator const T * () const;
int getSize() const; //取数组的大小
void resize(int sz); //修改数组的大小
}
顺序访问
节点 Node
节点是顺序访问的基本元素
节点特点:
节点基本功能:
代码实现
template <class T>
class Node {
private:
Node<T> *next; //指向后继结点的指针
public:
T data; //数据域
Node(const T &data, Node<T> *next = 0); //构造函数
void insertAfter(Node<T> *p); //在本结点之后插入一个同类结点p
Node<T> *deleteAfter(); //删除本结点的后继结点,并返回其地址
Node<T> *nextNode(); //获取后继结点的地址
const Node<T> *nextNode() const; //获取后继结点的地址
};
链表 List
链表特点:
- 访问必须从第一个对象开始
链表基本功能:
- 构造函数:构造函数、复制构造函数、析构函数
- 操作符重载:
代码实现
template <class T>
class LinkedList {
private:
//数据成员:
Node<T> *front, *rear; //表头和表尾指针
Node<T> *prevPtr, *currPtr; //记录表当前遍历位置的指针,由插入和删除操作更新
int size; //表中的元素个数
int position; //当前元素在表中的位置序号。由函数reset使用
//函数成员:
//生成新结点,数据域为item,指针域为ptrNext
Node<T> *newNode(const T &item, Node<T> *ptrNext = NULL);
//释放结点
void freeNode(Node<T> *p);
//将链表L 拷贝到当前表(假设当前表为空)。
//被拷贝构造函数、operator = 调用
void copy(const LinkedList<T>& L);
public:
LinkedList(); //构造函数
LinkedList(const LinkedList<T> &L); //拷贝构造函数
~LinkedList(); //析构函数
LinkedList<T> & operator = (const LinkedList<T> &L); //重载赋值运算符
int getSize() const; //返回链表中元素个数
bool isEmpty() const; //链表是否为空
void reset(int pos = 0);//初始化游标的位置
void next(); //使游标移动到下一个结点
bool endOfList() const; //游标是否到了链尾
int currentPosition() const; //返回游标当前的位置
void insertFront(const T &item); //在表头插入结点
void insertRear(const T &item); //在表尾添加结点
void insertAt(const T &item); //在当前结点之前插入结点
void insertAfter(const T &item); //在当前结点之后插入结点
T deleteFront(); //删除头结点
void deleteCurrent(); //删除当前结点
T& data(); //返回对当前结点成员数据的引用
const T& data() const; //返回对当前结点成员数据的常引用
//清空链表:释放所有结点的内存空间。被析构函数、operator= 调用
void clear();
}
栈 Stack
栈特点:
- 只能从一端存储和读取数据
栈基本功能:
- 构造函数:
- 栈操作函数
代码实现
template <class T, int SIZE = 50>
class Stack {
private:
T list[SIZE];
int top;
public:
Stack();
void push(const T &item);
T pop();
void clear();
const T &peek() const;
bool isEmpty() const;
bool isFull() const;
}
队列 Quene
队列特点:
- 只能从有一段存储数据,从另一端删除数据
队列基本功能:
- 构造函数:
- 队列操作(有修改的):入队、出队、清空
- 队列操作(无修改的):访问队首元素、求队列长度、判断队列是否为空、判断队列是否满
代码实例
template <class T, int SIZE = 50>
class Queue {
private:
int front, rear, count; //队头指针、队尾指针、元素个数
T list[SIZE]; //队列元素数组
public:
Queue(); //构造函数,初始化队头指针、队尾指针、元素个数
void insert(const T &item); //新元素入队
T remove(); //元素出队
void clear(); //清空队列
const T &getFront() const; //访问队首元素
//测试队列状态
int getLength() const;//求队列长度
bool isEmpty() const;//判断队列空否
bool isFull() const;//判断队列满否
}
索引访问
排序
排序分为内排序、外排序。内循环全部在内存中进行循环,外排序需要分批次从硬盘中读取数据。
插入排序
每次将待排序元素与已排序数组进行比较,并插入到合适位置上去。
选择排序
在未排序队列中选择最小的数据,放在未排序队列的末尾
交换排序
相邻元素两两比较,如果不合适,则进行交换
堆排序
什么是堆排序?
查找
顺序查找
将整个数组遍历一遍
二分查找
折半查找
泛型程序设计与STL标准模板库
泛型程序设计
泛型程序设计,是为了提高代码复用率的方法。在解决同一类问题的过程中,抽象出一种概念,根据这种概念设计通用的算法。。
术语
- 概念:具备一定功能的数据模型
- 模型:符合一个概念的数据类型,称为该概念的模型
使用概念作为模板参数名,设计对应的类模板和函数模板,从而可以解决同一类问题。
STL标准模板库
基本组件
迭代器是算法和容器的桥梁,将函数对象作为算法的参数。
- 迭代器:泛型指针,提供顺序访问容器对象的方法
- 容器:容纳、包含一组元素的对象,并且通过适配器实现基本容器的特殊化
- 算法:需要包含头文件,提供了70多种算法
- 函数对象:行为类似函数的对象,任何普通函数以及重载"()"运算符的类对象都可以作为函数对象使用
迭代器
输入流迭代器
用来从序列中读取数据
用法:
- 以输入流为参数构造
- 可以使用*(p++)获取下一个输入的元素
代码实例
istream_iterator<double>(cin)
输出流迭代器
用来向序列中写入数据
用法:
- 以输出流为参数构造
- 可以使用*(p++) = x,将x输出到输出流
代码实例
ostream_iterator<double>(cout,"\t")
前向迭代器
既是输入流迭代器也是输出流迭代器,可以对序列进行单向遍历
双向迭代器
与前向迭代器类似,不过可以在两个方向遍历数据
随机访问迭代器
与双向迭代器类似,但是可以在任意位置进行跳转。vector容器的迭代器就是随机访问迭代器。
容器
根据组织方式,可分为:顺序容器、关联容器
根据访问方式,可分为:可逆容器(又可衍生出随机访问容器的概念)、不可逆容器
通用接口:
- 默认构造函数
- 关系运算符
- begin(),end()
- clear()
- empty()
- size()
- swap()
顺序容器
举例:向量,双端队列,列表,单向链表,数组
通用接口:
- asign()
- insert()
- resize()
向量(vector):
- 可扩展的数组
- 尾部插入比较快,中间插入比较慢
双端队列(deque):
- 尾部和头部插入较快,中间插入较慢
- 随机访问比向量慢
列表(List):
- 任意位置插入和删除很快
- 不支持随机访问
数组(array):
- 对内部数组的封装
关联容器
特点:每个容器都有key,可根据key进行高效查找
分类:
- 按照键对应的元素多少,可分为单重关联容器和多重关联容器
- 按照类型参数的多少,可分为简单关联容器和二元关联容器
举例:set,multiset,map,multimap
通用接口: - insert()
- erase()
- find()
- lower_bound(),upper_bound(),equal_range()
- count()
集合(set):
- key即是元素本身
映射(map):
- 元素是由键和附加数据组成的二元组pair<type,type>
- 可以使用下标运算
#include <iostream>
#include <map>
#include <cctype>
using namespace std;
int main() {
map<char, int> s; //用来存储字母出现次数的映射
char c; //存储输入字符
do {
cin >> c; //输入下一个字符
if (isalpha(c)){ //判断是否是字母
c = tolower(c); //将字母转换为小写
s[c]++; //将该字母的出现频率加1
}
} while (c != '.'); //碰到“.”则结束输入
//输出每个字母出现次数
for (map<char, int>::iterator iter = s.begin(); iter != s.end(); ++iter)
cout << iter->first << " " << iter->second << " ";
cout << endl;
return 0;
}
多重集合(multiset):
- 允许有重复元素
多重映射(multimap):
- 一个键有多个附加数据
函数对象
算法
流类库与输入输出
异常处理
异常:可以预测但是无法避免的错误。
使用独立异常处理模块的原因:1.使得程序整体逻辑连贯;2.小的功能模块没有权限处理错误;
异常处理语法
throw 块
throw: 抛出异常
try 块
try: 所有可能抛出异常的语句,在try块中运行,一旦抛出异常,就中断运行
catch: 捕获异常
注:可在函数声明同时,声明异常类型,方便处理
程序抛掷A,B,C,D类型的异常
void fun() throw(A,B,C,D);
函数抛掷任意类型的异常
void fun();
函数不抛掷异常
void fun() throw();
异常处理机制会自动析构try块中构造的对象
标准异常类
exception
|-logic_error: 可以在程序中被预先检测出的异常
|-invalid_argument:
|-domain_error:
|-runtime_error: 难以被预先检测出的异常
异常安全性问题
异常安全性:异常发生时,既不泄露资源,也不能使任何对象陷入非法的状态。
原则:不抛掷异常是异常安全的基础
技巧:
- swap()函数一定不会抛出异常;
- 析构函数尽量不要抛出异常,否则在捕获异常时,析构栈上的对象,如果抛出异常,就会调用terminate函数;
- 在抛出异常前,将函数中动态申请的内存释放(过于繁琐);
- 尽量使用对象;
- 使用智能指针auto_ptr指向动态申请的地址,那么当异常被捕获时,会自动析构智能指针,也就释放了动态申请的内存;