微积分(下)
原图
多元函数微分
多元函数及其邻域
二元函数的严格定义: 假设\(D\)是二维向量\((x,y)\)的集合,\(D\)上的二元函数\(\ f\)是一个映射法则,它对\(D\)内的每一个有序对\((x,y)\)指定唯一的一个实数:
\[
z=f(x,y),\quad (x,y)\in D
\]
如果用\(P\)来代替\((x,y)\)的话,也可以写作:
\[
z=f(P),\quad P\in D
\]
\(D\)称为\(f\)的定义域,\(x、y\)(或\((x,y)\),或\(P\))称为\(f\)的自变量,\(z\)称为\(f\)的因变量。
如果定义域\(D\)是更高维的向量的集合,也就是说自变量为更高维的向量,那么 \(f\)可以称为多元函数,也叫作多变量函数。
邻域和去心邻域:
二维向量的邻域要比一维向量的复杂。对于二维向量\(P_0(x_0,y_0)\)而言,半径为\(\delta\) 邻域可以表示为平面点集:
\[
U(P_0,\delta)=\{(x,y)\ |\ (x-x_0)^2 + (y-y_0)^2 < \delta^2\}
\]
多元函数极限和连续 ...
微积分(上)
原图
极限
魏尔斯特拉斯的数列极限
设\({x_n}\)为一数列,如果存在实数 L,对于任意给定的正实数\(\epsilon\)(不论它多么小),总存在正整数 N,使得对所有的 n > N 时,有:
\[
\left|x_{n}-L\right|<\epsilon
\]
那么就称 L 是数列\({x_n}\)的极限,或者称数列\({x_n}\)收敛于 L,记为:
\[
\begin{gathered}
\lim _{n \rightarrow \infty} x_{n}=L
\end{gathered}
\] 或 \[
\begin{gathered}
x_{n} \rightarrow L(n \rightarrow \infty)
\end{gathered}
\]
一般函数的极限定义
设函数 f(x) 在\(\mathring{U}(x_0)\)上有定义。 如果存在常数 L,对任意给定的正数\(\epsilon\)(不论它多么小),总存在正数\(\delta\),使得当 x 满足不等式\(0 < |x - x_0| < \delta\)时,对应的函数 ...
C++设计模式(一)基于 Policy 的 class 设计
原图
概述
这一章将介绍所谓 policies 和 policy classes 它们是一种重要的 classes 设计技术,能够增加程序库的弹性并提高复用性,这正是 Loki 的目标所在。简言之,具备复杂功能的 policy based class 由许多小型 classes(称为 policies)组成。每一个这样的小型 class 都只负责单纯如行为或结构的某一方面( behavioral or structural aspect)。一如名称所示,一个 policy 会针对特定主题建立个接口。在“遵循 policy 接口”的前提下,你可以用任何适当的方法来实作 policies。
由于你可以混合并匹配各种 policies,所以藉由小量核心基础组件( core elementary components)的组合,你可完成一个“行为集”( behaviors set)。
软件设计的多样性(Multiplicity)
让我们考虑一个简単的入门级程序维型:-个 Smart Pointer(智能指针)。这种 class 可被用于单线程或多线程之中,可以运用不同的 ownership( ...
C++11(六)nullptr 与 Lambda
原图
nullptr
nullptr、NULL 和 0
般情况下,NULL 是一个宏定义。在传统的 C 头文件( stddef.h)里我们可以找到如下代码
123456#undef NULL#if defined(__cplusplus)#define NULL O#else#define NULL ((void *)0)#endif
可以看到,NULL 可能被定义为字面常量 0,或者是定义为无类型指针(void*)常量。不过无论采用什么样的定义,我们在使用空值的指针时,都不可避免地会遇到一些麻烦。
12345678910111213141516171819void func(int i){ std::cout << "int" << std::endl;}void func(void* p){ std::cout << "ptr" << std::endl;}int main(){ func(0); // int func(NULL); // ...
C++11(五)constexpr、原子与线程存储
原图
常量表达式(constexpr)
常量表达式机制是为了:
提供一种更加通用的常量表达式。
允许用户自定义的类型成为常量表达式。
提供了一种保证在编译期完成初始化的方法(可以在编译时期执行某些函数调用)。
考虑下面这段代码:
1234567891011121314enum Flags { good=0, fail=1, bad=2, eof=4 };constexpr int operator|(Flags f1, Flags f2){ return Flags(int(f1)|int(f2)); }void f(Flags x){ switch (x) { case bad: /* … */ break; case eof: /* … */ break; case bad|eof: /* … */ break; default: /* … */ break; }}
虽然“bad|eof”是一个表达式,但是因为这两个参数都是常量,在编译时期,就可以计算出它的结果,因而可以作为常量对 ...
C++11(四)智能指针
原图
shared_ptr
实现原理
一个 shared_ptr 对象的内存开销要比裸指针和无自定义 deleter 的 unique_ptr 对象略大。shared_ptr 需要维护的信息有两部分:
指向共享资源的指针
引用计数等共享资源的控制信息——实现上是维护一个指向控制信息的指针
所以,shared_ptr 对象需要保存两个指针。shared_ptr 的 deleter 是保存在控制信息中,所以,是否有自定义 deleter 不影响 shared_ptr 对象的大小。当我们创建一个 shared_ptr 时,其实现一般如下:
1std::shared_ptr<T> sptr1(new T);
复制一个 shared_ptr :
1std::shared_ptr<T> sptr2 = sptr1;
为什么控制信息和每个 shared_ptr 对象都需要保存指向共享资源的指针?可不可以去掉 shared_ptr 对象中指向共享资源的指针,以节省内存开销?
答案是:不能。 因为 shared_ptr 对象中的指针指向的对象不一定和控制块中的指针指向 ...
C++11(三)auto、decltype 与 for
原图
右尖括号>的改进
在 C++98 中,有一条需要程序员规避的规则:如果在实例化模板的时候出现了连续的两个右尖括号>,那么它们之间需要一个空格来进行分隔,以避免发生编译时的错误。
12345template <int i> class X{};template <class T> class Y{};Y<X<1> > x1; //编译成功Y<X<2>> x2; //编译失败
在 x2 的定义中,编译器会把>>优先解析为右移符号。
C++98 同样会将>>优先解析为右移。C++11 中,这种限制被取消了。事实上,C++11 标准要求编译器智能地去判断在哪些情况下>>不是右移符号。使用 C++11 标准,上述所示代码则会成功地通过编译。
auto 关键字
auto 的限制
使用 auto 的时候必须对变量进行初始化,这是 auto 的限制之一。那么,除此以外,auto 还有哪些其它的限制呢?
auto 不能在函数的参数中使用 这个应该很容易理解,我们在定 ...
C++11(二)右值引用与 POD
原图
继承构造函数
12345678910111213141516struct A{ A(int i){}; A(double d, int i) {}; A(float f, int i, const char* c){}; //...};struct B : A{ B(int i): A(i){}; B(double d, int i): A(d, i){}; B(float f, int i, const char*c): A(f, i, c)(){}; //... virtual void Extrainterface(){};};
继承于 A 的派生类 B 实际上只是添加了一个接口 Extralnterface,那么如果我们在构造 B 的时候想要拥有 A 这样多的构造方法的话,就必须一“透传”各个接口。这无疑是相当不方便的。事实上,在 C++中已经有了一个好用的规则,子类可以通过使用 using 声明来声明继承基类的构造函数。
12345678910111213struct A{ A(int i){}; A ...
C++11(一)assert 与 noexcept
原图
静态断言 static_assert
断言 assert 宏只有在程序运行时才能起作用,而#error 只在编译器预处理时才能起作用。有的时候,我们希望在编译时能做一些断言。读者也可以尝试一下 Boost 库内置的 BOOST_STATIC_ASSERT,其缺陷都是很明显的:诊断信息不够充分,从而难以准确定位错误的根源。
在 C++11 标准中,引人了 static_assert 断言来解决这个问题。 static_assert 使用起来非常简单,它接收两个参数,一个是断言表达式,这个表达式通常需要返回一个 bool 值;一个则是警告信息,它通常也就是一段字符串,在出错时会输出该字符信息,这样出错位置就是非常明确的。
noexcept
noexcept 形如其名,表示其修饰的函数不会抛出异常。在 C++11 中如果 noexcept 修饰的函数抛出了异常,编译器可以选择直接调用 std::terminate 函数来终止程序的运行,这比基于异常机制的 throw 在效率上会高一些。这是因为异常机制会带来一些额外开销,比如函数抛出异常,会导致函数栈被依次地展开( unwind), ...
重构(三)构筑测试体系
自测试代码的价值
一套测试就是一个强大的 bug 侦测器,能够大大缩减查找 bug 所需的时间。
事实上,撰写测试代码的最好时机是在开始动手编码之前。当我需要添加特性时,我会先编写相应的测试代码。听起来离经叛道,其实不然。编写测试代码其实就是在问自己:为了添加这个功能,我需要实现些什么?编写测试代码还能帮我把注意力集中于接口而非实现(这永远是一件好事)。预先写好的测试代码也为我的工作安上一个明确的结束标志:一旦测试代码正常运行,工作就可以结束了。
Kent Beck 将这种先写测试的习惯提炼成一门技艺,叫测试驱动开发(Test-Driven Development,TDD)[mf-tdd]。测试驱动开发的编程方式依赖于下面这个短循环:先编写一个(失败的)测试,编写代码使测试通过,然后进行重构以保证代码整洁。这个“测试、编码、重构”的循环应该在每个小时内都完成很多次。这种良好的节奏感可使编程工作以更加高效、有条不紊的方式开展。
第一组重构
提炼函数(Extract Function)
我会浏览一段代码,理解其作用,然后将其提炼到一个独立的函数中,并以这段代码的用途为这个函数命名。如果你 ...