第10章 子程序、过程、自定义函数_第1页
第10章 子程序、过程、自定义函数_第2页
第10章 子程序、过程、自定义函数_第3页
第10章 子程序、过程、自定义函数_第4页
第10章 子程序、过程、自定义函数_第5页
已阅读5页,还剩65页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第10章子程序、过程、自定义函数、内存变量与数组10.1子程序一、子程序与主程序的概念结构化程序设计方法的一个核心思想是程序的模块化,它可把一个大的复杂的应用程序按功能分解成若干个相对独立的模块,这个模块就叫做子程序。通过模块的组合来完成一个大的复杂的应用程序的编写。子程序是一个为实现某个功能的独立程序。其特点是:它总被其它程序调用而一般不能单独执行。通常把调用它的程序称为主程序,被主程序调用的程序称为子程序。当然,其概念也是相对的,即子程序也可以调用其他子程序。二、子程序的建立与修改1.建立、修改子程序ModifyCommand<子程序文件名>参数说明:◆子程序的建立、修改和存盘与其它VFP程序一样,用ModifyCommand<程序文件名>命令或用菜单建立和修改,其扩展名也是Prg。◆子程序一定要用Return命令结束。2.Return命令Return参数说明:◆Return如不作任何选项,该语句结束子程序文件的运行,返回到调用子程序的主程序的下一个语句,继续执行。三、子程序的调用Do〈子程序文件名〉主程序调用子程序后,会通过子程序中的Return命令返回到主程序调用子程序的下一条命令继续运行。例:编程求N!+M!的和Sum=0P1=1DoJcSum=Sum+P1DoJcSum=Sum+P1?”N!+M!的值=”+Str(Sum)ReturnModiCommJc&&建立子程序jcP1=1Input“请输入计算阶乘的值”ToAForI=1ToAP1=P1*IEndForReturn例:编写一个完成对“学生档案表”按“姓名”查询或按“学号”删除的程序。主程序名Main.Prg。查询子程序名Cx.Prg,Sc.Prg删除子程序名Sc.Prg。*主程序main.PrgUse学生档案表DoWhile.T.ClearK=0@6,12Say"1.按姓名查询"@8,12Say"2.按学号删除记录"@10,12Say"3.退出系统"@12,12Say"请输入相应的选择号"GetKReadDoCaseCaseK=1DoCx&&调用子程序cx.PrgCaseK=2DoSc&&调用子程序sc.PrgOtherwise@14,20Say“输入的选择号有错!”EndcaseWait"是否继续(Y/N)?"ToAIfUpper(A)<>"Y"ExitEndifEnddoReturn子程序Cx.Prg功能:输入姓名查询记录DoWhile.T.ClearAccept"请输入待查询的姓名"ToXmLocateFor姓名=XmIfEof()?"查无此人!"ElseDisplayEndifWait"是否继续查询(Y/N)?"ToAIfUpper(A)<>"Y"ExitEndifEnddoReturn

**子程序Sc.Prg功能:输入学号删除记录DoWhile.T.ClearAccept"请输入要删除的记录的学号:"ToXhLocateFor学号=XhIfFound()DeleDisplayWait"是否要作物理删除(Y/N?)"ToBIfUpper(B)=="Y"PackEndIfElse?"输入有错,没有该学号!"EndifWait"是否继续删除(Y/N)?"ToAIfUpper(A)<>"Y"ExitEndIfEndDoReturn10.2过程过程是子程序的另一种形式,一个过程也是一个为了实现某个功能的程序段。过程是主程序文件的一个组成部分,习惯上常把它写在主程序的最后。一个主程序可以包括多个过程,但每个过程都要用Procedure〈过程名〉开头来说明过程名、用Return来结束一个过程。

