type
status
date
slug
summary
tags
category
icon
password
Property
每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。形参初始化的机理与变量初始化一样。
形参的类型决定了形参和实参交互的方式:
- 当形参是引用类型时,称它对应的实参被引用传递或者函数被传引用调用。和其他引用一样,引用形参也是它绑定的对象的别名;也就是说,引用形参是它对应的实参的别名
- 当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象,称这样的实参被值传递或者函数被传值调用
如果形参不是引用类型,则函数对形参做的所有操作都不会影响实参
传递方式 | 是否发生拷贝 | 是否可修改 |
一般值传递 | 发生拷贝,拷贝的是实参的值 | 不可修改 |
指针传递 | 发生拷贝,拷贝的是指针的值,而不是指针指向的值 | 指针的值不可修改,所指向的对象的值可修改,修改指针的值需要传指针的指针 |
引用传递 | 不发生拷贝 | 可修改 |
传值参数
一般值传递
main()
中a的地址和func()
中a
的地址不同,是因为func()
中的a
只是拷贝出来的一个副本,所以对副本的修改不会影响到实参。指针传递
指针的行为和其他非引用类型一样。当执行指针拷贝操作时, 拷贝的是指针的值。拷贝之后, 两个指针是不同的指针。因为指针可以间接地访问它所指的对象, 所以通过指针可以修改它所指对象的值:
指针形参的行为与之类似:
调用
reset
函数之后, 实参所指的对象被置为0,但是实参本身并没有改变:C语言常常使用指针类型的形参访问函数外部的时象。在C++语言中, 建议使用引用类型的形参替代指针。
传引用参数
对于引用的操作实际上是作用在引用所引的对象上,通过使用引用形参,函数可以改变实参的值。
引用形参的行为与之类似,通过使用引用形参,允许函数改变一个或多个实参的值。
引用形参绑定初始化它的对象,即引用直接传入对象而无须传递对象的地址。
使用引用形参可以避免拷贝操作,拷贝大的类类型对象或容器对象比较低效,另外有的类(如
IO
类)根本就不支持拷贝操作,这时只能通过引用形参访问该类型的对象。除了内置类型、函数对象和标准库迭代器外,其他类型的参数建议以引用方式传递。
一个函数只能返回一个值,然而有时函数需要同时返回多个值,这个时候可以使用引用形参让函数返回额外信息。
如果函数无须改变引用形参的值,最好将其声明为常量引用。
const形参和实参
const
可以分为顶层const
与底层const
两种。一般对象只会有顶层const
,表示对象本身是常量不能修改;而对于指针与引用变量除了顶层const
外(表示自身是常量,一般只对指针而言,引用一般只关心底层const
),还有底层const
,表示自身指向或者引用的对象是常量。- 顶层
const
表示指针本身是个常量
- 底层
const
表示指针所指的对象是一个常量
指针类型既可以是顶层
const
也可以是底层 const
即(除了
const int ci = 42; //顶层const
):- 如果
const
右结合修饰的为类型或者*
,那这个const
就是一个底层const
- 如果
const
右结合修饰的为标识符,那这个const
就是一个顶层const
当形参有顶层
const
时,传递给它常量对象或非常量对象都是可以的。调用
fcn
函数时,既可以传入const int
也可以传入int
。忽略掉形参的顶层const
可能产生意想不到的结果:可以使用非常量对象初始化一个底层
const
形参,但是反过来不行。与变量初始化一样。当用形参去初始化实参时,
顶层const
将被忽略,所以形参的顶层const
不影响怎样传递实参,可以传递const
和非const对象
给有顶层const
的形参。如果重载的函数只是形参的顶层 const 修饰不一样,可以用相同的参数去调用这两个函数,编译器没有足够的信息来区分这两个函数。这种情况下将被认为是重复定义。对于指针和引用的底层
const
修饰,与变量初始化一样,可以将非const
对象用于初始化const
对象,但不能执行相反的过程。对于非 const
引用,用于初始化的实参必须是相同类型的。所以这里有一个原则就是尽可能使用const
引用,原因是非const
引用导致函数只能接收类型精确匹配的左值对象,任何const
对象、字面量或者需要转型的对象都被排除在外。除非是真正想要改变引用对应的实参值才会使用非const
引用。把函数不会改变的形参定义成普通引用会极大地限制函数所能接受的实参类型,同时也会给别人一种误导,即函数可以修改实参的值。此外,使用引用而非常量引用也会极大地限制函数所能接受的实参类型。
数组形参
数组的两个特殊性质对定义和使用作用在数组上的函数有影响,分别是:不允许拷贝数组以及使用数组时(通常)会将其转换成指针。
- 因为不能拷贝数组,所以无法以值传递的方式使用数组参数,但是可以把形参写成类似数组的形式。
- 因为数组会被转换成指针,所以当传递给函数一个数组时,实际上传递的是指向数组首元素的指针。
因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外信息。管理指针形参有三种常用的技术:
- 要求数组本身包含一个结束标记
- 传递指向数组首元素和尾后元素的指针
- 专门定义一个表示数组大小的形参
以数组作为形参的函数必须确保使用数组时不会越界。
如果函数不需要对数组元素执行写操作,应该把数组形参定义成指向
const
的指针。只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。形参可以是数组的引用,但此时维度是形参类型的一部分,函数只能作用于指定大小的数组。
将多维数组传递给函数时,真正传递的是指向数组首元素的指针,数组第二维(以及后面所有维度)的大小是数组类型的一部分,不能省略。
main:处理命令行选项
C++
程序允许从命令行传递选项到程序中去。方式是定义main
函数是指定两个额外的参数:- 第一个形参
argc
表示数组中字符串的数量
- 第二个形参
argv
是一个数组,数组元素是指向C风格字符串的指针
当实参传递给
main
函数后,argv
的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参,最后一个指针之后的元素值保证为0。当使用
argv
中的实参时,一定要记得可选的实参从argv[1]
开始;argv[0]
保存程序的名字,而非用户输入。默认实参
默认实参作为形参的初始值出现在形参列表中。可以为一个或多个形参定义默认值,不过一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。
使用默认实参调用函数
调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。如果想使用默认实参,只要在调用函数的时候省略该实参即可。
默认实参声明
虽然多次声明同一个函数是合法的,但是在给定的作用域中一个形参只能被赋予一次默认实参。函数的后续声明只能为之前那些没有默认值的形参添加默认实参,而且该形参右侧的所有形参必须都有默认值。
通常是将默认实参放在函数声明中,虽然放在函数定义的参数列表中也是可以的,但并不常用,说到底其实提供默认值的时候看的依然是当时可见的函数声明,函数定义的头部也是一种函数声明。
用于初始化默认实参的值除了可以是常量表达式之外,还可以是全局的变量、函数返回值,但不能是本地变量。作为函数默认实参的表达式中的名字将在函数声明时进行名字解析,而求值发生在函数调用时。另外,如果是内嵌的相同函数声明,将隐藏外部作用的函数声明。
默认实参初始值
局部变量不能作为函数的默认实参。只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参
用作默认实参的名字在函数声明所在的作用域内解析,但名字的求值过程发生在函数调用时。
可变形参
C++11
提供了两种主要方法处理实参数量不定的函数:- 如果实参类型相同,可以使用
initializer_list
标准库类型
- 如果实参类型不同,可以定义可变参数模板
C++
还可以使用省略符形参传递可变数量的实参,但这种功能一般只用在与C函数交换的接口程序中
initializer_list
initializer_list
是一种标准库类型,定义在头文件initializer_list
中,表示某种特定类型的值的数组。initializer_list
提供的操作:- 和
vector
一样,initializer_list
也是一种模板类型,定义对象时,必须说明列表中所含元素的类型
- 和
vector
不一样的是,initializer_list
对象中的元素永远是常量值,无法改变
使用如下的形式编写输出错误信息的函数,使其可以作用于可变数量的实参:
拷贝或赋值一个
initializer_list
对象不会拷贝列表中的元素,拷贝后,原始列表和副本共享元素。initializer_list
对象中的元素永远是常量值。如果想向 initializer_list
形参传递一个值的序列,则必须把序列放在一对花括号内。因为
initializer_list
包含 begin
和 end
成员,所以可以使用范围 for
循环处理其中的元素。省略符形参
省略号形如:
void foo(parm_list, ...);
,省略号只能出现在参数列表的末尾。parm_list
后的逗号可以省略,但最好不要这样做以免引起歧义。省略符形参是为了便于
C++
程序访问某些特殊的C代码而设置的,这些代码使用了名为varargs
的C标准库功能。通常,省略符形参不应该用于其他目的。省略符形参应该仅仅用于C
和C++
通用的类型,大多数类类型的对象在传递给省略符形参时都无法正确拷贝。有几个函数来帮助访问省略号形式的可变参数
va_start
使得可以开始访问可变参数
va_arg
访问下一个可变参数
va_list
保存供va_start
va_arg
va_end
访问的信息,必须首先调用
va_end
结束访问可变参数