CnPack Forum


 
Subject: CnPack Tip#2 关于形参的分析
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-2-2 11:40  Profile | Blog | P.M.  | QQ
CnPack Tip#2 关于形参的分析

CnPack Tip#2 关于形参的分析

作者:LiuXiao,小冬,Bahamut, 考拉, SkyJacker
http://www.cnpack.org
CnPack IV  QQ Group: 130970
2007-02-02
(转贴请注明作者、出处且保持完整)

一、形参的3种基本形式
function a(b: integer): integer;
function a(var b: integer): integer;
function a(const b: integer): integer;

b: 函数体内给b赋值后不会传出函数外
var b: 函数体内给b赋值后会传出函数外
const b: 函数体内不可以给b赋值  

二、解释

一种是用值传递的方式,
也就是说,在参数传递的时候只把参数的值传入函数内部
另一种是地址传递的方式,在参数传递时传入参数的地址 function a(var b: Integer): Integer;
在调用A函数的时候 a(i); 其实参数是@i。  

要清楚下面的解释过程,需要看一下 <<如何理解Move参数中的const Source和var Dest>>
地址:http://www.cnpack.org/showdetail.php?id=476?=zh-cn
(一种典型的上下文 context ? )

在函数内部的Pascal实现中,Source是传入的地址再指了一次而得到的结果,
因此仍然代表传进来之前的Source。欲取得汇编中实现时传入来的地址,则需要用@Source。
也就是说,这个var封装并且隐藏了函数被调用时传入的取地址操作和函数体内部使用时指了一次的操作。

举个浅显的例子:  
procedure(I: Integer);
var   J: Integer;
begin   
  J := I;
end;  
调用的时候,I的值被传进来,赋值给了J,这样理解没问题。  
但如果是:
procedure(var I: Integer);
var   J: Integer;
begin   
  J := I;
end;  
那么,在底层实现中,也就是在汇编中,I的地址被传进来了,
然后函数内部指了一次,取到了它的值,赋值给了J。  
但对外来讲,J := I;这句本身所完成的功能还是没有变化。
但如果在函数体里头写 I := 0,那么这俩函数的实现功能就有区别了。  

不带var的,传入的是一个I的值,这个值存在一个临时的地方,可能是寄存器,也可能是堆栈区。
I := 0;就是把这个临时地方的值塞成0.   

带var的,传入的是I的地址,这个地址值也存在一个临时地方。
但赋值的时候,这个地址指了一次,于是就指向传入前的I了,那么I:= 0就把传入前的变量的值给改变了。  

三、测试实例
根据以上的介绍,上机测试了一下。学习乃学而时习之嘛。
//调试环境:XP sp2 + Delphi6 + Update2
//调试设置:关闭优化选项.
//主要是看看效率如何
//实际编程中应该如何应用,如何避免出现形参声明混乱的情况呢。
//暂且将过程名A,B,C 叫为A模式,B模式,C模式
//注:下面仅是对Integer形参的分析.

procedure A(I: Integer);
var
  J: Integer;
begin
  J := I;
  I := 11;
end;

procedure B(var I: Integer);
var
  J: Integer;
begin
  J := I;
  I := 12; // 由于对 Var 类型的I 复制,因此无需关闭优化选项
end;

procedure C(const I: Integer);
var
  J: Integer;
begin
  J := I;
  //I := 12; // Could not compile
end;

procedure TForm1.btnTestVarClick(Sender: TObject);
var
  Ka: Integer;
  Kb: Integer;
  Kc: Integer;
begin
  Ka := 11;
  Kb := 12;
  Kc := 13;
  A(Ka);
  B(Kb);
  C(Kc);
  Log('Ka' + IntToStr(Ka));
  Log('Kb' + IntToStr(Kb));
  Log('Kc' + IntToStr(Kc));
end;

//Delphi局部变量的生成特点:
//存入堆栈,Ka,Kb,Kc在堆栈中的地址方向为:从高地址到低地址
0047D084 C745F80B000000   mov [ebp-$08],$0000000b  
0047D08B C745F40C000000   mov [ebp-$0c],$0000000c
0047D092 C745F00D000000   mov [ebp-$10],$0000000d
0047D099 8B45F8           mov eax,[ebp-$08]
0047D09C E86FFFFFFF       call A
0047D0A1 8D45F4           lea eax,[ebp-$0c]  //B Var取得是地址
0047D0A4 E883FFFFFF       call B
0047D0A9 8B45F0           mov eax,[ebp-$10]
0047D0AC E89BFFFFFF       call C
0047D0B1 8D55E4           lea edx,[ebp-$1c]

procedure A(I: Integer);
0047CD28 55               push ebp
0047CD29 8BEC             mov ebp,esp
0047CD2B 83C4F8           add esp,-$08
0047CD2E 8945FC           mov [ebp-$04],eax  //在堆栈中开辟一空间存放形参I值
0047CD31 8B45FC           mov eax,[ebp-$04]
0047CD34 8945F8           mov [ebp-$08],eax
0047CD37 C745FC0B000000   mov [ebp-$04],$0000000b
0047CD3E 59               pop ecx
0047CD3F 59               pop ecx
0047CD40 5D               pop ebp
0047CD41 C3               ret

