C++ primer摘要(4)---函数

函数

函数基础

  • 函数是一个命名了的代码块,我们通过调用函数执行相应的代码,函数可以有0个活多个参数,通常会产生一个结果,可以重载函数,也就是说,同一个名字可以对应多个不同的函数
  • 一个典型的函数(function)定义包括以下部分
- [x] 返回类型(return type)
- [x] 函数名字
- [x] 0个或多个参数(parameter)组成的列表
- [x] 函数体
  • 函数的调用完成两项工作
- [x] 用实参初始化函数相对应的参数
- [x] 将控制权转移给被调用函数
  • 主调函数(calling function)的执行被暂时中断,被调函数(called function)开始执行
  • return语句完成两项工作:
- [x] 返回return语句中的值
- [x] 将控制权从被调函数转移回主调函数
  • 函数的形参列表可以为空,但不能省略
void f1(){/*......*/}       //隐式地定义空形参列表
void f2(void){/*......*/}   //显式地定义空形参列表
  • 形参列表中的每一个形参都是含有一个声明符的声明,必须写出类型
  • 任意两个形参不能同名
局部对象
  • 在C++语言中,名字有作用域,对象有生命周期
- [x] 名字的作用域是程序文本的一部分,名字在其中可见
- [x] 对象 的生命周期是程序执行过程中该对象存在的一段时间
  • 局部变量的生命周期依赖于定义的方式
  • 自动对象
- [x] 对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它,只存在于块执行期间的对象被称为`自动对象`
- [x] 形参是一种自动对象,函数开始时为形参申请存储控件,因为形参定义在函数体作用域之内,所以一旦函数终止,形参也就被销毁
  • 局部静态对象
- [x] 当有必要令局部变量的生命周期贯穿函数调用之后的时间时,可以将局部变量定义成`static`类型从而获得这样的对象
- [x] 如果局部静态变量没有显式的初始值,它将执行初始化,内置类型的局部静态变量初始化为0
- [x] 局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响
size_t count_calls(){
    static size_t ctr = 0;
    return ++ctr;
}

