2011年6月26日星期日

Visual Studio 2005/2008/2010 常用快捷键

Alt + Shift + Enter 全屏,第二次退出全屏
Ctrl + E,D
Ctrl + K,D 格式化当前所有代码
Ctrl + E,F
Ctrl + K,F 格式化选中代码
Ctrl + E,C
Ctrl + K,C 批量注释
Ctrl + E,U
Ctrl + K,U 批量取消注释
Ctrl + Shift + U
Ctrl + U
对选定的字符串进行大写小写的切换.
将光标放在各种成对出现的符号前,按:Ctrl + ] 可实现光标在配对符号间切换,如() <> {}
同上:Ctrl + Shift + ] 实现选中,这样就不用再去找{的结尾在哪了。
Ctrl + Shift + F9删除所有断点
Ctrl + Shift + L删除当前行
F7 从设计模式到代码模式
Shift + F7 从代码模式到设计模式
在任意位置:Ctrl + Enter,当前行上边加一空行,同时光标定位
在任意位置:Ctrl + Shift + Enter,当前行下边加一空行,同时光标定位
Ctrl + K + K :收藏(Bookmark)
F5调试
Shift + F5 退出调试
Ctr+G 查找某行。
Ctrl+R+E 封装字段
调试快捷键
F6: 生成解决方案
Ctrl+F6: 生成当前项目
F7: 查看代码
Shift+F7: 查看窗体设计器
F5: 启动调试
Ctrl+F5: 开始执行(不调试)
Shift+F5: 停止调试
Ctrl+Shift+F5: 重启调试
F9: 切换断点
Ctrl+F9: 启用/停止断点
Ctrl+Shift+F9: 删除全部断点
F10: 逐过程
Ctrl+F10: 运行到光标处
F11: 逐语句编辑快捷键
Shift+Alt+Enter: 切换全屏编辑
Ctrl+B,T / Ctrl+K,K: 切换书签开关
Ctrl+B,N / Ctrl+K,N: 移动到下一书签
Ctrl+B,P: 移动到上一书签
Ctrl+B,C: 清除全部标签
Ctrl+I: 渐进式搜索
Ctrl+Shift+I: 反向渐进式搜索
Ctrl+F: 查找
Ctrl+Shift+F: 在文件中查找
F3: 查找下一个
Shift+F3: 查找上一个
Ctrl+H: 替换
Ctrl+Shift+H: 在文件中替换
Alt+F12: 查找符号(列出所有查找结果)
Ctrl+Shift+V: 剪贴板循环
Ctrl+左右箭头键: 一次可以移动一个单词
Ctrl+上下箭头键: 滚动代码屏幕,但不移动光标位置。
Ctrl+Shift+L: 删除当前行
Ctrl+M,M: 隐藏或展开当前嵌套的折叠状态
Ctrl+M,L: 将所有过程设置为相同的隐藏或展开状态
Ctrl+M,P: 停止大纲显示
Ctrl+E,S: 查看空白
Ctrl+E,W: 自动换行
Ctrl+G: 转到指定行
Shift+Alt+箭头键: 选择矩形文本
Alt+鼠标左按钮: 选择矩形文本
Ctrl+Shift+U: 全部变为大写
Ctrl+U: 全部变为小写
代码快捷键
Ctrl+J / Ctrl+K,L: 列出成员
Ctrl+Shift+空格键 / Ctrl+K,P: 参数信息
Ctrl+K,I: 快速信息
Ctrl+E,C / Ctrl+K,C: 注释选定内容
Ctrl+E,U / Ctrl+K,U: 取消选定注释内容
Ctrl+K,M: 生成方法存根
Ctrl+K,X: 插入代码段
Ctrl+K,S: 插入外侧代码
F12: 转到所调用过程或变量的定义
窗口快捷键
Ctrl+W,W: 浏览器窗口
Ctrl+W,S: 解决方案管理器
Ctrl+W,C: 类视图
Ctrl+W,E: 错误列表
Ctrl+W,O: 输出视图
Ctrl+W,P: 属性窗口
Ctrl+W,T: 任务列表
Ctrl+W,X: 工具箱
Ctrl+W,B: 书签窗口
Ctrl+W,U: 文档大纲
Ctrl+D,B: 断点窗口
Ctrl+D,I: 即时窗口
Ctrl+Tab: 活动窗体切换
Ctrl+Shift+N: 新建项目
Ctrl+Shift+O: 打开项目
Ctrl+Shift+S: 全部保存
Shift+Alt+C: 新建类
Ctrl+Shift+A: 新建项

Merge Sort

void mergeSort(int *sortedArr, int start, int end)
{
    if(start == end)
    {
        return;
    }
   
    int mid = (int)((start + end) / 2);
    mergeSort(sortedArr, start, mid);
    mergeSort(sortedArr, mid + 1, end );
    int *tempArr = new int[end - start + 1];
    int i = start;
    int j = mid + 1;
    int k = 0;
    while(i <= mid && j <= end)
    {
        if(sortedArr[i] < sortedArr[j])
        {
            tempArr[k] = sortedArr[i];
            i++; k++;
        }
        else
        {
            tempArr[k] = sortedArr[j];
            j++; k++;
        }
    }
    if(i == mid + 1)
    {
        for(; j <= end; j++, k++)
            tempArr[k] = sortedArr[j];
    }
    else
    {
        for(; i <= mid; i++, k++)
            tempArr[k] = sortedArr[i];
    }
    k = 0;
    for(int i = start; i <= end; i++, k++)
        sortedArr[i] = tempArr[k];
    delete[] tempArr;
}

2011年6月24日星期五

【转】解决hash冲突的策略

1 Overview
      Open addressing和Chaining是两种不同的解决hash冲突的策略。当多个不同的key被映射到相同的slot时,chaining方式采用链表保存所有的value。而Open addressing则尝试在该slot的邻近位置查找,直到找到对应的value或者空闲的slot, 这个过程被称作probing。常见的probing策略有Linear probing,Quadratic probing和Double hashing。
2 Chaining
2.1 Chaining in java.util.HashMap
      在分析open addressing策略之前,首先简单介绍一下大多数的Java 核心集合类采用的chaining策略,以便比较。 java.util.HashMap有一个Entry[]类型的成员变量table,其每个非null元素都是一个单向链表的表头。
  • put():如果存在hash冲突,那么对应的table元素的链表长度会大于1。
  • get():需要对链表进行遍历,在遍历的过程中不仅要判断key的hash值是否相等,还要判断key是否相等或者equals。
  • 对于put()和get()操作,如果其参数key为null,那么HashMap会有些特别的优化。
      Chaining策略的主要缺点是需要通过Entry保存key,value以及指向链表下个节点的引用(Map.Entry就有四个成员变量),这意味着更多的内存使用(尤其是当key,value本身使用的内存很小时,额外使用的内存所占的比例就显得比较大)。此外链表对CPU的高速缓存不太友好。
3 Open Addressing
3.1 Probing
3.1.1 Linear probing
      两次查找位置的间隔为一固定值,即每次查找时在原slot位置的基础上增加一个固定值(通常为1),例如:P = (P + 1) mod SLOT_LENGTH。其最大的优点在于计算速度快,另外对CPU高速缓存更友好。其缺点也非常明显:
      假设key1,key2,key3的hash code都相同并且key1被映射到slot(p),那么在计算key2的映射位置时需要查找slot(p), slot(p+1),计算key3的映射位置时需要查找slot(p), slot(p+1),slot(p+2)。也就是说对于导致hash冲突的所有key,在probing过程中会重复查找以前已经查找过的位置,这种现象被称为clustering。

3.1.2 Quadratic probing
      两次查找位置的间隔线性增长,例如P(i) = (P + c1*i + c2*i*i) mod SLOT_LENGTH,其中c1和c2为常量且c2不为0(如果为0,那么降级为Linear probing)。 Quadratic probing的各方面性能介于Linear probing和Double hashing之间。

3.1.3 Double hashing 
       两次查找位置的间隔为一固定值,但是该值通过另外一个hash算法生成,例如P = (P + INCREMENT(key)) mod SLOT_LENGTH,其中INCREMENT即另外一个hash算法。以下是个简单的例子:
      H(key) = key mod 10
      INCREMENT(key) = 1 + (key mod 7)
      P(15): H(15) = 5;
      P(35): H(35) = 5, 与P(15)冲突,因此需要进行probe,位置是 (5 + INCREMENT(35)) mod 10 = 6
      P(25): H(25) = 5, 与P(15)冲突,因此需要进行probe,位置是 (5 + INCREMENT(25)) mod 10 = 0
      P(75): H(75) = 5, 与P(15)冲突,因此需要进行probe,位置是 (5 + INCREMENT(75)) mod 10 = 1
      从以上例子可以看出,跟Linear probing相比,减少了重复查找的次数。
3.2 Load Factor
      基于open addressing的哈希表的性能对其load factor属性值非常敏感。如果该值超过0.7 (Trove maps/sets的默认load factor是0.5),那么性能会下降的非常明显。由于hash冲突导致的probing次数跟(loadFactor) / (1 - loadFactor)成正比。当loadFactor为1时,如果哈希表中的空闲slot非常少,那么可能会导致probing的次数非常大。

2011年6月21日星期二

<转>二叉树的层次遍历

1. 首先将root节点enqueue。
2.循环, 直到queue为空:
从queue中dequeue一个节点并访问。如果此节点存在left child, 将其enqueue。如果存在right child, 将其enqueue。


/*按层次遍历二叉树,借助队列来实现,为简便编程,借助数组实现队列功能*/
#include "stdio.h"
#include "conio.h"
#include "stdlib.h"
typedef char TElemType;
typedef struct BiTNode
{
     TElemType data;
     struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
/*先序创建二叉树*/
void createBiTree(BiTree *T)
{
     char c;
     scanf("%c",&c);
     if(c!='#')
    {
         *T=(BiTree)malloc(sizeof(BiTNode));
         (*T)->data=c;
         createBiTree(&((*T)->lchild));
         createBiTree(&((*T)->rchild));
     }
    else
         *T=NULL;
}
/*先序遍历二叉树*/
void preOrder(BiTree T)
{
if(T)
{
    printf("%c",T->data);
    preOrder(T->lchild);
    preOrder(T->rchild);
}
}
/*按层遍历*/
void levelTraverse(BiTree T)
{
    BiTree Queue[20];             
    BiTree p;
    int front=0,rear=0;           /*表示队头指针和队尾指针*/
    if(T)
   {
        p=T;                         /*根指针入队*/
       Queue[rear]=p;
        rear=(rear+1)%20;         /*队尾指针加一对20取余,可实现循环队列,合理利用空间*/
        while(front!=rear)          /*队不空*/
        {
             p=Queue[front];           /*出队,将值赋给p*/
             printf("%c",p->data);
             front=(front+1)%20;
             if(p->lchild)              /*如果p有左子树,将左子树入队*/
            {
                  Queue[rear]=p->lchild;
                   rear=(rear+1)%20;
             }
             if(p->rchild)                  /*如果p有右子树,将右子树入队*/      {
             Queue[rear]=p->rchild;
             rear=(rear+1)%20;
        }
    }
}
}
main()
{
     BiTree T;
     createBiTree(&T);
     preOrder(T);
     printf("\n");
     levelTraverse(T);
     getch();
}

<转>关于C++中函数指针的使用(包含对typedef用法的讨论)

(一)简单的函数指针的应用。
//形式1:返回类型(*函数名)(参数表)
char (*pFun)(int);
char glFun(int a){ return;}
void main()
{
    pFun = glFun;
    (*pFun)(2);
}

        第一行定义了一个指针变量pFun。首先我们根据前面提到的“形式1”认识到它是一个指向某种函数的指针,这种函数参数是一个int型,返回值是char类型。只有第一句我们还无法使用这个指针,因为我们还未对它进行赋值。
        第二行定义了一个函数glFun()。该函数正好是一个以int为参数返回char的函数。我们要从指针的层次上理解函数——函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。
        然后就是可爱的main()函数了,它的第一句您应该看得懂了——它将函数glFun的地址赋值给变量pFun。main()函数的第二句中“*pFun”显然是取pFun所指向地址的内容,当然也就是取出了函数glFun()的内容,然后给定参数为2。
(二)使用typedef更直观更方便。
//形式2:typedef 返回类型(*新类型)(参数表)
typedef char (*PTRFUN)(int);
PTRFUN pFun;
char glFun(int a){ return;}
void main()
{
    pFun = glFun;
    (*pFun)(2);
}

        typedef的功能是定义新的类型。第一句就是定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,这种函数以一个int为参数并返回char类型。后面就可以像使用int,char一样使用PTRFUN了。
        第二行的代码便使用这个新类型定义了变量pFun,此时就可以像使用形式1一样使用这个变量了。
(三)在C++类中使用函数指针。
//形式3:typedef 返回类型(类名::*新类型)(参数表) class CA
{
 public:
    char lcFun(int a){ return; }
};
CA ca;
typedef char (CA::*PTRFUN)(int);
PTRFUN pFun;
void main()
{
    pFun = CA::lcFun;
    ca.(*pFun)(2);
}

        在这里,指针的定义与使用都加上了“类限制”或“对象”,用来指明指针指向的函数是那个类的这里的类对象也可以是使用new得到的。比如:
CA *pca = new CA;
pca->(*pFun)(2);
delete pca;

        而且这个类对象指针可以是类内部成员变量,你甚至可以使用this指针。比如:
        类CA有成员变量PTRFUN m_pfun;
void CA::lcFun2()
{
   (this->*m_pFun)(2);
}

        一句话,使用类成员函数指针必须有“->*”或“.*”的调用。

<转>dynamic_cast使用方式

c++提供了四种新的cast机制,分别为static_cast, const_cast, dynamic_cast和reinterpret_cast。虽然也支持c中使用一对圆括号来cast,但是由于c++与c最大的区别是c++增加了类的概念,因此在子类与父类之间进行cast的时候,使用c的cast方式是无法保证其正确性的,因此c++提供了新的cast机制(虽然比较丑陋而且需要敲打更多的code,但是提供了安全性):dynamic_cast。

对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。因此可以用来在运行期间进行类型判断。如下述演示代码。

class CShape{...}
class CCircle:public CShape{...}
class CRectangle: public CShape{...}

CShape * pShape;
... //do something

if (dynamic_cast<CCircle * > (pShape))
{
    //process the Circle object
}
else if (dynamic_cast<CRectangle * > (pShape))
{
    //process the Rectangle object
}
else
{
    //error, just shape object
}

       如下面代码所示,将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理,即会作一定的判断。由于这会用到RTTI技术,因此需要启动“运行时类型信息”这一选项,而在VC.net 2003中默认是关闭的,所以需要人为的启动这一选项,如图1所示。否则编译器会报下面的警告:

warning C4541: “dynamic_cast”用在了带 /GR- 的多态类型“CBasic”上;可能导致不可预知的行为

从而导致程序在运行时发生异常。

但是dynamic_cast在将父类cast到子类时,父类必须要有虚函数。例如在图1中的代码中将CBasic类中的test函数不定义成virtual时,编译器会报错:
error C2683: dynamic_cast : “CBasic”不是多态类型
这是由于有了多态后,将子类对象赋值给父类对象才有意义。这样子类与父类之间的cast才有意义。
        当然如果使用dynamic_cast来直接将子类cast到父类(向上cast),那就无所谓了。即父类不需要一定有虚函数,也不需要开启“运行时类型信息”这一选项。

#include <iostream>
using namespace std;

class CBasic
{
public:
     virtual int test()...{return 0;}
};

class CDerived : public CBasic
{
public:
    virtual int test()...{    return 1;}
};

int main()
{
    CBasic        cBasic;
    CDerived    cDerived;
 
    CBasic * pB1 = new CBasic;
    CBasic * pB2 = new CDerived;

//dynamic cast failed, so pD1 is null.
    CDerived * pD1 = dynamic_cast<CDerived * > (pB1);
             
//dynamic cast succeeded, so pD2 points to  CDerived object                                      
    CDerived * pD2 = dynamic_cast<CDerived * > (pB2);
 
//dynamci cast failed, so throw an exception.          
//    CDerived & rD1 = dynamic_cast<CDerived &> (*pB1);

//dynamic cast succeeded, so rD2 references to CDerived object.
    CDerived & rD2 = dynamic_cast<CDerived &> (*pB2);

    return 0;
}