基于C++类型重定义的使用详解

 更新时间:2013年05月16日 10:47:17   作者:  
本篇文章是对C++中类型重定义的使用进行了详细的分析介绍,需要的朋友参考下
这几天工作时碰到一个C++的编译错误(我使用的是Visual C++ 7.0),说是有一个类重复定义,仔细想想我们的这个项目也是做了好几个Release了, 内部代码应该不会有这样的低级错误, 真把类型给重复定义了,检查结果正如我预料的一样。 就这样, 我左右没找到原因,被一个编译错误给卡在那里了。(在我的概念中, 程序错误的等级为:编译错误->链接错误->逻辑错误, 此错误属于最低级 )。这时我仔细看了一下错误提示, 发现重复定义是由于从两个不同的路径包含了同一个头文件而引起的,同事也建议从另外一个路径打开工程试试, 这才慢慢发现了原因。这个原因可能有些拗口,而事实上要出现这种错误也有些"曲折", 让我从不同情况下的类型重定义来解释一下吧。
我总结的类型重定义情况有三。
一、没有在文件头加#pragma once指示符。
复制代码 代码如下:

Type1.h:
//#pragma once
class Type
{
};
Main.cpp:
#include "Type1.h"
#include "Type1.h"
int main(int argc, char *argv[])
{  
return 1;
}

#pragma once的作用是保证本文件只被编译一次,如果没有在Type1.h中加这句话,那么在main.cpp里面包含了两次Type1.h, 就相当于在main.cpp里面定义了两次Type类, 自然就是类型重定义了。

二、两个不同的头文件中定义了相同的类型(均有#pragma once)
复制代码 代码如下:

Type1.h:
#pragma once
class Type
{
};
Type2.h:
#pragma once
class Type
{
};
Main.cpp:
#include "Type1.h"
#include "Type2.h"
int main(int argc, char *argv[])
{  
return 1;
}

这里main.cpp中同时包含了Type1.h, Type2.h两个头文件, 虽然其文件头都有#pragma once,但因为是不同的文件, 预编译器还是会两次把Type类的定义放在Main.cpp中, 所以也会出现了重定义。

三、从两个不同的路径包含了同一个头文件
前面两种是比较常见, 也是比较容易解决的情况, 而这里要讲的第三种情况, 比较少见, 而且一般出现在有虚拟映射盘的时候。(这样才能做到从两个不同的路径包含同一个头文件), 其他会在什么时候出现, 我还没想到, 知道的朋友顶一下:)。下面我来分析一下:
1) 有VC工程在D:\Test目录下。
2) 映射虚拟盘X为D:\Test.
不熟悉的网友可以按此操作: 开始->运行->在运行窗口输入:cmd->在cmd窗口输入:
Subst X: D:\Test
3) 该工程有文件Type1.h, main.cpp
复制代码 代码如下:

Type1.h:
#pragma once
class Type{};
Main.cpp:
#include "Type1.h"
#include "X:\Type1.h"
int main(int argc, char *argv[])
{  
return 1;
}

这里我们在main.cpp这样包含了两个头文件, 从本质上来讲, 它们都对应于物理盘D:\Test下的文件Type1.h, 是同一个文件。但在不同的操作下, VC对其有不同的解释。#include "X:\Type1.h"用的是绝对路径, 自然没有什么异议, 但#include "Type1.h"却有些变化:
•假如我从D:\Test\下打开工程, 那么#include "Type1.h"其实就是#include "D:\Test\Type1.h"
•假如从X:\下打开工程,那么#include "Type1.h"就解释为#include "X:\Type1.h"
4) 在D:\Test下打开工程, 编译, 出现类型Type重复定义错误
这种情况下,main.cpp预编译为:
复制代码 代码如下:

Main.cpp:
#include "D:\Test\Type1.h"
#include "X:\Type1.h"
int main(int argc, char *argv[])
{  
return 1;
}

#pragma once只保证本文件被编译一次, 这里VC将其认为是两个不同的文件, 所以都要编译, 出现编译错误自然也就不奇怪了。
当然, 这里如果从X:\ 下打开工程的话,VC就会认为都是从X:\Type1.h下包含这个文件,#pragma once起到了作用, 也就不会出现类型重定义了

