在设计程序时,可以将完成某一相对独立功能的程序定义为一个函数(functions)。在程序中,根据应用的需要,由自己定义函数,这类函数称为用户自定义的函数。C语言程序是从main()函数开始执行的。在C语言中没有类和对象概念,在程序模块中直接定义函数。可以认为,一个C程序是由若干个函数组成的,C语言被认为是面向函数的语言。C++面向过程的程序设计沿用了C语言使用函数的方法,在C++面向对象的程序设计中,主函数以外的函数大多是被封装在类中的,主函数或其他函数可以通过类对象调用类中的函数。有的函数完成某一操作,有的函数计算出一个值。通常,一个函数即能完成某一特定操作,又能计算数值。使用函数的目的:避免重复的编程和程序更加模块化,便于阅读、修改。
从用户使用的角度看,函数有两种:即库函数和用户自己定义的函数。从函数的形式看,函数分两类无参函数和有参函数。
本章演示函数的实例,如怎样定义函数,有参函数输入是字符串,函数参数直接引用变量,输入参数还可以缺省,main()函数接收参数。还演示了怎样用函数实现递归,函数重载和重载注意的问题。本章重点讲解了有参函数中输入参数和返回参数,返回可以多个参数,这些对于初学者反复多实践,加深理解,举一反三。
7.1 模块化带来的好处
本实例继续案例2-12随机数的演示,怎样产生随机数这个技术应用比较广泛,如在互联网上产生的验证码。下面的程序随机产生一个数,看是否正确,效果如图7-1所示。
图7-1 投骰子游戏(随机)
(1)定义枚举类型status,调用随机函数rolldice(),若游戏失败,看是否继续。其代码如下:
int rolldice(void);
int die1,die2,worksum;
int main()
{
//枚举类型,CONTINUE表示再次投骰子,WON、LOST分别表示游戏成功和失败
enum status{CONTINUE,WON,LOST};
int sum,mypoint;
enum status gamestatus;
sum=rolldice(); //调用随机函数,返回两个随机数的和
switch (sum) //根据两个随机数的和
{
case 7:
case 11: gamestatus=WON; //游戏获胜
break;
case 2:
case 3:
case 12: gamestatus=LOST; //游戏失败
break;
default: gamestatus=CONTINUE;
mypoint=sum;
cout<<"开出的点是:"<
break;
}
while (gamestatus==CONTINUE) //可以再次投骰子
{
sum=rolldice(); //调用随机函数,返回两个随机数的和
if (sum==mypoint)
gamestatus=WON;
else{
if(sum==7)
gamestatus=LOST;} //游戏失败
}
if (gamestatus==WON) //游戏获胜
cout <<"投骰者获胜"<
else
cout<<"投骰者输了"<
system("pause");
return 0;
}
(2)主函数定义一个随机种子,然后产生两个6以内的随机数,输出这两个随机数和它们的和。代码如下:
int rolldice(void)
{
time_t timenow;
srand((unsigned)time(&timenow)); //随机种子
//srand((unsigned)time(NULL));
die1=1+rand()%6; //产生6以内的随机数
die2=1+rand()%6;
worksum=die1+die2; //随机数相加
cout<<"投骰者骰数"<
return worksum;
}
(1)游戏者投两枚骰子,每个骰子分别有6面,每面包含1、2、3、4、5、6个点。投两次后计算点数之和。若第一次投的和,为7或11则游戏者获胜;和为2、3或12则游戏者输,庄家赢。如果第一次投的和为4、5、6、8、9或10,则这个和成为游戏者的点数。若想赢,就要继续投骰子,直到赚到点数。如果投7次之后还没有赚到点数,则游戏者输。
(2)代码中的N % i为取模运算,代码enum status{CONTINUE,WON,LOST};为枚举类型。
提示:for循环语句和do…while循环语句在实际编程中经常用到,读者应该细心观察。
本实例代码见实例215。编程中需要产生规定段整型的随机数。如常见的:游戏中随机数的生成,互联网图片验证码等,都是随机产生的,需要一定的算法。本例演示怎样利用随机函数rand()产生随机数,效果如图7-2所示。
图7-2 使用随机函数让程序带有随机性
函数rolldice(),功能是为投骰子计算和数、输出和数。代码实现如下:
int rolldice(void)
{ //投骰子、计算和数、输出和数
int die1,die2,worksum;
die1=1+rand()%6; //rand()%6随机产生一个数和6 取模运算
die2=1+rand()%6;
worksum=die1+die2;
cout<<"Player rolled"<
return worksum;
}
函数rand()%6;值为0到6。计算机的伪随机数是由随机种子根据一定的计算方法计算出来的数值。所以,只要计算方法一定,随机种子一定,那么产生的随机数就是固定的。只要用户或第三方不设置随机种子,那么在默认情况下随机种子来自系统时钟。
提示:随机数是由随机种子根据一定的计算方法计算出来的数值。所以,只要计算方法一定,随机种子一定,那么产生的随机数就不会变。
本例继续案例7-2演示,是个综合实例基本框架,涉及随机数和随机数种子,实例7-2已经讨论随机数产生问题,本实例重点随机数在掷骰子上的应用。本例效果如图7-3所示。
骰子、计算和数函数rolldice(),主函数输入随机数种子,将种子传递给rand()。代码实现如下:
图7-3 自定义函数生成一段随机数据
#include
#include
using namespace std;
int rolldice(void);
int main()
{
int gamestatus,sum,mypoint;
unsigned seed;
cout<<"Please enter an unsigned integer:";
cin>>seed; //输入随机数种子
srand(seed); //将种子传递给rand()
sum=rolldice(); //第一轮骰子、计算和数
switch(sum)
{
case 7: //如果结果和数为7或11则为胜,状态为1
case 11:
gamestatus=1;
break;
case 2: //和数为2、3或12则为负,状态为2
case 3:
case 12:
gamestatus=2;
break;
default: //其他情况,游戏尚无结果,状态为0,记下点数,为下一轮做准备
gamestatus=0;
mypoint=sum;
cout<<"point is"< break; } while(gamestatus==0) //只要状态为0,就继续进行下一轮 { sum=rolldice(); if(sum==mypoint) //某轮的和数等于点数则为胜,状态置为1 gamestatus=1; else //出现和数为7则为负,状态置为2 if(sum==7) gamestatus=2; //当状态不为0时上面的循环结果,以下程序段输出游戏结果 } if(gamestatus==1) cout<<"Player wins"< else cout<<"Plater loses"< system("pause"); return 0; } int rolldice(void) { //代码略 } (1)掷骰人的第一次掷骰称之为“现码”。为了完成“赌过关”赌注,只需要将筹码放在标记为“赌过关”的区域。如果掷骰子者达到自然数7或11,则获胜。如出手掷是2、3或12,则赌过关算输。如果骰子点数凑巧是4、5、6、8、9或10,则必须再次要那个点,而后掷7,算赢。 (2)srand()函数是随机数发生器的初始化函数,原型:void srand(unsigned seed)。 7.2 函数定义 使用函数可以把程序以更模块化的形式组织起来,从而利用好C++所能提供的所有结构化编程的潜力。C++同样支持面向过程的程序设计,其沿用了C语言使用函数的方法,函数是程序代码的一个自包含单元,用于完成某一个特定的任务。C++是由函数构成的,函数是C++的基本模块。 本实例是个格式打印日历,使用2个函数实现模块化,以便阅读和修改。本例效果如图7-4所示。 图7-4 格式打印(设计函数) 通过此程序可以查询任意一年的日历,输入要查询的年份,显示一年12个月每月的日历。系统总体结构:由f()、daysofmonth()和主函数构成;用f()判断元旦这一天是星期几;daysofmonth()输入的月和年,返回这个月的天数。用主函数调整日历输出格式。代码如下: #include #include int f(int year) //函数f()判断每年元旦是星期几 { int n=year-1900; n=n+(n-1)/4+1; n=n%7; return n; } int daysofmonth(int m,int year) //每月的天数 { switch (m) { case 1: case 3: case 5: case 7: case 8: case 10: case 12:return 31; //每月31天 case 4: case 6: case 9: case 11:return 30; //每月30天 case 2:if (((year%4==0 && year%100!=0)||year%400==0)) return 29; //闰年 else return 28; default: return 0; } } int main() //主函数 { int year,month,day,weekday,monthday,i; cout<<"请输入要查询的年份:"; cin>>year; cout<<"\n"< weekday=f(year); for(month=1;month<=12;month++) { cout<<"\n"< cout<<"****************************"< cout<<"日 一 二 三 四 五 六"< cout<<"****************************"< for(i=0;i cout<<" "; monthday=daysofmonth(month,year); for(day=1;day<=monthday;day++) { if(day>9) cout< else cout< weekday++; if(weekday==7) //满一个星期换行 { weekday=0; cout< } } cout< } system("pause"); return 0; } (1)代码((year%4==0 && year%100!=0)||year%400==0),判断这一年是不是闰年。函数f(int year),已知1900年元旦为星期一,用int n=year-1900;n=n+(n-1)/4+1;n=n%7;来判断每年的元旦是星期几。函数daysofmonth通过选择switch (m)得到各月不同的天数。 (2)有的函数完成某一操作如代码中的main(),有的函数计算出一个值如f(int year)。通常,一个函数只能完成某一特定操作,如计算数值。一个程序必须有且只有一个main( )函数,C++从main()函数开始执行。 (3)使用函数的目的是避免重复的编程和使程序更加模块化便于阅读、修改。所编写的函数应尽量少与主调函数发生联系,这样便于移植。 提示:一个源程序文件由一个或多个函数组成,编译程序是以文件而不是以函数为单位进行编译的。 本是例继续演示定义函数和函数有参输入。编程中根据应用的需要,由用户自己定义函数,这类函数称为用户自定义的函数,目的是实行程序模块化,本实例把特定数学运算求N的N次方,用一个函数实现。本例效果如图7-5所示。 图7-5 定义函数求N的N次方 第一个演示demo1(),利用循环方法和C++库函数pow();第二个演示demo2()比较复杂点,定义个整数数组a[200000]暂存产生进位,采用2个for循环N和进位。代码如下: #include #include using namespace std; int demo1(int number) { double k=1; int y; for (y=0;y { k=k*number; //每次循环乘以number } cout<<"循环计算出的"< cout< cout<<"pow("< return 0; } //求N*N的结果 int demo2(int number) { int a[200000]; int i,j,temp,m; //将已开辟内存空间a的首sizeof(a)/sizeof(int)个字节的值设为值0 memset(a,0,sizeof(a)/sizeof(int)); a[0]=1; m=1; temp=0; for(i=0;i { for(j=0;j { a[j]=a[j]*number; a[j]=a[j]+temp; temp=a[j]/10; //产生进位 a[j]=a[j]%10; //更新当前a[j]的值 if(temp&&j==m-1) //说明还有进位应当产生下一位,j==m-1 m++; } } cout<<"进位循环计算出的"< // printf("%d^%d=",number,number); while(m--) cout<
cout< return 0; } int main() { int number;//要计算的N的N次方的值 cout<<"输入要计算的N的N次方的值:"; cin>>number; demo1(number); cout <<"\n"; //换行 demo2(number); cout << "\n"; system("pause"); return 0; } (1)代码memset,原型void *memset(void *dest,int c,size_t count);将已开辟内存空间dest的首count 个字节的值设为值 c。 (2)上面代码pow(number,number),原型为double pow( double x, double y ),是C++的库函数实行求N的N次方功能。 提示:pow不能识别返回值264,如1.0E100.264。 main()主函数执行完毕后,是否可以再执行一段代码。可以用atexit()注册一个函数来实现这个功能。本实例就是演示主函数运行完再执行代码的实例,效果如图7-6所示。 图7-6 main()后执行代码 定义注册函数,在主函数中执行完毕,再执行注册过的函数。代码实现如下: # include # include int atexit(void(*function)(void)); //注册终止函数 #include void fn1(void),fn2(void),fn3(void),fn4(void); //函数声明 int main(void) { atexit(fn1); //注册终止函数 atexit(fn2); atexit(fn3); atexit(fn4); cout<<"This is executed first."< } void fn1() //主函数运行完再执行 { cout<<"next"< } void fn2() //主函数运行完再执行 { cout<<"executed"< } void fn3() //主函数运行完再执行 { cout<<"is"< printf("is\n"); //主函数运行完再执行 } void fn4() //主函数运行完再执行 { cout<<"this"< } 函数atexit()包含在头文件:#include 提示:按照ISO C的规定,一个进程可以登记多达32个函数,这些函数将由exit自动调用。atexit()注册的函数类型应为不接受任何参数的void函数,exit调用这些注册函数的顺序与它们登记时候的顺序相反。同一个函数如若登记多次,则也会被调用多次。 7.3 函数声明 本实例演示的函数的输入参数是个宽字符wchar_t,实际上函数输入参数可以是任意数据类型,这就增加了编程的灵活性,编程的时候不用考虑输入的数据类型。本例效果如图7-7所示。 图7-7 姓名测试 定义判断函数In()是否在编码区间内,convert()是个转换函数,根据单个字符的编码的高、低编码取英文字母。其代码如下: #include #include using namespace std; //wchar_t 是宽字符,占两个字节 bool In(wchar_t start, wchar_t end, wchar_t code) { if (code >= start && code <= end) //判断是否在编码区间内 { return true; } return false; } char convert(wchar_t n) { if (In(0xB0A1,0xB0C4,n)) return 'a'; //返回首字母 if (In(0XB0C5,0XB2C0,n)) return 'b'; if (In(0xB2C1,0xB4ED,n)) return 'c'; if (In(0xB4EE,0xB6E9,n)) return 'd'; if (In(0xB6EA,0xB7A1,n)) return 'e'; if (In(0xB7A2,0xB8c0,n)) return 'f'; if (In(0xB8C1,0xB9FD,n)) return 'g'; if (In(0xB9FE,0xBBF6,n)) return 'h'; if (In(0xBBF7,0xBFA5,n)) return 'j'; if (In(0xBFA6,0xC0AB,n)) return 'k'; if (In(0xC0AC,0xC2E7,n)) return 'l'; if (In(0xC2E8,0xC4C2,n)) return 'm'; if (In(0xC4C3,0xC5B5,n)) return 'n'; if (In(0xC5B6,0xC5BD,n)) return 'o'; if (In(0xC5BE,0xC6D9,n)) return 'p'; if (In(0xC6DA,0xC8BA,n)) return 'q'; if (In(0xC8BB,0xC8F5,n)) return 'r'; if (In(0xC8F6,0xCBF0,n)) return 's'; if (In(0xCBFA,0xCDD9,n)) return 't'; if (In(0xCDDA,0xCEF3,n)) return 'w'; if (In(0xCEF4,0xD188,n)) return 'x'; if (In(0xD1B9,0xD4D0,n)) return 'y'; if (In(0xD4D1,0xD7F9,n)) return 'z'; return '\0'; } int main(int argc, char* argv[]) { string sChinese = "张美丽"; //输入的字符串 char chr[3]; wchar_t wchr = 0; char* buff = new char[sChinese.length()/2]; //将已开辟内存空间buff的首sizeof(char)*sChinese.length()/2+1个字节的值置为 0x00 memset(buff, 0x00, sizeof(char)*sChinese.length()/2+1); for (int i = 0, j = 0; i < (sChinese.length()/2); ++i) { //将已开辟内存空间chr的首sizeof(chr)个字节的值设为值 0x00 memset(chr, 0x00, sizeof(chr)); chr[0] = sChinese[j++]; //字符赋值 chr[1] = sChinese[j++]; chr[2] = '\0'; //单个字符的编码,如:‘我’=0xced2 wchr = 0; wchr = (chr[0] & 0xff) << 8; //取高一个字节 wchr |= (chr[1] & 0xff); //取低一个字节 buff[i] = convert(wchr); } cout << "输入姓名 = [" << sChinese << "]" << endl; cout << "pin yin(首字母开头) = [" << buff << "]" << endl; system("pause"); return 0; } (1)wchar_t为C++宽整数类型,数据类型一般为16位或32位,包含在头文件stddef.h、stdlib.h中。(chr[1] & 0xff);取低1个字节;(chr[0] & 0xff) << 8;取高1个字节,这个在单片机和通信编程中经常用到。memset()为开辟内存空间设置字符,头文件包含在 (2)主函数main()根据输入buff开辟个空间buff,把输入中文字符串赋值给chr,取chr高、低1个字节,调用convert()函数转换,返回首字母。 提示:C++语言向下兼容C语言低级处理功能,如代码中取高、低字节,在编写底层软件的时候很有用。 7.4 函数的调用机制 在实际编程中经常用到有参函数。在有参函数中输入的参数常是个字符串,如姓名、密码之类的,然后对输入的字符串进行处理。本例就是输入姓名进行查找的通讯录信息系统,效果如图7-8所示。 图7-8 指令接收器(字符串形参) 程序定义类MyFriend,类的成员函数取得输入数据和打印通讯录信息;定义4个函数分别是输入数据、输出数据 、按姓名查询、字符串比较函数,对应func1()、func2()、func3()和comp()。代码如下: #include #include #include #include static int n=0; //朋友人数 int comp(char*,char*); //字符串比较函数 class MyFriend //定义个类 { unsigned int age; //年龄 char name[12]; //姓名 char TelNo[12]; //电话 public: void getdata() //取得输入数据 { cout<<"(年龄 姓名 电话):"; cin>>age>>name>>TelNo; } void disp() //显示通讯录信息 { cout< < } char *getname() //取得姓名 { return name; } }; void func1() //输入数据 { ofstream output("MyFrd.dat"); //存储通讯录文件 MyFriend s; cout<<"输入数据"< cout<<"我的朋友人数:"; cin>>n; for(int i=0;i { cout<<"第"<个我的朋友"; s.getdata(); output.write((char *)&s,sizeof(s)); //写入通讯录文件 }; output.close();//关闭文件 } void func2() //输出数据 { ifstream input("MyFrd.dat"); //存储通讯录文件 MyFriend s; cout<<"输入数据:"< cout< < input.read((char *)&s,sizeof(s)); //读取文件内容 while(input) { s.disp(); input.read((char*)&s,sizeof(s)); //读取文件内容 }; input.close(); //关闭文件 } void func3() //按姓名查询 { char sname[10]; ifstream file("MyFrd.dat"); //存储通讯录文件 MyFriend one; file.seekg(0); //对输入文件定位为开始位置 cout<<"输入要查询的姓名(可只输入姓氏):"; cin>>sname; cout<<"输出查询结果:"< cout< file.read((char *)&one,sizeof(one)); //读通讯录数据 while(file) { if(comp(one.getname(),sname)==1) //字符串比较函数 one.disp(); file.read((char*)&one,sizeof(one)); //读通讯录数据 }; file.close(); //关闭文件 } int comp(char s1[],char s2[]) //字符串比较函数 { int i=0; //不是最后一个字符,字符相同 while(s1[i]!='\0' && s2[i]!='\0' && s1[i]==s2[i]) i++; if(s1[i]=='\0' || s2[i]=='\0') //最后一个字符 return 1; else return 0; } void main() { int sel; do { cout<<"选择(1:输入数据 2:输出数据 3:按姓名查询 其他退出):"; cin>>sel; switch(sel) { case 1:func1();break; case 2:func2();break; case 3:func3();break; } }while(sel==1||sel==2||sel==3); //其他退出 system("pause"); } (1)代码中调用字符串比较函数comp(char*,char*),目的是比较和输入要查找字符串是否相同,第一个参数是从文件MyFrd.dat读出姓名,第二个是输入要查找字符串,用循环while判断每个字符是否相同。 (2)根据函数定义或调用时是否要给出参数,可将函数分为:无参函数和有参函数。 无参函数,主调函数并不将数据传给被调函数,无参函数主要用于完成某一操作。代码comp(char* s1,char* s2),是有参函数主调函数可以将参数传递给被调函数,被调函数中的结果也可以带回主调函数。 提示:在未出现函数调用时,形参并不占内存的存储单元。只有在函数开始调用时,形参才被分配内存单元,调用结束后,形参所占用的内存单元被释放。实参对形参变量的传递是“值传递”即单向传递。在内存中实参、形参分别占用不同的存储单元。形参只作用于被调函数,但可以在别的函数中使用相同的变量名。 在函数应用中数组可以作为函数的参数,也可以作为函数的返回值。如通讯录中,通讯录结构数组可以作为输入参数。作为参数时,用以向程序传递数组所占内存单元的首地址,这样增加了函数灵活性。本例演示的是输入学生信息和查找学生信息,效果如图7-9所示。 图7-9 求班级男女的人数 定义2个结构和3个函数,结构为出生日期date和学生信息address;第一个函数input()输入学生的基本信息,第二个函数output()输出学生的信息,第三个函数find()根据性别查找学生并显示信息。代码如下: #include #include #include struct date { int year; int month; int day; //定义一个出生日期结构 }; struct address { //定义姓名、性别、电话、出生日期 char name[10]; char sex;char tel[12]; date birthday; }; void input(address myfriend[],int &n) { int i=0; char name[10]; cout<<"输入同学姓名 ,用'#' 结束:"< cin>>name; while (strcmp(name,"#")!=0) //如果输入名字字符串为#,表示输入结束 { strcpy(myfriend[i].name,name); //字符数组复制 cout<<"继续输入同学的性别(F/M),电话和出生日期:"< cin>>myfriend[i].sex; cin>>myfriend[i].tel; cin>>myfriend[i].birthday.year>>myfriend[i].birthday.month>> myfriend[i].birthday.day; i++; cout<<"\n 输入同学姓名 ,用'#' 结束:"< cin>>name; } n=i; } void output(address myfriend[],int n) { int i; cout< < for(i=0;i { //设置输出方式 cout< < cout< cout< cout< } cout< } int find(address myfriend[],int n,char sex) { int i; bool findsuc=false; cout< < for(i=0;i { if(myfriend[i].sex==sex) //性别比较 { cout< < cout< cout< cout< findsuc=true; } } cout< if(!findsuc) return -1; else return 1; } void main() { address myfriend[50]; //最大50人 int n; int index; char findname[10]; //定义输入查找姓名字符串 char sex; input(myfriend,n); //输入数据 output(myfriend,n); //显示输入数据 cout<<"查找性别?"; cin>>sex; index=find(myfriend,n,sex); //查找男、女生 if(index==-1) cout<<" 没有查找到!"< } (1)在代码void input(address myfriend[],int &n),数组可以作为函数的参数,也可以作为函数的返回值;作为参数时,用以向程序传递数组所占内存单元的首地址。原型:返回类型 函数名(数组名[ ],其他参数),在函数内部使用siziof(数组名)返回的只是指针大小,而不是数组大小。一般情况下,在参数列表中显示注明数组元素的个数。当然,如果能保证不会出现越界访问的情况,这个参数可以省略。理论上,”数组名[]”中[]是空的,如果在其中写明元素大小,编译器也不予理会,不会进行错误检验。address myfriend[],myfriendshi是address型结构数组。 (2)多维数组也可作为函数参数,第一维大小可以省略,但其他维的大小是不能省略的,如int x[][5][6]。 提示:以数组作为函数参数,通过指针进行通信,是上级调用函数向被调用函数传递大量数据的一种方法。 一般而言函数返回只有一个值或者无,但是返回值可以不局限于return后所返回值的个数,完全可以使用引址传参,返回多个值。如输入需读取图像文件名,返回多个图像头文件数据。下面就是一个可以返回3个值的函数,效果如图7-10所示。 图7-10 分水果(使函数一次性返回N个值) 程序分3个演示,demo1()调用函数add(int a,int b),函数体内新分配一个数组new int[3],然后在调用该函数的函数体内删除掉;demo2()调用函数add(int *temp,int a,int b),向函数体内传入1个指向整数的指针int *temp;demo3调用函数Factor(),就是一个可以返回3个值的函数。其代码如下: #include "iostream" #include "iostream.h" #include #include short Factor(int n,int * p1,int *p2) { short Value=0; if(n>3000) //大于3000输入出错 { Value=1; } else { //赋值的同时,地址不变。 *p1=n*n; *p2=n*n*n; Value=0; } return Value; } //函数体内new一个数组,然后在调用该函数的函数体内delete掉 int* add(int a,int b) { int *num=new int[3]; //此处new了一个数组,别忘了delete掉 a=a+b; num[0]=a; num[1]=b; num[2]='\0'; return num; //返回数组首地址 } //向函数体内传入一个数组,将需要保存的值放入其中 void add(int *temp,int a,int b) { a=a+b; temp[0]=a; temp[1]=b; } int demo1() { int a=1;int b=32;int *p=0; p=add(a,b); //p接收了num的首地址,释放时要delete []p for (int i=0;*(p+i)!='\0';i++) { cout<<*(p+i)< } delete []p; //释放p p=0; //重新设置指针,这样才能使释放后的空间重新利用 return 0; } int demo2() { int num[2]; add(num,1,31); cout<<*num< cout<<*(num+1)< return 0; } int demo3() { int num,value1,value2; char *name[]={"老大分得","老二分得","老三分得"}; int A[]={0,0,0}; //数组存放各自所分到的数量 short error; cout<<"输入一个数:(0-3000):"; cin>>num; A[0]=num/2-1; A[1]=num/5-1; A[2]=num/3-1; //传num的值,通过处理求得value1、value2的值,两者都是引址传参 error=Factor(num,&value1,&value2); if(!error) { for(int j=0;j<3;j++) cout< < < < } else { cout<<"输入错误!\n"; } return 0; } int main() { demo1(); cout <<"\n"; //换行 demo2(); cout << "\n"; demo3(); cout << "\n"; system("pause"); return 0; } (1)demo1()调用函数int* add(int a,int b),add函数内新建了一个数组int *num=new int[3];赋值给num[0]、num[1]、num[,3];代码return num;返回数组首地址。 (2)demo3()调用函数Factor(),Factor就是一个可以返回三个值的函数,有一个是return返回,另外两个是根据地址传递,存储的值改变了但地址不变。 提示:除上面方法外,在函数中返回一个结构体或结构指针,由于结构体中有多个值,所以当返回结构体的时候,结构体外就可以取得结构体中相应的值了。 编程中,有的时候从可读性出发,调用函数的时候缺省一个或多个输入参数。如通讯编程中设置输入参数只要求输入个端口号,其他往往是默认的值,程序运行中能自动识别默认值。本例演示参数缺省问题,效果如图7-11所示。 图7-11 补充代码并保证变量A的值等于10 定义函数fac()和area(),函数demo1()采用了缺省调用fac(),demo2()采用了缺省调用area()和area()。代码实现如下: #include #include int fac(int n=2) //阶乘计算 { int t=1; for(int i=1;i<=n;i++) t=t*i; return t; } void demo1(void) { cout<<"fac(6)="; cout<< fac(6) < cout<<"fac()="; cout<< fac() < } int area(int len=5 , int width=4) //计算面积 { return len* width+1; //返回值 } void demo2(void ) { int a=3, b=3; cout<<"area(a,b)="; cout<< area(a,b) < cout<<"area(a)="; cout<< area(a) < cout<<"area()="; cout<< area( ) < } int main() { demo1(); cout <<"\n"; //换行 demo2(); cout << "\n"; system("pause"); return 0; } (1)area(int len=5 , int width=4) 只有一个参数被指明,但函数允许有两个参数。因为已经定义了它为该参数缺省的默认值,注意函数声明中的int width=4。 (2)在C++定义函数时,允许给参数指定一个缺省的值。在调用函数时,若给出了这种实参的值,则使用相应实参的值;若没有给出相应的实参,则使用缺省的值。 提示:使用具有缺省参数的函数时,应注意以下几点: 1、不可以靠左边缺省,如int area(int long , int width=2),但可以是int area(int long =4, int width) ; 2、函数原型说明时可以不加变量名,如float v(float,float=10,float=20); 3、只能在前面定义一次缺省值,即原型说明时定义了缺省值,后面函数的定义不可有缺省值。 常量和变量可用作函数实参,数组元素也可作函数实参其用法与变量相同。数组名也可以作实参和形参,传递的是数组的起始地址,此时实参与形参都用数组名,本例效果如图7-12所示。 有一个3×4的矩阵,求矩阵中所有元素中的最大值,要求用函数处理。代码实现如下: #include using namespace std; int main( ) { int max_value(int array[][4]); //函数声明 //定义3×4数组 int a[3][4]={{62,22,34,67},{12,24,36,88},{25,62,43,77}}; cout<<"max value is "< system("pause"); return 0; } int max_value(int array[][4]) //形参array是数组名 { int i,j,max; max=array[0][0]; for( i=0;i<3;i++) //2个for循环 for(j=0;j<4;j++) if(array[i][j]>max) max=array[i][j]; //取最大值 return max; //返回最大值 } 形参可以是数组名,也可以是指针变量,这些用来接收实参传来的地址。如果形参是数组名,代表的是形参数组首元素的地址。在调用函数时,将实参数组首元素的地址传递给形参数组名。这样实参数组和形参数组就共占同一段内存单元。 提示:在用变量作函数参数时,只能将实参变量的值传给形参变量,在调用函数过程中如果改变了形参的值,对实参没有影响,即实参的值不因形参的值改变而改变。 一般而言函数返回只有一个值或者无,但是返回值可以不局限于return后所接的个数,完全可以使用引址传参,返回多个值。如输入需读取固定格式文件,需返回多个头文件数据。下面就是一个可以返回3个值的函数,效果如图7-13所示。 图7-13 让函数一次返回多个值 程序调用函数fun(),函数输入是地址,返回3个值。代码实现如下: #include"iostream.h" #include "iostream" void fun(int& a,int& b,int& c) //输入3个整数地址 { a=a+2; b=b*3; c=c-1; } void main() { int a,b,c; //定义3个整数 cout<<"请输入三个数"< cin>>a>>b>>c; //取得3个整数 fun(a,b,c); //调用函数 cout<<"a="<输出整数 cout<<"b="<
cout<<"c="< system("pause"); } 主函数调用函数fun(),就是一个可以返回3个值的函数。3个值是根据地址传递,保持值改变,可是地址不变。 提示:除上面方法外,在函数中返回一个结构体或结构指针,由于结构体中有多个值,所以,当返回结构体的时候,结构体外就可以取得结构体中相应的值了。 7.5 递归函数 在排序和阶乘运算中,采用递归方法可降低问题的复杂度。除了main()函数外,其他函数是可以递归调用的。函数的递归调用指的是调用一个函数的过程中直接或间接地调用函数本身,是一种通用的编程技术。本实例就是用递归实现阶乘计算,效果如图7-14所示。 图7-14 阶乘计算1到100的积 程序主要是一个递归函数fac(int n),输入一个阶乘计算的数,在函数中判断输入是不能为负数,0!和1!的值为1,n>1时进行递归调用返回计算结果。代码实现如下: #include using namespace std; long fac(int); //函数声明 int main( ) { int n; //n为需要求阶乘的整数 float y; //y为存放n!的变量 cout<<"输入一个阶乘计算的数 n:\n"; //输入的提示信息 cin>>n; //输入n y=fac(n); //调用fac函数以求n! cout<<"阶乘计算结果为:"< return 0; } long fac(int n) //递归函数 { float f; if(n<0) { //如果输入为负数,报错并以-1作为返回值 cout<<"n<0,负数不能做阶乘计算!"< f=-1; } else if (n==0||n==1) f=1; //0!和1!的值为1 else f=fac(n-1)*n; //n>1时,进行递归调用 return f; //将f的值作为函数值返回 system("pause"); } (1)递归方法对排序和阶乘运算很有用。例如要获得一个数字n的阶乘,它的数学公式是:n! = n * (n-1) * (n-2) * (n-3) ... * 1 ,如4! 的值是4! =4 * 3 * 2 * 1 = 24。 (2)C++允许函数的递归调用,例如:int f(int x){int y,z; z=f(y);return (2*z); },在调用函数f的过程中,又要调用f函数。可以看出程序无终止的自身调用,显然,程序中不应出现这种无终止的递归调用,而只应出现有限次数的有终止的递归调用。这可以用if语句来控制,只有在某一条件成立时才继续执行递归调用,否则就不再继续。 提示:许多问题既可以用递归方法来处理,也可以用非递归方法来处理。在实现递归时,在时间和空间上的开销比较大,但符合人们的思路,程序容易理解。 本例是综合实例基本框架,涉及到递归和嵌套调用,实例103已经讨论过递归问题,本实例通过圈套调用,函数套函数实现编写程序逐步求精目的。本例效果如图7-15所示。 图7-15 互动式程序的基本框架 函数move()打印每次移动盘子信息,hanoi()函数应用递归实现盘子移动,一个盘子移动一次,后面的其他情况递归实现和嵌套调用move()。代码实现如下: #include #include void move(char one,char anoth) { cout< } // void hanoi(int n,char no1,char no2,char no3) { if (n==1) move(no1,no3); //函数的嵌套调用 else { hanoi(n-1,no1,no3,no2); //函数的递归调用 move(no1,no3); //函数的嵌套调用 hanoi(n-1,no2,no1,no3); //函数的递归调用 } } void main() { void hanoi(int n,char no1,char no2,char no3); int m; cout<<"请输入A柱上的金盘子总数:"; cin>>m; cout<<"当有"< hanoi(m,'A','B','C'); system("pause"); } (1)这是汉诺塔问题,是源于印度一个古老传说的益智玩具。如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序,需要多少次移动。这就需要用到递归的方法了。假设有n片,移动次数是f(n)。显然f(1)=1,f(2)=3,f(3)=7,且f(k+1)=2*f(k)+1。此后不难证明f(n)=2^n-1。n=64时,f(64)= 2^64-1=18446744073709551615。 (2)函数hanoi()是递归调用,函数中又嵌套调用move()。C++语言中所有函数都是平行独立的,无主次、相互包含之分。函数可以嵌套调用,不可嵌套定义。在main()函数中调用hanoi()函数,在hanoi()函数中又调用move()函数。 提示:在程序中实现函数嵌套调用时,需要注意的是:在调用函数之前,需要对每一个被调用的函数作声明,除非定义在前,调用在后。 编写通讯程序或对硬件操作程序的时候,经常要对数字进行进制数转换,如十进制转换成十六进制,如单片机控制输入是一个整数、输出是一字节一字节的字符,以便显示数字。本例效果如图7-16所示。 图7-16 编写一个进制数转换器 定义3个函数fun_1()、fun_2()和fun_3(),分别是十进制数转换成二进制数字、十进制数字转换成八进制数字、十进制数转换成十六进制数字。主函数取得一个输入数字,调用进制转换函数。其代码如下: #include #include //十进制数转换成二进制数字 void fun_1(int num) { if(num<2) cout< if(num>=2) //大于等于2 { fun_1(num/2); //递归 cout< } } //十进制数字转换成八进制数字 void fun_2(int num) { if(num<8) cout< if(num>=8) { fun_2(num/8); //递归 cout< } } //十进制数转换成十六进制数字 void fun_3(int num) { switch(num) //输出对应字符 { case 10: cout<<"A"; break; case 11: cout<<"B"; break; case 12: cout<<"C"; break; case 13: cout<<"D"; break; case 14: cout<<"E"; break; case 15: cout<<"F"; break; default: cout< } } void fun_4(int num) { if(num<16) fun_3(num); //小于16 if(num>=16) //大于等于16 { fun_4(num/16); //递归 fun_3(num%16); //取模运算 } } //主函数 void main() { int num; cout<<"请输入num的值: "< cin>>num; cout<<"十进制数字转换成二进制数字结果:"< fun_1(num); cout< cout<<"八进制数字结果:"; fun_2(num); cout< cout<<"十六进制数字结果:"; fun_4(num); cout< system("pause"); } (1)上面代码3个函数是个进制转换函数,根据用户输入的数字进行转换。 (2)上述代码用到递归如fun_1(num/2),在调用一个函数的过程中直接或间接地调用函数本身,称为函数的递归调用。 提示:许多问题既可以用递归方法来处理,也可以用非递归方法来处理。在实现递归时,在时间和空间上的开销比较大,但符合人们的思路,程序容易理解。 栈是一种先进后出的一种数据结构,在程序设计中广泛使用。栈的基本操作有:压栈、出栈。递归搜索就是利用栈的这个特征,不是最优或达不到目标,就退回上一步重新选择,记录下具体搜索的步骤。如本实例实现递归搜索,实现放置皇后位置的算法。如图7-17所示。 图7-17 皇后位置放置(栈+回溯法) 程序定义存放皇后位置值的变量a[8][8],调用递归搜索试探放置皇后函数Queen(),Queen中调用判断a[i][j]是否可以放置皇后的函数Judge()。其代码如下: #include using namespace std; void Queen(int j,int (*a)[8]); //递归搜索试探放置皇后 int Judge(int i,int j,int (*a)[8]); //判断在a[i][j]是否可以放置皇后 int ncount = 0; int main() { int i,j; int a[8][8] = {0}; //存储皇后位置的值 Queen(0,a); //调用递归搜索 cout< system("pause"); return 0; } void Queen(int j,int (*a)[8]) { int i,k; if (j==8) //递归结束条件 { ncount++; for (i=0; i<8; i++) //打印正确的放置 { for (k=0; k<8; k++) {【案例分析】
案例7-4 格式打印(设计函数)
【案例描述】
【实现过程】
【案例分析】
案例7-5 定义函数求N的N次方
【案例描述】
【实现过程】
【案例分析】
案例7-6 main()后执行代码
【案例描述】
【实现过程】
【案例分析】
案例7-7 姓名测试
【案例描述】
【实现过程】
【案例分析】
案例7-8 指令接收器(字符串形参)
【案例描述】
【实现过程】
【案例分析】
案例7-9 求班级男女的人数
【案例描述】
【实现过程】
【案例分析】
案例7-10 分水果(使函数一次性返回N个值)
【案例描述】
【实现过程】
【案例分析】
案例7-11 补充代码并保证变量A的值等于10
【案例描述】
【实现过程】
【案例分析】
案例7-12 使用数组名作函数参数
【案例描述】
【实现过程】
【案例分析】
案例7-13 让函数一次返回多个值
【案例描述】
【实现过程】
【案例分析】
案例7-14 阶乘计算1到100的积
【案例描述】
【实现过程】
【案例分析】
案例7-15 互动式程序的基本框架
【案例描述】
【实现过程】
【案例分析】
案例7-16 编写一个进制数转换器
【案例描述】
【实现过程】
【案例分析】
案例7-17 皇后位置放置(栈+回溯法)
【案例描述】
【实现过程】