贝塞尔曲线

贝塞尔曲线(Bézier curve)有着十分广泛的用途。在讨论贝塞尔曲线之前,我们还得先回顾上一篇文章《伯恩斯坦多项式》。在这篇文章中,我们提到了伯恩斯坦多项式的一般形式

$$
B_n(t) = \sum_{i=0}^{n}\beta_i \cdot b_{i, n}(t)
$$

其中,

$$
b_{i,n}(t) = \binom{n}{i}\cdot t^{i} \cdot (1-t)^{(n-i)}, \quad t\in[0, 1]
$$

是 n 阶伯恩斯坦基底多项式。而 $\beta_i$ 叫做伯恩斯坦系数。当伯恩斯坦系数是二维平面中的一系列固定点时,伯恩斯坦多项式就演变成了本篇要讨论的贝塞尔曲线。

阅读余文贝塞尔曲线

伯恩斯坦多项式

我们在研究贝塞尔曲线的时候,首先遇到的就是伯恩斯坦多项式(Bernstein polynomial),为此,有必要专门开出一篇文章来探讨伯恩斯坦多项式的性质。

从定义出发,伯恩斯坦多项式的第n阶项有如下形式:

$$
b_{i,n}(t) = \binom{n}{i}\cdot t^{i} \cdot (1-t)^{(n-i)}, \quad t\in[0, 1]
$$

其中 $i=0, 1, …, n$, 而

$$
\binom{n}{i} = \frac{n!}{i!(n-i)!}
$$

是二项式系数。伯恩斯坦 n 阶多项式可以形成一组 n 阶指数多项式的基底。一般伯恩斯坦多项式可以表示为:

$$
B_n(t) = \sum_{i=0}^{n}\beta_i \cdot b_{i, n}(t)
$$

其中,$\beta_i$ 叫做伯恩斯坦系数。读者看到这个形式可能一下子就联想到贝塞尔曲线了。是的,这就是贝塞尔曲线的函数形式。不过,贝塞尔曲线我们会在下一篇文章中去详细论述,本篇只探讨伯恩斯坦多项式的特性。

阅读余文伯恩斯坦多项式

匿名命名空间

在 C/C++ 的源文件中,为了对外隔离符号,即外界无法通过 extern 声明捕获到这个符号,我们一般会采用 static 声明的方式来处理这个符号。比如

// 采用static声明对外隐藏符号
static void internal_func();

static float internal_value;

这样, internal_funcinternal_value 这两个符号对外就是不可见的了。一般情况下,隐藏符号只通过 static 声明就可以了,但 static 声明也有如下缺点:

1、static 无法修饰类型,比如这样的类型声明就是错误的。

// 注意:这段代码是非法的
static class Widget {
public:
    ...
};

因此,如果想要隐藏 Widget 这个类,就不能用 static 声明的方式。

2、static 这个关键词用处过多,在不同的地方修饰的变量,其含义完全不同,容易造成混淆。比如下面定义的三个 static 变量,其含义就完全不同

class Widget
{
private:
    static float count;
};

static const float PI = 3.141592654;

void test()
{
    static int i = 0;
    ...
}

3、由于 static 声明会使得符号没有外部链接属性,因此某些模板参数将不能使用这个符号,比如

template<typename T, const int& N>
class Widget
{
public:
    ...
private:
    T data[N];
    ...
};

static const int kMaxSize = 30;

Widget<int, kMaxSize> w;   // 报错,因为kMaxSize并不具备外部链接属性

为了克服上述三个缺点,就需要引入另一种隐藏符号的方式——匿名命名空间。我们先来看这样一段声明

// 采用匿名命名空间对外隐藏符号
namespace {

void internal_func();

float internal_value;

}

在这段声明中,注意我们并没有指定 namespace 是什么。这种没有为 namespace 指定名字的命名空间就是所谓的“匿名命名空间”。

阅读余文匿名命名空间

calloc 和 malloc 的区别

在 C 语言中,从堆(heap)中动态分配内存一般有两种方式,一种是通过 calloc,另一种是通过 malloc。那么这两种动态分配内存的方式有什么区别呢?

阅读余文calloc 和 malloc 的区别

缓动曲线

缓动曲线在UI动画中的应用十分广泛。缓动曲线可以用来控制动画的运动速率,使其按照我们的意愿模拟真实物体的运动规律。举个例子,当我们往上抛出一个石块时,在不考虑空气阻力的情况下,石块会在重力的作用下,先匀减速上升,直至速度为零。而后,石块的速度又会从零开始匀加速下降。那么,如何模拟这样的运动过程呢?在高中物理课上,我们已经知道,石块的位移和时间之间满足平方关系:
$$
h(t) = v_0t – \frac{1}{2}gt^2 + h_0
$$

其中,$h(t)$ 是石块在 $t$ 时刻的高度,$v_0$ 是石块的初速度,$g$ 是重力加速度,$h_0$ 是石块的初始高度。从这个式子可以看出,我们需要一条二次方的运动曲线(抛物线)来模拟这样的运动过程。而类似这样的一条曲线,就是本文要讨论的缓动曲线。

总的来说,缓动曲线包含四大类,分别是线性(linear)、缓入(ease in)、缓出(ease out)和缓入缓出(ease in and out)。除了线性类,其余三大类又可以细分出各种子类,比如二次方缓动曲线(Quadratic)就是其中的一个子类。以上文提到的石块为例,石块的运动曲线满足二次方缓动曲线,而上升过程满足缓出的过程(速度先快后慢),下降过程则满足缓入过程(速度先慢后快)。本文将对线性类和缓动类的 10 种子类共 11 种类别 31 种组合情况进行公式化整理,并绘制相应的缓动曲线图,供大家查阅。这 11 种类别分别为:

  • Linear
  • Sinusoidal
  • Quadratic
  • Cubic
  • Quartic
  • Quintic
  • Exponential
  • Circular
  • Back
  • Elastic
  • Bounce

阅读余文缓动曲线

C/C++ 函数指针

函数指针是一个指向函数地址的指针,未来可以通过函数指针来调用函数。可以把函数指针的定义理解为一种协议代理机制。函数指针定义了函数应该长什么样,需要什么参数,会得到何种返回值。这就相当于定义了一个协议。不同的代理人根据这一层协议,去实现自己的函数体,也即实现自己的代理行为。不同的代理人可以把自己的函数体传递给函数指针的调用者,调用者并不关心是谁代理了这个协议,只要传递给他的是一个合法的非空指针的协议,就可以在合适的时机去执行这个函数体。从这个意义上来说,函数指针的一大好处就是封装了行为。

那么,函数指针具体是如何定义的,调用者是如何调用函数指针传进来的函数体的?这篇文章就通过几个例子来说明函数指针的用法。

阅读余文C/C++ 函数指针

函数式编程:柯里化

柯里化(Currying)是函数式编程一个不可或缺的技术。柯里化就像手机电脑一样,当你没有的时候,你也许觉得它无足轻重;可一旦你拥有了,就会发现再也离不开它了。那么,什么是柯里化?它会用来解决什么问题?为什么说它是一个不可或缺的技术?

阅读余文函数式编程:柯里化