终于懂了:Delphi的函数名不是地址,取地址必须遵守Object Pascal的语法(Delphi和C的类比:指针、字符串、函数指针、内存分配等)good
这点是与C语言不一样的地方,以前我一直都没有明白这一点,所以总是不明白:函数地址再取地址算怎么回事?
----------------------------------------------------------------------------------------------------------------
在学习Delphi的时候,一个很好的建议是和C/C++去类比着学习,从指针,到内存管理,到数组,到面向对象……各个方面,都是有很多可以相似和或者也有不同的方,类比着学习,一方面加深对Delphi的理解,一方面加深对C/C++的理解,一方面加深对计算机系统的理解,一方面加深对面向对象的理解……由1向多可以很方便的扩展,而且完全不冲突,完全是互相促进的过程。所以学习要有技巧!
大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运算上。因此,说指针是C语言的灵魂,一点都不为过。同时,这种说法也让很多人产生误解,似乎只有C语言的指针才能算指针。Basic不支持指针,在此不论。其实Pascal语言本身也是支持指针的。从最初的Pascal发展至现在的Object Pascal,可以说在指针运算上,丝毫不会逊色于C语言的指针
类型指针的定义
对于指向特定类型的指针,在C中是这样定义的
int * iPtr;
char *cPtr;
与之等价的Object Pascal是如何定义的呢?
var
iPtr : ^Integer;
cPtr : ^char; //注意 ^ 符号是放在类型的前面
其实也就是符号的差别而已
无类型指针的定义
C中有 void * 类型,也就是可以指向任何类型数据的指针。Object Pascal为其定义了一个专门的类型:Pointer。于是Delphi的
ptr : Pointer;
等价于C的
void *ptr;
指针的解除引用
要解除指针引用(即取出指针所指向区域的值),C的语法是
*ptr
而Object Pascal的语法是
ptr^ //注意 ^ 符号是放在指针变量的后面
取地址(指针赋值)
取某对象的地址并且将其赋值给指针变量,C的语法是
int i = 4;
int *iPtr = &i;
而Object Pascal的语法是
i : Integer;
iPtr : ^Integer;
iPtr := @i;
也只是符号的差别而已
指针运算
在C中,可以对指针进行移动的运算,如
char a[20];
char *ptr = a;
ptr++;
ptr +=2;
当执行ptr++时候,编译器会产生让ptr前进sizeof(char)步长的代码,之后,ptr将指向a[1]。ptr+=2;这句使得ptr前进两个sizeof(char)大小的步长。同样,我们来看一下Object Pascal中如何实现
var
a : array[1..20] of Char; //注意Delphi里面定义数组变量的这一种方法
ptr : ^char;
begin
ptr := @a; //此处不同于C,C中数组名就是指针,
//而在Delphi中还需要使用取数组名地址的语法:@a
Inc(ptr); //这句等价于C的ptr++
Inc(ptr, 2); //这句等价于C的ptr+=2;
end;
动态内存分配
在C中,使用malloc()库函数分配内存,free()函数释放内存。如这样的代码:
int *ptr, *ptr2;
int i;
ptr = (int *)malloc(sizeof(int) * 20);
ptr2 = ptr; //因为后来可能要对ptr指针进行操作,为了保存分配的内存空间的地址,
//所以要用新的指针 ptr2来记录分配的内存地址,保证后面可以在使用完
//内存后进行free,而不会造成内存泄露
for(i=0; i<20; i++){
*ptr = 1;
ptr++;
}
free(ptr2);
ptr2 = NULL;
在Object pascal中,动态内存分配的函数是GetMem(),与之对应的释放函数是FreeMem(),(传统的Pascal中获取内存的函数是New()和Dispose(),但是New()只能获取对象的单个实体的内存大小,无法取得连续的存放多个对象的内存块)。因此,与上面那段的C代码等价的Object Pascal的代码是:
var
ptr, ptr2 : ^Integer;
i : Integer;
begin
GetMem(ptr, sizeof(Integer)*20);
//这句话等价于C的ptr = (int *)malloc(sizeof(int) * 20);
ptr2 := ptr; //保留原始指针的位置
for i:=0 to 19 do
begin
ptr^ :=i; //注意指针的解除引用的语法格式
Inc(ptr);
end;
FreeMem(ptr2);
ptr2:= nil;
end.
对于上面的例子(无论是C的还是Object Pascal的),都需要注意一个问题,就是分配内存的单位是字节(BYTE),因此在使用GetMem时候,其第二个参数如果想写成20,那么就会出问题(内存访问越界)。因为GetMem(ptr, 20);实际上只分配了20个字节的内存空间,而一个整型的大小是4字节,那么访问第5个之后的所有元素都是非法的了(对于malloc()参数也是一样)
字符数组的运算
C语言中,是没有字符串类型的,因此,字符串都是用字符数组实现的,于是也就有了一套str 开头的库函数用于进行字符数组的运算,如以下的代码
char str[15];
char *ptr;
strcpy(str, "teststr");
strcat(str, "_testok");
ptr = (char *)malloc(sizeof(char) * 15);
strcpy(ptr, str);
printf(ptr);
free(ptr);
ptr = NULL;
而在Object Pascal中,有了String类型,因此可以很方便的对字符串进行各种运算。但是,有时候我们的Pascal代码需要与C进行交互(比如:用Object Pascal的代码调用C写的DLL或者用Object Pascal写的DLL准备允许用C写客户端的代码)的话,就不能使用string类型了,而必须使用两种语言通用的字符数组。其实,Object Pascal提供了完全相似C的一整套字符数组的运算函数,以上的那段代码的Object Pascal的版本是
var
str : array[1..15] of char;
ptr : ^char;
begin
StrCopy(@str, 'teststr'); //在C中,数组的名称可以直接作为数组的首地址指针用,
//但是在Delphi里面要注意,要在str前面加上取地址运算符
StrCat(@str, '_testok';
GetMem(ptr, sizeof(char) * 15);
StrCopy(ptr, @str);
Write(ptr);
FreeMem(ptr);
ptr:= nil;
end;
函数指针
在动态调用DLL的函数时候,就会用到函数指针。假设用C写一段代码如下:
typedef int (*PVFN)(int); //定义函数指针类型
int main(){
HMODULE hModule = LoadLibrary("test.dll");
PVFN pvfn = NULL;
pvfn = (PVFN)GetProcAddress(hModule, "Function1");
pvfn(2);
FreeLibrary(hModule);
}
C语言中定义函数指针的类型typedef代码的语法有些晦涩,而同样的代码在Object Pascal中却非常易懂
type
PVFN = Function(para : Integer) : Integer; //这里定义的PVFN是一个类型,而不是一个实例
var
fn : PVFN; //要使用PVFN类型的函数指针,就需要生成一个实例
//或者可以直接定义 fn : function(para : Integer) : Integer;
hm : HMOUDLE;
begin
hm := LoadLibrary("test.dll");
fn := GetProcAddress(hm, 'Function1');
fn(2);
FreeLibaray(hm);
end;
附加说明:
Delphi的指针功能非常强大,所有C中能够实现的指针Delphi都能实现,上面认为Delphi指针不是强项的只是一种误解(或者对指针的机制一知半解)
由于Pascal语言的限制,用Delphi的指针很多情况下需要强制类型转换,Delphi中提供了很多指针类型,而且非常方便的是你可以自定义自己的指针类型
一个经验是:要掌握一种数据类型并且熟练灵活应用,一个比较好的办法是别考虑什么类型是什么名字,而只需要考虑这种类型的变量将占用多少字节。凡是字节数亩相同的数据类型都可以认为是同一种类型,提供不同的类型只是为了编译器能够更方便的查找错误而已,比如:Integer、Pointer、PChar、TSmallPoint甚至是attay[0..3] of char你都可以把他们当成是同一类型加以使用(有了这种思路,可以实现很大的程序灵活性的代码高效性)
https://segmentfault.com/a/1190000003701825
-------------------------------------------------------------------------------------------
procedure TForm4.FormCreate(Sender: TObject); procedure ____(Sender: TObject);
begin
ShowMessage(Sender.ClassName);
end; var
M: TMethod;
begin
M.Data := BitBtn1;
M.Code := @____; // 取函数地址
TypInfo.SetMethodProp(BitBtn1, 'OnClick', M);
end;
或者也可以作 btn.Onclick := TNofitfyEvent(M);
http://bbs.2ccc.com/topic.asp?topicid=513824
-------------------------------------------------------------------------------------------
@标识符在函数的时候应该就像是一个强制类型说明的转为无符号指针,这是我的理解吧
因为你可以做个测试
var p: procedure;
p2: Pointer;
begin
p := @xx;
p();
p2 := TProc(p)();
我的理解就是强类型的问题,
终于懂了:Delphi的函数名不是地址,取地址必须遵守Object Pascal的语法(Delphi和C的类比:指针、字符串、函数指针、内存分配等)good的更多相关文章
- C标准库-数值字符串转换与内存分配函数
原文链接:http://www.orlion.ga/977/ 一.数值字符串转换函数 #include <stdlib.h> int atoi(const char *nptr); dou ...
- 网易云课堂_C语言程序设计进阶_第二周:指针:取地址运算和指针、使用指针、指针与数组、指针与函数、指针与const、指针运算、动态内存分配_2信号报告
2 信号报告(5分) 题目内容: 无线电台的RS制信号报告是由三两个部分组成的: R(Readability) 信号可辨度即清晰度. S(Strength) 信号强度即大小. 其中R位于报告第一 ...
- Delphi 调用C/C++的Dll(stdcall关键字, 会导致函数名分裂. 此时函数名变成_stdadd@8)
delphi调用C++写的Dll, 当然这个Dll要求是非MFC的Dll, 这样子才能被delphi调用. 根据C++定义函数的情况, Delphi有不同的相对应的处理方法.1. 声明中不加__std ...
- error C2825: '_Iter': 当后面跟“::”时必须为类或命名空间 -- 原因可能是参数错误或者自定义函数名和库函数名冲突
今天运行程序的时候遇到了下面这个bug > B1020.cpp >e:\vs2013\vs2013_rtm_ult_chs\data\vc\include\xutility(): erro ...
- C/C++函数调用的几种方式及函数名修饰规则以及c++为什么不允许重载仅返回类型不同的函数
我们知道,调用函数时,计算机常用栈来存放函数执行需要的参数,由于栈的空间大小是有限的,在windows下栈是向低地址扩展的数据结构,是一块连续的内存区域.这句话的意思是栈顶的地址和栈的最大容量是系统预 ...
- js中关于事件处理函数名后面是否带括号的问题
今天总结一个关于事件处理程序的小细节.首先回顾一下事件处理的一些概念. JS中的事件处理(事件绑定)就是让某种或某些事件触发某些活动.有两种常见的形式,分别是DOM Level 0 和DOM Leve ...
- python基础学习Day11 函数名的应用、闭包、迭代器
一.函数名的应用 1.函数名就是函数的内存地址 def func(): print(666) func() print(func) #函数的内存地址 2.函数名可以作为变量 def func1(): ...
- python 函数和函数名的应用
一.函数 1.函数定义 def 关键字 -- 定义 func 函数名 -- 和变量定义规则一样 ()必须要写格式 : 声明语句结束 def my_len(): 函数体 def func(a:int ...
- 编写一个函数来找出所有不带歧义的函数名,也就是 那些只在一个模块里出现过的函数名(erlang)
erlang程序设计第八章练习题第二题: code:all_loaded()命令会返回一个由{Mod,File}对构成的列表,内含所有Erlang系统 载入的模块.使用内置函数Mod:module_i ...
随机推荐
- N - Picture - poj 1177(扫描线求周长)
题意:求周长的,把矩形先进行融合后的周长,包括内周长 分析:刚看的时候感觉会跟棘手,让人无从下手,不过学过扫描线之后相信就很简单了吧(扫描线的模板- -),还是不说了,下面是一精确图,可以拿来调试数据 ...
- Groovy简洁开发,我用到的简洁之处
最近一直在用Groovy开发以前的项目,一边学习一边开发,工具用的是IDEA(欲哭无泪,不熟悉真是搞死人).......由于我做的是服务层,是为公司其它项目做服务支撑的,所以就没有用框架,只有一些se ...
- java中 SSL认证和keystore使用
java中 SSL认证和keystore使用 2013-10-12 11:08 10488人阅读 评论(0) 收藏 举报 目录(?)[+] 好久没用过SSL认证了,东西久不用,就有点生疏. ...
- (转)Linux bash shell脚本语法入门
http://www.linuxsky.org/doc/newbie/201004/389.html 1.基础 #!/bin/bash //bash脚本第一句都是这个,他会让系统指定以bash来解释这 ...
- Difference between <? super T> and <? extends T> in Java
stackoverflow 原文 [http://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends- ...
- JAVA基础1
阶段0:拟出一个计划 阶段1:要制作什么? 阶段2:如何构建? 阶段3:开始创建 阶段4:校订 阶段5:计划的回报 一.程序运行时,数据保存位置 1.寄存器.这是最快的保存区域,因为它位于和其他所 ...
- C++ primer 中文第三版 阅读笔记 第八章
一.寄存器对象: 函数中频繁被使用的变量可以加上register就可声明为寄存器对象.对于寄存器对象,假如能够放到寄存器中就会放到寄存器中,放不到的话就放到内存中.比如 register int a ...
- uva 10560 - Minimum Weight(数论)
题目连接:uva 10560 - Minimum Weight 题目大意:给出n,问说至少须要多少个不同重量的砝码才干称量1~n德重量,给出所选的砝码重量,而且给出k,表示有k个重量须要用上述所选的砝 ...
- linux diff具体解释
diff是Unix系统的一个非常重要的工具程序. 它用来比較两个文本文件的差异,是代码版本号管理的基石之中的一个.你在命令行下,输入: $ diff <变动前的文件> <变动后的文件 ...
- 2d-x中Lua类型强转问题
在Lua中,使用CCDictionary进行保存CCSprite对象,但是,在CCDictionary取出来的时候,此时是一个CCObject对象,无法调用子类精灵的一些方法.那只能进行强转的. 那么 ...