
2008年11月17日
写了个俄罗斯方块,逻辑算法部分中规中矩,棋盘主要采用二维数组的数据结构,而每种方块样式都是Block类的一个对象,方便扩充;界面部分采用了DialogBox,用GDI画,同时还使用双缓冲技术防止画面闪烁。考虑到可以在RC文件里设计程序界面,所以采用了Dialogbox作为主界面,于是麻烦便来了:DialogBox无法接受WM_KEYDOWN中的VK_UP,VK_DOWN等,也就是说上下左右按键没法用。。试了几种办法,都不行,干脆换成了home,end,page up,page down键。玩的时候很不方便~等我再查查资料再改吧,先把这个有残缺的1.0版本发上来。
程序截图:
下载地址:可执行文件,源代码
posted @
2008-11-17 00:59 彭小虎(Tigerkin) 阅读(153) |
评论 (4) |
编辑 收藏

2008年8月16日
我要在MSDN里搜Windows API DrawText函数,如图:

Google: 第一条就是。

Live Search: 找了半天没找到
posted @
2008-08-16 12:23 彭小虎(Tigerkin) 阅读(90) |
评论 (1) |
编辑 收藏
以往写Windows程序,用的较多的是Delphi的VCL,MFC用的很少,总觉得不习惯,相比MFC我倒宁愿用清新简单的Windows API。呵呵。于是乎,我萌生了一个想法,自己来封装Windows API。开始动手。。
首先我找了一个比较简单的Window API程序,试着把他转换成面向对象的形式。程序尽管简单,但刚上来一个棘手的问题就出现了。。消息机制的封装。
我们都知道,Windows中比如点击按钮,移动窗口等等的交互操作都是由消息机制来完成的。每做一个动作,例如点击一个按钮,Windows便会产生一个相应的消息,在这里就是BN_CLICKED,假设点击按钮后会弹出一个窗口,里面显示若干文字,而这些点击按钮后产生的效果就需要由我们程序员来编写。体现在Windows API中便是“消息处理函数”,如下:
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case BN_CLICKED:
//相关代码
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
每次都要写这么多case的确是挺麻烦的,于是我想用面向对象的方法解决他。
class MessageMap
{
public:
int count; // 消息映射的数目
Message* message[10]; // 可以存放10对消息映射
template <typename T>
void Add(MessageType 消息名称, T* 对应的处理函数); // 增加一对消息映射
};
template <typename T>
class Message
{
public:
MessageType 消息名称;
T* 对应的处理函数;
void Run()
{
对应的处理函数();
}
};
enum MessageType{
}
这样做其实是有问题的,请看这一段:
class MessageMap
{
public:

Message* message[10]; // 由于Message是模板类,这里应该提供模板参数。这样就失去了模板的作用。
// 我们使用模板就是为了能让他存储不同的函数指针。

};
想一想,如何用一个统一的接口来调用不同的函数?对了,那就是多态。
我们可以增加一个抽象的接口类来提供调用接口,而具体实现的类则由他派生。
class MessageInterface


{
public:
MessageType type;

virtual ~MessageInterface()
{}
virtual void Run() = 0;
};

template <typename T>
class Message : public MessageInterface


{
public:
T* classPtr;
void (T::* funcPtr)();
Message(MessageType _type, T* _classPtr, void (T::* _funcPtr)()) : classPtr(_classPtr), funcPtr(_funcPtr)

{
type = _type;
}
virtual void Run()

{
if (classPtr)

{
(classPtr->*funcPtr)(); // 注意调用函数指针时括号的用法
}
}
};

class MessageMap


{
public:
int count;
MessageMap()

{
count = 0;
Add(DESTROY, this, &MessageMap::OnDestroy);
}
MessageInterface* message[10];
void OnDestroy()

{
PostQuitMessage(0);
}
template <typename T>
void Add(MessageType type, T* classPtr, void (T::* funcPtr)())

{
message[count++] = new Message<T>(type, classPtr, funcPtr);
}
};
问题解决了!哈哈,测试成功!
具体的代码在这里,结构什么的还很不完善,仅仅是处理了消息机制而已,当作演示吧。
posted @
2008-08-16 11:45 彭小虎(Tigerkin) 阅读(1566) |
评论 (18) |
编辑 收藏

