C++入门第七章:函数-C++的编程模块
-
函数的基本知识
要使用C++函数,必须完成如下工作:
- 提供函数定义
- 提供函数原型
- 调用函数
库函数是已经定义和编译好的函数,可使用标准库头文件提供原型。
定义函数的模板:
typename functionName(parameterList)
{
statements
return value;
}
对于有返回值的函数,必须使用return语句返回。可以是常量、变量或者是表达式。其结果的类型只能为typename,若不是,会进行强制类型转换。
C++对返回值的类型有一定的限制:不能使数组,但可以是其他任何类型——整数、浮点数、指针甚至可以是结构和对象!(数组可以作为结构或者对象的部分来返回)
通常,函数通过将返回值复制到指定的cpu寄存器或者内存单元来将其返回。随后,调用程序将查看这个内存单元。
函数在执行返回语句后结束,如果函数中包含多条返回语句,执行第一条后结束。
函数原型
函数原型描述了函数到编译器的接口,也就是说,它将函数返回至的类型(若有)以及参数的类型和数量告诉编译器。
定义原型最简单的方法就是复制定义函数的函数头并加上分号。当然,定义原型并不需要变量名。
-
函数参数和按值传递
例:
double volume=cube(side);
double cube(double x);
调用函数时,该函数将创建一个名为x的变量,并初始化为side的值。函数cube()接收的是side的副本而不是side。用于接收传递值的变量被称为形参。传递给函数的值被称为实参。处于简化的目的,C++使用参数来表示实参,使用参量来表示形参。
在函数中声明的变量(包括参数)是该函数私有的。在函数被调用时,计算机将为这些变量分配内存,函数结束时,计算机将释放这些内存。这些变量被称为局部变量。
-
函数和数组
使用数组作为函数的参数:
例:int sum(int a[],int n)
注:在参数传递时,程序将数组的位置(指针)传递给函数,并不是整个数组。
C++和C语言一样,将数组名视为指针,C++将数组名解释为第一个元素的地址。
例外:
- 数组声明使用数组名来标记存储位置
- 对数组名使用sizeof()将得到整个数组的长度
- 将地址运算符&用于数组名时,将返回整个数组的地址。
使用函数填充数组
函数示例如下:
函数原型:
int fill_array(double a[],int limit);
函数的核心代码如下:
int fill_array(double a[],int limit)
{
using namespace std;
double temp;
int i;
for (i=0;i<limit;i++)
{
cin>>temp;
if (!cin)
{
cin.clear();
while(cin.get()!=' ')
continue;
cout<<"错误";
break;
}
else if (temp<0)
break;
a[i]=temp;
}
return i;
}
显示数组及用const保护数组
使用函数时,必须确保函数不修改原始数组,除非函数的目的就是修改传递给它的数据。
接受数组名的函数将使用原始数据。
为防止函数无意中修改数组的内容,声明形参时使用关键字:
例:void show_array(const double a[],int n)
上述函数并不是意味着不能使用a修改该数据,而是意味这将数组a[]视为只读数组,如果修改数组中的元素,编译器会报错。
使用数组区间的函数
对于处理数组的C++函数,必须将数据种类、数组的起始位置和数组中元素的数量提交给它。
还有另一种给函数提供所需信息的方法,即指定元素区间(range),这可以通过传递两个指针来完成,一个指针标识数组的开头,另一指针标识数组的结尾。STL使用"超尾"来指定区间,也就是说标识数组结尾的参数将会是指向数组结尾的指针。
例:
函数原型:int sum(const int *begin,const int *end);
调用:sum1=sum(a,a+3); //a的定义:int a[3]
函数:
int sum(const int *begin,const int *end)
{
const int *pt;
int total=0;
for (pt=begin;pt!=end;pt++)
total+=pt;
return total;
}
指针和const
const用于指针有一些很微妙的地方。可以使用两种方式将关键字const用于指针。第一种方法是让指针指向一个常量对象,这样能够防止使用该指针修改所指向的值,第二种方法是将指针变量本身声明为常量,这样能够防止指针指向的位置。
例:
int age;
const int *pt=&age;
对pt而言,age 是常量,不可使用*pt修改age的值。
还有一种情况:
const int age;
const int *pt=&age;
变量和指针均为const类型。
C++规定,禁止将const地址赋给非const指针。如果非要这样做,可以使用强制类型转换。
即,以下操作时不允许的:
const int a[3]={1,2,3};
有一函数的原型:int sum(int a[],int n)
上述函数调用试图将const指针赋给非const变量,编译器将禁止这种函数的调用。
建议:尽量使用const
理由:
- 避免由于无意间修改数据造成编程错误
- 使得const变量能够处理const和非const实参,否则只能接受非const数据。
如果条件允许,则应将指针形参声明为指向const的指针。
示例:
int age=4;
int * const pa=&age;
上述声明意味着可以使用*pa来修改age的值,但不允许pa指向另一个位置。
当然,如果使用如下定义:
const int * const pa=&age;
则表示pa只能指向age,而且*pa不能修改age的值。
-
函数和二维数组
正确的声明:
int sum(int a[][3],int size); //其中,int a[4][3];
上述声明指出,a是指针而不是数组,指针类型还指出,它是由三个指向int类型的指针组成的数组。
指针类型制定了行数,
调用函数:
sum(a,3); //计算所有a的值
或者:sum(a+2,1); //计算最后一行的值
如果我们声明一个二维数组:int a[100][20];
则a[r][c]=*(*(a+r)+c);完全等价
其中,
a代表指向第一行100个数组的指针。
a+r代表指向第r+1行数组的指针(指向r+1行首元素地址的指针)。
*(a+r)代表第r+1行。(指向r+1行首元素的指针)
*(a+r)+c代表指向r+1行第c+1个元素的指针。
*(*(a+r)+c)代表r+1行第c+1个元素。
5 函数和C-风格字符串
C-风格字符串有一系列字符组成,以空值字符结尾,作为参数时意味着传递地址。
若要将字符串作为参数传递给函数,表示字符串的方式有三种:
- char数组
- 使用引号括起的字符串常量(字符串字面值)
- 被设置为字符串地址的char指针
声明方式:int str(const char c[],char ch);
处理字符串中字符的标准方式:
while(*str)
{
statements
str++;
}
返回C-风格字符串的函数
函数声明:
char *buildstr(char c,int n)
调用:
char *ps=buildstr(ch,times); //字符串指针的初始化,ch='a',times=10;
函数:
char *buildstr(char c,int n)
{
char *pstr=new char [n+1];
pstr[n]=' ';
while(n-->0)
pstr[n]=c;
return pstr;
}
-
函数和结构
传递和返回结构
结构定义:
struct student
{
char name[20];
int age;
};
函数声明:student sum(const student s1,student s2);
函数调用:student s=sum(s1,s2); //初始化
函数:
student sum(const student s1,student s2)
{
student s;
s.age=s1.age+s2.age;
strcpy(s.name,s1.name);
strcat(s.name,s2.name);
return s;
}
按照上述操作,参数传递的是整个结构,如果结构很大,效率很低。
传递结构的地址
如果要传递结构的地址的话,需要修改三个地方,以上述函数为例:
- 调用函数时,将结构的地址(&s)而不是结构本身(s)传递给它
- 将形参声明为指向student的指针,即*student。
- 由于形参是指针而不是结构,因此使用间接成员运算符(->)而不是成员运算符(句点)。
修改后的程序如下:
结构定义:
struct student
{
char name[20];
int age;
};
函数声明:student sum(const student *s1,const student *s2);
函数调用:student s=sum(s1,s2); //初始化
函数:
student sum(const student s1,const student s2)
{
student s;
s->age=s1->age+s2->age;
strcpy(s->name,s1->name);
strcat(s->name,s2->name);
return s;
}
-
函数和string对象
和普通的数据类型的操作相同。
例:
void display(const string s[],int n)
{
for (int i=0;i<n;i++)
cout<<i+1<<":"<<sa[i]<<endl;
}
-
函数与array对象
函数声明:
void show(std::array<double,4> da); //da是数组名。
或者:
void show(std::array<double,4> *pa); //pa是指针
注意,第一种的参数传递是全部复制,第二种只是传递地址,效率较高。
-
递归
C++函数有一个有趣的特点:可以调用自己(C++不允许main函数自己调用自己,C语言可行),这种功能被称为递归。
包含一个递归调用的递归
如下:
void recurs(argumentlist)
{
statements1
if (test)
recurs(arguments)
statements2
}
test最终为false,程序断开。
如果recurs进行了5次递归调用,statements1将按函数调用顺序执行5次,statements2将按函数调用相反的顺序执行5次。
注意,每个递归调用都会创建自己的一套变量,所以程序在到达第五次调用的时候会有五个独立的变量,其中每个变量的值都不同。
包含多个递归调用的递归
递归方法有时被称为分而治之策略。
现演示多个递归调用的递归的实例:
#include<iostream>
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]=' ';
int max=Len-2;
int min=0;
ruler[min]=ruler[max]='|';
std::cout<<ruler<<std::endl;
for (i=1;i<=Divs;i++)
{
subdivide(ruler,min,max,i);
std::cout<<ruler<<std::endl;
for (int j=i;j<Len-2;j++)
ruler[j]=' ';
}
return 0;
}
void subdivide(char a[],int low,int high,int level)
{
if (level==0)
return 0;
int mid=(high+low)/2;
ar[mid]='|';
subdivide(ar,low,mid,level-1);
subdivide(ar,mid,high,level-1);
}
-
函数指针
与数据项类似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。对程序而言,这些地址很有用,例如,可以编写将一个函数的地址作为参数的函数。它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。
函数指针的基础知识
例子:设计一个名为estimate()的函数,估算编写指定行数代码所需要的时间,并且希望不同程序员使用该函数。对于用户看来说,estimate()中一部分代码是相同的,但函数允许每个程序员提供自己的算法来估计时间。为此,我们采用将不同程序员使用的算法函数的地址作为参数传递给estimate(),为此,必须完成以下工作:
- 获取函数地址
- 声明一个函数指针
- 使用函数指针来调用函数
获取函数地址:函数名即为函数的地址。
例如:函数int sum(int a,int b)的地址为sum。
声明函数指针:声明时应指出函数的返回值类型以及函数的特征表(参数列表)。
以函数int sum(int a,int b)为例,应按以下方法声明指向该函数的指针:
int (*ps)(int,int);其中,ps为函数指针。
ps=sum;//表示将ps指向函数sum
使用指针调用函数的两种方法:
函数int sum(int a,int b)为例,
int (*ps)(int,int); //ps为函数指针。
ps=sum; //表示将ps指向函数sum
函数调用方法:
常规:sum(a,b)
指针:(*ps)(a,b)或者ps(a,b)
示例(仅核心代码):
函数定义:
double betsy(int lns)
{
return 2*lns;
}
double pam(int lns)
{
return 2*lns+5*lns*lns;
}
void estimate(int lines,double (*pf)(int))
{
using namespace std;
cout<<lines<<"lines will take "<<(*pf)(lines)<<"hours";
}
main函数:
int main()
{
using namespace std;
int code=90;
estimate(code,betsy);
estimate(code,pam);
return 0;
}
深入探讨函数指针
例:声明一个函数指针:
const double *(*p1)(const double *,int);
也可在声明的同时进行初始化:
const double *(*p1)(const double *,int)=f1;
也可使用C++11的自动类型推断功能
auto p2=f2;
可完成函数指针p2的定义,代码大大简化,但是仅能用于单值,不能用于列表。
函数指针数组:
定义与初始化:
const double * (*pa[3])(const double *,int)={f1,f2,f3};
可使用typedef进行简化
typedef可创建类型别名
typedef double real; //创建double的别名real
typedef const double *(*p_fun)(const double *,int);
使用:p_fun p1=f1;