[ZZ]清华博士王垠的退学申请

         从此我感觉到了什么叫做研究。这跟我小时候干的那些事情没有什么两样。你在身边发现一个 问题,想知道为什么。然后你就想去获得解决这个问题的知识。你去看书,你去问专家,你上网去搜索。如果没有发现答案,那么好啦,你就可以自己试图去发现为什么,这是最有趣的部分。知道了为什么,就想让这个东西有用处,对人们的生活产生好处。这就是研究。

                                                                                                                —— 文摘

经过深思熟虑,我决定放弃清华大学的博士学位。其中的原因,你们有兴趣的话可以看看下面的文章。这就是我的一生与中国教育的故事。一个用血和泪换来的教训。

清华梦的粉碎—写给清华大学的退学申请 2005.9.22

继续阅读

发表在 网摘, 网络 | 标签为 , | 留下评论

Eclipse下使用 Winpcap

1. 下载Winpcap开发包,在主页选择 Winpcap –> Dev…

2. 解压,方法一,对应文件直接复制到Eclipse使用的GCC编译器文件夹下,如MinGW\include\.. Lib文件同理

                 方法二,在Eclipse的编译选项里添加-i include.

3. 查看官方文档,在开发包的Doc里有,选择 Using WinPcap in your programs 查看文档信息。

4. 文档提示要添加preprocessor definitions,Project –> Pro… –> C/C++ Build –>Setting –> GCC C compiler –>symbols , 添加 WPCAP HAVE_REMOTE

5. 复制官方的第一个实例,编译运行。

 

—-  MinGW 下 Winpcap编译不通过的问题解释 —-

You can replace your localtime_s call with:

localtime_r(&local_tv_sec, &ltime);

(Note the swapped arguments.)

Also, replace your scanf_s call with scanf.

localtime_s() and scanf_s() are Microsoft-specific extensions, and are not available in MinGW

发表在 C, Eclipse, Windows, 系统, 编程 | 标签为 , | 留下评论

[推荐]Linux C编程一站式学习

虽然一直觉得”一站式”的称号有些过分,不过这篇文档作为 Linux操作手册 还是很好的。

《Linux下C编程入门》就是一片不错的PDF文档

发表在 Linux, 系统 | 标签为 | 留下评论

[转载]可变参数宏

作者:Gavin Shaw(综合整理) 更新日期:2006-08-22
来源:upsdn.net

