C语言字符串摘要

本文介绍了C语言中字符串的基础定义、初始化方法、指针操作及输入输出函数。重点讲解了字符串的存储方式、指针与字符串的关系,并对比了不同输入输出函数的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义:C语言中字符串是以’\0’(空字符)结尾的char类型数组。

一、在C程序中定义字符串:

注意1:指定数组大小时,要确保数组的元素个数至少比字符串长度多1,所有未被使用的元素都被自动初始化为0。这一特性往往会不经意间造成内存浪费。
注意2:字符串大小和字符串长度不同。前者指该字符占用多少空间(字符串的大小是指其字符数加上一个空字符),后者指该字符串的字符个数。(比如字符串常量"string"的大小是7,长度是6)。

1、字符数组初始化 — 右值为字符串字面量(字符串常量)(这种形式比标准的数组初始化形式简单得多)

示例:
char greeting[50] = “how”“are you !”;(当然声明时如果后续不需要再向该字符数组中填充数据,那么也可以省略掉数组初始化声明中的大小,即方括号中的值)。
(你或许会疑问为什么字符串末尾没有(\0)呢,其实这种写法编译器会自动在末尾加上(\0))。
操作方式:操作greeting[ X ]来操作字符串(X属于[0,49])。

2、字符数组初始化 — 右值为字符数组(标准的数组初始化形式)

示例:
char m1[40] = {‘i’,‘l’,‘o’,‘v’,‘e’,‘y’,‘o’,‘u’,‘\0’};
这种初始化形式需要写上(\0),如果没有最后面的空字符,那么m1就是一个字符数组,不是一个字符串。
操作方式:操作m1[ X ]来操作字符串(X属于[0,39])。

3、运用指针

示例:
const char *p1 = “I LOVE YOU”;
操作方式:数组表示法和指针表示法操作皆可(推荐使用数组表示法,那样可读性更好)如:p1[ X ](X 取值依赖字符串末尾\0的下标)

4、思考

思考以下两种形式定义字符串的异同点:(其实说的是思考1,3的异同点)
①:const char * pt1 = “Something is pointing at me.”;
②:const char ar1[ ] = “Something is pointing at me.”;
相同点:pt1和ar1都是该字符串的地址(也是字符串首字符的地址)
不同点:
字符串常量被视为const数据,所以不能用指针改变它所指向的字符串常量(字符串字面量)(注意!一定不能用指针改变字符串字面量,压根就不行,C和C++中都不行,我试过了!24年9月01日发现把const去掉就是可以的,C,C++均可),指针应该声明为指向const 数据的指针(声明时const 位置在*的左边就行),但是可以用指针访问字符串常量中的字符,比如,语句printf(“%c”,*pt1);就能用于打印大S(即该字符串的开头字母),那你觉得语句printf(“%s”,*pt1);能打印出该字符串吗?答案肯定是不行的,因为指针pt1解引用后得到的是char类型的数据,而转换说明%s对应的待打印项要求是一个字符串的地址,我们可以用语句printf(“%s”,pt1);或者语句printf(“%s”,ar1);来打印该字符串。

通常,字符串作为可执行文件的一部分存储在数据段中。当程序载入内存时,也载入了程序中的字符串(字符串存储在静态存储区中),而程序在开始运行时才会为该数组分配内存,此时才将字符串拷贝到数组中。
所以说代码段②中字符串有两个副本,一个是在静态存储区(静态内存中的字符串字面量),一个是拷贝到对应数组中的字符串。
但是代码段①中字符串只有一个副本,而该字符串常量被视为const数据,指针是修改不了该字符串字面量的内容的,所以指针应该声明为指向const 数据的指针。
数组名为一个常量,不能像指针那样进行自增操作,但是用它可以改变字符串常量的数据,当然这只是改变字符串常量副本中的数据而已。
所以,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针(3的形式),使用数组(1的形式)。

二、指针和字符串

1、代码演示:

#include <stdio.h>
#define M 40
#define N 5

