一、函数重载
1、在同一个作用域下,函数名相同、参数列表(参数个数和类型)不同的函数构成重载关系
函数重载与返回值类型、参数名无关
2、C++是如何实现函数重载的
通过g++ -S xxx.cpp 生成汇编代码得知,编译器会把函数的参数类型缩写后追加到函数名后面,也就是说编译时会给函数进行换名
3、extern "C"
因为C++编译器在编译函数调用语句时,会找换名后的函数调用,这样就无法调用到已经使用C编译器编译成功的函数了
使用 extern "C"会让C++编译器按照C编译器的格式来翻译函数名,这样函数的声明与定义就匹配,就可以正确地调用C标准库、
系统库函数
4、重载和隐藏
在同一作用域下同名不同参的函数构成重载关系
在不同作用域(父子类)下同名函数遵循名字隐藏原则
5、参数的类型转换
当调用函数时,编译器会优先调用类型最精确的函数,如果没有则会做一定程度的类型提升,而不是全部直接报错,但具体优先级与编译
器有关,因此最优选择最准确的参数即可,否则容易产生函数调用的二义性
二、默认形参
1、什么是默认形参
C++中可以给函数的参数设置默认值,当函数调用者提供了实参则使用实参,如果没有提供则使用默认值
2、默认形参要靠右
如果函数有多个参数,设置了默认形参,必须遵循从右往左依次设置,否则有二义性
3、只能在函数声明处设置默认形参
如果函数声明与定义分开实现,只能在函数声明时设置默认形参,否则语法错误
4、默认形参可能会影响函数重载的效果
如果对同名函数进行了重载,又设置了默认形参,则调用时可能会有冲突
因此为重载过的函数设置默认参数时一定要小心
三、内联函数
1、普通函数
普通函数会被翻译成二进制指令存储在代码段中,调用语句是会生成一句跳转指令,并让程序跳转到该函数所在代码段处运行,运行结束
再返回。
2、内联函数
内联函数也会被翻译成二进制指令,调用语句不会生成跳转指令,而是直接把函数的二进制指令替换调用语句,这样就没有跳转也没有
返回,而是直接往下执行被调函数的代码
3、显示内联和隐式内联
显示内联:在函数的返回值前面加 inline , 则该函数就以内联机制调用,但不是所有编译器都支持内联机制,我们现在的g++
gcc都不支持
隐式内联:结构、联合、类中的成员函数会自动被当作内联函数处理
4、内联函数的适用条件
内联的优点:
节约了函数跳转、返回、传参的时间,提高了代码的运行速度
内联的缺点:
当多处调用内联函数时,它的二进制指令会被拷贝多份,产生代码冗余,导致最终的可执行文件增大
适用的条件:
1、适合内容简单且一次调用多次执行的情况,因此不适合内容多且调用少的函数,因为这样节约的时间还弥补不了牺牲的内存
2、带有递归属性的函数无法内联,编译器会自动屏蔽 inline 关键
5、内敛函数(inline)与宏函数(define)的相同点和不同点?
四、引用
什么是引用:引用是一种取别名的机制
为什么要使用指针:
1、跨函数共享变量(输出型参数)时,引用可替代
2、提高传参效率,引用可替代,且效率更高,引用不占字节
3、配合字符串时, string 可以替代
4、配合堆内存使用,继续使用指针
什么情况下适合用引用:
1、跨函数共享变量时,引用比指针安全(不存在空引用、极少出现野引用)、引用比指针更方便(不用取地址,也不用解引用)
2、提高传参效率,引用的传参效率比指针还高,因为指针还需要4/8字节用于存储内存地址,而引用一个字节都不需要,
但是引用和指针一样有被修改的风险,因此为了安全需要加const保护一下
重点: 指针与引用的相同点和不同点
相同点:
都可以跨函数共享变量、都可以提高函数传参效率、也都需要const保护
不同点:
1、引用是一种取别名的机制,而指针是一种数据类型
2、引用不需要额外的存储空间,而指针需要4/8字节内存来储存内存地址编号
3、引用不能更换指向目标,而指针可以更改指向
4、引用必须初始化,而指针可以不初始化
5、有空指针但是没有空引用
6、指针可以配合堆内存使用,引用不可以
7、可以定义指针数组,但不能定义引用数组(可以定义数组指针,也可以定义数组引用,可以定义函数指针,也可以定义函数引用)
使用引用时需要注意的问题:
1、引用必须初始化,不存在空的引用
2、可以引用右值,但必须使用const修饰
3、引用不可以修改目标
4、函数返回引用类型的数据时,不要返回局部变量的引用
五、强制类型转换
C语言中强制类型转换还能在C++中继续使用 因此C++中新的强制类型转换有点鸡肋
为什么 C++ 要重新设计新的强制类型转换?
因为C语言中的强制类型转换太危险
为什么 C++ 的强制类型转换设计得很复杂、使用麻烦?
因为C++之父认为只有在程序设计不合理情况下,才需要进行强制类型转换,之所以设计复杂就是不想让程序员使用,从而反思,
重新设计自己的代码
1、静态类型转换
static_cast<目标类型>(原数据)
目标类型与原数据类型之间必须有一个方向自动类型转换
2、动态类型转换
dynamic_cast<目标类型>(原数据)
目标类型与原数据类型之间必须存在继承关系,否则会出现错误
3、去常类型转换
const_cast<目标类型>(原数据)
目标类型与原数据类型必须是指针或引用,并且除了是否带const属性区别之外,其他类型都必须相同,否则出错
4、重解释类型转换
reinterpret_cast<目标类型>(原数据)
只能是把整数数据转换成指针、或者把指针转换成整数,否则出错
六、面向对象与面向过程
面向过程:
关注的是如何解决问题、以及解决问题的步骤
面向对象(四大特征)
抽象:先找出(想象出)能解决问题的"对象",分析该对象解决问题所需要的属性(成员变量)和行为(成员函数)
封装:把抽象的结果封装成一个类(结构),并给的类的成员变量、成员函数设置相应的访问权限(public/protected/private)
继承:
1、在封装类之前先考虑现有的类是否有能解决一部分问题,如果有则把现有的类继承过来,在此基础上进行扩展,
以此来节约解决问题的时间
2、把一个复杂的问题分析拆解成若干的小问题,每个问题设计一个类去解决,最后把这些类通过继承合并到一个能解决
最终问题的类中
多态:
发出一个指令、系统会根据实际的情况执行不同相应的操作,这种特征称之为多态(同一命令多种形态)
例如:重载过的函数,当调用函数时,编译器会根据参数的类型调用对应的重载版本,这就是一种多态,而且具体调用哪个版本
如果在编译时就能确定下来,这种重载称为编译时多态
特别注意:面向对象的行为细节依然是面向过程,因此面向对象只是从更高的维度去思考解决问题,而不是寻求解决问题的捷径
七、类和对象
什么是类和对象?
类是由程序员自己设计的一种数据类型,它里面包含了成员变量和成员函数两部分
而对象是类的实例化,其实可以理解为使用类创建的变量
类的设计和实例化
class 类型
{
成员变量;//类中成员默认属性是 private 私有
public:
成员函数;
};
实例化;
方法1: 类名 类对象名;
方法2: 类名* p = new 类型
类的声明、定义和实例化
1、在头文件中声明类
class 类型
{
成员变量;
public:
返回值 函数名(参数列表);
}; // 分号也不能少
2、在源文件中定义类
返回值 类名::函数名(参数列表)
{
// 成员函数内可以直接使用成员变量 不用.和->
}
注意: 如果类内的内容不多,可以考虑在头文件中全部实现出来
3、实例化
方法1: 类名 类对象名;
方法2: 类名* p = new 类型
注意:类不允许直接初始化成员变量
八、访问控制限定符
private
私有的,被它修饰的成员只能在类内访问,是类的默认访问属性
public
公开的,被它修饰的成员可以在任何位置访问,一般把类的成员函数设置为公开的
protected
保护的,被它修饰的成员可以在类内和子类中访问,但是不能在类外访问