2010-01-23 129 views
18

我现在在学习C语言,并且对字符数组 - 字符串有点困惑。C字符串混淆

这个没问题 - 它的一个数组,可容纳(?最多)15个字符

char name[]="Fortran"; 

ç计数为我的字符数,所以我不必 - 整齐!

char* name; 

好的。现在怎么办?我所知道的是,这可以容纳大量后来分配的字符(例如:通过用户输入),但是

  • 为什么他们称之为字符指针?我知道指针是变量的参考
  • 这是一个“借口”吗?这是否发现任何其他用途比在char *?
  • 这究竟是什么?它是一个指针吗?你如何正确使用它?

由于事先 喇嘛

回答

32

我认为这可以解释这种方式,由于一个图像相当于一千字s ...

我们将从char name[] = "Fortran"开始,这是一个字符数组,长度在编译时已知,确切的说是7,对吗?错误!它是8,因为'\ 0'是一个nul结尾字符,所有字符串都必须有。

 
char name[] = "Fortran"; 
+======+  +-+-+-+-+-+-+-+--+ 
|0x1234|  |F|o|r|t|r|a|n|\0| 
+======+  +-+-+-+-+-+-+-+--+ 

在链接时,编译器和链接给符号name的一个0x1234的内存地址。 例如,使用下标运算符(即name[1]),编译器知道如何计算内存中位于偏移量0x1234 + 1 = 0x1235处的字符,并且确实为'o'。这很简单,此外,对于ANSI C标准,数据类型的数据类型的大小为1个字节,这可以解释运行时如何获得该语义的值name[cnt++],假设cnt是一个integer并且具有值3例如,运行时自动递增1,并从零开始计数,偏移值为't'。迄今为止这很简单。

如果执行name[12]会发生什么情况?那么,代码将会崩溃,否则你会得到垃圾,因为数组的边界是从索引/偏移0(0x1234)到8(0x123B)。之后的任何内容都不属于name变量,这将被称为缓冲区溢出!

name在内存地址为0x1234,而作为例子,如果你做到这一点:

 
printf("The address of name is %p\n", &name); 

Output would be: 
The address of name is 0x000

为了简洁起见,用的例子保持一致,内存地址是32位,因此,你看到额外的0。很公平?好吧,让我们继续前进。

现在到指针... char *name是一个指针类型的char ....

编辑: 我们初始化如图感谢丹的指出了一点误差为NULL ...

 
char *name = (char*)NULL; 
+======+  +======+ 
|0x5678| -> |0x0000| -> NULL 
+======+  +======+ 

在编译/链接时,该name没有指向任何东西,但对于符号编译/链接时的地址name(0x5678),实际上它是NULL,因此name的指针地址是未知因此0x0000。

现在,请记住这是至关重要的,符号的地址是在编译/链接时已知,但指针的地址是未知的,与任何类型的指针

假设我们做交易时此:

 
name = (char *)malloc((20 * sizeof(char)) + 1); 
strcpy(name, "Fortran"); 

我们称为malloc分配一个内存块为20个字节,不,它不是21,I上的尺寸增加1的原因是为“\ 0” NUL结束符。假设在运行时,给出的地址为0x9876,

 
char *name; 
+======+  +======+   +-+-+-+-+-+-+-+--+ 
|0x5678| -> |0x9876| -> |F|o|r|t|r|a|n|\0| 
+======+  +======+   +-+-+-+-+-+-+-+--+ 

所以,当你这样做:

 
printf("The address of name is %p\n", name); 
printf("The address of name is %p\n", &name); 

Output would be: 
The address of name is 0x00005678 
The address of name is 0x00009876 

现在,这是那里的错觉,“数组和指针是一样的进场这里

当我们这样做:

 
char ch = name[1]; 

什么发生在运行时是这样的:

  1. 符号name的地址查找
  2. 取该符号,即0x5678的内存地址。
  3. 在该地址,包含另一个地址,一个指向内存的指针地址,并获取它,即0x9876
  4. 获取基于下标值1的偏移量并将其添加到指针地址,即0x9877以检索那个内存地址,即'o'并被分配到ch

