C++笔记

/–1--******/
一、C++介绍
本贾尼.斯特劳斯特鲁斯,于1979年4月份贝尔实验室的本贾尼博士在分析
UNIX系统分布内核流量分析时,希望有一种有效的更加模块化的工具
1979年10月完成了预处理器Cpre,为C增加了类机制,也就是面向对象,1983年
完成了C++的第一个版本,C with classes也就是C++,
C++与C的不同点:
1、C++完全兼容C的所有内容
2、支持面向对象的编程思想
3、支持运算符、函数重载
4、支持泛型编程、模板
5、支持异常处理
6、类型检查严格

二、第一个C++程序
1、文件扩展名
.cpp .cc .C .cxx
2、编译器
g++ 大多数系统需要额外安装,ubuntu系统下的安装命令:
sudo apt-get update
sudo apt-get install g++
gcc也可以继续使用但需要增加参数 -xC++ -lstdc++
3、头文件
#include
#include <stdio.h> 可以继续使用
#include 也可以使用
4、输入/输出
cout << 输出数据
cin >> 输入数据
cout/cin会自动识别类型
scanf/printf 可以继续使用
注意:cout和cin是类对象,而scanf/printf是标准库函数
5、增加了名字空间
std::cout
using namespace std;
所有的标准类型、对象、函数都位于std命令空间中

三、名字空间
1、什么是名字空间
在项目中函数名、全局变量、结构、联合、枚举、类,非常有可能
名字冲突,而名字空间就对这些命名进行逻辑空间划分(不是物理单元划分)
为了解决命名冲突
2、什么是名字空间
在C++中经常使用多个独立开发的库来完成项目,由于库的作者或开发人员
根本没见过面,因此命名冲突在所难免,C++之父为防止名字冲突给C++设计一个
名字空间的机制
通过使用namespace XXX把库中的变量、函数、类型、结构等包含在名字空间中,
形成自己的作用域,避免名字冲突。
namespace xxx
{

	} // 没有分号

	注意:名字在空间也是一种标识符,在同一作用域下不能重名。
3、同名的名字空间有自动合并(为了声明和定义分开写)
	同名的名字空间中如果有重名的依然会命名冲突,
4、名字空间的使用方法
	::域限定符
	空间名::标识符 // 使用麻烦
	using namespace 空间名;	// 把空间中定义的标识符导入到当前代码中
		不建议这样使用,相当于把垃圾分类后又倒入同一个垃圾车,依然会冲突
5、无名名字空间
	不属于任何名字空间的标识符,隶属于无名名字空间,无名名字空间中的成员使用
::标识符 进行访问
	如果访问被屏蔽的全局变量
6、名字空间嵌套
	名字空间内部可以在定义名字空间,这种名字空间嵌套
	内层的名字空间与外层的名字空间的成员,可以重名,内层会屏蔽外层的同名标识符
	多层名字空间在使用时逐层分解
	namespace n1
	{
		namespace n2
		{
			namespace n3
			{
				
			}
		}
	}
7、可以给名字空间取别名
	由于名字空间可以嵌套,这样就会导致在使用内层成员时过于麻烦,可以给名字空间取
别名来解决这类问题
	namespace n123 = n1::n2::n3;
	using namespace n123;

四、C++的结构
1、不再需要 typedef ,在定义结构变量时,可以省略struct关键字
2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->,
但是C的结构成员可以是函数指针
3、又一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)
4、可以继承,可以设置成员的访问权限,(面向对象)

五、C++的联合
1、不再需要 typedef ,在定义结构变量时,可以省略 union 关键字
2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->,
3、又一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)

六、C++的枚举
1、定义、使用方法与C语言基本一致
2、类型检查比C语言更严格

七、C++的布尔类型
1、C++具有真的布尔类型,bool是C++中的关键字,在C语言中用布尔类型需要
导入头文件stdbool.h
2、在C++中 true false 是关键字,而在c语言中不是
3、在C++中 true false 是1字节,而在C语言中是4字节

八、C++的void*
1、C语言中 void* 可以与任意类型指针 自动转换
2、C++中 void* 不能给其它类型的指针直接赋值,必须强制类型转换,但其它类型的
指针可以自动给 void* 赋值
3、C++为什么这样修改void*?
为了更安全,所以C++类型检查更严格
C++可以自动识别类型,对万能指针的需求不再那么强烈

九、操作符别名
某些特殊语言的键没有~,&符号,所以C++标准委员会为了让C++更有竞争力,为符号
定义了一些别名,让这些小语种也可以愉快编写C++代码

	&& <==> and
	|| <==> or
	!  <==> not
	{  <==> <%
	}  <==> %>

十、函数重载
1、函数重载
在同一个作用域下,函数名相同参数列表不同的函数,构成重载关系
2、重载实现的机制
C++代码编译时会将函数的参数类型添加到参数名中,借助这个方式来实现函数重载
也就是C++的函数在编译期间经历换名的过程
因此,C++代码不能调用C函数(C语言编译器编译出的函数)
注意:如果两个函数名一样,一定会冲突
3、extern “C” {}
告诉C++编译器按照C语言的方式声明函数,这样C++就可以调用C编译器编译出的函数
了(C++目标文件可以与C目标文件合并生成可执行程序)。
如果C想调用C++编译出的函数,需要将C++函数的定义用 extern “C” 包括一下
4、重载和作用域
函数的重载关系发生在同一作用域下,不同作用域下的同名函数,构成隐藏关系
5、重载解析
当调用函数时,编译器根据实参的类型和形参的匹配情况,选择一个确定的重载版本,
这个过程叫重载解析
实参的类型和形参的匹配情况有三种:
1、编译器找到与实参最佳的匹配函数,编译器将生成调用代码
2、编译找不到匹配函数,编译器将给出错误信息
3、编译器找到多个匹配函数,但没有一个最佳的,这种错误叫二义性
在大多数情况下编译器都能立即找到一个最佳的调用版本,但如果没有,编译就会
进行类型提升,这样备选函数中就可能具有多个可调用的版本,这样就可能产生二义性
错误
6、确定重载函数的三个步骤:
1、候选函数
函数调用第一步就是确定所有可调用的函数的集合(函数名、作用域),该集合
中的函数就是候选函数
2、选择可行函数
从候选函数中选择一个或多个函数,选择的标准时参数个数相同,而且通过类型
提升实参可被隐式转换为形参
3、寻找最佳匹配
优选每个参数都完全匹配的方案,其次参数完全匹配的个数,再其次是浪费内存的
字节数
7、指针类型也会对函数的重载造成影响
C++函数的形参如果是指针类型,编译时函数名中会追加Px

