sinablog2wordpress:从新浪博客搬家到WordPress

Filed Under (新长征路上的代码) by 逆铭 on 2010-02-06 21:23

Screenshot

要从新浪搬到Wordpres,网上广为流传的方法是利用blogbus的博客搬家服务获得blogbus格式的xml,然后再用一个Python写的脚本把它转换成WordPress认识的格式。但是这种方法在最近新浪博客升级以后就失效了。于是自己用现学的写了一个小程序,搬家时能保留标签目录评论评论回复这些信息。

猛击[这里]下载。注意,此程序仅支持2010年初的新版新浪博客,之前或之后的版本都不支持。

要运行程序,你需要确保已经安装过JRE。双击运行后显示如图界面,填入自己的博客地址(不要省略“http://”),然后点击“Start”即可。这时“Start”按钮变为灰色,标题栏显示“Extracting”。等待几分钟,当标题栏显示为“Done”、“Start”按钮重新变为可用时,程序所在目录下会出现一个blog.xml文件。把这个文件直接导入WordPress就可以了。

代码也打在jar包里了,MIT协议。欢迎报告bug。

下面是废话。

恩由于新浪用了ajax,评论信息是通过xhr异步读取的,用一般的方法没法抓到。我纠结许久,最后是用了非常ad hoc的方法解决的,不知道有没有什么什么不太麻烦的通用解决方案呢。

再扯两句。我都想不起来当初具体是怎么想到要学的,也许是为了了解下函数式编程,也许只是想在jvm上有一个喜欢的语言吧——Java写起来太不爽了;Java社区的低效和保守也已经开始显出C++的影子。

确实是非常强大和灵活;我在见到一些颇富技巧性的hack之后都有些怀疑社区的风气会不会慢慢变得像C++社区一样过分热衷技巧的炫耀。不过的设计目标就是以较简单的语法规则获得最大的scalability,不需要通过挖掘语言规范里的犄角旮旯来实现一些必要功能,所以不会像C++一样成为一门本身已相当复杂,却还需要别人反过来教语言发明者如何使用的语言。

毕竟表现力比Java强太多,代码也简洁太多。比如这次我需要实现一个抛出异常后重试若干次的逻辑,只需定义一个函数:

def tryFor[T](times: Int)(op: => T): T = {
  if (times <= 0) throw new RuntimeException("Operation failed.")
  try { return op } catch {
    case e: Throwable => e.printStackTrace
  }
  tryFor(times - 1)(op)
}

然后这样使用:

val source = tryFor(5) {new Source(url)}

程序就会不断获得网页源代码,并在5次失败后抛出异常。Java实现同样的东西可不会如此优雅了。又如下面这段代码返回一篇博文xml:

private def generateEntryXml(entry: BlogEntry) = {
  <item>
    <title>
      {entry.title}
    </title>
    <wp:post_date>
      {dateFormat.format(entry.postDate)}
    </wp:post_date>
    <category>
      {entry.category}
    </category>
    {for (tag <- entry.tags) yield <category domain="tag">{tag}</category>}
    <content:encoded>
      {xml.Unparsed(handleNewLines(entry.content))}
    </content:encoded>
    <wp:status>publish</wp:status>
    {for (comment <- entry.comments) yield generateCommentXml(comment)}
  </item>
}

注意,xml标签直接作为的源代码的一部分在代码中出现!虽然我觉得这样会使语言多出一种“特殊情况”,增加语言的复杂性,但不得不承认这样的设计确实非常优美简洁。

我比较看好,以后自己做跑在jvm上的东西应该是首选语言。推荐有兴趣的童鞋也了解一下。


Tags:, , , ,

有爱的小脚本:启动终端时显示一句箴言

Filed Under (新长征路上的代码) by 逆铭 on 2009-11-22 18:46

效果就是每次启动终端时都有一个小动物什么的讲一句有意思的话:doubanclaim469c1764e4db1ecf

cowsay-fortune

说话的东西和说的话都随机出现。这个效果是我在Linux Mint里面看到的,感觉很有爱。下周考Unix环境编程,周末恶补一下,顺便写这个小脚本练手。

以我在用的 ubuntu 为例。首先确保安装 fortunescowsay 两个包。前者用于显示各种各样的趣味短句,后者则提供了一头会说话的奶牛(和其它各种诡异的东西)。关于fortunes还有一些有趣的包你可能也想一起安装,比如fortune-zh里有唐诗宋词,fortunes-ubuntu-server则有关于使用Ubuntu Server的贴士,等等。

新建一个文件cowsay-fortune,把以下代码复制进去:

#!/bin/bash
# Cow randomly says a hopefully interesting adage

# Get a short message from fortune, both offensive and not.
# Remove -a if you don't want to see offensive ones.
# Remove -s if you don't mind reading the long messages.
msg=`fortune -a -s`

# Randomly pick a mode of the cow
modes=("" -b -d -g -p -s -t -w -y ); mode=${modes[$(($RANDOM % 9))]}

# cowsay or cowthink?
cowdos=(cowsay cowthink); cowdo=${cowdos[$(($RANDOM % 2))]}

# Radomly pick a cow picture file
speaker=`cowsay -l | sed '1d;s/ /\n/g'| sort -R | head -1`

# That's it ^^
echo "$msg" | $cowdo -n -f $speaker $mode

保存后加上执行权限:

chmod +x cowsay-fortune

然后把这个文件复制到/usr/bin下

sudo cp cowsay-fortune /usr/bin

最后打开/etc/bash.bashrc

sudo gedit /etc/bash.bashrc

并在最后加上一行:

cowsay-fortune

保存后打开终端,应该就是这个效果了:

cowsay-fortune

顺便抱怨一下,shell编程时对空格的要求也太诡异了吧。。一会儿要求空,一会儿要求不空,一会儿又不限制。。。


Tags:,

[POJ1771] Elevator Stopping Plan

Filed Under (新长征路上的代码) by 逆铭 on 2009-11-21 00:33

新浪的 spamer 越来越多,很早就想搬到独立的 上去,但是一直没顾上。只好先在sina这里凑合着了。

Elevator Stopping Plan
Time Limit: 1000MS  Memory Limit: 30000K  Special Judge

Description

ZSoft Corp. is a software company in GaoKe Hall. And the workers in the hall are very hard-working. But the elevator in that hall always drives them crazy. Why? Because there is only one elevator in GaoKe Hall, while there are hundreds of companies in it. Every morning, people must waste a lot of time waiting for the elevator.

Hal, a smart guy in ZSoft, wants to change this situation. He wants to find a way to make the elevator work more effectively. But it’s not an easy job.

There are 31 floors in GaoKe Hall. It takes 4 seconds for the elevator to raise one floor. It means:

It costs (31-1)*4=120 seconds if the elevator goes from the 1st floor to the 31st floor without stop. And the elevator stops 10 second once. So, if the elevator stops at each floor, it will cost 30*4+29*10 = 410 seconds (It is not necessary to calculate the stopping time at 31st floor). In another way, it takes 20 seconds for the workers to go up or down one floor. It takes 30*20 = 600 seconds for them to walk from the 1st floor to the 31st floor. Obviously, it is not a good idea. So some people choose to use the elevator to get a floor which is the nearest to their office.

After thinking over for a long time, Hal finally found a way to improve this situation. He told the elevator man his idea: First, the elevator man asks the people which floors they want to go. He will then design a stopping plan which minimize the time the last person need to arrive the floor where his office locates. For example, if the elevator is required to stop at the 4th, 5th and 10th floor,the stopping plan would be: the elevator stops at 4th and 10th floor. Because the elevator will arrive 4th floor at 3*4 = 12 second, then it will stop 10 seconds, then it will arrive 10th floor at 3*4+10+6*4 = 46 second. People who want to go 4th floor will reach their office at 12 second, people who want to go to 5th floor will reach at 12+20 = 32 second and people who want to go to 10th floor will reach at 46 second. Therefore it takes 46 seconds for the last person to reach his office. It is a good deal for all people.
Now, you are supposed to write a program to help the elevator man to design the stopping plan,which minimize the time the last person needs to arrive at his floor.

Input

The input consists of several testcases. Each testcase is in a single line as the following:
n f1 f2 … fn
It means, there are totally n floors at which the elevator need to stop, and n = 0 means no testcases any more. f1 f2 … fn are the floors at which the elevator is to be stopped (n <= 30, 2 <= f1 < f2 … fn <= 31). Every number is separated by a single space.

Output

For each testcase, output the time the last reading person needs in the first line and the stopping floors in the second line. Please note that there is a summary of the floors at the head of the second line. There may be several solutions, any appropriate one is accepted. No extra spaces are allowed.

Sample Input

 3 4 5 10
 1 2
 0

Sample Output

 46
 2 4 10
 4
 1 2

Source

Asia Guangzhou 2003

Solution

很久不更新了,写个水水的解题报告充数。。。这是最近做的算法习题,网上看到这题的题解都是二分+贪心的,这里提供一个dp解法。

理解题意的时候有一点需要特别注意:题目所描述的整个过程是“并行”的。所以所有人都到达各自楼层的用时只与最晚到达的人有关。

首先,由于去各楼层乘客的具体数目对结果没有影响,为表述方便,我们假设每个目标楼层只有一个人要去,并且把各个目标楼层与要去该楼层的那个乘客对应。下面在说“电梯里还剩i个人”的时候,就是在说“电梯里的乘客还要去i个楼层”。

粗略的阶段划分和状态表示还是很简单的。

电梯从第1层开始层层上升,每层都看做一个阶段,任意时刻的状态都可以由“电梯在几楼”和“电梯上都有谁”这两个参数唯一确定。初始状态就是“电梯在1楼”和“所有人都在电梯上”。

在每个阶段需要做出决策,选择让电梯上的哪些人下来自己走(如果没人下来就表示电梯在这层不停)。每个决策发生后,原来电梯里的人被分成两拨:一拨留在电梯上继续上升,另一拨离开电梯开始爬楼锻炼身体。要求某个状态下电梯里的所有人到达各自楼层所需的最短时间,只需要找到一个最优的决策,使得上述两拨人中最晚到达的人尽早到达。

即,若设决策后留在电梯里的人全部到达各自楼层需要时间 T1,离开电梯的人全部到达需要时间 T2,则要求的就是 min{ max(T1, T2) }。其中 T1 可由“当前层数+1”和“决策后剩下的人”确定的状态得到;T2 则是下电梯的人中走的最远的那位所花的时间。

按照上述想法很容易列出转移方程。但是“电梯上都有谁”这一参数有 2^n 种取值,整个算法的复杂度因此能到达令人发指的O(m*2^2n),对于m=31,n=30的数据规模这是不可接受的。

我们需要设法减少需要考虑的状态数目。下面给出两个引理:

1. 电梯决定停在第k层时:要去 1..k 层的人应选择在这时下电梯,这样一定可以得到当前决策下的一个最优解。如图:

2. 电梯在第k层时,若要去 k+r 层的人选择在这时下电梯,则:要去k+1..k+r-1层的人也应选择在此时下电梯,这样一定可以得到当前决策下的一个最优解。如图:
http://gallery.tomtung.com/pictures/poj1771_2.png
以上两点很容易证明。由这两点可以得到一个很简单但足以解决问题的结论:

无论电梯停在哪一层,若要去第 k 层的人选择在这时下电梯,则:所有要去低于k层(第1..k-1层)的人也应选择在此时下电梯,这样一定可以得到当前决策下的一个最优解。如图:

http://gallery.tomtung.com/pictures/poj1771_3.png

也就是说,如果把初始时的 n 名乘客按照各自要去的层数从(注意此顺序与输入相反)排列,并依此编号为第 1、2、3…n 个人,第 i 个人要去第 f[i] 层(f[1]>f[2]>…>f[n]),那么可以认为任意时刻电梯里乘客的编号都是 1, 2,..,x 这样一个连续序列。也就是说,对于电梯里的人我们只需要考虑编号为 1, 2, 3 或 1, 2, 3, 4, 5 这样连续排列的情况,而无需考虑 1, 2, 4(缺3)或2, 3, 4(缺1)这样的情况。
http://gallery.tomtung.com/pictures/poj1771_4.png
这样一来,每个状态都能由两个数[i,j]来表示:电梯在第i层,电梯里有j个人,即要去楼层最高的第1,2,..,j个人。

下面给出转移方程:

f[i,j]表示电梯在第i层,电梯上有要去楼层最高的j个人时,电梯上的人全部到达各自楼层所需的最短时间

f[i,j] = min{ max(t1, t2) } (0<=k<=j)

t1 = f[i+1, k] + 电梯停留时间 + 电梯上升一层所用时间

t2 = max{ |d[l] – i| * 人爬一层楼所用时间 } ( k+1<=l<=j )

边界条件、最优解的构造方法以及其它细节问题不再赘述,详见代码。复杂度O(m*n^2)。代码中其实还有优化的空间,但已经是0ms过的,没必要了。

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

#include <cstring>
using std::memset;

#include <algorithm>
using std::max;

#include <cmath>
using std::abs;

#include <limits>
using std::numeric_limits;

#include <vector>
using std::vector;

const int maxN = 30, maxF = 31;
const int ve = 4, st = 10, vw = 20; // 电梯上一层所需时间;电梯停一层所需时间;人走一层所需时间

int n, f[maxN + 1];

bool input()
{
    cin >> n;
    if (n==0) return false;

    // 注意:f[1..n]中楼层数从高到底排列
    for (int i = n; i>=1; --i)
        cin >> f[i];

    return true;
}

int dp[maxF + 1][maxN + 1], nextJ[maxF + 1][maxN + 1];

// 现在电梯在第currF层,第L到第R人离开电梯
// 函数返回这些离开电梯的人中最晚到达目的楼层所需的时间
int tLeave(int currF, int l, int r)
{
    if (l>r)  return 0;
    // 仅需考虑两端
    return max(abs(currF-f[l]), abs(currF-f[r])) * vw;
}

// 现在电梯在第i层,电梯里本来有j个人,在要下电梯的人离开后还剩jj个人
// 函数返回这些留在电梯里的人中最晚到达目的楼层所需的时间
int tStay(int i, int j, int jj)
{
    // 没人下电梯
    if (j==jj)
        return dp[i+1][jj] + ve;
    // 所有人都离开电梯
    else if (jj==0)
        return 0;
    // 第1层不计算电梯停留时间
    else if (i==1)
        return dp[i+1][jj] + ve;
    //普通情况
    else
        return dp[i+1][jj] + ve + st;
}

void calculate()
{
    // 边界:电梯在顶楼时所有人都必须下电梯
    int topFloor = f[1];
    for (int j = 1; j<=n; ++j)
        dp[topFloor][j] = tLeave(topFloor,1,j);

    for (int i = topFloor - 1; i>=1; --i)
        for (int j = 1; j<=n; ++j)
        {
            dp[i][j] = numeric_limits<int>::max();
            for (int jj = 0; jj <= j; ++jj)
            {
                // 取离开电梯的人和留下的人中的最晚到达者
                int tmp = max(tStay(i,j,jj),tLeave(i,jj+1,j));
                if (dp[i][j] > tmp)
                {
                    dp[i][j] = tmp;
                    nextJ[i][j] = jj;
                }
            }
        }
    cout << dp[1][n] << endl;
}

void rebuildSolution()
{
    vector<int> stops;
    int j = nextJ[1][n], topFloor = f[1];
    for (int i = 2; i<=topFloor; ++i)
        if (nextJ[i][j]!=j)
        {
            stops.push_back(i);
            j = nextJ[i][j];
            if (j==0) break;
        }

    cout << stops.size();
    for (int i = 0; i!=stops.size(); ++i)
        cout << ' ' << stops[i];
    cout << endl;
}

void solve()
{
    memset(dp,0,sizeof(dp));
    memset(nextJ,0,sizeof(nextJ));
    calculate();
    rebuildSolution();
}

int main()
{
    while (input())
        solve();
}

Tags:,

读取RFC822格式日期时间的类[C#]

Filed Under (新长征路上的代码) by 逆铭 on 2009-08-09 20:06

比较无法理解为什么 .NET 类库里面没有提供足够对RFC822格式日期时间的支持。网上有人实现,但是都不太让人满意。比较囧的是,关于单字母表示时区,好几个实现都只考虑了 Z、A、M、N、Y 五个 spec 里直接列出的字母……我在用的 Argotic 也是这样。所以就自己实现了一个 parser,然后修改 Argotic 的代码直接用它。

这里下载源代码及其单元测试。需要的话就拿去吧,lgpl。

我实现的时候相比严格的 spec 又放宽了一些,比如允许小时、分钟、秒只有一位数字(spec 要求必须两位),允许不指定时区,允许用四位数字表示年份(这个是 rss 的 spec 要求的),允许只指定日期不指定时间。如果你觉着不爽就自己改改吧,看代码就知道非常好改(就在“Set Format Strings”那个 region 里)。

欢迎报告bug。


Tags:

敏捷开发学习笔记(思维导图)

Filed Under (新长征路上的代码) by 逆铭 on 2009-07-04 18:06

终于看完了《Agile Principles, Patterns, and Practices in C#》的第一个Section,上学期间读这些书的进度还真不是一般的慢……第一次尝试用MindMap做笔记,感觉不错,不过效率还是不够高。老蒋说我笔记做得太详尽了,像抄书…反思中。

篇幅所限,书中还是有很多内容不够深入。一些内容需要另一本书的篇幅深入讨论(如重构),甚至需要看一本书才能从纸上谈兵转到实践(如TDD、Acceptance Tests)。。。望不到头的书单啊T_T

做笔记使用的工具是 XMind

单击这里进入下载页面。

(需要下载才能看到全部内容)


Tags:, , ,

[BHOJ10235] 窗口取数

Filed Under (新长征路上的代码) by 逆铭 on 2009-03-28 21:52

http://gallery.tomtung.com/pictures/sliding-window.jpg

窗口(超级版)

时间限制:5000 ms 内存限制:65535 KB

描述

有一串整数在排队……

有N个整数,你有一个可以框住M个连续段整数的木框,现在你想知道,对于这个队列中任意的连续M个整数,最大和最小的整数是哪个?

例:(M大小的窗口向右滑动)

1 2 3 2 1   M=2
1 2    MAX 2  MIN 1
2 3    MAX 3  MIN 2
3 2    MAX 3  MIN 2
2 1    MAX 2  MIN 1

最后需要输出的是两行:

1 2 2 1
2 3 3 2

输入

第一行包含一个整数T,表示有T组测试数据;
以下每组测试数据格式:
第一行包含2个整数N,M表示有N个整数在排队,取连续M个整数。
第二行包含N个整数。
其中N不大于1,000,000,M不大于N。

输出

按照题目描述格式输出结果,第一行为MIN,第二行为MAX。

样例输入

1
5 2
1 2 3 2 1

样例输出

1 2 2 1
2 3 3 2

提示

注意数据规模!

问题来源

软件学院07级数据结构第二次测试

题解

很久不考虑算法问题了的说……当然也好久不写解题报告了。上面这题让我体会到久违了的思考的乐趣(i.e.我废掉好久了>_<)

最显然的解法是利用平衡树始终保持木框内M个数的有序状态,大体是O(NlogM)的复杂度。这个不用多说。

问题是:能否找到O(N)的解法?在读下去之前你可以好好想想。

我纠结了好久,终于……也没想出来- – 尽管想出很多优化,终究还是不能达到要求。网上搜了下,看到 CS大牛csdn 上有人给出了一个解法(你也可以在读完全文后再回头看这一段):

是可以到o(n);

编程之美上有一个类似的问题:”队列中取最大值操作问题”;
实际上窗口移动就相当于对队列做了一次出队与入队操作,所以lz这道题可以套用该解法;

书上是使用两个栈来模拟队列,假设为分别A,B;
1)当入队的时候,push A;
2)当出队的时候,
a)若B非空,pop B,
b)若B为空,则先将A中的元素依次pop并push到B,再pop B;
这样,就使用两个栈达到了队列的功能;