,上面是理解这种区别的关键,数组和指针的区别就是运行时如何获取数据,在指针上,有获取额外的间接。

记住类型T的阵列将总是衰变成类型T的第一个元素的指针。

当我们这样做:

 
char ch = *(name + 5); 
  1. 符号name的地址查找
  2. 取该符号,即0x5678的内存地址。
  3. 在这个地址,包含了另一个地址,指针地址,内存,把它拿来,即0x9876
  4. 获取基于对5的值,并将其添加到地址指针偏移,即0x987A检索在该值内存地址,即'r',并被分配到ch

顺便说一下,你也可以做到这一点的字符数组也...

更进一步,通过在一个阵列,即char name[] = "...";name[subscript_value]的环境中使用标运算符是真的一样*(名称+下标_值)。 即

 
name[3] is the same as *(name + 3) 

而且由于表达*(name + subscript_value)交换,这是反向,

 
*(subscript_value + name) is the same as *(name + subscript_value) 

因此,这就解释了为什么在回答一个上面可以写这样的( 尽管它,尽管它是非常合法的,不推荐这种做法!

 
3[name] 

好的,我如何获得指针的值? 这是*是做什么用的, 假设指针name有0x9878该指针的内存地址,再次,指的是上面的例子,这是它是如何实现的:

 
char ch = *name; 

这意味着,获得内存地址0x9878指向的值,现在ch的值将为'r'。这被称为解引用。我们只取消了一个name指针来获取值并将其分配给ch

此外,编译器知道sizeof(char)是1,因此你可以这样做

 
*name++; 
*name--; 

指针递增/递减操作指针自动步上/下由一个结果。

当我们做到这一点,假设0x9878指针存储器地址:

 
char ch = *name++; 

什么是* name的值和地址是什么,答案是,在​​现在将包含“T”和将其分配给ch,并且指针存储器地址为0x9879。

在这里你必须小心谨慎,其原理和精神与前面关于第一部分内存边界的内容相同(参见'如果名字[12]被执行会发生什么'以上)结果将是相同的,即代码崩溃和烧伤!

 
+======+  +======+ 
|0x5678| -> |0x0000| -> NULL 
+======+  +======+ 

是,内存块被释放:现在

,如果我们解除分配的内存块指向name通过调用C函数freename作为参数,即free(name)会发生什么并交还给运行时环境供其他即将到来的代码执行malloc使用。现在

,这就是分段故障的通用符号进场,因为name没有指向任何东西,会发生什么,当我们取消对它的引用,即

 
char ch = *name; 

是,该代码将崩溃,用“分段错误”进行刻录,这在Unix/Linux下很常见。在Windows下,对话框将沿着“不可恢复的错误”或的线条出现“已与应用程序时发生错误,你希望将报告发送给Microsoft?” ......如果指针一直没有malloc d和任何解除引用的企图都会被保证崩溃并烧毁。

另外:记住这一点,每一个malloc有相应free,如果没有相应的free,你必须在内存分配但未被释放了内存泄漏。

有你有它,那就是指针工作,以及如何阵列如何为指针不同,如果你正在阅读一本教科书,说他们是一样的,撕了该网页,撕了! :)

我希望这是帮助您了解指针。

+1

哇!感谢这个美好的,详细的答案! :O – lamas 2010-01-24 01:22:22

+2

未初始化并不意味着初始化为NULL。 – 2010-01-24 01:40:53

+1

@丹:哦...好...我会解决这个问题了...谢谢指点出来... – t0mm13b 2010-01-24 01:59:20

1

一个是一个实际的阵列对象,而另一个是一个参考指针以在此阵列对象。

可以混淆的事情是,无论是在他们的第一个字符的地址,但只是因为一个地址的第一个字符,另一个地址是内存中的一个字包含字符的地址。

区别可以看出&name的值。在前两种情况下,它与name的值相同,但在第三种情况下,它是与指向字符或**char的指针不同的类型,它是指针本身的地址。也就是说,它是一个双重间接指针。

