C++ Primer Plus学习:第七章

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()!='\n')

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]='\0';

while(n-->0)

pstr[n]=c;

return pstr;

}

  1. 函数和结构

传递和返回结构

结构定义:

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;

}

  1. 函数和string对象

和普通的数据类型的操作相同。

例:

void display(const string s[],int n)

{

for (int i=0;i<n;i++)

cout<<i+1<<":"<<sa[i]<<endl;

}

  1. 函数与array对象

函数声明:

void show(std::array<double,4> da); //da是数组名。

或者:

void show(std::array<double,4> *pa); //pa是指针

注意,第一种的参数传递是全部复制,第二种只是传递地址,效率较高。

  1. 递归

C++函数有一个有趣的特点:可以调用自己(C++不允许main函数自己调用自己,C语言可行),这种功能被称为递归。

包含一个递归调用的递归

如下:

void recurs(argumentlist)

{

statements1

if (test)

recurs(arguments)

statements2

}

test最终为false,程序断开。

如果recurs进行了5次递归调用,statements1将按函数调用顺序执行5次,statements2将按函数调用相反的顺序执行5次。

注意,每个递归调用都会创建自己的一套变量,所以程序在到达第五次调用的时候会有五个独立的变量,其中每个变量的值都不同。

包含多个递归调用的递归

递归方法有时被称为分而治之策略。

现演示多个递归调用的递归的实例:

//ruler.cpp 使用递归来标定直尺的中点

#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]='\0';

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);

}

  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;

 

posted @ 2016-03-11 19:34  我是一个NLPer哦啦啦  阅读(1276)  评论(0编辑  收藏  举报