GAE 速成简易博客 - 更多数据库操作
上节回顾 - 表单处理与基本的数据库操作现在首页能显示文章列表了, 但是 - 文章的顺序貌似是乱的, 而一般来说, 博客系统会按照发布的时间先后顺序来放置
- 构造一个页面, 看指定的某一篇文章的内容
那么, 现在就开始修改数据库吧. 为文章加上 ID 和日期添加属性修改 model.py class Post(db.Model): pid = db.IntegerProperty() title = db.StringProperty(multiline=False) content = db.TextProperty() date = db.DateTimeProperty(auto_now_add=True)
def put_post(title, content):
其中 pid 表示 post id, 是一篇文章的唯一标识; date 是文章的发布时间, 它被设置为对象被存入数据库时自动设置为当前时间 ( auto_now_add=True ). 在 GAE 存储中, 并没有类似 auto_increment 的设置, 因此 pid 的管理需手动进行. 在数据库中, GAE 也有给每个对象设置一个全局唯一的 id , 可以通过如 post.key().id() 来获取, 但是这样获取的 id 值在发布服务器上没有规律可言, 不具备有序性, 不建议使用. 按 ID 排序查询和自增 ID刚刚为 Post 添加的两个属性中, date 是会自动添加到数据库中的, 但 pid 并不会, 得手动给加上. 想要实现自增 ID 的功能, 一个简单的思路是, 从数据库中取出 pid 最大的那篇, 在它的基础上 +1 赋值给新文章即可. 那么继续修改 model.py def next_post_id(): posts = db.GqlQuery('SELECT * FROM Post ORDER BY pid DESC') return 0 if posts.count() == 0 else posts[0].pid + 1
def put_post(title, content): post = Post() post.pid = next_post_id() post.title = title post.content = content post.put()
这里 GQL 中的 ORDER BY pid DESC 表示按照 pid 排序, 而且是降序排列. 另外, 还得修改 add_post.py 里的 AddPostHandler 不能让它乱来了, 而应该改为调用 put_post class AddPostHandler(webapp.RequestHandler): def post(self): # new_post = model.Post() # new_post.title = self.request.get('title') # new_post.content = self.request.get('content') # new_post.put() model.put_post(self.request.get('title'), self.request.get('content')) self.redirect('/add_post')
Posted at Jan 20 2012 - 11:43:58
Permanent Link:
/p/478
Load full text
|
Post tags:
Web Server
Python
Tutorial
Google AppEngine
|
GAE 速成简易博客 - 表单处理与数据存取
上节回顾 - 站点基本配置与模板没有数据滋养的首页还是个半残废, 下面开始折腾数据库. 添加文章入口 虽然添加文章这种事情可以直接通过后台暴力搞数据库来做, 但既然要写的是 Web 应用, 那么写个页面提供添加文章的入口也是理所当然的事情. 页面嘛, 肯定得有个 HTML 模板撑着, 来建个文件, templates/add_post.html <html> <head><title>Add Post</title></head> <body> <form> <table> <tr><td>Title</td><td><input type='text' name='title'></td></tr> <tr><td style='vertical-align: top;'>Content</td> <td><textarea name='content'></textarea></td></tr> <tr><td><input type='submit'/></td></tr> </table> </form>
接下来得增加一个 RequestHandler , 新建文件 add_post.py from google.appengine.ext import webapp from google.appengine.ext.webapp import template import os
class AddPostEntry(webapp.RequestHandler): def get(self): path = os.path.join(os.path.dirname(__file__), 'templates/add_post.html') self.response.out.write(template.render(path, dict()))
这个文件看起来与 index.py 差不多, 只不过因为 add_post.html 不需要参数, 因此传入 render 的字典是空字典. 最后在 main.py 中新增一项, 将一个 URL 映射到 AddPostEntry import wsgiref.handlers from google.appengine.ext import webapp
import index import add_post
if __name__ == '__main__': application = webapp.WSGIApplication([ ('/', index.Index), ('/add_post', add_post.AddPostEntry), ], debug=True) wsgiref.handlers.CGIHandler().run(application)
现在访问 http://localhost:8080/add_post/, 就可以看到这个入口页面了. 不过因为表单还没有设置处理者, 因此点破提交按钮就没有反应的. 处理表单 还是得添加一个处理请求的类, 修改 add_post.py, 加入这个类 import model
class AddPostHandler(webapp.RequestHandler): def post(self): new_post = model.Post() new_post.title = self.request.get('title') new_post.content = self.request.get('content') new_post.put() self.redirect('/add_post')
Posted at Jan 19 2012 - 03:31:17
Permanent Link:
/p/477
Load full text
|
Post tags:
Web Server
Tutorial
Google AppEngine
Python
|
GAE 速成简易博客 - 开张
前期准备目前 GAE 官方推荐的 Python 版本是 2.7 (终于脱离了 2.5 的泥潭啊), 实际上 2.6.x 版本也是没问题的 (写个 CMS 这种简易的设备, 应该还用不到太多高端的语言特性). GAE 当然也是必不可少的. 下载和安装步骤在 Google Code 上都有 详细文档. 如果使用 ArchLinux, 还可以通过 AUR 安装. 这里就不废话了. 找个地方, 建立一个目录, 比如叫做 cms-build, 切到这个目录作为工作目录. 下面开始. 数据结构既然是写博客, 那么最关键的当然要是文章 (post) 了. 先来建立用于定义数据的源文件 model.py from google.appengine.ext import db
class Post(db.Model): title = db.StringProperty(multiline=False) content = db.TextProperty()
每篇文章的基本属性有标题 title , 内容 content 和创建时间 date . 其中标题不允许多行; 而内容使用 db.TextProperty 类型而不是 db.StringProperty (根据 GAE 文档, StringProperty 只能存至多 500 字符). 首页模板 首先, 将首页给弄出来, 建立目录 templates, 并在这个目录下弄一个 index.html 文件, 内容是 <html> <head><title>My Blog</title></head> <body> {% for post in posts %} <h1>{{ post.title }}</h1> <p>{{ post.content }}</p> {% endfor %}
在 GAE 中, HTML 模板文件使用的是 django 的模板语法. 上面文件中, {% for ... %} 到 {% endfor %} 是对传入模板的参数 posts 的迭代. {{ post.title }} 则是对 post 对象的 title 的引用, 而 {{ post.content }} 则是引用 content . 简而言之 {% xxx %} 是模板中的控制语句, 而 {{ xxx }} 则是引用值. 在 django 官方网站可以看到 详细文档, 鄙博客之前也对 django 有过 简介. 这个首页是简单了点, 不过呢, 先看看样子. 视图源文件 光有 HTML 模板还不行, 至少, Python 源代码才是核心. 现在添加一个 Python 源文件 index.py
Posted at Jan 19 2012 - 03:30:27
Permanent Link:
/p/476
Load full text
|
Post tags:
Tutorial
Web Server
Template
Python
Google AppEngine
|
单链表 给我翻过来
下面是一个简单的单链表节点结构 struct node { int value; struct node* next; };
那么如何将这个链表反转过来呢? void reverse(struct node* head); 下面采用递归的方式实现, 废话少说, 直接上代码 struct node** _reverse(struct node* n) { if (NULL != n->next) *_reverse(n->next) = n; return &(n->next); }
void reverse(struct node* head) { *_reverse(head) = NULL; }
上面的实现假定传入的 head 不会是空节点. 如果要检测, 可以加入一个判断 void reverse(struct node* head) { if (NULL != head) *_reverse(head) = NULL; }
下面来验证一下吧 #include <stdio.h>
struct node { int value; struct node* next; };
void reverse(struct node* head);
void print_list(struct node const* head);
int main(void) { struct node a = { 0, NULL }, b = { 1, &a }, c = { 2, &b }; // c(2) -> b(1) -> a(0) -> NULL print_list(&c); reverse(&c); // changed to a(0) -> b(1) -> c(2) -> NULL print_list(&a); return 0; }
void print_list(struct node const* head) { printf("["); for (; NULL != head; head = head->next) { printf(" %d", head->value); } printf(" ]\n"); }
struct node** _reverse(struct node* n) { if (NULL != n->next) *_reverse(n->next) = n; return &(n->next); }
void reverse(struct node* head) { *_reverse(head) = NULL; }
Posted at Dec 21 2011 - 04:59:53
Permanent Link:
/p/475
Load full text
|
Post tags:
C
Algorithm
Data Structure
|
天净沙 – 神社 入夜
苍穹尽处云燃 茜霞相伴游园 伫立残阳送远 孤身留眷 待清宵月悄悬 遥观烈绽绯霞 若袭辉烨彤纱 簇抱光晕涣洒 晚风萧飒 叶枝摇似涤砂 长空浅印清瞳 帐帷轻掩天宫 暮霭沉沉郁滃 幽幽归梦 眼帘阖意朦胧 卖文艺了 :D 以上, 翻译自三澤秋老师的 Shinto Shrine, 在 这里可以看到歌词 (页面中有整个专辑的歌的歌词, 请搜索 Shinto Shrine), 直至 "とばりが落ちる" 一句.
Posted at Dec 21 2011 - 02:58:10
Permanent Link:
/p/464
Load full text
|
Post tags:
Tou-hou
Tian-jing-sha
|
位运算的优先级
在现代计算机程序设计语言中, 每一种成熟的语言都支持许多算符. 对于除了像 Lisp 那样表达式本身以中缀式形式书写的语言, 一般语言都会规定表达式的结合顺序, 也就是算符优先级. 由于语言算符数量的庞大, 算符优先级很难整个记下来, 但是通常有迹可循, 比如看到下面的代码 x = y + z 一般会自然地认为 + 将在 = 之前执行, 这是因为很多语言的编译器设计中, 赋值类运算在算术类运算之后进行, 可以认为这是一个常识, 跟乘除在加减之前进行一样自然. 同理下面的代码 x == y and y == z x == y + z 很自然地能想到, 比较运算 == 会在逻辑运算 and 之前, 而算术运算 + 应该在比较运算之前. 如果要给算符分个类. 以整数运算为例, 日常二元算符分类的大类应该按照运算结果的类型来分类, 也就是 结果类型 | 算符 | 整数 | + - * / % ^ & | >> << | boolean | == != < > >= <= or and |
从结果类型上也可以看出一些端倪, 比如 x == y + z 这个算式, 如果先计算 == , 此时 + 左边是 boolean, 而右边是个整数, 这样根本没法圆场了. 所以一般来说, 算术运算拥有较高的优先级完全是天经地义的. 现在的问题是, 位运算 ( ^ & | ) 到底算啥? 从之前的说法来看, 它们也应该是算术运算的一种. 不过优先级还真不好说. 最近在某个项目中我将一个后台功能转移到前台, 需要把一段 Python 代码转换成 Javascript 代码, 这时发生了一个悲剧: Python 对于位运算优先级的实现与我的个人观点非常接近, 认为时至今日, 位运算是日常的算术运算之一, 因此它们的优先级要高于比较运算, 也就是说 x + y == x ^ y 在 Python 中等价于 (x + y) == (x ^ y) 而与之相对的, Javascript 认为位运算的优先级要低, 甚至低过了比较运算, 因此上述代码在 V8 引擎眼中等价于 ((x + y) == x) ^ y 这种槽点爆表的设计真是坑死爹了. 好吧, 我去溯源一下, 谁家的语言设计这么疼. 结果原来 C 语言老大哥是这么个整法的, 什么 Javascript 还有个跟这名字差不多不过没有 script 的语言这么承袭的. 哎, 你们这么折腾到底图个啥啊.
Posted at Oct 23 2011 - 08:29:11
Permanent Link:
/p/454
Load full text
|
Post tags:
Javascript
Python
C
Operator Precedence
|
元気娘美紗緒参上
Posted at Oct 23 2011 - 08:21:21
Permanent Link:
/p/449
Load full text
|
Post tags:
Lucky Star
ASCII Art
|
日常的数据结构 - 堆的实现与第 K 最小堆
在 前一篇文章中纸上谈了堆的性质以及如何在插入元素和弹出最值时保持这些性质. 这篇文章将聊聊实现方式. 从实现的角度来说, 使用完全二叉树作为堆的前提的好处是, 完全二叉树非常容易实现, 甚至可以说是最容易实现的二叉树. 由于完全二叉树的节点编号是连续的, 那么它可以被拉平, 放进一个日常的数组中, 如 +---+ | 4 | +---+ / \ +---+ +---+ | 5 | | 9 | +---+ +---+ / \ +---+ +---+ | 8 | | 5 | +---+ +---+
这样一棵完全二叉树可以被转换成 .----. /--. \ +---+---+---+---+---+---+----- | - | 4 | 5 | 9 | 8 | 5 | ... +---+---+---+---+---+---+----- \______/ / \________/
其中的线连接节点与它们的子节点. 如果用节点的编号来标识这个数组, 则会是 .----. /--. \ +---+---+---+---+---+---+----- | 0 | 1 | 2 | 3 | 4 | 5 | ... +---+---+---+---+---+---+----- \______/ / \________/
这里有个很奇妙的性质, 索引为 i 的节点, 它的左子节点的索引是 2i, 而右子节点的索引是 2i+1, 其父节点索引则是 floor(i/2) (根节点除外). 如果用 0 号节点而不是 1 号节点存储根节点呢? 如 .----. /--. \ +---+---+---+---+---+---+----- | - | 0 | 1 | 2 | 3 | 4 | ... +---+---+---+---+---+---+----- \______/ / \________/
也能很容易计算, 索引为 i 的节点, 左子节点索引是 2i+1, 右子节点索引是 2i+2, 父节点索引是 floor((i-1)/2). 似乎也没什么太大区别. 不过, 之前那种计算方式的好处在于, 2i, 2i+1, i/2 这样的算式都能换成极快的位运算: 2i 等效于 i << 1 , 2i+1 等效于 (i << 1) | 1 , floor(i/2) 等效于 i >> 1 , 这还能提供一丁点效率优化 (和一部分代码混乱程度加成, 以及大量的极客自豪感上升). 既然堆的逻辑结构是数组, 那么可以采用 std::vector 作为存储数据结构. 此外, 将比较方式以模板形式抽出, 这样可以构造一个抽象的最值堆, 而不是死板的最大堆或者最小堆. 下面是堆的框架 template <typename _T, typename _Less> class heap { std::vector<_T> array; _Less const less; typedef typename std::vector<_T>::size_type size_type; public: heap() : array(1) /* insert a placeholder, array[0] */ , less(_Less()) {}
void push(_T const& value); _T pop(); };
Posted at Oct 07 2011 - 09:23:13
Permanent Link:
/p/441
Load full text
|
Post tags:
C++
Algorithm
Template
Heap
Order Statistic
Generic Programming
Data Structure
|
对空结构体求 sizeof
C++ 声称完全兼容了 C, 这一点在某些细节上不尽然. 比如对空结构体 --- 没有成员, 不含虚函数, 虽然 C 还生活在没有虚函数的三叠纪 --- 求 sizeof 的结果. 具体地说就是下面这个表达式 Code Snippet 0-0
struct empty {};
sizeof empty; // 值为 0
在 C 和 C++ 中会得到不同的值: C 中其值为 0 (在主流编译器中如此), 而 C++ 中其值为 1. 这个微妙的不同步源于 C 中的一个指针相减问题, 如下代码 Code Snippet 0-1
#include <stdio.h>
struct empty {};
int main() { struct empty x; struct empty y; printf("%ld", &x - &y); return 0; }
以 C 语言编译并运行, 程序会直接崩溃掉, 因为在 C 中计算表达式 &x - &y 的值等同于 ((char*)&x) - ((char*)&y) / sizeof struct empty . 这个整数除法非常糟糕, 毫无疑问 C 编译器应该了解到危险所在了: 在编译期, 它完全能够发现该除法算式的常数分母是整数 0, 但是它还是义无反顾地生成了代码, 甚至连警告也不给, 将程序推入运行时崩溃的深渊. 本来这种事情应该偷偷改掉拉倒, 可是 C 标准对这个事情讳而不谈, 丢出一张王牌 "对空结构体或联合求 sizeof 将会是*未定义*行为". 对此 C++ 只好吐了个槽, 说*任何对象至少要占用 1 字节空间*. 所以其实 C++ 标准也没有明确说出 "对空结构体或联合求 sizeof 将会是 1" 这样的话, 但是根据前面这个规定, 由编译器厂商演绎出来的结果就是这样的, sizeof 纷纷得到结果 1, 包括下面这样的情况 Code Snippet 0-2
struct empty_base_a {}; struct empty_base_b {}; struct empty_inherit : empty_base_a, empty_base_b {};
sizeof empty_inherit; // 值为 1
即对从空类上 (多重) 继承的空子类求 sizeof 也将得到 1. 这一招看起来很挫, 但还真的管用了, 用 C++ 编译器编译并运行上述程序, 零也不除了, 程序也不会崩了, 还能给出正确地结果. 虽然把两个什么空的东西用继承的方式捏在一起不会产生体积变大, 但是一个数组的什么空的东西则会导致体积累加, 如 struct empty {};
int main() { struct empty x[4]; printf("%ld", sizeof x); /* 4 */ return 0; }
这段 C++ 代码的运行结果将是 4, 也就是 x 占用了 4 个 1 字节. 这又扯到 C++ 另一个核心编程思维 --- 面向迭代器. 例如下面一坨代码 Code Snippet 0-3
struct empty {};
void echo(empty) { std::cout << "echo" << std::endl; }
int main() { struct empty x[4]; std::for_each(x, x + 4, echo); return 0; }
如果认为整个数组是一个对象, 打个包求 sizeof 才能得到 1, 而 x[0] 与 x[4] 等等有相同的地址, 那么 std::for_each 中的循环将一次也不被执行. 类似的, 让多个空类对象聚合在一个空类对象中时, 它们占用的空间大小是会累加的, 如 Code Snippet 0-4
struct empty {}; struct twin { empty a; empty b; };
sizeof twin; // 2
Posted at Oct 07 2011 - 09:15:26
Permanent Link:
/p/438
Load full text
|
Post tags:
sizeof
C
C++
|
日常的数据结构 - 动态最值统计与堆
如果设计一个顺序统计系统, 需要动态向集合内添加元素, 又可以随时从集合中取得并 丢弃最小值. 由于集合中有集合会被移除, 因此接下来再次取最小值时如果重新扫一次集合, 时间开销会相当大. 在一般情形中, 若为了均衡时间开销, 需要考虑维护一个更复杂的数据结构. 这个数据结构建立在 满二叉树 (full binary tree)和 完全二叉树 (complete binary tree)的概念上. "满" 这个字眼提示在树的每一层都摆满了节点, 而这恰好又是个充要条件, 即如果一棵二叉树每一层都堆满了节点, 那么它就是满二叉树. 满二叉树的定义干脆就按节点个数来: 一棵二叉树如果深度为 K, 而拥有 2 K-1 个节点, 那么它就是一棵满二叉树. 如下面是 2 层和 3 层满二叉树, 分别拥有 3 个和 7 个节点 +---+ | a | +---+ +---+ | a | .---' `---. +---+ +---+ +---+ / \ | b | | c | +---+ +---+ +---+ +---+ | b | | c | / \ / \ +---+ +---+ +---+ +---+ +---+ +---+ | d | | e | | f | | g | +---+ +---+ +---+ +---+
而如果一棵二叉树满足 - 除了最后一层, 其余层构成一棵满二叉树
- 最后一层从右起缺少 0 个或多个连续的节点
那么它就是一棵完全二叉树. 更直观一些, 将一个满二叉树的节点按照广度优先 (即逐层向下) 遍历的方式顺序编号, 编号从 1 开始 (而不是从 0 开始), 如
Posted at Oct 03 2011 - 13:01:06
Permanent Link:
/p/434
Load full text
|
Post tags:
Data Structure
Heap
Algorithm
Order Statistic
|
正确重载 operator=
以一个整数数组类型为例, 下面的写法 class int_array { int* array; int size; public: int_array& operator=(int_array const& rhs) { if (this == &rhs) { return *this; }
delete[] array; array = new int[]; std::copy(rhs.array, rhs.array + rhs.size, array); size = rhs.size; return *this; }
/* other members */ };
是完全错误的. 设想在下面这样的上下文中 int_array a /* init */; int_array b; try { b = a; } catch (std::bad_alloc) { } /* ''b'' is bad now! */
进入 operator= 之后, 先执行了 delete[] , 这一句没问题, 如果之后的 array = new int[]; 内存分配失败, 这时 b 的成员 array 已经崩坏掉了, 以后再继续使用 b 显然是一件很糟糕的事情. 为了保持对象状态一致性, 很自然地应该将对象状态的切换放入一个尽可能安全的环境中. 一个方法是使用先复制一份对象, 然后利用绝对安全的 std::swap 将副本与当前对象交换. 假如在复制过程中出错, 并不会破坏当前对象的状态一致性. class int_array { public: int_array& operator=(int_array const& rhs) { if (this == &rhs) { return *this; }
int_array copy(rhs); swap(copy); return *this; }
void swap(int_array& other) { std::swap(array, other.array); std::swap(size, other.size); }
/* other members */ };
一个与之类似的例子是维护程序配置文件, 当程序在退出或者某个其它时机需要将配置写回文件时, 直接清空原来的文件然后提笔并不是一个好主意, 而应该先写入一个临时文件, 再将临时文件 mv 到配置文件.
Posted at Sep 30 2011 - 11:25:19
Permanent Link:
/p/430
Load full text
|
Post tags:
Operator Overload
C++
RAII
|
日常的算法 - 顺序统计
顺序统计指的是在一坨 并没有排序的数据上找出跟顺序量有关的元素. 典型的顺序统计包括找最小值或最大值算法, 这两个算法可以说没有任何技巧而言, 暴力地遍历一次数据集合就行了, 如找最小值算法的实现 (以 C++ 面向迭代器的程序设计描述, 不考虑集合为空的情形. 以后的例子相同) template <typename _Iterator> _Iterator find_min(_Iterator begin, _Iterator end) { _Iterator min = begin; for (++begin; begin != end; ++begin) { if (*begin < *min) { min = begin; } } return min; }
将这两件事情揉合在一起, 即从一个集合中同时找到最小值和最大值, 相比于分头寻找, 能够节省不少时间, 原因不仅仅是两次循环合并成一次循环, 运用一些技巧能显著地减少比较的次数. 在每一次取出剩余元素时, 同时取出两个, 先将这两个比较大小, 然后将较小的元素与当前最小值比较, 而将较大的值与当前最大值比较取出 +-------+-----------+----- | begin | begin + 1 | ... +-------+-----------+----- | | +--( < )--+ / \ / \ +----------+ +-------------+ +-----+ | less one | | greater one |--( < )--| max | +----------+ +-------------+ +-----+ | | +-----+ +-------------------------( < )--| min | +-----+
这样每寻访 2 个元素, 需要 3 次元素比较, 加上判断循环是否结束需要 1 次比较, 而分开查找, 则每 1 个元素需要 2 次元素比较, 加上 1 次循环判断结束的比较. 之所以这么计较比较的次数, 因为目前计算机体系结构和分支语句非常不友好, 太多分支 (意味着大量的预测失败) 的程序会因为无法充分利用流水线而显著地降低实际执行效率. 下面是实现的例子
Posted at Sep 30 2011 - 09:20:03
Permanent Link:
/p/426
Load full text
|
Post tags:
Generic Programming
STL
C++
Order Statistic
Algorithm
Template
|
Angel Beats! & SSS
____ __ _____ __ __ / |__ _____ _____ _ ___ | | | _ `. ___ ___ _ | |_ _____ | | / _ || ' __ \/ __ Y | ,' `. | | | | \ | ,' `. ,' _ ` || _| / ___ `.| | / / | || r' | || | `. |; ,-. || | | |_/ / ; ,-. |/ ,' `. || | | / `-'| | / /__| || | | || | | || `---` || | | __ \ | `---` || | | || | | `----. | | / ____ || | | || | | || .-----'| | | | \ || .-----'| | ; || | `----. \| | / / | || | | || '---' |: `.__,-:| | | |__/ |: `.__,-:| `._' || |_ .-.___; ||_| / / |_||_| |_|\_____ | `.____.'|_| |_____//\`.____.' `.__,|_||__/ `._____.'(_) --' /------------------,-----' |-------------------' \,---------------------------------- ---'-------------------\______/---------------------'\ ,--------------------------------- \/
猛击 这里查看动画版.
Posted at Sep 15 2011 - 16:17:44
Permanent Link:
/p/414
Load full text
|
Post tags:
ASCII Art
AngelBeats!
|
Pipeline: 我所希望的列表处理方法
本文仅表现个人从事程序设计以来的感受和想法, 带有主观色彩, 可以认为结论式的句子均省略了 "我认为" 这样的字眼. 编程语言中最基本的事情就是数据变换, 比如基本的四则运算, 或是求正弦, 或是将字符串拼接起来, 前面这三种都是针对数据个体的, 而且在不同语言中实现得大同小异. 而对于数据集体的操作, 比如对一个列表进行变换, 各种编程语言会有各自独特的方法, 甚至同一种语言在语言特性升级前后都会有不同, 比如在 C++ 中, 一直以来采用 for 来迭代, 在 C++11 新标准引入了 lambda 之后, std::for_each 就咸鱼翻身变得流行起来; 又比如在 Python 中, 列表迭代采用 for element in list 的方式, 而在引入 yield 之后的版本中, 转为了 for element in generator . 虽然语法没有变, 但是整体思想改变了不少. 而在这些眼花缭乱迭代方式当中, 程序员究竟想要得到什么? 解决实际问题的过程中, 最常见的操作应当是 - 映射: 给出一个函数 f 和集合 {x}, 得到新的集合 {f(x)}
- 过滤: 给出一个函数 g 和集合 {x}, 得到新的集合 {x 当且仅当 g(x) 为真}
将这些需求提出来之后, 最让我感到有趣的莫过于 Shell 的管道. 比如这样一句 find -type f | awk '{ print "mv", $1, "/tmp/" }' 相当与进行了一次映射, 集合为 find -type f 给出的文件列表, 函数 f 就是 awk 程序. 而这个例子 find -type f | grep wav$ 则是一个过滤. 上面的例子中, 并没有任何迭代的过程, 可以说 Shell 的管道完全抽象了集合迭代, 让对集合的操作变为了对元素个体的操作. 在语言层面上将迭代过程本身封装起来, 这确实是一件很实在的事情. 因此, 我很乐意将这个特性引进到了 Stekin 程序设计语言中. 在当前的 dev 分支上, Stekin 实现了类似管道的列表操作方式. 具体的语法类似 列表 | return 表达式 列表 | if 布尔表达式
分别表示映射和过滤. 在 awk 中, 使用 $1 等表示该行的第一个词, 而 $2 表示第二个, 等等. 而在 Stekin 中并没有这个困扰, 因为迭代的列表中元素类型是确定的, 因此直接使用 $element 来表示元素, 如下面的代码 list: [0, 1, 2, 3, 4] sqr_list: list | return $element * $element
将每一个元素 $element 映射到其平方 $element * $element , 得到的 sqr_list 将是 [0, 1, 4, 9, 16] . 除了在管道中可以使用 $element 引用元素之外, 还可以使用 $index 引用当前元素的索引 (从 0 开始). 不过这种表示法仍然有待商榷, 我曾想过使用如 $$ , $@ 这样看起来很 Shell 很 Makefile 的符号, 但那样实在太不直观, 于是使用了 $element 这样直接写单词的方法. 如果你有什么想法或建议, 欢迎在回复中提出来. 而过滤操作则除了将管道符号 | 之后的 return 关键字换成 if 之外, 它要求接下来的表达式是布尔类型的. 当且仅当这个表达式的值为真时, 则该元素将被添加到结果列表中去, 如 list: [0, 1, 2, 3, 4] new_list: list | if $element % 3 != 0
Posted at Aug 31 2011 - 07:43:35
Permanent Link:
/p/408
Load full text
|
Post tags:
Stekin
List Processing
Pipeline
|
头文件禁区: C++ 中的 ABI
在 上一篇文章中写到了 C 的 ABI ( application binary interface) 有关的内容. 而 C++ 这货也采用 header / cpp 分离的方式来组织代码, 并在编译时将头文件直接在 cpp 中插入, 这种暴力方法已经不能简单说是为了兼容 C 的各种编译期行为, 完全就是彻头彻尾地重蹈覆辙, 甚至有过之而无不及. 首先, C++ 也会遇到 "如果直接用了动态库中修改过签名的函数怎么办" 这个全静态语言的千古难题. 而且比起 C 语言更要命的是, C++ 的类是不能进行前置声明来回避对象内存布局耦合: 前置声明就没有引用成员函数了, 那还怎么面向坑爹的对象呢? 不仅仅对象成员布局有此问题, 连同 C++ 藉由实现多态的虚函数亦面临着同样的窘境. 试想动态库中虚函数表都改得面目全非时, 使用该对象的代码却还用原来的方式访问, 那还不是各种鸡飞狗跳. 对于如此恶劣环境下仍然想保持 ABI 兼容性的 C++ 程序员来说, 倒也不是完全无路可走. 具体的方案就不在这篇文章中赘述了, 请转到 陈硕老师的 C++ 工程实践: 二进制兼容性和 C++ 工程实践: 避免使用虚函数作为库的接口. 两篇文章都写得非常详细. 撇开这些跟 C 语言有关的孽缘不谈, C++ 本身也提供了一些机制来帮助程序员养成编码恶习, 比如一些函数实现可以直接写进头文件中, 而无须担忧任何链接错误. 首当这个批评的就是成员函数, 包括静态或非静态, 虚或非虚. inline 函数或者 template 函数实现要写头文件这点可以理解可以忍, 但成员函数来凑什么热闹? 成员函数的不往头文件里面写, 可以避免一些 ABI 耦合, 比如 class Date { int month; int day; public: int getDay() const; int getMonth() const; };
这种情况下, 在使用 Date* date /* initialize */; date->getDay();
不必担心内存布局耦合, 因为作为非虚的成员函数, Date::getDay() const 这样的函数签名正如 getday(Date const*) 一样, 这就回到了上一篇文章最后提到的解决方法了. 但是如果用得不恰当, 比如头脑发热认为 inline 一下能提高效率什么的, 而活生生地写成 class Date { int month; int day; public: inline int getDay() const { return day; }
inline int getMonth() const { return month; } };
Posted at Aug 31 2011 - 06:52:02
Permanent Link:
/p/404
Load full text
|
Post tags:
C++
Template
ABI
STL
inline
|
struct 禁区: 内存布局耦合与 C 语言 ABI 基础
假如现在有个某复杂系统需要存放人员信息. 人员类型定义类似: struct person_t { name_t name; age_t age; };
这个系统的人员添加功能由两个部分通过 动态库集成到一起, 后端的部分只负责拿到人物信息并做诸如存入数据库, 数据统计等工作, 它向前端提供了两种风格的接口, 分别是 void add_person_0(name_t name, age_t age); void add_person_1(struct person_t const* person);
两种看上去没有太多区别. 对于前一种接口, 使用该接口的代码很可能写成这样子 add_person_0("hideyosi", 16);
而对于后一种接口, 代码很可能这样写 struct person_t person = { "hideyosi", 16 }; add_person_1(&person);
现在考察一次扩展性, 而且来最坏的情况: 接口提供方先行更新了动态库, 而使用方在一段时间内并不知情. 更新的内容包括, 在 name 和 age 之间插入一项 gender (当然前一种人员添加接口也有变动): struct person_t { name_t name; gender_t gender; age_t age; };
void add_person_0(name_t name, gender_t gender, age_t age); void add_person_1(struct person_t const* person);
这时会怎么样呢? 显然两种情况都好不到那里去. 对于 add_person_0 这个接口, 由于参数个数都改变了, 签名对不上号, 那么使用该接口的一方在通过动态库访问此函数时会少压入一个参数, 并且压入的第二个参数 age 对上的还是 gender 这个参数的位置. (在 C++ 中, 由于允许函数重载, 动态库中的函数名会因签名不同而被 name-mangling 成不同的函数, 因此会导致函数找不到错误. 当然这并不意味着 C 和 C++ 孰优孰劣, 因为这本来就是两个不同的语言, 只是在这一个问题上有不同的死法罢了.) 这样一调用函数程序立马崩掉不客气. 而对于第二种使用, 因为使用方在初始化 person_t 时, 仍然按照旧的逻辑, 即在偏移为 0 的地方放上 name , 在偏移为 sizeof(name_t) 的地方放上 age , 然后将这样初始化的 person 结构体传递给接口提供方; 而此时提供方对这一片内存区的解释有很大不同, 至少它认为的 sizeof(person_t) 都比使用方给出的要多出一个 sizeof(gender_t) . 使用这样的 person 就像拿着三年前的武汉市地图到今天的洪山广场游玩结果掉进工地不明不白地挂掉了一样. 那么有没有什么无痛的方法能够在这种不同模块分离更新的环境下让程序仍然, 至少在新来的人可能都缺少性别的情况下, 能够继续像模像样地跑呢? 要找到这个方案, 自然应该先看看问题出在什么地方. 这里问题的核心围绕着内存布局, 即双方在 person_t 这一结构体到底包含了什么数据需要达成一致, 换句话说就是因为 person_t , 双方紧耦合了. 解决方案很自然的就是, 把所有跟 person_t 内存布局相关的代码全部移到一个模块 (当然是接口提供方那边) 里去, 并提供一套操作接口 (为了彻底不让使用方知道 person_t 的内存布局, 干脆只给个前置声明好了)
Posted at Aug 08 2011 - 09:07:25
Permanent Link:
/p/390
Load full text
|
Post tags:
C
ABI
|
C++ inline 坑爹真相
准备两个文件 a.cpp b.cpp, 内容如下 /* a.cpp */
#include <iostream>
void b();
inline void echo() { std::cout << 0 << std::endl; }
int main() { echo(); b(); return 0; }
/* b.cpp */
#include <iostream>
inline void echo() { std::cout << 1 << std::endl; }
void b() { echo(); }
然后执行 $ c++ a.cpp b.cpp -o a.out && ./a.out
也许会出现两个 0, 或者两个 1, 但是不会出现 0 1 这样的组合, 更不会链接冲突了. 如果有任何跳过生成目标文件直接到生成可执行文件的顾虑, 尝试下面的方法结果是一样的 $ c++ -c a.cpp $ c++ -c b.cpp $ c++ -o a.out a.o b.o $ ./a.out
甚至最直接地, 如果查看两个中间文件的符号 $ nm a.o $ nm b.o
会在两次结果中都看到一条名为 _Z4echov 的记录, 这就是 echo 函数被 name-mangling 之后的标识. C++ 文档和标准中过度赞扬 inline 这个关键字, 然而一笔带过的是, 编译器可以选择忽略 inline 标记, 至于忽略了会有什么后果, 如何链接等细节都一字不提. 如果这是一个封装行非常良好的编译器特性, 那也就没什么. 但实际上这里敷衍了一些重要事实, 既然 0) 有的函数即使程序员写了 inline , 编译器可以不进行 inline , 则 1) 很有可能出现这样的函数, 并在编译后被生成到目标文件中去, 而且 2) 由于 inline 函数一般实现在头文件中, 那么 3) 编译器在编译 每一个包含了这个头文件的 cpp 文件时, 都会将该函数符号生成到对应目标文件中去, 而若要 4) 链接这多个目标文件不发生链接冲突, 只好由 5) 编译器或者链接器耍一些手段来保证, 可是 6) 耍了手段, 却有可能造成上面的问题发生. 与 inline 失败的函数有关的真相是, 编译器会为 inline 函数做上标记, 链接器根据这个标记, 从目标文件中找到这个函数的多处实现, 不负任何责任地选取其中一个作为正身, 并抛弃其它的实现, 无论这些实现是否相同 (链接器也无从判断是否相同). 开头的例子非常猎奇, 但是也不排除这样日常的情况: 0) lib 作者因为各种原因, 让 inline 失败的函数编入 lib; 1) 这个函数没有在文档中被提及, 头文件也隐藏得很好; 2) 引用 lib 的程序员定义了一个同 namespace 且同名的 inline 函数 (当然最有可能在全局名字空间这么干了). 于是就悲催了. 现在再回头看一下 inline 的描述 - 要想 inline, 得先
inline - 若是
inline , 未必 inline
这是不是有种在看葵花宝典开头的感觉?
Posted at Jul 24 2011 - 04:24:52
Permanent Link:
/p/379
Load full text
|
Post tags:
inline
C++
|
ArchLinux 64 位重装记
本本上一直是 Win7 Home 和 32 位 ArchLinux (下称 arch) 双系统. 不过, 吐槽一下, Win7 跟 WinXP 之间巨大的操作差异让我真心戒掉了 Windows, 就像 Gnome3 的巨大变化让我转投了 Xfce 一样. 另, 要与时代接轨 (虽然刚才好像我对新东西都很抵触) 想尝试一下 64 位系统, 所以决定: wipe out & reintall. 这里记录一下自己重装的过程, 让以后的折腾有所参考. 前期准备 其实最重要的就是前期准备了. 准备好了后面装系统改配置都是轻松加愉快啊. 备份配置 备份必要的配置文件当然是必须的. 首先告诉大家一个好消息, 最让人纠结的 xorg.conf (一般放在 /etc/X11/xorg.conf) 不需要. 貌似现在 arch 启动 X 并不需要这个文件了 :D 如 pacman.conf 这样的文件可以备份一下, 我实在不太记得住 archlinuxfr 的网址. 此外, arch 现在还启动了 multilib 计划, 可以把下面这个加入 pacman.conf [multilib] Include = /etc/pacman.d/mirrorlist
其它的还有 /etc/locale.gen 以及 /etc/host* 这些文件. 如果有修改 /etc/bash_aliases 或 /etc/vimrc 等等, 不过这些一般也没谁去改吧, 盯好自己的 ~/.bashrc ~/.bash_profile ~/.vimrc 就好了. 如果你跟我一样使用 compiz 来卖萌, 记得可以使用 ccsm 将 compiz 的设置导出到文件哦. 个人目录下其它值得备份的文件还有 .bash_history .fonts .mplayer 等等, 看各自的需求了. 当然别忘了其它重要的文件比如小电影什么的. 已安装的软件包 接下来一件比较重要的事情就是看看系统现在装着哪些软件, 装好新系统后立刻把它们都装上, 在 arch 下通过 $ pacman -Q
来查询安装的所有软件, 包括 aur. 导出这个列表 $ pacman -Q | awk 'BEGIN { print "echo -n \"pacman\" -S"} { print "echo -n \" " $1 "\"" }' | sh > install-packages
最后, 准备安装镜像. 再看一眼旧的系统, 马上就要说再见了哦. 安装 用 ArchLinux 安装镜像启动后, 会进入这样一个 shell 交互环境 root@archiso ~ #
之后就是全手动安装了. 这么坑的设定, 萌生退意也是理所当然的了... 格式化磁盘 如果没有必要重新格式化磁盘, 请跳过这个步骤. 首先要搞清楚硬盘在挂在哪个地方, 一般是 /dev/sda, 使用 U 盘安装的用户将还会看到 /dev/sdb, 或者 sda 是 U 盘而 sdb 才是磁盘. 对应地请小心行事, 不要误删数据了. 如果不想退的话, 先从格式化分区开始吧. 下面是用 parted 进行分区 (以在 /dev/sda 上安装为例, 高亮的部分是输入, 其它是 shell 或 parted 回显)
Posted at Jul 24 2011 - 03:29:55
Permanent Link:
/p/376
Load full text
|
Post tags:
ArchLinux
|
Google & Baidu 图片识别搜索测评之二次元
Posted at Jul 01 2011 - 13:14:45
Permanent Link:
/p/368
Load full text
|
Post tags:
|
C++ 对象构造时的异常与 RAII 的救赎
在 上一篇文章中简单介绍了一下 RAII 的原理, 举了个读文件的例子. 这个例子稍微单薄了一些, 它只封装了一个需要 RAII 机制管理的资源 ( FILE* ). 软件工程中流行的观念是, 不具备扩展性, 经不起追加功能的东西注定会悲剧. 现在假如需要给这货增加个缓冲区, 也许这样是可以的 struct File { File(char const* filename, int buffer_size) : file(fopen(filename, "r")) , buffer(new char[buffer_size]) { if (NULL == file) { throw std::runtime_error(std::string("fail to open ") + filename); } }
~File() { fclose(file); delete[] buffer; } private: FILE* file; char* buffer;
/* other members */ };
在 buffer 分配失败时, 一般会抛出 std::bad_alloc . 这个类型的破绽相当多, 稍不注意就有可能漏资源. 首先是刚刚提到的 buffer 分配失败抛异常, 那么假如这个时候 file 已经打开成功了, 它会被关闭么? 其次, 假设 buffer 成功分配, 但这时 file 打开失败, 那么 buffer 是否会被释放呢? 很不幸的, 两者的答案都是 否. 还是那句话, 因为 File 的构造函数没有走完, 这时抛出异常, 那么析构函数不会被执行. 因此, 不要尝试构造控制多于一个资源的类型. 而遇到这种需求, 应该拆分资源, 然后将这些单元类型进行聚合, 如 struct File { explicit File(char const* filename) : file(fopen(filename, "r")) { if (NULL == file) { throw std::runtime_error(std::string("fail to open ") + filename); } }
~File() { fclose(file); } private: FILE* file;
/* other members */ };
struct Buffer { explicit Buffer(int buffer_size) : buffer(new char[buffer_size]) {}
~Buffer() { delete[] buffer; } private: char* buffer;
/* other members */ };
struct BufferedFile { BufferedFile(char const* filename, int buffer_size) : file(filename) , buffer(buffer_size) {}
File file; Buffer buffer;
/* other members */ };
Posted at Jun 21 2011 - 13:27:55
Permanent Link:
/p/363
Load full text
|
Post tags:
RAII
Exception Handling
C++
|
从 Python 的 with 到 RAII
在 上一篇文章中提到了 Python 的 with 结构, 其背后的思想正是 RAII. 在 C++ 中, RAII 的样子看起来跟 Python 中的会非常不一样, 还是以打开读取文件为例子 #include <cstdio> #include <stdexcept> #include <iostream>
struct File { explicit File(char const* filename) : f(fopen(filename, "r")) { if (NULL == f) { throw std::runtime_error(std::string("fail to open ") + filename); } }
std::string readLine() { char buffer[256] = { 0 }; // just for demo if (NULL == fgets(buffer, 256, f)) { if (!feof(f)) { throw std::runtime_error("error occurs while reading file"); } throw std::out_of_range("end of file") } return buffer; }
~File() { fclose(f); } private: FILE* const f; };
int main() { try { File file("litsu"); while (true) { std::cout << file.readLine(); } } catch (std::runtime_error e) { std::cerr << e.what() << std::endl; } catch (std::out_of_range) {} return 0; }
注意看 try 块中间的语句, 看起来流程很清晰, 完全不像 Python 里面那样, 还要弄个 with 块, 多一级缩进. 都说 C++ 代码没 Python 的精神, 然而在这种小地方 C++ 反而超过了 Python 呢. 其实, Python 的 with 块更像个语法糖, 上篇文章中有提到, 双层 try 在 Python 中能等效地解决这个问题, 只是看起来丑很多. 这个问题的根本在于, Python 这样不具备 RAII 特性的语言没能处理好 对象从不可用状态切换到可用状态的状态过渡. 回顾一下双层 try 结构的处理方式 def readFile(): try: f = open('sawako', 'r') pass try: process(f.readlines()) except: print 'error occurs while reading file' finally: f.close() except: print 'error occurs while reading file'
Posted at Jun 20 2011 - 08:43:07
Permanent Link:
/p/358
Load full text
|
Post tags:
Exception Handling
C++
RAII
Python
|
码中萌
SourceQtQ_Q Q_D 这两个真的是 Qt 中定义的宏来的. Cint V = 0, _ = 1, U = 2, o = 3; int ___ = , l_l = 0; assert('_'); assert(V^_^V); assert(-_-U); assert((o, o)); assert(U>_<U);
assert(( ___, ___ | _ | U | l_l | o ));
Lisp(define < 0) (define o 0)
(> o <)
注释HTML<!--...--> Shell 注释#= o= C/* _ */
Posted at Jun 12 2011 - 14:42:09
Permanent Link:
/p/349
Load full text
|
Post tags:
C
Shell
Lisp
ASCII Art
Qt
|
Python: try finally with 简介
用 Python 做一件很平常的事情: 打开文件, 逐行读入, 最后关掉文件; 进一步的需求是, 这也许是程序中一个可选的功能, 如果有任何问题, 比如文件无法打开, 或是读取出错, 那么在函数内需要捕获所有异常, 输出一行警告并退出. 代码可能一开始看起来是这样的 def read_file(): try: f = open('yui', 'r') print ''.join(f.readlines()) except: print 'error occurs while reading file' finally: f.close()
不过这显然无法运作, 因为 f 是在 try 块中定义的, 而在 finally 中无法引用. 如果将 f 提取到 try 块外部, 如 def read_file(): f = open('azusa', 'r') try: print ''.join(f.readlines()) except: print 'error occurs while reading file' finally: f.close()
那么, 问题在于当打开文件失败, 抛出异常将不会被捕获. 挫一点的方法自然是, 再套一层 try 吧 def read_file(): try: f = open('sawako', 'r') try: print ''.join(f.readlines()) except: print 'error occurs while reading file' finally: f.close() except: print 'error occurs while reading file'
当然这不仅仅是多一层缩进挫了, 连警告输出都白白多一次呢. 正规一点的方式是, 使用 Python 引入的 with 结构来解决, 如 def readFile(): try: with open('mio', 'r') as f: print ''.join(f.readlines()) except: print 'error occurs while reading file'
当文件打开失败时, 异常自然会被 except 到; 否则, 在 with 块结束之后, 打开的文件将自动关闭. 除了打开文件, 还有其它这样可以用于 with 的东西么? 或者说, 怎么自定义一个什么东西, 让它能用于 with 呢? 直接回答后一个问题吧, 秘密在于 Python 虚拟机在 with 块退出时会去寻找对象的 __exit__ 方法并调用它, 把释放资源的动作放在这个 __exit__ 函数中就可以了; 另外, 对象还需要一个 __enter__ 函数, 当进入 with 块时, 这个函数被调用, 而它的返回值将作为 as 后引用的值. 一个简单的例子是
Posted at Jun 12 2011 - 11:18:54
Permanent Link:
/p/328
Load full text
|
Post tags:
Exception Handling
Python
RAII
|
归
归
Posted at May 28 2011 - 09:00:58
Permanent Link:
/p/318
Load full text
|
Post tags:
Story
Tian-jing-sha
|
Python 中函数交叉引用中的问题与解决方法
Python (下面的讨论都是基于 2.7 版本的) 中, 先定义的函数可以调用后定义的函数, 比如下面这段代码 def first(): return second()
def second(): return 0
x = first()
不过, 这种调用是有局限性的, 稍作修改为下面这个样子 def first(): return second()
x = first()
def second(): return 0
就会产生符号错误, 解释器给出的信息是符号 second 不存在. Python 作为一门解释型语言, 出现这样的事情很有可能是这样的原因, 假设现在正在交互模式下执行, 那么输入 def first(): return second()
x = first()
到此为止, second 这个符号确实还没有被定义, 故产生错误. 但是如果上述代码内容是写在文件里的, 那么 Python 完全可以扫描整个文件以获得充分的上下文信息, 并正确地检测出 second 函数定义, 只是 Python 没有这么做. 熟悉 C++ 的同学这时候肯定跳出来了, 因为在 C++ 类空间中, 这种引用是合法的, 如 struct type { int first() { return second(); }
type() : x(first()) {}
int second() { return 0; }
int x; };
不过, C++ 这种事情其实极其危险的, 比如下面这一段代码 struct type { int first() { return second(); }
type() : x(first()) {}
int second() { return x; }
int x; };
这实际上使用了还处于量子态的 x 来初始化它自身, 虽然这样的代码只有手贱的程序员在极其罕见的情况下才会写出这样的代码 (比如我在写这篇博客的时候), 但是本着编译器的职责, 完全应该想方设法让这种情况编不过去. 好了, 还是先回到 Python 上来, Python 不允许这样的代码编译过去, 是因为这个原因么? 假设有下面的程序成功被解释执行 def first(): return second()
x = first()
def second(): return x
Posted at May 16 2011 - 13:38:21
Permanent Link:
/p/314
Load full text
|
Post tags:
Compiler Construction
Python
C++
|
C++ 2011 中调用对象的 Rvalue Reference 成员函数重载
之前提到的 rvalue reference 还有一点没有结算, 其实本来早就想写完它, 但是尝试了很久, 最后 到 stackoverflow 上问来问去才最终解决, 那就是当某个对象是 rvalue 时, 怎么调用 rvalue 的成员函数重载. 之前在有关 rvalue 函数重载的讨论中说到参数是 rvalue 的情况, 很自然地, 如果认为 (简单起见, 非 virtual 的) 对象成员函数等价于普通函数加上一个 this 参数, 那么如何分辨 this 参数是 left value 还是 rvalue 呢? 受到对象成员函数可以 仅根据 const 修饰与否来重载这个现状的启发, 对于对象本身是 rvalue 的重载当然很可能是为成员函数加上某种修饰, 类似下面这个样子 struct Type { void func(); // for normal left reference void func() const; // for const left reference void func() rvalue-qualified-id; // for rvalue reference };
想法当然好, 而且确实有这么一回事, 只是标准敢正常一点么? 下面上真实代码, 这段代码目前似乎只能在 clang++ (我试验的版本是 2.9) 上编译通过 #include <cstdio>
struct Type { Type() {}
void func() & { puts("a"); }
void func() const& { puts("b"); }
void func() && { puts("c"); } };
int main() { Type a; a.func();
Type const b; b.func();
Type().func();
return 0; }
输出是 a b c
前两个函数重载看起来很怪异? 原因是, 如果成员函数重载中只要有一个函数是给 rvalue 用的 (也就是函数后面有 && 修饰), 那么其它重载也必须标注上一个 & 号. 读的时候去掉前两个重载的 & 就很容易看明白了. 而最后一个重载正是给 rvalue 用的. 这样变态地使用符号, 看来标准委员会看来是毫不介意 C++ 逐渐演化成既不表意又不象形的新世纪面向对象强化版 Brainfuck. 真的在代码中写这种东西的死 coder 是伤不起的!
Posted at May 01 2011 - 09:15:20
Permanent Link:
/p/303
Load full text
|
Post tags:
C++
Function Overload
Right Reference
C++11
Left Value
|
C++ Howto: 在栈上构造一个对象
C++ 2011 标准引入的各种符号让我感到 C++ 已经快 变成一门妖术了. 考虑到很多不明真相的 C 程序员同学们觉得 C++ 妖术是 C with classes, 那么这里先介绍一下 C++ 魔法大典中最基本东西: 怎么运用编译器的魔力在栈上召唤一个对象? 假设现在有个类叫做 type , 它有个 无参数的构造函数, 那么下面的代码 type object(); 是巫师学徒们常犯的错误, 这个语句实际上是声明了一个叫做 object 的函数, 它返回一个 type 对象. 施展无参构造魔法的正确咒语是 type object; 此外, 还有一个很吐血的咒术是 type(object); 同样是定义一个名字为 object 类型为 type 的对象. 是否很震惊? 其实这里的括号是可以任意加的, 如 type object; type(object); type((object));
它们的效果相同. 如果它们写在一起, 后边的会跟第一个发生定义冲突. 如果现在它有个构造函数需要一个 int 类型参数, 那么下面这两种代码 type object = 0; type object = type(0);
是不正确的, 哦, 不, 至少是不严谨的, 而且看起来就是土气的 C 程序++, 而不是 C++ 魔法, 正确的咒语是 type object(0); 因为当 type 的构造函数是如下定义时第一种写法是编不过去的 struct type { explicit type(int); };
而第二种写法确实读起来更象是 "调用构造函数, 初始化对象" 两步, 不过它实际上要用到复制构造函数, 虽然很可能编译器有足够强大的魔力优化掉这一次对象复制, 但如果把复制构造函数 delete 掉或者置于 private 或 protected 封印之下, 编译器会给出一个恼人的错误. PS: 在 The C++ Programming Language (C++ 程序设计语言) 一书中, 11.3.3 中有提到一个例子 ... For example, complex b = 3; means complex b = complex(3); 我认为这一处是错误的, 原因如上所说. 好, 现在再回到刚才那个括号打水漂的定义方式, 下面这种写法 int a = 3; type(a);
后面一句是在召唤一个临时的 type 对象么? 当然不是, 编一下就会知道, 那仍然是在定义一个名字为 a 的对象, 即使 type 没有无参构造函数.
Posted at May 01 2011 - 09:05:45
Permanent Link:
/p/292
Load full text
|
Post tags:
C++
Constructor
Copy Constructor
|
C++ 异常不怎么常见知识点 --- 后传
上一篇后 timothyqiu 同学回复提到通过 std::set_unexpected 指定一个处理方法, 在函数抛出与异常声明不相符的异常时调用. 比如下面这样 #include <stdexcept> #include <iostream>
void func_throws() throw (std::out_of_range) { throw std::logic_error("danger!"); }
void throw_another() { throw std::out_of_range("safe"); }
int main() { std::set_unexpected(throw_another); try { func_throws(); } catch (std::out_of_range e) { std::cerr << e.what() << std::endl; } return 0; }
有了这个处理函数后, 确实异常被神奇般地抓住了. 不过换个写法, 如果处理函数里面也继续抛 std::logic_error #include <stdexcept> #include <iostream>
void func_throws() throw (std::out_of_range) { throw std::logic_error("danger!"); }
void throw_another() { throw std::logic_error("not safe!"); }
int main() { std::set_unexpected(throw_another); try { func_throws(); } catch (std::out_of_range e) { std::cerr << e.what() << std::endl; } return 0; }
程序照崩. 也就是说, 处理函数相当于替换原来的函数, 在错误处原地重新抛出一个异常, 但异常必须匹配原来函数的异常声明, 否则异常仍然无法捕捉. 明显扯淡么, 随便找两个异常声明不搭界的函数, 这货不就不管用了么, 总不能随抛随设吧. 当然, 标准还规定了一个很犀利的异常类型, 叫做 std::bad_exception , 如果把这家伙加进异常声明, 那就给力了 #include <stdexcept> #include <iostream>
void func_throws() throw (std::out_of_range, std::bad_exception) { throw std::logic_error("danger!"); }
void throw_another() { throw; }
int main() { std::set_unexpected(throw_another); try { func_throws(); } catch (std::bad_exception e) { std::cerr << e.what() << std::endl; } return 0; }
好了, 原来的异常在 throw_another 里面摇身一变, 成了 std::bad_exception , 不过看 std::cerr 吐出来的东西, 信息都消失了啊, 这不是坑爹么. 其实说到底, 这跟写成 void throw_another() { throw std::bad_exception("std::bad_exception"); }
Posted at Apr 14 2011 - 14:35:07
Permanent Link:
/p/275
Load full text
|
Post tags:
C++
Exception Handling
|
C++ 异常不怎么常见知识点
之前也反复有人推荐 Google 的 C++ Coding Style, 昨天看到一位老师写的 解读 Google C++ code style 谈对 C++ 的理解, 文后的讨论大多集中在 Google 不允许 C++ 异常. 我这里就不多插嘴了, 不过有些关于异常的基本情况还是想略聊一聊. 文中的例子使用 g++ 4.5.2 和 clang++ 2.9 编译测试通过. 如果一个函数声明了异常声明与该函数实际抛出的异常不匹配, 那么这个异常 不会被捕获到. 也就是说 catch (...) 不是银弹. #include <stdexcept>
void func_throws() throw (std::out_of_range) { throw std::logic_error("danger!"); }
int main() { try { func_throws(); } catch (std::out_of_range) {} catch (std::logic_error) {} catch (...) {} return 0; }
补丁: std::set_unexpected 函数与不预期的异常 而一个有趣的 C++ 规定是, 如果一个函数没有任何异常声明, 则该函数有可能抛出 任何异常. 这个规定的一个隐喻是, 一切 C 函数都有可能抛出任何异常. 虽然 C 连异常是什么都不知道, 不过没关系. 因为 C 函数是 异常透明的. 比如下面的例子 #include <stdexcept>
extern "C" void exception_pass();
void func_throws() { throw std::logic_error("danger!"); }
void upper_cpp_func() { exception_pass(); }
int main() { try { upper_cpp_func(); } catch (std::logic_error) { return 0; } return 1; }
extern "C" void exception_pass() { func_throws(); }
在异常发生后的 stack unwind 阶段, C 函数调用栈帧与 C++ 函数栈帧的处理方式相同. 执行结果是一样的. 但这并不意味着一种好的 C/C++ 编程方式, C 是没有 RAII 的! 另外, 分离编译再链接时行为会不同, 可能出现异常无法被捕获的情况.
Posted at Apr 11 2011 - 13:42:28
Permanent Link:
/p/256
Load full text
|
Post tags:
Exception Handling
C
C++
|
表达式求值之优先关系矩阵法
YACC 或者 ANTLR 或者 PLY 之类的东西是很不错啦, 不过当面对一个类似 C(++) 这么变态的需要将语法和符号表勾搭在一起的语言形式时, 还是可以考虑一下使用 Python 纯手工打造. 搞定表达式求值这种现代高级程序设计语言中基础部分, 用 LR(n), LALR 分析之类的当然是可行的, 假定需要支持括号和下面这些运算 | 算符 | 优先级由高到低 | + - (正负号) | ^ (幂运算) | * / | + - (加减号) | 结合方式 | 数值左边 | 右结合 | 左结合 | 左结合 |
先搞个产生式集合 (运算符就不搞什么转义了, | 表示或) Expr := Term +|- Expr := Term Term := Exponential *|/ Term := Exponential Exponential := Factor ^ Exponential := Factor Factor := +|- Factor := Number := ( Expr ) 接着还要再计算 FIRST 集啊什么的, 等到最后写成代码, 秀吉都嫁人了. 所幸这个世界上无痛的解决方案还是挺多的, 比如为符号设定优先级, 使用优先级来指导算符是应该移进还是规约. 这个方法可称为 优先关系矩阵法. 这里仍然会用到 移进 (Shift) 和 规约 (Reduce) 这两个术语, 不过它们的意义跟 LR 分析法里面的有些不同. 在 LR 分析中, 符号指的是词法分析中得到的 Token, 而不是运算符, 也就是说数和运算符, 以及其它任何东西都放在同一个栈中; 而在运用优先关系法的表达式分析过程中, 符号栈包括两个不同的栈, 一个专门用来存运算符 (这个栈称为 算子栈), 另一个存放数或者已解析的表达式 (这个栈称为 表达式栈). 在这里, 移进表示将运算符压入算子栈, 或将数压入表达式栈; 而规约指的是, 根据运算符的元数, 从表达式栈中弹出指定数目的 遇到一个数时无条件移进, 而遇到运算符则要视情况决定移进还是规约, 而所谓的情况就是算符优先级. 比如下面这个表达式 3 + 4 * 5 + 6 首先读入 3 , 移进 表达式栈 3 算子栈 (空)
然后是 + , 算子栈栈顶为空, 没得比, 那好, 先移进 表达式栈 3 算子栈 +
继续, 4 移进, 接着是 * , 比一下, 发现 * 优先级较栈顶的 + (加号, 不是正号) 优先级高, 那肯定得移进 表达式栈 3 4 算子栈 + *
接着移进 5 , 再往后又是一个 + (加号), 这时栈顶的 * 优先级高于它, 所以可以开始规约流程: 从算子栈弹出 * , 它是二元的, 于是再从表达式栈弹出栈顶 (刚刚压入的 5 ) 和次栈顶 ( 4 ), 将这三者组成表达式 4 * 5 , 再压回表达式栈 (下面以一个二叉树的形象描述) 表达式栈 3 * / \ 4 5 算子栈 +
还没完呢, 现在的算子栈顶 + (加号) 与手头上拿着的 + (加号) 平级, 由于加号是左结合的, 所以应当继续规约, 得到 表达式栈 + / \ 3 * / \ 4 5 算子栈 (空)
现在算子栈又空了, 于是 + (最后强调一次, 加号) 压栈, 最后是 6 , 移进之, 结束时, 整个算符栈从头到尾规约一次, 大功告成. 回顾一下刚才的过程, 规则是 - 前面说过的, 数无条件入栈
- 如果算子栈为空, 移进当前算子
- 如果算子栈顶的优先级高于当前算子, 或者两者优先级相等, 但当前符号是左结合的, 那么进行规约, 直到不满足规约条件
- 算子无法规约时则移进
这一些初步地写成代码类似这样
Posted at Apr 11 2011 - 13:31:40
Permanent Link:
/p/250
Load full text
|
Post tags:
Compiler Construction
Python
Tutorial
|
C++ 2011 中的 Left Value 与 Rvalue Reference
#include "某 C++ 概念之序篇" 答: std::stringstream() 产生的临时变量无法匹配类型 std::stringstream& . 但是 std::stringstream() 可以匹配类型 std::stringstream const& , 也就是说, 如果函数 std::stringstream& operator<<(std::stringstream& os, double d) 的签名和实现作修改成下面这个样子就行了 std::stringstream const& operator<<(std::stringstream const& os, double d) { const_cast<std::stringstream&>(os).operator<<(d); return os; }
之前扯过 C++ 已经没有语法意义上的左值了, 不过这里似乎有一点点很微妙的, 跟左值很类似的东西, 那就是, C++ 引用. 任何一本 C++ 入门书籍里面都会花一些篇幅来讨论引用, 其基本意义, 无非是 某个东西的别名, 因此, 这 某个东西对于引用而言显然是至关重要的. 最基本的一点是, 如果被引用的东西是临时变量 (顺便一提, 如果是字面常量这种比较尴尬的情况, 视同临时变量) 怎么办? 比如之前代码样例 A 中的情况? 这就牵扯到 C++ 中正统的 left value. 因为 left 已经不再有任何 "左" 相关的意义的, 所以我不太想在本文中把这货称之为 "左值" (实际上英文在这些方面更加灵活, 比如 Copyleft 这词跟 left 也没太大关系, 用来吐槽则非常不错). 一个东西如果是 left value, 那么它就可以被赋值给 left value 引用, 所谓 left value 引用嘛, 就是就是类似 std::stringstream& 的引用类型, 而暗含 const 的类型的引用则不算 typedef int const int_const; int_const& this_is_not_a_left_value_reference;
那究竟什么是 left value 呢? 一个语句之后仍然存在的变量, 表达式, 或者这样类似的 "具有 值属性的什么东西" 就是 left value. 而 std::cout << (std::stringstream() << d).str().length() << std::endl; 这句之后, 显然临时构造的 std::stringstream() 就被析构了, 因此这家伙不算是 left value, 自然也就无法被 std::stringstream& 所引用. 那常数为什么不算 left value 呢? 当然, 可以认为常数居阴阳之外, 别谈语句之后, 吐核退出, 就算是宇宙毁灭, 什么 π 啊 e 啊还活得好好的; 不过, 从计算机的角度来理解, 常数是 CPU 指令的立即数, 所以别谈语句之后, 只要这条指令结束, 它就没了. 然而, C++ 一个很邪恶的规定是, 即使一个东西不是 left value, 它仍然可以绑定 const 引用, 比如以下语句都是合法的 std::pair<int, int> const& pair = std::make_pair(0, 1); std::stringstream const& strm = std::stringstream(); std::string const& x = "Raki suta"; int const& i = 0;
这样直白地写出来是有点让人觉得不舒服, 不过作为函数调用的参数则好得多, 比如之前的代码片段 B 那样
Posted at Mar 28 2011 - 13:07:31
Permanent Link:
/p/232
Load full text
|
Post tags:
Move Semantic
Operator Overload
C++
Right Reference
Copy Constructor
Left Value
C++11
|
某 C++ 概念之序篇
C++ 虔诚的信徒们, 请阅读下面的代码, 回答码后的问题 /* 完整样例 A */ #include <iostream> #include <sstream>
std::stringstream& operator<<(std::stringstream& os, double d) { os.operator<<(d); return os; }
int main() { double d; std::cin >> d; std::cout << (std::stringstream() << d).str().length() << std::endl; return 0; }
问: 为什么上述代码无法通过编译? /* 代码片段 B */ #include <map> #include <string> #include <stdexcept>
struct key_error : public std::logic_error { explicit key_error(std::string const& what) : std::logic_error(what) {} };
std::string const& lookup(std::map<std::string, std::string> const& map , std::string const& key) throw(key_error) { std::map<std::string, std::string>::const_iterator i = map.find(key); if (map.end() == i) { throw key_error("key ``" + key + "'' not found in map"); } return i->second; }
问: 上述代码中的 lookup 函数有什么隐患? 答案已揭晓, 点此查看, 谢谢关注.
Posted at Mar 26 2011 - 09:37:59
Permanent Link:
/p/222
Load full text
|
Post tags:
C++11
Copy Constructor
Move Semantic
C++
Operator Overload
Left Value
Right Reference
|
Qt 囧形: QRect
Qt 中的整点矩形类 QRect 的表示很独特, 存放的是左上角坐标和右下角坐标 (准确地说, 是 int x1; int y1; int x2; int y2; ). 对 QRect 取上下左右都是闭区间上的边界, 比如 QRect rect(QPoint(0, 2), QSize(8, 8)); rect.left(); // 0 rect.right(); // 7 : 0 + 8 - 1 rect.top(); // 2 rect.bottom(); // 9 : 2 + 8 - 1
其实也没什么大问题, 因为确实就是这样. 但是, 自古以来就被训练成左开右闭思维的键盘打击手们, 一不小心就会出错, 比如下面的 Python 列表切片代码示例, 取一个列表的一半, 后一半 list = range(0, 8) list[0: len(list) / 2] // [0, 1, 2, 3] list[len(list) / 2: len(list)] // [4, 5, 6, 7]
而如果给 QRect "切片", 求一个矩形的左半边和右半边, 代码则看起来像 QRect rect(QPoint(0, 2), QSize(8, 8));
QRect leftHalf(rect); leftHalf.setRight(leftHalf.left() + leftHalf.width() / 2 - 1);
QRect rightHalf(rect); rightHalf.setLeft(rightHalf.left() + rightHalf.width() / 2);
设置右边界的代码修正这个 -1 看起来非常非常不自然. 而在实际运用中, 经常会遇到各种 +1 -1 的修正. 至于 Nokia 的开发者们自己是不是搞清楚了 QRect 我也不太清楚, 不过 QPainter::drawRect(QRect const&) 这个函数是看起来有 bug 的样子, 比如下面这个例子 #include <QApplication> #include <QToolButton> #include <QBrush> #include <QPainter>
struct TestButton : public QToolButton { void paintEvent(QPaintEvent*) { QPainter painter(this); painter.setBrush(Qt::NoBrush);
painter.setPen(QPen(Qt::white, 1)); painter.drawRect(rect().adjusted(3, 3, -3, -3));
painter.setPen(QPen(Qt::blue, 1)); painter.drawRect(rect().adjusted(2, 2, -2, -2));
painter.setPen(QPen(Qt::green, 1)); painter.drawRect(rect().adjusted(1, 1, -1, -1));
painter.setPen(QPen(Qt::red, 1)); painter.drawRect(rect()); } };
int main(int argc, char* argv[]) { QApplication app(argc, argv); TestButton button; button.show(); return app.exec(); }
放大了看, 最外侧红框的右边和下边都不见了.
Posted at Mar 26 2011 - 09:11:12
Permanent Link:
/p/219
Load full text
|
Post tags:
Qt
QRect
C++
|
拿去吧, 四字节整数类型
对于 C(++) 之流在平台相关这种水深火热之中挣扎的语言, 对机器字长, 以及整数类型字长在一些情况下是极其敏感的. 所以每个大型项目建设之前, 在某个头文件里面定义一大堆 int32 或者是 unsigned8_t 之类的固定字长类型显得非常必要, 而且让工程看起来很专业的样子. 然而, 由于标准库中没有, 项目之间又互相不信任, 结果是几乎每个库都会自定一套, 比如 boost 就有 int8_t , int_least8_t 等类型, 而 Qt 更是拿出了叫做 qulonglong 的类型来卖萌. 虽然说是跨平台, 但是如果迁移平台时使用了错误版本的库呢? 因此有时依然需要写一个类似下面的程序来人肉验证 int main() { std::cout << sizeof(int4_type) << " " << sizeof(int8_type) << std::endl; return 0; }
然而, 人都是不可靠的, 如果有人对你说, 喔, 我跑了上面的程序, 结果是 "4 8", 也许你仍会强迫症一样, 自己再验证一次. 好了, 也许你已经厌烦了, 这么一点破事情, 难道不能交给机器做么? 理想的解决方案是, 只需要这样一个模板就好了 template <int _SizeOf> struct IWantAnIntegralTypeOf;
int main() { IWantAnIntegralTypeOf<4> this_is_a_4_bytes_int; IWantAnIntegralTypeOf<8> this_is_a_8_bytes_int; return 0; }
不是吗? 这是完全可行的, 实际上, 可以把 C 支持的所有内建整数类型先拿出来, 组成一个类型链表来搜索, 像下面这样 struct __null__ {};
struct c_char { typedef char type; typedef __null__ next; };
struct c_short { typedef short type; typedef c_char next; };
struct c_int { typedef int type; typedef c_short next; };
struct c_long { typedef long type; typedef c_int next; };
struct c_long_long { typedef long long type; typedef c_long next; };
struct __head__ { typedef c_long_long next; };
接下来, 弄个模板来搜, 搜索当然是用模板偏特化的方法 template <typename _TypeNode, int _SizeOf, bool _SizeMatched> struct type_finder;
template <typename _TypeNode, int _SizeOf> struct type_finder<_TypeNode, _SizeOf, true> { typedef typename _TypeNode::type type; };
template <typename _TypeNode, int _SizeOf> struct type_finder<_TypeNode, _SizeOf, false> { typedef typename type_finder< typename _TypeNode::next , _SizeOf , _SizeOf == sizeof(typename _TypeNode::next::type)>::type type; };
Posted at Mar 26 2011 - 09:08:36
Permanent Link:
/p/217
Load full text
|
Post tags:
Generic Programming
Metaprogramming
C++
Template
|
软件翻译与 locale
看了一篇 会 "吐核" 的终端, 了解到 wget 还有烦人的 "eta(英国中部时间)" 提示, 看来软件翻译这档子事还真是很能产生喜感的. 在 Linux 环境下, 主流的软件翻译方法是通过工具抓取源代码中的需翻译字符串到外部文本文件, 形成一个字典, 翻译之后在运行期加载. 更专业一点就是, 从软件本身要能国际化 (internationalization, 一般简写为 i18n, 因为 i 和 n 之间有 18 个字符), 支持从源代码中提取字符串来翻译; 而翻译这个步骤则成为本地化 (localization, 简称 l10n). 要让软件具备国际化属性, 这必须程序员们亲自努力, 为需翻译字符串附加一些修饰. 如下面这个程序 int main(int argc, char* argv[]) { if (1 == argc || 0 == strcmp("hello", argv[1])) { puts("Hello"); } else { puts("World"); } return 0; }
是很难具备国际化能力的, 因为文本扫描程序无法区分哪些字符串是需要翻译的. 要加上区分信息很简单, 比如 #define TRANSLATE_THIS(x) x
int main(int argc, char* argv[]) { if (1 == argc || 0 == strcmp("hello", argv[1])) { puts(TRANSLATE_THIS("Hello")); } else { puts(TRANSLATE_THIS("World")); } return 0; }
将文件保存为 hello.c, 使用 xgettext 工具提取字符串 $ xgettext hello.c --keyword=TRANSLATE_THIS -o hello.po 那么 xgettext 将提取被宏 TRANSLATE_THIS 所包括的所有字符串到 hello.po 纯文本文件中. 在 po 文件中, 每个被提取的字符串是一个 msgid, 而翻译则是接下来一行中的 msgstr, 现在所有的 msgstr 都空着, 在 po 文件中直接写入翻译结果并保存即可. 此外还要指定 po 文件头部有文件编码等信息. 翻译完成后, 在本地创建 locale 相关目录, 再使用 msgfmt 工具编译 po 文件为二进制文件, 并放入 locale 目录中 $ mkdir -p $LANG/LC_MESSAGES && msgfmt hello.po -o $LANG/LC_MESSAGES/hello.mo 这个目录的意义待会儿再解释. 使用纯文本编辑器盯着 po 文件翻译条目是很累很无趣的事情, 好在有许多工具可以选取. 比较流行的有 poEdit 和 Qt Linguist. 接下来继续讨论程序的事情. 到此为止, 程序本身还不具备本地化的能力, 因为宏 TRANSLATE_THIS 什么也没干, 程序本身的逻辑并没有变化, 也不知道如何导入翻译目标字符串替换原有字符串. 现在将宏定义换掉, 使用 locale 中的组件来完成本地化工作
Posted at Mar 19 2011 - 15:06:59
Permanent Link:
/p/208
Load full text
|
Post tags:
Locale Programming
l10n
Tutorial
C
i18n
|
ASCII Art 让开机充满爱
之前有位爱心人士提议 更改 Linux SSH 登入歡迎畫面為新世紀福音戰士裡的 Nerv 的 Logo, 然而, 对于我等宅男而言, 只在登录 SSH 时出现个 Nerv Logo 是不够的, 每次开机的时候来上一次也是必须的. 简单方案是在启动脚本里加一句 cat, 不过最好不要加在 rc.sysinit 脚本开始, 因为这时内核还没有切分辨率. 我个人觉得效果最好的是加进 rc.multi 开头, 这时有充分的时间展示 (特别是如果恰好要硬盘检查), 而且之后也不容易被刷屏刷掉 (对于 DAEMONS 不是很多的同学). 至于 cat 的文件内容, 嗯, 下面是我自己制作的一份 Nerv Logo, 比较大, 38 行, 最宽处 67 字符, 通常的终端字体在 14 吋本本的显示器 (1366×768 或 1280×800) 都能放下.
Posted at Mar 13 2011 - 14:09:23
Permanent Link:
/p/194
Load full text
|
Post tags:
ASCII Art
Lucky Star
Logo
Nerv
|
std::unique_ptr< cookbook>
std::unique_ptr 是 C++11 标准的 STL 中, 用来取代 std::auto_ptr 的指针容器 (如果在代码中使用了 std::auto_ptr , 使用 gcc 4.5 之后的版本加上 -std=c++0x -Wall 参数编译时, gcc 会报出警告 std::auto_ptr 已经过时). 同 std::auto_ptr 一样, std::unique_ptr 的基本功效也能当作简单的自动对象管理指针来使用, 如 #include <memory> /* for std::unique_ptr */ #include <iostream>
struct echo { echo() { std::cout << "ctor" << std::endl; }
~echo() { std::cout << "dtor" << std::endl; }
echo(echo const&) = delete; };
void test() { std::unique_ptr<echo> echo_ptr(new echo); }
int main() { test(); std::cout << "." << std::endl; return 0; }
输出应该如 ctor dtor .
类型 echo 在构造和析构时的输出信息可以帮助掌握对象的生命周期. 如上述程序中, echo 对象在 test 函数结束时析构. std::unique_ptr 的复制构造函数被 delete 处理了, 所以类似下面的代码会出错 std::unique_ptr<int> a(new int(0)); std::unique_ptr<int> b(a); // ERROR // deleted function // ‘std::unique_ptr<...>::unique_ptr(std::unique_ptr<...> const&)'
如果确实需要复制一份整数的值, 需要可以做如下操作 std::unique_ptr<int> b(new int(*a));
而如果是想要将 a 所持有的指针转移到 b 中, 需要显式调用 std::move 将来移交指针控制权 std::unique_ptr<int> a(new int(0)); std::unique_ptr<int> b(std::move(a));
此时 a 中的指针会变为 NULL . 明确的转移语义由 std::move 函数表达, 与 std::auto_ptr 暧昧的构造函数不同, std::move 明确指出 std::unique_ptr 对象是通过转移构造进行参数传递的. 当函数需要使用 std::unique_ptr 作为参数或返回值时, 可以是如下几种形式
Posted at Mar 13 2011 - 08:34:22
Permanent Link:
/p/181
Load full text
|
Post tags:
C++
C++11
unique_ptr
Move Semantic
Tutorial
|
C++ 到底怎么使用流来输出对象
书上写的做法当然是类似 struct my_type { void set_x(int xx); void set_y(double yy);
my_type(int xx, double yy) : x(xx) , y(yy) {}
friend std::ostream& operator<<(std::ostream& os, my_type const&); private: int x; double y; };
std::ostream& operator<<(std::ostream& os, my_type const& m) { return os << m.x << ' ' << m.y; }
一般使用这样没问题, 不过在想要写得很省略的用况下, 这样就很纠结 my_type m(10, 20.0); std::stringstream ss; std::string image = (ss << m).str(); // WRONG!
因为 operator<< 返回的类型已经变成没有 str() 取得字符串函数的基类 std::ostream& 了. 看起来要什么流类型进来什么流类型出才行, 比如 struct my_type { void set_x(int xx); void set_y(double yy);
my_type(int xx, double yy) : x(xx) , y(yy) {}
friend template<typename _OS> _OS& operator<<(_OS& os, my_type const&); // WRONG private: int x; double y; };
template <typename _OS> _OS& operator<<(_OS& os, my_type const& m) { os << m.x << ' ' << m.y; return os; }
不过很遗憾, 泛型函数无法做朋友 那要不先把 my_type 对象弄成字符串了, 再把字符串输出好了 struct my_type { void set_x(int xx); void set_y(double yy);
my_type(int xx, double yy) : x(xx) , y(yy) {}
std::string str() const { std::stringstream ss; ss << m.x << ' ' << m.y; return ss.str(); } private: int x; double y; };
template <typename _OS> _OS& operator<<(_OS& os, my_type const& m) // ACTUALLY NOT NEEDED ANY MORE { os << m.str(); return os; }
这看起来多疼啊. 一个可能的方案是, 为每个自定义类型实现 str() 成员函数 (有必要的还可以虚一下), 然后全局声明一个 template <typename _OS, typename _T> _OS& operator<<(_OS& os, _T const& t) { os << t.str(); return os; }
也许这样会好很多.
Posted at Mar 05 2011 - 15:14:50
Permanent Link:
/p/177
Load full text
|
Post tags:
C++
Operator Overload
Output Stream
|
Hello Closure
C++0x 搞了 lambda 算子, Java 更耐不住寂寞, 直接上了 Clojure, 函数式或伪函数式编程大潮一波波地袭来, 我终于还是下定决心, 直接去学习来自远古时代的 Lisp. 当然了, 既然都是图灵完全的, C++, Python 什么的, 跟 Lisp 的实际表达能力差不多. 不过呢, 就像之前我对 STL 里 对 std::for_each 的吐槽那样, 语言不给力, 要表达出想要的东西会很麻烦. 在动手写了一些 Lisp 代码之后我觉得, Lisp 在这方面不会成问题. 问题倒是, Lisp 代码似乎很难读懂, 括号很容易看错, 而且脑子里面要有一台能容纳至少七八个调用栈帧的 Lisp 虚拟机, 吃力啊. 我倒也不指望成为 Lisp 达人, 以后用 Lisp 写各种各样的煮咖啡程序什么的, 只是想换换脑子, 让我找找 “原来代码还能这样写” 的感觉. 言归正传, 说说 Lisp 的闭包. 所谓闭包, 是是带有 编译期绑定的 自由变量且是 第一级对象的函数. 不愧是 Wikipedia, 描述太精准了, 让人难以看明白. 下面先简介一下这几个术语. 自由变量指的是不在函数参数或函数域中的变量 (全局变量不算), 比如嵌套函数中内层函数引用的外层函数的参数或局部变量时, 这样被引用的变量; 甚至没有压根儿没有声明的变量 (当然前提是编译器支持这样的行为). 不过上面说的后一种情况, 根本没声明的变量, 就不满足 编译期绑定这个条件了. 此外, 一些动态名字查找的行为也会导致这个规则失效. 所以诸如 sin , cos 之类的数学库函数都不是闭包, 它们并不引用自由变量. 而 第一级对象这个约束条件则更多的是要求语言支持, 第一级对象要求对象能够被存入变量, 能被作为函数参数或函数返回值, 能被聚合进对象, 能够运行期构造, 并且任意取名都不会影响其行为, 这些条件全部满足. Java 语言就不能将函数存入变量, 或作为参数返回值; 而 C++ 在 C++0x 标准之前没有嵌套函数, 无法构造出引用除了全局变量之外自由变量的函数, 所以这些语言的函数都不是闭包. 概念就这么多, 下面开始闭包的基本应用, 以 Scheme 为例, 解释器使用 Guile. 这里是一个简单闭包的例子 (define (multiple-by n) (define (multiple x) (* x n)) multiple )
这里嵌套在内部定义的 multiple 就是闭包原型, 它引用了定义在 multiple-by 中的参数 n 作为自由变量. 调用 multiple-by 则可以得到一个闭包实例, 如 (define multiple-by-3 (multiple-by 3)) 那么再 (multiple-by-3 4) 就能得到 12. Lisp 支持 lambda 算子语法来简化函数定义, 同时减去为函数起名字的麻烦, 如上例可以改成 (define (multiple-by n) (lambda (x) (* x n)) )
闭包能引用自由变量的特性是很奇妙的, 把上面的例子改成下面这样 (define (multiple-by n) (lambda (x) n) )
虽然这个时候它已经完全名不副实了, 调用 multiple-by 产生的闭包再被调用时只会傻乎乎地一个劲地返回相同的值. 这倒是一个另类的闭包, 它的实际作用不是运算, 而是数据运载. 也就是说, 闭包的作用不一定是执行某种操作, 而可以是存放数据的对象. 比如下面这个例子, 就利用闭包构造了一个 pair
Posted at Feb 26 2011 - 15:01:32
Permanent Link:
/p/174
Load full text
|
Post tags:
Closure
Lisp
|
Yet Another BitFocus Site
博客也换了好几茬了, 域名这也是第二个了, 各种原因. 现在又弄了一个站点, 不知道能坚持到什么时候. 这次略创造性地, 把 ARecord 的根做了个封面, 把博客搭在 blog 这个 CName 下面, 而很具有迷惑性的 www 那个 CName 则挂着 GAE, 不知道走错路的小朋友会不会偶然撞墙或者发现站点剧变. 此外就是 mail.bitfoc.us 挂在 Google App 这个很不错, 推荐大家去折腾一下, 免费账户就给 10 个 7G+ 的 GMail 个性域名账户哦.
开年第一周, 上了六天班, 周末还加了一记, 虽然加班实际上也没做什么事情, 还让我偷空把这个站点给买了修了整了, 不过怎么说也是兆头不好啊.
之前的博客在年前挂掉了, 写了一篇算是总结的东西只好挂在豆瓣上, 现在想想总归是一篇吐槽文, 也就不再转回博客上了, 只是最后用的那句歌词还是贴在这里, 算是对自己新一年的勉励:
除了装作无畏的战士一样, 什么也做不了 (恐れを知らない戦士のように 振る舞うしかない / Unistall / 石川智晶)
歌词是比较惨, 很能体现鄙司鄙项目组现在的工作气氛. 不过说回来, 描述现在的学习气氛也差不了太多, 看了看 SICP, 觉得之前都白学白搞了, 也完全不知道将来会发现什么, 领悟什么.
Posted at Feb 19 2011 - 15:35:57
Permanent Link:
/p/155
Load full text
|
Post tags:
Blog
Bit Focus
|
0
1
2
3
4
5
Page 6
7
8
9
10
11
12
|