十一、默认形参
1、在C++中函数的形参可以设置默认值,调用函数,如果没有提供实
参数,则使用默认形参
2、如果形参中只有一部分设置了默认形参,则必须靠右排列
3、函数的默认形参是在编译阶段确定的,因此只能使用常量、常量表达式、全局变量
数据作为默认值
4、如果函数的声明和定义需要分开,只需要在函数声明时设置默认形参即可。
5、默认形参会对函数重载造成影响,设置默认形参时一定要慎重

十二、内联函数
1、普通函数调用时是生成调用指令(跳转),然后当代码执行到调用位置时跳转到函数
所在的代码段中执行
2、内联函数就把函数编译好的二进制指令直接复制到函数的调用位置
3、内联函数的优点就是提高程序的运行速度(因为没有跳转,也不需要返回)
但这样会导致可执行文件增大(冗余),也就是牺牲空间来换取时间
4、内联分为显式内联和隐式内联
显式内联:在函数前 inline (C语言C99标准也支持)
隐式内联:结构、类中内部直接定义的成员函数,则该类型函数会被优化成内联函数
5、宏函数在调用时会把函数体直接替换到调用位置,与内联函数一样也是使用空间来换取时间
宏函数与内联函数的区别(优缺点):
1、宏函数不是真正的函数,只是代码替换,不会有参数压栈,出栈,及返回值,
也不会检查参数类型,因此所有类型都可以使用,但这样会有安全隐患
2、内联函数是真正的函数,函数调用时会进行传参,会进行压栈、出栈,可以
有返回值,严格检查参数类型,但这样就不能通用,如果想被多种类型调用需要重载
6、内联适用的条件
由于内联会造成可执行文件变大,并增加内存开销,因此只有频繁调用的
简单函数适合作为内联函数
调用比较少的复杂函数,内联后并不显著提高性能,不足以抵消牺牲空间带来的
损失,所以不适合内联
带有递归特性和动态绑定特性的函数,无法实施内联,因此编译器会忽略声明部
部分的 inline 关键字

十三、引用
引用就是取艺名。
1、引用的基本特性
引用就是取别名,声明一个标识为引用,就是表示该标识符是另一个对象
的外号。
1、引用必须初始化,不存在空引用,但又悬空引用
2、可以引用无名对象和临时对象,但必须使用常引用(变量死了,名还留着)。
3、引用不能更换目标
引用一但完成了定义和初始化就和普通变量名一样了,他就代表了目标,
一经引用终生不能在引用其它目标。
4、引用目标如果具备const属性,那么引用也必须带const属性
2、引用型参数
引用当作函数的参数能到达指针两样的效果,但不具指针的危险,还比指针
方便。
引用可以在简单实现函数间共享变量的目的,而且是否使用引用由被调函数
说了算
引用当作函数的参数还能提高传参数效率,指针至少还需要4字节内存,而引用
只需要增加一条标识符与内存之间的绑定(映射)
3、引用型返回值
不要返回局部变量的引用,会造成悬空引用。
如果返回值是一个临时对象(右值),如果要使用引用接收的话,必须使用常引用
注意:C++中的引用是一种取别名的机制,而C语言中的指针是一种数据类型(代表
内存编号的无符号整数)

指针与引用的区别
	可以跨函数共享变量
	可以优化传参效率

	指针				引用
	有自己存储空间		没有存储空间
	是数据类型			不是数据类型

练习1:实现一个C++版本的swap函数

十四、C++的内存管理
1、new/delete C++具备申请/释放堆内存功能的运算符
相当于C语言中的malloc和free
new 类型; 会自动计算类型所需要字节数,然后从堆中分配对应字节数的内存
并返回内存的首地址(具备类型)
delete 指针; 会自动释放堆内存。
注意:new/delete与malloc/free不能混用,因为new和delete会自动调用类、结构
的构造函数、析构函数

2、数组的分配与释放
	new 类型[n];	n表示数组长度,如果类、结构会自动调用n次构造函数
	delete[] 指针;	通过new[] 分配的内存,必须通过delete[]释放
		new[] 返回值前4个字节中存放着数组的长度

3、重复释放
	delete/delete[]不能重复释放同一块内存
	delete/delete[]释放野指针的后果不确定。但释放空指针是安全的。

4、内存分配失败
	当分配的内存过大,没有能满足需求的整块内存就会抛出异常std::bad_alloc,
异常
	
	new/delete与C语言的malloc相同点不同点(区别)
		身份 		运算符				标准库函数
		参数 		类型(自动计算)		字节数
		返回值		带类型的地址		void*地址
		调用构造 	自动调用			不能调用构造/析构函数
		出错		抛异常				返回NULL
		
		相同点:
			1、都能管理堆内存
			2、不能重复释放
			3、可以释放NULL
			
	注意:在C++中尽量使用引用、new/delete

/–2--******/

面向过程编程:
关注是问题解决的过程步骤(事情是如何解决的),算法
面向对象编程:
关注的是认证能解决问题(类),需要什么样的数据(成员变量),具备什么
样的技能(成员函数)才能解决问题
抽象:找出一个能够解决问题的“对象”(观察研究对象),找出解决问题所必须
的数据(属性),功能(成员函数)。
封装:把抽象的结构,归结为一个类(数据类型),然后实例化出类对象,设置
对象的属性,调用对象的功能达到解决问题的目的
继承:在解决问题前,先寻找之前的类能不能解决问题,或解决部分问题,如果
可以则把旧的类型继承后再次拓展,来缩短解决问题的时间,降低解决
问题的难度
多态:对象的多种形态,外部看到个对象,然后向对象发出指令,对象会根据自身
情况做出独特反应

一、类和对象
1、通过分析 “对象” 的属性和行为设计出一个类
2、类就是数据类型
简单类型:只能表示一个属性(变量),C/C++内建数据类型
数组类型:可以表示多个属性(变量),类型必须相同
结构类型:可以表示多个属性(变量),但缺少行为(函数)
类类型:既能表示属性,也能表示行为,一种复合数据类型
3、对象就是类这种数据类型创建出的实例,相当于结构变量

