在字符串上使用指针
Use of pointers on strings
我对在字符串上使用指针感到很困惑。
感觉他们遵守不同的规则。
考虑以下代码
char *ptr = "apple";// perfectly valid here not when declaring afterwards like next
ptr = "apple"; // shouldn't it be *ptr = "apple"
而且 printf()
的行为也不同 -
printf("%s", ptr) // Why should I send the address instead of the value
另外我在一本书上看到了下面的代码
char str[]="Quest";
char *p="Quest";
str++; // error, constant pointer can't change
*str='Z'; // works, because pointer is not constant
p++; // works, because pointer is not constant
*p = 'M'; // error, because string is constant
我不明白什么意思
请帮忙,我在别处找不到任何信息
“*”与指针一起使用时表示获取指针指向的内容,在以下情况下:
char* ptr;
ptr 是一个指向字符的指针,你可以像这样将它分配给一个字符串:
const char* ptr = "test";
内存中的布局是 "t",然后是 "e"、"s"、"t",最后是 nul 终止符 '\0'。
当你像上面的 ptr 一样分配它时,它会将指针分配给恰好是 "t".
的第一个内存位置
*ptr returns ptr 指向的内容,并且始终是它声明的类型的大小,如本例中的 a "char",单字节。
*(++ptr) 将 return "e",因为 ptr 在 returning 现在指向的内容之前递增到下一个位置。
char *ptr;
ptr = "apple"; // shouldn't it be *ptr = "apple"
不,因为 *ptr
会是 char
。所以,你可以写*ptr = 'a'
,但你不能按照你的建议写。
printf("%s", ptr) // Why should I send the address instead of the value
因为 C 中的字符串是以零(空字符又名 \x0
)结尾的字符序列 (char
) 的地址。
char str[] = "Quest";
char *p = "Quest";
str++; // error, constant pointer can't change
不,指针可以完美改变,但在这里,str
是一个数组(与指针略有不同)。但是,因此,它不能处理指针运算。
*str='Z'; // works, because pointer is not constant
不,它有效,因为 *str
应该是 char
。
p++; // works, because pointer is not constant
不,它起作用了,因为这次,这是一个指针(不是数组)。
*p = 'M'; // error, because string is constant
同上,这又是一个char
,所以它起作用是因为它是正确的类型而不是因为字符串是'constant'。而且,正如 Michael Walz 在评论中所述,即使它可能会编译,它也会在运行时产生未定义的行为(很可能是 segfault
的崩溃),因为规范没有说明 [= 指向的字符串是否26=] 是否是只读的(然而,似乎大多数现代编译器实现决定将其设为只读)。这可能会产生 segfault
。
有关详细信息,请参阅 this SO question。
"SOME STRING"
在内存中创建一个以 [=11=]
结尾的字符序列和 returns 它的第一个字符地址,因此您可以将它分配给一个指针:
char *ptr = "Hello";
printf
函数也适用于地址,类型说明符定义了它应该如何从内存中读取数据。
char str[]="Quest";
char *p="Quest";
在第一个中,你创建了一个有 6 个房间的数组并在其中存储 'Q', 'u', 'e', 's', 't', '[=15=]'
,然后你可以通过 str[2] = 'x'
更改一些索引值,但数组名称本身是一个常量,其地址为它指向的第一个位置,所以你不能用 str++;
之类的东西改变它
但是在第二个中 "Quest[=18=]"
是一个常量字符串,保存在内存中的某个位置,它的第一个内存位置存储在 p
中,所以你不能改变它,但指针本身不是 const
你可以做 p++;
.
我只会回答子问题 1。但是您已经触及了 C 语言中一个常见但微妙的混淆,即初始化指针的方式与分配给该指针的方式之间的轻微不匹配。仔细观察。
如果我有一个 int
变量,我可以在声明它时初始化它:
int i = 42;
或者,我可以在一行中声明它(不初始化它),然后再给它一个值:
int i;
i = 42;
没有什么神秘之处。但是当涉及到指针时,它看起来有点不同。同样,我可以在一行中声明和初始化:
char *ptr = "apple";
或者我可以拆分声明和赋值:
char *ptr;
ptr = "apple";
但是,乍一看这看起来很奇怪 -- 基于第一种语法,第二种方式不应该像这样吗?
*ptr = "apple"; // WRONG
不,不应该,原因如下。
ptr
是指向某些字符的指针。这是在 C 中引用字符串的一种方式。
*
是指针间接运算符。在表达式中,*ptr
指的是 ptr
指向的字符(只是一个字符)。所以如果我们想获取字符串的第一个字符,我们可以使用 *
来做到这一点:
printf("first character: %c\n", *ptr);
请注意,此 printf
调用中的格式使用 %c
,因为它只打印一个字符。
我们也可以给指针赋值。如果我们使用指向 char
的指针,并且因此我们将这些指针视为 "strings",这是在 C 中进行字符串赋值的一种方法。如果我说
ptr = "apple";
那么无论ptr
以前指向哪里,现在它都指向一个包含字符串"apple"的字符数组。如果我稍后说
ptr = "pear";
然后 ptr
不再指向字符串 "apple";现在它指向另一个包含字符串 "pear" 的字符数组。您可以将此指针视为一次分配字符串的所有字符(尽管实际上它根本不是这样做的)。
所以如果*ptr
只访问一个字符,而ptr
是指针值本身,那么为什么第一种形式
char *ptr = "apple";
工作?
答案是当你说
char *ptr = "apple";
里面出现的*
不是指针间接运算符。这并不是说我们正在尝试访问任何内容的第一个字符。
当你说
char *ptr = "apple";
*
是说 ptr
是一个指针。就像你说
char *ptr;
*
是说 ptr
是一个指针。
C' 的指针声明语法有点奇怪。以下是如何考虑它。语法是
type-name thing-that-has-that-type ;
所以当我们说
char *ptr;
type-name 是 char
,thing-that-has-that-type 是 *ptr
。
我们说 *ptr
将是 char
。如果 *ptr
将是 char
,则意味着 ptr
必须是指向 char
.
的指针
然后,当我们说
char *ptr = "apple";
我们说 ptr
(我们刚刚说的是指向 char
的指针)应该有一个指向包含字符串的数组的指针作为其初始值"apple".
1- 我认为您混淆了变量声明和定义。这一行:
char *ptr = "apple";
声明一个指向char的指针,并将第一个字符"a"的地址赋值给变量ptr。这一行相当于下面的 2:
char* ptr;
ptr = "apple";
现在,C 中的字符串文字是只读的。它们是隐式常量,这与做
相同
const char* ptr;
所以实际上,你不能改变这个指针指向的位置的内容。现在,即使你可以,你做的方式也是错误的。因为 ptr 指向字符串第一个字符的位置,所以当您执行 *ptr 时,您正在访问该字符串第一个字符的内容。所以它需要一个字符,而不是一个字符串。所以它会是这样的:*ptr = 'a'
;
2-
嗯,这就是 printf 的工作方式。如果要打印带有 %s 说明符的字符串,它需要一个指向该字符串的指针,即字符串第一个字符的地址,而不是字符串的值本身。
3- 现在我要评论你的代码。
str++; // error, constant pointer can't change
你是对的。别人口口声声说数组和指针略有不同,其实不然。数组只是程序员说你正在存储一系列值的抽象。在装配层面,完全没有区别。您可以说数组是具有可变内容的不可变指针。数组存储值序列的第一个元素的地址。您可以更改数组的内容,但不能更改地址(它指向的第一个元素)。
*str='Z'; // works, because pointer is not constant
现在你搞糊涂了。指针实际上是常量,也就是说,你不能改变它存放的地址。但是你可以改变地址指向的内容,这就是上面那行所做的。它正在更改数组中值序列的第一个值。
p++; // works, because pointer is not constant
正确。指针不是常量,尽管它指向的内容是常量。您可以更改指针存储的地址,但不能更改它指向的值。字符串文字是指向不可变字符串的可变指针。
*p = 'M'; // error, because string is constant
正确,字符串是不可变的。
ptr = "apple"; // shouldn't it be *ptr = "apple"
从头开始...
字符串文字"apple"
存储在char
的6元素数组中,像这样:
+---+---+---+---+---+---+
|'a'|'p'|'p'|'l'|'e'| 0 |
+---+---+---+---+---+---+
结尾的 0
标记字符串的结尾(称为 字符串终止符 )。
当"N-element array of T
"类型的表达式出现在表达式中时,它将被转换("decay")为"pointer to T
"类型的表达式并且表达式的值将是数组第一个元素的地址,除非数组表达式是sizeof
或一元&
运算符的操作数,或者用于初始化字符数组在声明中。
因此,在声明中
ptr = "apple";
表达式 "apple"
从类型为“char
的 6 元素数组”的表达式转换 ("decays") 为 "pointer to char
"。表达式ptr
的类型是char *
,或者"pointer to char
";因此,在上面的赋值中,ptr
将接收到 "apple"
的第一个元素的地址。
应该不写成
*ptr = "apple";
因为表达式 *ptr
的计算结果是 ptr
指向的东西的值,此时它是 a) 不确定的,和 b) 错误的赋值类型。表达式 *ptr
的类型是 char
,与 char *
不兼容。
我编写了一个实用程序,可以打印内存中的项目图;给定代码
char *ptr = "apple";
char arr[] = "apple";
地图看起来像这样:
Item Address 00 01 02 03
---- ------- -- -- -- --
apple 0x400c80 61 70 70 6c appl
0x400c84 65 00 70 74 e.pt
ptr 0x7fffcb4d4518 80 0c 40 00 ..@.
0x7fffcb4d451c 00 00 00 00 ....
arr 0x7fffcb4d4510 61 70 70 6c appl
0x7fffcb4d4514 65 00 00 00 e...
字符串文字 "apple"
位于地址 0x400c80
1。变量 ptr
和 arr
分别位于地址 0x7fffcb4d4518
和 0x7fffcb4d4510
2。
变量 ptr
包含值 0x400c80
,这是 "apple"
字符串字面量的第一个元素的地址(x86 将多字节值存储在 "little-endian" 顺序,因此最低有效字节在前,这意味着您必须从右到左阅读)。
还记得上面的 "except" 子句吗?在第二个声明中,字符串文字 "apple"
用于初始化声明中 char
的 array;字符串文字的 内容 没有被转换为指针值,而是被复制到数组中,您可以在内存转储中看到它。
printf("%s", ptr) // Why should I send the address instead of the value
因为那是 %s
转换说明符 所期望的 - 它接受一个指向以 0 结尾的字符串的第一个字符的指针,并将打印出从该位置开始的字符,直到它看到终止符。
3 ... I can't understand what is supposed to imply
您不能更改数组对象的值。让我们看看 str
在内存中会是什么样子:
+---+
str: |'Q'| str[0]
+---+
|'u'| str[1]
+---+
|'e'| str[2]
+---+
|'s'| str[3]
+---+
|'t'| str[4]
+---+
| 0 | str[5]
+---+
你可以写每个str[i]
3(改变它的值),但是你不能写str
因为没有什么可写到。没有 str
对象与数组元素分开。即使 expression str
将 "decay" 指向一个指针值,也不会在任何地方为该指针预留存储空间——转换是在编译时完成的。
类似地,尝试修改字符串文字的内容会调用未定义的行为4;你可能会遇到段错误,或你的代码可能会按预期工作,或你可能会在列支敦士登发射核武器。所以你不能写入 *p
或 p[i]
;但是,您 可以 将新值写入 p
,将其指向不同的位置。
- 从技术上讲,它是
0x0000000000400c80
; %p
说明符删除前导零。
- 同样的交易 - 从技术上讲,值为
0x000000007fffcb4d4518
和 0x000000007fffcb4d4510
。请注意,具体地址值将从 运行 运行 变化。
*str
等价于 str[0]
- C 语言定义识别了某些错误的操作,但没有对编译器提出任何要求以任何特定方式处理该代码。不同的平台以不同的方式存储字符串文字;有些将它们放在只读内存中,因此尝试修改它们会导致段错误,而其他平台将它们存储在可写段中以便操作成功。有些人可能会以不会出现段错误的方式存储它们,但字符串不会更改。
我对在字符串上使用指针感到很困惑。 感觉他们遵守不同的规则。 考虑以下代码
char *ptr = "apple";// perfectly valid here not when declaring afterwards like next ptr = "apple"; // shouldn't it be *ptr = "apple"
而且
printf()
的行为也不同 -printf("%s", ptr) // Why should I send the address instead of the value
另外我在一本书上看到了下面的代码
char str[]="Quest"; char *p="Quest"; str++; // error, constant pointer can't change *str='Z'; // works, because pointer is not constant p++; // works, because pointer is not constant *p = 'M'; // error, because string is constant
我不明白什么意思
请帮忙,我在别处找不到任何信息
“*”与指针一起使用时表示获取指针指向的内容,在以下情况下:
char* ptr;
ptr 是一个指向字符的指针,你可以像这样将它分配给一个字符串:
const char* ptr = "test";
内存中的布局是 "t",然后是 "e"、"s"、"t",最后是 nul 终止符 '\0'。
当你像上面的 ptr 一样分配它时,它会将指针分配给恰好是 "t".
的第一个内存位置*ptr returns ptr 指向的内容,并且始终是它声明的类型的大小,如本例中的 a "char",单字节。
*(++ptr) 将 return "e",因为 ptr 在 returning 现在指向的内容之前递增到下一个位置。
char *ptr;
ptr = "apple"; // shouldn't it be *ptr = "apple"
不,因为 *ptr
会是 char
。所以,你可以写*ptr = 'a'
,但你不能按照你的建议写。
printf("%s", ptr) // Why should I send the address instead of the value
因为 C 中的字符串是以零(空字符又名 \x0
)结尾的字符序列 (char
) 的地址。
char str[] = "Quest";
char *p = "Quest";
str++; // error, constant pointer can't change
不,指针可以完美改变,但在这里,str
是一个数组(与指针略有不同)。但是,因此,它不能处理指针运算。
*str='Z'; // works, because pointer is not constant
不,它有效,因为 *str
应该是 char
。
p++; // works, because pointer is not constant
不,它起作用了,因为这次,这是一个指针(不是数组)。
*p = 'M'; // error, because string is constant
同上,这又是一个char
,所以它起作用是因为它是正确的类型而不是因为字符串是'constant'。而且,正如 Michael Walz 在评论中所述,即使它可能会编译,它也会在运行时产生未定义的行为(很可能是 segfault
的崩溃),因为规范没有说明 [= 指向的字符串是否26=] 是否是只读的(然而,似乎大多数现代编译器实现决定将其设为只读)。这可能会产生 segfault
。
有关详细信息,请参阅 this SO question。
"SOME STRING"
在内存中创建一个以[=11=]
结尾的字符序列和 returns 它的第一个字符地址,因此您可以将它分配给一个指针:
char *ptr = "Hello";
printf
函数也适用于地址,类型说明符定义了它应该如何从内存中读取数据。char str[]="Quest"; char *p="Quest";
在第一个中,你创建了一个有 6 个房间的数组并在其中存储'Q', 'u', 'e', 's', 't', '[=15=]'
,然后你可以通过str[2] = 'x'
更改一些索引值,但数组名称本身是一个常量,其地址为它指向的第一个位置,所以你不能用str++;
之类的东西改变它 但是在第二个中"Quest[=18=]"
是一个常量字符串,保存在内存中的某个位置,它的第一个内存位置存储在p
中,所以你不能改变它,但指针本身不是const
你可以做p++;
.
我只会回答子问题 1。但是您已经触及了 C 语言中一个常见但微妙的混淆,即初始化指针的方式与分配给该指针的方式之间的轻微不匹配。仔细观察。
如果我有一个 int
变量,我可以在声明它时初始化它:
int i = 42;
或者,我可以在一行中声明它(不初始化它),然后再给它一个值:
int i;
i = 42;
没有什么神秘之处。但是当涉及到指针时,它看起来有点不同。同样,我可以在一行中声明和初始化:
char *ptr = "apple";
或者我可以拆分声明和赋值:
char *ptr;
ptr = "apple";
但是,乍一看这看起来很奇怪 -- 基于第一种语法,第二种方式不应该像这样吗?
*ptr = "apple"; // WRONG
不,不应该,原因如下。
ptr
是指向某些字符的指针。这是在 C 中引用字符串的一种方式。
*
是指针间接运算符。在表达式中,*ptr
指的是 ptr
指向的字符(只是一个字符)。所以如果我们想获取字符串的第一个字符,我们可以使用 *
来做到这一点:
printf("first character: %c\n", *ptr);
请注意,此 printf
调用中的格式使用 %c
,因为它只打印一个字符。
我们也可以给指针赋值。如果我们使用指向 char
的指针,并且因此我们将这些指针视为 "strings",这是在 C 中进行字符串赋值的一种方法。如果我说
ptr = "apple";
那么无论ptr
以前指向哪里,现在它都指向一个包含字符串"apple"的字符数组。如果我稍后说
ptr = "pear";
然后 ptr
不再指向字符串 "apple";现在它指向另一个包含字符串 "pear" 的字符数组。您可以将此指针视为一次分配字符串的所有字符(尽管实际上它根本不是这样做的)。
所以如果*ptr
只访问一个字符,而ptr
是指针值本身,那么为什么第一种形式
char *ptr = "apple";
工作?
答案是当你说
char *ptr = "apple";
里面出现的*
不是指针间接运算符。这并不是说我们正在尝试访问任何内容的第一个字符。
当你说
char *ptr = "apple";
*
是说 ptr
是一个指针。就像你说
char *ptr;
*
是说 ptr
是一个指针。
C' 的指针声明语法有点奇怪。以下是如何考虑它。语法是
type-name thing-that-has-that-type ;
所以当我们说
char *ptr;
type-name 是 char
,thing-that-has-that-type 是 *ptr
。
我们说 *ptr
将是 char
。如果 *ptr
将是 char
,则意味着 ptr
必须是指向 char
.
然后,当我们说
char *ptr = "apple";
我们说 ptr
(我们刚刚说的是指向 char
的指针)应该有一个指向包含字符串的数组的指针作为其初始值"apple".
1- 我认为您混淆了变量声明和定义。这一行:
char *ptr = "apple";
声明一个指向char的指针,并将第一个字符"a"的地址赋值给变量ptr。这一行相当于下面的 2:
char* ptr;
ptr = "apple";
现在,C 中的字符串文字是只读的。它们是隐式常量,这与做
相同const char* ptr;
所以实际上,你不能改变这个指针指向的位置的内容。现在,即使你可以,你做的方式也是错误的。因为 ptr 指向字符串第一个字符的位置,所以当您执行 *ptr 时,您正在访问该字符串第一个字符的内容。所以它需要一个字符,而不是一个字符串。所以它会是这样的:*ptr = 'a'
;
2- 嗯,这就是 printf 的工作方式。如果要打印带有 %s 说明符的字符串,它需要一个指向该字符串的指针,即字符串第一个字符的地址,而不是字符串的值本身。
3- 现在我要评论你的代码。
str++; // error, constant pointer can't change
你是对的。别人口口声声说数组和指针略有不同,其实不然。数组只是程序员说你正在存储一系列值的抽象。在装配层面,完全没有区别。您可以说数组是具有可变内容的不可变指针。数组存储值序列的第一个元素的地址。您可以更改数组的内容,但不能更改地址(它指向的第一个元素)。
*str='Z'; // works, because pointer is not constant
现在你搞糊涂了。指针实际上是常量,也就是说,你不能改变它存放的地址。但是你可以改变地址指向的内容,这就是上面那行所做的。它正在更改数组中值序列的第一个值。
p++; // works, because pointer is not constant
正确。指针不是常量,尽管它指向的内容是常量。您可以更改指针存储的地址,但不能更改它指向的值。字符串文字是指向不可变字符串的可变指针。
*p = 'M'; // error, because string is constant
正确,字符串是不可变的。
ptr = "apple"; // shouldn't it be *ptr = "apple"
从头开始...
字符串文字"apple"
存储在char
的6元素数组中,像这样:
+---+---+---+---+---+---+
|'a'|'p'|'p'|'l'|'e'| 0 |
+---+---+---+---+---+---+
结尾的 0
标记字符串的结尾(称为 字符串终止符 )。
当"N-element array of T
"类型的表达式出现在表达式中时,它将被转换("decay")为"pointer to T
"类型的表达式并且表达式的值将是数组第一个元素的地址,除非数组表达式是sizeof
或一元&
运算符的操作数,或者用于初始化字符数组在声明中。
因此,在声明中
ptr = "apple";
表达式 "apple"
从类型为“char
的 6 元素数组”的表达式转换 ("decays") 为 "pointer to char
"。表达式ptr
的类型是char *
,或者"pointer to char
";因此,在上面的赋值中,ptr
将接收到 "apple"
的第一个元素的地址。
应该不写成
*ptr = "apple";
因为表达式 *ptr
的计算结果是 ptr
指向的东西的值,此时它是 a) 不确定的,和 b) 错误的赋值类型。表达式 *ptr
的类型是 char
,与 char *
不兼容。
我编写了一个实用程序,可以打印内存中的项目图;给定代码
char *ptr = "apple";
char arr[] = "apple";
地图看起来像这样:
Item Address 00 01 02 03
---- ------- -- -- -- --
apple 0x400c80 61 70 70 6c appl
0x400c84 65 00 70 74 e.pt
ptr 0x7fffcb4d4518 80 0c 40 00 ..@.
0x7fffcb4d451c 00 00 00 00 ....
arr 0x7fffcb4d4510 61 70 70 6c appl
0x7fffcb4d4514 65 00 00 00 e...
字符串文字 "apple"
位于地址 0x400c80
1。变量 ptr
和 arr
分别位于地址 0x7fffcb4d4518
和 0x7fffcb4d4510
2。
变量 ptr
包含值 0x400c80
,这是 "apple"
字符串字面量的第一个元素的地址(x86 将多字节值存储在 "little-endian" 顺序,因此最低有效字节在前,这意味着您必须从右到左阅读)。
还记得上面的 "except" 子句吗?在第二个声明中,字符串文字 "apple"
用于初始化声明中 char
的 array;字符串文字的 内容 没有被转换为指针值,而是被复制到数组中,您可以在内存转储中看到它。
printf("%s", ptr) // Why should I send the address instead of the value
因为那是 %s
转换说明符 所期望的 - 它接受一个指向以 0 结尾的字符串的第一个字符的指针,并将打印出从该位置开始的字符,直到它看到终止符。
3 ... I can't understand what is supposed to imply
您不能更改数组对象的值。让我们看看 str
在内存中会是什么样子:
+---+
str: |'Q'| str[0]
+---+
|'u'| str[1]
+---+
|'e'| str[2]
+---+
|'s'| str[3]
+---+
|'t'| str[4]
+---+
| 0 | str[5]
+---+
你可以写每个str[i]
3(改变它的值),但是你不能写str
因为没有什么可写到。没有 str
对象与数组元素分开。即使 expression str
将 "decay" 指向一个指针值,也不会在任何地方为该指针预留存储空间——转换是在编译时完成的。
类似地,尝试修改字符串文字的内容会调用未定义的行为4;你可能会遇到段错误,或你的代码可能会按预期工作,或你可能会在列支敦士登发射核武器。所以你不能写入 *p
或 p[i]
;但是,您 可以 将新值写入 p
,将其指向不同的位置。
- 从技术上讲,它是
0x0000000000400c80
;%p
说明符删除前导零。 - 同样的交易 - 从技术上讲,值为
0x000000007fffcb4d4518
和0x000000007fffcb4d4510
。请注意,具体地址值将从 运行 运行 变化。 *str
等价于str[0]
- C 语言定义识别了某些错误的操作,但没有对编译器提出任何要求以任何特定方式处理该代码。不同的平台以不同的方式存储字符串文字;有些将它们放在只读内存中,因此尝试修改它们会导致段错误,而其他平台将它们存储在可写段中以便操作成功。有些人可能会以不会出现段错误的方式存储它们,但字符串不会更改。