在C的引擎盖下会发生什么?

时间:2017-02-18 18:51:47

标签: c arrays string

将自己从Python转移到C进行算法课程,我很难理解常见字符串在这个新地狱中的作用。

根据我的理解:

  • 在C中,本身没有字符串,而是字符数组。
  • 数组的变量名称指向数组中第一个元素的地址(在内存中排列),因此无需指出每个字符。

令我困惑的是:

char greeting[] = "Hello world";
printf("%s", greeting); 

1)为什么没有必要将数组传递给问候[]如{" H"," e"," l",&# 34; l"," o"}等,但单个字符串就够了?

2)为什么printf打印出整个消息,当它实际上是一个简单的数组?在prinf中使用字符串格式是否通过for循环,在没有换行的情况下打印出每个元素?

char *greeting = "Hello world";
printf("%s", greeting);

3)什么?让我猜一下...... C接受插入的字符串,得到它的长度,创建一个字符数组然后做点(2)魔术?指针变量做什么样的shenigans?东西[] ==& a AND a [0] == * a ???

char *moreGreetings[] = {"Hello", "Greetings", "Good morning"};
printf("%s", moreGreetings[0]); // Returns "Hello"

4)我再也不能......为什么调用moreGreetings [0]会调出整个字符数组" Hello" ???

除非有一堆诡计在幕后进行,否则我不知道这有什么意义。有人可以解释发生了什么吗?

5 个答案:

答案 0 :(得分:4)

  

1)为什么没有必要将数组传递给问候[]如{" H"," e"," l",&# 34; l"," o"}等,但单个字符串就够了?

因为C语法允许"字符串"文字,是表示C风格字符串的简写方式。

顺便说一下,{"H", "e", "l", "l", "o"}是一个字符串数组,而不是char的数组。字符数组如下所示:{'H', 'e', 'l', 'l', 'o'},但"Hello"实际上代表数组{ 'H', 'e', 'l', 'l', 'o', '\0' }(字符串的工作方式是在结尾处使用字符串终止符\0)。< / p>

  

2)为什么printf打印出整个消息,当它实际上是一个简单的数组?在prinf中使用字符串格式是否通过for循环,在没有换行的情况下打印出每个元素?

%s令牌告诉printf您希望它将值视为&#34;字符串&#34;,因此它将其作为一个处理,逐个打印字符直到遇到字符串终止字符\0,它自动出现在任何&#34;字符串的末尾#34;你使用字符串文字语法创建。

  

3)什么?让我猜一下...... C接受插入的字符串,得到它的长度,创建一个字符数组然后做点(2)魔术?指针变量做什么样的shenigans?东西[] ==&amp; a AND a [0] == * a ???

我不知道这个问题意味着什么。

  

4)我再也不能......为什么调用moreGreetings [0]会调出整个字符数组&#34; Hello&#34; ???

moreGreetings是一个字符串数组(或指向chars数组的指针数组,如果您愿意的话)。所以moreGreetings[0]是该数组中的第一个元素,即&#34;字符串&#34; "Hello"。如果您将其传递到printf并使用%s告诉它将值视为字符串,那么它将会。

答案 1 :(得分:4)

计算机是外星人。他们认为没有像我们这样做。 计算机不知道字符串是什么。

编程语言是人与外星的翻译。 Python就像阅读一本习惯翻译的书。 C就像阅读一个字面翻译,即便如此,它也做了很多工作。

  

1)为什么没有必要将数组传递给问候[]如{&#34; H&#34;,&#34; e&#34;,&#34; l&#34;,&# 34; l&#34;,&#34; o&#34;}等,但单个字符串就够了?

编译器会为您处理。你也在最后错过了空字节。而那些不是人物。

C是最终的DIY语言。来自Python,它可能 非常 迷失方向。 C给你最低限度(是的,我看到大会程序员在后面挥动你的手臂,不要让事情变得复杂)。这样做A)非常快,B)让你建造任何东西。不幸的是,它并不总是以最明显的方式做到这一点。如果您不了解C语言中的内容,计算机内存的工作原理,您就会遇到麻烦。

