如何理解字符串变量?它们是同时串和指针吗?

时间:2016-08-16 03:19:05

标签: c pointers

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

int main(int argc, const char * argv[]) {
    char str[] = "hello";
    printf("%s, %p", str, str);
    return 0;
}

上面的代码给出了输出

  

你好,0x7fff5fbff7aa

令我困惑的是,为什么str可以同时成为字符串和指针? 我知道字符串是指向char 的指针。所以我认为str只是pointer

但编译器如何知道%s给出了str指向的字符串?

编译器是如何工作的?

P.S

我认为,当我们将%c%i用于char变量并获得不同的输出时,情况会发生同样的情况。

5 个答案:

答案 0 :(得分:4)

在此代码中,str是一个数组。数组和指针是不同的。您可以创建一个指向数组元素的指针。

在代码printf("%s, %p", str, str);中,str的两个用法实际上都请求指向数组第一个元素的指针。你可以写&str[0]来表示同样的事情,但是从C开始就是一个设计决定,在大多数情况下写一个数组的名称实际上会请求这样的指针。

定义printf函数,如果它看到%s,则它跟随(取消引用)相应的指针并打印出字符,直到它到达空终止符。如果它看到%p,那么它会打印出指针本身的某种表示(而不是指针指向的那种)。

答案 1 :(得分:2)

char str[] = "hello";

如果您将str视为标识符,则它是一个字符数组

数组和指针的行为不同,例如,

sizeof(array);
// would give you the sizeof(type of array)*total elements in array
sizeof(pointer);
// would give you just the size of the pointer in your system , say 8 bytes

但是当将函数 decays to pointer传递给数组的第一个元素时的数组,如

printf("%s, %p", str, str);
// same as printf("%s, %p", &str[0], &str[0]);

这里str,虽然它是一个数组被认为是指向数组第一个元素的指针,即&str[0]

你得到的结果不同只是因为你使用了不同的格式说明符,即%s&amp; %p分别决定如何打印内容。

答案 2 :(得分:0)

printf函数用于使用相同的标识符str打印不同的值。

%s%p是格式说明符。对于%sprintf将从str给出的地址开始打印字符串。对于%pprintf将打印str给出的内存地址。

编译器在这里没有特殊的作用。但是,如果格式说明符和相应的参数之间存在类型不匹配,它可以警告您。例如,如果您执行printf("%s",10);,则可以警告这种不匹配,表示printf期待char * - %s - 但您传递的是int