同时,对于单个栈,由于pop,push都是在栈顶进行的,所以每个栈都可以方便地维护自己的最
大值与最小值在栈内的索引;
以最大值为例;
用max_idx保存栈内最大值的索引;
另使用一个跟栈的最大长度一样的数组idx,idx[i]表示栈的索引范围在0到i-1的元素中的最
大值的索引为idx[i];
1) 当push的时候,比较栈顶元素与栈的已保存的最大值,
a) 若栈顶元素大于已保存的最大值,那么idx[top]=max_idx,max_idx=top;
b) 若栈顶元素不大于已保存的最大值,那么idx[top]=max_idx(书上是idx[top]=-1);
2) 当pop的时候,max_idx=idx[top](书上是先比较top与max_idx,若top==max_idx,则
max_idx=idx[top]);

具体到这个题目;
可以使用两个长度为m的栈来模拟窗口;
1)将前m个元素依次push到栈A;
2)移动窗口就相当于分别进行pop B 与 push A操作;
3) 窗口的最大值==max[A.items[A.max_idx]),B.items[B.max_idx])];

由于每个元素最多进入两栈各一次;
所以总的复杂度是o(n)的;

这个解法的确可行,但描述仍不直观。为什么两个栈暧昧地眉来眼去一番,就在O(N)时间内把问题解决了呢?褪去实现上的种种细节,从更高的角度观察这个算法,它的思路是怎么样的呢?

