字符串是C语言中最常用、最重要的数据类型之一。
字符串是以空字符(\0)结尾的char类型数组。
定义字符串的几种方式:
(1)字符串常量
#define MSG “Hello,CSDN.”
(2)char类型数组
char words[LENGTH] = "Today is 2023-04-14.";
(3)指向char的指针
const char *p = "Today is Friday";
用双引号括起来的内容称为字符串字面量(strring-literal),也叫字符串常量(string-constant)。
编译器自动加入末尾的 ‘\0’ 字符也作为字符串存储在内存中。
如果字符串字面量之间没有间隔或者用空白字符分隔,C会将其自动串联起来。
char s1[50] = "This is" " Visual Studio.";
char s2[50] = "This is Visual Studio.";
如果要在字符串内部使用双引号,需要在双引号前加一个反斜杠(\)。
puts("这是一个双引号\"\".");
字符串常量属于静态存储类别,如果在函数中使用字符串常量,该字符串只会被存储一次,在整个程序的生命周期内存在,即使函数被调用多次,用双引号括起来的内容被视为该字符串存储位置的指针。(类似于将数组名看做指向该数组位置的指针。)
用指定的字符串初始化字符串数组:
char s2[30] = "This is Visual Studio.";
这比标准的数组初始化形式简单的多:
char s3[20] = { 'H','e','l','l','o' }; //这是字符数组
char s4[20] = { 'H','e','l','l','o','\0'}; //这是字符串
指定数组大小时,要确保数组的元素个数至少比字符串长度多1,为了容纳编译器自动补全的 ‘\0’ ,所有没有使用的元素都被初始化为 ‘\0’ 。
也可以在初始化数组时不指明数组的大小:
har s5[] = "I love you more than i can say.";
字符数组名和其他数组名一样,是该数组首原属的地址。
对于上面的字符串数组 s5:
printf("s5 = %p = &s5[0] = %p\n",s5,&s5[0]);
printf("*s5 = %c = s5[0] = %c\n",*s5,s5[0]);
printf("*(s5+3) = %c = s5[3] = %c\n", *(s5+3), s5[3]);
输出:
s5 = 000000837278F710 = &s5[0] = 000000837278F710
*s5 = I = s5[0] = I
*(s5+3) = o = s5[3] = o
使用数组和指针都可以创建一个字符串:
char s5[] = "I love you more than i can say.";
const char *ps5= "I love you more than i can say.";
以上2个声明:s5 和 ps5 都是该字符串的地址。
s5+1
这样的操作是允许的,即指向数组的下一个元素,而 ++s5
这样的操作是错误的,因为递增运算符只能用于可修改的左值(变量名)前。++ps5
的操作当然是允许的。 char s5[] = "I love you more than i can say.";
const char *ps5= "I love you more than i can say.";
初始化字符数组来存储字符串和初始化指针来指向字符串的区别:
数组名是常量,指针名是变量。
前面的数组,只包含一个字符串,现在考虑含有多个字符串的数组。
指针数组表示法:
char* ps6[3] = { "你说我比大笨钟还笨要怎么比",
"吵架我太安静,",
"钟至少还有声音" };
char型数组的数组:
char s6[3][30] = {"街灯下的橱窗,",
"有一种落寞的温暖,",
"吐气在玻璃上..."};
相关输出:
你说我比大笨钟还笨要怎么比 街灯下的橱窗,
吵架我太安静, 有一种落寞的温暖,
钟至少还有声音 吐气在玻璃上...
size of ps6 = 24, size of s6 = 90
相同点:
两者都代表3个字符串,使用一个下标时表示一个字符串,使用两个下标时表示某个字符串中的某个字符。
不同点:
ps6 是一个含有 3 个指针的数组,占用24个字节(64位系统,一个指针占用8个字节);
s6 是一个含有3个数组的数组,每个数组含有3讴歌char类型的值,共占用90个字节;
ps6 中的指针指向初始化时所用的字符串常量的位置,这些字符串字面量被存储在静态存储中,s6 中的数组则存储着字符串常量的副本,即每个字符串都被存储了2次,内存使用效率低;
指针数组中的字符串,长度不用全都相同,它们也不必存储在连续的内存中。
如果要将一个字符串读入程序,首先需要预留该字符串的空间,然后再使用输入函数获取该字符串。
程序不会在读取字符串时顺便计算它的长度,然后再分配空间,除非你自己写了一个这样的函数。
错误的输入:
char* name;
scanf("%s",name);
有效的输入:
char name[20];
scanf("%s",name);
另一种方法是使用c库函数来分配内存,后面文章介绍。
在读取字符串时,scanf 和 %s 的方式只能读取一个单词,但我们的输入通常更长。
以前,可以使用gets()函数来完成整行输入,直到遇到回车(换行符),它会丢弃换行符,并且会在末尾添加一个空字符。
char string[50];
gets(string);
puts(string);
gets()函数只有一个参数,他无法检查数组是否能容纳用户输入,如果输入太长,则会导致缓冲区溢出,如果多余输入的字符只是占用了未被使用的内存,则可能不会出现问题;否则会擦掉其他数据。
因此,该函数具有安全隐患。C11标准废除了gets()函数,但大部分编译器依然支持他,因为现存代码很多都用了gets(),而且使用得到的情况下也很好用。
fgets()函数通过第二个参数来限制读入的字符数量。
该函数专门设计用以处理文件输入,与gets()函数的区别主要有:
stdin
fgets()函数把换行符放在字符串末尾。通常要与fputs函数配合使用,其第二个参数只能他要写入的文件,若要显示在屏幕上,值为:
stdout
示例:
void string_io()
{
char str[20];
puts("Your input:");
fgets(str,20,stdin);
puts(str);
puts(str);
fputs(str,stdout);
fputs(str, stdout);
}
输出:
Your input:
hhhsgdfjiai hfdh
hhhsgdfjiai hfdh
hhhsgdfjiai hfdh
hhhsgdfjiai hfdh
hhhsgdfjiai hfdh
puts()函数输出时会自动加1个换行,加上fgets()函数不会丢弃输入的换行符,所有会多出1个空行;
当输入长度超出19时,只会读取前面19个字符。
fgets()函数返回指向char的指针,如果进行顺利,返回的地址与传入的第1个参数相同。如果函数读到文件结尾,将返回空指针,保证不会指向有效的数据。
void string_io()
{
char str[5];
puts("Enter your words:");
while (fgets(str, 5, stdin) != NULL && str[0] != '\0')
fputs(str,stdout);
}
输出:
Enter your words:
Hello, I am JayChou.
Hello, I am JayChou.
好像我输入的长度大于了4,也能正常输出?
解读:
第一次只读入了:“Hell”,存储为“Hell\0”,fputs()函数打印该字符串,这里并没有换行;
第二次继续读入:“o, I”,存储为“o,I\0”,继续在上次打印后面打印…
…
直到最后的:“hou.\n”,fgets()将其存储为“hou.\n\0”,打印…
换成puts输出可以清晰地看到这一过程:
Enter your words:
Hello, I am JayChou.
Hell
o, I
am
JayC
hou.
不想在结尾加换行符:
while(str[i]!='\n')
i++;
str[i]='\0';
丢弃多出的字符:
while(fgets() != '\n')
continue;
空字符和空指针:
- 都可以用数字0表示
- 空字符是字符类型,编码为0,唯一的;
- 空指针是指针类型,还可以用NULL表示,不会与任何数据的有效地址对应。
C11新增gets_s()函数(可选),用一个参数限制读入字符数。
与fgets()函数的区别:
gets、fgets、gets_s比较:
- 目标存储区装得下输入行时,3个函数都可以,但fgets后面会有一个换行符;
- 输入太长时:gets不安全;gets_s函数很安全,但要额外编写相应的处理函数。fgetshanshu 最好用。
看成获取单词的函数即可。
也有溢出风险,可以指定输入宽度,如:
scanf("%10s",str);
将在读取10个字符或者读取到第一个空白字符(空行、空格、制表符、换行符)时停止。
scanf()返回一个整数值,等于成功读取的项数或EOF(文件结尾)。
char str[10];
puts("Enter your words:");
printf("%d\n",scanf("%c %c",str,str+1));
puts(str);
输出:
Enter your words:
A B
2
AB
C有3个标准库函数用于打印字符串:puts()、fputs()、printf()
将字符串的地址传递给他即可,输出会自动在末尾添加一个换行符。
char str1[] = "The stars change, but the mind remains the same.";
char* str2 = "Refrain from excess.";
puts("There is always a better way.");
puts(str1);
puts(str2);
puts(str1+4);
puts(&str1[4]);
puts(str2+8);
puts(&str2[8]);
输出:
There is always a better way.
The stars change, but the mind remains the same.
Refrain from excess.
stars change, but the mind remains the same.
stars change, but the mind remains the same.
from excess.
from excess.
puts遇到空字符才会停止输出,所以字符数组就不能使用它。
是puts针对稳健的定制版。
不多说了,也不会自动追加换行符。
打印多个字符串很方便。
输出时,执行时间更长(但用户无感知)。
在getchar()和putchar()函数的基础上自己编写输入输出函数。
输入函数示例:
void my_input(char string[])
{
int i=0;
int j = 0;
while((string[i] = getchar())!= '\n')
i++;
while (string[j] != '\n')
j++;
string[j] = '\0';
}
输出函数示例:
void my_output(const char* string)
{
while (*string)
putchar(*string++);
}
测试:
Fortune favors the bold.
Fortune favors the bold.
当然有很多问题,仅做演示。
完整code:
#include
void string_array();
void string_io();
void my_input(char string[]);
void my_output(const char* string);
int main()
{
char test_str[30];
//string_array();
//string_io();
my_input(test_str);
my_output(test_str);
return 0;
}
// 字符串数组、指针输出测试
void string_array()
{
char s1[30] = "This is" " Visual Studio.";
char s2[30] = "This is Visual Studio.";
char s3[20] = { 'H','e','l','l','o' };
char s4[20] = { 'H','e','l','l','o','\0'};
char s5[] = "I love you more than i can say.";
const char *ps5= "I love you more than i can say.";
char* ps6[3] = { "你说我比大笨钟还笨要怎么比",
"吵架我太安静,",
"钟至少还有声音" };
char s6[3][30] = {"街灯下的橱窗,",
"有一种落寞的温暖,",
"吐气在玻璃上..."};
puts(s1);
puts(s2);
puts("这是一个双引号\"\".");
puts(s3);
puts(s4);
printf("s5 = %p = &s5[0] = %p\n",s5,&s5[0]);
printf("*s5 = %c = s5[0] = %c\n",*s5,s5[0]);
printf("*(s5+3) = %c = s5[3] = %c\n\n", *(s5+3), s5[3]);
for (int i = 0;i < 3;i++)
printf("%-36s %-25s\n", ps6[i], s6[i]);
printf("\nsize of ps6 = %zd, size of s6 = %zd\n",sizeof(ps6),sizeof(s6));
}
// 字符串输出函数测试
void string_io()
{
char str1[] = "The stars change, but the mind remains the same.";
char* str2 = "Refrain from excess.";
puts("There is always a better way.");
puts(str1);
puts(str2);
puts(str1+4);
puts(&str1[4]);
puts(str2+8);
puts(&str2[8]);
}
// 自定义输入函数
void my_input(char string[])
{
int i=0;
int j = 0;
while((string[i] = getchar())!= '\n')
i++;
while (string[j] != '\n')
j++;
string[j] = '\0';
}
// 自定义输出函数
void my_output(const char* string)
{
while (*string)
putchar(*string++);
}