Cstring混淆
我现在正在学习C语言,并且对字符数组 – string有点困惑。
char name[15]="Fortran";
没有问题 – 它可以容纳(最多?)15个字符的数组
char name[]="Fortran";
C计数的字符数,所以我不必 – 整洁!
char* name;
好的。 现在怎么办? 我所知道的是,这可以容纳大量的后来分配的字符(例如:通过用户input),但是
- 为什么他们把这个叫做char指针? 我知道指针作为variables的引用
- 这是一个“借口”? 这是否发现任何其他用途比在char *?
- 这究竟是什么? 这是一个指针吗? 你如何正确使用它?
在此先感谢喇嘛
我认为这可以这样解释,因为一张图片胜过千言万语。
我们将以char name[] = "Fortran"
,这是一个字符数组,长度在编译时已知,准确的说是7? 错误! 它是8,因为'\ 0'是一个结尾字符,所有的string都必须有。
char name [] =“Fortran”; + ====== + + - + - + - + - + - + - + - + - + |×1234 | | F | O | R | T | R | A | N | \ 0 | + ====== + + - + - + - + - + - + - + - + - +
在链接时,编译器和链接器给符号name
0x1234的内存地址。 使用下标运算符(例如name[1]
,编译器知道如何计算内存中的位置是偏移量0x1234 + 1 = 0x1235处的字符,而且确实是“o”。 这很简单,而且,ANSI C标准中, char
数据types的大小为1个字节,这可以解释运行时如何获得这个语义name[cnt++]
,假设cnt
是一个intger且具有例如值为3,运行时间自动递增1,并从零开始计数,偏移值为“t”。 迄今为止这很简单。
如果name[12]
被执行会怎么样? 那么,代码将崩溃,否则你会得到垃圾,因为数组的边界是从索引/偏移量0(0x1234)到8(0x123B)。 之后的任何内容都不属于name
variables,这将被称为缓冲区溢出!
内存中的name
地址是0x1234,如下例所示:
printf(“名称的地址是%p \ n”,&名称); 输出将是: 名字的地址是0x00001234
为了简洁起见,并保持例子,内存地址是32位,因此你看到额外的0。 很公平? 好吧,让我们继续前进。
现在指针… char *name
是指向char
types的指针….
编辑:我们初始化为NULL,如图所示感谢丹指出了小错误…
char * name =(char *)NULL; + ====== + + ====== + | 0x5678的| - > | 0x0000 | - > NULL + ====== + + ====== +
在编译/链接时, name
不指向任何东西,但是具有符号name
(0x5678)的编译/链接时间地址,实际上它是NULL
, name
的指针地址是0x0000。
现在请记住 , 这是至关重要的,符号的地址在编译/链接时间是已知的,但指针地址是未知的,当处理任何types的指针
假设我们这样做:
name =(char *)malloc((20 * sizeof(char))+ 1); strcpy(名字,“Fortran”);
我们调用malloc
为20个字节分配一个内存块,不,不是21,我把1加到大小上的原因是'\ 0'结尾字符。 假设在运行时,给出的地址是0x9876,
char *名字; + ====== + + ====== + + - + - + - + - + - + - + - + - + | 0x5678的| - > | 0x9876 | - > | F | o | r | t | r | a | n | \ 0 | + ====== + + ====== + + - + - + - + - + - + - + - + - +
所以当你这样做的时候:
printf(“名称的地址是%p \ n”,名称); printf(“名称的地址是%p \ n”,&名称); 输出将是: 名字的地址是0x00005678 名字的地址是0x00009876
现在,这就是“ 数组和指针是相同的幻觉”
当我们这样做时:
char ch = name [1];
运行时会发生什么:
- 查找符号
name
的地址 - 获取该符号的内存地址,即0x5678。
- 在那个地址中,包含另一个地址,一个指向内存的指针地址并取回它,即0x9876
- 根据下标值1获得偏移量,并将其添加到指针地址,即0x9877以检索该存储器地址的值,即“o”并分配给
ch
。
上面的内容对于理解这个区别至关重要,数组和指针之间的区别在于运行时如何获取数据,而指针则有一个额外的取指间接。
请记住 , typesT的数组总是会衰减到 typesT 的第一个元素的指针 。
当我们这样做时:
char ch = *(name + 5);
- 查找符号
name
的地址 - 获取该符号的内存地址,即0x5678。
- 在那个地址中,包含另一个地址,一个指向内存的指针地址并取回它,即0x9876
- 根据5的值获取偏移量,并将其添加到指针地址,即0x987A以检索该存储器地址的值,即'r'并分配给
ch
。
顺便说一句,你也可以做到这一点的字符数组…
此外,通过在数组的上下文中使用下标运算符,即char name[] = "...";
和name[subscript_value]
是真的是相同的*(名称+下标值)。 即
名称[3]与*(名称+ 3)相同
而且由于expression式*(name + subscript_value)
是可交换的 ,即相反,
*(下标值+名称)与*(名称+下标值)相同
因此,这解释了为什么在上面的答案之一,你可以这样写( 尽pipe它,即使它是非常合法的,不推荐这种做法! )
3 [名称]
好的,我如何获得指针的值? 这就是*
的用法,假设指针name
指针存储地址为0x9878,再次参考上面的例子,这是如何实现的:
char ch = * name;
这意味着,获得0x9878的内存地址所指向的值,现在ch
将具有'r'的值。 这被称为解引用。 我们只是取消引用name
指针来获取值并将其分配给ch
。
另外,编译器知道sizeof(char)
是1,因此可以像这样做指针增加/减less操作
*名++; *名称 - ;
指针自动上/下作为一个结果。
当我们这样做时,假设指针的内存地址为0x9878:
char ch = * name ++;
* name的值是什么,地址是什么,答案是, *name
现在包含't'并将其分配给ch
,指针存储器地址是0x9879。
在这个地方你也必须小心谨慎,与前面关于第一部分记忆界限所说的相同的原则和精神(见上文“如果名字[12]被执行会发生什么”)结果将是相同的,即代码崩溃和烧伤!
现在,如果我们通过以name
作为参数来free
C函数free(name)
即free(name)
来释放name
指向的内存块,会发生什么情况:
+ ====== + + ====== + | 0x5678的| - > | 0x0000 | - > NULL + ====== + + ====== +
是的,内存块被释放,并交给运行时环境供其他即将到来的malloc
代码执行使用。
现在,这是分割错误的常见符号,因为name
并不指向任何东西,当我们解引用时会发生什么
char ch = * name;
是的,这些代码会崩溃,并且出现“分段错误”,这在Unix / Linux下很常见。 在Windows下,将出现一个对话框,沿着“不可恢复的错误”或“应用程序发生错误,您是否希望将报告发送给Microsoft?”….如果指针不是malloc
d和任何解除引用的企图都会被保证崩溃和焚烧。
另外:记住这一点,对于每个malloc
都有一个相应的free
,如果没有相应的free
,你有内存泄漏,内存分配但没有释放。
在那里,你有它,指针是如何工作的,以及数组是如何不同的指针,如果你正在阅读一个教科书,说他们是相同的,撕下那页,撕了! 🙂
我希望这有助于你理解指针。
这是一个指针。 这意味着它是一个在内存中保存地址的variables。 它“指向”另一个variables。
它实际上不能 – 本身 – 拥有大量的字符。 它本身只能在内存中保存一个地址。 如果在创build时为其分配字符,则会为这些字符分配空间,然后指向该地址。 你可以这样做:
char* name = "Mr. Anderson";
这其实和这个差不多:
char name[] = "Mr. Anderson";
字符指针派上用场的地方是dynamic内存。 您可以随时在程序中为字符指针指定一个任意长度的string,如下所示:
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中看到字符指针的原因是因为它允许你用一个不同大小的string重新分配string。 静态字符数组不这样做。 他们也更容易传递。
此外,字符指针是很方便的,因为它们可以用来指向不同的静态分配的字符数组。 喜欢这个:
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操纵这两者,它们只是指向缓冲区和string文字存储在内存中的指针。
在函数中工作时要记住一些事情。 如果你有一个返回字符指针的函数,不要返回一个指向函数中分配的静态字符数组的指针。 它会超出范围,你会有问题。 重复,不要这样做:
char *myFunc() { char myBuf[64]; strcpy(myBuf, "hi"); return myBuf; }
这是行不通的。 在这种情况下,您必须使用一个指针并分配内存(如前所示)。 分配的内存将继续存在,即使您超出了function范围。 如前所述,不要忘记释放它。
这比我想要的更多的百科全书,希望它的帮助。
编辑删除C ++代码。 我经常混淆这两个,有时我会忘记。
char * name只是一个指针。 沿着内存的某个地方必须被分配,并且该内存的地址被存储在名字中 。
- 它可以指向一个单字节的内存,并且是一个“真”指向单个字符的指针。
- 它可以指向一个连续的记忆区域,其中包含许多字符。
- 如果这些字符碰巧以一个空终止符结束,低,并且看到你有一个指向string的指针。
char *name
,在它自己的, 不能容纳任何字符 。 这个很重要。
char *name
只是声明了这个name
是一个指针(也就是一个variables的值是一个地址),这个指针将被用来存储一个或多个字符的地址在程序后面的某个点。 但是,它并不分配内存中的任何空间来实际保存这些字符,也不保证该name
甚至包含有效的地址。 以同样的方式,如果你有一个像int number
这样的声明,除非明确地设置它,否则无法知道number
的值是什么。
就像在声明一个整数的值之后,稍后可以在声明一个指向char的值之后设置它的值( number = 42
),稍后可以将其值设置为包含字符的有效内存地址 -人物 – 你感兴趣的。
这确实令人困惑。 理解和区分的重要之处在于, char name[]
声明了数组, char* name
声明了指针。 两个是不同的动物。
但是,C中的数组可以隐式转换为指向其第一个元素的指针。 这使您能够执行指针算术并遍历数组元素(它不关心什么types的元素, char
或不)。 如上所述,可以同时使用索引运算符或指针运算来访问数组元素。 实际上,索引运算符只不过是指针运算的语法糖(同一expression式的另一种表示forms)。
区分数组和指针的第一个元素的区别很重要。 可以使用sizeof
运算符查询声明为char name[15]
的数组的sizeof
:
char name[15] = { 0 }; size_t s = sizeof(name); assert(s == 15);
但是如果将sizeof
应用于char* name
,则会在您的平台上获得指针大小(即4个字节):
char* name = 0; size_t s = sizeof(name); assert(s == 4); // assuming pointer is 4-bytes long on your compiler/machine
而且,char元素数组的两种定义forms是等价的:
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++;
在C中,string实际上只是一个字符数组,正如您在定义中看到的那样。 但是,从表面上看,任何数组只是一个指向其第一个元素的指针,请参阅下面的细微的错综复杂。 在C中没有范围检查,你在variables声明中提供的范围只对variables的内存分配有意义。
a[x]
与*(a + x)
,即指针a的取值以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'stringfoo的第四个字符。
#include <stdio.h> int main(int argc, char** argv) { char foo[] = "foobar"; printf("%c\n", 3[foo]); return 0; }
一个是实际的数组对象,另一个是对这种数组对象的引用或指针 。
可以混淆的是,它们都具有第一个字符的地址,但只是因为一个地址是第一个字符而另一个地址是包含字符地址的内存中的一个字。
差异可以在&name
的值中看到。 在前两种情况下,它与name
相同,但在第三种情况下,它是一个不同的types,称为指向char的指针 ,或者是**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