C++第四章:结构化程序设计(一)

设计编写大型程序有2种方法

  1. 结构化程序设计方法
  2. 面向对象程序设计方法

结构化程序设计的基本概念

程序描述了某种数组处理的过程和步骤,数据时程序处理的对象,一个复杂的程序任务,可能要处理大量数据,为此c++提供了数组的语法形式,数组可以存储大量同类型的数据。

将数据处理的过程,细分成一组严格的操作步骤,这组操作步骤就叫做算法。如果数据处理的算法很长很复杂。

结构性程序设计方法,就是将一个很长很复杂的程序设计任务分解成多个简单的模块,分而治之,然后将这些模块组装起来,最终完成数据处理算法。

c++支持结构化程序设计的方法,以函数的语法形式来描述和组装模块,这就是函数的定义和调用。****

将一个数据处理过程,分解成多个模块,各模块之间需要共享数据,c++语言提供了分散管理集中管理这两种数据管理策略

一、结构性程序设计方法

机构性程序设计又叫面向过程的程序设计方法,基本方法如下:

将一个求解复杂问题的过程划分为若干个子过程,每个子过程完成一个独立的、相对简单的功能;用算法描述各个过程的操作步骤,每个算法称为一个模块;采用“自顶向下,逐步细化”的方法逐步分解和设计算法模块,再通过调用关系将各个模块组织起来,最终形成一个完整的数据处理算法。

采用结构性程序设计方法,程序员重点考虑的是如何分解和设计算法。

基于模块的协助开发:

  • 自顶向下,逐步细化

    将复杂的算法逐步分解成功能单一的,可重复调用的算法

  • 模块重用

    结构化程序设计,应当合并重复模块,合并后的模块,可以被不同的上层模块调用,同一模块可以被多个模块调用,或被一个上层模块调用多次,这称为模块的重用或共用。

  • 主模块和被调模块

    算法被划分成多个模块之后,调用其他模块的模块,称为主调模块,而被其他模块调用的模块称为被调模块,可以将不同的模块设计任务,交给不同程序员去完成。模块之间应当事先协调好被调函数的调用接口。

    1. 被调模块的名称
    2. 被调模块需要输入参数
    3. 被调模块应该返回什么样的结果,又称返回值。

结构化程序设计方法:

  1. 模块化是团队开发的基础
  2. 模块接口时团队协助的基础
  3. 模块重用影响到大型软件开发的组织与管理方式

代码重用:

  • 现在的软件开发现目可以重用以前项目所开发的代码
  • 一个软件项目可以重用另一个现目中的代码,即跨项目重用
  • 可以重用本单位已有的代码,亦可以购买外单位已有的代码,或委托外单位开发所需的子模块,即跨组织机构重用

模块的4大要素

  1. 模块名称
  2. 输入参数
  3. 返回值
  4. 算法

模块的设计者和调用者具有不同的角色,不同角色对模块及其4大要素的理解是不一样的。

模块设计者

站在模块设计者的角度,他要思考的是接收输入参数,将输入参数作为原始数据进行处理,得到计算结果,并返回该结果,模块设计者,重点考虑处理算法,该算法因当能够按照模块的功能要求返回正确的计算结果,就是返回正确的返回值,模块算法是属于模块设计者的知识产权。而模块名称、输入参数、返回值是模块设计者为他人使用算法提供的开发接口,通称为模块的调用接口。调用接口必须是公开的,否则他人无法使用,算法是模块内部的实现细节可以不对外公开,在模块设计中,只有拿到模块设计的源程序,才能了解模块算法的实现细节,通常模块经常是以编译后的机器语言提供给他人使用的。

模块调用者

模块其实可以看做一个函数f(x),调用函数f(x)就是给的某个具体的x的值,就能得到对应的函数结果,模块名称相当于是函数名,输入参数是原始数据相当于自变量x,模块的计算结果就是返回值,相当于函数值,调用者通过模块名称调用模块,调用时按要求给定具体的输入参数值,然后接收返回值,得到所需的计算结果,调用者只需了解调用接口,即可调用模块,无需了解模块内部的算法。

主模块与子模块

