为啥我需要使用 type** 来指向 type*?
Posted
技术标签:
【中文标题】为啥我需要使用 type** 来指向 type*?【英文标题】:Why do I need to use type** to point to type*?为什么我需要使用 type** 来指向 type*? 【发布时间】:2015-07-18 23:25:29 【问题描述】:这几天我一直在阅读 Learn C The Hard Way,但这是我想真正理解的内容。作者 Zed 写道,char **
用于“指向(指向 char 的指针)的指针”,并说这是必要的,因为我试图指向二维的东西。
这是网页上写的确切内容
一个 char * 已经是一个“指向 char 的指针”,所以它只是一个字符串。但是,您需要 2 个级别,因为名称是二维的,这意味着您需要 char ** 作为“指向(char 的指针)”类型的指针。
这是否意味着我必须使用一个可以指向二维的变量,这就是为什么我需要两个**
?
稍微跟进一下,这是否也适用于n维?
这是相关代码
char *names[] = "Alan", "Frank", "Mary", "John", "Lisa" ;
char **cur_name = names;
【问题讨论】:
仅供参考,char *names[] = /* five string literals */ ;
不是二维数组。它是一个char
指针数组。
如果你想要一个指向some_type
类型变量的指针,你需要一个some_type *
。 some_type
是 int
、char *
还是 char ***********
无关紧要。如果您需要指向它的指针,请添加另一个 *
。这只是语法。
如果T
是任何类型,那么T *
指向T
。这就是类型系统的定义方式。
厌倦了所有垃圾教程和书籍。我发誓我花在不教错误练习上的时间比教正确练习上的时间要多。
@solitude names
在您的示例中是一个指针数组。它不是数组数组,cur_name
也不是。此外,如果您尝试修改“数组”中的一项,代码将崩溃并烧毁,因为以未定义的行为写入字符串文字。一个好的教程会教你将其声明为const char* names[]
。没有理由不这样做。
【参考方案1】:
不,该教程质量有问题。不建议继续阅读。
char**
是一个指向指针的指针。它不是二维数组。
它不是指向数组的指针。
它不是指向二维数组的指针。
本教程的作者可能会感到困惑,因为有一种普遍存在的错误做法,即您应该像这样分配动态 2D 数组:
// BAD! Do not do like this!
int** heap_fiasco;
heap_fiasco = malloc(X * sizeof(int*));
for(int x=0; x<X; x++)
heap_fiasco[x] = malloc(Y * sizeof(int));
然而,这不是一个二维数组,它是一个缓慢的、碎片化的查找表,分配在整个堆中。访问查找表中一项的语法heap_fiasco[x][y]
看起来就像数组索引语法,因此很多人出于某种原因认为这是您分配二维数组的方式。
动态分配二维数组的正确方法是:
// correct
int (*array2d)[Y] = malloc(sizeof(int[X][Y]));
您可以看出第一个不是数组,因为如果您这样做memcpy(heap_fiasco, heap_fiasco2, sizeof(int[X][Y]))
,代码将崩溃并烧毁。这些项目没有分配在相邻的内存中。
同样memcpy(heap_fiasco, heap_fiasco2, sizeof(*heap_fiasco))
也会崩溃和烧毁,但出于其他原因:你得到的是指针的大小而不是数组。
虽然memcpy(array2d, array2d_2, sizeof(*array2d))
可以工作,因为它是一个二维数组。
【讨论】:
@Zaibis 当您想要复制整个“数组”时,memcpy()
的行为将不符合预期,因为sizeof *heap_fiasco
将返回整数指针的大小,因为*heap_fiasco
实际上并不是数组类型。我相信声明 (*array2d)[Y]...
是从 6.7.5.2 开始的。
@Lundin 将二维数组作为参数传递给函数的正确方法是什么?在您的示例中,函数原型将是什么 Y 需要事先知道?您能否扩展您的答案以包括带有函数参数的二维数组?【参考方案2】:
指针花了我一段时间才理解。我强烈推荐绘制图表。
请阅读并理解this part of the C++ tutorial(至少就指针而言,这些图表确实对我有帮助)。
告诉你你需要一个指向二维数组的 char 指针的指针是谎言。您不需要它,但这是一种方法。
内存是连续的。如果你想像 hello 一样将 5 个字符(字母)放在一行中,你可以定义 5 个变量并始终记住使用它们的顺序,但是当你想保存一个单词时会发生什么有6个字母?你定义了更多的变量吗?如果只是将它们按顺序存储在内存中会不会更容易?
所以你要做的就是向操作系统请求 5 个字符(每个字符恰好是一个字节),系统会返回给你一个内存地址,你的 5 个字符序列开始的地方。您获取此地址并将其存储在我们称为指针的变量中,因为它指向您的内存。
指针的问题在于它们只是地址。你怎么知道那个地址存储了什么?是 5 个字符还是 8 个字节的大二进制数?或者它是您加载的文件的一部分?你怎么知道的?
这就是像 C 这样的编程语言试图通过给你类型来帮助你的地方。类型告诉你变量存储的是什么,指针也有类型,但它们的类型告诉你指针指向什么。因此,char *
是一个指向内存位置的指针,该位置保存单个char
或chars
序列。可悲的是,您需要记住自己有多少char
s。通常,您将该信息存储在一个变量中,以提醒您那里有多少个字符。
那么当你想要一个二维数据结构时,你如何表示呢?
最好用一个例子来解释。让我们做一个矩阵:
1 2 3 4
5 6 7 8
9 10 11 12
它有 4 列和 3 行。我们如何存储它?
好吧,我们可以制作 3 个序列,每个序列有 4 个数字。第一个序列是1 2 3 4
,第二个是5 6 7 8
,第三个也是最后一个序列是9 10 11 12
。因此,如果我们要存储 4 个号码,我们将要求系统为我们保留 4 个号码并给我们一个指向它们的指针。这些将是指向数字的指针。然而,由于我们需要其中的 3 个,我们将要求系统给我们 3 个指向 pointers numbers 的指针。
这就是你最终得到建议的解决方案的方式......
另一种方法是意识到您需要 4 乘以 3 的数字,然后要求系统将 12 个数字按顺序存储。但是,您如何访问第 2 行和第 3 列中的数字?这就是数学的用武之地,但让我们在我们的示例中尝试一下:
1 2 3 4
5 6 7 8
9 10 11 12
如果我们将它们彼此相邻存储,它们将如下所示:
offset from start: 0 1 2 3 4 5 6 7 8 9 10 11
numbers in memory: [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]
所以我们的映射是这样的:
row | column | offset | value
1 | 1 | 0 | 1
1 | 2 | 1 | 2
1 | 3 | 2 | 3
1 | 4 | 3 | 4
2 | 1 | 4 | 5
2 | 2 | 5 | 6
2 | 3 | 6 | 7
2 | 4 | 7 | 8
3 | 1 | 8 | 9
3 | 2 | 9 | 10
3 | 3 | 10 | 11
3 | 4 | 11 | 12
我们现在需要制定一个简单易用的公式来将行和列转换为偏移量...当我有更多时间时,我会回到它...现在我需要回家(对不起)...
编辑:我有点晚了,但让我继续。要从行和列中找到每个数字的偏移量,您可以使用以下公式:
offset = (row - 1) * 4 + (column - 1)
如果你注意到这里的两个-1
并考虑一下,你就会明白这是因为我们的行和列编号以 1 开头,我们必须这样做,这就是为什么计算机科学家更喜欢从零开始的原因偏移量(因为这个公式)。但是,使用 C 语言中的指针,当您使用多维数组时,语言本身会为您应用此公式。因此,这是另一种方式。
【讨论】:
【参考方案3】:从您的问题中,我了解到您在问为什么需要 char ** 作为声明为 *names[] 的变量。所以答案是当你简单地写names[]时,它是数组的语法,而数组基本上是一个指针。
所以当你写 *names[] 时,这意味着你指向一个数组。由于数组基本上是一个指针,所以这意味着您有一个指向指针的指针,这就是为什么如果您编写编译器不会抱怨
char ** cur_name = 名称;
在上面一行中,你声明了一个指向字符指针的指针,然后用指向数组的指针初始化它(记住数组也是指针)。
【讨论】:
以上是关于为啥我需要使用 type** 来指向 type*?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 getConstructor 反射 API 需要 int.class 参数?
为啥 Oracle odbc 驱动程序没有为整数和数字类型提供 TYPE_NAME?
IOS CoreData,使用哪个 NSManagedObjectContextConcurrencyType,为啥?
为啥 ostream::write() 在 C++ 中需要“const char_type*”而不是“const void*”?