C++函数
函数是一组一起执行一个任务的语句。与C程序类似,每个 C++ 程序都至少有一个函数,即主函数 main(),通过函数,还可以把一个复杂任务分解成为若干个易于解决的小任务,充分体现结构化程序设计由粗到精,逐步细化的设计思想,即将任务合理划分为功能相对简单的若干子任务,分别进行设计调试,并通过某种机制将其连接成完整的程序,可以提高程序设计的效率。
按函数是否由系统定义:分为库函数(系统函数)和自定义函数。
C++ 标准库提供了大量的程序可以调用的内置函数。例如,函数 strcat() 用来连接两个字符串,函数 memcpy() 用来复制内存到另一个位置。
本文重点介绍自定义函数
C++ 中的函数定义的一般形式如下:
return_type function_name( parameter list )
{
函数体
}
在 C++ 中,函数由一个函数头和一个函数主体组成。下面列出一个函数的所有组成部分:
返回类型:一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
函数名称:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
函数体:包含一组定义函数执行任务的语句。
注意:对于main函数,main 函数的类型必须是 int ,当遇到到return 0;语句,那么整个程序就会停止,退出程序的执行。对于一般自定义函数,函数定义为什么样的类型,若需要有return语句,就应该返回相应类型的值,并且,当遇到return 语句,那么程序就会返回到调用该函数的地方接着执行。
C++自定义函数的定义
按函数是否带有参数:分为无参函数和有参函数。
无参函数:无参函数定义格式为:
[<类型>]<函数名> ([void])
{<函数体>}
有参函数:有参函数定义格式为:
[<类型>]<函数名> (<类型1> <形式参数1>[,<类型2><形式参数2>,…] )
{<函数体>}
函数的调用
创建 C++自定义函数后,就可以通过调用之来执行它。当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。
无参函数的调用格式为:
<函数名>( )
有参函数的调用格式为:
<函数名>(<实际参数表>)
例、编写一个求x的n次方的函数
#include<iostream>
using namespace std;
double power(double x,int n)
{
double val=1.0;
while(n--)
val*=x;
return val;
}
int main()
{
float x,y;
cout<<"输入底数和指数(两数之间空格分隔):";
cin>>x>>y;
cout<<x<< "的"<<y<< "次方是"<<power(x, y)<<endl;
return 0;
}
运行之,参见下图:
C++ 中的空函数
一个空函数示例:
void displayMessage()
{
cout << "Hello from the function displayMessage.\n";
}
该函数的名称是 displayMessage,意思是“显示消息”,它是一个描述性的名称,说明了函数的功能。函数就应该按这种方式命名,即通过名称揭示其功能。因为该函数不需要接收任何信息以执行其任务,所以它的括号中没有形参列表。
该函数的返回类型是 void。这意味着函数在完成执行后不返回值,并返回到调用该程序的部分。因为没有返回值,所以不需要 return 语句。当函数中的语句己经完成执行并且遇到结束函数的封闭大括号时,程序将自动返回。
演示空函数的例:
#include <iostream>
using namespace std;
// 定义空函数
void displayMessage()
{
cout << "你好,来自displayMessage函数的问候。\n";
}
//mian函数
int main()
{
cout << "你好,来自mian函数的问候。\n";
displayMessage(); // 调用displayMessage
cout << "现在又回到了mian函数。\n";
return 0;
}
运行之,显示如下:
C++函数原型(prototype)声明
被调用的函数一般定义在前,调用在后,像上面的例子。
可否将被调用的函数在后面定义?可以,此时就需要在前面先声明一个函数原型(),这样也就可以进行调用了。
函数原型声明形式如下:
函数类型 函数名(形式参数表);
包括:函数的类型,函数名,形式参数表,注意函数原型末尾有一个分号。
演示函数原型的例,本例和上例不同之处是,使用函数原型声明:
#include <iostream>
using namespace std;
// 函数原型(Function prototype)声明
void displayMessage();
//mian函数
int main()
{
cout << "你好,来自mian函数的问候。\n";
displayMessage(); // 调用displayMessage
cout << "现在又回到了mian函数。\n";
return 0;
}
// 定义空函数
void displayMessage()
{
cout << "你好,来自displayMessage函数的问候。\n";
}
运行之,显示如下:
再举一例、求两数最大值
#include<iostream>
using namespace std;
int max(int x,int y);//函数的声明
int main()
{
int x,y,z;
while(1)
{
cout<<"请输入两个不同数值,以空格分开\n";
cin>>x>>y;
z=max(x,y); //函数的调用
cout<<"最大值是:"<<z<<endl;
}
}
int max(int x,int y)//函数的定义:返回两数最大值
{
int z;
if (x>y)
z=x;
else
z=y;
return z;
}
C++函数的实参和形参
形式参数(形参)和实际参数(实参)
形式参bai数就是定义函数时候的参数表,只是定义了调用时参数的个数、类型和用来引用的名字,并没有具体的内容。
实际参数是调用函数传递的具体数据,实参可以是常量、变量或者表达式,且要与形参类型一致!实参对形参数据传递时是单向传递——形参相当于剧本中的人物;实参相当于演员,演员(实参)去扮演(替换)剧本里的角色(形参),不可能用剧中人物去替代现实的演员!
形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。 每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参。形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值;如果形参为引用类型,则它只是实参的别名。
形参和函数体内部定义的变量统称为局部变量,仅在函数的作用域内可见,同时局部变量还会隐藏在外层作用域中同名的其他所有声明(局部变量和全局变量可以重名)
C++局部变量和全局变量
局部变量
在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的。同样,在复合语句中定义的变量只在本复合语句范围内有效。这称为局部变量(local variable)。参见如示意图:
对局部变量的一些说明:
1)局部变量在定义时可加修饰词auto,但通常省略。局部变量在定义时若未初始化,其值为随机数。
2) 主函数main中定义的变量(m, n)也只在主函数中有效,不会因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
3) 不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰。例如,在f1函数中定义了变量b和c,倘若在f2函数中也定义变量b和c,它们在内存中占不同的单元,不会混淆。
4) 可以在一个函数内的复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为分程序或程序块。
5) 形式参数也是局部变量。例如f1函数中的形参a也只在f1函数中有效。其他函数不能调用。
6) 在函数声明中出现的参数名,其作用范围只在本行的括号内。实际上,编译系统对函数声明中的变量名是忽略的,即使在调用函数时也没有为它们分配存储单元。例如:
int max(int a, int b);//函数声明中出现a、b
int max(int x, int y) //函数定义,形参是x、y
{
cout<<x<<y<<endl; //合法,x、y在函数体中有效
cout<<a<<b<<endl; //非法,a、b在函数体中无效,编译时报错
}
全局变量
程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量是外部变量,称为全局变量(global variable。全局变量的有效范围为从定义变量的位置开始到本源文件结束。参见如示意图:
p、q、c1、c2都是全局变量,但它们的作用范围不同,在main函数和f2函数中可以使用全局变量p、q、c1、c2,但在函数f1中只能使用全局变量p、q,而不能使用c1和c2。
在一个函数中既可以使用本函数中的局部变量,又可以使用有效的全局变量。
对全局变量的一些说明:
1)全局变量在编译时建立在全局数据区,在未给出初始化值时系统自动初始化为0。
2) 全局变量的作用是增加函数间数据联系的渠道,但要慎重使用,建议不在必要时不要使用全局变量,因为:
全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
它使函数的通用性降低了,因为在执行函数时要受到外部变量的影响。如果将一个函数移到另一个文件中,还要将有关的外部变量及其值一起移过去。但若该外部变量与其他文件的变量同名,就会出现问题,降低了程序的可靠性和通用性。在程序设计中,在划分模块时要求模块的内聚性强、与其他模块的耦合性弱。即模块的功能要单一(不要把许多互不相干的功能放到一个模块中),与其他模块的相互影响要尽量少,而用全局变量是不符合这个原则的。
一般要求把程序中的函数做成一个封闭体,除了可以通过“实参——形参”的渠道与外界发生联系外,没有其他渠道。这样的程序移植性好,可读性强。
使用全局变量过多,会降低程序的清晰性。在各个函数执行时都可能改变全局变量的值,程序容易出错。因此,要限制使用全局变量。
3) 如果在同一个源文件中,全局变量与局部变量同名,则在局部变量的作用范围内,全局变量被屏蔽,即它不起作用。
简单的说,在{...}中出现的都是局部变量,否则就是全局变量。
变量的有效范围称为变量的作用域(scope)。归纳起来,变量有4种不同的作用域、文件作用域(file scope)、函数作用域(function scope)、块作用域(block scope)和函数原型作用域(function prototype scope)。文件作用域是全局的,其他三者是局部的。
除了变量之外,任何以标识符代表的实体都有作用域,概念与变量的作用域相似。
变量作用域示例1:
#include <iostream>
using namespace std;
int i=10;//全局变量
int main()
{
cout<<i<<endl;//输出全局变量i值10
i=5;//为全部变量i赋值
{
int i;//局部变量,局部作用域
i=7;
cout<<i<<endl;//输出7
}
cout<<i<<endl;//输出5
return 0;
}
运行之,参见下图:
变量的有效范围称为变量的作用域(scope),C++变量有三种作用域:文件作用域(file scope)、函数作用域(function scope)、块作用域(block scope)。文件作用域是全局的,函数作用和域块作用域是局部的。
函数的参数传递
函数参数传递的三种方式:
1)值传递(传值):
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
2)引用传递(传引用):
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。因此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
3)指针传递(传址):
形参指向实参地址,当对形参操作时,就相当于对实参本身进行的操作。指针变量中可以存放的是其它变量的地址, 向函数参数中传递指针变量,意味着指针变量这个形参指向实参变量的实际存储位置, 对实际位置中的内容进行操作, 实参的值自然就会跟着改变。
下面通过例子说明。
☆值传递
值传递是指向函数传递自身的一个副本, 也可以认为是自身的克隆, 他最大的一个特点就是函数对传入的副本进行操作不会影响到实参的本身。值传递的简单例子:
#include<iostream>
using namespace std ;
void fun(int val)
{
val = 100 ; //将传进来的参数的值更改为100
}
int main()
{
int n = 0 ;
fun(n); // 尝试通过函数改变n的值
cout<<"n = "<<n ;
return 0 ;
}
运行之,参见下图:
可以看到, n的值并未发生任何改变。
☆引用传递
引用传递不是使用实参的"副本", 而是真实的实参值在内存中的地址, 因此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。引用传递的简单例子:
#include<iostream>
using namespace std;
void fun(int &r)
{
r = 100 ; //通过引用改变实参的值
}
int main()
{
int n = 0 ;
int &rn = n ;
fun(rn) ; //传递n的引用rn
cout<<"n = "<<n ;
return 0 ;
}
运行之,参见下图:
☆指针传递
指针变量中可以存放的是其它变量的地址, 向函数参数中传递指针变量,意味着指针变量这个形参指向实参变量的实际存储位置, 对实际位置中的内容进行操作, 实参的值自然就会跟着改变。关于指针参见“C++指针”一文。指针传递的简单例子:
#include<iostream>
using namespace std;
void fun(int *p)
{
*p = 100 ; //通过指针p改变实参的值
}
int main()
{
int n = 0 ;
int *p = &n ;
fun(p) ; //将指向变量n的指针传入到函数fun
cout<<"n = "<<n ;
return 0 ;
}
运行之,参见下图:
内联函数
内联函数(inline函数)的作用就是优化被频繁调用的函数,就相当于把inline函数中的函数体直接复制到被调用的函数中一样, 不再使其频繁的中断。 定义inline函数非常简单, 只要在定义时在前面加上 inline 关键字即可, 例如:
#include<iostream>
using namespace std;
inline void fun() //在 void fun() 前有inline
{
cout<<"Hello, world!\n" ;
}
int main()
{
int i ;
for(i=0; i<2000; i++)
fun() ; //短时间内将会被调用2000次
return 0 ;
}
在使用inline函数时需要注意的几点问题:
☆inline 函数的函数体语句不适合过多,因为inline 函数以空间换时间 ;
☆inline 函数中不能有 循环、if或switch语句, 否则编译器将会把该函数当做普通函数来处理 ;
☆一个文件中定义的inline函数不能再另一个文件中使用,否则无效。
递归函数
递归算法(recursion algorithm)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。递归算法是一种直接或者间接调用自身函数或者方法的算法。
运用递归的条件:每一步进行的操作基本相同,并且问题规模逐渐减小。
递归的过程
递归,顾名思义,其包含了两个意思:递 和 归,这正是递归思想的精华所在。递归就是有去(递去)有回(归来),用递归求4!如下图所示:
用递归求阶乘的代码
#include<iostream>
using namespace std;
int fun(int n)
{
if(n == 0 or n == 1)
return 1;
else
return (n * fun(n - 1));
}
int main()
{
cout<<"4!结果" << fun(4) << "\n" ;
return 0 ;
}