"C++ Primer" 读书笔记 第七章 函数

  关于函数,我们比较关心函数参数的传递方式和函数是如何返回值的。此外还有内联函数、类成员函数、重载函数和函数指针等概念。

  函数可以看做是程序员自己定义的操作,和内置操作符相似(具体区别?)。与操作符一样,函数支持重载,即函数名可以复用。

  • 函数调用:实参初始化形参,然后控制权交给被调用函数,主调函数挂起,被调函数开始执行。
  • 函数体内部的变量都是局部变量,只在函数运行时存在,只在该函数的作用域内可见。
  • 当执行到return语句时,函数调用结束,被挂起的主调函数在调用处恢复执行,并将被调函数的返回值作为调用操作符的结果。
  • 形参就是形参,不是局部变量。形参是在函数的形参表中定义的(就是括号里啦!)并由实参初始化之。实参必须与形参相同(或者能隐式转换为形参的类型)。
  • 函数不能返回另一个函数或内置数据类型,但可以返回指向函数的指针,或指向数组元素的指针的指针。
  • C++静态强类型语言:定义变量时必须指定类型,函数调用时编译器将检查实参类型。

  参数传递

  1. 非引用形参

  只是拷贝了实参的值,并没有访问实参本身。

    • 指针形参 可以改变实参指向的对象的值,但不能让实参指向另一个对象。注意,形参是const int*, 实参可以是const int*或int*(可以指向非const对象),但形参为int*不能用const int*初始化,这样形参指针的对象的值就不能修改了。
    • const形参 非引用const形参,则既可以传递const对象也可以传递非const对象(实际上加const意义不大,编译器会去掉const支持兼容C语言)。
    • 需要修改实参的值、拷贝实参的开销太大、对象没法复制时:引用形参或者指针形参

  2. 引用形参

    比如实现swap函数。提倡在C++中使用引用形参而不用指针形参。

    • 使用引用形参返回信息(而不是仅仅靠return)
    • 使用const引用避免拷贝(例如大型数组,直接在原地修改比较好)
    • 如果对象只是单纯给函数的计算提供数据,请用const引用;如果需要在对象上操作,则用非const引用(不能用const对象、右值表达式与字面值初始化)
    • 如何让两个指针的值交换?注意是交换指针指向的地址,不是对象的值。
void swap(int * &p1, int * &p2){ // 这里&p1就是说p1只是实参int*对象的别名!很好理解
   /**/
} 

   3. 容器与数组作为函数参数

  将容器如vector作为形参,应该用&引用传参方式,避免拷贝;另外一种方法是利用迭代器形参。

  将数组作为形参,有三种方式:

void func(int a[]);
void func(int a[10]); // 此处长度并没有意义,编译器不会检查是不是真的长度一致
void func(int *a);

void func(int (&arr)[10]); // 引用传参,圆括号是必须的(下标优先级高)

  一般使用指针传递数组比较好。在实参是多维数组时,形参指针的类型不再是元素类型,而是包含元素的数组。

void func(int * p[10]);  // 存放10个指针的数组
void func(int (*p)[10]){
// p是指向包含十个元素的数组的指针
// (*p)[1]是第一个子数组
}
void func(int p[][10]); // 上面等价于

  传递给函数数组时如何避免越界错误?

  1. 在数组内部加结束符

  2. 传递给函数两个指针,begin与end(最后一个元素再下一个位置)

  3. 传递头指针和数组大小

 

   main函数的形参

  我们注意到main(int argc, char *argv[]){...}包含两个形参,这两个形参的值将取决于我们执行程序时输入的参数,argc将是输入参数的数量,argv则是一个字符串数组。

  含有可变形参的函数

  C++中有一种特殊的省略符形参

  返回引用

  函数总是需要返回一个值,用于初始化在调用函数处创建的临时对象。return的变量不是引用类型,则是把结果拷贝给临时对象。如果是引用类型,则直接返回引用的对象本身(没有拷贝操作)。注意不要返回局部变量的引用,因为函数执行完毕时,分配给局部对象的内存将被释放,会导致运行时错误。同理,也不要返回指向局部变量的指针。

  函数声明

  规范的做法是把函数声明放在头文件中。声明时可以指定函数的默认实参,最可能使用默认实参的放在最后!避免出现对应错误的问题。

  局部对象

  每个对象都有其作用域生命周期,即知道该对象的程序代码区域与程序执行过程中对象存在的时间。形参与局部变量在函数调用时以自动对象形式存在,在函数结束时均被撤销。静态局部对象,即static 局部对象,在函数内部定义的static关键字对象,其作用域只能是这个函数,但是生命周期与全局变量相同。在多次调用该函数时,静态局部变量将忽视定义初始化语句,而是上一次调用后的值。

  内联函数

  为什么需要内联函数?因为函数的调用(比求解一系列表达式)开销较大:保存寄存器状态、在返回时恢复;复制实参对形参进行初始化;转向一个新位置执行。

  内联函数为什么能减少开销?因为内联函数只在被调用到时,才像宏一样展开,取消了函数的参数压栈,省去了函数调用的开销。

  和宏有什么区别?

  宏只是在预处理cpp阶段做一个本质上是字符串替换的操作!不对参数进行检查,因此可能不安全;而内联函数是编译器控制实现的,能够检查参数(像函数一样实现!)。

  为什么不到处都用内联函数?  

  如果函数内计算其实很少!主要开销就是调用本身,那么推荐用内联函数。

  如果函数内部表达式本身也要一定的计算开销,实际上调用的开销占总的开销其实就不大了,效率提升很一般。

  适合用内联函数:反复调用、代码量很小,函数内部实际没怎么计算,(例如类的setValue(),getValue()这样的存取函数)

  不适合内联函数:代码量大展开臃肿、计算开销大、递归函数

  内联函数的定义应该放进头文件中。保证在需要调用内联函数时对编译器可见,在调用点出进行内联展开。头文件中对内联函数做了修改,则使用了该头文件的所有源文件都需要重新编译。

 

posted @ 2019-06-30 15:38  LiaoQian1996  阅读(87)  评论(0编辑  收藏  举报