我需要从数据文件中读取以下内容:
Sabre Corporation新墨西哥州新科里奥西Henness Lane 15790号65790
我的变量是char companyName[20+1]
,char companyAddress[30+1]
,char companyCity[15+1]
,char companyState[15+1]
,int companyZip
;
我使用companyName
读了%[^\n]
的第一行,但是试图以相同的方式读取第二行,变量保持为空。
void getCompanyData(FILE *CompanyFile, char *companyName, char *companyAddress, char *companyCity, char *companyState, int *companyZip)
{
fscanf(CompanyFile,"%[^\n]%[^\n]%s%s%d", companyName, companyAddress, companyCity, companyState, companyZip);
}
当我运行此代码并打印出变量时,companyName
看起来很好,“ Sabre Corporation”,但是companyAddress
却没有显示为任何内容。
如果我将第二个输入切换为简单的%s
,它将读取地址的数字。
是否有办法像整行一样将整行读取为第一行,而不是将其他许多变量连接成一个更大的变量?
答案 0 :(得分:4)
C的一点翻译。
"%[^\n]%[^\n]%s%s%d"
%[^\n] // reads everything up to the new line
// But does not read the new line character.
// So there is still a new line character on the stream.
%[^\n]%[^\n] // So the first one reads up to the new line.
// The second one will immediately fail as there is a new line
// still on the stream and thus not read anything.
所以:
int count = scanf(CompanyFile,"%[^\n]%[^\n]%s%s%d", /*Variables*/ );
printf("Count = %d\n", count);
由于仅填充了一个变量,因此将打印1。
我知道使用以下内容来读一行很诱人。
fscanf("%[^\n]\n", /* Variables*/ );
但这是一个坏主意,因为很难发现空行。空行不会将任何内容读入变量,因此在读取新行之前会失败,因此它实际上不会读取空行。因此最好将其分解为多个语句。
int count;
do {
count = fscanf("%[^\n]", /* Variables*/ );
fscanf("\n");
} while (count == 0);
// successfully read the company name and moved on to next line
// while skipping completely empty lines.
现在这似乎是上述内容的逻辑扩展。
但这不是最好的方法。如果您假设一行可能以上一行的'\ n'开头(并且您想忽略数据行上的任何前导空白),则可以在前面使用一个空格。
int count = fscanf(" %[^\n]", /* Variables*/ );
// ^ The leading space will drop all white space characters.
// this includes new lines so if you expect the last read may
// have left the new line on the stream this will drop it.
要注意的另一件事是,您应始终检查fscanf()
的返回值,以确保实际扫描了预期已扫描的变量数。
答案 1 :(得分:2)
是否有办法像整行一样将整行读取为第一行,而不是将其他许多变量连接成一个更大的变量?
如果目标是读取行(所有字符包括'\n'
,包括fgets()
)。
buffer[256];
if (fgets(buffer, sizeof buffer, CompanyFile) {
// success!
}
将行读入 string 后,对其进行解析。
“如果没有其他规则,“ Sabre Corporation 15790 West Henness Lane New Corio,新墨西哥州65790”显然无法解析为companyName[], companyAddress[], companyCity[], companyState[], companyZip
。
我希望会有更多的逗号。
考虑
3M 255 Century Ave N Maplewood,MN 55119(公司名称和数字)
西南航空P.O. Box 36647 Dallas,德克萨斯州75235(无街道编号,邮政信箱)
答案 2 :(得分:1)
您遇到的最大问题是确定街道地址的终止位置和城市的起始位置:
Sabre Corporation 15790 West Henness Lane New Corio, New Mexico 65790
正如@chux所提到的,您可能希望看到其他逗号或其他分隔符,以将公司名称与街道地址分开,并将街道地址与城市名称分开。但是,所有信息并没有丢失,而是需要假设街道地址的末尾带有可识别的单词,例如"boulevard", "drive", "lane", "street", etc...
(以及缩写,例如"Blvd.", "Dr.", etc..
,您可以根据需要添加)然后可以创建一个简单的查询,将字符串中的各个单词进行比较,以识别街道地址的结尾和城市的起点。
在C语言中,只要有遵循的规则就可以解析任何内容,这些规则使您可以从较大的文本中找到想要的内容的开头和结尾。在此出于示例目的,我们将假定公司名称不包含数字(您可以添加代码以处理@chux答案中建议的名称)
有很多方法可以从输入行中解析所需的信息。对于初学者,您要将其存储在哪里?每当您需要将不同的信息作为一个对象进行协调时,您都应该考虑struct
(如果您有多个对象,它就会适合于结构数组)。因此,在您的情况下,我们可以定义类似于以下内容的结构(实际上是结构的typedef
)
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define ADDR 30
#define NAME 20
#define COMP 15
typedef struct { /* struct to hold address parts */
char name[NAME+1],
addr[ADDR+1],
city[COMP+1],
state[COMP+1];
int zip;
} co_t;
正如我的评论中所讨论的,一个好的方法是使用fgets
将整行读入缓冲区,从而可以独立验证(1)读取本身,以及(2)从缓冲区解析项目。此外,当您一次读取整行时,整行都被消耗了,为下一次读取准备了输入缓冲区,而您不必担心fscanf
matching 或 input 失败,从而在输入缓冲区中留下了部分未读的行。这样,您可以使用以下命令将行读入缓冲区:
int main (int argc, char **argv) {
char buf[MAXC], *p = buf, *ep = buf, tmp[MAXC];
co_t company = { .name = "" };
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
if (!fgets (buf, MAXC, fp)) { /* read entire line into buf */
fputs ("(user canceled or stream error)\n", stderr);
return 1;
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
(注意:,在上面声明了缓冲区buf
,然后是两个指向缓冲区的指针,分别将起始指针p
和结束指针ep
声明为帮助解析,以及另一个临时缓冲区tmp
也有帮助。您声明结构的实例(初始化为全零),并在实际从文件中读取一行之前验证您已打开文件以供读取)
要将您的公司名称与其他名称分开,我们可以轻松地在缓冲区中找到第一个数字(假设您的公司名称不包含数字,则第一个数字将是街道地址的开头),然后进行备份以找到名称中的最后一个字母字符(将+1向前移动到其后的第一个空格,因此我们将名称放在buf
的开头和指针之间)。然后只需将名称复制到company.name
并 nul-terminate 。
由于地址开头的数字可以是任何数字,因此您可以使用isdigit()
从头开始检查,也可以使用非常方便的strpbrk()
函数来查找地址的第一个数字您返回指向它的指针,例如
if ((p = strpbrk (p, "0123456789"))) { /* locate 1st digit in buf */
ep = p; /* set endptr to pointer */
while (ep > buf && !isalpha (*ep)) /* backup to find name end */
ep--;
ep++; /* adv. to space after name */
if (ep - buf <= NAME) { /* if name will fit */
memcpy (company.name, buf, ep - buf); /* copy to company.name */
company.name[ep - buf + 1] = 0; /* nul-terminate */
}
}
else { /* no number found, invalid input */
fputs ("error: street number not found.\n", stderr);
return 1;
}
(注意:,上面我们保存了p
,使其继续指向地址的开头,然后使用ep
进行备份以查找名称的结尾(它们之间可能有多个空格,等等。)然后将名称放在buf
和ep
之间)
将想要的文本放在方括号中之后,指针(ep - buf
)的不同为您提供了需要复制到company.name
的字符串的字符数(长度),因此您只需使用memcpy
,因为名称末尾没有 nul-termination 字符,从而消除了使用strcpy
作为可能的解决方案。无论如何,没有理由使用strcpy
,因为您已经知道要复制多少个字符,因此以下就是您所需要的:
memcpy (company.name, buf, ep - buf); /* copy to company.name */
company.name[ep - buf + 1] = 0; /* nul-terminate */
回想一下,p
指向街道地址的开头,但是在这里我们遇到了一个问题:地址在哪里停止,城市在哪里开始?字符串中下一个可识别的里程碑将是城市之后的','
。因此,让我们使用strchr
来查找逗号,将端点指针ep
设置为指向逗号,然后将整个地址和城市复制到临时缓冲区tmp
中以进行进一步处理。 / p>
我们需要一个临时缓冲区,因为我们将使用strtok
将临时缓冲区拆分为令牌(单词),称为“令牌化”字符串。之所以需要临时缓冲区,是因为strtok
在解析字符串中的单词时,通过用 nul-character 替换每个分隔符序列来修改其作用的缓冲区。这里的想法是将tmp
缓冲区分成令牌,并逐字检查街道的结尾,例如"boulevard", "drive", "lane", "street", etc...
查找地址的末尾。 (注意:为了简化比较,所有内容均以小写字母列出)。
因此,我们要做的就是获取每个令牌,将其转换为小写字母,然后与每个街道的结尾进行比较以找到地址的结尾。如果令牌是结束符,则返回1
的简短函数;如果不是,则返回0
的简短函数,例如
/* simple function to look for word that is street ending */
int streetending (const char *s)
{
char buf[MAXC], *p = buf; /* temporary buf, convert s to lowercase */
char *endings[] = { "boulevard", "drive", "lane", "street", NULL },
**e = endings; /* pointer to endings */
strcpy (buf, s); /* copy s to buf */
while (*p) { /* convert buf to all lowercase (for comparison) */
*p = tolower (*p);
p++;
}
while (*e) { /* loop over street endings compare to buf */
if (strcmp (buf, *e) == 0) /* if match, return success */
return 1;
e++; /* advance pointer to next ending */
}
return 0; /* s not street ending, return failure */
}
有了该辅助功能,我们可以按以下步骤将地址和城市分开:
if ((ep = strchr (p, ','))) { /* find ',' after city */
memcpy (tmp, p, ep - p); /* copy address & city to tmp */
tmp[ep - p] = 0; /* nul-terminate */
/* split tmp into tokens checking for street ending ("Lane") */
for (char *wp = strtok (tmp, " \n"); wp; wp = strtok (NULL, " \n")) {
/* keep track of no. of chars added, check it fits -- here */
strcat (company.addr, wp); /* copy to address */
if (streetending (wp)) { /* check if street eding */
wp += strlen (wp) + 1; /* adv. past current word */
while (!isalpha (*wp)) /* adv. to start of city */
wp++;
strcpy (company.city, wp); /* copy city to struct */
break; /* done */
}
strcat (company.addr, " "); /* not street ending, add space */
}
}
(注意:,我们在城市名称上方使用了一个临时单词指针wp
,将p
指向','
,这样我们就可以在那里将国家与邮编区分开来
接下来要做的是从p
开始在字符串的其余部分中向前搜索,寻找下一个以State开头的字母字符,然后我们可以使用相同的“查找第一位”逻辑我们在上面用来定位地址的开头,在邮政编码的开头,例如
while (!isalpha (*ep)) /* adv. endptr to start of state */
ep++;
p = ep; /* set pointer to start of state */
if ((ep = strpbrk (ep, "0123456789"))) { /* locate start of zip */
char *zp = ep; /* set zip pointer */
while (ep > p && !isalpha (*ep)) /* backup to end of state */
ep--;
ep++;
if (ep - p <= COMP) { /* make sure city fits */
memcpy (company.state, p, ep - p); /* copy state to struct */
company.state[ep - p + 1] = 0; /* nul-terminate */
}
if (sscanf (zp, "%d", &company.zip) != 1) { /* convert zip to int */
fputs ("error: invalid integer for zip.\n", stderr);
return 1;
}
}
(注意::另一个临时zip指针zp
用于保存指向Zip开头的指针,然后再备份以找到状态的结尾)
基本上,这是将生产线最小化为所需部分的全部所需内容。 (如果公司名称中有数字,则由您自己决定是否添加该逻辑,依此类推...)将这个基本示例放在一起,您可以执行以下操作:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define ADDR 30
#define NAME 20
#define COMP 15
typedef struct { /* struct to hold address parts */
char name[NAME+1],
addr[ADDR+1],
city[COMP+1],
state[COMP+1];
int zip;
} co_t;
/* simple function to look for word that is street ending */
int streetending (const char *s)
{
char buf[MAXC], *p = buf; /* temporary buf, convert s to lowercase */
char *endings[] = { "boulevard", "drive", "lane", "street", NULL },
**e = endings; /* pointer to endings */
strcpy (buf, s); /* copy s to buf */
while (*p) { /* convert buf to all lowercase (for comparison) */
*p = tolower (*p);
p++;
}
while (*e) { /* loop over street endings compare to buf */
if (strcmp (buf, *e) == 0) /* if match, return success */
return 1;
e++; /* advance pointer to next ending */
}
return 0; /* s not street ending, return failure */
}
int main (int argc, char **argv) {
char buf[MAXC], *p = buf, *ep = buf, tmp[MAXC];
co_t company = { .name = "" };
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
if (!fgets (buf, MAXC, fp)) { /* read entire line into buf */
fputs ("(user canceled or stream error)\n", stderr);
return 1;
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
if ((p = strpbrk (p, "0123456789"))) { /* locate 1st digit in buf */
ep = p; /* set endptr to pointer */
while (ep > buf && !isalpha (*ep)) /* backup to find name end */
ep--;
ep++; /* adv. to space after name */
if (ep - buf <= NAME) { /* if name will fit */
memcpy (company.name, buf, ep - buf); /* copy to company.name */
company.name[ep - buf + 1] = 0; /* nul-terminate */
}
}
else { /* no number found, invalid input */
fputs ("error: street number not found.\n", stderr);
return 1;
}
if ((ep = strchr (p, ','))) { /* find ',' after city */
memcpy (tmp, p, ep - p); /* copy address & city to tmp */
tmp[ep - p] = 0; /* nul-terminate */
/* split tmp into tokens checking for street ending ("Lane") */
for (char *wp = strtok (tmp, " \n"); wp; wp = strtok (NULL, " \n")) {
/* keep track of no. of chars added, check it fits -- here */
strcat (company.addr, wp); /* copy to address */
if (streetending (wp)) { /* check if street eding */
wp += strlen (wp) + 1; /* adv. past current word */
while (!isalpha (*wp)) /* adv. to start of city */
wp++;
strcpy (company.city, wp); /* copy city to struct */
break; /* done */
}
strcat (company.addr, " "); /* not street ending, add space */
}
}
while (!isalpha (*ep)) /* adv. endptr to start of state */
ep++;
p = ep; /* set pointer to start of state */
if ((ep = strpbrk (ep, "0123456789"))) { /* locate start of zip */
char *zp = ep; /* set zip pointer */
while (ep > p && !isalpha (*ep)) /* backup to end of state */
ep--;
ep++;
if (ep - p <= COMP) { /* make sure city fits */
memcpy (company.state, p, ep - p); /* copy state to struct */
company.state[ep - p + 1] = 0; /* nul-terminate */
}
if (sscanf (zp, "%d", &company.zip) != 1) { /* convert zip to int */
fputs ("error: invalid integer for zip.\n", stderr);
return 1;
}
}
/* if it all worked, your values should be nicely separated */
printf ("'%s'\n'%s'\n'%s'\n'%s'\n%d\n",
company.name, company.addr, company.city,
company.state, company.zip);
return 0;
}
使用/输出示例
使用存储在文件dat/coaddress.txt
中的输入行,并在所有字符串字段周围添加单引号来标记提取的字符串,然后在输入上运行程序将提供:
$ ./bin/companynameaddr dat/coaddress.txt
'Sabre Corporation'
'15790 West Henness Lane'
'New Corio'
'New Mexico'
65790
读取一行或一千行都是相同的。该代码的唯一区别是将处理包裹在while (fgets (buf, MAXC, fp)) { ...
循环中,为结构数组保留索引,并将文件的结尾移到末尾。
仔细研究。有很多很多方法可以做到这一点。我们所做的基本上称为“遍历字符串”,其中,您基本上是在字符串下方“一对虫”一对指针以提取所需信息。我们使用strpbrk
和strchr
帮助推进指针,并且让strtok
帮助分离一个临时缓冲区,寻找街道结尾词"Lane"
,以确定街道的终点和城市开始。您可以至少执行10种不同的方式。如果您对以上操作还有其他疑问,请告诉我。