procedure B(var I: Integer);
0047CD44 55               push ebp
0047CD45 8BEC             mov ebp,esp
0047CD47 83C4F8           add esp,-$08
0047CD4A 8945FC           mov [ebp-$04],eax  //在堆栈中开辟一空间存放传入的地址
0047CD4D 8B45FC           mov eax,[ebp-$04]
0047CD50 8B00             mov eax,[eax]     //多了一次寻址
0047CD52 8945F8           mov [ebp-$08],eax
0047CD55 8B45FC           mov eax,[ebp-$04] //多了一次寻址
0047CD58 C7000C000000     mov [eax],$0000000c
0047CD5E 59               pop ecx
0047CD5F 59               pop ecx
0047CD60 5D               pop ebp
0047CD61 C3               ret

procedure C(const I: Integer);
0047CD64 55               push ebp
0047CD65 8BEC             mov ebp,esp
0047CD67 83C4F8           add esp,-$08
0047CD6A 8945FC           mov [ebp-$04],eax //在堆栈中开辟一空间存放形参I值
0047CD6D 8B45FC           mov eax,[ebp-$04]
0047CD70 8945F8           mov [ebp-$08],eax
0047CD73 59               pop ecx
0047CD74 59               pop ecx
0047CD75 5D               pop ebp
0047CD76 C3               ret

说明如下
//对整型形参而言,
A, C的执行代码效率是比较高的,
A比较简洁,C会增加编译器处理Const的时间
A与C除了const外,内部执行过程没有任何不同。
B 用于在函数内部修改外部变量的情况.

具体如何使用呢,我想看看SysUtils.pas,StrUtils如何处理的.
我对SysUtils,StrUtils走马观花看了一下,
在SysUtils中,
大部分函数的形参,如果是基本类型,比如Integer,Byte 基本上是按照A模式.
如果形参是String,全部是B模式常量声明(没有看到没有用const的)。

比如:
function StrToInt(const S: string): Integer;
function StrToIntDef(const S: string; Default: Integer): Integer;
function TryStrToInt(const S: string; out Value: Integer): Boolean;
function IntToStr(Value: Integer): string; overload;
function IntToStr(Value: Int64): string; overload;
function TrimLeft(const S: string): string; overload;
function TrimLeft(const S: WideString): WideString; overload;

