头文件的血案

不小心在一个头文件里,加了函数的定义,结果导致编译时,提示这个函数被重复定义:(

Quote.h

#ifndef __QUOTE_H__
#define __QUOTE_H__ #include <iostream> class Quote{
public:
Quote() = default;
Quote(const std::string& book, double pri)
:bookNo(book), price(pri){}
std::string isbn() const{return bookNo;}
virtual double net_price(std::size_t n)const{
return n * price;
}
virtual ~Quote() = default;
private:
std::string bookNo;
protected:
double price = 0.0;
}; class Bulk_quote : public Quote{
public:
Bulk_quote() = default;
Bulk_quote(const std::string&, double, std::size_t,
double);
double net_price(std::size_t n)const override;
private:
std::size_t min_qty = 0;//适用于折扣的最低购买数量
double discount = 0.0;//折扣额
}; //不小心在这个头文件里,加了下面这个函数的定义,结果导致编译时,提示这个函数被重复定义:(
double print_total(std::ostream& os,
const Quote& item, size_t n){
double ret = item.net_price(n);
os << "ISBN: " << item.isbn()
<< " # sold: " << n << " total due: " << ret << std::endl;
return ret; #endif

Quote.cpp

#include "Quote.h"

Bulk_quote::Bulk_quote(const std::string& book, double p,
std::size_t qty, double disc):
Quote(book, p), min_qty(qty), discount(disc){} double Bulk_quote::net_price(std::size_t cnt)const {
if(cnt >= min_qty){
return cnt * (1 - discount) * price;
}
else{
return cnt * price;
}
}

mainQuote.cpp

#include "Quote.h"

int main(){
Quote q("01",100.5);
double d = print_total(std::cout, q, 1); Bulk_quote bq("01", 100.5, 5, 1);
}

编译方法:

g++ -g Quote.cpp mainQuote.cpp -std=c++11

编译结果:

multiple definition of `print_total(std::ostream&, Quote const&, unsigned long)`

错误原因,大概知道了,但是不知道为什么

  • 因为在Quote.cpp里包含了Quote.h,在mainQuote.cpp里也包含了Quote.h,自己错的的认为:在头文件的开头只要加了保护 #ifndef _QUOTE_H_,就不会把Quote.h包含2次,导致函数重复定义。但是没有什么卵用,这是为什么呢??? 用g++ -E Quote.cpp mainQuote.cpp > main.i,生成了预编译后的文件main.i,这个文件里确实有2个地方有print_total的定义。
  • 网上说,最好不要在头文件里定义函数和变量,否则会导致重复定义,看来网上说的还真对!但是如果把函数print_total定义为inline函数的话,编译就没有问题。这又是为什么呢???

修改方案1,把函数放到Quote.cpp里

Quote.h

#ifndef __QUOTE_H__
#define __QUOTE_H__ #include <iostream> class Quote{
public:
Quote() = default;
Quote(const std::string& book, double pri)
:bookNo(book), price(pri){}
std::string isbn() const{return bookNo;}
virtual double net_price(std::size_t n)const{
return n * price;
}
virtual ~Quote() = default;
private:
std::string bookNo;
protected:
double price = 0.0;
}; class Bulk_quote : public Quote{
public:
Bulk_quote() = default;
Bulk_quote(const std::string&, double, std::size_t,
double);
double net_price(std::size_t n)const override;
private:
std::size_t min_qty = 0;//适用于折扣的最低购买数量
double discount = 0.0;//折扣额
};
#endif

Quote.cpp

#include "Quote.h"

Bulk_quote::Bulk_quote(const std::string& book, double p,
std::size_t qty, double disc):
Quote(book, p), min_qty(qty), discount(disc){} double Bulk_quote::net_price(std::size_t cnt)const {
if(cnt >= min_qty){
return cnt * (1 - discount) * price;
}
else{
return cnt * price;
}
} double print_total(std::ostream& os,
const Quote& item, size_t n){
double ret = item.net_price(n);
os << "ISBN: " << item.isbn()
<< " # sold: " << n << " total due: " << ret << std::endl;
return ret;
}

mainQuote.cpp

#include "Quote.h"

int main(){
Quote q("01",100.5);
double d = print_total(std::cout, q, 1); Bulk_quote bq("01", 100.5, 5, 1);
}

编译结果:

error: ‘print_total’ was not declared in this scope

错误原因,Quote.h里没有函数print_total的声明和定义,所以当然出这个错误

修改方法:加一个函数print_total的声明,就好了。

mainQuote.cpp

#include "Quote.h"

double print_total(std::ostream& os,
const Quote& item, size_t n);
int main(){
Quote q("01",100.5);
double d = print_total(std::cout, q, 1); Bulk_quote bq("01", 100.5, 5, 1);
}

从上面2次错误的经验,感受到了,c++编译器的脾气了。

编译阶段:编译器编译到某个函数print_total调用的地方时,就要去前面找这个函数的声明或者定义,如果只有声明没有定义,就先把定义的地方预留出来。

