指针表达式:*ptr++、*++ptr 和 ++*ptr

Posted

技术标签:

【中文标题】指针表达式:*ptr++、*++ptr 和 ++*ptr【英文标题】:Pointer expressions: *ptr++, *++ptr and ++*ptr 【发布时间】:2013-08-31 03:54:42 【问题描述】:

最近遇到这个问题,我自己也搞不懂。

这三个表达式REALLY是什么意思?

*ptr++
*++ptr
++*ptr

我试过里奇。但遗憾的是无法按照他所说的这 3 个操作。

我知道它们都是为了增加指针/指向的值而执行的。我也可以猜测可能有很多关于优先级和评估顺序的事情。就像一个先增加指针然后获取该指针的内容,一个简单地获取内容然后增加指针等等等等。如您所见,我对他们的 actual 没有清楚的了解操作,我想尽快清除。但是当我有机会将它们应用到程序中时,我真的迷失了。例如:

int main()

    char *p = "Hello";
    while(*p++)
         printf("%c",*p);
    return 0;

给我这个输出:

ello

但我的期望是它会打印 Hello 。 最后一个请求——请举例说明每个表达式在给定代码 sn-p 中的工作方式。大多数时候,我脑海中只有一段理论。

【问题讨论】:

你错过了第四个:(*ptr)++(括号需要消除*ptr++的歧义) 因为您在打印之前增加了指针。你想要 while(*p) 和 printf("%c", *p++); 面试的好问题。实际用途有限。我希望 C 没有那些指针 :) @Himanshu 如果这能烤到你的受访者的面,试试这个:有一个 global 指针,char* p,指向一个有效终止的唯一字符字符串。然后有一个函数fn(char ch) 打印both ch 参数 p 指向的当前字符。现在调用fn(*p++); 问:fn 是否打印相同的字符 twice ?你会惊讶于有多少教授把这个问题弄错了。 因为 p 指向一个字符串文字,你应该写 const char* p = "Hello"; 【参考方案1】:

这是一个详细的解释,希望对您有所帮助。让我们从您的程序开始,因为它是最容易解释的。

int main()

    char *p = "Hello";
    while(*p++)
        printf("%c",*p);
    return 0;

第一句话:

char* p = "Hello";

p 声明为指向char 的指针。当我们说“指向char 的指针”时,这是什么意思?表示p的值是一个char的地址; p 告诉我们在内存中的哪个位置留出一些空间来保存 char

该语句还将p 初始化为指向字符串文字"Hello" 中的第一个字符。在本练习中,重要的是要理解 p 不是指向整个字符串,而只是指向第一个字符 'H'。毕竟,p 是指向一个char 的指针,而不是指向整个字符串的指针。 p的值是"Hello"'H'的地址。

然后你设置一个循环:

while (*p++)

循环条件*p++ 是什么意思?这里有三件事情使​​这令人费解(至少在熟悉之前):

    两个运算符的优先级,后缀++和间接* 后缀增量表达式的值 后缀增量表达式的副作用

1.优先级。快速浏览运算符的优先级表会告诉您后缀增量的优先级 (16) 高于取消引用/间接 (15)。这意味着复杂表达式*p++ 将被分组为:*(p++)。也就是说,* 部分将应用于p++ 部分的值。所以让我们先来看看p++ 部分。

2。后缀表达式值。 p++ 的值是p 的值在增量之前。如果你有:

int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);

输出将是:

7
8

因为i++ 在增量之前计算为i。同样p++ 将评估为p 的当前值。我们知道,p的当前值是'H'的地址。

所以现在*p++p++ 部分已被评估;这是p 的当前值。然后* 部分发生。 *(current value of p) 表示:访问p 持有的地址处的值。我们知道那个地址的值是'H'。所以表达式*p++ 的计算结果为'H'

现在等一下,你是说。如果*p++ 的计算结果为'H',为什么上面的代码中没有'H' 打印?这就是副作用发挥作用的地方。

