【问题标题】:Pointers and Arrays in C, Need for more UnderstandingC中的指针和数组,需要更多了解
【发布时间】:2015-07-04 18:43:28
【问题描述】:

我在 C 中做一些指针和数组练习,我注意到我所有的四种方法都返回了相同的答案。我的问题是使用以下任何一种方法都有缺点吗?我对这四个如何给我相同的输出感到震惊。我刚刚注意到您可以像使用数组一样使用指针,也可以像使用指针一样使用数组?

char *name = "Madonah";
int i= 0;
for (i=0;i<7; i++){
    printf("%c", *(name+i));
}

char name1 [7] = "Madonah";
printf("\n");
int j= 0;
for (j=0;j<7; j++){
    printf("%c", name1[j]);
}

char *name2 = "Madonah";
printf("\n");
int k= 0;
for (k=0;k<7; k++){
    printf("%c", name2[k]);
}

char name3 [7] = "Madonah";
printf("\n");
int m= 0;
for (m=0;m<7; m++){
    printf("%c", *(name+m));
}

结果:

Madonah
Madonah
Madonah
Madonah

【问题讨论】:

  • 使用char * name= malloc(sizeof(char)*8); strcpy(name,"Madonah");,这样您之后可以轻松释放空间。
  • 记得为'\0'分配内存。 C 中的字符串文字会自动附加 '\0'
  • 注意:第一个和最后一个相同,除了索引变量的名称(i vs.m); name3 在此代码中未使用。
  • @pcluddite 感谢编辑评论
  • @RichardPennington 在这种情况下它可以工作,但是对于通常的字符串操作它不会工作(printf("%s",name1) 会失败)

标签: c arrays pointers


【解决方案1】:

确实,指针和数组在某些情况下是equivalent“等价”意味着它们既不相同也不可互换数组不是指针
指针算术和数组索引是等价的,指针和数组是不同的

哪一个更可取以及优点/缺点?

这取决于您想如何使用它们。如果你不想修改字符串,那么你可以使用

char *name = "Madonah";  

基本等价于

char const *name = "Madonah";  

*(name + i)name[i] 都是一样的。我更喜欢name[i] 而不是*(name + i),因为它简洁且最常被C 和C++ 程序员使用。

如果你想修改字符串,那么你可以去

char name1[] = "Madonah";

【讨论】:

  • 虽然char *name = "Madonah";char const *name = "Madonah"; 基本相同,但我建议尽可能使用后者; C 程序不得尝试修改字符串字面量下的存储空间,const 可帮助您避免意外违反该规则。
  • 这里有很多问题: 1)const=常量,就是你不打算改变变量的值。 2)const 告诉编译器不要期望值发生变化,因此它可以更好地优化幕后的东西。 3) const 如果您尝试修改它也可以/将产生警告。 4) char *name = "a"char name[] = "a" 是相同的,但后者更适合初始化字符串/缓冲区。 5) wanna 不是一个词。
  • @Geoffrey;我想说在这里对你从 1 到 4 的所有观点进行一些研究。感谢第 5 点。
  • 您说“指针和数组是不同的”,但并没有真正解释为什么/如何。它只是一个符号差异,还是有功能含义? 我们为什么要关心它们的不同?您的回答并没有真正说明这一点。
  • @R.M.;我知道这对初学者来说有点混乱。数组和指针是两种完全不同的数据类型。我没有解释为什么/如何,因为我认为这里不需要。指针和数组之间的一个主要difference 是数组是不可修改的左值,即它不能是赋值运算符 (=) 的左操作数,这意味着你不能重新分配数组,或者你不能应用数组上的 ++-- 运算符与指针不同。
【解决方案2】:

在 C 中,a[b]b[a]*(a+b) 是等价的,这 3 种没有区别。所以你只有 2 种情况:

char *name = "Madonah"; /* case 1 */

char name3 [7] = "Madonah"; /* case 2 */

前者是指向字符串文字的指针。后者是一个 7 个字符的数组。

首选哪一个取决于您对name3 的使用。

如果您打算修改字符串,那么您可以使用 (1),我也会将其设为 const char* 以明确并确保不会意外修改字符串文字.修改字符串文字是 C 中的undefined behaviour

如果您确实需要修改它,那么应该使用 (2),因为它是一个可以修改的字符数组。需要注意的一点是,在情况 (2) 中,您已将数组的大小明确指定为 7。这意味着字符数组 name3 最后没有空终止符 (\0)。所以它不能用作字符串。我宁愿不指定数组的大小,让编译器自己计算:

char name3 [] = "Madonah"; /* case 2 */
/* an array of 8 characters */

