Contents
数组,指针,字符串(上)
绝大多数执行语句语法其实已经说完了。无非还有些零散的操作以后谈。几乎常用的代码,你都可以实现,不过仅是说语句语法部分,诸如
i+3
j < 5
for(i = 0 ; i < 4 ; i++) {
}
while (1) {
}
do {
}while(0)
if () { } else { }
goto
等等。其实绝大多数的你所能见到的程序,无非有四种东西,就可以实现其全部逻辑了。
- 操作,诸如+,- ,* ,/ ,%,>> ,<< 等等。
- 比较 , 诸如 < ,> ,!= ,== ,<=,>=等等。
- 判断, if
- 跳转 if (){} else()
鬼话:相信我,只要有上面4个部分,你的任何见到的程序逻辑都可以描述了。无非有时会比较笨拙,因此有了for , while 等。不过我们现在还是没办法把MVC继续完善下去。因为存储空间,我们还停留在位宽的概念上,这不够。
前面提到过指针,指针是C语言里的一个大学问,为了突出这个“大”字,所以我对指针尽可能少用教科书的语言,而是反复的用存储空间的值,和指向两个概念来谈。如下: char p; 这是啥意思,咱们需要复读机,按行分,跟着我读,记得从右向左读: p 这是一个存储空间,申请空间的名称为p,存放的是一个指针,这个指针指向一个空间,哪个?下面这个。 (打断下,这里不需要你跟读,我说的是p,不是char p;) * p 这是一个存储空间,存放的是一个指针,这个指针指向一个空间,哪个?下面这个。 * p 这是一个存储空间,存放的是一个指针,这个指针指向一个空间,哪个?下面这个。 * ***p 这是一个存储空间,存放的是一个指针,这个指针指向一个空间,哪个?下面这个。 char ,这个存储空间大家就不用读了吧。
别怀疑我排版有问题。我可不是混行数的。但注意,只有第一行有 “申请空间的名称为p”。因此这里你需要确保了解2个事实。
- char ***p,别数有多少个就认为这有多少空间。实际只有一个空间,位宽是指针类型,这个空间的名字在代码和编译器看到的,就是p。
- 存储空间里的内容指向哪并不重要,重要的是指向的空间对应的类型。此处的类型有两层含义,一、是“是否是指针”,二、最终指向的空间是什么类型。
关于第二点,你可以如此想一下,如同俄罗斯的套娃,一层层的套,总算最终有个娃了,但至少每层模样相同,外面是美女,里面最终的娃也得是一样的美女,不能外面是关羽,里面是项羽。
为什么要这样,编译器在最终指向实际类型空间时需要检测类型宽度。下面说说怎么利用p。为了不绕口令,我们用3级指针,别搞4级指针了。
鬼话:通常数组将维度,二维,三维,指针我叫做“级”,级联的级。你问我是哪篇英文大作的哪个英文单词的翻译?拜托我野鬼自创的,只要能说明问题,为什么非要翻译英文。顺带说个冷笑话
问,为什么C的入口函数不是entry,而是,main?
答一:是因为发明C做实例的家伙,忘记entry怎么拼写了。
答二:错了,其实是因为main 比entry 少了个字母。
答三:你们,有没有学问?是叫主函数。而不叫入口函数。 主函数翻译成英文就是main,entry那叫入口,没文化真可怕,国人就这素质!
这个笑话的意思是说,何必认真于名词,除了老师和形而上学的研究C而不是利用C的。标准和潜规则叫什么就是什么。默默的享受吧。
回到正题,三级字符类型指针定义就如下: char **ppp; 对比下,我们前面曾经有过类似的例子 char p; 那么这是一级的,一级的指针p,存放的是个指针,指针指向一个char位宽的空间。那么按照这个位宽顺着数i个char位宽的位置所对应的char 位宽的空间,我们就叫做 p[i],注意,p[i] 和 char a;一样,两个都是个存储空间,不是值也不是变量。
对应的, ppp[i]是什么呢?一个char 位宽的空间?非也,非也。是一个 类似char p;的空间。p是二级的,显然是个存放指针的空间。而对应 ppp[i]则是,ppp这个存储空间里的值所指向的空间,我们顺着数i个指针位宽所对应的指针位宽的空间。
你不信?那么我们看 ,ppp[0] 和 ppp[1]总表示不同的内容吧。好的,
ppp[0]的存储空间的地址是什么?就是在 ppp这个存储空间里存放的值。如果ppp[1]对应的空间地址和ppp[0]对应的空间地址的差*8,小于一个指针的位宽(例如32位),那么ppp[0]的存放的数据不就和ppp[1]存放的数据打架了吗?
那么你也可以想,为什么ppp[1]存储空间的地址,不能大于和 ppp[0]存储空间地址差*8呢?你高富帅,内存买得起。你可以查查发明C时,电脑价格和内存价格。当然,由于电脑确实越来越便宜,特别是内存(实际是外存,CPU外面的),因此从算法优化的角度,在一些特殊情况,例如不是4的倍数,8的倍数的存储结构,扩展到这些倍数,以提高数据获取的效率,这得另谈。
由此你可以理解,ppp[i] 和 ppp[i+j]两个存储空间差了多少位? sizeof(void )8j ,存储空间的地址差了多少?sizeof(void )*j
这里扩展介绍个玩意,sizeof,在参考文献1 6.5.3.4 当然兼顾需要看6.2.3.1,对应 7.19.2中给出了sizeof返回类型,是个size_t 类型。一个无符号的整型。位宽多少,你的自己看咯。
sizeof是一个操作,和 + , <= 等一样,是个操作(全称是计算操作),特殊的是,他是个个编译器帮你算好的操作。有两种用法,通常如我上面的方式,sizeof(type name),就是()里放个类型。为什么是void ,与其你不记得是个什么指针类型,不如但凡是指针的地址宽度的计算,你都使用void ,一个指向没有意义的空间(其实可能有意义,无非你在看这个指针本身时不需要考虑指向的空间是什么类型和位宽)。
这里突出强调sizeof的利用,是因为,在C语言里,对于位宽,以及地址偏移等,经常使用sizeof,多写点sizeof不仅体现你的水平,更体现你的品味。
但需要记得,sizeof返回的是针对地址空间不是位。而计算机的地址,每个地址空间有8位,所以要乘以8。
那么我们看一下,ppp[0][3] 和 ppp[0][4]这两个存储空间的地址他们之间差了多少?很显然,还是sizeof(void ) ,我特意没有写sizeof(void**)就是希望强调,
鬼话:指针都一样,如果我们不考虑他存储的空间里的值,所指向的空间是什么类型,那么就是指针。信我,工程上方便你思考问题,不信我,信研究派,那你可以出论文。
但 ppp[0][0][3] 和 ppp[1][1][4],这两存储空间的地址差了多少?问我?回答:不知道!没错,不单单我不知道,编译器也不知到。为什么?我们回顾一下ppp[0]和ppp[1]里放的是什么?
废话,当然是指针咯。好,我们回顾一下,char p; 对应的 p[0],p[1],这是两个存储否?是的。是否相邻?显然。什么类型?char型。存什么? 不知到。对了。同样的道理, ppp[0] ,ppp[1],他们是两个存储空间,他们也是相邻,但他们里面存放的值,你并不知到。由此,ppp[0]和ppp[1]里面存放的内容可以完全没联系,甚至相等。那么自然,ppp[0][1] 的空间,应该是 ppp[0]里存储的内容(一个指针)所指向的空间地址(假设是 A)加上 sizeof(void ),,而ppp[1][1]的空间,应该是 ppp[0]所指向的空间地址(假设是 B)加上 sizeof(void *)。
A和B是不确定的。你怎么敢说 ppp[0][1] 和ppp[1][1]里存的内容的关系是确定的?这不确定,你又怎么敢说 ppp[0][1][3] 和 ppp[1][1][4]的两个存储空间的地址的关系是确定的?
级联的指针,就有这个问题。不单单有这个问题,还有所指向空间在哪的问题,这个和后面我讲多维数组的差异就非常大。因此我特别喜欢说,char ****pppp;是4级指针,而不是4维指针。
鬼话:哪个老师说后者,你可以大声的带我问他,你写过程序吗?你吃过BUG的苦吗?
当然,ppp[i][j],甚至ppp[i][j][k]是完全符合编译标准的。不过类似 char p;我们曾经有过介绍, p[i] 和 (p+i)的存储空间是完全一样的。注意反复强调 (p+i) = 3; 意思是,把3放到 (p+i)对空间里,该空间的地址是 p这个存储空间里的值所指向的地址+3个char 所对应的空间。
p[j] = (p+i),意思是,将(p+i)的空间里的值取出来,再放到p[j]里的空间。至于这个空间的地址,我就不复读机般的再解释了。但你始终还要知道,(p+i)不是变量,不是值,而是一个存储空间。这个取地址空间内容的操作对应的*p,意思是取出p这个存储空间的值(它是个地址),指向另一个 存储空间。
回到前面说的一个问题,char ***ppp;定义了。那么ppp[0], ppp[1][2],ppp[3][4][5]指向哪?首先,ppp[0]就是个空白支票,你爱写哪写哪。而且和ppp[1]还没关系,因此,你要知道 p[3][4][5]指向哪,你必须用代码明确,ppp[3]指向哪,随后,明确,ppp[3][4]指向哪,最后明确 了ppp[3][4][0] 在哪。
注意,我可没说一定要明确 ppp[3][4][5]指向哪。为什么呢? 记得,ppp是三级指针,ppp[i][j][0]已经指向了实际存储空间,此时这个空间存放的是char 类型的数据。而不在是个指针。既然我们定义了 char ,这可不是白写的。那么实际已经约束了ppp[i][j][0]和ppp[i][j][1]之间的地址差异。就是sizeof(char)
那么 char ***ppp;申请的是一个存储空间,ppp[0],ppp[1][2],ppp[3][4][5]这些空间哪呢?显然,编译器不会帮你分配,爱指哪指哪,不过更多情况,你可能需要一个独立的空间来使用并指向。于是乎。。。。
我要抄起锣,“哐,哐,哐.."的敲无数下,大喊“瞧一瞧,看一看啦。。。”,当然不是看猴子,我也长的不像猴子,而是“隆重“的介绍C语言的两个标准函数,malloc和free,你可以在参考文献7.22.3.4&7.22.3.3 中间找到他们,当然你可以对应把7.22.3都看一下,对比下malloc和其他几个函数的区别。
之所以如此隆重,是因为,几乎我现在写代码,没有这两玩意,都不知道怎么写了。先说下 malloc 函数接口,也即原型如下: void *malloc(size_t size); size_t前面介绍过了。那么size 表示空间数量。你可以理解你的酒量。总要有点面子嘛,和你兄弟去喝酒,你可以大喊,小二,来2瓶二锅头,哦,不是2两装的,要一斤的那种,或者,小二,5箱啤酒,记得大支的,12一箱的,或者,小二,给我2立方白开水(如果你实在没酒量)。。。“您这是喝酒呢?还是搓澡呢?”,不怕不怕。你可以很牛的摔两个一元的钢蹦,说“咱有的是钱,买得起内存,只怕你操作系统支持不得,哦错了,怕这小店里没那么大的鱼缸”
注意两点。
- malloc之类,是动态分配内存的。啥意思?程序运行到malloc这个函数,这个函数是标准库实现的,所以有对应的代码,去向操作系统要空间。这个空间是按照堆的方式管理的(堆是啥方式?自己到相关部门索取资料,操作系统的内存管理)。但记得,这个空间是动态获取的,所以也经常叫堆空间。和堆栈空间,一般为了区别,更多叫栈空间不一样,和编译器的数据段的空间也不一样,后两者是编译器分配的。包含在了程序的执行代码里面,程序进入运行,形成进程时,就已经对应存在了。
- size是要申请的存储空间大小,但是里面放的内容,通常我们按照单位空间*数量的方式来描述。你问我为什么。我就问你了,为啥你要说大支装的,5箱?谁去酒家要两立方的水喝啊?
一种学院派的做法如下,我们仍然使用model.c文件。完整如下:
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
jmp_buf context_buf;
#if 0
static int test = 0;
static void model_done(void){
int i;
printf("param_done func !\n");
for (i = 0 ; i < 100; i++){
printf("touch the dog! \n");
if (test >= 10){
printf("the officers come ! run away!\n");
longjmp(context_buf,test);
}
test += 1;
}
return;
}
#else
static void model_done(void){
char *p;
int i,j;
p = (char *)malloc(sizeof(char)*100);
for (i = 0 ; i < 100 ;i++){
p[i] = i;
}
for (i = 0 ; i < 100 ; i++){
if (p[i] != i){
printf("why?\n");
return ;
}else{
j += i;
}
}
printf("the total from 0 to 99 is %d\n",j);
return;
}
#endif
static void model_exit(void){
printf("param_exit func !\n");
return;
}
void model(int status){
int t;
int flag = 0;
do {
switch (status){
case 0:
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
}
break;
case 1:
model_done();
break;
case 2:
model_exit();
break;
default:
printf("error ! the status value is illegal!\n");
status = 2;
flag = 1;
}
}while (flag);
return;
}
编译链接,运行,打印出了 the total i from 0 to 99 is 4950 计算正确。表示确实至少有100个空间了。否则,肯定有某个空间被重新覆盖了,会导致计算的结果不对。至于是否实际空间有多少是这个连续的块?这事,我不告诉你,因为需要看实际怎么分配内存的,同时,我建议你,别想歪点子,你就认为只有100个空间。
先注意下两个,
- #include <stdlib.h>这个你看标准的标准库的函数时,一定要注意,某个函数属于对应的哪个头文件进行函数接口定义的,否则又有warning,有这个warning一般不会出故事,不过要出,通常都是事故。
- p = (char )malloc(sizeof(char)100); 中,(char )是什么意思?注意,malloc的原型是 void ,你不写,也没错,如同我前面说的例子,指针类,sizeof(void )足以,因为指针类型,本身就是指针,不在乎你指向哪。但这个根据编译器不同,会有不同warning。当然这个warning不会出故事。不过个人建议,加上(char)。(type)的意思是,将后面的内容,强制转换成type类型。不加,通常会有warning,不过除了两头都是指针外,有时(不是一般咯)会出事故,而不单单是故事。此放后面展开讨论。
为什么说是学院派。通常学院派的性子都比较急,认为写代码和改作业一样,如果一段时间不出成果就会受不了,你要让他规划个3年开发计划,多半会崩溃,所以空间一拿到,就想用。
而庸俗的工程派会怎么样呢?malloc后,最多在这个函数里做初始化工作,而用在别的地方用。而且通常一个空间从系统开始就申请,在系统释放时在丢弃。没办法,庸俗啊,好不容易发了点工资,先存着,该花时再花。
?这里有个丢弃的动作。对了,这就是free。free函数也很简单。只要告诉它曾经申请到的空间的地址,当然这个地址被一个指针类型的空间存放,那么就可以告诉操作系统,这个指针的空间里存放的值所指向的那个地址,可是你曾经给我那个块的第0个位置,你总该有记录吧,我还给你,不要了。
工程派这么做有什么好处呢?如下:
- 任何一个系统都有边界。因为硬件资源是有限的。你不可能无休止的获取资源。所以设计系统时,有一点非常强调,我支持的处理能力,这就对应了,我实际最大能处理的工作任务所需要的存储空间。而如果这个存储空间你一开始不申请足了,真的工作任务来了怎么办?难道说,客户问题,都有哪些服务?你说,怎么样都行,真得开始了,你左一个“哎呀,今天不方便”,右一个,“哎呀,没地了”你让客户怎么想?
- 如果用时在申请,操作系统虽然大多数时间不是很忙,但将心比心,一个人动不动让你做同样的事情,你烦不烦?你做事情也要成本,操作系统申请空间也要时间,如果你在一个循环里,申请,利用,释放,何苦呢?别以为一个函数里,你不会这么做,经常学院派的高雅写法是在计算过程中,循环调用包括申请,利用,释放的函数。
- 工程设计,强调一个稳定性,其中包括资源的稳定性。稳定的地址空间,可以为有效的保证计算性能提供一定帮助。
因此,我们应该如下写,先给出value.h ,value.c的代码
#include "value.h"
#include <stdlib.h>
static int init_flag;
static char *s_pmodel = 0;
static int init_model(void){
if (s_pmodel == 0){
s_pmodel = g_pmodel = (char *)malloc(sizeof(char)*100);
return 1;
}
return 0;
}
static void free_model(void){
if (s_pmodel){
free(s_pmodel);
s_pmodel = 0;
g_pmodel = 0;
}
}
static int init_status(void){
g_status = 1;
return 1;
}
static void free_status(void){
return;
}
int init_all(void){
if (init_flag == 1) {return init_flag;}
init_flag = 1;
init_flag = init_flag & init_status();
init_flag = init_flag & init_model();
return init_flag;
}
int get_init_status(void){
return init_flag;
}
void free_all(void){
if (init_flag == 0) {return;}
init_flag = 0;
free_model();
free_status();
return;
}
int g_status;
char *g_pmodel;
value.h的内容如下
#ifndef _VALUE_H_
#define _VALUE_H_
extern int g_status;
extern char *g_pmodel;
int init_all(void);
void free_all(void);
#endif
model.c的 model_done函数修改如下:
#else
static void model_done(void){
char *p = g_pmodel;
int i,j;
for (i = 0 ; i < 100 ;i++){
p[i] = i;
}
for (i = 0 ; i < 100 ; i++){
if (p[i] != i){
printf("why?\n");
return ;
}else{
j += i;
}
}
printf("the total from 0 to 99 is %d\n",j);
return;
}
#endif
这里先说说value.c里的几个改动。
- 多了 static int init_model(void) 和 static void free_model(void),并分别在init_all()和 free_all里面被调用。
鬼话:一定要养成习惯,涉及malloc申请空间的函数和其对应的释放函数尽可能的靠近。同样,init_model,和free_model是针对模块的,你可以将一个模块的所有动态数据申请,在这里申请个够。
- 切记,申请的函数,和释放的函数,要使用,局部函数定义static来约束。也就是其他C代码不可见。这就是模块封装的一个精神。但和面向对象的所谓私有函数保持举例,咱是面向模块,别串,串了你,哪天你摔粪坑里可别说是挖的。因为free的正确释放需要对一个指针所指向的地址正确的获取,这也是为什么我增加 static char *s_pmodel = 0;的原因,s_pmodel是不会被外部改变的。而g_pmodel可能会被外部改变。
鬼话:或许又要有人说了。你累不累啊,搞两个存储空间,空间不要钱啊,我只想说,设计代码的行为习惯和常用手法,都是我无数次摔坑的经验总结。等哪天你苦命的抓指针的问题时,或许能理解我,另可多要个空间,简单的事情搞复杂,也不图学院派的高雅,潇洒,咱就是一占满臭粪的庸俗野鬼。
- s_pmodel在定义时设置了0,确保了第一次init动作可以执行空间,且不会s_pmodel不会被连续两次申请,导致第一次申请的空间地址被丢失,从而无法还给操作系统。虽然操作系统在进程退出时,会帮你清理空间,但是写代码要有原则,好借好还。
- 注意
free_model();
free_status();
和 init_flag = init_flag & init_status(); init_flag = init_flag & init_model(); 的顺序。申请空间,有时不一定都能申请到,那么你可以关闭部分功能模块,而保留系统最基础的服务。这种栈式写法,则有助于在你增加了些辅助信息时,能有效的释放该释放的空间,即便你可以通过一堆的s_pxxx是否不为0,来判断是否释放。但一个行为习惯,最好坚持,否则摇摆不定,我只会送你一句话,骑墙的主,通常某些地方会疼。 - 重复讨论下,g_pmodel,和 s_pmodel,这里建议,通常的指针,前缀p,是否下划杠隔离后面的设计空间名称,看习惯。虽然g_pmodel会因为外部代码的逻辑错误被改写,也导致指针乱飞,内存出错的问题,但是这个问题,和s_pmodel如果错误导致内存出错的问题的性质不一样。你甚至可以不定期的比较g_pmodel 和s_pmodel是否一致,做自我检测,这对抓代码BUG都是有帮助,这在后面BUG定位方法中会展开讨论。
好了。其他没什么了。value.h里记得加上外部存储空间标记。就是extern char *g_pmodel;
这就好了。真的好了。或许有人会问,你空间什么时候申请的 ?init_all啊。init_all在系统刚启动,不就折腾了吗?这就是结构化某块化编程的好处。各忙各的事,model你做你的逻辑操作。空间申请的事情别乱操心。
扩展讨论下,一个好的模块化设计是什么?各有标准。就我的理解如下:
鬼话:好的模块化设计,无非是方便抓BUG,方便加逻辑。对于后者就是,团队一致性的惯性思维,什么动作该什么模块做,那么仅修改该模块就可以搞定。当然,模块内部有子模块,因此也可以说,几个动作组合的业务需求,你可以很容易且非常精确的分散到各个模块中,用最少的代码实现,这就是好模块的设计。这中间有个经验,不是可以教的,是要自己悟的,就是,业务逻辑在模块化设计中的逻辑投影问题,也就是如何拆分一个需求成各个明确的模块内部动作。对了,怎么悟?很简单。和我一样,多摔几次粪坑。
这里说了一级指针的空间申请。那么对于二级,三级指针呢?我们先回顾下,三级指针。 char *ppp; 这个ppp的空间,有了。编译器帮你分配好了。但是 ppp[0]的空间没有,不妨假设你p里面存放的指针,向指向的空间及其随后的空间可以粗放5个char pp的内容。那么至少有 5个 指针型。申请完了,再依次对ppp[0]到ppp[4]里的存储内容进行设置,就是再次申请空间5次,分别保存到ppp[0]到ppp[4]里。因此应该有如下逻辑。假设三级指针分别对应了5,6,7的空间大小,注意,是7个char类型的空间。
char ***ppp;
int i,j;
ppp = (char ***)malloc(sizeof(void *)*5);
for (i = 0 ; i < 5 ; i++){
ppp[i] = (char **)malloc(sizeof(void*)*6);
}
for (i = 0 ; i< 5; i++){
for (j = 0 ; j < 6 ; j++){
ppp[i][j] = (char *)malloc(sizeof(char)*7);
}
}
此时,对应ppp[1][2]里面存放了一个指针,这个指针指向了一个地址,由该地址开始,连续的7个char 类型的位宽对应的空间被申请到。这个和 char p = malloc(sizeof(char)100);类似。
鬼话:不同级的指针不谈,但对于诸如 (type )malloc开头的,你需要仔细检查是否是sizeof(type)。嘿嘿,其实我个人习惯是 ppp[i] = (char **)malloc(sizeof(char)6); ppp[i][j] = (char)malloc(sizeof(char)*7); 这样检查起来更方便。
为什么我仍然坚持告诉你如果是指针类型,管他指向的什么类型都用void *呢。因为上面方法被我淘汰了。因为开发经验的积累和方法的学习,我更喜欢用另一个方法,如下,修改,value.c,例如三级指针,描述一个三维空间(注意我开始使用维度用语,因为说维度,通常可以联想到正交空间,每个维度不相关,因此申请起空间来,是个方方正正的家伙),分别对应5,6,100。
static char ***s_pmodel = 0;
static int init_model(void){
if (s_pmodel == 0){
#if 1
int i;
char **pp;
s_pmodel = (char ***)malloc((5 + 5*6)*sizeof(void *) + 5*6*100 *sizeof(100));
s_pmodel[0] = (char **)s_pmodel + 5;
for (i = 1 ; i < 5 ; i++){
s_pmodel[i] = s_pmodel[i-1] + 6;
}
pp = s_pmodel[4] + 6;
for (i = 0 ; i < 5 * 6 ; i++){
pp[i] = (char *)(s_pmodel[4] + 6) + 100* i;
}
g_pmodel = s_pmodel[0][0];
#else
s_pmodel = g_pmodel = (char *)malloc(sizeof(char)*100);
#endif
return 1;
}
return 0;
}
记得 #if 1的用法,如果你尝试替换一段代码,做临时使用,或者尚未通过测试和决定,它有存在价值,记得#if 起来,确保老代码存在。
注意上面的函数中几个细节
只有一个malloc,此处把,5个char 类型的存储空间,56个char 类型的存储空间,和56100个char 类型的存储空间一次申请够。有啥好处?你看我没有贴出free_model的函数,表示,free_model的函数不需要修改。
鬼话:而使用前面的例子,记得有几个malloc显示的写出来,那么就应该有几个free也显示的写出来。那么我需要修改两个函数。 同时,所有申请的空间是连续的。是否物理连续放一边不谈。至少逻辑空间连续,可以更好的确保数据的连续性。这个在算法优化CACHE和内存访问调度策略中会展开讨论。
s_pmodel[0] = (char **)s_pmodel + 5; 为什么加5.我们继续复读机一下。
- s_pmodel此时是一个存储空间,里面存放的内容是个指针,指针指向的存储空间是个二级指针类型,即char **。根据空间要求,这样的有5个
- s_pmodel[0]此时是一个存储空间,里面存放的内容是个指针,指针指向的存储空间是个一级指针类型,即char *。根据空间要求,每组这样的空间有6个。
- s_pmodel[0][0]此时是一个存储空间,里面存放的内容是个指针,指针指向的存储空间是个字符类型,即char 。根据空间要求,每组这样的空间有100个。
- 所以s_pmodel[0]里面放的是char ,而自身是一个char **的存储空间。这样的空间有5个。自然s_pmodel[0]需要指向char ,我们已经占用了5个,所以要加5。
有啥不理解的,可以把这些存储空间所在的地址打印出来。一个存储空间所在的地址是什么值?取地址操作啊,&.注意*是取当前地址空间的内容所指向的存储空间。
这样有什么好处?连续啊。连续有什么好处,除了快点。我还可以明确知道s_pmodel[0][3]和s_pmodel[1][4]的存储空间内的值的差异啊。而且,如果你尝试要对上述三维空间的所有内容做统一的设置,例如设置-1那么你可以如下:
char *p = s_pmodel[0][0];
for (i = 0 ; i < 5 * 6 * 100 ; i++){
p[i] = -1;
}
这不比
for (i = 0 ; i < 5; i++){
for (j = 0 ; j < 6; j++){
for (k = 0 ; k < 100; k++){
s_pmodel[i][j][k] = -1;
}
}
}
清爽多了?
且慢,三维空间不是5,6,100吗?你这个i明显出界了你最大可以取到 2999的值。野鬼你不学好啊,教大家如何指针溢出,跑飞指针?
显然不是。注意p的含义,是什么,char ,我们对三维空间的实际char的申请,是统一的,因此此时char p,p表示一级指针,谁说是三维空间了。我就不能把10瓶2两二锅头倒一个碗里,让你一口喝完啊?
鬼话:编写C语言,一旦设计到指针,你要有个概念,它的含义是什么,它指向的空间大小是什么,如同闭上眼睛,你能想到里家里每个房间的尺寸大小,形状。若问我,我那么大一个系统呢,怕什么?初入一个城市,你会晕,待个10年8春秋的,各个路还不都熟悉了?给你点时间,你会习惯的。
虽然你完全可以这么写
for (i = 0 ; i < 5 * 6 * 100 ; i++){
s_pmodel[0][0][i] = -1;
}
类型没有任何问题,编译器会很认同,学院派会给出10种证明方式,证明s_pmodel[0][0][i]和p[i]是一会事,但我仍然要说,一个重要的代码设计习惯,对我的团队甚至是要求
鬼话:代码要逻辑正确,而不单单是编译器认可的逻辑,这个逻辑包括业务逻辑,空间逻辑。
扩展的说下#define 。方法前面已经简答的介绍过了。为什么说他?注意我们代码
s_pmodel = (char ***)malloc((5 + 5*6)*sizeof(void *) + 5*6*100 *sizeof(100));
s_pmodel[0] = (char **)s_pmodel + 5;
for (i = 1 ; i < 5 ; i++){
s_pmodel[i] = s_pmodel[i-1] + 6;
}
pp = s_pmodel[4] + 6;
for (i = 0 ; i < 5 * 6 ; i++){
pp[i] = (char *)(s_pmodel[4] + 6) + 100* i;
}
如果你的领导,10分钟后,说,不行,我们需要 67180的数据,你改吧。漏了一个地方,例如:
s_pmodel = (char ***)malloc((6 + 6*7)*sizeof(void *) + 6*7*100 *sizeof(180));
s_pmodel[0] = (char **)s_pmodel + 6;
for (i = 1 ; i < 6 ; i++){
s_pmodel[i] = s_pmodel[i-1] + 7;
}
pp = s_pmodel[4] + 7;
for (i = 0 ; i < 6 * 7 ; i++){
pp[i] = (char *)(s_pmodel[4] + 7) + 180* i;
}
相信我,你的代码有很大情况下,会出事故,不单单是故事。你找找哪错了。
那么如果我们用#define 就很方便了。可以如下写value.h的前半部分
#include "value.h"
#include <stdlib.h>
#define S_PMODEL_3 6
#define S_PMODEL_2 7
#define S_PMODEL_1 180
static int init_flag;
static char ***s_pmodel = 0;
static int init_model(void){
if (s_pmodel == 0){
#if 1
int i;
char **pp;
s_pmodel = (char ***)malloc((S_PMODEL_3 + S_PMODEL_3* S_PMODEL_2)*sizeof(void *) +S_PMODEL_3* S_PMODEL_2* S_PMODEL_1 *sizeof(char));
s_pmodel[0] = (char **)s_pmodel + S_PMODEL_3;
for (i = 1 ; i < S_PMODEL_3 ; i++){
s_pmodel[i] = s_pmodel[i-1] + S_PMODEL_2;
}
pp = s_pmodel[S_PMODEL_3 - 1] + S_PMODEL_2;
for (i = 0 ; i < S_PMODEL_3 * S_PMODEL_2 ; i++){
pp[i] = (char *)(s_pmodel[S_PMODEL_3 - 1] + S_PMODEL_2) + S_PMODEL_1* i;
}
g_pmodel = s_pmodel[0][0];
#else
感觉何如?其实我知道,我带的新人,嘴巴上会说,哦,这样也可以,你真牛。其实心里说,不行了,看得想吐了。吐就吐吧,一堆宏定义,吐吐就习惯了。
有啥好处?领导让你改,不行我们得改回去,还是5,6,100这样比较好看。那么你只要改动3个地方如下
#define S_PMODEL_3 5
#define S_PMODEL_2 6
#define S_PMODEL_1 100
就OK了。宏定义,有一个最重要的优势,不是文本的便捷替换,而是逻辑的含义明确。对于上述代码中,任何设计到100的,当前都是表示一个逻辑描述含义,就是三维空间的最低维的大小,那么你就应该用宏替换掉。因为,如果你只看到了文本替换的方便,你可能会不理解,为什么我实际代码会如下写
#define S_PMODEL_3 6
#define S_PMODEL_2 7
#define S_PMODEL_1 180
#defein S_PMODEL_P_NUM (S_PMODEL_2 * S_PMODEL_3)
并且将诸如 for (i = 0 ; i < S_PMODEL_3 * S_PMODEL_2 ; i++){ 改成 for (i = 0 ; i < S_PMODEL_P_NUM ; i++){ 这不是让你吐的程度减轻。而是存在明确的含义,表示1级指针的数量。而S_PMODEL_1表示第0维的空间大小。
补充一点,需要注意,但凡宏定义中,存在操作,无论什么操作,诸如 * + - 比较,强制自己写上(),诸如 #define TEST(a,b,c,d,e) ((((a * b) + c) >>d) != e) 你写成 #define TEST(a,b,c,d,e) (a * b + c >>d != e) 恩,你可以自信漫漫的认为操作优先级,你考试满分。
鬼话:听我一句劝,好记性不如好习惯,任何操作都按照逻辑要求加上(),比你熟记优先级要有效的多。重点不是优先级问题,重点是非常明确的描述了逻辑关系。代码易读性大大增强。所以说,但凡问我优先级的问题,和详细解释优先级的书,我都不理不睬,纯粹没事挖坑的主,我摔的够多了。不想再摔了。
对了。你找到上面的错了吗?如果没找到,我这列个答案。是忘了吧 pp = s_pmodel[4] + 7; 修改为 pp = s_pmodel[6 - 1] + 7; 了。
又有谁,不知道 6 - 1 == 5呢?可要改的代码一多,有又谁能保证写上5呢?不是你计算功底不行,而是你在一堆1,2,3,4,5中间很难理解都是什么含义。
鬼话:当你看到了宏,你需要看到的不是宏,而是逻辑描述或逻辑关系,当然你要问我,它究竟是什么?废话,还是宏啊。
上一篇:你这个“死”循环
下一篇:数组,指针,字符串(中)