头文件包含和重定义问题

zjfhgdx 2011-01-15 12:44:45
我在a.h中写了一个函数(直接把函数写在.h中,没有用cpp)

然后我把这个a.h包含到stdafx.h中

再在其它地方调用a.h中的函数,结果出现重定义错误。

我把函数声明写在a.h,定义写在cpp文件里就没有问题了。

请问为什么?
...全文
354 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
VC天下 2011-01-17
  • 打赏
  • 举报
回复
在H中定义的函数,别实现它,就是别写上{},就不会出现你这个问题了。出了问题,可以用上面的方法。

你也完全可以写在一个CPP中,然后在其它CPP想调用的时候,声明一下(extern),就行了。
johnlove9527 2011-01-16
  • 打赏
  • 举报
回复
mark,学习,貌似我有过类似错误。9楼的用过,7楼的不知道
zjfhgdx 2011-01-16
  • 打赏
  • 举报
回复
#pragma once用了,没有效果
Wang471981125 2011-01-16
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 hw_henry2008 的回复:]

引用 7 楼 yiruirui0507 的回复:
#ifndef _MYSTACK_H
#define _MYSTACK_H

正解。
或者用楼上的:#pragma once
避免二次包含。
尤其:如果你想把全局 变量放在头文件里,那就彻底错了,而且出现的问题异常诡异!
千万不要把全局非静态变量放在头文件里。不然很可能发现2个cpp文件里面修改的变量互相不可见!
当然了,常量是……
[/Quote]

++++
yangsp2009 2011-01-16
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 wltg2001 的回复:]

H文件在编译时会被直接插入到CPP文件中去的,你的a.h被包含了多次,那么这个函数就相当于有多个实现,当然会重定义了。
[/Quote]

很对啊
hw_henry2008 2011-01-16
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 yiruirui0507 的回复:]
#ifndef _MYSTACK_H
#define _MYSTACK_H
[/Quote]
正解。
或者用楼上的:#pragma once
避免二次包含。
尤其:如果你想把全局 变量放在头文件里,那就彻底错了,而且出现的问题异常诡异!
千万不要把全局非静态变量放在头文件里。不然很可能发现2个cpp文件里面修改的变量互相不可见!
当然了,常量是没问题的,因为你不改变它。最好用下面的方法:
//*.h 头文件里声明,不定义,函数也一样。只申明。
extern int a ;

//*.cpp//在CPP文件里面定义,这样在链接的时候能够找到,
//而且不会发现不同CPP里面用的是不同的副本,编译速度会更快。可执行文件也应该更小。
int a = 0;
vann1982 2011-01-16
  • 打赏
  • 举报
回复
#pragma once
zjfhgdx 2011-01-16
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 hw_henry2008 的回复:]
引用 7 楼 yiruirui0507 的回复:
#ifndef _MYSTACK_H
#define _MYSTACK_H

正解。
或者用楼上的:#pragma once
避免二次包含。
尤其:如果你想把全局 变量放在头文件里,那就彻底错了,而且出现的问题异常诡异!
千万不要把全局非静态变量放在头文件里。不然很可能发现2个cpp文件里面修改的变量互相不可见!
当然了,常量是没……
[/Quote]
这样确实是可以,但在我的工程里就会多一个CPP文件。
因为不想太多的文件,所以想直接写在H里
zjfhgdx 2011-01-15
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 wltg2001 的回复:]
H文件在编译时会被直接插入到CPP文件中去的,你的a.h被包含了多次,那么这个函数就相当于有多个实现,当然会重定义了。
[/Quote]
tks.
可是我在.h里定义的类为什么就没事呢?
Eleven 2011-01-15
  • 打赏
  • 举报
回复
采用预编译指令
wltg2001 2011-01-15
  • 打赏
  • 举报
回复
H文件在编译时会被直接插入到CPP文件中去的,你的a.h被包含了多次,那么这个函数就相当于有多个实现,当然会重定义了。
yiruirui0507 2011-01-15
  • 打赏
  • 举报
回复
#ifndef _MYSTACK_H
#define _MYSTACK_H
zjfhgdx 2011-01-15
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 visualeleven 的回复:]
采用预编译指令
[/Quote]
厚着脸皮向大大讨个简单例子。
zjfhgdx 2011-01-15
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 wltg2001 的回复:]
引用 3 楼 zjfhgdx 的回复:
引用 1 楼 wltg2001 的回复:
H文件在编译时会被直接插入到CPP文件中去的,你的a.h被包含了多次,那么这个函数就相当于有多个实现,当然会重定义了。