将一个复杂算法分解成多个模块,其中一个为主模块,由它负责调用其他模块,主模块不能被其他模块调用,除了子模块之外的其他模块统称为子模块,子模块可以被调用,也可以调用其他子模块。

c++语言支持结构化程序设计方法,以函数的语法形式来描述和组装模块,即函数的定义和调用

二、函数的定义和调用

任务说明:

设计任务:公园修建1个长方形观赏鱼池,另外配套修建一大一小2个圆形蓄水池,分别存放清水和污水,养鱼池和蓄水池造假均为10元/m^2。请设计一个测算总工厂造价的算法。

2.1 c++语法:定义函数

函数类型 函数名(形式参数列表)
{
    函数体
}

/*举例*/
double func(double r)
{
    double s;
    s = 3.14*r*r;
    return s;
}
  • 函数类型:定义的是函数返回值(即函数值)的数据类型,函数类型由函数功能决定,可以是除数组之外的任何数据类型,省略时默认为int型,某些函数可能只是完成某种功能,但没有返回值,此时函数类型应定义为void;
  • 函数名:定义函数的名名称,由程序员命名,需复合标识符的命名规则,通常函数之间不能重名;
  • 形式参数列表:定义了函数接收输入参数所需的变量,这些变量称为形式参数,简称为形参,可以有多个形参,每个形参以“数据类型 变量名”的形式定义,形参之间用逗号“,”隔开,某些函数可能不需要输入参数,此时形参列表省略为空。
  • 函数体:是描述数据处理算法的c++语句序列,用大括号{}括起来,函数体中可以定义专供本函数使用的变量,如果函数有返回值,则应用return语句返回,返回值的数据类型应与函数类型一致。
  • 函数类型 函数名(形式参数列表),此部分被称为函数头,它定义了函数的调用接口,即函数名、输入参数和返回值类型。

2.2 函数的调用

c++语法:

函数名(实际参数列表)
  • 函数名:指定被调用函数的名称;
  • 实际参数列表:指定函数所需要输入的参数,调用函数时应按被调用函数的要去给定具体的输入参数值,这些参数称为实际参数,简称实参,实际参数可以是常量、变量或表达式,参数之间用逗号","隔开,调用时,首先将实参值按位置顺序一一赋值给对应的形式参数变量,这称为函数调用时的参数传递,实参于形参应当个数一致,类型一致。
  • “函数名(实际参数列表)” 是调用某个函数,有返回值的函数调用可作为操作数参与表达式运算,该操作数等于函数返回值,某些函数可能只是完成某些功能,但是没有返回值。无返回值得函数调用加分号";"即构成一条函数调用语句;
  • 一个函数调用另一个函数,调用别人得函数称为主调函数,被调用得函数称为被调函数

实例:

#include <iostream>
using namespace std;
/*用于求长方形鱼池的造价*/
double RectCost(double x, double y)
{
    double s;
    s = x*y*10;
    return s;
}
/*用于求圆形鱼池的造价*/
double CircleCost(double r)
{
    double s;
    s = r*3.14*r*10;
    return s;
}
/*主函数,通过向被调函数形参赋值,调用子函数完成整个计算功能*/
int main()
{
    double a,b,r1,r2,totalcost=0;
    cout << "请输入长方形的长宽:"<<endl;
    cin >> a>>b;  //在函数调用里面,一次性输入多个参数,不能使用cin>>a>>b,这种写法导致最后的参数b是不能传递的。
    cout << "请输入圆形的半径"<<endl;
    cin >> r1>>r2;

    totalcost += RectCost(a,b);
    totalcost += CircleCost(r1);
    totalcost += CircleCost(r2);
    cout<<"总造价为:"<<totalcost<<endl;

    return 0;
}

数据参数化:函数的形式参数,就是将要处理的原始数据,经过提炼,而形成的变量,它是提高函数代码重用性,扩大重用范围的主要手段。

c++语言主调函数和被调函数执行过程详解

一个c++程序,可以有并且只有一个名为main()的主函数,可以没有子函数,也可以包含多个子函数,计算机执行程序,是从主函数第一条语句开始执行的,一直执行到最后一条语句结束,或者是执行到主函数的return语句中途退出。