int main(){
    for(size_t i = 0 ; i != 10 ; ++i)
    {
        cout<
函数声明
  • 函数可以只声明不定义
  • 函数的声明和函数的定义类似,唯一的区别是函数声明无需函数体,用一个分号替代
  • 函数的三要素:返回类型 函数名 形参类型
  • 函数声明也称作函数原型
  • 函数的声明以及变量的声明都放在头文件
  • 含有函数声明的头文件应该被包含到定义函数的源文件中

参数传递

  • 每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化
  • 当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象,我们这样的实参被值传递或者函数被传值调用
传值参数
  • 当初始化一个非引用类型的变量时,初始值被拷贝给变量,此时,对变量的改动不会影响初始值
  • 熟悉C的程序猿常常使用指针类型的形参访问函数外部的对象,在C++语言中,建议使用引用类型的形参替代指针
  • 使用引用形参避免拷贝
- [x] 拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作,当某种类型不支持拷贝操作时,函数只能使用引用形参访问该类型的对象
  • 如果函数无须改变引用形参的值,最好将其声明为常量引用
const形参和实参
  • 当形参有顶层const时,传给它常量对象或者非常量对象都是可以的
数组形参
  • 和其他使用数组的代码一样,以数组作为形参的函数也必须确保使用数组时不会越界
  • 数组以指针的形式传递给函数,所以一开始函数并不知道数组的确切尺寸,调用这应该为此提供一些额外的信息以管理,如以下三种:
- [x] 使用标记指定数组的长度
    - 数组本身包含一个结束标记,使用这个方法的典型示例是C风格字符串,
    - C风格字符串存储在字符数组中,并且在最后一个字符后面跟着一个空字符,函数在处理C风格字符串时遇到空字符停止
    void print(const char * cp)
    {
        if(cp)
        {
            while(*cp)
                cout<<*cp++;
        }
    }
- [x] 使用标准库规范
    - 传递数组首元素和尾后元素的指针
    - 通过`begin()` && `end()`函数可以获取数组的首地址以及尾后地址
- [x] 显式传递一个表示数组大小的形参
    - 专门定义一个表示数组大小的形参,在C程序和过去的C++程序中经常使用这样的方法
void print(const char * cp , size_t size)
{
    for(size_t i = 0 ; i < size ; ++i)
    {
        ...
    }
}
  • 数组形参和const
- [x] 当函数不需要对数组元素执行写操作的时候,数组形参应该是指向const的指针,只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针
  • 数组的引用形参
- [x] 形参可以是数组的引用,引用形参绑定到对应的实参上,也就是绑定在数组上
void print(int (&arr)[10])
{
    ....
}
  • 传递多维数组
- [x] C++语言中实际上没有真正的多维数组,所谓多维数组实际上都是数组的数组
int * matrix[10];   \\10个int指针元素构成的数组
int (*matrix)[10];  \\指向10个int元素的数组的指针
main:处理命令行选项
  • 当用户通过设置一组选项来确定函数所要执行的操作时
int main(int argc, char * argv[])
{
    ......
}
  • 通过上述main函数的声明方式可以给main函数传递参数
  • 第一个形参argc表示数组中字符串的数量,第二个形参argv是一个数组,它的元素是C风格的字符串的指针
int main(int argc, char ** argv)
{
    ......
}
  • 当实参传给main函数之后,argv的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参,最后一个指针之后的元素值保证为0
  • 当使用argv中的实参时,一定要记得可选的实参从argv[1]开始;argv[0]保存程序的名字,而非用户的输入
含有可变形参的函数
  • 有时我们无法提前预知应该向函数传递几个实参,例如,我们想要编写代码输出程序产生的错误信息,此时最好用同一个函数实现该功能
  • 为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要方法
- [x] 如果所有实参类型相同,可以传递一个名为`initializer_list`的标准库类型
- [x] 如果实参类型不同,可以编写一种特殊的函数,也就是所谓的可变参模板函数
- [x] C++有一种特殊符号`省略符`,可以用它传递可变数量的实参
initializer_list形参
  • 如果函数的实参数量未知但是全部实参类型都相同,可以使用initializer_list类型形参
  • 与vector不同,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中的元素值
void error_msg(initializer_list il)
{
    for(auto beg = il.begin() ; beg != il.end() ; ++beg)
    {
        ......
    }
}
  • 如果想向initializer_list形参中传递一个值的序列,则必须把序列放在一堆花括号内
error_msg({"functionx","expected","actual"});
省略符形参
  • 省略符形参是为了便于C++程序访问某些特殊的C代码而设置的,这些代码使用了名为varargs的C标准库功能,通常,省略符形参不应有于其他目的
  • 省略符形参应该仅仅用于C和C++通用的类型,特别应该注意的是,大多数类类型的对象在传递给省略符形参时都无法正确拷贝
  • 省略符形参只能出现在形参列表的最后一个位置
void foo(int n,...);
void foo(...);

返回类型和return语句

无返回值函数
  • 无返回值的return语句只能用在返回类型是void的函数中
  • 返回void的函数不要求非得有return语句,因为这类函数的最后一句后面会隐式的执行return
有返回值的函数
  • 只要函数的返回类型不是void,则该函数内的每条return语句必须返回一个值,return语句的返回值的 类型必须与函数的返回类型相同,或者能隐式的转换成函数的返回类型
  • 在含有return语句的循环后面也应该有一条return语句,如果没有的话程序就是错误的,而编译器无法发现此类错误
  • 不要返回局部对象的引用或指针
- [x] 函数完成后,它所占用的空间也随之被释放掉,因此,函数终止意味着局部变量的引用将指向不再有效的内存区域
  • 返回类类型的函数和调用运算符
auto sz = shorterString(s1,s2).size();
  • 列表初始化返回值
- [x] C++11新标准规定,函数可以返回花括号包围的值的列表
vector process()
{
    ....
    ....
    return {"aaaa","bbbb","cccc"};
}
  • 主函数main的返回值
- [x] 我们允许main函数没有return语句直接结束,编译器会隐式的插入一条return语句
- [x] main函数的返回值可以看作是状态指示器,`返回0表示执行成功,返回其他值表示执行失败`,其中非0值的具体含义依机器而定
- [x] 为了使返回值与机器无关,cstdlib头文件定义了两个预处理变量
int main()
{
    if(some_failure)
        return EXIT_FAILURE;
    else
        return EXIT_SUCCESS;
}
  • 递归
- [x] 如果一个函数调用了它自身,不管这种调用是直接的还是间接的,都成该函数为递归函数
int factorial(int val)
{
    if(val > 1)
    {
        return factorial(val-1)*val
    }
    
    return 1;
}
- [x] 在递归函数中,一定有某条路径是不包含递归调用的
  • main函数不能调用它自己
返回数组指针
  • 因为数组不能被拷贝,所以函数不能返回数组,不过,函数可以返回数组的指针或引用
  • 使用尾置返回类型
auto func(int i)->int(*)[10]{......}
  • 将函数返回类型放在形参列表后,可以清楚的看到func函数返回的是一个指针,指向了含有10个int元素的数组
  • 任何函数的定义都能使用尾置返回
函数重载
  • 如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数
  • main函数不能重载

重载与作用域

  • 不要在局部作用域中声明函数(即不要在函数中声明函数)
  • 在C++语言中,名字查找发生在类型检查之前

特殊用途语言特性

默认实参
  • 某些函数有这样一种形参,在函数的很多次调用中被赋予一个相同的值,我们将这个反复出现的值成为函数的默认实参
void screen(int = 10 , string s = "hehe");
  • 通常应该在函数声明中指定默认实参,并将该声明放在合适的头文件中
  • 默认变量不能作为默认实参,除此之外,只要表达式的类型能转换成形参所需要的类型,该表达式就能作为默认实参
内联函数和constexpr函数
  • 内联函数可以避免函数调用的开销
  • 内联函数将在编译过程中展开,以消除函数运行时的开销
  • 内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求
  • 一般来说,内敛机制用于优化规模较小,频繁调用的函数,很多编译器都不支持内联递归的函数,而且一个75行的函数也不可能在调用点内联的展开
调试帮助
  • assert是一种预处理宏
assert(expr);
  • 首先expr求值,如果表达式为假(0),assert输出信息并终止程序的执行,如果为真(非0),assert什么也不做
  • assert宏定义在cassert头文件中
  • 含有cassert头文件的程序不能再定义名为assert的变量、函数或其他实体,实际编程中,即使我们没有包含cassert头文件,最好也不要为了其他目的使用assert
  • FUNC存放当前调试函数的名字,是const_char的一个静态数组
  • FILE存放文件名的字符串字面值
  • LINE存放当前行号的整型字面值
  • TIME存放文件编译时间的字符串字面值
  • DATE存放文件编译日期的字符串字面值

函数匹配

  • 调用重载函数时应尽量避免强制类型转换,如果在实际应用中确实需要强制类型转换,则说明我们设计的形参不合理

函数指针

  • 函数指针指向的是函数而非对象
  • 函数指针指向某种特定类型,函数的类型由它的返回类型和形参类型共同决定与函数名无关
  • 如下为例:
bool lengthCompare(const string & , const string &);

bool (*ptr)(const string & , const string &);     //ptr指向函数,该函数的参数是两个const string &的引用,返回值是bool类型

ptr = lengthCompare;    //ptr指向名为lengthCompare的函数
ptr = &lengthCompare;   //等价赋值语句:取地址符可选

//可以直接使用指向函数的指针调用该函数,无需提前解引用指针
bool b1 = ptr("hello","goodbye");
bool b2 = (*ptr)("hello","goodbye");        //等价调用
bool b1 = lengthCompare("hello","goodbye");  //等价调用
  • 在指向不同函数类型的指针之间不存在转换贵组
  • 可以为函数指针赋一个nullptr或者值为0的整型常量表达式

你可能感兴趣的:(C++ primer摘要(4)---函数)