低效率地使用strstr和strchr

时间:2019-01-06 14:12:37

标签: c

在审查我的代码时,我的教授说,我使用strstrstrchr会浪费大量资源,因为它们每个人都扫描字符串。

我可以很好地减少功能量吗?

此代码扫描字符串,并根据设置的参数确定输入是否有效。
ch1'@'ch2'.',(email[i])是字符串。

    for (i = 0; email[i] != 0; i++) {
        {
            if (strstr(email, "@.") ||
                strstr(email, ".@") ||
                strstr(email, "..") ||
                strstr(email, "@@") ||
                email[i] == ch1 ||
                email[i] == ch2 ||
                email[strlen(email) - 1] == ch1 ||
                email[strlen(email) - 1] == ch2) {
                printf("The entered e-mail '%s' does not pass the required parameters, Thus it is invalid\n", email);
            } else {
                printf("The email '%s' is a valid e-mail address\n",email);
            }
            break;
        }
    }

这是我正在谈论的摘录。

我应该编写自己的代码进行一次检查吗?如果是这样,您能在这方面给我一些指示吗? 谢谢。

编辑:非常感谢您的答复,我确实了解了代码中的错误,并希望我能从中学习。
再次感谢!

编辑:2:我要再次感谢您的答复,他们为我提供了极大帮助,并且我相信我编写了更好的代码

int at_count = 0, dot_count = 0, error1 = 0, error2 = 0;
int i;
size_t length = strlen(email);
int ch1 = '@', ch2 = '.';

for ( i = 0; email[i] != '\0'; i++)  /* for loop to count the occurance of the character '@' */
    {
    if ( email[i] == ch1)
        at_count++;
    }

for ( i = 0; email[i] != '\0'; i++)  /* for loop to count the occurance of the character '.' */
    {
    if ( email[i] == ch2)
        dot_count++;
    }

if ( email[0] == ch1 || email[0] == ch2 || email[length-1] == ch1 || email[length-1] == ch2 )
        {
    error1++;
        }
else
        {
    error1 = 0;
        }


if ( strstr(email,".@") || strstr(email, "@.") || strstr(email, "..") || strstr(email, "@@"))
        {
    error2++;
        }
else
        {
    error2 = 0;
        }

if ( (at_count != 1) || (dot_count < 1) || (error1 == 1) || (error2 == 1))
    {
    printf("The user entered email address '%s' is invalid\n", email);
    }
else
    {
    printf("'%s' is a valid email address\n", email);
    }

我觉得这是更优雅,更简单的代码,也更高效。
我的主要灵感来自@chqrlie,因为我觉得他的代码非常好并且易​​于阅读。 反正有什么可以改善的吗?
(电子邮件检查仅是为了练习,不要介意!)  非常感谢大家!

3 个答案:

答案 0 :(得分:3)

您的代码确实存在多个问题:

for (i = 0; email[i] != 0; i++) {   // you iterate for each character in the string.
    {   //this is a redundant block, remove the extra curly braces
        if (strstr(email, "@.") ||  // this test only needs to be performed once
            strstr(email, ".@") ||  // so does this one
            strstr(email, "..") ||  // so does this one
            strstr(email, "@@") ||  // again...
            email[i] == ch1 ||      // this test is only performed once
            email[i] == ch2 ||      // so is this one
            email[strlen(email) - 1] == ch1 ||  // this test is global
            email[strlen(email) - 1] == ch2) {  // so is this one
            printf("The entered e-mail '%s' does not pass the required parameters, Thus it is invalid\n", email);
        } else {
            printf("The email '%s' is a valid e-mail address\n", email);
        }
        break;  // you always break from the loop, why have a loop at all?
    }
}

您确实扫描了字符串4次以测试各种模式,然后对strlen()进行了2次扫描。在一次扫描过程中应该可以执行相同的测试。

还请注意,更多问题没有引起注意:

  • 应该只有一个@
  • 不应有空格
  • 通常,地址中允许的字符是受限制的。

某些测试似乎有些过分:为什么在..之前拒绝@,为什么在.之前拒绝结尾的@

这是一个更有效的版本:

int at_count = 0;
int has_error = 0;
size_t i, len = strlen(email);

if (len == 0 || email[0] == ch1 || email[0] == ch2 ||
    email[len - 1] == ch1 || email[len - 1] == ch2) {
    has_error = 1;
}

for (i = 0; !has_error && i < len; i++) {
    if (email[i] == '.') {
        if (email[i + 1] == '.' || email[i + 1] == '@') {
            has_error = 1;
        }
    } else if (email[i] == '@') {
        at_count++;
        if (i == 0 || i == len - 1 || email[i + 1] == '.' || email[i + 1] == '@') {
            has_error = 1;
        }
    }
    // should also test for allowed characters         
}

if (has_error || at_count != 1) {
    printf("The entered e-mail '%s' does not pass the required tests, Thus it is invalid\n", email);
} else {
    printf("The email '%s' is a valid e-mail address\n", email);
}

答案 1 :(得分:3)

