dun*_*nno 5 c fopen numbers sum while-loop
我有一个大文本文件。在这个文件中有一些我想加在一起的数字。
我试过的:
int sum = 0, i = 0;
file = fopen(filename, "r");
while ((i = fgetc(file)) != EOF) {
if (isdigit(i)) {
sum++;
}
}
printf("Sum of numbers is: %i", sum);
fclose(file);
Run Code Online (Sandbox Code Playgroud)
但这isdigit(i)只是这个文件包含多少位数的计数器,而不是数字的总和。
输入是:"This text 15 is very 19 nice."
结果应该是:Sum of numbers is: 34
问题代码中缺少的部分是累积数字(而不是用 sum++;)并在添加下一个数字之前乘以 10 之前的累积数字。
答案在:
number = number * 10 + i - '0';
这 - '0'部分将 ASCII 数字转换为数字。
下面代码中的其他所有内容都经过检查以确保没有明显的溢出并且正确支持与数字相邻的减号,以及忽略小数点后的数字。我敢肯定它并不完美,但这里的想法是提供一个如何完成的工作示例,而不是经过良好测试的代码并使用库调用来为您完成。
应大众要求(评论现已删除),我添加了一个简单但有效的溢出检查:
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <error.h>
#include <limits.h>
int main(int argc, char* argv[]) {
int sum = 0, state = 0, i = 0, dir = 1;
unsigned int number = 0, check;
if (argc < 2) {
fprintf(stderr, "Missing filename\n");
return EXIT_FAILURE;
}
char* filename = argv[1];
FILE* file = fopen(filename, "r");
if (!file) {
perror(filename);
return EXIT_FAILURE;
}
while (i != EOF) {
i = fgetc(file);
if (isdigit(i)) {
if (dir) {
state = 1;
check = number;
number = number * 10 + i - '0';
if (check > number || number > INT_MAX) {
fprintf(stderr, "Single number overflow error\n");
fclose(file);
return EXIT_FAILURE;
}
}
} else {
if (state && dir) {
check = number;
if (dir < 0 && sum < 0)
check -= sum;
else if (dir > 0 && sum > 0)
check += sum;
if (check > INT_MAX) {
fprintf(stderr, "Sum overflow error\n");
fclose(file);
return EXIT_FAILURE;
}
sum += number * dir;
number = 0;
}
state = 0;
dir = i == '-' ? -1 : i == '.' ? 0 : 1;
}
}
printf("Sum of numbers is: %i\n", sum);
fclose(file);
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
测试运行:
$ cat opString.txt
This text 15 is very 19 nice.
$ ./test2 opString.txt
Sum of numbers is: 34
$
Run Code Online (Sandbox Code Playgroud)
以防万一您使用的是 64 位 linux 系统,并且需要更高的性能(您提到大文件),下面的代码将映射整个文件(即使文件大于内存,内核也会很好地处理它)并且不会创建库调用每个字符。在我的测试中,isdigit()并strtol()显着减慢了速度。
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <limits.h>
#include <sys/mman.h>
int addToSum(unsigned int* number, int* sum, int dir, FILE* file) {
unsigned int check;
check = *number;
if (dir < 0 && *sum < 0)
check -= *sum;
else if (dir > 0 && *sum > 0)
check += *sum;
if (check > INT_MAX) {
fprintf(stderr, "Sum overflow error\n");
fclose(file);
exit(EXIT_FAILURE);
}
*sum += *number * dir;
*number = 0;
}
int main(int argc, char* argv[]) {
int sum = 0, state = 0, i = 0, dir = 1;
unsigned int number = 0, check;
if (argc < 2) {
fprintf(stderr, "Missing filename\n");
return EXIT_FAILURE;
}
char* filename = argv[1];
FILE* file = fopen(filename, "r");
if (!file) {
perror(filename);
return EXIT_FAILURE;
}
if (fseek(file, 0L, SEEK_END) < 0) {
perror("fseek failed");
fclose(file);
return EXIT_FAILURE;
}
long fsize = ftell(file);
char* fmap = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fileno(file), 0);
if (fmap == MAP_FAILED) {
perror("map failed");
fclose(file);
return EXIT_FAILURE;
}
long pos = 0;
while (pos < fsize) {
i = fmap[pos++];
if (i >= '0' && i <= '9') {
if (dir) {
state = 1;
check = number;
number = number * 10 + i - '0';
if (check > number || number > INT_MAX) {
fprintf(stderr, "Single number overflow error\n");
fclose(file);
return EXIT_FAILURE;
}
}
} else {
if (state && dir) addToSum(&number, &sum, dir, file);
state = 0;
dir = i == '-' ? -1 : i == '.' ? 0 : 1;
}
}
if (state && dir) addToSum(&number, &sum, dir, file);
printf("Sum of numbers is: %i\n", sum);
fclose(file);
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
但 isdigit(i) 只是一个计数器,计算该文本内容有多少位数字,而不是数字的总和。
请记住,函数isdigit()每次调用都会读取一个字符。9因此,例如,如果它读取该字符,则该值sum应增加i - '0'(或 57 - 48,或 9)。如果序列中有两个字符,例如92,一次读取一个字符,则该值sum同样会增加9+2 -> 11,而不是92。鉴于这就是您想要的,以下是如何做到这一点:
您确定的数字值实际上是ASCII值,因此在查看此表时,您可以看到所有数字的值都是从“0”到“9”(或者在 ASCII 中为 48 到 57)。因此,在您的代码中,您只需更改一行即可对值计数求和:
int sum = 0;
file = fopen(filename, "r");
while ((i = fgetc(file)) != EOF) {
if (isdigit(i))
sum += (i - '0');//subtract 48 from every 'i' verified as digit
//Sum will therefore add up values
//between (48-48) to (57-48)
//(or between 0 to 9)
}
printf(f,"Sum of numbers is: %i", sum);
fclose(file);
Run Code Online (Sandbox Code Playgroud)
但是,如果您想对缓冲区内的数字序列表示的数值求和,则代码是不同的。读取缓冲区内容时需要保留一个标志。
在伪代码中:
char accumlator[10] = {0}; max possible sequential digits (change as needed)
int found = 0;//flag
int sum = 0;
while ((i = fgetc(file)) != EOF)
{
if (isdigit(i))
{
accumulator[found] = i;
found++;
}
else
{
if(found != 0)
{
sum += atoi(accumulator);
found = 0;
accumulator[0] = 0;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)