#include <stdio.h> 

char name1[] = "fortran"; 
char *name2 = "fortran"; 

int main(void) { 
    printf("%lx\n%lx %s\n", (long)name1, (long)&name1, name1); 
    printf("%lx\n%lx %s\n", (long)name2, (long)&name2, name2); 
    return 0; 
} 
Ross-Harveys-MacBook-Pro:so ross$ ./a.out 
100001068 
100001068 fortran 
100000f58 
100001070 fortran 
2

char * name只是一个指针。沿着存储器的某处必须被分配,并且存储在名称中的那个存储器的地址。

  • 它可以指向单个字节的内存并且是指向单个字符的“真”指针。
  • 它可能指向一个连续的内存区域,其中包含多个字符。
  • 如果这些字符碰巧以空终止符结束,低,并且看到你有一个指向字符串的指针。
2

在C中,字符串实际上只是一个字符数组,正如您在定义中所看到的。但是,从表面上看,任何数组只是指向其第一个元素的指针,请参阅下面的细微错综复杂。 C中没有范围检查,您在变量声明中提供的范围仅对变量的内存分配有意义。

a[x]*(a + x)相同,即对指针增加了x的指针的解引用。

,如果您使用以下:

char foo[] = "foobar"; 
char bar = *foo; 

栏将被设置为“F”

为了避开混乱,避免误导人,一些多余的话对指针和数组之间更复杂的差异,由于avakar:

在一些情况下的指针实际上是从一个数组,的例子(非穷举)列表语义不同:

//sizeof 
sizeof(char*) != sizeof(char[10]) 

//lvalues 
char foo[] = "foobar"; 
char bar[] = "baz"; 
char* p; 
foo = bar; // compile error, array is not an lvalue 
p = bar; //just fine p now points to the array contents of bar 

// multidimensional arrays 
int baz[2][2]; 
int* q = baz; //compile error, multidimensional arrays can not decay into pointer 
int* r = baz[0]; //just fine, r now points to the first element of the first "row" of baz 
int x = baz[1][1]; 
int y = r[1][1]; //compile error, don't know dimensions of array, so subscripting is not possible 
int z = r[1]: //just fine, z now holds the second element of the first "row" of baz 

终于有点琐事;因为a[x]等于*(a + x)您可以实际使用例如'3 [a]'来访问数组a的第四个元素。即以下是完全合法的代码,并将打印'b'字符串foo的第四个字符。

#include <stdio.h> 

int main(int argc, char** argv) { 
    char foo[] = "foobar"; 

    printf("%c\n", 3[foo]); 

    return 0; 
} 
+2

数组是*不*指针。在某些情况下,它们只会*衰减*。 – avakar 2010-01-23 22:17:08

+0

@avakar严格地说你是对的,但为了简单理解,它使事情变得更容易。 – wich 2010-01-23 22:25:15

+0

指针的取消引用增加了x TIMES SIZEOF(内容类型)(以字节为单位)。 – 2010-01-24 01:03:24

2

char *name,在它自己的,不能举行任何字符。这个很重要。

char *name只是声明name是一个指针(即其值为地址的变量),将用于存储程序中稍后某个点的一个或多个字符的地址。但是,它并不分配内存中的任何空间来实际保存这些字符,也不保证name甚至包含有效地址。同样,如果您有像int number这样的声明,则无法知道number的值是什么,除非您明确设置它。

就像在声明整数的值之后,稍后可以在声明指向char的指针后设置其值(number = 42),稍后可以将其值设置为包含字符的有效内存地址,或者字符序列 - 你有兴趣。

3

这是一个指针。这意味着它是一个在内存中保存地址的变量。它“指向”另一个变量。

实际上它本身不能保存大量的字符。它本身只能在内存中保存一个地址。如果您在创建时为其分配字符,则会为这些字符分配空间,然后指向该地址。你可以这样说:

char* name = "Mr. Anderson"; 

这实际上几乎是一样的:

char name[] = "Mr. Anderson"; 

