C++
绪论
程序设计方法
面向过程的程序设计方法
按照步骤
泛型程序设计方法
面向对象的程序设计方法
程序开发过程
算法设计
源代码编辑
编译
汇编程序
编译程序
直接翻译成机器语言C++
解释程序
半编译半解释java
编译成java虚拟机的二进制代码.class
解释.class二进制文件变为机器码(二进制指令)
纯解释
语法检查
连接
可执行程序:连接目标程序以及库中的某些文件,生成的一个可执行文件如exe
测试
调试
计算机基本功能
算术运算
逻辑运算
信息在计算机中的表示与存储
控制信息
二进制指令
数据信息
数值信息
定点数
浮点数
非数值信息
字符数据
逻辑数据
信息的存储单位
位(bit,b)
数据最小单位,表示一位二进制信息
字节(byte,B)
八位二进制数字组成1 byte = 8 bit
千字节 1 KB = 1024 B
计算机的数字系统
二进制系统
八进制与十六进制
十进制整数→R进制整数
除以R取余
十进制小数→R进制小数
乘以R取整
编码规则
负数表示(-)
原码
补码
模数
补数
反码
数据溢出
小数表示(.)
定点方式
浮点方式
字符表示
计算机软件
应用软件
系统软件(操作系统)
中间件(应用和系统间的衔接)
软件=程序+文档
计算机程序
指令的序列
描述解决问题的方法和数据
数据结构
队列
先入先出(FIFO)
普通队列
缺点:效率低
环形队列
栈
后进先出(LIFO)
线性表
线性表是n个数据元素的有限序列
树
图
任意两个结点都是连通的
邻接矩阵【数组存储】
邻接表【链式存储】
十字链表【链式存储】
邻接多重表【链式存储(无向图)】
图的遍历
深度优先搜索
广度优先搜索
最小生成树
普里姆算法
克鲁斯卡尔算法
C++简单程序设计
(数据处理)
C++构词法
关键字(预定义的单词)
标识符(程序员声明的单词)
开头用下划线或字母
文字
分隔符
运算符(操作符)
空白符
基本数据类型
整数类型
按符号分(整数变量)
符号的(默认signed一般不写)
非负整数(unsigned)
unsigned若省略后一个关键字,大多数编译器都会认为是unsigned int
按数据范围分(整数变量)
短整数(short int)
整数类型(int)
长整数(long int)
长长整数(long long int)
整数常量
十进制
八进制
十六进制
整数常量默认为int类型
后缀
后缀L(或l)表示类型至少是long
后缀LL(或ll)表示类型是long long
后缀U(或u)表示unsigned类型
实数类型(浮点)
按数据精度分(浮点变量)
单精度(float)
双精度(double)
扩展精度(long double)
浮点数常量
小数形式
例:12.5,-12.5
指数形式(科学计数法)
例:0.3E+2,-3.4E-3
浮点常量默认为double类型
后缀F(或f)代表float类型
如12.3f
字符类型(char单字符)
unsigned char
char a="1";
语法错误!
字符串类型
字符串常量
"string"
C风格字符串常量
比单字符类型末尾多了'\0'
字符串变量(C++基本数据类型没有字符串变量,但在std库内提供String类)
一般采用字符数组(C风格)来存储字符串。
例子
char A[] = "abc"
相当于
char A[] = {'a', 'b', 'c', '\0'}
char[] A 错误!
改变字符常量或字符串常量类型
布尔类型(bool)
true
false
bool类型做为整型用时为true=1,false=0,其实n≠0都是true
数据
常量(存放到变量中)
直接使用符号(文字)表示的值
文字常量
例:12,3.5,'A'
符号常量
符号常量定义语句的形式:
const 数据类型说明符 常量名 = 常量值
或
数据类型说明符 const 常量名 = 常量值
例:const float PI = 3.1415926
符号常量在定义时一定要初始化(不初始化会报错),在程序运行过程中不能改变其值
变量(接收数据)
定义变量
类型
变量名(指向一组内存单元的标识符)
变量初始化
tips:
1.定义全局变量不初始化也有初始默认值,不初始化编译也通过
2.定义局部变量不初始化会是垃圾值(定义和初始化过程分开情况下可调试看出),
当局部变量未初始化但使用了(赋值给别的变量或者输出等操作)编译无法通过
初始化方式
复制初始化:
int a = 0
直接初始化:
int a(0)
int a = {0}
使用大括号的方式称为列表初始化
注:列表初始化形式不允许信息丢失,如double值初始化int变量就会造成数据丢失
int a{0}
基本运算
运算符
单目是只需要一个操作数的意思 比如 a++ a-- *a &a 双目是需要两个操作数的意思 比如 a+b a-b a*b a/b a%b 三目是需要三个操作数的意思 比如 a=c>b?c:b;
算术运算符
+(加)
-(减)
*(乘)
/(除)
注:整数相除,结果取整,如1/5 = 0,应该写为1/5.0 = 0.2
%(取余,操作数为整数)
++(自增)
++i(前置)
i++(后置)
--(自减)
--i(前置)
i--(后置)
=(赋值运算符)
复合赋值运算符
+=
-=
*=
/=
%=
<<=
>>=
&=
^=
|=
,(逗号运算符)
表达式1,表达式2
说明:先求解表达式1,再求解表达式2,最终结果为表达式2的值
例:a = 3*5, a*4
结果:60
关系运算符(结果类型为bool)
优先级相同(高)
<
<=
>
>=
优先级相同(低)
==
判断两个浮点数是否相等:
abs(a-b)<1e-10
!=
逻辑运算符(结果类型为bool)
!(非)
&&(与)
短路特性
||(或)
短路特性
条件表达式
表达式1?表达式2:表达式3
注:其中表达式1必须是bool类型
先求解表达式1,结果为true,则求解表达式2,表达式2的结果作为最终结果
结果为false,则求解表达式3,表达式3的结果作为最终结果
sizeof运算符
语法形式
sizeof(类型名)
或
sizeof 表达式
例:sizeof(short)
sizeof x
位运算符
按位与(&)
按位或(|)
按位异或(^)
取反(~)
移位
左移(<<)
右移(>>)
类型转换
隐含转换
含义:对于二元运算符中两个操作数类型不一致时,
低类型数据自动转换为高类型数据
显示转换(强制转换)
语法形式
类型说明符(表达式)
(类型说明符)表达式
类型转换操作符<类型说明符>(表达式)
const_cast
动态转换(运行时)
dynamic_cast
参数为引用或指针
reinterpret_cast
静态转换(编译时)
static_cast
例:
int(z)
等同于:
(int)z
等同于:
static_cast<int>
数据输入/输出
cout
插入运算符<<
cin
提取运算符>>
流程控制
选择语句(判断)
if...else...
开关语句
switch case分支
case后的值必须是跟int兼容的类型如枚举类型
循环语句
while
do while
for
while和do while循环都可以用for代替
范围for语句:
for(声明:表达式)
语句
组成
循环体
循环控制条件
循环终止条件
循环计数器
其他控制语句
break
continue
goto
类型别名
typedef 已有类型名 新类型名表
typedef double Area, Volume;
using 新类型名 = 已有类型名
例:using Area = double
枚举类型
不限定作用域的枚举类型
语法形式:
enum 枚举类型名{变量值列表}
enum Weekday{SUN, MON, TUE, WED, THU, FRI, SAT}
说明:枚举元素是常量,不能对其赋值,枚举元素具有默认值,依次为0,1,2,...
因此,默认情况下SUN=0, MON=1,...
也可以在声明时另行指定其他值
如:enum Weekday{SUN=7, MON=1, TUE, WED, THU,FRI, SAT}//后续自动2,3,4...
tips:
1.整数不能直接赋值给枚举变量
2.如果需要将整数赋值给枚举变量,应进行强制类型转换
3.因为枚举是整数的子集,所以枚举值可以赋值给整型变量
限定作用域的枚举类型
其他
auto类型
编译器通过初始值自动推断变量的类型
例:auto val = val1 + val2;
>如果val1+val2是int型,则val是int型
>如果val1+val2是double型,则val是double型
decltype类型
定义一个变量与某一表达式的类型相同,
但并不用该表达式初始化变量
(用初始值但不用初始值的类型)
例:decltype(i) j = 2
表示j以2作为初始值,类型不是int而是和i一致。
::作用域限定符
四舍五入
floor(amount + 0.5);
四舍五入并保留小数点后两位
floor(amount * 100 + 0.5)/100;
函数
(程序的功能模块)
函数定义
返回值类型标识符 函数名(形参表)
{
语句序列
//return
}
函数调用形式
函数原型声明
返回值类型标识符 被调用函数名(形参列表)
函数必须先声明后使用
函数调用形式:
函数名(实参列表)
函数名(实参列表)
↓初始化
形参列表
嵌套调用
函数的参数传递
(形实结合)
含义:函数被调用时,实参传递给形参
值传递(参数单向传递)—默认传参方式
引用传递(参数双向传递)
常引用作参数以保障实参数据安全使用(const只读)
引用类型
引用(&)是变量(标识符)的别名
int i, j;
int &ri = i;//定义int引用ri,并初始化为变量i的引用
j = 10;
ri = j;//相当于i = j
只有别名没有原名是不行的!
例如:
int &a = NULL;//错误!
int &a = 10;//错误!
int &a;//错误,引用不能单独存在!
tips:
1.引用定义时就必须初始化,即指向一个已存在对象。
2.一旦一个引用被初始化后,就不能改为指向其它对象。
3.C++中引用类型(变量别名)一般作为形参,
而函数被调用时,首先进行参数传递,
传入的对象实参正好用于引用类型的形参初始化。
4.指针传的是实参的存储地址,而引用只是实参的别名!
可变长度形参表(运行时)
参数类型相同时
initializer_list(std类型)
参数类型不同时
编写可变参数模板
相关讨论
参数传递过程相当于先发生了
形参 形参名(实参);
等同于:
形参 形参名 = 实参;
再执行函数体!
int a(0); 等同于 int a = 0;
Point a(b); 等同于 Point a = b;
Point &a(b); 等同于 Point &a = b;
Point *a(&b); 等同于 Point *a = &b;
值传递:
函数定义:Line::fun1(Point a){//函数体}
函数调用:fun1(b);
参数传递时,先发生了Point a = b;(会发生拷贝构造),再执行函数
形参a是新创建的一个Point对象,是局部变量,在函数执行结束前(return前或“}”前)会被析构掉,
而实参b则在主函数执行结束前(return前或“}”前)才被析构!
值传递的底层是拷贝构造,而拷贝构造又是通过引用传
递实现的(不管是自定义的拷贝构造还是默认的拷贝构
造)!就算传递的是int类型,也是进行拷贝构造,只不过效果一样
引用传递:
函数定义:Line::fun1(Point &a){//函数体}
函数调用:fun1(b);
参数传递时,先发生了Point &a = b;,再执行函数
并未发生对象初始化过程,即没有构造新对象,因此形参a不是一个新对象(Point),
而仅仅是一个在此函数体内使用的一个对象b的别名!
因此函数结束后只是这个别名不能用了而已,它所对应的对象即b在主函数中还存在着!
指针传递:
函数定义:Line::fun1(Point *a){//函数体}
函数调用:fun1(&b);
参数传递时,先发生了Point *a = &b;,再执行函数
创建了一个用于存放对象b地址的指针变量a(不是Point对象),因此函数结束后该变量a会被删除
内联函数(inline)
why:简单功能函数可能多次重用,但又希望避免因子函数调用和返回开销
两种形式
使用inline关键字显示声明
在类的声明中直接写函数体从而隐式声明
inline只是对编译器的建议,直接调用函数体,缩减开销,编译器可采纳也可不采纳
(一般对于功能简单的函数,编译器会自动把它变为内联函数)
注意:
1.内联函数体内不能有循环语句和switch语句
2.内联函数的定义必须出现在内联函数第一次被调用前
3.对内联函数不能进行异常接口声明
constexpr函数(C++11)
常量表达式(编译时可确定返回值)
举例说明:
constexpr int get_size(){return 20;}
constexpr int foo = get_size();
//正确:foo是一个常量表达式
带默认参数值的函数
函数调用时需要使用默认值的实参列表位置不写(即参数列表参数个数变少了)
参考代码:3_15.cpp
函数重载(静态多态)
早绑定(编译阶段)
形参匹配
函数名必须相同
形参个数可不同
形参类型可不同
函数返回值可同可不同(不能仅返回值不同!)
注意:
1.参数名不同(类型相同)作为区分标识。
2.不能仅以返回值不同作为区分标识。
3.不要将不同功能函数声明为重载函数,虽然编译没问题但会造成误解
C++系统函数
数学函数库
函数声明在头文件cmath中,#include
...
VS调试工具
step into(单步调试 F11)
run to cursor(运行到光标处 Ctrl+F10)
按位置分
成员函数
非成员函数(外部函数)
全局函数
类与对象
类与对象关系
类:对同一类对象的共有属性和行为进行概括(抽象)。
数据抽象:描述某类对象的属性或状态
代码抽象:描述某类对象的共有行为特征或具有的功能
继承:在已有类的基础上,进行扩展形成新类
多态:同一名称,不同功能实现方式。
目的:达到行为标识统一,减少程序中标识符的个数。
对象:现实中对象的模拟,具有属性和行为,是类的实例。
定义类的对象,才可以通过对象使用类中定义的功能。
使用一个对象,在程序里就是使用一个标识符(对象名)
>定义对象时,通过构造函数初始化
>删除对象时,通过析构函数释放资源
类的定义
规范化的类定义结构:
类的原型声明(.h头文件)
+ 类成员实现(.cpp源文件)
说明:
1.为了方便使用,一般在类体中声明函数原型。
2.在类外实现类成员(成员函数+成员变量)需要使用作用域限定符(也叫作用域分辨符)::
3.也可以在类中直接给出函数体(前提:充分简单),形成内联成员函数。
4.允许声明重载函数和带默认参数值的函数。
构造函数(对象初始化)
作用:在一个对象被创建时使用特定的值构造对象,
将对象初始化为一个特定的初始状态。
构造函数形式与特点:
1.函数名与类名相同
2.不能定义返回值类型(void也不行),不能有return语句
3.可以有形参,也可以没有
4.可以是内联函数
5.可以重载
6.可以带默认参数值
7.不需要显示调用,在创建一个对象时会自动调用
无参构造定义
类中原型声明:Clock();
类外实现:Clock:: Clock(): hour(0), minute(0), second(0){}
初始化列表(使用默认值)
: hour(0), minute(0), second(0)
初始化列表中可以给成员变量赋值(括号直接赋值方式),可以给自定义成员变量初始化(调用该类的构造函数进行初始化),还可以调用父类的构造函数。
调用父类的构造函数和给类成员对象初始化写法很像,但前者括号外是写类名,后者括号外则是写成员对象名!
初始化列表在类的实现中书写,在声明时不用写出!
注意:当需要初始化const修饰的变量时,必须使用初始化列表,而不能在构造函数函数体内初始化!
初始化列表和构造函数初始化的区别
所以如果非要在构造函数体内使用赋值方式初始化成员对象,那么该对象类一定需要有默认构造函数!
也就说成员对象的初始化工作必须全部在构造函数执行体前完成!构造函数体内的所有操作也就只是赋值而已,并不是初始化工作!
对象创建:Clock c2;
注意:此处和java不同,此处不需要new,对象已构造完成可直接使用该对象
隐含生成构造函数
(当类定义时没有显式的构造函数,编译器自动生成默认构造函数,函数体为空)
默认构造函数特点:
1.参数列表为空,不为数据成员设置初始值(初始化数据成员)
2.如果类内定义了成员的初始值,则使用类内定义的初始值
3.如果没有定义类内初始值,则以默认方式初始化
4.基本类型的数据默认初始化的值是不确定的(垃圾值)
5.对象类型的数据成员默认初始化方式由他们自己的构造函数确定
6.如果类中定义了构造函数(无论有参还是无参),编译器则不再隐含生成默认构造函数,除非使用"=default"。
如:类定义时写 Clock() = default;//指示编译器提供默认构造函数
有参构造定义
类中原型声明:Clock(int newH, int newM, int newS);
类外实现:Clock::Clock(int newH, int newM, int newS): hour(newH), minute(newM), second(newS){}
初始化列表:
:hour(newH),minute(newM),second(newS)
用括号内的对象初始化括号外的类成员对象
注意:初始化顺序按照类定义中的数据成员顺序,
而不是按照此处初始化列表列出的顺序。
这里其实是使用了拷贝构造
对象创建
Clock c1(8,10,0); //栈中分配
对于无参构造函数,创建对象时不能写成Clock c1();形式!
因为这可能是在声明函数!所以这种情况下只能写成Clock c1;
Clock c2 = Clock(8,10,0); //栈中分配
Clock* c3 = new Clock(8,10,0); //堆中分配
全默认构造定义(全默认值)
类中原型声明:Clock(int hour = 0, int minute = 0, int second = 0);
类的声明中写默认值,在类的实现里可不写
注意:
1.当类的定义里同时存在默认构造定义和无参构造时,
使用Clock c1();实例化对象时无法确定是有参还是无参!
2.全默认构造函数也叫默认构造函数!
委托构造函数(C++11)
委托一个构造函数实现该构造函数的初始化
回顾Clock类中两个构造函数:
1.Clock::Clock(int newH, int newM, int newS): hour(newH), minute(newM), second(newS){}
2.Clock:: Clock(): hour(0), minute(0), second(0){}
使用委托构造函数2.可以变为:
Clock::Clock():Clock(0,0,0){}
拷贝构造函数
(也叫复制构造)
说明:特殊的构造函数,其形参为本类的对象引用。
作用:用一个已存在对象去初始化同类型的新对象。
注意拷贝构造函数的形参是本类的对象引用而不是本类的对象!所以以下写法编译器会报错:
构造函数原型声明:
Point(Point p);
构造函数实现:
Point::Point(Point p) {
x = p.x;
y = p.y;
}
注意:和普通构造函数不同,拷贝构造函数不能重载!
调用拷贝构造的过程其实就是把值传递的过程描述了一次!如果不显式地定义一个拷贝构造函数即代表着进行了值传递
使用场景
情况一,用b初始化a时发生拷贝构造。
Teacher t1;
Teacher t2 = t1;//发生拷贝构造
Teacher t3(t1);//发生拷贝构造
情况二,a是函数fun1的形参,对象b作为fun1的实参,形实结合时发生拷贝构造(多个参数发生形实结合时,从后往前依次传递)。
情况三,函数fun2的返回值是类对象(局部对象),fun2返回时(运行到return)发生拷贝构造(构造一个临时无名对象)。
如:Point b = fun2();
注意:在主函数接收对象通过赋值方式接收到返回的临时无名对象,此处只是把临时无名对象的参数一一赋值给接收对象,并不发生拷贝构造!
拷贝构造定义:
类名(const 类名 &对象名);//拷贝构造函数原型声明
类名::类(const 类名 &对象名)//拷贝构造函数的实现
{
函数体
}
//此处给参数加const修饰,是为了新构造的对象只
拷贝已有对象的属性进行初始化,而不能做修改改
变拷贝的源对象!
不写拷贝构造函数时,编译器会生成隐含的拷贝构造函数即浅拷贝
(只第一层拷贝,深入层不发生拷贝也就是用的现有对象的指针)
功能:用初始值对象的每个数据成员,初始化将要创建对象的对应数据成员
不希望对象被拷贝构造
C++98做法:将拷贝构造函数声明为private,并且不提供函数实现
C++11做法:用"=delete"指示编译器不生成默认拷贝构造函数。
如:Point(const Point &p) = delete;
析构函数(销毁对象)
作用:删除对象时(一个对象不再被需要),通过析构函数释放资源(善后)
原型声明:~Point();
没有参数,也没有返回类型
实现:Point::~Point(){
//函数体
}
隐含的默认析构函数:~Point(){}
组合类
构造函数设计
原则:不仅要负责对本类基本类型成员数据的初始化,还要对对象成员初始化。
声明形式:
类名::类名(对象成员所需的形参,本类成员形参):
对象1(参数),对象2(参数),...
{
//函数体其他语句
}
构造组合类对象的初始化次序
1.成员对象构造函数调用顺序:按对象成员的定义顺序,先声明者先构造
2.初始化列表中未出现的成员对象,调用默认构造函数(即无形参的)初始化
3.处理完初始化列表之后,再执行构造函数的函数体
Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {//函数体}
//此处先发生Point xp1, Point xp2的形实结合拷贝构造(从后往前,先xp2后xp1),再进行初始化列表的拷贝构造,最后进行函数体执行!
构造组合类对象的析构次序与初始化次序正好相反
UML简介
事物
关系
作用关系(关联)
包含关系(聚集)
共享聚集
组成聚集(组合)
继承关系(泛化)
图
结构体(struct)
类的缺省访问权限是private
结构体的缺省访问权限是public
用处:定义主要用于存储数据,而没有什么操作的类型,
默认数据成员是公有的,因此访问数据较为方便
结构体定义:
struct 结构体名称{
公有成员
protected:
保护型成员
private:
私有成员
};
联合体(union)
存储空间共用
联合体定义:
union 联合体名称{
公有成员
protected:
保护型成员
private:
私有成员
};
特点:
1.成员共用同一组内存单元,内存大小等于占用最多内存的成员
2.任何两个成员不会同时有效
枚举类(enum class)
也叫强类型枚举
语法形式:
enum class 枚举类型名: 底层类型{枚举值列表}
例:
enum class Type{General, Light, Medium, Heavy};
enum class Type: char{General, Light, Medium, Heavy};
enum class Category{General=1, Pistol, MachineGun, Cannon};
优势
可以指定底层类型
例:
enum class Type: char{General, Light, Medium, Heavy};
数据共享和保护
对象的生存期、作用域与可见性
作用域
函数原型作用域
形参标识符的作用域为函数原型声明的形参表( )内
局部作用域(块作用域)
函数形参和在函数体中声明的标识符的作用域为块{ }内
类作用域
类成员包括类体和成员函数体
静态成员:通过类名,或者该类的对象名、对象引用访问
非静态成员:通过类名,或者该类的对象名、对象引用、对象指针访问
文件作用域
全局变量
命名空间作用域
可见性
从标识符引用角度看
内层标识符屏蔽外层同名标识符,但不影响作用域
如:局部变量会覆盖全局变量
对象生存期
静态生存期
特点:程序结束才消亡即全局寿命,作用域范围内可重复使用即局部可见,不会重新初始化
两种情况
全局定义的(没有初始化的会有默认值)
局部作用域中用static修饰的(没有初始化的会有默认值)
注意:
void other(){
static int a = 2;//静态局部变量
a++;
}
当other函数重复被调用时a的值不会重新初始化为2,只在第一次运行a时初始化!
动态生存期
离开作用域立即消亡,下次再需要重新初始化
数据的共享与保护
共享
类的静态成员
静态数据成员——属于整个类的数据成员
该类的所有对象共享
必须在类外定义和初始化,用(::)来指明所属的类
static int count;//静态数据成员定义和初始化,用于记录对象个数
int Point::count = 0;//静态数据成员定义和初始化(不用再写static修饰符,同理静态成员函数也一样),使用类名限定,
如果不设置初始值则int默认是0,作为共享数据,
一般不要在构造函数内修改其值,要在数据成员定义时初始化!
调用Point::count
也可以通过对象调用静态成员
静态函数成员——用于处理静态数据成员的函数
非静态成员函数里可以调用静态成员(数据成员/成员函数)
只能访问静态数据(因为静态成员函数属于类的,所以不能在没有对象的前提下使用本应属于对象的数据成员)
静态成员函数不能用const修饰(常函数),因为加了const代表函数的隐形参数this指针加了const修饰,而static函数没有this指针参数!
友元(friend)
友元函数
授权外部类函数访问私有成员函数,注意声明的友元函数并不是类的成员函数
友元的声明不受访问限定符的影响,可以是私有的或者公开的或者是保护的都行!
但建议放在类的声明最前面方便看清楚,不写任何访问限定符
友元函数的实现不能在前面加类名,因为友元属于全局函数
友元函数的声明需要写friend关键字,但在实现时不需要加friend关键字
优点:效率高,可以直接访问私有或保护成员,而不是通过提供给外部的成员函数进行调用,省去了函数调用的时间和空间开销
缺点:私有成员内容可能会被修改,存在安全隐患
友元类
1.友元是C++提供的一种破坏数据封装和数据隐藏的机制。
2.若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。
3.单向关系:声明B类是A类的友元 ≠ A类是B类的友元
A类声明B类是自己的友元:
class A{
friend class B;
public:
void display(){
cout << x <
private:
int x;
};
B类使用A类作为自己的对象成员
class B{
public:
void set(int i);
void display();
private:
A a;
};
测试使用:
void B::set(int i){
a.x = i;
}
void B::display(){
a.display();
}
保护
私有成员
常类型(const)
常对象
const 类名 对象名;
如:A const a(3,4);//a是常对象,不能被更新
const A a(3,4);//一个意思
int const a = 3; int *p = &a;//错误:指针指向const修饰的变量时,应该是const int *p = &a;或const int const *p = &a;
因为小权限的不能赋值给大权限的!存在风险,编译器无法编译通过!
常成员
用const修饰的类成员:常数据成员和常函数成员
常函数成员
类型说明符 函数名(参数表)const;
声明和定义都需要同步加上const修饰符
常数据成员
使用const说明的数据成员
const int a; //非静态常数据成员
static const int b; //静态常数据成员
1.当在类体外实现初始化时,如:
const int A::b = 10;//静态常数据成员,公有且不能修改成员!
2.当在构造函数内初始化时,常数据成员只能在初始化列表内初始化,不能放在函数体内初始化,如:
A::A(int i):a(i){}
常引用
const 类型说明符 &引用名;
常见使用位置:实参
如:float dist(const Point &p1, const Point &p2){}
优点
既能获得引用带来的较高的执行效率,成本低
又能保证实参的安全性
常引用相当于把引用的双向性变为单向性(只读)!
常数组
类型说明符 const 数组名[大小]...
如:const char key[ ] = {'a', 'b', 'c', 'd'};
常指针
指向常量的指针
一个工程的多文件结构(源文件)
类声明文件(.h文件)
自己的#include "Point.h"
系统的#include
类实现文件(.cpp文件)
类的使用文件(main()所在的.cpp文件)
外部变量(文件作用域中定义的变量)
extern关键字声明使用
外部函数(在文件作用域中,所有类外声明的函数,即非成员函数)
调用之前进行引用性声明,即声明函数原型
命名空间
匿名命名空间中定义的变量和函数,
不会暴露给其他编译单元。
如:
namespace{//匿名命名空间
int n;
void f(){}
}
编译预处理命令
#include 包含指令
将一个源文件嵌入到当前源文件中该点处
#include <文件名>
按标准方式搜索,文件位于C++系统目录的include子目录下
#include "文件名"
首先在当前目录中搜索,若没有再按照标准方式搜索
#define 宏定义指令
为了避免重复包含头文件
#ifndef CLIENT_H_
#define CLIENT_H_
Client类定义内容
#endif
定义符号常量,很多情况下被const定义语句取代
定义带参数宏,已被内联函数取代
MFC宏
字符串
#define WSG(abc) #abc
WSG(DEF)
#define WSG(A,B) A##B
int WSG(abc,def),
int abcdef,
#undef 取消宏定义符号
条件编译指令
#if
#endif
#if 常量表达式
//当“常量表达式”非零时编译程序正文
#endif
#else
#if 常量表达式
//当“常量表达式”非零时编译程序正文1
#else
//当“常量表达式”为零时编译程序正文2
#endif
#elif
#if 常量表达式1
程序正文1
#elif 常量表达式2
程序正文2
#else
程序正文3
#endif
#ifdef
#ifdef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”经#define定义过,且未经undef删除,则编译程序段1;
否则编译程序段2
数组、指针与字符串
数组
(静态数组)
具有一定顺序关系的若干相同类型变量的集合体,组成数组的变量称为该数组的元素。
数组定义
类型说明符 数组名[常量表达式] [常量表达式]...
int [3] a;//错误!
int a[5][3];
数组使用
b[1][2]=a[2][3]/2
数组的存储与初始化
一维数组
一维数组的存储
数组元素在内存中顺序存放,地址连续元素间物理地址相邻,对应着逻辑次序上相邻
数组名字是数组首元素的内存地址
数组名是一个常量(存放数组地址),不能被赋值
一维数组初始化
全部元素初始化
int a[10]={0,1,2,3,4,5,6,7,8,9};
int a[]={0,1,2,3,4,5,6,7,8,9};
部分元素初始化
int a[10]={0,1,2,3,4};
二维数组
二维数组的存储
按行存储:
a00 a01 a02 a03 a10 a11 a12 a13 a20 a21 a22 a23
float a[3][4];
a[0]是第一行的首地址
a[1]是第二行的首地址
二维数组初始化
因为二维数组按行存储,所以可以将所有初值写在一个{}中,按顺序初始化
例:int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
分行列出二维数组元素的初值
例:int a[3][4]={{1,2,3,4},{5,6,7,8}{9,10,11,12}};
只对部分元素初始化
例:int a[3][4]={{1},{0,6},{0,0,11}};
列出全部初始值时,第1维下标个数可省略
例:int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
或:int a[][4]={{1,2,3,4},{5,6,7,8}{9,10,11,12}};
不初始化
局部作用域中的非静态数组会是垃圾数据
static数组中的数据默认初始化为0
例:static int a[3][4];
部分初始化中未初始化的部分数据会初始化为0
数组作为函数参数传递
数组元素做实参,与单个变量一样
数组名作实参,形、实参数都应是数组名,类型要一样。
传送的是数组首地址,对形参数组的改变会直接影响到实参数组。
对象数组
对象数组定义
类名 数组名[元素个数];
对象数组访问
数组名[下标].成员名
对象数组初始化
通过初始化列表赋值
例:Point a[2]={Point(1,2),Point(3,4)};
如果没有为数组元素指定显式初始值,数组元素便使用默认值初始化(调用默认构造函数)
基于范围的for循环
自动遍历容器内所有元素
指针
访问内存空间方式
通过变量名访问
变量名
变量别名(引用)
通过地址访问
指针
指针变量
讨论:C++代码书写规范
指针概念
定义
指针:内存地址,用于间接访问内存单元
指针变量:用于存放地址的变量
&取地址符——指针运算符
*寻址符(寻找该地址指向的对象)——地址运算符
指向常量的常量指针
既不可以修改p指向的地址,也不可以修改*p的值。
const int * const p;
等价于:
int const * const p;
const int const *p;//错误写法!
初始化
语法形式
存储类型 数据类型 *指针名 = 初始地址;
例:int *pa = &a;
注意:
1.初始地址必须是合法获取到的地址
2.用变量地址作为初值时,该变量必须在指针初始化之前已声明过,且变量类型应与指针类型一致。
3.可以用一个已有合法值的指针去初始化另一个指针变量。
4.不要用一个内部非静态变量去初始化static指针
赋值
语法形式
指针名 = 地址
注意:
1.地址中存放的数据类型与指针类型必须相符
2.向指针变量赋的值必须是地址常量或变量,不能是普通整数。
合法地址
通过地址运算“&”求得已定义的变量和对象的起始地址
动态内存分配成功时返回的地址
例外:整数0(NULL)可以赋给指针,表示空指针。
类型安全的空指针nullptr(C++11)
允许定义或声明指向void类型的指针。
该指针可以被赋予任何类型对象的地址。
void *general;
只能存放地址,但不能直接用其访问地址指向的对象(内存空间)
可以通过转换的方式使用
static_cast
注:不能声明void类型的变量
指向变量的指针(变量指针)
变量包括数组、函数、对象等,所以这是个统称,一般不这么叫
指向常量的指针(常指针)
const指针(只读指针)
常指针只能访问常函数
例
int a;
const int *p1 = &a;//p1是指向常量的指针
int b;
p1 = &b; //正确,p1本身的值可以改变
*p1 = 1; //编译出错,不能通过p1改变所指的对象
因为有const修饰,所以p1保存的地址值对应的其他对象无法被修改,但p1存什么对象的地址无所谓
指针类型的常量(指针常量)
Tips:
1.指针常量保存的地址值不能改变。
2.指针常量保存地址值对应的对象可以改变。
指向对象的指针(对象指针)
对象指针的定义:类名 *对象指针名;
例:Point a(5,10);
Point *ptr;
ptr=&a;
通过指针访问对象成员
对象指针名->成员名
例:ptr->getx()相当于(*ptr).getx()相当于a.getx()
this指针
含义:指向自身的指针
this->x//正确
this.x//错误!
只有对象才能使用 "."
指针必须使用 "->"
链式编程
Point & Point::setPoint()
{
return *this;//返回对象本身
}
a.setPoint().setPoint();
Point* Point::setPoint()
{
return this;//返回对象本身指针
}
a->setPoint()->setPoint();
指针类型的算术运算
指针与整数的加减运算
意义是指针当前指向位置的前方或后方第n个数据的起始位置
指针++,--(自增,自减)运算
意义是指向下一个或前一个完整数据的起始位置
运算结果值取决于指针指向的数据类型,总是指向一个完整数据的起始位置。
当指针指向连续存储的同类型数据时,指针与整数的加减运算和自增自减运算才有意义
指针类型的关系运算
Tips:
1.指向相同类型数据的指针之间可以进行各种关系运算。
2.指向不同数据类型的指针,以及指针与一般整数变量之间的关系运算是无意义的。
指针可以与0之间进行比较
例:p==0或p!=0
指针指向数组元素
int a[10], *pa;
pa=&a[0]; 或 pa=a;
单独一个a指的是数组首元素指针
a->num;等价于a[0].num;
a[i], *(pa+i), *(a+i), pa[i]都是等效的!
指针数组(数组元素是指针类型)
用途:常用于锯齿数组(数组每行的列数不等)
Point *pa[2];
↑
由pa[0]、pa[1]两个指针组成
指针作为函数参数
使用场景
需要数据双向传递(引用也可以达到此效果)
需要传递一组数据(数组),只传首地址运行效率较高
Tips:
1.在函数的声明或定义中,形参带'&'号,表示形参是该类型的引用类型;
所谓引用是一个变量的别名,这样对形参的修改会反映在实参上;
此时函数的实参位置是一个对象。
2.在函数的调用中,实参带'&'号,表示取地址运算,结果是一个指向操作变量的指针;
此时函数的形参位置是指针变量类型。
常指针做参数
不希望主调函数修改传递指针的变量信息
void print(const int *p){}
Tip:一般情况下如果只是为了读取变量,
传递指针使用常指针即可,不要开
放过多的权限。
指针类型的函数(指针函数)
返回值的类型是指针类型
指针函数的定义形式:
存储类型 数据类型 *函数名()
{//函数体语句
}
指向函数的指针(函数指针)
存储函数代码的起始地址
函数指针的定义:存储类型 数据类型 (*函数指针名)();
含义:函数指针指向的是程序代码存储区的首地址。
典型用途—实现函数回调
1.通过函数指针调用的函数
例如:将函数的指针作为参数传递给一个函数,
使得在处理相似事件的时候可以灵活使用不同的方法。
2.调用者不关心谁是被调用者
需知道存在一个具有特定原型和限制条件的被调用函数。
案例描述
定义函数指针
int(*func)(int,int)
类似于委托中间方法compute负责运行时调用指定方法
相关讨论
函数必须先声明后使用吗?
全局函数
类成员函数
声明类的时候在里面直接写一些方法的实现算什么,以后要使用时可以不声明直接使用吗?算内联函数?
引用&与取地址&(获取首地址)的区别:
1.引用在赋值=的左边,而取地址在赋值的右边。
2.和类型在一起的是引用,和变量在一起的是取址。
类似的:
1.和类型在一起的*代表指针变量
2.和变量在一起的*代表指针变量指向的变量
&:取址、引用
*:寻址、指针
动态分配与释放内存
(必须使用指针的情况)
动态申请内存操作符(new)
new 类型名T(初始化参数列表)
功能:在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值。
结果值:
成功:T类型的指针,指向新分配的内存;
失败:抛出异常。
申请内存可能不成功,所以一般动态分配内存后需要先判断一下再使用相应的指针变量!
if(NULL== p)
{
system("pause");
return 0;
}
new后面的对象名带与不带括号的区别
自定义类型:
CBase *base = new CDerived();
等价于
CBase *base = new CDeviced;
内置类型:
int *a = new int();
不同于
int *a = new int;
第一个动态申请的空间里面的值是随机值;
第二个进行了初始化,里面的值为0。
使用场景
动态数组
分配:new 类型名T[数组长度]
数组长度可以是任何整数类型表达式,或在运行时计算出的
释放:delete[] 数组名p
释放指针p所指向的数组。
p必须是用new分配得到的数组首地址。
如果没有[]代表只释放数组首地址指向的内存空间。
delete 数组名p:只代表释放数组首元素内存空间
动态创建数组案例【图】
Point *ptr = new Point[2];
delete[] ptr;
ptr = NULL;//保险起见
int *a = new int(10);//在堆上分配int类型值,并赋初始值10!
不同于:
int *a = new int[10];//在堆上分配int类型数组,长度为10!
Point *ptr = new Point[2];
Point类需要有默认构造函数,不然没法初始化数组空间
动态多维数组
动态创建多维数组案例【图】
int (*cp)[9][8] = new int[7][9][8];
*(*(*cp + i) + j) + k) = (i * 100 + j * 10 + k);
等同于:
cp[i][j][k] = (i * 100 + j * 10 + k);
delete[] cp;//三维数组释放也只需要一个[]
将动态数组封装成类
(管理动态数组)
优点:
1.更加简洁,便于管理。
2.可以在访问数组元素前检查下标是否越界。
动态数组类
相关讨论:C++左值、右值、右值引用?
左值
右值
纯右值
将亡值
左值引用
右值引用
释放内存操作符(delete)
相关讨论:
1.能用引用实现的都可以用指针实现,但指针实现
理解较复杂,且存在安全隐患!因此这种情况下
能用引用尽量不用指针!
2.而在此节三种情况必须使用指针实现,引用无法
实现!即指针功能强于引用
智能指针(C++11)
内存泄漏
1.所谓内存泄露往往发生在程序还运行的时候,当程序强行结束后操作系统会在该程序进程退出之后回收内存!但如果是内存泄漏的程序不结束,内存中泄漏的内存就一直无法被本程序或其他程序利用!
unique_ptr
不允许多个指针共享资源,可以用标准库中的move函数转移指针
解释:该指针存放的地址不能赋值到其他指针中去,但可以用move函数把地址转移给别的指针
用于取代auto_ptr 指针
shared_ptr
多个指针共享资源
解释:多个指针指向同一块内存单元
weak_ptr
可复制shared_ptr,但其构造或者释放对资源不产生影响
vector对象(一个安全的数组)
原理:将动态数组封装成模板类
C++标准库中的一个模板类
历史演变过程:
1.数组(指针实现)→
2.动态数组(数组+new)→
3.动态数组封装类(数组+new+类)→
4.动态数组封装模板类vector(数组+new+模板类)
优点:
1.封装任何类型的动态数组,自动创建和删除。
2.数组下标越界检查。
vector对象的定义
vector<元素类型> 数组对象名(数组长度);
例:
vector
建立大小为5的int数组
声明+初始化
std::vector
使用:
for(int i=0;i<vec.size();i++)
{
cout<<vec[i]<<endl;
}
链表(iterator 遍历)
list<int> list1;
list1.push_back(4);
list<int>::iterator itor = list1.begin();
for(;itor != list.end();itor++)
{
cout<<*itor<<endl;
}
链表(for 遍历)
list<int> list1;
list1.push_back(4);
for(int i=0;i<list1.size();i++)
{
cout<<list1[i]<<endl;
}
map<string,string>::iterator itor = m.begin();
for(;itor != list.end();itor++)
{
cout<<itor->first<<endl;//key
cout<<itor->second<<endl;//value
}
vector对象的使用
对数组元素的引用,与普通数组具有相同形式:
vector对象名[下标表达式] //vector类重写了'[ ]'下标运算符
vector数组对象名不表示数组首地址
用size函数,获得数组长度:
vector对象名.size()
基于范围的for循环配合auto举例
std::vector
for(auto i = v.begin();i != v.end();++i)
{
}
std::vector
for(auto e : v)
{
}
对象的复制与移动
深拷贝(深拷贝构造函数)
在复制构造函数内完整复制对象内容
做法:在复制构造函数的函数体或初始化列表里把所有对象内容都复制过来。
注意:隐含的复制构造函数(啥都不写)是只拷贝了基本类型成员和类成员。
使用场景:一个类中含有指针成员变量,且指针指向的空间是通过动态内存分配方式创建的(数组也是用指针实现的)
示例案例—深拷贝改进
相关讨论
如果一个类包含引用类型的成员变量,
默认的浅拷贝构造函数会对其进行复制吗?
移动构造(C++11)
源对象资源的控制权全部交给目标对象
使用场景:一个对象即将消亡,而其资源需要被再利用时(废物再利用!)
移动构造函数:
class_name(class_name &&)
使用深层拷贝构造函数达到移动构造效果
移动构造
疑问:程序在执行到return语句时是调用哪个构造函数?复制?移动?
字符串
字符串常量
例:"program"相当于一个隐含创建的字符常量数组,
各字符连续、顺序存放,每个字符占一个字节,以'\0'结尾。
出现在表达式中,表示这个char数组的首地址,
首地址可以赋值给char常量指针:const char *STRING1="program";
z注意:两个直接字符串常量不能相加
例如:string message = “Hello” + “, world”;//错误!
因为C++标准库中没有实现“+”号对于char[]类型的重载以支持char[] + char[]操作
char *p="123456";//字符指针
char s[] = "abc";//字符数组
继承与派生
派生与继承关系:是同一过程从不同角度看
继承目的:实现设计与代码的重用
派生目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,
需要对原有程序进行改造。
单继承时派生类的定义
语法
class 派生类名:继承方式 基类名
{
成员声明;
}
例
class Derived: public Base
{
public:
Derived();
~Derived();
}
多继承时派生类的定义
语法
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,...
{
成员声明;
}
注意:每一个“继承方式”,只用于限制对紧随其后的基类的继承。
例
class Derived: public Base1, private Base2
{
public:
Derived();
~Derived();
}
派生类的构成
吸收基类成员
包含除基类构造和析构外的所有成员
注:C++11规定可以使用using语句继承基类的构造函数。
改造基类成员
派生类声明一个和基类同名的新成员,隐藏基类成员
添加新的成员
新增新成员,进行功能拓展
继承方式
继承方式的影响:
1.派生类成员对基类成员的访问权限;
2.通过派生类对象对基类成员的访问权限。
三种继承方式
公有继承(public)
继承的访问控制
1.基类的public和protected成员在派生类中的访问权限不变
2.基类的private成员不可直接访问
继承方式必须要写,不写调用会有问题!
私有继承(private)
继承的访问控制
1.基类的public和protected成员在派生类中以private身份出现
2.基类的private成员不可直接访问
保护继承(protected)
继承的访问控制
1.基类的public和protected成员:都以protected身份出现在派生类中
2.基类的private成员:不可直接访问
protected成员的特点与作用
1.对建立其所在类对象的模块来说,它与private成员的性质相同。
2.对于其派生类来说,它与private成员的性质相同。
3.既实现了数据隐藏,又方便继承,实现了代码重用。
类型转换
1.派生类的对象可以隐含转换为基类对象;
2.派生类的对象可以初始化基类的引用;
3.派生类的指针可以隐含转换为基类的指针;
覆盖和隐藏
隐藏不代表没有了,还可以通过特殊的手段(soldier.Person::play();)访问
建议:不要重新定义继承而来的非虚函数
(子类重写父类同名非虚函数)
派生类的构造函数
继承基类的构造函数
using B::B;
菱形继承(包含多重继承和多继承)
不继承基类的构造函数
单继承时构造函数的定义语法:
派生类名::派生类名(基类所需形参, 本类成员所需形参):
基类名(参数表), 本类成员初始化列表
{
//其他初始化;
}
多继承时构造函数的定义语法:
派生类名::派生类名(参数表):
基类名1(基类1初始化参数表),
基类名2(基类2初始化参数表),
...
基类名n(基类n初始化参数表),
本类成员初始化列表
{
//其他初始化;
}
多继承且有对象成员时派生的构造函数定义语法:
派生类名::派生类名(形参表):
基类名1(参数),基类名(参数),...,基类名n(参数),
本类成员(含对象成员)初始化列表
{
//其他初始化
}
讨论:派生类不写构造函数会怎样?
基类有默认构造函数时
需执行基类中带参数的构造函数时
构造函数执行顺序
调用基类默认构造函数
对初始化列表中成员进行初始化
执行派生类的构造函数体中的内容
派生类的复制构造函数
派生类的析构函数
派生类存在同名成员
多继承指的是一个类继承多个父类,而不是指一个父类有多个子类
虚基类(用于解决二义性和冗余性)
需解决的问题:
当派生类从多个基类派生,而这些基类又有共同基类,则在访问此共同基类中的成员时,
会产生冗余,并有可能因冗余带来不一致性。
虚基类声明:
class B1:vitual public B
作用:为最远的派生类提供唯一的基类成员,而不重复产生多次复制。
构造函数传参
虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的
多态性
实现机制:绑定
编译时(早绑定)
静态多态性
函数重载
运算符重载
不能重载的运算符:“.”、“.*”、“::”、“?:”
重载为类成员的运算符函数定义形式:
函数类型 operator 运算符(形参)
{
...
}
参数个数=原操作数个数-1(后置++、--除外)
索引运算符重载vecter[index]
索引运算符必须是成员函数重载形式,不能是友元函数重载形式!
【声明】
int operator [](int index);
【定义】
int Coordinate::operator [](int index)
{
if(0 == index)
{
return m_iX;
}
if(1 == index)
{
return m_iY;
}
}
【使用】
coor[0];
单目运算符重载
前置单目运算符重载规则
如++time相当于time.operator ++()
前置++重载实现
// 前置--运算符重载
Coordinate& operator--()
{
--m_iX;
--m_iY;
return *this;
}
运算符重载为类外非成员函数
(左操作数是基本类型)
使用场景:
基本数据类型+自定义类型
或别人定义的类
运算符重载为非成员函数规则
双目运算符“+”重载
A + B等同于operator + (A, B)
// +号运算符重载
Coordinate operator+(const Coordinate &coor)
{
Coordinate temp(0,0);
temp.m_iX= coor.m_iX + m_iX;
temp.m_iY= coor.m_iY + m_iY;
return temp;
}
“<<”双目运算符重载为非成员函数,并声明为类的友元
左操作数是std::ostream引用,右操作数是类A的常引用
cout << a1 << a2;
等同于:
operator <<(operator << (cout, a1), a2);
输出运算符不能是成员函数重载形式,必须用友元函数重载!
【声明】
#include <ostream>
using namespace std;
friend ostream &operator<<(ostream &output, Coordinate &coor);
【定义】
#include <ostream>
using namespace std;
ostream &operator<<(ostream &output, Coordinate &coor)
{
output<<coor.m_iX<<","<<coor.m_iY;
return output;
}
【使用】
cout<<coor<<endl;
前置单目运算符“++”重载
++A等同于operator + (A)
后置单目运算符“++”重载
A++等同于operator + (A, 0)
运行时(晚绑定)
虚函数(virtual)
函数签名一致:函数名 参数列表 const
class Base1{
public:
virtual void display() const;//虚函数
}
void fun(Base1 *ptr){
ptr -> display();
}
虚析构函数
存在意义:为了避免因使用父类指针释放子类对象时造成的内存泄露问题
如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。
所有类中的析构函数最好都加上virtual关键字!
注意:
1.虚函数的调用需要动态绑定,因此父类的虚函数不能写成内联函数(内联函数是在编译过程直接调用的)
2.虚函数属于对象成员,因此不能定义为static静态成员
3.构造函数不能是虚函数,析构函数可以是虚函数
4.虚函数声明只能出现在类定义的函数原型声明中,不能出现在成员函数实现时
5.如果子类声明了一个未写vitual的同名函数,且函数原型声明与父类一致,则编译器也认为此函数为虚函数
6.派生类中的虚函数还会隐藏基类中同名函数的所有其他重载形式。
7.一般习惯在派生类函数中也使用virtual关键字,增加程序的可读性
虚表与动态绑定
虚表
抽象类
带有纯虚函数(一个以上)的类就是抽象类
不能创建一个抽象类的对象(无法实例化对象)
接口类
对仅含有纯虚函数(成员函数)的类叫做接口类(无数据成员)
只有.h声明文件
RTTI(运行时类型识别)
运行时类型识别
#include <typeinfo>
typeid(*obj) == typeid(Bird)
Bird *bird = dynamic_cast<Bird*>(obj);
动态转换
纯虚函数
1.vitual关键字不能修饰普通函数;
2.vitual关键字不能修饰静态成员函数;
3.vitual关键字不能修饰内联函数;
4.vitual关键字不能修饰构造函数。
override
(派生类)
C++11
显式覆盖override
使用:在派生类声明一个带有override的虚函数,编译器
就会去检查基类是否存在一个函数签名一致的虚函
数,若不存在则报错!
final
模板与群体数据
模板
函数模板
why:仅因为参数类型不同,而设置多个几乎一样的重载函数可能会给程序带来冗余和不一致问题
能够节省工作量
函数模板定义语法
语法形式:
template <模板参数表>
函数定义
单参数
template <typename T>
void display(T t)
{
cout<< t <<endl;
}
display<int>(10);
多种类型参数
/**
* 定义模板函数swapNum
* 实现功能:交换两个数的位置
*/
template <typename T, class C>
void swapNum(T &a, C &b)
{
T temp = a;
a = b;
b = temp;
}
/**
* 定义模板函数swapNum
* 实现功能:交换两个数的位置
*/
template <typename T>
void swapNum(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
int x = 10;
int y = 20;
// 调用模板函数
swapNum<int,int>(x,y);
int x = 10;
int y = 20;
// 调用模板函数
swapNum<int>(x,y);
类型参数+变量
template <typename T, int kSize>
void display(T t)
{
for(int i=0;i<kSize;i++)
{
cout<< t <<endl;
}
}
display<int,5>(6);
模板参数表内容
类型参数:class(或typename)标识符
常量参数:类型说明符 标识符
模板参数:template <参数表> class 标识符
注意:
一个函数模板并非自动可以处理所有类型的数据
只有能够进行函数模板中运算的类型,才可以作为类型实参
自定义的类,需要重载模板中的运算符,才能作为类型实参
类模板
作用:
类模板使用户可以为类声明一种模式,使得
类中的某些数据成员、某些成员函数的参数
、某些成员函数的返回值,能取任意类型
(包括基本类型和自定义类型的)
类模板声明:
template <模板参数表>
class 类名
{类成员声明}
实际使用过程中template
效果等同于template
单类型参数
声明:
定义:
每个成员函数的定义(如果不在类声明内)都得加上如上模板声明
使用:
类型参数+变量
声明:
定义:
使用:
群体数据
线性群体
元素次序与逻辑位置关系对应
按照访问元素方法不同分为
直接访问
数组类
静态数组(元素个数编译时确定,运行时无法修改)
动态数组
(个数可调)
vector
顺序访问
链表类
链表类模板
单链表结点类模板
结点类模板定义
//Node.h
#ifndef NODE_H
#define NODE_H
//类模板定义
template
class Node{
private:
Node
public:
T data;//数据域
Node(const T& item, Node
void insertAfter(Node
Node
Node
const Node
};
#endif
类函数实现
结点后插入一个结点
template <class T>
void Node<T>::insertAfter(Node<T> *p){
//p节点指针域指向当前节点的后继节点
p->next = next;
next = p;//当前节点的指针域指向p
}
删除结点后一个结点:
template <class T>
Node<T> *Node<T>::deleteAfter(void){
Node<T> *tempPtr = next;
if(next == 0)
return 0;
next = tempPtr -> next;
return tempPtr;
}
基本操作
生成链表
插入结点
查找结点
删除结点
遍历链表
清空链表
队列(一种存取操作受限制的线性结构)
存在的问题:前面出队了,后面就得往前移动,
不移就会造成假溢出,移了就有开销
解决方案——循环队列
(数组实现)
队列基本操作
队列的定义与实现
索引访问
非线性群体
排序
基本操作
比较两个数大小
调整元素在序列中的位置
内部排序
含义:待排序数据元素存放在计算机内存中进行排序
插入排序
选择排序
交换排序
冒泡排序
外部排序
含义:待排序数据元素数量很大,以致内存中一次无法容纳全部数据,
在排序过程中尚需对外存进行访问的排序过程。
查找
顺序查找
折半查找(二分法查找)
泛型程序设计与C++标准模板库
C++标准模板库(STL)
基本组件
容器(container)
含义:容纳包含一组元素的对象
基本容器类模板
顺序容器
数组(array)
向量(vector)
双端队列(deque)
单链表(forward_list)
列表(list)
(有序)关联容器
集合(set)
多重集合(multiset)
映射(map)
multimap(多重映射)
无序关联容器
无序集合(unordered_set)
无序多重集合(unordered_multiset)
无序映射(unordered_map)
无序多重映射(unorder_multimap)
容器适配器
栈(stack)
队列(queue)
优先队列(priority_queue)
迭代器(iterator)
含义:泛型指针
函数对象(function object)
算法(algorithms)
输入/输出类
容器类与抽象数据类型
存储管理类
算法
错误处理
运行环境支持
泛型程序设计
流类库与输入/输出
IO流类库
程序与外界环境信息交换
程序对象
文件对象
键盘
硬盘
显示器
流是一种抽象,负责在数据的生产者和消费者间建立联系,并管理数据的流动
流对象与文件操作
2.指定流对象和某个文件对象建立连接
输出流
(插入)
输出流类
ostream 标准输出流类
ofstream 文件输出流类
ostringstream 字符串输出流类
输出流对象
预定义的输出流对象
cout 标准输出
cerr 标准错误输出,没有缓冲,发送给它的内容立即被输出。
clog 类似于cerr,但是有缓冲,缓冲区满时才被输出。
构造输出流对象
支持磁盘文件输出
ofstream myFile("filename");//构造时,文件自动打开
可以在调用默认构造函数后,使用open函数打开文件
ofstream myFile;//声明一个静态文件输出流对象
myFile.open("filename");//打开文件,使流对象与文件建立联系
构造对象或用open打开文件时可以指定模式
ofstream myFile("filename", ios_base::out | ios_base::binary);
文件输出流成员函数
与操作符等价的成员函数
执行非格式化写操作的成员函数
其他修改流状态且不同于操纵符或插入运算符的成员函数
向文本文件输出
插入(<<)运算符
浮点值输出精度控制
精度
浮点数输出精度默认6位
如:3466.98
改变精度:setprecision操纵符(定义在iomanip头文件中)
向二进制文件输出(内存对象二进制输出)
向字符串输出
向字符串输出,将数值转换为字符串
输入流
(提取)
输入流类
istream类
适合顺序文本模式输入
ifstream类支持磁盘文件输入
istringstream
输入/输出流
iostream类
3.程序操作流对象
4.流对象通过文件系统对所连接的文件对象产生作用
异常处理
抛给调用者—抛出异常(throw)
...
throw 表达式;
...
打印异常
try
{
}
catch(Exception &e)
{
e.printException();
}
就地解决—捕获并处理异常(try...catch)
1.异常接口声明:void fun() throw(A, B, C, D);
2.无异常接口声明,表示函数可以抛出任何类型异常
3.一个函数肯定不抛掷任何异常的函数声明:void fun() throw();
异常对象自动析构
当找到一个匹配的catch异常处理后:
1.初始化异常参数
2.将从对应的try块开始到异常被抛掷处之间构造而尚未析构的所有对象进行析构
3.从最后一个catch处理之后开始恢复执行
相关讨论
C++函数返回对象和返回引用
函数不能返回在函数中创建的临时对象的引用
因为粗细导致的乌龙!
错误说明:类的声明中明明定义了m_iSize和m_pTree变量,但是在函数实现中就是一直显示未定义!
错误原因:在加作用域限定符时加错位置了!函数的返回值类型是int *被作用域限定符人为隔开了,导致此函数并不是类声明文件中那个函数!
接口和虚基类