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!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 undefined3、注意基类中有派生类时的构造函数
//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、注意extern和static关键字
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的问题