首页 > 解决方案 > 为什么在 Objective-C 中将指针 * 用于字符串而不用于整数

问题描述

我首先学习了 Swift,也许这应该是在学习 Obj-C 之后,所以现在我尝试完全理解 Obj-C,我遇到了以下问题:

Obj-C 变量

NSInteger score = 556;
NSString *name = @"matt";

在 Swift 中,变量的一切都是一样的

var score = 556
var name = "matt"

为什么objective-c 用于字符串 * 虽然整数不是 * 使用,为什么会有这样的不同?它与运行时间有关吗?

你能用实际的例子给我解释一下吗?谢谢

标签: objective-coop

解决方案


你知道,没有指针,Objective-C 只是带有分号的 Swift。

假设您需要分配一个内存区域供您自己使用。您可以在那里存储整数、图像、对象字典或声音剪辑。指针是您对该段内存的句柄。它主要告诉您获得的内存区域的位置,但在 Objective-C 中,它还跟踪例如引用计数,因此您无需担心分配和释放它。所以事情会很快变得相当复杂。

让我们假设最简单的情况。您需要一些内存来存储对象。说一个字符串,但这没关系。此外,对于那里的所有纯粹主义者来说,这是一个一般性讨论,并不意味着在技术上是正确的,而是为了向 OP 说明这个想法。

首先,您将分配该对象

NSString * p = [NSString alloc];

这将要求操作系统提供一个正确大小的内存区域来存储字符串,并且操作系统将以指向新分配区域的指针进行响应,该地址现在存储在p. 这只是一个内存区域,你可以用它做你想做的事。你甚至不需要在那里存放一根绳子,但如果你像那样走开,东西很快就会变成梨形。

现在操作系统只是给了你一个内存区域。那个内存不是空白的,它保存了一些以前使用过的剩余位。所以你的首要任务是清除内存——初始化它。所以下一步就是初始化它

p = [p init];

在此之后,内存区域已由一些初始化程序准备好,例如,它被归零或将一些默认值加载到内存区域中以用于更复杂的类。

请注意,实际上这有点不同 - 请参阅下面的编辑。

在 C 中,你有并且alloc可以做到这一点。其中一些将仅分配内存,而其他将分配内存并将其全部设置为 0 - 清除所有位。在 Objective-C 中,您通常将其作为一个步骤来完成malloccalloc

p = [[NSString alloc] init];

由于操作系统分配了一个内存区域,因此您需要在完成释放它时告诉它,例如

[p release];

或在 C 中free完成后使用。

现在这只是学术性的,因为 ARC 会通过保持对象的内部引用计数器来自动为您执行此操作,以确定它何时超出范围然后释放它,但我离题了。现在让我们假设您不必担心将那块内存释放回操作系统,ARC 会为您处理它。

现在让我们假设一个更复杂的类,比如说

p = [[Invoice alloc] init];

然后当你做

p.items = 100;

编译器将知道它需要将整数值复制100到该内存块内的正确位置,它会为您这样做。这个主题有很多变体,但基本上,当您为 ivars 分配值时,这些值会被复制到分配的内存区域中,并且当您读取它们时,它们也会从正确的位置复制回局部变量中。

另一方面,如果你给一个简单的 int 变量赋值

int items = 100;

这不会复制到您预先分配的区域。相反,消息或函数有一个特殊的本地工作区,称为堆栈,在其中创建这些变量,然后动态丢弃。因此,上面的语句会将值加载100到堆栈上并记下该位置,以便以后如果您引用items它,将知道在哪里可以找到并读取或写入堆栈上的值。

同样地

Invoice * p = [[Invoice alloc] init];

被加载到堆栈上,但这里指针的值或内存地址被加载到堆栈上,而不是它指向的内容。它是一个指针这一事实向您表明它指向其他地方的某个感兴趣区域。

如果以后你这样做

Invoice * q = p;

您将 p 的相同指针值加载(在堆栈上)到 q 中,现在 p 和 q 都指向存储实际对象的同一内存区域。因此,指针在可以解释为指向内存中地址的指针的值与存储在该地址的数据之间添加了这一层分离。这可以继续,例如,您可以并且确实获得指向指针的指针等等。

由于您不能自己在动态堆栈上分配内存,因此您不能将对象存储在堆栈上,但可以将指向对象的指针存储在那里。

当您直接访问内存时,指针为您提供了不可思议的力量。例如,您可以编写一个通用函数来分配内存或将内存写入磁盘或清除内存或通过网络传输内存块或将内存块加载到屏幕区域。现代编译器为我们处理了这些事情,所以听起来没什么大不了的,但强大的是您对所有对象使用相同的例程。如果您的数组存储指针,那么您可以在数组中存储任何内容作为另一个示例。

指针的寿命也比堆栈长。你放在堆栈上的东西,例如

int items = 100;

只要你在函数内部,它就可以存活。当函数返回时堆栈被丢弃,因此您需要一个专门分配的内存区域以超越函数甚至应用程序的方式进行通信。

这是一个更深层次的例子,只使用整数指针,而不是 Objective-C 对象的指针,来进一步说明堆栈上的指针和值之间的区别。

在C中你会做

int * p = malloc ( 10 * sizeof( int ) );

这将分配足够的空间来存储 10 个整数并返回指向分配空间的指针pp现在在堆栈上并包含刚刚返回的内存区域的地址。

下一步如果你这样做

int q = 123;