class Student
{
	成员变量;
	成员函数:
};

Student stu;

二、类的定义与实例化
1、类的一般形式

头文件中声明
class 类名:继承方式 父类
{
	public/private/protected;	访问控制限制符
	成员变量:
	//构造函数
	类名(形参表)
	{
		
	}
	//析构函数
	~类名(void)
	{
		
	}
};

2、访问控制限制符
public:公有成员,在任何位置都可以访问
private:私有成员,只能类的成员函数中访问
protected:受保护成员,只能在类和子类帐访问
	类中的成员变量、成员函数默认是 private ,结构中的成员变量、
成员函数默认是 public
注意:C++中类和结构的区别只有成员函数和成员变量默认访问权限不同 

3、构造函数
	1、什么是构造函数:类的同名函数就是构造函数没有返回值
	2、什么时候调用,谁调用,调用几次?
	
		创建类型对象时会自己调用(每创建一个类对象就会调用一次),对象
	整个生命周期中一定会被调用一次,只能被调用一次
	3、负责干什么
		成员变量的初始化,分配相关资源,设置对象的初始状态

	class 类名:继承方式 父类
	{
		//构造函数
		类名(形参表)
		{
			
		}
	};
	
	4、类的创建过程
		1、分配类型所需要空间,无论栈还是堆
		2、传递实参调用构造函数,完成如下任务:
			A、根据继承表依次调用父类的构造函数
			B、根据成员变量的顺序依次调用成员变量的构建函数
			C、执行构造函数体中的代码
		注意:执行构建函数的代码是整个构造函数的最后一步
			要保证构造函数代码所需要的一切资源和先觉条件在该代码执行
			前已经准备充分,并得到正确的初始化
			
	5、对象的创建方法
		在栈上创建:类名  对象;	//不需要括号
					类名  对象(实参);
		在堆上创建:类名* 对象指针 = new 类名;
					类名* 对象指针 = new 类名(实参);
		
		创建多个对象:
			类名  对象 = {(实参),(实参),(实参)};
			类名* 对象指针 = new 类名[n]{(实参),(实参),(实参)};
			
			注意:通过malloc创建的类对象不能调用构造函数
			注意:通过new[]创建的对象,一定要通过delete[]释放
	
	6、类的声明、实现、调用
		1、在头文件中声明
			class 类名:继承方式 父类
			{
				成员变量;
			public:
				//构造函数
				类名(形参表);
				//析构函数
				~类名(void);
				//其它成员函数
			};
		2、	源文件实现类的相关函数
			返回值 类名::函数名(参数列表)
			{
	
			}
		3、调用时只需要导入头文件,然后与类函数所在的源文件一起编译即可
			注意:如果一个类内容不多,可以考虑在头文件完全实现
				也可以只在头文件中实现函数一部分
			注意:类中自动生成的函数,在源文件中实现时,也需要在头文件中声明

三、构造函数与初始化列表
1、构造函数可以被重载(同一个名字的函数有多个不同版本)
2、缺省构造是编译器自动生成的一个什么都不做的构造函数(唯一的作用就是避免错误);
注意:当类实现一个有参构造后,缺省构造就不会再自动生成,如果有需要必须显式的
写出来
3、无参构造未必无参,当给有参构造的所有参数设置默认形参,调用这种构造函数就不
需要传参

	注意:所谓的"编译器生成的某某函数"其实不是真正语法意义上的函数,而是功能意义的
			函数,编译器作为可执行指令的生成者,他会直接生成具有某项功能的二进制指令
			,不需要借助高级语言语义上的函数完成此任务
	注意:如果一个类A是其它类B的成员变量,那么一定要为它保证它有一个无参构造,当B的
			构造函数执行时会先执行成员变量的无参构造,而此时类B是无法给类A成员变量
			提供参数的
4、单参构造与类型转换
	如果构造函数的参数只有一个,那么Test t = n语句就不会出错,他会自动调用单参构造
来达到类型转换的效果
	如果想禁止这种类型转换需要再单参构造前加 explicit

5、初始化列表
	为类型成员进行初始化用的。
	
	构造函数(参数):成员(参数),成员1(参数),成员2(参数)....
	通过初始化列表可以给类成员变量传递参数,以此调用类成员的有参构造
	初始化列别也可以给 const 成员 引用成员进行初始化
	成员的初始化顺序与初始化列表没有关系,而是再类中的定义顺序有关
	注意:初始化列表运行类成员变量还没定义成功 

作业:封装一个List类。
以C++编程方式实现2048游戏

/–3--********/

一、this指针
类的成员变量单独存储在每个类对象中,成员函数存储在代码段中,所有的类对象
共享一份成员函数
成员函数是如何区别调用他的是哪个类对象的?
答:借助了this指针,类的每个成员函数都有一个隐藏的参数this指针,它指向
类对象

	类的构造函数中也同样有this指针,指向的就是正在构造的这个对象。
	在类中(成员、构造、析构)对成员变量、成员函数的访问都是借助了this指针
	this指针是隐藏的,但也可以显式使用:
		1、参数与成员一样时,使用this可以区别出成员函数与参数名
		2、在成员函数中如果想返回当前对象的指针、引用等,可以使用this指针
			实现
		3、将this指针作为函数的参数,从一个对象传递给另一个其它类对象,可以
			实现对象间的交互

二、常函数
在函数的参数列表与函数体之间有const修饰的函数,这个const其实就是在
修饰this指针。
不能在常函数内修改成员变量的值,普通成员函数可以调用常函数,而常函数只能
调用常函数

	如果在常函数中真的需要修改某个成员变量的数据,那么需要这个成员被 mutable 修饰

三、析构函数
1、特殊的成员函数
函数名必须是类名
~类名(void)
{

	}
	没有参数、没有返回值,不能重载
2、谁来调用
	析构函数会在销毁对象时自动调用,在对象整个生命周期内最多被调用一次
3、析构函数负责什么
	析构函数负责释放在构造函数期间所获取的所有资源,它的执行过程:
		1、先执行函数本身代码
		2、调用成员类的析构函数
		3、调用父类的析构函数
