了解声明C字符串的两种方法

时间:2014-12-25 05:13:54

标签: c string types char declaration

几周前,我开始学习编程语言C.我对网络技术有所了解,比如HMTL / CSS,Javscript,PHP和基本的服务器管理,但是C让我很困惑。据我所知,C语言没有字符串的数据类型,只有字符,但是我可能错了。

我听说有两种声明字符串的方法。这两行声明字符串有什么区别:

a。)char stringName[];
b。)char *stringName;

我知道char stringName[];是一个字符数组。但是,第二行让我感到困惑。据我所知,第二行是一个指针变量。 Aren的指针变量应该是另一个变量的内存地址吗?

3 个答案:

答案 0 :(得分:8)

在C语言中,正如您所说,“字符串”是char的数组。 C规范中内置的大多数字符串函数都希望字符串为“NUL终止”,这意味着字符串的最后char0。不是代表数字零的代码,而是0的实际值。

例如,如果您的平台使用ASCII,则以下“字符串”为“ABC”:

char myString[4] = {65, 66, 67, 0};

当您使用char varName[] = "foo"语法时,您将在堆栈上分配字符串(或者如果它在全局空间中,您将全局分配,但不是动态分配。)

C中的内存管理比您可能遇到的许多其他语言更加手动。特别是,存在“指针”的概念。

char *myString = "ABC"; /* Points to a string somewhere in memory, the compiler puts somewhere. */

现在,char *是“指向char或char数组的地址”。请注意该声明中的“或”,程序员必须知道具体情况。

确保您执行的任何字符串操作都不会超过您为指针分配的内存量,这一点很重要。

char myString[5];
strcpy(myString, "12345"); /* copy "12345" into myString. 
                            * On no! I've forgot space for my nul terminator and 
                            * have overwritten some memory I don't own. */

“12345”实际上是6个字符(不要忘记最后的0),但我只保留了5个字符。这就是所谓的“缓冲区溢出”,并且是导致许多严重错误的原因。

“[]”和“*”之间的另一个区别是,正在创建一个数组(正如您猜测的那样)。另一个不是保留任何空间(除了保持指针本身的空间。)这意味着直到你指出它知道有效的地方,不应该使用指针的值来读或写。

另一点(由评论中的某人制作)

您不能将数组作为参数传递给C中的函数。尝试时,它会自动转换为指针。这就是为什么我们将指针传递给字符串而不是字符串本身

答案 1 :(得分:3)

在C中,字符串是字符值序列,后跟0值字节 1 。处理字符串的所有库函数都使用0终止符来标识字符串的结尾。字符串存储作为char的数组,但并非所有char数组都包含字符串。

例如,字符串"hello"表示为字符序列{'h', 'e', 'l', 'l', 'o', 0} 2 要存储字符串,您需要一个6个元素的char数组 - 5个字符加0终结符:

char greeting[6] = "hello";

char greeting[] = "hello";

在第二种情况下,数组的大小是根据用于初始化它的字符串的大小计算的(计算0终止符)。在这两种情况下,您都要创建一个包含char的6元素数组,并将字符串文字的内容复制到其中。除非在文件范围(任何函数的oustide)或static关键字声明数组,否则它仅在声明的块的持续时间内存在。

字符串 literal "hello"也存储在char的6元素数组中,但它的存储方式是在程序加载时分配的进入内存并保持到程序终止 3 ,并在整个程序中可见。当你写

char *greeting = "hello";

您要将包含字符串文字的数组的第一个元素的地址分配给指针变量greeting

一如既往,一张图片胜过千言万语。这是一个简单的小程序:

#include <string.h>
#include <stdio.h>
#include <ctype.h>

int main( void )
{
  char greeting[]   = "hello";  // greeting contains a *copy* of the string "hello";
                                // size is taken from the length of the string plus the
                                // 0 terminator

  char *greetingPtr = "hello";  // greetingPtr contains the *address* of the 
                                // string literal "hello"

  printf( "size of greeting array:              %zu\n", sizeof greeting );
  printf( "length of greeting string:           %zu\n", strlen( greeting ) );
  printf( "size of greetingPtr variable:        %zu\n", sizeof greetingPtr );

  printf( "address of string literal \"hello\":   %p\n", (void * ) "hello" );
  printf( "address of greeting array:           %p\n", (void * ) greeting );
  printf( "address of greetingPtr:              %p\n", (void * ) &greetingPtr );
  printf( "content of greetingPtr:              %p\n", (void * ) greetingPtr );

  printf( "greeting:                            %s\n", greeting );
  printf( "greetingPtr:                         %s\n", greetingPtr );

  return 0;
}