一、过程的编写格式Procedure〈过程名〉&&以过程说明语句开头〈过程体命令序列〉&&过程体Return[ToMaster/To<程序名>]&&过程的结束语句参数说明:◆Procedure〈过程名〉:过程总是写在主程序的后面,每个过程都要用Procedure〈过程名〉开头来说明过程名,主程序通过Do命令能够直接调用过程。◆〈过程体命令序列〉:是一个完成指定任务的程序段,是过程的核心部分。◆Return:是过程的结束语句,每个过程必须用Return结束。二、过程的调用Do〈过程名〉例:对任意给的正整数A、B、C,用过程编写程序计算S=A!+(3/5)*B!+(C!/2)*主程序JSG.PRGSetTalkOffClearT=1Input“A=”ToXDoJcA1=TInput“B=”ToXDoJcB1=TInput“C=”ToXDoJcC1=T?”S=”,A1+(3/5)*B1+(C1/2)Return求阶乘的过程Jc,注意:应直接写在主程序后面。ProcedureJcT=1ForI=1ToXT=T*IEndforReturn10.3过程文件如果在主程序中过程太多,那么,主程序调用多个过程、多次访问磁盘会影响到程序的运行速度。为了克服调用多个过程、多次访问磁盘的问题,一般使用过程文件来解决。过程文件是由若干个过程组成的一个独立程序文件。它把多个过程放在一个过程文件中,其文件类型仍为Prg。过程文件一旦被打开,过程文件中包含的所有过程都被打开。从而,克服了调用多个过程、多次访问磁盘的问题,提高了程序运行的速度。一、建立过程文件ModifyCommand〈过程文件名〉过程文件是一个独立程序文件,同样是用ModifyCommand〈过程文件名〉建立的。二、过程文件的编写格式过程文件中的每一个过程都要用Procedure<过程名>开始来说明一个过程名,用Return来结束一个过程。Procedure〈过程名1〉〈过程体命令序列1〉Return&&每个过程都要用return结束……Procedure〈过程名n〉〈过程体命令序列n〉Return三、过程文件的使用使用过程文件一般要经过以下几步:打开过程文件、调用过程文件和关闭过程文件。1.打开过程文件SetProcedureTo<过程文件名>在主程序调用过程前,一定要打开包含该过程的过程文件。过程文件一旦打开,包含在该过程文件中的所有过程都已打开。系统规定,某一时刻只能打开一个过程文件。2.过程文件中的过程的调用

Do〈过程名〉过程文件被打开后,能用DO〈过程名〉命令来调用过程文件中的任何一个过程。主程序执行到过程调用语句,就转去执行指定的过程,执行到Return语句则返回主程序调用过程的下一条命令。3.过程文件的关闭SetProcedureToCloseProcedure过程文件打开后,若不再调用过程。为了节省内存空间和保护过程文件,应将过程文件关闭。例:假设主程序名为MAIN.PRG,过程文件名为GCWJ.PRG,过程文件中包含二个名为PA和PB的过程。过程文件及主程序内容如下:*主程序MAIN.PRGSetProcedureToGcwj&&打开过程文件DoPa&&调用过程paDoPb&&调用过程pb?"这是主程序"CloseProcedure&&关闭过程文件Return*过程文件gcwj.PrgProcedurePa?"这是过程pa"ReturnProcedurePb?"这是过程pb"Return例:把主菜单程序中的追加记录、修改记录、删除记录作为3个过程编写成一个过程文件。*主程序Main.PrgSetTalkOffK=0UseXsdaSetProcedureToGcDoWhile.T.ClearK=0@6,12Say”1.追加记录”@8,12Say“2.修改记录”@10,12Say“3.删除记录”@12,12Say”4.退出系统”@14,12Say“请输入相应的选择号”GetKReadDoCaseCaseK=1DoZj&&调用追加记录过程zj.PrgCaseK=2DoXg&&调用修改记录过程xg.PrgCaseK=3DoSc&&调用删除记录过程sc.PrgCaseK=4ExitOtherwiseWait”输入的选择号有错,请重新选择输入!”EndcaseEnddoSetProcedureToReturn*Gc.Prg过程文件ProcedureZj&&追加记录的过程zjAppend&&用全屏幕追加记录ReturnProcedureXg&&修改功能过程xgInput"请输入修改记录的学号:"ToXhLocateFor学号=XhIfFound()Edit&&用edit修改该记录Else?“查无该学生!”EndIfReturnProcedureSc&&删除功能过程scAccept"请输入删除记录的条件表达式:"ToNrLocateFor&NrIfFound()DisplayDeleteWait“是否要作物理删除(Y/N)?”ToA1IfSupper(A1)=”Y”PackEndIfElse?“查无满足条件的记录!”EndifReturn10.4自定义函数尽管VisualFoxPro系统提供了丰富的标准函数,但有时它们并不能满足某些应用程序的设计要求。这时,用户就可以为应用程序定义一些特殊的函数,用户自定义函数。一、自定义函数的基本概念自定义函数和子程序、过程相似,也是实现某个功能的一个程序块。自定义函数既可以写在主程序的后面,也可以是一个独立的程序。所不同的是自定义函数必须通过Return<表达式>返回一个函数值。二、自定义函数的建立1、自定义函数可以和调用程序共处同一程序文件中,习惯上常把它写在主程序的最后。2、也可以用ModifyCommand<程序文件名>命令建立一个独立的程序,程序名应定义为函数名,其扩展名也是Prg。三、自定义函数的编写格式不论自定义函数是写在主程序的后面,还是作为一个独立的程序来建立,其编写格式均为:Function〈函数名〉[Parameters〈形式参数表〉]〈函数体〉Return〈表达式〉参数说明:◆<函数名>:它是由字母、数字或下划线组成,必须以字母或下划线开头,不能与系统提供的标准函数名同名。◆必须用Function〈函数名〉说明语句开头。◆Parameters:如果要向函数中传递参数,则必须用该语句定义形式参数表,各参数间用逗号隔开。尤其注意,形式参数表中各参数的类型必须与主程序的实际参数表相一致。◆Return<表达式>:将<表达式>的值作为函数值返回到调用程序中。四、自定义函数的调用自定义函数的调用与系统标准函数的调用方法相同,其形式为:<函数名>[<参数表>]例:用自定义函数的形式,编写程序计算圆面积

