.h文件和.cpp文件组织结构

本文介绍了C++编程中头文件.h和实现文件.cpp的组织原则,包括如何处理基类与派生类的相互引用问题、避免互相包含、正确使用构造函数以及extern和static关键字的运用。强调了头文件中通常进行声明,实现文件中进行定义,以防止重复定义错误,并提高编译效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、包含关系:

.h文件一般包含在.cpp文件中,.h文件中多为变量和类的声明,而.cpp文件才是变量和类中函数的真正定义。

但是#include <iostream> 这个文件既不是.c也不是.h,那我们能不能不用它,改用iostream.h?一般来说只是为了使用cout这种对象是可以的。但意义上有差别,iostream和iostream.h是不一样的,一个是模板库,一个是C++库头文件。这里顺便提起一下以前的一件事,我刚进入上上家公司的时候,头要我们写个类模板,于是我就跟写一般的类一样把它分为两个文件,一个.h,一个.cpp,一个放定义,一个放实现。你们认为我的做法有问题么?写过模板的人都应该知道,这样是不行的,因为C++中的实现是针对类的实现,而不是针对模板的实现,分开写会导致连接时候找不到实现的。因此必须将模板的“定义”与“实现”写在同一个文件中,那这个文件究竟叫“.h”好呢还是“.cpp”好呢?都不好,它既不是类的定义也不是类的实现,它是模板,模板就是模板,那干脆就不用文件扩展名,STL就怎么干的,That’s OK!

摘自:http://wenku.baidu.com/link?url=VxeYRH5Lf3cgBwCYO6vRnYIsRDr5UynV8WwFwlg5-39puJklkkf-UndQ40PDOpwl-_AHmmzXbGc6r231ZKMWQcTUjpVmUN2hPfCkddCTaii

2、基类中有派生类的指针,或者A类中有B类指针,B类中有A类指针

基类为CBase,子类有CDerivedA,CDerivedB,这倒没什么,但CBase中竟然有这种函数:

class CBase
{     
	//…… 
	virtual CDerivedA* GetA();
	virtual CDerivedB* GetB();
};

DerivedA.h和DerivedB.h中需要include Base.h,而CBase竟然也用到了它的子类……那根据哪里用到就哪里包含的法则,Base.h是不是也要include DerivedA.h和DerivedB.h?这岂不是形成了“互相包含”?是的,如果出现了这种互相包含,VC++就会给出编译警告。当然如果你做了“反重复包含” 的工作,编译警告就不会出现,也不会出现“重复定义”,而取而代之的是“未定义”,程序还是通不过的。比如下面这个简单的例子:

//基类 Base.h 
#ifndef __BASE_H__#define __BASE_H__   
#include"DerivedA.h"   
class CBase
{ 
public:     
	CBase();    
	~CBase();     
	virtual CDerivedA*GetA(){return NULL;}
};
#endif
//子类 DerivedA.h
#ifndef __DERIVED_A_H__
#define __DERIVED_A_H__
#include"Base.h" 
classCDerivedA:public CBase
{
public:
	CDerivedA();
	~CDerivedA();
	virtual CDerivedA* GetA()
	{return this;}
}; 
#endif

编译出现的错误大致如下,但并非一定,甚至每次都有可能不同,这和编译的顺序有关系:

error C2143:syntax error : missing ';' before '*' 

error C2433:'CDerivedA' : 'virtual' not permitted on data declarations

error C2501:'CDerivedA' : missing storage-class or type specifiers

error C2501:'GetA' : missing storage-class or type specifiers   

总之出现了这种基类“需要”子类的情况的话,就不能这样include了。取而代之的是使用一个类的声明:在Base.h中把“#include "DerivedA.h"”去掉,用“class CDerivedA;”取代它。这样编译就没有问题了。   

