[C++ Primer Note5] 函数

  1. 大多数类型都能用作函数的返回类型,一种特殊的返回类型是void,它表示函数不返回任何值。函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针。
  2. 在C++中,名字有作用域,对象有生命周期:
  • 名字的作用域是程序文本的一部分,名字在其中可见
  • 对象的生命周期是程序执行过程中该对象存在的一段时间
  1. 对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它。我们把只存在于块执行期间的对象称为自动对象(automatic object)。当块的执行结束后,块中创建的自动对象的值就变成未定义的了。
  2. 某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可将局部变量定义成static类型从而获得这样的对象。局部静态对象(local static object)在程序执行流第一次经过对象定义语句时初始化,并且知道程序终止时才被销毁,即使对象所在的函数结束执行也不会对它有影响。
    5.和其他名字一样,函数的名字也必须在使用之前声明。类似于变量,函数只能定义一次,但可以声明多次。函数的声明和函数的定义非常类似,唯一的区别是函数声明无须函数体,用一个分号替代即可。函数的三要素(返回类型,函数名,形参类型)描述了函数的接口,说明了调用该函数所需的全部信息,函数声明也称作函数原型(function prototype)
  3. 函数应该在头文件中声明而在源文件中定义,同时定义函数的源文件应该把含有函数声明的头文件包含进来,编译器负责验证函数的定义和声明是否匹配。
  4. 当形参是引用类型时,我们说它对应的实参被引用传递(passed by reference)。当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递(passed by value)
  5. 指针的行为和其他非引用类型一样,当执行指针拷贝操作时,拷贝的是指针的值,两个指针是不同的指针。因为指针可以使我们间接地访问它所指的对象,所以通过指针可以修改它所指对象的值,但本质上还是属于值传递
  6. 在C++中,建议使用引用类型的形参替代指针来访问外部对象。
  7. 拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本不支持拷贝操作。这种情况下函数只能通过引用形参访问该类型的对象。
  8. 如果函数无需改变引用形参的值,最好将其声明为常量引用
  9. 一个函数只能返回一个值,然而有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。我们可以利用引用形参来保存需要返回的多个值。
  10. 当用实参初始化形参时会忽略掉形参的顶层const,传给它常量对象或者非常量对象都是可以的。
  11. 数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别是,不允许拷贝数组以及使用数组时通常会将其转换成指针。所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。尽管不能以值传递的方式传递数组,但是我们可以形参写成类似数组的形式:
  • void print(const int*)
  • void print(const int[])
  • void print(const int[10]) 这里的维度只是我们期望的,实际上不一定
    尽管表现形式不同,但上面的三个函数是等价的:每个函数的唯一实参都是const int*类型的。
  1. 因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外的信息。管理指针形参有三种常用的技术:
  • 使用结束标记:比如C风格字符串
  • 使用标准库规范:传递指向数组首元素和尾后元素的指针
  • 传递一个表示数组大小的形参
  1. 当函数不需要对数组元素执行写操作时,数组形参应该是指向const的指针
  2. C++允许将变量定义成数组的引用,基于同样的道理,形参也可以是数组的引用。此时,引用形参绑定到数组上。但此时函数只能作用于固定大小的数组。
    //形参是数组的引用,维度是类型的一部分
    void print(int (&arr)[10]){
        for (auto elem : arr)
            cout<<elem<<endl;
    }
  1. 当将多维数组传递给函数时,真正传递的是指向数组首元素的指针,所以首元素本身就是一个数组。指针就是一个指向数组的指针,数组第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略
  void print(int (*matrix)[10]),int roSize);

上述声明将matrix声明成指向含有10个整数的数组的指针。

  1. 命令行选项通过两个(可选的)形参传递给main函数:
  int main(int argc,char *argv[]){...}
  1. 为了编写能处理不同数量实参的函数,C++11标准提供了两种主要方法:如果所有实参类型相同,可以传递一个initializer_list的标准库类型;如果类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板。除此以外,还有一种省略符形参,不过一般用于与C函数交互的接口程序。
  2. 返回void的函数不要求非得有return语句,因为这类函数最后一句后面会隐含执行return
  3. 在含有return语句的循环后面应该也有一条return语句,因为循环中的return不一定会被执行到
  4. 函数返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。如果返回引用类型,则不需要拷贝。
  5. 必须要注意的是,千万不要返回局部对象的引用或指针,因为函数完成后,它所占用的存储空间也随之被释放掉,所以要想确保返回值安全,我们不妨提问:引用所引的是在函数之前已经存在的哪个对象?
  6. C++11标准规定函数可以返回花括号包围的值的列表(这与之前所述的列表初始化语法实际上相照应),比如用于返回一个vector<int>类型。如果返回内置类型则花括号只能包含一个值。
  7. 之前介绍过,如果函数的返回值不是void,那么它必须返回一个值。但这条规则有一个例外:我们允许main没有return语句结束。如果控制流到达了main函数的结尾处而没有return语句,编译器将隐式地插入一条返回0的return语句。
  8. 因为数组不能被拷贝,所以函数不能返回数组,但是可以返回数组的指针和引用。使用类型别名可以简化声明过程。
  typedef int arrT[10];    //arrT表示含有10个整数的数组
  arrT* func(int i);    //func返回一个指向含有10个整数的数组的指针

