Margoo

...?

基础函数图像的绘制 铜牌收录

一、基础的一次、二次函数图像绘制

先来考虑最简单的情况:若有函数 ,记其图像为 ,若 ,先要写一个程序:使用 EasyX 绘制出 ,应该怎么做呢。

考虑到 为一次函数,一次函数的图像实际上就是一条直线,所以可以计算出函数在窗口最左边和最右边的点坐标,并将两点相连即可,这种方法非常简单,但是只能处理严格递增或严格递减的线性函数,并处理不了复杂的函数,显然应该换另外一种方法。

回想初中阶段第一次学习函数图像的时候,课本介绍的就是描点连线的方法,所以我们可以使用描点连线的方法来绘制函数图像,先计算每个 X 坐标对应的函数坐标值,然后描点连线,这样子可以就可以处理非线性函数了,代码如下:

#include <conio.h>
#include <graphics.h>

constexpr double k			= -2;
constexpr double b			= 40;
constexpr int	 width		= 640;
constexpr int	 height		= 480;
constexpr int	 halfWidth	= width / 2;
constexpr int	 halfHeight = height / 2;

double f(const double& x) {
	return k * x + b;
}

void drawAxis() {
	setlinecolor(RED);
	line(-halfWidth, 0, halfWidth, 0);
	line(0, -halfHeight, 0, halfHeight);
}

int main() {
	initgraph(width, height);

	// 设置中心点,相当于确定直角坐标系 XoY 的原点
	setorigin(halfWidth, halfHeight);
	// 绘制坐标轴
	drawAxis();

	setlinecolor(YELLOW);

	for (int pixel = -halfWidth; pixel < halfWidth; ++pixel) {
		// 计算当前点和下一个点
		int localValue = static_cast<int>(f(pixel));
		int nextValue  = static_cast<int>(f(pixel + 1));

		// 描点连线
		putpixel(pixel, localValue, YELLOW);
		putpixel(pixel + 1, nextValue, YELLOW);
		line(pixel, localValue, pixel, nextValue);
	}

	_getch();

	return 0;
}

运行代码后,会有如下效果:

linear-function-graph

不过,可能已经有读者发现了,绘制出来的函数图像其实并不正确,这是因为在执行 setorigin 以后,建立的平面直角坐标系 XOY 与我们平时常用的坐标系的 Y 轴是相反的,所以在函数 f 中,应该修改函数的返回值为负数,就像这样:

double f(const double& x) {
	return -(k * pow(x, 2) + b);
}

这个方法不仅能够画出一次函数的图像,还能够画出二次函数的图像:

的图像。

目前看来,这个方法似乎能很好地绘制出函数的图像,然而存在两个问题:

  1. 绘制出的函数图像不能够调整缩放比例。
  2. 这个方法不能处理函数图像上的断点和某些趋于无穷的函数图像。

第一个问题非常好解决,只需要加入一个 step 变量代表计算机上每一个像素对应的步长,和一个 scale 代表 y 轴的缩放参数,代码如下:

#include <conio.h>
#include <graphics.h>

constexpr double k			= 0.01;
constexpr double b			= -40;
constexpr int	 width		= 640;
constexpr int	 height		= 480;
constexpr int	 halfWidth	= width / 2;
constexpr int	 halfHeight = height / 2;

double f(const double& x) {
	return -(k * x * x + b);
}

void drawAxis() {
	setlinecolor(RED);
	line(-halfWidth, 0, halfWidth, 0);
	line(0, -halfHeight, 0, halfHeight);
}

int main() {
	initgraph(width, height);

	// 步长
	constexpr double step	 = 0.6;
	// y 轴缩放系数
	constexpr double scale	 = 1.7;
	double			 stepNow = -halfWidth * step;

	// 设置中心点,相当于确定直角坐标系 XoY 的原点
	setorigin(halfWidth, halfHeight);
	// 绘制坐标轴
	drawAxis();

	setlinecolor(YELLOW);

	for (int pixel = -halfWidth; pixel < halfWidth; ++pixel) {
		// 计算当前点和下一个点
		int localValue = static_cast<int>(f(stepNow));
		int nextValue  = static_cast<int>(f(stepNow + step));

		// 描点连线
		putpixel(pixel, scale * localValue, YELLOW);
		putpixel(pixel + 1, scale * nextValue, YELLOW);
		line(pixel, scale * localValue, pixel, scale * nextValue);

		stepNow += step;
	}

	_getch();

	return 0;
}

然而第二个问题就比较棘手了,为了更加方便读者理解,这里我放出使用这个方法绘制的两个函数的图像:

其中图一是 的图像,而图二是 的图像,可以发现,这种方法都错误的多绘制了一条类似渐近线的线条,且都没有正确地绘制出曲线,然而这当然不会是渐近线。

导致没有正常绘制出曲线的原因是在原本的代码中,计算函数值的时候就将函数的值转换成了 int 类型,丢失了浮点数的小数位,就像这样:

int localValue = static_cast<int>(f(stepNow));
int nextValue  = static_cast<int>(f(stepNow + step));

其实这个问题非常好解决,只需要把代码改成这样:

// 计算当前点和下一个点
double localValue = f(stepNow);
double nextValue  = f(stepNow + step);

// 描点连线
putpixel(pixel, int(scale * localValue), YELLOW);
putpixel(pixel + 1, int(scale * nextValue), YELLOW);
line(pixel, int(scale * localValue), pixel, int(scale * nextValue));

就可以正常绘制出来了,但是多画出来的渐近线问题始终没有被解决。

二、解决断点问题

解决断点问题,有很多种方法,数学上断点的定义是