1、参数传递:每次调用函数都会重新创建它的形参,并用传入的实参对形参进行初始化。形参的初始化和变量的初始化是一样的。
形参的类型决定了形参和实参的交互方式。如果形参是引用类型,他将绑定到对应的实参上。
当形参是引用类型时,我们说他对应的实参被引用传递或者函数被传引用调用。和其他引用一样,引用形参也是它绑定的对象的别名;也就是说,引用形参是他对应的实参的别名。
(1)传值参数:函数对形参做的所有操作都不会影响实参。
(2)指针形参:可以通过指向元素的指针,来改变实参。
注意;熟悉C的程序员常常使用指针类型的形参访问函数外部的对象。在c++语言中,建议使用引用类型的形参替代指针。
(3)传引用参数:
对于引用的操作实际上是作用所引的对象上。
1
2
3
4
5
|
int n= 0 ,i= 42 ; int & r=n; //r绑定了n(r引用了n) r= 42 ; //现在n的值和i相同 r=i; //现在n的值和i相同 i=r; //i的值和n相同 |
引用形参的行为与之类似。通过使用引用形参,允许函数改变一个或多个实参的值。
使用引用可以避免拷贝:
拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型根本就不支持拷贝操作。当某种类型不支持拷贝操作是,函数只能通过引用形参访问该类型的对象。
如果函数无需改变引用形参的值,最好将其声明为常量引用。
(4)使用引用形参返回额外的信息
一个函数返回一个值,然而有时需要返回多个值,引用形参为我们一次返回多个结果提供了途径。
(5)const形参和实参:
和其他初始化一样,当用实参初始化形参时会忽略掉顶层const。换句话说,形参的顶层const被忽略掉了。当形参顶层const时,传给他常量对象或者非常量对象都是可以的。
1
2
3
|
int i= 42 ; const int * cp=&i; //r不能改变i const int & r=i; //r不能改变i |
注意:把函数不会改变的形参定义成(普通)引用是常见错误,这样会给函数的调用者带泪一种误导,即函数可以修改它的实参的值。
顶层const作用于本身,
const int ci=42;//不能改变ci,const是顶层的
int i=ci;//当拷贝ci时忽略了他的顶层const
int * const p=&i;//const是顶层的,不能给p赋值
*p=0;//正确,通过p改变对象的内容是允许的,现在i变成了0。
使用实参初始化形参时会忽略掉顶层const。换句话说,形参的顶层const被忽略掉了。当形参有顶层const时,传给他常量对象或者非常量对象都是可以的。
void fun(const int i){fun能够读取i,但是不能向i写值}
void fun(int i)//错误,重复定义了fun(int)
在C++语言中,允许我们定义若干具有相同名字的函数,不过前提是不同函数的形参列表应该有明显的差别。因为顶层的const被忽略掉了,所以在上面的代码中传入两个fun函数的参数可以完全一样。
尽量使用常量引用:
把函数不会改变的形参定义成(普通)引用是一种比较常见的错误,这么做带给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大的限制函数所能接受的实参类型。
无需再函数中改变的参数就是常量引用。
数组形参:
数组的:不允许拷贝数组,会将数组转换成指针。这两个特性,让我们无法以值传递的方式使用数组参数。因为数组会被转换成指针,当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
虽然我们不能以值的方式传递数组,但是我们可以把形参写成类似数组的形式。
其实当我们传给函数的是一个数组,则实参自动的转换成指向数组首元素的指针,数组的大小对函数的调用没有影响。
由于,函数并不知道数组的大小:管理指针形参有三种常用的技术:
(1)使用标记指定数组的长度:
其实就是要求数组本身包含一个结束标记。比如c风格的字符串。
1
2
3
4
5
6
|
void print( const char*p) { if (p) //若p不是一个空指针 while (*p) //指针所指字符不是空字符 cout<<*p+= 1 ; //输出当前字符并将指针向前移动一个位置 } |
(2)使用标准库规范。
传递指向数组首元素和尾元素的指针,这种方法受到了标准库技术的启发。
1
2
3
4
5
6
7
8
|
void print( const int *beg, const int * end) { //输出beg到end之间不含(end)的所有元素 while (beg!=end) { cout<<*beg++<<endl; //输出当前指针并将指针向前移动一个位置 } } |
while循环使用解引用运算符和后置递减运算符输出当前元素并在数组内将beg向前移动一个元素,当beg和end相等时结束循环。
为了调用这个函数,我们传入两个参数,一个指向首元素,一个指向尾元素下一个位置。
(3)直接传递数组的地址,和数组的大小。
1
2
3
4
|
void print( int *a, int b) { for ( int i= 0 ;i!b;i+= 1 ) } |
直接通过size来判断要输出多少个元素。
数组形参和const:
当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。
数组引用形参:
C++允许将变量定义成数组的引用,形参也可以是数组的引用。此时引用绑定到对应的实参上,也就是绑定到数组上
1
2
3
4
5
6
7
|
void print( int (&arr)[ 10 ]) { for (auto elem:arr) { cout<<elem<<endl; } } |
注意:两端的括号必不可少。
int &(arr);//定义成了引用的是数组。
int(&arr)【10】;//arr是具有10个整数的整形数组的引用
传递多维数组:其实传递的就是指针。
1
2
3
4
5
|
void print( int (*arr)[ 10 ]), int size)); //将arr声明称指向含有10个整数的数组的指针。 //再一次强调,×arr两端的括号必不可少。 int * arr[ 10 ]; 10 个指针构成的数组。 int (* arr)[ 10 ];指向含有 10 个整数的数组的指针。 |
main:处理命令行选项:
main函数是演示C++程序如何向函数传递数组的好例子。到目前为止,我们定义main函数都只有空形参列表。
int main()
2、含有可变形参的函数。
如果函数的实参数量未知,但是全部实参的类型都相同,我们可以使用initializer_list_list的类型的形参。initializer_list是一种标准库类型,用于表示某种特定类型的值的数组。initializer_list类型定义在同名的头文件中,
initializer_list<T>lst | 默认初始化:T类型元素的空列表 |
initializer_list<T>lst{a,b,c}; | lst的元素数量和初始值一样多,lst的元素是对应初始值的副本,列表中的元素是const |
lst2(lst) | 拷贝或者赋值一个initializer_list对象不会拷贝列表中的元素,拷贝之后,原始列表和副本共享元素。 |
lst.size() | 列表中的元素的数量 |
lst.begin(); | 返回指向lst中首元素的指针 |
lst.end() | 返回指向lst中尾元素硒一个位置的指针 |
和vector一样,initializer_list也是一种模板类型。定义inttializer对象时,必须说明列表中元素的类型。
和vector不同的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中的值。
1
2
|
initializer_list<string>ls; //initializer_list的元素类型是string类型。 initializer_list< int >li; //initializer_list的元素类型是int |
对于initializer_list对象的begin和end操作类似于vector对应成员。
3、返回类型和return语句:
return语句终止当前正在执行的函数并将控制权返回到该函数的地方。return有两种形式:
return;
return expression;
(1)无返回值函数:
无返回值的return语句只能用于void函数中。返回void的函数不要求非得有return语句,因为在这类函数的最后一句后面会隐式的执行return。
通常情况下,void函数若是想中间退出,可以使用return语句。return的用法有些类似于我们的break语句退出循环。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
int main() { int a = 1 , b = 2 ; jiaohuan(&a, &b); cout << a << ":" << b << endl; system( "pause" ); } void jiaohuan( int *a, int * b) { if (*a == *b) { return ; //如果两个数是相等的,直接退出 } int t; t=*a; *a = *b; *b = t; } |
(2)有返回值的函数。
只要函数的返回类型不是void,则该函数内的每条return语句必须返回一个值。return语句返回值的类型必须与函数的返回类型相同。或者能隐式的转换成函数返回类型。
注意:在含有return语句的循环后面应该也有一条return语句,如果没有的话该程序就是错误的。
(3)值是如何返回的。
返回一个值的方式和初始化一个变量或形参的方式完全一样,返回的值用于初始化调用点的一个临时量,该临时量,就是函数的返回结果。
4、返回数组指针:
因为数组不能被拷贝,所以函数不能返回数组,不过函数可以返回数组的指针或引用。虽然从语法讲,要想定义一个返回数组的指针或引用的函数比较繁琐,但是有一些方法可以简化这个任务。使用类型别名。
typedef int arr[10];//arr是一个类型别名,他表示的类型是含有10个整数的数组。
using arr=int[10];//arr的等价声明
arr* func(int i);//func(函数)返回一个指向含有10个数组的指针。
arr是含有10个整数的数组的别名。因为我们无法返回数组,所以将返回类型定义成数组的指针。因此func函数接受一个int实参,返回一个指向包含10个整数的数组的指针。
5、函数重载:
如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
1
2
3
|
void print( const char * cp); void print( const int *beg, const int * end); void print( const int a[],size_t size); |
这些函数接受的形参类型不一样,但是执行的操作非常类似。当调用这些函数时,编译器会根据传递的实参类型推断想要的是那个函数。
int j[2];={0,1};
print("hello world");//调用print(const char *);
print(j.end(j)-j.begin)(j));//调用print(const int *,size_t)
注意:函数的名字仅仅是让编译器知道它调用的是那个函数,而函数的重载可以在一定程度上减轻程序员起名字,记名字的负担。
6、定义重载函数:
有一种典型的数据库应用,需要创建几个不同的函数分别根据名字,电话,账户,密码等信息查找记录。函数重载使得我们可以定义一组函数,他们的名字都是lookup,但是查找的依据不同。我们能通过以下行驶中的任意一种调用lookup函数。
1
2
3
|
record lookup( const accounts&); //根据account查找记录 record lookup( const phone&); //根据phone查找记录 record lookup( const name&); //根据name查找记录 |
虽然定义的函数的名字相同,但是形参是不同的。
重载和const形参:
顶层const不影响传入函数的对象。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来;
record lookup(phone);
record lookup(const phone);//重复声明了record lookup(phone);
这两个声明是等价的。
另一方面,如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的。
record lookup(account&);//函数作用于account的引用
record lookup(const account&);//新函数,作用于常量引用
函数重载与作用域:
重载对作用域的一般性质并没有设么改变,如果我们
在内层作用域中声明名字,他将隐藏外层作用域的同名实体。在不同的作用域中无法重载函数名。
6、特殊用途语言特性:
默认实参,内联函数,constexpr函数,以及在程序调试过程中常用的一些功能。
(1)默认实参:
声明:对于函数的声明来说通常习惯将其放在头文件中,并且一个函数只声明一次。
string screen(sz,sz,char='');
我们不能修改一个已经存在的默认值:
但是可以按照如下形式添加默认实参
string screen(sz=24,sz=80,char)
通常在函数声明中指定默认实参,并将该声明放在合适的头文件中。
默认实参初始值:
局部变量不能作为默认实参。
(2)内联函数和constexpr函数
可以避免函数运行时的开销。在shorterstring函数前面加上关键字inline,这样就可以将它声明成内联函数了:
inline const string &
shortstring(const string &s1,const string &s2)
{
return s1.size()<=s2,size()?s1:s2;
}
内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。
一般说来,内联机制用于优化规模较小,流程直接,平凡使用的函数。
constexpr函数:
指能用于常量表达式的函数。规定:函数的返回类型及所有形参的类型都的是字面值的类型,而且函数体中必须有且只有一条return 语句: