代码重定义

引言

    重定义,简而言之就是同一个变量定义了两次或者多次。如下面的代码所示,在test.h中定义了一个类,但是我在add.h中和main.cpp中都引用了test.h文件,这时候在编译的过程中会产生错误。

测试代码

/*****test.h*****/
class Test{
public:
    int i;
};
/*****add.h*****/
#include "test.h"

int Add1(int a,int b);
/*****add.cpp*****/
#include <iostream>
#include "add.h"

int Add1(int a, int b)
{
  Test test_add;
  std::cout<<"This Add1 function"<<std::endl;
  return a+b;
}
/*****main.cpp*****/
#include <iostream>

#include "add.h"
#include "test.h"

int main()
{
  Test test_main;
  std::cout<<"Run main"<<std::endl;
  std::cout<<"1+1="<<Add1(1,1)<<std::endl;
  return 0;
}

如何避免

1. 使用#ifndef

    使用过程中可以采用#ifndef宏命令来避免这种问题,使用方式如下:

#ifndef MY_TEST_TEST_H
#define MY_TEST_TEST_H
class Test{
public:
    int i;
};
#endif //MY_TEST_TEST_H

    ifndef这套条件编译是为了防止同一个cpp文件中包含多个相同.h文件的(比如有一个main.cpp文件包含了test.h,add.h又包含了test.h,那么当你在main.cpp引用Add函数时,会#include<test.h>(因为有上面的条件编译的关系而不会出错)。因为编译器在编译的时候是按照cpp文件为单位编译的,每个cpp文件编译成.o文件,然后再将这些.o文件链接起来,最后与运行库链接形成可执行文件。你在两个cpp文件中包含了同一个.h文件。这根本和条件编译没什么关系。你在.h文件中定义了Test类。那么就表示你两个cpp文件中,每个cpp文件中都有这样的全局变量定义:class Test;当每个cpp文件单独编译当然没有问题,但是当它们链接起来的时候就出现问题了:两个cpp文件中都有变量Test类,所以当然是重复定义啊。问题是出在链接的时候的。
    虽然说使用条件编译可以解决这个问题,但是随着文件的增多也会导致宏的明明会重复,这时候就会导致条件编译失效的问题。而且编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,#ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。

2. 使用#pragma once

    使用过程中可以采用#pragma once宏命令来避免这种问题,使用方式如下:

#pragma once
class Test{
public:
    int i;
};

    你无法对一个头文件中的一段代码作#pragma once声明,而只能针对文件。其好处是,你不必再担心宏名冲突了,当然也就不会出现宏名冲突引发的奇怪问题。大型项目的编译速度也因此提高了一些。在跨平台的时候,有些平台不支持这种使用方式,目前来说我还没有遇到。

3. 使用匿名命名空间(拓展)

    使用方法如下:

namespace {
  class Test{
  public:
    int i;
  };
}

    编译器在内部会为这个命名空间生成一个唯一的名字,而且还会为这个匿名的命名空间生成一条using指令。所以上面的代码在效果上等同于:

     namespace __UNIQUE_NAME_ {
         class Test{
         public:
             int i;
         };
     }
     using namespace __UNIQUE_NAME_;

    为什么使用匿名空间也可以防止变量的重定义问题,是因为匿名空间具有internal链接属性,这和声明为static的全局名称的链接属性是相同的,即名称的作用域被限制在当前文件中,无法通过在另外的文件中使用extern声明来进行链接。如果不提倡使用全局static声明一个名称拥有internal链接属性,则匿名命名空间可以作为一种更好的达到相同效果的方法。

三种链接属性(拓展)

链接属性(linkage)分为三种——外部(external)、内部(internal)、无(none)。

外部属性:外部链接属性就意味着,一个标识符,不仅可以在当前的源文件中使用,还可以在程序的其他文件中使用。
内部属性:使用static修饰的标识符具有内部链接属性,只在本源文件中有效。const修饰的也默认是内部链接属性,当出现extern const时,extern的属性压过const的属性,成为外部链接属性。
无属性:局部变量。

反思

    为什么会写这篇文章。这是因为在实践过程中出现了变量的重定义,在test.h中使用了const char* str="test";当时我在这个头文件中使用了#pragma once,但是没有作用,我采用了匿名空间才解决了问题。这是为什么呢?
    头文件一般存放变量的声明,不应该存放变量的定义。而我当时存放的是变量的定义,并且多个文件引用,导致出现了报错,出现问题的时候还在纳闷,加了#pragma once,还会出现这种问题,没有道理呀!!!如果你读完这篇文章,那么你还会问上面说的const修饰的也是内部链接属性,那我使用const char 为什么还会报错?
    const修饰的变量是内部链接属性,这时毫无疑问的。出错的原因就是因为我所使用的是const char
,此时的const修饰的是char,而非指针变量str,所以str在这里是全局的。如果const修饰的是指针变量p(char const str),这时候str对外链接才是不可见的,所以我遇到的问题还可以讲const char变为char *const 或者const std::string(这时候使用std::string就有点大材小用了)

参考链接

匿名空间:https://www.cnblogs.com/youxin/p/4308364.html
链接属性:https://blog.csdn.net/u011285208/article/details/94738969
const内部链接属性:https://www.cnblogs.com/sheshiji/p/3427896.html

猜你喜欢

转载自www.cnblogs.com/SmallBlackEgg/p/12522173.html