OK OK,可能你又有问题了,如果基类中的函数不是“virtual CDerivedA* GetA()”,而是“virtual CDerivedA GetA()”,那怎么又通不过了?哇哈哈……老兄,你别扯了,我保证你找遍全世界的高手,也没有人能解决这个问题的,因为它逻辑上已经错误了,父在诞生的时候需要子,而父没诞生,哪来的子?又一个典型的鸡生蛋,蛋生鸡的问题。至于指针为什么就可以,因为指针在Win32中归根到底只是一个long型,它并不需要理解CDerivedA究竟何方神圣,只需要连接的时候找到就行了,反过来如果不是指针,CBase就要尝试寻找CDerivedA并生成实例,这可能吗?

注意:在派生类从基类继承时,此时必须要有基类的完整声明,不能只有前向声明,即class Base;这样是不能通过编译的。如下代码:注意是错的

#include<iostream>
using namespace std;

class A;
class B:public A
{
	
};

class A
{
	int m;
public:
	void fun();
};

int main()
{
	A aa;
	B bb;

	system("pause");
	return 0;
}
输出的错误提示为:error C2504: 'A' : base class undefined

3、注意基类中有派生类时的构造函数

//a.h
#ifndef _AAA_
#define _AAA_
class Derived;
class Base
{
protected:
	Derived *p;
public:
	Base(){};
	Base(Derived &d);
	~Base(){}
	void fun();
};
#endif

//a.cpp
#include"a.h"
#include"b.h"
Base::Base(Derived &d)
{
	p=&d;
}
void Base::fun()
{
	//可以调用Derived类中的函数,因为b.h中有函数声明
	p->getA();
}

//b.h
#ifndef _BBB_
#define _BBB_
#include"a.h"
class Derived:public Base
{
	int a;
	int b;
public:
	Derived(){}
	Derived(int _a,int _b);
	~Derived(){}
	int getA();
};
#endif

//b.cpp
#include"b.h"
#include<iostream>
Derived::Derived(int _a,int _b)
{
	a=_a;
	b=_b;
	p=new Derived();
	std::cout<<"111"<<std::endl;
}

int Derived::getA()
{
	std::cout<<a<<std::endl;
	return a;
}

//test.cpp
#include<iostream>
#include"a.h"
#include"b.h"
using namespace std;

int main()
{
	Base a;
	Derived d(1,2);
	Base b(d);
	b.fun();

	system("pause");
	return 0;
}
如果基类只有一个构造函数,那么这个构造函数必须有缺省参数,而且只能复制为0,这样在构造派生类对象时,调用基类的构造函数,就不会存在再调用派生类构造函数的无限循环中了,即派生类中绝不能出现p=new Derived();因为这样将回到先有鸡还是先有蛋的问题上了。

3、如果不用头文件,而直接定义基类和派生类又会有问题:如下是人和狗的问题,即Man类中有Dogs类的指针,表示人拥有的狗,而Dogs类中也有Man的指针,表示狗属于的人。这样如果先写Dogs类,就需要前向声明Man类,但是在Dogs类中的void printmsg();函数需要调用Man类中的char *getName()来得到名字,但是此时只有Man进行了前向声明,无法得知char *getName()函数的任何信息,因此就需要将void printmsg();函数放到char *getName()函数的声明或者定义之后。

#include <iostream>
using namespace std;

class Man;
class Dogs
{
public:
	char name[20];
	Man *master;
	Dogs(char *name,Man *m):master(m)
	{
		strcpy(this->name,name);
	}
	Dogs(char *name)
	{
		strcpy(this->name,name);
		master=NULL;
	}
	void addMasterMsg(Man &m)
	{
		master=&m;
	}
	//在这里不能定义,因为用到了Man类中的函数,而Man类在前面只是声明了类,
	//并没有声明函数,因此不能用Man类中的函数
	void printmsg();
};


class Man
{
	char name[20];
public:
	Dogs *dogs[10];
	Man(char *name)
	{
		strcpy(this->name,name);
		for(int i=0;i<10;i++)
		{
			dogs[i]=NULL;
		}
	}
	void addDog(Dogs *dog,int i)
	{
		dogs[i-1]=dog;
	}
	void printmes()
	{
		cout<<name<<"的狗有:"<<endl;
		for(int i=0;i<10;i++)
		{
			if(dogs[i])
				cout<<"第"<<i<<"只狗名字是:"<<dogs[i]->name<<endl;
			
		}
	}
	char *getName()
	{
		return name;
	}
};

