一、函数基础
1、形参和实参
实参是形参的初始值。第一个实参初始化第一个形参,第二个实参初始化第二个形参,以此类推。尽管实参与形参存在对应关系,但是并没有规定实参的求值顺序。编译器能以任意可行的顺序对实参求值。
2、函数返回类型
函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针。
3、局部对象
对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它。我们把只存在于块执行期间的对象称为自动对象。如果自动对象变量定义时本身不含初始值,执行默认初始化。
某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型从而获得这样的对象。局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。如果局部静态变量没有显示的初始值,它将执行值初始化,内置类型的局部静态变量初始化为0。
#include <iostream>
int func()
{
static int count = ;
return ++count;
}
int main()
{
for (int i = ; i < ; ++i)
{
std::cout << func() << " ";
}
std::cout << std::endl;
return ;
}
4、函数声明
类似于变量,函数只能定义一次,但可以声明多次。函数的声明和函数的定义非常类似,唯一的区别是函数声明无须函数体,用一个分号替代即可。因为函数的声明不包含函数体,所以可以省略形参的名字。
二、参数传递
形参的类型决定了形参和实参交互的方式。如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋给形参。
1、指针形参
指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值。
2、const形参和实参
当用实参初始化形参时会忽略掉顶层const。
3、数组形参
1)为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
void print(const int *);
void print(const int[]);
void print(const int[]);//这里的维度表示我们期望数组含有多少元素,实际不一定
//尽管表现形式不同,但上面的三个函数是等价的:每个函数的唯一形参都是const int*类型的
2)数组引用形参
引用形参绑定到对应的实参上,也就是绑定到数组上。
void print(int(&arr)[]); // 只能将函数作用于大小为10的数组
3)传递多维数组
C++语言中实际上没有真正的多维数组,多维数组其实是数组的数组。和所有数组一样,当将多维数组传递给函数时,真正传递的是指向数组首元素的指针。因为我们处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针。数组第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略。
void print(int (*arr)[]); //arr指向数组的首元素,该数组的元素是由10个整数构成的数组
void print(int arr[][]); // arr的声明看起来是一个二维数组,实际上形参是指向含有10个整数的数组的指针
// 上面2个函数等价
4、main:处理命令行选项
有时需要给main传递实参,一种常见的情况是用户通过设置一组选项来确定函数所要执行的操作。例如我们假定main函数位于可执行文件DailyProject之内,我们可以向程序传递下面的选项:
DailyProject hello world
这些命令行选项通过两个可选的形参传递给main函数:
#include <iostream>
#include <string> int main(int argc, char *argv[])
{
std::cout << "argc:" << argc << std::endl;
for (auto i = ; i < argc; ++i)
{
std::cout << "argv[" << i << "]:" << argv[i] << std::endl;
}
return ;
}
第一个形参argc表示数组中字符串的数量;第二个形参argv是一个数组,它的元素是指向C风格字符串的指针。
当实参传递给main函数之后,argv的第一个元素指向程序的可执行文件的名字,接下来的元素依次传递命令行提供的实参。
5、含有可变形参的函数
为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型;如果实参的类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板(618)。
1)initializer_list形参
如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用initializer_list类型的实参。initializer_list是一种标准库类型,用于表示某种特定类型的值的数组。initializer_list定义在同名的头文件中,它提供的操作如下表所示:
操作 | 说明 |
initializer_list<T> lst | 默认初始化;T类型元素的空列表 |
initializer_list<T> lst{a,b,c,..} | lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const |
lst2(lst) | 拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素 |
lst2=lst | 拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素 |
lst.size() | 列表中元素的数量 |
lst.begin() | 返回指向lst中首元素的位置 |
lst.end() | 返回指向lst中尾元素下一位置的指针 |
和vector一样,initializer_list也是一种模板类型。定义initializer_list对象时,必须说明列表中所含元素的类型。和vector不一样的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。
如果想向initializer_list形参中传递一个值的序列,则必须把序列放在一对花括号内。
#include <iostream>
#include <string>
#include <initializer_list> void error_msg(std::initializer_list<std::string> lst)
{
for (auto it = lst.begin(); it!=lst.end(); ++it)
{
std::cout << *it << std::endl;
}
}
int main(int argc, char *argv[])
{
error_msg({ "hello", "world", "QAQ" });
return ;
}
三、返回类型和return语句
return语句终止当前正在执行的函数并将控制权返回到调用函数的地方。return语句有两种形式。
return;
return expression;
1、无返回值函数
没有返回值的return语句只能用在返回类型是void的函数中。返回void的函数不要求非得有return语句,因为在这类函数的最后一句后面会隐式地执行return。
一个返回类型是void的函数也能使用return语句的第二种形式,不过此时return语句的expression必须是另一个返回void的函数。强行令void函数返回其他类型的表达式将产生编译错误。
2、有返回值函数
return语句的第二种形式提供了函数的结果。只要函数的返回类型不是void,则该函数内的每条return语句必须返回一个值。return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型。
不要返回局部对象的引用和指针。
和其他运算符一样,调用运算符也有优先级和结合律。调用运算符的优先级与点运算符和箭头运算符相同,并且也符合左结合律。因此,如果函数返回指针、引用或类的对象,我们就能使用函数调用的结果访问结果对象的成员。
函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。可以像使用其他左值那样来使用返回引用的函数的调用,特别是,我们能为返回类型是非常量引用的函数的结果赋值。
C++11新标准规定,函数可以返回花括号包围的值的列表。类似于其他返回结果,此处的列表也用来表示函数返回的临时变量进行初始化。如果列表为空,临时变量执行值初始化;否则,返回的值由函数的返回类型决定。
3、返回数组指针
1)使用类型别名
从语法上来说,定义一个返回数组的指针或引用的函数比较繁琐,但是也有一些方法可以简化这一任务,其中最直接的方法是使用类型别名:
typedef int arrT[]; // arrT是一个类型别名,它表示的类型是含有10个整数的数组
using arrT2 = int[]; // arrT的等价声明
arrT *func(); // 返回一个指向含有10个整数的数组指针
2)声明一个返回数组指针的函数
如果我们想定义一个返回数组指针的函数,则数组的维度必须跟在函数名字之后。然而,函数的形参列表也跟在函数名字后面且形参列表应该是先于数组的维度。因此,返回数组指针的函数形式如下所示:
Type (*function(parameter_list))[dimension]
Type表示元素的类型,dimension表示数组的大小。(*function(parameter_list))两端的括号必须存在,如果没有这对括号,函数的返回类型将是指针的数组。
int(*func(int i))[];
可以按照以下的顺序来逐层理解该声明的含义:
func(int i)表示调用func函数时需要一个int类型的实参;
(*func(int i))表示我们可以对函数调用的结果执行解引用操作;
(*func(int i))[10]表示解引用func的调用将得到一个大小是10的数组;
int (*func(int i))[10]表示数组中的元素是int类型。
3)使用尾置返回类型
在C++11新标准中还有一种可以简化上述func声明的方法,就是使用尾置返回类型。任何函数的定义都能使用尾置返回,但是这种形式对于返回类型比较复杂的函数最有效,比如返回类型是数组的指针或者数组的引用。尾置返回类型跟在形参表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个auto:
auto func(int i) -> int(*)[]; // 返回一个指向10个整数的数组的指针
4)使用decltype
如果我们知道函数返回的指针指向哪个数组,就可以使用decltype关键字声明返回类型。
#include <iostream> int arr[] = { , , };
decltype(arr) *func()
{
return &arr;
}
int main()
{
std::cout << (*func())[] << std::endl;
return ;
}
注意:decltype并不负责把数组类型转换成对应的指针,所以decltype的结果是个数组,要想表示func返回数组还必须在函数声明时加上一个*符号。