C语言中的指针和字符串

前言

务必理解指针与内存模型,不要死记硬背。

内存里的字符串

C语言中的字符串一般是char *类型的,这是怎样存在内存中的呢?

Cchar *s = "NIHAO";

|     s:400     |
|---|---|---|---|

|'N'|'I'|'H'|'A'|'O'| 0 |
|---|---|---|---|---|---|
|400|401|402|403|404|405|

如是上图,假设字母A处于内存的第400号格子,那么后面几个字母也是紧跟着的。
变量s本身并没有储存字符串,而存的是字符串的首地址400。也即,s指向这个字符串。

为什么没有专门一个字符串的类型而是要靠一个指针指向它呢?因为字符串的长度是不固定的,所以一个字符串还包含着长度信息,基本类型是无法处理数据结构的。

我们都知道字符串是以0结尾的,而且这个更像是一种约定,C编译器本身并没有对此做任何保证。比如这样

Cchar s[3] = "asd";
puts(s); /* prints "asd" or something longer */

这样做是危险的,因为s只有3个格子,字符串结尾的0并没有放进去。如果在它后面的内存格子并不是0,那这个字符串就跟我们预期的不一样了。

字符串常量不可写

"abc"[0] = 'z'; /* wrong */

char *s = "abc";
s[0] = 'z'; /* wrong */

char s[5] = "abc";
s[0] = 'z' /* right */

当指针s指向的是字符串常量(即直接写在程序里面的字符串时),要注意它是不可写的
为啥用数组就没问题呢,因为数组的初始化和指针有点区别

char s[5] = "abc";
/* 相当于 */
char s[5];
strcpy(s, "abc");

如果担心自己会不小心写错,可以加上const关键字,这样编译的时候就会报错
这是一个好习惯,接下来的示例程序中都会这么写。

const char *s = "abc";
s[0] = 'z'; /* causes a compiling error instead of runtime error */

指针是要初始化才能使用的

/* wrong */
char *s;
char c = s[0];

上面的程序编译是能过的(可能有warning),但运行是一定会出错的,因为编译器并不知道s指向哪些格子。

/* right */
const char *s = "NIHAO";
char c = s[0];

这样,其实是隐式的分配了6个格子(包括字符串结尾的0),并让s指向它们

/* right */
char s[6];
char c = s[0];
/* right */
char s[6] = "NIHAO";
char c = s[0];

数组其实跟指针没什么区别,主要的区别是它在声明的时候就分配好了格子(方括号里的6就是告诉编译器给我6个格子),而且数组不能改变它的指向(也不能再要更多的格子)。

为什么不能用等号来比较字符串?

比较字符串

const char *s = "abcd";
const char *t = "abcd";
/* wrong */
if (s == t) {
    ...
}
/* right */
if (!strcmp(s, t)) {
    ...
}

因为s和t都没有存字符串的内容,它们存的是字符串的地址,如果用==比较,比较的是两个字符串的地址是否相同。我们希望比较的是内容是否相同。

请使用C语言库函数中的strcmp比较字符串是否相等

复制字符串

/* tries to copy a string */
char s[5] = "abcd";
char *t = s;
t[3] = 'z';
puts(s); /* puts "abcz" */

上面这种做法让t和s指向同一字符串,修改t指向的内容,会发现s指向的内容也被修改了。这种做法没有错,经常会用到,但不一定是你想要的。

/* wrong */
char *s = "abcd";
char *t; /* not initialized */
strcpy(t, s);
/* right */
char *s = "abcd";
char t[10] = {0}; /* or char *t = (char *) malloc(5*sizeof(char)); */
strcpy(t, s);

使用strcpy复制字符串的内容而不是指针,但也要注意初始化t这个指针

怎样让函数得到一个字符串结果

int,float之类的很简单直接return就好
但现在我想写一个函数,它能够得到一个字符串

三种错误的或者不太好的做法

/* 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的指针。这种情况你可以得到正确的答案,但是不推荐,调用这个函数的人很有可能

  • 不知道函数里面分配过内存
  • 不知道应该什么时候free这部分内存
  • 忘了free这部分内存

一旦没有注意,多次调用这个函数,结果就是内存溢出,这样的错误还非常不好排查,所以不推荐

正确的做法

正确的做法是把分配内存这种事情放在函数外面做,正如strcpy一样

char *strcpy(char *dest, const char *src)
{
    int i;
    for (i = 0; i < strlen(src); i++) {
        dest[i] = src[i];
    }
    return dest;
}

注意这里返回了char *但其实返回的正是原本传进来的dest,这里只是为了方便而已。
调用方法在上面也提到过。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值