2008年8月7日
今天接到leader布置的一个任务,从TextBox继承一个新的控件并为其增加一些功能,其中一个功能如下:
Add a property: FormatString, when the text box lost focus, the content will be replace with string.Format(FormatString, actualContent),我在做第二个:“当textbox失去焦点时自动格式化文本”的地方遇到了问题。。。
简单起见,简化成“当textbox失去焦点时改变文本内容”。
一种方法是:
重写OnLeave()方法,如下:
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
this.Text = "Override OnLeave";
}
另一种是给Leave事件增加一个订阅者:
private void NewReceiver(object sender, EventArgs e)
{
this.text = "NewReceiver";
Invalidate();
}
//constructor
public MyTextBox()
{
this.Leave += new EventHandler(NewReceiver);
}
经测试,均可运行。
现在,把两段代码和在一起,并注释掉第一种方法:
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
//this.Text = "Override OnLeave";
}
private void NewReceiver(object sender, EventArgs e)
{
this.text = "NewReceiver";
Invalidate();
}
//constuctor
public MyTextBox()
{
this.Leave += new EventHandler(NewReceiver);
}
运行程序,测试,显示NewReceiver。
接下来再把base.OnLeave(e)注释掉:
protected override void OnLeave(EventArgs e)
{
//base.OnLeave(e);
//this.Text = "Override OnLeave";
}
private void NewReceiver(object sender, EventArgs e)
{
this.text = "NewReceiver";
Invalidate();
}
//constuctor
public MyTextBox()
{
this.Leave += new EventHandler(NewReceiver);
}
运行,发现文本框失去焦点后不会改变内容,仍未空。由此,第二种方法失效了。为何?我们来看一下base.OnLeave()的内容:
protected virtual void OnLeave(EventArgs e)
{
EventHandler handler = (EventHandler) base.Events[EventLeave];
if (handler != null)
{
handler(this, e);
}
}
原来,注释掉这段以后,handler(this, e)无法得到执行,也就没法激发事件,文本框内容当然也就没法改变了。
由此,我猜测,C#的事件触发过程大致是这样的:
文本框失去焦点 --> 触发Leave事件 --> 调用OnLeave()函数(这里是因为什么机制调用的?) --> 调用base.OnLeave() --> 再次触发Leave事件 --> 调用NewReceiver() --> 返回,执行this.text = "Override OnLeave"。
红色字,大家谁知道的,帮我解个惑吧~~
posted @
2008-08-07 19:22 彭小虎(Tigerkin) 阅读(72) |
评论 (0) |
编辑 收藏

2008年3月16日
class String


