CS50 第 4 周是这两种创建字符串的不同方式吗?

CS50 Week 4 are these 2 different ways of creating strings?

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

z[4]是否分配内存?

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

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

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 Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined

关于你的问题

Does the z[4] allocate memory?

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

char z[4] creates a array of 4 chars and z is a pointer to the first char? Are these the same thing?

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

中的字符

Does the z[4] allocate memory?

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

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

Where in memory are my variables stored in C?

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

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

What and where are the stack and heap?

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

from my understanding char* is a pointer to the first char byte of our allocated memory. Char z[4] creates a array of 4 chars and z is a pointer to the first char?

不是,z是4个char的数组。在它出现在表达式中的大多数地方,它的(数组)值自动 转换为 指向数组中第一个 char 的指针(它“衰减”为指针), 但这是完全不同的事情。

Are these the same thing?

不,一点也不。但是 z 衰减到的指针值与 s.

具有相同的类型

Does the z[4] allocate memory?

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

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

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

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

我认为 explain/understand 这些没有图片是不可能的。

char z[4];

这为您提供了一个包含 4 个字符的数组,名称为 z:

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

这最多可以容纳三个字符长的字符串(为尾部 [=29=] 留出空间)。

char *s = malloc(5);

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

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

malloc'ed 区域为您提供了最多 4 个字符长的字符串空间。

因为指针可以指向任何地方,所以您可以稍后重新分配它们。例如,您可以通过说

使 s 指向 z 数组的第一个字符
s = &z[0];

那么你会有这张照片:

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

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

s = z;

起初这可能没有意义。看起来我们正在尝试获取一个数组(右侧)并将其分配给一个指针(左侧)。这看起来像是类型不匹配。您也可能听说过(真的)您不能在 C 中分配数组。所以这看起来是双重不可能的。 但是,根据 Dennis Ritchie 的特别许可,您 可以 这样做,并且根据定义,它与您所说的 完全相同

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 | [=21=]|
   +---+---+---+---+

但是当我说

char *s = "bear";

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

   +---------------+       +---+---+---+---+---+
s: |       *-------------->| b | e | a | r | [=23=]|
   +---------------+       +---+---+---+---+---+

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

t = z;

然而,当您复制字符串时,您必须确保有足够的 space。如果你说

strcpy(z, "kangaroo");

例如,事情可能会爆炸,因为“袋鼠”的字符多于数组 z 可以容纳的字符。

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

char *s = "bear";

你不能说

strcpy(s, "kangaroo");

有两个原因:第一,s指向的匿名数组不够大,但也许更重要的是,s指向的匿名数组是常量,你不准在上面乱写