和冬冬讨论了半天,似乎找到了一个比较靠近此算法根本动机的理解方向。如下:

一、简化

此问题中,框的左端和右端同时在向右移动。这时,要在任意时刻用O(1)的时间获得框内最小值(最大值同理,故仅以最小值为例)并不容易。但是,如果假设这个框是可伸缩的,将其一端固定,仅移动另外一端,任意时刻能否在O(1)的时间内获得最小值呢?

稍加思考,就发现要找到这样的算法是很容易的(事实上在我最初考虑时,就是试图在这样一个算法的基础上进行优化的)。

假如这个框是左端固定,右端不断向右移动的(称之为左定右动框)。对于框内的每个数,有一个对应指针指向框内它左边(包括自身)所有数中最小者。这样,框内最小值就是框内最右端元素对应的指针所指向的元素。如在下图中,最小元素就是右端7对应指针所指向的元素6。

http://gallery.tomtung.com/pictures/bhoj-10235_1.png

min = 6

当框的右端向右扩展时,可以通过递推获得新元素对应指针应指向的元素。以上图为例。加入2时,由7对应的指针可知原框中最小值为6,而6>2,所以新元素2对应的指针应指向自己。

http://gallery.tomtung.com/pictures/bhoj-10235_2.png

min = 2

加入8时,由2的指针知原框中最小值为2,而2<8,所以新元素8对应指针应指向2的指针所指向的元素2。

