C++Primer_ch6

第6章 函数


6.1 函数基础
6.2 参数传递
6.3 返回类型和return语句
6.4 函数重载
6.5 特殊用途语言特性
6.6 函数匹配
6.7 函数指针


6.1 函数基础

  • 典型的函数包括:返回类型函数名字0或多个形参组成的列表函数体
  • 形参和实参. 实参是形参的初始值,类型匹配
  • 函数的形参列表.函数的形参列表可以为空,但是不能省略。定义一个不带形参的函数,可以书写空的形参列表,也可以使用关键字void表示函数没有形参func(void)
  • 函数返回类型.void返回类型表示函数不返回任何值。函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针
  • 名字有作用域,对象有生命周期。形参和函数体内部定义的变量统称为局部变量。同时局部变量还会隐藏在外层作用域中同名的其他声明中。局部变量的生命起依赖于定义的方式。
  • 自动对象.当函数的控制路径经过变量定义语句是创建该对象,当到达定义所在的块末尾时销毁它,我们把只存在于块执行期间的对象称为自动对象。形参是一种自动对象
  • 局部静态对象.在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才销毁,在此期间即使对象所在的函数结束执行也不会对它有影响
  • 函数声明.函数名字必须在使用之前声明。函数只能定义一次,但是可以声明多次。函数声明不包含函数体,用一个分号(;)代替。函数三要素:返回类型、函数名、形参类型。函数声明也称作函数原型
  • 建议变量或函数在头文件声明,在源文件中定义
  • 分离式编译允许我们把程序分割到几个文件中,每个文件独立编译
#假设fact函数定义位于一个名为fact.cc的文件中,它的声明位于Chapter6.h的头文件中。fact.cc应该包含Chapter6.h的头文件。另外在名为factMain.cc的文件中创建main函数,main函数中将调用fact函数。要生成可执行文件,必须告诉编译起我们用到的代码在哪里,编译过程如下:
$ CC factMain.cc fact.cc
$ CC factMain.cc fact.cc -o main
#修改了其中一个源文件,只需要重新编译改动的文件。分离式编译
$ CC -c fact.cc
$ CC -c factMain.cc
$ CC factMain.o fact.o
$ CC factMain.o fact.o -o main

6.2 参数传递

  • 形参初始化的机理与变量初始化一样.形参的类型决定了形参与实参交互的方式。如果形参是引用类型,它将绑定到对应的实参上;否则将实参的值拷贝后赋给形参。当形参是引用类型时,它对应的实参被引用传递或者函数被传引用调用。和其他引用一样,引用形参也是它绑定的对象的别名。当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象,实参被值传递或者函数被传值调用
  • 如果函数无须改变引用形参的值,最好将其声明为常量引用
  • 使用引用形参返回额外的信息
  • const形参和实参.和其他初始化过程一样,当用实参初始化形参时会忽略掉顶层const。当形参有顶层const时,传给它常量对象或者非常量对象都是可以的。
  • 指针或引用形参与const.可以使用非常量初始化一个底层的const对象,但是反过来不行;同时一个普通的引用必须用同类型的对象初始化
  • 尽量使用常量引用.把函数不会改变的形参定义成普通的引用是一种比较常见的错误,这么做带给函数调用者一种误导,即函数可以修改实参的值
  • 数组形参.数组的两个性质:不允许拷贝数组、使用数组时通常会将其转换成指针。为函数传递一个数组时,实际上传递的是指向数组首元素的指针。如果传给print函数的是一个数组,则实参自动地转换成指向数组首元素的指针,数组大小对函数的调用没有影响。因为数组时以指针的形式传给函数的,所以一开始函数并不知道数组的确切大小,需要提供额外信息。管理指针形参有三种常用的技术:使用标记指定数组长度(要求数组本身包含一个结束标记)、使用标准库规范(传递指向数组首元素和尾后元素的指针)、显式传递一个表示数组大小的形参。当函数不需要对数组元素执行写操作的时候,数据形参指向const指针;只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针
void print(const int*);
void print(const int[]);
void print(const int[10]);
//等价
int i=0,j[2]={0,1};
print(&i);  // &i类型是int*
print(j);   //j转换成int* 并指向j[0]
  • 数组引用形参.