4、缺省的析构函数
	如果一个类没有实现析构函数,编译器会自动生成一个具有析构函数的
功能的二进制指令,它负责释放编译器能够看的到的资源(成员变量、类成员、
父类成员),这就是缺省析构
	如果一个类没有动态资源,也不需要做善后工作,缺省析构就完全够用了,
不需要再实现新的析构函数
	注意:缺省析构不能释放动态资源(堆内存)。
	
作业:类对象创建过程与释放过程
	创建:分配内存(对象空间)-> 父类构造 -> 成员构造 -> 自己构造
			父类构造:按照继承表从左到右依次构造。
			成员构造:按照声明顺序从上到下依次构造
	释放:自己析构 -> 析构成员 -> 析构父类 -> 释放内存(对象)	
			析构成员:按照继承表从下到上依次析构
			析构父类:按照继承表从右到左依次析构

四、拷贝构造
拷贝构造又称为复制构造,是一种特殊的构造函数,它是使用一个现有的对象构造
一个新的对象,只有一个引用型的参数(对象本身)。
类名(类& )
{

	}
	拷贝构造的参数应该加 const 保护,但编译器并没有强制限制
	编译器会自己生成一个拷贝构造函数,它负责把旧对象中的所有数据拷贝给新创
建的对象
	深拷贝与浅拷贝的区别:
		如果类成员有指针,浅拷贝只拷贝指针变量的值,而深拷贝是拷贝指针变量
	所指向的目标。
	
	什么情况下需要实现拷贝构造:
		当类成员中有指针成员,此时默认的拷贝构造旧无法完成任务,需要自己
	动手实现拷贝构造(深拷贝)
	
	什么情况下会调用拷贝构造:
		1、使用一个旧对象给新对象赋值时
			User user1 = user;
		2、使用对象当作函数的参数,当调用函数参数时就会一起调用拷贝构造

五、赋值构造(赋值运算符)
当一类旧对象给另一个类旧对象赋值时,就会调用赋值构造
void operator = (类& )
{

}
什么时候会调用:对象 = 对象;

编译器会生成一个缺省的赋值构造,他负责把一个对象的内存拷贝给另一个对象

什么情况需要实现赋值构造:
	当需要深拷贝时需要自己手动实现赋值构造,也就是说拷贝构造与赋值构造
都需要同时实现。

编译器会自动生成四个成员函数:构造、析构、赋值构造、拷贝构造

六、关于拷贝构造、赋值构造的建议
1、缺省的拷贝构造、赋值构造不光会拷贝本类的数据,也会调用成员类对象和
父类的拷贝构造和赋值构造,而不是单纯的按字节赋值,因此尽量少用指针成员
2、在函数参数中尽量使用类指针或引用来当参数(不要直接使用类对象),减
少调用拷贝构造和赋值构造的机会,也可以降低数据传递的开销。
3、如果由于特殊原因无法实现完整的拷贝构造、赋值构造,建议将它们私有化,
防止误用
4、一但为一个实现了拷贝构造,那么也一定要实现赋值构造

七、静态成员
类成员一但被 static 修饰就会变成静态成员,而是单独一份存储在bss或data内存
段中,所有的类对象共享(静态成员属于类,而不属于某个对象)。
静态成员在类内声明,但必须在类外定义、初始化,与成员函数一样需要加"类名::"
限定符表示它属于那个类,但不需要再额外加 static

	成员函数前也可以被 static 修饰,这种函数叫静态成员函数,这种成员函数没有
this 指针,因此再静态函数中不能直接访问类的成员、成员函数,但可以直接访问静态
成员变量、静态成员函数。

	静态成员变量、函数依然受访问控制限定符的影响
	
	因为在代码编译完成后静态成员已经定义完成(有了存储空间),因此可以不通过
类对象而直接调用,类名::静态成员名

普通成员函数中可以直接访问静态成员变量、静态成员函数。

	静态成员变量可以被当作全局变量来使用(访问限定符必须是public),与静态成员
函数可以当作类的接口,实现对类的管理

八、单例模式
什么是单例模式,只能创建出一个类对象(只有一实际的实例)的叫单例模式
单例模式的应用场景:
Window系统的任务管理器
Linux/Unix系统的日志系统
网站的访问计数器
服务端程序的连接池、线程池、数据池
获取单一对象的方法:
1、定义全局(C语言),但不受控制,防君子不能防小人。
2、专门写一个类,把类的构造函数设置私用,借助静态成员函数提供一个接口
以此来获取唯一的实例

	C++如何实现单例:
		1、禁止类的外部创建类对象:构造函数设置私有
		2、类自己维护一个唯一的对象:使用静态指针指向
		3、提供一个获取实例的方法:静态成员函数获取指针
	
饿汉模式:
		将单例类的唯一实例对象定义为成员变量,当程序开始运行,实例对象就已经
	创建完成
		优点:加载进行时静态创建单例对象,线程安全
		缺点:无论使用与否,总要创建,浪费内存
	
懒汉模式:
		用静态成员指针来指向单例类的唯一实例对象,只有真正调用获取实例
	对象的静态接口时,实例对象才被创建。
		优点:什么时候用什么时候创建,节约内存
		缺点:在第一次调用访问获取实例对象的静态接口才真正创建,如果在多线程
			操作情况下有可能被创建出多个实例对象(虽然可能性低)存在线程不安全问题


总结:C语言与C++有哪些不同点
	内存管理: new/delete  malloc/free
	static
	const
	void*
	字符串:string系列函数 string

/–4--******/

一、操作符函数重载
什么是操作符函数:在C++中针对类类型的对象的运算符,由于它们肯定不支持真正的运算符
操作,因此编译器会将它们翻译成函数,这种就叫操作符函数(运算符函数)。
编译器把运算符翻译成运算符函数,可以针对自定义的类类型设计它独有的运算功能。
其实各种运算已经具备一些功能,再次实现它的就是叫做运算符重载

	双目运算符:
		a+b
		成员函数
			a.operator+(b);
		全局函数
			operator+(a,b);
	
	单目运算符:
		!a
		成员函数
			a.operator!(void);
		全局函数
			operator!(a);

