C++中的definition & declaration的区别,涉及到extern关键字
有关这两者的区别和联系,之前其实一直都非常的模糊,特别是extern关键字。这次读C++ Primer,在第二章正好读到,于是好好理解了一次,而且做了一些代码测试。结论是这样的:
1. definition只能用于变量,也就是定义一个变量,此时,变量的内存空间会被分配。诸如int i, int i = 10这样的都是definition,因为i变量会被分配内存。
2. declaration可以用于变量或类型(比如声明一个struct,但是不定义变量),如果用于变量,该变量不会被分配内存,而且前面必须加上 extern(表示这个变量的definition不是在这里,而且在其他地方,所以是extern);如果是类型,那就没什么好说的了,本来类型就不需 要分配内存,只有变量才需要内存,如果在类型的声明的前面加上extern,也是可以的,只不过这样做没有任何意义。注意:如果这样写:extern int i = 10; ,那么,这也是definition,因为给i赋值了,extern就和没加一样。
3. 无论整个Program有多少个源文件,代码量有多大,一个变量只能被definition一次,但是declaration不限次数。
注:说到这里,有关function方法的定义和声明,也是类似的。一般来说,如果没有写出方法中的代码,那么,这就是declaration, 如果写出了代码,那么就是对方法的definition了。所以,一般在头文件中写一个方法的declaration,比如void print_sth();,然后在源文件中写出方法的实现即可。只不过在这里,void print_sth();和extern void print_sth();效果是一样的,加不加extern都一样。
4. 综合以上三点,如果我们在一个a.cc中定义了一个全局变量int i,那么在b.cc中如果想使用这个i,那么,必须声明成extern int i才可以,如果也写int i,那就是重复definition。说 到这里,我在学习的时候就有一个疑问了:如果说,我们把int i这句代码写在一个名为common.h的头文件中,然后前面加上条件编译,最后这个头文件被a.cc和b.cc都include,这样a.cc和 b.cc中不就不需要extern int i这样的代码不就可以使用i了么?事实上,经过试验,这样的想法是极端错误的。
测试代码可以这样构建:common.h
a.cc这样:
b.cc这样:
然后编译: g++ -o test a.cc b.cc,出现错误:
/tmp/ccDYjlJE.o(.data+0x0): multiple definition of `i'
/tmp/ccNz3Dvy.o(.data+0x0): first defined here
collect2: ld returned 1 exit status
为什么会出现这样的错误呢?这里面有一个概念没有搞清楚:
A. 以为使用条件编译可以让 int i = 10; 这句代码只执行一次。事实上,我们通过这个命令行:g++ -E a.cc b.cc >& output ,这个命令行是让g++在做完预处理之后就停止,然后将预处理的结果打印到屏幕,然后我们打开output文件,在里面,我们会发现int i = 10;这句代码出现了两次。所以,结论就是:条件编译只在一个源文件中生效,换句话说,通过使用条件编译,我们可以保证在一个源文件中,不会产生多余的 include,但是,在多个源文件中,条件编译是无效的。注意:一个小知识点,g++的-E option不要和-o option连用,否则会导致输出的预处理后的代码不完整。
所以,上面的代码是错误的。根据上面的例子,我们又可以总结一些东西了,接着上面的第四点:
5. 在头文件中(.h文件中),一般我们只写declaration,所以,在头文件中,我们一般做的是:定义类型(各种struct,typedef等), 定义函数(前面说过了,没有代码的函数声明是declaration)。如果要在头文件中定义变量,那么,必须保证以下两点中的一点:
a)这个头文件只会被一个源文件include
b)将这个变量定义变成声明,也就是前面加上extern,然后在一个且只能有一个源文件中对该变量做definition
如果不是这样,那就必然出现multiple definition。任何源文件想要引用其他源文件中或头文件中定义的变量,必须要使用extern,表示该变量不是在当前源文件中definition的。当然,前提条件是这个变量是全局变量(废话 )。
所以,前面给出的那个测试例子,可以这样修改就OK了。在common.h中:
a.cc:
b.cc:
这样就OK了,程序编译通过,输出是:
i is: 10
i in b.cc is: 20
或者直接在common.h中,将 int i = 10; 改成extern int i;然后在a.cc中做definition:int i = 10; ,这样也是可以的。
1. definition只能用于变量,也就是定义一个变量,此时,变量的内存空间会被分配。诸如int i, int i = 10这样的都是definition,因为i变量会被分配内存。
2. declaration可以用于变量或类型(比如声明一个struct,但是不定义变量),如果用于变量,该变量不会被分配内存,而且前面必须加上 extern(表示这个变量的definition不是在这里,而且在其他地方,所以是extern);如果是类型,那就没什么好说的了,本来类型就不需 要分配内存,只有变量才需要内存,如果在类型的声明的前面加上extern,也是可以的,只不过这样做没有任何意义。注意:如果这样写:extern int i = 10; ,那么,这也是definition,因为给i赋值了,extern就和没加一样。
3. 无论整个Program有多少个源文件,代码量有多大,一个变量只能被definition一次,但是declaration不限次数。
注:说到这里,有关function方法的定义和声明,也是类似的。一般来说,如果没有写出方法中的代码,那么,这就是declaration, 如果写出了代码,那么就是对方法的definition了。所以,一般在头文件中写一个方法的declaration,比如void print_sth();,然后在源文件中写出方法的实现即可。只不过在这里,void print_sth();和extern void print_sth();效果是一样的,加不加extern都一样。
4. 综合以上三点,如果我们在一个a.cc中定义了一个全局变量int i,那么在b.cc中如果想使用这个i,那么,必须声明成extern int i才可以,如果也写int i,那就是重复definition。说 到这里,我在学习的时候就有一个疑问了:如果说,我们把int i这句代码写在一个名为common.h的头文件中,然后前面加上条件编译,最后这个头文件被a.cc和b.cc都include,这样a.cc和 b.cc中不就不需要extern int i这样的代码不就可以使用i了么?事实上,经过试验,这样的想法是极端错误的。
测试代码可以这样构建:common.h
- Code: Select all
#ifndef _COMMON_H
#define _COMMON_H
int i = 10;
void print_sth();
#endif
a.cc这样:
- Code: Select all
#include <iostream>
#include "common.h"
using namespace std;
int main()
{
cout << "i is: " << i << endl;
print_sth();
return 0;
}
b.cc这样:
- Code: Select all
#include <iostream>
#include "common.h"
using namespace std;
void print_sth()
{
cout << "i in b.cc is: " << i << endl;
}
然后编译: g++ -o test a.cc b.cc,出现错误:
/tmp/ccDYjlJE.o(.data+0x0): multiple definition of `i'
/tmp/ccNz3Dvy.o(.data+0x0): first defined here
collect2: ld returned 1 exit status
为什么会出现这样的错误呢?这里面有一个概念没有搞清楚:
A. 以为使用条件编译可以让 int i = 10; 这句代码只执行一次。事实上,我们通过这个命令行:g++ -E a.cc b.cc >& output ,这个命令行是让g++在做完预处理之后就停止,然后将预处理的结果打印到屏幕,然后我们打开output文件,在里面,我们会发现int i = 10;这句代码出现了两次。所以,结论就是:条件编译只在一个源文件中生效,换句话说,通过使用条件编译,我们可以保证在一个源文件中,不会产生多余的 include,但是,在多个源文件中,条件编译是无效的。注意:一个小知识点,g++的-E option不要和-o option连用,否则会导致输出的预处理后的代码不完整。
所以,上面的代码是错误的。根据上面的例子,我们又可以总结一些东西了,接着上面的第四点:
5. 在头文件中(.h文件中),一般我们只写declaration,所以,在头文件中,我们一般做的是:定义类型(各种struct,typedef等), 定义函数(前面说过了,没有代码的函数声明是declaration)。如果要在头文件中定义变量,那么,必须保证以下两点中的一点:
a)这个头文件只会被一个源文件include
b)将这个变量定义变成声明,也就是前面加上extern,然后在一个且只能有一个源文件中对该变量做definition
如果不是这样,那就必然出现multiple definition。任何源文件想要引用其他源文件中或头文件中定义的变量,必须要使用extern,表示该变量不是在当前源文件中definition的。当然,前提条件是这个变量是全局变量(废话 )。
所以,前面给出的那个测试例子,可以这样修改就OK了。在common.h中:
- Code: Select all
#ifndef _COMMON_H
#define _COMMON_H
void print_sth();
#endif
a.cc:
- Code: Select all
#include <iostream>
#include "common.h"
using namespace std;
int i = 10;
int main()
{
cout << "i is: " << i << endl;
print_sth();
return 0;
}
b.cc:
- Code: Select all
#include <iostream>
#include "common.h"
using namespace std;
extern int i;
void print_sth()
{
i = 20;
cout << "i in b.cc is: " << i << endl;
}
这样就OK了,程序编译通过,输出是:
i is: 10
i in b.cc is: 20
或者直接在common.h中,将 int i = 10; 改成extern int i;然后在a.cc中做definition:int i = 10; ,这样也是可以的。