http://gallery.tomtung.com/pictures/bhoj-10235_3.png

min = 2

其余如法炮制。

http://gallery.tomtung.com/pictures/bhoj-10235_4.png

min = 2

可见,在框左端固定、右端不断扩展时,任意时刻都能在常数时间内确定框内元素的最小值。(不仅如此,即使需要让右端向左收缩,也能在常数时间内,通过最右端元素指针获得得到收缩后框内所剩元素的最小值。) 整个过程演示如下(先扩张后收缩):

http://gallery.tomtung.com/pictures/bhoj-10235_5.gif

演示中的收缩操作在当前问题中并无必要。演示出来是为了说明无论右端如何移动,只要左端不动,就可以在任意时刻立刻得到最小值。同时也是为了与下面一个演示保持一致。

如果一个框右端固定,左端收缩(称之为右定左动框),也可以在任意时刻花费常数时间获得框内的最小元素。过程和上面左右对称。具体地说,每个元素对应一个指针,指向框内它右边(包括自身)所有数中最小者。首先需要从右到左计算出各个元素对应的指针。这个过程类似于将框的左端由右向左扩展。计算完毕后就可以在收缩左端的过程中在常数时间内获得最小元素了。

演示如下(先扩张后收缩,可以看到完全和上面对称):

http://gallery.tomtung.com/pictures/bhoj-10235_6.gif

对于当前问题,在“扩张”过程中并不需要求最小值,而仅仅需要计算出指针来为收缩过程作准备。这里在全过程中标注出最小值是为了说明任意时刻都有能力得到它,也为了与上一个演示保持一致。

上面拉拉杂杂说了一堆,只为了说明:如果框的一端固定,仅移动另外一端,则在此过程中可以仅花费常数时间获得框内的最小元素。针对左定右移、右定左移两种情况的算法是彼此对称的。

最后顺带提一点实现细节,希望不会影响你对整体的理解。上面所使用的指针也可以用索引号来代替,这里使用指针是为了显得更形象。事实上,不保存索引或指针而直接保存最小的“值”也是可行的,但是并不推荐。设想,如果每个元素不是数而是四五米长的字符串(>_<),偏序关系使用字符串长度的比较,那么如果不保存指针/索引而保存值,所带来的元素复制开销恐怕不是你想要的。

二、推广

怎样把上面只允许一端移动的解法推广到同时允许两端移动呢?

一种想法是,将以上二者合二为一。具体地说,将框内的数看成左右两部分,左边一部分看成右定左动的,右边一部分看成左定右动的。这样,在左边收缩、右边扩张的过程中,左右两部分都可以在常数时间内得到最小元素。取两个最小元素中更小者,即为整个框中的最小元素。

OK,这就是O(N)算法的基本思路了。回到原题目,下面看一个例子。

假设我们要处理的是这么一串数:

3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3

框的大小为5。

首先,把最先被框住的5个数看成被一个右定左动的框框住,这5个数右边看成是一个空的左定右动框。当然,首先需要计算出开始这5个元素对应的指针:

http://gallery.tomtung.com/pictures/bhoj-10235_7.gif

接下来,左边的右定左动框收缩,右边的左定右动框扩张。在此过程中,框在框中的M个元素的最小者可以在常数时间内获得。

http://gallery.tomtung.com/pictures/bhoj-10235_8.gif

到左边部分收缩至空时就没有办法继续收缩了。怎么继续这个过程呢?

解决方法是,将右端含有M个元素的左定右动框重新处理为右定左动框,并在右边再放上一个空左定右动框:

http://gallery.tomtung.com/pictures/bhoj-10235_9.gif

接下来继续这个过程就行了。下面是全过程的演示:

http://gallery.tomtung.com/pictures/bhoj-10235_X.gif

你可能会对每次将右边的左定右动框重新处理为左边的右定左动框(有点晕- -)时计算指针造成的额外开销存有疑虑。然而,每次重新计算时需要处理M个元素的指针,每隔M个元素才会进行一次这样的处理,N/M*M仍然为N,并不会升高复杂度的阶。换句话说,每个元素至多被重新计算两次指针,所以总体复杂度仍然为O(N)。

以上就是整个解法。

三、关于栈

我们可以把右定左动框看成是一个底在右顶在左的栈,左端向左扩张看成是向栈push元素,左端向右收缩看成是栈在pop元素。左定右动框亦然。前面提到,两种框上的操作是对称的;如果把它们都看成栈,则push和pop时的操作完全一致。这样就将两种框对称的操纵统一在了同一个数据结构上,使得实现起来更为简洁。

这就是对开始所引用那段算法描述的解释。

四、问题的扩展

更进一步,我们还可以使用类似的方法处理此问题的变种。如,对于一个队列,随意进行入队出队操作(相当于木框的宽度不再固定为M,其两端也不同时向右移动),求在任意时刻队列中的最小元素。或者,一个双端队列,在两端随意进行入队出队操作,求任意时刻队列中的最小元素。等等。

最后顺便一提,在搜索此题资料时发现这个问题似乎涉及到数据流的处理算法。这个方面我一无所知,没办法站在那样一个高度阐述,抱歉。


Tags:, ,

小试模板元编程

Filed Under (新长征路上的代码) by 逆铭 on 2009-03-01 14:37

很久不更新了,再水一下吧。

前两天新开的数据结构课布置上机作业,里面又出现了不知道之前出现过多少次的输出质数。每次都交表也会很乏味,所以……这次还是要交表 – -||| 不过要玩一点小花样——让编译器在编译期把质数表算出来。

这个当然涉及到一点模板元编程了。之前虽然看了《Effective C++》里关于模板元的简介挺感兴趣,但看到《学习C++:实践者的方法》里告诫不要在这种“20%场景下的复杂性”上白花时间(“这些细节或技术在日常编程中极少用到,尤其是各种语言缺陷衍生出来的workarounds,构成了一个巨大的长尾……绝大多数只在库开发当中需要用到”),我一直对模板元编程敬而远之。这次也只是消遣一下,并无深入学习的打算。

以下是代码:

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

std::vector<int> Primes;