PS :注意str是char数组 - 它与char指针不同 - 但是当你将它传递给函数时,它变成了一个char指针({{1指向其第一个元素的地址。

答案 3 :(得分:0)

这是一个新的C程序员很难与什么是数组交朋友?什么是指针?以及如何数组<当数组作为函数调用中的参数传递时,/ em>转换为指针。通过了解适用的一些简单的fules,可以很容易地理解所有这些。

首先,任何变量(例如a = 5;)其中a包含 int的地址以及构成该地址的字节数内存保存5立即值。因此,当您分配a = 5;时,您将变量标签a指向的内存设置为5的值。这里的关键是,从这个意义上讲,所有变量都可能指向内存中的某个位置。这里的区别在于正常变量指向的内存,正常变量指向的内存包含一些立即值5这里),而在这种情况下指针,指针变量指向memmory中的地址,其中可以找到其他地址。 (例如,指针只是一个指向某些类型的地址的变量,而不是某些)。

经典的例子值得再看一遍

int a = 5;    /* 'a' stores a memory address holding the value '5' as its value  */

int *pa = &a; /* pointer-to-a 'pa' stores the memory address of 'a' as its value */

应用于任何数组,数组的第一个元素是整个数组的起始地址。你可以这样想:

int a[] = { 1, 2, 3, 4 }; /* where &a[0] is the memory address for the array,
                             a[i] = *(a + i), thus &(*(a + 0)), is simply 'a' */

所以在char str[] = "hello";的情况下,字符数组的第一个元素(根据它的初始化方式(例如char array[] = "stuff";)将包含地址在初始化程序的开始和结束"之间给出的 nul-termminated 字符数组的第一个字符。

数组的第一个元素位于整个数组的地址,可由array[0]引用,正如我们上面所见,它等同于{{1} }或仅*(array + 0),然后使用 urnary *array运算符获取 & array地址只是&(*array)

这就是为什么您可以将array作为str本身(第一个元素的地址)传递给格式说明符的原因array打印为字符串,以及为什么您可以将%s传递给格式说明符 str来打印指针地址。

希望你在这一点上回答你的问题,但是......

这也是理解当字符数组 %p作为函数参数列表str(或任何数组类型)中的参数传递时的原因作为指针传递(即,当作为参数传递给函数时,任何数组中的第一级间接是转换到指针)数组作为指针传递的历史原因只需要做节省记忆。

表示,不需要传递数组中所有元素的副本,而只需要引用第一个元素的地址。

答案 4 :(得分:0)

在C中,字符串是一系列字符值,后跟一个0值终结符。例如,字符序列{'H', 'e', 'l', 'l', 'o', 0}是一个字符串,但{'H', 'e', 'l', 'l', 'o'}不是 - 0终结符有所不同。

字符串(包括字符串文字)存储作为char的数组。鉴于声明

char str[] = "Hello";

你会得到像

这样的东西
     +---+
str: |'H'| str[0]
     +---+ 
     |'e'| str[1]
     +---+ 
     |'l'| str[2]
     +---+
     |'l'| str[3]
     +---+
     |'o'| str[4]
     +---+
     | 0 | str[5]
     +---+

在记忆中。请注意,没有为指向数组第一个元素的指针留出存储空间。

在大多数情况下,表达式类型为&#34; N元素数组T&#34;将被转换(&#34;衰变&#34;)到类型为#34的表达式;指向T&#34;并且表达式的值将是数组的第一个元素的地址。此规则的例外情况是,数组表达式是sizeof或一元&运算符的操作数,或者表达式是用于在声明中初始化数组的字符串文字。

所以,让我们采取以下代码:

char str[] = "Hello";
char *ptr  = "World";

printf( "%s, %s\n", str, ptr );

字符串文字 "Hello""World""%s, %s\n"存储为char的数组,以便在程序启动时分配它们在程序的整个生命周期内可用。

"Hello""World""%s, %s\n"str都是数组表达式(它们都有类型&#34; N元素数组char&#34;)。在ptr的声明中,"World"数组表达式不是sizeof或一元&运算符的操作数,也不用于初始化{{1}的数组1}},所以表达式被转换(&#34;衰减&#34;)到#34;指向char&#34;的指针,表达式的值是第一个元素的地址对于数组,所以char最终会指向ptr的第一个字符。

同样,在"World"调用中,数组表达式printf"%s, %s\n"不是str或一元sizeof运算符的操作数,因此它们是也转换为指针表达式,这些指针值实际上是传递给&的。

但是,在printf的声明中,str字符串文字 用于初始化"Hello"数组,因此它是转换为指针表达式;相反,char使用字符串文字的内容进行初始化,其大小也由文字的大小决定。

这是我在系统上生成的代码的具体内存映射:

str

字符串文字 Item Address 00 01 02 03 ---- ------- -- -- -- -- "Hello" 0x400b91 48 65 6c 6c Hell 0x400b95 6f 00 30 30 o.00 "World" 0x400b60 57 6f 72 6c Worl 0x400b64 64 00 25 73 d.%s "%s, %s\n" 0x400b66 25 73 2c 20 %s,. 0x400b6a 25 73 0a 00 %s.. str 0x7fff7cec1a50 48 65 6c 6c Hell 0x7fff7cec1a54 6f 00 00 00 o... ptr 0x7fff7cec1a48 60 0b 40 00 `.@. 0x7fff7cec1a4c 00 00 00 00 .... 从地址"Hello"开始存储,0x400b91从地址"World"开始存储,格式字符串0x400b60开始存储在地址"%s, %s\n"(无论出于何种原因,编译器将0x400b66"World"放在彼此旁边。

数组"%s, %s\n"从地址str开始存储,它包含字符串文字0x7fff7cec1a50内容的副本。指针"Hello"从地址ptr开始存储,并包含字符串文字0x7fff7cec1a48地址(x86存储多字节值,如little-endian中的指针订购)。

"World"来电将收到指针printf0x400b660x7fff7cec1a50。格式字符串中的0x7fff7cec1a48转换说明符表示&#34;打印从地址开始的字符序列并继续,直到我看到0终结符&#34;。