当前位置: 首页 > article >正文

09-C语言格式化字符串操作

一、格式化字符串概述

在前面的学习中已经接触过了字符串格式化的相关操作,如:格式化输出printf("我的名字是:%s,今年%02d岁了\n", name, age),这个格式化输出是默认输出到终端设备上的。其实也可以将字符串格式化输出到指定的字符数组,或者文件中去,同时也可以对一段字符串按照格式拆解成一个个字串。格式化字符串在后续学习中有较为广泛的运用,包括在网络通信部分,涉及到通信数据的拆包、组包操作等。

二、字符串组包

1.sprintf函数

sprintf函数用于将格式化字符串数据输出到指定的字符数组,用于数据的组包操作。

  • 代码演示
int main() {
    // 创建一个空字符数组
    char str[64] = "";
    // 定义三个日期变量
    int year = 2024;
    int month = 12;
    int day = 5;
    // 将上面的日期按照****年**月**日格式组包到str数组,不足位补0
    int len = sprintf(str, "%04d年%02d月%02d日", year, month, day);
    printf("%s\n", str);
    printf("%d", len);
    return 0;
}
  • 运行结果
2024年12月05日
14 
  • 注意:
    1. 由上面的代码可以看出,sprintf函数对目的字符数组有写操作,因此这里的字符数组必须允许读写操作,而不能定义成char *指针指向文字常量区;
    2. sprintf函数的返回值为组包的字符串的长度。

2.fprintf函数

fprintf函数和sprintf差不多,都是用于数据的组包操作,只不过这里的函数是将格式化字符串写入到文件里面。

  • 代码演示:文件操作后面学习,这里知道fprintf函数用于将组包数据写入文件即可
int main() {
    // 以写入模式打开一个文件
    FILE *fd = fopen("a.txt", "w");
    // 定义三个日期变量
    int year = 2024;
    int month = 12;
    int day = 5;
    // 将上面的日期按照****年**月**日格式组包到a.txt文件,不足位补0
    fprintf(fd, "%04d年%02d月%02d日\n", year, month, day);
    printf("%s\n", fd);
    fclose(fd);
    return 0;
}
  • 运行结果:生成了一个 a.txt 文件,并且往文件里写入了组好的数据

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

三、字符串解包

1.sscanf函数

sscanf函数用于按照指定的格式从字符串中提取想要的数据,用于数据的解包操作。

1.1sscanf字符串提取

字符串提取:sscanf%s结合提取字符串。

  • 代码演示
int main() {
    // 创建字符数组
    char str1[32] = "hello world!";
    char *str2 = "hel\0lo world!";

    // 创建字符数组用于保存解包数据
    char str3[32] = "";
    sscanf(str1, "%s", str3);
    char str4[32] = "";
    sscanf(str2, "%s", str4);
    
    printf("%s\n", str3);
    printf("%s\n", str4);
    return 0;
}
  • 运行结果
hello
hel 
  • 根据上面代码的运行结果可知:
    1. 函数格式:sscanf(被解包的字符串,解包格式,存放解包结果的内存起始地址)
    2. sscanf按照%s解包操作提取字符串的时候,遇到空格、'\0’和回车结束;
    3. 被解包的字符串只是读操作,可以存放在文字常量区,但用于存放解包结果的字符数组要允许读写操作。

1.2sscanf数值提取

数值提取:sscanf%d %ld %hd %u %lu %hu结合提取数值。

  • 代码演示
int main() {
    // 创建字符数组
    char str[64] = "2358dece8679";
    int num = 0;
    // %d提取数值
    sscanf(str, "%d", &num);
    printf("%d\n", num);
    return 0;
}
  • 运行结果
2358
  • 说明:
    1. 数值提取时,遇到非数值字符结束提取;
    2. 因为要将提取的数值通过一个int变量来接收,即将提取数值写入数值变量,sscanf第三个元素为普通变量的时候要取地址。

1.3sscanf字符提取