您的教授很好地指出了email中重复扫描字符的效率低下。最佳情况下,每个字符仅应扫描一次。无论您使用for循环和字符串索引(例如email[i])还是简单地沿email字符串向下移动指针都取决于您,但是每个字符只能定位一次。相反,在您当前的代码中,您正在做

for email中的每个字符 ,您

  • 使用email扫描strstr 4次以找到给定的子字符串,然后
  • 使用email两次扫描到strlen的末尾两次

考虑一下。对于email中的每个字符,您要调用strlen两次,该命令会向前扫描email的全部内容,以查找以n结尾的字符。您所有的strstr调用中的所有四个都以不同的组合定位两个字符。您可以至少扫描一个或另一个,然后检查先前的字符和随后的字符。

@chqrlie指出了应检查的其他字符组合和条件,但是由于我认为这是一种学习练习,而不是用于生产代码的东西,因此足以意识到需要附加条件制定电子邮件验证程序。

尽管包括string.h并没有问题,并且对于更长的字符串(通常大于32个字符),string.h函数中的优化将提供不同程度的改进效率,但是没有需要承担任何函数调用开销。无论您要在输入中寻找什么,都可以始终使用指针检查每个字符并根据需要采取适当的操作来沿着字符串走下去。

使用简单的goto代替错误标志来解决问题的方法的简短示例可能类似于以下内容:

#include <stdio.h>

#define MAXC 1024

int main (void) {

    char buf[MAXC] = "",    /* buffer to hold email */
        *p = buf;           /* pointer to buf  */
    short at = 0;           /* counter for '@' */

    fputs ("enter e-mail address: ", stdout);
    if (fgets (buf, MAXC, stdin) == NULL) {     /* read/validate e-mail */
        fputs ("(user canceled input)\n", stderr);
        return 1;
    }

    while (*p && *p != '\n') {  /* check each character in e-mail */
        if (*p == '@')          /* count '@' - exactly 1 or fail */
            at++;
        if (p == buf && (*p == '@' || *p == '.'))   /* 1st char '@ or .' */
            goto emailerr;
        /* '@' followed or preceded by '.' */
        if (*p == '@' && (*(p+1) == '.' || (p > buf && *(p-1) == '.')))
            goto emailerr;
        /* sequential '.' */
        if (*p == '.' && (*(p+1) == '.' || (p > buf && *(p-1) == '.')))
            goto emailerr;
        p++;
    }   /* last char '@' or '.' */
    if (*(p-1) == '@' || *(p-1) == '.' || at != 1)
        goto emailerr;

    if (*p == '\n')     /* trim trailing '\n' (valid case) */
        *p = 0;

    printf ("The email '%s' is a valid e-mail address\n", buf);
    return 0;

  emailerr:;
    while (*p && *p != '\n')    /* locate/trim '\n' (invalid case) */
        p++;
    if (*p == '\n')
        *p = 0;
    printf ("The email '%s' is an invalid e-mail address\n", buf);
    return 1;
}

如前所述,有许多方法可以进行电子邮件验证,在很大程度上,您不应该专注于“微优化”,而应该专注于通过声音验证编写逻辑代码。但是,正如您的教授所指出的,与此同时,您的逻辑不应不必要地重复地将低效率注入代码中。编写高效的代码需要不断练习。进行此练习的一个好方法是编写不同版本的代码,然后将代码转储到汇编中并进行比较或对操作中的代码进行时间/分析,以了解可能存在的效率低下的地方。玩的开心。

仔细检查一下,如果还有其他问题,请告诉我。

答案 2 :(得分:0)

考虑strpbrk。可能所有条件都可以通过一封电子邮件进行评估。

#include <stdio.h>
#include <string.h>

int main( void) {
    char email[1000] = "";
    char at = '@';
    char dot = '.';
    char *find = NULL;
    char *atfind = NULL;
    char *dotfind = NULL;
    int atfound = 0;

    if ( fgets ( email, sizeof email, stdin)) {
        email[strcspn ( email, "\n")] = 0;//remove trailing newline
        find = email;
        while ( ( find = strpbrk ( find, "@."))) {//find a . or @
            if ( find == email) {
                printf ( "first character cannot be %c\n", *find);
                return 0;
            }
            if ( 0 == *( find + 1)) {
                printf ( "email must not end after %c\n", *find);
                return 0;
            }
            //captures .. @@ .@ @.
            if ( dot == *( find + 1)) {
                printf ( ". cannot follow %c\n", *find);
                return 0;
            }
            if ( at == *( find + 1)) {
                printf ( "@ cannot follow %c\n", *find);
                return 0;
            }
            if ( dot == *( find)) {
                dotfind = find;
            }
            if ( at == *( find)) {
                atfind = find;
                atfound++;
                if ( atfound > 1) {
                    printf ( "multiple @\n");
                    return 0;
                }
            }
            find++;
        }
        if ( !atfind) {
            printf ( "no @\n");
            return 0;
        }
        if ( !dotfind) {
            printf ( "no .\n");
            return 0;
        }
        if ( atfind > dotfind) {
            printf ( "subsequent to @, there must be a .\n");
            return 0;
        }
    }
    else {
        printf ( "problem fgets\n");
        return 0;
    }
    printf ( "good email\n");
    return 0;
}