声明方式
字符串实际上是一个由'\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
(空)。