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;

C++ Primer Plus学习:第七章的更多相关文章

  1. Java基础知识二次学习--第七章 容器

    第七章 容器   时间:2017年4月27日15:08:30 章节:07章01节~07章04节 视频长度:20:21 +12:38 +3:55 +2:57 内容:容器API 心得: Java API ...

  2. C#高级编程 (第六版) 学习 第七章:委托和事件

    第七章 委托和事件 回调(callback)函数是Windows编程的一个重要方面,实际上是方法调用的指针,也称为函数指针. .Net以委托的形式实现了函数指针的概念,.Net的委托是类型安全的. 委 ...

  3. C++ Primer Plus学习:第二章

    C++入门第二章:开始学习C++ 进入C++ 首先,以下是一个C++程序: //myfirst.cpp 显示一行文字 #include<iostream> //预处理器编译指令 int m ...

  4. C语言学习第七章

    今天开始学习指针,指针在C语言中具有很重要的地位,按照老师所说,学C学不好指针跟没学一样,可见指针在C语言中的重要地位.废话不多说,首先我们先要知道什么是指针. 指针:指针是一个变量,它存储另一个对象 ...

  5. C++ Primer Plus学习:第九章

    C++第九章:内存模型与名称空间 C++在内存中存储数据方面提供了多种选择.可直接选择保留在内存中的时间长度(存储持续性)以及程序哪一部分可以访问数据(作用域和链接)等. 单独编译 程序分为三个部分: ...

  6. C++ Primer Plus学习:第一章

    C++入门第一章:预备知识 C++简介 C++融合了三种不同的编程方式: C语言代表的过程性语言. C++在C语言基础上添加的类代表的面向对象语言. C++模板支持的泛型编程. C++简史 20世纪7 ...

  7. 【转载】Gradle学习 第七章:Java快速入门

    转载地址:http://ask.android-studio.org/?/article/22 7.1. The Java plugin(Java插件) As we have seen, Gradle ...

  8. Python爬虫学习==>第七章:urllib库的基本使用方法

    学习目的: urllib提供了url解析函数,所以需要学习正式步骤 Step1:什么是urllib urllib库是Python自带模块,是Python内置的HTTP请求库 包含4个模块: >& ...

  9. 区块链开发学习第七章:第一个Dapp-猜拳游戏

    第一个简单的Dapp-猜拳游戏.本智能合约的功能很简单,就是用户与电脑猜拳,用户选择出手后,电脑随机一个选项,然后调用智能合约方法把两个选项值传过去,在智能合约上进行比较,并通过区块链合约事件广播结果 ...

  10. C primer plus 练习题 第七章

    1. #include <stdio.h> #define SPACE ' ' #define NEWLINE '\n' int main() { int spaces,newlines, ...

随机推荐

  1. laravel5.5源码阅读草稿——application

    构建方法传入整个项目根目录路径(public文件夹上一级)将其设为基础路径(存在本类basePath属性中). __construct > setBasePath > bindPathsI ...

  2. Python - 魔法字符串

    ''' #capitalize() ---首字母转换为大写--- s="sslssd" v=s.capitalize(); print(v) ''' ''' #center(20, ...

  3. Cloudera Manager 安装集群遇到的坑

    Cloudera Manager 安装集群遇到的坑 多次安装集群,但每次都不能顺利,都会遇到很多很多的坑,今天就过去踩过的坑简单的总结一下,希望已经踩了的和正在踩的童鞋能够借鉴一下,希望对你们能有所帮 ...

  4. C#串口通信及数据表格存储

    1.开发环境 系统:win10 开发工具:Visual Studio 2017 2.界面设计 串口通信的界面大致如此,在此基础上添加项目所需的调试指令与数据存储功能,界面排布方面可参考其他教程. 3. ...

  5. python学习之简介与环境安装

    [转自]http://www.cnblogs.com/wupeiqi/articles/5433925.html --Python可以应用于众多领域 如:数据分析.组件集成.网络服务.图像处理.数值计 ...

  6. C语言堆排序

    堆是一种类似二叉树的数据结构,分为最大堆和最小堆,最大堆得定义是当前节点必须大于左右子节点,堆中所有节点都要符合这个定义.最小堆反之.这一点不同于二叉树排序.假设有数组int a[10] = {90, ...

  7. 基于visual studio 2017 以及cubemx 搭建stm32的开发环境(2)

    主要解决 vs2017中,printf无法打印数据的问题. 在keil环境下正常使用printf功能,但是以下的重定向代码在vs2017下使用不了: #ifdef __GNUC__ /* With G ...

  8. Go 跨域请求问题

    在使用go语言写测试服务的时候遇到了前端跨域请求问题,只需在go中加入 w.Header().Set("Access-Control-Allow-Origin", "*& ...

  9. scala 求数组排序后每两个元素的差值

    求数组排序后每两个元素的差值 例如数组 1,5,8,10,2 求得结果为 1,3,3,2 一般什么样的场景会有这种需求呢? 比如 计算一堆数据在一定时间内的计算时延, 或者得到这段时间内数据的平均计算 ...

  10. 20155301 2016-2017-2 《Java程序设计》第8周学习总结

    20155301 2016-2017-2 <Java程序设计>第8周学习总结 教材学习内容总结 1.java.util.logging包提供了日志功能相关类与接口.使用日志的起点是logg ...