c++ 函数重载、内联函数、引用、类与对象、构造/析构函数
函数重载
什么是函数重载
同一作用域、函数名相同,参数列表不同(参数的个数或类型不同)的函数构成重载关系。
注意:函数的返回值与重载无关。
C++是如何实现函数重载的
通过g++ -S xxx.cpp 生成 xxx.s文件可知
编译器会把函数的参数类型缩写后追加到函数名的末尾,只要函数的参数列表不同,最终生成的函数名就不一样,该过程叫作换名。
参数的类型转换
当调用函数时,会优先调用类型最精准的函数,如果没有再对实参进行类型提升,而不是直接出错。
注意:当有多个备选者时可能会出错,调用重载过的函数时,参数要精准。
重载和隐藏
子作域中的标识符会屏蔽上层作用域中的同名标识符,这种关系叫隐藏。
不同作域下的同名函数,那怕参数列表同,也不会冲突。
extern “C”
C++代码在调用函数时会优先调用换名过函数,但最终的调用目标可以是C编译器编译出的目标文件或库文件,这样会造成连接失败。
解决方法:在声明函数 extern “C” { } 包一个函数的声明,这样C++编译器就会以C语言的规则就调用函数。
指针的const属性会影响函数重载
默认形参
什么是默认形参
C++中的函数可以给参数设置一个默认值,当函数被调用时,如果调用者提供了实参则使用实参,如果没有提供实参则使用默认值。
默认形参的约束
1、如果只有一部分形参设置了默认参数,则这些参数要全部靠右边。
2、如果函数的声明和定义分开了,只需要在声明时设置默认参数即可。
3、设置的默认参数必须是常量、常量表达式或全局变量。
4、默认形参可能会引起重载函数的歧义。
内联函数
普通函数与内联函数
普通函数:会被编译器翻译成二进制指令,存储在代码段,当调用函数时,会在调用位置生成一句跳转指令,当执行到调用函数的代码时,会跳转到函数所在位置执行,执行完成后再返回。
内联函数:会被编译器翻译成二进制指令,存储在代码段,当调用内联函数时,编译器会从代码段中把内联函数的二进制指令拷贝到调用位置,当执行调用内存函数的代码时则直接执行不会发生跳转。
注意:内联是否生成由具体的编译器决定。
内联函数的优缺点
优点:执行速度快
缺点:最终的可执行文件会变大,代码段中有冗余。
什么情况适合内联
代码量少,调用的位置少,执行的次数多
内联函数与宏函数的相同点与不同点
相同点:都是用空间来换取时间(用代码增加的代价换取执行速度的提升)
不同点:
1、编译阶段拷贝、预处理阶段替换
2、会检查参数类型(安全,有局限必),不检查参数(不安全,通用)
3、有返回值,没有返回值(但可以有执行结果)
4、不会产生二义性,可能产生二义性
显式内联和隐式内联
显式内联:普通函数返回值前加 inline 关键字
隐式内联:结构、联合、类的成员函数会自动内联
引用
什么是引用
引用是一种取名机制。
int num = 10;
int& number = num;
number <=> num
引用有哪些用处
1、跨函数共享变量(获取函数的执行结果)(具体就是函数的参数里面加&类似指针参数要*,比如void func(int &num))
2、提高传参数效率(不需要任何的内存拷贝)
使用引用有哪些约束
1、引用必须初始化
2、引用不更换目标
3、当为了提高传参效率使用引用时,也需要 const。
4、引用常量时必须加 const
const int& num = 5;
5、引用临时数据,但必须加 const
const int& num = 3+3;
相比指针有哪些优点
1、安全
不可能有空引用,不可能越界,极少有野引用(悬空),
注意:返回局部变量的引用,就会产生野引用。
2、效率高
使用指针传参,至少需要拷贝4|8字节数据,而使用引用一字节也不用拷贝
5、指针与引用的相同点和不同点
相同点:
1、跨函数共享变量
2、提高传参数效率
3、都需要 const 保护
不同点:
身份:数据类型 取名机制
字节数:4|8字节 0字节
初始化:可以不初始化 必须初始化
特殊值:空指针、野指针 没有空引用、极少有野引用
目标:可以更换指向目标 不能更换引用目标
堆内存:方便配合堆内存 不擅长引用堆内存
可以定义指针型数组,不能定义引用型数组,但可以定义数组引用
int (&arr)[10];
可以定义多级指针,但不能定义多级引用。
强制类型转换
C++中重新设计一套类型转换规则,但C语言中的强制类型转换还可以继续使用。
static_cast<目标类型>(源类型对象) 静态类型转换(可以自动转换的)
源类型的目标类型之间必须存在隐式转换关系,否则报错。
const_cast<目标类型>(源类型对象) 去常类型转换
源类型具有const属性的指针 目标类型没有const属性的指针,其它必须相同,否则报错。
reinterpret_cast<目标类型>(源类型对象) 重解释类型转换
源类型的目标类型必须是整型或指针类型
dynamic_cast<目标类型>(源类型对象) 动态类型转换
源类型和目标类型之间具有继承关系
操作符别名
{ <%
} %>
[ <:
] :>
# %:
&& and
|| or
! not
!= not_eq
& bitand
| bitor
~ compl
面向对象和面向过程
面向过程:关注的是如何解决问题,以及解决问题的步骤。
面向对象:
抽象:先找出(想象)能解决问题的对象,分析该对象解决问题时需要的属性(成员变量)和行为(成员函数)。
封装:把抽象的结果设计成一个类(结构),并且给属性和行为设置相应的访问权限(public/protected/private)。
继承:
1、在设计类的时候考虑现有的类是否能解决一部分问题,如果有则继承该类,然后在此基础上进行扩展达到解决问题的目的,以此缩短解决问题的时间。
2、把一个复杂的问题拆分成若干个小问题,然后每个问题设计一个类去解决,最后使用继承把这些类型合并在一起达到解决问题的目的。
多态:
当发出一个指令时,系统会根据实际情况去执行相应的操作,这种特性就叫多态(指令的多种形态)。
如:重载过的函数,编译器会根据调用时的实参类型决定最终调用哪个函数,由于该过程发生在编译时,因此也中编译时多态。
注意:面向对象是从更宏观的解决去思考如何解决问题,这种方式解决问题的速度更快,更有利于团队合作,但具体到类的行为还是要以面向过程的方式去编程。
类和对象
什么是类和对象:
一种由程序员设计的数据类型,相当于C语言中的结构。
对象由类创建出的变量,相当于C语言中的结构变量,创建对象的过程也叫之前实例化。
类型的设计格式:
class 类名
{
private://可以省略
成员变量;
public:
成员函数;
};
注意:默认的成员默认权限是private
对象的实例化:
类名 对象名;(栈内存)
类名* 对象指针 = new 类名;(堆内存)
1、成员函数中可以直接访问成员变量。
2、一般会把成员变量设设置为private。
3、给需要外界使用的成员设计 getXxx、setXxx。
public:可以在任何位置访问
protected:可以在成员函数和子类中访问
private:只能在成员函数中访问
类的声明与实现:
在头文件声明类:
class 类名
{
定义成员变量;
public:
返回值 函数名(参数列表);
};
注意:如果类的代码量不多,可以直接在头文件全部实现。
在源文件实现类:
返回值 类名::函数名(参数列表);
构造函数和析构函数
构造函数:类的同名成员函数,当类对象实例化时会自动执行,一般负责对象的初始化和资源申请。
1、构造函数必须是public,否则无法实例化对象。
2、构造函数可以重载,可以有多个版本。
3、带参构造函数的调用:
Test t(实参);
Test* p = new Test(实参);
Test t(); // 函数声明
4、默认情况下,编译器会自动给类生成无参构造函数,如果显式实现了构造函数,则编译器不再生成无参构造,再调用无参构造就会出错。
Test t; // 调用无参构造
Test* p = new Test; // 调用无参构造
注意:一般实现了有参构造,需要再实现一个什么都干的无参构造。
析构函数:负责类对象销毁时自动执行的函数。
1、析构函数必须是public
2、没有参数,没有返回值
3、当对象的生命周期过期时或使用delete销毁对象时自动调用。
4、构造函数肯定执行,但析构函数不一定会执行。
5、如果没有显式实现析构函数,编译器会自动生成一个什么都做的析构函数。
注意:malloc和free不会自动调用构造函数析构函数。