【问题标题】:CS50 Week 4 are these 2 different ways of creating strings?CS50 第 4 周是这两种不同的创建字符串的方式吗?
【发布时间】:2021-02-24 21:23:08
【问题描述】:

据我了解,char* 是指向我们分配的内存的第一个 char 字节的指针。 Char z[4] 创建一个由 4 个字符组成的数组,而 z 是指向第一个字符的指针?这些是一样的吗?

z[4] 是否分配内存?

  char *s = malloc(5);
  char z[4];

【问题讨论】:

  • s 指向一个 5 字节的块(可以指向其他地方),z 一个 4 字节的块(不能指向是别的什么)。在许多用途中,z 被静默转换为指向这 4 个字节中第一个字节的指针。
  • 更正:s指向一个5字节块的第一个字节...
  • z 不是指针。想象它是一个常见的混淆来源。 (说“数组是常量指针”之类的话的讲师是问题的很大一部分。)

标签: arrays c pointers cs50 implicit-conversion


【解决方案1】:

我认为没有图片就无法解释/理解这些。

char z[4];

这为您提供了一个由 4 个字符组成的数组,名称为 z

   +---+---+---+---+
z: |   |   |   |   |
   +---+---+---+---+

这可以容纳最多三个字符的字符串(为结尾的 \0 留出空间)。

char *s = malloc(5);

char *s 部分为您提供指向char 的指针,它可以指向任何位置的任何字符。 malloc(5) 部分实际上为您提供了内存中某处(更正式地,在“堆”上)的 5 个字符的无名数组。然后=(赋值)运算符将前者指向后者。你可以想象它是这样的:

   +---------------+
s: |       *       |
   +-------|-------+
           |
           V
         +---+---+---+---+---+
         |   |   |   |   |   |
         +---+---+---+---+---+

malloc 区域为最多 4 个字符的字符串提供空间。

因为指针可以指向任何地方,所以您可以稍后重新分配它们。例如,您可以让s 指向z 数组的第一个字符,通过说

s = &z[0];

那么你会有这张照片:

         +---+---+---+---+
      z: |   |   |   |   |
         +---+---+---+---+
           ^
           |
   +-------|-------+
s: |       *       |
   +---------------+
           
           
         +---+---+---+---+---+
         |   |   |   |   |   |
         +---+---+---+---+---+

但现在我们来到了“有趣”的部分。一开始这听起来很奇怪或荒谬,但一旦你明白了,突然之间,C 对指针和数组(以及字符串)的处理就会变得更有意义。如果你说呢

s = z;

起初这可能没有意义。看起来我们正在尝试获取一个数组(在右侧)并将其分配给一个指针(在左侧)。这看起来像是类型不匹配。你也可能听说过(真的)你不能在 C 中分配数组。所以这看起来是不可能的。 但是,根据丹尼斯·里奇的特殊安排,你可以做到这一点,根据定义,就像你说的一样

s = &z[0];

即:每当你尝试在表达式中使用数组时,你得到的是指向数组第一个元素的指针。也就是说,在这个例子中,就像你说过&z[0].

确实,在 C 语言中存在两种传递字符串的方法:作为数组和作为指针。你可以用数组的方式说

char y[] = "cat";
char z[4];
strcpy(z, y);

或者你可以用指针的方式说

char *s = "bear";
char *t;

t = s;

这两种方法都将一个字符串“分配”给另一个字符串,但其中一种是通过复制字符(使用strcpy)来实现的,其中一种是通过操作指针来实现的。

我还掩盖了一点。当我说

char y[] = "cat";

编译器为我初始化了数组,给了我这个:

   +---+---+---+---+
y: | c | a | t | \0|
   +---+---+---+---+

但是当我说

char *s = "bear";

编译器在某处匿名内存中为我创建了字符串"bear",有点像(但绝对不完全像)当我调用malloc时:

   +---------------+       +---+---+---+---+---+
s: |       *-------------->| b | e | a | r | \0|
   +---------------+       +---+---+---+---+---+

您也可以在一定程度上混搭。正如我们之前看到的,你可以让一个指针指向一个数组,这样就可以了:

t = z;

但是,当您复制字符串时,您必须确保有足够的空间。如果你说

strcpy(z, "kangaroo");

例如,事情可能会爆炸,因为“kangaroo”的字符数超过了数组z 可以容纳的字符数。

您还必须小心使用常量字符串。如果你刚刚说

char *s = "bear";

你不能说

strcpy(s, "kangaroo");

有两个原因:一是s指向的匿名数组不够大,但也许更重要的是s指向的匿名数组是常量,不允许乱写就可以了。

