ObjectC学习总结

本文深入探讨Objective-C的动态特性,包括其作为C语言扩展的特性,如运行期代码和数据、对象方法的动态绑定、与C++和Java的对比、内存管理、属性和消息转发机制。Objective-C的灵活性允许动态添加方法,但可能牺牲性能。文章还讨论了类对象结构、协议、委托以及与其他语言如C++和Java的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. ObjC是一种动态语言,所谓动态是在编译时增加了很多的运行期代码和运行期数据。这些运行期代码负责将编译后的ObjC代码粘接起来形成一个系统,而运行期数据则用于动态查询内存数据的描述信息(如类描述信息)。

 

  1. ObjC依然是C语言的扩充,是增加面向对象编程的C,与C++有很多共同特点,但是又有比C++多得多的运行期支持。ObjC一样会编译成可执行的机器码。兼容C语言所有特性,支持指针操作,通过指针可以遍历内存。这同C#、java、swift不同,后三种语言是基于引用,不支持指针操作,内存管理全部由编译器实现,编译时会加入代码管理内存的分配与释放。

 

  1. ObjC基于C++和C#/java之间,ObjC将很多编译期和链接器的工作放到了运行期,这样做的好处就是让程序实现更加灵活,而坏处就是牺牲部分性能(但是这可以通过优化来弥补)。对于局部变量的处理,ObjC与C语言是一样的,都基于栈空间的管理,而对于对象的管理,ObjC比C++增加了很多东西,可以说,C++在编译完成后,基本就没有了对象的概念,这同用C语言写的代码在内存中的内容没有太多差别,C++唯一保留的就是对象的类型信息和虚方法表(用于实现多态),因此C++编写的程序的内存内容与程序源码相差不大。而ObjC的运行期对象相对复杂很多。

 

  1. ObjC对象的方法调用是运行期绑定的,而在C++中只有虚拟方法表中的方法才是通过动态绑定的,而且C++的运行期绑定其实也只是通过对象指针找到虚拟方法表然后进行地址偏移来获得虚拟方法地址的,这只是地址计算,编译器加入的代码相对简单,通过地址偏移计算,速度也相对较快。而在ObjC中,任何对象方法的调用都是动态查询的,方法调用至少有两个参数,一个是对象本身(如果是类方法,就没有对象指针),一个就是方法名,这是一个字符串参数,方法名在C++编译中会变成一个符号(最终会变成一个地址),而在ObjC编译中,编译器会将方法名保存在常量数据区,而所有的方法调用编译时都会变成了对objc_msgSend(id self, SEL cmd, ….)函数的调用,cmd是一个对字符串对象的引用(字符串对象保存的就是方法名,注意这不是直接的字符串地址,而是一个NSString对象,必须通过对象中的指针才能找到字符串常量)。这个函数会根据id(地址)找到对象本身,而对象中则保存了指向类信息(类对象,就是类描述信息)的指针,类信息中则有方法表,通过遍历方法表就可以找到目标方法的地址,如果没找到则抛出异常。这样实现的好处,让程序变的异常的灵活,你可以向一个对象发送一个方法调用消息,而不必知道对象的类型,在ObjC中,有一个id类型,就是无类型对象指针,如果我们编写一个函数,通过将id类型作为参数,然后就可以向这个id发送一个方法调用,当然可以通过@selector查询是否支持某个方法调用,那么只要对象有这个方法,就可以实现调用,而无论对象类型。

      在delphi中的dynamic方法的调用有些类似,或者windows的COM对象的方法调用也有些类似。

 

  1. ObjC中,声明对象变量,必须加上“*”,也就是说,对象变量必须是指针,如果不加上“*”,那么编译器认为你在申请一个栈空间变量。

       NSString  str; 这样的声明是错误的,因为对象不能在栈空间申请,编译器也不会增加对构造

      函数的调用,而在C++中,这样声明是容许的。

      不过同C语言一样,struct变量是在栈空间申请的,因此可以直接这样定义。

      CGRect  rect;结构体可以直接在栈空间申请。

 

  1. 在类头文件中尽量少引入其他头文件,因为当其他程序要用到本类时,会引入本类的头文件,如果在本类头文件引入不必要的其他头文件,就会让编译增加时间。而且也容易引起混乱。

      在类头文件中,可以对引用的类用@class className 来声明一个符号是一个类。

      在实现(implementation)文件中再#import 该类头文件。

 

  1. 字面量(常量):

      采用@开头,如@1.0 , @2.5f,@YES,@“This is a String”,@(x+y),@‘A’

      NSDictionary *dict = @{@“key”:@“value1”,……}

 

  1. 多用类型常量const,少用#define宏定义(预处理指令)

      static NSString *const  constStr = @“Some Strings”

      static const Int  kInt =  12;

      static:与C++语言的static有些区别,ObjC的static表示变量仅在当前的编译单元(输出目标文件.o)可见。

     如果不声明为static,则编译器在.o目标文件的输出符号表中包含该符号定义,这样如果有两个目标文件定义同样的名称,那么链接器就会报错。

      如果我们期望一个变量能在另一个目标文件能使用,那么就采用extern声明

    

     在头文件中声明变量

     extern NSString *const  constStr;

     在实现文件中初始化变量;

     NSString *const constStr = @“SomeString”;

     那么在引用该类头文件的程序单元中,编译器会发现定义了extern变量,就会在外部单元去查找,而在本单元的目标文件中的输入符号表会包含该变量名。当链接器链接时,会全局查找符号表。

     extern与C语言的用法基本一样。

 

  1. ObjC中的enum类型,可以定义类型,还可以指定值。

      enum  state:NSInteger{    //也可以不指明NSInteger,那么可以用byte类型来保存state

             on  =1,

             off = 0,                         //必须要加逗号,如果不指明=0,那么编译器会自动填2

      }

      type enum state state;  定义一个state类型代替enum state, 这样我们就不需要每次都写上enum关键字

      enum keyDowned{

            NoKey = 0,

            RightKey = 1 << 0,

            LeftKey   = 1 << 1,

            MidKey   = 1 << 2,

     }

    这个枚举类型可以做集合类型使用,在delphi中,有专门的set类型定义,在ObjC中enum就可以实现集合类型功能,

    enum keyDowned keydown = RightKey | LeftKey;

    if(keydown & RightKey)…….

   在C++中,这种操作是不容许的,因为RightKey | LeftKey的结果是一个整数,而左边为enum类型,类型是不匹配的。C++中必须经过显式转换类型。

   在SDK基础框架中,已经定义了宏来定义enum变量,NS_ENUM、NS_OPTIONS(对应集合类型),如

   typedef  NS_OPTIONS(NSUInteger,varName){选项},此时定义了typedef  enum V V模式

 

  1. import与include的区别,include是输入源码,而import则只是输入声明。

 

  1. 对象属性(Field域),与对于对象属性的引用,在C++、Delphi中,是通过对象的偏移地址来直接获取的,而在ObjC中,则是通过属性名先查询类找到属性在对象中的偏移地址,然后再通过对象地址获取属性,这样做的好处,是如果类增加了属性,改变了属性顺序,依然可以通过属性名获取到对象的属性值。在C++和Delphi中,编译后,对对象属性值的引用,都会变成了[对象基地址+偏移值],而ObjC通过增加中间层,虽然速度有所减低,但是当类升级后,原来的代码依然可以不需要重新编译就可以使用新的类。

     

      注意对象属性与对象变量是两个概念:对象的属性可能并不存储在对象中,也就是说不一定有存储变量,例如我们定义一个属性property,同时指定它的get和set方法函数,那么在get函数中,我们可能从数据库、网络或其他地方获得数据,并不一定就从类定义的变量中获取数据(类中定义的变量都会是对象的组成部分,常量则保存在类对象、代码区或常量数据区),也可以能是一个通过计算的结果。而set也并不一定存储该值,也可能只是改变对象的状态(其他变量)或引起某事件(执行某函数)。

    

    在C++和java中没有属性这个概念,属性概念,只是语法上的变通而已,并没有什么新的内容。在C++和java中,点语法只支持对存在有实体变量的访问,也就是说点语法会直接转换成对对象基地址的偏移。而在ObjC和Delphi中,点语法的处理有两种,一种是通过对象内部偏移访问变量(类中没有声明为property的变量,还有一种是指定了property的变量,点语法则会转变成get和set方法函数。在编写java程序时,通常建议给属性编写get/set方法(javabean中默认就是),而不是直接将对象变量声明为public,这样做的好处是,当我们要修改类,对属性值加工或检查才返回给调用者时,调用者并不需要清楚类的修改。另一个好处是,当一个类的属性顺序修改了,生成的对象中的属性顺序变了,那么通过get/set方法调用来获得值时就不需要重新编译本类,否则就会引起错误,这对于java编程来说,非常重要,因为类经常会被更新。

    

     在ObjC中如果将property 变量 在实现文件中,并不实现get/set,那么就要在实现文件中指示为

     @dynamic,这样编译器就不会为这个类属性创建get/set,但是那样有什么意义呢,这只有在一种情况下,就是属性的get和set是在运行期生成的。

 

     属性具有的特性:

     默认为atomic,也就是对属性进行读写时必须是原子操作(这在操作系统中有说明),也就是线程安全的,保证一个属性读写(get/set)一次性完成而不被其他线程打断。

     在几乎所有的IOS编程中,都声明为nonatomic,因为属性访问非常频繁,原子操作对性能影响大

     

     属性的读写:readonly,则只会产生get方法,或者说,编译器只找get方法。

     对内存管理的定义:

     assign :赋值的意思,就是对纯量类型(通常就是基本类型)进行简单赋值,不管理内存。

                   编译器不会增加额外的代码来处理内存。

     strong :强类型,也就是要求编译器管理内存的,当赋值时,强类型指向的对象必须要先清理,

                  否则就会引起内存泄漏,因为没有指针再指向这个对象时,程序员必须自己释放对象,

                  而声明为strong,则将这个工作抛给了编译器,当然对象必须实现了free方法,这也是为

                  什么ObjC的类必须继承于NSObject,因为这个父类实现了一些通用方法。如果我们自己

                 实现一个基类,那么运行时就可能找不到释放对象的方法。

    weak : 当赋值时,set自己本身不释放旧的对象,因为原对象可能是被其他容器(对象)管理的

                 通常在IOS的UIView编程中,因为控件是被视窗管理的,因此通常被声明为weak。

                 这种属性,在指向的对象被释放时,自己也会自动变成nil,而实现这个的原理就在于回调

                (猜测:当设置属性为目标对象时,会在目标对象的引用表中增加自己的地址)。

    unsafe_unretained: 不安全_不保留,与assign效果一样,只是属性是对象指针,也就是说,

                 当指向的对象被释放时,并不会通知自己,赋值时,自己也不清除旧对象。因此对于此种

                 属性,在引用操作时,就必须要检测其对象是否已经释放。而实际上,是没法检测对象地

                 址是否有效(是否原对象)。

 

   copy     释放原来的对象,但是会copy新的对象,而不是直接引用赋值的新对象,这样做的目的

                是为了保护现场,将当前对象的状态保存起来,这在很多时候是很有意义的,如并行处理

    //注意:在XCode编程中,可以关闭ARC,但是关闭ARC(自动引用计数)后,这些自动内存管理的特性定义也必须取消。

 

   在自动生成get/set时,声明这些属性,编译器会自动在get/set代码中增加相应的代码。如果是自己编写get/set,那么建议最好遵从自己的声明,并不是强制要求如此,而是当别人引用你的代码时,不要误导别人,如属性声明为strong的属性,别人给属性赋值后就不再手动释放这个属性指向的对象,因为类的使用者认为你的对象会负责属性引用对象的内存释放。

在UIView的子控件是由父控件管理的,因此对于指向子控件的变量定义,声明为weak是可以的。

             

       在对象外部不建议直接访问对象的实例变量(如果该实例变量是对应一个property的),当然如果在类声明中采用@public声明的实例变量,那么只能通过直接访问方式。同java一样,我们在设计类时,有些采用直接实例变量访问,有些通过方法访问,要视具体情况而定,直接访问肯定会快。

 

  1. 对象等同性:在delphi中,字符串的比较可以直接通过=号判断,而在java、ObjectC中,必须采用equals函数来判断,而如果我们开发的类对象要用于比较,那么也要覆盖isEqual函数。

      在delphi中override与overload是两个不同的概念,前者是覆盖父类的方法,而后者则是声明多个同名函数(重载)。

       在collection(Set、Map、HashTable、Dictionary)中加入对象,则在外部最好不要再修改该对象,特别是对于key值对象,不过一般的字典设计中,当向容器加入key时,会复制一份,这样这个key对象就类似一个常量,这样才能保证hashcode不会变,否则通过key取值时计算的hashcode已经变化就取不到原来的值。

 

  1. 类工厂,在C++中有(产生一个对象的方法),在java中也有(叫获得实例),在delphi中一样有,这些都是类方法,就是一个父类有一个方法可以用来产生子类的对象。

      NSArray就是一个类工厂,它本身并不能实例化,而只提供产生数组的方法。而要实现实例对象,就必须采用工厂方法生成一个子类数组对象。要实现一个继承NSArray的数组,那么就要自己设计存储对象,并实现count方法和objectAtIndex方法。数组对象的下标引用,在编译时会变成对方法objectAtIndex的调用。注意NSArray是一个对象,与直接数组声明是不同的。NSArray中可以保存不同的对象类型,其实就是一个对象指针数组。

 

  1. objc_msgSend  /  objc_msgSend_stret / fpret/ Super, 对应一些特殊的方法调用,如对超类方法调用,返回的参数是浮点数(需要特殊处理的)。

      每个类中都保存了指向这些标准函数的指针的一个表。这样在类装载时就可以初始化这些指针,因为这些函数在内存中位置并不确定,就如动态库,而编译器也就没有必要确定这些函数地址。这同API函数引用在加载可执行文件时才动态初始化地址是一样的。

 

  1. 消息转发:ObjC中,类的方法是可以动态修改和增加的,修改的方法就是修改方法表,而当objc_msgSend函数查找对象(类)的方法表时,如果没有找到方法,就会调用类方法

      + (BOOL)resolveInstanceMethod:(SEL)selector

      这个方法是NSObject的方法,可能被子类覆盖,方法返回是否能动态生成目标方法。

      如果我们覆盖这个方法,并在这个方法中动态增加一个目标方法,objc_msgSend函数在调用上述方法后,如果获得的是YES,那么就会重新查询方法表获得动态增加的方法地址。

       增加方法的方式如下

       class_addMethod(self,seletor,(IMP)方法名,“参数类型表”)

       参数类型表按照Object C标准,如“v@:@”,v表示void,@:表示方法,@表示字符串。

       如果我们没有动态增加某个方法,那么resolveInstanceMethod返回就是NO,此时objc_msgSend会调用类的

       -(id)forwardTargetForSelector:(SEL)selector

         注意这个方法是对象实例方法,如果我们不覆盖这个方法,那么就返回nil,如果我们通过在类(对象)内部创建一个另一个对象来响应某个方法,那么可以覆盖这个方法,对于某个方法调用,我们返回创建的内部对象,而objc_msgSend回重新查询获得的(id指向的)对象的类是否含有目标方法名。 例如在Sys类中,如果我希望能输出UserPriv类的方法,那么可以这样处理,那么我们的程序直接向Sys调用(UserPriv)某个方法就可以了。注意这里只能同名转发。

       如果上述方法返回的是nil,那么objc_msgSend还会进行第三步尝试,调用对象的

      -(void)forwardInvocation:(NSInvocation *)invocation;

       这个方法是完整转发了一个消息,消息包含self,selector(方法名),parameters(方法参数)在这个方法中,程序员可以修改目标,方法名另其转发到另一个对象的不同方法,实现不同对象不同方法的嫁接。如果对象发现自己不能处理,通常就调用父类的同名方法,而最终到达NSObject,而NSObject则会抛出异常,程序终止。

      methodSignatureForSelector:方法签名;

 

       class_getInstanceMethod/ classgetClassMethod

       method_exchangeImplementations(上述方法获得的Method 1,2);  交换两个方法。

       可以交换方法,我们如果新建一个方法,然后将自己的方法交换给指定的方法,就可以实现对现有类行为的修改,这一点在java中也是可以做到的。这样做是极度不安全的,不过对于JVM和隔离很强的IOS/OSX程序来说,实现了方法hook也仅仅是局部范围的(仅自己的程序)。在java的方法注入中,我们可以修改JDBC的Conntion类实现连接池功能,而用户程序根本不用修改,而且也不知道有链接池的存在。

 

  1. Class Object,类对象,就是类信息在内存中的结构。对象是一个结构体,类对象也是一个结构体,从id的定义说明,对象结构体的第一个指针就是Class 结构体的具体对象。

 

       id 是一个结构体指针,这个结构体只有一个元素,就是类对象指针。

       typedef struct objc_object{

             Class isa;

       }  *id

 

       typedef struct objc_class{

           Class isa;                       // 指向了metaclass,是类对象本身的描述。

           Class super_Class;     // 父类类对象。

           const  char *name;    // 类名

           long version;                // 版本

           long info;                         //特征信息

          long  instnce_size;     // 实例对象大小

          struct objc_ivar_list  *ivars;  //  对象属性变量的类型说明, Delphi中为fieldList,

                                                      // 访问属性先遍历这个表,找到属性在对象中的偏移。

          struct objc_method_list **methodLists; //包含多个方法表

          struct objc_cache   *cache;  // 动态查询方法等缓冲,不用每次查询

          struct objc_protocal_list *protocols;  //实现的多个协议对象列表,因为ObjectC不支持多继承

 

         这与delphi的Class对象模型非常相似,对象的第一个指针也是指向类对象。在C++中,则没有类对象,类信息直接保存在了对象中。java则因为是解释执行的,字节码不用地址操作,因此类信息的保存比其他语言更加复杂,保存内容比其他语言更多。方法参数信息是通过方法名的后缀表达的,ObjectC、java都如此,而C++(虚方法)、Delphi则不保存参数信息,但是对于重载函数,方法名则包含了参数信息。

          注意:上述结构体并不是类对象的内存结构,是通过NSObject的class方法(注意是方法)搜集到的类信息然后产生的类对象。不过对象的第一个指针指向类对象是确定的,然后在类对象中还包含了比上述结构体多得多的信息,其对象组织形式也不是这样的,类信息是一个树结构(也是网络结构)。

 

  1. 继承与协议

       java、Object C、Delphi等都不支持多重继承,似乎C++就成了另类。多重继承会让问题马上变得非常复杂。单继承是一颗树,而多继承就变成了一个网络。

      java中增加了interface(接口),Delphi增加了abstract抽象类,Object C中则增加了protocol协议,这些都是只声明不实现的类。其实就是对实现类的一个特征描述,表示实现类实现了哪些方法

 

  1. delegate,委托、代理

      在ObjectC中,因为不支持多重继承,那么可以让某个类实现某些特定的协议(接口),实际上,在处理调用不同对象的同名方法时,我们可以通过id类型来传递对象参数,实现同等的多重继承实现的功能,由于ObjectC是非常动态的语言,因此实现的方式灵活,解决问题的方法也很多。这里delegate的意思是当一个对象(类)自己不处理某个消息(方法调用)时,将调用转给(委托)另一个对象来处理,而另一对象则必须实现了规定的某个协议(protocal),其实delegate就是一个实现了某协议的对象变量。在java中就是“接口变量”,变量可以指向任意实现了该协议的对象。

     id<SomeDelegate> delegateVariable;                       // 声明实现了某个协议的对象变量。

     interface myClass:SuperClass<SomeDelegate>{ };  // 声明类实现某个协议。

 

  1. 同样的问题有多重解决方法。

      linux中,堆内存管理时,申请的堆指针前面保存了当前分配的内存大小,而在MAC OSX中,分配的内存中没有额外的信息,因此linux的内存free函数可以通过传入的指针向前4字节(32位系统)就可以知道当前指针的大小,而OSX则采用了内存表来指明该地址块的大小,因此linux的free比OSX操作会快一点,但是也带来另一个问题,就是安全性,如果某个程序误修改了指针前的4字节数据,那么free时就会造成系统崩溃。

     在delphi中,不是采用\0字符来结束字符串,而是采用在字符串前头写入字符串长度,因此在delphi中向API传递字符串参数时,要进行字符串转换,就是要给字符串加上\0字符。delphi有这样做的理由,例如获取字符串的长度,相当简单。字符串也可以包含\0,虽然显示上没有意义,但是对于数据处理则不同一般。

 

  1. 方法是被编译成地址,还是可以在运行时动态找到方法。

      Delphi中私有的含义:相对于unit外部,对于同一个unit,即使是不同类,一样可以访问私有成员。私有方法被直接编译成地址引用,对于property,则会出现在输出符号表中。

      java和object C等动态语言,private只是给编译器检查语法的,运行期程序可以通过遍历访问到私有成员(包括属性和方法)。

 

  1. 栈的实质:就是只有起始没有终点的内存延伸区域。在x86和ARM等处理器中,都有一个栈指针寄存器,设计这个栈寄存器作用是方便保存处理器状态的,所谓状态就是处理器计算的中间结果,这些中间结果保存在寄存器中,而采用POP和PUSH指令可以将寄存器数据保存在一个内存区域,为什么不采用其他数据结构呢,这是当时的处理器设计者决定的。如果我们在内存分配一块专门用于保存寄存器的区域,那么我们可以定义[SP+偏移量]固定存储某寄存器值也是可以的,那么 CPU可以设计SAVE EAX 指令来保存寄存器到固定内存位置(如[SP+4])。在JVM中,方法有自己的私有栈空间,这个区域就是由JVM程序维护的。处理器的发展与软件的发展是离不开的,如保护模式与操作系统的相互促进。SP/BP寄存器的设计与高级语言编译器发展分不开,编译器采用这两个寄存器很容易分配局部变量内存,如果我们使用汇编语言写程序,其实可以实现任意的局部变量内存分配管理,在汇编编程时,因为内存分配是程序员自己的事情。而编译器正是解决了程序员每次的内存规划问题,让程序更加健壮和灵活。高级语言代替了程序员的内存规划工作。如果没有SP/BP等寄存器,那么编译器一样可以实现栈操作,而这个操作就比寄存器操作要耗时间些,如指定一个符号地址(相当于寄存器)专门存放栈顶,那么每次的PUSH,就是修改这个符号地址,PUSH功能则在编译时不是加入PUSH指令,而是采用多条指令代替(如MOV R0,[SPVar];MOV [R0],pushValue;  ADD R0,4;MOV[SPVar],R0;)(装入栈顶地址,向栈顶地址写入要PUSH的变量,然后修改栈顶值,存回栈顶指针变量), 4条语句实现的功能与PUSH指令是一样的。在虚拟机中,其实也是通过类似方法进行指令模拟的。

      栈与堆是同样的内存,只是不同的用途。可以说堆是一块被管理的相对安全的区域,而栈则因为可以无限延伸,因此不能无限制使用。

 

  1. 对象的alloc与init:alloc是申请内存空间,其实就是根据类中描述的对象大小向内存管理器(堆管理器)申请一块空内存,内存管理器不会清0这块内存。init则是对对象所含变量进行初始化。如果在init中我们什么也不做,那么获取到的变量就会是赃数据。对象的释放则是释放对象引用的其他对象,在NSObject的释放函数中会扫描对象所有属性(变量)指向的对象,并调用release来减少引用,因此子类总是要调用父类的dealloc/release函数。最终的对象释放就是将内存块还到可用内存池中,而内存管理器(其实就是一堆函数)并不清0归还的内存,因为那样太耗时间,通常内存也是在使用时才初始化,因此对于刚归还的栈和堆内存,其实还是可以通过指针获取到变量(对象)内容,因此在对象free时,总是紧接着就给对象指针赋nil(就是0)。在delphi中更是直接提供freeAndNil函数来释放一个对象。如果不及时赋nil,可能程序引用了free的指针依然不会出错。

 

  1. ObjectC中,指针“->”操作与“.”引用操作是不同的概念。

      self-> _var1 = 1;    // 地址操作

      self.var1 = ;              // 是property的setter(setVar1)方法调用。

      在C++中,点号引用也是与指针操作不同的,点号不像ObjectC中的方法调用,而是对当前变量(对象)的直接地址偏移。

       CObject obj;                                    // 直接在栈空间创建了对象,采用了默认构建函数。

       CObject *pobj  = new Object(); // 或*pobj = obj;  在堆中创建对象,在栈中申请变量

       obj.someMethod();                            // 注意直接打印obj,可以看到obj就是栈空间的地址,

       pobj -> someMethod();      // 编译器会将obj符号处理为直接对象地址,而pobj内容才是地址。

       (*pobj).someMethod();      // 编译器对于语法的严格执行。

 

       在delphi中,只有点语法,因此编译自己负责对符号的处理。

        PRecordTest = ^ RecordTest

        RecordTest = record

            var1: ^Integer;

            var2: PInteger;

        end;

        rtest1 : RecordTest;                    //声明一个结构体

        prtest1:PRecordTest;                  //声明结构体指针

        prtest1 := @rtest1;                      //取地址操作采用 @、addr都可以。必须保证右边必须为地址

        rtest1.var1 = addr(rtest1);           //此时、第一个变量记录了结构体自己的地址

        prtest1.var2 = @rtest1.var1;       // 此时、第二个变量记录了第一个变量的地址。

        prtest1.var2 = Pointer(Integer(rtest1.var1^)+4);    //第二变量记录了自己的地址,

        // rtest1.var1^ 取第一个变量的值,就是结构体自己的地址,如果不用^,是var1的地址,相同。

        // delphi 对于指针操作非常清晰,直接进行类型转换,就将地址转为整数,然后进行移动,可单字节移动,delphi不容许直接的指针运算。在C/C++中,指针运算时是根据指针类型大小增减字节数的。

        注意rtest1.var2与prtest1.var2是一样的,编译器会对prtest先进行地址获取(如MOV EAX,[EBP-$24])然后再进行偏移(MOV [EAX+$4], 值);

        

       java中,其实没有指针的概念,因为任何的地址操作,都经过了JVM的翻译,而这个翻译过程就是地址操作,JVM没有提供地址操作的字节指令码。java是完全面向对象的,变量属于对象或类,任何变量的引用都受JVM严格监控。

 

  1. 在ObjectC中,可以对nil发送信息,这如果在delphi和C++中是会引发系统报“读0地址错误”或引发程序崩溃的,因为在后者中,对象方法的引用是通过地址偏移实现的,在delphi中有一种函数可以像ObjectC中一样,那就是dynamic方法函数,因为那实现方法与ObjectC的对象方法调用类似。因此在delphi中,直接这样操作

             TObject(0).someDynamicMethod;

       并不会报错,系统不执行任何操作;如果有返回值,那么返回的是0,就是EAX寄存器中是0,这个方法调用是被动态方法查询函数接管的。

      nil.anyMethod; 在objectC中都不会报错,只是返回0值(如果有返回值)。因为即使对于0地址,obj_sendMsg等函数一样可以查询到其是否包含该方法函数,对于0值处理,objectC不报异常,而是直接返回0值,这可以让程序语言实现更多功能,但是对于程序员来说,就要清楚不报异常并不是一定就获得了正确的对象。

 

  1.  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值