在Objective-C中,两个星号**表示什么意思?

15

我知道一个星号*表示指针,但两个星号**代表什么意思呢?

我在文档中偶然看到了这个:

- (NSAppleEventDescriptor *)executeAndReturnError:(NSDictionary **)errorInfo

猜测一下:一个指向指针的指针? :) - Constantin
是的,我也猜到了,但我在谷歌上找不到任何确认。 :) - John
5个回答

34

这是一个指向指针的变量,就像在C语言中一样(Objective-C基于C语言,尽管它有一些奇怪的方括号语法):

char c;
char *pc = &c;
char **ppc = &pc;
char ***pppc = &ppc;

等等,不断地重复(或直到变量空间用完)。

它通常用于向必须能够更改指针本身的函数传递指针(例如重新分配可变大小对象的内存)。

=====

根据您的要求,这里是我为另一篇帖子编写的一些代码,它演示了如何使用它。这是一个appendStr()函数,它管理自己的分配(您仍然需要释放最终版本)。最初,您将字符串(char *)设置为NULL,函数本身将根据需要分配空间。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void appendToStr (int *sz, char **str, char *app) {
    char *newstr;
    int reqsz;

    /* If no string yet, create it with a bit of space. */

    if (*str == NULL) {
        *sz = strlen (app) + 10;
        if ((*str = malloc (*sz)) == NULL) {
            *sz = 0;
            return;
        }
        strcpy (*str, app);
        return;
    }

 

    /* If not enough room in string, expand it. We could use realloc
       but I've kept it as malloc/cpy/free to ensure the address
       changes (for the program output). */

    reqsz = strlen (*str) + strlen (app) + 1;
    if (reqsz > *sz) {
        *sz = reqsz + 10;
        if ((newstr = malloc (*sz)) == NULL) {
            free (*str);
            *str = NULL;
            *sz = 0;
            return;
        }
        strcpy (newstr, *str);
        free (*str);
        *str = newstr;
    }

    /* Append the desired string to the (now) long-enough buffer. */

    strcat (*str, app);
}

 

static void dump(int sz, char *x) {
    if (x == NULL)
        printf ("%8p   [%2d]   %3d   [%s]\n", x, sz, 0, "");
    else
        printf ("%8p   [%2d]   %3d   [%s]\n", x, sz, strlen (x), x);
}

static char *arr[] = {"Hello.", " My", " name", " is", " Pax",
                      " and"," I", " am", " old."};

int main (void) {
    int i;
    char *x = NULL;
    int sz = 0;

    printf (" Pointer   Size   Len   Value\n");
    printf (" -------   ----   ---   -----\n");
    dump (sz, x);
    for (i = 0; i < sizeof (arr) / sizeof (arr[0]); i++) {
        appendToStr (&sz, &x, arr[i]);
        dump (sz, x);
    }
}

代码输出如下。你可以看到,当当前分配的内存空间不足以扩展字符串时(在注释处),指针会发生变化:
 Pointer   Size   Len   Value
 -------   ----   ---   -----
# NULL pointer here since we've not yet put anything in.
     0x0   [ 0]     0   []

# The first time we put in something, we allocate space (+10 chars).
0x6701b8   [16]     6   [Hello.]
0x6701b8   [16]     9   [Hello. My]
0x6701b8   [16]    14   [Hello. My name]

# Adding " is" takes length to 17 so we need more space.
0x6701d0   [28]    17   [Hello. My name is]
0x6701d0   [28]    21   [Hello. My name is Pax]
0x6701d0   [28]    25   [Hello. My name is Pax and]
0x6701d0   [28]    27   [Hello. My name is Pax and I]

# Ditto for adding " am".
0x6701f0   [41]    30   [Hello. My name is Pax and I am]
0x6701f0   [41]    35   [Hello. My name is Pax and I am old.]

在这种情况下,你需要传递 **str,因为你需要能够更改 *str 的值。
=====
或者使用以下方法,在没有数组的情况下对字符串进行未卷动的冒泡排序(真丢人!)。它通过直接交换字符串的地址来实现。
#include <stdio.h>

static void sort (char **s1, char **s2, char **s3, char **s4, char **s5) {
    char *t;

    if (strcmp (*s1, *s2) > 0) { t = *s1; *s1 = *s2; *s2 = t; }
    if (strcmp (*s2, *s3) > 0) { t = *s2; *s2 = *s3; *s3 = t; }
    if (strcmp (*s3, *s4) > 0) { t = *s3; *s3 = *s4; *s4 = t; }
    if (strcmp (*s4, *s5) > 0) { t = *s4; *s4 = *s5; *s5 = t; }

    if (strcmp (*s1, *s2) > 0) { t = *s1; *s1 = *s2; *s2 = t; }
    if (strcmp (*s2, *s3) > 0) { t = *s2; *s2 = *s3; *s3 = t; }
    if (strcmp (*s3, *s4) > 0) { t = *s3; *s3 = *s4; *s4 = t; }

    if (strcmp (*s1, *s2) > 0) { t = *s1; *s1 = *s2; *s2 = t; }
    if (strcmp (*s2, *s3) > 0) { t = *s2; *s2 = *s3; *s3 = t; }

    if (strcmp (*s1, *s2) > 0) { t = *s1; *s1 = *s2; *s2 = t; }
}

