C primer plus学习笔记 —— 7、字符串
文章目录
- 定义字符串
- 字符串常量
- 字符串数组
- 指向char的指针
- 不同点
- 操作常量区字符串
- 定义字符串数组
- 字符串IO(输入/输出)
- 字符串输入
- 几种字符串输入函数对比
- 字符串输出
- 字符串函数
- 拼接
- 拷贝
- 比较
- 格式化字符串
- 字符串和数字之间转换
- 数字转换成字符串
- 字符串转换成数字
- atoi atof atol
- strtol strtoul strtod
定义字符串
字符串是以空字符\0
结尾的char类型数组
三种方式:
- 字符串常量
- char类型数组
- 指向char的指针
字符串常量
"I am a string"
用双引号括起来的内容就是字符串常量。编译器会在末尾自动加空字符\0
他属于静态存储类别,也就是该字符串只会被存储一次,在整个程序生命周期内存在。
printf("s%," "sdf");
使用%s在printf中打印字符串转换。
字符串数组
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数组,有了空字符他才可以表示字符串。
- 所以我们一般采用第三种方式来让编译器自动计算数组大小。非常方便。
因为字符串数组和数组一样,所以我们也可以使用指针表示数组首元素地址
char * p = words;
p == words == &words[0]
指向char的指针
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;
}
本身字符串是存储在静态常量区,我们打印出他的地址。虽然打印了两次这个字符串,实际程序使用的都是同一个地址,也就是他在静态存储区中只存在一份。
我们发现字符指针是直接指向这个地址,而字符串数组的首地址并不指向这个地址。
也就是有:
- 数组形式的定义,表示在计算机分配了一个包含29个元素的数组。把静态存储区中的字符串拷贝到数组中。
- 字符指针是直接指向这个字符串所在静态存储区中的地址。
操作常量区字符串
如果我试图修改常量区的字符串,比如上面代码,修改字符串中某一个字符。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"
};
定义字符串数组,也可以使用上述两种方式。
指针方式:效率更高,因为不用存储字符串副本,但是不能修改原字符串。(绝大多数字符串操作由指针完成)
数组方式:效率低,但是可以修改字符串。
字符串IO(输入/输出)
字符串输入
在读取字符串时,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()。
- gets() 输入太长会擦除数据
- gets_s() 当输入不符合预期则处理不太方便
- fgets()通常作为最佳选择
- scanf() 会在空白字符(空行,空格,制表符或换行符)就读入停止
字符串输出
直接使用puts()函数
字符串函数
字符串函数都在string.h头文件中
strlen(char *) 返回值是字符串长度
拼接
strcat(char*, char *) 接收两个字符串,并把第二个字符串附加到第一个末尾。然后将第一个字符串修改为拼接后的字符串,第二个不变,返回第一个字符串的地址。
但是strcat有潜在危险,他不会检查第一个字符串是否能够容纳拼接后的字符串。容易造成溢出。如果第一个参数容量不够,则第一个字符串就会丢失’/0’而导致程序崩溃。
char * a = "abcde";
char * b = "fgh";
strcat(a,b); //segment fault
strncat(char*, char *,int ) 在strcat的基础上,增加第三个参数,他表示能添加的最大字符串长度。strncat 函数, 多了一个参数 len ,意思是追加的字符串的 元素的个数 ,从而 避免了 字符串在 追加本身的时候 ,由于丢失了 ‘\0’ ,而 导致的程序崩溃 。还是需要保证第一个字符串容量足够,虽然程序不会崩溃,但是会可能截断第二个参数。
拷贝
与拼接原理类似
strncpy可以保证程序不崩溃,如果
比较
在比较字符串时我们不能用==。因为双等号比较的是地址是否相等。比较字符串内容我们使用strcmp。
strcmp(char* , char*); 字符串比较函数,比较的是两个字符串的内容。而不是整个数组,即使第一个字符串数组长度比第二个大,只要内容相同。就相等。
如果两个字符串相同,则返回0. 如果第一个字符串在第二个字符串前面,则返回负数。如果第一个字符串在第二个字符串后面则返回正数。
strncmp(char*, char*, int); 第三个参数指定,比较的长度,也就是比较两个字符串的前n个字符。
格式化字符串
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()是把标准输入的字符串给转换成数值保存到某个变量。
数字转换成字符串
- 使用sprintf()函数,如上所讲。
- 使用itoa,用到stdlib
字符串转换成数字
atoi atof atol
#include <stdio.h>
#include <stdlib.h>
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.这种未定义的情况可能造成不安全的问题。所以我们可以选择下面的函数。
strtol strtoul strtod
long int strtol(const char *str, char **endptr, int base) 把参数 str 所指向的字符串根据给定的 base 转换为一个长整数(类型为 long int 型),base表示进制
#include <stdio.h>
#include <stdlib.h>
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