目录
一、函数重载的概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
注意:C语言是不支持函数重载的。
具体实现例子
同名函数的参数个数不同
#include<iostream>
using namespace std;
int Add(int x, int y) {
return x + y;
}
int Add(int x, int y, int z) {
return x + y + z;
}
int main() {
//同名函数的参数个数不同
int x = Add(1, 2);
int y = Add(1, 2, 3);
cout << "x = " << x << endl;
cout << "y = " << y << endl;
return 0;
}
输出结果:
同名函数的参数类型不同
#include<iostream>
using namespace std;
int Add(int x, int y) {
return x + y;
}
double Add(double x, double y) {
return x + y ;
}
int main() {
//同名函数的参数类型不同
int x = Add(1, 2);
double y = Add(1.2,1.2);
cout << "x = " << x << endl;
cout << "y = " << y << endl;
return 0;
}
输出结果:
同名函数的参数类型顺序不同
#include<iostream>
using namespace std;
int Add(int x, double y) {
return x + y;
}
double Add(double x, int y) {
return x + y ;
}
int main() {
//同名函数的参数类型不同
int x = Add(12.2, 2);
double y = Add(1.2,1);
cout << "x = " << x << endl;
cout << "y = " << y << endl;
return 0;
}
输出结果:
C++支持函数重载的原因
主要是由于在C++语法中,编译器会对函数名在编译的进行修饰。
而C语言是不会对其函数名进行修饰的,所以C不支持重载。
如下:
//gcc编译器下,编译器对函数名的修饰规则大致为:
//_Z3Adddi
//其中,_Z是固定前缀,Add是函数名,后面两个d i 指的是函数参数类型的首字母
double Add(double x, int y) {
return x + y ;
}
如此,便可以简单理解下为什么C++支持重载,并且了解为什么要同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同了。这样函数名经过编译器修饰后,便可以看成不同的函数来使用了。
补充:
如果两个函数的函数名和参数是一样的,返回值不同是不构成重载的。
二、引用
引用概念:
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
例如,一人之下国漫中,王也这个人物,有个外号叫也总,亲近点的人叫小王也。
也总是指王也这个人,王也这个人可以是也总,小王也也是王也也可以是也总。
总之都是指的同一个人。
所以,简单点来说,其实引用在现实生活中就相当于给变量取了个外号了。
特性:
1.引用在定义时必须要初始化
2.一个变量可以有多个引用
3.引用一旦引用一个实体,再不能引用其他实体
具体用法:
类型 & 引用变量名(对象名) = 引用实体;
int x = 0;
int& y = x;
这样,y也就是可以看作是x了,共用同一块内存空间。
实际应用场景:
场景一:做参数
主要是输出型的参数,形参的改变,影响实参。
场景的导入:
如果我们想使用一个函数来交换两个数的位置,代码你会怎么写呢?
是这样么?
void Swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
int main() {
int x = 3;
int y = 5;
Swap(x, y);//交换两个数的位置的函数
cout <<"x = " << x << endl;
cout <<"y = " << y << endl;
return 0;
}
运行结果如下:
以上我们发现其函数根本没有发挥作用,其数据根本没有进行交换。
那么这是为什么呢?
是因为在函数的调用中,形参只是实参的一份临时拷贝,对形参的改变或进行相关的操作不会影响到实参。
那么在C语言中会如何做呢?
如下:
C语言采用了指针的方法来解决,通过对指针进行解引用,来实现交换函数功能。
那么C++是如何解决的呢?
使用引用来解决这个问题就比较方便了,比使用指针方便一点
在调用参数的时候,对其参数进行引用就可以了,这样就可以把形参和实参看成同一个变量,于是在Swap函数调用时,对形参的改变,其同时也对实参进行了改变了。
注意:引用类型和引用实体必须是同种类型的。
场景二:做返回值
1.传值返回(函数返回时,返回对象已经还给系统时使用)
这里n变量已经还给系统了。
2.传引用返回(函数返回时,返回对象还没还给系统时使用。如:静态,全局,上一层栈帧,malloc出来的等)
这里n变量在静态区,未还给系统。
易错点一:
以上代码编译不通过。
这是因为 Count() 函数返回的是一个新创建的临时变量,即 n 变量的一个副本,只是值相同。并且该临时变量会被定义成 const 类型,所以无法修改。
解决方法是返回变量 n 的引用。即写成:
const int & ret = Count();
额外补充:
这里临时变量会被定义为const类型的主要原因是,在进行类型转换时,都会产生临时变量,即其需要转换变量的一个副本,只是他们两的值相等,并且该临时变量会被定义成const类型,所以无法修改,相当于一个常变量。
例如:
int i = 0;
double & di = i;//编译不通过。这里进行了类型转换。
const double & di = i;//编译通过
易错点二:
int& Mul(int a, int b) {
int c = a * b;
return c;
}
int main() {
int& ret = Mul(1, 2);
Mul(3, 4);
cout << "Mul(1,2) is:" << ret << endl;
cout << "Mul(3,4)is:" << ret << endl;
return 0;
}
输出结果:
Mul函数中 c 没用static 修饰的话,则会随着Mul函数的调用完成后,一同销毁掉。所以不能使用引用返回,得使用传值返回。
(这里的销毁指的是对这块空间没有使用权了,系统回收后可能会将这块空间存储其他数据,也可能继续暂时存储n的数据。)
所以如果这时继续使用传引用返回,则会造成返回结果未知。
传引用返回的实际应用:
#define N 10//定义数组的长度
typedef struct Array {
int a[N];
int size;
}Array;
//引用返回
//1.减少拷贝
//2.调用者可以修改返回对象
int& PosAt(Array& arr, int i) {
assert(i < N);
return arr.a[i];
}
int main() {
Array arr;
for (int i = 0; i < N; i++) {
PosAt(arr, i) = i * 10;//修改返回对象
cout << arr.a[i] << endl;
}
return 0;
}
常引用
无法对常量进行引用
int main() {
const int a = 10;
int& ra = a;//这里会报错
return 0;
}
补充:
补充一:
typedef struct Node{
struct Node* next;
int val;
}Node,*PNode;//给Node这个结构体起了两个别名,Node和一个它的指针名*PNode
void PushBack(PNode& phead, int x) {//这里的 PNode& 指的是对 Node*起的别名,对其的引用
Node* newNode = (Node*)malloc(sizeof(Node));
if (phead == NULL) {
phead = newNode;
}
}
补充二:
指针和引用,在赋值或初始化的时候,权限可以缩小,但是不能放大。
注意:这个前提是在使用指针或者引用的时候才算成立。
int main() {
//权限放大
const int a = 2;//只读不能写
int& b = c;//可读可写,编译不通过
const int* pi = NULL;//只读不能写
int* p2 = pi;//可读可写,编译不通过
//权限保持
const int d = 1;//只读不能写
const int& e = d; //只读不能写
const int* pj = NULL;//只读不能写
const int* p2 = pj;//只读不能写
//权限缩小
int f = 2;//可读可写
const int g = f;//只读不能写
int* pk = NULL;//可读可写
const int* p3 = pk;//只读不能写
return 0;
}
引用和指针的不同点:
1.引用概念上定义一个变量的别名,指针存储一个变量地址。
2.引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何-个同类型实体
4.没有NULL引用,但有NULL指针
5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7.有多级指针,但是没有多级引用
8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
三、内联函数
定义:
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
内联说的以时间换空间指的是编译时期的空间,并且内联不一定会调用,只是给编译器的建议。有可能会在编译的被call,也就是跳过。
//C++推荐
//const和enum替代宏常量
//inline去替代宏函数
//
//
//宏缺点:
//1、不能调试
//2、没有类型安全的检查
//3、有些场景下非常复杂,容易出错,不容易掌握
//
//
//要求实现ADD宏函数
#define Add(x,y) ((x) + (y))
inline int Add1(int x, int y) {
int z = x + y;
return z;
}
int main() {
//问题一:Add(x , y)(x) + (y) 外面的大括号不要可以么?不行
//如下,如果不加外面大括号,则会变成 (1) + (2) * 3.
//而原本你是想要 1 + 2 的 和 乘以 3 的 。但由于乘的优先级高于加法现在变成了 1 + (2 * 3)
Add(1, 2) * 3;
//问题二:Add(x,y) (x + y)里面的大括号不要可以么?不行
//由于宏是直接暴力替换的
//所以以下程序在不加里面的大括号的时候
//调用宏函数的时候会变成(x & y + x | y).
//而你原本是想要 x&y 的结果与 x|y的结果进行相加了。
int x = 1, y = 2;
Add(x & y, x | y);
//用内联函数来替代
int ret = Add1(1, 2);
cout << ret << endl;
return 0;
}
注意:内联函数的声明和定义应该合在一起,而不是分开。否则会产生链接错误。
假如一旦只在.h文件中对函数进行声明,在.cpp文件中对函数进行定义。那么main函数入口在调用这个函数的时候,只会包含以下.h文件,从而导致在编译阶段没有找到这个对应函数的定义,就会导致报链接错误!