如何测试此C程序的“效率”?最有趣的用法是,对于足够大的输入,它返回负输出,否则行为与预期的差不多。您是否会提出如何改进代码的建议,main中使用的ui应该既有效又最好是平台无关的,并且不使用太多库来实现可移植性和优化。我认为我可以做出的明显改进是将int编码为unsigned int

/*
 * NextPrime
 *
 * Return the first prime number larger than the integer
 * given as a parameter. The integer must be positive.
 */
#define PRIME_FALSE   0     /* Constant to help readability. */
#define PRIME_TRUE    1     /* Constant to help readability. */
#include <stdio.h>
#include <stdlib.h>
int nextprime(int inval) {
    int perhapsprime; /* Holds a tentative prime while we check it. */
    int testfactor; /* Holds various factors for which we test perhapsprime. */
    int found; /* Flag, false until we find a prime. */

    if (inval < 3) /* Initial sanity check of parameter. */
    {
        if (inval <= 0)
            return (1); /* Return 1 for zero or negative input. */
        if (inval == 1)
            return (2); /* Easy special case. */
        if (inval == 2)
            return (3); /* Easy special case. */
    } else {
        /* Testing an even number for primeness is pointless, since
         * all even numbers are divisible by 2. Therefore, we make sure
         * that perhapsprime is larger than the parameter, and odd. */
        perhapsprime = (inval + 1) | 1;
    }
    /* While prime not found, loop. */
    for (found = PRIME_FALSE; found != PRIME_TRUE; perhapsprime += 2) {
        /* Check factors from 3 up to perhapsprime/2. */
        for (testfactor = 3; testfactor <= (perhapsprime >> 1) + 1; testfactor
                += 1) {
            found = PRIME_TRUE; /* Assume we will find a prime. */
            if ((perhapsprime % testfactor) == 0) /* If testfactor divides perhapsprime... */
            {
                found = PRIME_FALSE; /* ...then, perhapsprime was non-prime. */
                goto check_next_prime;
                /* Break the inner loop, go test a new perhapsprime. */
            }
        }
        check_next_prime: ; /* This label is used to break the inner loop. */
        if (found == PRIME_TRUE) /* If the loop ended normally, we found a prime. */
        {

            return (perhapsprime); /* Return the prime we found. */
        }
    }

    return (perhapsprime); /* When the loop ends, perhapsprime is a real prime. */
}

int main(int argc, char *argv[]) {
    int intvar;
    if (sscanf(argv[1], "%i", &intvar) != 1) {
        printf("please use an integer parameter to compute the next prime\n");
    } else
        printf("%d => %d\n", intvar, nextprime(intvar));
    return 0;
}


#1 楼

您可以改善的事情

效率



如@rolfl所述,您想使用Eratosthenes筛(该答案为+1)。



我看不到您是否已经在执行此操作,但是您应该使用编译器的最高优化级别进行编译。以GCC为例,这将是-O3

可移植性


请记住要尽可能严格地遵循标准,因为支持C的系统还必须遵守标准。
如果希望程序具有尽可能好的可移植性,请查看用于打包程序的AutoTools套件。在您的情况下,这有点极端,因为您没有使用外部库,并且您的程序没有很多系统依赖性。

标准



您不必在0的末尾返回main(),就像您不会费心将return;放在void -returning函数的末尾一样。 C标准知道它的使用频率,并且让您不必打扰。


C99和C11§5.1.2.2(3)

...到达终止}函数的main()返回0的值。



使用标准库<stdbool.h>而不是定义自己的布尔值。


#define PRIME_FALSE   0     /* Constant to help readability. */
#define PRIME_TRUE    1     /* Constant to help readability. */



它仍然有助于提高可读性。并且由于它是C语言标准的一部分,因此,如果系统支持C语言,则可以保证系统具有此语法。 br />不要使用goto



是的,在某些罕见的情况下,您可能会发现有必要使用它。这不是其中的一个。一个简单的break;就足够了,因为您只是在打破内循环。


现在,您正在使用一种有点奇怪的方法来测试是否有整数输入命令行参数。


if (sscanf(argv[1], "%i", &intvar) != 1)



请改用argc测试输入到程序中的参数数量,并使用isdigit测试输入是否包含数字。
然后将输入转换为整数。

if (argc != 2 || !isdigit(**(argv+1))) printf("Please use an integer parameter to compute the next prime.\n");


请注意,输入的顺序很重要。我们要先测试argc,因此我们将其放在有条件的测试中。如果失败,则甚至不会评估||另一侧的第二部分,并执行条件语句之后的语句。如果我们切换这些测试,那么如果不输入命令行参数,可能会发生不确定的行为。


在不格式化字符串时,请使用puts()而不是printf()。这使您不必担心打印出的字符串末尾的换行符(\n)。

int intvar = atoi(*(argv+1));



您无需修改​​值intval函数中的nextprime()的整数,因此应将参数声明为常量。

puts("Please use an integer parameter to compute the next prime.");



您可以减少其中一个测试条件。


int nextprime(const int inval)



if (inval == 1)
    return (2); /* Easy special case. */
if (inval == 2)
    return (3); /* Easy special case. */




评论


\ $ \ begingroup \ $
“ *(argv + 1)”为什么不是argv [1]?
\ $ \ endgroup \ $
–尼古拉斯B.
2014年3月31日下午3:17

