让我们谈谈与 C 中的数组相关的 表达式 和 类型。
数组
当你声明一个数组时
char line[256];
表达式 line 的类型为“char 的 256 元素数组”;除非该表达式是sizeof 或一元& 运算符的操作数,否则它将被转换(“衰减”)为“指向char”的类型的表达式,表达式的值将是数组的第一个元素的地址。鉴于上述声明,以下所有内容都是正确的:
Expression Type Decays to Equivalent value
---------- ---- --------- ----------------
line char [256] char * &line[0]
&line char (*)[256] n/a &line[0]
*line char n/a line[0]
line[i] char n/a n/a
&line[0] char * n/a n/a
sizeof line size_t n/a Total number of bytes
in array (256)
注意表达式line、&line和&line[0]都产生相同的值(数组的第一个元素的地址与数组的地址相同本身),只是类型不同。在表达式&line中,数组表达式是&运算符的操作数,所以上面的转换规则不适用;我们得到一个指向 char 的 256 元素数组的指针,而不是指向 char 的指针。类型很重要;如果你写如下内容:
char line[256];
char *linep = line;
char (*linearrp)[256] = &line;
printf( "linep + 1 = %p\n", (void *) (linep + 1) );
printf( "linearrp + 1 = %p\n", (void *) (linearrp + 1) );
每行都会得到不同的输出; linep + 1 将给出line 之后的下一个char 的地址,而linearrp + 1 将给出line 之后的下一个256 元素数组char 的地址。
表达式line 不是可修改的左值;你不能分配给它,所以像
char temp[256];
...
line = temp;
将是非法的。没有为变量line 与line[0] 到line[256] 分开留出存储空间;没有什么可以分配给 。
因此,当您将数组表达式传递给函数时,函数接收的是指针值,而不是数组。在函数参数声明的上下文中,T a[N] 和T a[] 被解释为T *a;这三个都将a 声明为指向T 的指针。参数的“数组”在调用过程中已经丢失。
所有的数组访问都是通过指针运算完成的;表达式a[i] 被评估为*(a + i)。数组表达式a 首先按照上述规则转换为指针类型的表达式,然后我们从该地址偏移i elements 并取消引用结果。
与 Java 不同,C 不会为指向数组的指针留出与数组元素本身分开的存储空间:所有留出的内容如下:
+---+
| | line[0]
+---+
| | line[1]
+---+
...
+---+
| | line[255]
+---+
C 也不为堆中的数组分配内存(对于任何堆定义)。如果数组声明为auto(即,块的本地且没有static 关键字),则内存将从实现为局部变量获取内存的任何地方分配(我们大多数人称之为堆栈)。如果数组在文件范围内声明或使用static 关键字声明,则内存将从不同的内存段分配,并将在程序启动时分配并保持到程序终止。
与 Java 不同的是,C 数组不包含有关其长度的元数据; C 假设您在分配数组时知道数组有多大,因此您可以自己跟踪该信息。
指针
当你声明一个指针就像
char *line;
表达式line 的类型为“指向char 的指针”(废话)。留出足够的存储空间来存储char 对象的地址。除非您在文件范围内或使用 static 关键字声明它,否则它不会被初始化并且将包含一些可能对应于或不对应于有效地址的随机位模式。鉴于上述声明,以下所有内容都是正确的:
Expression Type Decays to Equivalent value
---------- ---- --------- ----------------
line char * n/a n/a
&line char ** n/a n/a
*line char n/a line[0]
line[i] char n/a n/a
&line[0] char * n/a n/a
sizeof line size_t n/a Total number of bytes
in a char pointer
(anywhere from 2 to
8 depending on the
platform)
在这种情况下,line 和 &line 确实给了我们不同的值,以及不同的类型; line 是一个简单的标量对象,所以&line 为我们提供了该对象的地址。同样,数组访问是根据指针算术完成的,因此line[i] 的工作方式相同,无论 line 是声明为数组还是指针。
所以当你写的时候
char *line = malloc( sizeof *line * 256 ); // note no cast, sizeof expression
这是像 Java 一样工作的情况;你有一个单独的指针变量,它引用从堆中分配的存储,如下所示:
+---+
| | line -------+
+---+ |
... |
+---+ |
| | line[0] <---+
+---+
| | line[1]
+---+
...
+---+
| | line[255]
+---+
与 Java 不同,当不再有对它的引用时,C 不会自动回收该内存。完成后,您必须使用 free 库函数显式释放它:
free( line );
至于你的具体问题:
fgets( *line, sizeof(line), stdin );
你什么时候使用指针字符'*',什么时候不使用?在上面的示例中,是否需要在 fgets 中包含“*”,还是正确?
这是不正确的; fgets 期望第一个参数的类型为“指向char 的指针”; 表达式 *line 的类型为char。这来自声明:
char *line;
其次,sizeof(line) 只给你指针的大小,而不是指针指向的大小;除非您想准确读取 sizeof (char *) 字节,否则您必须使用不同的表达式来指定要读取的字符数:
fgets( line, 256, stdin );
现在,我想创建一个字符串数组,或者更确切地说,一个指向字符串的指针数组。我会这样做吗?
char *arr[20]; // Declares an array of strings with 20 elements
C 不像 C++ 或 Java 那样具有单独的“字符串”数据类型;在 C 中,string 只是一个以 0 结尾的字符值序列。它们存储为char 的数组。请注意,您在上面声明的只是一个指向char 的20 元素指针数组;这些指针可以指向不是字符串的东西。
如果您的所有字符串都将具有相同的最大长度,您可以声明一个char 的二维数组,如下所示:
char arr[NUM_STRINGS][MAX_STRING_LENGTH + 1]; // +1 for 0 terminator
然后你会将每个字符串分配为
strcpy( arr[i], "some string" );
strcpy( arr[j], some_other_variable );
strncpy( arr[k], MAX_STRING_LENGTH, another_string_variable );
虽然要小心strncpy;如果源字符串比目标字符串长,它不会自动将 0 终止符附加到目标字符串。在尝试将它与字符串库的其余部分一起使用之前,您必须确保存在终止符。
如果要为每个字符串分别分配空间,可以声明指针数组,然后分配每个指针:
char *arr[NUM_STRINGS];
...
arr[i] = malloc( strlen("some string") + 1 );
strcpy( arr[i], "some string" );
...
arr[j] = strdup( "some string" ); // not available in all implementations, calls
// malloc under the hood
...
arr[k] = "some string"; // arr[k] contains the address of the *string literal*
// "some string"; note that you may not modify the contents
// of a string literal (the behavior is undefined), so
// arr[k] should not be used as an argument to any function
// that tries to modify the input parameter.
注意arr的每个元素都是一个指针值;这些指针是否指向 strings(以 0 结尾的 char 序列)取决于您。
现在更糟糕的是,我想要一个字符串数组(例如,如果我想保存多个参数向量,以便按管道序列执行多个命令)。会这样声明吗?
char **vector_arr[20]; // An array of arrays of strings
你声明的是一个指向 char 指针的指针数组;请注意,如果您不知道需要在每个元素中存储多少指向 char 的指针,这是完全有效的。但是,如果您知道每个元素的最大参数数,那么写起来可能会更清楚
char *vector_arr[20][N];
否则,您必须动态分配char * 的每个数组:
char **vector_arr[20] = { NULL }; // initialize all the pointers to NULL
for ( i = 0; i < 20; i++ )
{
// the type of the expression vector_arr is 20-element array of char **, so
// the type of the expression vector_arr[i] is char **, so
// the type of the expression *vector_arr[i] is char *, so
// the type of the expression vector[i][j] is char *, so
// the type of the expression *vector_arr[i][j] is char
vector_arr[i] = malloc( sizeof *vector_arr[i] * num_args_for_this_element );
if ( vector_arr[i] )
{
for ( j = 0; j < num_args_for_this_element )
{
vector_arr[i][j] = malloc( sizeof *vector_arr[i][j] * (size_of_this_element + 1) );
// assign the argument
strcpy( vector_arr[i][j], argument_for_this_element );
}
}
}
因此,vector_arr 的每个元素都是一个指向 char 的 M 元素数组的指针的 N 元素数组。