C没有显示的字符串数据类型。
字符串是以空字符\0结尾的char类型数组。
定义字符串的三种方式:
"I am a string"
用双引号括起来的内容就是字符串常量。编译器会在末尾自动加空字符\0
他属于静态存储类别,也就是该字符串只会被存储一次,在整个程序生命周期内存在。
printf("s%," "sdf");使用%s在printf中打印字符串转换。
字符串常量就是指针。
"xyz"+1; //结果是指向第二个字符y的指针
*"xyz" //x
"xyz"[2] //z
*("xyz"+4) //不可预测,超出了范围。
char words[71] = "I am a string in an array.";
char wokd[10] = {'c','g','a','/0'};
char words3[] = "I am a string in an array.";
因为字符串数组和数组一样,所以我们也可以使用指针表示数组首元素地址
char * p = words;
p == words == &words[0]
char * pt1 = "Something is pointing at me.";
char pt2[] = "Something is pointing at me.";
以上两个声明几乎相同。但是他们字符串的存储位置不同。
pt1和pt2都是该字符串的首地址。
#define MSG "I'm special"
int main()
{
char ar[] = MSG;
char *pt = MSG;
printf("address of \"I'm special\": %p \n", "I'm special"); //0x400660
printf(" address of MSG: %p\n", MSG); //0x400660
printf(" address ar: %p\n", ar); //0x7ffe88148230
printf(" address pt: %p\n", pt); //0x400660
printf("address of \"I'm special\": %p \n", "I'm special");//0x400660
//操作字符串常量区
printf("\n");
printf("%s \n", MSG);
pt[2] = '4'; //segment fault
// printf("%s", MSG);
return 0;
}
本身字符串是存储在静态常量区,我们打印出他的地址。虽然打印了两次这个字符串,实际程序使用的都是同一个地址,也就是他在静态存储区中只存在一份。
我们发现字符指针是直接指向这个地址,而字符串数组的首地址并不指向这个地址。
也就是有:
如果我试图修改常量区的字符串,比如上面代码,修改字符串中某一个字符。pt[2] = '4';会在运行时报段错误。
所以建议在使用指针指向的字符串时加const防止修改字符串。
const char *pt = "I'm special";
如果要修改原字符串,则不能使用指针表示法,可以使用字符串数组表示法,这样就可以修改字符串的副本。
const char *mytalents[LIM] = {
"Adding numbers swiftly",
"Multiplying accurately", "Stashing data",
"Following instructions to the letter",
"Understanding the C language"
};
char yourtalents[LIM][SLEN] = {
"Walking in a straight line",
"Sleeping", "Watching television",
"Mailing letters", "Reading email"
};
定义字符串数组,也可以使用上述两种方式。
指针方式:效率更高,因为不用存储字符串副本,但是不能修改原字符串。(绝大多数字符串操作由指针完成)
数组方式:效率低,但是可以修改字符串。
在读取字符串时,scanf(%s)只能读取一个单词。而我们常常需要读取一整行输入。
gets()函数是直接读取一整行。但是他只有一个唯一的参数,无法检查空间是否能装下用户输入的字符数量。所以C99建议不适用这个函数,而C11直接将该函数废弃。
通常使用fgets()来代替gets()
#define STLEN 14
int main(void)
{
char words[STLEN];
puts("Enter a string, please.");
fgets(words, STLEN, stdin); //标准输入,从键盘读取
return 0;
}
fgets()第二个参数会限制读入字符的最大数量。会读入n-1个字符(存储一个空字符),或者遇到读到遇到的第一个换行符为止。
fgets会读入输入的换行符,比如输入 apple, 那么words中存储为apple\n\0
scanf(),gets(), gets_s(), fgets()。
直接使用puts()函数输出字符串
字符串函数都在string.h头文件中
size_t strlen(char const *string) 返回值是字符串长度,
他是一个无符号整数类型。
//复制
char *strcpy(char *dst, char const *src);//src复制到dst位置,如果两个内存出现重叠则结果是未定义的。所以必须保证dst空间足够,如果src超过dst则结果会有问题。返回值为dst的地址。
//拼接
char *strcat(char *dst, char const *src);//src添加到dst后面,返回dst地址。也必须保证dst空间充足不然结果未定义。
//比较
int strcmp(char const *s1, char const *s2); //字典比较, 字母顺序比较,如果s1s2则返回值大于0,s1=s2则返回值等于0.
对上面函数进行改进,防止难以预料到长字符串在目标数组溢出。
//复制
char *strncpy(char *dst, char const *src, size_t len);//src复制到dst位置,向dst只写入len个字符
//拼接
char *strncat(char *dst, char const *src, size_t len);//src添加到dst后面,向dst只写入len个字符
//比较
int strncmp(char const *s1, char const *s2, size_t len); //字典比较, 字母顺序比较,如果s1s2则返回值大于0,s1=s2则返回值等于0.
这里注意,如果len长度大于strlen(src),则会拷贝整个src。如果len长度小于src长度,则只拷贝len个字符,而不包括NUL字符结尾。
strchr
char* strchr(const char* str,char c)
功能:查找字符串中第一次出现c的位置
返回值:返回首次出现c的位置的指针,如果s中不存在c则返回NULL
strrchr
char* strchr(const char* str,char c)
功能:查找字符串中最后一次出现c的位置
返回值:返回最后一次出现c的位置的指针,如果s中不存在c则返回NULL
strpbrk
char* strpbrk(const char* s1, const char* s2)
功能:在字符串s1中寻找字符串s2中任何一个字符相匹配的第一个字符的位置,空字符NULL不包括在内
strstr
char* strstr(const char* str,const char* substr)
功能:检索子串在字符串中首次出现的位置
返回值:返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回NULL。如果substr为空字符串则返回str
strspn
size_t strspn(const char* str1, const char* str2)
功能:用来计算str1字符串开始部分匹配str2字符串的字符个数
返回值:返回字符串str1开头连续包含字符串str2内的字符数目。
如果str所包含的字符都属于str2,那么返回str1的长度
如果str的第一个字符不属于str2,那么返回0
strcspn
size_t strspn(const char* str1, const char* str2)
功能:用来计算str1字符串开始部分不匹配str2字符串的字符个数
返回值:返回字符串str1开头部分不出现在字符串str2内的字符数目。
strtok
char* strtok(char* str,const char* sep)
功能:根据分隔符将字符串分隔成一个个片段
返回值:返回下一个分割后的字符串指针,如果已无从分割则返回NULL
说明:
#include
#include
int main() {
char str[] = "abc@def.efg";
char str1[50] = { 0 };
strcpy(str1, str);
const char* sep = "@.";
printf("%s\n", strtok(str1, sep));//打印分隔符分开的第一部分abc
printf("%s\n", strtok(NULL, sep));//打印分隔符分开的第二部分def
printf("%s\n", strtok(NULL, sep));//打印分隔符分开的第三部分efg
}
sprintf()该函数和printf()函数相同,只不过他是把格式化后的字符串放到字符数组中而不是显示到屏幕。
int sprintf( char *buffer, const char *format [, argument,...] );
第一个参数就是指向要写入的那个字符串的指针
int main()
{
char str[80];
float a = 3.14;
sprintf(str, "Pi 的值 = %f", a);
puts(str);
return(0);
}
数字可以以字符形式存储,也可以以数值形式。
比如213,他可以已字符数组2,1,3,‘0/’的形式存储,也可以以int形式直接存储。
**C语言在运算的时候只能以数值形式,但是在打印显示的时候要求以字符串形式。**比如printf中我们需要把数值使用%d转换成字符串显示出来,而scanf()是把标准输入的字符串给转换成数值保存到某个变量。
#include
#include
int main(void)
{
char* s = "3";
int count = atoi(s);
for (int i = 0; i < count; i++)
{
printf("%d \n", i);
}
return 0;
}
但是这些函数有个问题就是他只把开头的整数转成字符,比如"42rng"他会返回整数42,如果字符串中没有数字,他会返回0.这种未定义的情况可能造成不安全的问题。所以我们可以选择下面的函数。
long int strtol(const char *str, char **endptr, int base) 把参数 str 所指向的字符串根据给定的 base 转换为一个长整数(类型为 long int 型),base表示进制
#include
#include
int main(void)
{
char str[30] = "2030300 This is test";
char *ptr;
long ret;
ret = strtol(str, &ptr, 10); //第一个参数要转换的字符串,第二个参数是转换后的字符串部分,返回值是数值部分
printf("数字(无符号长整数)是 %ld\n", ret);
printf("字符串部分是 |%s|", ptr);
return 0;
}
注意没有strtoi