3.后缀表达式副作用。后缀++ 具有当前操作数的,但它具有增加该操作数的副作用。嗯?再看看int 代码:

int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);

如前所述,输出将是:

7
8

i++ 在第一个printf() 中求值时,它的求值为7。但C 标准保证在第二个printf() 开始执行之前的某个时间点,副作用 ++ 运算符将发生。也就是说,在第二个printf() 发生之前,i 将由于第一个printf() 中的++ 运算符而增加。顺便说一句,这是该标准对副作用时间的少数保证之一。

然后,在您的代码中,当计算表达式 *p++ 时,它的计算结果为 'H'。但是当你得到这个时:

printf ("%c", *p)

出现了讨厌的副作用。 p 已增加。哇!它不再指向'H',而是指向'H'之后的一个字符:即'e'。这解释了你的 cockneyfied 输出:

ello

因此,其他答案中的有用(和准确)建议的合唱:要打印收到的发音"Hello" 而不是它的伦敦对应物,您需要类似

while (*p)
    printf ("%c", *p++);

就这么多。其余的呢?你问这些的含义:

*ptr++
*++ptr
++*ptr

我们刚刚讲了第一个,让我们看看第二个:*++ptr

我们在前面的解释中看到后缀增量p++ 具有一定的优先级副作用。前缀增量++p 与它的后缀对应物具有相同的副作用:它将其操作数增加1。但是,它具有不同的优先级和不同的价值

前缀增量的优先级低于后缀;它的优先级为 15。换句话说,它与取消引用/间接操作符* 具有相同的优先级。像

这样的表达式
*++ptr

重要的不是优先级:两个运算符的优先级相同。所以 associativity 开始了。前缀增量和间接运算符具有左右关联性。由于这种关联性,操作数ptr 将与最右边的运算符++ 组合在更左侧的运算符* 之前。换句话说,表达式将被分组*(++ptr)。因此,与*ptr++ 一样,但出于不同的原因,这里* 部分也将应用于++ptr 部分的值。

那么这个值是什么?前缀增量表达式的值是操作数在增量之后的值。这使它成为与后缀增量运算符截然不同的野兽。假设您有:

int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);

输出将是:

8
8

... 与我们在后缀运算符中看到的不同。同样,如果您有:

char* p = "Hello";
printf ("%c ", *p);    // note space in format string
printf ("%c ", *++p);  // value of ++p is p after the increment
printf ("%c ", *p++);  // value of p++ is p before the increment
printf ("%c ", *p);    // value of p has been incremented as a side effect of p++

输出将是:

H e e l                // good dog

你明白为什么吗?

现在我们来看看你问的第三个表达式++*ptr。实际上,这是最棘手的。两个运算符具有相同的优先级和左右结合性。这意味着表达式将被分组++(*ptr)++ 部分将应用于*ptr 部分的值。

如果我们有:

char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);

令人惊讶的自负输出将是:

I

什么?!好的,所以*p 部分将评估为'H'。然后++ 发挥作用,此时它将应用于'H',而不是指针!将 1 加到 'H' 时会发生什么?你得到 1 加上'H' 的 ASCII 值,72;你会得到 73。将其表示为 char,你会得到 ASCII 值为 73 的 char'I'

这可以解决您在问题中提出的三个表达方式。这是另一个,在您的问题的第一条评论中提到:

(*ptr)++ 

那个也很有趣。如果你有:

char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);

它会给你这个热情的输出:

HI
    

发生了什么事?同样,这是优先级表达式值副作用的问题。由于括号,*p 部分被视为主要表达式。主要表达胜过其他一切;他们首先得到评估。如您所知,*p 的计算结果为 'H'。表达式的其余部分,++ 部分,应用于该值。因此,在这种情况下,(*p)++ 变为 'H'++

'H'++ 的值是多少?如果您说'I',那么您已经(已经!)忘记了我们关于后缀增量的价值与副作用的讨论。请记住,'H'++ 的计算结果是 'H' 的当前值。所以第一个printf() 将打印'H'。然后,作为副作用'H' 将增加到'I'。第二个printf() 打印出'I'。你有你愉快的问候。