这是输出:

size of greeting array:              6
length of greeting string:           5
size of greetingPtr variable:        8
address of string literal "hello":   0x4007f8
address of greeting array:           0x7fff59079cf0
address of greetingPtr:              0x7fff59079ce8
content of greetingPtr:              0x4007f8
greeting:                            hello
greetingPtr:                         hello

请注意sizeofstrlen之间的差异 - strlen计算所有字符,直至(但不包括)0终结符。

所以这就是记忆中的样子:

Item            Address            0x00 0x01 0x02 0x03
----            -------            ---- ---- ---- ----
"hello"         0x4007f8            'h'  'e'  'l'  'l'
                0x4007fc            'o' 0x00  ???  ???
                ...
greetingPtr     0x7fff59079ce8     0x00 0x00 0x00 0x00
                0x7fff59879cec     0x00 0x40 0x7f 0xf8
greeting        0x7fff59079cf0      'h'  'e'  'l'  'l'
                0x7fff59079cf4      'o' 0x00  ???  ???

字符串文字"hello"存储在变化的低地址(在我的系统上,这对应于可执行文件的.rodata部分,用于静态的常量数据)。变量greetinggreetingPtr存储在更高的地址,对应于我系统上的堆栈。如您所见,greetingPtr存储字符串文字"hello"地址,而greeting存储字符串内容的副本。

这里的事情会让人感到困惑。让我们看看以下打印陈述:

printf( "greeting:                            %s\n", greeting );
printf( "greetingPtr:                         %s\n", greetingPtr );

greetingchar的6个元素数组,greetingPtr是指向char的指针,但我们将它们都传递给printf以完全相同的方式,正确打印出字符串;怎么办呢?

除非它是sizeof或一元&运算符的操作数,或者是用于在声明中初始化另一个数组的字符串文字,否则表达式类型为“ T“的N元素数组将被转换(”衰减“)为”指向T的指针“的表达式,表达式的值将是第一个元素的地址。阵列。

printf调用中,表达式greeting的类型为“6个元素数组char”;因为它不是sizeof或一元&运算符的操作数,所以它被转换(“衰减”)为“指向char”的类型的表达式({{1} }),第一个元素的地址实际传递给char *。 IOW,它与<{1}}调用 4 中的printf表达式一样完全

greetingPtr转换说明符告诉printf其对应的参数类型为%s,并且它应该打印出从该地址开始的字符值,直到它看到0终结符。

希望有所帮助。

<小时/> 1.通常被称为printf终结者;这不应该与char *指针常量混淆,后者也是0值但在不同的上下文中使用
2.您还会看到终止的0值字节写为NUL。前导反斜杠“转义”该值,因此不会被视为字符 NULL(ASCII 48),而是将其视为 {{1} }(ASCII 0))。

3.实际上,在生成的二进制文件中为它留出空间,通常在标记为只读的部分;尝试修改字符串 literal 的内容会调用未定义的行为。

4.这也是'\0' 声明将字符串内容复制到数组的原因,而'0'的声明复制了字符串第一个元素的地址。字符串文字0也是一个数组表达式。在第一个声明中,由于它用于初始化声明中的另一个数组,因此将复制数组的内容。在第二个声明中,目标是指针而不是数组,因此表达式从数组类型转换为指针类型,并将生成的指针值复制到变量中。

答案 2 :(得分:1)

在C(和C ++中)中,数组和指针的表示方式相似;数组由数组中第一个元素的地址表示(这足以获得对其他元素的访问,因为元素在数组内的存储器中是连续的)。这也意味着数组本身不会指示它的结束位置,因此您需要某种方法来识别数组的结尾,或者通过将长度作为单独的变量传递或者使用某些约定(例如有一个sentinel值放在数组的最后一个位置,表示数组的结束)。对于字符串,后者是常见的约定,使用&#39; \ 0&#39; (NUL字符)表示字符串的结尾。