*主程序ClearInput"请输入圆的半径:"ToA

&&输入要计算圆的半径

P=Area(A)

&&调用自定义函数area,(A)为实际参数?“圆面积s=”,PReturn*自定义函数FunctionArea&&定义函数名为areaParametersR&&定义函数的形参RS=3.14159*R*RReturnS&&返回函数的值

例:用自定义函数计算组合数cmn=M!/N!/(M-N)!*主程序ClearInput“请输入m的值”ToMInput“请输入n的值”ToNCmn=Jc(M)/Jc(N)/Jc(M-N)?“Cmn=”,CmnReturn*自定义阶乘函数FunctionJcParameterKT=1ForI=1ToKT=T*IEndforReturnT10-5参数的传递在实际应用中,有时需要在调用程序与被调用的子程序之间进行一些参数的传递来提高程序设计的灵活性。无论是在子程序、过程,还是在自定义函数中,参数传递都是一个非常重要的应用。一、传送参数与接受参数的命令1.传递参数的命令格式Do<子程序名/过程名>With<实际参数表>2.接受参数的命令格式Parameters<形式参数表>3.参数传送与接受的规则<实际参数表>与<形式参数表>应一一对应。接受参数的Parameters<形式参数表>命令必须是被调用程序(子程序、过程)的第一条可执行语句。当<实际参数表>的数量少于<形式参数表>的数量,多余的形式参数取逻辑假的值。二、参数传送方式子程序、过程、函数与主程序之间的数据通信是通过实际参数与形式参数的对应传送来实现的,参数传送有值传送和地址传送两种方式。1.值传送方式当With<实际参数表>是常量、内存变量表达式列表和加圆括号的简单内存变量时,这种传递接受参数的方式是值传送方式。值传送方式是一种单向传送,在调用时With<实际参数表>中的变量值会按顺序传递给Parameters<形式参数表>对应的变量。但返回时With<实际参数表>中的变量值不会随Parameters<形式参数表>对应变量值的变化而变化,With<实际参数表>中的内存变量在子程序中也不会被隐含。函数的参数传送方式默认为值传送方式。2.地址传送方式在实际变量参数前加@符号为地址传送方式。当With<实际参数表达式列表>是简单内存变量列表时,这种传递接受参数的方式是地址传送方式。地址传送是一种双向传送,在调用时With<实际参数表>中的变量值会按顺序传递给Parameters<形式参数表>对应的变量。而返回时With<实际参数表>中的变量值会随Parameters<形式参数表>对应变量值的变化而变化,With<实际参数表>中的内存变量在子程序中会被隐含。例:已知半径为R,求圆面积S。其程序及过程如下:*主程序ClearS=0Input"请输入圆的半径:"ToR1DoSubWithR1,S&&6,S为实际参数表,必须在过程的调用中说明?"圆面积S=",SReturn*Sub过程ProcedureSub&&Sub过程说明语句ParametersR2,Area&&R、Area为过程的形式参数表,与实参表对应Area=3.14159*R2*R2Return例:求S=A!+B!+C!的值。*主程序