int main()
{
    //一个指针变量一般在内存中占4字节。(占8字节的也有)
    //ar1是一个指针数组,ar2是char类型数组的数组,也就是二维char类型数组
    printf("==========ONE===========\n");
    const char * ar1[N] = {
        "I love you.",
        "Do you love me?",
        "I think your reply is definitely yes.",
        "Oh my god,"
    };
    char ar2[N][M] = {
        "More than 1.31 million residents ",
         "he said at a news conference ",
         " where",
         "floodwaters have receded after "
    };
    printf("sizeof ar1:%zd sizeof ar2:%zd\n",sizeof(ar1),sizeof(ar2));

    //指针的指向特性!
    printf("==========TWO===========\n");
    const char * mesg = "Don't be a fool!";
    const char * copy;

    copy = mesg;
    printf("%s\n",copy);
    printf("mesg = %s; &mesg = %p; value = %p\n", mesg, &mesg, mesg);
    printf("copy = %s; &copy = %p; value = %p\n",copy, &copy, copy);

    //双引号中的内容被视为指向该字符串存储位置的指针
    printf("==========THREE===========\n");
    printf("%s, %p, %c\n","we", "are", *"space farers");
    return 0;
}

2、知识点:

ONE:一个指针变量一般在内存中占4字节。(占8字节的也有)
TWO:字符串的地址和指向它的指针的值相同,(指针的值就是它所指向对象的地址值)。但是不同指针自身的地址肯定是不同的,包含某一段内容的数组和指向该内容的指针的地址也肯定不一样。
THREE:字符串字面量属于静态存储类别,这说明如果在函数中使用它,该字符串只会存储一次,在整个程序的生命期内存在,字符串字面量(“……”)可被视为指向该字符串存储位置的指针。

3、运行截图:

运行截图
.

三、字符串输入输出函数(字符串I/O)

puts()应该与gets()配对使用,fputs()应该和fgets()配对使用。

1、输入函数

(1)gets()函数(有弊端,使得程序比较危险)

传入一个指针参数,通常为数组名,它读取一整行输入,直至遇到换行符,然后丢弃换行符,存储其余字符,并在这些字符的末尾添加一个空字符使其成为一个C字符串。
返回值:
①:若gets()函数成功执行,则会返回其参数值,同时参数值自身也跟着改变,比如参数值是一个空字符数组,调用gets()函数后就不空了
②:gets()函数读到文件结尾或者在读入数据时出现了某些错误会返回空指针,文件结尾是可以在键盘输入中模拟的,当然在真实文件中没必要模拟,其会自动识别文件结尾
不安全:如果输入的字符过长,会导致缓冲区溢出,即多余的字符超出了指定的目标空间,可能擦写掉程序中其他数据,导致程序异常终止。
C99标准承认了该函数的弊端,但仍保留了该函数,C11标准已经完全摒弃了该函数,但是编译器仍然大部分还是支持该函数以兼容以前的代码,codeblocks就是一个扩展性很好的壳子,gcc才是编译器,最新的gcc仍然支持gets()函数,但是给了一个警告说它不安全

(2)fgets()函数(最佳选择)

作为gets()函数的替代品之一,专门设计该函数用于处理文件输入,该函数有三个参数,
参数介绍:
第一个是指针参数,通常是数组名。
第二个参数指明了读入字符的最大数量,(如果为n,那么fgets()函数将读入n-1个字符,最后一个位置留作\0符号,或者读到第一个换行符为止,与gets()函数不同,fgets()函数不会丢弃读到的第一个换行符。)。(如果一行中只输入一个换行符,那么fgets()并不会返回空,它会存储\n\0,当然这要求第二个参数大于等于2)
第三个参数指明要读入的文件,如果要读入键盘中的数据,则以stdin(标准输入)作为参数,该标识符定义在stdio.h中。
返回值:
  ①:fgets()函数返回指向char的指针。如果一切进行顺利,该函数返回的地址与传入的第一个参数值相同。
   ②:当读到文件结尾(Windows命令行键盘输入ctrl + z模拟,当然在真实文件中没必要模拟,其会自动识别文件结尾)或者在读入数据时出现了某些错误,fgets()函数将返回一个空指针:通常用宏NULL表示。(也可以用数字0来代替,但是用0与其他形式容易造成冲突误解)

当输入行太长会怎么样,使用gets()函数不安全,他会擦写现有数据,存在安全隐患,(gets_s()函数很安全,但是如果不希望程序终止或退出,就要知道如何编写特殊的“处理函数”。另外,如果打算让程序继续运行,gets_s()函数会丢弃输入行的其余字符,无论你是否需要)前面这个括号里面的内容是书上的原话,看不懂没关系,了解即可。由此可见,当输入太长,超过数组可容纳的字符数时,fgets()最容易使用,而且可以选择不同的处理方式。
1、如果要让程序继续使用输入行中超出的字符,那么可以使用下面程序的处理方法

#include <stdio.h>
#define STLEN 10

