学好编译原理对于学习一门编程语言是十分重要的,对于理解程序如何在系统中运行也是很有帮助的。但是似乎大部分计算机专业的人都没有把这门课学好,编译原理学不好没关系,但是其中的一些概念还是应该理解的。
编译型语言VS解释型语言
有这样一个面试题,Java是编译型语言还是解释型语言?
这个问题其实有三种答案:编译型语言;解释型语言;既是编译型语言,也是解释型语言。其实这三个答案不重要,关键是看怎么解释了。
解释型语言:An interpreted language is a programming language that avoids explicit program compilation. The interpreter executes the program source code directly, statement by statement, as a processor or scripting engine does. This can be contrasted with compiled language programs, which the user must explicitly translate into a lower-level machine language executable.
编译型语言:A compiled language is a programming language whose implementations are typically compilers (translators which generate machine code from source code), and not interpreters (step-by-step executors of source code, where no pre-runtime translation takes place).The term is somewhat vague; in principle any language can be implemented with a compiler or with an interpreter. A combination of both solutions is also increasingly common: a compiler can translate the source code into some intermediate form (often called bytecode), which is then passed to an interpreter which executes it.
总而言之,编译型语言就是多了一个从源代码翻译成机器语言的过程,先翻译成机器语言,然后再让机器一句一句解释机器语言。而解释型语言相当于直接就是机器语言了(这个机器可能是虚拟的),直接把代码拿过来就开始一句一句解释了。
实际上,编译型语言和解释型语言的界限很模糊,维基百科上都说了,The term is somewhat vague,不必在这两个概念上纠结,但是还是有必要了解一下。一般认为编译成机器语言(物理机器)的语言称为编译语言,而直接就执行源代码(Shell,Python)或者编译成虚拟机的机器语言(Java,C#)的称为解释型语言。因此更倾向于Java是一种解释型语言,但是它跟Shell绝对不是一个类型,Shell是堂堂正正最典型的解释型语言。
把Java看成一种编译型语言,是因为它有一个编译的过程,只不过编译后是字节码,是JVM的机器语言,而不是我们常用的机器语言,但是其实有些物理机器它的机器语言就是JVM,因此说Java是一种编译型语言没错。
把Java看成一种解释型语言,也没错。因为JVM读取字节码,翻译成真正的机器语言,这是一个解释的过程。
编译时机制VS运行时机制
在编程时一般会遇到编译时错误和运行时错误。例如直接用对象指针访问对象的private成员就是一个编译时错误,private成员只有编译时是受保护的,在运行时,只要得到了这个成员的地址,就可以随便访问了。因此Java放弃了指针,实现了对私有成员的真正保护。
还有像sizeof,const,final等等都是编译时机制。sizeof(xxxx)在编译之后就成了一个立即数(有一种特殊情况除外),const的情况比较复杂,而final修饰的变量一般也都直接替换成了立即数。
关于sizeof()
又是一道面试题,我出的。
- #include <iostream>
- using namespace std;
- int main()
- {
- int a[sizeof(int)];
- int *b = new [sizeof(int)];
- cout <<sizeof(a);
- cout <<sizeof(b);
- return 0;
- }
假入这段程序在32位系统上运行,结果是什么呢?结果应该是16,4;
为什么呢?
int a[sizeof(int)]也就是int a[4],它的类型是int [4],长度为4的int数组,所以大小为16.
int *b,就是一个普通的指针,在32位系统上大小为4.
sizeof可以看成宏,但是宏是在预处理时替换的,sizeof是在类型检查时替换的。
再来一道面试题
i = 0;
j = sizeof(i++);
执行完后i的值是多少?
这道题的答案是0。sizeof在大多数情况下是编译时定值的,表达式中的任何副作用(包括有副作用的运算符、函数调用等)都不会发生。
上面说了大部分情况,对于sizeof(),有一种特殊情况,就是C99中的可变长度数组,使用sizeof()时,是运行时求值的。
- int fun(int n)
- {
- int a[n];
- printf("%d\n",sizeof(a));
- return 0;
- }
- int main()
- {
- fun(5);
- fun(10);
- return 0;
- }
两次调用的结果是不同的。
还有一道面试题。如何用函数实现sizeof()。方法在我以前的博客里有
http://nxlhero.blog.51cto.com/962631/732347
const修饰符
对于const的理解
1.首先,const是一种编译时机制
- const char * name="xinliniu";
- char * name="xinliniu";
"xinliniu"存储在程序的常量区,不能修改。
使用了后者,如果在代码中有name[0] = 'a',可以编译通过,会在运行时出错。
而使用前者,在编译时发现name[0]='a'就编译不过。C/C++提供了const,可以让你在编译时就找到错误。这种机制称为语法盐,定义见后面。
2.const的作用范围
没有指针的情况放哪都一样
const int a = int const a
有指针的情况不一样,分为是指针本身只读和指针指向的地址只读
const int *a = int const *a 指针本身可变,指针指向的地址不可变
int * const a 指针本身不可变,指针指向的地址可变
const int * const a = int const * const a 都不可变
3.const变量只能在声明时赋值,可以赋值其他变量,也可以直接赋值,总之,只能在声明时初始化一次
4.不可写是相对的,只是说不能通过这个const类型的变量修改
例如
- const int c = 4;
- int main()
- {
- const int a = 5;
- int * b = &a;
- *b = 6;
- printf("%d\n",a);
- b = &c;
- printf("%d\n",*c);
- *b = 5;
- }
这个程序以C语言编译后,会发生运行时错误。a被放到了栈里,运行时不受保护了。而c放在了数据区的rodata区,也就是常量区,对他进行写操作会发生段错误。
但是上述语言以C++编译时,根本编译不过,因为编译器直接用立即数代替了a和c,这种机制称为常量折叠,因为这些常量不应被修改,替换成立即数后可以提高性能。Java中的final变量也会被替换成立即数。
对于C/C++来说,程序在运行时最常碰到的是两个错误:段错误,缺少库。
有一个面试题,free()函数如何知道要释放多少内存?
看下面的代码:
- int main()
- {
- int *a = malloc(4000);
- printf("%d\n",a[-1]);
- printf("%d\n",a[-2]);
- a = malloc(20);
- printf("%d\n",a[-1]);
- printf("%d\n",a[-2]);
- return 0;
- }
运行结果:
是不是能看出点端倪。实际上,这地方和操作系统关系不大。操作系统就给程序一个堆,起始地址,结束地址,大小,都存在给你,然后你爱咋用咋用,完全可以自己实现一个内存分配和回收的函数。上述代码使用gcc编译,libc对内存的管理应该是在分配的内存前边保存一个头部信息,其中包含申请内存的长度。
关于编译时和运行时,还有一些很多概念,例如编译时多态,运行时多态等等。
语法糖(Syntactic Sugar)VS语法盐(Syntactic Salt)
语法糖:Specifically, a construct in a language is called syntactic sugar if it can be removed from the language without any effect on what the language can do: functionality and expressive power will remain the same. For instance, in the C language the a[i] notation is syntactic sugar for *(a + i) .
语法糖去掉后,并不影响语言的功能。例如a += b,可以使用基本的 a = a + b代替,a[i]可以使用基本的*(a+i)代替。判断一个语法是不是语法糖,就是讲看看这个语法有没有最基本的可替代的语法。
语法盐:The metaphor has been extended by coining the term syntactic salt, which indicates a feature designed to make it harder to write bad code. Specifically, syntactic salt is a hoop programmers must jump through just to prove that they know what's going on, rather than to express a program action. For example, Javawill not allow you to declare a variable as an int and then assign it a float or double value, while C and C++ will automatically truncate any floats assigned to an int.
语法盐是为了防止写出不好的代码,可能给编程者带来不便。上面说的Java不能为一个int类型的赋值一个浮点常量,这称为强类型(strong typing)。
强类型(Strong Typing)VS弱类型(Weak Typing)
强类型:Most generally, "strong typing" implies that the programming language places severe restrictions on the intermixing that is permitted to occur, preventing thecompiling or running of source code which uses data in what is considered to be an invalid way. For instance, an addition operation may not allow to add aninteger to a string value; a procedure which operates upon linked lists may not be used upon numbers. However, the nature and strength of these restrictions is highly variable.
如果一个语言是强类型的,那么它会对不同类型的变量或常量之间的混用做严格限制。某个地方只能用int,就不能用float。
例如Java中,int a; a = 1.5; 编译不能通过。而在C中,这段代码可以通过,编译器隐式地将1.5强制转换成了int,但是此处的转换没有那么简单。
另外,每种类型都有一些与之对应的操作,例如int类型的自加,a++,但是对于float是没有这样的操作的,但是C/C++中可以对float的进行自加。
下面是C代码:
int a = 1.5; //这段代码直接被编译器变成了int a = 1;
float b = 1.5;
int a = b; //这段代码就不是实际上牵涉到一大堆复杂的操作,也即将浮点数的整数部分提取出来,存到a中
转换的时候需要很多条指令。在C和C++中,编译器为你做了,但是Java中你必须显示的执行转换操作(例如调用一个函数,虽然这个函数Java为你提供了)
int a = float_to_int(b);
弱类型:弱类型是与强类型相对的,弱类型淡化了类型的概念,例如C/C++,最弱的就是汇编语言,几乎没有类型的概念,在C/C++中,一般情况下占字节长度相同的类型就可以看做一种类型,甚至长度不同的也可以。
动态类型(Dynamic Typing)VS静态类型(Static Typing)
静态类型语言是指在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型,某些具有类型推导能力的现代语言可能能够部分减轻这个要求. C/C++,Java等都属于这种类型。一个变量被使用前,编译器必须知道它是什么类型。
动态类型语言是在运行时确定数据类型的语言。变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。
A programming language is said to be dynamically typed when the majority of its type checking is performed at run-time as opposed to at compile-time. In dynamic typing values have types, but variables do not; that is, a variable can refer to a value of any type. Dynamically typed languages include APL, Erlang, Groovy, JavaScript, Lisp,Lua, MATLAB, GNU Octave, Perl (for user-defined types, but not built-in types), PHP, Pick BASIC, Prolog, Python, Ruby, Smalltalk and Tcl.
动态语言的例子(Python):
- a = (Integer)raw_input("input a num")
- if a == 1:
- var = 1
- else:
- var = "12345"
- var = var + 1
- print var
运行结果:
在执行var = var + 1这条语句之前,根本不知道var是什么类型,所以在运行时会出现类型错误。
再看下面这个例子
- my_variable = 10
- while my_variable > 0:
- i = foo(my_variable)
- if i < 100:
- my_variable++
- else
- my_varaible = (my_variable + i) / 10
(上面这段代码,下面的变量拼错了,不易查错,这是动态类型的一个坏处)
以上英文部分均摘自维基百科。