C++入门基础07:函数定义与声明、函数传参(传值、传地址、传引用)、函数重载
1、函数定义与声明
函数是一起执行一个任务的一组语句。每个程序(C/C++)都有一个主函数 main() ,
所有简单的程序都可以定义其他额外的函数。可以把代码划分到不同的函数中,函数
划分通常是根据每个函数执行一个特定的任务来进行的。
函数定义的语法
函数由一个函数头和一个函数主体组成。下面列出一个函数的所有组成部分:
int max(int x, int y)
{
//函数体
}
返回类型:一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
函数名称:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
函数主体:函数主体包含一组定义函数执行任务的语句。
函数声明
函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。
如下是一个函数声明:
int max(int num1, int num2);
在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明:
int max(int, int);
当您在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,您应该在调用函数的文件顶部声明函数。
函数声明告诉编译器函数的名称、返回类型和参数。
函数定义提供了函数的实际主体。
C++中常将声明与定义分开。
函数必须先声明或者定义才能使用。
函数调用
当程序调用函数时,程序控制权会转移给被调用的函数。
被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。
调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值。
下面这个代码会报错:
#include <iostream>
//系统定义头文件一般是尖括号
#include<fstream>
#include<string>
#include<exception> //标准库的头文件,标准的异常
#include<cstring>
using namespace std;
int print_string(string x)
{
fun(x);
if (x.empty())
return -1;
cout << x << endl;
return 0;
}
string fun(string str)
{
return str;
}
int main()
{
string str = "cplusplus";
int res = print_string(str);
if(res == -1)
{
cout << "input string is empty!" << endl;
}
return 0;
}
报错内容是找不到fun标识符。
这是由于fun函数是在print_string函数之后,那么在定义print_string函数的时候,fun函数还没定义,就不知道这个函数的名称。所以会报错。函数还没有声明。
如果要解决这个报错就需要将这个fun函数的声明放到print_string函数之前。
下面代码是将fun函数在print_string函数前声明了,就不会报错了。
#include <iostream>
//系统定义头文件一般是尖括号
#include<fstream>
#include<string>
#include<exception> //标准库的头文件,标准的异常
#include<cstring>
using namespace std;
string fun(string str); //声明fun函数,fun函数在print_string函数前声明了,就不会报错了。
//参数的名称并不重要,这里我们将参数名删除也是不会报错的。
int print_string(string x)
{
fun(x);
if (x.empty())
return -1;
cout << x << endl;
return 0;
}
string fun(string str)
{
return str;
}
int main()
{
string str = "cplusplus";
int res = print_string(str);
if(res == -1)
{
cout << "input string is empty!" << endl;
}
return 0;
}
此外在声明函数时参数的名称并不重要,这里我们将参数名删除也是不会报错的。
就如下面代码中fun函数的声明,参数的参数名删除了,也没有报错,是可以编译成功的。
#include <iostream>
//系统定义头文件一般是尖括号
#include<fstream>
#include<string>
#include<exception> //标准库的头文件,标准的异常
#include<cstring>
using namespace std;
//string fun(string str); //声明fun函数,fun函数在print_string函数前声明了,就不会报错了。
string fun(string ); //fun函数的声明,参数的参数名删除了,不会报错。
int print_string(string x)
{
fun(x);
if (x.empty())
return -1;
cout << x << endl;
return 0;
}
string fun(string str)
{
return str;
}
int main()
{
string str = "cplusplus";
int res = print_string(str);
if(res == -1)
{
cout << "input string is empty!" << endl;
}
return 0;
}
2、 函数传参
函数传参有三种传参方式:传值、传址、传引用。
1、按值传递
(1)形参和实参各占一个独立的存储空间。
(2)形参的存储空间是函数被调用时才分配的,调用开始,系统为形参开辟一个临时的存储区,然后将各实参传递给形参,这是形参就得到了实参的值。
2、地址传递
地址传递与值传递的不同在于,它把实参的存储地址传送给形参,使得形参指针和实参指针指向同一块地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。
3、引用传递
引用传递是以引用为参数,则既可以使得对形参的任何操作都能改变相应数据,又使函数调用方便。引用传递是在形参调用前加入引用运算符“&”。引用为实参的别名,和实参是同一个变量,则他们的值也相同,该引用改变则它的实参也改变。
传值与传引用的差别:
1 传引用时,形参和实参是同一个变量,即使用相同的内存空间,二者有相同的地址。而传值时二者地址不同;
2 传引用时,由于没有新建变量,所以对于类对象参数,不会产生构造和析构。而如果是传值调用,调用时会进行构造,退出函数时会进行析构,会有性能的消耗,能传引用的地方就传引用;
3 由于传引用使用的是原本实参的地址,所以对引用参数值的修改,会在退出函数后体现在主调函数中,而传值调用对参数的修改不会影响到主调函数。
传地址的例子(传指针):
可以发现输出cout的地址是一样的。
#include <iostream>
//系统定义头文件一般是尖括号
#include<fstream>
#include<string>
#include<exception> //标准库的头文件,标准的异常
#include<cstring>
using namespace std;
//传指针
int print_string(string *x)
{
if ((*x).empty()) //这里(*x)是对指针的一个解引用。
return -1;
cout << "取x地址:" << x << endl;
return 0;
}
int main()
{
string str = "cplusplus";
int res = print_string(&str); //传一个地址,取str变量的地址。
cout << "取str地址:" << &str << endl;
if(res == -1)
{
cout << "input string is empty!" << endl;
}
return 0;
}
传引用的例子:
引用相当于实际参数的一个别名,所以可以直接使用。也就是和传实际参数是一样的。
引用的参数改变,对应的原本参数值也会改变。就如下面这个代码:
#include <iostream>
//系统定义头文件一般是尖括号
#include<fstream>
#include<string>
#include<exception> //标准库的头文件,标准的异常
#include<cstring>
using namespace std;
//传引用
//引用相当于实际参数的一个别名,所以可以直接使用。也就是和传实际参数是一样的。
int print_string(string &x)
{
if (x.empty()) //这里(*x)是对指针的一个解引用。
return -1;
cout << "x的内容:" << x << endl;
x = "cpp";//这里改变了参数的值。
cout << "x改变内容:" << x << endl;
return 0;
}
int main()
{
string str = "cplusplus";
int res = print_string(str); //将变量本身传进去。
cout << "str的内容:" << str << endl; //这里可以看到变量本身也是改变了。
if(res == -1)
{
cout << "input string is empty!" << endl;
}
return 0;
}
3、函数重载
C++ 允许在同一作用域中的某个函数指定多个定义,这就是函数重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当调用一个重载函数时,编译器通过把你所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数的过程,称为重载决策。
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形参(如参数的个数、类型或者顺序)必须不同,不能仅通过返回类型的不同来重载函数。
如下代码中同名函数 print() 被用于输出不同的数据类型:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
注意,参数列表不同包括参数的个数不同、类型不同或顺序不同,仅仅参数名称不同是不可以的。函数返回值也不能作为重载的依据。
函数的重载的规则:
函数名称必须相同。
参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
函数的返回类型可以相同也可以不相同。
仅仅返回类型不同不足以成为函数的重载。
C++代码在编译时会根据参数列表对函数进行重命名(不同的编译器有不同的重命名方式,这里仅仅举例说明,实际情况可能并非如此),例如 void print(int i)会被重命名为_print_int,void print(float f)会被重命名为_print_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错。
因此,函数重载只是在用户层面看到的函数名称是一致的,本质上它们还是不同的函数,占用不同的内存,函数地址也不一样。
#include <iostream>
//系统定义头文件一般是尖括号
#include<fstream>
#include<string>
#include<exception> //标准库的头文件,标准的异常
#include<cstring>
using namespace std;
//定义了三个函数,这三个函数的参数列表不同。
//这三个函数的功能都是返回较大的那个值。
int mymax(int x, int y)
{
cout << "call max int!" << endl;
return (x > y) ? x : y;
}
float mymax(float x, float y)
{
cout << "call max float!" << endl;
return (x > y) ? x : y;
}
char mymax(char x, char y)
{
cout << "call max char!" << endl;
return (x > y) ? x : y;
}
int main()
{
int a = 10, b = 100;
cout << mymax(a, b) << endl;
char c = 'a', d = 'A';
cout << mymax(c, d) << endl;
float e = 10.1f, f = 100.3f;
cout << mymax(e, f) << endl;
return 0;
}
这里将float型和char型的mymax注释掉了。可以看到依然是可以执行的,都是调用的int型的mymax函数了,只不过都进行隐式转换,转换到了int类型。
#include <iostream>
//系统定义头文件一般是尖括号
#include<fstream>
#include<string>
#include<exception> //标准库的头文件,标准的异常
#include<cstring>
using namespace std;
//定义了三个函数,这三个函数的参数列表不同。
//这三个函数的功能都是返回较大的那个值。
int mymax(int x, int y)
{
cout << "call max int!" << endl;
return (x > y) ? x : y;
}
//float mymax(float x, float y)
//{
// cout << "call max float!" << endl;
// return (x > y) ? x : y;
//}
//
//char mymax(char x, char y)
//{
// cout << "call max char!" << endl;
// return (x > y) ? x : y;
//}
int main()
{
int a = 10, b = 100;
cout << mymax(a, b) << endl;
char c = 'a', d = 'A';
cout << mymax(c, d) << endl;
float e = 10.1f, f = 100.3f;
cout << mymax(e, f) << endl;
return 0;
}