C++ 术语与概念

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指向动态申请的地址,那么当异常被捕获时,会自动析构智能指针,也就释放了动态申请的内存;
发布了4 篇原创文章 · 获赞 0 · 访问量 16

猜你喜欢

转载自blog.csdn.net/yu_xiaoxian_2018/article/details/105326178
今日推荐