二、双目操作符重载
成员函数:
类对象 operator#(const 类& that) const
{
return 类(参数#参数);
}
注意:双目运算符的运算结果是个右值,返回值应该加 const 然后为
const 对象能够调用参数应写 const ,函数也应该具备 const 属性

全局函数:
类 operator#(const 类&a,const 类& b)
{
	
}
注意:全局函数不是函数,可能会访问到类的私有成员,解决这种问题可以

友元:在类的外部想访问类的私有成员(public/protected)/private)时,需
	要所在的函数声明为友元,但友元只是朋友,没有实际的拥有权,因此它只有
	访问权(其根本原因是它没 this 指针)
	
友元声明:把函数的声明写一份到类中,然后在声明前加上 friend 关键字
注意:友元函数与成员函数不会构成重载关系,它们不在同一个作用域内

使用友元既可以把操作符函数定义为全局的,也可以确保类的封装性

三、赋值类型的双目操作符
1、获取单参构造成赋值运算的调用方式。
String str = “sunll”;
str = “hehe”;

2、左操作数不能具有const属性
	1、成员函数不能是常函数
	2、全局函数第一个参数不能有 const 属性

3、返回值应该都(成员/全局)具备 const 属性

四、单目操作符重载
成员
const 类 operator#(void) const
{

	}
全局
	const 类 operator#(const 类& that)
	{
		
	}

前++/--
	类& operator#(void)
	{
		
	}
	类& operator#(类& that)
	{
		
	}
后++/--
	const 类& operator#(int)
	{
		
	}
	const 类& operator#(类& that,int)
	{
		
	}

五、输入输出操作符重载
cout 是 ostream 类型的对象
cin 是 istream 类型的对象

	如果 <</>>运算符实现为成员函数,那么调用者应该是ostream/istream,而
我们无权增加标准库的代码,因此输入/输出运算符只能定义为全局函数

ostream& operator<<(ostream& os,const 类& n)
{
	
}

istream& operator>>(istream& is,类& p)
{
	
}

注意:在输入输出过程中,cin/cout会记录错误标志,因此不能加const

六、特殊操作符的重载
1、下标操作符 [],常用于在容器类型中以下标方式获取元素
类型& operator[](int i)
{

	}
	
2、函数操作符(),一个类如果重载函数操作符,那么它的对象就可以像
	函数一样使用,参数的个数、返回值类,可以不确定,它是唯一一个可以
	参数有缺省参数的操作符
	
3、解引用操作符*、成员访问操作符->
	如果一个类重载了*和->,那么它的对象就可以像指针一样使用。
	所谓的智能指针就是一种类对象,它支持解引用和成员访问操作符

4、智能指针
	常规指针的缺点:
		当一个常规指针离开他的作用域时,只有该指针所占用的空间会被释放,
	而它指向的内存空间能否被释放就不一定了,在一些特殊情况(人为、业务
	逻辑特殊)free或delete没有执行,就会形成内存泄漏。
	
	智能指针的优点:
		智能指针是一个封装了常规指针的类类型对象,当它离开作用域时,它
	的析构函数会自动执行,他的析构函数会负责释放常规指针所指向的动态内存
	(以正确方式创建的智能指针,它的析构函数才会正确执行)。
	
	智能指针和常规指针的相同点:都支持*和->运算
	智能指针和常规指针的不同点:任何时候,一个对象只能使用一个智能指针指向,
								而常规指针可以指向多次
	智能指针的赋值操作需要经过拷贝构造和赋值构造特殊处理
	
	auto_ptr:标准库中封装好的智能指针,实现了常规指针的基本功能,头文件 #include <memory>
			用法:auto_ptr<指向的类型> 指针变量名(指向对象的地址)
			auto_ptr的局限性:
				1、不能跨作用域使用,一旦离开作用域指针变量会释放它指向的对象也会释放
				2、不能放入标准容器
				3、不能指向对象数据
	
	5、new/delete/new[]/delete[] 运算符重载
		1、C++中缺省的堆内存管理器速度较慢,重载new/delete底层使用malloc/free可以
			提高运行速度
		2、new在失败时会产生异常,而每次使用new时为了安全都应该进行异常捕获,而
			重载new操作符只需要在操作符函数中进行一次错误处理即可
		3、在一些占字节数比较小的类,频繁使用new,可能会产生大量的内存碎片,而重载
			new操作后,可以适当的扩大每次申请的字节,减少内存碎片产生的几率
		4、重载 new/delete 可以记录堆内存使用的信息
		5、重载 delete 可以检查到释放内存时的信息,检查到内存泄漏

七、重载操作符的限制
1、不能重载的操作符
域限定符 ::
直接成员访问操作符 .
三目运算符 ? :
字节长度操作符 sizeof
类型信息操作符 typeid
2、重载操作符不能修改操作符的优先级
3、无法重载所有基本类型的操作符运算
4、不能修改操作的参数个数
5、不能发明新的操作符

关于操作符重载的建议:
	1、在重载操作符时要根据操作符实际的功能和意义来确定具体参数,返回值
		是否具有 const 属性,返回值是否是引用或者是临时对象
	2、重载运算符要符合情理(要有意义),要以实际用途为前提
	3、重载操作符的意义是为了让对象的操作更简单、方便,提高代码可读性,
		而不是为了炫技
	4、重载操作符要与默认的操作符的功能、运算规则一致,不要出反人类的操作

/***************************–5--*********************************/

一、类的继承
1、共性与特性
共性:表达不同类型事物之间共有的属性和行为
个性:个性用于刻画每种事物特有的属性和行为
2、共性表示为父类(基类),个性表示为子类(派生类)
子类继承自父类
基类派生出子类

二、继承的基本语法
1、继承表
一个子类可以同时继承零到多个父类,每个父类的继承方式可以相同也可以不同
class 子类:继承方式1 父类,继承方式2 父类,…
{

	}
2、继承方式
	public		公有继承:父类的特性可通过子类向外扩展
	private		私有继承:父类的特性只能为子类所有
	protected	保护继承:父类的特性只能在继承链内扩展

三、继承的基本特点
1、公共特点(所有继承都有的特点)
子类对象可以当作父类对象使用,子类对象与父类没有本质上的区别
子类的逻辑空间小于父类,但他的物理空间要大于等于父类
子类对象 IS A 父类对象
2、向上和向下转换(造型)
从子类到父类:子类的指针或引用可以隐式转换成父类的指针或引用,
这是一种缩小类型的转换,对于编译器来说是安全(父类指针指向子类对象
是安全的)
从父类到子类:父类的指针或引用不可以转换成子类的指针或引用,
这是一种扩大类型的转换,在编译器看来是危险的(子类的指针指向父类的
对象,是不安全的)
编译器仅仅是检查指针或引用的数据,而对实际引用的目标对象不关心
(构成多态的基础)。
类型一致:父类的指针或引用实际的目标类型是否需要转换成实际的指针
或引用由程序员自己决定
3、子类会继承父类的所有成员(公开、私有、保护)

4、子类会隐藏父类的同名成员
	1、可以通过域限定符父类::隐藏成员 进行访问父类中的隐藏成员
	2、可以使用父类的指针或引用来指向子类对象,然后访问父类中的隐藏成员

5、虽然子类继承所有父类中的成员,但不能访问父类中的私有成员

四、继承方式影响访问控制
1、访问控制限定符:

2、继承方式的影响范围

/–6--****/

一、子类的构造、析构、拷贝
1、子类的构造在执行他的构造函数前会根据继承表的顺序执行父类的构造函数
默认执行父类的无参构造
显式调用有参构造,在子的构造函数后,初始化列表中显式调用父类的有参构造函数
2、子类在它的析构执行完后,会根据继承表的顺序逆顺序执行父类的析构函数
注意:父类的指针可以指向子类对象,当通过父类指针释放对象时,只会调用父类的
析构函数,而这种析构方式有可能造成内存泄漏
3、当使用子类对象来初始化新的子类对象时,会自动调用缺省的拷贝构造函数,并且会先
调用父类缺省的拷贝构造函数
如果子类中实现的拷贝构造,需要显式调用父类拷贝构造,否则就会调用无参构造

二、私有继承、保护继承
使用 private 的方式继承父类,公开的成员变成私有的,其它的不变,这种继承方式防止
父类成员扩散
使用 protected 方式继承父类,公开的成员变成在子类中会变成保护的,其它的不变,
这种继承方式可以有限防止父类的成员扩散
子类以私有或保护方式继承父类,禁止向上造型(子类的指针或引用不能隐式转换成父类的
指针或引用,要想试下多态只能以公开方式继承父类)

三、多重继承、钻石继承、虚继承
1、多种继承
在C++中一个子类可以由多个父类,在继承表中按照顺序继承多个父类中的属性和行为,并
按照顺序表中的调用父类的构造函数。
按照从低到高的地址顺序排列父类,子类中会标记每个父类存储位置
当子类指针转换成父类的隐式转换时,编译器会自动计算父类中的内容所在子类中的位置
地址会自动进行偏移计算

2、名字冲突
	如果父类中有同名的成员,可以正常继承,但如果直接使用会造成歧义,需要 类名::成员名
进行访问

3、钻石继承
	假如有一个类A,类B继承类A,类C继承类A,然后类D继承类B和类C
	一个子类继承多个父类,这些父类有一个共同的祖先,这种继承叫钻石继承
	注意:钻石继承不会导致继承错误,但访问祖先类中的成员时每次需要使用 类名::成员名,
		重点是这种继承会造成冗余

4、虚继承 virtual
	当进行钻石继承时,祖先类中的内容会有冗余,而进行虚继承后,在子类中内容指挥保留
一份
	注意:但使用虚继承时子类中会多了一些内容(指向祖先类继承来的成员)。

5、构造函数
	一但进行了虚继承(钻石)祖先类的构造函数只执行一次,由孙子类直接调用,祖先类的有参构造也需
要在孙子类中显式调用

6、拷贝构造
	在虚继承(钻石)中祖先类拷贝构造也由孙子类直接调用,子类中不再调用祖先类的拷贝构造,在手动
实现的拷贝构造时(深拷贝),祖先类中的内容也有孙子类负责拷贝,同理赋值构造也一样

四、虚函数、覆盖、多态
1、虚函数
类的成员函数前加 virtual 这种函数就叫虚函数

2、覆盖
	在子类会覆盖父类中的虚函数

3、多态
	当子类覆盖了父类的虚函数时,通过父类指针指向子类对象时,调用虚函数,会根据具体的对象
是谁来决定执行谁的函数,这就是多态。

五、覆盖和多态的条件
1、覆盖的条件
必须是虚函数
必须是父子类之间
函数签名必须相同(参数列表完全一致、const 属性也会影响覆盖的结果)
返回值必须是同类或父子类(子类的返回值要能向父类隐式转换)
访问属性不会影响覆盖
常函数属性也会影响覆盖
2、重载、隐藏、覆盖(重写)的区别
重载:同一作用域下,同名函数,函数签名不同,构成重载
覆盖:符合一系列条件
隐藏:父子类之间的同名成员如果没有形成覆盖,且能通过编译,必定构成隐藏
3、多态的条件
1、父子类之间有的函数有覆盖关系
2、父类的指针或引用指向子类对象
4、在构造、析构函数中调用虚函数
在父类的构造函数中调用虚函数,此时子类还没创建完成(回顾构造函数调用过程),
因此只能调用父类的虚函数,而不是覆盖版本的虚函数。
在子类的析构函数中调用虚函数,此时子类已经释放完成,因此只能调用父类的虚函数,
而不是覆盖版本的虚函数。

六、纯虚函数和抽象
1、纯虚函数
在虚函数的声明的后面添加=0,这种虚函数就叫纯虚函数,可以不实现,但如果实现
必须在类外(只能在父类中的构造函数、析构函数中调用)。
virtual 返回值 函数名(参数列表) = 0;

2、抽象类
	成员函数中有纯虚函数,这种类叫抽象类,抽象类不能实例化(不能创建对象)。
	抽象类必须被继承且纯虚函数被覆盖后,由子类实例化对象
	如果继承抽象类,但没有覆盖纯虚函数,那么子类也将成为抽象类,不能实例化

3、纯抽象类
	所有成员函数都是纯虚函数,这种只能被继承的类叫纯抽象类,这种类一般用来设计
接口,这种类在子类被替换后不需要修改或少量的修改即可继续使用

/–7--*****/