一些特例:
procedure ScanToNumber(const S: string; var Pos: Integer);
function ScanChar(const S: string; var Pos: Integer; Ch: Char): Boolean;
function StrLCat(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar;
function StrComp(const Str1, Str2: PChar): Integer;

在StrUtils中,
function ReverseString(const AText: string): string;
function LeftStr(const AText: string; const ACount: Integer): string;
function RightStr(const AText: string; const ACount: Integer): string;
function MidStr(const AText: string; const AStart, ACount: Integer): string;


System.pas中
procedure _WStrDelete(var S: WideString; Index, Count: Integer);
function StrPos(const Str1, Str2: PChar): PChar; assembler;
function StrUpper(Str: PChar): PChar; assembler;
function StrLower(Str: PChar): PChar; assembler;
function StrPas(const Str: PChar): string;


关于形参声明形式的个人小总结
  相信 Delphi 中的 System,SysUtils,StrUtils 中函数形参的使用,都有其用意.
  站在巨人的肩膀上,在实际开发中,声明大部分普通函数的形参的原则为:
  如果是string格式的一定要加上const,使用C模式.
  如果是基本类型如Integer,则使用A模式,什么也不加.
  如果考虑到一些特殊的需求,比如传输内存,含有字符串删除等操作, 则考虑使用var声明.

  还有许多没有解决或没有被证明的问题:
  1、为什么string最好声明为const
  2、const 的作用 等等。
  
  
后记:
  之后,看了下 <<Pascal精要>> 第六章 过程与函数,觉得有些地方还是需要列出来以供参考:

1、
"Pascal 例程的传递参数可以是值参也可以是引用参数。
值参传递是缺省的参数传递方式:即将值参的拷贝压入栈中,例程使用、操纵的是栈中的拷贝值,不是原始值。
当通过引用传递参数时,没有按正常方式把参数值的拷贝压栈(避免拷贝值压栈一般能加快程序执行速度),
而是直接引用参数原始值,例程中的代码也同样访问原始值,这样就能在过程或函数中改变参数的值。
"  

2、
参数引用技术在大多数编程语言中都有,C语言中虽没有,但C++中引入了该技术。
在C++中,用符号 &表示引用;在VB中,没有ByVal 标示的参数都为引用。

3、
通过引用传递参数对有序类型、传统字符串类型及大型记录类型才有意义。
实际上Delphi总是通过值来传递对象,因为Delphi对象本身就是引用。
因此通过引用传递对象就没什么意义(除了极特殊的情况),因为这样相当于传递一个引用到另一个引用。
Delphi 长字符串的情况略有不同,长字符串看起来象引用,但是如果你改变了该字符串的串变量,
那么这个串在更新前将被拷贝下来。
作为值参被传递的长字符串只在内存使用和操作速度方面才象引用,
但是如果你改变了字符串的值,初始值将不受影响。
相反,如果通过引用传递长字符串,那么串的初始值就可以改变。

[ 本帖最后由 skyjacker 于 2007-2-2 11:46 编辑 ]




一壶清茶煮青春.
Top
shenloqi
灌水处处长
Rank: 4



UID 34
Digest Posts 1
Credits 287
Posts 179
点点分 287
Reading Access 10
Registered 2003-3-15
Status Offline
Post at 2007-2-2 13:44  Profile | P.M. 
其实Delphi在使用cdecl还可以使用可变参数的:
type
  VA_FN = function(const par1, par2{, ...}: Pointer): Boolean; cdecl varargs;

function fn(const par1, par2{, ...}: Pointer): Boolean; cdecl;
begin
end;

之后就可以这样用了:
if VA_FN(@fn)(nil, nil, edit1.text, edit2.text, edit3.text, edit4.text) then ShowMessage('');
Top
Passion (LiuXiao)
管理员
Rank: 9Rank: 9Rank: 9


UID 359
Digest Posts 19
Credits 6838
Posts 3591
点点分 6838
Reading Access 102
Registered 2004-3-28
Status Offline
Post at 2007-2-2 13:54  Profile | Blog | P.M. 
还真不知道这个!
Top
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-2-2 14:00  Profile | Blog | P.M.  | QQ
感觉像回调。
可是又好像相当灵活
if VA_FN(@fn)(nil, nil, edit1.text, edit2.text, edit3.text, edit4.text) then ShowMessage('');

不知道能够实际应用到那些方面。




一壶清茶煮青春.
Top
kendling (小冬)
高级版主
Rank: 8Rank: 8
MyvNet


Medal No.1  
UID 703
Digest Posts 5
Credits 978
Posts 580
点点分 978
Reading Access 101
Registered 2005-2-18
Location 广东
Status Offline
Post at 2007-2-2 14:30  Profile | Site | Blog | P.M.  | QQ | Yahoo!


QUOTE:
原帖由 shenloqi 于 2007-2-2 13:44 发表
其实Delphi在使用cdecl还可以使用可变参数的:
type
  VA_FN = function(const par1, par2{, ...}: Pointer): Boolean; cdecl varargs;

function fn(const par1, par2{, ...}: Pointer): Boolean; cdecl;
begin
end;
...

牛!不过还不知道为啥这样写。




小冬
http://MyvNet.com
Top
bahamut8348
灌水司司长
Rank: 6Rank: 6


UID 4743
Digest Posts 14
Credits 337
Posts 79
点点分 337
Reading Access 10
Registered 2007-1-18
Status Offline
Post at 2007-2-2 14:51  Profile | Blog | P.M. 
应空气的要求:
BS下空气;
PS:这么奇怪的要求到是少见……




做人要厚道,看帖要回贴
Top
zzzl (早安的空气)
版主
Rank: 7Rank: 7Rank: 7



UID 590
Digest Posts 0
Credits 399
Posts 199
点点分 399
Reading Access 100
Registered 2004-11-29
Status Offline
Post at 2007-2-2 14:52  Profile | Blog | P.M.  | QQ
你还缺少早安语录
Top
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-2-2 14:52  Profile | Blog | P.M.  | QQ
哎,人活着就是为了被伤害。




一壶清茶煮青春.
Top
jAmEs_
灌水部部长
Rank: 8Rank: 8



Medal No.1  
UID 886
Digest Posts 0
Credits 1134
Posts 600
点点分 1134
Reading Access 10
Registered 2005-6-5
Location 广东
Status Offline
Post at 2007-2-2 15:35  Profile | Blog | P.M. 
讨论的确能增长不少知识~
Top
kendling (小冬)
高级版主
Rank: 8Rank: 8
MyvNet


Medal No.1  
UID 703
Digest Posts 5
Credits 978
Posts 580
点点分 978
Reading Access 101
Registered 2005-2-18
Location 广东
Status Offline
Post at 2007-2-5 17:11  Profile | Site | Blog | P.M.  | QQ | Yahoo!
对哇,绝对支持!!!




小冬
http://MyvNet.com
Top
 




All times are GMT++8, the time now is 2024-11-22 12:14

    本论坛支付平台由支付宝提供
携手打造安全诚信的交易社区 Powered by Discuz! 5.0.0  © 2001-2006 Comsenz Inc.
Processed in 0.012490 second(s), 9 queries , Gzip enabled

Clear Cookies - Contact Us - CnPack Website - Archiver - WAP