四、总结
我在VC7, VC8,和Dev C++中都测试了第三种情况, 发现只有Dev C++是可以通过编译的。这可能是微软VC的#pragma once还不够智能吧,轻易的被Windows的虚拟盘给蒙蔽了双眼, 看不到其本质(只是猜测, 或许VC这么处理是有其他用意的)。

因为在稍大一点的工程开发中, 我们一般都会用虚拟盘来方便工作, 一是访问快捷,简化了路径, 二是因为多人协同开发,我们一般希望大家源代码路径相同,但我们不应强制要求大家都把源代码放死在某一目录下, 这时把你放源代码的路径映射为一个虚拟盘(比如说统一为X:)就能把大家的代码路径统一起来了。但是另一方面,有了虚拟盘, 就为出现类型重定义提供了条件, 以下是我得出的两个解决方法:
1) 抛弃#pragma once使用古老但集稳定性与移植性于一身的
复制代码 代码如下:

#ifndef _XXX_H
#define _XXX_H
//...#endif

来保证头文件只被编译一次。这样不管是包含两个相同的文件,还是包含两个不同的文件,或是包含两个文件相同但路径不同的文件, 只要_XXX_H被定义过, 就不会再编译那个编译(但这里我们要保证_XXX_H的唯一性, 如果两个不同的头文件里用了同一_XXX_H,是会出问题的)
2) 在包含头文件时,不要使用绝对路径, 哪怕那是虚拟盘的绝对路径。

相关文章

  • Windows环境给FFmpeg集成AVS3解码器

    Windows环境给FFmpeg集成AVS3解码器

    libuavs3d是AVS3标准的解码器,支持windows/linux/arm/ios等所有常用平台,在移动端最高支持4K/30fps视频实时解码,解码速度大幅领先AV1开源解码器dav1d和aomdec,由于FFmpeg默认未启用libuavs3d,因此需要重新配置FFmpeg,标明启用libuavs3d,然后重新编译安装FFmpeg
    2024-05-05
  • 举例讲解C语言对归并排序算法的基础使用

    举例讲解C语言对归并排序算法的基础使用

    这篇文章主要介绍了C语言对归并排序算法的使用,归并排序算法的平均事件复杂度为(n\log n),需要的朋友可以参考下
    2016-05-05
  • c语言 数据存储与原码 反码 补码详细解析

    c语言 数据存储与原码 反码 补码详细解析

    不知道你是否和我一样好奇,学习编程语言的同时想,各个数据类型是怎样在我们的内存中储存的呢,如果你仔细深入了解的话,你会了解其中的乐趣,了解科学家们的伟大,了解c语言
    2022-02-02
  • C++类的构造与析构特点及作用详解

    C++类的构造与析构特点及作用详解

    本文章将会可能会涉及到汇编的知识,不过没有关系,我会讲的尽量通俗易懂;另外本篇文章开始前,建议了解下什么是函数重载,这个概念很简单的--有相同的函数名,但参数列表不相同的函数,就是函数重载
    2022-10-10
  • 用C语言实现三子棋

    用C语言实现三子棋

    这篇文章主要为大家详细介绍了用C语言实现三子棋,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • C语言实现获取内存信息并输出的实例

    C语言实现获取内存信息并输出的实例

    这篇文章主要介绍了C语言实现获取内存信息并输出的实例的相关资料,需要的朋友可以参考下
    2017-03-03
  • C++中对C语言结构体用法的扩充

    C++中对C语言结构体用法的扩充

    今天小编就为大家分享一篇关于C++中对C语言结构体用法的扩充,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • C/C++根据年月日计算星期几(蔡勒公式篇)

    C/C++根据年月日计算星期几(蔡勒公式篇)

    这篇文章主要给大家介绍了关于C/C++根据年月日计算星期几(蔡勒公式篇)的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • C语言中的typedef、#define、const特点和用法介绍

    C语言中的typedef、#define、const特点和用法介绍

    在C 语言中,typedef、#define和const都用于定义常量和简化代码,但它们的用途和工作方式有显著区别,下面详细讲解这三者的特点和用法,感兴趣的朋友一起看看吧
    2024-08-08
  • C++实现LeetCode(147.链表插入排序)

    C++实现LeetCode(147.链表插入排序)

    这篇文章主要介绍了C++实现LeetCode(147.链表插入排序),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07

最新评论