template <int toTest, int factor> // factor should be odd
class IsPrime
{
   public:
      enum {
         result = ( toTest == 2 )
         ||  toTest % factor
          && IsPrime < toTest , factor - 2 >::result
      };
};

template<int toTest>
class IsPrime<toTest, 1>
{
   public:
      enum {result = ( toTest == 2 )  || ( toTest & 1 ) };
};

template <int upperBound> // upperBound should be odd or 2
class PrimePick : public PrimePick < upperBound - 2 >
{
   public:
      enum {
         isPrime = IsPrime < upperBound, ( upperBound >> 1 ) | 1 >::result
      };
      PrimePick<upperBound>() {
         if ( isPrime )
            Primes.push_back ( upperBound );
      }
};

template<>
class PrimePick<2>
{
   public:
      PrimePick<2>() {
         Primes.push_back ( 2 );
      }
};

template<>
class PrimePick<1> : public PrimePick<2> {};

int main()
{
   PrimePick<999> PrimeInitializer;

   int m;
   std::cin >> m;
   for ( int i = 0; i < m; ++i ) {
      int n;
      std::cin >> n;

      std::vector<int>::iterator end = Primes.begin();
      while ( end != Primes.end() && *end <= n )
         ++end;

      std::ostream_iterator<int> out ( std::cout, " " );
      std::copy ( Primes.begin(), end, out );
      std::cout << std::endl;
   }
}

原理比较简单,主函数第一行初始化 PrimeInitializer 时,由于继承关系,构造函数会层层递归调用,从里至外利用另一个模板类 IsPrime 判断模板参数是否是质数,并把测试确认的质数放进一个 vector 里,这样就得到了编译期计算出的质数表。IsPrime 则使用最原始的试除方法判断质数。代码里一些写得很纠结的地方,一方面是为了尽量简化计算,减少编译时间,另一方面更主要是因为 g++ 默认最大只能实例化500层模板,以题目的数据规模(1000)不纠结一下编译器会抱怨。

这个程序……很不幸没能 AC。Buaa 的 OJ 在设计时估计就考虑了这种情况,对编译时间做出了限制,在编译20秒左右还没编译成功时会结束编译,直接判 CE……这个程序在我的系统上编译需要20分钟(VC 编译)到半个小时以上( g++ 编译)的时间,冬冬的64位 Ubuntu 上用 g++ 编译也需要将近10分钟时间。如果OJ不做这个限制估计会像当年vijos一样pending很多页吧……

恩,第一次模板元编程经历就以这样悲惨收场了 T_T

更新:

vijos果然被卡住了 – -

http://gallery.tomtung.com/pictures/vijos-puppy-stuck.jpg

可怜的puppy

已作为bug报告。

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

std::vector<int> Primes;

template <int toTest, int factor> // factor should be odd
class IsPrime
{
public:
enum {
result = ( toTest == 2 )
||  toTest % factor
&& IsPrime < toTest , factor – 2 >::result
};
};

template<int toTest>
class IsPrime<toTest, 1>
{
public:
enum {result = ( toTest == 2 )  || ( toTest & 1 ) };
};

template <int upperBound> // upperBound should be odd or 2
class PrimePick : public PrimePick < upperBound – 2 >
{
public:
enum {
isPrime = IsPrime < upperBound, ( upperBound >> 1 ) | 1 >::result
};
PrimePick<upperBound>() {
if ( isPrime )
Primes.push_back ( upperBound );
}
};

template<>
class PrimePick<2>
{
public:
PrimePick<2>() {
Primes.push_back ( 2 );
}
};

template<>
class PrimePick<1> : public PrimePick<2> {};

int main()
{
PrimePick<999> PrimeInitializer;

int m;
std::cin >> m;
for ( int i = 0; i < m; ++i ) {
int n;
std::cin >> n;

std::vector<int>::iterator end = Primes.begin();
while ( end != Primes.end() && *end <= n )
++end;

std::ostream_iterator<int> out ( std::cout, ” “ );
std::copy ( Primes.begin(), end, out );
std::cout << std::endl;
}
}


Tags:,

C++ Primer 读书笔记 – 第八章

Filed Under (新长征路上的代码) by 逆铭 on 2008-09-02 15:12

[笔记索引]

第8章 标准IO库

⒈ IO标准库类型
类名           派生自            头文件       描述
istream        ios               iostream   输入流
ostream        ios               iostream   输出流
iostream       istream和ostream  iostream   输入/输出流
ifstream       istream           fstream    输入文件流
ofstream       ostream           fstream    输出文件流
fstream        iostream          fstream    输入/输出文件流
istringstream  istream           sstream    输入字符串流
ostringstream  ostream           sstream    输出字符串流
stringstream   iostream          sstream    输入/输出字符串流

⑴ 标准库分别实现了以上继承层次的的两个版本,分别面向 char 类型(如上)和 wchar_t 类型
后者采用和前者一样的命名规则,仅在每个类和对象名前加上字母w以示区别
⑵ IO对象不可赋值或赋值

⒉ 标准输入输出对象
extern istream cin; 标准输入流
extern ostream cout; 标准输出流
extern ostream cerr;  标准错误输出流
extern ostream clog; 标准日志输出流

⒊ 文件输入输出流
⑴ 使用 ifstream, ofstream 和 fstream 型对象可以实现对文件的读写
⑵ 可以调用公共成员函数 open 或在创建时使用构造函数使流对象关联到一个文件
二者都接受两个参数:一个C风格字符串表示文件名,一个可选的 ios_base::openmode 型值表示流打开模式
① openmode 是用以表示流打开模式标识的位掩码类型,支持位操作符
该类型对象的值可以是以下打开模式标识或它们之间进行位操作所得的结果
和以下(openmode 型)模式标志均为 ios_base 的公共成员
Ⅰ app (append)每次执行输出操作前都定位到流的末尾
Ⅱ ate (at end)打开时定位到流末尾
Ⅲ binary 在二进制(而非文本)模式下操作
Ⅳ in (input)允许输入操作
Ⅴ out (output)允许输出操作
Ⅵ trunc (truncate)抛弃流中现存的所有内容,打开时假定长度为0
(openmode 和上述标识分别是 ios_base 的公共成员类型和公共成员常量)
② fstream 默认使用 in | out 模式
ifstream 自动使用 in 模式
ofstream 自动使用 out 模式(效果上等同于 out | trunc)
⑶ 完成文件操作后可以使用公共成员函数 void close() 关闭
关闭后可以再次 open 新文件,但之前应调用 clear 清除该流的状态
流对象被析构时也会自动调用 close()

⒋ 字符串流
⑴ 常用于特定数据类型和格式化的字符串之间的相互转换
3个类的构造函数各有带或不带 string 形参的两个不同版本
使用带 string 参数的版本,则创建储存该实参副本的字符串流对象
3个类的都有公共成员函数 str,它的两个版本分别返回和设置与该流对象相关的 string 对象
string str() const;
返回与该流对象相关的 string 对象
string str(const string&);
将该流对象相关的 string 对象设置为实参的副本

