> 文章列表 > 【C 字符串】01 字符串定义与输入输出

【C 字符串】01 字符串定义与输入输出

【C 字符串】01 字符串定义与输入输出

文章目录

  • 一、表示字符串和字符串I/O
    • 1.1 字符串的定义
      • 1.11 字符串字面量(字符串常量)
      • 1.12 字符串数组及其初始化
      • 1.13 字符串的数组形式和指针形式
      • 1.14 数组和指针的区别
      • 1.15 指针数组和char型数组的数组
  • 二、字符串输入
    • 2.1 分配空间
    • 2.2 gets()、fgets()、gets_s()、s_gets()函数
      • 2.21 不安全的gets()函数
      • 2.22 fgets()函数
      • 2.23 gets_s()函数
    • 2.3 scanf()函数
  • 三、字符串输出
    • 3.1 puts()函数
    • 3.2 fputs()函数
    • 3.3 printf()函数
  • 四、自定义输入输出函数

字符串是C语言中最常用、最重要的数据类型之一。

一、表示字符串和字符串I/O

字符串是以空字符(\\0)结尾的char类型数组。

1.1 字符串的定义

定义字符串的几种方式:

(1)字符串常量

#define MSG “Hello,CSDN.

(2)char类型数组

char words[LENGTH] = "Today is 2023-04-14.";

(3)指向char的指针

const char *p = "Today is Friday";

1.11 字符串字面量(字符串常量)

双引号括起来的内容称为字符串字面量(strring-literal),也叫字符串常量(string-constant)。

编译器自动加入末尾的 ‘\\0’ 字符也作为字符串存储在内存中。

如果字符串字面量之间没有间隔或者用空白字符分隔,C会将其自动串联起来。

	char s1[50] = "This is" " Visual Studio.";char s2[50] = "This is Visual Studio.";

如果要在字符串内部使用双引号,需要在双引号前加一个反斜杠(\\)。

puts("这是一个双引号\\"\\".");

字符串常量属于静态存储类别,如果在函数中使用字符串常量,该字符串只会被存储一次,在整个程序的生命周期内存在,即使函数被调用多次,用双引号括起来的内容被视为该字符串存储位置的指针。(类似于将数组名看做指向该数组位置的指针。)

1.12 字符串数组及其初始化

用指定的字符串初始化字符串数组:

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

1.13 字符串的数组形式和指针形式

使用数组和指针都可以创建一个字符串:

	char s5[] = "I love you more than i can say.";const char *ps5= "I love you more than i can say.";

以上2个声明:s5 和 ps5 都是该字符串的地址。

  1. 两种声明方式,编译器都为该字符串在静态区分配了32个元素的空间,包括字符串的结尾字符 ‘\\0’ 在内。
  2. 在数组形式中, s5 是地址常量,不能被更改,如果改变则意味着改变了数组的存储位置(但存储位置不是我们手动指定的)。s5+1 这样的操作是允许的,即指向数组的下一个元素,而 ++s5 这样的操作是错误的,因为递增运算符只能用于可修改的左值(变量名)前。
  3. 而 ps5 是一个指针变量,他本是有一个存储位置,这个位置存储的是它指向的字符串的首字符地址,++ps5 的操作当然是允许的。
  4. 字符串常量被视为const数据,由于 ps5 指向这个const数据,所有应该白 ps5 声明为指向 const 数据的指针。即不能用 ps5 改变他所指向的数据,但是仍可以改 变 ps5 的值(即他可以指向别处)。
  5. 初始化数组,吧静态存储区的字符串拷贝到数组中;而初始化指针只把字符串的地址拷贝给指针。

1.14 数组和指针的区别

	char s5[] = "I love you more than i can say.";const char *ps5= "I love you more than i can say.";

初始化字符数组来存储字符串和初始化指针来指向字符串的区别:

数组名是常量,指针名是变量。

  1. 两者都可以使用数组表示法:s5[3]、ps5[3]
  2. 两者都能进行指针加法操作:* (s5+2)、* (ps5+3)
  3. 只有指针表示法可以使用地址操作(可修改的左值,即变量)
  4. 非const数组的数组名是常量,数组元素是变量。如果要修改字符串,使用数组表示法。

1.15 指针数组和char型数组的数组

前面的数组,只包含一个字符串,现在考虑含有多个字符串的数组

指针数组表示法:

	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次,内存使用效率低;

指针数组中的字符串,长度不用全都相同,它们也不必存储在连续的内存中。

二、字符串输入

如果要将一个字符串读入程序,首先需要预留该字符串的空间,然后再使用输入函数获取该字符串。

2.1 分配空间

程序不会在读取字符串时顺便计算它的长度,然后再分配空间,除非你自己写了一个这样的函数。

错误的输入:

char* name;
scanf("%s",name);

有效的输入:

char name[20];
scanf("%s",name);

另一种方法是使用c库函数来分配内存,后面文章介绍。

2.2 gets()、fgets()、gets_s()、s_gets()函数

2.21 不安全的gets()函数

在读取字符串时,scanf 和 %s 的方式只能读取一个单词,但我们的输入通常更长。

以前,可以使用gets()函数来完成整行输入,直到遇到回车(换行符),它会丢弃换行符,并且会在末尾添加一个空字符。

	char string[50];gets(string);puts(string);

gets()函数只有一个参数,他无法检查数组是否能容纳用户输入,如果输入太长,则会导致缓冲区溢出,如果多余输入的字符只是占用了未被使用的内存,则可能不会出现问题;否则会擦掉其他数据。

因此,该函数具有安全隐患。C11标准废除了gets()函数,但大部分编译器依然支持他,因为现存代码很多都用了gets(),而且使用得到的情况下也很好用。

2.22 fgets()函数

fgets()函数通过第二个参数来限制读入的字符数量。

该函数专门设计用以处理文件输入,与gets()函数的区别主要有:

  1. fgets()函数用第二个参数来限制读入字符的最大数量,假设为 n ,则会在读入 n-1 个字符,或者遇到换行符时停止;
  2. 读到换行符时不会丢弃;
  3. fgets()函数的第三个参数指明要读入的文件,如果从键盘读入,参数值则为: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 hfdhhhhsgdfjiai hfdhhhhsgdfjiai 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, Iam
JayC
hou.

不想在结尾加换行符:

while(str[i]!='\\n')i++;
str[i]='\\0';

丢弃多出的字符:

while(fgets() != '\\n')continue;

空字符和空指针:

  1. 都可以用数字0表示
  2. 空字符是字符类型,编码为0,唯一的;
  3. 空指针是指针类型,还可以用NULL表示,不会与任何数据的有效地址对应。

2.23 gets_s()函数

C11新增gets_s()函数(可选),用一个参数限制读入字符数。

与fgets()函数的区别:

  1. gets_s()函数只从标准输入中读取数据,所以没有第三个参数;
  2. gets_s()函数读取的换行符会丢弃;
  3. gets_s()读取字符数大于限制时,处理比较麻烦…

gets、fgets、gets_s比较:

  1. 目标存储区装得下输入行时,3个函数都可以,但fgets后面会有一个换行符;
  2. 输入太长时:gets不安全;gets_s函数很安全,但要额外编写相应的处理函数。fgetshanshu 最好用。

2.3 scanf()函数

看成获取单词的函数即可。

也有溢出风险,可以指定输入宽度,如:

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()

3.1 puts()函数

字符串的地址传递给他即可,输出会自动在末尾添加一个换行符。

	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遇到空字符才会停止输出,所以字符数组就不能使用它。

3.2 fputs()函数

是puts针对稳健的定制版。

  1. 第二个参数指定要写入的文件,显示在屏幕上则设为:stdout
  2. 不会在输出末尾追加换行符。

3.3 printf()函数

不多说了,也不会自动追加换行符。

打印多个字符串很方便。

输出时,执行时间更长(但用户无感知)。

四、自定义输入输出函数

在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<stdio.h>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++);
}