好吧,但是在最后两种情况下,我为什么需要

char q[] = "Hello";
char* p = q;

为什么我不能有类似的东西

char* p = "Hello";
printf ("%c", ++*p);   // attempting to change string literal!

因为"Hello" 是字符串文字。如果您尝试++*p,您正在尝试将字符串中的'H' 更改为'I',从而使整个字符串"Iello"。在 C 中,字符串文字是只读的;试图修改它们会调用未定义的行为。 "Iello" 在英文中也未定义,但这只是巧合。

相反,你不能拥有

char p[] = "Hello";
printf ("%c", *++p);  // attempting to modify value of array identifier!

为什么不呢?因为在这种情况下,p 是一个数组。数组不是可修改的左值;您不能通过前后递增或递减来更改p 指向的位置,因为数组的名称就好像它是一个常量指针一样工作。 (实际上并非如此;这只是一种方便的查看方式。)

总结一下,你问的有以下三点:

*ptr++   // effectively dereferences the pointer, then increments the pointer
*++ptr   // effectively increments the pointer, then dereferences the pointer
++*ptr   // effectively dereferences the pointer, then increments dereferenced value

这是第四个,和其他三个一样有趣:

(*ptr)++ // effectively forces a dereference, then increments dereferenced value

如果ptr 实际上是一个数组标识符,第一个和第二个将崩溃。如果ptr 指向字符串文字,第三个和第四个将崩溃。

你有它。我希望现在一切都是水晶。你是一个很棒的观众,我整个星期都会在这里。

【讨论】:

在来这个论坛之前,我已经搜索了3本我拥有的“C”类书籍。我还尝试了一些著名的在线教程。但是它们都没有接近您的解释(尤其是您将它们放在一起的方式)。您不仅回答了我提出的问题,而且还从基层层面讨论了很多事情。其实今天你教会了我很多基本的东西,这是我以前缺乏的。我忍不住切换了我接受的答案。 :) 再次感谢。 +1 我认为这是我在 SO 上读到的最佳长答案。我想每个人都可以从这个答案中学到很多东西。 @verbose 欢迎您!我建议避免将重要文本用作评论和水平滚动条。 你先生,应该写一本关于 C 的书。 @verbose 你先生,辜负了你的名字.. :)【参考方案2】:

假设ptr指向数组arr的第i个元素。

    *ptr++ 计算结果为 arr[i] 并将 ptr 设置为指向 arr 的第 (i+1) 个元素。相当于*(ptr++)

    *++ptrptr 设置为指向arr 的第(i+1) 个元素并计算为arr[i+1]。相当于*(++ptr)

    ++*ptrarr[i] 增加一并计算其增加的值;指针ptr 保持不变。相当于++(*ptr)

还有一个,但你需要括号来写它:

    (*ptr)++arr[i] 增加一并在增加之前评估其值;指针ptr 再次保持不变。

其余的你自己想办法; @Jaguar 也回答了这个问题。

【讨论】:

【参考方案3】:

*ptr++ : post increment a pointer ptr

*++ptr : Pre Increment a pointer ptr

++*ptr : preincrement the value at ptr location

阅读here 了解前置增量和后置增量运算符


这将给出Hello 作为输出

int main()

    const char *p = "Hello";
    while(*p)
         printf("%c",*p++);//Increment the pointer here 
    return 0;

【讨论】:

@Nik-Lz 是的,输出将是Hello【参考方案4】:

循环中的条件不好:

while(*p++)
    printf("%c",*p);

一样
while(*p)

    p++;
    printf("%c",*p);

这是错误的,应该是:

while(*p)

    printf("%c",*p);
    p++;
 

*ptr++*(ptr++)相同,即:

const char  *ptr = "example";
char  value;

value = *ptr;
++ptr;
printf("%c", value); // will print 'e'

