开发控制台股票统计程序遇到的问题汇总

利用c语言开发最基础的控制台股票统计程序,整个程序的逻辑非常简单。
就是利用fopen打开数据文件,然后读取数据,处理统计。
但就这么一个简单的程序,开发过程中也遇到不少问题,消耗了很多时间进行调试。

1. 在读取沪深3000多只股票数据的时候,反复出错。

检查发现有些个股的名字加了多余的空格,比如“深 赛 格”,造成读取格式出错。这样的股票不下百只,只能采用人工修改原始数据。幸好只需要修改一次,以后再用就没问题了。

2. 对文件循环读取的时候,特别注意eof标记仅仅是最后被读完后才会设置,因此非常容易造成多循环一次的错误。

比如一个文件有20行,当读完第20行的时候,eof标记并没有设置,此时循环会再次进行。
当再运行一次fgets,发现到达文件尾,fgets返回NULL,整个循环就进行了21次,造成股票统计不正确。
修改方法在原有的fgets函数调用之后,增添 if (feof(fp) !=0) break;

事实上,feof达成条件时,内部是先由read等函数返回错误时,然后才被设定的。
当输出时,表现出就是最后一行重复输出(因为多循环一次)。

解决方法,要么不用feof作为结尾判断,直接用各种read函数,实在要用feof,循环中再次判断一下f(feof(fp))。
feof ==0,表示没结尾。
feof !=0,表示结尾。

3. 验证了好几个小时,想要直接在原文件上读写,发现不行。
以fopen(r+)模式打开,设想是能读取一行,修改这行,再把这行写回原文件。

比如文件a.txt,内容大概为:
张三 | 75 | 男
李四 | 60 |男
王五 | 90 |男

程序目的:
设想是能读取一行,修改这行,再把这行写回原文件。
假设读取李四行,修改数字60变为75,再把这行写回原行。

为什么需要这种目的,并非我闲的蛋疼。
比如一个新需求,把所有行的“男”都改成“男性”,批量替换。
又或者只需要替换修改某几行的数据,无需把所有行数据都读取出来。
既然用记事本打开文本文件,都可以使用批量替换,那就说明程序也能做到。

fscanf之后用fprintf失败,完全无效,同理fgets之后fputs也失效。
网上查询,说在输入操作后,如果不调用fseek,rewind或fflush函数等,就不能接着进行输出操作。
原理是需要清空内部缓冲区。

fscanf(fp);
……
……
printf();
fprintf(fp);

个人验证:在fscanf和fprintf之间添加上fflush或fseek,仍然失败。
printf()之后接fprintf显示器会出乱码。
我能想到的两种方法变相解决:

1.强调内容使用另一个temp文件,读取原文件每行,处理完直接fputs写入temp文件,最后修改temp名为原文件名,删除原文件(已编码实现)。

2.使用一个结构体数组保存,当所有修改完成后,调用rewind(或者先close原文件,再w模式打开)回到文件开头,最后遍历结构体数组写入(已编码实现)。

有没有什么办法不使用这两种(会额外消耗资源),直接能达到预想目标呢??

网上请教有人说,学会fseek就相当于在记事本里用“光标”移动,暂时还没编码成功,需要再学一学,琢磨琢磨。

4. 用strtok分割字串。

最初以为分隔符是用空格,调试的时候老是内存溢出强制终止,后来折腾2个多小时,反复查找程序逻辑都没发现问题,最后才发现股票数据文件导出是用的\t来分割,肉眼看不出来郁闷。

5. 取余运算符%,控制流程
 for(i=0;i<N;i++)
    {if(i%4==0)
         printf;
     }   

预想是每隔4次输出一下,但当第一次执行时,因为0与4取余仍然为0,所以到i为4满足条件时候,循环执行了5次。
没有完成每隔4次输出的目的。

应该把循环i修改为1开始,i<=N格式正确。

 for(i=1;i<=N;i++)
    {if(i%4==0)
         printf;
     }   

这样循环次数不变,也满足预期。

6. 二维数组的认识

并没有什么二维数组,计算机里数据都是一维,仅仅是逻辑上方便我们辨识。
二维数组可以看成是一维数组,只不过每一个元素又是由数组构成。

char a[3];
char *p=a; 数组名既是地址
或者 char*p =&a[0];

char b[3][4];
char *pb=b[0]; 把b[0]逻辑上当成是一个元素,本身既是地址。
或者 char (*p)[4]=b;

7. 栈stack,堆heap,静态存储区