如果主函数调用了某个子函数,计算机将在执行的函数调用语句时,暂停主函数的执行,跳转去执行子函数的函数体,执行完子函数的函数体,或执行到其中的return语句时,停止子函数的执行,返回主函数,继续执行剩余的指令,子函数可以调用其他的子函数。就形成了函数的嵌套调用。

计算机在执行函数调用语句时,主函数和被调函数之间有2次数据传递,

  • 主调函数按位置顺序将实参一一传递并赋值给被调函数的形参。作用是将主调函数中的原始数据传递给被调函数,这就是函数调用时的参数传递。
  • 被调函数使用return语句,将计算结果返回给主调函数,主调函数接收返回值,如果函数没有形式参数,则成为无参函数,调用无参函数传递时没有参数传递,一个函数也可以没有返回值,该函数的函数类型应该指定为void,调用没有返回值的函数,就没有返回值了,

被调函数使用return语句加返回值,返回给主调函数,return语句的语法有2种方式:

return(表达式);//表达式可以是:常量/变量,如果是常量或者变量时,可以省略小括号
或
return; //不带返回值
  • 如果函数有返回值,则应当用return(表达式); 语句结束函数的执行,返回主调函数,表达式指定返回时的返回值,其数据类似应当与函数类型一致,常量或变量可认为是一个最简单的表达式。,小括号可以省略
  • 如果函数类型为void,即没有返回值,则可以使用return; 语句结束函数执行,返回主调函数,如果省略return; 语句,则在执行完函数体种最后一条语句后,自动返回主函数。

可以使用return语句,简化代码:

/*以前文设计的计算长方形养鱼池造假举例*/
double RectCost(double a, double b)
{
    return(a*b*10); //可以直接一个表达式,在主程序调用时,或自动使用该表达式计算结果。
}

2.3 函数的声明

c++程序中,在调用函数之前,因当声明被调函数的函数原型,就是函数的调用接口,其中包括函数名称、形式参数列表和函数类型。

函数类型 函数名(形式参数列表);
//例如:
double RectCost(double, double)
  • 一个函数的原始声明语句可简单认为是有该函数定义中的函数头+分号";"组成
  • 形式参数列表中的数据类型是函数原型声明中必须包含的信息,它除了指明形参的数据类型之外,还暗含了形参个数,而形参变量名不重要,可以省略,形参名的作用是为了便于调用该函数的程序员理解其实际含义
  • c++源程序中,被调函数的原型声明语句可以放在主调函数定义之前,或整个源程序文件的前面,也可以放在主调函数的函数体里面,但必须在该函数的调用语句之前
  • 如果被调函数和主调函数定义在同一个源程序文件中,并且被调函数定义在主调函数之前,则被调函数的原型声明语句可以省略。
  • 声明函数原型的目的是将被调函数的调用接口预先告知编译器程序,这样编译器程序就可以按照该调用接口检查接下来的函数调用语句是否正确。

实列:

#include <iostream>
using namespace std;

double RectCost(double, double);
double CircleCost(double);
/*主函数在前,被调函数在后,故必须声明被调函数。*/
int main()
{
    double a,b,r1,r2,totalcost=0;
    cout << "请输入长方形的长宽:"<<endl;
    cin >> a>>b; 
    cout << "请输入圆形的半径"<<endl;
    cin >> r1>>r2;

    totalcost += RectCost(a,b);
    totalcost += CircleCost(r1);
    totalcost += CircleCost(r2);
    cout<<"总造价为:"<<totalcost<<endl;

    return 0;
}

double RectCost(double x, double y)
{
    double s;
    s = x*y*10;
    return s;
}

double CircleCost(double r)
{
    double s;
    s = r*3.14*r*10;
    return s;
}

源程序中定义的函数可能被执行,也可能不被执行,只有被主调函数直接或间接调用的函数才会被执行,否则就不会执行。

源程序中定义的函数可能被执行多次,函数被调用一次就执行一次,调用多次则执行多次

程序员与函数的关系:

编写被调函数的程序员:被调函数应能够完成特定的程序功能,程序员编写被调函数就是为了自己多次使用,或者给别的程序员使用

