C语言笔记(二)

  • 利用 strrchr() 和 strcmp() 组合判断文件后缀名
  • 利用 srand() 和 rand() 组合生成 (伪) 随机数
  • 一个简单的问题

一、识别文件后缀名

在第二次程设作业中,需要将命令行参数作为文件名open,于是在这一步需要对文件名做规范检查,以下是网上查到的一些规范:

  1. 文件名或目录最长255字符

  2. 保留字符(不能使用)

    • 任何控制字符(0-31)
    • / 斜线(SLASH) (使用为路径分隔线;UNIX中的根目录符号)
    • | 管道(PIPE)
    • \ 反斜线(BACKSLASH) (使用为路径分隔线)
    • ? 问号(QUESTIONMARK) (在Windows操作系统中使用为一个通配符)
    • " 双引号(DOUBLE-QUOTATIONMARK) (这使用于标示含有空白字符的文件名称)
    • * 星号(STAR) (在Windows操作系统中使用为通配符)
    • : 冒号(COLON) (这使用于决定哪一个挂载点 / Windows操作系统中的磁盘)
    • < 小于(LESS THAN) (原先由用户在主控台输入的消息改由文字档输入)
    • > 大于(GREATER THAN)(原先输出至主控台的消息改输出至文字档)
    • . 句点(可允许使用,但最后的句点会被诠释为扩展名的分隔)
    • 另外,某些文件名称亦会保留,不能作为文件名称使用,如下
    1
    CON, PRN, AUX, CLOCK$, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.

上述仅针对windows(NTFS)文件系统,即:

更多详情请见文件命名
所以在打开文件或是新建文件时需要检查预设文件名是否合理,那么这个时候出现了一个小小的问题,就是比如说用户想要建立一个名叫“abc.txt”的文本文件,但是用户输入文件名时可能出现两种情况:

  • 用户输入的是“abc”
  • 用户输入的是“abc.txt”

那么为了避免歧义(或者说能够让程序更加聪明一点),可以通过检查后缀名的方式来决定是否自动添加“.txt”扩展名
由此我们将话筒交给strrchr()函数:

Q: 请问您是做什么工作?
A: “I can return a pointer to the last occurrence of character in the C string str.”
译: 返回一个指针,指向 C 字符串 str 中最后一次出现的(要找的)字符。

Q: 请问该如何使用呢?
A: 下面是我的原型,注意,当整个字符串都没有该字符时,返回空指针

1
2
3
const char * strrchr ( const char * str, int character );
char * strrchr ( char * str, int character );
//character即要定位的字符,虽然是int型,但在内部会转换回char型

所以说,在此我们可以利用strrchr()函数找到预设文件名中最后一个’.’,若返回值非空指针,则调用strcmp()函数将其与“.txt”等扩展名字符串作比较。
即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const char suffix[] = ".txt";//定义需要识别或添加的后缀名
char *temp = strrchr(fname,'.');//fname是前面获取的预设文件名
if(temp == NULL)
{
...//先判断添加后缀名是否还合乎命名规则,以及是否有足够空间连接
strcat(fname, suffix);//添加后缀名
}
else
{
int t = strcmp(temp,suffix);
if(t != 0)//不相等,即并非为文本文件
{
strcat(fname, suffix);//添加后缀名
}
//else不处理,这样即达到了识别后缀名的目的
}

二、生成随机数

又是在第二次程设作业中,需要实现的功能是生成随机整数三元组构成的数据文件。于是一开始查到了stdlib.h库里的rand()函数,搜索发现rand()函数返回值是0到RAND_MAX(库中定义的常值,最小为32767)中的任意一个整型数(包括边界),那么可以用下式来获得自定义区间的值:

1
rand() % (MAX - MIN + 1) + MIN;//该式得到[MIN,MAX]的一个任意值

但是上式有一个致命问题,就是生成的实际是伪随机数,而由于生成时设立的种子没变,所以多次连续使用得到的值是不变的
于是找到了srand()函数,它的作用是重新设立种子,具体使用方法如下:

1
2
3
4
5
6
7
srand((unsigned)time(NULL));
for(int i = 0;i <= n;i++)
{
n = rand() % 100 + 1;
printf("%d\n",n);
//每次生成的数是随机数
}

三、一个简单的问题

又又是程设实验二,在登记生成数据的条数时想到了一个小问题,即这个记录条数必然得是正数吧,那如果输入是负数,或者输入是不合规范的该怎么办,不能直接任由它malloc一个负的内存吧。所以想到了用while循环来保证得到一个正整数。这里采用的是scanf()从标准输入流获取:

1
2
3
4
5
6
//num有初值
while(num <= 0)
{
printf("%d不是正整数,请重新输入:",num);
scanf("%d",&num);
}

显然,上述代码无法处理输入为诸如“bkajbc”的样子,因为scanf()在处理错误输入时会返回EOF(但是据老师所说是返回错误码,恕我没能找到有关的文章或者是我没看懂解释,先码住,以后填坑),然后将这些输入放在缓冲区(stdin是默认为行缓冲的),于是乎,while循环便停不下来了。
由此考虑到清空缓冲区的操作,搜到了以下几种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//方法一
fflush(stdin)
//此函数的操作是清空缓冲区,stdin代表是标准输入流
//但是这个函数查到说是c的扩展,不推荐使用,所以我没有采用这个方法
//Linux系统下好像也用不了这个函数

//方法二
rewind(stdin)
//这个函数的操作是将与流关联的位置指示符设置为文件的开头。
//成功调用此函数后,与流关联的文件结尾和错误内部指示符将被清除
//我采用的即是这个方法

//方法三
while ((ch = getchar()) != EOF && ch != '\n');
//此方式将所有缓冲区的暂存都“吃掉”

至此本应告一段落,但是我还找到了一个方法:

1
2
3
setbuf(stdin,NULL);
//含义是将stdin的缓冲设为NULL,也就是从行缓冲改为无缓冲
//此函数应作用于所有输入输出之前

本以为这个也能发挥作用,但没细想,这个只是将缓冲区改成无缓冲罢了,错误的依旧错误地读入了,并没有消除。
老师课上提出了鲁棒性(Robust)的一个方面 ,即scanf是有返回值的,而通常都会需要去检查返回值(printf不需要检查是因为,出错信息一般也是用printf标准输出,要检查的话不就陷入循环论证的圈子了www),由墨菲定律可知,只要有犯错的可能,那么一定会出错,所以不能抱这种态度。根据老师提出的建议,我的想法大概如下:

1
2
3
4
5
6
7
8
9
10
11
//前面已获取一个num值
while(num<=0)//这里是判定条件
{
printf("数据错误,请重新输入:\n");
rewind(stdin);
int t = scanf("%d",&num);
if(t != 1)
{
num = 0;
}
}

还提出了关于exit()的问题,exit()的用法如下:

1
2
exit(EXIT_SUCCESS);//代表正常退出,或可使用参数0
exit(EXIT_FAILURE);//代表异常退出,或可使用参数1或-1

一些总结

  • 首先是工程作业很能锻炼到架构能力,从流程图能够直观的反映出对业务需求的理解能力、和业务逻辑业务规则的完备性,往往做一些算法题可能几十行就能解决,但是在这种情况,就需要考虑命名的规范性,程序的健壮性,可移植性和高性能性
  • 老师提到了查找资料最好是用官方文档,就算是很多英文专有名词也要争取读懂,而博客啊或者其他的资料仅能用于参考,md深有体会。

C语言笔记(二)
https://zongjy.github.io/2022/03/29/06c52e941e91/
作者
zongjy
发布于
2022年3月29日
许可协议