int main (int argCount, char *argVar[]) {
    char *a = "77";
    char *b = "55";
    char *c = "99";
    char *d = "88";
    char *e = "66";

    printf ("Unsorted: [%s] [%s] [%s] [%s] [%s]\n", a, b, c, d, e);
    sort (&a,&b,&c,&d,&e);
    printf ("  Sorted: [%s] [%s] [%s] [%s] [%s]\n", a, b, c, d, e);
    return 0;
}

生成的结果为:

Unsorted: [77] [55] [99] [88] [66]
  Sorted: [55] [66] [77] [88] [99]

不用在实现排序上花费过多注意力,只需要注意变量被作为char **传递,以便于能够轻松地交换它们。任何真正的排序都可能是在一个真正的数据数组上进行操作而非单个变量,但这并不是本例的重点。


你能详细说明一下如何在代码中使用它吗?我仍然不明白如何使用它。 - John
在appendToStr()中,您可以使用realloc()代替malloc()+strcpy()+free()。另外,没有理由对冒泡排序5个元素感到羞耻:冒泡排序/插入排序/选择排序绝对是非常小的数据集最快的算法——大多数高阶排序函数在分治过程中都会切换到这些算法作为基本情况。 - Adam Rosenfield
@Adam,malloc/copy/free 是为了确保指针改变而采用的一种折衷方法,否则我的调试输出可能不够明显 :-) 而且我并不是真的对冒泡排序感到羞耻,我实际上使用它(稍微优化和双向修改)来处理大多数已排序数据集(例如,我用于业务的 OOo 电子表格,其中按日期排序的交易添加了少量新交易,但它们可能是无序的),在这种情况下,它比许多其他算法都要好。 - paxdiablo
但是,大多数已排序的数据集、非常小的数据集和家庭作业可能是我使用它的唯一三个场合。 - paxdiablo
感谢您的精彩解释。空间不足是我们需要更改指针的唯一原因吗?此外,什么定义了“当前分配的内存”?所有对象的初始分配内存大小都相同还是根据类型或其他因素而变化? - mfaani
为什么可变对象(例如NSMutableDataNSMutableArray),它们也可能会扩展并耗尽空间,不使用 呢? - mfaani

3

指向指针

"指针"的定义是它是一种特殊的变量,用于存储另一个变量的地址(而不是值)。那个变量很可能也是一个指针。这意味着指针可以合法地指向另一个指针。

假设我们有一个指向另一个指向字符 c 的指针p2的指针p1。在内存中,三个变量可以被视为 :

enter image description here

因此,我们可以看到,在内存中,指针p1保存了指针 p2的地址。指针p2保存了字符 c 的地址。

因此,p2是指向字符 c 的指针,而 p1 是指向p2的指针。或者我们还可以说,p2是指向指向字符 c 的指针。

现在,在代码中,p2可以声明为:

char *p2 = &c;

但是p1声明为:

char **p1 = &p2;

因此,我们可以看到,p1是双重指针(即指向字符的指针的指针),因此在声明中有两个 *。

现在,

  • p1p2的地址,即5000
  • *p1p2保存的值,即8000
  • **p1是8000处的值,即 c

我认为这应该清楚了这个概念,让我们举一个小例子:

来源:http://www.thegeekstuff.com/2012/01/advanced-c-pointers/

一些用例:

通常用于传递指向函数的指针,该函数必须能够更改指针本身,其中一些用例包括:

  • 例如处理错误,它允许接收方法控制指针引用的内容。参见此问题
  • 用于创建不透明结构体,即其他人无法分配空间。参见此问题
  • 在其他答案中提到的内存扩展情况。

随意编辑/改进此回答,因为我正在学习:]


1

(参考资料:更多iOS 6开发)

在Objective-C方法中,包括对象指针在内的参数都是按值传递的,这意味着被调用的方法会得到自己的指针副本。因此,如果被调用的方法想要改变指针本身而不是指针所指向的数据,则需要另一级间接性,即指向指针的指针。


1
一个指向指针的指针。

1
在C语言中,指针和数组可以被视为同一类型,例如char*是一个字符串(字符数组)。如果你想传递一个数组的数组(例如多个字符串)到一个函数中,你可以使用char**。

有一个很重要的区别:sizeof(char [])!= sizeof(char *):很多人都因此受过伤。 - paxdiablo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接