Contents
从 MVC 开始模块化编程(上)
MVC是什么,WIKI上可以查查。难得对WIKI鄙视一把。很多新手经常被老手忽悠。什么MVC模式,MVC架构。我不知道为什么特别是JAVA的程序员喜欢把架构放在嘴边。落在C开发上,大家还是低调点,别架构架构。架构设计,我在第四部分,C开发项目管理 中会提到,但绝不是什么MVC模块架构这种架构概念,而是软件系统组成架构。现在属于第一部分,C开发基础篇。我们最多只谈到模块。
MVC无非是三个英文单词的简写。也就是模型,展示,控制。绝大多数C的模块,可以分模型,即内部实现,接口两个部分。而接口,通常又存在对外输出,和对内输入。因此我们可以不太精确的对应,M位内部逻辑,V是外部输出,C是外部输入。
在本篇开始,我们需要具备新的资料。gcc的 C 库的手册。下载地址为: http://www.gnu.org/software/libc/manual
PDF格式其实蛮好。推荐。哈。
这里展开说一下,如果你编写C语言程序,可能被不同编译器在不同操作系统上编译。原则上最好坚持使用一种编译工具。这也是我推荐GCC的原因。因为适用广泛。不仅有各种硬件平台支持,也可在不同的操作系统上进行开发。而C标准虽然强制要求各个编译器提供标准库,但这些标准库并不足够,各个编译器对应的库,扩充了很多实用的函数,以方便C程序员开发。例如除了ISO C以外,还提供了POSIX的函数。POSIX具体可以WIKI,我就不多介绍。
回到正题。今天我们要做的是一个模块,用于将指定的输入参数属性进行解读,并输出。我们先确定一个设计任务如下:
设计目标:
- 要求对输入数据进行输出。
够简单吧。不急,想复杂的,本篇后续慢慢来。
设计方案:
- 使用C,设计一个程序,在程序名后输入参数。将参数对应打印在屏幕上。
OK,这个方案不抵触目标,你的项目主管签字同意。代码可以设计如下:
#include <stdio.h>
int main(int argc ,char *argv[]){
printf("the input is %s\n",argv[1]);
return 0;
}
在当前目录下,保存为 attr.c。 我们简化一下GCC的操作如下(现在编译和链接的概念不是我们讨论的重点嘛):
gcc attr.c -o attr 执行 attr 1 如果上述步骤一切正常,则会打印 the intput is 1
此时,你很HIGH的找领导。“我搞定啦”。领导晃晃悠悠的过来,对着屏幕输入
attr 1 2 此时,你的屏幕还是attr 1。很明显,交流问题。这种事情经常发生。设计目标里没说要支持2个,支持一个也算支持啊。
于是,你聪明了点,明确问领导,你需要支持多少个?领导,看看你,张开手掌。哦,5个。这简单。你会有如下写法,以你目前所学的C语言知识。
int main(int argc ,char *argv[]){
printf("the input is %s\n",argv[1]);
printf("the input is %s\n",argv[2]);
printf("the input is %s\n",argv[3]);
printf("the input is %s\n",argv[4]);
printf("the input is %s\n",argv[5]);
return 0;
}
保存,编译链接,形成执行文件,继续执行。
attr 1 2 3 4 5 则打印了5行。
领导,跑过来,看一眼屏幕,说,我需要每个参数有意义,你这1,2,3,4,5分别表示什么?就是input? 靠,不早说?其实领导以为你知道。当然也有概率是领导知道你不知道他知道你不知道,所以故意刁难一下。 那么现在领导要求,每个输入参数首先要是名称,其次要是数字。假设名称如下: height ,高度,width ,宽度, frames 幀数,InputMode 输入模式,OutputMode 输出模式 对于最初级的菜鸟,你琢磨,这不就是10个参数嘛。无非先输入的字符串,后输入的是数字,我去查参考文献1,在7.22.1数值转换库函数中,找到了7.22.1.4,strtol这个函数,strtol函数的使用方法为:
- 先给出第一个字符的地址,
- 再给出一个可以存放字符地址的地址的空间,当识别完一个数字后此时字符的地址会依次想后挪,将字符地址的空间里设置为下一个字符的地址,
如同10个男生在前,10个女生在后,你看哦男人,没兴趣,走到下个位置,又是男人,接着走,到了女生面前,听下来了。来一句,就是她了。此时你的位置,就被存在在第二个参数所指向的存储空间里。再次强调,第二个参数本身是个指针,指向一个存储区域,这个存储区域存放的还是个地址,而这个地址指向的是一个存放字符的存储空间。
base按照10进制,就可以。
随后我再用printf 的%d打印出来。由此绝对没问题了。 同时你也可以在参考文献 3 的20.11.1查到GCC的libc库中,该函数的解释。 无论哪种解释,你可以明确发现下面的代码有逻辑错误的地方。
教科书喜欢说对的,我野鬼倒很喜欢给你留错的,以思考。同时下面的(int)是强制转换,如果你去掉,gcc会进行warning 警告,表示存储位宽不匹配,此后续会展开,但此处先给出强制转换的一个实例,强制转换是C的一个利器,和指针一样,非常值得使用前提是对原理搞的清楚。
根据7.22.1我知道是stdlib.h对应的模块,所以要增加一个头文件的引入。于是代码修改如下:
#include <stdio.h>
#include <stdlib.h>
int main(int argc ,char *argv[]){
printf("the input is %s\n",argv[1]);
printf("the data is %d\n",(int)strtol(argv[2],&argv[2],10));
printf("the input is %s\n",argv[3]);
printf("the data is %d\n",(int)strtol(argv[4],&argv[4],10));
printf("the input is %s\n",argv[5]);
printf("the data is %d\n",(int)strtol(argv[6],&argv[6],10));
printf("the input is %s\n",argv[7]);
printf("the data is %d\n",(int)strtol(argv[8],&argv[8],10));
printf("the input is %s\n",argv[9]);
printf("the data is %d\n",(int)strtol(argv[10],&argv[10],10));
return 0;
}
编译,链接。执行 ./attr
height 10 width 8 frames 5 InputMode 6 OutputMode 7
打印出来,堪称完美。于是,你继续骚扰你领导。你领导瞄了一眼,说,你知道for是做什么的?此时,鬼话一下:
for 很简单,就是循环重复。你按你至少上面10个printf可以有5组,他们的差异只是在数值上。因此我们可以通过for循环来缩小代码。
for 循环相当于一个计数器,当计数判断成立时,就继续循环,不成立时就不再循环。因此,通常for循环,有三个内容是要填的。起始,判断继续的条件,以及每次循环后,对计数器的修正方法。
简单说,到马路边上看见一卖贵宾犬的,你问,100元卖不卖?对方说,100元?做梦,5块让你摸一下,没钱滚蛋您。你当场扔出500元,对他说,我摸100下。为了把5元摸回来,你每摸一下时间都超长,各种方式,反正只有当你把手拿出来,老板才在黑板上加上一次,过了很长时间,卖狗的终于发现,黑板上的数是100了,大声喊,停。于是,你很HIGH的叼着牙签,满足的走了。落下一地狗毛,卖狗的哭诉着说,都肿了。。。“老板,你这个沙皮怎么卖的啊?“
那么for循环就如下设计 for (黑白写0; 老板判断黑板是否小于100;老板在黑板上加1) 摸狗一次; for 循环,后面首先要有(),()里正常跟三个动作,中间用两个";"分割。首先写初始情况,其次写可以进行循环的判断,后面写每次循环后必须执行的动作。
如果你的循环动作只有一个。你大可以直接写“摸狗一次”,再加上;,如同我们printf,return等语句,后面均有分号。但是如果我们把镜头放慢,其实你摸狗一次的动作有许多细节要描述,例如: 手张开; 伸进笼子; 手拿出来; 按&摩按&摩自己的手,休息一下; 如果如下写: for (黑白写0; 老板判断黑板是否小于100;老板在黑板上加1) 手张开; 伸进笼子; 手拿出来; 按&摩按&摩自己的手,休息一下;
老板一定很开心,因为for循环只会针对随后的第一条语句。你可就惨了。5元可不是摸一下了,最多张开手时看一眼。为了保证5元物超所值,你需要用{}将你的每次循环的所有动作,{}起来。为了将来不吃亏,即便只有一个动作,摸狗一次,你也强制{}如下:
for (黑白写0; 老板判断黑板是否小于100;老板在黑板上加1){ 摸狗一次; }
省得以后一不小心,细分了动作后,摸一下成了看一眼。
落在C语言开发和计算机计算里,这黑板,实际就是一个存储区域。和黑板有大有小一样,实际存储区域也有大有小。小的有char 类型,只有8个位宽。大的有long long 有 64个位宽。 摸狗嘛,反正100下,你要10000下估计那狗得成标本了。因此我们只需要一个小黑板,char类型就可以了。但得在用之前提前说好。于是你在for语句前,就要做一个内存空间的申请。简单如下: char i; 记得有分号。 char表示8位的位宽。i表示一个存储区域。很多书籍包括标准,说这是个变量,你当然可以认为这就是个变量,但实际是个存储区域,就是那个黑板。于是for循环可以如下写
char i; for (i = 0 ; i < 100 ; i++){ 摸狗一下; }
i = 0;表示,将0这个值写在黑板上。i < 100,是一个判断,判断黑板上的值是否小于,如果小于则你可以继续摸狗。如果你写成i >=100,则意味着,你付出了500元,而一次没摸,老板说只有当你摸了100下你才能继续摸。这逻辑可就问题大了。i++表示黑板上的数值加上了1.其可等同于 i = i + 1;
i = i + 1;的解释很简单,将i这个存储区域的数值加1,再放回i中。也就是,老板把黑板上的数值记录下来,演算了一次+1的操作,再把答案重新写到黑板上。
我们重新回放下慢动作,前后顺序是:
- 我们找来一个黑板,也就是申请了一个存储空间i
- for循环开始,老板在黑板上写了个0
- 老板判断了一下黑板上是否小于100,如果小于,则放你摸狗,否则转到7
- 你很HIGH的摸呀摸,直到你完成了最后一个动作,把手拿出来。
- 老板在抄下黑板上的数,加上了1,重新写在黑板上。也即 i = i+ 1; 和i++有相同效果。
- 老板继续 3的动作。
这里需要注意,老板如果发现条件不成立,不会继续无聊的在黑板上加1.如果老板悄悄的将上面代码修改为
for (i = 100 ; i< 100 ; i++){ ... }
放心,黑板最后一定还是100。因为老板在第3步骤,就把你扔出去了。为什么i不是从1开始,而是从0开始,记得我们说的,一切从0开始。编程习惯而已。没有为什么。
回到办公现场吧。我们继续参数问题。此时你的代码就可以如下写:
#include <stdio.h>
#include <stdlib.h>
int main(int argc ,char *argv[]){
int i;
for (i = 1 ; i < 10 ; i+=2){
printf("the input is %s\n",argv[i]);
printf("the data is %d\n",(int)strtol(argv[i+1],&argv[i+1],10));
}
return 0;
}
这里说3点,为什么i 是从1开始,又不是从0开始呢?那是因为printf("the input is %s\n",argv[i]);在每次循环,正好都是奇数且从1开始,你何必死脑筋的听我野鬼一切从0开始的鬼话呢?
i+=2是什么意思? i += 2 基本等同于 i = i+2 ,即我们将i的存储区的数加上2后,放回原位置。如果读取计算后的存储是一个位置,则你可以将i=i+2 ;写为 i+= 2; i <= 10 应该也可以吧。毕竟我们有argv[10]的使用。这么写,这个程序没错。但是逻辑描述上存在问题。标准的这个循环逻辑是,对1到9进行输出,每次间隔2.而每次输出,除了自身值对应位置外,还输出下一个。因此我们应该是i < 10。而不是 i<= 10。如果我们的另一个循环逻辑是,对1到9进行输出,每次间隔2,每次输出除了自身值对应位置外,还输出后续第三个。难道你的循环判断,写成 i <= 12?如下
printf("the input is %s\n",argv[1]);
printf("the data is %d\n",(int)strtol(argv[4],&argv[4],10));
printf("the input is %s\n",argv[3]);
printf("the data is %d\n",(int)strtol(argv[6],&argv[6],10));
printf("the input is %s\n",argv[5]);
printf("the data is %d\n",(int)strtol(argv[8],&argv[8],10));
printf("the input is %s\n",argv[7]);
printf("the data is %d\n",(int)strtol(argv[10],&argv[10],10));
printf("the input is %s\n",argv[9]);
printf("the data is %d\n",(int)strtol(argv[12],&argv[12],10));
很显然,上面的for循环写法,应该仍然是 i < 10,和上述差别仅是 一个 +1 ,一个是+4,如下
for (i = 1 ; i < 10 ; i+=2){
printf("the input is %s\n",argv[i]);
printf("the data is %d\n",(int)strtol(argv[i+4],&argv[i+4],10));
}
鬼话:我称i是循环参量。在写代码是,需要非常注意该参量的变化,也就是涉及到该参量可能被修改的地方,同时不要让其他由该参量关联的值来影响对该参量的判断。例如上述10或者12对 i的边界判断。
现在你可以交差了。领导看了一眼,说,不错!并嘿嘿一笑说到:有问题。我记得我和你说的是50个参数吧。。。。 估计你没看到领导的坑。认为这个很简单。5个,是1到10,50个,那么就是1到50,其他不变。于是代码修改如下:
#include <stdio.h>
#include <stdlib.h>
int main(int argc ,char *argv[]){
int i;
for (i = 1 ; i < 50 ; i+=2){
printf("the input is %s\n",argv[i]);
printf("the data is %d\n",(int)strtol(argv[i+1],&argv[i+1],10));
}
return 0;
}
当场,交差。领导,说 “你运行一下”。得,你现在知道上当了。50个参数,意味着你要输入100个内容。
鬼话:如果你装过ubuntu和window 的双系统时,会出现时区差问题。就是有个系统总和实际时间不对,我们google一下解决方案,如下, 在Ubuntu下修改/etc/default/rcS 文档,将 UTC=yes 改为 UTC=no 即可。
这个/etc/default/rcS就是一个典型的配置文档。也可以看做参数属性文档。为什么我们不把50个参数写入到文档中去呢?然后一次性读上来,这多简单。这样有很多好处。
- 一个模块,一旦被编译链接完毕,则功能是固定的。但是不代表配置参数是固定的。我们完全可以对配置参数文件进行修改,重新启动模块,甚至模块内部存在一个类reset工作,用于重新加载参数的功能。
- 减少了模块与模块之间参数的传递,我们可以将所需要传递的复杂内容,描述成文件,通过传递文件名来实现两个模块的实际参数的推送。
那么我们现在修改下设计目标,如下:
- 程序存在一个参数,该参数位配置文件文件名。
- 当该文件存在时,读取该文件中的配置数据
- 当该文件不存在时,创建新文件,使用默认数据,并将默认数据写如对应路径文件名中。
在该设计目标的方案提出前,我们第一次讨论下指针的概念。你估计听过某些前辈,老师,老工程师说,C语言,指针很难的。其实真的不难。这里我们说两个例子。该设计方案将在本篇下中继续。
首先就是这个设计目标。程序后面跟的参数是什么?路径文件名啊。实际配置参数在哪里?在这个对应的文件里啊。那就对了。在新的设计目标里,程序的入口参数,argv[1]所包含的内容就是个地址。你全可当做实际配置参数的指针。你有了这个路径文件名,就可以有效索引到真正的配置参数。
另一个例子,我们前面依葫芦画瓢的写了很多char ,char 我们理解了,是个8位宽的类型。char的意思就是说,这是个指向8位宽类型的指针的类型。如果我们定义 char *p; 那么解释就是 这里有个存储区域,名字叫p,p里放的什么呢?是个指针,如同路径文件名一样,而指向了另一个区域,同时那个区域,编译器会认为是8位宽的类型,也就是说那个区域如同小黑板一样,只有8位。 那么对应p这个用于存放地址的存储区域又是多少呢?32位的系统,是32位宽,64位的系统是64位宽。也就是说,无论你的一个指针,指向的存储区是8位宽的还是32位宽的,哪怕是128位宽的(结构体类型的存储区域有这种情况,以后展开讨论),指针本身的存储区域的位宽只和系统有关。
如果我们只是简单的 char *p ;这样定义,你可能会问,那p指向哪?
那我要反问你,我送你块小黑板, char i;,你告诉我黑板上写什么,也就是说此时i里面存储了什么?
一样的道理。如果你只是 char *p;此时,p里面即便有内容,对你也没有意义。如同现在小黑板上,被前面的老板大大的写上了100的字样。而你想让p指向i这个存储区域的地址时,也就是指向黑板在哪里。你需要 p = &i;
&是取地址的操作。后面是一个存储区。p = &i表示,提取了存储区i的地址,并写入p这个存储区。而通常在申请一个p 的存储区时,你想对它进行设置,那么写法如下, char p = &i;这等同于如下 char p; p = &i; 如果你想申请2个指针存储区。那么应该如下: char p1; char p2; 当然可以简写一下,如下: char p1,p2; 这和char p1,p2;是不同的。,是用于区分不同的申请内容。而编译器优先看到的 char,一个8位宽的数据。实际表示指针含义的是 p。当然你可以使用typedef ,此我们后续展开。此时仅是一定要注意,
char p; 是跟着p的,因此 char *p,i;表示你同时申请了两个存储区。一个是指针,指向对地址对应的存储区是8位宽。另一个就是8位宽的存储区i。
这里展开讨论一下,没什么说,定义变量就是申请空间呢?定义变量,是通常教科书对 char i;的描述。或许有前辈对你说,申请空间?你的用malloc,alloc等等。那可是从堆上申请的。堆是什么?内存堆,由操作系统维护。而实际上呢? char i;本身也是在申请空间。这个空间是谁给分配的呢?是编译器。编译器在编译是,发现存在 char i;,则理解到,哦,这得有个小黑板。那么他的空间会在哪呢?一般是三种情况。
寄存器。如果寄存器足够空,INTEL的8086的寄存器是在太少。我更喜欢DSP的寄存器。好多好多。放宽一点,ARM的也有不少(下一篇会讨论ARM的汇编以解释C语言的逻辑实现)。因此如果这个小黑板仅是教室里上课用的,谁需要了。谁写,等下一个人想用时,擦了就行。
但寄存器毕竟总有限,因为寄存器是直接可以和计算单元,例如加法器,乘法器相连的。速度也是最快的。因此,如果寄存器不够,对于函数内申请的空间,编译器会指定使用堆栈空间进行存放。实在太大的,则根据编译策略,会放在数据区。前面已经说过,链接的作用,就是把代码区,数据区等等,连一连,分配分配。姑且你认为,编译时,至少申请到了。如同车牌摇号,你中奖了。
上面特地强调了函数内,那么函数外,不属于任何一个函数,落在文件里的char i;存放在哪呢?存放在数据区。还记得函数体需要{}进行框定吗?申请空间,也一样,如果你在函数内,那么不好意思,全当教室的讲台,进了这个函数,你上了这个讲台,那么这个区域才是你用的,无论它实际存放在哪。你爱摆什么东西,摆什么东西。但你离开了,不好意思,和你没关系了,如果私人物品,请带好,否则丢了别找我。下一个人用时,爱摆什么摆什么。
而如果不在函数里的空间申请。这就如同黑板。到了讲台,你可以用,下了讲台,你带不走,其他人可能也会用黑板,至于是继续在黑板上把50,进行+1,写上51,还是擦掉重新用,这得看他怎么处理。而你和他对这个黑板的协同默契,则有程序员决定。
需要注意,编译,是进行行识别的,依次重上到下完成,因此,对定义变量,也就是本篇谈的编译器型的空间申请,只有在本行即一下行中才能知道。而如果恰好的空间申请落在某个{}内,那么从本行开始,一直到}结束,}外就不知道了。这就是作用域的概念。
例如
int i;
for (i = 0 ; i < 2; i++){
int iii = 3;
iii += i;
//知道i,iii,不知到 ii
}
int ii = 3;
//知道 i, ii ,不知道 iii
{
int iiii;
int iii;
....
//知道 i, ii ,iiii,iii,但此处的iii和for {}里的iii不是同一个存储区域。哪怕他们实际存放的地方相同。
}
//知道 i,ii ,不知到 iiii,iii
这里鬼话几句:
原则上,所有{}内的定义,均在{一开始进行。而不要在中间写来写去。上述 ii的定义,换我审核代码,就直接打XX。虽然现在的国际标准允许任意位置定义变量,也即存储区域的申请。但是写代码,要做到心中有数,简单的一个设计标准,任何一个存储区(变量),你得证明缺了他逻辑描述就有问题。变量定义到处飞。你到处飞。则表示你对空间没有规划。我反复说,变量就是存储区。无论它在哪。一个没有空间概念的C程序员,是个不合格的C程序员。你可以按照iiii那样定义,但前提是,如iiii的位置那样加上{},这里没有其他关联的{}是允许的,通常是描述一个局部逻辑。我也称其位代码片。代码片,我会在或许介绍。你可以简单想想是拍电影的第N个镜头。
如果是循环类的计数器,通常定义为i ,j,l,k。这是历史原因,哈。ii ,iii,iiii都是不“老鸟”的写法。而如果是指针类,通常会以p开头,你可以p1,p2等等,也可以phead ,ptail等等。但记得p开头。如果你问我。我的循环嵌套了4层,也即循环里面有循环,后续会有专门讨论,我i,j,l,k不够用怎么办?那么就函数调函数。您能写出超过4层的循环,相信我,从算法性能优化角度,函数调函数不会吃亏。在第二部分,C算法设计与优化中会专门讨论。
你都注意到了。{}我喜欢将{紧跟在诸如for的描述,或函数描述后,而不是另起一行(另一种常见写法),这只是习惯。没有谁对谁错。我只能说,万恶的16:9显示器,我希望一个屏幕能看到更多行,所以这么写。而}你会发现,我要么和main函数的起始,int进行对齐,要么和for对齐,这叫缩进。有利于容易查找到这个 }对应的起始位置。
上面的1,2,3,鬼话,都出于一个目的,令代码方便阅读。什么是好代码?要看怎么解释好这个字。正确,自然需要,易读,易理解也是。如果你是新手,不妨听听我上面的鬼话,对你后面的代码的可阅读,可理解,会有所帮助。
//是用于注释的。你可以使用 //的方式。区别在于//是注视到行尾,/ /如同{}一样,是只注视所包含的部分。
这里感谢 @USIDCBBS 参与的变量申请空间的讨论。我们补充看一下下面的例子:
#include <stdio.h>
int main(int argc,char *argv[]){
int i;
for (i = 0 ; i < 3 ; i++){
int j = 0 ;
j = j+i;
printf("i=%d,j=%d\n",i,j);
}
return 0;
}
编译,链接,运行,你会发现打印的结果是0,1,2。很显然,j的空间分配只有一次。同时而每次循环均会对 j = 0;进行操作,而实际逻辑上是三个不同的空间。相反,你可以再试一下
#include <stdio.h>
int main(int argc,char *argv[]){
int i;
for (int j = 0,i = 0 ; i < 3 ; i++){
j = j+i;
printf("i=%d,j=%d\n",i,j);
}
return 0;
}
答案完全不一样。你可以琢磨琢磨为什么不一样。有点肯定。第一个例子,并不会创建3次j的存储空间。但每次进入{}后,j的逻辑含义是不一样的。第一次是第一次的,第二次是第二次,哪怕你没有做 j = 0;的赋值。而第二个例子,j却是在整个循环周期内,唯一的。当然补充一点,第一个例子,大多数情况下,编译器会将每次循环的j复用同样的地址。第一个例子,如果我们展开的逻辑等同于如下:
#include <stdio.h>
int main(int argc,char *argv[]) {
int i;
i = 0;
{
int j,jj,jjj;
j = jj = jjj = 0;
j = j+i;
printf("i=%d,j=%d\n",i,j);
i += 1;
jj = jj + i;
printf("i=%d,j=%d\n",i,j);
i += 1;
jjj = jjj + i;
printf("i=%d,j=%d\n",i,j);
i += 1;
}
return 0;
}
鬼话几点:
1.当你有几个相同位宽的存储空间,需要装填相同的数值,你完全可以类似我的写法j = jj = jjj = 0;但切记,不同位宽的存储空间,不要这么操作。虽然即便大多数情况下是对的。一个典型的例子如下:
#inlucde <stdio.h>
int main(int argc,char *argv[]){
int i;
char j;
i = j = 255;
printf("i = %d , j = %d\n",i,j);
j = i = 255;
printf("i = %d , j = %d\n",i,j):
return 0;
}
这里不讨论为什么 8位宽有符号类型,存储255会为-1,但这个例子非常明确的说明 i = j = 255的操作是 , j = 255,随后, i = j;如果存储类型的位宽不同,则可能引发高位宽向低位宽传递数据时的数据丢失,如同你的小饭盒装不下整锅菜一样。同时也会引发,使用低位作为高位宽存储数据中转的数据丢失。
这里我不想展开讨论不同位宽的转换问题,有这个功夫,不如你记得我上面的那句话,不是同一位宽,就别按上述方法书写。至于我没有强调不是同一类型,后续会展开讨论为什么。
2.如果需要for循环终止,i 是需要加到 不小于3的地步。因此,等同逻辑,在最后,第三次printf后,仍然有个i += 1;这个再后续,goto ,while ,do while,for 中会详细讨论。
上一篇:从一条内裤说起
下一篇:从 MVC 开始模块化编程(中)