*++ptr*(++ptr)相同,即:

const char  *ptr = "example";
char  value;

++ptr;
value = *ptr;
printf("%c", value); // will print 'x'

++*ptr++(*ptr)相同,即:

const char  *ptr = "example";
char  value;

value = *ptr;
++value;
printf("%c", value); // will print 'f' ('e' + 1)

【讨论】:

我绝对同意答案的第一部分。在第二部分中,使用整数初始化指针(指向整数!)对于那些难以理解指针用法的人来说是令人困惑的。【参考方案5】:

你说得对,注意* 优先于前缀增量,但不超过后缀增量。以下是这些细分的方式:

*ptr++ - 从左到右,取消引用指针,然后增加指针值(不是它指向的值,因为后缀优先于取消引用)

*++ptr - 增加指针然后取消引用它,这是因为前缀和取消引用具有相同的优先级,因此它们按从右到左的顺序计算

++*ptr - 在优先级方面与上述类似,再次从右到左依次取消引用指针,然后递增指针指向的内容。请注意,在您的情况下,这将导致未定义的行为,因为您正在尝试修改只读变量 (char* p = "Hello";)。

【讨论】:

【参考方案6】:

我将添加我的观点,因为虽然其他答案是正确的,但我认为他们遗漏了一些东西。

 v = *ptr++

意思

 temp = ptr;
 ptr  = ptr + 1
 v    = *temp;

在哪里

 v = *++ptr

意思

 ptr = ptr + 1
 v   = *ptr

了解后增量(和后减量)的含义很重要

 temp = ptr       // Temp created here!!!
 ptr  = ptr + 1   // or - 1 if decrement)
 v    = *temp     // Temp destroyed here!!!

为什么重要?好吧,在 C 中,这并不是那么重要。尽管在 C++ 中 ptr 可能是一个复杂的类型,比如迭代器。例如

 for (std::set<int>::iterator it = someSet.begin(); it != someSet.end(); it++)

在这种情况下,因为it 是一个复杂类型it++ 可能会因为temp 创建而产生副作用。当然,如果你很幸运,编译器会尝试丢弃不需要的代码,但如果迭代器的构造函数或析构函数做了任何事情,那么 it++ 将在创建 temp 时显示这些效果。

我想说的是写下你的意思。如果你的意思是increment ptr,那么写++ptr而不是ptr++。如果你的意思是temp = ptr, ptr += 1, temp,那么写ptr++

【讨论】:

【参考方案7】:
*ptr++    // 1

这与:

    tmp = *ptr;
    ptr++;

所以ptr指向的对象的值被检索出来,然后ptr递增。

*++ptr    // 2

这与:

    ++ptr;
    tmp = *ptr;

所以指针ptr递增,然后读取ptr指向的对象。

++*ptr    // 3

这与:

    ++(*ptr);

所以ptr指向的对象自增; ptr 本身没有改变。

【讨论】:

【参考方案8】:

指针表达式:*ptr++、*++ptr 和 ++*ptr:

注意:指针必须初始化并且必须有有效的地址。因为在 RAM 中,除了我们的程序(a.out)之外,还有更多程序同时运行,即如果您尝试访问一些未为您保留的内存,操作系统将通过分段错误。

在解释之前让我们先看一个简单的例子?

#include<stdio.h>
int main()

        int num = 300;
        int *ptr;//uninitialized pointer.. must be initialized
        ptr = &num;
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr = *ptr + 1;//*ptr means value/data on the address.. so here value gets incremented
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        /** observe here that "num" got changed but manually we didn't change, it got modified by pointer **/
        ptr = ptr + 1;//ptr means address.. so here address got incremented
        /**     char pointer gets incremented by 1 bytes
          Integer pointer gets incremented by 4 bytes
         **/
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);

分析上面代码的输出,希望你得到上面代码的输出。从上面的代码中可以清楚地看出,指针名称 (ptr) 意味着我们正在谈论 address*ptr 意味着我们正在谈论 abbout 价值/数据。