Store0ToS,P1ForJ=1To3Input"请输入求阶乘的值"ToX1DoPpWithX1,P1S=S+P1Endfor?"S=",SReturn*过程ppProcedurePp&&过程说明语句ParametersX2,P2&&形式参数表P2=1ForK=1ToX2P2=P2*KEndForReturn10-6内存变量的作用域在多模块程序中,每一个内存变量都有自己的有效作用范围,通常称之为作用域。内存变量根据其作用域可分为全局变量、私有变量(普通)、局部(本地)变量和隐蔽变量四类。一、全局变量1.全局变量的概念所谓全局变量是一种可在所有程序的任何位置中定义、赋值、调用或更改的变量。在VisualFoxpro运行期间,全局变量可以被所有的程序使用,而不管它是在那一层程序中定义的。所以,不管在哪一层程序改变了全局变量的值,这个改变都会反映到其他程序中。因此,定义全局变量要特别小心。程序执行结束后,除非用命令清除。否则,全局变量将一直保留在内存中。2.全局变量的定义Public<内存变量表>用Public定义的变量是全局变量,它将<内存变量表>指定的变量定义为全局变量,并同时将这些变量的初值赋以逻辑假.F.。如果定义多个内存变量,<内存变量表>中各变量名之间用逗号隔开。3.全局变量的作用域在VisualFoxpro运行期间,全局变量可以在所有的程序中起作用。在上层程序中定义的全局变量,如果在下层程序中只是被重新赋值而没有改变变量类型,那么返回上层程序后,重新赋的值仍然有效。上层程序中定义的全局变量在下层程序中允许重新定义为同名的隐蔽型变量。这时,则把上层程序中定义的全局变量隐蔽起来。在该层程序中,只是重新定义的那个隐蔽型变量起作用。但返回上层程序后,隐蔽型变量被释放,全局变量恢复作用。例:全局变量示例。PublicA,B,CA=25B=125C=A+B?"主程序中变量C的值="+Str(C)DoGc1?"调用过程后C的值="+Str(C)ReturnProcedureGc1C=2*A+B?"过程中C的值="+Str(C)Return6-2普通(私有)变量1.普通(私有)变量的概念普通(私有)变量是一种直接用赋值方法定义的变量。普通(私有)变量可以在任何一层程序中定义,普通(私有)变量可以在定义变量那层程序中或下层程序中起作用。如果对其进行修改,也只能影响到建立它的那层程序或下层程序。程序执行结束后,则这些普通变量也随之被释放。2.普通变量的定义普通(私有)变量的定义有直接赋值法和间接赋值法两种方法。(1)直接赋值法A=123(2)间接赋值法Store“Abcd”ToA1,A2,A33.普通(私有)变量的作用域普通(私有)变量可以在定义变量那层程序中或下层程序中起作用。上层程序中定义的普通(私有)变量在下层程序中允许重新定义为同名的隐蔽型变量。这时,则把上层程序中定义的普通(私有)变量隐蔽起来。在该层程序中,只是重新定义的那个隐蔽型变量起作用。但返回上层程序后,隐蔽型变量被释放,上层程序中定义的普通(私有)变量恢复作用。普通(私有)变量若在下层程序中只是重新赋值,则在返回上层程序仍然有效。例:普通(私有)变量的示例。ClearX=10Y=Fuc(20)W=30??X,Y,WReturnFunctionFucParaZM=X+ZW=40?X,M,WReturnM6-3局部(本地)变量1.局部(本地)变量的概念局部变量是一种使用范围最为严谨的变量。局部变量只能在定义它的那层程序起作用,而不能被高层的或低层的程序访问。一旦包含局部变量的程序执行结束后,则这些局部变量也自动随之被释放。局部变量可以区别于同名的全局变量,在程序中优先使用同名的局部变量。当你希望某些内存变量只能在某一层程序中起作用,那就需要将变量定义为局部变量,以防止与其它变量发生干扰。2.局部(本地)变量的定义Local<内存变量表>用Local定义的变量是局部变量,它将<内存变量表>指定的变量定义为局部变量,并同时将这些变量的初值赋以逻辑假.F.。如果定义多个内存变量,<内存变量表>中各变量名之间用逗号隔开。3.局部(本地)变量的作用域局部变量可以区别于同名的全局变量,在程序中优先使用同名的局部变量。局部变量只能在定义它的那层程序起作用,而不能被高层的或低层的程序访问。上层程序中定义的局部变量变量在下层程序中允许重新定义为同名的隐蔽型变量。这时,则把上层程序中定义的局部变量隐蔽起来。在该层程序中,只是重新定义的那个隐蔽型变量起作用。但返回上层程序后,隐蔽型变量被释放,上层程序中定义的局部变量恢复作用。例:下面是一个有关局部变量使用的例题,请分析错误。ReleaseallmemoryLocalAPublicBA="ABCD"?"主程序中的A值:",A??"主程序中的B值:",BDOGC2?"调用后的A值:",A?"调用后的B值:",B?"调用后的C值:",CProcedureGC2LocalCC=123?"过程中的A值:",A?"过程中的B值:",B?"过程中的C值:",CReturn§6-4隐蔽型变量1.隐蔽型变量的概念隐蔽型变量只是在当前的程序层中,把上层程序中已定义的同名的全局变量、普通(私有)变量和局部变量隐蔽起来。隐蔽后,你便可以在当前的程序层中创建和使用同名的变量。隐蔽型变量定义后,一定要赋值。否则,会提示“找不到变量”。2.隐蔽型变量的定义Private〈内存变量名表〉严格地说Private并不是在创建变量,只是把上层程序中已定义的同名的全局变量、普通(私有)变量和局部变量隐蔽起来。3.隐蔽型变量的作用域隐蔽型变量只能在当前定义的程序中有效,当Private所在的程序执行结束后,隐蔽型变量将自动被释放,它所隐蔽的上层程序中的变量可恢复作用。而且,它的值与隐蔽前一样,不会有任何改变。例:理解主程序调用子程序过程中的变量屏蔽作用。*程序一PublicAA="ABC"B=123LocalCC="456"?A,B,CDoSub1?A,B,CProcSub1PrivateA,B,CA="BCD“B=456C="CDE"?A,B,CReturn*程序二ClearPublicI,JStore1ToI,J,KDoScx2?'主程序的输出结果:'?'I='+Str(I,2)+'J='+Str(J,2)+'K='+Str(K,2)CancelProcedureScx2ClearPrivateJ,KI=I*2J=2&&J不赋值可否?K=J+1?'过程中的输出结果:'?'I='+Str(I,2)+'J='+Str(J,2)+'K='+Str(K,2)Return程序运行结果:过程中的输出结果:I=2J=2K=3主程序的输出结果:I=2J=1K=17数组一、数组的概念内存变量,其特点是一个内存变量只能存放一个数据。VisualFoxPro还有另一种内存变量类型,即数组变量。数组是一种特殊的内存变量,是以同一个名字组织起来的N个内存变量的集合。其中,每一个内存变量称为一个数组元素,每个数组元素都有各自的标识符。VisualFoxPro在内存允许的情况下,最多可建立65000个数组,每个数组最多可拥有65000个元素,每个数组元素的数据类型可以不同。对内存变量进行命名、赋值、保存、恢复、释放等操作对数组同样有效。数组在使用之前需要先定义,由专门的数组定义语句来实现。数组元素默认的初始值是逻辑值.F.。每个数组元素的数据类型可以不同,数组分为一维数组和二维数组。二、数组的定义在通常情况下,数组在使用之前需要先定义,所谓定义数组,就是指明该数组的名称、确定数组的维数与大小、确定数组的作用范围。数组名以字符、下划线开头,可以包括字符、数字及下划线,最长可达254字符。VisualFoxPro支持一维及二维数组。一个数组的维数与大小决定了一个数组所能保存的元素的个数。如果是一维数组的话,该数组有几个元素。如果是二维数组,则该数组有几列、几行。根据数组的作用范围,数组又可分为局部数组和全局数组。1.局部数组定义Dimension数组名1(<数值表达式1>[,<数值表达式2>])[,数组名2(<数值表达式3>[,<数值表达式3>])……]2.全局数组定义Public数组名1(<数值表达式1>[,<数值表达式2>])[,数组名2(<数值表达式3>[,<数值表达式3>])……]参数说明:◆数组的定义包括二个内容:定义数组名、数组的维数或数组元素的个数。◆数组定义后,数组所有元素被自动赋为逻辑假“.F.”◆如果是定义一维数组,则<数值表达式1>表示数组元素的个数,如果是定义二维数组,,则<数值表达式1>和<数值表达式2>表示二维数组的行数和列数。如果是定义多个数组,则各数组之间用逗号“,”分隔。◆数组定义后,数组的最小下标是1。◆数组可以重新定义,而且原数组元素的值可以根据重新定义的情况保持或删除。例:数组定义示例。DimeAr1(6),Mn(12)