【讨论】:

    【解决方案3】:

    除了其他人所说的,我将添加一张图片以更好地说明。如果你有

    char a[] = "hello";
    char *p = "world";
    

    在第一种情况下,通常会在堆栈上为a(6 个字符)分配足够的内存,并将字符串“hello”复制到从a 开始的内存中。因此,您可以修改此内存区域。

    在第二种情况下,“世界”被分配到其他地方(通常在只读区域中),并返回指向该内存的指针,该指针简单地存储在p 中。在这种情况下,您无法通过 p 修改字符串文字。

    这是它的外观:

    但是对于您的问题,请使用更容易的符号,我更喜欢[]。 有关数组和指针之间关系的更多信息是here

    【讨论】:

    • 哦,谢谢你的澄清。我从 cmets 中学到了很多东西,这张图解释了很多。我有一个结构,所以我现在知道如何使用 []。
    • 太棒了!我想知道为什么其他答案不包含这些图表。这就是解释指针和数组的方式。
    【解决方案4】:

    在所有情况下,在 C 中,pointer + indexpointer[index] 相同。此外,在 C 中,表达式中使用的数组名称被视为指向数组第一个元素的指针。当您考虑到加法是可交换的时,事情会变得更加神秘,这使得 index + pointerindex[pointer] 也合法。不管你怎么写,编译器通常会生成类似的代码。

    这允许使用很酷的东西,例如 "hello"[2] == 'l'2["hello"] == 'l'

    【讨论】:

    • 这个“pointer + indexpointer[index]相同”不正确。它应该是“取消引用pointer + indexpointer[index]相同”或“pointer + index与获取pointer[index]的地址相同”。
    【解决方案5】:

    使用数组语法进行随机访问很方便

    int getvalue(int *a, size_t x){return a[x];}
    

    顺序访问的指针算术语法

    void copy_string(char *dest, const char *src){while((*dest++=*src++));}
    

    许多编译器可以比使用数组索引更好地优化对指针的顺序访问。

    【讨论】:

    • 请注意,对于一个参数,char *xchar x[] 是相同的。对于数组,x 调整为指针。
    • @Olaf 只是数组索引经常不在 size_t (或 ptrdiff_t)中并导致额外的 mov 指令。
    • 我绝对不会在这里添加优化。现代编译器很可能会将索引访问转换为指针算术,因此没有实际区别。对于索引变量的类型:我同意,但实际上更喜欢 size_t,但ptrdiff_t 是一个有趣的点,我没有考虑。
    【解决方案6】:

    对于练习 C 中的指针和数组习语,这些都是有效的代码块,并说明了表达同一事物的不同方式。

    对于生产代码,您不会使用其中任何一个;您将使用一些更易于人类阅读且更有可能被编译器优化的东西。你会完全放弃循环,然后说:

    printf("%s", name);
    

    (请注意,这需要name 在字符串末尾包含\0 字符,printf 依赖于该字符。您的name1name3 定义,如所写,不分配必要的8字节。)

    如果您尝试在代码中做一些棘手的事情,需要您发布的更冗长的方法之一,您选择哪种方法将取决于您正在尝试做的棘手事情(您当然会在代码中解释cmets - 否则,您会在六个月后查看自己的代码并问自己,“我到底在做什么??”)。一般来说,对于从字符串中提取字符,name[i]*(name+i) 更常见。

    【讨论】:

    • 为什么要打印字符串?而不是字符?
    【解决方案7】:
    1. char name[] = "Madonah";
    2. char* name = "Madonah";

    当在函数中声明时,第一个选项会在每次调用函数时产生额外的操作。这是因为每次调用函数时,都会将字符串的内容从 RO 数据部分复制到堆栈中。

    在第二个选项中,编译器简单地将变量设置为指向该字符串在内存中的地址,该地址在整个程序执行过程中保持不变。

    因此,除非您打算更改本地数组的内容(不是原始字符串 - 那个是只读的),否则您可以选择第二个选项。

    请注意,以上所有细节都取决于编译器实现,而不是由 C 语言标准规定。

    【讨论】:

      【解决方案8】:

      我能知道哪个好用吗[...]

      尝试坚持使用索引运算符[]

      有一次您的代码变得更复杂,然后您注意到使用指针算术表达式编写代码“更容易”。

      当您也开始使用地址运算符时,通常会出现这种情况:&amp;

      例如,如果您发现自己需要编码:

      char s[] = "Modonah";
      char * p = &s[2]; /* This gives you the address of the 'd'. */
      

      您很快就会发现它“更容易”编写:

      char * p = s + 2; /* This is the same as char * p = &s[2];. */
      

      【讨论】:

        【解决方案9】:

        数组只是一个包含多个值的变量,每个值都有一个索引,你可能已经知道了。
        在您意识到需要使用它们之前,指针是您不需要知道的事情之一。指针本身不是变量,它们实际上是指向变量的指针。
        一个示例,说明您可能希望使用指针的方式和原因。
        您在一个函数中创建了一个要在另一个函数中使用的变量。
        您可以将变量传递给函数头中的新函数。 这有效地将原始变量中的值复制到新函数本地的新变量中。在新函数中对其进行的更改只会更改新函数中的新变量。
        但是,如果您希望在新函数中进行更改以更改它在第一个函数中的原始变量呢?
        您使用指针。
        不是将实际变量传递给新函数,而是传递一个指向变量的指针。现在,对新函数中的变量所做的更改会反映在第一个函数中的原始变量中。
        这就是为什么在您自己的示例中使用指向数组的指针并在同一个函数中使用实际数组具有相同结果的原因。他们都说“改变这个数组”。

        【讨论】:

          猜你喜欢
          • 2020-11-02
          • 2016-11-17
          • 1970-01-01
          • 2015-02-12
          • 2021-12-02
          • 1970-01-01
          • 1970-01-01
          • 2013-04-29
          相关资源
          最近更新 更多