⒌ 条件状态
⑴ 流对象内都用一个 iostate 型值表示条件状态
· iostate 是用以表示流错误状态标志的位掩码类型,支持位操作符
该类型对象的值可以是以下条件状态标识或它们之间进行位操作所得的结果
Ⅰ eofbit 输入操作中遇到文件结束符(同时会设置failbit)
Ⅱ failbit 输入操作失败(通常可恢复)
Ⅲ badbit 流被损坏(通常不可恢复)
Ⅳ goodbit 没有错误,值为0
(iostate 和上述标识分别是 ios_base 的公共成员类型和公共成员常量)
⑵ 查询条件状态
bool ios::eof() const;
eofbit位被设置则返回 true, 否则返回 false
bool ios::fail() const;
failbit 或 badbit 位被设置则返回 true, 否则返回 false
bool ios::bad() const;
badbit 位被设置则返回 true, 否则返回 false
bool ios::good() const;
流对象有效则返回 true, 否则返回 false
ios_base::iostate rdstate() const;
返回当前条件状态
⑶ 控制条件状态
void ios::clear( ios_base::iostate = goodbit);
使用参数值替换当前条件状态
void ios::setstate( ios_base::iostate );
将参数状态加入当前状态(如同使用了位或操作符|)
⑷ 可以直接检查流对象的真值来确认是否可用

⒍ 输出缓冲区的管理
每个IO对象都管理一个缓冲区,作为流和写入目标间的媒介
下面几种情况将导致输出流被刷新(即写入到真实的输出设备或者文件)
⑴ 程序正常结束或目标文件被关闭
注: 程序崩溃不会刷新缓冲区
调试崩溃的程序时如果用最后的输出定义错误位置,应确保缓冲区已刷新
因此输出时应多使用endl而非‘\n’
⑵ 当缓冲区满时会自动刷新
⑶ 使用操纵符显式刷新缓冲区
(以下均定义在<ostream>中)
① endl 输出换行符并刷新缓冲区
② flush 刷新缓冲区(不添加任何字符)
③ ends 输出空字符‘\0′并刷新缓冲区
注:cplusplus.com,cppreference.com和msdn上都没有表示ends会刷新缓冲区,存疑
⑷ 使用 unitbuf 操纵符可以设置流对象的相关格式状态标志,使缓冲区在每次插入操作后都刷新
使用 nounitbuf 操纵符可以恢复为正常的、系统控制的缓冲区刷新方式
⑸ 可以把流对象绑定到一个输出流对象,前者进行任何I/O操作都会刷新后者
① 可以使用 ios::tie 公共成员函数查询、修改绑定状态
ostream* ios::tie() const;
返回指向所绑定到的输出流对象的指针
ostream* ios::tie( ostream* );
绑定到参数中指针指向的输出流对象(实参为0则解除绑定),并返回指向原绑定对象的指针
② cin, cerr, clog 默认绑定到 cout, 而它们的宽字符版本则默认绑定到 wcout

⒎ 控制格式状态
istream/ostream(及它们的派生类)对象都支持使用提取操作符(>>)/插入操作符(<<),从流中提取/向流中插入格式化的对象数据
把提取/插入操作符和一些操纵符(manipulator)一起使用,可以改变流对象的特性或格式设置
其中除setbase,setprecision,setw,setfill定义在头文件<iomanip>中外,其余操纵符均定义在<ios>中
⑴ 布尔值格式
从流中提取/向流中插入布尔值时形式使用 0, 1(默认) 还是 false, true
· 使用操纵符 boolalpha noboolalpha
⑵ 整数格式
① 基数设置
从流中提取/向流中插入整数时使用八进制、十进制(默认)还是十六进制
· 使用操纵符 oct dec hex
· 使用操纵符 setbase(int) (实参只能是8,1016)
② 基数前缀设置
向流中插入整数时是否显示基数前缀(八进制的0和十六进制的0x)(默认不显示)
· 使用操纵符 showbase noshowbase
③ 十六进制和科学计数法中字母的大小写设置
向流中插入整数时若使用十六进制或科学计数法,出现的字母使用小写(默认)还是大写
· 使用操纵符 uppercase nouppercase
⑶ 浮点数格式
① 精度设置
向流中插入浮点数精度为多少(默认为6)
· 使用 ios_base 的公共成员函数 precision 可以获得和设置流的浮点数精度
streamsize precision() const;
返回流对象的浮点数精度
streamsize precision( streamsize );
设置流的浮点数精度为实参值并返回先前的精度值
· 使用操纵符 setprecision( streamsize )
以上 streamsize 为表示流中大小的实现类型,是一个带符号基本整型的别名
注: 精度的解释在使用不同情况下不同
· 默认情况下解释为最大有效数字(位数不足不用小数末尾的零补齐)
· 在强制使用fixed,scientific或showpoint时解释为小数点后的确切位数(位数不足则用小数末尾的零补齐)
② 计数法设置
向流中插入浮点数时,让系统自动选择计数法(默认),还是强制指定为定点计数法或科学计数法
· 使用操纵符 fixed scientific
· 计数法不存在恢复默认的操纵符,但可以通过给成员函数 unsetf 传入常量 floatfield 达到目的
(unsetf 和 floatfield 均为 ios_base 的公共成员)
③ 小数点显示设置
向流中插入浮点数时,若小数部分为0,是否显示小数点和小数部分的0(默认不显示)
· 使用操纵符 showpoint noshowpoint
④ 非负数(整数和浮点数)正号设置
向流中插入非负数(包括0)时是否显示正号+ (默认不显示)
· 使用操纵符 showpos noshowpos
⑷ 对齐和填充格式
① 使用操纵符 setw( streamsize ) 可指定下次对流的插入操作的最小字段长度
注: 和endl一样,它不改变流的内部状态,只影响下一个输出
② 使用操纵符 setfill( char ) 设置向流中插入对象时用来填充字段的字符(默认为空格)
③ 字段内对齐方式设置(默认左对齐)
· 使用操纵符left right internal
关于internal:
- 对数值表示左对齐前缀(正负号和十六进制的0x)右对齐数值
- 对非数值则与 right 等效
⑸ 空白字符的处理
· 使用操纵符 skipws noskipws 设置从流中提取对象时是否忽略空白字符(默认忽略)
· 使用操纵符 ws 忽略输入序列中从当前位置开始尽可能多的空白字符,直到遇到非空白字符才停止
当然,如果已经(默认)设置了 skipws 则没有必要使用 ws