{
public:
String( const char *str = "" );
~String();
String( const String &another )

{
const char *str = another.m_data; // 可以正常访问

/**//*省略*/
private:
char *m_data;
};
c++里,类的访问权限是class level,不是object level的。
访问权限只在编译时对编译器有效,在运行时,不存在访问权限这道栅栏。
以下转自网上:
/**
* 请大家看看下面这段程序,它是能够正常编译连接运行的。但是为什么
* assign成员函数没有错呢?谁能告诉我c++中关于这种情况的内幕?谢谢!
*/
#include <stdio.h>
class Test
{
private:
int a;
public:
Test(int);
void assign(Test const* source);
};
void Test::assign(Test *source)
{
a = source->a;
}
int main()
{
Test t1(1), t2(2);
t2.assign(&t1);
}
1: 关于ACCESS-CONTROL的定义
<<C++98 std/chapter 11>>:
1 A member of a class can be
--private; that is, its name can be used only by
member functions, static data members, and
friends of the class in which it is declared.
--protected; that is, its name can be used only by
member functions, static data members, and
friends of the class in which it is declared
and by member functions, static data members,
and friends of classes derived from this class.
--public; that is, its name can be used
anywhere without access restriction.
2: 一些相关的note:
1) name
ACCESS-CONTROL 只应用到name(simple identifier).
按照上面的定义,ACCESS-CONTROL针对且只针对
member of class.
即使是涉及到模板,也只是关心name,
而不关心template-argument
2) unnamed object
无名对象不考虑access-control:
如下:
struct A
{
private:
struct B
{
int i;
};
B m_b;
public:
B& f() { return m_b; }
};
int main()
{
A a;
f()->i; // only names are f, i,
// f and i is public,
// so it's legal even
// if B in private A member
}
3) 关于protected member name有一个例外情况,
参见(4:)中例子的new note
3: 让人迷惑的代码段的注记
void Test::assign(Test *source)
{
a = source->a;
/**
1) 这里出现的name是
void, Test, assign, source, a
2) void, Test, source,
不是class member,
不做access-control考虑
3) assign是成员函数名,
但我们只是定义它,
不在这里使用,
也不做access-control考虑
4)a是private Test member,
可以在A的成员函数中使用,
现在刚好是在Test的
assign成员函数体内。
5) but what about "source->a":
OK, a in "source->a" means
a is a member name of source's class Test
and used in Test member function,
everything is fine!!!
(still confusing, see following example ...)
*/
}
4: 更加清楚的例子(我的)注记
struct Base
{
protected:
int m_protected;
};
struct Son2 : Base {};
struct Son1 : Base
{
void foo(Son1& s)
{
s.m_protected; /**
new note:
same as 3/5)
*/
}
void foo(Base& b)
{
b.m_protected; /**
new note:
m_protected in "b.m_protected" means:
m_protected is Base(b's type) class
member, not Son1 class member
which means:
Base's protected member is used
in Son1's member function
By definition, it's legal, but
因为C++标准规定一个关于
proected member的例外情况,
When a member function of a
derived class references
a protected nonstatic member
of a base class, an access check
applies in addition to those
described earlier in this clause:
Except when forming a pointer to
member,the access must be through
a pointer to,
reference to,
<---我们的情况
or object
<---下一个成员函数的情况
of the derived class itself
(or any class derived from that class).
因为儿子不能管老子,所以结论是
illegal
*/
}
void foo(Base b)
{
b.m_protected; /**
new note:
illegal : 儿子不能管老子
原因同 void foo(Base& c)的分析。
*/
}
};
posted @
2008-03-16 19:29 彭小虎(Tigerkin) 阅读(41) |
评论 (0) |
编辑 收藏

2008年3月4日
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef unsigned char BYTE;
typedef long LONG;

struct BITMAPFILEHEADER


{
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
};

sizeof(WORD)==2
sizeof(DWORD)==4
但sizeof(BITMAPFILEHEADER)==16
不等于2+4+2+2+4=14
为什么?这里存在一个字节对齐问题
以下为转帖:
一.什么是字节对齐,为什么要对齐?
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。
二.字节对齐对程序的影响:
先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)
short:2(有符号无符号同)
int:4(有符号无符号同)
long:4(有符号无符号同)
float:4 double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12
结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
后面我们再讲解#pragma pack()的作用.
三.编译器是按照什么样的原则进行对齐的?
先让我们看四个重要的基本概念:
1.数据类型自身的对齐值:
对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
有 了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是 表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数 据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数 倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
例子分析:
分析例子B;
struct B
{
char b;
int a;
short c;
};
假 设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定 对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4, 所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的 都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那 么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一 个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其 自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只 是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第 一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续 字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合 0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C 只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.
四.如何修改编译器的默认对齐值?
1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.
五.针对字节对齐,我们在编程中如何考虑?
如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照 类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做 法是显式的插入reserved成员:
struct A{
char a;
char reserved[3];//使用空间换时间
int b;
}
reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.
六.字节对齐可能带来的隐患:
代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;
p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.
七.如何查找与字节对齐方面的问题:
如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。
八.其他
当数据定义中出现__declspec( align() )时,指定类型的对齐长度还要用自身长度和这里指定的数值比较,然后取其中较大的。最终类/结构的对齐长度也需要和这个数值比较,然后取其中较大的。
可以这样理解, __declspec( align() ) 和 #pragma pack是一对兄弟,前者规定了对齐的最小值,后者规定了对齐的最大值,两者同时出现时,前者拥有更高的优先级。
__declspec( align() )的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:
在__declspec( align() )之前,数据按照#pragma pack规定的方式填充,如前所述。当遇到__declspec( align() )的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长度为max(数据自身长度,指定值) ),然后把被指定的数据类型从这个点开始填充,其后的数据类型从它的后面开始,仍然按照#pragma pack填充,直到遇到下一个__declspec( align() )。
当所有数据填充完毕,把结构的整体对齐数值和__declspec( align() )规定的值做比较,取其中较大的作为整个结构的对齐长度。
特别的,当__declspec( align() )指定的数值比对应类型长度小的时候,这个指定不起作用。
posted @
2008-03-04 19:49 彭小虎(Tigerkin) 阅读(21) |
评论 (0) |
编辑 收藏