\ $ \ begingroup \ $
@NiklasB。没关系。您可以用任何一种方式编写它。那就是我选择的方式。
\ $ \ endgroup \ $
–syb0rg
2014年3月31日在3:23



#2 楼

让我们回顾一下此函数的上下文。

如果经常通过大量输入调用此函数,则预先计算一堆质数然后找到输入的位置是有意义的值在预计算的集合中。预计算素数时,最常见的方法是使用Eratosthenes筛(维基百科)。

您的函数不是Sieve,因此,仅用于计算一个质数...。这实际上不是批评,而只是上下文化。

现在,关于您的代码:

IF语句简化

您的声明是:


if (inval < 3) /* Initial sanity check of parameter. */
{
    if (inval <= 0)
        return (1); /* Return 1 for zero or negative input. */
    if (inval == 1)
        return (2); /* Easy special case. */
    if (inval == 2)
        return (3); /* Easy special case. */
} else {
    /* Testing an even number for primeness is pointless, since
     * all even numbers are divisible by 2. Therefore, we make sure
     * that perhapsprime is larger than the parameter, and odd. */
    perhapsprime = (inval + 1) | 1;
}



这有一些我不喜欢的“特征”:


块的“真”面具有if (intval == ?)跌落条件。最后一个条件将始终为真,那么为什么要对其进行测试?
1不是质数。第一个质数是2。
if的“真”面将始终返回,因此不需要else块。

底线是将这种情况简化为:

if (inval < 2) /* Initial sanity check of parameter. */
{
    return (2); /* Easy special case. */
}

/* Testing an even number for primeness is pointless, since
 * all even numbers are divisible by 2. Therefore, we make sure
 * that perhapsprime is larger than the parameter, and odd. */
perhapsprime = (inval + 1) | 1;


简化的循环

退出条件:

您的代码具有相对简单的“大” for循环:


for (found = PRIME_FALSE; found != PRIME_TRUE; perhapsprime += 2) {



...但是,在该循环内,在迭代之前,请仔细检查found条件并返回是否是PRIME_TRUE ....为什么要仔细检查?仔细检查会使循环返回语句不可访问。

代码的尴尬表明您使用的是错误的结构。我会完全建议使用其他系统。...此问题空间非常适合带有“特殊”初始化程序的do-while循环。考虑以下循环:

// set up 'special' initializer (it will be re-incremented in the loop)
perhapsprime -= 2;
do {
    perhapsprime += 2;

    // check if perhapsprime is true now
    ....

} while (found != PRIME_TRUE);
return perhapsprime;


现在,出于可读性考虑,我实际上将那个循环的全部内容提取到另一个函数,例如is_prime(int perhapsprime),然后将整个nextprime函数将变为:

int nextprime(int inval) {
    int perhapsprime; /* Holds a tentative prime while we check it. */
    int found;

    if (inval < 2) /* Initial sanity check of parameter. */
    {
        return (2); /* Easy special case. */
    }

    /* Testing an even number for primeness is pointless, since
     * all even numbers are divisible by 2. Therefore, we make sure
     * that perhapsprime is larger than the parameter, and odd. */
    perhapsprime = (inval + 1) | 1;

    perhapsprime -= 2; /* pre-set the loop variable */
    do {
        perhapsprime += 2;
        found = is_prime(perhapsprime);
    } while (found != PRIME_TRUE);
    return perhapsprime;
}


但是:现在,函数提取已经发生,可以用简单的while循环代替do-while来代替:

    /* Testing an even number for primeness is pointless, since
     * all even numbers are divisible by 2. Therefore, we make sure
     * that perhapsprime is larger than the parameter, and odd. */
    perhapsprime = (inval + 1) | 1;

    while (is_prime(perhapsprime) != PRIME_TRUE) {
        perhapsprime += 2;
    }
    return perhapsprime;


is_prime

好,现在我们必须将循环的内脏移至is_prime函数。在大多数情况下,这是一个复制粘贴练习。...

int is_prime(int perhapsprime) {

    int limit;
    int testfactor;

    limit = (perhapsprime >> 1) + 1; /** SEE NOTES!!! */

    for (testfactor = 3; testfactor <= limit; ++testfactor) {
        if ((perhapsprime % testfactor) == 0) /* If testfactor divides perhapsprime... */
        {
            return PRIME_FALSE; /* ...then, perhapsprime was non-prime. */
        }
    }

    return PRIME_TRUE;
}


此处的特殊说明:


使用limit = (perhapsprime >> 1) + 1可能仅通过一次计算极限来提高性能。我的C经验不足以肯定地告诉您。无论如何,它使代码更具可读性。
使用limit = (perhapsprime >> 1) + 1并不是最好的限制。要获得真实的限制,应该是sqrt(perhapsprime)
我在循环增量器中使用了++testfactor,因为最好的做法是进行预增量而不是+= 1

结论

希望这会给您一些想法,并表明for循环不一定是最好的或唯一的循环结构。

编辑:我将所有这些放在一起ideone。

评论


\ $ \ begingroup \ $
措辞上的小问题:筛子主要在您希望在相当窄范围内进行大量查询时有用。对于广泛的输入,最终只使用计算出的数据的一小部分。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2014年3月30日19:24