即使走的再远,也勿忘启程时的初心
C/C++ 游戏开发
Hello,米娜桑们,这里是君兮_,我之前看过一套书叫做《明朝那些事儿》,把本来枯燥的历史讲的生动有趣。而C++作为一门接近底层的语言,无疑是抽象且难度颇深的。我希望能努力把抽象繁多的知识讲的生动又通俗易懂,因此,咱们这个讲解C++的系列博客就叫做《C++那些事儿》啦,而今天我们要讲的内容是C++中的函数重载与引用
- 好了废话不多说,开始我们今天的学习吧!!
C++那些事儿
- 函数重载
- 函数重载的概念
- C++支持函数重载的原理--名字修饰(name Mangling)
- 引用
- 1.引用概念
- 2.引用特性
- 3.常引用
- 4.引用的使用场景
- (1).做函数的参数
- (2).做返回值
- 传值与传引用效率比较
- 引用和指针的区别
- 总结
函数重载
- 说到函数重载,很多人不理解重载是什么意思,其实它就在我们身边,我来举个例子:
一天,你的舍友有节课没去上课,但是碰巧老师上课点名点到他了,老师就问你:他来了吗?
你回答说:如来
老师又问你:到底来没来?
你回答说:如来
于是老师就把你请出教室了
- 自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了
- 那在C++中什么是函数重载呢?
函数重载的概念
- 函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
不同的问题
#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int x, int y)
{
cout << "int类型的Add" <<" "<< x + y << endl;
return x + y;
}
double Add(double x, double y)
{
cout << "double类型的Add" << " " << x + y << endl;
return x + y;
}
// 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;
}
int main()
{
int x = 0;
int y = 0;
Add(1, 5);
f();
f(10);
f(10, 'b');
f('a', 10);
return 0;
}
- 了解了函数重载的概念和基本使用方法后,很多人可能会想,为什么C++支持函数重载而C不支持函数重载呢?下面我们来讨论一下这个问题
C++支持函数重载的原理–名字修饰(name Mangling)
- 在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
- 这里对于没有接触过汇编语言以及编译链接的同学来说非常复杂,也不是我几句话就能说清楚的,因此大家简单理解记住结论即可,我也不会讲太深,随着之后我们C++之旅的进程,当我们接触到更多有关内容后,我再在合适的地方具体解释。
- 这张图片大概展示了在编译链接阶段各阶段进行的操作
- 由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,我们通过在linux下编译后生成的汇编来讲解这部分内容
- Linux环境下采用C语言编译器编译后结果
- 结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变
- 采用C++编译器编译后结果
- 结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中
最终结论(在目前学习阶段记住这个结论就行)
1.C语言之所以没办法支持重载,是因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
2. 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。
引用
1.引用概念
- 引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
- 在C++中,引用的作用类似于指针,但是它不开辟空间,在很多情况下,引用是比使用指针更好的选择。
- 至于起别名,引用就相当于这个变量的外号一样
比如 孙悟空,如果你是唐僧,你可以叫他悟空
如果你是猪八戒或者沙僧,你可以叫他大师兄
如果你是某地界的土地神,你可以叫他孙大圣
如果你是他的死对头,你可以叫他泼猴或者弼马温… - 上述的这些,都是孙悟空的"引用:,也就是别名
类型& 引用变量名(对象名) = 引用实体;
void Test()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
int main()
{
Test();
}
-
引用与原变量指向同一块空间
-
注意:引用类型必须和引用实体是同种类型的
2.引用特性
- 1. 引用在定义时必须初始化
- 2. 一个变量可以有多个引用
- 3. 引用一旦引用一个实体,再不能引用其他实体
void Test()
{
int a = 10;
int& ra = a;//<====定义引用类型
int& b = a;//一个变量可以有多个别名
printf("%p\n", &a);
printf("%p\n", &ra);
printf("%p\n", &b);
}
- 多个引用仍然指向同一块空间
- 必须初始化,不然会报错
3.常引用
void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
}
- 最常见的一种常引用,我们知道引用作为变量的别名,当改变引用的值时,是会改变变量的值的,因此当变量被const修饰时,它的引用也必须用const修饰
void TestConstRef()
{
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}
- 当我们直接吧一个常量给一个引用时,必须加const,原因与第一种情况相同,而常量就更不可能让你通过引用来修改它的值了
void TestConstRef()
{
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}
- 这里是存在一个类型的隐式转换的,把double类型的转换成int型,这里类型发生了转换,因此我们也不能通过别名来修改变量,因此必须加const
4.引用的使用场景
(1).做函数的参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
- 和指针在这里的使用方法是类似的,就不过多展开了
(2).做返回值
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
- 下面我们来看一个例子
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
- 因此,当我们运行时出现这种结果,也就不奇怪了
- 那应该怎么解决上述的这种问题呢?
- 很简单,我们让函数解释时,不会释放申请的空间就好了
int& Add(int a, int b)
{
static int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;
return 0;
}
传值与传引用效率比较
- 以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
- 总的来说,你在这里可以暂时把引用类比成指针来使用,等之后学习了类和对象之后,才能更加明白引用在使用时候的妙处
引用和指针的区别
- 在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
- 在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
int main()
{
//int& ret = Add(1, 2);
char c = 'a';
char& rc = c;
char* rrc = &c;
cout << "&rc"<<" "<< sizeof(c) << endl;
cout << "rrc" <<" "<< sizeof(rrc) << endl;
return 0;
}
- 在这里编译器告诉我们引用占一个字节,而指针占8个字节,可事实真的如此吗?
- 通过底层的汇编代码,我们可以知道,实际上,引用是按指针方式实现的是占空间的,至于为什么编译器告诉你它不占空间,因为在C++规定时说引用是一个别名是不占空间的,它总不能自己打自己脸呀
引用与指针的其他区别点
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
总结
-
好啦,我们今天的内容就先到这里啦!今天讲解了函数重载与引用使用方法以及有关它们使用的细节和注意事项,这两块的知识点会一直伴随你C++学习之路,是非常重要的,因此希望大家把有关的重点和难点多看几遍加深理解。
-
有任何的问题和对文章内容的疑惑欢迎在评论区中提出,当然也可以私信我,我会在第一时间回复的!!
新人博主创作不易,如果感觉文章内容对你有所帮助的话不妨三连一下再走呗。你们的支持就是我更新的动力!!!
**(可莉请求你们三连支持一下博主!!!点击下方评论点赞收藏帮帮可莉吧)**