tks.
可是我在.h里定义的类为什么就没事呢?

类又不是变量或是函数,那只是一个类型。
[/Quote]
可是类里面也有函数的定义啊。
你说:H文件在编译时会被直接插入到CPP文件中去的。
那类不是要重定义了。
wltg2001 2011-01-15
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 zjfhgdx 的回复:]
引用 1 楼 wltg2001 的回复:
H文件在编译时会被直接插入到CPP文件中去的,你的a.h被包含了多次,那么这个函数就相当于有多个实现,当然会重定义了。

tks.
可是我在.h里定义的类为什么就没事呢?
[/Quote]
类又不是变量或是函数,那只是一个类型。
为PDF文件增加了目录,便于快速浏览。 摘录一些不知道以及没有做到的: 1、防止多包含(文件的形式不能完美做到) 所有文件都应该使用#define防止文件被多包含(multiple inclusion),命名格式当是:___H_ 为保证唯一性,文件的命名应基于其所在项目源代码树的全路径。例如,项目foo中的文件foo/src/bar/baz.h按如下方式保护: #ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_BAZ_H_ 2、能依赖声明就不要依赖定义 使用前置声明(forward declarations)尽量减少.h文件中#include的数量。 当一个文件被包含的同时也引入了一项新的依赖(dependency),只要该文件被修改,代码就要新编译。如果你的文件包含了其他文件,这些文件的任何改变也将导致那些包含了你的文件的代码新编译。因此,我们宁可尽量少包含文件,尤其是那些包含在其他文件中的。 使用前置声明可以显著减少需要包含的文件数量。举例说明:文件中用到类File,但不需要访问File的声明,则文件中只需前置声明class File;无需#include "file/base/file.h"。 在文件如何做到使用类Foo而无需访问类的定义? 1) 将数据成员类型声明为Foo *或Foo &; 2) 参数、返回值类型为Foo的函数只是声明(但不定义实现); 3) 静态数据成员的类型可以被声明为Foo,因为静态数据成员的定义在类定义之外。 另一方面,如果你的类是Foo的子类,或者含有类型为Foo的非静态数据成员,则必须为之包含文件。 3、为什么要使用内联函数,又如何改进性能? 定义(Definition):当函数被声明为内联函数之后,编译器可能会将其内联展开,无需按通常的函数调用机制调用内联函数。 要的是,虚函数和递归函数即使被声明为内联的也不一定就是内联函数。通常,递归函数不应该被声明为内联的(译者注:递归调用堆栈的展开并不像循环那么简单,比如递归层数在编译时可能是未知的,大多数编译器都不支持内联递归函数)。析构函数内联的主要原因是其定义在类的定义中,为了方便抑或是对其行为给出文档。 4. 函数参数顺序(Function Parameter Ordering) 定义函数时,参数顺序为:输入参数在前,输出参数在后。 C/C++ 函数参数分为输入参数和输出参数两种,有时输入参数也会输出(译者注:值被修改时)。输入参数一般传值或常数引用(const references),输出参数或输入/输出参数为非常数指针(non-const pointers)。对参数排序时,将所有输入参数置于输出参数之前。不要仅仅因为是新添加的参数,就将其置于最后,而应该依然置于输出参数之前。 5、文件定义顺序 将包含次序标准化可增强可读性、避免隐藏依赖(hidden dependencies,译者注:隐藏依赖主要是指包含的文件中编译时),次序如下:对应该CPP的文件、C库、C++库、其他库的.h、项目内的.h。 相同目录下文件按字母序是不错的选择。 1. 命名空间(Namespaces) 在.cc文件中,提倡使用不具名的命名空间(unnamed namespaces,译者注:不具名的命名空间就像不具名的类一样,似乎被介绍的很少:-()。使用具名命名空间时,其名称可基于项目或路径名称,不要使用using指示符。 定义:命名空间将全局作用域细分为不同的、具名的作用域,可有效防止全局作用域的命名冲突。 优点:命名空间提供了(可嵌套)命名轴线(name axis,译者注:将命名分割在不同命名空间内),当然,类也提供了(可嵌套)的命名轴线(译者注:将命名分割在不同类的作用域内)。 举例来说,两个不同项目的全局作用域都有一个类Foo,这样在编译或运行时造成冲突。如果每个项目将代码置于不同命名空间中,project1::Foo和project2::Foo作为不同符号自然不会冲突。 缺点:命名空间具有迷惑性,因为它们和类一样提供了额外的(可嵌套的)命名轴线。在文件中使用不具名的空间容易违背C++的唯一定义原则(One Definition Rule (ODR))。 1) 不具名命名空间(Unnamed Namespaces) 在.cc文件中,允许甚至提倡使用不具名命名空间,以避免运行时的命名冲突: namespace { // .cc 文件中 // 命名空间的内容无需缩进 enum { UNUSED, EOF, ERROR }; // 经常使用的符号 bool AtEof() { return pos_ == EOF; } // 使用本命名空间内的符号EOF } // namespace 然而,与特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员或静态成员函数(什么意思?),而不是不具名命名空间的成员。像上文展示的那样,不具名命名空间结束时用注释// namespace标识。 2、函数载 结论:如果你想载一个函数,考虑让函数名包含参数信息,例如,使用AppendString()、AppendInt()而不是Append()。 3. 缺省参数(Default Arguments) 禁止使用缺省函数参数。 优点:经常用到一个函数带有大量缺省值,偶尔会写一下这些值,缺省参数为很少涉及的例外情况提供了少定义一些函数的方便。 缺点:大家经常会通过查看现有代码确定如何使用API,缺省参数使得复制粘贴以前的代码难以呈现所有参数,当缺省参数不适用于新代码时可能导致问题。 结论:所有参数必须明确指定,强制程序员考虑API和传入的各参数值,避免使用可能不为程序员所知的缺省参数。 8. 类型转换(Casting) 使用static_cast<>()等C++的类型转换,不要使用int y = (int)x或int y = int(x);。 定义:C++引入了有别于C的不同类型的类型转换操作。 优点:C语言的类型转换问题在于操作比较含糊:有时是在做强制转换(如(int)3.5),有时是在做类型转换(如(int)"hello")。另外,C++的类型转换查找更容易、更醒目。 缺点:语法比较恶心(nasty)。 结论:使用C++风格而不要使用C风格类型转换。 1) static_cast:和C风格转换相似可做值的强制转换,或指针的父类到子类的明确的向上转换; 2) const_cast:移除const属性; 3) reinterpret_cast:指针类型和整型或其他指针间不安全的相互转换,仅在你对所做一切了然于心时使用; 4) dynamic_cast:除测试外不要使用,除单元测试外,如果你需要在运行时确定类型信息,说明设计有缺陷(参考RTTI)。 10. 前置自增和自减(Preincrement and Predecrement) 对于迭代器和其他模板对象使用前缀形式(++i)的自增、自减运算符。 定义:对于变量在自增(++i或i++)或自减(--i或i--)后表达式的值又没有没用到的情况下,需要确定到底是使用前置还是后置的自增自减。 优点:不考虑返回值的话,前置自增(++i)通常要比后置自增(i++)效率更高,因为后置的自增自减需要对表达式的值i进行一次拷贝,如果i是迭代器或其他非数值类型,拷贝的代价是比较大的。既然两种自增方式动作一样(译者注,不考虑表达式的值,相信你知道我在说什么),为什么不直接使用前置自增呢? 缺点:C语言中,当表达式的值没有使用时,传统的做法是使用后置自增,特别是在for循环中,有些人觉得后置自增更加易懂,因为这很像自然语言,主语(i)在谓语动词(++)前。 结论:对简单数值(非对象)来说,两种都无所谓,对迭代器和模板类型来说,要使用前置自增(自减)。 16. sizeof(sizeof) 尽可能用sizeof(varname)代替sizeof(type)。 使用sizeof(varname)是因为当变量类型改变时代码自动同步,有些情况下sizeof(type)或许有意义,还是要尽量避免,如果变量类型改变的话不能同步。 8. TODO注释(TODO Comments) 对那些临时的、短期的解决方案,或已经够好但并不完美的代码使用TODO注释。 这样的注释要使用全大写的字符串TODO,后面括号(parentheses)里加上你的大名、邮件地址等,还可以加上冒号(colon):目的是可以根据统一的TODO格式进行查找: // TODO(kl@gmail.com): Use a "*" here for concatenation operator. // TODO(Zeke) change this to use relations. 如果加上是为了在“将来某一天做某事”,可以加上一个特定的时间("Fix by November 2005")或事件("Remove this code when all clients can handle XML responses.")。 TODO很不错,有时候,注释确实是为了标记一些未完成的或完成的不尽如人意的地方,这样一搜索,就知道还有哪些活要干,日志都省了。

16,471

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