(2020.11.26 Thur)
函数的定义、声明和调用
格式如下
<函数返回类型> <函数名> (<形式参数表>) /* 函数头 /
{
expressions;
} / 函数主体 */
- 函数返回类型顾名思义是返回值的类型,有的情况不返回值,其类型关键字是void。如果定义函数时没有明确指定类型,则默认类型是int。
- 函数名,尽量避免下划线开头,因编译器常常定义一些下划线开头的变量或函数。
- 形式参数表,可以有多个形式参数,也可以没有,故函数分为两类根据形参,即有参函数和无参函数。
声明
如果在使用函数前没有进行定义,则必须对其声明。函数原型声明有函数名称、类型和参数,一般格式为
[<属性说明>] <函数返回类型> <函数名>(<形式参数>);
- 属性说明,可以默认,一般是inline(内联函数)、static(静态函数)、virtual(虚函数)、friend(友元函数)等。
- 其余各项的要求与函数定义相似,除了第一行结尾加了;。
- 声明语句里,形参 变量名可省略,只标注形参类型,如下面两个语句等价
double func(double x, int y, float z);
double func(double, int, float);
- 函数声明中的形参名和函数定义中的形参名可以不同
double func(double x, int y, float z); //声明
...
double func(double a, int b, float c) //定义
{
...
}
调用
double func(int, float) { //定义
...
}
...
x = func(a, b); //调用,a和b称为实际参数
- 使用库函数时,需要将库函数所对应的头文件引入,需要使用预编译指令#include<...>,而如果调用的是用户自定义函数,预编译指令为#include "..."
函数参数传递机制问题,本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信方法的问题。根据参数传递方式,函数调用分成两种方式,按值传递和地址传递或引用传递
按值传递
被调函数的形式参数作为被调函数的局部变量处理,即在对战中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为实参的一个副本。其特点是被调函数以形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。其过程如下
- 计算出实际参数表达式的值,接着给对应的形参变量分配一个存储空间,该空间的大小等于该形参类型的长度;
- 把已求出的实参表达式的值一一存入到为形参变量分配的存储空间中,成为形参变量的初值,供被调函数执行时使用。
这种调用方式的被调函数本身不对实参进行操作,也就是即使形参值在函数中发生了变化,实参值也不受影响,仍为调用前的值。
引用调用和指针调用
传值调用的特点是形参值的改变不能对实参产生影响,因此在有些地方不适用,比如交换两个数的值,无法用传值调用实现。
引用调用过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但其存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何惭怍都被处理成间接寻址,也就是通过堆栈中存放的地址访问主调函数中的实参变量。因此,被调函数对形参做的任何操作都影响了主调函数中的实参变量 。
引用调用方式是在函数定义时在形参面前加上引用运算符&,在调用时,参数传递的内容不是实参的值,而是实参的地址,即将实参的地址放倒C++为形参分配的内存空间中,因此对形参的任何操作都会改变实参的值。
#include <iostream.h>
void swap(int &a, int &b);
void main() {
int x = .. , y=.. ;
swap(x,y);
}
void swap(int &a, int &b) {
t=a;
a=b;
b=t;
}
可以注意到,除了在声明和定义中的形参加了符号&,其他部分和传值调用一样。加了符号&表示该参数被调用时采用的是引用调用,传递给函数的是实参的地址,所以实现了交换的功能。
由于传递的是地址,在调用函数时不创建新的参数变量(即开辟新的内存空间),因此在程序中对占用内存较多的数据参数,为了节省内存,可采用引用传递的方式。
引用传递的另一种形式是指针传递,定义函数时将形参说明成指针,而调用函数时就需要制定地址值形式的实参。
#include <iostream>
void swap(int *a, int *b);
...
int x = .. , y = .. ;
swap(&x, &y);
...
void swap(int *a, int *b){
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
带默认形参的调用
当一个函数既有声明也有定义时,默认参数值必须在声明中给出,而不能在定义中给出;当函数只有定义时,定义中给出。默认值的定义遵循从右到左的原则,也就是形参列表中如果一个形参没有默认值,则它左边的变量不能有默认值。
void afunc(int a, float b, char c = "this is a function", float d = 11.26);
void afunc(int a , float b, char c, float d){...}
main函数
每个C++程序都必须有一个唯一的main()函数,称为主函数。main函数中可调用其他函数,负责总控。main函数的最简单形式
main() {
变量声明语句;
执行语句;
}
在main函数中被调用的函数需要在main函数前声明或定义,至少要声明。
main函数带参数
main函数可以带参数,允许携带两个参数,一个是argc,int类型;另一个是指向字符型的指针数组argv[ ]。argc表示命令行中字符串的个数,argv[ ]指向命令行中的各个字符串。其他名字也可以,但习惯上用这两个变量名。main函数一般能在调用时追加参数。声明格式为
int main(int argc, char *argv[ ])
一个demo
#include <iostream.h>
int main(int argc, char *argv[ ])
{
cout << 'the program file is: ' << argv[0] <<endl; //输出信息,argv[0]输出程序的当前路径
for (int k = 1; k < argc; k++)
cout << 'argv[' << k<< "]=" << argv[k] << ' '; //循环输出第二个参数中的信息
court << endl;
return 0;
}
变量的作用域
作用域即变量的作用范围。一个程序将操作系统分配给其运行的内存块分为4个区域
- 代码区,存放程序的代码,即程序中的各个函数中的代码块
- 全局数据区,存放程序全局数据和静态数据
- 堆区,存放程序的动态数据
- 栈区,存放程序的局部数据,即各个函数中的数据
根据变量的作用域可将变量分为局部变量和全局变量
局部变量
在一个函数内部说明的变量时内部变量,其只在该函数范围内有效,在函数外这个变量就无效了。因此这些内部变量被称为局部变量。
注意,
- 形参变量也是局部变量,属于被调用函数;实参变量,则是调用函数的内部变量
- 不同的函数中可以使用相同的变量名
- 符合语句中可定义变量,作用域只在复合语句范围内
全局变量
又称外部变量,在函数外部定义的变量。不属于任何一个函数,可被作用域内所有函数直接引用,其作用域从外部变量的定义位置开始,到本文件结束为止。
#include <iostream.h>
int s1, s2; //全局变量,不需要写global
int vs(int a, int b)
{
s1 = a *b; //函数之前已经定义全局变量,在函数中调用时直接调用,不需要global
s2 = a+ b;
return 0;
}
void main()
{
int c = 1, d = 3;
k = vs(c,d);
cout<<'s1 = '<<s1<<endl;
cout<<'s2 = '<<s2<<endl;
}
- 同一个源文件中,允许外部变量和内部变量同名。在内部变量的作用域内,外部变量被屏蔽而不起作用
- 全局变量的作用域是从定义点到本文件结束。如果定义点之前的函数需要引用这些外部变量时,需要在函数内对被引用的外部变量做声明:
extern 数据类型 外部变量1[,外部变量2...];
也就是说extern用于引用尚未声明的外部变量,或在全局变量声明前引用。
函数的作用域
函数作用域的概念跟变量的存储位置和生命期有关。函数的参数和在函数中声明并定义的变量即局部变量,其被分配在堆栈上,随着函数的执行而生成,随着函数的退出而消亡。
标号是唯一具有函数作用域的标识符,goto语句使用标号。标号声明使得该标识符在一个函数内的任何位置都可以被使用。
#include <iostream.h>
void fn()
{
goto S;
int b;
cin>>b;
if (b>0)
{
S:
goto End;
}
End:
cout<<'Cannot input b '<<endl;
}
void main()
{
fn();
}
函数重载
函数重载是指同一个函数名可以对应着多个函数的实现。每一类实现对应着一个函数体,名字相同,但是函数的参数的类型不同,这就是重载。
函数重载又称函数的多态性,所谓的不同实现,指的是这些函数的形参表必须互不相同,或者是形参的个数不同,或者形参的类型不同,或者都不相同,否则无法实现函数重载。下面的是合法的重载
int func(int, int);
int func(int);
double func(int, long);
double func(long); //合法
重载函数的(返回)类型,可以相同也可以不同。但如果仅仅是返回类型不同而函数名相同、形参表也相同,是不合法的,编译器会给出语法错误提示,下面的重载不合法
int func(int a, float b);
double func(int a, float b); //不合法
参数类型和个数不同的重载
参数类型不同的重载case
#include <iostream.h>
int add(int, int);
double add(double, double);
void main() {
cout<<add(1, 2)<<endl; //调用第一个int add函数
cout<<add(1.1, 2.2)<<endl; //调用第二个double add函数
}
int add(int x,int y)
{
return x+y;
}
double add(double x, double y)
{
return x+y;
}
参数个数不同的重载case
#include <iostream.h>
int min(int, int); //两个参数的函数
int min(int, int, int);//三个参数的函数
void main()
{
cout<<min(2,3)<<endl;
cout<<min(2,3,4)<<endl;
}
int min(int a, int b)
{
return min(a,b);
}
int min(int a, int b, int c)
{
t = min(a,b);
return min(t,c);
}
函数的重载在类和对象中应用的比较多,特别是在类的多态性中。
Reference
1 刘蕾编著,21天学通C++(第五版),电子工业出版社
2 聚慕课教育研发中心 编著,C++从入门到项目实践(超值版),清华大学出版社