因为给股票数组设定过大,造成程序调试运行出错,最初以为是指针泄漏。
反复查验源码,没有发现问题,无意将数组申请的最大值改小后运行正常。

通过查询资料,局部变量开在stack里,大小与编译器有关,通常是1M。
全局变量开在静态存储区,大小可能与编译器有关,vs2010通常小于2G。
heap空间由程序员申请和释放,就是系统内存的剩余量,硬件有多大就有多大。
所有的stack,heap和静态区在内存中的分配位置,跟硬件架构和操作系统都有关系。
我最后可耻的选择了把数组给申请成了全局静态。

8. 查询数组,表驱动法

股票数据读入数组后要进行查询,读取。
我都是使用的穷举遍历法,代码足够简单,几乎不可能出错。想起以前学过的表驱动法,还模糊的听过状态机的概念,它们有什么差别呢?

所谓的表驱动法,广义上就是用查表的方法获取数据,因此遍历法仅仅是表驱动法的具体实现逻辑之一。
而状态机,是利用表驱动法完成的。
数据库雏形,也是利用了表驱动法.

表驱动法的特征在于简化编码,比如把10进制数0,1,2,3转化为字符#,a,b,c,通常会使用

 ifnum==1)
                   ch='a';
          else if(num==2)
                   ch='b';

当转换数据较大时,会编写出冗长的if else语句,一旦要对转换进行修改,增删都非常麻烦容易出错,时间复杂度o(n)。

我们可以把数据构造为一个结构体数组
0 | #
1 | a
2 | b
3 | c
每一个元素由 num和ch组成,给定一个数n,通过遍历结构体数组(表),取出每一进行if判断,也就获得了其对应的字符.
注意,第二种方法并没有减少计算机的工作量,复杂度也为o(n),它简化了冗长的逻辑语句编写(程序员舒服了)。

再次仔细观察,发现十进制0,1,2,3是有规律的整数,完全能把它当作下标简化为一维数组,char table[4];
对于给定一个数n,无需做循环和if判断,直接 ch=table[n];其复杂度o(1)。

第三种方法能够简化成功,是有前提条件的,根据给定的关键值去表中查询。
如果题目换成把字符转化为十进制数字,该方法就失效了。
此时给定我们的是一个字符,在一维数组table[]中它的位置是多少? 我们并不清楚,只能采用第二种方法遍历(也可以用其他查找算法优化)。

表驱动法总结:
表驱动法是数据驱动编程的的一种,数据驱动编程认为数据压倒一切,数据是容易被改变的(易变),而逻辑不容易变化(不易变)。
将易变和不易变的两者分离开来,有助于编写更优质的程序。

9. 对程序性能的预判错误

因为使用的穷举遍历,最初我预判是这个算法消耗最多时间,应该对原始数据进行整理,这样可以使用2分查找或者其他算法。
在网上查询优化资料后,发现一篇文章写的非常好,原文说:

我编写程序至今有35年了,我做了很多关于程序执行速度方面优化的工(一个示例),我也看过其它人做的优化。我发现有两个最基本的优化技术总是被人所忽略。 注意,这两个技术并不是避免时机不成熟的优化。并不是把冒泡排序变成快速排序(算法优化)。也不是语言或是编译器的优化。也不是把 i*4写成i<<2 的优化。 这两个技术是:
①使用 一个profiler。
②查看程序执行时的汇编码。
使用这两个技术的人将会成功地写出运行快的代码,不会使用这两个技术的人则不行

我们知道,程序运行时的90%的时间是用在了10%的代码上。我发现这并不准确。一次又一次地,我发现,几乎所有的程序会在1%的代码上花了99%的运行时间。但是,是哪个1%?一个好的Profiler可以告诉你这个答案。
有一次,我和我的同事去了一个负载过大的交易所,我同事坚持说他知道哪里是瓶颈,毕竟,他是一个很有经验的专家。最终,我把我的Profiler在他的项目上运行了一下,我们发现那个瓶颈完全在一个意想不到的地方。

在这之前我从来没用过profiler,因为都是写一些简单的控制台程序,再加上vs2015社区版并不支持profiler,这次股票统计程序比较需要效率,临时学习使用。
非常惊讶,和原文的例子如出一辙(当然我远不是专家),预判性能出错,穷举遍历压根就不是瓶颈。
这里写图片描述
这里写图片描述
fscanf消耗了87%的总时间,如何优化呢?
我想不出来,暂时留待以后解决。

总结:第3点和第9点问题标记。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值