在节前拜读张哥dvlinker的博客_CSDN博客-VC++常用功能代码封装,C++相关,C++软件调试与异常排查从入门到精通系列教程领域博主的C++专栏后,毅然决然,想在春节期间系统的学习下C++入门知识,本文算是学习过程的小结及感悟!
C++语言中函数的声明形式如下:
返回值类型 函数名(参数表);
如声明将两个整型参数相加的函数add()的程序如下:
int add(int x, int y);
下面从函数参数的引用传递、函数重载两个方面介绍C++语言中的函数。
01、引用传递
引用是一种特殊的声明,可以用来限定变量的类型。如果在声明一个变量的同时将它声明为另一个变量的引用,则意味着这两个变量等同于一个变量,即声明为引用的变量是它所引用的变量的别名。下面的程序中将变量ri声明为变量i的引用。
int i = 0, j = 1;
int & ri = i; //ri是一个int类型的引用,它引用的变量是int类型的i
ri = 10; //相当于i = 10
ri = j; //相当于i = j
使用引用时必须注意下面两个问题。
(1)定义一个引用时,必须同时对它初始化,即明确它所引用的变量。
(2)引用类型的变量所引用的那个变量只能在初始化时指定,指定之后不能修改(实际上也没法修改)。
实际上,编译器对引用类型的变量自动地按照指针常量的形式进行了变换,编译器变换上面声明引用变量ri的语句如下:
int * const ri = &i;
这样,就可以很清楚地看到ri是一个常变量,该常变量是一个指针常量,并且该指针指向一个int型数据。因为ri是常变量,所以声明时必须要初始化,也因此不能在声明之后修改它的值。这就很好地解释了为什么对引用类型的变量有前面两条的限制。
同理,编译器将前面给引用变量ri赋值的语句自动地改写为:
*ri = 10;
*ri = j;
综上,编译器把“引用”改成了“指针”,所做的变换如下:
(1)将引用运算符“&”转换成指针运算符“* const”。
(2)在定义引用类型的变量时对右值取地址。
(3)对于函数的形参,如果它是引用类型,则形实结合时对实参取地址。
(4)对于使用引用变量的语句,在引用变量前加“*”。
总之,编译器自动地把“引用”改成了“指针常量”。
使用引用类型可使程序变得简洁、容易理解。使用引用类型的形参如例1所示。在该例中,swap函数完成两个数据的交换:函数中是交换形参的值,但由于形参是引用类型,所以实际上完成了实参的交换。在本例中,交换前变量x等于5,变量y等于10;交换后变量x等于10,变量y等于5。
【例1】 使用引用类型的形参。
1. #include<iostream>
2. using namespace std;
3. void swap(int & a, int& b)
4. {
5. int t;
6. t = a;
7. a = b;
8. b = t;
9. }
10. int main()
11. {
12. int x(5), y(10);
13. cout << "x = " << x << " y = " << y <<endl;
14. swap(x,y);
15. cout << "x = " << x << " y = " << y <<endl;
16. return 0;
17. }
例2说明了函数返回引用类型的情况,此时可直接给函数调用后的返回值赋新值:变量x和变量y的初值分别是2和5;在调用了larger()函数之后给其返回值加5,由于变量y的初值较大且larger()函数的形参和返回值都是引用类型,所以实现了将变量y的值增5的功能,因此程序最后一行的输出为“x = 2 y = 10”。
【例2】 函数返回引用类型。
1. #include<iostream>
2. using namespace std;
3. int & larger(int & a, int & b)
4. {
5. return a > b ? a : b;
6. }
7. int main()
8. {
9. int x = 2, y = 5;
10. cout << "x = " << x << " y = " << y <<endl;
11. cout << (larger(x, y) += 5) << endl;
12. cout << "x = " << x << " y = " << y <<endl;
13. return 0;
14. }
函数返回引用类型的数据是经常用到的,因为返回值经常需要作为左值。需要注意的是,不能将非静态局部变量按引用类型返回,因为在函数返回后,该局部变量已经不存在了,从而返回值也不能作为左值。比如例2.3中,将larger()函数改为如下形式时将会出现警告。
int & larger(int & a, int & b)
{
int i = a > b ? a : b;
return i; //因为i为局部变量且返回值为引用类型,所以编译时会有警告
}
两种使用引用传递的主要情形如下:
(1)用于传递大量数据。因为引用传递被自动转换为指针传递,这能够减少复制数据的开销。
(2)用于返回一个内存空间作为左值。比如上面的larger()函数,为了能够给其返回值赋值,需要使用引用(或采用相应的指针形式,但比较麻烦)。
02、函数重载
在面向对象程序设计中,成员函数是对类的行为的描述。对于同一个类,存在这样一种情况:虽然它执行的动作的称谓是相同的,但在不同的情况下执行的过程却是不同的。对于普通的函数也是如此。比如,已知两个数,要求把它们加起来。此时,如果这两个数是整数,那么可以用整数的加法运算完成计算;如果已知的数是虚数,那么可以用虚数的加法运算来完成计算。对于这样的“把两个数相加”的要求,可以把它们抽象成函数。不过,由于执行过程不同,在C语言中需要使用不同的函数名来完成,如下面的函数声明所示(假设complex是已定义好的表示虚数的类型)。
int add_int(int a, int b);
complex add_complex(complex a, complex b);
显然,这样设计不利于阅读和使用。为解决此类问题,在C++语言中引入了函数重载的概念,即允许声明同名的函数。对于一个函数调用,编译器根据实参和形参的类型、个数及顺序自动确定调用哪个同名函数。比如上面两个函数可重新声明如下:
int add(int a, int b);
complex add(complex a, complex b);
在调用函数时,根据实参的类型来选择调用哪个函数:如果实参是整型,则调用第一个函数版本;如果实参的类型是虚数,则调用第二个函数版本。
实际上,编译器在编译C++语言的源程序时自动重命名了重载函数,重命名的方式大致是函数名加上参数类型列表,如上面两个函数会被重命名为类似于下面的形式。
int add_int_int(int a, int b);
complex add_complex_complex(complex a, complex b);
而对于调用语句“add(1, 2);”则会自动变为“add_int_int(1, 2);”。通过这种方式,编译器能够确定需要调用函数的哪个重载形式。
从上面的程序也可看出,在使用函数重载时要注意:尽管函数名可以相同,但形参的类型、个数及顺序一定要有不同,否则编译器就无法确定到底该调用哪个函数了。另外,函数的返回值类型不能用来区分函数的重载形式,因为在调用函数时常常不关心返回值,从而编译器也无法利用返回值的类型来确定该调用哪个函数。示例程序如下:
int add(int x, int y){ ... } //第一个函数
float add(float x, float y){ ... } //参数类型不同,与第一行的函数构成重载
int add(int x, int y, int z){ ... } //参数个数不同,与前两行的函数构成重载
int add(int a, int b){ ... } //与第一行的函数只是形参名不同,重载出错
void add(int x, int y){ ... } //不以返回值区分函数,重载出错