一、虚函数表
什么是虚函数表,在C++的类中,一但成员函数中有虚函数,这个类中就会多一个虚函数
表指针,这个指针指向一个虚函数表,表里面记录了这个类中所有的虚函数,当这个类被继承,
他的子类中也会有一个虚函数表(不管子类中有没有虚函数),如果子类的成员函数中的有函
数签名与父类的虚函数一样就会用子类中的函数替换它在虚函数表中的位置,这样就达到了覆
盖的效果
当通过类指针或引用调用函数时,会根据对象中实际的虚函数表记录来调用函数,这样就
达到了多态的效果

二、虚析构
当使用delete释放一个父类指针时,不管实际指向的对象是子类还是父类都只会调用父类
的析构函数(多态肯定会出现的问题)
如果子类的析构函数有需要负责释放的内存,就会造成内存泄漏
为了解决这个问题,可以把父类的析构函数设置为虚函数,析构函数进行覆盖时不会比较
函数名。
当父类的析构为虚函数时,通过父类指针或引用释放子类对象时,会自动调用子类的析构
函数,子类的析构函数执行完成后也会调用父类的析构函数。

	注意:析构函数可以是虚函数,但构造函数不行

三、强制类型转换
注意:C++中为了兼容C语言,(目标类型)源类型 依然可以继续使用,但C语言的强制类型
转换安全性差,因此建议使用C++中的强制类型转换
注意:C++之父认为如果代码设计的完善,根本不需要用到强制类型转换,而C++的强制类
型转换之所以设计很复杂,是为了让程序员多关注代码本身的设计,尽量少使用

C++中的强制类型转换保证没有很大安全隐患

static_cast<目标类型>(源类型)	编译器会对源类型和目标类做兼容性检查,不通过则报错
dynamic_cast 编译器会对源类型和目标类型是否同为指针或引用,并且存在多态型的继承关系
const_cast 编译器会对源类型和目标类是否同为指针或引用,除了常属性外其它必须完全相同否则报错
reinterpret_cast 编译器会对源类型和目标类型是否为指针和整数进行检查,也就是说把整数转换
				成指针或指针转换成整数

动态编译,静态编译。
	静态编译:指针或引用的目标是确定,在编译时期就确定所有类型检查、函数调用
	动态编译:指针或引用的目标是不确定(多态),只有运行时候才确定,具体是哪个子类

四、I/O流
I/O流的打开模式
ios::app 打开文件用于追加,不存在则创建,存在则清空
ios::ate 打开时定位到文件末尾
ios::binary 以二进制模式进行读写
ios::in 以读权限打开文件,不存在则失败,存在不清空
ios::out 以写权限打开文件,不存在则创建,存在则清空
ios::trunc 打开文件时清空

fstream/ifstream/ofstream 类用于进行文件操作
	构造函数或成员函数 open 用于打开文件
	good成员函数检查流是否可用
	eof成员函数检查输入流是否结束
>> 操作符用于从文件中读取数据到变量
<< 操作符用于输出数据到文件

IO流有一系列格式化控制函数,类似:左对齐、右对齐、宽度、填充、小数点位数

二进制读写:read/write
	read(char_type *_s,streamsize_n);
	gcount成员函数可以获取上次流的二进制读操作的字节数
	
	write(char_type *_s,streamsize_n);
	good成员函数可以获取到写操作是否成功

随机读写:seekp(off_type,ios_base::seekdir);
	功能:设置文件的位置指针
	off_type:偏移值
		正值向右,负值向左
	seekdir:基础位置
		ios::beg	文件开头
		ios::cur	当前位置
		ios::end	文件末尾

获取文件位置指针:tellp
	该成员函数返回文件流的位置指针(字节数)
	也可以借助此函数获取文件的大小

练习:使用C++的标准I/O,实现带覆盖检测的cp命令。

五、类型信息 typeid
#include
用于获取数据的类型信息,返回type_info
name成员函数,可以获取类型的名字,内建类型名字使用缩写。
同时还支持 == != 用来比较是否是同一种类型

如果用于判断父子类的指针或引用,它不能准确判断出实际的对象类型。
但可以判断具有多态继承的关系的父子类的指针或引用,它的实际对象类

sudo find / -name filename	
sudo find / | grep "std"	
grep 'Base' *				当前目录下查找文件中包含的字符
grep -r 'Base' *			当前目录查找文件中包含的字符,包括所有子级目录
grep -r 'Base' * dir 		指定目录下查找文件中包含的字符,包括所有子级目录

六、异常处理
抛异常
throw 数据
抛异常对象
抛基本类型
注意:不能抛出局部对象的指针或引用
注意:如果异常没有被捕获,程序就会停止
捕获异常
try{
可能抛出异常的代码
}
catch(类型 变量名) // 根据数据类型进行捕获
{
// 处理异常,如果无法处理可以继续抛出异常
}

	注意:捕获异常的顺序是自上而下的,而不是最精准的匹配,针对子类异常捕获时
		要放在父类的前面。

函数的异常声明:
	返回值类型 类型名(参数列表)throw(类型1,类型2,...);
	注意:如果不写异常声明什么类型的异常都可能抛出
	注意:如果写了异常声明表示只抛出某些类型的异常,一但超出异常声明的范围,
		程序无法捕获
	注意:throw()表示什么类型都不会抛出

设计异常类
class Error
{
	int errno;
	char errmsg[255];
public:
	Error(int errno = -1,const char* msg = "未知错误")
	{
		this->errno = errno;
		strcpy(errmsg,msg);
	}
	int getError(void)
	{
		return errno;
	}
	const char* getErrmsg(void)
	{
		return errmsg;
	}
};

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
功能:多路描述符
nfds:最大文件描述符+1
readfds:监控读操作文件描述符集合
writefds:监控写操作文件描述符集合
exceptfds:监控异常操作文件描述符集合
timeout:设置超时时间
返回值:监控到文件描述符的个数,超时返回0,出错返回-1

void FD_CLR(int fd, fd_set *set);
功能:从集合中删除文件描述符
int FD_ISSET(int fd, fd_set *set);
功能:测试集合中是否有文件描述符
void FD_SET(int fd, fd_set *set);
功能:向集合中添加文件描述符
void FD_ZERO(fd_set *set);
功能:清空文件描述符集合

