一、前言
小伙伴们好,我是 a n d u i n anduin anduin . 今天我们继续讲解 C++ 入门的知识,内容主要为两大块:函数重载和引用 ,这两块在 C++ 中可谓是重量级选手,特别是引用,学完使用会很舒适。虽然引用的点很多,但是没关系, a n d u i n anduin anduin 对它全方面进行了讲解。
话不多说,我们开始学习吧!
二、函数重载
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
对于c语言是不允许重名函数的存在的,当函数名字相同时,就会报错。但是对于 c++ 可以。
1、重载规则
当函数重载条件满足如下三条时,则可以构成函数重载:
- 参数类型不同
- 参数个数不同
- 参数类型顺序不同
对于 c++ ,同名函数是允许存在的:
// 参数类型不同 int add(int left, int right) { return left + right; } int add(double left, double right) { return left + right; } // 参数个数不同 int add(double left, double right, int mid) { return left + right; } // 参数类型顺序不同 int add(int left, char right) { return left + right; } int add(char right, int left) { return left + right; }
函数重载需要在同一个命名空间。
对于相同类型的数据,顺序不同,不构成函数重载,因为编译器无法识别:
int add(int right, int left) { return left + right; } int add(int left, int right) { return left + right; }
因为函数重载是根据类型识别的。
2、函数名修饰规则
对于函数重载后的函数,执行会不会变慢?不会,因为不是在运行时匹配,而是在编译时。
编译时如何进行识别?
对于编译而言,调用函数处会变成 call + add(地址) 的形式,然后通过汇编指令完成调用。
那么调用函数时,如何找到这个函数?
对于C语言来说,就是依靠函数名去找函数的,如果函数名相同,则会冲突,因为不知道找哪个.
证明修饰规则:
对于C++来说,不同平台就有不同的修饰规则,对于 vs 上比较复杂,暂且不谈;这里我们讲 linux 上的:
int add(int left, int right) { return left + right; }
对于这个函数,就会被修饰为 _Z3addii :
_Z 是前缀
3 是函数名长度
ii 代表参数类型的首字母
当编译时,就拿修饰以后的函数的名字去找,找到了就可以调用了。所以只要参数类型,个数,顺序不同均可以满足,因为此刻修饰后的函数名是可以被区分的。
从这里也可以看出为什么参数类型相同但是顺序不同不可以构成重载:因为识别不了。
Linux 下修饰规则(重要):格式: _ Z + 函数名称长度 + 函数名 + 类型首字母 \_Z+函数名称长度+函数名+类型首字母 _Z+函数名称长度+函数名+类型首字母
证明:
code:
#include <iostream> using namespace std; int add(int left, int right) {} int main() { return 0; }
编译自定义名称为 mytest :
使用 objdump -S exeName
查看修饰规则,exeName 为可执行程序名称:
发现名字是符合修饰规则的。
修改代码,再次验证:
#include <iostream> using namespace std; int add(int left, int right) {} int adddd(int left, double right) {} char subb(double* left, int right) {} // 验证指针 int main() { return 0; }
对于指针参数,则会在参数类型首字母前加上大写P修饰,P代表point,表示它是个指针 :
对于相同名字的函数,函数重载就根据参数的类型,顺序,个数,以这些为基准,来区别不同的函数。
而根据上面的验证,我们也知道为什么 返回值不同 和 参数类型相同但顺序不同 为什么不能构成函数重载的原因:
因为对于 参数类型相同但顺序不同,形成的后缀还是一样的 ,并不能区分该调用哪个函数;而对于返回值不同的其他都相同的函数来说,则是因为分不清调用哪个函数,不仅仅是因为函数返回值不在修饰规则内。
比如 int add() 和 double add() ,在函数调用时,我该调用哪个?编译器在这时候就错乱了,根本上是语法层面的问题。
Windows 下修饰规则(简单了解):
去除函数定义,主函数调用后报错:
三、引用
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
别名,又可以说是外号,代称,比如水浒传里几乎是别名最多的地方。李逵,在家称为"铁牛",江湖上人称"黑旋风"。铁牛和黑旋风就是李逵的别名。
1、区分
&
就是引用,但是&
这个操作符和取地址 & 操作符是重叠的。
所以它们需要不同的场景规范:
当 &b
单独存在时,这时就代表取地址,为取出变量的地址。
但是如果这样:
int main() { int a = 10; int& b = a; // 引用 int* p = &b; // 取地址 return 0; }
当 &
位于类型和变量名之间时,为引用。
2、本质
调试查看一下 a 和 b 的关系:
我们发现a和b不仅值相等,连地址也是相同的。而这就说明,b 就是 a ,在语法层面上,这里 b 并不是开辟的新空间,而是对原来的 a 取了一个新名称,叫做 b 。
就好比李逵被叫做黑旋风一样,李逵还是李逵,黑旋风也是它;而 a 就是 a ,但是 b 也是 a 。
而如果这时候对 a 或 b 任意一个修改,那么 a 和 b 都会发生修改。
3、特性
引用有三个注意点。
1) 引用必须在定义时初始化
引用是取别名,所以在定义的时候必须明确是谁的别名。
2)一个变量可以有多个引用
就和李逵一样,他可以叫黑旋风也可以叫铁牛,这都是它。
所以一个变量也可以有多个别名。
而对于一个起过别名的变量,对它的别名取别名也是可以的。
就好比说有人可能知道李逵也叫铁牛,并不知道他真实姓名,但是他觉得李逵很黑,于是叫他黑旋风,这也没问题,因为这里描述的都是一个人,同理,描述的也是同一个变量。
而从根本上看,就可以这么理解:
本质上还是一个变量。
但是别名不能和正式名字冲突,就比如取过别名,就不能定义和别名重命的变量,即使它们的类型不同。
但是这里的报错信息并不准确,实际上为命名冲突。
3)引用一旦引用一个实体,就不能引用其他实体
int main() { int a = 10; int& b = a; int c = 20; b = c; return 0; }
对于下一组代码,有什么含义?
- 让 b 变成 c 的别名?
- 还是把 c 赋值给 b ?
这里的代码意思是第二个含义,就是赋值,我们调试看看:
调试我们也可以看到,我们只是把 c 的值赋值给了 b ,b 的地址还是没变的 ,并且 a 的值也改变了。
这就说明引用一旦引用一个实体,就不能引用其他实体,引用是不会发生改变的。
因为它们是完全独立的两个变量,仅有的关联也只是值相等,改变 b 并不能影响 c ,但是此时 b 是 a 的别名,所以改变 b 会影响 a 。
图:
但是对于指针,则是截然不同的:
int main() { int a = 10; int c = 20; int* p = &a; p = &c; return 0; }
对于指针来说,指针可以时刻修改:
p原本指向 a ,现在指向 c .
但是引用也有局限性,因为引用之后的变量是不可修改引用的,比如链表,节点是要不断更替迭代的,所以还需要指针配合,C++才可以写出一个链表。