链接阶段:把在编译阶段预留出函数print_total的定义的空缺,填补上。(print_total的定义在文件Quote.cpp里)

c/c++ 学习互助QQ群:877684253

本人微信:xiaoshitou5854

c/c++ 头文件的血案的更多相关文章

  1. 【C】.h头文件的重复包含问题

    .h头文件存在的意义就是封装,可以方便多个.c源文件使用,但要防止.h头文件被同一个.c源文件多次包含. 例如, io.h文件 #ifndef _IO_H_ #define _IO_H_ #defin ...

  2. C/C++头文件区别

    在从C迁移到C++时,引用的头文件经常忘记是C的还是C++特有的 1. *.h   limits.h ctype.h 2. c* climits cctype [结尾不含.h] 3. 其余的都属于C+ ...

  3. iOS开发中遇到的错误整理 - 集成第三方框架时,编译后XXX头文件找不到

    iOS编译报错-XXX头文件找不到 错误出现的情况: 自己在继承第三方的SDK的时候,明明导入了头文件,但是系统报错,提示头文件找不到 解决方法 既然系统找不到,给他个具体路径,继续找去! 路径就填写 ...

  4. 使用powershell批量添加Keil和IAR的头文件路径

    在Keil和IAR的工程中,为了使文件结构清晰,通常会设置很多的子文件夹,然后将头文件和源文件放在不同的子文件夹中,这样就需要手动添加这些头文件夹的路径.当工程结构非常复杂时,文件夹的数量就非常多,特 ...

  5. C/C++头文件使用 #ifndef #define #endif 的原因

    背景 在编译的时候,出现"redefine"的错误,最后检查才发现对应的头文件没有写正确的预编译信息: #ifndef _HeadFileName_H #define _HeadF ...

  6. Qt - 错误总结 - 在自定义类头文件中添加Q_OBJECT 编译时报错(undefined reference to ‘vtable for xxThread)

    错误提示:在添加的QThread子类头文件添加Q_OBJECT时,编译程序,出现"undefined reference to 'vtable for xxThread'"错误提示 ...

  7. C语言中,头文件和源文件的关系(转)

    简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句, ...

  8. 配置apue的头文件apue.h和unp的头文件anp.h

    配置apue的头文件apue.h和unp的头文件anp.h 如果要使用gcc -g 来生成可调试文件一定要修改Make.defines.linux文件中的CFLAGS变量 修改为:CFLAGS=-an ...

  9. c/c++头文件_string

    string, cstring, string.h 一.string头文件 主要包含一些字符串转换的函数 // sto* NARROW CONVERSIONS// sto* WIDE CONVERSI ...

随机推荐

  1. Python档案袋( Json、pickle、加密与解密)

    Json是各程序通用的数据格式:pickle是Python特有的,可以存储很多Python特有的数据,如函数地址等 Json的简单使用: import json jsondata={ "us ...

  2. React 中 Link 和 NavLink 组件 activeClassName、activeStyle 属性不生效的问题

    首先 导航链接应该使用  NavLink 而不再是  Link NavLink 使用方法见 https://github.com/ReactTraining/react-router/blob/mas ...

  3. Linux清空文件内容

    日志文件太多,需要清空: echo "" > mylog.log

  4. knockoutjs 上自己实现的flux

    在knockoutjs 上实现 Flux 单向数据流 状态机,主要解决多个组件之间对数据的耦合问题. 一.其实简单 flux的设计理念和实现方案,很大程度上人借鉴和参考了Vuex的实现,只是简化了某些 ...

  5. 【MongoDb入门】15分钟让你敢说自己会用MongoDB了

    一.MongDB是什么呢,我该如何下手呢? MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. 如果小伙伴你的机器上还没有安装Mon ...

  6. OAuth 2.0 授权码请求

    关于OAuth 2.0,请参见下面这两篇文章(墙裂推荐): <OAuth 2.0> <Spring Security OAuth 2.0> 纸上得来终觉浅,绝知此事要躬行.理论 ...

  7. Linux~Sh脚本一点自己的总结

    从.netCore开源项目来看,eShopOnContainers来说,它的部署是跨平台的,可以部署在linux,docker上,在linux上运行它也可以写一些集成的小脚本,这是微信工程师为我们提供 ...

  8. Chapter 5 Blood Type——3

    Disappointment flooded through me as my eyes unerringly focused on his table. 当我的眼睛完全集中在他的桌上时,失望如洪水般 ...

  9. 17-Flink消费Kafka写入Mysql

    戳更多文章: 1-Flink入门 2-本地环境搭建&构建第一个Flink应用 3-DataSet API 4-DataSteam API 5-集群部署 6-分布式缓存 7-重启策略 8-Fli ...

  10. Jvm垃圾回收器(算法篇)

    在<Jvm垃圾回收器(基础篇)>中我们主要学习了判断对象是否存活还是死亡?两种基础的垃圾回收算法:引用计数法.可达性分析算法.以及Java引用的4种分类:强引用.软引用.弱引用.虚引用.和 ...