编写主调函数的程序员:主调函数调用被调函数实际上重用其代码,以实现相应的程序功能,编写主调函数的程序员只关心如何调用函数,比如被调函数的函数名是什么,需传递什么参数,返回值是什么类型,不会关心函数体中的算法是怎么实现的。

三、数据的管理策略

机构化程序设计,将一个数据处理过程,分解成多个算法模块,模块之间需要共享数据,c++语言以函数的形式来描述模块,每个模块被定义成一个函数,函数之间需要共享数据,才能完成规定的数据处理任务,c++语言为程序员提供了2种数据管理策略。分别时分散管理集中管理

  • 数据分散管理

    将数据分散交由各个函数管理函数各自定义变量申请自己所需的内存空间,其他函数不能直接访问其中的数据,需要时可通过数据传递来实现共享。采用分散管理策略时,程序员应当定义变量语句在函数的函数体中,这样定义的变量称为局部变量,局部变量属本函数所有,其他函数不能直接访问。

  • 数据集中管理

    将数据集中管理,同一定义公共的变量来存放共享数据,所有函数都可以访问,采用集中管理策略时,程序员应当将定义变量语句放在函数体外面(不在任何函数的函数体中),这样所定义的变量称为全局变量,全局变量不属于任何函数,是公共的,所有函数均可访问。

3.1 数据分散管理策略

核心思想就是分散管理,按需传递,例如前面设计的养鱼池造价测试程序。

采用分散管理策略时,主调函数和被调函数互相不能访问彼此内部的局部变量,无法共享数据,c++语言通过形实结合,和返回值,这两个数据传递计机制,实现了主调函数和被调函数间的数据共享。主调函数的函数体中含有对被调函数的调用语句,当执行对被调函数的调用语句时,计算机将自动执行2次数据传递操作,就是形实结合和返回值return。

3.2 数据的集中管策略

数据集中管理,全局共享。

数据集中管理策略就是将数据集中管理,统一定义公共的变量来存放共享数据,所有函数都可以访问,这样可以减少函数间的数据传递。采用集中管理策略时,程序员应当将定义变量语句放在函数外面(不在任何函数的函数体中),这样所定义的变量称为全局变量。全局变量不属于任何函数,是公共的,所有函数都可以访问。

#include <iostream>
using namespace std;

/*下列变量为全局变量,是共有的,所有函数都可以访问*/
double a,b; //定义的是长方形养鱼池的长和宽
double r1,r2; //定义圆形养鱼池的半径
double totalcost;  //存储总造价

/*当使用全局变量后,RectCost不在需要定义形式参数,来接收主函数传递过来的长宽数据*/
void RectCost() //因为该函数没有返回值,故采用void定义。
{
    double cost; //cost为局部变量
    cost = a*b*10;
    totalcost += cost; //直接使用全局变量进行计算,将结果累加在totalcost中。
    return; //该语句可以省略
}

/*CircleCost算法因为要被重用,需要通过形式参数接收不同的参数,从而产生不同的结果。故还是定义为带形参的函数,变量为局部变量。*/
double CircleCost(double r)
{
    double cost;
    cost = 3.14*r*r*10;
    return cost;
}

int main()
{
    double a,b,r1,r2,totalcost=0;
    cout << "请输入长方形的长宽:"<<endl;
    cin >> a>>b; 
    cout << "请输入圆形的半径"<<endl;
    cin >> r1>>r2;

    RectCost(); //调用函数长方形造价计算函数RectCost。就可以就算出长方形养鱼池的造价,并存放到全局变量total中。
    /*此时的r1,r2是一个全局变量,结果也累加到全局变量中*/
    totalcost += CircleCost(r1);
    totalcost += CircleCost(r2);
    cout<<"总造价为:"<<totalcost<<endl;

    return 0;
}

通常一个c++程序既有全局共享使用的数据,也有仅供局部使用的数据,程序员应合理的决定管理策略,将需要全局共享的数据定义成全局变量,集中管理,供所有函数访问,这样可以有效降低函数间的数据传递,而将仅供局部使用的数据,定义成局部变量,分散交由各个函数自己管理,这样可以降低管理的复杂性。

