基础回顾-5 字符串

333 阅读5分钟

声明方式

字符串实际上是一个由'\0'(尾零)字符结尾的字符集合。我们声明字符串时,经常是

char a[] = "hello world!";
// or 
char *p = "hello world!";

乍看起来,char a[]char *p 是一样的,那 a == p 这个是不是为真呢?
(打印字符串时,用 %s 输出符)

char a[] = "hello";
char *p = "hello";
   
printf("%s\n",a);       // hello
printf("%s\n",p);       // hello
printf("%d \n",a == p); // 0

答案是 false ,打印出来的结果明明是一样的,why?
这里又有一个关于内存存储区的问题。 hello 这是一个字符串常量,我们写这个代码时,编译器会在 静态存储区 分配六个字节( hello 的长度 + 尾零字符)长的空间,来存储这个字符串。

char *p = "hello";

是找到这段静态存储区里 hello 的内存地址的首地址,用指针 p 指向它, hello 或者 &hello 的值是字符串的首地址。而

char a[] = "hello";

是分配了另一端内存区给了这个 char a[] 数组,数组里面的存的值是 hello + '\0' ,相当于将静态存储区的 hello 拷贝了一份,存放到了别的地址

char a[] = "hello";
char *p = "hello";
   
printf("%p\n",a);       // 0x7ffeefbff5aa
printf("%p\n",p);       // 0x100000f91

所以可以得知,当修改字符串里某个字符时

a[0] = 'H';         //这个是合法的,修改数组里的字符串值
p[0] = 'H';         //这个是错误的,静态存储区里的值不能修改

除非

char a[] = "hello";
char *p = a;
//这时,p[0] 可修改

字符串和字符的区别

"h" ,'h' 的区别
"h" 是字符串,是 'h' + '\0' 两个字符组成的,占两个字符长度,即 两个字节
'h' 是字符,在内存中以数字值存储(ASCII码),占一个整型长度,即 四个字节

字符串的一些方法

一般需要导入 stdlib.h 头文件。

size_t strlen(const char *__s)

传入一个字符串首字符的地址,返回这个字符串的有效字节长度,遇到字符串第一个 \0字符结束,如 strlen("hello") 返回值为 5

int atoi(const char *)

传入一个字符串首字符的地址,返回是一个整型数字,将字符串转换为数字的方法,遇到第一个不合法字符就会结束。

atoi("123");    // 123
atoi("-123");   // -123
atoi("123-33"); // 123
atoi("12.3");   // 12

头文件 string.h \

int strcmp(const char *__s1, const char *__s2)

传入两个字符串,返回也是一个整型,比较两个字符串的大小,按需按序比较ASCII码大小。结果为 1 表示前者比后者大, -1 表示后者大,为 0 表示两者相等。

char *strcpy(char *__dst, const char *__src)

传入两个字符串,将后者字符串拷贝到前者的内存空间里,要求前者的存储空间确实存在而且比后者要大。返回值是拷贝后的字符串。

//自实现 strcpy 方法
char *mycpy (char *dest, const char *src) {
    assert( dest != NULL && src != NULL); //安全控制
    char *tmp = dest;
    while ((*dest++ = *src++)) {}   // 两层小括号
    tmp += '\0';  
    return  tmp;
}

//精简版
char *mycpy (char *dest, const char *src) {
    assert( dest != NULL && src != NULL) ;  
    while ((*dest++ = *src++)) {}
    return dest;
}

while 循环的判断条件是 (*dest++ = *src++) 这条赋值语句,非空即为真原则,当 *src 有值时这个赋值语句一直是成立的,直到 '\0' 之后,是 NULL 则赋值语句不成立,退出循环。

char *strcat(char *__s1, const char *__s2)

拼接字符串方法,将后一个字符串,拼接到前一个字符串之后,要求前一个字符串的存储空间,需要足够容纳拼接之后的字符串,返回值是拼接之后的字符串。

char *strstr(const char *__s1, const char *__s2)

在前面的字符串中查找第一次出现后者字符串的位置,返回值为前字符串中第一次出现后字符的位置到尾零这一段字符串(相当于截去前部分字符串),找不到时返回的是空 NULL

char *p = "welcome to China";
char *q = "come";
printf("%s \n",strstr(p,q));    // come to China 

char *strtok(char *__str, const char *__sep)

分割字符串,将前者字符串按照后者字符串作为条件或者分隔符进行分割。代码更为直观。

char buf[] = "I am a good man";     //只能分割可变字符串
char *p = "I am a good man";        //代表字符串常量,不可分割

printf("%ld\n",sizeof(buf));        // 16 ,十六个字符长
printf("%s\n",buf);                 // I am a good man

char *ret = strtok(buf, " ");       
printf("%ld\n",sizeof(buf));        // 16 
printf("%s\n",buf);                 // I
printf("%s\n",ret);                 // I

ret = strtok(NULL, " ");            // 传空表示,继续分割之前的字符串
printf("%s\n",ret);                 // am
ret = strtok(p, " ");               // 非空表示分割新的字符串,不过此时报错,p 不可分割

分割完了之后,buf 的长度未发生变化,说明 buf 原先的内存还在,那换种方式来看看

char buf[] = "I am a good man";
char *ret = strtok(buf, " ");
printf("%s**\n",buf + 1); // 打印结果为:**
printf("%s**\n",buf + 2); // 打印结果为:am a good man**
char buf[] = "I am a good man";
char *ret = strtok(buf, "a");
printf("%s**\n",buf + 1); // 打印结果为: **
printf("%s**\n",buf + 2); // 打印结果为:**
printf("%s**\n",buf + 3); // 打印结果为:m a good man**

两段代码看下来,strtok 方法,只是将参数一字符串中,第一次出现参数二字符串(分隔符)的位置的字符,替换成了 '\0' ,然后将前一段字符串返回。

  • 第二个参数是分隔符,可以是单个字符,也可以是 a* 这样的一段字符串,表达的意思是将第一参数按照 a* 分割,而且不是 a* 这种整体。
  • 如果第一参数中,没有第二参数的字符,则结果返回为 NULL(空)。