Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1202042
  • 博文数量: 181
  • 博客积分: 6155
  • 博客等级: 准将
  • 技术积分: 1805
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-05 09:24
文章分类

全部博文(181)

文章存档

2015年(2)

2014年(3)

2013年(9)

2012年(29)

2011年(30)

2010年(36)

2009年(40)

2008年(32)

分类: C/C++

2011-08-25 10:23:19

通过实例谈谈在c++的头文件中连接外部类型时,是使用include包含;还是使用类型声明,而在cpp文件中再include。
在c++类中声明自定义类型栈成员变量时,需要在头文件中包含该自定义类型的头文件。声明自定义类型堆成员变量时,只需要提供自定义类型名称的定义即可,自定义类型的头文件在该类的定义文件中出现即可。例如:

1 声明自定义类型栈成员变量时:

#include "mydata.h"

class test{
public:
test();
~test();
private:
mydata m_data;
};


2 声明自定义类型堆成员变量时:

// in .h file

class mydata;

class test{
public:
test();
~test();
private:
mydata *m_data;
};

// in .cpp file
#include "mtdata.h"

test::test()
{
m_data = new mydata();
}

~test::test()
{
delete m_data;
}

原因是这样的,栈成员变量的对象在类构造时候必须参与构造,所以类需要知道栈成员变量的构造声明。但是,堆成员变量的对象是动态创建的,肯定在类的构造函数之后,所以类无需知道堆成员变量的构造声明。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
差不多一年时间没用过C++写过程序了,由于工作的需要,我又回到了C++的阵形。在工作的过程中遇到了很多麻烦,当我往工程里加一个类,而且那个类又与工程里的类相关,如有那个类型的成员变量。情况如下
//////A.h///////////
class A
{
.......
};
////////B.h//////////
class B:A
{
....
A member;
}
结果,编译就会出错,说找不到类形A。解决的办法是在B.h里#include “A.h”。但是有时候不用#include “A.h”,只要在classB:A前加class A;就可以了。更严重的是不但要#include “A.h”,还要class A;。
起初觉得没问题,因为这样搞来搞去总会编译通过的,而且不会让程序变大,因为有#ifndef...#endif和#pragmaonce控制。直到有一次,我需要那些常量放到一个文件中“const.h”,然后include到其它需要它的类中,结果怎么也编译不成功(因为文件多了,而且每个文件都这样互相include,把我也蒙了)
直到今天终于从《Effective C++》里找到原理。现向大家分享一下,首先我以下面这个类结构作例子。(先不管我为什么不加一个Woman,为什么Man就有child,我只是作例子解说,绝没有性别歧视。

代码如下:

////////////main.h//////////////

#include "stdafx.h"

#include "man.h"

int main(){

    Man m;

    return 0;

}

////////////Person.h/////////////

#pragma once

class Person

{

public:

    Person(void);

    ~Person(void);

};

////////Person.cpp///////////

#include "StdAfx.h"

#include ".\person.h"

Person:erson(void){

}

Person::~Person(void){

}

/////////Man.h///////////

#pragma once

#include "person.h"

class Man : public Person

{

public:

    Man(void);

    ~Man(void);

private:

    Person child;

};

/////////////Man.cpp//////////////

#include "StdAfx.h"

#include ".\man.h"

Man::Man(void){

}

Man::~Man(void){

}


上述代码,编译运行一切正常。现在我作以下修改:

/////////Man.h///////////

#pragma once

//#include "person.h"        //去掉

class Man : public Person

{

public:

    Man(void);

    ~Man(void);

private:

    Person child;

};

/////////Man.h///////////

#pragma once

//#include "person.h"   //去掉

class Person;           //加入

class Man:public Person

{

public:

    Man(void);

    ~Man(void);

private:

    Person child;

};

error C2504: “Person” : 未定义基类
error C2504: “Person” : 未定义基类

/////////Man.h///////////

#pragma once

//#include "person.h"   //去掉

class Person;           //加入

class Man:public Person

{

public:

    Man(void);

    ~Man(void);

private:

    Person *child;      //改为指针

};

/////////Man.h///////////

#pragma once

//#include "person.h"   //去掉

class Person;           //加入

class Man               //去掉erson

{

public:

    Man(void);

    ~Man(void);

private:

    Person *child;      //改为指针

};
error C2504: “Person” : 未定义基类

编译通过

要讲解上面的代码还要一些预备知备,看下面代码:

int main()

{

    int x;

    Person p;//用C++时编译不通过;

}
当编译器看到x定义式时,它们知道必须配置足够的空间以放置一个int。没问题,每个编译器都知道int有多大。然而当编译器看到p的定义式时,虽然它们也知道必须配置足够空间以放置一个Person,但一个Person对象有多大呢?编译器获得这项信息的唯一办法就是询问class定义式。然而class的定义式可以合法地不列出实现细节(如:
只写出class Person;)那么编译器又如何知道该配置多少空间呢?


对Java等语言对此问题的解法是,当程序定义出一个对象时,只配置足够空间给一个“指向该对象的指针”使用,如
public Person;

public static void main(String[] args)

{

    Person p;

}

对于C++就如下那样:

class Person;

int main()

{

    Person *p;//编译器当要配置一个指针大小的空间的指针给p就可以了。

    //Person &p2; 这个理论上也可以,但references object必须“言之有物”

    return 0;

}

看回刚才那段代码为什么“Person p;//用C++时编译不通过;”呢?因为它要调用Person constructor。那就是Person的实现细节。



现在可以解说上面的表格了,我的目的是 去掉#include Person.h并加入class Person; 所以要做有:
1.
将Person child改为Person *child。因为child也是Man的成员,Man的大小与Child相关,而child不是内部类型,它的大小编译器不知道。
2.
将:public Person去掉。因为Man继承Person,所以编译器也要知道Person是怎样实现的,那样才能构造出正确的Man来(为了编译成功,我忍痛割爱了)。

同时我也要对原码作一下解释:

/////////Man.h///////////

#pragma once

#include "person.h"

class Man : public Person

{

public:

    Man(void);

    ~Man(void);

private:

    Person child;

};


这里#include “person.h”不但包含了Person的定义,也包含了Person的实现细节,所以是编译成功的。

结论
1.
当不需要调用类的实现时,包括constructor,copy constructor,assignment operator,member function,甚至是address-of operator时,就不用#include,只要forward declaration就可以了。
2.       当要用到类的上面那些“方法”时,就要#include

扩充

为了加深认识,我分享遇到的另一情况。

////////////Person.h/////////////

#pragma once

class Person

{

public:

    Person(void);

    ~Person(void);

    virtual void addChild(Person p) = 0;//将Person变为抽象类

};

/////////Man.h///////////

#pragma once

//#include "person.h"   //去掉

class Person;           //加入

class Man               //去掉erson

{

public:

    Man(void);

    ~Man(void);

private:

    Person *child;

    void addChild(Person p);//相应地在Man.cpp中加上这个空函数

};

error C2259: “Person” : 不能实例化抽象类

/////////Man.h///////////

#pragma once

#include "person.h"    //加回来

class Person;      //加不加入也没所谓

class Man               //去掉erson

{

public:

    Man(void);

    ~Man(void);

private:

    Person *child;

    void addChild(Person p);//相应地在Man.cpp中加上这个空函数

};

/////////Man.h///////////

#pragma once

#include "person.h"    //加回来

class Person;      //加不加入也没所谓

class Man               //去掉erson

{

public:

    Man(void);

    ~Man(void);

private:

    Person *child;

    void addChild(Person *p);//将形参变为Person*

};

error C2259: “Person” : 不能实例化抽象类

编译成功

/////////Man.h///////////

#pragma once

#include "person.h"    //加回来

class Person;      //加不加入也没所谓

class Man               //去掉erson

{

public:

    Man(void);

    ~Man(void);

private:

    Person *child;

    void addChild(Person &p);//将形参变为Person&

};


编译成功



为什么出现不能实例化抽象类?我并没有实例化过它。

这是参数的传递问题。当一个变量传给函数时,我们说是实参传给形参(pass-by-value),形参是通过copy constructor建立的,所以就是实例化了一个抽象类。而pass-by-reference和传指针就没问题了。(全文完
阅读(2010) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~