• C++ primer 查漏补缺九:第六章 函数


    函数的调用完成两项工作:

    1. (隐式地)定义并用实参初始化函数对应的形参
    2. 将控制权转移给被调用函数(主调函数的执行被暂时中断,被调函数开始执行)
    3. 当遇到一条return语句时函数结束执行过程。

    return语句也完成两项工作:
    一是返回return语句中的值(如果有的话),
    二是将控制权从被调函数转移回主调函数。函数的返回值用于初始化调用表达式的结果,之后继续完成调用所在的表达式的剩余部分。

    局部静态对象

    某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型从而获得这样的对象。

    局部静态对象(local static object)在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使
    对象所在的函数结束执行也不会对它有影响。

    举个例子,下面的函数统计它自己被调用了多少次,这样的函数也许没什么实际意义,但是足够说明问题:

    size_t count_calls()
    {
    	static size_t ctr =0; //结束调用后,值仍然有效
    	return ++ctr;
    }
    
    int main()
    {
    	for(size_t i=0; i!=10 ; ++i)
    	{
    		cout<<count_calls()<<endl;
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在控制流第一次经过ctr的定义之前,ctr被创建并初始化为0。每次调用将ctr加1并返回新值。每次执行count calls函数时,变量ctr的值都已经存在并且等于函数上一次退出时ctr的值。

    如果局部静态变量没有显式的初始值,它将执行值初始化(参见3.3.1节,第88页),内置类型的局部静态变量初始化为0。

    函数声明

    函数的名字也必须在使用之前声明。函数只能定义一次,但可以声明多次。函数的声明中可以省略形参的名字。

    如果一个函数永远也不会被我们用到,那么它可以只有声明没有定义。虚函数除外,所有的虚函数都必须有定义。

    函数的三要素(返回类型、函数名、形参类型)描述了函数的接口,说明了调用该函数所需的全部信息。
    函数声明也称作函数原型(function prototype)。

    参数传递

    值传参数

    当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递(passed by value)或者函数被传值调用(called by value)。

    当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会初始值。也就是说,形参的改变不会影响实参。

    传递指针:拷贝的是指针,会形成两个不同的指针。但是两个指针指向同一个地方,可以间接访问它所指的对象。

    传引用参数

    当形参是引用类型时,我们说它对应的实参被引用传递(passed by reference)或者函数被传引用调用(called by reference)。和其他引用一样,引用形参也是它绑定的对象的别名:也就是说,引用形参是它对应的实参的别名。

    对引用的操作实际上是作用在引用所指的对象上

    使用引用避免拷贝
    拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。

    使用引用形参返回额外信息
    一个函数只能返回一个值,然而有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。

    const形参和实参

    补充说明:
    顶层const:表示对象本身是一个常量,比如常量指针
    底层const:表示指针所指向的对象是一个常量

    int i=0;
    int *const pl =&i;// 顶层const  不能改变pl的值
    const int ci =42; // 顶层const  不能改变ci的值
    const int *p2 = &ci ; //允许改变p2的值,底层const
    
    • 1
    • 2
    • 3
    • 4

    *const 常量指针
    const * 指向常量

    数组形参

    数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别是:不允许拷贝数组(参见3.5.1节,第102页)以及使用数组时(通常)会将其转换成指针(参见3.5.3节,第105页)。因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。尽管不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式:以下三个等价

    void print(const int*);
    void print(const int[]);
    void print(const int[10]);//10只代表期望有10个数,但实际不一定
    
    • 1
    • 2
    • 3

    函数并不知道数组实际的大小,所以要注意数组越界。有以下三种改进方法

    使用标记指定数组长度

    要求数组本身含有结束标记,比如C字符串

    void print(const char*cp)
    {
    	if(cp)     //如果cp不是空指针
    	{
    		while(*cp)   //不是空字符
    		cout<<*cp++;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用标准库规范

    在这里插入图片描述
    在这里插入图片描述

    显示传递数组大小

    void print(const char*cp ,size_t size)
    
    • 1

    特殊数组

    在这里插入图片描述

    可变形参

    initializer_list

    如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用 initializer_list 类型的形参。initializer_list是一种标准库类型,用于表示某种特定类型的值的数组(参见3.5节,第101页)。initializer_list类型定义在同名的头文件中,它提供的操作如表所示。

    template<class T> class initializer_list;
    
    initializer_list<T> lst; 
    //默认初始化;T类型元素的空列表
    initializer_list<T> lst{a,b,c...};
    //lst的元素数量和初始值一样多;lst的元素是对应初始值的副本
    lst2(lst)   
    lst2=lst  
    //拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本元素共享
    lst.size()  //列表中的元素数量
    lst.begin()  //返回指向lst中首元素的指针
    lst.end()   //返回指向lst中尾元素下一位置的指针
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    使用方法

    //函数定义
    void error_msg(initializer_list<string> il)
    {
       for(auto beg=il.begin();beg!=il.end();++beg)
          cout<<*beg<<" ";
       cout<<endl;
    }
    
    //函数调用
    //expected和actual是string对象
    if(expected != actual)
       error_msg({"functionX",expectde,actual});
    else
       error_msg({"functionX","okay"});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    365天挑战LeetCode1000题——Day 080 寻找重复的子树 岛屿的最大面积 统计子岛屿
    小样本学习——匹配网络
    mmdetection环境配置和安装
    Node.js初体验
    最短路:leetcode1334. 阈值距离内邻居最少的城市
    JavaScript高级,ES6 笔记 第三天
    云服务器yum安装mysql
    打车APP开发平台具体解决了哪些出行问题?
    python上下文管理器
    微信小程序怎么测试
  • 原文地址:https://blog.csdn.net/fuyouzhiyi/article/details/126016136