如果不使用类型别名,返回数组指针的函数形式如下:

  int (*func(int i)) [10];

这里的*表示函数func的返回结果可以执行解引用操作。

  1. C++11标准中有一种可以简化上述func声明的方法,就是使用尾置返回类型(trailing return type)
  auto func(int i) -> int (*)[10];
  1. 还有一种情况,如果我们知道函数返回的指针指向哪个数组,就可以使用decltype关键字声明返回类型:
  int odd[]={1,3,5,7,9};
  int even[]={0,2,4,6,8};
  decltype(odd) *arrPtr(int i){
    return (i%2) ? &odd : &even;
  }

decltype并不负责把数组类型转换成对应的指针,所以decltype的结果是一个数组,要想表示返回指针还必须在声明时加一个 *

  1. 对于重载而言,顶层const不影响传入函数的对象。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。
  2. 对于底层const,形参是某种类型的指针或引用,则可以区分const。即使非常量也能被底层const所指,但是编译器会优先非常量版本的函数。
  3. 可以将const_cast和重载搭配起来使用
  4. 当有多个重载函数可以匹配调用但每一个都不是明显的最佳选择时也将发生错误,称为二义性调用(ambiguous call)
  5. 在不同的作用域中无法重载函数名
  6. C++同样支持默认参数,默认参数必须在参数列表的最右边
  string screen(int hz=24,int wid=80,char backgrnd=' ');
  1. 在给定作用域中一个形参只能被赋予一次默认参数,后续声明智能给没有默认值的形参添加默认实参。
  2. 只要表达式的值能转换成形参所需的类型,该表达式就能作为默认实参。
  3. 将函数指定为内联函数(inline),通常就是将它在调用点”内联地“展开。只需要在函数的返回类型前面加上关键字inline。内联函数一般用于优化规模较小,流程直接,频繁调用的函数。
  4. constexpr函数是指能用于常量表达式的函数,函数的返回值和所有形参类型都得是字面值类型。
  5. assert是一种预处理宏,assert宏用一个表达式作为它的条件:
  assert(expr);

如果表达式为假,assert输出信息并终止程序执行;如果为真,assert什么也不做。

  1. assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义NDEBUG,assert什么也不做。默认情况下没有定义NDEBUG,此时assert执行运行时检查。
  2. 函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型形参类型共同决定,与函数名无关。要想声明一个可以指向函数的指针,只需要用指针替换函数名即可。
  bool (*pf)(const string &,const string &);
  1. 当我们把函数名作为一个值使用时,该函数自动转换成指针。此时取地址符是可选的。
  2. 我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针。
  3. 指向不同函数类型的指针不存在转换规则,但是我们可以和往常一样为函数指针赋一个nullptr或者值为0的整型常量表达式,表示该指针没有指向任何一个函数
  4. 如果定义了指向重载函数的指针,指针类型必须与重载函数中的某一个精确匹配
  5. 和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用:
  void test(int pf(int));   //等价的声明
  void test(int (*pf)(int));

我们可以直接把函数名作为实参使用,此时它会自动转换成指针。

  1. 类型别名和decltype能让我们简化声明:
  //Func和Func2是函数类型
  typedef bool Func();
  typedef decltype(test) Func2();
  //FuncP和FuncP2是指向函数的指针
  typedef bool (*FuncP)();
  typedef decltype(test) *Func2();

因为decltype返回函数类型,所以要加上*才能得到指针。

  1. 和数组类似,虽然不能返回一个函数,但是能返回指向函数类型的指针。与往常一样,最简单的方法是声明一个类型别名,但此时返回类型不会自动转换成指针,我们必须显式地将返回类型指定为指针
  int (*f1(int)) (int *,int);  //也能直接声明一个函数指针返回值

我们还可以使用尾置返回类型的方式声明一个返回函数指针的函数:

  auto f1(int)->int (*)(int*,int);
  1. 如果我们明确知道返回的函数哪一个,可以参考上文所述返回数组指针一样使用decltype简化书写函数指针返回类型的过程。但记得要显式加上*表示我们需要返回指针
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容