2008年1月23日
typedef:
第一个陷阱:
typedef char * pstr;
int mystrcmp(pstr, pstr);
int mystrcmp(const pstr, const pstr);
这是错误的,按照顺序,‘const pstr’被解释为‘char * const’(一个指向 char 的常量指针),而不是‘const char *’(指向常量 char 的指针)。这个问题很容易解决:
typedef const char * cpstr;
int mystrcmp(cpstr, cpstr); // 现在是正确的
第二个陷阱:
typedef register int FAST_COUNTER; // 错误
编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef 声明中不能用 register(或任何其它存储类关键字)。
和#define区别:
#define:编译与处理时
typedef:编译时
|
__cdecl
|
__stdcall
|
|
C 和 C++ 程序的缺省调用规范
|
为了使用这种调用规范,需要你明确的加上 __stdcall (或 WINAPI )文字。即 return-type __stdcall function-name[(argument-list)]
|
|
在被调用函数 (Callee) 返回后,由调用方 (Caller) 调整堆栈。
1. 调用方的函数调用
2. 被调用函数的执行
3. 被调用函数的结果返回
4. 调用方清除调整堆栈
|
在被调用函数 (Callee) 返回前,由被调用函数 (Callee) 调整堆栈。图示:
1. 调用方的函数调用
2. 被调用函数的执行
3. 被调用函数清除调整堆栈
4. 被调用函数的结果返回
|
|
因为每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大。
|
因为调整堆栈的代码只存在在一个地方(被调用函数的代码内),所以最后生成的文件较小。
|
|
函数的参数个数可变(就像 printf 函数一样),因为只有调用者才知道它传给被调用函数几个参数,才能在调用结束时适当地调整堆栈。
|
函数的参数个数不能是可变的。
|
|
对于定义在 C 程序文件中的输出函数,函数名会保持原样,不会被修饰。
对于定义在 C++ 程序文件中的输出函数,函数名会被修饰, MSDN 说 Underscore character (_) is prefixed to names . 我实际测试( VC4 和 VC6 )下来发现好像不是那么简单。
可通过在前面加上 extern “C” 以去除函数名修饰。也可通过 .def 文件去除函数名修饰。
|
不论是 C 程序文件中的输出函数还是 C++ 程序文件中的输出函数,函数名都会被修饰。
对于定义在 C 程序文件中的输出函数, An underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list.
对于定义在 C++ 程序文件中的输出函数,好像更复杂,和 __cdecl 的情况类似。
好像只能通过 .def 文件去除函数名修饰。
|
|
_beginthread 需要 __cdecl 的线程函数地址
|
_beginthreadex 和 CreateThread 需要 __stdcall 的线程函数地址
|
|
两者的参数传递顺序都是从右向左。
为了让 VB 可以调用,需要用 __stdcall 调用规范来定义 C/C++ 函数。请参看Microsoft KB153586 文章:How To Call C Functions That Use the _cdecl Calling Convention。
当你 LoadLibrary 一个 DLL 文件后, 把 GetProcAddress 取得的函数地址传给上面三个线程生成函数时,请务必确认实际定义在 DLL 文件的输出函数符合调用规范要求。否则,编译成 Release 版后运行,可能会破坏堆栈,程序行为不可预测。
VC 中的相关编译开关:/Gd /Gr /Gz。另外,VC6中新增加的 /GZ 编译开关可以帮你检查堆栈问题。
|
posted @
2008-01-23 10:03 彭小虎(Tigerkin) 阅读(27) |
评论 (0) |
编辑 收藏

2008年1月20日
120G日立笔记本硬盘今天到手~~
我那30G的前辈也正式退役干起了移动硬盘~哈
换了硬盘不仅增大了我的空间,更出乎意料地加快了电脑的速度,5400RPM+垂直技术 > 4200RPM ,实践证明,硬盘真是个大瓶颈!~
posted @
2008-01-20 21:24 彭小虎(Tigerkin) 阅读(6) |
评论 (0) |
编辑 收藏