C++声明与定义、内部链接与外部链接的意义

一. 声明与定义

A. 大多数情况下,声明与定义是相同的,但是有少些情况下,声明并非定义,而定义又非声明:
1.是声明,但是并不是定义:

1) 声明了一个没有具体说明函数体的函数; void declaration(int a,int b);
2) 包含了一个extern说明符,并没有初始化或函数体;Extern int number;
3) 它是一个类定义内的静态类数据成员的声明; class test{...; static int a; ...};
4) 它是一个类名的声明; class A;
5) 它是一个typedef声明; tepedef int INT;

2.是定义,但是并不是声明:

1) 定义了一个静态类数据成员;  int test::a = 4 或者 static int a=42) 定义了一个non-inline成员函数; void declaration(int a,int b){return (a<b?a:b)}

B. 一个声明将一个名字引入到一个程序中,一个定义提供了一个实体(例如,类型、实例、函数)在一个程序中的唯一描述
在C++ 中,声明与定义的区别在于:在一个给定的作用域重复一个声明是合法的。大部分声明可以重复,但是定义只能重复一次。

重复多次:

void declaration(int a,int b);
void declaration(int a,int b);
class A;
class A;
typedef int INT;
typedef int INT;
friend IntSetIter;
friend IntSetIter;
extern int number;
extern int number;

以上都是声明,在单个作用域内可以重复多次。另一方面,下面这些在文件作用域中的声明也是定义,因此在一个给定的作用域内不能出现超过一次,否则就会出发一个编译错误:

int x;
char *p;
Extern int number=1;
static int number;
static int f(int. int) {/* ... */}
inline int h(int. int) {/* ... */}
enum color{RED,GREEN,BLUE}   //只要是枚举的其实只能重复一次
const double DEFAULT_TOLERANCE = 1.0e-6
class stack{/*...*/}
union Rep {/*...*/}

函数和静态数据成员声明是例外,它们虽然没有定义,但是一个类的定义中也不能重复定义:

class NoGood{
        static int i; //declaration
        static int i; // illegal in C++
    public:
        int f(); //declaration
        int f(); //illegal in C++
}

二. 内部链接与外部链接

编译一个.c 文件时,C预处理器(C Pre-Processor,CPP)首先递归包含头文件,形成包含所有必要信息的单个源文件。之后,编译这个中间文件(称为编译文件)生成一个和根文件名字一样的.o文件(目标文件)。

链接程序把各种编译单元产生的符号链接起来,生成一个可执行程序。有俩种不同的链接:内部链接和外部链接。链接类型会直接影响我们如何将一个给定的逻辑结构合并到物理设计中。

(简单来说则俩步:1.对各个编译单元生成跟根文件(test.cpp) .o 文件(test.o),2.通过链接程序将各种符号链接起来,生成可执行文件)

1) 内部链接定义:内部链接意味着对这个定义的访问受到当前编译单元的限制。也就是一个有内部链接的定义对于任何其他编译单元都是不可见的。例如:

static int x;
enum Boolean{No, Yes};

具体有:

1.静态(static)全局变量的定义、静态自由函数的定义、静态友元函数的定义
2.类的定义
3.内联函数定义
4.Union共同体定义
5.const常量定义
6.枚举类型定义
7.所有的声明

用内部链接定义的一个重要的例子就是类的定义。类的描述(如下)是一个定义。因此,它不能够在同一作用域的编译单元内重复定义。在单个编译单元外部使用的类必须在一个头文件中定义。

class Point{
    int d_x;
    int d_y;
  public:
     Point(int x,int y):d_x(x),d_y(y){}   //internal linkage
     int x() const{return d_x;}   //internal linkage
     int y() const{return d_y;}   //internal linkage
};

inline int ..() {};  //internal linkage

2) 外部链接定义:外部链接意味着这个定义不局限于单个的编译单元。在.o文件中,具有外部链接的定义产生外部符号,这些外部符号可以被所有其他编译单元访问,用来解析其他编译单元中未定义的符号。
具体有:

1.类的非内联函数(包括成员函数和静态类成员函数)的定义
2.类的静态成员变量的定义
3.名字空间或全局的非静态的自由函数,非静态变量,非友元函数的定义

有一些名字定义所表示的实体拥有外部链接,这样就意味着他可以跨越编译单元去进行代码的链接。

所以,拥有外部链接的实体如果被声明在头文件并且被多个.cpp文件包含,可能就会出现链接冲突错误,因为每个包含这个拥有外部链接实体的.cpp都会分配空间,当多个编译单元链接的时候,连接器就会面对多个相同的名字,无法正常链接到正确的对象。

//math.h

namespace lesson_n
{
    int test_n;
}
class lesson_c
{
    int test_c
}
//main.cpp

#include "lesson.h"
int _tmain(intargc,_TCHAR*argv[])
{
         system("pause");
         return0;

}
//math.cpp

#include "lesson.h"
CMakeFiles/chess.dir/main.cpp.o:(.bss+0x4): multiple definition of `lesson_n::test_n'
CMakeFiles/chess.dir/math.cpp.o:(.bss+0x0): first defined here

发现namespace定义的test报错,而class定义的test不报错。这是因为namespace定义的是外部链接,而class定义的是内部链接,编译器会当做是各个编译单元都有的私有的副本。

最后再给出一个C++编程建议,慎重考虑在头文件中定义有链接的实体

一,如果头文件是像int a=1;这样的定义,被包含在多个.cpp文件后肯定会报出链接错误。

二,如果是想static int a = 2;这样的定义就会在所有包含他的.cpp文件中生成一个副本,如果被大量源文件include的话,就会占据大量的空间,造成内存浪费。

参考:大规模C++ 程序设计/(美) 洛科什 (Lakos. J.)著; 刘冰,张林译. —北京: 机械工业出版社,2014.8

猜你喜欢

转载自blog.csdn.net/hansry/article/details/80207143
今日推荐