- 作者: 丶布布
文章预览:
- 一、返回类型
- 二、内联函数inline
- 三、函数杂合用法总结
- 四、const char*、char const*、char* const三者的区别
- 五、函数形参中带const
一、返回类型
前置类型: 在函数声明和定义的时候,把函数返回类型写到函数名字之前的形式,叫前置返回类型
void func(int a); //函数声明
void func(int a) //函数定义
{
return;
}
后置类型: C++11中,在函数声明和定义的时候,把返回类型写在参数列表之后的形式,叫后置返回类型
auto func1(int a) -> void; //函数声明
auto func1(int a) -> void //函数定义
{
return;
}
作用差异: 随着对类这种概念学习的不断深入,有一些成员函数返回类型非常复杂,这时使用后置返回类型会使得代码看起来比较清爽,容易看懂。
PS:建议函数声明放在.h
文件,函数定义放在.cpp
文件中,如果函数定义放在头文件A.h
中,当B.cpp
和C.cpp
都包含A.h
的时候,会报错:找到一个或多个多重定义符号(函数重复定义)。
二、内联函数inline
定义: 在函数定义前,增加关键字inline
,会使该函数变成内联函数。
适用: 当函数体很小,并且被频繁调用的函数,适合成为inline
函数被调用。
解析: 大家都知道,调用函数是需要消耗系统资源的,尤其是函数体很小,只有一两行的代码,但是我需要一个循环频繁调用,这种频繁调用一个函数体很小的小函数是很不划算的,因为调用一个函数需要将函数形参压栈(开辟内存),往堆栈里面放数据,函数调用完之后返回出栈,这种调用过程需要频繁消耗内存,可能函数代码执行只用了0.1s
,但是压栈出栈就用了1s
,所以压栈出栈这种内存操作很不划算。
用法:
//.h
inline int func2(int test)
{
return 1;
}
特性:
1、在编译阶段,当编译器识别到函数为inline
函数时,系统会尝试将调用inline
函数的动作替换为函数本体,以此来提升性能。
int main()
{
int abc = func2(5); //当编译器识别到func2为内联函数inline时
int abc = 1; //系统会尝试用int abc = 1(函数本地);来取代int abc = myfunc(5)(调用内联函数的动作);前者毫无疑问比压栈出栈这种正常的函数调用快很多
}
2、inline
函数只是我们开发者对编译器的一个建议,编译器可以尝试去做,也可以不去做,这取决于编译器的诊断功能,就是说,决定权在编译器,我们控制不了。
3、上面我们说过函数声明要放到.h
文件中,函数定义放在.cpp
文件中。而内联函数的恰恰相反,内联函数的定义就要放到.h
文件中,这样需要用到内联函数的.cpp
文件时,能够通过#include
直接把内联函数的源代码#include
进来。以便编译器找到函数本体的源代码,并尝试使用函数体内的实现语句替换函数的调用,达到更高效的目的。
缺点以及规避:
1、代码膨胀问题:内联函数的实现语句较多
如:
//.h 内联函数func2定义
inline int func2(int test)
{
int num;
num= test;
return num;
}
而我们调用内联函数的语句只有一句:
int main()
{
int temp = func2(5);
return 0;
}
即上面这种情况我们使用内联函数时,需要用内联函数的函数体(3句)替换一句调用函数(1句),很明显不太划算,造成了代码膨胀。
在举个夸张一些的例子,假设某函数的函数体很多,执行需要1000s
,那么将其转换成内联函数其实也就节省个压栈出栈的时间,比如1s
,那么整体来说使用内联函数与否已经意义不大了,所以我们一般要求内联函数的函数体要尽量小一些。
注意:各种编译器对inline函数的处理各不相同,有的可能比较智能,有的编译器看你内联函数有计算什么的比较复杂,可能会直接当成普通函数处理。所以要求inline
函数尽量简单,代码尽可能少,尤其是涉及到循环、分支、递归调用等语句、尽量不要出现在inline
函数中,否则编译器很可能会因为你写这些代码而拒绝让这个函数成为一个inline
函数。
拓展:
①constexper
函数,也要求函数体比较简单,否则会报错,我们可以看成是更严格的一种内联函数。
②#define
宏展开也类似于内联函数,但也有一些,比如类型检查方面的差别,这里不再展开说明,感兴趣的自行查阅。
三、函数杂合用法总结
1、函数返回类型为void
,表示无返回值类型。但我们可以调用一个返回类型是void
的函数,让它作为另一个另一个返回类型是void
函数的返回值。
void func_a()
{
return;
}
void func_b()
{
//return; //可以
return func_a();//这也可以
}
2、函数返回指针和返回引用的情况
情景1: 函数返回指针类型
int *func_c()
{
int tempValue = 9;
return &tempValue; //这不可以,因为函数执行完毕后,tempValue的内存被系统回收,不能在继续使用,返回出去毫无意义。
}
int main()
{
int *p = func_c();
*p = 6; //你往一个不属于你的地址写了数字。
return 0;
}
大家想一想上面的指针函数调用会出现什么问题?
上面的代码虽然可以正常编译通过,这时调用指针函数func_c()
时,系统会为函数内部的临时变量tempValue
分配一段内存地址,但是当调用结束时,临时变量tempValue
的地址会被系统回收,这时int *p = func_c();
中指针p
指向的是一段你不能操控的地址。*p = 6;
虽然将6
写进了指针p
指向的地址中,但是这段地址是不属于你的,造成的后果就是轻则运行一段时间啪—崩溃,重则运行时立马啪—崩溃。。
解决:将局部变量tempValue
改成全局变量,这样其不会因为函数执行结束而被系统回收内存,至始至终都会保留其内存地址。
情景2: 函数返回引用类型
int &func_d()
{
int tempValue = 9;
std::cout << "tempValue内存地址:" << &tempValue << std::endl;
return tempValue;
}
int main()
{
int &k = func_d();
std::cout << "k内存地址:" << &k << std::endl;
k = 7; //你往一个不属于你的地址写了数字。
return 0;
}
和情景1类似,都是往一个不属于你的地址写了数字
可以看到,虽然k
的地址和函数func_d
返回的引用地址相同,但是这块地址在传给k
之前已经被系统回收了,是不属于你的,所以不可以用。
还有其他调用方法:
int main()
{
int k = func_d(); //返回值承接一个整型值
std::cout << "k内存地址:" << &k << std::endl;
k = 7; //安全
return 0;
}
相信这里大家已经懵逼了,明明函数func_d
返回的是一个引用,为什么却能用一个整形值k
来接?
不过确实有这种调用,大家知道就好了,我感觉应该是编译器内部做了相关的转换,将函数返回的引用内部转换成了整形值,我们可以看到k
的地址已经不是函数func_d
调用结束后被释放掉的的局部变量tempValue
的地址了,而是被系统新分配了一块内存地址,所以可以对其的地址处进行存值。
3、没有形参可以保持形参列表为空,或者形参为viod类型
int func_e(void)
{
return 1;
}
4、如果一个函数我们不调用的话,则该函数可以只有声明部分,没有定义部分。
5、普通函数定义只能定义一次(定义放在.cpp
文件中),声明可以声明多次(每次include
一个头文件,都相当于声明了一次该头文件的函数)
6、C++
中,更习惯用引用类型的形参来取代指针类型的形参,提倡在C++
中,多使用引用类型的形参。
7、C++
中,函数允许同名,但形参列表的参数类型或者数量应该有明显的区别。
void func_f(int a)
{
return;
}
void func_f(double b) //可以,名称相同,形参类型不同
{
return;
}
void func_f(int a, int b) //可以,名称相同,形参数量不同
{
return;
}