其中字符指针派上用场的地方是动态内存。你可以做这样的事情在程序中的任何时间将任何长度的字符指针的字符串:

char *name; 
name = malloc(256*sizeof(char)); 
strcpy(name, "This is less than 256 characters, so this is fine."); 

或者,你可以使用strdup()功能分配给它,就像这样:

char *name; 
name = strdup("This can be as long or short as I want. The function will allocate enough space for the string and assign return a pointer to it. Which then gets assigned to name"); 

如果以这种方式使用字符指针 - 并为其分配内存,则必须释放名称中包含的内存,然后重新分配内存。像这样:

if(name) 
    free(name); 
name = 0; 

请务必在尝试释放其内存之前检查该名称,实际上是一个有效点。这是if语句的作用。

你在C中看到字符指针的原因是因为它们允许你用一个不同大小的字符串重新分配字符串。静态字符数组不这样做。他们也更容易传递。

此外,字符指针很方便,因为它们可以用来指向不同的静态分配的字符数组。像这样:

char *name; 

char joe[] = "joe"; 
char bob[] = "bob"; 

name = joe; 

printf("%s", name); 

name = bob; 
printf("%s", name); 

这是当您将一个静态分配的数组传递给一个带有字符指针的函数时经常发生的情况。例如:

void strcpy(char *str1, char *str2); 

如果再传递:

char buffer[256]; 
strcpy(buffer, "This is a string, less than 256 characters."); 

将操控这两个通过str1和str2的这都只是指针点到缓冲区和字符串常量存储在内存中。

在函数中工作时要记住一些事情。如果你有一个返回字符指针的函数,不要返回一个指向函数中分配的静态字符数组的指针。它会超出范围,你会有问题。重复,不要这样做:

char *myFunc() { 
    char myBuf[64]; 
    strcpy(myBuf, "hi"); 
    return myBuf; 
} 

这是行不通的。在这种情况下,您必须使用指针并分配内存(如前所示)。分配的内存将继续存在,即使您超出了功能范围。如前所述,不要忘记释放它。

这比我想要的更具百科全书性,希望它有帮助。

编辑删除C++代码。我经常混淆这两个,我有时会忘记。

+1

那些用我美丽的C语言做的新事物和删除事物是什么? – 2010-01-23 22:22:58

+0

啊,右,C ... *编辑* – 2010-01-23 22:26:30

+1

有没有必要检查指针调用free()函数之前空。 C标准保证free()对空指针的调用不起任何作用,不起作用。这是完美的格式代码:int * p = NULL;自由(P); – mloskot 2010-01-23 22:53:51

2

确实令人困惑。理解和区分的重要之处在于char name[]声明了数组,并且char* name声明了指针。这两个是不同的动物。

但是,C中的数组可以隐式转换为指向其第一个元素的指针。这使您能够执行指针算术并遍历数组元素(它不关心什么类型的元素,或不是)。如上所述,您可以同时使用索引操作符或指针算术来访问数组元素。实际上,索引运算符只是指针运算的语法糖(同一个表达式的另一种表示形式)。

区分数组和第一个元素的指针是非常重要的。它可以查询使用sizeof运营商声明数组的大小char name[15]

char name[15] = { 0 }; 
size_t s = sizeof(name); 
assert(s == 15); 

,但如果你申请sizeofchar* name你会得到指针的大小你的平台上(即4个字节):

char* name = 0; 
size_t s = sizeof(name); 
assert(s == 4); // assuming pointer is 4-bytes long on your compiler/machine 

而且,这两种形式的炭元件的阵列的定义是等价的:

char letters1[5] = { 'a', 'b', 'c', 'd', '\0' }; 
char letters2[5] = "abcd"; /* 5th element implicitly gets value of 0 */ 

阵列的双重性质,阵列的隐式转换的指针的第一个元素在C(和也C++)语言,指针可被用作迭代通过数组元素行走:

/ *skip to 'd' letter */ 
char* it = letters1; 
for (int i = 0; i < 3; i++) 
    it++;