知乎 | 为什么有很多人执着于中文编程?
这个问题,前两天每天两万浏览。但它显然伤害了一些人的利益。于是两天后,它的浏览量马上被人为拉到0。沉寂十几天后,大概利益相关方的水军已经到位,宣传计划制定妥当,于是限流期结束,浏览量回升。
真好。真是彻底体现了我们的优越性,证明了鼓吹中文编程的都是些什么人。
好吧,在此,我宣布,在下从今天起无条件支持中文编程,呼吁禁止在代码中使用英文标点。并对自己之前的认识不清道歉。原答案不改,就留在下面,以供有识之士们唾弃:
因为他们想忽悠那些不懂编程的人。
真懂了编程,你就知道,其实“编程语言”压根就是一门新语言——不是汉语、日语也不是德语、英语、世界语。
它是为计算机硬件、为开关电路量身定做的一套“符号系统”;这套“符号系统”定义了一堆类似数学符号的“关键字”,使得程序员可以像做数学题那样,把万事万物的数学骨架表达给电脑——最后,再通过“数字信息”到“位置”“色彩”“亮度”等东西的对应表,把算出来的东西转换成人类看得懂的图形、文本、表格之类形式。
如果你会做数学题,那么你一定不会抱怨什么“=不写成‘等于’”、“x不写成‘未知数’‘天元’”“+应该写成‘加’”……
你绝不敢说这些简化的数学符号妨碍了你的理解、使得你解不了数学题。
恰恰相反,不使用这种从格式开始就有明确区分、因此易写易读的数学符号,反而使得你的表述冗长、难以理解。
没人搞“中文数学”是因为方程这种东西是初中必修。大家都明白,所以没法忽悠人——说出来,就显得你傻。
编程是完全一样的。
几乎所有编程语言都支持三种控制结构,它们是:顺序、分支、循环。
顺序很简单,一行挨一行就行。
分支的助记符是if,if后面跟条件表达式,如果条件表达式计算出来为真,就走第一分支;否则走第二分支。写出来就是:
if (你中午没吃饭) {
去吃饭;
} else {
午休;
}
你当然也可以写成:
如果 (你中午没吃饭) {
去吃饭;
} 否则 {
午休;
}
类似的,各种编程语言里,常见的循环有四种:for循环、while循环、do-while/until循环和foreach循环。
for(int I=0; I<10; I++) {
}
while(p!=end) {
}
do {
} while(p!=end);
do {
} until(p==end)
foreach item in list {
}
你当然可以把它们也翻译成中文:
为(整型 循环变量=0; 循环变量<10; 循环变量 自增) {
}
当(指针 不等于 尾指针) {
}
做 {
} 当(指针 不等于 尾指针);
做 {
} 直到(指针 等于 尾指针);
为每个 元素 在 列表 {
}
多页面视图
当然,如果你懂编程,可能会觉得这个写法怪异。不过没关系,看多了自然就习惯了。
基于计算机工作原理,和数学一样,这套符号一共也就三四十或者五六十个(有的语言多一些,有的少一些)。它们就是所谓的“关键字”。
每个关键字,都代表着一种指令/属性/指示。
程序员必须像熟悉数学运算符/速记符那样,熟悉所有这些指令/属性/指示;然后组合它们,把希望计算机执行的操作无歧义的清晰表达出来。
嗯,我不打算讨论中文编程键入多不多、全角半角符号混淆起来有多烦人、中文空格和英文空格、制表符混用有多恶心。
这些都是能克服的。
事实上,只要我们在C里面导入一个头文件:
//中文关键字定义
//我懒,没写全。但C语言一共也只有32个关键字,你把它们以类似格式define一下就行。
//我都做1/3强了,你补上剩下2/3就行。汉化它就这么容易。
#define 若 if
#define 否则 else
#define 为 for
#define 当 while
#define 做 do
#define 整型 int
#define 字符 char
#define 短整 short
#define 单精数 float
#define 双精数 double
#define 结构体 struct 多页面视图
那么,C语言现在已经支持中文关键字了。
中文编程语言已经有了。
类似的,你也可以如此对待C++。
其他编程语言或许没有宏,做不到如此轻易,但写个程序做个字符串替换,这是初中生都能办到的。
哪怕现在最最复杂的语言也不过几十个关键字而已——前面我说过了,关键字就对应于数学符号,和+-×÷=、Σ、lim、|x|、∫等等都是一回事(数学符号往往还比编程语言关键字多得多,比如c语言据说就是32个关键字、48个运算符:运算符就是数学符号)。
总之,或者你利用宏,或者直接写个转换程序——这个程序完全可以是通用的,定义个转换表就能对付所有语言,就好像uedit/notepad++的语法提示那样,简单的识别-替换就足够用了——任何编程语言都能当即变成“中文编程语言”。
这事的难度就有这么低。
按照IT界一贯的尿性,如果这种东西真那么好用、那么有用的话,那么你到github上随便一搜,一定能得到一大堆的“中文编程头文件”“中文程序转换器”。
你看,只要你把C语言的32个关键字翻译一下,写到头文件里,github一丢,轻轻松松拿到几十几百万个star。
30分钟拿百万github star,这事真是想想都能笑醒。
但为什么就没人做这事呢?
太简单了。
俩字:扯淡。
就好像做数学题你不写Σ写求和、不写lim写极限、不写ln写以e为底的对数一样,纯属扯淡。
你不仅是为自己找别扭,你也是为所有看你的证明的人找别扭。
这一套符号,本就是键盘输入最简单、区分度最高又最为约定俗成的一套“速记符”;替换它不仅毫无意义,反而带来了输入、阅读、记忆诸方面的许多负担。
尤其是,诸如指针的指针、类A里面的类B对象的方法C返回的数据结构D里面的元素E这样的东西,拿“速记符”写,轻巧短小,一目了然;拿其他任何东西写……
只要真的接受过几天正规的编程教育,你就知道这东西纯属扯淡。
就好像只要上了初中,你就一定会写 4x+3y=20、就绝不会支持“四乘天元加三乘地元等于二十”这种写法一样。
因此,不会有人把这种东西丢上github,丢上去也不会有人关注它——除非故意恶搞。
反过来说也对:那些最为大力支持中文编程的人,也不会写这么个头文件出来。
因为这种东西,尽管吹、别落地,它看起来就是高大上的——越玄乎越好。
一旦落地,其中的荒谬滑稽,就透了出来。
嘴炮结束。现在让我们回归传统吧——talk is cheap, show me the code!
这是一段实际项目中的源代码。当然,我“鸡贼”的改了函数名,免得某些滥竽充数的家伙直接搜一些结论蒙混过关。
当然,这是段非常简单的代码。只要你的数据结构能学会六成以上,这是什么算法,你大概已经心中有数了。
嘘~~
别出声!
和我一起调戏调戏那些“中文编程党”吧【坏笑】:
void __cdecl SplitSort (
void *base,
size_t num,
size_t width,
int (__cdecl *comp)(const void *, const void *)
)
{
char *lo, *hi; /* ends of sub-array currently sorting */
char *mid; /* points to middle of subarray */
char *loguy, *higuy; /* traveling pointers for partition step */
size_t size; /* size of the sub-array */
char *lostk, *histk;
int stkptr; /* stack for saving sub-array to be processed*/
if (num < 2 || width == 0)
return; /* nothing to do */
stkptr = 0; /* initialize stack */
lo = base;
hi = (char *)base + width * (num-1); /* initialize limits */
recurse:
size = (hi - lo) / width + 1; /* number of el's to sort */
if (size <= CUTOFF) {
shortsort(lo, hi, width, comp);
} else {
mid = lo + (size / 2) * width; /* find middle element */
/* Sort the first, middle, last elements into order */
if (comp(lo, mid) > 0) {
swap(lo, mid, width);
}
if (comp(lo, hi) > 0) {
swap(lo, hi, width);
}
if (comp(mid, hi) > 0) {
swap(mid, hi, width);
}
loguy = lo; /* traveling pointers for partition step*/
higuy = hi; /* traveling pointers for partition step*/
/* Note that higuy decreases and loguy increases on every iteration,
so loop must terminate. */
for (;;) {
if (mid > loguy) {
do{
loguy += width;
} while (loguy < mid && comp(loguy, mid) <= 0);
}
if (mid <= loguy) {
do{
loguy += width;
} while (loguy <= hi && comp(loguy, mid) <= 0);
}
do{
higuy -= width;
} while (higuy > mid && comp(higuy, mid) > 0);
if (higuy < loguy)
break;
swap(loguy, higuy, width);
/* If the partition element was moved, follow it.Only need
to check for mid == higuy, since before the swap,
A > A implies loguy != mid. */
if (mid == higuy)
mid = loguy;
/* A <= A, A > A; so condition at top
of loop is re-established */
}
/* A <= A for lo <= i < loguy,
A > A for higuy < i < hi,
A >= A
higuy < loguy
implying:
higuy == loguy-1
or higuy == hi - 1, loguy == hi + 1, A == A */
/* Find adjacent elements equal to the partition element.The
doubled loop is to avoid calling comp(mid,mid), since some
existing comparison funcs don't work when passed the same value
for both pointers. */
higuy += width;
if (mid < higuy) {
do{
higuy -= width;
} while (higuy > mid && comp(higuy, mid) == 0);
}
if (mid >= higuy) {
do{
higuy -= width;
} while (higuy > lo && comp(higuy, mid) == 0);
}
/* OK, now we have the following:
higuy < loguy
lo <= higuy <= hi
A<= A for lo <= i <= higuy
A== A for higuy < i < loguy
A>A for loguy <= i < hi
A >= A */
/* We've finished the partition, now we want to sort the subarrays
and .
We do the smaller one first to minimize stack usage.
We only sort arrays of length 2 or more.*/
if ( higuy - lo >= hi - loguy ) {
if (lo < higuy) {
lostk = lo;
histk = higuy;
++stkptr;
} /* save big recursion for later */
if (loguy < hi) {
lo = loguy;
goto recurse; /* do small recursion */
}
}
else {
if (loguy < hi) {
lostk = loguy;
histk = hi;
++stkptr; /* save big recursion for later */
}
if (lo < higuy) {
hi = higuy;
goto recurse; /* do small recursion */
}
}
}
/* We have sorted the array, except for any pending sorts on the stack.
Check if there are any, and do them. */
--stkptr;
if (stkptr >= 0) {
lo = lostk;
hi = histk;
goto recurse; /* pop subarray from stack */
}
else
return; /* all subarrays done */
} 多页面视图
现在,我们汉化它的关键字,看看能不能降低理解难度:
空 函数调用模式乙 SplitSort (
空 *base,
尺寸类型 num,
尺寸类型 width,
整型 (函数调用模式乙 *comp)(不变的 空 *, 不变的 空 *)
)
{
字符 *lo, *hi; /* ends of sub-array currently sorting */
字符 *mid; /* points to middle of subarray */
字符 *loguy, *higuy; /* traveling pointers for partition step */
尺寸类型 size; /* size of the sub-array */
字符 *lostk, *histk;
整型 stkptr; /* stack for saving sub-array to be processed*/
若 (num < 2 || width == 0)
返回; /* nothing to do */
stkptr = 0; /* initialize stack */
lo = base;
hi = (char *)base + width * (num-1); /* initialize limits */
recurse:
size = (hi - lo) / width + 1; /* number of el's to sort */
若 (size <= CUTOFF) {
shortsort(lo, hi, width, comp);
} 否则 {
mid = lo + (size / 2) * width; /* find middle element */
/* Sort the first, middle, last elements into order */
若 (comp(lo, mid) > 0) {
swap(lo, mid, width);
}
若 (comp(lo, hi) > 0) {
swap(lo, hi, width);
}
若 (comp(mid, hi) > 0) {
swap(mid, hi, width);
}
loguy = lo; /* traveling pointers for partition step*/
higuy = hi; /* traveling pointers for partition step*/
/* Note that higuy decreases and loguy increases on every iteration,
so loop must terminate. */
为 (;;) {
若 (mid > loguy) {
做{
loguy += width;
} 当 (loguy < mid && comp(loguy, mid) <= 0);
}
若 (mid <= loguy) {
做{
loguy += width;
} 当 (loguy <= hi && comp(loguy, mid) <= 0);
}
做{
higuy -= width;
} 当 (higuy > mid && comp(higuy, mid) > 0);
若 (higuy < loguy)
中断;
swap(loguy, higuy, width);
/* If the partition element was moved, follow it.Only need
to check for mid == higuy, since before the swap,
A > A implies loguy != mid. */
若 (mid == higuy)
mid = loguy;
/* A <= A, A > A; so condition at top
of loop is re-established */
}
/* A <= A for lo <= i < loguy,
A > A for higuy < i < hi,
A >= A
higuy < loguy
implying:
higuy == loguy-1
or higuy == hi - 1, loguy == hi + 1, A == A */
/* Find adjacent elements equal to the partition element.The
doubled loop is to avoid calling comp(mid,mid), since some
existing comparison funcs don't work when passed the same value
for both pointers. */
higuy += width;
若 (mid < higuy) {
做{
higuy -= width;
} 当 (higuy > mid && comp(higuy, mid) == 0);
}
若 (mid >= higuy) {
做{
higuy -= width;
} 当 (higuy > lo && comp(higuy, mid) == 0);
}
/* OK, now we have the following:
higuy < loguy
lo <= higuy <= hi
A<= A for lo <= i <= higuy
A== A for higuy < i < loguy
A>A for loguy <= i < hi
A >= A */
/* We've finished the partition, now we want to sort the subarrays
and .
We do the smaller one first to minimize stack usage.
We only sort arrays of length 2 or more.*/
若 ( higuy - lo >= hi - loguy ) {
若 (lo < higuy) {
lostk = lo;
histk = higuy;
++stkptr;
} /* save big recursion for later */
若 (loguy < hi) {
lo = loguy;
去到 recurse; /* do small recursion */
}
} 否则 {
若 (loguy < hi) {
lostk = loguy;
histk = hi;
++stkptr; /* save big recursion for later */
}
若 (lo < higuy) {
hi = higuy;
去到 recurse; /* do small recursion */
}
}
}
/* We have sorted the array, except for any pending sorts on the stack.
Check if there are any, and do them. */
--stkptr;
若 (stkptr >= 0) {
lo = lostk;
hi = histk;
去到 recurse; /* pop subarray from stack */
}
否则
返回; /* all subarrays done */
} 多页面视图
看懂了吗?
是不是好懂多了?
真懂了?
那么,回答如下问题:
1、这个排序算法的复杂度级别是?
2、什么情况下,这个算法的复杂度会变成O(N^2)?
3、这个排序算法是稳定的吗?
还不懂?
那咱继续——现在,我们的目标是:不再有英文!
空 函数调用模式乙 分割排序(
空 指针 基,
尺寸类型 数字,
尺寸类型 宽度,
整型 (函数调用模式乙 指针 比较)(不变的 空 指针, 不变的 空 指针)
)
{
字符 指针 低, 指针 高; /* 当前排序的子数组两头 */
字符 指针 中; /* 指向子数组中间的指针 */
字符 指针 低小伙, 指针 高小伙; /* 分区步骤的行进指针 */
尺寸类型尺寸; /* 子数组的大小 */
字符 指针 低栈[栈大小], 指针 高栈[栈大小];
整型栈指示器; /* 用于保存要处理的子数组的堆栈 */
若 (数字 < 2 || 宽度 == 0)
返回; /* 不需要做任何事 */
栈指示器 = 0; /* 初始化栈 */
低 = 基;
高 = (char 指针)基 + 宽度 * (数字-1); /* 初始化限制 */
递归:
尺寸 = ( 高 -低) / 宽度 + 1; /* 需要排序的元素数目(良心翻译啊,el我都译了!)*/
若 ( 尺寸 <= 隔断) {
短排序( 低,高, 宽度, 比较);
} 否则 {
中 =低 + ( 尺寸 / 2) * 宽度; /* 找到中间元素 */
/* 把起首、中间、后端元素排序*/
若 (比较( 低,中) > 0) {
交换( 低,中, 宽度);
}
若 (比较( 低,高) > 0) {
交换( 低,高, 宽度);
}
若 (比较( 中,高) > 0) {
交换( 中,高, 宽度);
}
低小伙 =低; /* 分区步骤的行进指针 */
高小伙 =高; /* 分区步骤的行进指针 */
/* 注意:在每一次迭代中,高小伙递减且低小伙递增,
因此循环必然会终结。*/
为 (;;) {
若 ( 中 >低小伙) {
做{
低小伙 += 宽度;
} 当 ( 低小伙 <中 && 比较( 低小伙,中) <= 0);
}
若 ( 中 <=低小伙) {
做{
低小伙 += 宽度;
} 当 ( 低小伙 <=高 && 比较( 低小伙,中) <= 0);
}
做{
高小伙 -= 宽度;
} 当 ( 高小伙 >中 && 比较( 高小伙,中) > 0);
若 ( 高小伙 <低小伙)
中断;
交换( 低小伙,高小伙, 宽度);
/* 如果分界元素移动了, 跟随它.只需要检查中 ==高小伙, 因为在交换前,
A[ 低小伙] > A[ 中] 意味着 低小伙 !=中. */
若 ( 中 ==高小伙)
中 =低小伙;
/* A[ 低小伙] <= A[ 中], A[ 高小伙] > A[ 中]; 因此在循环之初,初始条件会重新建立 */
}
/* A <= A[ 中] for低 <= i <低小伙,
A > A[ 中] for高小伙 < i <高,
A[ 高] >= A[ 中]
高小伙 <低小伙
意味着:
高小伙 ==低小伙-1
or高小伙 ==高 - 1,低小伙 ==高 + 1, A[ 高] == A[ 中] */
/* 识别相邻元素和分区元素相同的情况。分成两个循环是为了避免调用 比较( 中, 中), 因为传入的两个指针指向同一位置时,某些比较函数无法正常工作。*/
高小伙 += 宽度;
若 ( 中 <高小伙) {
做{
高小伙 -= 宽度;
} 当 ( 高小伙 >中 && 比较( 高小伙,中) == 0);
}
若 ( 中 >=高小伙) {
做{
高小伙 -= 宽度;
} 当 ( 高小伙 >低 && 比较( 高小伙,中) == 0);
}
/* 好了,现在我们得到如下状态:
高小伙 <低小伙
低 <=高小伙 <=高
A<= A[ 中] 当低 <= i <=高小伙
A== A[ 中] 当高小伙 < i <低小伙
A>A[ 中] 当低小伙 <= i <高
A[ 高] >= A[ 中] */
/* 我们已经结束了分割。现在我们希望排序子数组[ 低,高小伙] 和 [ 低小伙,高].
我们先处理较小的那个数组,以尽量少的占用栈
我们仅对长度大于等于2的数组排序。*/
若 (高小伙 -低 >=高 -低小伙 ) {
若 ( 低 <高小伙) {
低栈[ 栈指示器] =低;
高栈[ 栈指示器] =高小伙;
++ 栈指示器;
} /* 为将来保存大递归 */
若 ( 低小伙 <高) {
低 =低小伙;
去到 递归; /* 执行小递归 */
}
} 否则 {
若 ( 低小伙 <高) {
低栈[ 栈指示器] =低小伙;
高栈[ 栈指示器] =高;
++ 栈指示器; /* 为将来保存大递归 */
}
若 ( 低 <高小伙) {
高 =高小伙;
去到 递归; /* 执行小递归 */
}
}
}
/* 我们已经排序了数组,但栈里保存的除外。
检查一下栈里是否还有未排序数组,排序它。 */
-- 栈指示器;
若 ( 栈指示器 >= 0) {
低 =低栈[ 栈指示器];
高 =高栈[ 栈指示器];
去到 递归; /* 从栈里弹出子数组 */
}
否则
返回; /* 所有的子数组已经排序 */
} 多页面视图
这回看懂了吗?
有没有变得更好懂呢?
真懂了?
那么,回答如下问题:
1、这个排序算法的复杂度级别是?
2、什么情况下,这个算法的复杂度会变成O(N^2)?
3、这个排序算法是稳定的吗?
很显然,所谓“中文关键字/中文标识符就能让程序变得更好懂”完全是在扯淡。
如你所见,这个程序甚至还变得更难懂了。
为什么更难懂了?
1、缺乏合理的命名规范,整个程序命名太过随意,完全无法区分不同语言元素。
如果你熟悉C编码规范,通过大小写以及命名规范,很容易看出哪些是常量、哪些是变量、哪些是函数——通过规范的编码来说明程序功能、快速读懂一个陌生程序,这是程序员的基本功。
但缺乏规范和大小写区分的中文翻译抹杀了这些区别,把它变得更难懂了。
2、互联网上已经有海量的、关于这个算法的分析资料。
但一旦译成中文,因为资料的贫乏,你反而再也得不到任何帮助。
你的知识,再也不可能更新,再也不可能提高。
翻译者给你画多大个圈,你就只能呆在这个圈里面。翻译者忘了喂你,你就只能饿死。
3、真正理解这段程序,需要的并不是看这段代码。
恰恰相反,想看懂这段程序,课本上是整整一章的内容;加上其他相关知识,你起码要看4、50页书,这才能明白它的设计思路,这才能学会它。
而随意翻译断开了“具体代码”和“相关资料”之间的链接。
那么,这段代码,尤其是这段中文代码,如果单独拎出来的话,这个世界上恐怕都不会有人能看懂它。
因为只要我不说,就没人能把里面的指令、指示、数据类型、存储对象区分开。
那么,真正的程序员是怎么学、怎么写、怎么应付这类情况的呢?
很简单,做好索引,甚至干脆数字化。
比如说,编译器发现你的程序错了,它不仅会给你一串中文/英文提示信息,还会给你个C开头的四位数字。
所有这些数字都遵循同一个规范;因此你只需打开浏览器,把“C4996”输入,搜索,好了,问题解决。
类似的,链接器发现你的程序有问题,它也会给你个LNK开头的四位数字——这个数字同样遵循同一个规范。
类似的,MySQL、Windows、Linux,它们报的所有错误,也都会带一个唯一的错误码。
到网上搜这个错误码,你就你能得到你的母语写的、铺天盖地的心得体会、排错经验。
类似的,各种语言、各种框架/库,它们往往也会用相关的接口名称作为关键字,帮你索引相关文章。
比如,建立socket时,ErrorCode等于394是什么意思?
你看,socket、ErrorCode、394,这三个关键字就能锁定问题。
替换了其中任意一个,你就慢慢头疼去吧。
所以,你看,把现有代码替换成中文真的很蠢。
它彻底的断绝了你求助的可能,杜绝了你进步的可能——因此,对中文编程鼓吹者,有一个算一个,肯定没一个懂编程。
当然,在程序里使用中文仍然是可行的、有益的。比如说,国内程序员习惯用中文注释:
//经理说这里要慢一点,客户掏钱再给他优化
sleep(30000
本帖来自安卓秘书
页:
[1]