void Dogs::printmsg()
{
	cout<<name<<"是"<<master->getName()<<"的狗"<<endl;
}

int main()
{
	Man m1("zhangsan");
	cout<<m1.getName()<<endl;
	Dogs d1=("dog1");
	d1.addMasterMsg(m1);
	m1.addDog(&d1,1);

//	Dogs d2=("dog2",&m1);不知道为什么这样做构造函数不行,而且构造函数用初始化列表也不行。
	//只能如上,用add函数增加
//此处应该用Dogs d2=Dogs(“dog2”,&m1);才对,这样用的是默认的拷贝构造函数,临时构造一个对象,
//完成拷贝,或者Dogs d2(“dog2”,&m1);也可以,千万不能用上面所示的错误的代码。赋值根本就不合//语法!!!
	Dogs d3=Dogs("dog3",&m1);
	Dogs d4("dog4",&m1);

	m1.printmes();
	d1.printmsg();

	cout<<sizeof(Man)<<"	"<<sizeof(Dogs)<<endl;

	system("pause");
	return 0;
}
在这里如果有多个函数需要调用不同类之间的函数,就需要不断的进行声明,或者进行顺序化的定义,防止一个函数定义时,其调用的函数还没有声明。但是如果利用.h和.c文件来组织程序,就能够避免混乱的程序代码。此时,只需要分别定义Dogs.h实现Dogs类声明,当然要前向声明class Man,Man.h实现Man类声明,当然也要前向声明classDogs,或者包含Dogs.h文件。然后在定义Dogs.cpp和Man.cpp都包含Dogs.h和Man.h即可。完美的解决了上面的问题,而且思路很清晰,结构也很明了。

4、注意externstatic关键字

extern表示声明变量,一般用在.h文件中,而定义放在.cpp文件中,static关键字其实是为了屏蔽extern的声明,或者说是当前.cpp文件中的全局变量,但是不会被其他文件所用到。而且当前.cpp文件中有和extern变量重名,在此cpp中,用static变量。

//a.h
#ifndef _AAA_
#define _AAA_
extern int globalVariant;
#endif

//a.cpp
#include"a.h"
int globalVariant=10;

//test.cpp
#include<iostream>
#include"a.h"
using namespace std;

static int globalVariant=100;

int main()
{
	cout<<globalVariant<<endl;
	globalVariant=3;
	cout<<globalVariant<<endl;

	system("pause");
	return 0;
}

5、.cpp文件是需要编译的,但是.h文件是不需要进行编译的,如果包含.h文件就能够减少编译的时间。

如果把函数的定义写在.h文件中,例如把fun1()写在了a.h中,而在b.cpp中包含了a.h,在test.cpp中也包含了a.h,那么此时就会报错,说函数重复定义了,因此把声明写在.h文件中,把定义写在.cpp文件中是比较好的办法。

//a.h
#ifndef _AAA_
#define _AAA_
int fun1()
{
	int a=1;
	int b=2;
	return a;
}
#endif

//b.cpp
#include"a.h"
void fun2()
{}

//test.cpp
#include<iostream>
#include"a.h"
using namespace std;

int main()  
{  
	fun1();

    system("pause");  
    return 0;  
}

更多内容可看:C语言中关于.h和.c的问题