在 GNU C 中,宏可以接受可变数目的参数,就象函数一样,例如:
#define pr_debug(fmt,arg…) \
printk(KERN_DEBUG fmt,##arg)
用可变参数宏(variadic macros)传递可变参数表
你可能很熟悉在函数中使用可变参数表,如:

void printf(const char* format, …);

直到最近,可变参数表还是只能应用在真正的函数中,不能使用在宏中。

C99编译器标准终于改变了这种局面,它允许你可以定义可变参数宏(variadic macros),这样你就可以使用拥有可以变化的参数表的宏。可变参数宏就像下面这个样子:

#define debug(…) printf(__VA_ARGS__)

缺省号代表一个可以变化的参数表。使用保留名 __VA_ARGS__ 把参数传递给宏。当宏的调用展开时,实际的参数就传递给 printf()了。例如:

Debug(“Y = %d\n”, y);

而处理器会把宏的调用替换成:

printf(“Y = %d\n”, y);

因为debug()是一个可变参数宏,你能在每一次调用中传递不同数目的参数:

debug(“test”); //一个参数

可变参数宏不被ANSI/ISO C++ 所正式支持。因此,你应当检查你的编译器,看它是否支持这项技术。

用GCC和C99的可变参数宏, 更方便地打印调试信息

gcc的预处理提供的可变参数宏定义真是好用:

#ifdef DEBUG
#define dbgprint(format,args...) \
fprintf(stderr, format, ##args)
#else
#define dbgprint(format,args...)
#endif

如此定义之后,代码中就可以用dbgprint了,例如dbgprint("aaa %s", __FILE__);。感觉这个功能比较Cool  :em11:

下面是C99的方法:

#define dgbmsg(fmt,...) \
             printf(fmt,__VA_ARGS__)

新的C99规范支持了可变参数的宏

具体使用如下:

以下内容为程序代码:

#include <stdarg.h> #include <stdio.h>

#define LOGSTRINGS(fm, …) printf(fm,__VA_ARGS__)

int main() {      LOGSTRINGS("hello, %d ", 10);      return 0; }

但现在似乎只有gcc才支持。

可变参数的宏里的‘##’操作说明

带有可变参数的宏(Macros with a Variable Number of Arguments)

在1999年版本的ISO C 标准中,宏可以象函数一样,定义时可以带有可变参数。宏的语法和函数的语法类似。下面有个例子:

#define debug(format, …) fprintf (stderr, format, __VA_ARGS__)

这里,‘…’指可变参数。这类宏在被调用时,它(这里指‘…’)被表示成零个或多个符号,包括里面的逗号,一直到到右括弧结束为止。当被调用时,在宏体(macro body)中,那些符号序列集合将代替里面的__VA_ARGS__标识符。更多的信息可以参考CPP手册。

GCC始终支持复杂的宏,它使用一种不同的语法从而可以使你可以给可变参数一个名字,如同其它参数一样。例如下面的例子:

#define debug(format, args…) fprintf (stderr, format, args)

这和上面举的那个ISO C定义的宏例子是完全一样的,但是这么写可读性更强并且更容易进行描述。

GNU CPP还有两种更复杂的宏扩展,支持上面两种格式的定义格式。

在标准C里,你不能省略可变参数,但是你却可以给它传递一个空的参数。例如,下面的宏调用在ISO C里是非法的,因为字符串后面没有逗号:

debug ("A message")

GNU CPP在这种情况下可以让你完全的忽略可变参数。在上面的例子中,编译器仍然会有问题(complain),因为宏展开后,里面的字符串后面会有个多余的逗号。

为了解决这个问题,CPP使用一个特殊的‘##’操作。书写格式为:

#define debug(format, …) fprintf (stderr, format, ## __VA_ARGS__)

这里,如果可变参数被忽略或为空,‘##’操作将使预处理器(preprocessor)去除掉它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,GNU CPP也会工作正常,它会把这些可变参数放到逗号的后面。象其它的pasted macro参数一样,这些参数不是宏的扩展。

怎样写参数个数可变的宏

一种流行的技巧是用一个单独的用括弧括起来的的 “参数" 定义和调用宏, 参数在 宏扩展的时候成为类似 printf() 那样的函数的整个参数列表。

    #define DEBUG(args) (printf("DEBUG: "), printf args)

if(n != 0) DEBUG(("n is %d\n", n));

明显的缺陷是调用者必须记住使用一对额外的括弧。

gcc 有一个扩展可以让函数式的宏接受可变个数的参数。 但这不是标准。另一种 可能的解决方案是根据参数个数使用多个宏 (DEBUG1, DEBUG2, 等等), 或者用 逗号玩个这样的花招:

    #define DEBUG(args) (printf("DEBUG: "), printf(args))
#define _ ,

DEBUG("i = %d" _ i);

C99 引入了对参数个数可变的函数式宏的正式支持。在宏 “原型" 的末尾加上符号 … (就像在参数可变的函数定义中), 宏定义中的伪宏 __VA_ARGS__ 就会在调用是 替换成可变参数。

最后, 你总是可以使用真实的函数, 接受明确定义的可变参数

如果你需要替换宏, 使用一个 函数和一个非函数式宏, 如 #define printf myprintf。

发表在 C, 编程 | 标签为 , | 留下评论

[转载]如何检测当前系统的 shell 版本

当 Unix/Linux 系统启动时,操作系统的内核部分被装入内存,其余部分仍在硬盘上,只有当用户请求执行时才会被加载到内存中。Unix/Linux 系统完成启动后,由内核中的 init 进程激活 getty 进程,getty 进程在相应的终端上显示“login: ”提示,等待用户登录。当用户输入用户名时,getty 进程读取用户输入并启动 login 进程,由 login 进程完成整个登陆过程。用户完成登录后,login 进程根据启动配置文件,启动与该登录用户相对应的“默认 shell ”。此时,shell 进程被启动,用户可以通过 shell 终端与系统内核进行交互。

各种各样的 shell

和 Unix/Linux 中的其它程序一样,shell 也仅仅是一个程序,在 Unix/Linux 系统中并没有特权。这也是为什么 Unix/Linux 系统中出现各种各样 shell 的原因。
1、Bourne shell(sh)。大多数 Unix 系统的默认安装 shell。
2、Korn shell(ksh)。
3、C shell(csh)。
4、TENEX/TOPS C shell(tcsh)。
5、Bourne Again shell(bash)。大多数 Linux 系统的默认安装 shell。

切换 shell

大多数的 Unix/Linux 系统都提供了多个 shell。当系统启动时,由启动配置文件确定启动(默认的) shell,用以用户与系统进行交互。
在 Red Hat 9 中,可以通过以下任何一种命令,确定登录用户的默认 shell。
cat /etc/passwd #M1
echo $SHELL #M2
在决定要切换 shell 之前,必须知道系统中安装了哪些 shell。在 Red Hat 9 中,可以通过以下命令,确定系统中已经安装的各种 shell 程序。
cat /etc/shells
在我的 Red Hat 9 系统中,得到了如下的结果:
/bin/sh
/bin/bash
/sbin/nologin
/bin/bash2
/bin/ash
/bin/bsh
/bin/tcsh
/bin/csh
可以看出来,这其实是一个 shell 程序文件的列表。一般来说,/bin 和 /sbin 目录都在环境变量 PATH 中。为了切换为我们喜欢的 shell 种类,(我们已经知道,shell 也仅仅是一种与系统进行交互的程序),我们只需要在命令提示符下输入 shell 在 /bin 和 /sbin 目录中的相应名称即可。

我在使用哪个 shell

Unix/Linux 系统极少被重新启动,多个对 shell 有不同爱好的管理员可能操纵过计算机,我们姑且假设他们都使用相同的管理帐号。于是,这就产生了一个问题,我的终端上究竟运行着什么样的 shell?
针对这个问题,我在 Google 上搜寻了一下,有各种各样的答案。有一个回答是
echo $SHELL
我觉得这是不对的,SHELL 是环境变量,在整个会话中都不回改变,它表示的是登录用户的“默认 shell ”。还有人说用
cat /etc/passwd
这样得到的结果也是登录用户的“默认 shell ”,很显然也不能解决上述问题。
在 Red Hat 9 系统上,我用下述方法解决这个问题:
首先,使用命令
echo $$
获得当前 shell 进程的 PID。我在测试中,得到的结果是 10650。
然后,使用命令
ps -A | grep 10650
获取进程列表,并进行筛选。得到这样的测试结果:
10650 pts/0 00:00:00 csh
于是得到的结论是,测试中使用的 shell 是 csh。

发表在 Linux, 系统 | 标签为 , | 留下评论

《上海堡垒》

你是凝结的时间,流动的语言, 
黑色的雾里,有隐约的光. 
可是透过你的双眼,会看不清世界, 
花朵的凋萎,在瞬间, 
而花朵的绽放,在昨天。

 

 

        有些事情,遇见了就不会忘记。  —— 2011 年 5 月 16 日

发表在 读书 | 标签为 , | 留下评论

函数调用的汇编流程

测试环境:Windwos 7 + GCC 4.5.2 Target: mingw32

测试代码:

   1: code.c:
   2:  
   3: int accum = 0;
   4:  
   5: int sum(int x, int y)
   6: {
   7:     int t = x +y;
   8:     accum += t;
   9:     return t;
  10: }
  11:  
  12: main.c:
  13:  
  14: int main()
  15: {
  16:     return sum(1, 3);
  17: }

测试流程:

1. > gcc –O2 –S code.c

生成code.s 部分省略

   1: _sum: 
   2:     pushl    %ebp 
   3:     movl    %esp, %ebp 
   4:     movl    12(%ebp), %eax 
   5:     addl    8(%ebp), %eax 
   6:     addl    %eax, _accum 
   7:     leave 
   8:     ret 

可以注意到: sum函数首先保存ebp, 然后把当前esp 存入ebp, 因为esp寄存器存放当前栈顶指针,所以根据ebp的偏移获取参数x, y. (注意因为push ebp后,所以偏移为 12, 8, leave指令相当于 movl %ebp,%esp  popl %ebp, 对应的还有ENTER指令)

2 >gcc –O2 –c code.c

>objdump –d code.o

得到输出

   1: 00000000 <_sum>: 
   2:    0:   55                      push   %ebp 
   3:    1:   89 e5                   mov    %esp,%ebp 
   4:    3:   8b 45 0c                mov    0xc(%ebp),%eax 
   5:    6:   03 45 08                add    0x8(%ebp),%eax 
   6:    9:   01 05 00 00 00 00       add    %eax,0x0 
   7:    f:   c9                      leave 
   8:   10:   c3                      ret 
   9:   11:   90                      nop 
  10:   12:   90                      nop 
  11:   13:   90                      nop

注意这里 accum 的地方地址为全0,还没有最后确定。

3 >gcc –O2 –o prog code.o main.c

> objdump –d prog > tmp.txt

生成tmp.txt约1193行,查找到相关汇编码:

   1: 004013c0 <_sum>: 
   2:   4013c0:    55                       push   %ebp 
   3:   4013c1:    89 e5                    mov    %esp,%ebp 
   4:   4013c3:    8b 45 0c                 mov    0xc(%ebp),%eax 
   5:   4013c6:    03 45 08                 add    0x8(%ebp),%eax 
   6:   4013c9:    01 05 20 50 40 00        add    %eax,0x405020 
   7:   4013cf:    c9                       leave  
   8:   4013d0:    c3                       ret    
   9:   4013d1:    90                       nop 
  10:   4013d2:    90                       nop 
  11:   4013d3:    90                       nop
  12:  
  13: 004013d4 <_main>: 
  14:   4013d4:    55                       push   %ebp 
  15:   4013d5:    89 e5                    mov    %esp,%ebp 
  16:   4013d7:    83 e4 f0                 and    $0xfffffff0,%esp 
  17:   4013da:    83 ec 10                 sub    $0x10,%esp 
  18:   4013dd:    e8 3e 06 00 00           call   401a20 <___main> 
  19:   4013e2:    c7 44 24 04 03 00 00     movl   $0x3,0x4(%esp) 
  20:   4013e9:    00 
  21:   4013ea:    c7 04 24 01 00 00 00     movl   $0x1,(%esp) 
  22:   4013f1:    e8 ca ff ff ff           call   4013c0 <_sum> 
  23:   4013f6:    c9                       leave  
  24:   4013f7:    c3                       ret    
  25:     ...

发现: 这时候 accsum处已经填入了真实地址,不过main函数内的行为有点奇怪,首先保存ebp和esp, 然后清空低4位的%esp, 加上16,调用___main, 而___main处理信息有些奇怪,判断 %405060地址(由accsum的地址知道那应该是全局变量区附近)处内容是否为空来决定是否调用

401a3d:    eb 81                    jmp    4019c0 <___do_global_ctors>

不过与我们现在的讨论无关,先行跳过,之后到Linux上去调试看看。

之后的送入参数,奇怪的是 sub 0x10 把栈增加了16个长度后只使用最上面的8个。

最后执行调用,值得注意的是,无论是被调用函数还是调用函数都不再负责保持堆栈平衡,只要使用esp,ebp反复恢复上一次的栈顶情况就可以了。

发表在 C, 系统, 编程 | 标签为 , , | 留下评论

【转】堆和栈的区别——给初学者

http://www.chinaunix.net 作者:dreamice  发表于:2009-08-02 18:05:41

发表评论】 【查看原文】 【Linux讨论区】【关闭

一、预备知识—程序的内存分配 
由C/C++编译的程序占用的内存分为以下几个部分 
1、栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 
2、堆区(heap): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 
3、全局区(static): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后有系统释放 。 
4、文字常量区: 常量字符串就是放在这里的, 程序结束后由系统释放。 
5、程序代码区: 存放函数体的二进制代码。 
Example: 
int a = 0; // 全局初始化区 
char *p1; // 全局未初始化区 
main() 

int b; // 栈 
char s[] = "abc"; // 栈 
char *p2; // 栈 
char *p3 = "123456"; // 123456\0在常量区,p3在栈上。 
static int c =0; // 全局(静态)初始化区 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); // 分配得来得10和20字节的区域就在堆区。 
strcpy(p1, "123456"); // 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 

二、堆和栈的理论知识 
2.1 申请方式 
栈: 由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 
堆: 需要程序员自己申请,并指明大小,在c中malloc函数:如p1 = (char *)malloc(10); 在C++中用new运算符 如p2 = (char *)malloc(10); 但是注意p1、p2本身是在栈中的。 
2.2 申请后系统的响应 
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 
2.3 申请大小的限制 
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 
2.4 申请效率的比较: 
栈:由系统自动分配,速度较快。但程序员是无法控制的。 
堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。 
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。 
2.5 堆和栈中的存储内容 
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。 
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。 
2.6 存取效率的比较 
char s1[] = "aaaaaaaaaaaaaaa"; 
char *s2 = "bbbbbbbbbbbbbbbbb"; 
aaaaaaaaaaa是在运行时刻赋值的; 
而bbbbbbbbbbb是在编译时就确定的; 
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。 
比如: 
#include 
void main() 

char a = 1; 
char c[] = "1234567890"; 
char *p ="1234567890"; 
a = c[1]; 
a = p[1]; 
return; 

对应的汇编代码 
10: a = c[1]; 
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 
0040106A 88 4D FC mov byte ptr [ebp-4],cl 
11: a = p[1]; 
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 
00401070 8A 42 01 mov al,byte ptr [edx+1] 
00401073 88 45 FC mov byte ptr [ebp-4],al 
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。 
2.7 小结: 
堆和栈的区别可以用如下的比喻来看出: 使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。 使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。 
还有就是函数调用时会在栈上有一系列的保留现场及传递参数的操作。栈的空间大小有限定,VC的缺省是2M。栈不够用的情况一般是程序中分配了大量数组和递归函数层次太深。有一点必须知道,当一个函数调用完返回后它会释放该函数中所有的栈空间。栈是由编译器自动管理的,不用你操心。堆是动态分配内存的,并且你可以分配使用很大的内存。但是用不好会产生内存泄漏。并且频繁地malloc和free会产生内存碎片(有点类似磁盘碎片),因为C分配动态内存时是寻找匹配的内存的。而用栈则不会产生碎片。在栈上存取数据比通过指针在堆上存取数据快些。一般大家说的堆栈和栈是一样的,就是栈(stack),而说堆时才是堆heap。栈是先入后出的,一般是由高地址向低地址生长。

发表在 C, 系统, 编程 | 标签为 , , | 留下评论

[摘抄]『煮酒论史』 [国学宗教]深入浅出说《心经》

晚上AE渲染的时候顺便看了下这篇文章。作者释戒悲写的挺好的  🙂

看的比较细,有点小困,先写两点。

1. 有求于佛,何谓解脱?

  求佛并不会得到世俗上的解脱,般若希望人们直面人生。 问题还是要靠自己解决的。

菩萨虽然大慈大悲救苦救难,但是如果铲这种事您还是找God Father比较专业。解铃还需系铃人。自己的问题终归还要自己解决。有了大智慧到彼岸当然就离开了此案的种种痛苦烦恼。但并不是说你真的把一个烂摊子甩下,然后自己划着小船去通吃岛了。烂摊子还是原来的烂摊子,你该住几平米的房子还住几平米。只不过原来的问题已经不成其为问题了。按照更佛教一点的说法应该是本来就没有实际的问题,只是你自生分辨,认为有很多的问题。

2. 万物皆空?

   总的来说,万物自性空,即最开始什么都不是,却又是最细小的存在。

   因缘起而不断合和而成,这时候我们为他们加上了相,成为了我们所见的万物,都是缘起合和后在我们心理的投影。

“色即是空”的“空”也是指“缘起而无自性”。就是所谓的“自性空”。无自性可以用拳头来做比喻。世界上真的有一种实在的东西叫做拳头吗?其实没有。只不过是当我们把手指攥在一起的时候我们称它为拳头。这就是自性空。它不是说你伸出来的拳头本身是不存在的。而是说拳头只是一种“相”,是由因缘和合而成的。所以没有实在的“自性”。不存在一个绝对意义上的拳头,所以是“假名有”;但由于依五蕴和合而生,又是“自性空”。
同样的可以说海浪。我们如果说“空”就认为世界上不存在海浪,那就是执着于实空,是“恶趣空”,是不正确的观点。如果认为有一个实有自性的海浪存在,那就是执着于实有,也是不正确的观点。离开实有和实空两边的偏见,认识到浪是由水的形态幻化而出,既非实有也非实无而是“性空而假名有”才是中道实相。
同理,刚才我们说的那个瓷杯子,你把它当成“杯具”还是当成“洗具”也都是你的自生分辨而已了。

发表在 唠叨, 网摘, 网络 | 标签为 , | 留下评论

罗马字母

你可能经常看到罗马数字,即使你没有意识到他们。

你可能曾经在老电影或者电视中看到他们(“版权所有 MCMXLVI” 而不是 “版权所有1946”),或者在某图书馆或某大学的贡献墙上看到他们(“成立于 MDCCCLXXXVIII”而不是“成立于1888”)。

你也可能在某些文献的大纲或者目录上看到他们。这是一个表示数字的系统,他能够真正回溯到远古的罗马帝国(因此而得名)。

在罗马数字中,利用7个不同字母进行重复或者组合来表达各式各样的数字。

  • I = 1
  • V = 5
  • X = 10
  • L = 50
  • C = 100
  • D = 500
  • M = 1000

下面是关于构造罗马数字的一些通用的规则的介绍:

  • 字符是叠加的。 I表示1, II表示2, 而III表示3. VI 表示 6 (字面上为逐字符相加, “51”), VII 表示 7, VIII 表示 8.
  • 能够被10整除的字符(I, X, C, 和 M)至多可以重复三次. 对于4, 你则需要利用下一个最大的能够被5整除的字符进行减操作得到,你不能把4 表示成 IIII; 而应表示为 IV (比“51”)。数字40写成XL (比5010), 41 写成 XLI, 42 写成 XLII, 43 写成 XLIII, 而 44 写成 XLIV (比5010, 然后比51).
  • 类似的,对于数字 9,你必须利用下一个能够被10整除的字符进行减操作得到: 8 表示为 VIII, 而 9 则表示为 IX (比101), 而不是 VIIII (因为字符I 不能连续重复四次)。数字90 表示为 XC, 900 表示为 CM.
  • 被5整除的字符不能重复。数字10 常表示为X, 而从来不用VV来表示。数字100常表示为C, 也从来不表示为 LL.
  • 罗马数字经常从高位到低位书写,从左到右阅读,因此不同顺序的字符意义大不相同。DC 表示 600; 而CD 是一个完全不同的数字(为400, 也就是比500100). CI 表示 101; 而IC 甚至不是一个合法的罗马字母(因为你不能直接从数字100减去1; 比需要写成XCIX, 意思是 比10010, 然后加上数字9,也就是比 101的数字).

发表在 唠叨 | 标签为 | 2条评论