字符提取:sscanf与`%c结合提取一个字符。

  • 代码演示
int main() {
    // 创建字符数组
    char str[64] = "2358dece8679";
    char ch = '0';
    // %c提取字符
    sscanf(str, "%c", &ch);
    printf("%c\n", ch);
    return 0;
}
  • 运行结果
2
  • 说明:不管是提取字符串、数值还是字符,都应该遵循提取什么类型的数据就按对应格式提取。因为提取的结果是通过一个新的变量来接收,是写操作,因此用于接收的变量作为sscanf函数参数的时候要地址传递。

2.sscanf的高级用法

2.1提取指定个数的字符或数字

提取指定个数的字符或数字,按照 %3s %3d来提取,3代表要提取的字符或数字的个数,也可以是其它值。

  • 代码演示:%ns
int main() {
    // 创建字符数组
    char str[64] = "hello world";
    char str1[64] = "hello\0world";
    
    // 提取指定长度字符串
    char str2[64] = "";
    sscanf(str, "%3s", str2);
    printf("%s\n", str2);
    sscanf(str, "%10s", str2);
    printf("%s\n", str2);
    sscanf(str1, "%10s", str2);
    printf("%s\n", str2);
    return 0;
}
  • 运行结果
hel  
hello
hello
  • 结论:

    1. %ns提取指定个数字符的时候,如果还没有取到n个字符就遇到了空格,则提前结束;
    2. %ns提取指定个数字符的时候,如果还没有取到n个字符就遇到了’\0’,则提前结束。
  • 代码演示:%nd

int main() {
    // 创建字符数组
    char str[64] = "1234cewc89";

    // 提取数值
    int num = 0;
    sscanf(str, "%3d", &num);
    printf("num = %d\n", num);
    printf("num = %d\n", ++num); // 124,可以进行数值运算,证明是数值
    sscanf(str, "%6d", &num);
    printf("num = %d\n", num);
    return 0
}
  • 运行结果
num = 123 
num = 124 
num = 1234
  • 结论:%nd提取指定个数值的时候,如果还没有取到n个数值就遇到了非数值,则提前结束。

2.2跳过提取到的字符或数值

%*c跳过提取到的单个字符,%*s跳过提取到的1个字符,%*d 跳过提取到的1数值,%*3s 跳过提取到的3个字符, %*3d 跳过提取到的3个数值。

  • 代码演示
int main() {
    // 创建字符数组
    char str[64] = "hello world!";
    char str1[64] = "123456789";

    // 提取字符
    char str2[64] = "";
    sscanf(str, "%*6s%s",str2);
    printf("%s\n", str2);
    // 提取数值
    int num = 0;
    sscanf(str1, "%*3d%3d", &num);
    printf("%d\n", num);
    return 0;
}
  • 运行结果
world!
456
  • 结论:虽然sscanf通过%s提取字符串的时候遇到空格’和\0’会提前结束,但是%*s屏蔽的时候,会将空格屏蔽,'\0’无法屏蔽。

3.3匹配指定范围的字符

如:%[a-z] 表示匹配 a 到 z 中任意字符。

  • 代码演示
int main() {
    // 创建字符数组
    char str[64] = "hello12345worldABCDE";

    char str1[64] = "";
    sscanf(str, "%[a-z | 1-9]", str1);
    printf("%s\n", str1);
    return 0;
}
  • 运行结果
hello12345world
  • 匹配指定范围的的字符,支持与或操作,遇到非范围内的字符就结束提取。

3.4匹配指定多个字符中的字符

如:%[heA]匹配heA中的字符,遇到非heA的字符结束提取

  • 代码演示
int main() {
    // 创建字符数组
    char str[64] = "hello12345worldABCDE";

    char str1[64] = "";
    sscanf(str, "%[heA]", str1);
    printf("%s\n", str1);
    return 0;
}
  • 运行结果
he

3.5匹配非指定范围内的字符

如:%[^0-9],匹配非0-9的字符,遇到0-9中任意一个字符就结束;%[^l35],匹配非l35的字符,遇到任意l35字符中的一个就结束。

  • 代码演示
int main() {
    // 创建字符数组
    char str[64] = "hello12345worldABCDE";

    char str1[64] = "";
    sscanf(str, "%[^0-9]", str1);
    printf("%s\n", str1);
    sscanf(str, "%[^l35]", str1);
    printf("%s\n", str1);
    return 0;
}
  • 运行结果
hello
he
  • 案例:
int main() {
    // 创建字符数组
    char str[64] = "(hello:world!)";

    char str1[64] = "";
    // 提取hello
    sscanf(str, "%*c%[^:]", str1);
    printf("%s\n", str1);
    // 提取world
    sscanf(str, "%*[^:]%*c%[^!]", str1);
    printf("%s\n", str1);
    return 0;
}
  • 运行结果
hello
world

3.6案例歌词提取

  • 提取如下字符串的内容,"[02:16.33][04:11.44]我想大声宣布对你依依不舍",打印在第多少秒输出歌词
int main() {
    // 创建字符数组
    char str[128] = "[02:16.33][04:11.44]我想大声宣布对你依依不舍";

    // 定义数组用于存放歌词
    char str1[128] = "";
    // 用于存放分钟
    int min1 = 0;
    // 用于存放秒数
    int sec1 = 0;
    int min2 = 0;
    int sec2 = 0;
    sscanf(str, "%*[^]]%*c%*[^]]%*c%s", str1);
    sscanf(str,"%*c%d", &min1);
    sscanf(str,"%*[^:]%*c%d", &sec1);
    printf("第%ds播放歌词:%s\n", min1 * 60 +sec1, str1);
    sscanf(str,"%*c%*[^[]%*c%d", &min2);
    sscanf(str,"%*[^:]%*c%*[^:]%*c%d", &sec2);
    printf("第%ds播放歌词:%s\n", min2 * 60 +sec2, str1);
    return 0;
}
  • 运行结果
第136s播放歌词:我想大声宣布对你依依不舍
第251s播放歌词:我想大声宣布对你依依不舍

虽然能够得到结果,但代码存在大量冗余,且代码不具备通用性,假设现在时间增加一个或多个,上面的代码就无法实现需求了。

  • 较通用版本:观察发现时间的格式都是一样的,可以利用这点
int main() {
    // 创建字符数组
    char str[128] = "[02:16.33][04:11.44][05:11.44]我想大声宣布对你依依不舍";

    // 定义一个指针指向歌词位置
    char *lrc = str;
    while (*lrc == '[')
    {
        lrc += 10;
    }

    // 定位不同时间
    char *time = str;
    while(*time == '[')
    {
        int min = 0;
        int sec = 0;
        sscanf(time, "%*c%d", &min);
        sscanf(time, "%*[^:]%*c%d", &sec);
        printf("第%ds播放歌词:%s\n", min * 60 +sec, lrc);
        time += 10;
    }
    return 0;
}
  • 运行结果
第136s播放歌词:我想大声宣布对你依依不舍
第251s播放歌词:我想大声宣布对你依依不舍
第311s播放歌词:我想大声宣布对你依依不舍

四、const修饰的变量

const修饰的变量就不能再通过被修饰的变量改变相应的值了。

1.const修饰普通变量

  • 代码演示
int main() {
    const int num = 10;
    printf("num = %d\n", num);
    // 不能通过num去修改num的值了
    // num = 20; // error: assignment of read-only variable 'num'

    // 通过一个指针指向num的值
    int *p = (int *)#
    *p = 20;
    printf("num = %d\n", num); // 修改成功
    return 0;
}
  • 运行结果
num = 10
num = 20
  • 说明:
    1. const修饰的变量可以对其初始化赋值;
    2. 除了初始化赋值,后面不能再通过被const修饰的变量修改相应的值;
    3. 这里仅仅是不能通过被修饰的变量名修改其值,但是找到数据存储的内存地址依旧能改变其值,因为这里仅仅是修饰变量名而已,而不是修饰存储数据的空间。

2.const修饰指针变量

2.1const在*的左边

const*的左边:const int *p和int const *p 是一个意思,这种情况下,const修饰的是*,即不能通过*p进行写操作。

  • 代码演示
int main() {
    int num = 10;
    printf("num = %d\n", num);
    const int *p = #

    // 可以通过*p进行读操作,不能进行写操作
    printf("%d\n", *p); // 10
    // *p = 20; // error: assignment of read-only location '*p'

    // p可读可写
    int num2 = 20;
    p = &num2;
    printf("%d\n", *p); // 20
    return 0;
}
  • 运行结果
num = 10
10      
20
  • 说明:
    1. 由上面的代码可以看出,const*的左边时,*p可读不可写,p可读可写。
    2. 实际应用中这种写法通常运用于函数的参数,我们将一个外部变量作为函数参数时,实参太大,所以就地址传递,但是又不希望函数内部修改外部的变量,因此就对*p*左边加const修饰。

2.2const在*的右边

const在*的右边;即int * const p,此时const修饰的是p。

  • 代码演示
int main() {
    int num = 10;
    printf("num = %d\n", num);
    int *const p = #

    // 可以通过*p修改num的值
    *p = 20;
    printf("%d\n", num); // 20
    // 不能修改p的指向
    int num2 = 40;
    // p = &num2; // error: assignment of read-only variable 'p'
    return 0;
}
  • 运行结果
num = 10
20
  • 说明:
    1. 由上面的代码可以看出,const*的右边时,*p可读可写,p可读不可写;
    2. 实际应用中,这种const修饰方式用于不希望修改指针指向的情况。

2.3const既在*左又在*

const即在*左又在*右:即const int *const p

  • 代码演示
int main() {
    int num = 10;
    printf("num = %d\n", num);
    const int *const p = #

    // 不可以通过*p修改num的值
    // *p = 20; // error: assignment of read-only location '*(const int *)p'
    printf("%d\n", num);
    // 不能修改p的指向
    int num2 = 40;
    // p = &num2; // error: assignment of read-only variable 'p'
    return 0;
}
  • 说明:
    1. 由上面的代码可以看出,const*的左右两边时,*p可读不可写,p可读不可写;
    2. 实际应用中,这种const修饰*左右两边的方式,是不希望修改指针指向的内容同时不希望修改指针的指向。

五、别名

typedef给已有类型取别名,当我们需要重复定义某种类型的数据的时候,如果类型名过长,定义起来比价麻烦,就需要为其取一个较短的别名。

1.别名的定义步骤

  • 代码演示
int main() {
    typedef int I;
    I num1 = 50;
    printf("%d\n", num1); // 50
    return 0;
}
  • 别名定义步骤:
    1. 先定义想要定义的类型的变量:int num
    2. 然后在其前面假设typedef关键字:typedef int num
    3. 最后通过别名替换变量名:typedef int I

2.创建的typedef定义形式

2.1指针类型起别名

  • 代码演示
int main() {
    int num = 0;
    typedef int *P_INT;
    P_INT p = #
    *p = 10;
    printf("%d\n", num); // 10
    return 0;
}
  • 案例:下面代码p1,p2和p3,p4有什么区别
#define P_TYPE1 int*
typedef int *P_TYPE2;

int main() {
    // 宏替换相当于:int *p1, p2;
    P_TYPE1 p1, p2;
    // 起别名,将int * 看成一个整体,相当于:int *p3, *p4
    P_TYPE2 p3, p4;
    return 0;
}
  • 答案:p1是指针变量,类型为int *;p2是普通变量,类型为int;p3,p4都是指针变量,类型为int *

2.2数组类型起别名

  • 代码演示
int main() {
    typedef int INTNUM_5[5];
    INTNUM_5 nums = {1, 2, 3, 4, 5};
    // 遍历数组
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        printf("%d ", nums[i]);
    }
    printf("\n");
    return 0;
}
  • 运行结果
1 2 3 4 5 

2.3函数指针类型

  • 代码演示
int main() {
    typedef int (*MY_FUNC)(int, int);
    MY_FUNC p = my_add;
    MY_FUNC p1 = my_mul;
    printf("%d\n", p(3, 5));
    printf("%d\n", p1(3, 5));
    return 0;
}
  • 运行结果
8
15
  • 在实际写代码的过程中,可能很多的函数的形参都是别名,可以在一下高级编译软件下编程时按住ctrl,然后鼠标左键点击能查看其本来面目。

http://www.kler.cn/a/445069.html

相关文章:

  • React 工具和库面试题(一)
  • Vscode打开后闪退问题
  • 【leetcode100】随机链表的复制
  • Java游戏开发基础:从零开始制作一个简单的2D游戏
  • Linux下搭建和简单配置FTP服务器
  • (11)YOLOv9算法基本原理
  • Vue.js前端框架教程3:Vue setup语法糖和异步操作
  • Redis——缓存双写一致性问题
  • 预览和下载 (pc和微信小程序)
  • git bash中文显示问题
  • ubuntu history 存放 更多
  • 软件项目需求分析的实践探索(1)
  • How to run Flutter on an Embedded Device
  • 1_HTML5 Canvas 概述 --[HTML5 API 学习之旅]
  • 电商数据采集电商,行业数据分析,平台数据获取|稳定的API接口数据
  • 如何使用 Wireshark:从入门到进阶的网络分析工具
  • 实用技巧:在Windows中查找用户创建记录
  • Sigrity System Explorer Snip Via Pattern From Layout模式从其它设计中截取过孔模型和仿真分析操作指导
  • 【MFC】多工具栏如何保存状态
  • jmeter 接口性能测试 学习笔记