<think>好的,我现在需要向用户解释如何将C++代码分成.h头文件.cpp文件。首先,我要确保自己理解这个问题的核心要点,然后按照用户的要求,结构清晰、分步骤地讲解。 首先,用户可能不太清楚头文件文件的作用,所以我要先介绍两者的基本概念。头文件.h)通常用于声明类、函数、变量等,而源文件.cpp)则包含这些声明的具体实现。分开的好处包括模块化、代码复用减少编译依赖。 接下来,我需要分步骤说明如何实际操作。例如,创建一个类时,如何在头文件中声明类的成员变量函数,然后在源文件中实现这些函数。同时,要提到头文件保护符(#ifndef、#define、#endif)的作用,防止重复包含。 然后,应该给出一个具体的例子,比如一个简单的Calculator类,展示头文件中的声明文件中的定义。这样用户能更直观地理解结构。需要确保例子中的LaTeX公式正确,比如数学表达式用$...$包裹,独立公式用$$...$$。 还要注意用户提到的系统级指令,比如所有行内数学表达式使用正确的格式。例如,在解释代码结构时,如果涉及到数学表达式或变量,要正确应用LaTeX语法,但在这个特定的问题中可能数学内容不多,但需要留意是否有相关部分。 另外,要检查是否有常见的错误,比如头文件中忘记加#pragma once或保护符,或者源文件中没有包含对应的头文件,导致链接错误。同时,提醒用户编译时的注意事项,比如如何一起编译.cpp文件。 最后,总结分文件的优点,如提高可维护性、团队协作方便等,并确认用户是否理解,是否需要进一步举例或其他说明。整个过程要使用口语化的中文,避免技术术语过于晦涩,同时保持结构清晰,逐步引导用户理解。</think>在C++编程中,将代码分为.h(头文件.cpp(源文件)是一种常见的模块化设计方法。下面分步骤解释其作用用法: ### 一、头文件(.h)的作用 1. **声明接口** - 存放类、函数、结构体、全局变量的**声明** - 示例: ```cpp // Calculator.h #ifndef CALCULATOR_H // 头文件保护 #define CALCULATOR_H class Calculator { // 类声明 public: int add(int a, int b); // 函数声明 double sqrt(double x); }; #endif ``` 2. **包含保护机制** - 通过`#ifndef`、`#define`、`#endif`防止重复包含 - 或使用`#pragma once`(编译器扩展) ### 二、源文件(.cpp)的作用 1. **实现细节** - 包含头文件中声明的具体实现 - 示例: ```cpp // Calculator.cpp #include "Calculator.h" // 包含对应头文件 int Calculator::add(int a, int b) { // 实现成员函数 return a + b; } double Calculator::sqrt(double x) { return x >= 0 ? std::sqrt(x) : 0; } ``` ### 三、分离优势 1. **编译效率** - 修改.cpp文件时只需重新编译该文件 - 通过头文件声明实现编译隔离 2. **代码复用** - 头文件可被多个源文件包含 - 例如:`#include "Calculator.h"`可出现在多个.cpp中 3. **结构清晰** - 接口(.h)与实现(.cpp)分离 - 便于阅读维护 ### 四、使用场景示例 假设要实现一个向量运算库: ```cpp // Vector3.h #ifndef VECTOR3_H #define VECTOR3_H class Vector3 { public: Vector3(double x, double y, double z); double magnitude() const; // 模长计算 Vector3 operator+(const Vector3& other); // 向量加法 private: double x_, y_, z_; }; #endif ``` ```cpp // Vector3.cpp #include "Vector3.h" #include <cmath> Vector3::Vector3(double x, double y, double z) : x_(x), y_(y), z_(z) {} double Vector3::magnitude() const { return std::sqrt(x_*x_ + y_*y_ + z_*z_); // 计算公式:$\sqrt{x^2 + y^2 + z^2}$ } Vector3 Vector3::operator+(const Vector3& other) { return Vector3(x_+other.x_, y_+other.y_, z_+other.z_); } ``` ### 五、编译方法 使用g++编译时需指定所有.cpp文件: ```bash g++ main.cpp Calculator.cpp Vector3.cpp -o program ``` ### 六、注意事项 1. 头文件不应包含函数实现(内联函数除外) 2. 避免在头文件中定义全局变量(易引发重复定义) 3. 使用相对路径包含头文件:`#include "myfolder/mylib.h"` 通过这种分离方式,可以更好地组织大型项目,提高代码的可维护性复用性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值