⒏ 无格式I/O操作
⑴ 单字节操作
① istream& get ( char& );
istream 的公共成员函数,从流中提取一个字符存入实参中
int get( );
无参数版本从流中提取一个字符并返回其值
② ostream& put ( char );
ostream 的公共成员函数,将字符参数值写入输出缓冲区并返回*this
③ istream& putback ( char );
istream 的公共成员函数,将字符参数值放回流中并返回*this
④ istream& unget ( );
istream 的公共成员函数,将流退回1字节并返回*this
int peek ( );
istream 的公共成员函数,读取并返回流中的下一个字符,但不提取它
注: 为允许返回EOF,put(无参数版本)和peek的返回值都为 int
⑵ 多字节操作
① istream& get (char* s, streamsize n, char delim );
istream 的公共成员函数,不断从流中提取字符并以字符串形式存入s指向首地址的数组,
直到出现以下情况之一:
- 已经提取了(n-1)个字符;
- 遇到了定界字符delim(如果不提供此参数则为‘\n’);
(被找到的定界字符不会被提取,而是继续留在流中作为下一个要被提取的字符)
- 到达文件末尾;
- 提取过程中发生错误;
最后返回*this.
注: 提取的字符以字符串的形式储存,故将自动添加结尾的‘\0′,而被提取的最大字符数也为(n-1)而非n
② istream& getline (char* s, streamsize n, char delim );
istream 的公共成员函数,与上面参数表相同的get版本类似,唯一区别在于定界字符会被提取并丢弃
③ istream& read ( char* s, streamsize n );
istream 的公共成员函数,从流中提取n个字符存入s指向首地址的数组,
直到出现以下情况之一:
- 已经提取了n个字符
- 到达文件末尾(eofbit和failbit会被设置)
- 提取过程中发生错误
最后返回*this.
注: 与get和getline不同,read不把提取的字符保存为字符串,故不会自动加上‘\0′
④ streamsize  gcount ( ) const;
istream 的公共成员函数,返回上次无格式输入操作提取的字符个数
(peek, putback, unget不提取字符,故对此gcount返回0)
⑤ ostream& write ( const char* s , streamsize n );
ostream 的公共成员函数,把s指向首地址的数组的前n个字符写入输出流缓冲区
⑥ istream&  ignore ( streamsize n = 1, int delim = EOF );
istream 的公共成员函数,从流中提取并抛弃字符,直到:
- 已经提取了n个字符或遇到了定界字符delim(该字符不会被提取)
最后返回*this.

⒐ 流的随机访问
⑴ 用于随机访问的成员函数
· pos_type tellg ();
pos_type tellp ();
返回输入/输出流中的标记当前的绝对位置
· istream& seekg ( pos_type pos );
ostream& seekp ( pos_type pos );
将输入/输出流中的标记重新定位至参数值表示的位置
· istream& seekg ( off_type off, ios_base::seekdir dir );
ostream& seekp ( off_type off, ios_base::seekdir dir );
将输入/输出流中的标记重新定位至距离dir偏移量为off的位置
⑵ 关于上述函数参数和返回值的类型
① pos_type和off_type都是类的成员类型,分别表示流中的标记位置和偏移量
后者可正可负,分别表示向前和向后偏移
② seekdir类型用来表示偏移的启示位置,其可能值如下
beg 流的开头
cur 流的当前位置
end 流的末尾
⑶ 以上tell和seek版本分别有两个版本g(get)和p(put)
其区别在于g版是istream类的成员,而p版是ostream类的成员
对于iostream对象,而者都适用,它们都操作同一个标记(而非输入输出各一个)
⑷ 普通iostream对象一般不允许随机访问,以上内容主要适用fstream和sstream


Tags:,

C++ Primer 读书笔记 – 第七章

Filed Under (新长征路上的代码) by 逆铭 on 2008-08-28 17:14

[笔记索引]

第7章 函数

㈠ 函数的声明和定义
⒈ 与变量类似:
⑴ 函数必须在调用前声明
⑵ 函数声明可与定义分离
⑶ 一个函数只能定义一次但可声明多次
⒉ 函数声明由函数返回类型、函数返回类型和形参列表组成
三者描述了函数的接口,称为函数原型(function prototype)
⑴ 函数的操作数,即形参(parameter),在一对圆括号中声明,并以逗号分隔
形参名是可选的,但形参需要在定义函数时命名才能使用
⑵ 函数执行的运算在一个称为函数体(function body)的块语句中定义
⒊ 函数一般在头文件中声明,在源文件中定义
此时应使后者包含前者,以便编译器检查定义和声明是否一致
⒋ 将一个较小的、常被调用的函数指定为
inline 可使函数在调用点展开,以避免调用函数的额外开销
但内联说明对编译器来说只是一个建议,编译器也可能选择忽略

㈡ 函数的调用和参数传递
⒈ 函数的调用
⑴ 使用调用操作符()实现函数调用
① 操作数是函数名和一组(可能为空的)用逗号分开的实参(argument)
② 结果类型为函数返回值的类型,结果为函数的返回值
⑵ 函数调用做两件事:
① 首先(隐式)定义形参,并用对应的实参进行初始化
· 形参的初始化与变量一样:
如果形参为非引用类型则复制实参的值,如果形参为引用类型则它只是实参的别名
② 主调函数(calling function)的执行被挂起,被调函数(called function)开始执行
⒉ 非引用形参
⑴ 指针形参
① 可以通过传入指针来间接访问指针所指对象
② 若需要保护指针指向的对象,可指定形参为指向 const 对象的指针
const 形参
① 用来初始化 const 形参的实参无论是不是 const 都可以
在函数中不能改变该实参的局部副本
② 非引用形参是否为 const 不影响编译器对其所属函数类型的判别
·注:对于指针,注意区分 const 是修饰指针本身的性质还是所指对象的性质
⑶ 不适合复制实参的情况
① 需要修改实参值时
② 需要将大型对象作为实参传递时
③ 无法复制对象时
以上情况可通过将形参定义为指针或引用类型解决
⒊ 引用形参
⑴ 可以使用引用形参返回额外信息
⑵ 可以利用 const 引用形参避免复制实参
⑶ 使用引用形参时,将不需要修改的定义为 const 会更灵活
const 引用形参不能用 const 左值或右值初始化,而定义为 const 则无此问题
⒋ 一般通过传递迭代器来传递 vector 等容器
⒌ 数组形参
⑴ 当把数组直接作为实参传给函数时:
① 若形参不是数组的引用,则自动转换为指向首元素的指针
例如以下三种定义完全等价:
void f(int*); //推荐,明确指出操作对象为指针
void f(int[]);
void f(int[10]);//维数被编译器忽略,写法容易引起误解
② 若形参是数组的引用则不发生转换,函数得到的是数组本身
此时编译器会检查形参和实参数组的维数是否匹配
⑵ 把数组传递给函数处理时,可以:
⒈ 使用标准库规范,即传入首元素指针和超出末端指针
⒉ 传递数组首元素地址,并显式传递表示数组大小的形参
⑶ 可以通过传递指向数组的指针来传递多维数组
⒍ 含有可变形参的函数
为兼容C语言而保留的特性,只能传入简单数据类型,大多数类类型对象都不能正确复制
⒎ 默认实参
⑴ 通过给形参表中的形参提供明确的初值可指定默认实参,如
void f( int, int = 1, int = 2 );
① 默认实参可以是任何适当类型的表达式
② 如果一个形参有默认实参,则其后的所有形参都必须有默认实参
③ 在一个文件中,只能为一个形参指定默认实参一次
通常应在函数声明中指定默认实参,并将该声明放在合适的头文件中
⑵ 调用函数时可省略有默认值的实参:若省略则使用默认实参,否则使用用户提供的实参
· 默认实参只能用来替换函数调用缺少的靠后的实参
因此设计带有默认实参的函数时,应将最少使用默认实参的形参排在最前,最多使用默认实参的形参排在最后

㈢ 局部对象的作用域和生命期
⒈ 函数体是一个作用域,在其中定义的变量称为局部变量(local variable),只可在该函数中访问
局部变量和形参均不能重名
⒉ 自动对象(automatic objects)是局部于函数的对象,会在每次函数调用时重新创建,并在函数结束时撤销
非静态局部变量和形参都是自动对象
static 局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时初始化,一旦创建在程序结束前都不会撤销
在所在函数被多次调用的过程中,static 局部对象会持续存在并保持它的值