案例 1*ptr++、*++ptr、*(ptr++) 和 *(++ptr):

上面提到的所有 4 种语法都是相似的,在所有 address gets incremented 中,但是地址如何增加是不同的。

注意:求解任意一个表达式,求表达式中有多少个运算符,然后求出运算符的优先级。我有多个具有相同优先级的运算符,然后检查可能从右(R)到左(L)或从左到右的进化顺序或关联性

*ptr++ :这里有两个运算符,即取消引用(*)和++(增量)。两者都具有相同的优先级,然后检查 R 到 L 的关联性。所以从右到左开始求解,无论运算符先到。

*ptr++ : 第一个 ++ 是在从 R 到 L 求解时出现的,所以地址会递增,但它的后递增。

*++ptr :与第一个相同,地址也增加,但它的预增加。

*(ptr++) : 这里有 3 个操作符,其中 grouping () 具有最高优先级,所以首先 ptr++ 解决了,即地址增加但发布。

*(++ptr) :与上面的情况相同,这里的地址也增加但预增加。

案例 2 : ++*ptr, ++(*ptr), (*ptr)++ :

上面提到的所有 4 种语法都是相似的,在 所有值/数据都会递增,但值如何改变是不同的。

++*ptr :第一个 * 在从 R 到 L 求解时出现,因此值发生了变化,但它的预增量。

++(*ptr) : 同上例,修改值。

(*ptr)++ : 这里有3个操作符,其中grouping () 优先级最高,Inside () *ptr 在那里,所以首先解决*ptr 即值递增但是发帖。

注意 : ++*ptr 和 *ptr = *ptr + 1 都是相同的,在这两种情况下值都会改变。 ++*ptr : 仅使用 1 条指令 (INC),直接单次更改值。 *ptr = *ptr + 1 :这里第一个值递增(INC),然后赋值(MOV)。

要理解上述所有不同的指针增量语法,让我们考虑简单的代码:

#include<stdio.h>
int main()

        int num = 300;
        int *ptr;
        ptr = &num;
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr++;//address changed(post increment), value remains un-changed
//      *++ptr;//address changed(post increment), value remains un-changed
//      *(ptr)++;//address changed(post increment), value remains un-changed
//      *(++ptr);//address changed(post increment), value remains un-changed

//      ++*ptr;//value changed(pre increment), address remains un-changed
//      (*ptr)++;//value changed(pre increment), address remains un-changed
//      ++(*ptr);//value changed(post increment), address remains un-changed

        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);

在上面的代码中,尝试注释/取消注释 cmets 并分析输出。

Pointers as Constant : 没有任何方法可以将指针设为常量,我在此仅提及。

1)const int *p OR int const *p :这里value常量地址不是常量 即 p 指向哪里?某个地址?在那个地址上的价值是什么?一些价值吧?该值是恒定的,您不能修改该值,但指针指向哪里?一些地址吧?它也可以指向其他地址。

要理解这一点,让我们考虑下面的代码:

#include<stdio.h>
int main()

        int num = 300;
        const int *ptr;//constant value, address is modifible
        ptr = &num;
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr++;//
//      *++ptr;//possible bcz you are trying to change address which is possible
//      *(ptr)++;//possible
//      *(++ptr);//possible

//      ++*ptr;//not possible bcz you trying to change value which is not allowed
//      (*ptr)++;//not possible
//      ++(*ptr);//not possible

        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);

尝试分析上述代码的输出

2)int const *p:它被称为'**constant pointe**r',即address is constant but value is not constant。此处不允许更改地址,但可以修改值。

注意:常量指针(上例)必须在声明自身时初始化。

为了理解这一点,让我们检查一下简单的代码。

#include<stdio.h>
int main()

        int x = 300;
        int* const p;
        p = &x;
        printf("x = %d p =%p and *p = %d\n",num,p,*p);

