一、从C到C++ 学习
1.C++应用领域
(1).服务器端开发
(2).游戏引擎的开发
(3).虚拟现实:VR等
(4).数字图像处理:AutoCAD的系统开发,OpenCV的视觉识别等
(5).网络软件:C++拥有很多成熟的用于网络通信的库,例如:ACE库。
2.C++是C的增强
(1).新增自定义的类型转换
C语言:实现基本数据类型的转换,对于非基本的数据类型转换,不能实现转换。
C++:实现基本数据类型的转换,实现自定义类型的转换,例如:类类型的转换。
(2).严格的数据类型检查
C++是一门静态型语言,如果程序有错误,在编译阶段就会将错误提出来
(3).新增变量引用
引用:
符号:&
解释:引用实质上是给已经存在的变量取一个别名
使用:格式: 数据类型 &别名=已经存在的变量名; //数据类型:基本数据类型,自定义类型
注意:(1)编译器不会给引用变量另外开辟空间
(2)引用类型的变量一旦存在必须对其进行初始化工作
(3)引用类型的变量必须与一个已经存在的变量进行绑定
(4)新增函数带默认值参数
函数中的参数在声明的时候带了一个默认值
格式: 函数返回值 函数名(参数1,数据类型 标识符=初始值)
{
}
例如:
void function(int a,int b=20)//参数b带默认值
{
}
如何调用带默认值的函数?
(1)不更新默认值的形式调用
例如:function(40)
(2)更新默认值的形式调用
例如:function(40,30)
注意:写法上,要将不带默认值的参数放在带默认值的参数前
(5)新增了函数重载以及运算符重载
函数重载:是指函数名相同,函数的参数类型或参数个数或参数的顺序不同,即可发生函数重载。
注意:函数重载与函数返回值无关
内联函数:
关键字:inline //用于声明一个函数是内联函数
格式:
inline 函数返回值 函数名(参数列表)
{
}
内联函数的使用:
针对使用频度高的代码,且短小精简,提高了编译器的执行效率。
(6)新增了智能指针
作用是防止内存泄漏,即将用户用裸指针开辟的堆空间的权利交给智能指针去管理
(7)新增了异常处理机制
C++可以主动产生异常,也可以主动捕获异常并处理
4.C++文件架构及后缀
文件架构:
(1)主程序文件:.cpp
在该文件里包含了一个main函数,就称该文件为主程序文件。
编译器在执行时,执行的是主程序文件
放置变量的初始化,函数的定义或实现,类的声明或实现等等
(2)头文件:.h
该文件里主要放置变量的声明、函数的声明、类的声明等等
(3)源文件:.cpp
该文件里主要放置变量的初始化,函数的定义和实现,类的成员函数的实现等等。
编译器的执行流程:
(1)预处理
工作内容:
a.将源文件以外的内容全部加入到源文件中
b.删除掉注释
c.进行宏替换工作
d.条件编译
(2)编译
工作内容:
a.将用户编写代码转换成汇编语言
b.检查用户编写代码是否符合语法规范
(3)汇编
工作内容:
a.将编译阶段翻译的汇编语言转换为机器语言
(4)链接
工作内容:生成可执行文件
5.C语言和C++的区别
编程思想上:
C语言:面向过程的语言,侧重点是在于过程
C++:既面向过程也面向对象,C++的侧重点是在于对象
使用上:
参考C++是C语言的增强
6.了解基本术语
(1)类
类是用于描述事物的特征和行为
(2 )对象
对象是类的实例化的过程
(3)特征
是指变量或属性、成员变量
(4)行为
是指函数或方法、成员方法
(5)继承与多继承
一个新的类共享一个或多个已经存在的类的方法和属性
(6)聚合或组合
一个类包含了另外一个类的对象,称之为“组合和聚合”
(7)静态绑定
一个函数或表达式的地址在编译时就已经确定,称之为“静态绑定”
(8)动态绑定
一个函数或表达式的地址在运行时才确定,称之为“动态绑定”
(9)多态
一种接口,多种实现方法
7.介绍第一个C++程序
#include "iostream"
//#include宏,用于包含其它的库文件
//库文件:iostream //输入/输出流类:包含了输入流(用于输入数据)和输出流(用于输出数据)
//iostream i:input o:output stream:流
//iostream : istream(输入流)和ostream(输出流)
//istream:对象:cin ---- istream cin
//ostream:对象:cout ---- ostream cout
//符号:<> 或 “”
//<>括号括起来的库:编译器首先是从C++标准库寻找库文件,然后再从项目中寻找该文件
//“”括号括起来的库:编译器首先是从项目中寻找库文件,然后再从C++标准库中寻找该文件
//iostream后面没有.h后缀
//如果使用C++标准库里提供的库文件,则不需要加上后缀.h
//如果使用用户自己定义的库文件,则需要加上后缀.h
using namespace std;//使用的是C++提供的命名空间
//命名空间里存放了C++实例化的流对象:cin、cout、endl、cerr、clog等等
int main() //程序的入口地址
{
cout << "hello world" << endl;//第一个C++代码
//cout输出流对象:将数据从内存里取出来输出到控制台或显示器
//endl:结束换行
//符号“<<”(输出符号:数据从内存流向控制台):在C语言中表示移位运算符,在C++中对该运算符进行重载,表示数据的流动方向
//符号“>>”(输入符号:数据从控制台流向内存):在C语言中表示移位运算符,在C++中对该运算符进行重载,表示数据的流动方向
// “//”表示行注释 “/* 内容 */”表示块注释 快捷注释:ctrl+shift+/
return 0;
}
二、内存模型与名字空间
1.名字空间(命名空间)
一个项目里,有时候可能会出现两个一模一样的变量或函数或类等等,可能在同一个文件里出现标识符重名的情况,可以通过命名空间解决同一个文件中出现的标识符重名的问题。
2.名字空间的使用
(1)有名的命名空间
关键字:namespace
格式:
namespace 标识符
{
//函数、变量等等
}
解释:
从全局取出一块内存并对其进行命名(不能将名字空间声明在局部范围之内)
访问名字空间里的内容:
1)第一种访问方式
符号: “::” //域作用符号
格式:
名字空间名称 :: 访问的内容
例如: sum :: a;
2)第二种访问方式
关键字:using
格式:
using 名字空间 :: 指定的内容; //在当前作用域里声明了一个内容是来自于指定的名字空间,之后就可以直接使用指定的内容
3)第三种访问方式
关键字:using、namespace
格式:
using namespace 命名空间
(2)名字空间的嵌套
关键字:namespace
格式:
namespace 标识符1
{
namespace 标识符2
{
//嵌套其他命名空间
}
}
访问名字空间里的内容:
符号:“::”
格式:
名字空间1 :: 名字空间2 :: ...... :: 指定访问的内容
(3)命名空间可以无名
关键字:namespace
格式:
namespace
{
//变量或函数等等
}
访问名字空间里的内容:
符号: “::”
格式:
:: 指定访问的内容
3.内存模型
C++提供了另外的操作符用于去向堆区申请空间或释放,C语言里提供了malloc和free操作堆区内存空间,malloc和free只适用于基本的数据类型,C++提供了new和delete去操作堆区内存空间。
new=malloc:申请空间
delete=free:释放空间
(1)申请变量地址空间
操作符:new
格式:
数据类型 * 操作符 =new 数据类型; //使用new申请的内存空间大小就是数据类型的大小
数据类型:可以是基本的数据类型,也可以是自定义的类型(类类型)
申请变量地址空间时可以对空间进行初始化工作:
操作符:new
格式:
数据类型 * 标识符 =new 数据类型(初始值) //使用new开辟堆空间时同时进行初始化工作,初始值的类型是与数据类型相匹配
释放new申请的变量地址空间:
操作符:delete
格式:
delete 指针变量名;//释放指针所指向堆空间
(2)申请数组地址空间
操作符:new
格式:
数据类型 * 标识符 =new 数据类型[数组的大小] //向堆区申请所指定大小的数组空间
申请数组地址空间时,同时对空间进行初始化工作
符号:new
格式:
数据类型 * 标识符 = new 数据类型[数组的大小] {初始值1,初始值2,......}; //申请数组地址空间时对其进行初始化工作
释放new申请的数组地址空间:
操作符:delete
格式:
delete [ ] 指针变量名; //释放数组地址空间
4.C++里的作用域
(1)局部作用域
是指存在于函数体、代码块、循环体等等的内部,称之为“局部作用域”,局部作用域中的标识符的生命周期只存在于局部,超出了局部作用域声明周期即结束。
(2)全局作用域
是指存在于函数体、代码块、循环体等等的外部,即存在于整个文件中,称之为“全局作用域”,全局作用域中的标识符对于整个文件都是可见的
(3)命名空间作用域
是指在全局作用域中取出来的一块作用域并命名为命名空间(namespace),命名空间作用域中的标识符的生命周期也只存在于命名空间中
(4)类作用域
是指在全局作用域中声明了一个类,类就有一个作用域存在,作用域称之为“类作用域”,类作用域中的标识符的生命周期也只存在于类作用域中。
三、类和对象
1.类
类,是指一类事物的统称,可以描述一些事物的特征和行为,可以理解为是抽象的不真实的存在。
关键字:class-----用于声明一个类
格式:
class 标识符 //标识符就是类名
{
//特征-----变量&属性-----成员
//行为------函数&方法-----成员
}
2.对象
对象是类的实例化(具体化)的过程,特指某一个对象,相当于是真实存在的对象。
如何进行实例化:
(1)栈区进行实例化
格式:
类名 对象名 ;//在栈区实例化类,对象名是标识符
(2)堆区进行实例化
格式:
类名 * 指针变量名 =new 类名;//在堆区对类进行实例化
注意:使用完注意释放堆区空间,防止内存泄漏
类的成员访问方式:
(1)在栈区实例化的对象的访问方式
格式:
对象名.成员 //访问类的成员
(2)在堆区实例化的对象的访问方式
格式:
对象名->成员 // 访问类的成员
3.类的成员访问修饰符
目的是为了保护类的成员,C++提供了三种类的成员访问修饰符,分别是public(公共)、private(私有)、protected(受保护)
格式:
class 类名
{
访问修饰符1:
成员1;
成员2;
....... // 成员具备访问修饰符1的特性
访问修饰符2:
成员3;
成员4;
....... //成员具有访问修饰符2的特性
访问修饰符3:
成员5;
成员6;
....... //成员具有访问修饰符3的特性
};
(1)public修饰符
public下的成员对于类的外部是可以访问的
public下的成员对于类的内部是可以访问的
public下的成员对于派生类是可以访问的
public下的成员对于友元也是可以访问的
(2)protected修饰符
protected下的成员对于类的外部是不可以访问的
protected下的成员对于类的内部是可以访问的
protected下的成员对于派生类是可以访问的
protected下的成员对于友元也是可以访问的
(3)private修饰符
private下的成员对于类的外部是不可以访问的
private下的成员对于类的内部是可以访问的
private下的成员对于派生类是不可以访问的
private下的成员对于友元也是可以访问的
规定:
(1)一般将变量放在private属性下
(2)如果类中没有任何的访问修饰符,默认是放置在private属性下。
4.构造函数
C++提供一种特殊的函数,其实质与普通函数有点类似,作用一般是对类的成员变量进行初始化工作,存在于类中,如果用户没有显示的写出构造函数,那么编译器会自动生成一个默认的隐式的构造函数,如果用户显示的写出构造函数,那么编译器就不会生成构造函数。
构造函数的原型:
类名 (参数列表) //构造函数
{
}
(1)构造函数的调用时机
1)在创建对象时,会调用构造函数,去构造对象,构造隐式调用 //实例化对象时,会调用构造函数,如果构造函数带参,在实例化对象时需要传递参数
类的成员变量初始化方式:
1)类的对象调用成员变量进行初始化
2)类的构造函数参数对成员变量进行初始化
3)类的构造函数初始化列表的形式对成员变量进行初始化
格式:
类名 (参数列表) : 成员变量1(初始值1),成员变量2(初始值2),成员变量3(初始值3),.....
{
}
2)构造函数的显示调用:直接通过类名的形式
格式:
类名(实参);//构造函数的显示调用
5.析构函数
析构函数与构造函数类似,一种特殊的函数,作用:C++提供析构函数的作用是释放资源(用户向堆区开辟的空间,可以通过析构函数进行释放)
格式:
~类名()
{
}
析构函数的调用时机(编译会自动调用):
(1)当对象使用完成时(当对象超出当前作用域时),会自动调用析构函数
(2)当释放对象时,会自动调用析构函数
组合(聚合)中构造函数和析构函数的调用时机:
类A中实例化类B对象:先调用类B的构造函数,再调用类A的构造函数,调用类A的析构函数,调用类B的析构函数(先构造后析构,后构造先析构)
注:
如果类A中有类B的对象和类C的对象,构造函数的调用时机:
如果类B先实例化,则先调用类B的构造函数,后调用类C的构造函数
如果类C先实例化,则先调用类C的构造函数,后调用类B的构造函数
析构函数:先构造后析构,后构造先析构
总结:
(1)析构函数不能带有参数
(2)析构函数没有返回值
(3)析构函数不能发生重载
(4)析构函数是由编译器自动调用
6.拷贝构造函数
C++提出拷贝构造函数的作用是通过该函数能实现将原有对象的数据成员重新拷贝一份,用户没有显示的写出拷贝构造函数,那么编译器也会自动生成一个默认的隐式的拷贝构造函数,如果用户显示的写出拷贝构造函数,那么编译器不会自动生成拷贝构造函数。
拷贝构造函数
拷贝:复制,可以将原有对象拷贝(复制)一份
(1)拷贝构造函数的原型
格式:
类名 (const 类名 &obj)
{
}
拷贝又分为浅拷贝和深拷贝
类中没有涉及到指针(向堆区申请空间)或引用时,浅拷贝和深拷贝没有任何区别,都是将原来的数据成员拷贝一份给新的对象,类中涉及到指针(向堆区申请空间)或引用时,拷贝分为浅拷贝和深拷贝。
浅拷贝:
将原来的数据成员(指针对象)对象名拷贝一份,还将指针对象指向的地址也拷贝一份
深拷贝:
将原来的数据成员(指针对象)对象名拷贝一份,又重新申请一块空间用指针对象指向新的空间
如何实现深拷贝?
利用显示的拷贝构造函数实现 //利用深拷贝解决堆空间被重复释放的问题
7.友元
友元,称之为“朋友”,C++提出的友元是为了给类的外部提供一个访问私有成员的接口
友元:
友元函数:将函数声明为友元,称之为“友元函数”
友元类:将类声明为友元,称之为“友元类”
(1)友元函数
关键字:friend
格式:
friend 函数返回值 函数名 (参数列表)//声明一个函数为友元函数
步骤:
创建一个类
将函数声明为友元,放置在类中,表示函数是类的友元(朋友)
(2)友元类
关键字:friend
格式:
friend class 类名;//将类声明为一个友元类
8.this指针
C++为了方便类的使用,在类的普通成员函数中引入了this指针,this指针是作为成员函数的参数,this默认的隐式的
原型:
const 类名 * this;//this指针
访问类的成员格式:
this->function() //表示访问类的成员function函数
9.类的静态成员
将类的成员用static关键字进行声明,表示类的成员是静态成员
如果在类外对变量和函数用关键字static声明,与C语言一样。
(1)静态成员变量
关键字:static
格式:
static 数据类型 变量名; //将类中的变量设置为static类型
注意:类中的静态成员要求:
(1)可以在类中对静态成员设置初始值,满足静态成员是用const修饰为常量
(2)一般是在类中定义静态成员变量,在类外对静态成员变量进行初始化
类外对静态成员变量进行初始化的格式:
数据类型 类名 :: 变量名 = 初始值;
(3)类的非静态成员是与特定对象相对的(类的非静态成员是特定对象私有的),类的静态成员是类的所有对象所共有的(静态成员看成是全局成员)(静态成员只存在一份)
(2)静态成员函数
关键字:static
格式:
static 函数返回值 函数名 (参数列表)
{
}
静态成员的类外访问:
(1)通过类的对象访问类的静态成员
(2)直接通过类访问类的静态成员
格式:
类名::静态成员
静态成员注意:
(1)类的静态成员函数中不能访问类的非静态成员(非静态成员是某个对象特有的,如果访问就不知道是哪一个对象的非静态成员)
(2)类的静态成员函数中可以访问类的静态成员
(3)类的非静态成员函数既可以访问非静态成员也可以访问类的静态成员
(4)类的静态成员函数不含有this指针
10.const关键字
使用const关键字修饰类的成员表示类的成员是const属性
(1)const修饰成员变量
与C语言一样
关键字:const
格式:
const 数据类型 变量名;
(2)const修饰成员函数
作用:const修饰的成员函数,在该函数中不允许修改类的成员,即const修饰的成员函数是以只读的形式访问类的成员(保护类的成员)
关键字:const
格式:
函数返回值 函数名 (参数列表) const
{
}
注意:关键字const一定要放在函数名的后面,才起作用
四、运算符重载
(1)重载
重载分为函数重载和运算符重载
函数重载:函数名相同,函数参数类型、顺序、个体不同等即可发生函数重载
运算符重载:是指将基本的运算符赋予新数据类型(自定义的类型)的运算,其实质是将运算符重载强制修饰为函数形式
C语言:基本的预算符只适用于基本的数据类型
C++:基本的运算符满足不了自定义的数据类型,C++引入运算符重载实现自定义的类型的运算
哪些基本运算符可以发生重载?哪些运算符不能发生重载?
(2)重写
重写发生在具有继承关系的类中
重写:是指将原来的函数重写一遍,只不过函数的内容被更新
(3)覆盖/隐藏(屏蔽)
覆盖/隐藏发生在具有继承关系的类中
覆盖/隐藏:是指子类继承父类,子类中有与父类相同的属性和方法时,子类会将父类相同的属性和方法给覆盖或隐藏。
1.成员函数运算符重载
将运算符重载作为类的成员函数使用
关键字:operator
格式:
函数返回值类型 operator 重载的运算符 (参数1,参数2,......)
{
}
如何调用?
格式:
对象1 重载的运算符 对象2
注:如果是类的成员函数运算符,那么重载的运算符参数this指针必须传递一个参数
(2)普通函数(不是类的成员)运算符重载
关键字:operator
格式:
返回值类型 operator 重载的运算符 (参数列表)
(3)友元函数运算符重载
关键字:operator、friend
格式:
返回值类型 operator 重载的运算符(参数列表)
{
}
class 类名
{
friend 返回值类型 operator 重载的运算符(参数列表);
}
五、输入/输出流
1、流
通信的数据像水流一样,从一个地方流向另外一个地方,称之为“流”
2、输入流
数据从控制台或显示器等流向内存,称之为“输入流”,输入流中的数据在流动的时候经过了输入缓冲区,为了防止与输出数据相冲突
3、输出流
数据从内存流向控制台或显示器,称之为“输出流”,输出流中的数据在流动的时候经过了输出缓冲区,为了防止与输入数据相冲突。
4、标准的输入/输出流
iostream流类,集成了输入流类和输出流类
(1)输入流:istream类
istream,input stream,输入流,用于从控制台或显示器等获取数据,将数据存入内存
istream类,在C++的命名空间std中已经实例化为对象:cin------console input
符号:>> ,在C语言中表示右移运算符,在C++中对该运算符进行重载,变成了数据的流动方向,“>>”表示数据流入
原型:
istream & cin >> 基本的数据类型;
istream & cin >>基本的数据类型1>>基本的数据类型2>>.....; //输入多个数据,数据之间通过空格隔开
istream & cin >> char ;
istream & cin >> int ;
istream & cin >> string;
istream & cin >> const char *;
istream & cin >>long;
istream & cin >>double;
istream & cin >>float;
istream & cin >>格式控制符;
数据的输入:
当数据输入完成时,按下回车,告诉编译器,用户输入完成
(2)输出流:ostream类
ostream,output stream,输出流,用于从内存中获取数据,将数据输出到控制台或显示器
ostream类,在C++的命名空间std中已经实例化为对象:cout(console output)、cerr(consle errorr)、clog(console log)
符号:<< ,在C语言中表示左移运算符,在C++中对该运算符进行重载,变成了数据的流动方向,“<<”表示数据流出
原型:
ostream & cout << 基本的数据类型的数据;
ostream & cout << 基本的数据类型1<<基本的数据类型2<<......;
数据的输出:
(1)在应用程序执行完时,会自动刷新一次输出缓冲区
(2)遇到endl结束符时,也会自动刷新一次输出缓冲区
(3)当缓冲区BUSIZ装满时,也会自动刷新一次缓冲区
1、 用控制符控制输出格式
应当注意:这些控制符是在头文件iomanip中定义的,因而程序中应当包含头文件iomanip。
2.用流对象的成员函数控制输出格式 除了可以用控制符来控制输出格式外,还可以通过调用流对象cout中用于控制输出格式的成员函数来控制输出格式。 流成员函数setf和控制符setiosflags括号中的参数表示格式状态,它是通过格式标志来指定的。格式标志在类ios中被定义为枚举值。因此在引用这些格式标志时要在前面加上类名ios和域运算符“::”。
六、类的继承与多态
1、继承
是指一个新的类共享其他一个或多个已经存在的类的属性和方法,称之为“继承”,类似于生活中儿子继承父亲
单继承:一个新的类共享其他一个已经存在的类的属性和方法,称为“单继承”(单继承只有一个父类)
多继承:一个新的类共享其他多个已经存在的类的属性和方法,称为“多继承”(多继承有多个父类)
直接基类:儿子的父亲,父亲就称为儿子的直接父类
间接基类:儿子的祖宗,祖宗就称为儿子的间接父类
新的类:子类或派生类
已经存在的类:父类或基类
单继承的格式:
关键字:class
class 子类名 : 继承权限 父类
{
};
多继承的格式:
关键字:class
class 子类名 : 继承权限1 父类1 , 继承权限2 父类2 ,......
{
}
2、继承权限
C++提供的继承权限有三种:public(公有继承)、protected(受保护继承)、private(私有继承)
(1)public(公有继承)
父类的public下的成员继承到子类的public修饰符下
父类的protected下的成员继承到子类的protected修饰符下
父类的private下的成员是没有被继承到子类中
(2)protected(受保护继承)
父类的public下的成员继承到子类的protected修饰符下
父类的protected下的成员继承到子类的protected修饰符下
父类的private下的成员是没有被继承到子类中
(3)private(私有继承)
父类的public下的成员继承到子类的private修饰符下
父类的protected下的成员继承到子类的private修饰符下
父类的private下的成员是没有被继承到子类中
注:
(1)父类的构造函数和析构函数没有被继承到子类中
(2)如果父类与子类中有相同的属性和方法,那么子类会自动屏蔽父类与子类相同的属性和方法
(3)父类的构造函数/析构函数和子类的构造函数/析构函数的调用时机?先调用父类的构造函数,再调用子类的构造函数,先释放子类的析构函数,再释放父类析构函数
(4)如果父类构造函数携带参数,子类实例化对象时怎么处理?使用子类构造函数初始化列表的形式传参
(5)多继承时父类的构造函数/析构函数与子类的构造函数/析构函数调用时机?
3、虚继承
C++引入虚继承解决的是菱形继承造成的二义性问题
虚继承:
是指在继承时,在继承权限前加上关键字virtual,就表示该继承为虚继承
格式:
class 子类 : virtual 继承权限 父类
{
}
或
class 子类 : 继承权限 virtual 父类
{
}
4、抽象类
显示世界中的一些事物,不能被实例化(具体化),用抽象类进行描述,即抽象类中只是描述事物具备的特性和行为,不具体实现行为;抽象类只是给子类提供一个基类
抽象类:
含有纯虚函数的类,称为“抽象类”,在子类中实现纯虚函数
虚函数:
成员函数前,加上关键字“virtual”,表示函数是“虚函数”
纯虚函数:
令虚函数等于0,称之为“纯虚函数”
虚函数格式:
virtual 函数返回值 函数名 (参数列表); //该函数是虚函数
例如:
virtual void function() ;//function函数是虚函数
纯虚函数格式:
virtual 函数返回值 函数名 (参数列表)=0 ;//表示该函数为纯虚函数
例如:
virtual void function()=0 ;//function函数是纯虚函数
5、多态
多态:
多种形态或多种实现方法,是指一种接口,多种实现方法,一种接口:通过父类接口,多种实现方法:子类中对于方法的实现不一样
构成多态性的条件:
(1)具有继承关系的类中
(2)子类重写父类的虚函数
(3)父类指针或引用指向子类
虚函数格式:
virtual 函数返回值 函数名 (参数列表); //该函数是虚函数
例如:
virtual void function() ;//function函数是虚函数
虚函数表:
当类中有虚函数时,程序在运行时编译器会创建一张虚函数表,用于存放类中的虚函数
静态绑定和动态绑定
静态绑定:
函数或表达式的地址在编译时就已经确定调用,称为“静态绑定”或静态联编
例如:
C语言中的函数调用
C++中的函数重载
动态绑定:
函数或表达式的地址在运行时才确定调用,称为“动态绑定”或动态联编
例如:
C++中的多态中的虚函数
6、虚析构函数
C++提出虚析构函数的作用:当父类指针或引用指向子类时,释放父类指针或引用会出现子类资源得不到释放的问题。
面试题:
当父类指针或引用指向子类时,释放父类指针或引用会导致什么问题出现?子类所占用的资源得不到释放
如何解决?采用虚析构函数
虚析构函数:
析构函数前加上关键字“virtual”,表示该函数是虚析构函数
格式:
virtual ~类名 ()
{
}
例如:
virtual ~People() //虚析构函数
{
}
七、异常
C++提供了一种容错(包容产生错误)机制,程序员可以主动产生错误(异常),并捕获错误,然后进行解决。
编译器出现的状况:
(1)C++程序中产生错误,编译器不会主动帮助用户去解决
(2)C++程序执行过程中,编译器不知道什么时候会产生错误(异常)
产生异常错误步骤流程:
(1)检查程序代码是否会产生异常
(2)如果产生异常,捕获(找到)该异常
(3)解决产生的异常
C++异常处理机制:
try代码块:作用是用于检查用户编写代码是否有问题
throw关键字:作用是用于主动产生异常
catch代码块:作用是用于捕获异常并处理异常
1、异常处理机制的使用
(1)try代码块
格式:
try
{
//用户编写代码
}
(2)throw关键字
格式:
throw 数据类型 ; //抛出的异常类型“数据类型”,数据类型:基本的数据类型和类类型
(3)catch(数据类型)代码块
格式:
catch(数据类型)
{
//对捕获的异常类型进行处理
}
注意:
(1)try代码块 后面必须紧跟着至少有一个catch块(处理程序)
(2)try..catch中如果产生了异常,catch块没有捕获异常并处理,就交给上一层的try...catch块,如果也没有处理,以此类推,直到所有的try...catch块都没有捕获该异常并解决,调用windows自带的terminate终止程序。
(3)如果有多个catch块,捕获异常的顺序是按照catch块先后的顺序进行捕获
(4)catch块的参数如果是“...”,表示捕获所有异常
(5)catch块如果是捕获所有异常,就将catch块放置在其它的catch最后面
(6)try....catch是可以发生嵌套
2、异常的嵌套
格式:
try
{
try
{
//嵌套try...catch块
}
catch(参数1)
{
}
catch(参数2)
{
}
catch(参数3)
{
}
......
}
catch(参数1)
{
}
catch(参数2)
{
}
catch(参数3)
{
}
...........
3、自定义的异常类型
C++里的所有异常都是继承自异常的父类:std::exception(所有异常的父类),std::exception异常父类中有what方法,可以通过what方法查看产生的异常
自定义异常需要继承自异常的父类std::exception,并重写what方法
class MyException :public exception
{
public:
MyException(const char *obj)
{
this->str = obj;
}
const char* what()const
{
return str;
}
private:
const char* str;//字符串
};
4、标准的异常类型:C++内置的异常
std::exception 该异常是所有标准 C++ 异常的父类。
std::bad_alloc 该异常可以通过 new 抛出。
std::bad_cast 该异常可以通过 dynamic_cast 抛出。
std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid 该异常可以通过 typeid 抛出。
std::logic_error 理论上可以通过读取代码来检测到的异常。
std::domain_error 当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument 当使用了无效的参数时,会抛出该异常。
std::length_error 当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range 参数超出有效范围
std::runtime_error 理论上不可以通过读取代码来检测到的异常。
std::overflow_error 当发生数学上溢时,会抛出该异常。
std::range_error 当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error 当发生数学下溢时,会抛出该异常。
八、模板:template
从模板开始进入泛型编程,模板:类似于画板,例如:画板画好了人像,眼睛的颜色(可以是五颜六色),模板分为模板函数和模板类
泛型:
泛:广泛,很多的意思
型:是指数据类型(基本的数据类型,也可以是类类型)
1、模板函数
将函数声明为模板,称为“模板函数”
关键字:template(用于声明模板)、typename/class(用于声明模板形参)
格式:
template <typename 模板形参1,typename 模板形参2,........> 函数返回值 函数名 (函数形参1 ,函数形参2,.........)//将函数声明为模板
{
}
或
template <class 模板形参1,class 模板形参2,........> 函数返回值 函数名 (函数形参1 ,函数形参2,.........)//将函数声明为模板
{
}
例如:
template <class T1 , class T2> void function(int a, int b);//function函数是模板
template <class T1 , class T2> void function(T1 a, T2 b); //表示函数形参的类型是泛型,T1、T2就可以表示任意的数据类型
注:
(1)template用于声明模板
(2)<>里的内容是模板形参,用关键字typename/class来声明
(3)模板形参名与函数形参名类似,都是C++规定的标识符,模板形参可以表示任意的数据类型
(4)函数返回值类型,可以是基本的数据类型,也可以是类类型,还可以是模板形参类型
(5)函数形参的类型,可以用基本的数据类型,也可以是类类型,还可以是模板形参类型
(6)模板形参T的数据类型是根据用户传递的函数实参数据类型而定
(7)模板形参和函数形参是一一对应
函数模板的调用:
(1)模板函数的隐式调用
与普通函数一致
格式:
函数名(实参1,实参2,.......);
例如:
function(10 ,20);
(2)模板函数的显示调用
格式:
函数名 <数据类型1,数据类型2,......>(实参1,实参2,.........);
例如:
function<int ,int >(10,10);//显示的指定模板形参的参数类型
2、类模板
将类声明为一个模板,称为“模板类”
关键字:template,typename/class
格式:
template <class T1, class T2,............>class 类名 //声明模板类
{
}
或
template <typename T1, typename T2,............>class 类名 //声明模板类
{
}
模板类的实例化:
格式:
类名 <数据类型1,数据类型2,..........>对象名(构造函数参数列表);//对模板类实例化
例如:
Test<int , char > test; //实例化模板形参
注:
类模板只有显示调用,不能像函数模板一样隐式调用
模板形参中有非类型参数:
非类型参数:
不是模板形参,而是基本的数据类型,存在于模板形参括号“<>”中
例如:
template <class T1, class T2,............,int >class 类名 //声明模板类
{
} //int称为非类型参数
九、智能指针
C语言中的指针,称为“裸指针”,C++引入智能指针的作用是为了防止内存泄露,帮助用户管理开辟的堆空间
引入智能指针的情况:
(1)用户使用裸指针向堆区开辟空间,空间使用完之后,忘记释放
(2)用户使用裸指针向堆区开辟空间,在一个地方使用时,释放掉该空间,但是另外一个地方空间还在使用中
智能指针:
智能:
用户向堆区开辟空间,不用手动的进行释放,利用智能指针帮助进行释放
指针:
C++智能指针不是C语言的指针,它实质上是一个类,该类重载了指针的相关运算(取地址&、->、*符号等等),称为“智能指针”
C++提供了智能指针:
(1)shared_ptr:共享指针
(2)weak_ptr:弱指针
(3)unique_ptr:唯一指针
(4)auto_ptr:自动指针(舍弃掉)
1、shared_ptr
帮助用户管理堆空间,该指针可以允许其它的智能指针一起管理同一块内存空间
引入引用计数机制:
每当一个智能指针管理空间时,引用计数机制+1,每当一个智能指针解绑该空间时,引用计数机制-1,当引用计数减到0时,会自动释放管理的堆空间
智能化:
(1)当引用计数减到0时,会自动释放管理的堆空间
(2)当智能指针超出它所在的作用域时,也会自动释放所管理的堆空间
每创建一个智能指针shared_ptr时,该指针会指向两个区域:
(1)一个区域是管理的堆空间
(2)一个区域是引用计数空间
如何使用shared_ptr共享指针:
shared_ptr是模板类
格式:
//shared_ptr<数据类型> 对象名=管理的裸指针;
shared_ptr<数据类型>对象名(管理的裸指针);
例如:
int* p = new int(10);//p->指向开辟的堆空间
int* q = new int(40);
Test* test = new Test;
shared_ptr<Test>share4(test);
//..几万行代码
shared_ptr<int>share1(p);//share1管理p指向的堆空间
shared_ptr<int>share2 = share1;//调用拷贝构造函数
shared_ptr<int>share3(q);
cout << "p指向的地址:" << p << endl;
cout << "share1指向的地址:" << share1 << endl;
cout << "share3指向的地址:" << share3 << endl;
cout << "*share1=" << *share1 << endl;
cout << "*p=" << *p << endl;
//查看引用计数值
cout<<"引用计数值:"<<share1.use_count()<<endl;
cout << "引用计数值:" << share2.use_count() << endl;
//查看所管理的堆空间
cout<<"管理的空间的地址:"<<share1.get()<<endl;
//解绑管理的堆空间
share2.reset();
cout << "引用计数值:" << share1.use_count() << endl;
cout << "引用计数值:" << share2.use_count() << endl;
cout << "管理的空间的地址:" << share2.get() << endl;
cout << "share2管理的空间的数据:" << *share2 << endl;
//重载指针的运算:*、&、->、.
*share1 = 20;//将share1的空间里的内容更新20
cout << "*share1=" << *share1 << endl;
cout << "*p=" << *p << endl;
//交换所管理的堆空间
share1.swap(share3);//将share1和share3管理的地址交换
cout << "share1指向的地址:" << share1 << endl;
cout << "share3指向的地址:" << share3 << endl;
2、weak_ptr
使用shared_ptr会出现资源被循环利用得不到释放得问题,weka_ptr的提出目的是为了解决该问题
使用与shared_ptr类似
3、unique_ptr
唯一指针,它只允许有一个智能指针管理一块空间,与共享指针相反
十、标准模板库STL
1、向量
vector相当于一个动态数组
数组方式连续存储,可以使用[] 符号进行随机访问,
#include "vector"
(1)初始化vector对象的方式:
vector<T>v1 ; //默认的初始化方式,内容为空
vector <T>v2(v1) ;//v2是v1的一个副本
vector<T>v3(n ,i) ;//v3中包含了n个数值为i的元素
vector<T>v4(n) ;//v4包含了n个元素,每个元素的值为0
(2)vector常用函数
empty():判断向量是否为空,为空返回真,否则为假
begin():返回向量(数组)的首元素地址
end(): 返回向量(数组)的末元素的下一个元素的地址
clear():清空向量
front():返回得到向量的第一个元素的数据
back():返回得到向量的最后一个元素的数据
size():返回得到向量中元素的个数
push_back(数据):将数据插入到向量的尾部
pop_back():删除向量尾部的数据
(3)遍历:
1、可以使用数组下标方式访问[i] 遍历
2、使用迭代器进行访问
vector<int>::iterator itor;//itor相当于指针
2、链表相对于vector向量来说的优点在于:
(a)动态的分配内存,当需要添加数据的时候不会像vector那样,先将现有的内存空间释放,在次分配更大的空间,这样的话效率就比较低了。
(b)支持内部插入、头部插入和尾部插入
缺点:不能随机访问,不支持[]方式和vector.at()、占用的内存会多于vector(非有效数据占用的内存空间)
#include "list"
(1)初始化list对象的方式
list<int>L0; //空链表
list<int>L1(3); //建一个含三个默认值是0的元素的链表
list<int>L2(5, 2); //建一个含五个元素的链表,值都是2
list L3(L2); //L3是L2的副本
list L4(L1.begin(),L1.end()); //c5含c1一个区域的元素[begin, end]。
(2)list常用函数
begin():返回list容器的第一个元素的地址
end():返回list容器的最后一个元素之后的地址
rbegin():返回逆向链表的第一个元素的地址(也就是最后一个元素的地址)
rend():返回逆向链表的最后一个元素之后的地址(也就是第一个元素再往前的位置)
front():返回链表中第一个数据值
back():返回链表中最后一个数据值
empty():判断链表是否为空
size():返回链表容器的元素个数
clear():清除容器中所有元素
insert(pos,num):将数据num插入到pos位置处(pos是一个地址)
insert(pos,n,num):在pos位置处插入n个元素num
erase(pos):删除pos位置处的元素
push_back(num):在链表尾部插入数据num
pop_back():删除链表尾部的元素
push_front(num):在链表头部插入数据num
pop_front():删除链表头部的元素
sort():将链表排序,默认升序
.....
(3)遍历方式
双向链表list支持使用迭代器正向的遍历,也支持迭代器逆向的遍历,但是不能使用 [] 索引的方式进行遍历。
list<int> List
list<int>::iterator iter = List.begin();
for( ; iter!=List.end() ; iter++)
{
cout<<*iter<<endl ;
}
3、队列
合并了 vector 和 list的特点,
优点
随机访问方便,即支持[]操作符和vector.at(n)
在内部方便的进行插入和删除操作
可在两端进行push、pop
缺点占用内存多
使用区别
如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
如果你需要大量的插入和删除,而不关心随机存取,则应使用list
如果你需要随机存取,而且关心两端数据的插入和删除,则应使用deque
#include "deque"
deque<int> d1; //创建一个空的双端队列d
deque<int>d2(n) ;//创建一个元素个数为n的队列
deque<int>d3(n,num);//创建一个元素个数为n的队列,并且每个元素为num
成员函数:
push_back() //在队尾插入元素
push_front() //在队首插入元素
insert(d.begin()+1,9); //第一个元素之后插入9
size() //双端队列的大小
empty() //判断是否为空
begin() //队首的指针,指向队首元素
end() //队尾元素的下一位作为指针
rbegin() //以最后一个元素作为开始
rend() //以第一个元素的上一位作为指针
erase() //删除某一个元素
clear() //删除所有元素
pop_front() //删除队首元素
pop_back() //删除队尾元素
deque<int>::iterator it; //迭代器