一. 前言
小伙伴们大家好,今天我们继续学习c++入门知识,今天的内容主要是
二. 函数重载
1. 概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这 些同名函数的形参列表(参数个数 或 类型 或类型顺序)不同,常用来处理实现功能类似数据类型 不同的问题
可以理解为一词多义,比如一句话**“谁也赢不了”**,分别用在国乒乓队和国足的身上,就是两种截然不同的意思,大家细品。
int Add(int left, int right) { cout << "int Add(int left, int right)" << endl; return left + right; } double Add(double left, double right) { cout << "double Add(double left, double right)" << endl; return left + right; }
然而c语言不支持函数重载,上述代码运行会直接报错。
三种重载方式:
1· 参数类型不同
2、参数个数不同
3、参数类型顺序不同
//参数类型不同 int Add(int left, int right) { cout << "int Add(int left, int right)" << endl; return left + right; } double Add(double left, double right) { cout << "double Add(double left, double right)" << endl; return left + right; } // 2、参数个数不同 void f() { cout << "f()" << endl; } void f(int a) { cout << "f(int a)" << endl; } // 3、参数类型顺序不同 void f(int a, char b) { cout << "f(int a,char b)" << endl; } void f(char b, int a) { cout << "f(char b, int a)" << endl; }
2.函数名修饰规则
由之前的知识我们知道,函数调用需要建立栈帧,那么编译器是如何精确找到这些同名函数的地址呢?
c语言是根据函数名寻找函数,如果函数名相同,就会起冲突。
而c++通过函数名重载对函数的地址进行修饰
windows下的函数修饰名规则太复杂,所以我们转到Linux上对其讲解
Linux下修饰规则:格式:_ Z + 函数名称长度 + 函数名 + 类型首字母
代码段:
1 #include<stdio.h> 2 3 void add(int a,int b) 4 { 5 } 6 7 int main(){ 8 9 return 0; 10 }
进行编译形成可执行文件后,还需借助objdump -S exeName 查看修饰规则
结果为
我们发现c语言是直接根据函数名查找对应函数
改图为c++编译后的代码段
所以说:
对于相同名字的函数,函数重载就根据参数的类型,顺序,个数,以这些为基准,来区别不同的函数
但是不同返回值的函数不能构造函数重载
对于返回值不同的其他都相同的函数来说,在函数调用时,无法进行区分返回值,分不清调用哪个函数,不仅仅是因为函数返回值不在修饰规则内。
三 .引用(&)
1. 概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间
通俗来说就是取外号:
你很瘦,他人给你取了个外号叫“竹竿”,他的意思就是”竹竿“也代表了你
定义:
类型& 引用变量名(对象名) = 引用实体
实现如下:
int a = 10; int& b = a; b = 20; cout << a << endl;
可以看出,a和b共用一个地址,且b为(int &)类。
注意:引用类型必须和引用实体是同种类型的
2. 引用特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
前俩条很好理解,最后一个是指当引用初始化之后,就不能改变引用的指向了
可以看出,b的值变成了20,a的值也改变成为了20,但b的地址没有改变,说明了
引用一旦引用一个实体,再不能引用其他实体
3.应用
1.做参数
还记得c语言中的交换函数怎么实现的吗
void swap(int *a ,int *b) { int tmp=*a; *a=*b; *b=tmp; }
是不是感觉十分别扭,既要传指针,又要进行解引用,
现在我们可以用引用来进行简化
void swap(int& a ,int& b) { int tmp=a; a=b; b=tmp; }
传递过来的都是实参的别名,可以直接修改,无需解引用
2. 做返回值
看如下代码:
int add(int a, int b) { int c = a + b; return c; } int main() { int ret = add(1, 2); cout << ret << endl; return 0; }
我们在调用函数时,会建立函数栈帧,对于传值返回,如上述代码的add()函数,在函数调用完成后,函数栈帧销毁,c变量也会销毁,所以传会的并不是变量c,而是c的一份拷贝,是一个临时变量。
但是临时变量在哪?
如果 c 比较小(4/8 byte),一般是寄存器充当临时变量。
如果 c 比较大,临时变量放在调用 add 函数的栈帧中。
如果定义一个静态的变量呢,static修饰的变量储存于静态区,不受函数栈帧的影响,那么返回他会不会进行拷贝呢,答案是会的。
那么就会产生浪费,如何进行规s避呢
我们试一试传引用返回
int& add(int a, int b) { int c = a + b; return c; } int main() { int& ret = add(1, 2); cout << ret << endl; add(10, 20); cout << ret << endl; return 0; }
运行结果:
可知,ret为int&类,在第一次调用add()函数栈帧销毁后,ret任可以访问当初函数c的地址,所以在第二此调用add()后,ret的值被改为30;
也就是非法访问。
引用返回有一个原则:
如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
3. 传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低
通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大。
4.引用和指针的区别
int main() { //引用 int a = 10; int& ra = a; ra = 20; //指针 int* pa = &a; *pa = 20; return 0; }
我们看看反汇编码
发现,指针和引用的底层逻辑是一样的。
所以:
从语法概念上来说,引用是没有开辟空间的,而指针是开辟了空间的
但是:
从反汇编代码上来看,引用其实是开空间的,并且实现方式和指针一样,引用其实也是用指针实现的。
引用和指针的不同点:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
四 . 结语
大家好,我是展轩,
本次博客学习就到这里,如果觉得有帮助的话,记得
一键三连哦ヾ(≧▽≦*)o。