值 123 存储在堆栈中,并且在q编译器将在您使用时将其转换为堆栈上的特定位置的名字对象下,q例如为其分配一个值。

接下来,如果你这样做

* p = q;
* ( p + 1 ) = q / 2;

这会将 (123) 的值从堆栈加载q到 指向的内存区域,该区域指向p10 个整数中的第一个。接下来,您计算q / 2该值并将其存储到下一个整数的位置,即第一个整数之后的 1,由 指向p和引用* ( p + 1 )。编译器可以很容易地做到这一点,因为它知道整数的大小,所以它知道如何翻译

* ( p + 1 )

进入正确的区域,该区域指向从 base 偏移 1 的整数p

这是另一个关于指针的主题,它允许非常有效的指针算法,您可以使用它来遍历内存区域。此外,这就是为什么sizeof在 C 中如此重要的关键字,因为它与语言如何通过指针处理内存直接相关。

在 C 中,完成后,您需要将该区域释放回操作系统,以便您执行

free( p );

完成后。

这看起来很简单,但实际上很难跟踪所有已分配的内存区域。很容易发生您不发布或将数据加载到已发布的区域。前者会泄漏内存,而后者可能会产生灾难性的后果。

这就是为什么 Objective-C 的 ARC 代表了语言发展的一大步,因为它会为您解决这个问题。在 ARC 世界中,内存的分配和释放,指针中的一个大话题,不再那么重要或可见——因此它也可以屏蔽用户对指针的一些理解。

在例如 Swift 中,这种分配和释放完全在幕后进行,您不必真正担心直接分配或释放内存。这使得很难理解指向内存和被指向的内存之间的区别,这是指针的核心。

你知道,没有指针,Objective-C 只是带有分号的 Swift。

我以这个颇有争议的声明开始和结束。

为什么?

当我还是一名为指针概念而苦苦挣扎的程序员时,我曾想过完全放弃它。这是在对象之前,您可以(尝试)以这种方式在堆栈和侧步指针上做所有您需要做的事情。

我刚从 Pascal(那里你不经常使用指针)来,并且在 C 中挣扎,尤其是在指针方面,然后在某个地方读到,没有指针,C 只是带有花括号的 Pascal!我知道 C 比 Pascal 更强大,但要掌握这一点,我必须掌握指针。

注意 - 正如 Sulthan 所提到的,Pascal 也有指针,如果一个比另一个更强大,这是非常值得商榷的。

这是一个很好的示例来说明很多这些概念,以及如何使用指针来遍历内存。

int main(int argc, const char * argv[]) {
    @autoreleasepool
    {
        // insert code here...
        int a = 10;      // a loaded on stack
        int * b = & a;   // b now points to a

        NSLog ( @"a pointer %p value %d", & a, a );
        NSLog ( @"b value %p points to %d", b, * b );

        int * p = malloc ( 10 * sizeof ( int ) );
        int * q = p;

        // Show p on the stack and * p allocated somewhere
        // Note the values, & p agrees with & a of earlier e.g.
        NSLog ( @"p address %p on stack but points to %p", & p, p );

        for ( int i = 0; i < 10; i ++ )
        {
            * q = i; // loads value i into area pointed to by q

            NSLog ( @"i now %d", i );
            // Note the huge difference between addresses on the stack and addresses alloc'd
            NSLog ( @"\tp %p p + i %p value pointed to is %d", p, p + i, * ( p + i ) );
            NSLog ( @"\tq %p value pointed to is %d", q, * q );

            q ++;       // Increment pointer, not value, now points to next int
        }

        // Of course ...
        free ( p );
    }

    return 0;
}

编辑 Objective-C 分配

我在文中提到内存需要在 Objective-C 分配之后初始化。我现在找不到任何文档,但我很确定 Objective-C 的alloc,例如

NSString * s = [NSString alloc];

实际上分配清除或归零内存。这init并不是真正的零,而是为了让类正确初始化其成员。

但是,既然我们正在讨论指针,并且您正在寻找编程示例,那么有什么比编写一段代码来测试它更好的了。

下面的代码将创建您选择的两个 Objective-C 类。一个只会被分配,一个会被分配和初始化。然后它使用指针逐字节比较这两个类,看看是否有区别。另一个使用指针的好例子。

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

// The object we will test
#define TEST_OBJECT NSFileManager

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // Allocate some complex class
        TEST_OBJECT * obja = [TEST_OBJECT alloc];
        TEST_OBJECT * obji = [[TEST_OBJECT alloc] init];

        // Size of this class
        unsigned long n = class_getInstanceSize ( TEST_OBJECT.class );

        NSLog ( @"Object size is %lu", n );
        NSLog ( @"\tLocation in memory %p", obja );
        NSLog ( @"\t                   %p", obji );

        // Get pointers to the two instances
        void * p = ( __bridge void * ) obja;
        void * q = ( __bridge void * ) obji;

        // Compare the two
        int cmp = memcmp( p, q, n );

        NSLog ( @"Result is %d - %@", cmp, cmp ? @"different" : @"same" );

        // Dump the actual bytes side by side
        for ( int i = 0; i < n; i ++ )
        {
            int l = ( unsigned ) ( * ( ( unsigned char * ) p + i ) );   // Left
            int r = ( unsigned ) ( * ( ( unsigned char * ) q + i ) );   // Right

            NSLog ( @"Byte %3d | %3d | %3d | %@", i, l, r, l != r ? @"***" : @"" );
        }
    }
    return 0;
}

推荐阅读