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;
}
函数和结构
传递和返回结构
结构定义:
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次。
注意,每个递归调用都会创建自己的一套变量,所以程序在到达第五次调用的时候会有五个独立的变量,其中每个变量的值都不同。
包含多个递归调用的递归
递归方法有时被称为分而治之策略。
现演示多个递归调用的递归的实例:
//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);
}
函数指针
与数据项类似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。对程序而言,这些地址很有用,例如,可以编写将一个函数的地址作为参数的函数。它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。
函数指针的基础知识
例子:设计一个名为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学习:第七章的更多相关文章
- Java基础知识二次学习--第七章 容器
第七章 容器 时间:2017年4月27日15:08:30 章节:07章01节~07章04节 视频长度:20:21 +12:38 +3:55 +2:57 内容:容器API 心得: Java API ...
- C#高级编程 (第六版) 学习 第七章:委托和事件
第七章 委托和事件 回调(callback)函数是Windows编程的一个重要方面,实际上是方法调用的指针,也称为函数指针. .Net以委托的形式实现了函数指针的概念,.Net的委托是类型安全的. 委 ...
- C++ Primer Plus学习:第二章
C++入门第二章:开始学习C++ 进入C++ 首先,以下是一个C++程序: //myfirst.cpp 显示一行文字 #include<iostream> //预处理器编译指令 int m ...
- C语言学习第七章
今天开始学习指针,指针在C语言中具有很重要的地位,按照老师所说,学C学不好指针跟没学一样,可见指针在C语言中的重要地位.废话不多说,首先我们先要知道什么是指针. 指针:指针是一个变量,它存储另一个对象 ...
- C++ Primer Plus学习:第九章
C++第九章:内存模型与名称空间 C++在内存中存储数据方面提供了多种选择.可直接选择保留在内存中的时间长度(存储持续性)以及程序哪一部分可以访问数据(作用域和链接)等. 单独编译 程序分为三个部分: ...
- C++ Primer Plus学习:第一章
C++入门第一章:预备知识 C++简介 C++融合了三种不同的编程方式: C语言代表的过程性语言. C++在C语言基础上添加的类代表的面向对象语言. C++模板支持的泛型编程. C++简史 20世纪7 ...
- 【转载】Gradle学习 第七章:Java快速入门
转载地址:http://ask.android-studio.org/?/article/22 7.1. The Java plugin(Java插件) As we have seen, Gradle ...
- Python爬虫学习==>第七章:urllib库的基本使用方法
学习目的: urllib提供了url解析函数,所以需要学习正式步骤 Step1:什么是urllib urllib库是Python自带模块,是Python内置的HTTP请求库 包含4个模块: >& ...
- 区块链开发学习第七章:第一个Dapp-猜拳游戏
第一个简单的Dapp-猜拳游戏.本智能合约的功能很简单,就是用户与电脑猜拳,用户选择出手后,电脑随机一个选项,然后调用智能合约方法把两个选项值传过去,在智能合约上进行比较,并通过区块链合约事件广播结果 ...
- C primer plus 练习题 第七章
1. #include <stdio.h> #define SPACE ' ' #define NEWLINE '\n' int main() { int spaces,newlines, ...
随机推荐
- Hadoop HA 高可用集群搭建
一.首先配置集群信息 vi /etc/hosts 二.安装zookeeper 1.解压至/usr/hadoop/下 .tar.gz -C /usr/hadoop/ 2.进入/usr/hadoop/zo ...
- ACM--移动桌子--贪心--HDOJ 1050--Moving Tables
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Problem Descript ...
- 20155212 2016-2017-2 《Java程序设计》第10周学习总结
20155212 2016-2017-2 <Java程序设计>第10周学习总结 教材学习内容总结 密码学基础 ava API支持多种加密算法.如MessageDigest类,可以构建MD5 ...
- 20155226 实验四 Android开发基础
20155226第四次实验报告 一.实验内容及步骤 Android Stuidio的安装测试: 安装 Android Stuidio 完成Hello World, 要求修改res目录中的内容,Hell ...
- 20155310第一周JAVA实验报告
20155310第一周JAVA实验报告 实验内容 1.使用JDK编译.运行简单的Java程序: 2.使用Eclipse 编辑.编译.运行.调试Java程序. 实验要求 使用JDK和IDE编译.运行简单 ...
- kali更新源相关 -- 没有release文件、签名无效、404
kali更新源相关 -- 没有release文件.签名无效.404 这个随笔主要是处理Mac下使用VMare虚拟机安装Kali时候我遇到的一些关于更新源的问题 (因为本人为了这个问题折腾了四五个小时, ...
- vim 查找
一.用/和?的区别:/后跟查找的字符串.vim会显示文本中第一个出现的字符串.?后跟查找的字符串.vim会显示文本中最后一个出现的字符串.二.注意事项:不管用/还是?查找到第一个字符串后,按回车,vi ...
- 3-3 修改haproxy配置文件
1.需求 2.个人思路 3.个人心得 4.
- js页面动态时间展示
效果图: 具体代码 js代码 <script type="text/javascript"> var t = null; t = setTimeout(time,100 ...
- 理解依赖注入(Dependency Injection)
理解依赖注入 Yii2.0 使用了依赖注入的思想.正是使用这种模式,使得Yii2异常灵活和强大.千万不要以为这是很玄乎的东西,看完下面的两个例子就懂了. class SessionStorage { ...