例如,请注意" vs ''H'是单个字符H,实际上是短整数(即1个字节)整数72(确切的数字取决于您的语言环境)。 "H"是一个双字符数组{'H', '\0'},它实际上是{72, 0}

要理解C和所有数组中的字符串,关键是他们只是将一大块内存分成1个字节的块。那是 it 。他们甚至不会存储自己的长度,你必须将其存储在其他地方(比如在结构中)或者用某些东西终止列表。

C字符串是一块内存,分为1个字节块,以空字节(即0)结尾。就是这样。这些在概念上等同于

const char *string = "Hello";
char string[] = {'H', 'e', 'l', 'l', 'o', '\0'};

两者都包含相同的字节,它们的存储方式不同。

  

2)为什么printf打印出整个消息,当它实际上是一个简单的数组?在prinf中使用字符串格式是否通过for循环,在没有换行的情况下打印出每个元素?

printf有点像Python的str。你告诉它如何将东西转换为字符,然后它将转换为东西。 %s表示它是由空字节终止的字符数组。 %d说它是一个整数。 %f表示浮点数。所有这些内容在内存中的表示方式不同,需要对字符进行不同的转换。

printf实际如何运作是一个实施细节,但自己实施它是一个很好的练习。你可以用for循环一次写出一个字节并在空字节处停止。

for( const char *pos = string; pos[0] != '\0'; pos++ ) {
    putchar(pos[0]);
}

请注意,我不是通过数组索引,而是向前移动数组的开头。 string只不过是指向数组开头的指针。通过将其复制到pos,我可以更改该指针而不会影响string。这避免了必须为索引分配额外的整数,并且它避免了必须对数组查找进行额外的数学运算。 pos[0]仅在pos之后读取1个字节。

是的,如果你忘记了空字节,它会继续读取字符串末尾的内存,直到它看到0或操作系统因为超出界限而触发它。这个过程。

  

3)什么?让我猜一下...... C接受插入的字符串,得到它的长度,创建一个字符数组然后做点(2)魔术?指针变量做什么样的shenigans?东西[] ==&amp; a AND a [0] == * a ???

不,C字符串不存储长度。为了获得长度,他们必须遍历整个字符串,然后再次遍历整个字符串以打印它。相反,它们打印到空字节。

  

4)我再也不能......为什么调用moreGreetings [0]会调出整个字符数组&#34; Hello&#34; ???

因为moreGreetings是指向更多字符数组的指针数组。 char *moreGreetings[] 大致等同于char **moreGreetings。它是指向字符指针的指针。

它是一个字符串数组,你要求第一个字符串,所以你得到一个字符串。

请记住, Python是用C 编写的(是的,现在还有其他实现)。 C是堆栈的底部(几乎)。 Python和其他所有程序最终都必须处理这些相同的&#34; shenanigans&#34; C确实如此,但实际上它处理的是计算机如何工作的现实。

他们通常不会使用C字符串,因为他们非常笨拙且容易出错,他们构成了自己的字符串,但是他们仍然用数字填充固定大小的内存并调用它们& #34;串&#34;

我能给你的最好建议是打开编译器警告。他们都是! C编译器警告可以揭示许多简单的错误,但默认情况下它们会关闭。打开它们的典型方法是-Wall,但并非所有警告。有很多很多额外的东西。这是我在Makefile中使用的公式(有一个Makefile)。

CFLAGS  += -Wall -Wshadow -Wwrite-strings -Wextra -Wconversion -std=c99 -pedantic $(OPTIMIZE)