f(int &arr[10]);    //错误;将arr声明成引用的数组
f(int (&arr)[10]);  //正确;arr是具有10个整数的整数数组的引用
  • 传递多维数组.数组的数组
int *matrix[10];    //10个指针构成的数组
int*matrix)[10]; //指向含有10个数组的指针
//matrix指向数组的首元素,该数组的元素是由10个整数构成的数组
void print(int (*matrix)[10],int rowSize) {}
//等价定义
void print(int matrix[][10],int rowSize){}
  • main:处理命令行选项.
//给main传参,假定main函数位于可执行文件prog之内
//这些命令行选项通过两个可选的形参传递给main函数

prog -d -o ofile data0

int main(int argc,char *argv[]) {}
//第一个形参argc表示数组中字符串的数量,第二个形参argv是一个数组。因为第二个形参是数组,所以main函数也可以定义成:
int main(int argc,char **argv){}

//当实参传给main函数之后,argv第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为0.
argv[0] = "prog";
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;
//当使用argv中的实参时,可选实参从argv[1]开始
  • 含有可变形参的函数.两种方法:如果所有的实参类型相同,可以传递名为initializer_list的标准库类型;如果实参类型不同,可以编写特殊的函数,即可变参数模版,详细见16章。还有一种特殊的形参类型,即省略符,可以用它传递可变数量的实参。
    initializer_list:如果函数的实参数量未知但是全部实参的类型都相同。是一种标准库类型,用于表示某种特定类型的值的数组,initializer_list类型定义在同名的头文件中。和vector一样,initializer_list也是一种模版类型,与vector不同的是,initializer_list对象中的元素永远是常量值,无法改变。
    在这里插入图片描述
void error_msg(initializer_list<string> il)
{
	for (auto beg=il.begin();beg!=il.end();++beg)
		cout<<*beg<<" ";
	cout<<endl;
 
}

if (expected != actual)
	error_msg({"functionX",expected,actual});
else
	error_msg({"functionX","okay"})

省略符形参:是为了C++程序访问某些特殊C代码而设置的。

void foo(param_list, ...);
void foo(...);

6.3 返回类型和return语句

  • return语句终止当前正在执行是函数并将控制权返回到调用函数的地方。两种形式:return; return expression;
  • 无返回值函数.没有返回值的return语句只能用在返回类型是void的函数中。返回void函数不要求非得有return语句,因为在这类函数的最后一句会隐式地执行return。void函数中可以使用return语句,中间位置退出,类似于break
  • 有返回值函数.只要函数返回类型不是void,则函数内的每条return必须返回一个值,返回类型必须与函数的返回类型相同,或者能隐式地转换成。
  • 返回值.返回的值用于初始化调用点的一个临时量,该临时量就是函数调用点结果。如果函数返回引用,则该引用仅是它所引用对象的一个别名
  • 不要返回局部对象的引用或指针.函数完成后,它所占的存储空间也随之被释放掉,函数终止意味着局部变量的引用将指向不再有效的内存区域
  • 返回类类型的函数和调用运算符.如果函数返回指针、引用或类的对象,我们就能使用函数调用的结果访问结果对象的成员
  • 引用返回左值.调用一个返回引用的函数得到左值,其他返回类型得到右值。特别是,能为返回类型是非常量引用的函数的结果赋值
  • 列表初始化返回值.如果列表为空,临时量执行值初始化;否则返回的值由函数的返回类型决定
    在这里插入图片描述
    -主函数main的返回值.允许main函数没有return语句直接结束,编译器会隐式地插入一条返回0的return语句。main函数的返回值可以看做状态指示器。返回0表示执行成功,返回其他值表示执行失败,其中非0的值具体含义依机器而定。为了使返回值与机器无关,cstdlib头文件定义了两个预处理变量,可以使用这两个变量分别表示成功与失败:
int main()
{
	if(some_failure)
		return EXIT_FAILURE; //定义在cstdlib头文件中
	else
		return EXIT_SUCCESS; //定义在cstdlib头文件中
}
//因为它们是预处理变量,所以既不能在前面加上std::也不能在using声明中出现
  • 递归.如果一个函数调用了它的自身,不管这种调用是直接的还是间接的,都称为该函数为递归函数。函数将不断地调用自身直到程序栈空间耗尽为止。main函数不能调用自己
  • 返回数组指针.数组不能拷贝,函数不能返回数组,不过可以返回数组的指针或者引用。
    在这里插入图片描述
  • 声明一个返回数组指针的函数.
    在这里插入图片描述
  • 使用尾置返回类型.任何函数的定义都能使用尾置返回,但是这种形式对于返回类型比较复杂的函数最有效,比如返回类型时数组指针或者数组引用。尾置返回类型跟在形参列表后面并以一个->符号开头
//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*)[10]

  • 使用decltype.如果知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明函数返回类型。
    在这里插入图片描述

6.4 函数重载

  • 如果同一作用域内的几个函数名字相同但是形参列表不同,称为重载函数。当调用函数时,编译器会根据传递的实参类型推断选择的函数。main函数不能重载

  • 不允许两个函数除了返回类型外其他所有的要素都相同

  • 重载和const形参.一个拥有顶层const的形参无法和另一个没有顶层const形参区分开来。另一方面,如果形参时某种类型的指针或引用,则通过区分其指向的是某常量对象还是非常量对象可以实现函数重载,此时const是底层。非const可以转换成const,所以下面四个函数都能作用于非const对象或者指向非const对象的指针
    在这里插入图片描述

  • const_cast和重载
     const_cast<string&>
    在这里插入图片描述
    在这里插入图片描述

  • 调用重载的函数.把函数调用与一组重载函数中某一个关联起来,叫函数匹配,也叫重载确定。调用重载函数时有三种可能的结果:
    最佳匹配
    无匹配的错误信息
    二义性调用

  • 当调用函数时,编译器首先寻找对该函数的声明,一旦在当前作用域找到了,编译器就会忽略外层作用域中的同名实体。在C++中,名字查找发生在类型检查之前
    在这里插入图片描述


6.5 特殊用途语言特性

  • 默认实参.默认实参作为形参的初始值出现在形参列表中。一旦某个形参被赋予了默认值,它后面所有的形参都必须有默认值。函数调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参。通常,应该在函数声明中指定默认实参,并将声明放在合适的头文件中。局部变量不能作为默认实参,除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参
  • 内联函数.内联函数可避免函数调用的开销,通常就是将它在每个调用点上“内联地”展开。
    在这里插入图片描述
    在shorterString函数的返回类型前面加上关键字inline,这样就可以将它声明为内联函数
    在这里插入图片描述在这里插入图片描述

-constexpr函数. 是指能用于常量表达式的函数。定义constexpr函数与其他方法类似,不过函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句。为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数。constexpr函数不一定返回常量表达式
在这里插入图片描述

  • 对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须完全一致。基于这个原因,内联函数和constexpr函数通常定义在头文件中

  • 调试帮助.程序可以包含一些用于调试的代码,但是这些代码只在开发程序时使用。当应用程序编写完成准备发布时,要先屏蔽掉调试代码,这种方法要用到两项预处理功能:assertNDEBUG
    assert是一种预处理宏。所谓预处理宏其实是一个预处理变量,有点类似于内联函数,assert宏使用一个表达式作为它的条件:assert(expr);对expr求值,若干表达式为假,assert输出信息并终止程序的执行,如果为真,什么也不做。assert宏定义在cassert头文件中。预处理名字由预处理器而非编译器管理
    NDEBUG.assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。
    在这里插入图片描述
    _ _ func _ _ 输出当前调试的函数名字。
    在这里插入图片描述


6.6 函数匹配

  • 确定候选函数和可行函数. 候选函数具有两个特征:一是被调用的函数同名,二是其声明在调用点可见。可行函数也具有两个特征:一是形参数量与本次调用提供的实参数量想等,二是每个实参的类型与对象的形参类型相同,或者能转换成形参的类型
  • 寻找最佳匹配基本思想是,实参类型与形参类型越接近,它们匹配得越好
f(int,int)
f(double,double)
// (42,2.56)
//二义性

  • 实参类型转换

6.7 函数指针

  • 函数指针指向的是函数而非对象。想要声明一个可以指向该函数的指针,只需要用指针替换函数名即可
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

发布了46 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/sinat_34686158/article/details/104405963
今日推荐