设计编写大型程序有2种方法
结构化程序设计方法
面向对象程序设计方法
一、结构性程序设计方法 二、函数的定义和调用 2.1 定义函数 2.2 函数的调用 2.3 函数的声明 三、数据的管理策略 3.1 数据分散管理策略 3.2 数据的集中管策略 3.3 变量的作用域 3.4 全局变量的作用域 3.5 变量的重名及局部优先原则 四、程序代码和变量的储存原理 4.1 动态内存分配 4.2 函数指针 五、函数间参数传递的3种方式 5.1 值传递 5.2 引用传递 5.3 指针传递 六、在函数间传递数组
程序描述了某种数据处理的过程和步骤,数据是程序处理的对象,一个复杂的程序任务,可能要处理大量数据,为此c++提供了数组的语法形式,数组可以存储大量同类型的数据。
将数据处理的过程,细分成一组严格的操作步骤,这组操作步骤就叫做算法。如果数据处理的算法很长很复杂。
结构性程序设计方法,就是将一个很长很复杂的程序设计任务分解成多个简单的模块,分而治之,然后将这些模块组装起来,最终完成数据处理算法。
c++支持结构化程序设计的方法,以函数的语法形式来描述和组装模块,这就是函数的定义和调用。****
将一个数据处理过程,分解成多个模块,各模块之间需要共享数据,c++语言提供了分散管理和集中管理这两种数据管理策略
结构性程序设计又叫面向过程的程序设计方法,基本方法如下:
将一个求解复杂问题的过程划分为若干个子过程,每个子过程完成一个独立的、相对简单的功能;用算法描述各个过程的操作步骤,每个算法称为一个模块;采用“自顶向下,逐步细化”的方法逐步分解和设计算法模块,再通过调用关系将各个模块组织起来,最终形成一个完整的数据处理算法。
采用结构性程序设计方法,程序员重点考虑的是如何分解和设计算法。
基于模块的协助开发:
将复杂的算法(函数)逐步分解成功能单一的,可重复调用的算法(函数)
结构化程序设计,应当合并重复模块,合并后的模块,可以被不同的上层模块调用,同一模块可以被多个模块调用,或被一个上层模块调用多次,这称为模块的重用或共用。
算法被划分成多个模块之后,调用其他模块的模块,称为主调模块,而被其他模块调用的模块称为被调模块,可以将不同的模块设计任务,交给不同程序员去完成。模块之间应当事先协调好被调函数的调用接口。
结构化程序设计方法:
代码重用:
模块的4大要素
模块的设计者和调用者具有不同的角色,不同角色对模块及其4大要素的理解是不一样的。
模块设计者
站在模块设计者的角度,他要思考的是接收输入参数,将输入参数作为原始数据进行处理,得到计算结果,并返回该结果,模块设计者,重点考虑处理算法,该算法因当能够按照模块的功能要求返回正确的计算结果,就是返回正确的返回值,模块算法是属于模块设计者的知识产权。而模块名称、输入参数、返回值是模块设计者为他人使用算法提供的开发接口,通称为模块的调用接口。调用接口必须是公开的,否则他人无法使用,算法是模块内部的实现细节可以不对外公开,在模块设计中,只有拿到模块设计的源程序,才能了解模块算法的实现细节,通常模块经常是以编译后的机器语言提供给他人使用的。
模块调用者
模块其实可以看做一个函数,调用函数就是给的某个具体的x的值,就能得到对应的函数结果,模块名称相当于是函数名,输入参数是原始数据相当于自变量x,模块的计算结果就是返回值,相当于函数值,调用者通过模块名称调用模块,调用时按要求给定具体的输入参数值,然后接收返回值,得到所需的计算结果,调用者只需了解调用接口,即可调用模块,无需了解模块内部的算法。
主模块与子模块
将一个复杂算法分解成多个模块,其中一个为主模块,由它负责调用其他模块,主模块不能被其他模块调用,除了子模块之外的其他模块统称为子模块,子模块可以被调用,也可以调用其他子模块。
c++语言支持结构化程序设计方法,以函数的语法形式来描述和组装模块,即函数的定义和调用
任务说明:
设计任务:公园修建1个长方形观赏鱼池,另外配套修建一大一小2个圆形蓄水池,分别存放清水和污水,养鱼池和蓄水池造假均为10元/。请设计一个测算总工厂造价的算法。
函数类型 函数名(形式参数列表)
{
函数体
}
/*举例*/
double func(double r)
{
double s;
s = 3.14*r*r;
return s;
}
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语句加返回值,返回给主调函数,return语句的语法有2种方式:
return(表达式);//表达式可以是:常量/变量,如果是常量或者变量时,可以省略小括号
或
return; //不带返回值
可以使用return语句,简化代码:
/*以前文设计的计算长方形养鱼池造假举例*/
double RectCost(double a, double b)
{
return(a*b*10); //可以直接一个表达式,在主程序调用时,或自动使用该表达式计算结果。
}
c++程序中,在调用函数之前,因当声明被调函数的函数原型,就是函数的调用接口,其中包括函数名称、形式参数列表和函数类型。
函数类型 函数名(形式参数列表);
//例如:
double RectCost(double, double)
实列:
#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种数据管理策略。分别是分散管理和集中管理
将数据分散交由各个函数管理函数各自定义变量申请自己所需的内存空间,其他函数不能直接访问其中的数据,需要时可通过数据传递来实现共享。采用分散管理策略时,程序员应当定义变量语句在函数的函数体中,这样定义的变量称为局部变量,局部变量属本函数所有,其他函数不能直接访问。
将数据集中管理,同一定义公共的变量来存放共享数据,所有函数都可以访问,采用集中管理策略时,程序员应当将定义变量语句放在函数体外面(不在任何函数的函数体中),这样所定义的变量称为全局变量,全局变量不属于任何函数,是公共的,所有函数均可访问。
核心思想就是分散管理,按需传递,例如前面设计的养鱼池造价测试程序。
采用分散管理策略时,主调函数和被调函数互相不能访问彼此内部的局部变量,无法共享数据,c++语言通过形实结合,和返回值,这两个数据传递计机制,实现了主调函数和被调函数间的数据共享。主调函数的函数体中含有对被调函数的调用语句,当执行对被调函数的调用语句时,计算机将自动执行2次数据传递操作,就是形实结合和返回值return。
数据集中管理,全局共享。
数据集中管理策略就是将数据集中管理,统一定义公共的变量来存放共享数据,所有函数都可以访问,这样可以减少函数间的数据传递。采用集中管理策略时,程序员应当将定义变量语句放在函数外面(不在任何函数的函数体中),这样所定义的变量称为全局变量。全局变量不属于任何函数,是公共的,所有函数都可以访问。
#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++程序既有全局共享使用的数据,也有仅供局部使用的数据,程序员应合理的决定管理策略,将需要全局共享的数据定义成全局变量,集中管理,供所有函数访问,这样可以有效降低函数间的数据传递,而将仅供局部使用的数据,定义成局部变量,分散交由各个函数自己管理,这样可以降低管理的复杂性。
在函数的函数体中定义的变量是局部变量,只能被本函数访问,定义在函数外面(不在任何函数的函数体中)的变量是全局变量,可以被所有函数访问,不同类型的变量具有不同的访问范围,这就是变量的作用域。
c++源程序中的变量,需要遵循先定义后访问的原则,即变量在定义之后,其后续的语句才能访问该变量,变量的作用域(Scope)指的是c++源程序中可以访问该变量的代码区域。
c++语言根据定义位置,将变量分为局部变量、全局变量和函数形参等3种类型,它们具有不同的作用域,作用域也分为3种,分别是块作用域、文件作用域和函数原型作用域。所有变量只能在其作用域范围内访问。
用一对大括号括起来的源程序代码称为一个代码块,例如函数的函数体就是一个代码块,一条复合语句也是一个代码块,块作用域是从变量定义位置开始,一直到其所在代码块的右大括号为止。局部变量具有块作用域,只能被用作域内的语句访问。
文件作用域是从变量定义位置开始,一直到其所在源程序文件结束为止,全局变量具有文件作用域,其作用域内的函数都可以访问。
形式参数分为函数定义时的形参和函数申明时的形参两种,函数定义中的形式参数具有块作用域,这里的代码块指的是该函数的函数体,函数定义中的形参只能被本函数体内的语句访问。
而函数声明中的形参不能也不需要被访问,其作用域为空,称为函数原型作用域。声明函数时,其形参列表可以只声明形参个数和类型,而形参名可以省略。函数声明中形参的作用仅仅是为了方便调用该函数的程序员理解其实际含义,没有其他语法作用。
实列:计算
#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++语言中,不同函数的局部变量可以重名。
全局变量的文件作用域,是从变量定义位置开始,直到其所占源程序文件的结束,符合先定义后访问额原则,但全局变量的作用域,可以通过外部申明,从定义位置往前延申,通过关键字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延申其作用域。
c++语言规定:同一作用域中的变量不能重名,不同作用域的变量可以重名。不同的函数可以定义一样名字的局部变量和形式参数,因为它们属于不同的块作用域。当不同作用域的变量重名时,访问重名变量时局部优先。
程序加载后立即为其中的全局变量或静态变量分配内存,全局变量将一直占用所分配的内存,直到程序执行结束退出时才被释放,这种内存分配方法称为静态分配
局部变量是在计算机执行到其定义语句时才分配内存,到其所在代码块执行结束即被释放,这种内存分配方法称为自动分配。也叫动态分配。
生存期:
计算机执行函数调用语句的具体过程:
程序员可以根据实际需要,在程序中使用new运算符来分配内存,使用完之后使用delete运算符将其释放,这种动态分配方法让程序员可以更主动、更直接的管理内存,根据需要分配尽可能少的内存,同时尽早释放以减少内存的占用时间。
动态内存,可以提供内存的使用率,动态分配的内存是从系统空闲的内存就是堆中分配来的,需要程序员自己释放,否则及时程序结束退出时,这个内存也可能被一直占用,无法被操作系统回收再利用,这个现象被称为内存泄漏,内存的动态分配、访问以及释放都是通过内存地址来实现的,因此使用动态内存分配,需要额外定义一个指针变量。
可以通过内存地址访问变量,也可以通过内存地址调用函数。
计算机程序在执行时被读入内存,在内存中建立一个程序副本,其中包括各函数的代码,也就是说,执行时程序中各函数的代码是存放在内存中的。
调用函数一般时通过函数名来调用,也可以通过函数代码的首地址来调用
通过地址调用函数需分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也是允许的
#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种传递方式:定义2个变量int x = 5,y=10;编写一个函数swap来交换这2个变量的数值。交换和,x的值应该为10,y的值应该为5
值传递是将主调函数中实参的值传递给被调函数的形参,形参单独分配内存,另外保存一份实参值的副本,被调函数访问形参中的副本。
#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(),是无法完成数据的交换的。
值传递的特点:
函数中定义的局部变量不能被其他函数直接访问,但能够被间接访问,引用传递就是传递实参变量的引用,被调函数通过引用间接访问主调函数中的变量,从而达到传递数据的目的。
#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;
}
引用传递的特点:
指针传递就是传递实参变量的内存地址,被调函数通过该地址间接访问主调函数中的变量,从而达到传递数据的目的。
采用指针传递时,被调函数的形参需定义成指针变量,以接收实参变量的内存地址。
#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;
}
指针传递的特点:
数组是用于传递大量数据的,在函数间传递数组,实际上就是在函数间传递大量的数据,
演示程序:求一个一维数组里的最大值
#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;
}
演示程序:通过指针形式的形参传递一维数组
#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;
}