字符串和字符串函数(一)
表示字符串和字符串I/O
字符串是以空字符(\0)结尾的char类型数组。C提供了许多用于处理字符串的函数。
表示字符串的几种方式:
#include<stdio.h>
#define STR "I am a symbolic string constant."
#define SIZE 50
int main(void)
{
char words[SIZE] = "I am a string in an array.";
const char* pt1 = "Something is pointing at me.";
puts("Here are some strings.");
puts(STR);
puts(words);
puts(pt1);
return 0;
}
运行结果:
puts()函数也属于stdio.h系列中的输入输出函数。puts()函数只显示字符串,而且自动在显示的字符串末尾加上换行符。
在程序中定义字符串
字符串字面量(字符串常量)
用双引号括起来的内容称为字符串字面量,也叫字符串常量。
ANSI C标准起,如果字符串字面量之间没有间隔,或者空白字符分隔,C会将其视为字符串字面量。
示例:
char greeting[40] = "hello, ""nice to meet you!";
等价于
char greeting[40] = "Hello, nice to meet you!";
字符串常量属于静态存储类别,这说明如果函数中使用字符串常量,该字符串只会被存储一次,在整个程序的生命期内存在。
用双引号括起来的内容被视为指向该字符串存储位置的指针。
字符串数组和初始化
定义字符串数组时,必须让编译器知道需要多少空间。
一种方法是用足够的空间的数组存储字符串。示例:
const char str[50] = "This is a string.";
在指定数组大小时,要确保数组的元素个数至少比字符串长度多1,是为了容纳空字符。所有未被使用的元素都被自动初始化为0。
示例:
const char str[10] = "string.";
(画的有点丑。。凑合看)
省略数组初始化声明中的大小,编译器会自动计算数组大小:
#include<stdio.h>
int main()
{
const char str[] = "I only hope that I will one day "
"deserve what you have done for me.";
printf("The size of str is %zd.\n", sizeof(str));
return 0;
}
运行结果:
字符数组名和其他数组名一样,是该数组首元素的地址。
示例:若有
const char str[10] = "String";
则下面表达式都为真:
str == &str[0] ;
*str == 'S';
*(str + 1) == str[1] == 't';
还可以使用指针表示法创建字符串:
const char* ptr = "Something is pointing at me.";
但和数组的声明不完全一样。
数组和指针
通常,字符串都作为可执行文件的一部分存储在数据段中。字符串存储在静态存储区中。
程序示例:
#include<stdio.h>
#define STR "I am a computer."
int main()
{
//指针形式
const char* ptr = STR;
//数组形式
const char str[] = STR;
printf("address of STR: %p\n", STR);
printf("address of ptr: %p\n", ptr);
printf("address of str: %p\n", str);
return 0;
}
输出结果:
可以看出宏定义的字符串地址和指针指向的地址相同,原因是字符串存储在内存中的r data区域,而数组表示法的地址和他们不同,原因是声明数组是在栈区中开辟空间,再把字符串拷贝进去,所以地址不一样。
数组和指针区别
char str1[] = "I am a string.";
const char* ptr = "I am a string.";
主要区别:
1, 数组名str1是常量,而指针名ptr是变量。
2,两者都可以使用数组表示法:
putchar(str1[1]);
putchar(ptr[1]);
3,两者都能进行指针加法操作:
putchar(*(str1 + 1));
putchar(*(ptr + 1));
但是, 只有指针表示法可以进行递增操作:
putchar(*(ptr++));
若想让两者统一:
ptr = str1; //ptr指向str1
str1 = ptr; //非法构造,错误
其实还是左值和右值问题,左值一般是可被赋值的变量,右值是常量。
字符串数组
字符串数组经常使用两种表示方法:指向字符串的指针数组和char类型数组的数组。
程序示例:
#include<stdio.h>
#define SLEN 40
#define LIM 5
int main(void)
{
const char* mytalent[LIM] = {
"Adding number swiftly",
"Multiplying accurately",
"Stashing data",
"Following instructions to the letter",
"understanding the C language"
};
char yourtalent[LIM][SLEN] = {
"Walking in a straight line",
"Sleeping", "Watching television",
"Mailing letters", "Reading email"
};
int i = 0;
printf("%-36s %-25s\n", "Mytalent", "Yourtalent");
for (i = 0; i < LIM; ++i)
{
printf("%-36s %-25s\n", mytalent[i], yourtalent[i]);
}
printf("\nsizeof mytalent: %zd, sizeof yourtalent: %zd\n",
sizeof(mytalent), sizeof(yourtalent));
return 0;
}
运行结果:
mytalent数组只占用了20字节,因为数组里面的每个元素都是指针,每个占4字节。
虽然指针数组占用空间少且更高效,但数组里的这些指针指向的字符串字面量不能更改。
字符串输入
如果想把一个字符串读入程序,首先必须预留存储该字符串的空间,然后用输入函数获取该字符串。
分配空间
最简单的方法是,在声明时指明数组大小且空间足够:
char str[40] = ”I am a string.";
gets()函数
该函数用于显示字符串,并在末尾添加换行符。常与puts()配对使用。
如果输入的字符串过长,会导致缓冲区溢出。。
在C11标准中已经不再使用它,比如 vs2019不支持gets()函数。
gets()的替代品
过去常用fgets()代替,C11标准中新增的gets_s()也可以代替
fgets()函数
它通过第二个参数限制读入的字符数来解决溢出的问题。
格式:
fgets(words, STLEN, stdin);
和gets()的区别有:
- fgets()函数的第二个参数指明了读入字符的最大数量。如果参数为n,则读入n - 1个字符,或者读到第一个换行符为止。
- 如果fgets()读到一个换行符, 会把它存储在字符串中,gets()会直接丢弃。
- fgets()函数的第三个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin(标准输入)作为参数,该标识符定义在stdio.h中。
gets_s()函数
格式:
gets_s(words, STLEN);
和fgets()区别:
- 只从标准输入中读取数据,所以不需要第三个参数。
- 遇到换行符会丢弃它而不是存储它。
- 如果gets_s()函数读到最大字符数都没有读到换行符,会执行以下操作:首先把目标数组中的首字符设置为空字符,读取并丢弃随后的输入直至读到换行符或文件结尾,然后返回空指针。
s_gets()函数
读取整行输入并用空字符代替换行符。
或者读取一部分输入,并丢弃其余部分。
char* s_gets(char* st, int n)
{
char* ret_val;
int i;
ret_val = fgets(st, n, stdin);
if (ret_val) //ret-val != nullptr
{
while (st[i] != '\n' && st[i] != '\0')
{
++i;
}
if (st[i] == '\n')
st[i] == '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
scanf()函数
scanf()更像是“获取单词”, 因为它从第一个非空白字符作为字符串的开始,若使用%s转换说明,以下一个空白字符(空行,空格,制表符或换行符)作为字符串的结束。
示例:
#include<stdio.h>
#include<string.h>
int main(void)
{
char name[20];
printf("Please input name:\n");
scanf_s("%s", name, 20);
printf("%s\n", name);
return 0;
}
运行结果:
可以看出,scanf()只读取了空格前面的一个单词。
字符串输出
C有三个标准库函数来打印字符串:
- puts()
- fputs()
- printf()
puts()函数
使用时只需把字符串的地址传递给它。
const char* str = "I am a string.";
puts(str);
fputs()函数
fputs()是puts()函数针对文件定制的版本,区别如下:
- fputs()函数的第二个参数指明要写入数据的文件。如果要打印在显示器上,用stdout作为该参数。
- 与puts()不同,fputs()函数不会在输出的末尾添加换行符。
注意
gets()丢弃输入中的换行符,而puts()在输出中添加换行符。
fgets()保留输入中的换行符,fputs()不在输出中添加换行符。
printf()函数
printf()不会自动在每个字符串末尾加上换行符,因此必须在参数中指明在哪里使用换行符。示例:
printf("%s\n", str);
等价于
puts(str);
自定义输入输出函数
假设需要一个类似puts()但不会自动添加换行符的函数。
#include<stdio.h>
void put1(const char* string)
{
while (*string != '\0')
putchar(*string++);
}
假设要设计一个类似puts()的函数,而且该函数还给出待打印字符的个数。
#include<stdio.h>
int put2(const char* string)
{
int count = 0;
while (*string != '\0')
{
putchar(*string++);
count++;
}
putchar('\n'); //不统计换行符
return count;
}
示例:
#include<stdio.h>
void put1(const char* string)
{
while (*string != '\0')
putchar(*string++);
}
int put2(const char* string)
{
int count = 0;
while (*string != '\0')
{
putchar(*string++);
count++;
}
putchar('\n'); //不统计换行符
return count;
}
int main(void)
{
put1("I only hope that I will oneday");
put1("deserve what you have done for me.\n");
printf("This string has %d characters.\n",
put2("You like an angel."));
return 0;
}
运行结果:
。。。