3.3 变量的作用域

在函数的函数体中定义的变量是局部变量,只能被本函数访问,定义在函数外面(不在任何函数的函数体中)的变量是全局变量,可以被所有函数访问,不同类型的变量具有不同的访问范围,这就是变量的作用域

c++源程序中的变量,需要遵循先定义后访问的原则,即变量在定义之后,其后续的语句才能访问该变量,变量的作用域(Scope)指的是c++源程序中可以访问该变量的代码区域。

c++语言根据定义位置,将变量分为局部变量全局变量函数形参等3种类型,它们具有不同的作用域,作用域也分为3种,分别是块作用域文件作用域函数原型作用域。所有变量只能在其作用域范围内访问。

  1. 局部变量

    用一对大括号括起来的源程序代码称为一个代码块,例如函数的函数体就是一个代码块,一条复合语句也是一个代码块,块作用域是从变量定义位置开始,一直到其所在代码块的右大括号为止。局部变量具有块作用域,只能被用作域内的语句访问。

  2. 全局变量

    文件作用域是从变量定义位置开始,一直到其所在源程序文件结束为止,全局变量具有文件作用域,其作用域内的函数都可以访问。

  3. 形式参数

    形式参数分为函数定义时的形参和函数申明时的形参两种,函数定义中的形式参数具有块作用域,这里的代码块指的是该函数的函数体,函数定义中的形参只能被本函数体内的语句访问。

    而函数声明中的形参不能也不需要被访问,其作用域为空,称为函数原型作用域。声明函数时,其形参列表可以只声明形参个数和类型,而形参名可以省略。函数声明中形参的作用仅仅是为了方便调用该函数的程序员理解其实际含义,没有其他语法作用。

实列:计算x = y^2 + z^2

#include <iostream>
using namespace std;
int func1(int p1,int p2); //声明func1的作用,是计算x*x+y*y,形参具有函数原型作用域
int func2(int p); //func2函数申明,计算p的平方,函数p具有函数作用域
int x=0; //全局变量用于保存最终结果

int main(){
    cout<<"请输入x 和 yx的值:"<<endl;
    int x,y;
    cin>>x>>y;//局部变量x,y具有块作用域
    x = func1(x,y);
    cout<< x;
    return 0;
}

int func1(int p1,int p2) //形参p1 ,p2,具有块作用域
{
    int result;
    result=func2(p1);  //局部变量result具有块作用域
    result += func2(p2);
    return result;
}
int func2(int p) //形参p具有块作用域
{
    int result;  //局部变量result具有块作用域
    result=p*p;
    return result;
}

c++语言中,不同函数的局部变量可以重名。

3.4 全局变量的作用域

全局变量的文件作用域,是从变量定义位置开始,直到其所占源程序文件的结束,符合先定义后访问额原则,但全局变量的作用域,可以通过外部申明,从定义位置往前延申,通过关键字extern来进行申明。

程序一:将全局变量定义在源程序的开头

#include <iostream>
using namespace std;
int r; //将全局变量r定义在源程序的开头
int main()
{
    cin>>r;
    cout<<(3.14*r*r);
    return 0;
}

程序二:将全局变量定义在源程序的末尾,在文件开头通过extern进行申明。延申局部变量的作用域

#include <iostream>
using namespace std;
extern int r;//通过申明,来延申局部变量r的作用域
int main(){
    cin>>r;
    cout<<(3.14*r*r);
    return 0;
}
int r; //将全局变量r定义在源程序的末尾

不能通过extern来延申局部变量和形参的作用域。仅有全局变量才能通过extern延申其作用域。

3.5 变量的重名及局部优先原则

c++语言规定:同一作用域中的变量不能重名,不同作用域的变量可以重名。不同的函数可以定义一样名字的局部变量和形式参数,因为它们属于不同的块作用域。当不同作用域的变量重名时,访问重名变量时局部优先。

四、程序代码和变量的储存原理

程序加载后立即为其中得全局变量分配内存,全局变量将一直占用所分配的内存,直到程序执行结束退出时才被释放,这种内存分配方法称为静态分配

