我正在以下位置学习教程:http://www.learn-c.org/en/Arrays_and_Pointers
在代码中,他们有:
// Allocate memory for nrows pointers
char **pvowels = (char **) malloc(nrows * sizeof(char));
// For each row, allocate memory for ncols elements
pvowels[0] = (char *) malloc(ncols * sizeof(char));
pvowels[1] = (char *) malloc(ncols * sizeof(char));
但是如果我删除(字符*)和(字符**),它仍然有效:
// Allocate memory for nrows pointers
char **pvowels = malloc(nrows * sizeof(char));
// For each row, allocate memory for ncols elements
pvowels[0] = malloc(ncols * sizeof(char));
pvowels[1] = malloc(ncols * sizeof(char));
本教程没有解释这是什么,我假设它是某种类型转换,也许对于此示例,实际类型强制转换它并不重要,而是作者认为显式类型转换很重要。
我也不熟悉双"*"是什么(**)。
只是想确保我没有遗漏任何东西,并感谢您的任何解释。
源代码中的 malloc 函数正在为您的目的分配特定数据类型所需的内存量(char 变量),返回值过去必须进行类型转换,但现在不再,它仍然是一种典型的预防措施。
char *
是指向 char 类型的变量的指针。char **
是指向 char 类型变量的指针的指针。
我不知道已经向您解释了多少关于指针的信息,但是您可以将char指针视为char变量列表中引用的第一个char。这就是字符串在内存中的设置方式,字符串只是 char 变量的集合。
如果更深入,则可以使用字符指针指针,该指针指向字符串列表中引用的第一个字符串。这就是作者在这里所做的,他正在分配 char 指针数组所需的内存,并且由于该数组中的每个 char 指针本身都指向一组 char 值,因此它们也必须为每个字符值分配内存。
编辑:更具体地说,这些事物的"列表"称为数组。可以将 char 数组视为字符串,将 char 数组数组视为句子。
在 C 语言的原始 K&R 定义(1989 年之前)中,*alloc
函数返回char *
。 由于该语言不允许(并且仍然不允许)在不兼容的指针类型之间进行赋值,因此如果未char *
该类型,则必须将结果显式转换为目标类型:
int *p;
...
p = (int *) malloc( n * sizeof (int) );
1989 年的标准引入了void *
类型,它是上述分配规则的例外,并用作"通用"指针类型 - 可以将值void *
分配给任何指针类型而无需显式强制转换,因此从 C89 标准开始,
p = malloc( n * sizeof (int) );
会工作。
请注意,这在C++中不是真的 - 在该语言中,必须将void *
值转换为目标类型,因此C++您仍然必须编写
p = (int *) malloc( n * sizeof (int) );
但是如果你写的是C++,你无论如何都不应该使用malloc
。
在我们大多数人中,投下malloc
的结果被认为是错误的。 在该语言的 C89 版本下,您实际上可以通过以下方式引入错误 这样做。 在该版本下,如果编译器看到一个函数调用,而函数调用在作用域中没有声明,它将假定该函数返回int
。 所以,如果你不知何故忘记包括stdlib.h
并写
int *p = (int *) malloc( n * sizeof (int) );
编译器将假定malloc
返回了一个int
。 如果没有强制转换,这将导致"分配中的类型不兼容"诊断;但是,包括强制转换会抑制诊断,并且您不会意识到存在问题,直到您遇到(有时非常微妙且难以诊断)运行时问题。
情况不再如此 - 该语言的 C99 修订版取消了隐式int
声明,因此如果您忘记包含stdlib.h
,您将获得诊断。
但是,我们中的许多人认为强制转换增加了不必要的维护负担(如果您更改目标指针的类型,您也必须更新转换),并且 IMO 它使代码更难阅读。
为什么这种做法在C中仍然存在? 几个原因:
- 作者在 1970 年代末/1980 年代初开始编写 C 代码,但从未摆脱使用显式强制转换的习惯;
- 作者从过时的参考文献或阅读非常旧的代码中学习;
- 作者希望明确记录所涉及的类型。
就个人而言,我认为写*alloc
电话的正确方法是
T *p = malloc( n * sizeof *p );
T *p = calloc( n, sizeof *p );
T *tmp = realloc( p, n * sizeof *p );
干净,如果你改变了p
的类型,你不需要改变任何关于*alloc
调用本身的信息。
编辑
我也不熟悉双"*"是什么(**)。
在 C 中可以有多个间接寻址 - 您可以有指向指针的指针、指向指针的指针等。 例如,您的pvowels
对象指向指向char
的指针序列中的第一个:
char ** char * char
------- ------- ----
+---+ +---+ +---+
pvowels: | | -----> pvowels[0]: | | ------> pvowels[0][0]: | |
+---+ +---+ +---+
pvowels[1]: | | ----+ pvowels[0][1]: | |
+---+ | +---+
... | ...
+---+ | +---+
pvowels[r-1]: | | --+ | pvowels[0][c-1]: | |
+---+ | | +---+
| |
| | +---+
| +-> pvowels[1][0]: | |
| +---+
| pvowels[1][1]: | |
| +---+
| ...
| +---+
| pvowels[1][c-1]: | |
| +---+
...
由于pvowels
指向指向char
的指针序列中的第一个,其类型必须是"指向char
的指针",或char **
。
当你为一系列T
的对象*alloc
内存时,目标指针必须具有T *
类型:
T *p = malloc( n * sizeof *p ); // allocating a sequence of T
在上面的语句中,表达式*p
的类型为T
,因此sizeof *p
等价于sizeof (T)
。
如果我们将T
替换为指针类型P *
,它看起来像
P **p = malloc( n * sizeof *p ); // allocating a sequence of P *
我们不是为P
序列分配空间,而是为指向P
的指针序列分配空间。 由于p
有类型P **
,表达式*p
有类型P *
,所以sizeof *p
和sizeof (P *)
相同。
请注意,在上面的pvowels
情况下,您拥有的不是2Dchar
数组;pvowels
指向指针序列中的第一个,每个指针都指向char
序列中的第一个。 您可以像 2D 数组一样为它编制索引,但它的结构不像 2D 数组;"行"在内存中不相邻。
malloc
返回一个void *
指针,在较新版本的 C 中,可以将其分配给任何指针类型,而无需显式强制转换。情况并非总是如此,而且是经常被留下的历史文物。
pvowel 的类型为 char **(指向字符指针(char* )的指针)。 因此,Malloc(返回 void*)必须转换为 PVOWEL 的"类型"。
如果在 sizeof 函数中删除 char*,它的工作原理就是您想要的(malloc 保留内存 nrows 的大小为 char 类型),这可能会保存 char 指针的地址。
如果您删除强制转换字符**,我的编译器会抛出错误,正如我所期望的那样(在 CPP 中编译)。在c中,它工作得很好。