博文
[077] DDA画线算法(2007-01-15 22:19:00)
摘要:《计算机图形学》
整理一下计算机图形学上机时做过的一些小程序,某些部分参考了网上资源,由于时间已久,已经忘记出处。
所有程序均在TC环境编写测试,主要是因为在TC的图形系统下,分辨率较低,单个像素比较大,观察结果直观。另外,在TC下代码编写容易,可以把精力放在算法上。关于TC的图形系统参见:
[056] Turbo C的图形系统 。
DDA画线算法原理每本图形学的书里都有,这里将画线函数加了一个ms参数,用于延时,每画完一个点后用delay(ms)延时,这样就可以看到画线的过程了。由于TC里delay的定义有点老,所以在现在的机器上默认都不是按真正多少毫秒来执行的。
TC在我机器上默认识别的分辨率是640×480,即X方向为0~639,Y方向为0~479。所以定义X_MAX为639,Y_MAX为479。另外由于CRT显示器的原点在左上角,而我们习惯上将原点定义在左下角,所以在输出Y方向的坐标时要用高度最大值减去实际值,具体在程序中的Y_MAX - ((int) y)一句。主程序测试时画了一条由左下角到右上角的直线。
#include <math.h>
#include <stdio.h>
#include <graphics.h>
#define X_MAX 639 /* X方向最大值 */
#define Y_MAX 479 /* Y方向最大值 */
void initgr();
int LineDDA(int x1, int y1, int x2, int y2, int color, long ms);
int main(void)
{
long ms = 5000; /* 两点之间延持时间,便于观察画线过程 */
int color = 10; /* 设定颜色为绿色 */
&nbs......
[077] 任意大于6的偶数必定由两个素数组成(2007-01-01 22:17:00)
摘要:《C程序设计》(夏宝岚)
[相关] [031] 判断m是否是素数
[相关] [066] 筛选法求素数
哥德巴赫猜想的命题之一是:任意一个大于6的偶数必定由两个素数组成,试编写程序,验证并输出6至60以内的所有偶数的素数之和表达式。
由于每拆分一个偶数需要两次判断素数,因此程序中首先定义函数prime(),用于素数的判断,另外设计函数sumprime(int x),通过调用prime函数将一个偶数拆分成两个素数之和的表达式。
#include <stdio.h>
int prime(int x)
{
int i;
for (i = 2; x % i != 0; i++)
;
return x == i ? 1 : 0;
}
void sumprime(int x)
{
int t1, t2;
t1 = 1;
do
{
for (;;)
{
if (t1 <3 )
t1 = t1 + 1;
else<......
[076] 递归实现整数各位的分离(由高位到低位)(2006-12-27 21:21:00)
摘要:《C程序设计》(夏宝岚)
[相关] [048] 整数各位的分离(由低位到高位)
7.18 编写递归函数,以5位宽度左对齐的格式顺序输出正整数n中的每一位数字。例如,输入620857,则输出 6 2 0 8 5 7 。
#include <stdio.h>
void digit(long n)
{
long w;
if (n >= 10)
{
w = n / 10;
digit(w);
}
printf("%-5d", n % 10);
}
void main()
{
long x;
scanf("%ld", &x);
digit(x);
}
运行结果(VC):
=============================
620857↙
6 2 0 8 5 7
=============================
★ 输出部分,书上用的是 printf("%-5c", n % 10 + '0'); 即用字符格式输出的。在练习循环结构时,曾经写过倒序输出一......
[075] 求排列组合总数的函数(2006-12-01 12:15:00)
摘要:看到一个用C语言求排列组合总数的题,一时记不起公式了,翻了下概率书才想起,
排列数公式Pmn =m(m-1)…(m-n+1) , 可以用求阶乘的方法来做。
求出排列数,组合数就简单了,Cmn=Pmn /n! 而n!=Pnn。
根据这个思路,设计程序如下:
#include <stdio.h>
float P(int m, int n) /* 求排列数函数*/
{
int i;
float t = m;
for(i = 1; i < n; i++)
{
t = t * (m - i);
}
return t;
}
int main()
{
int m;
int n;
float s;
scanf("%d %d", &m, &n);
s = P(m, n) / P(n, n); /* 由排列数得到组合数 */
printf("C(%d,%d)=%-5.0f\n", m, n, s);
return 0;
}
运行结果(VC):
==========================
10 3↙
C(10,3)=120
==========================
[相关] [033] 阶乘->数据的范围 ......
[074] 字符串逆序输出(递归)(2006-11-19 22:18:00)
摘要:《C程序设计》(夏宝岚)
[相关] [053] 字符串逆序输出 。
编写递归函数,实现将输入的字符串以倒序输出。
#include <stdio.h>
void revers()
{
char c;
if((c = getchar()) != '\n')
revers();
if(c != '\n')
putchar(c);
}
void main()
{
revers();
printf("\n");
}
运行结果(VC):
=================
I am a student↙
tneduts a ma I
=================
★ 仔细“品”一下这个题目会发现很意思,特别是对理解递归的执行流程很有帮助(随便找一本C语言的书,都会找到执行流程的示意图)。不容易理解的就是那句putchar(c); 乍一看好像是输入回车后只会把最后的一个字符(即回车符)输出,而做了c!='\n'的限制后就应该什么都不输出才对,其实这正是递归的实质所在。
递归在编译系统里是通过堆栈来实现的,即如果函数当前层不满足结束条件,就会再次调用其本身,在调用之前,编译系统会为函数代码、形参以及函数中定义的局部变量在堆栈中分配单元,即将这些信息暂时存储起来,下一层函数若仍不满足结束条件,则重复此过程,继续将信息存储在系统堆栈中。在本题中,每一层输入的字符都已经存储在了堆栈中。当满足结束条件时,从最后层堆栈开始回归,一层层的返回。此题中从最后输入的回车符开始返回,完成了逆序将堆栈中存储的字符输出。
可能某些用词并不准确,不过我想我的理解应该没错^_^
[073] 递归函数的注意事项(2006-11-19 21:39:00)
摘要:《C程序设计》(夏宝岚)
<1> 一个正确的递归函数必须保证递推过程是有限制的,也就是说,递归函数的调用是带条件的,而且每次调用后条件会得到改变,否则将出现无休止地递推而无法回归的死递归。
<2> 递归函数的主要优点是算法设计容易,用于迭代、级数、链表等方面的算法有特殊效果,但递归函数的优点是在牺牲存储空间的基础上得到的,因为每进入下一层的调用,编译系统都将为函数代码、形式参数以及函数中定义的局部变量在堆栈中分配存储单元,只有当回归时,才从堆栈中退出。所以递归函数不能不分场合地乱用、滥用。......
[072] 求勒让德多项式(递归)(2006-11-19 21:18:00)
摘要:《C程序设计》(夏宝岚)
勒让德多项式定义为:
┌ 1 (n=0)
n!=ㄧ x (n=1)
└ ((2*n-1)*p(n-1,x)-(n-1)*p(n-2,x))/n (n>1)
#include <stdio.h>
double p(int n, double x)
{
if(n == 0)
return 1;
else
if(n == 1)
return x;
else
 ......
[071] 递归法求阶乘(2006-11-19 20:31:00)
摘要:《C程序设计第二版》(谭浩强)
[相关] [033] 阶乘->数据的范围
n!的递归公式:
┌ 1 (n = 0, 1)
n!=ㄧ
└ n * (n - 1)! (n > 1)
#include <stdio.h>
float fac(int n)
{
float f;
if(n < 0)
printf("n < 0, error!");
else
if(n == 0 || n == 1)
f = 1;
else
f = fac(n - 1) * n;
return f;
}
int main(void)
{
int n;
float y;
printf("input a integer:");
scanf("%d", &n);
y = fac(n);
&......
[070] 函数声明和函数原型(2006-11-19 16:49:00)
摘要:《C程序设计第二版》(谭浩强)
对函数的“定义”和“声明”不是一回事。“定义”是指对函数功能的确立,包括指定函数名,函数值类型、形参类型、函数体等,它是一个完整的、独立的函数单位。而“声明”的作用则是把函数的名字、函数类型以及形参类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如函数名是否正确,实参与形参的类型和个数是否一致)。从程序中可以看到对函数的声明与函数定义中的函数首部基本上是相同的。因此可以简单地照写已定义的函数的首部,再加一个分号,就成为了对函数的“声明”。在函数声明中也可以不写形参名,而只写形参的类型。
在C语言中,函数声明称为函数原型(function prototype)。使用函数原型是ANSI C的一个重要特点。它的作用主要是利用它在程序的编译阶段对调用函数的合法性进行全面检查。
说明:
<1> 以前的C版本的函数声明方式不是采用函数原型,而只是声明函数名和函数类型。
如:float add(); 不包括参数类型和参数个数。系统不检查参数类型和参数个数。新版本也兼容这种用法,但不提倡这种用法,因为它未进行全面的检查。
<2> 实际上,如果在函数调用前,没有对函数作声明,则编译系统会把第一次遇到的该函数形式(函数定义或函数调用)作为函数的声明,并将函数类型默认为int型。如一个max函数,调用之前没有进行函数声明,编译时首先遇到的函数形式是函数调用"max(a, b)",由于对原型的处理是不考虑参数名的,因此系统将max()加上int作为函数声明,即int max(); 因此不少教材说,如果函数类型为整型,可以在函数调用前不必作函数声明。但是使用这种方法时,系统无法对参数的类型做检查。或调用函数时参数使用不当,在编译时也不会报错。因此,为了程序清晰和安全,建议都加以声明为好。
<3> 如果被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译系统已经先知道了已定义的函数类型,会根据函数首部提供的信息对函数的调用作正确性检查。
<4> 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调用函数中不必对所调用的函数再作声明。
......
[069] 函数实参表列的求值顺序(2006-11-19 16:21:00)
摘要:《C程序设计第二版》(谭浩强)
如果实参表列包括多个实参,对实参求值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左顺序。许多C版本(例如Turbo C和MS C)是按自右而左的顺序求值。例:
int f(int a, int b);
void main()
{
int i = 2;
int p;
p = f(i, ++i)
}
如果按自左至右顺序求实参的值,则函数调用相当于f(2, 3)。若按自右至左顺序求实参值,则它相当于f(3, 3)。可以在不同的环境中试一试, 就可知所用系统的处理方法。由于存在上述情况,使程序通用性受到影响。因此应避免这种容易引起不同理解的情况。
如果本意是按自右而左顺序求实参的值,可以改写为
j = i;
k = ++i;
p = f(j, k);
如果本意是按自左而右顺序求实参的值,可以改写为
j = ++i;
p = f(j, j);
这种情况在printf函数中也同样存在,如
printf("%d, %d", i, i++);
也发生上述同样的问题,使用时应注意,避免使用这种容易混淆的用法。
......