局部变量是在计算机执行到其定义语句时才分配内存,到其所在代码块执行结束即被释放,这种内存分配方法称为自动分配。也叫动态分配。

  1. 一个函数或复合语句可能被执行多次,随着这些函数或复合语句的执行,其中定义的局部变量将不断重复内存的分配-释放过程,这个过程是自动完成的,不需程序员干预。
  2. 函数中定义的形参也是自动分配内存的,当执行到该函数的调用语句时为形参分配内存,并将传递来的实际参数值写入该内存,然后执行函数体,当函数体执行结束后即释放内存。即在栈分配。

生存期:

  1. 从加载到执行结束退出这个时间段是一个程序在内存中的生存期
  2. 从内存分配到释放这个时间段就是一个变量在内存中的生存期。

计算机执行函数调用语句的具体过程:

  1. 执行到函数调用语句时,暂停主函数的执行,跳转去执行被调函数。
  2. 为被调函数定义的形参分配好内存,计算调用语句中的实参表达式,将表达式结果按位置顺序宜以赋值给对应的形参,即形参结合。
  3. 保存好返回地址和当前CPU状态信息,即保存主调函数调用前的现场。
  4. 执行被调函数的函数体
  5. 执行完被调函数的函数体或执行到其中的return语句,停止被调函数的执行,如果有返回值,将返回值传回主调函数。
  6. 恢复主调函数调用前的现场,调用结束
  7. 按照返回地址继续执行主调函数中剩余的指令。

4.1 动态内存分配

程序员可以根据实际需要,在程序中使用new运算符来分配内存,使用完之后使用delete运算符将其释放,这种动态分配方法让程序员可以更主动、更直接的管理内存,根据需要分配尽可能少的内存,同时尽早释放以减少内存的占用时间。

动态内存,可以提供内存的使用率,动态分配的内存是从系统空闲的内存就是堆中分配来的,需要程序员自己释放,否则及时程序结束退出时,这个内存也可能被一直占用,无法被操作系统回收再利用,这个现象被称为内存泄漏,内存的动态分配、访问以及释放都是通过内存地址来实现的,因此使用动态内存分配,需要额外定义一个指针变量。

4.2 函数指针

可以通过内存地址访问变量,也可以通过内存地址调用函数。

计算机程序在执行时被读入内存,在内存中建立一个程序副本,其中包括各函数的代码,也就是说,执行时程序中各函数的代码是存放在内存中的。

调用函数一般时通过函数名来调用,也可以通过函数代码的首地址来调用

通过地址调用函数需分3步,依次是定义函数型指针变量,将函数首地址赋值给该指针变量,通过指针变量间接调用函数。

语法:

函数类型(*指针变量名)(形式参数列表);
  • 函数型指针变量需要与函数匹配,“匹配”的含义是:在定义函数型指针变量时,除了在变量名前加指针说明符“*”之外,同时要需要指定函数的形式参数和返回值类型。
  • 形式参数列表和函数类型分别指定了函数的形参和返回值类型,其中的形参名可以省略,指针变量将只能指向其定义语句中所规定形参和返回值类型的函数
  • 指针变量名需复合标识符的命名规则
//定义一个double函数。
double fun1(double x, int y)
{
    return(x+y);
}
// 可以通过调用函数名的方式来使用fun1
cout<<fun1(3.5,2);
//定义一个double型指针变量P,并指定形参列表,这样可以通过指针变量来访问fun1
double(*p)(double,int);
p = fun1; //然后将函数fun1的首地址赋值给指针变量p//通过指针变量p间接调用函数fun1
cout<<(*p)(3.5,2);
cout<<p(3.5,2); //直接使用指针变量p也是允许的
  1. 函数名实际上可以理解成是指向函数代码的指针,只不过它是一种指针常量,固定指向某个函数

  2. 一个函数型指针变量可以指向多个函数,这些函数都需要与指针变量匹配,即他们具有相同的形参和返回值类型。

    #include <iostream>
    using namespace std;
    double fun1(double y,int x){ return(x+y); }
    double fun2(double y,int x){ return(x-y);   }
    double fun3(double y,int x){ return(x*y); }
    double fun4(double y,int x){ return(x/y); }
    
    int main()
    {
       double(*p)(double,int);
       /*函数返回类型相同且形参相同,那么可以使用一个指针变量指向它们。*/
       p = fun1; cout<<(*p)(3.5,2)<<endl;
       p = fun2; cout<<(*p)(3.5,2)<<endl;
       p = fun3; cout<<p(3.5,2)<<endl;
       p = fun4; cout<<p(3.5,2)<<endl;
       return 0;
    }
    