开启&#34;所有&#34;警告,&#34;额外&#34;警告,以及我发现有用的一些其他特定警告。它说我从1999年开始使用ISO C标准(稍后会详细介绍),我希望编译器能够遵循标准,因此我的代码可以在编译器和环境之间移植。我做了很多开源工作,但是当你开始工作时它很好,所以你不会沉迷于非标准的编译器扩展。

关于标准。 C很老了,并且在1990年才被标准化。许多人学会了使用非标准C进行编码,你会在很多C教材中看到它。即使有一个2011年的标准,许多C程序员也会编写和教授C90甚至更早。甚至C99也被认为是新的&#34;很多人Visual Studio在标准合规性方面特别糟糕,但它们最终都在追赶最新版本。

答案 2 :(得分:1)

  

为什么没有必要将数组传递给问候[],如{&#34; H&#34;,&#34; e&#34;,&#34; l&#34;,&#34; l&#34;,&#34; o&#34;}等,但单个字符串就足够了吗?

确实可以将"Hello"指定为数组。

char greetings[] = {'H', 'e', 'l', 'l', 'o', '\0'};

但是这个作业很难写,所以char greetings[] = "Hello"将是一个捷径。但这两项任务是一样的。

  

为什么printf打印出整条信息?

printf具有不同的行为,具体取决于它接收的格式参数。当你要求printf以字符串格式%s打印一个值时,它会获取一个指向一个字符的指针,并逐个打印它的值及其后续字符,直到它到达空终止符\0。 / p>

  

为什么调用moreGreetings [0]会调出整个字符数组&#34; Hello&#34;?

指向数组的指针是指向该数组的第一个元素的指针。因此,在printf("%s", greetings[0]);printf("%s", greetings);中,您传递的指针指向相同的内存位置,从而产生相同的输出。

答案 3 :(得分:0)

  1. 它是一种语言功能 - 您可以使用字符串文字初始化字符数组,它可以执行您的意思,即char greeting[] = "foo"将被解释为char greeting[] = {'f', 'o', 'o', '\0'}。这没有任何代价,因为否则char greeting[] = "foo"将是编译时错误。

  2. Google C阵列衰变。简而言之,传递一个指向预期指针的数组就好像传递了指向数组第一个元素的指针一样。这在许多情况下都很有用,特别是对于字符串。

  3. 见#2。

  4. 因为您声明了一个指向char(字符串)的指针数组,并且正在将这些指针中的第一个传递给printf。它等同于撰写printf("%s", "Hello")

答案 4 :(得分:0)

1)为什么没有必要将数组传递给问候[]如{&#34; H&#34;,&#34; e&#34;,&#34; l&#34;,&# 34; l&#34;,&#34; o&#34;}等,但单个字符串就够了?

当你传递一个数组或一个字符串时(事实证明它们都是一样的),你给出了数组中第一个元素的内存地址。因为数组元素一个接一个地存储在内存中,所以访问数组中的下一个元素(或字符串中的字符)所需要的只是增加传递的内存地址。

2)为什么printf打印出整个消息,当它实际上是一个简单的数组?在prinf中使用字符串格式是否通过for循环,在没有换行的情况下打印出每个元素?

通常,所有系统支持都是一个简单的putchar()函数调用。为了使用更方便的IO功能,创建了库。 printf函数可能使用for循环来打印字符串中的每个元素。

3)什么?让我猜一下...... C接受插入的字符串,得到它的长度,创建一个字符数组然后做点(2)魔术?指针变量做什么样的shenigans?东西[] ==&amp; a AND a [0] == * a ???

C编译器会计算字符串的长度。我只是想澄清一下,这不会在运行时发生,它发生在编译时。在运行时,字符串由其指针引用。

指针变量只是一个普通变量。它只包含一些内存地址。为了让编译器知道如何处理指针,给指针一个类型,即int *,char *。

注意:如果没有引用类型,就会出现void *。

当程序想要直接访问某个指针所指向的内存位置时,让我们将其称为int * p,它只是将p的值增加p ++或p + 1.