在上面的代码中,如果你观察到没有 ++*p 或 *p++ 所以你可能认为这是简单的情况,因为我们没有改变地址或值,但它会产生错误。为什么 ?我在 cmets 中提到的原因。

#include<stdio.h>
int main()

        int x = 300;
        /** constant pointer must initialize while decaring itself **/
        int* const p;//constant pointer i.e its pointing to some address(here its pointing to garbage), it should point to same address(i.e garbage ad
dress only 
        p = &x;// but here what we are doing ? we are changing address. we are making p to point to address of x instead of garbage address.
        printf("x = %d p =%p and *p = %d\n",num,p,*p);

那么这个问题的解决方案是什么?

     int* const p = &x;

有关此案例的更多信息,请考虑以下示例。

#include<stdio.h>
int main()

        int num = 300;
        int *const ptr = &num;//constant value, address is modifible
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr++;//not possible
//      *++ptr;//not possible bcz you are trying to change address which is not possible
//      *(ptr)++;//not possible
//      *(++ptr);//not possible

//      ++*ptr;// possible bcz you trying to change value which is allowed
//      (*ptr)++;// possible
//      ++(*ptr);// possible
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);

3)const int* const p:这里地址和值都是常数

要了解这一点,请查看以下代码

#include<stdio.h>
int main()

        int num = 300;
        const int* const ptr = &num;//constant value,constant address 
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr++;//not possible
        ++*ptr;//not possible
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);

【讨论】:

【参考方案9】: 后缀 ++ 的优先级高于一元 *。 前缀 ++ 和一元 * 具有相同的优先级,两者的表达式具有从右到左的运算符关联性,这意味着右侧的操作数在左侧之前绑定到操作数(s)。

因此:

*ptr++ 将指针递增 1 项,然后取消引用它在递增之前的内存位置。 *++ptr 将指针增加 1 项,然后取消引用它现在指向的内存位置。 ++*ptr 取消引用内存位置,然后将 contents(值)增加 1。

【讨论】:

【参考方案10】:

后缀和前缀的优先级高于取消引用所以

*ptr++ 在这里发布增量 ptr 然后指向新的 ptr 值

*++ptr here Pre Increment 拳头然后指向新的ptr值

++*ptr这里先获取ptr指向的值,然后递增那个值

【讨论】:

这是不正确的。后缀具有更高的优先级,但前缀与取消引用具有相同的优先级。【参考方案11】:
const char *p = "Hello";   

*p means "Hello"
          ^
          | 
          p

*p++ means "Hello"
             ^
             | 
             p

*++p means "Hello"
            ^
            |     (WHILE THE STATEMENT IS EXECUTED)
            p

*++p means "Hello"
             ^
             |     (AFTER THE STATEMENT IS EXECUTED)
             p

++*p 表示您正在尝试增加 *p 的 ASCII 值

   is "Hello"
       ^
       | 
       p

你不能增加这个值,因为它是一个常数,所以你会得到一个错误

对于您的 while 循环,循环将一直运行,直到 *p++ 到达字符串的末尾,那里有一个 '\0'(NULL) 字符。

现在,由于 *p++ 跳过了第一个字符,因此您只能从第二个字符开始获得输出。

下面的代码不会输出任何东西,因为while循环有'\0'

const char *p = "Hello";
    while('\0') 
         printf("%c",*p);

以下代码将为您提供与下一个代码相同的输出,即 ello 。

const char *p = "Hello";
    while(*++p)
         printf("%c",*p);

.......................

const char *p = "Hello";
    while(*p++)
         printf("%c",*p);

【讨论】:

以上是关于指针表达式:*ptr++、*++ptr 和 ++*ptr的主要内容,如果未能解决你的问题,请参考以下文章

第4章 表达式

原创二级指针中在内存中的样子

指针算法:++*ptr 还是 *ptr++?

智能指针的模拟实现 auto_ptr scoped_ptr shared_ptr

C语言指针 ptr,*ptr,&ptr的含义?

智能指针之auto_ptr和scoped_ptr