五、函数间参数传递的3种方式

采用数据分散管理时,数据分散在各个函数中进行管理,函数各自定义局部变量保存数据,其他函数不能直接访问,调用函数时,主调函数和被调函数之间,需要通过形实结合,来传递数据,将保存在主调函数里的原始数据,以实参的形式传递个被调函数的形参,这就称为函数间的参数传递

通过一个实例来讲解3种传递方式:定义2个变量int x = 5,y=10;编写一个函数swap来交换这2个变量的数值。交换和,x的值应该为10,y的值应该为5

5.1 值传递

值传递是将主调函数中实参的值传递给被调函数的形参,形参单独分配内存,另外保存一份实参值的副本,被调函数访问形参中的副本。

#include <iostream>
using namespace std;
void change(int a, int b)
{
    int t;
    t = a; a = b; b = t;
    cout <<"change函数中的x="<< a <<",y的值="<< b; //输出值为a=10,y=5
}
int main()
{
    cout<<"exchange x and y"<<endl;
    int x=5,y=10;
    cout << x <<","<<y<<endl; //输出结果x=5,y=10
    change(x,y);
    cout << x<<","<<y; ////输出结果x=5,y=10
    return 0;
}

通过上述例子,我们可以看到,通过值传递的方式,在主函数中调用main(),是无法完成数据的交换的。

值传递的特点:

  1. 值传递只是将主调函数中的实参值传递给被调函数的形参,无论被调函数如何修改形参都不会影响到实参。
  2. 值传递只能将数据从主调函数传递给被调函数,不能反向传递,这是一种单向数据传递机指
  3. 值传递时,实参可以是常量、变量或表达式
  4. 值传递的好处是:如果被调函数错误修改了形参,它不会影响到主调函数中的实参。

5.2 引用传递

函数中定义的局部变量不能被其他函数直接访问,但能够被间接访问,引用传递就是传递实参变量的引用,被调函数通过引用间接访问主调函数中的变量,从而达到传递数据的目的。

  • 函数中定义的局部变量不能被其他函数直接访问,但能够被间接访问。引用传递就是传递实参变量的引用,被调函数通过该引用间接访问主调函数中的变量,从而达到传递数据的目的
  • 采用引用传递时,被调函数的形参需要定义为引用变量。
#include <iostream>
using namespace std;
void change(int &a, int &b) //将change中的形参定义为引用变量。
{
    int t;
    t = a; a = b; b = t;
    cout <<"change函数中的x="<< a <<",y的值="<< b;
}

int main()
{
    cout<<"exchange x and y"<<endl;
    int x=5,y=10;
    cout << x <<","<<y<<endl;
    /*
    此时的参数传递:因为change的形参是引用变量,调用时相当于执行了以下2条语句
    int &a=x;
    int &b=y;
    形参a是x的别名,形参b是y的别名
    系统不会再为a,b单独分配内存空间,此时a,b和x,y共用内存空间。访问形参a,b就是再访问对应的实参x,y。
    */
    change(x,y); //调用形式不改变
    cout<<endl;
    cout << x<<","<<y; //输出结果为x=10,y=5
    return 0;
}

引用传递的特点:

  1. 引用传递将被调函数的形参定义成主调函数实参变量的引用,被调函数通过该引用间接访问主调函数函数中的变量。
  2. 被调函数修改形参实际上修改的是对应的实参,换句话说,主调函数可以通过引用将数据传给被调函数,被调函数也可以通过该引用修改实参的值将数据传回主调函数。引用传递是一种双向数据传递机制。
  3. 采用引用传递时实参必须是变量。
  4. 引用传递的好处:一是形参直接引用实参,不需分配内存、传递数值,这样可以提高函数调用速度,降低内存占用,二是可以通过引用实现双向数据传递。

