案例1:当我写作时
char*str={"what","is","this"};
thenstr[i]="newstring";
有效,而str[i][j]='j';
无效。
案例2:当我写作时
char str[][5]={"what","is","this"};
thenstr[i]="newstring";
无效,而str[i][j]='J';
有效。
为什么会这样?我是一个初学者,在阅读其他答案后已经非常困惑。
案例1:当我写作时
char*str={"what","is","this"};
thenstr[i]="newstring";
有效,而str[i][j]='j';
无效。
案例2:当我写作时
char str[][5]={"what","is","this"};
thenstr[i]="newstring";
无效,而str[i][j]='J';
有效。
为什么会这样?我是一个初学者,在阅读其他答案后已经非常困惑。
首先:一个建议:请阅读数组不是指针,反之亦然!
也就是说,为了启发这种特殊情况,
在第一种情况下,
char*str={"what","is","this"};
不做你认为它做的事。这是违反约束的,需要任何符合 C 实现的诊断,根据章节§6.7.9/P2:
任何初始化程序都不应尝试为未包含在正在初始化的实体中的对象提供值。
如果你启用警告,你会(至少)看到
警告:标量初始化器中的多余元素
char*str={"what","is","this"};
但是,开启了严格一致性的(ny)编译器应该拒绝编译代码。如果编译器无论如何都选择编译和生成二进制文件,则行为不在 C 语言的定义范围内,这取决于编译器的实现(因此,可能会有很大差异)。
在这种情况下,编译器决定此语句仅在功能上与char*str= "what";
所以,这里str
有一个指向 a 的指针char
,它指向一个字符串字面量。您可以重新分配给指针,
str="newstring"; //this is valid
但是,像这样的声明
str[i]="newstring";
将是无效的,因为这里尝试将指针类型转换并存储为char
类型不兼容的类型。在这种情况下,编译器应该发出有关无效转换的警告。
之后,像这样的声明
str[i][j]='J'; // compiler error
在语法上无效,因为您在[]
不是“指向完整对象类型的指针”的东西上使用数组下标运算符,例如
str[i][j] = ...
^^^------------------- cannot use this
^^^^^^ --------------------- str[i] is of type 'char',
not a pointer to be used as the operand for [] operator.
另一方面,在第二种情况下,
str
是一个数组数组。您可以更改单个数组元素,
str[i][j]='J'; // change individual element, good to go.
但您不能分配给数组。
str[i]="newstring"; // nopes, array type is not an lvalue!!
最后, 考虑到你打算写(如评论中所见)
char* str[ ] ={"what","is","this"};
在您的第一种情况下,数组的相同逻辑成立。这会产生str
一个指针数组。所以,数组成员,是可分配的,所以,
str[i]="newstring"; // just overwrites the previous pointer
完全可以。但是,存储为数组成员的指针是指向字符串文字的指针,因此出于上述相同的原因,当您想要修改属于字符串文字的内存元素之一时,您会调用未定义的行为
str[i][j]='j'; //still invalid, as above.
内存布局不同:
char* str[] = {"what", "is", "this"};
str
+--------+ +-----+
| pointer| ---> |what0|
+--------+ +-----+ +---+
| pointer| -------------> |is0|
+--------+ +---+ +-----+
| pointer| ----------------------> |this0|
+--------+ +-----+
在这个内存布局中,str
是一个指向各个字符串的指针数组。通常,这些单独的字符串将驻留在静态存储中,尝试修改它们是错误的。在图中,我曾经0
表示终止的空字节。
char str[][5] = {"what", "is", "this"};
str
+-----+
|what0|
+-----+
|is000|
+-----+
|this0|
+-----+
在这种情况下,str
是位于堆栈上的连续二维字符数组。当数组初始化时,字符串被复制到这个内存区域,并且各个字符串用零字节填充,以使数组具有规则的形状。
这两种内存布局从根本上是互不兼容的。您不能将其中任何一个传递给需要指向另一个指针的函数。但是,对单个字符串的访问是兼容的。当你写的时候str[1]
,你会得到一个char*
包含字节的内存区域的第一个字符is0
,即一个C字符串。
在第一种情况下,很明显这个指针只是从内存中加载的。在第二种情况下,指针是通过 array-pointer-decay 创建的:str[1]
实际上表示正好是五个字节的数组 ( is000
),在几乎所有上下文中,它都会立即衰减为指向其第一个元素的指针。但是,我相信对数组指针衰减的完整解释超出了这个答案的范围。如果你好奇的话,谷歌数组指针衰减。
第一个定义一个变量,它是一个指向 a 的指针char
,它通常只用作一个字符串。它将指针初始化为指向字符串字面量"what"
。编译器还应该抱怨列表中有太多的初始化程序。
第二个定义创建str
了一个由三个数组组成的数组,每组 5 个char
。也就是说,它是一个由三个五个字符组成的字符串的数组。
稍有不同,可以看到如下内容:
对于第一种情况:
+-----+ +--------+ | 字符串 | --> | “什么” | +-----+ +--------+
而对于第二个你有
+--------+--------+--------+ | “什么” | “是” | “这个” | +--------+--------+--------+
另请注意,对于第一个版本,使用指向单个字符串的指针,表达式str[i] = "newstring"
也应该导致警告,因为您尝试将指针分配给单个char
元素 str[i]
。
该分配在第二个版本中也是无效的,但出于另一个原因:str[i]
是一个数组(包含五个char
元素),您不能分配给数组,只能复制到它。所以你可以尝试这样做strcpy(str[i], "newstring")
,编译器不会抱怨。但是这是错误的,因为您尝试将 10 个字符(记住终止符)复制到 5 个字符的数组中,这将导致写入越界导致未定义的行为。
在第一个声明中
char *str={"what","is","this"};
声明str
一个指向 a 的指针char
并且是一个标量。标准说
6.7.9 初始化(p11):
标量的初始值设定项应为单个表达式,可选择用大括号括起来。[...]
也就是说,标量类型可以有括号封闭的初始化程序,但只有一个表达式,但在
char *str = {"what","is","this"}; // three expressions in brace enclosed initializer
如何处理这个问题取决于编译器。请注意,其余初始化程序发生的事情是一个错误。确认编译器应给出诊断信息。
[Warning] excess elements in scalar initializer
5.1.1.3 诊断(P1):
如果预处理翻译单元或翻译单元包含违反任何语法规则或约束的行为,则符合要求的实现应产生至少一个诊断消息(以实现定义的方式标识),即使该行为也明确指定为未定义或实现-定义
您声称“str[i]="newstring";
有效而str[i][j]='j';
无效。 ”
str[i]
是char
类型的,并且只能保存一个char
数据类型。分配"newstring"
(属于char *
)无效。该语句str[i][j]='j';
无效,因为下标运算符只能应用于数组或指针数据类型。
您可以str[i]="newstring";
通过声明str
为数组来进行工作char *
char *str[] = {"what","is","this"};
在这种情况下str[i]
是char *
类型 并且可以为其分配字符串文字,但修改字符串文字str[i]
指向 将调用未定义的行为。那就是说你做不到str[0][0] = 'W'
。
片段
char str[][5]={"what","is","this"};
声明str
为 s 的数组char
。str[i]
实际上是一个数组,因为数组是不可修改的左值,所以你不能将它们用作赋值运算符的左操作数。这使得str[i]="newstring";
无效。Whilestr[i][j]='J';
有效,因为可以修改数组的元素。
仅仅因为你说其他答案让我感到困惑,让我们先看看一个更简单的例子发生了什么
char *ptr = "somestring";
这"somestring"
是一个字符串文字,它存储在内存的只读数据部分中。ptr
是一个指针(就像同一段代码中的其他变量一样分配),它指向分配的内存的第一个字节。
因此考虑这两个陈述
char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a'; //statement 2 error
语句 1 正在执行完全有效的操作(将 1 个指针分配给另一个),但语句 2 不是有效操作(尝试写入只读位置)。
另一方面,如果我们写:
char ptr[] = "somestring";
这里 ptr 实际上不是一个指针,而是一个数组的名称(与指针不同,它不占用内存中的额外空间)。"somestring"
它分配与(非只读)所需的相同数量的字节,仅此而已。
因此考虑相同的两个语句和一个额外的语句
char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a'; //statement 2 OK
ptr = "someotherstring" //statement 3 error
语句 1 正在执行完全有效的操作(将数组名分配给指针,数组名返回第一个字节的地址),语句 2 也有效,因为内存不是只读的。
语句 3 不是有效操作,因为这里 ptr 不是指针,它不能指向其他内存位置。
现在在这段代码中,
char **str={"what","is","this"};
*str
是一个指针(str[i]
与 相同*(str+i)
)
但在这段代码中
char str[][] = {"what", "is", "this"};
str[i]
不是指针。它是一个数组的名称。
与上述相同的事情如下。
要消除混淆,您必须正确理解指针、数组和初始化程序。C 编程初学者的一个常见误解是数组等同于指针。
数组是相同类型的项目的集合。考虑以下声明:
char arr[10];
该数组包含 10 个元素,每个元素类型为char
。
初始化列表可用于以方便的方式初始化数组。下面使用初始化列表的相应值初始化数组元素:
char array[10] = {'a','b','c','d','e','f','g','h','i','\0'};
数组不可赋值,因此初始化列表的使用仅在数组声明时有效。
char array[10];
array = {'a','b','c','d','e','f','g','h','i','\0'}; // Invalid...
char array1[10];
char array2[10] = {'a','b','c','d','e','f','g','h','i','\0'};
array1 = array2; // Invalid...; You cannot copy array2 to array1 in this manner.
在声明数组之后,必须通过数组索引运算符或其等效项对数组成员进行赋值。
char array[10];
array[0] = 'a';
array[1] = 'b';
.
.
.
array[9] = 'i';
array[10] = '\0';
循环是为数组成员赋值的一种常用且方便的方法:
char array[10];
int index = 0;
for(char val = 'a'; val <= 'i'; val++) {
array[index] = val;
index++;
}
array[index] = '\0';
char
数组可以通过字符串字面量来初始化,这些字面量是常量以空结尾的char
数组:
char array[10] = "abcdefghi";
但是,以下内容无效:
char array[10];
array = "abcdefghi"; // As mentioned before, arrays are not assignable
现在,让我们来看看指针......指针是可以存储另一个变量的地址的变量,通常是相同类型的。
考虑以下声明:
char *ptr;
这声明了一个类型的变量char *
,一个char
指针。也就是可能指向char
变量的指针。
与数组不同,指针是可赋值的。因此以下是有效的:
char var;
char *ptr;
ptr = &var; // Perfectly Valid...
由于指针不是数组,因此只能为指针分配一个值。
char var;
char *ptr = &var; // The address of the variable `var` is stored as a value of the pointer `ptr`
回想一下,必须为指针分配一个值,因此以下内容无效,因为初始值设定项的数量不止一个:
char *ptr = {'a','b','c','d','\0'};
这是违反约束的,但您的编译器可能只是分配'a'
并ptr
忽略其余部分。但即便如此,编译器也会警告您,因为诸如默认类型的字符文字,'a'
并且与 is 的类型不int
兼容。ptr
char *
如果这个指针在运行时被取消引用,那么它将导致访问无效内存的运行时错误,从而导致程序崩溃。
在您的示例中:
char *str = {"what", "is", "this"};
同样,这是违反约束的,但是您的编译器可能会将字符串分配what
给str
并忽略其余部分,并仅显示警告:
warning: excess elements in scalar initializer
.
现在,这是我们消除关于指针和数组的混淆的方法:在某些情况下,数组可能会衰减为指向数组第一个元素的指针。因此以下是有效的:
char arr[10];
char *ptr = arr;
通过在赋值表达式中使用数组名arr
作为rvalue
,数组衰减为指向它的第一个元素的指针,这使得前面的表达式等价于:
char *ptr = &arr[0];
请记住,它arr[0]
是类型char
,并且&arr[0]
是它的地址类型char *
,它与变量兼容ptr
。
回想一下,字符串文字是常量以 null 结尾的char
数组,因此以下表达式也是有效的:
char *ptr = "abcdefghi"; // the array "abcdefghi" decays to a pointer to the first element 'a'
现在,在您的情况下,char str[][5] = {"what","is","this"};
是一个由 3 个数组组成的数组,每个数组包含 5 个元素。
由于数组不可赋值,str[i] = "newstring";
因此与数组一样无效str[i]
,但str[i][j] = 'j';
它是有效的,因为
str[i][j]
它本身不是数组,并且是可赋值的。
首先
char*str={"what","is","this"};
甚至不是有效的 C 代码1),所以讨论它不是很有意义。出于某种原因,gcc 编译器只发出警告就让这段代码通过。不要忽略编译器警告。使用 gcc 时,请确保始终使用-std=c11 -pedantic-errors -Wall -Wextra
.
gcc 在遇到这种非标准代码时似乎会做的,就是把它当作你写char*str={"what"};
的 . 这又与char*str="what";
. 这绝不是 C 语言所保证的。
str[i][j]
尝试将指针间接两次,即使它只有一级间接,因此会出现编译器错误。它和打字一样没有意义
int array [3] = {1,2,3}; int x = array[0][0];
.
至于 和 的区别char* str = ...
,char str[] = ...
见FAQ:char s[]和char *s有什么区别?.
关于这种char str[][5]={"what","is","this"};
情况,它会创建一个数组数组(二维数组)。最里面的维度设置为 5,最外面的维度由编译器根据程序员提供的初始化程序的数量自动设置。在这种情况下 3,所以代码等价于char[3][5]
。
str[i]
为您提供数组数组i
中的数组编号。您不能在 C 中分配给数组,因为这就是语言的设计方式。此外,无论如何对字符串这样做是不正确的,FAQ:如何正确分配新的字符串值?
1)这是违反 C11 6.7.9/2 的约束。另见 6.7.9/11。
当我写
char*str={"what","is","this"};
thenstr[i]="newstring";
有效,而str[i][j]='j';
无效。
第二部分
>> char*str={"what","is","this"};
在这个语句中,str
是一个指向char
类型的指针。编译时,您必须收到有关此语句的警告消息:
warning: excess elements in scalar initializer
char*str={"what","is","this"};
^
警告的原因是 - 您为标量提供了多个初始化程序。
[算术类型和指针类型统称为标量类型。]
str
是一个标量,来自C 标准#6.7.9p11:
标量的初始值设定项应为单个表达式,可选择用大括号括起来。..
此外,为标量提供多个初始化器是未定义的行为。
来自C 标准#J.2 未定义的行为:
标量的初始值设定项既不是单个表达式,也不是括在大括号中的单个表达式
由于根据标准它是未定义的行为,因此没有必要进一步讨论它。讨论第I.II 部分和第 I.III 部分假设 - char *str="somestring"
,只是为了更好地理解char *
类型。
似乎您想创建一个指向字符串的指针数组。在讨论了这两种情况之后,我在这篇文章的下面添加了一个关于字符串指针数组的简要说明。
第 I.II 部分
>> then str[i]="newstring"; is valid
不,这是无效的。
同样,由于转换不兼容,编译器必须在此语句上给出警告消息。
因为str
是指向char
类型的指针。因此,在[ ]所指向的对象之外str[i]
的地方是一个字符。i
str
str[i] --> *(str + i)
"newstring"
是字符串文字,字符串文字会衰减为指针,除非用于初始化类型的数组char *
并且在这里您尝试将其分配给char
类型。因此编译器将其报告为警告。
第 I.III 部分
>> whereas str[i][j]='j'; is invalid.
是的,这是无效的。
([]
下标运算符)可以与数组或指针操作数一起使用。
str[i]
是一个字符,str[i][j]
表示您[]
在char
无效的操作数上使用。因此编译器将其报告为错误。
当我写
char str[][5]={"what","is","this"};
thenstr[i]="newstring";
无效,而str[i][j]='J';
有效。
第二部分.I
>> char str[][5]={"what","is","this"};
这是绝对正确的。这里,str
是一个二维数组。根据初始化器的数量,编译器会自动设置第一维。在这种情况下,内存中的视图str[][5]
将是这样的:
str
+-+-+-+-+-+
str[0] |w|h|a|t|0|
+-+-+-+-+-+
str[1] |i|s|0|0|0|
+-+-+-+-+-+
str[2] |t|h|i|s|0|
+-+-+-+-+-+
根据初始化列表,将初始化 2D 数组的各个元素,并将其余元素设置为0
.
第二部分第二部分
>> then str[i]="newstring"; is not valid
是的,这是无效的。
str[i]
是一维数组。
根据 C 标准,数组不是可修改的左值。
来自C 标准#6.3.2.1p1:
左值是一个表达式(对象类型不是 void),它可能指定一个对象;64) 如果一个左值在计算时没有指定一个对象,则行为未定义。当一个对象被称为具有特定类型时,该类型由用于指定该对象的左值指定。可修改的左值是没有数组类型、没有不完整类型、没有 const 限定类型,并且如果它是结构或联合,则没有任何成员(包括递归地,任何成员或所有包含的聚合或联合的元素)具有 const 限定类型。
此外,数组名称转换为指向数组对象初始元素的指针,除非它是 sizeof 运算符、_Alignof 运算符或一元 & 运算符的操作数。
除非它是 sizeof 运算符、_Alignof 运算符或一元 & 运算符的操作数,或者是用于初始化数组的字符串文字,否则类型为 ''array of type'' 的表达式将转换为具有type ''pointer to type'' 指向数组对象的初始元素并且不是左值。
由于str
已经初始化并且当您将一些其他字符串文字分配给i
th的数组时str
,字符串文字转换为一个指针,这使得分配不兼容,因为您有char
数组类型的左值和类型的右值char *
。因此编译器将其报告为错误。
第二部分第三部分
>> whereas str[i][j]='J'; is valid.
是的,只要i
和j
是给定数组的有效值,这就是有效的str
。
str[i][j]
是 type char
,所以你可以给它分配一个字符。请注意,C 不会检查数组边界,并且越界访问数组是未定义的行为,其中包括 - 它可能偶然地完全按照程序员的意图执行或分段错误或默默地生成不正确的结果或任何事情都可能发生。
假设在案例 1中,您想要创建一个指向字符串的指针数组。
它应该是这样的:
char *str[]={"what","is","this"};
^^
的内存视图str
将是这样的:
str
+----+ +-+-+-+-+--+
str[0]| |--->|w|h|a|t|\0|
| | +-+-+-+-+--+
+----+ +-+-+--+
str[1]| |--->|i|s|\0|
| | +-+-+--+
+----+ +-+-+-+-+--+
str[2]| |--->|t|h|i|s|\0|
| | +-+-+-+-+--+
+----+
"what"
,"is"
并且"this"
是字符串文字。
str[0]
,str[1]
并且str[2]
是指向相应字符串文字的指针,您也可以使它们指向其他字符串。
所以,这很好:
str[i]="newstring";
假设i
为 1,因此str[1]
指针现在指向字符串文字"newstring"
:
+----+ +-+-+-+-+-+-+-+-+-+--+
str[1]| |--->|n|e|w|s|t|r|i|n|g|\0|
| | +-+-+-+-+-+-+-+-+-+--+
+----+
但你不应该这样做:
str[i][j]='j';
(假设i=1
and j=0
,str[i][j]
第二个字符串的第一个字符也是如此)
根据标准,尝试修改字符串文字会导致未定义的行为,因为它们可能存储在只读存储中或与其他字符串文字组合。
如果它们的元素具有适当的值,则未指定这些数组是否不同。如果程序尝试修改这样的数组,则行为未定义。
C 语言中没有原生字符串类型。在 C 语言中,字符串是以空字符结尾的字符数组。您应该知道数组和指针之间的区别。
我建议您阅读以下内容,以更好地了解数组、指针、数组初始化:
情况1 :
char*str={"what","is","this"};
首先上述声明无效,请正确阅读警告。str
是单指针,它可以一次指向single
char 数组而不是multiple
char 数组。
bounty.c:3:2:警告:标量初始化程序中的多余元素 [默认启用]
str
是 achar pointer
并且它存储在section
RAM 的部分中,但它contents
存储在code(Can't modify the content
RAM 的部分中,因为str
它是用 初始化的string(in GCC/linux)
。
正如你所说str[i]="newstring"; 有效而 str[i][j]='j'; 是无效的。
str= "new string"
不会导致修改代码/只读部分,在这里您只是分配new address
给str
这就是为什么它有效但
*str='j'
or无效str[0][0]='j'
,因为您在此处修改只读部分,试图更改.str
案例2:
char str[][5]={"what","is","this"};
这str
是2D
数组str
,即str[0],str[1],str[2]
本身存储在stack section
其中,RAM
这意味着您可以更改每个str[i]
内容。
str[i][j]='w';
这是有效的,因为您正在尝试堆叠可能的部分内容。但
str[i]= "new string";
这是不可能的,因为str[0]
它本身是一个数组,而数组是 const 指针(不能更改地址),你不能分配新地址。
只是在第一种情况下 str="new string"
是valid
因为str
是,而pointer
不是array
在第二种情况下 str[0]="new string"
是not valid
因为str
不是。array
pointer
我希望它有所帮助。