函数
一、函数基本知识
要使用c++函数,必须完成如下工作:
- 提供函数定义;
- 提供函数原型;
- 调用函数;
1.定义函数
可以将函数分成两类:没有返回值的函数和有返回值的函数。
//没有返回值的函数被称为void函数。
void functionName(parameterList//指定传参的参数类型和数量)
{
statement(s);
return;
}
//有返回值的函数将生成一个值,并将它返回给调用函数
typeName functionName(parameterList)
{
statements;
return value;
}
2.函数原型和函数调用
//函数原型:函数原型是一条语句,因此必须以分号结束。获得原型最简单的方法是:复制函数定义中的函数头,并添加分号。
double cube(double x);
//函数原型不要求提供变量名,有类型列表就足够了。对于cheers()的原型,该程序只提供了参数类型:
void cheers(int);
实例:
#include <iostream>
using namespace std;
void cheers(int);
double cube(double);
int main()
{
cheers(5);
cout<<"Give me a number:";
double side;
cin>>side;
double volume = cube(side);
cout<<"A"<<side<<"-foot cube has a volume of";
cout<<volume<<"cubic feet"<<endl;
cheers(cube(2));
return 0;
}
void cheers(int n)
{
for(int i=0;i<n;i++)
cout<<"Cheers!"<<endl;
}
double cube(double x)
{
return x*x*x;
}
main()使用函数名和参数来调用void类型的函数:cheers(5);这是一个函数调用语句。但由于cube()
有返回值,因此main()
可以将其用在赋值语句中。
double volume = cube(side);
3.函数调用
调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值。如:
cheers(5);
double volume = cube(side);
4.多个参数
函数可以有多个参数,在调用函数时,只需要使用逗号将这些参数分开。
n_chars("R",25);
可以这样定义:
void n_chars(char c,int n)
实例:
#include <iostream>
using namespace std;
void n_chars(char,int);
int main()
{
int times;
char ch;
cout<<"Enter a character:";
cin>>ch;
while(ch !='q')
{
cout<<"Enter an integer:";
cin>>times;
n_chars(ch,times);
cout<<"\nEnter another character or press yhe q-key to quit:";
cin>>ch;
}
cout<<"time is"<<times<<endl;
return 0;
}
void n_chars(char c, int n)
{
while(n-->0)
cout<<c;
}
二、函数调用
1.传值调用
向函数传递参数的值,即把参数的值复制给函数的形式参数。
这时修改函数内的形式参数,并不会影响到函数外的实际参数,
形参不改变实参。
实例:
#include <iostream>
using namespace std;
int main()
{
int x=10;
int y=20;
cout<<"x原值="<<x<<endl;
cout<<"y原值="<<y<<endl;
swap(x,y);
cout<<"x后值="<<x<<endl;
cout<<"y后值="<<y<<endl;
return 0;
}
void swap(int a,int b)
{
int temp;
temp=a;
a=b;
b=temp;
}
2.指针调用
向函数传递参数的指针,即把参数的地址复制给形式参数,在函数内,该地址用于访问要用到的实际参数,这意味着修改形式参数会改变实际参数。
实例:
#include <iostream>
using namespace std;
void swap(int *a,int *b);
int main()
{
int x=10;
int y=20;
cout<<"x原值="<<x<<endl;
cout<<"y原值="<<y<<endl;
swap(&x,&y);
cout<<"x后值="<<x<<endl;
cout<<"y后值="<<y<<endl;
return 0;
}
void swap(int *a,int *b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
3.引用调用
向函数传递参数的引用,即把引用的地址复制给形式参数,在函数内,该引用用于访问要用到的实际参数,这也意味着修改形式参数会改变实际参数。
实例:
include <iostream>
using namespace std;
void swap(int &a,int &b);
int main()
{
int x=10;
int y=20;
cout<<"x原值="<<x<<endl;
cout<<"y原值="<<y<<endl;
swap(x,y);
cout<<"x后值="<<x<<endl;
cout<<"y后值="<<y<<endl;
return 0;
}
void swap(int &a,int &b)
{
int temp;
temp=a;
a=b;
b=temp;
}
三、函数和c-风格字符串,string对象,array对象,函数和数组,函数和结构,函数和递归
1.将c-风格字符串作为参数的函数
若要将字符串作为参数传递给函数,则表示字符串的方式有三种:
-
char数组。
-
用引号括起的字符串常量。
-
被设置为字符串的地址的char指针。
但三种情况的类型都是char指针,因此可以用作字符串处理函数的参数:
char ghots[15]="galloping";
char * str ="galumphing";
int n1=strlen(ghots);
int n2=strlen(str);
int n3=strlrn("galumphing");
可以说将字符串作为参数来传递,但实际传递的是字符串第一个字符的地址。
2.返回c-风格字符串的函数
编写一个返回字符串的函数,函数无法返回一个字符串,但可以返回字符串的地址。
实例:下面程序定义一个名为buildstr()
函数,该函数返回一个指针,该函数接受两个参数:字符和数字。函数使用new创建一个长度与数字参数相等的字符串,将每个元素初始化为该函数。然后,返回指向新字符串的指针·。
#include <iostream>
using namespace std;
char * buildstr(char c,int n);
int main()
{
int times;
char ch;
cout<<"Enter a character:";
cin>>ch;
cout<<"Enter an integer:";
cin>>times;
char *ps=buildstr(ch,times);
cout<<ps<<endl;
delete [] ps;
ps=buildstr('+',20);
cout<<ps<<"-DONE"<<ps<<endl;
delete [] ps;
return 0;
}
char * buildstr(char c, int n)
{
char * pstr=new char(n+1);
pstr[n]='\0';
while (n-->0)//循环n,直到n减少到0
pstr[n]=c;
return pstr;
}
3.函数和string对象
-
C-风格字符串和string对象的用途相同,但与数组相比,string对象与结构更相似。如:可以将结构赋给结构,对象赋给对象。可以将结构作为完整的实体传递给函数,也可以将对象作为完整的实体传递。
-
需要多个字符串时,可以声明一个string对象数组,而不是二维char数组。
实例:下面程序它声明了一个string对象数组,并将该数组传递给一个函数:
#include <iostream>
#include<string>
using namespace std;
const int SIZE=5;
void display(const string sa[],int n);
int main()
{
string list[SIZE];
cout<<"Enter your "<<SIZE<<"favorite astronomical sights:"<<endl;
for(int i=0;i<SIZE;i++)
{
cout<<i+1<<": ";
getline(cin,list[i]);
}
cout<<"Your list:"<<endl;
display(list,SIZE);
return 0;
}
void display(const string sa[], int n)
{
for(int i=0;i<n;i++)
cout<<i+1<<": "<<sa[i]<<endl;
}
4.函数与array对象
在C++中,类对象是基于结构的,因此结构编程方面的有些因素也适用于类,如下面使用c++模板类array的例子,使用array对象存储一年四季的开支:
array<double,4>expenses;
//若函数来显示expenses的内容,可安值传递expenses:
show(expenses);
//修改对象expenses,则需将对象的地址传递给函数:
fill(&expenses);
实例:
#include <iostream>
#include<string>
#include<array>
using namespace std;
const int Seasons=4;
const array<string,Seasons> Snames=
{"Spring","Summer","Fall","Winter" };
void fill(array<double,Seasons>*pa);
void show(array<double,Seasons> da);
int main()
{
array<double,Seasons>expenses;
fill(&expenses);
show(expenses);
return 0;
}
void fill(array<double,Seasons>*pa)
{
for(int i=0;i<Seasons;i++)
{
cout<<"Enter"<<Snames[i]<<"expenses:";
cin>>(*pa)[i];
}
}
void show(array<double, Seasons> da)
{
double total=0;
cout<<"\nEXPENSES\n";
for(int i=0;i<Seasons;i++)
{
cout<<Snames[i]<<":$"<<da[i]<<endl;
total+=da[i];
}
cout<<"Total Expenses:$"<<total<<endl;
}
注:模板array并非只能存储基本数据类型,它还可以存储类对象。上面const array
对象Snames是在所有函数之前声明的,因此可以在如何函数定义中使用它。
5.函数和数组
- 函数是处理更复杂的类型(如数组和结构)的关键,如:
int sum_arr(int arr[],int n)
。 - 方括号指出
arr
是一个数组,而方括号为空则表明,可以将任何的数组传递给该函数。 arr
实际上并不是数组,而是指针,在编写函数其余部分时,可以将arr看做数组。
实例:
面程序用数组那样使用指针的情况。并使用sum_arr()
函数计算总数,注意点sum_arr()
函数使用arr时,就像是使用数组名一样。
#include <iostream>
using namespace std;
const int ArSize = 8;
int sum_arr(int arr[], int n);
int mian()
{
int cookies[ArSize] = { 1,2,4,8,16,32,64,128 };
int sum = sum_arr(cookies, ArSize);
cout << "Total cookies eaten:" << sum << endl;
return 0;
}
int sum_arr(int arr[], int n)
{
int total = 0;
for (int i = 0; i < n; i++)
total = total + arr[i];
return total;
}
-
C++和C语言一样,也将数组名视为指针。c++将数组名计解释为其第一个元素的地址:
cookies==&cookies[0];
-
首先,数组声明使用数组名来标记存储位置,次,对数组名使用sizeof将得到整个数组的长度;最后,将地址运算符&用于数组名时,将返回整个数组的地址。
//函数调用:
int sum=sum_arr(cookies,ArSize);
//cookies是数组名,而根据c++规则,cookies是其第一个元素的地址,因此函数传递的是地址。
//由于数组的元素的类型为int,因此cookies的类型必须是int指针,即 int*。
//正确的函数头应该如下面所示:
int sum_arr(int * arr,int n);
//其中用int *arr代替了int arr[]。都意味着arr是一个int指针。arr不仅指向int,还指向int数组的第一个int。
变量arr实际上就是一个指针。无论arr是指针还是数组名,表达式arr[3]
指的是数组的第四个元素。下面的两个恒等式重要应该记住:
arr[i]==*(arr+1)
&arr[i]==arr+I;
将指针加1,实际上是加上了一个与指针指向的类型的长度的相等值。
6.显示数组及用const保护数组
创建显示数组步骤:将数组名和填充的元素数目传递给函数,然后该函数使用循环来表示每个元素。为防止函数无意修改数组的内容,可在声明形参时使用关键字const
:
void show_array(const double ar[],int n)
{
for(int i=0;i<n;i++)
{
cout<<"~~~"<<(i+1)<<": ";
cout<<ar[i]<<endl;
}
}
7.修改数组
对数组进行的第三项操作是每个元素与同一个重新评估因子相乘。需要传递三个参数:因子,数组和元素的数目。该函数不需要返回值:
void revalue(double r,double ar[],int n)
{
for(int i=0;i<n;i++)
ar[i] *=r;
}
//由于这个函数修改数组的值,因此在声明ar时,不能使用const。
8.数组处理函数的常用编写方式
编写一个double数组的函数,若函数修改数组,原型如下:
void arr_sum(double ar[],int n);
函数不修改数组,原型如下:
void arr_sum(const double ar[],int n);
9.函数和二维数组
编写二维数组作为参数的函数,数组名被视为其地址,因此,相应的形参是一个指针,就像一维数组一样,比较难处理的是如何正确使用指针 。例如:
int data[3][4]={{1,2,3,4},{9,8,7,6},{2,4,6,8}};
int total=sum(data,3);
//data是一个数组名,该数组有三个元素,第一个元素本身是一个数组,由4个int值组成。
因此data的类型是指向由4个int组成的数组的指针。因此正确的类型如下:
int sum(int (*ar2)[4],int size);
其中的括号是必不可少的,因为下面的声明将声明一个由4个指向int的指针组成的数组,而不是由一个指向由4个int组成的数组的指针。另外,函数参数不能是数组:
int *ar2[4]
还有另外一种格式,这种格式与上述原型的含义完全相同,但可读性更强:
int sum(int ar2[][4],int size);
由于参数ar2是指向数组的指针,那么我们如何在函数定义中使用它呢?最简单的方式是将ar2看做是一个二维数组的名称。下面是一个可行的定义:
int sum(int ar2[][4],int size)
{
int total=0;
for(int r=0;r<ssize;r++)
for(int c=0;c<4;c++)
total+=ar2[r][c];
return total;
}
ar2[r]
是编号为r的元素,由于该元素本身就是由4个int组成的数组,因此ar2[r]
是由4个int组成的数组中的一个元素。是一个int的值。必须对指针ar2执行两次解除引用,才能得到数据。最简单方式是使用方括号两次:ar[r][c]
。
10.函数和结构
//定义结构:
struct travel_time
{
int hours;
int times;
};
//返回两个这种结构的总和的sum()的原型
//返回值的类型应为travel_time,两个参数也应为这种类型
travel_time sum(travel_time t1,travel_time t2);
实例:
#include <iostream>
using namespace std;
struct travel_time
{
int hours;
int mins;
};
const int Mins_per_hr=60;
travel_time sum(travel_time t1,travel_time t2);
void show_time(travel_time t);
int main()
{
travel_time day1={5,45};
travel_time day2={4,55};
travel_time trip =sum(day1,day2);
cout<<"Two-day total:";
show_time(trip);
travel_time day3={4,332};
cout<<"Three-day total:";
show_time(sum(trip,day3));
return 0;
}
travel_time sum(travel_time t1, travel_time t2)//构造函数
{
travel_time total;
total.mins=(t1.mins+t2.mins)%Mins_per_hr;
total.hours=t1.hours+t2.hours+
(t1.mins+t2.mins)/Mins_per_hr;
return total;
}
void show_time(travel_time t)
{
cout<<t.hours<<"hours,"<<t.mins<<"minutes"<<endl;
}
首先将分钟成员相加,然后通过整数除法得到小时值,通过求模运算符%得到剩余的分钟数。
实例:
下面的程序将定义两个结构来处理空间,用于表示两种不同的描述位置的方法,然后开发函数,将一种方式转换另一种格式。
#include <iostream>
#include<cmath>
using namespace std;
struct polar//第一种直角坐标系
{
double distance;
double angle;
};
struct rect//第二种距离和方向
{
double x;
double y;
};
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
int main()
{
rect rplace;
polar pplace;
cout << "Enter the x and y values:";
while (cin >> rplace.x >> rplace.y)
{
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "Next two numbers (q to quit):";
}
cout << "Done" << endl;
return 0;
}
polar rect_to_polar(rect xypos)
{
polar answer;
answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y);//计算距离
answer.angle = atan2(xypos.y, xypos.x);//计算角度
return answer;
}
void show_polar(polar dapos)
{
const double Rad_to_deg = 57.29577951;
cout << "distance=" << dapos.distance;
cout << ",angle=" << dapos.angle * Rad_to_deg;
cout << "degrees" << endl;
}
11.传递结构
要传递结构的地址而不是整个结构以节省的时间和空间,使用结构指针。需要重新编写show_polar()
函数,注意事项:
- 调用函数时,将结构的地址
(&pplace)
而不是结构本身(pplace)
传递给它。 - 将形参声明为指向polar的指针,即
polar*
类型,由于函数不应该修改结构,因此使用const修饰符。 - 形参是指针而不是结构,应该使用间接成员运算符
(->)
而不是句点。
修改后:
void show_polar(const polar * pda)
{
const double Rad_to_deg = 57.29577951;
cout << "distance=" << pda->distance;
cout << ",angle=" << pda->angle * Rad_to_deg;
cout << "degrees" << endl;
}
12.函数和递归
调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。
若递归函数调用自己,则被调用的函数也将调用自己,这将无限循环下去,除非代码中包含终止调用的内容通常的方法将递归调用放在if语句
中,如:void类型的递归函数recurs()的代码如下:
void recurs(argumentlist)
{
statements1
if(test)
recurs(arguments)
statement2
}
- test最终将为false,调用链将断开。
- 只要if语句为true,每个recurs()调用都将执行statements1,然后调用recurs(),而不会执行statement。
- 当if语句为false,当前调用将执行statement2。
- 若
recurs()
进行了5次递归调用,则第一个statement1部分将按函数调用的顺序执行5次,然后statement2部分将以与函数调用相反的顺序执行5次。 - 进入5层递归后,程序将返回。
实例1:
#include <iostream>
using namespace std;
void countdown(int n);
int main()
{
countdown(4);
return 0;
}
void countdown(int n)
{
cout << "Counting down..." << n << endl;
if (n > 0)
countdown(n - 1);//不继续往下执行,将一直调用此函数
cout << n << ":Kaboom!" << endl;
}1
实例2:使用递归计算第5个学生的年龄,第1为10岁,第5比第4大2岁,第4比第3大2岁,第3比第2大2岁,第2比第1大2岁。要得到第5个学生年龄必须知道第三,依次类推。
数学公式表达:
#include <stdio.h>
int age(int n);
int main()
{
printf("ON5,age:%d\n",age(5));
return 0;
}
int age(int n)
{
int c;
if(n==1)
c=10;
else
c=age(n-1)+2;//递归四次,n=5是在main函数中调用
return c;
}
/*步骤一:回溯
* n=5在main函数调用
*5:n=5; c5=age(5-1)+2;
*
*下面四次在age函数递归调用自己
*4:n=4; c4=age(4-1)+2;
*3:n=3; c3=age(3-1)+2;
*2:n=2; c2=age(2-1)+2;
*1:n=1; n=1不在递归; c1=age(1)=10;
*
*
*步骤二:递归
*n=1时,c1=age(1)=10;
*n=2时,c2=age(1)+2=12;
*n=3时;c3=age(2)+2=14;
*n=4时;c4=age(3)+2=16;
*n=5时;c5=age(4)+2=18;
*/
13.多个递归
实例:
下面程序使用递归函数subdivide()
,该函数使用一个字符串,该字符串除两端为|字符外,其他为空格。mina()
函数使用循环循环调用subdivide()
函数6次。函数调用自身时,直至减到0,不在调用自己。
#include <iostream>
using namespace std;
const int Len = 66;
const int Divs = 6;
void subdivide(char ar[], int low, int high, int level);
int main()
{
char ruler[Len];
int i;
for (i = 1; i < Len - 2; i++)
ruler[i] = ' ';
ruler[Len - 1] = '\0';
int max = Len - 2;
int min = 0;
ruler[min] = ruler[max] = '|';
cout << ruler << endl;
for (i = 1; i <= Divs; i++)
{
subdivide(ruler, min, max, i);
cout << ruler << endl;
for (int j = 1; j < Len - 2; j++)
ruler[j] = ' ';
}
return 0;
}
void subdivide(char ar[], int low, int high, int level)
{
if (level == 0)
return ;
int mid = (high + low) / 2;
ar[mid] = '|';
subdivide(ar, low, mid, level - 1);
subdivide(ar, mid, high, level - 1);
}