在字符串上使用指针

Use of pointers on strings

我对在字符串上使用指针感到很困惑。 感觉他们遵守不同的规则。 考虑以下代码

  1. char *ptr = "apple";// perfectly valid here not when declaring afterwards like next
    
    ptr = "apple"; // shouldn't it be *ptr = "apple"
    
  2. 而且 printf() 的行为也不同 -

    printf("%s", ptr) // Why should I send  the address instead of the value
    
  3. 另外我在一本书上看到了下面的代码

    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

  1. "SOME STRING" 在内存中创建一个以 [=11=] 结尾的字符序列和 returns 它的第一个字符地址,因此您可以将它分配给一个指针:
    char *ptr = "Hello";

  2. printf 函数也适用于地址,类型说明符定义了它应该如何从内存中读取数据。

  3. 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-namecharthing-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" 位于地址 0x400c801。变量 ptrarr 分别位于地址 0x7fffcb4d45180x7fffcb4d4510 2

变量 ptr 包含值 0x400c80,这是 "apple" 字符串字面量的第一个元素的地址(x86 将多字节值存储在 "little-endian" 顺序,因此最低有效字节在前,这意味着您必须从右到左阅读)。

还记得上面的 "except" 子句吗?在第二个声明中,字符串文字 "apple" 用于初始化声明中 chararray;字符串文字的 内容 没有被转换为指针值,而是被复制到数组中,您可以在内存转储中看到它。

  1. 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;你可能会遇到段错误,你的代码可能会按预期工作,你可能会在列支敦士登发射核武器。所以你不能写入 *pp[i];但是,您 可以 将新值写入 p,将其指向不同的位置。


  1. 从技术上讲,它是 0x0000000000400c80%p 说明符删除前导零。
  2. 同样的交易 - 从技术上讲,值为 0x000000007fffcb4d45180x000000007fffcb4d4510。请注意,具体地址值将从 运行 运行 变化。
  3. *str 等价于 str[0]
  4. C 语言定义识别了某些错误的操作,但没有对编译器提出任何要求以任何特定方式处理该代码。不同的平台以不同的方式存储字符串文字;有些将它们放在只读内存中,因此尝试修改它们会导致段错误,而其他平台将它们存储在可写段中以便操作成功。有些人可能会以不会出现段错误的方式存储它们,但字符串不会更改。