酒店管理系统:
前提:
楼层,房间编号,房间类型,价格
开房:姓名,房间,随行人员,押金,时长
续费:延时,修改类型
退房:押金,消费

使用C++语言编写。

/–8--*****/

一、为什么使用模板
1、C/C++ 是一种静态类型预言(预处理->汇编->编译->链接),好处是速度快,
缺点是实现通用代码麻烦,例如:实现支持所有类型的快速排序
2、借助函数重载实现通用代码,好处是实现简单,但代码段会增加
3、借助宏函数实现通用代码,类型检查不严格
4、借助回调函数实现通用代码,使用麻烦
5、由于以上原因C++之父在C++实现了模板技术,让C++能够支持泛型编程。

二、函数模板
1、函数模板的定义
template <typename 类型参数, typename 类型参数2, …>
类型参数1 函数名(类型参数2 参数名)
{

		return 返回值;
	}

	template <typename T>
	find(T* arr,size_t len)
	{
		return val;
	}

	可以任何标识符作为类型参数名,但使用'T' 是俗称约定的,它表示调用这个函数时
所指定的任意类型。

2、函数模板的使用
	C++编译的编译器并不是把模板编译成一个可以处理任何类型的单一实体,而是根据
模板的使用者的参数产生不同的函数实体
	根据具体类型代表模板参数生成函数实体过程叫实例化

	模板是在使用时才实例化,可以自动实例化,也可以手动实例化(在函数调用时函数名
与小括号之间 <类型参数> )

	每个函数模板都会进行二次编译,第一次编译在实例化之前,检查模板代码本身是否
正确,第二次时实例化过程中,结合所使用类型参数,再次检查模板代码,是否所有的代码
都有效
	注意:第二次编译才会生成二进制指令,第一次编译仅仅是在编译器内部生成一个用于
描述模板的数据结构

3、函数模板的隐式推算
	函数模板虽然可以手动实例化,但使用麻烦,因此一般都根据参数类型进行隐式推断模板
的参数
	注意:不能隐式推算的三种情况
	1、函数参数与模板类型参数没有关系
	2、不允许隐式类型转换
	3、返回值类型不能隐式推断

4、函数模板与默认形参之间有冲突。

5、普通函数与同名的模板函数构成重载,编译器会优先调用普通函数,如果实现一个与模板
	函数功能一致的普通函数,那么这叫做模板函数的特化。

注意:一般char*类型都需要特化

练习 1:实现冒泡、选择、插入、快速、归并、堆等排序算法的函数模板

三、类模板
1、类模板的定义
template <typename M, typename R, typename A, typename O>
class Test
{
public:
M val;
Test(A a)
{
O var;
}

		R func(void)
		{

		}
	};

2、类模板的使用
	类模板的参数不支持隐式推断,必须显式指定类型参数
	类名<类型...> 对象;

	类模板分为两步进行实例化:
	编译期:编译器将类模板实例化类,并生成类对象创建指令
	运行期:处理器执行类对象创建指令,将类实例化为对象
	类模板也是一种静态多态

	类模板中,只有那些被调用的成员函数才实例化,即产生二进制指令(调用谁实例化谁)。

3、类模板的静态成员
	静态成员需要在类外定义,这一点不改变,但与普通类的定义不同
	template <typename ...> 类型 类名<...>::成员名;

4、递归实例化
	类模板的参数可以是任何类型,只要该类型提供类模板所需要的功能
	类模板的实例化已经是一个有效的类型了,因此它可以当作类模板的参数,这种叫做递归实例化
	vector<vector<int>> arr;	//二维数组

	练习2:实现一个顺序栈的模板类

5、类的局部特化
	当类的某个成员函数不能通用,需要对特殊类型(char*)实现一个特殊版本,这叫类的局部特化
	template<> 返回值类型 类名<类型>::函数名(参数)
	{

	}
	注意:在类外实现

6、全类特化
	当需要针对某种类型对类全部实现一个特殊版本,这种叫类的全部特化。
	template<> 类名<类型>
	{
		...
	};

7、类模板的缺省值
	类模板的类型参数可以设置默认值类型,规则与函数的默认形参基本一致(设置缺省值类型靠右)
	后面的类型参数可以使用前面的类型,但前面不能使用后面的

8、普通数据也可以做模板参数
	template <typename T,类型 B>
	{
		int arr[B];
	}

	给类模板一个数据,在类中就可以像使用宏名一样使用参数。
	注意:实例化类中提供的数据必须是常量

作业:实现List模板类

/–9--****/

一、模板的技巧
1、typename 可以用 class 替换
2、不能直接使用模板父类中的成员,需要:类名::函数名
3、在类模板中可以定义虚函数(多态),但虚函数不能是模板函数

二、STL介绍
STL 标准模板库,由惠普实验室提供,里面集成了常用的数据结构类模板和算法
函数模板等
容器:用来存储各类型数据的数据结构
迭代器:类似于专门指向容器成员的指针,用来遍历、操作、管理容器中的成员,可以大大
提高容器的访问速度
算法:STL实现了常见的排序、查找算法

List:双端链表容器
iterator:用来指向容器中的元素
	begin():获取指向第一个元素的迭代器
	end():获取指向最后一个元素的下一位置迭代器
vector:向量容器,俗称数组
stack、queue:栈和队列
deque:双端队列,用法于向量基本一致,但可以在头和尾快速插入和删除

注意:vector和deque是支持[]运算,因此基本不需要迭代器,其它容器一律使用迭代器进行遍历

set:集合容器,集合中的数据会自动排序,不能重复
maps:是一种关联容器,在其它编程语言中叫字典,C++中叫映射,以key/value键值对的方式进行
	存储,key的值不能重复
multimap:多重映射,它与map的很像,区别是它key的值可以重复
multiset:多重集合,它与set很像,区别是它的值可以重复
priority_queue:优先队列,它会根据元素的比较结果进行排序

总结:
	1、vector和deque是支持[]运算,因此基本不需要迭代器,其它容器一律使用迭代器进行遍历
	2、stack、queue、priority_queue容器没有迭代器
	3、set、multiset、priority_queue会对元素进行排序,因它存储元素要支持比较运算符

STL中常用的算法函数:
	使用时要加头文件:algorithm
	查找:find、search
	排序:sort
	复制:copy
	删除:remove
	比较:equal
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值