函数基础
return_type function_name( parameter list )
{
body of the function
}
int max(int num1, int num2)
{
// 局部变量声明
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
执行函数的第一步是(隐式地)定义并初始化它的形参。因此,对于值传递来说,当调用函数时,首先创建一个名为val的参数类型变量,然后将它初始化为调用时所用的实参。
函数体是一个语句块。块构成一个新的作用域,可以在其中定义变量。形参和函数体内部定义的变量统称为局部变量(local variable)。它们对函数而言是“局部”的,仅在函数的作用域内可见。
static
static用于声明静态变量或静态对象,可以将局部变量定义成static类型,局部静态对象(local static object)在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。
也就是说,static对象无论定义在哪都是初始化执行,程序结束后销毁。
size_t count_calls() {
static size_t ctr=0; // 调用结束后,这个值仍然有效
return ++ctr;
}
int main() {
for (size_t i=0;i!=10;++i)
cout << count_call1s() << endl; // 1-10
return 0;
}
函数声明
函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义,在函数声明中,参数的名称并不重要,只有参数的类型是必需的。
return_type function_name( parameter list );
int max(int num1, int num2);
int max(int, int);
在分离式编译中,函数声明于头文件(.h) ,定义于源文件(.cpp)。
参数默认值
可以指定参数默认值
int sum(int a, int b=20){
int result = a + b;
return (result);
}
如果函数的某个参数已经有了默认值,那么从该位置往后从左到右都必须有默认值。
如果函数声明有默认参数,那么实现就不能有默认参数,因此一般在函数声明时指定默认参数。
函数指针
函数指针是指向某个函数的地址,可以使用函数指针来调用函数,当把函数名作为值使用时会自动转换为指针。
double cal(int); // 函数
double (*pf)(int); // 指针pf指向具有一个参数,其类型为int的函数, 返回值为double
pf = cal; // 指针赋值,等价于pf = &cal ,会自动进行转换
void estimate(int lines, double (*pf)(int)); // 函数指针作为参数传递
// 使用指针调用函数
double y = cal(5); // 函数调用
double y = (*pf)(5); // 推荐写法
double y = pf(5); // 这样也对, 但不推荐
配合typedef类型定义表示函数的别名,常用于函数回调。
typedef int (*Ptr)(int,int);
int add(int a,int b){
return (a+b);
}
int main()
{
Ptr pInt = add;
cout<<pInt(3,5)<<endl;
return 0;
}
参数传递
值传递与引用传递
值传递是最基础的传递,将实参的拷贝值传递给函数。
为加快性能同时可以改变参数,传递引用参数可以直接改变参数对象,为实参创建引用对象并交给函数使用。
void swap(int &x, int &y)
{
int temp = x;
x = y;
y = temp;
return;
}
int main(){
int a = 100;
int b = 200;
swap(a, b); // 相当于初始化创建a,b的引用类型,然后进行交换,直接改变引用参数对象
}
指针传递
与引用传递一样直接传递实际参数指针给函数使用,建议使用引用传递,使用指针传递多用于配合函数指针传递函数实现回调。
const引用参数
对参数加上const表示函数只能读取参数值,不能修改值,建议所有只读的引用参数都加上const,这样可以传递字面值常量,否则会导致编译错误。
string::size_type find_char(string &s,char c,string::size_type &occurs);
find_char("He11o World", 'o', &ctr); // 编译错误,形参应该设置为const &string
数组参数
数组不允许拷贝传值,因此只能传递指针,传递指针是无法得知数组长度的,因此只能再传递一个数组长度参数或者在函数内部直接迭代数组(指针) 。但是如果明确知道是一个数组,可以使用sizeof获取数组长度:
constexpr size_t sz= sizeof(ia)/sizeof(*ia)
main函数
程序的主入口
int main(int argc,char *argv[]){} // int main(int argc,char **argv){}
argc:函数参数数量argv:字符串数组,参数数组,第一个字符串为程序名称,因此传递给main的参数总是从argv[1]开始
在可执行文件后面输入空格分开的传入参数即可:exe argc1 argc2 。
函数重载
函数名称相同,参数类型,个数,顺序不同,提高复用性,函数重载的原理是编译器在编译.c文件的时候会给函数进行简单的重命名,在原函数名称前面加上_,如原函数名叫做add,编译后就叫做_add 。 c++在底层编译.cpp文件时,虽然函数名称一样,但是在后面添加了一些其他东西导致名称不一样,其中?表示开始,@@YA表示参数的开始,后面三个字符HHH和MMM分别表示函数返回值类型,两个参数类型,@Z表示函数名称结束,所以由于函数生成的符号表中的名称不一样,可以编译通过。
编译器通过把所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
inline内联函数
使用关键字inline声明内联函数,编译器会把内联函数里的内容写在调用内联函数处,相当于不用执行进入函数的步骤,直接执行函数体。
- 相当于宏,却比宏多了类型检查,真正具有函数特性;
- 编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数;
- 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数
其主要作用是将简单的函数声明为内联函数可以加快速度。
// 声明1(加 inline,建议使用)
inline int functionName(int first, int second,...);
// 声明2(不加 inline)
int functionName(int first, int second,...);
// 定义
inline int functionName(int first, int second,...) {/****/};
// 类内定义,隐式内联
class A {
int doA() { return 0; } // 隐式内联
}
// 类外定义,需要显式内联
class A {
int doA();
}
inline int A::doA() { return 0; } // 需要显式内联
优点
- 内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度
- 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会
- 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能
缺点
- 代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间
- inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接
- 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器
函数回调
回调函数:作为参数被传递给另一个函数的函数叫做回调函数(区别就在这,一般函数的形参接收的是一个基本类型的变量,而这个函数接受的参数是一个"函数",这个作为参数的函数就叫回调函数),回调函数是作为另一个函数的形参出现的。
- 一般函数:
function a(int a, string b),接收的参数是一般类型 - 回调函数:
function b(function c),接收的参数是一个函数c,这个函数就叫回调函数
回调函数是一种新的设计思路:自定义回调函数交给其他模块执行,其他模块不需要知道函数具体细节,只需要调用即可,如排序函数中传入的比较函数。
回调函数更重要的用法在于异步调用,节省时间,为什么前端代码里满地都是回调?程序员说用户点击按键后需要弹窗Hello。这里包裹alert的匿名函数就是一个回调。问题是用户什么时候点击无法知道。只好把函数整个传过去,用户什么时候点,什么时候调用。
button.addEventListener('click', () => alert('Hello!'));
传统调用方式:
res = request();
handle(res);
异步调用方式可以使用回调实现:
request(handle);
此时根本不用关心request什么时候真正获取结果,只需要把获取到结果后该怎么处理告诉request就可以了,因此request函数可以立刻返回,真正获取结果的处理可能是在另一个线程、进程、甚至另一台机器上完成。
函数指针实现
C风格的函数回调,首先定义函数指针,配合typedef传参直接传入函数指针实现回调
typedef int (*Ptr)(int, int);
int CallBack(Ptr pInt, int a, int b) {
return pInt(a, b);
}
int add(int a, int b) {
return (a + b);
}
int main()
{
cout << CallBack(&add, 3, 5) << endl;
}