务必理解指针与内存模型,不要死记硬背。数组
C语言中的字符串通常是char *
类型的,这是怎样存在内存中的呢?数据结构
cchar *s = "NIHAO"; | s:400 | |---|---|---|---| |'N'|'I'|'H'|'A'|'O'| 0 | |---|---|---|---|---|---| |400|401|402|403|404|405|
如是上图,假设字母A处于内存的第400号格子,那么后面几个字母也是紧跟着的。
变量s自己并无储存字符串,而存的是字符串的首地址400。也即,s指向这个字符串。less
为何没有专门一个字符串的类型而是要靠一个指针指向它呢?由于字符串的长度是不固定的,因此一个字符串还包含着长度信息,基本类型是没法处理数据结构的。函数
咱们都知道字符串是以0结尾的,并且这个更像是一种约定,C编译器自己并无对此作任何保证。好比这样指针
cchar s[3] = "asd"; puts(s); /* prints "asd" or something longer */
这样作是危险的,由于s只有3个格子,字符串结尾的0并无放进去。若是在它后面的内存格子并非0,那打印这个字符串时就跟咱们预期的不同了。code
c"abc"[0] = 'z'; /* wrong */ char *s = "abc"; s[0] = 'z'; /* wrong */ char s[5] = "abc"; s[0] = 'z' /* right */
当指针s指向的是字符串常量(即直接写在程序里面的字符串时),要注意它是不可写的
为啥用数组就没问题呢,由于数组的初始化和指针有点区别内存
cchar s[5] = "abc"; /* 至关于 */ char s[5]; strcpy(s, "abc");
若是担忧本身会不当心写错,能够加上const
关键字,这样编译的时候就会报错
这是一个好习惯,接下来的示例程序中都会这么写。字符串
cconst char *s = "abc"; s[0] = 'z'; /* causes a compiling error instead of runtime error */
c/* wrong */ char *s; s[0];
上面的程序编译是能过的(可能有warning),但运行是必定会出错的,由于编译器并不知道s指向哪些格子。编译器
c/* right */ const char *s = "NIHAO"; s[0];
这样,实际上是隐式的分配了6个格子(包括字符串结尾的0),并让s指向它们string
c/* right */ char s[6]; s[0];
c/* right */ char s[6] = "NIHAO"; s[0];
数组其实跟指针没什么区别,主要的区别是它在声明的时候就分配好了格子(方括号里的6就是告诉编译器给我6个格子),并且数组不能改变它的指向(也不能再要更多的格子)。
cconst char *s = "abcd"; const char *t = "abcd"; /* wrong */ if (s == t) { ... } /* right */ if (!strcmp(s, t)) { ... }
由于s和t都没有存字符串的内容,它们存的是字符串的地址,若是用==
比较,比较的是两个字符串的地址是否相同。咱们但愿比较的是内容是否相同。
请使用C语言库函数中的strcmp
比较字符串是否相等
c/* tries to copy a string */ char s[5] = "abcd"; char *t = s; t[3] = 'z'; puts(s); /* puts "abcz" */
上面这种作法让t和s指向同一字符串,修改t指向的内容,会发现s指向的内容也被修改了。这种作法没有错,常常会用到,但不必定是你想要的。
c/* wrong */ char *s = "abcd"; char *t; /* not initialized */ strcpy(t, s);
c/* right */ char *s = "abcd"; char t[10] = {0}; /* or char *t = (char *) malloc(5*sizeof(char)); */ strcpy(t, s);
使用strcpy
复制字符串的内容而不是指针,但也要注意初始化t这个指针
int,float之类的很简单直接return就好
但如今我想写一个函数,它可以获得一个字符串
c/* no problem, but meaningless */ const char *f() { const char *s = "abcd"; return s; } /* wrong */ char *f() { char s[100]; /* do something with s */ return s; } /* result correct but not good */ char *f() { int n = 10; char *s = (char *) malloc(n*sizeof(char)); /* do something with s */ return s; }
第一种状况就不说了,返回一个字符串常量并无问题由于它不可修改,可是不可修改也就没什么意义了。
第二种状况是彻底错误的,返回一个局部的数组。这个数组的内存会在函数调用完后被收回,所以返回的指针指向的时候没有意义的地方。现代编译器通常都会对这个有warning。
第三种状况是返回malloc的指针。这种状况你能够获得正确的答案,可是不推荐,调用这个函数的人颇有可能
一旦没有注意,屡次调用这个函数,结果就是内存溢出,这样的错误还很是很差排查,因此不推荐
正确的作法是把分配内存这种事情放在函数外面作,正如strcpy
同样
cchar *strcpy(char *dest, const char *src) { int i; for (i = 0; i < strlen(src); i++) { dest[i] = src[i]; } return dest; }
dest
是咱们想要返回的字符串,它是从外面传进来的缘由是咱们不想在函数内部为它分配内存,而是在外面分配好了,里面只对这个字符串进行修改。
注意这里返回了char *
但其实返回的正是本来传进来的dest
,这里只是为了方便而已。