C++............................................................................................................................................................................ 2前端
STL............................................................................................................................................................................ 30ios
C++
1、C++语言的背景介绍
1.C++语言的江湖地位
C Java Objective-C C++
2.历史人物
Ken Thompson,B语言之父,UNIX发明人之一,1943-活着。
Dennis Ritchie,C语言之父,UNIX之父,黑客之父,1941-2011。
Bjarne Stroustrup,C++之父,1950-活着。
3.C++之父的贡献
1979年4月,分析UNIX系统因为内核分布而形成的网络流量,试图将simula语言的面向对象特性和B语言的高效性结合起来。
1979年10月,Cpre预编译器,为C语言增长了一些相似simula的机制。
1983年,C with Classes,后来被改名为C++。
- simula的类
- algol的操做符重载
- BCPL的注释风格——“//”
- ADA的模板和名字空间
- Clu/ML的异常
1985年,CFront 1.0发布
1987年,GNU C++发布
1990年,Borland C++发布
1992年,Microsoft C++发布
1998年,ISO标准,C++98
2003年,对C++98进行修订,C++03
2011年,大幅升级,C++11(C++0x)
2014年,对C++11作了部分扩展,C++14
2、无处不在的C++
游戏、科学计算、网络和分布式应用、操做系统和设备驱动、嵌入式、编译器、虚拟机和脚本引擎,等等。
3、C++的竞争对手
Java、C#、C
同时具有高性能和良好抽象建模能力。
4、更好C
1.纯编译语言,与C语言具备彻底相同的编译模型。
2.强类型语言,比C语言的类型检查更加严格。
i = 10;
i = "hello";
int i;
3.去除了一些C中很差的特性。如函数的隐式声明等。
4.增长了一些新的特性:面向对象、操做符重载、异常和泛型编程。
5.和C相比C++更加适合从事大型系统的开发。
5、第一个C++程序
1.编译命令
g++,也能够用gcc,可是要加上-lstdc++
2.扩展名
.cpp/.cc/.C/.cxx,也能够用.c,可是用gcc命令编译时须要加上-x c++
3.头文件
#include <iostream>
4.I/O对象
cin:标准输入(stdin)
cout:标准输出(stdout)
cerr:标准错误(stderr)
<<:插入
>>:提取
a*b
*p
5.名字空间
全部标准C++语言中的函数、对象、类型等都在std名字空间中。
6、名字空间
1.为何?
1)避免产生名字冲突。
2)将基于逻辑的划分和基于物理的划分独立开。
2.什么是?
namespace 名字空间名 {
名字空间成员1;
名字空间成员2;
...
}
namespace ns {
int var = 0;
void fun (void) { ... }
}
3.名字空间合并
namespace 名字空间名 {
名字空间成员1;
名字空间成员2;
}
namespace 名字空间名 {
名字空间成员3;
}
a.cpp:
namespace ns {
int var = 0;
void fun (void) { ... }
}
b.cpp:
namespace ns {
struct type { ... };
}
4.使用名字空间中的名字
1)经过做用域限定操做符“::”
2)名字空间指令
using namespace 名字空间名;
该条指令之后的代码中,对于所使用的名字空间中的名字能够直接引用,前提是没有冲突。
3)名字空间声明
using 名字空间名::成员名;
将指定名字空间中的特定成员引入当前做用域。
using ns::var;
using ns::fun;
using ns::type;
4)无名名字空间
C++会将不属于任何有名名字空间中的名字通通放到无名名字空间中。对于无名名字空间中的名字能够直接经过“::”访问。
5)名字空间嵌套
7、结构、联合和枚举
struct Student {
...
};
struct Student s1 = { ... }; // C
Student s1 = { ... }; // C++
union Array {
...
};
union Array a1; // C
Array a1; // C++
enum COLOR { ... };
enum COLOR c1; // C
COLOR c1; // C++
1.C++的结构体能够定义函数,并且在这些函数中能够直接访问结构体的字段(成员变量)。
2.C++中定义匿名联合。
3.C++中的枚举不能和整型通用。
8、字符串
C++语言提供了专门的字符串类型:string。
9、布尔类型
bool:true/false
1、重(chong)载(overload)
1.同一个做用域中,函数名相同,参数表不一样的函数,构成重载关系。
2.调用函数时,根据实参与形参的类型匹配状况,选择一个肯定的重载版本,这个过程称为重载解析。
3.经过函数指针指向具备重载关系的函数,所选择的重载版本由该函数指针的类型决定。
4.C++换名:C++编译器会按照必定的规则,将函数的参数表信息与函数的原始名混合编码,造成一个新的函数名。所以具备重载关系的函数,虽然其原始名同样,可是由于其参数表的不一样,最后编译生成的实际函数名是不一样的。
5.经过extern "C"能够要求C++编译器按照C的方式处理函数接口,即不换名,固然也就重载。
extern "C" int add (int a, int b);
extern "C" int sub (int a, int b);
或
extern "C" {
int add (int a, int b);
int sub (int a, int b);
}
若是包含extern "C"声明指定符的文件也须要被C编译器处理,为了防止C编译器由于没法识别extern "C"而致使报错:
#ifdef __cplusplus
extern "C" {
#endif
...
#ifdef __cplusplus
}
#endif
只有在C++编译器中__cplusplus宏才有定义,在C编译器中该宏无定义。
2、缺省参数
1.能够为函数的参数指定缺省值,调用函数时若未指定实参,则与该实参相对应的形参取缺省值。
2.函数的缺省参数是在编译阶段解决的,所以只能用常量、常量表达式或者全局变量等非局部化数值做为缺省参数。
3.若是函数的声明和定义分开书写,那么该函数的缺省参数只能出如今声明部分,定义部分不能指定缺省参数,可是能够经过注释提升代码的可读性。
4.若是函数的某一个参数带有缺省值,那么该参数后面的全部参数必须都带有缺省值。
5.不要由于使用缺省参数而致使重载歧义。
3、哑元
1.只指定类型而不指定名称的函数参数,谓之哑元。
2.哑元主要应用于版本升级过程当中的向下兼容和特殊的操做符重载等场合。
4、内联
1.内联就是用函数已被编译好的二进制代码,替换对该函数的调用指令。
2.内联在保证函数特性的同时,避免了函数调用的开销。经过牺牲代码空间,赢得了运行时间。
3.内联会使可执行文件的体积和进程代码区的内存变大,所以只有频繁调用的简单函数才适合内联。稀少被调用的复杂函数,调用开销远小于其执行开销,由内联而得到的时间性能改善,不足以抵消空间性能的损失,故不适合内联。
4.递归函数不能内联。
5.经过inline关键字能够显式地请求编译器将某个函数处理为内联函数。
6.inline关键字仅仅表示一种对函数实施内联优化的指望,但该函数是否真的会被处理为内联,还要由编译器的优化策略决定。
7.多数现代编译器已经把内联做为一种缺省的优化机制,即便不是显式使用inline关键字,只要该函数符合内联优化的条件,编译器也会自动将其处理为内联函数。
5、动态内存分配
1.C中动态内存分配函数在C++中能够继续使用。
#include <cstdlib>
void* malloc (size_t size);
void* calloc (size_t nmemb, size_t size);
void* realloc (void* ptr, size_t size);
void free (void* ptr);
typedef unsigned int size_t;
2.new/delete运算符
int a = 0;
sizeof (a=5);
cout << a << endl; // 0
1)动态分配/释放单个变量
int* p = new int;
int* p = new int (123);
delete p;
2)动态分配/释放数组变量
int* p = new int[5] { ... };
delete[] p;
3)动态分配/释放高维数组
int (*prow)[4] = new int[3][4];
delete[] prow;
4)new在分配内存失败的状况下,不是返回NULL指针,而是抛出bad_alloc异常。
5)定位分配
new (地址) 类型[大小]
int* pn = new (pool) int (123);
1、引用
1.引用的基本特性
1)引用即别名
声明一个标识符为引用,即表示该标识符可做为另外一个有名或无名对象的别名。
int a = 10;
int& b = a; // b是a的一个引用,即b是a的一
// 个别名
++b;
cout << a << endl; // 11
int& c = b; // c是a的另外一个引用
++c;
cout << a << endl; // 12
常引用的目标不可修改,只能读取。
int const& d = c;
++d; // ERROR
在C++中,无名对象(字面值常量、临时变量)都被视做右值,只能经过常引用引用之。
int const& a = 10; // 纯右值
int const& a = x + y; // 将亡右值
2)引用必须初始化
int a = 10;
int a;
int* p = NULL;
int* p;
int& r; // ERROR
int& r = a;
3)没法定义一个什么都不引用的引用。
int* p = NULL; // 什么都不指向的指针
int& r = NULL; // ERROR
4)引用一经初始化便不能再引用其它对象。
int a;
int& r = a;
int b;
r = b; // b -> a
2.引用型参数
1)函数的形参是实参的别名
能够将函数的形参声明为引用形式,该形参在参数传递过程当中由对应的实参初始化,并成为该实参的别名。
2)在函数中修改实参的值
经过引用型形参,能够在函数体内部修改调用者实参的值,成为除返回值和指针参数之外,第三种由函数内部向函数外部输出数据的途径。
3)避免对象复制的开销
经过引用传递参数,形参只是实参的别名而非其副本,这就避免了从实参到形参的对象复制,这对于具备复杂数据结构的参数而言能够提升其传参的性能。
若是函数只是读取引用型参数的目标对象,那么最好经过常引用接收实参,防止在函数中意外地修改实参变量的值。
3.引用型返回值
foo () = 10;
++foo ();
1)返回左值
2)函数的局部变量只具备函数级甚至块或语句级的生命周期,函数一旦返回,全部的局部变量即刻销毁,即便经过返回值得到了对它们的引用,其目标也将是未定义的。所以不要从函数中返回对局部变量的引用,而返回全局、静态、成员、堆变量的引用是安全的。
int* foo (void) {
int a = 10;
...
return &a;
}
printf ("%d\n", *foo ());
int b = 20;
int c = b + *foo ();
4.虽然引用是经过指针实现的,可是在高级语言层面引用和指针仍是具备若干不一样的特性
1)指针能够不初始化,其目标能够在初始化后随意变动;可是引用必须初始化,并且一旦初始化就没法变动其目标
int x = 10, y = 20;
int* p; // p没有初始化
p = &x; // p指向x
p = &y; // p指向y
--------------------
int x = 10, y = 20;
int& r = x; // r必须初始化
r = y; // r引用不了y
2)能够定义空指针,即什么也不指向的指针,可是不能定义空引用,引用必须有所引用,不然引用将失去意义。
int* p = NULL;
int& r = NULL; // ERROR
3)能够定义指向指针的指针,可是没法定义引用引用的引用。
int x = 10;
int* p = &x; // 一级指针
int** pp = &p; // 二级指针 // 指向指针的指针
------------------
int x = 10;
int& r = x;
int&& rr = r; // ERROR
C++2011中相似“int&&”的类型是合法的,可是它表示右值引用,而非二级引用。
4)能够定义引用指针的引用,可是没法定义指向引用的指针。
int x = 10;
int* p = &x;
int*& q = p; // q是p的别名
cout << *q << endl; // 10
----------------
int x = 10;
int& r = x;
int&* s = &r; // ERROR
int* s = &r; // OK
5)能够定义存放指针的数组,可是没法定义存放引用的数组。能够定义引用数组的引用。
int a = 10, b = 20, c = 30;
int* p[3] = {&a, &b, &c}; // 指针数组
-----------------
int a = 10, b = 20, c = 30;
int& r[3] = {a, b, c}; // ERROR
-----------------
int a[3] = {10, 20, 30};
int (&r)[3] = a; // 数组引用
cout << r[0] << endl; // 10
r[1]++;
cout << a[1] << endl; // 21
5.函数指针和函数引用
int x = 10;
int* p = &x;
int& r = x;
*p = 20;
r = 20;
----------------------------
int func (double); // 函数
int (*pfunc) (double) = &func; //函数指针
int (&rfunc) (double) = func; //函数引用
(*pfunc) (3.14);
rfunc (3.14);
2、显式(强制)类型转换
1.C风格的显式类型转换
(目标类型)源类型变量
int i = 1234;
char c = (char)i;
2.C++风格的显示类型转换
1)静态类型转换
static_cast<目标类型> (源类型变量)
编译器会对源类型和目标类型作相容性检查,检查不经过报错。
A.若是在源类型和目标类型之间,至少有一个方向能够作隐式类型转换,那么这两个类型就是相容类型。
short x = 10;
void* v = &x;
short* p = static_cast<short*> (v);
--------------------
short x = 10;
short* v = &x;
int* p = static_cast<int*> (v); // ERROR
B.若是从源类型到目标类型存在自定义的转换规则(类型转换构造函数/类型转换运算符函数),那么它们也能够被称为相容类型。
2)动态类型转换
dynamic_cast<目标类型> (源类型变量)
用于具备多态性的父子类型的指针或引用。
3)去常类型转换
const_cast<目标类型> (源类型变量)
去除指针或引用的常属性。
int x = 10;
int const* cp = &x;
int* p = const_cast<int*> (cp);
char* q = const_cast<char*> (cp); // 错误
char* q = (char*)cp; // OK,风险
4)重解释类型转换
reinterpret_cast<目标类型> (源类型变量)
转换任意类型的指针或引用。
在任意类型的指针和整型之间转换。
3、面向对象
1.为何要面向对象
1)相比于分而治之的结构程序设计,强调大处着眼的面向对象程序设计思想,更适合于开发大型软件。
2)得益于数据抽象、代码复用等面向对象的固有技术,软件开发的效率得到极大的提高,成本却大幅下降。
3)面向对象技术在数据库、网络通讯、图形界面等领域的普遍应用,已催生出各类设计模式和应用框架。
4)面向对象已经成为现代计算机程序设计语言的发展潮流,不但几乎全部新诞生的语言都是面向对象的,并且不少传统的结构化语言也在不断地引入面向对象的机制。
2.什么是面向对象
1)万物皆对象。
2)把大型软件当作一个由对象组成的社会。
3)对象拥有足够的智能,可以理解来自其它对象的信息,并以适当的方式做出反应。
4)对象可以从高层对象继承某些特性,并容许低层对象从本身继承某些特性。
5)编写程序的过程就是一个描述对象的过程,最终是问题域和解域得到完美的统一。
6)面向对象的四大要素:封装、继承、多态、抽象。
1、类和对象
1.经过属性和行为描述具体的对象,其中属性表示对象的静态特征,而行为则表示对象的动态特征。
2.拥有相同属性和行为的对象被分为一组,即一个类。
属性 行为
狗 犬种 进食
犬龄 睡眠
体重 玩耍
毛色
学生 姓名 吃饭
年龄 睡觉
学号 学习
手机 品牌 接打电话
型号 收发短信
价格 上网
玩游戏
3.类即逻辑抽象
1)简单类型:只能表示一个属性(变量)。
2)数组类型:能够表示多个属性(元素),可是类型必须相同。
3)结构体类型:能够多个类型不一样的属性(字段),但缺乏对行为(函数)的描述。
4)类类型:既能够表示多个不一样类型的属性(成员变量),同时也能够表示多个不一样的行为(成员函数)。
现实世界 逻辑空间 虚拟世界
小狗 -> 狗类 -> 狗对象
真实对象 抽象描述 逻辑对象
^
OOP
2、类的定义与实例化
1.类的主要内容
1)成员变量:描述对象的各类属性。
2)成员函数:描述对象的各类行为。
3)构造函数:对对象作初始化。
4)析构函数:对对象作终结化。
5)访控属性:决定成员的访问特性。
public(struct缺省) - 公有,谁均可以访问;
private(class缺省) - 私有,只有本身能够访问;
protected - 保护,只有本身和本身的子类能够访问。
6)继承方式与基类:继承。
class Student {
public:
// 吃饭
void eat (string const& food) { ... }
// 睡觉
void sleep (int time) { ... }
// 学习
void learn (string const& course) { ... }
private:
string m_name; // 姓名
int m_age; // 年龄
int m_no; // 学号
};
2.构造函数
1)函数名与类名相同,且没有返回类型。
2)构造函数在建立对象时被系统自动调用。
A.直接定义变量,栈对象;
B.用new操做符建立对象,堆对象。
3)构造函数在对象整个生命期内,必定会被调用,且仅被调用一次。
4)构造函数负责对成员变量进行初始化,分配必要的资源,设置对象的初始状态。
3、构造函数与初始化表
1.构造函数能够重载
1)构造函数经过参数表的差异化以重载的形式提供不经过的初始化方法。
2)重载的构造函数经过构造实参的类型选择匹配。
3)使用缺省参数能够减小构造函数重载版本的数量。注意避免重载冲突。
2.具备特殊意义的构造函数
1)缺省构造函数
可以以无参的方式被调用的构造函数称为缺省构造函数。
缺省构造函数表示对象的默认初始化状态。
若是一个类中没有定义任何构造函数,那么编译器就会自动为其生成一个缺省构造函数。对于基本类型的成员变量不作任何处理,而对于类类型的成员变量,调用其相应类型的缺省构造函数。
2)类型转换构造函数
凡是能够经过一个参数调用的构造函数都属于类型转换构造函数。如:
class 目标类型 {
目标类型 (源类型 const& 引用) { ... }
};
经过该构造函数将源类型的对象隐式或显式地转换为目标类型的对象。若是但愿该转换必须显式完成,能够在该类型转换构造函数前面加上explicit关键字。
3)拷贝构造函数
当用一个对象构造与它同类型的副本对象时,编译器会经过拷贝构造函数完成该副本对象的初始化。如:
class 类 {
public:
类 (类 const& 引用) { ... }
};
若是一个类没有定义拷贝构造函数,编译器就会自动提供一个缺省的拷贝构造函数,该函数对于基本类型的成员变量,按照字节复制,而对于类类型的成员变量,则会调用其相应类型的拷贝构造函数,构造其副本。
多数状况下不须要本身定义拷贝构造函数,编译器提供的缺省版本已经足以知足要求。但在某些特殊状况下,须要自行定义以弥补缺省版本的不足。
拷贝构造会下降应用程序的性能,设计时尽可能避免,好比经过引用传递参数。
编译器会经过必要的优化策略,减小拷贝构造的机会。经过-fno-elide-constructors选项能够关闭此优化特性。
自定义构造函数 系统定义构造函数
无 缺省构造函数
缺省拷贝构造函数
非拷贝构造函数 缺省拷贝构造函数
拷贝构造函数 无
3.初始化表
1)经过在类的构造函数中使用初始化表,指明该类的成员变量如何被初始化。
2)类的类类型成员变量,要么在初始化表中显式初始化,要么经过相应类型的缺省构造函数初始化。
3)类的常量型和引用型成员变量,必须在初始化表中显式初始化,不能在构造函数体中赋初值。
4)类的成员变量按其在类中被声明的顺序依次初始化,而与其在初始化表中的排列顺序无关。
程序员写:
Student s ("张飞", 25);
编译后的机器指令至关于以下:
从栈内存中分配sizeof (Student)字节
调用Student::Student ("张飞", 25)函数初始化
程序员写:
Student* s = new Student ("张飞", 25);
被编译器处理为:
Student* s = malloc (sizeof (Stduent));
调用Student::Student ("张飞", 25)函数初始化(s->Student ("张飞", 25)/
Student::Student (s, "张飞", 25))
1、C++对象模型
class Student {
public:
Student (char const* name, int age) {
strcpy (m_name, name);
m_age = age;
}
void print (void) {
cout << m_name << "," << m_age
<< endl;
}
private:
char m_name[256];
int m_age;
};
Student s1 ("张飞", 25);
1.C++的对象模型和C的结构模型没有任何区别,包括成员的布局,以及对齐补齐的规则。
2.对象中只有成员变量,没有成员函数。类的成员变量在该类的每一个对象中都一份独立的拷贝。可是类的成员函数只有一份,且为该类的全部对象共享。
3.为了在一个函数中区分出不一样的调用对象,编译器会为每一个成员函数提供一个隐藏的指针参数——this指针——指向调用该成员函数的对象。在成员函数中对全部成员变量的访问,以及对其它成员函数的调用,实际上都是经过this指针完成的。
4.类的构造函数中一样具备this指针参数,指向这个正在被构造的对象。
class B;
class A {
B m_b;
};
class B {
A m_a;
};
A a; sizeof (A) ?
5.显式使用this指针的场合
1)解决成员变量的名字冲突;
2)从成员函数中返回调用对象的自引用;
3)经过成员函数实现对象间的交互;
4)在成员函数函数销毁调用对象自身。
2、常函数
1.在类成员函数的参数表以后,函数体的左花括号以前,加上const关键字,该成员函数的this指针即具备常属性,这样的成员函数被称为常函数。
2.在常函数内部,由于this指针被声明为常量指针(即目标只读的指针),因此没法修改为员变量的值,除非该成员变量被mutable关键字修饰。
3.常对象只能调用常函数,很是对象既能够调用很是函数也能够调用常函数。原型相同的常函数和很是函数能够构成重载关系。
3、析构函数
1.析构函数是类的特殊的成员函数
1)析构函数的函数名就是在类名前面加“~”;
2)析构函数没有返回类型;
3)析构函数没有参数
4)析构函数不能重载
2.析构函数在对象被销毁时自动调用
1)局部对象的析构函数由其所在最小做用域的右花括号调用;
2)堆对象的析构函数被delete运算符调用;
3)全局对象的析构函数,被进程加载器调用。
3.缺省析构函数
若是一个类没有定义析构函数,编译器提供一个缺省析构函数。该函数对于基本类型的成员变量什么也不作,而对于类类型的成员变量,则会调用其相应类型的析构函数。
4、拷贝构造和拷贝赋值
1.缺省方式的拷贝构造和拷贝赋值,对包括指针在内的基本类型成员变量按字节复制,致使浅拷贝问题。
2.为了得到完整意义上的对象副本,必须本身定义拷贝构造函数和拷贝赋值运算符函数,针对指针型成员变量作深拷贝。
3.拷贝赋值运算符函数的实现
1)避免自赋值;
2)分配新资源;
3)释放旧资源;
4)拷贝新内容;
5)返回自引用。
1、静态成员
1.属于类而非对象
1)静态成员变量不包含于对象实例中,具备进程级的声明周期
2)静态成员函数没有this指针,也没有常属性
3)静态成员函数只能访问静态成员(变量或函数),非静态成员函数既能够访问静态成员,也能够访问非静态成员。
2.静态成员也受访问控制的约束。
3.静态成员变量必须在类的外部定义或初始化,静态成员函数既能够在类的外部也能够在类的内部定义。
2、成员指针
经过类型的约束,表达指向类特定类型成员的指针。
3、操做符(运算符)重载
1.操做符标记
1)单目操做符:只有一个操做数的操做符。
-/++/--/&/*/->/!/~/()/类型转换,等等
2)双目操做符:有左右两个操做数的操做符。
算术运算:*///%/+/-
关系运算:>/>=/</<=/==/!=
逻辑运算:&&/||
位运算:&/|/^/<</>>
赋值和复合赋值:=/+=/-=/*=//=/...
下标运算:[]
int a[5] = { ... };
cout << a[3] << endl;
3)三目运算符:包含三个操做数的操做符。
条件运算:A ? B : C
2.操做符函数
1)在特定条件下,编译器有能力把一个由操做数和操做符组成的表达式,解释成为一个全局或者成员函数调用,该全局或者成员函数就被成为操做符函数。
复数(3+4i)
2)经过定义操做符函数,能够实现针对自定义类型的运算法则,并使之与内置类型具备一致的语义。
3.双目操做符
L#R
-> L.operator# (R) // 成员,左调右参
-> ::operator# (L, R) // 全局,左一右二
1)运算类:左右操做数均可觉得左值或右值,表达式的值必须是右值。
友元:能够经过friend关键字,把一个函数或者类声明为一个类友元。被声明有关的函数或类能够自由访问受权类的任何私有成员。友元声明能够位于受权类的公有/私有/保护任何区域,其效果都同样。
2)赋值类:右操做数可为左值或右值,但左操做数必须是左值,表达式的值是左值且为左操做数自己(而非副本)。
3)输入输出:左操做数是ostream/istream,右操做数对于输出能够是左值也能够是右值,对于输入只能是左值,表达式的值是左操做数自己。
Complex c1 (...);
Complex const c2 (...);
cout << c1; // cout.operator<< (c1)
// ::operator<< (cout, c1)
cout << c2;
cin >> c1;
cin >> c2; // ERROR
cout << c1 << c2 << endl;
4.单目操做符
#O/O#
->O.operator# ()
->::operator# (O)
1)运算类: 操做数既能够是左值也能够是右值,操做数在运算先后不发生变化,表达式的值是右值。
-x = 10; // ERROR
(0-x) = 10; // ERROR
2)前缀类:操做数为左值,表达式的值是左值,并且就是操做数自己。运算先后操做数的值会发生变化,表达式的值是变化之后的值。
3)后缀类:操做数为左值,表达式的值是右值,而是操做数运算以前的历史备份。运算先后操做数的值发生变化,表达式的值是变化之前的值。
5.三目操做符:没法重载
6.其它操做符:下标、函数、类型转换
7.不能重载的操做符
::/./.*/?:/sizeof/typeid
8.操做符重载的限制
1)全部操做数都是基本类型的不能重载
int a = 1, b = 1;
int c = a + b; // c = 10000 ?
2)没法改变操做符的优先级
z + x ^ y -> (z + x) ^ y
3)没法改变操做符的目数
4)没法发明新的操做符
**4
s1 @ s2
5)保持操做符的语义一致性
c1 + c2
str1 + str2
1、继承的基本概念
1.共性和个性
学生:姓名、年龄、学号,吃饭、睡觉、学习
教师:姓名、年龄、工资,吃饭、睡觉、授课
---------------------------------------------
人类:姓名、年龄,吃饭、睡觉 - 共性
学生是人:学号,学习 - 个性
教师是人:工资,授课 - 个性
1)共性表达了不一样类型事物之间共有的属性和行为。
2)个性则着意刻画每种类型事物特有的属性和行为。
2.超集与子集
1)超集体现了基于共性的通常。
2)子集体现了针对个性的特殊。
3.基(父)类和子类
1)基类表示超集,体现共性,描述共有的属性和行为。
2)子类表示子集,体现个性,描述特有的属性和行为。
4.继承与派生
基类: 人类(姓名、年龄、吃饭、睡觉)
派生 V / \ ^ 继承
子类: 学生 教师
(学号、学习) (工资、授课)
5.继承语法
class 子类 : 继承方式1 基类1, 继承方式2 基类2, ... {};
class Human {...}; // 人类
class Student : public Human {...}; // 学生
class Teacher : public Human {...}; // 教师
class Assistant : public Student, public Teacher {...}; // 助教
继承方式:
public - 公有继承,最多见的继承方式
protected - 保护继承
private - 私有继承
6.公有继承的特色
1)皆然性:子类即基类
学生是人
教师是人
任何子类对象中都包含着它的基类子对象,并且一般位于子类对象的低地址部分。所以把一个指向子类对象的指针或者引用子类对象的引用转换为其基类类型能够隐式完成。实际上指这种转换就意味着把一个子类对象的基类部分看做是一个实际的基类对象。这种类型转换亦称向上造型。
2)可访问性
子类能够访问基类中哪些成员:公有成员、保护成员。基类的私有成员不能为子类所直接访问,可是在子类中存在。
3)隐藏性
若是在子类中定义和基类同名的标识符,那么子类中标识符就会隐藏基类中的标识符。除非经过做用域限定符显式地指明所访问的标识符源自基类。
4)传导性
当子类对象被构造、析构或者复制时,其基类子对象也须要同时被构造、析构或者复制。
A->B->C
当经过delete操做一个指向子类对象的基类指针时,实际被执行的是基类的析构函数,该函数不会调用(传导)子类的析构函数,子类所特有动态资源将造成内存泄漏。
7.继承方式对访控属性的影响
考虑继承方式:经过子类访问其所继承。
class A { void foo (void) { ... } };
class B : public A {
void bar (void) {
foo(); //直接访问基类,不考虑继承方式
}
};
class C : public B {
void bar (void) {
foo (); //经过子类B访问其从基类A中
//继承的foo,须要考虑继承方式
}
};
int main (void) {
B b;
b.foo (); // 经过B访问其继承的foo
// 须要考虑继承方式
return 0;
}
基类 公子 保子 私子
公有 公有 保护 私有 //公有继承保留基类访控属性
保护 保护 保护 私有 //保护继承覆盖基类公有属性
私有 私有 私有 私有 //私有继承覆盖基类公有和保护属性
8.私有继承和保护继承
class DCT { // $1000
public:
void codec (void);
};
class Jpeg : protected DCT {
public:
void render (void) {
... codec () ...
}
};
class Jpeg2K : public Jpeg {
};
// $1
class PDF : public Jpeg {
public:
void show (void) {
... render () ...
... codec () ... // ERROR
}
};
私有继承亦称实现继承,其目的在于将基类中的公有和保护成员,在子类中私有化,防止其经过子类扩散。可是,若是但愿在子类的子类能够继续访问这些成员,而只是限制在子类外部的访问,则能够将子类从基类的继承方式设置为保护继承。
保护和私有继承不具备皆然性。
2、多重继承
1.一个子类从多个基类中派生。
电话 媒体播放器 计算机
\ | /
智能手机
2.钻石继承问题
A
/ \
B C
\ /
D
A
/ | \
B C D
\ / \ /
E F
A
/ \
B D
| |
C |
\ /
E
1)一个子类继承自多个基类,而这些基类又源自共同的祖先(公共基类),这样的继承结构称为钻石继承。
2)派生多个中间子类的公共基类子对象,在继承自多个中间子类的汇聚子类对象中,存在多个实例。
3)在汇聚子类中,或经过汇聚子类对象,访问公共基类的成员,会因继承路径的不一样而致使不一致。这种现象称为钻石继承问题。
3.虚继承
1)经过虚继承,能够保证公共基类子对象在汇聚子类对象中,仅存一份实例,且为多个中间子类对象所共享。
2)为了表示虚继承,须要在继承表中使用virtual关键字。
3)通常而言,子类的构造函数不能指定间接基类的构造方式,可是一旦这个间接基类被声明为虚基类,它的全部子类,不管是直接子类仍是间接子类,都必须显式地指明该公共基类子对象的构造方式,不然系统将按照缺省方式构造该子对象。
1、类型的决定性
经过一个指针或者引用访问类的成员,编译器只是根据指针或者引用的类型决定是否能够访问该成员,而与此指针或者引用的实际目标对象无关。
2、虚函数与多态
若是将基类中的某个成员函数声明为虚函数(在其返回类型前面加上virtual关键字),那么其子类中的同型函数就也是虚函数(不管其是否带有virtual关键字),并且和基类版本造成覆盖关系。这时经过一个指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数,实际被执行的将是子类中的覆盖版本,而非基类的原始版本。这种现象谓之多态。
3、重载、隐藏和覆盖
重载必须在同一个做用域中。
覆盖必须是同型的虚函数。
若是不是重载也不是覆盖,并且函数名还同样,那就必定是隐藏。
4、有效覆盖的前提条件
1.只有类的非静态成员函数才能被声明为虚函数,全局函数和类的静态成员函数都不能是虚函数。
2.只有在基类中被声明为虚函数的成员函数才能在子类中覆盖。
3.虚函数在子类中的覆盖版本必须和该函数的基类版本拥有彻底相同的签名,即函数名、形参表、常属性严格一致。
4.若是基类中虚函数的返回类型为基本类型或类类型的对象,那么子类的覆盖版本必须返回相同的类型。
5.若是基类中的虚函数返回类类型的指针或引用,那么该函数在子类中的覆盖版本能够返回其基类版本返回类型的公有子类的指针或引用——类型协变。
class ShapeEditor
{
……
};
class Shape
{
public:
virtual const ShapeEditor& getEditor ()const = 0; //Factory Method
};
class Circle;
class CircleEditor : public ShapeEditor
{
……
};
class Circle : public Shape
{
public:
const CircleEditor& getEditor ()const ;
};
6.子类中覆盖版本不能比基类版本说明抛出更多的异常。
7.不管基类中的虚函数位于该类的公有、私有仍是保护部分,该函数在子类中的覆盖版本均可以出如今任何访控区域。
class Base {
virtual void foo (void); // 1
virtual void foo (void) const; // 2
};
class Derived : public Base {
virtual void foo (void); // 3
virtual char foo (void) const; // 4
};
1和2构成重载
3和4构成重载
3隐藏了2,覆盖了1
4隐藏了1,在试图覆盖2时出错
重载在同一个类中,覆盖在虚继承的子类实现中必须有相同的签名和返回值,不然隐藏。
5、多态的条件
多态性除了须要在子类和基类间造成有效的虚函数覆盖之外,还必须经过指针或者引用访问该虚函数。
当基类的构造函数被子类的构造函数调用时,子类对象尚不能说是子类类型的,它只表现出基类类型的外观和行为。这时调用虚函数,只能被绑定到基类版本,没有多态性。
当基类的析构函数被子类的析构函数调用时,子类对象已再也不是子类类型的了,它只表现出基类类型的外观和行为。这时调用虚函数,只能被绑定到基类版本,没有多态性。
6、纯虚函数、抽象类、纯抽象类
1.形如virtual 返回类型 函数名 (形参表) [const] = 0;的虚函数成为纯虚函数。
2.至少包含一个纯虚函数的类就叫抽象类,抽象类不能实例化为对象。
3.若是一个抽象类的子类没有覆盖其基类中的所有纯虚函数,那么该子类就也是抽象类。
4.除了构造、析构和静态成员函数之外的所有成员函数都为纯虚函数的抽象类就叫作纯抽象类,亦称接口类。
7、虚函数表和动态绑定
1.对于包含虚函数的类,编译器会为其生成一张虚函数表,即存放每一个虚函数地址的函数指针数组,简称虚表(vtbl),每一个虚函数对应一个虚函数表的索引号。
2.当编译器看到经过指针或引用调用虚函数时,并不急于生成有关函数调用的指令,相反它会用一段代码替代该调用语句,这段代码在运行时执行,完成以下操做:
1)肯定调用者指针或引用的目标对象,并从中获取到虚表指针;
2)根据所调用函数的索引号从虚表中提取相应的函数地址;
3)根据函数地址调用该虚函数。
这个过程由于是在运行时完成的,因此称为动态绑定。
3.动态绑定对性能的影响
1)虚函数表自己会增长内存空间的开销;
2)虚函数调用的时间开销会大于普通函数;
3)虚函数不能内联。
建议只有在确实须要多态性的场合才使用虚函数,不然尽可能避免使用虚函数。
8、运行时类型信息(RTTI)
1.动态类型转换(dynamic_cast)
动态类型转换应用在具备多态继承关系的父子类的指针或引用之间。在运行期间检查转换源的目标对象的类型与转换目的类型是否一致,若是一致则返回实际的对象指针或引用,不然返回空指针或抛出异常。
2.typeid运算符
在运行期间动态获取对象的类型信息。
9、虚析构函数
若是将基类的析构函数声明为虚函数,那么子类的析构函数就也是虚函数,并且对基类版本构成覆盖。这时delete一个指向子类对象的基类指针,实际被执行的是子类的析构函数,该函数在释放完成子类特有的资源之后,会自动调用基类的析构函数,完成对基类资源的释放,最终释放掉全部的资源,没有内存泄漏。
10、虚与非虚
1.能够被声明为虚函数的函数
普通成员函数
成员函数形式的操做符函数
析构函数
2.不能被声明为虚函数的函数
静态成员函数
全局函数形式的操做符函数
构造函数
全局函数
1、异常
1.抛出异常:throw 异常对象;
异常对象:基本类型的变量或者类类型的对象。
throw 100;
throw "内存分配失败";
throw MemoryEerror ();
2.捕获异常
try {
可能引起异常的操做;
}
catch (异常类型1& 异常对象引用) {
对异常类型1的处理;
}
catch (异常类型2& 异常对象引用) {
对异常类型2的处理;
}
...
catch (...) {
对其它异常的处理;
}
3.异常流程
1)当代码执行到throw语句时,一方面会将所抛出的异常对象复制到系统安全区中,另外一方面将流程执行到包含此throw语句的最小做用域的右花括号处,并沿着函数调用的路径,向上回溯,直到try块的右花括号处。而后根据异常对象的类型匹配一个适当的catch分支,执行其中的异常处理代码。
2)若是一个被抛出的异常没有被任何代码捕获,最终将被系统捕获,并终止进程,同时打印异常的类型信息。
3)若是一个函数没有捕获它所引起的异常,那么该异常将继续向上抛出。直到调用路径中的某个函数捕获了该异常。
4.异常说明
throw (异常类型1, 异常类型2, ...)
1)在声明函数时,能够经过异常说明,声明该函数所可能抛出的异常。该函数能够抛出异常说明之外的异常,但这些异常不能被其调用者捕获。
5.构造函数中的异常
1)在构造函数中能够抛出异常,表示在构造过程当中出现的错误。
2)若是一个对象在构造过程当中出现了异常,那么这个对象就会被不完整构造,而一个不完整构造的对象,它的析构函数永远不会被执行。
6.析构函数中的异常
析构函数中不要抛出异常,对能够引起异常的操做,尽可能在内部捕获,不要使之被抛到析构函数外部。
7.标准库异常
#include <stdexcept>
exception : const char* what (void) const throw () = 0;
--> overflow_error - 上溢异常
--> underflow_error - 下溢异常
--> invalid_argument - 无效参数异常
--> out_of_range - 越界异常
--> bad_alloc - 内存分配失败(new)
--> bad_cast - 动态类型转换失败(引用)
--> bad_type_id - 非法类型
2、I/O流
ifstream - 输入文件流,从文件读取
ofstream - 输出文件流,向文件写入
fstream - 输入输出文件流,既可读也可写
打开->读/写/跳->关闭
operator bool (void) {}
istream& istream::read (char* buffer, streamsize num);
istream->bool : true - 读到num字节
false - 读取的字节数少于
num,或者出错
istream::gcount ()返回最近一次读取的字节数
ostream& ostream::write (
const char* buffer, streamsize num);
ostream->bool : true - 写成功
false - 写失败
istream::seekg (off_type offset,
ios::seekdir origin);
ostream::seekp (off_type offset,
ios::seekdir origin);
origin - ios::beg
ios::cur
ios::end
pos_type istream::tellg (void);
pos_type osteam::tellp (void);
STL
C/C++
静态类型
int i = 0;
char c = (char)i;
Student student ("张飞", 25);
cout << student.m_name << endl;
student.learn ("C++");
优势:效率高、安全。
缺点:灵活性差、通用性差。
i = 100;
i = "hello";
i = Student (...);
1、模板的由来
1.C/C++语言的静态类型系统在保证效率和安全性的同时,也增长了编写与类型无关的通用代码(数据结构和算法)的难度。
2.经过宏定义能够摆脱类型的约束,但同时也失去了源自类型系统的安全性保障。
3.经过宏定义实现通常化的算法表示,利用预编译器根据该宏生成针对不一样类型的具体实现。
4.将预处理器生成具体函数的工做转移到编译器中——这就是模板的意义。
2、函数模板
1.定义
template<typename 模板形参1, typename 模板形参2, ...>
返回类型 函数模板名 (调用形参表) { ... }
例如:
template<typename A, typename B, typename C> A foo (B b) { C c; ...; }
2.使用
函数模板名<模板实参1, 模板实参2, ...> (调用实参表);
例如:
int a;
double b = 3.14;
a = foo<int, double, string> (b);
3.只有那些能够知足函数模板所有要求的类型才能用于实例化该函数模板。
4.二次编译
每一个函数模板事实上都要被编译两次。一次是在实例化以前,先检查模板代码有无语法硬伤,若是检查经过则生成该函数模板的内部表示。另外一次是在实例化该函数模板的过程当中,即将函数模板变成具体函数的过程,结合所提供的模板实参再次检查模板代码的正确性,若是检查经过则生成具体函数的二进制指令。
5.隐式推断
若是函数模板调用参数的类型与该模板的模板参数相关,那么在调用该函数模板时即便不显式指定模板实参,编译器也有能力根据调用实参的类型隐式地推断出相应模板参数的类型。
6.函数模板重载
1)函数模板和普通函数同样,也能够重载,重载解析的具体规则见代码overload.cpp。
2)在重载函数模板的时候,应该尽量地把改变限制在参数的个数或参数的具体类型上,而对于参数的引用或常属性尽可能保持不变,以免出现返回局部对象引用的状况。
3、类模板
1.类的成员变量、成员函数和基类若是包含参数化的类型,那么这个类就称为类模板。
2.定义
template<typename 模板形参1, typename 模板形参2, ...>class 类模板名 { ... };
3.使用
类模板自己并非一个类型,只有先将类模板实例化为具体类,才能用于建立对象,声明变量。
类模板名<模板实参1, 模板实参2, ...>
类模板不能隐式推断,必须显式实例化。
4.类模板的两步实例化
实例化 实例化
类模板------>类------>对象
编译期 运行期
编译器 处理器
5.类模板全部的成员函数,包括构造函数、析构函数、运算符函数、通常成员函数、静态成员函数,无一例外地都是函数模板。
6.在类模板声明中,凡是使用类型的场合,严格的语法应该是:"类模板名<模板参数>";而对类名的引用则能够直接使用"类模板名"。
7.类模板中,只有那些被调用的成员函数才会被实例化,即产生二进制指令代码。所以某些类型虽然并无提供类模板所须要的所有功能,但照样能够实例化该类模板,只要不直接或间接调用那些依赖于未提供功能的成员函数便可。
8.类模板的静态成员变量,在该模板的每一个实例化类中各有一份独立的拷贝,并为该实例化类的全部对象所共享。
类模板->类1->对象1
->对象2
->类2->对象3
->对象4
9.类模板递归实例化
用类模板的实例化类实例化该类模板自身。
一般用这种方法表达一些在空间上具备递归特性的数据结构,如多维数组、复合链表、多叉树等。
3、类模板
9...
10.特化
1)一个针对任意类型的通用的类模板可能对于某些个别的特殊类型并不可以很好地支持,这时能够经过特化定义一个专门针对某个特定类型的实现取代通用版本,这就叫作类模板的特化(特例化)。
2)全类特化:对整个类模板进行特化。
template<>
class 类模板名<特化实参> { ... };
例如:
template<>
class Comparator<char const*> { ... };
3)成员特化:只特化类模板中那些与类型相关的成员函数。
template<>
返回类型 类模板名<特化实参>::成员函数名 (
调用形参表) [const] { ... }
例如:
template<>
char const* Comparator<
char const*>::max (void) const { ... }
4)全类特化至关于另写了一个新的类,其实现能够和通用版本彻底不一样,而成员特化,其声明与通用版本共享,其函数原型除了把通用版本的类型参数变成具体的特化类型之外,不能有任何不一样。
11.局部特化(偏特化)
1)类模板能够被局部特化,即一方面为类模板指定特定的实现,另外一个方面又容许用户对部分模板参数自行指定。
2)若是多个局部特化同等程度地匹配某个声明,那么该声明将因二义性而致使歧义错误。
3)函数模板不支持局部特化。
12.缺省参数
1)类模板的模板参数能够带有缺省值,即缺省模板参数。
2)若是某个类模板的模板参数带有了缺省值,那么它后面的全部模板参数必须都带有缺省值。
3)C++98标准规定函数模板不能带有缺省模板参数,可是C++2011标准容许函数模板带有缺省模板参数。
GCC4.6:g++ ... -std=c++0x
GCC4.8:g++ ... -std=c++11
4)模板参数的缺省值能够取前面定义的参数。
13.非类型参数
1)不管是函数模板仍是类模板均可以带有数值形式的参数,即非类型参数。
2)类模板和C++2011之后的函数模板的非类型参数均可以带有缺省值。
3)传递给模板非类型参数的实参只能是常量、常量表达式、带有常属性(const)的变量,可是不能同时具备挥发性(volatile)。
4)模板的非类型参数必须是整数型,具体包括char/short/int/long/long long及其unsigned版本,还包括指针。
5)向模板传递字符串:须要用具备外部连接特性的字符数组做为非类型实参。
字面值字符串不能够。
string对象不能够。
全局指针不能够。
只有内部连接特性的字符数组不能够。
4、其它的技术细节
1.嵌套依赖
若是须要使用某个依赖于模板参数的内部类型,那么就须要在该类型前面加上typename关键字,不然编译器会将该内部类型解释为一个静态成员变量,进而致使编译错误。
class - 声明类
\ 声明模板的
/ 类型参数
typename - 解决嵌套依赖
2.依赖于模板参数的模板成员
若是须要调用模板参数类型中的模板型成员函数,那么须要在该成员函数名前面加上template关键字,不然该函数会被编译器解释为一个普通函数,而对后面的"<>"产生编译错误。
3.在子模板中访问基模板
在子模板中访问那些在基模板中声明且依赖于模板参数的符号,应该在它们前面加上做用域限定符“基模板::”或this指针“this->”,不然编译器将只在子类和全局做用域中查找所访问的符号,而忽略基类的做用域。
4.模板形式的模板参数
5.零初始化
T t = T (); - string t = string (); //缺省构造
- int t = int (); // 整数0
- double t = double (); // 浮点0
6.类模板中能够定义模板成员函数,类模板中也能够定义虚函数,可是类模板中的虚函数不能同时又是模板成员函数。
解释:类模板的模板型成员函数不能是虚函数,即不能为类模板定义虚成员函数模板。虚函数调用机制的广泛实现须要依赖于一个名为“虚函数表”的函数指针数组。该数组在类模板被实例化的过程当中生成,而此时成员函数模板还没有实例化,其入口地址和重载版本的个数,要等到编译器处理完对该函数的全部调用之后才能肯定。成员函数模板的延迟编译阻碍了虚函数表的静态构建。
7.包含模型
A类 - a.h - 声明A类
- a.cpp - 实现A类
- main.cpp - 使用A类
1)优势:把模板的声明、实现和使用经过文件包含的方式约束到同一个编译单元中,使编译器看到模板被实例化的同时也能找到模板的定义,避免连接错误。
2)缺点:模板的实现源代码没法对用户保密。延长编译时间。
max.cpp // 3
|
max.h
/ | \
a.cpp b.cpp c.cpp
经过预编译头能够在必定程度上提高编译速度。
容器、迭代器和泛型算法
1、容器
借助模板语法实现通用的数据结构。
2、泛型算法
以通常化的方式访问或处理容器中的数据。
3、迭代器
以一种一致且透明的方式访问任意容器中的数据。
以双向线性链表容器为例。
STL - Standard Templates Library,标准模板库
三大组件:容器、迭代器和泛型算法。
1、十大容器
1.线性容器
1)向量(vector)
2)双端队列(deque)
3)列表(list)
2.适配器容器
1)堆栈(stack)
2)队列(queue)
3)优先队列(priority_queue)
3.关联容器
1)映射(map)
2)多重映射(multimap)
3)集合(set)
4)多重集合(multiset)
1、向量
1.基本特性
1)连续存储与下标访问
2)动态内存分配
3)经过预分配内存空间下降动态内存管理的开销
4)总体性复制,支持深拷贝
int a[5] = {10, 20, 30, 40, 50};
int b[5];
// b = a; // 错误
for (int i = 0; i < 5; ++i)
b[i] = a[i];
memcpy (b, a, 5 * sizeof (int));
5)支持在任意位置的插入和删除,可是效率并不平均,插入删除的位置越靠近容器的尾端,容器中的元素个数越少,操做效率越高,反之越低。
2.实例化
#include <vector>
using namespace std;
1)建立空向量
vector<元素类型> 向量对象;
vector<int> vi;
2)指定初始大小
vector<元素类型> 向量对象 (元素个数);
vector<int> vi (5);
基本类型:用0初始化全部的元素。
类类型:用缺省构造函数初始化全部的元素。
3)指定初始大小同时指定初值
vector<元素类型> 向量对象 (元素个数,
元素初值);
vector<int> vi (5, 13);
vector<Student> vs (5,
Student ("张飞", 22));
4)经过一个已有的容器初始化
vector<元素类型> 向量对象 (起始迭代器,
终止迭代器);
int a[5] = {1, 2, 3, 4, 5};
vector<int> vi (a, a + 5);
vector<int> vi (&a[0], &a[5]);
vector<int> vi (&a[0], &a[4]); // 错误
3.元素访问
1)下标运算符:向量对象[基0索引];
2)迭代器
iterator - 正向迭代器
const_iterator - 常正向迭代器
reverse_iterator - 反向迭代器
const_reverse_iterator - 常反向迭代器
随机迭代器,连续内存容器的固有特征
能够整数作加减法运算
迭代器之间能够作"<"或">"比较运算
迭代器之间能够作减法运算
注意,任何致使容器结构发生变化操做均可能引发内存布局的改变,这时先前获取到的迭代器可能失效,须要从新获取一次再继续使用。
4.其它成员函数
push_back()
pop_back()
back()
front()
erase()
insert()
begin()/end()
rbegin()/rend()
5.泛型算法
#include <algorithm>
iterator find (iterator begin,
iterator end, value_type const& key);
查找成功返回第一个匹配元素的迭代器,失败返回end。
void sort (iterator begin, iterator end);
void sort (iterator begin, iterator end,
less cmp);
将[begin, end)范围中元素按升序排列。
6.类类型向量
元素类型须要支持深拷贝。
可能还须要支持缺省构造。
可能还须要支持小于运算或者小于比较器。
7.大小和容量
大小:实际容纳元素的个数。
容量:最多容纳元素的个数。
size() - 大小
capacity() - 容量
class Student {
char m_name[1024];
};
class Student {
Student (char* const name) :
m_name (new char[1024]) {
strcpy (m_name, name);
}
~Student {
delete[] m_name;
}
char* m_name;
};
class Student {
string m_name;
};
2、双端队列
物理结构和向量同样,也是用连续的内存空间保存数据元素,惟一区别就是前端开放,体如今多了两个成员函数:push_front/pop_front
其它方面与向量彻底一致。
3、列表
1.优势:空间利用率高,随机插入删除效率高
2.缺点:随机访问效率低下
3.经常使用成员函数
front/push_front/pop_front
back/push_back/pop_back
insert/erase
size/clear/empty
begin/end/rbegin/rend
remove
将连续重复的元素惟一化
void unique (void);
排序
void sort (void);
拆分
void splice (iterator pos, list& ls);
将ls中的所有元素剪切到调用列表的pos以前
void splice (iterator pos, list& ls,iterator del);
将ls中的del元素剪切到调用列表的pos以前
void splice (iterator pos, list& ls,iterator begin, iterator end);
将ls中从begin到end之间的元素剪切到调用列表的pos以前
合并
void merge (list& ls);
将有序的ls合并到有序的调用列表中,保证合并后的结果依然有序。
4、堆栈
1.实例化
#include <stack>
stack<元素类型[, 底层容器类型]> 堆栈对象;
2.底层容器:vector/deque(缺省)/list
3.成员函数
push -> push_back
pop -> pop_back
top -> back
size -> size
empty -> empty
5、队列
1.实例化
#include <queue>
queue<元素类型[,底层容器类型]> 队列对象;
2.底层容器:deque(缺省)/list
3.成员函数
push -> push_back
pop -> pop_front
back -> back
front -> front
size -> size
empty -> empty
6、优先队列
1.实例化
#include <queue>
priority_queue<
元素类型[,底层容器类型[,比较器类型]]>
优先队列对象;
2.底层容器:vector/deque(缺省)
3.成员函数
push
pop - 弹出优先级最高的元素
top - 获取优先级最高的元素
size
empty
4.优先级
1)缺省状况,经过元素类型“<”运算符比较大小,以大者为优。
2)经过比较器人为规定元素的优先级
7、映射
1.经过平衡有序二叉树存放由键和值组成的一一对应序列。
2.键必须是惟一的。
3.经过pair容器封装键和值。
template<typename F, typename S>
class pair {
public:
pair (F const& f, S const& s) :
first (f), second (s) {}
F first;
S second;
};
用pair::first表示键,用pair::second表示值。:q