简单的数据类型?感觉不那么简单。
C语言的数据类型我认为可以划分为3类,分别是空类型void、基本类型(字符型、整型、浮点型和枚举类型)和派生类型(数组、函数、指针、结构体和共用体)。
标准C上规定int类型范围为-32768 ~32767,然而实际上往往并非如此,这取决于编译器的设计,比如下述代码:
#include <stdio.h>
int main(int argc, char*argv[]){
printf("%d/n",sizeof(int));
printf("%d/n",sizeof(float));
printf("%d/n",sizeof(double));
printf("%d/n",sizeof(long long));
return 0;
}
在vc2008中的输出为:4 4 8 8。
这说明了在vc2008编译环境下,int类型占据了4个字节的内存空间。而看到int类型,很容易让人联想到自加运算(++),关于自加运算不妨看看a+++b、a+++++b和a+++(++b)有何区别。下面是测试的结果:
#include <stdio.h>
int main(int argc, char*argv[]){
inta,b,c;
a = b = c = 0;
//c = a+++b; //输出1,0,0
//c = a+++++b; //无法通过编译,提示++需要左值
//c =a+++(++b);//输出1,1,1
printf("%d,%d,%d/n",a,b,c);
return 0;
}
插入旁白:关于左值、右值,网上有人说是翻译错误,应理解为可寻址的和可读的,我觉得也颇有几分道理,但这里就引用编译器报错原文。
第一种结果很容易理解,先执行c=a+b,之后a自加1;第三种结果也好理解,在赋值运算前++b先得到执行,所以c为1,赋值之后a++得到执行。关键在第二种,为什么会编译出错?“++需要左值”是指哪个“++”?为什么是那个“++”?在此,不妨反问一句,如果让自己设计编译器,那么遇到a+++++b这样的语句应该怎样处理?先带着这个疑问继续探寻。
编译器会对a+++++b这样的表达式进行出错处理,但却接受a+++b这样的表达式,从一定程度上说明了编译器“聪明”到可以处理a++,但却还不够“聪明”去处理++b。当然,这里指的++b是特定地、位于a+++++b这样表达式当中的限定场合的。为什么呢?这就属于词法分析的问题了。仍然回到上面那个问题,如果让自己设计编译器,遇到a+++b要怎么办呢?
为了找出是哪个“++”需要左值,我们不妨将代码稍加改动为:
#include <stdio.h>
int main(int argc, char*argv[]){
inta,b,c;
a = b = c = 0;
c = ((a++)++)+b;
printf("%d,%d,%d/n",a,b,c);
return 0;
}
编译器仍然提示同样的错误,于是我们可以推出是第三个加号和第四个加号组成的“++”运算符需要左值,即a+++++b等效于((a++)++)+b。
想来现在的最新问题是,(a++)后面为什么不能接自加运算“++”?
回到l-value和r-value上来,不管采用左值、右值一说,或者采用可寻址值和可读值一说,其本质都是对内存的读写操作(我无法确定这里是否有寄存器操作),也就是说源操作数和目的操作数的地址要能确定。这问题个人感觉不是很好回答,因为依旧涉及到了编译器的设计问题,在此仅谈谈个人的理解。
++a是先对a执行加1操作,然后返回对a的引用,自然就可以确定此时a的地址,所以(++a)++是可以编译通过的(于visual c++ 2008编译环境,经博友Promi测试,VC6和GCC并不能编译通过)。而a++是新建了一个临时变量并赋予其当前a的值,再对a进行增1运算,最后返回临时变量值,此时就无法再编译的时候获得该临时变量的地址,于是造成了((a++)++)+b无法编译通过。
由此可知,在++a和a++不影响逻辑结构的情况下,我们应该倾向于使用++a。需要强调的是,我使用的是vc2008编译环境。
接着回到编译器设计考虑的问题。在C语言编译器的词法分析中有“贪心法”一说,因为存在“++”运算,所以遇到“+”自然要再往前看是否还有“+”。或许应该换一种说法,在我的知识范围内,词法分析都有“贪心法”一说,即识别单词过程应该包含尽可能多的字符。这很好理解,比如C语言中要求变量名必须以下划线或者字母开头,之后可以下划线、字母和数字混合,那么当我们遇到_abc123def时,我们难道分析道_abc就停止了吗?自然不是的,我们要继续往前以识别出一个完整的单词。这是词法分析的知识范畴,那么编译器是如何识别出标识符的呢?关于这个问题,杭电ACM网站上有这么一道简单题,可以去测试一下自己的思路对不,http://acm.hdu.edu.cn/showproblem.php?pid=2024。再深入想想,如果是判断合法的数值类型应该怎么操作呢?这里的数值类型包括整形、浮点型和指数型。
对于上述杭电2024题目,我第一次的思路是分为两种情况:第一个字母只能有下划线和字母,其它字母只能有下划线、字母或数字,于是产生了以下代码:
#include <stdio.h>
#include <ctype.h>
int main(int argc, char*argv[]){//hdu2024_1
intn,flag;
charid[100];
scanf("%d",&n);
getchar();//由于scanf会将'/n'放在缓冲区中,所以先处理掉它
while(n--){
flag = 0;
gets(id);
if(!(id[0]== '_' || isalpha(id[0]))) flag = 1;
for(int i=1; id[i] != '/0';++i){
if(!(isalpha(id[i])|| isdigit(id[i]) || id[i] == '_')){
flag = 1;
break;
}
}
printf("%s/n",(flag == 1) ? "no" : "yes");
}
return 0;
}
这种思路比较简单,适用于识别标识符,但如果是要识别数值呢(含整形、浮点和指数型)?这里给出另外一种识别标识符的思路,可以用来识别数值类型,即编译器设计时使用的状态转换图(或其改进后的自动机):
上述识别标识符转换图的含义为:开始状态为0,如果遇到字母或者下划线则转向状态1;在状态1下,如果遇到字母、数组或者下划线则仍转向状态1,遇到其它则表示识别出一个标识符了。由其可产生如下代码:
#include <stdio.h>
#include <ctype.h>
int main(int argc, char*argv[]){//hdu2024_2
int n,flag, state;
charid[100];
scanf("%d",&n);
getchar();//由于scanf会将'/n'放在缓冲区中,所以先处理掉它
while(n--){
flag = state = 0;
gets(id);
for(int i=0; id[i] != '/0';++i){
switch(state){
case 0:
if(isalpha(id[i]) || id[i] == '_') state = 1;
else{ flag= 1; break; }
break;
case 1:
if(isalpha(id[i]) || isdigit(id[i]) || id[i] == '_') state= 1;
else{ flag= 1; break; }
break;
case 2: //这里的case2对于hdu2024这道题目不是必须的,但在词法分析中却是需要的
flag = 1; break;
}
}
printf("%s/n",(flag == 1) ? "no" : "yes");
}
return 0;
}
数据类型果真不那么简单,上面那么多的“+”已经有点眼花缭乱了,但对于数据类型的知识范围而言不过是刚刚开了个头。现在给自己一道饭后甜点,char类型的单个字符为什么不能表示一个汉字字符呢?
2010-1-26 晚
----------------------------------------cuttingline----------------------------------------