【讨论】:

    【解决方案2】:

    声明了两种不同类型的对象:指针和数组。

    char *s = malloc(5);
    char z[4];
    

    注意:我将使用 char[5] 类型而不是 char[4] 类型来使分配的元素数量相等。

    要查看差异,您可以运行以下简单程序。

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void) 
    {
        char *s = malloc(5);
        char z[5];
        
        printf( "sizeof( s ) = %zu\n", sizeof( s ) );
        printf( "sizeof( z ) = %zu\n", sizeof( z ) );
        
        free( s );
        
        return 0;
    }
    

    程序输出可能看起来像

    sizeof( s ) = 8
    sizeof( z ) = 5
    

    指向数组类型对象的指针看起来像

    char ( *p )[5] = &z;
    

    虽然指向指针类型的指针看起来像

    char **p = &s;
    

    在其他情况下(除了使用字符串文字来初始化数组),数组指示符确实会转换为指向其第一个元素的指针。

    来自 C 标准(6.3.2.1 左值、数组和函数指示符)

    3 除非它是 sizeof 运算符的操作数或一元 & 运算符,或者是用于初始化数组的字符串文字, 具有“类型数组”类型的表达式被转换为 类型为“pointer to type”的表达式,指向初始 数组对象的元素并且不是左值。如果数组对象 有注册存储类,行为未定义

    至于你的问题

    z[4] 是否分配内存?

    然后你声明了一个为其分配内存的数组。

    【讨论】:

      【解决方案3】:

      据我了解 char* 是指向我们的第一个 char 字节的指针 分配的内存。 Char z[4] 创建一个由 4 个字符组成的数组,z 是一个 指向第一个字符的指针?

      不,z 是一个由 4 个char 组成的数组。在它出现在表达式中的大多数地方,它的(数组)值会自动转换为指向数组中第一个char 的指针(它“衰减”为指针),但这是一个非常不同的东西。

      这些是一样的吗?

      不,一点也不。但是z衰减到的指针值与s的类型相同。

      z[4] 是否分配内存?

        char *s = malloc(5);
        char z[4];
      

      声明char z[4]; 声明了一个包含四个char 的数组,并确保为此类对象提供内存。因此,是的,它会分配内存,但它不会动态地malloc() 那样分配内存。

      比较前面的语句:它声明了一个指向 char 的指针并确保为指针提供内存,但它也调用malloc() 来获取该指针的初始值。假设malloc() 成功,在这种情况下会有两个 分配:一个所谓的“自动”分配给s,一个动态分配给s 最初指向的对象。

      假设这两个声明出现在块范围内,当包含它们的最内层块的执行终止时,它们的生命周期结束,然后它们占用的内存自动可供重用。 malloc() 分配的内存并非如此。该内存块的生命周期仅在它为free()d 时结束,无论s 发生什么情况。如果您丢失了指向该内存的指针,例如通过在释放块之前让s 的生命周期结束,而不以某种方式保留指针值,则在程序运行期间无法释放块 - 这是已知的作为“内存泄漏”。

      【讨论】:

        【解决方案4】:

        char z[4] 创建一个由 4 个字符组成的数组,z 是指向第一个字符的指针?这些是一样的吗?

        它们不一样,z是一个4个字符的数组,它不是一个指针,它的类型不同于s,它确实是一个指向char的指针。后者可以指向字符数组中的任何给定字符,您分配的字符或任何其他字符,例如,它也可以指向z 中的字符。

        z[4] 是否分配内存?

        两者使用的存储类型不同z存储在堆栈中,假设它是在函数内部声明的,(它不是全局变量) ,而s,或者更好的是,s指向的内存是存放在堆上的,这就是所谓的动态分配的数组,第一个是自动处理的,后者是手动处理的,你必须自己保留内存,因为您确实使用malloc 保留了一个可以存储 5 个字符的内存块。

        这里有更多关于内存管理的信息:

        Where in memory are my variables stored in C?

        在存储少量数据时应优先考虑堆栈,因为访问速度更快,但它的大小非常有限,因此如果您有大数据缓冲区,则必须将它们存储在堆中。

        如果您想了解有关此事的更多详细信息,可以查看此帖子作为参考:

        What and where are the stack and heap?

        另一个区别是s的生命周期仅限于它所属的范围的生命周期,并且总是具有相同的大小,不能重新调整大小,而s指向的内存不限于一个给定范围,并且可以调整大小,您还必须跟踪它并最终在不再需要它时释放它,否则会导致内存泄漏。

        【讨论】:

        • 尽管有几个 Stack Overflow 答案都这么说,但我不相信 z 被放入堆栈除非它在函数内部声明。其他几个答案说C规范实际上并没有指定这些存储的位置。作为一个全局变量,z 的大小在编译时是已知的,因此将它存储在堆栈中并没有多大意义。
        • @RobertHarvey 我以为是,但你是对的,必须提及。
        猜你喜欢
        • 1970-01-01
        • 2011-11-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多