Tag Archives: 定义

C++笔记: 定义,声明,头文件及其他 (Part 2)

2. 变量

这里主要是讨论全局变量的使用,对于局部变量(即在函数内部定义的,在栈上分配空间的变量)不予考虑。全局变量的情况与前一篇说的函数的情况类似,但是更迷惑人一些。全局变量的定义是在所有函数的外面,比如下面语句:

  int roba;

定义了一个叫做roba的int型全局变量。而全局变量的声明是这样:

  extern int roba;

全局变量与函数一样要求,定义只能一次,声明可以多次,在使用前编译器必须看到声明。如果多个源文件要共享一个全局变量,初学者常犯的错误有两种:一种是在多个文件中都写了int roba;,这样会导致链接时发生重复定义的错误;另一种是只在一个文件中写了int roba;,以为在其他的文件的文件中可以直接用,但实际上因为在其他文件中没有对这个全局变量的声明,所以会发生编译错误。正确的写法是,在某一个文件中用int roba的形式定义,而在其他用到该变量的文件中用extern int roba的形式声明。

如果要把变量放到头文件里,从前面的讨论可以看出,一定不要把int roba;这样的定义放进去(因为这样的话如果这个头文件被包括进多个cpp文件就会产生重复定义),而应该写extern int roba;这样的声明,然后在某个cpp文件中定义int roba; 。

与函数类似,如果我们不想其他的文件看到某个全局变量,也可以用static修饰它,表示它的作用域局部于此文件内。(注意我说的不是static的局部变量)

下面又开始说特例了。对于这种写法:

  extern int roba = 1;

是被当作定义而不是声明的,尽管加了一个extern。

另外一个要注意的是const型的全局对象,它默认是局部于文件的,也就是说默认就是static的。这是因为C++允许(并且鼓励)这样的写法:

  const int N = 100;
  int loli[N];

我们可以看出,编译器必须知道N的值以后,才能确定loli数组的大小并生成相应代码。所以如果像非const的全局变量一样,把const int N定义在别的文件里,编译器就没办法在编译时知道它的值,也就不能单独编译这个cpp文件,这就违反了上次说的那个要求。基于这个考虑,C++规定const全局对象是局部于文件的,也就是说我们可以安全地把 const int N = 100; 这样的东西放在头文件里,不会出现重定义。

我们也可以用extern const int N = 100;这样的写法强制使其全局可见,在其他的文件中用extern const int N;来进行声明,但我认为这样会产生不必要的混乱,对程序效率也并没有提升。可能有人会觉得如果在每个文件里声明一个局部于本文件的全局const对象会浪费空间,但实际上编译器基本都会把这个const优化掉的,产生的效果和古老的 #define N 100 宏定义是一样的。

(待续)

C++笔记: 定义,声明,头文件及其他 (Part 1)

最近看了很多IT民工相关的东西,感觉还是应该记录一下,不然很快就忘了。下面这些都是比较简单的内容,但是我之前并不是十分清楚,请路过大牛多多指点。

首先必须区分各种对象(这里的“对象”是一个广义的说法,它包括变量、函数、类等等东西)的定义(definition)和声明(declarition)。下面来分如下几种主要情况讨论:

1. 函数

我觉得这是一种最容易理解的情况。函数的声明就是函数原型,函数的定义就是函数的具体实现。编译器在遇到一个函数调用语句时,必须知道这个函数的原型以进行相应的类型检查,因此必须把该函数的声明(当然定义也可以,因为在此时定义隐含了声明)放在前面。

只要是比ACM题目那种一百行左右代码再大一点的项目,一般都不会把所有代码写在一个文件里。注意编译器的执行过程是分两步的,首先把每个.cpp文件(或者.c, .cc等等)分别编译成目标文件(通常是.o, .obj等等),然后再用链接器把目标文件组装成一个可执行文件。注意“分别”两字,也就是说,编译器必须能够不依赖其他cpp文件就可以把当前的cpp文件编译成目标文件。

我们先不考虑类等等内容,假设现在我实现了一系列逻辑上有一定关联的工具函数,为了清楚起见我把它们放在同一个util.cpp文件里,而把使用这些工具函数的其他代码放在文件main.cpp里,那么要编译main.cpp,必须知道里面用到的工具函数的原型,所以必须在main.cpp里有一份该函数的声明(只需要声明就够了,编译器可以据此生成压栈、取返回值等等汇编代码,至于函数内部的实现代码是在util.o目标文件里,等链接的时候组装在一起就可以)

然而,当有多个cpp文件都要用到这些工具函数时,如果我们每次都要在cpp文件前面附加一堆函数声明的话,就显得有点太啰嗦了。于是头文件就派上用场了。这时惯常的做法是把util里(想提供给外界使用的)函数的声明放在.h头文件里,如果另外的cpp文件要用到某个函数,只要把对应的头文件include进来即可。注意一定不要把函数的定义放在头文件里,否则的话,如果这个头文件被包含进多个cpp文件里,在链接时就会出现函数重定义的错误。也就是说,函数可以声明多次,但只能定义一次(有一个例外下面会提到)。

另外一点是,如果不想某个文件里的函数被外面看到,应该用static修饰。应该只开放那些想公开的函数,并把想公开的函数的声明放到对应的头文件里。

这里再插一句,头文件只是为了方便程序员,编译器是看不到头文件的,把include的文件插入到相应的位置之类的事情都是预处理器完成的。头文件不必有对应的.cpp文件,不必是.h扩展名,实际上它可以是随便什么东西,只要你愿意就好,编译器根本不care。

关于头文件还有一点,如果我们不小心的话,经常会出现同一份头文件被包含了多次的情况(有时候可能是不可避免的)。比如我自己写了一个roba.h,里面有一句#include <string>,又写了一个main.h,里面即include了roba.h,又include了<string>,这样string就被包含了两次。这样就有可能出现重复定义(因为头文件里不止有函数声明,还有比如类的定义),这时我们可以用#ifndef这个东西控制这个头文件的内容只被包含一次。常见写法如下:

  #ifndef _ROBA_H_
  #define _ROBA_H_
  //...
  //the body of roba.h
  //...
  #endif

最后再说一下inline函数,这是一个特例,inline函数的定义应该写在头文件里。因为编译器要内联这个函数,就必须知道函数的实现,不然的话是无法单独编译出一个目标文件的。对inline函数不必担心重定义的问题,它可以看作局部于所在的源文件的。