让我们来谈谈表达式 and types因为它们与 C 中的数组相关。
Arrays
当你声明一个数组时
char line[256];
the 表达 line
具有类型“256 元素数组char
"; 除非该表达式是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
, and &line[0]
全部产生相同的结果value(数组第一个元素的地址与数组本身的地址相同),只是类型不同。在表达式中&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
会给出下一个的地址char
下列的line
, while linearrp + 1
会给出下一个的地址256 个元素的数组char
下列的line
.
表达方式line
不是一个可修改的左值;你不能分配给它,所以像
char temp[256];
...
line = temp;
将是非法的。没有为变量预留存储空间line
从.....分离line[0]
通过line[256]
;没有什么可分配的to.
因此,当您将数组表达式传递给函数时,函数接收到的是指针值,而不是数组。在函数参数声明的上下文中,T a[N]
and 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 假设您在分配数组时知道该数组有多大,因此您可以自己跟踪该信息。
Pointers
当你声明一个pointer like
char *line;
表达方式line
具有类型“指向char
“(废话)。留出足够的存储空间来存储a的地址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
and &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 );
When do you use the pointer character '*', and when don't you? In the example above, is including the '*' in fgets necessary, or correct?
这是不正确的;fgets
期望第一个参数的类型为“指向char
"; the 表达 *line
有类型char
。声明如下:
char *line;
第二,sizeof(line)
只给你的大小pointer,不是指针所指的大小to;除非你想准确地阅读sizeof (char *)
字节,您必须使用不同的表达式来指定要读取的字符数:
fgets( line, 256, stdin );
Now, I would like to create an array of strings, or rather, an array of pointers which point to strings. Would I do so as follows?
char *arr[20]; // Declares an array of strings with 20 elements
C 不像 C++ 或 Java 那样有单独的“字符串”数据类型;在C中,一个string只是一个以 0 结尾的字符值序列。它们是stored作为数组char
。请注意,上面声明的只是一个包含 20 个元素的指针数组char
;这些指针可以指向不是字符串的东西。
如果所有字符串都具有相同的最大长度,则可以声明一个二维数组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
)或不取决于你。
Now even worse, I would like an array of arrays of strings (for example, if I wanted to hold multiple argument vectors, in order to execute multiple commands in pipe sequence). Would it be declared as follows?
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
是指向 M 元素数组的指针的 N 元素数组char
.