㈣ 函数的返回值和 return 语句
⒈ 函数返回类型可为内置类型、类类型或复合类型,但不能是另一个函数或内置数组类型
void 类型表示该函数不返回任何值
return 语句
用于结束当前函数,将控制权返回给该函数的主调函数
⑴ 没有返回值的 return 语句
① 只能用于返回类型为 void 的函数
② 非必需,隐式 return 发生在函数最后一个语句完成时
⑵ 具有返回值的 return 语句
① 返回类型不是 void 的函数必须返回一个值(主函数 main 除外)
注:应在函数中的每条执行路径末尾都提供 return 语句;若不提供编译器可能也无法发现
② 用函数返回值初始化在调用函数处创建的临时对象,与用实参初始化形参的方法一样
即若返回非引用类型则得到副本,若返回引用类型则得到对象本身
③ 切勿返回局部对象的引用或指针

㈤ 函数的重载
⒈ 出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则为重载函数(overloaded function)
仅仅是返回类型不同、默认实参不同、非引用形参的 const 性质不同不能实现重载
⒉ 若局部地声明函数,则该函数屏蔽而非重载在外层作用域中声明的同名函数
⒊ 重载确定的三个步骤
⑴ 确定候选函数集合
候选函数(candidate function)是与被调函数同名的函数,且在调用点上声明可见
⑵ 从候选函数集合中选择可行函数(找不到则此调用错误)
可行函数(viable function)满足两个条件:
① 函数形参与该调用的实参个数匹配(考虑默认实参)
② 每个实参的类型须与对应形参匹配(类型相同或可隐式转换)
⑶ 寻找最佳匹配(有多个匹配程度相同的则调用有二义性)
原则是实参类型与形参类型越接近则匹配越佳。具体匹配程度从高到低为:
① 精确匹配:实参与形参类型相同
② 通过类型提升实现的匹配
③ 通过标准转换实现的匹配
④ 通过类类型转换实现的匹配

㈥ 函数指针
⒈ 定义形式
返回类型 (*标识符)(形参表)
⒉ 可使用 typedef 简化定义
typedef 返回类型 (*自定义类型名)(形参表)
⒊ 对函数指针进行初始化和赋值,可使用:
⑴ 同类型的函数
引用函数名但不调用该函数时,函数名被自动解释为指向函数的指针
因此可以直接使用函数名对函数指针初始化或赋值,不需要使用取地址操作符&
·注:指针类型须与函数完全匹配
⑵ 同类型的函数指针
0值常量表达式
⒋ 通过函数指针调用函数
可以不使用解引用操作符,直接使用函数调用操作符()调用所指函数
⒌ 函数指针作形参时,星号*可写可不写
⒍ 函数的指针作函数返回值
函数指针返回类型 (*函数名(函数形参表))(函数指针形参表)

㈦ 主函数 main
⒈ 返回值
⑴ 主函数返回类型为 int, 返回值在大多数系统中是一个状态指示器
返回0表示程序运行成功,其它大部分值表示失败
⑵ 不需要显式使用 return 语句,编译器将隐式插入返回0的语句
⑶ 为使返回值独立于机器,cstdlib 头文件定义了两个预处理变量
EXIT_FAILURE(运行失败)    EXIT_SUCCESS(运行成功)
⒉ 使用主函数形参处理命令行选项
int main(int argc, char* argv[])
· argv为一个C风格字符串数组,储存了从命令行调用程序时输入的字符串(包括程序名和参数)
· argc表示argv中字符串的个数
⒊ 主函数 main 不允许被显式调用、取地址或重载


Tags:,

C++ Primer 读书笔记 – 第六章

Filed Under (新长征路上的代码) by 逆铭 on 2008-08-24 17:01

[笔记索引]

第6章 语句
㈠ 简单语句
⒈ 表达式语句(expression_r statement)
一个表达式加上结尾的分号,执行时导致该表达式被求值
⒉ 空语句(null statement)
只由一个单独的分号组成,当语法上需要一个语句但逻辑上并不需要时使用
⒊ 声明语句
用于声明或定义对象或类
㈡ 复合语句
⒈ 复合语句(compound statement)又被称为块(block),是用一对花括号{}括起的(可能为空的)语句序列
⒉ 通常用于语法规则要求使用单个语句但程序逻辑需要多个语句时
⒊ 块标示了一个作用域,在块中引入的名字只能在其内部访问
㈢ 控制流语句
注:作为语句控制结构的一部分定义的变量,仅在该语句内可见
⒈ 条件分支结构
if 语句
关于 else-if 匹配的二义性问题: else 匹配给最后出现尚未匹配的 if
switch 语句
switch 在计算表达式的值后跳转到匹配的标号处(无匹配则跳转至 default),并从该点开始一直执行下去,
直至 switch 语句结束或遇到 break 语句
switch 求解表达式的结果须为整型,每个 case 标号的值也须为各不相同的整型常量表达式
switch 内部的变量定义
· 可以在 switch 求解的表达式中定义和初始化变量
· 为防止跳过变量定义,只允许在最后一个标号后定义变量
· 也可以引入块语句,在其中定义变量
⒉ 循环
while 语句
注:循环条件中定义的变量在每次循环时都要经历创建和撤销的过程
for 循环语句
注:语句头中的初始化语句、循环条件和表达式三者都可以省略
循环条件省略表示永远为 true
do while 语句
注:不能在循环条件中定义变量
break 语句
用于结束最近的外围 while, do while, forswitch 语句,并在该语句后继续执行
continue 语句
导致最近的外围循环语句(for, while, do while)正在进行的这次迭代提前结束
goto 语句
goto 语句提供了函数内部的无条件跳转,实现从 goto 语句跳转到同一函数内某个带标号的语句
除非有足够理由,应避免使用 goto 语句
⑵ 在任何语句前提供一个标识符和冒号,就得到一个带标号的语句(labeled statement)
标识符: 语句
使用 goto 语句跳转到该语句: goto 标识符;
由于这里的标识符只能用作 goto 的目标,因此可以与其它类型的标识符(如变量名)同名
goto 语句不能跨越变量的定义语句向前跳转
若确实需在 goto 和跳转目标位置间定义变量,则须定义在块中
try, catch 语句和 throw 表达式
用于异常处理
return 语句
用于结束当前函数,返回函数被调用处继续执行

⒍⒕ 使用预处理器进行调试
⒈ 使用 NDEBUG 预处理变量实现有条件的调试代码(类似头文件保护符)
#ifndef NDEBUG
#define NDEBUG
// 调试代码
#endif
如果定义了 NDEBUG 就不执行调试代码
⒉ 使用 NDEBUG 预处理变量以及 assert 预处理宏
定义在头文件cassert中,常用来检查不可能发生的状况,形式为
assert(表达式)
如果表达式结果为 false, assert 输出信息并终止程序
如果定义了 NDEBUG 预处理变量,assert 将被忽略,不会产生任何运行时代价
⒊ 预处理器定义了四种在调试时有用的常量
__FILE__ 文件名
__LINE__ 当前行号
__TIME__ 编译时间
__DATE__ 编译日期


Tags:,
Get Adobe Flash playerPlugin by wpburn.com wordpress themes

Search:

Codes, Notes & Scribbles Rss