按照我所了解的当代中国本科计算机教育,大多人工科学生学习的第一门编程语言应该是C,接下来如果还有需要的话,就是C++或者Java了。不幸的是,鄙人也是这样过来的,幸运的是,鄙人天资愚鲁,学了半年C++后知难而退,转战了实战主义的Shell Script和Python,这一年来闹中取静,为了探寻MapReduce算法模型的本原,啃了几本Lisp的书,回过头来,总算对C++的诸多繁杂特性有了一些全局性的认识,这种认识来自于跨语言的佐证和思考,而非来自于C++语言本身。事实上C++中的很多概念在C++中是无法学到通透的,比如:

C++11的lambda:你可以不知道Alonzo Church, 也可以不会Lambda calculusHigher-order function

STL:STL几乎就是C++经典库和设计的代名词,通过迭代器将算法和组件分离可惜你不知道的是,迭代器并不是一个高层次的抽象机制,迭代器的本质是一种迭代遍历操作,至于通过什么手段来迭代遍历,这些本不应该是使用者所应该关心的细节问题。所以对于下面的这段c++伪代码:

for (vector::iterator itr = v.begin(); itr != v.end(); ++itr)

{

do_something(*itr)

}

其高阶抽象代码应该是:

for_each(v, do_something)

稍微了解一点Lisp的读者都能想到如下的等价伪代码

