字符串
定义字符串的几种方式:
-
char c[] = {'h','e','l','l','o'};
这种是字符数组
可以用for循环打印:
for(int i=0;i<5;i++) printf("%c",c[i]);
不推荐用下面两种方式打印,因为输出结果最后会多出个小方框的符号(gcc编译器):
printf("%s\n",c); puts(c);
输出结果:
-
char c[] = "hello";
可用下面三种方式打印:
for(int i=0;i<5;i++) printf("%c",c[i]); printf("%s\n",c); puts(c);
-
char *c = "hello";
可用下面三种方式打印:
for(int i=0;i<5;i++) printf("%c",*(c[i]); printf("%s\n",c); puts(c);
字符串的一些性质
-
char c[] = "hello";
这样定义的是字符串变量,它(数组元素)允许修改
c[0] = 'H'; puts(c);
输出结果:
Hello
-
用数组形式定义字符串,数组名就是地址常量,不能更改。如果改变了数组名,则意味着改变了数组的存储位置。可以进行类似
数组名+1
这种操作,标识数组的下一个元素。但是不能进行++数组名
这样的操作。递增运算只能用于变量名前,不能用于常量:char arr[] = "Hello World!"; int i = 0; printf("%c ",*(arr+1)); //printf("%c ",*(arr++));//error
-
char *p = "hello";
这样定义是字符串常量,它不允许被修改:
*p = 'H';//error
这样写不会报错,但是会导致程序崩掉
-
用指针形式定义的字符串,可以使用类似
++指针名
这样的操作char *s = "Hello World!"; printf("%c\n",*(++s));
输出结果是:
e
。
动态内存分配和字符串
char *p;
*p = 'H';
printf("%c\n",*p);
运行上面的代码尽管不会报错,但是程序运行到第二句会崩掉,第三局不会执行。原因是因为p
是野指针(不知道指向哪里),对野指针进行赋值相当于直接修改内存里面某个地方的值,而这个值用户并没有权限修改。
char *p = (char *)malloc(1);
*p = 'H';
printf("%c\n",*p);
修改成上面代码,malloc()
函数在内存分配了一个空间,这个空间对用户来说是可以操作的。把这个空间的地址赋给指针p
,这样就能通过指针p
来修改这个空间里面的值
sizeof()
-
sizeof
与字符数组:char c[] = {'h','e','l','l','o'}; printf("%d\n",sizeof(c)); printf("%d\n",sizeof(c[0])); printf("%d\n",sizeof(c)/sizeof(c[0]));
输出结果:
5 1(一个字符占一个字节) 5
-
sizeof
与字符串:char c[] = "hello"; printf("%d\n",sizeof(c)); printf("%d\n",sizeof(c[1])); printf("%d\n",sizeof(c)/sizeof(c[0]));
输出结果:
6 1 6
系统会自动在字符串"hello"最后加多一个字符串的结束标志:
\0
,因此结果为6,字符数组则不会加
strlen()
strlen()
得到的是有效字符数,而且它不会算上系统为字符串添加的结束符
-
char c[] = "hello"; printf("%d\n",sizeof(c));//输出结果:6 printf("%d\n",strlen(c));//输出结果:5
-
char c[100] = "hello"; printf("%d\n",sizeof(c));//输出结果:100 printf("%d\n",strlen(c));//输出结果:5
-
char *c = "hello"; printf("%d\n",sizeof(c));//输出结果:8 printf("%d\n",strlen(c));//输出结果:5
sizeof(c)
在这里得到的是计算机用多少字节来表示一个地址(等同于sizeof(char *)
)
strcpy()
用于拷贝字符串,这个函数在头文件string.h
里被定义
原型是char *strcpy(char* dest, const char *src)
,两个参数分别是“拷贝到哪里去”和“从哪里拷贝”
小例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char *p = (char *)malloc(15);
strcpy(p,"HelloWorld");
puts(p);
return 0;
}
上面分配空间的语句如果只申请1个字节:(char *)malloc(1)
,编译器尽管不会报错,结果也能正常输出,但不可以这么做。能正常输出只是刚好后面的内存没有其它程序在用,如果后面的内存被占用了则上面的这个程序就会崩溃
补充
-
如果p1和p2都是指向字符串的指针,则p1 = p2;这个语句只是拷贝字符串的地址,而不是字符串本身
-
注意下面这种字符串赋值是不正确的:
char c[20]; target = "Hello World!";
正确的应该是:
strcpy(c,"Hello World!");
-
不能对未初始化的指针进行赋值:
char * c; strcpy(c,"Hello World");
c
未被初始化,该字符串可能被拷贝到任何地方。 -
strcpy()
接受两个字符串指针作为参数 -
strcpy()
返回的类型是char *
,返回的是第一个参数的值(见小例子二) -
strcpy()
第一个参数不必指向数组(或指针)的开始,它可以只拷贝数组的一部分(见小例子二)
小例子二:
char *c1 = "beast";
char c2[40] = "Be the best that you can be.";
//char *c2 = "Be the best that you can be.";//error,不会报错,但是程序运行不起来,原因:beast是字符串常量,不能修改
char * c3;
c3 = strcpy(c2 + 7,c1);
puts(c2);
puts(c3);
输出结果:
Be the beast
beast
由于strcpy()
第一个参数是c2+7
,所以c3
指向c2
中的第8个元素,因此输出为beast
strncpy()
strcpy()
不能检查目标空间是否能容纳字符串的副本,使用strncpy()
拷贝更安全
strncpy(target,source,n)
三个参数:
- target:拷贝去哪里
- source:要拷贝的东西
- n:拷贝多长(多少字节)
strncpy(target,source,n)
把source中的n个字符或空字符之前的字符拷贝至target中
-
如果source中的字符数小于n,则拷贝整个字符串,包括空字符
char * p = "abc"; char c[] = "ABCDE"; strncpy(c,p,4); printf("%s\n",c);
输出结果:
abc
-
如果source中的字符数大于n,则一直拷贝到n,不带空字符
char * p = "abc"; char c[] = "ABCDE"; strncpy(c,p,2); printf("%s\n",c);
输出结果:
abCDE
因此拷贝后,target中不一定会有空字符
scanf()
能用于获取用户输入的字符串
char c[128] = "\0";
scanf("%s",c);
puts(c);
这里要注意一点,如果输入像“Hello World”这种带有空格的字符串,scanf()
只会读入“Hello”,而忽略掉“World”,所以最终只会输出Hello
%s:从第1个非空白字符开始,到下一个空白字符之前的所有字符都是输入
gets()
能用于获取用户输入的字符串
gets()
读取整行的输入,直至遇到换行符,然后丢弃换行符,存储其余字符,并在这些字符的末尾添加一个空字符使其成为一个C字符串。 ——《C Primer Plus》
char c[128] = "\0";
gets(c);
puts(c);
注意:gets()
无法检查数组是否装得下所输入的字符串。因此如果要使用gets()
的时候一定要保证有足够的空间
如果输入的字符串过长,会导致缓冲区溢出,即多余的字符超出了指定的目标空间。如果这些多余的字符只是占用了尚未使用的内存,就不会立即出现问题;但如果它们擦写掉程序中的其他数据,会导致程序异常终止。 ——《C Primer Plus》
strcat()
用于拼接两段字符串。原型是:char *strcat(char *dest, const char *src)
,把src
所指向的字符串(包括\0
)复制到dest
所指向的字符串后面(删除*dest
原来末尾的\0
)。
要自行保证*dest
有足够空间容纳拼接后的字符串
*src
中原有的字符不变
strcat()
返回指向dest
的指针
char *p = "abc";
char c[128] = "ABC";
strcat(c,p);
puts(c);//ABCabc
strcmp()
用于比较两个字符串
原型是int strcmp(const char *str1, const char *str2)
- 若
str1
和str2
相同,则返回零 - 若
str1
<str2
,则返回负数 - 若
str1
>str2
,则返回正数
多数情况下只需关心strcmp()
的返回值是否为0即可
assert()
用于某个检测表达式是否为真,如果为假则终止程序
使用assert()
需包含头文件assert.h
频繁调用assert()
会极大影响程序的性能
assert(表达式);
类似于:
if(表达式)
{
...;
}
else
{
报错&&终止程序;
}
strcmp()
用于比较两个字符串
原型是int strcmp(const char *str1, const char *str2)
- 若
str1
和str2
相同,则返回零 - 若
str1
<str2
,则返回负数 - 若
str1
>str2
,则返回正数
多数情况下只需关心strcmp()
的返回值是否为0即可
assert()
用于某个检测表达式是否为真,如果为假则终止程序
使用assert()
需包含头文件assert.h
频繁调用assert()
会极大影响程序的性能
assert(表达式);
类似于:
if(表达式)
{
...;
}
else
{
报错&&终止程序;
}