int main(void)
{
    char words[STLEN];

    puts("enter strings (empty line to quit)");
    while (fgets(words,STLEN,stdin) != NULL && words[0] != '\n')
    {
        fputs(words,stdout);
    }
    return 0;
}

运行截图:
循环终止原因:当执行完第11次(4+4+3)while循环后(第一行相当于有4个9位),再检查while循环条件的时候发现words[0] == ‘\n’
运行截图1
循环终止原因:当执行完第一次while循环后,再检查while循环条件的时候发现words[0] == ‘\n’
运行截图2
循环终止原因:当执行完第二次while循环后,再检查while循环条件的时候发现fgets(words,STLEN,stdin) == NULL
运行截图3

2、如果想丢弃输入行超出的字符,可以参考下面程序的处理方法

#include <stdio.h>
#define STLEN 10

int main()
{
    char words[STLEN];

    int i = 0;

    puts("enter strings (empty line to quit)");
    while (fgets(words,STLEN,stdin) != NULL && words[0] != '\n')
    {
        while (words[i] != '\n' && words[i] != '\0')
         i++;
        if(words[i] == '\n')
        {
            words[i] = '\0';
        }
        else{
            while(getchar() != '\n')
                continue;
        }
        fputs(words,stdout);
        printf("\n");
    }
    return 0;
}

运行截图如下:(最后是通过empty line来终止程序的)
运行截图1
以如下图片中的输入为例,我们把19到22行注释掉,此时没有了getchar()函数来消耗下面图片输入中最后的换行符,那么就会导致最外层循环只执行一次(最外层的while循环的第二个条件不满足)。
运行截图2

(3)gets_s()函数(有难度,且还未普及,了解即可)

C11新增,有两个参数(和fgets()函数的第一,二个参数一个意思)
说第一个参数和fgets()一个意思还能理解的过来。
第二个参数跟fgets()是一个意思该怎么理解呢?
你就想啊,这设计C语言方法库的人肯定不会先设计一个读取前n-1个字符的函数,后却设计一个读取前n个字符的函数。
第一个是指针参数,通常是数组名。
第二个参数是一个整数,来限制读入的字符数。
gets_s()函数只从标准输入中读取数据,不需要第三个参数(这点跟fgets()函数不一样)
gets_s()函数读取到换行符,会像gets()函数一样丢弃它而不是存储它(这点跟fgets()函数不一样)
只要输入行未超过最大字符数,完全可以用gets_s()函数替换gets()函数。

(4)scanf()函数(读到文件结尾会返回EOF)

scanf()函数返回一个整数值,该值等于scanf()函数成功读取到的项数或者EOF。
无法读取多个单词的书名或者歌曲名。输入行过长也会导致数据的溢出,当然这点可以在%s转换说明中使用字段宽度来防止溢出。
典型用法是读取并转换混合数据类型为某种标准形式。

2、输出函数

(1)puts()函数

传入一个指针参数,通常为数组名,该函数用于输出字符串,并在末尾添加换行符。

(2)fputs()函数

fputs()函数是puts()函数针对文件定制的版本
它有两个参数。
第一个是指针参数,通常是数组名。
第二个参数指明要写入的文件,如果要打印在显示器上,那么可以用定义在stdio.h中的stdout(标准输出)来作为该参数。
与puts()函数不同,fputs()函数不会在输出的末尾加上换行符。

(3)printf()函数

printf()函数的格式:printf(格式字符串,待打印项1,待打印项2…);
与puts()函数一样,当用于输出字符串时,printf()也把字符串的地址作为参数,但是它的用处不仅仅局限于输出字符串,它还可以格式化不同的数据类型。
printf()的返回值是其打印输出功能的附加用途,通常很少用到,它返回的是它所打印的信息的字符数(包括空格和不可见的换行符(\n)),如果有输入错误,其返回一个负值。比如:
int b=10;
int a = printf(“%d\n”,b);//输出10换行
printf(“%d”,a);//输出3

有趣实验:

用转义序列‘\a’(蜂鸣)、printf()函数、Sleep()睡眠函数(需要包含windows.h头文件)和system("cls")实现界面倒数10。
#include <stdio.h>
#include<windows.h>

int main()
{
    for(int i=0; i<10; i++)
    {
        printf("\a");
        printf("%d",10-i);
        Sleep(1000);//一千毫秒=一秒
        system("cls");
    }
    return 0;
}

这个实验的具体运行情况就不演示了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菩提one

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值