(mapcar #'do_something v)

每次你写"v.begin(), v.end()"这样的代码时,你就不知不觉地降低了自己的抽象层次,使自己脱离问题域而转向去纠结于实现域,不要小看这种力量, 软件工程的一切欢乐和痛苦,只是聚沙成塔的两个极端而已。

事实上STL本身包含很多函数式编程的思想,比如说 functor这种组件,其实质是将在c++中作为second-class的函数通过类封装的手段提升至first-class,如此一来,函数的核心操作摇身一变成为functor的时候,就可以像函数式语言里面的Higher-order function一样,可以用类成员变量来模拟实现闭包,可以被当做普通参数传递返回(这样就不用费力去写令很多新手语法不过关的函数指针了),甚至可以通过std::bind1st/std::bind2nd这种奇技淫巧实现一个蹩脚的线性代数级别的函数映射与变换。

STL里面大量的算法都是基于迭代器的抽象而进行序列的批量化操作,同种算法多种容器的核心技术是 基于C++模板实现的静多态 ,"It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures",从这个角度上来讲,STL算法和Lisp中针对sequence类型数据的各种函数(mapcar/remove/remove_if/member等)有异曲同工之妙。

最后来八一八STL之父Alexander Stepanov,其实人家是莫斯科大学数学系毕业的高材生,所以STL背后有着很深的数学思想,"Elements of Programming"或许是解开这个谜题的钥匙。另外,Alexander是反对OOP的。

泛型与模板:这大概是Modern C++中最重口味的话题了,也是很多C++初学者的噩梦。我认为C++模板足够强大,但同时也足够扭曲且非人道,布满了大大小小的地雷和陷阱。探究起来,C++模板之所以有那么多坑,其历史原因在于C++模板是一种被发现而非被发明的技术C++模板的本质在于用编程的手段显式地控制编译器的代码生成。没错,聪明的你已经想到,Lisp的macro做的也是同样的事情。但是不同于Lisp的macro,由于C++模板的先天不足和C++静态类型系统的限制,C++在语言层面上对模板编程的支持非常有限。荣耀先生有一篇非常精炼的PPT《C++模板元编程技术与应用》, 基本上概括了C++模板编程的核心机制和语言实现,我摘录了一些如下:

模板元编程使用静态C++语言成分,编程风格类似于函数式编程,其中不可以使用变量、赋值语句和迭代结构等。

在模板元编程中,主要操作整型(包括布尔类型、字符类型、整数类型)常量和类型。被操纵的实体也称为元数据(Metadata)。所有元数据均可作为模板参数。

由于在模板元编程中不可以使用变量,我们只能使用typedef名字和整型常量。它们分别采用一个类型和整数值进行初始化,之后不能再赋予新的类型或数值。如果需要新的类型或数值,必须引入新的typedef名字或常量。

编译期赋值通过整型常量初始化和typedef语句实现。例如:

enum { Result = Fib::Result + Fib::Result};

static const int Result = Fib::Result + Fib::Result;

成员类型则通过typedef引入,例如:

typedef T1 Result;

条件结构采用模板特化或条件操作符实现。如果需要从两个或更多种类型中选其一,可以使用模板特化,如前述的IfThenElse

静态C++代码使用递归而不是循环语句。递归的终结采用模板特化实现。如果没有充当终结条件的特化版,编译器将一直实例化下去,一直到达编译器的极限

而正是由于底层支撑性语言机制的匮乏,使得C++模板编程非常的冗长、丑陋,甚至有些扭曲乃至非人道有时候你想要舞蹈的时候,要低头看看,你的脚上是否带着不必要的镣铐。 C++的静态类型系统对于泛型编程而言,就是这样的镣铐。

引用、指针、const、static等:除了以上比较“重口味”的C++语言特性,C++里还有各种各样的语言小尾巴,而且这个尾巴一般都拉的特别长。当然,尾巴长的好处之一就是可以养活很多语言专家,什么effective啊、exceptional啊、faq啊啥的,在所有的编程语言中,C++这点绝对是独树一帜。其实每个语言特性的背后都有值得深究的知识, 没有任何事情是想当然的。 const够简单了吧?可是你知道const pointer和pointer to const的区别吗?你知道什么时候用const引用传参什么时候返回const引用什么时候返回值吗?你知道const成员函数吗?你知道为什么会有初始化成员列表的存在吗?再来说说引用这个概念,其本质上就是一种受限指针加上编译器层面上的语法糖修饰,按理说不太难,但是什么时候传引用返回引用确是值得深究的好问题,搞清楚了这点,你就会搞明白C++中的copy constructor/copy assignment operator,Java中的Object.clone(),Python中的"is"、和Lisp中的eq/eql/equal。传引用/指针还是传值涉及到深刻的程序语言原理,并不是你想象的那么简单而已。

以上谈了这么多,读者可能会问,既然C++如此繁杂,还要不要学习C++?学,当然要学,否则你怎么批判呢?怎么学?批判地学。要去学习语言机制的根源和本质而不要迷失在语言特性的森林里

最后,还是回到面试题上,还是放上鄙人的C++代码,也好和Lisp/Python版的程序做一个小对比:

#include

#include

#include

#include

#include

#include

#include

#include

#include

using namespace std;

struct vertex

{

int index; /// the vertex index, also the vertex name

vertex* prev; /// the prev vertex node computed by bfs and bfs_shortest

int dist; /// the distance to the start computed by bfs

/// and bfs_shortest

vector adj; /// the adjacency list for this vertex

vertex(int idx)

: index(idx) {

reset();

}

void reset() {

prev = NULL;

dist = numeric_limits::max();

}

};

class graph

{

public:

graph() { }

~graph();

void add_edge(int start, int end);

void bfs(int start);

void bfs_shortest(int start);

list get_path(int end) const;

void print_graph() const;

protected:

vertex* get_vertex(int idx);

void reset_all();

list get_path(const vertex &end) const;

private:

/// disable copy

graph(const graph &rhs);

graph& operator=(const graph &rhs);

typedef map > vmap;

vmap vm;

};

graph::~graph() {

for (vmap::iterator itr = vm.begin(); itr != vm.end(); ++itr)

{

delete (*itr).second;

}

}

/**

* return a new vertex if not exists, else return the old vertex, using std::map

* for vertex management

*

* @param idx vertex index

*

* @return a (new) vertex of index idx

*/

vertex* graph::get_vertex(int idx) {

/// cout << "idx: " << idx << "\tvm.size(): " << vm.size() << endl;

vmap::iterator itr = vm.find(idx);

if (itr == vm.end())

{

vm[idx] = new vertex(idx);

return vm[idx];

}

return itr->second;

}

/**

* clear all vertex state flags

*

*/

void graph::reset_all() {

for (vmap::iterator itr = vm.begin(); itr != vm.end(); ++itr)

{

(*itr).second->reset();

}

}

/**

* add an edge(start --> end) to the graph

*

* @param start

* @param end

*/

void graph::add_edge(int start, int end) {

vertex *s = get_vertex(start);

vertex *e = get_vertex(end);

s->adj.push_back(e);

}

/**

* print the graph vertex by vertex(with adj list)

*

*/

void graph::print_graph() const {

for (vmap::const_iterator itr = vm.begin(); itr != vm.end(); ++itr)

{

cout << itr->first << ": ";

for (vector::const_iterator vitr = itr->second->adj.begin();

vitr != itr->second->adj.end();

++vitr)

{

cout << (*vitr)->index << " ";

}

cout << endl;

}

}

/**

* traversal the graph breadth-first

*

* @param start the starting point of the bfs traversal

*/

void graph::bfs(int start) {

if (vm.find(start) == vm.end())

{

cerr << "graph::bfs(): invalid point index " << start << endl;

return;

}

vertex *s = vm[start];

queue q;

q.push(s);

s->dist = -1;

while (!q.empty()) {

vertex *v = q.front();

cout << v->index << " ";

q.pop();

for (int i = 0; i < v->adj.size(); ++i)

{

if (v->adj[i]->dist != -1)

{

q.push(v->adj[i]);

v->adj[i]->dist = -1;

}

}

}

}

/**

* the unweighted shortest path algorithm, using a std::queue instead of

* priority_queue(which is used in dijkstra's algorithm)

*

* @param start

*/

void graph::bfs_shortest(int start) {

if (vm.find(start) == vm.end())

{

cerr << "graph::bfs_shortest(): invalid point index " << start << endl;

return;

}

vertex *s = vm[start];

queue q;

q.push(s);

s->dist = 0;

while (!q.empty()) {

vertex *v = q.front();

q.pop();

for (int i = 0; i < v->adj.size(); ++i)

{

vertex *w = v->adj[i];

if (w->dist == numeric_limits::max())

{

w->dist = v->dist + 1;

w->prev = v;

q.push(w);

}

}

}

}

/**

* get the path from start to end

*

* @param end

*

* @return a list of vertex which denotes the shortest path

*/

list graph::get_path(int end) const {

vmap::const_iterator itr = vm.find(end);

if (itr == vm.end())

{

cerr << "graph::get_path(): invalid point index " << end << endl;

return list();

}

const vertex &w = *(*itr).second;

if (w.dist == numeric_limits::max())

{

cout << "vertex " << w.index << " is not reachable";

return list();

}

else {

return get_path(w);

}

}

/**

* the internal helper function for the public get_path function

*

* @param end

*

* @return a list of vertex index

*/

list graph::get_path(const vertex &end) const {

list l;

const vertex *v = &end;

while (v != NULL) {

l.push_front(v->index);

v = v->prev;

}

return l;

}

class chessboard {

private:

struct point {

int x;

int y;

point(int px, int pb)

: x(px), y(pb) { }

};

public:

chessboard(int s);

void solve_knight(int x, int y);

protected:

bool is_valid(const point &p);

point next_point(const point &p, int i);

private:

graph board;

int size;

};

/**

* constructor, build a underlying graph from a chessboard of size s

*

* @param s

*/

chessboard::chessboard(int s)

: size(s) {

for (int i = 0; i < size; ++i)

{

for (int j = 0; j < size; ++j)

{

int start = i * size + j;

point p(i, j);

for (int k = 0; k < 8; ++k)

{

/// the next possible knight position

point np = next_point(p, k);

if (is_valid(np))

{

int end = np.x * size + np.y;

/// add edges in both directions

board.add_edge(start, end);

board.add_edge(end, start);

}

}

}

}

}

/**

* find and print a path from (x, y) to (size, size)

*

* @param x

* @param y

*/

void chessboard::solve_knight(int x, int y) {

int start = (x-1) * size + (y-1);

int end = size * size - 1;

board.bfs_shortest(start);

list l = board.get_path(end);

int count = 0;

for (list::const_iterator itr = l.begin(); itr != l.end(); ++itr)

{

cout << "(" << *itr/size + 1 << ", " << *itr%size + 1<< ")";

if (count++ != l.size() - 1)

{

cout << " -> ";

}

}

cout << endl;

}

/**

* whether or not the point is valid in the chessboard

*

* @param p

*

* @return true for valid

*/

bool chessboard::is_valid(const point &p) {

if (p.x < 0 || p.x >= size - 1 || p.y < 0 || p.y >= size - 1)

{

return false;

}

return true;

}

/**

* the next possible position, every has 8 next possible position, though not

* all 8 position is valid

*

* @param p the original knight position

* @param i

*

* @return

*/

chessboard::point chessboard::next_point(const point &p, int i) {

int knight[8][2] = {

{2, 1}, {2, -1},

{-2, 1}, {-2, -1},

{1, 2}, {1, -2},

{-1, 2}, {-1, -2}

};

return point(p.x + knight[i][0], p.y + knight[i][1]);

}

int main(int argc, char *argv[])

{

if (argc != 4)

{

cerr << "Wrong arguments! Usage: knight.bin N x y" << endl;

return -1;

}

int N = atoi(argv[1]);

int x = atoi(argv[2]);

int y = atoi(argv[3]);

chessboard chess(N);

chess.solve_knight(x, y);

return 0;

}

栏杆的lisp_Lisp - 行者无疆 始于足下 - 行走,思考,在路上相关推荐

  1. [盘点]从《行者无疆》开始了解欧洲

    "欧洲文明虽然至今还深沉于中部.灿烂于西部,却以既不深沉也不灿烂的南部和北部为命脉."                                --<行者无疆>( ...

  2. [转]日月悠长,山河无恙,行者无疆

    原文载于"格隆汇"公众号 港股明日开市. 这意味着,短暂的寂静.休憩与疗伤过程结束.凭栏处,潇潇雨未歇,边关酒觞冷,渔阳战鼓催,是时候披甲上马,再战江湖了. 此去山高路远,格隆在此 ...

  3. 大疆 行者无疆(二)

    目录 1.技术铸品牌之基 2.应用场景壮品牌之骨 3.传播强品牌之势 应用场景壮品牌之骨 大疆从推出精灵第一代开始,便被认为开创了非专业无人机市场,实现了年均100%的增速.据前瞻产业研究院数据显示, ...

  4. 大疆 行者无疆(一)

    目录 1.技术铸品牌之基 2.应用场景壮品牌之骨 3.传播强品牌之势 近日,正式发布Mavic 3行业无人机的大疆,搬迁新建办公楼天空之城的大疆,被美国防部列入黑名单的大疆,再次霸占热搜和头条.惊艳唯 ...

  5. 行者无疆——自行车,危险的运动

    码表终于到货了,今晚的数据是行程35.67公里,最高车速58.7公里/小时.也许是总想着白天的短信,总是分神,状态很差,虽然跟昨天一样的路线,可明显慢了很多,还出了几次状况,其中一次刚好在想那条&qu ...

  6. 行者无疆——自行车也“拉高速”

    汽车有"磨合期",有"拉高速",那么自行车呢? 经过一段时间的骑行,我的公路自行车也算是快过"磨合期"了.首先,综合自己的骑行姿势和发力习惯 ...

  7. 开源无疆!CSDN 董事长蒋涛、GitHub 副总裁 Thomas Dohmke 即将重磅对话

    作者 | 屠敏 责编 | 唐小引.胡永波 众所周知,作为全球知名的社交编程及代码托管网站 GitHub,自 2008 年推出后,一度受到广大开发者的欢迎与喜爱.根据 2018 年 GitHub 年度报 ...

  8. 电商场景化营销主要从哪几方面展开行无疆带你了解

    场景营销是指根据用户的使用场景触发相应的功能和活动,以满足用户需求;而电子商务场景营销则是在电子商务促销活动的组织中运用场景营销思维,以提升活动效果. 实时场景营销:比如扫描代码使用自行车,搜索某个关 ...

  9. 数据无界·存储无疆:大数据时代下,闪存已经势不可挡

    近日,IBM在深圳召开"数据无界 • 存储无疆,IBM为大数据再造存储盛典"主题大会暨中国闪存联盟第三季启动仪式. 数据无界 • 存储无疆,IBM为大数据再造存储盛典 在" ...

最新文章

  1. 日常刷python总结
  2. deepLink iOS 应用到自己APP 记录
  3. 【飞行术】Web2.0如何改变电信业
  4. 搭建kafaka_kafka单机环境搭建及其基本使用
  5. python语言学了有用吗-转行学习Python开发有什么优势
  6. java-基础-变量
  7. 在gitee上创建自己的仓库步骤
  8. 2016第11届四川省高校计算机(软件)院长论坛纪要(旁听)
  9. push api v3_充分利用Push API的指南
  10. hihoCoder 1014trie树(字典树)
  11. 【渝粤教育】国家开放大学2018年春季 3781-21T燃气燃烧技术与设备 参考试题
  12. 自定义基于HTML5的video播放器—Customize your video player
  13. IT 史上那些不为人知的第一次
  14. 系统辨识工具箱使用指南
  15. 船舶远程监测系统的物联网解决方案
  16. 【喷嚏图卦】 委内瑞拉崩溃的背后:渐行渐近的石油危机
  17. html+监听+页面滚动到底部,JS监听页面滚动到底部事件
  18. pandas计算方差,平均值,分位数,中位数
  19. redis恢复阿里云rdb文件
  20. 20145212罗天晨 逆向及Bof基础实践

热门文章

  1. python静态变量_python如何设置静态变量
  2. 《C#妹妹和Objective-C阿姨对话录》(05)自动释放池--拆迁队的外援
  3. linux单用户模式
  4. mysql中视图和表的关系
  5. php继承 重写方法吗,php中如何重写一个方法呢?
  6. 关于Taro下载附件并保存
  7. 有什么oa系统推荐?
  8. JS合并两个数组的方法
  9. forEach、map、for..of、for..in、for循环实现异步变同步的问题
  10. VLAN经典诠释(转)