5.3 指针传递

指针传递就是传递实参变量的内存地址,被调函数通过该地址间接访问主调函数中的变量,从而达到传递数据的目的。

采用指针传递时,被调函数的形参需定义成指针变量,以接收实参变量的内存地址。

#include <iostream>
using namespace std;
//指针传递:将形参定义成指针变量
void change(int *a, int *b)
{
    int t;
    t = *a; *a = *b; *b = t;//通过指针的形式近接访问实参,
    cout <<"change函数中的x="<< a <<",y的值="<< b;
}

int main()
{
       
    cout<<"exchange x and y"<<endl;
    int x=5,y=10;
    cout << x <<","<<y<<endl;
     /* 
    调用函数change时,相当于执行了:
    int *a = &x;
    int *b = &y;
    所以在change的函数体中,*a实际是对应的是变量x的首地址,*b对应的是y的首地址。
    */
    change(&x,&y); //实参改为变量x和y的内存地址。
    cout<<endl;
    cout << x<<","<<y; //显示结果x=10,y=5
    return 0;
}

指针传递的特点:

  1. 指针传递将主调函数中实参变量的内存地址传给被调函数的指针形参,被调函数通过该内存地址间接访问主调函数中的变量
  2. 被调函数可以通过内存地址修改对应的实参,换句话说,主调函数可以通过内存地址将数据传给被调函数,被调函数也可以通过该地址修改实参的值将数据传回主调函数,指针传递也是一种双向数据传递机制。
  3. 指针传递时,实参必须是指针变量或指针表达式
  4. 指针传递的好处:
    • 一是可以通过内存地址实现双向数据传递
    • 二是在传递批量数据(例如数组)时只需要传递一个内存首地址,这样可以提高函数调用速度,降低内存占用。

六、在函数间传递数组

数组是用于传递大量数据的,在函数间传递数组,实际上就是在函数间传递大量的数据,

  • 函数在函数体中定义的数组是局部变量,其它函数不能直接访问其中的数组,需要时可通过传递来实现对数组的共享
  • 传递数组时,被调函数需定义数组形式的形参,同时还需要传递表明数组大小的参数,例如数组的元素个数。

演示程序:求一个一维数组里的最大值

#include <iostream>
using namespace std;
int Max(int array[],int length) //功能是求数组array中的最大值,length表示该数组的长度
{
    int value = array[0];//假定array[0]就是最大的,将其保存在value中。
    int n;
    for(n=1;n<length;n++)
    {
        if(array[n]>value) value = array[n];
    }
    return value;
}

int main()
{
    int a[5]={2,8,4,6,10}; //定义一维数组a,数组a为局部变量,定义时初始化
    cout << Max(a,5)<<endl; //调用函数Max求数组a中元素的最大值,结果为10
    return 0;
}
  1. 计算机内部对数组的管理和访问是通过指针(即内存地址)来实现的
  2. 在函数间传递数组,所传递的实际上是数组的首地址,换句话说,函数间数组传递采用的是指针传递,而不是值传递。

演示程序:通过指针形式的形参传递一维数组

#include <iostream>
using namespace std;
int Max(int *pArray,int length) //指针变量pArray指向一维数组a的首地址。
{
    int value = pArray[0];
    int n;
    for(n=1;n<length;n++)
    {
        if(pArray[n]>value) value = pArray[n]; //pArray[n]与*(pArray+n)等价
    }
    return value;
}

int main()
{
     int a[5]={2,8,4,6,10};
    cout << Max(a,5)<<endl; //调用函数Max求数组a中元素的最大值,结果为10,数组a可以理解为一个一维数组a的首地址的符号常量。
    return 0;
}

演示程序:如何在函数间传递二维数组

#include <iostream>
using namespace std;
int Max(int array[][3],int row) //row是行数,3是列数。
{
    int value = array[0][0];
    int m,n;
    for(m=0;m<row;m++)
        for(n=0;n<3;n++)
        {
            if(array[m][n]>value) value = array[m][n];
        }
    return value;
}

int main()
{
   int a[2][3]={2,8,4,6,10};
   cout << Max(a,2)<<endl;
   return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容