文章目录
- 🚀前言
- 🚀函数重载
- 注意:
- ✈️为什么C++可以实现函数重载,而C语言却不行呢?
- 🚀引用
- ✈️引用的特性
- ✈️C++中为什么要引入引用
- ✈️引用与指针的区别
- 🚀内联函数
- ✈️内联函数特性
🚀前言
大家好啊!好久没更文了,课多还有就是备战前几天考完的蓝桥杯,好了不多bb,接着带大家从C语言过度到C++!!!
🚀函数重载
当年本贾尼,本大爷在编C++的时候觉得C语言中的函数不够方便,于是就整了个函数重载
函数重载:在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;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
return 0;
}
运行输出:
int Add(int left, int right)
double Add(double left, double right)
上面这两个个add
函数如果用C语言编写还不得整个add_int
和add_double
啊,这多麻烦,C++的函数重载简直不要太爽
- 形参数不同
void f(int a){
cout<<"void f(int a)"<<endl;
}
void f(int a,int b){
cout<<"void f(int a,int b)"<<endl;
}
int main(){
f(1);
f(2,3);
return 0;
}
运行输出:
void f(int a)
void f(int a,int b)
- 形参类型顺序不同
void f(int a,double b){
cout<<"void f(int a,double b)"<<endl;
}
void f(double a,int b){
cout<<"void f(double a,int b)"<<endl;
}
int main(){
f(1,2.0);
f(1.0,2);
return 0;
}
运行输出:
void f(int a,double b)
void f(double a,int b)
注意:
函数的返回类型不同并不构成函数重载
也就是:
void add(int a,int b){....}
int add(int a,int b){return 0}
上面的add
函数并不构成重载
为什么呢?我们接着往下看⬇️
✈️为什么C++可以实现函数重载,而C语言却不行呢?
这就与编译器有关了,一般C/C++的程序运行起来可以分为两个大的过程——编译和链接
,在程序编译过程中会将每一个函数解析成唯一的标识符,然后将函数标识符及其地址编成符号表(符号表,我们可以简单把它理解成是一张维护标识符(变量、常量、函数)以及其相关信息的映射表),而后的链接过程中遇到一个函数调用时,就会通过该函数的标识符在符号表中找到对应的函数地址并与函数调用关联起来。
而C++可以实现实现重载,C却不能,主要是因为函数在编译过程解析成标识符这一步不同
比如:
void add(int a,int b){}
上面的函数在C编译器上一般会被编译类似 add 这样的标识符
而在C++中则会编译成类似 addii 这种标识符,后面的两个i表示该函数有两个int类型的形参
就是因为C语言在处理函数标识符时只与函数名有关,而一旦函数名相同就造成函数重定义,所以就注定C语言无法构成重载,而C/C++对于函数标识符的处理都与返回值无关所以返回值不同无法构成重载
🚀引用
引用:语法层面上就是已定义变量的别名,与该变量共用一块内存空间
栗子🌰:
类型& 引用变量名(对象名) = 引用实体
引用的类型与被引用的变量类型一致
int a = 10;
int& b = a;
b = 5;
b 对于a的关系 就相当于你的名字和小名以及外号的关系 都指的是你
对b进行修改其实就相当于改了a
其实不妨这么理解,定义a就相当于想系统申请了一块空间名字是a,而引用b则是给这块空间又起了一个名字,如果再次int& c = b
就相当于这块空间又有了一个名字c,对a,b,c
操作实际上都是对这块空间操作,所以a,b,c
的值都会更改
✈️引用的特性
- 引用必须初始化:
这很好理解,引用就是所定义变量的别名,如果不初始化那么引用就没有所代表的内存空间
int main(){
int& a;
return 0;
}
不初始化就会报错
- 引用一旦引用实体后,就不能引用别的变量了
int main()
{
int a = 5;
int c = 1;
int& b=a;
b = c;//这样b就是c的别名了吗?
cout << a << " " << b << " " << c << endl;
return 0;
}
运行输出:
1 1 1
其中b = c
并非是b
成了c
的引用,而是将c
的值赋给b
,而b
是a
的引用,b
改了,a
也就改了
✈️C++中为什么要引入引用
C/C++都是追求效率的语言,而C++中引入的引用其实就是代替了C中指针的大部分职能,比如在函数传参过程中如果是传值调用,这样就会出现拷贝,这极大的降低了效率,而C语言中通常会通过指针使用传址调用提高效率,而C++中则可以使用传引用做到与C中指针的传址调用同样的效果,甚至更便捷、安全。
栗子🌰:
//通过传引用交换变量
void swap(int& a,int& b){
int tmp = a;
a = b;
b = tmp;
}
int main(){
int a = 0,b = 4;
swap(a,b);
return 0;
}
注意⚠️:引用可以作为函数的返回值,一旦引用对象的生命周期只在函数内则会造成,返回的引用属于悬空引用,即引用指向的空间已销毁
✈️引用与指针的区别
大家应该应该注意到上文对于引用,我只说引用在语法上是已定义变量的别名,与该变量共用一块内存空间,其实在底层实现上引用本质就是指针
注意: 引用只能完成指针较为简单的部分,但是不能替代指针!!
- 引用概念上定义一个变量的别名,指针存储一个变量地址
引用
在定义时必须初始化
,指针没有要求引用在初始化时引用一个实体后,就不能再引用其他实体
,而指针可以在任何时候指向任何一个同类型实体- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
有多级指针,但是没有多级引用
- 访问实体方式不同,
指针需要显式解引用,引用编译器自己处理
(编译器在底层帮我们创建指针)引用比指针使用起来相对更安全(因为引用必须初始化,而指针不用,没有空引用,但是有空指针)
- 在有些需要用二级指针的场景,较难理解,用引用就可以简化一点
🚀内联函数
内联函数其实是本大爷为了解决C中那让人恶心的宏函数而设计出来的
宏缺点:
1、不能调试(预处理阶段宏就被处理了)
2、没有类型安全的检查
3、有些场景下非常复杂,容易出错,不容易掌握
内联函数:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率
栗子🌰:
inline int add(int a,int b){
return a + b;
}
int main(){
int a = add(1,2);
return 0;
}
✈️内联函数特性
- inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用
缺陷:由于是在调用处展开,则代码量将会扩大,也就导致目标文件的增大.
优势:少了调用开辟函数栈帧的开销,提高程序运行效率
- inline对于编译器而言只是一个建议(所以内不内联得看编译器脸色),不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现不是递归、频繁调用的函数采用inline修饰,否则编译器将不会采用=内联方式
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到,也就是定义和声明在一块,因为如果不在一块链接过程就会出问题,因为内联后的函数被编译进符号表,遇到函数调用时就会出问题
栗子🌰:
//定义和声明在一块
inline int add(int a,int b){
return a + b;
}
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是阿辉前进的动力!