&&定义二个一维局部数组publicabc(12),Bcd(5,8)

&&定义一个一维全局数组和一个二维全局数组7-3数组元素的表示与引用数组元素的表示是对数组元素访问的标识符。数组元素的表示有数组下标和数组元素序号二种表示方法:数组下标表示方法:数组名<行号,列号>,数组元素序号表示方法:数组名<序号>。对于一维数组而言,数组元素的下标表示方法与序号表示方法是相同的。对于二维数组而言,数组元素的下标表示法与序号表示法是不相同的,例:数组元素的下标与序号示例。DimeAB(6)下标表示:AB(1),AB(2),AB(3),AB(4),AB(5),AB(6)序号表示:AB(1),AB(2),AB(3),AB(4),AB(5),AB(6)DimeCD(2,4)下标表示:CD(1,1),CD(1,2),CD(1,3),CD(1,4),CD(2,1),CD(2,2),CD(2,3),CD(2,4)序号表示:CD(1),CD(2),CD(3),CD(4),CD(5),CD(6),CD(7),CD(8)例:数组元素下标表示与数组元素序号表示。DimeA(5,8)Store5ToAA(3,6)=A(1)+A(38)?A(2,7)+A(22)+A(1)7-4数组的赋值数组的赋值和内存变量赋值一样,有间接赋值和直接赋值二种,赋值的形式有给整个数组赋值和给数组元素赋值二种。没有赋值的数组元素的值自动为.F.。1.数组间接赋值Store〈表达式〉To〈数组名|数组元素〉Input"输入数据:"ToX(i)例:定义数组并给数组间接赋值。DimensionAr(6),Mn(4,6)Store12ToAr&&给Ar局部数组所有元素赋值为12DisplayMemoryLikeArStore“字符”ToMn(2,1)&&给Mn局部数组Mn(2,1)元素赋值为“字符”Store{2005-11-08}ToMn(2,2)&&给Mn局部数组Mn(2,2)元素赋值为{2005-11-08}Store.T.ToMn(2,3)&&给Mn局部数组Mn(2,3)元素赋值为逻辑值.T.Store24ToMn(2,4)&&给Mn局部数组Mn(2,4)元素赋值为24,其余为.F.DisplayMemoryLikeMn例:建立一个X(N)、AB(5,8

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论