c++ Primer 5 12.1.6节练习 (头文件相互包含的问题及解决办法)

本文讨论了C++中头文件相互包含导致的编译错误,解释了前置声明的作用,并提出了避免此类问题的原则。总结了四种处理头文件相互依赖的方法,包括使用前置声明、在cpp文件中包含头文件、使用全局变量和extern、利用基类指针,以及将类定义放在同一个头文件中。

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

12.19

拿此题来说,StrBlobPtr类要用到StrBlob类,而StrBlob类要用到StrBlobPtr类。

即如下:

class StrBlob{
friend class StrBlobPtr;
public:
	typedef std::vector<std::string>::size_type size_type;
	StrBlob();//默认构造函数
	StrBlob(std::initializer_list<std::string>);//接收1个参数的initializer_list作为形参
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	void push_back(const std::string &t) { return data->push_back(t); }
	void pop_back();
	std::string& front();
	std::string& back();
	StrBlobPtr begin() { return StrBlobPtr(*this); }//返回一个指向首元素的迭代器
	StrBlobPtr end(){ auto ret = StrBlobPtr(*this, this->size()); return ret; }//返回尾后的迭代器
private:
	std::shared_ptr<std::vector<std::string>> data;//一个指向vector<string>的智能指针
	void check(size_type i, const std::string &msg) const;//检查data[i]是否合法,抛出异常
};

class StrBlobPtr{

public:
	StrBlobPtr() :curr(0) { }//默认构造函数,令curr为0
	StrBlobPtr(StrBlob &a, size_t t = 0) :wptr(a.data), curr(t)  { }//带参数的构造函数
	string & deref() const;//解引用操作
	StrBlobPtr & incr();//递增操作
private:
	shared_ptr<vector<string>> check(size_t, const string&) const;//检查下标是否过界
	weak_ptr<vector<string>> wptr;//定义一个空的weakptr
	size_t curr;//定义元素下标
};

可以看到这两个类要相互包含,那么该怎样处理?

1.如果两个都在各自的.h中,并且相互包含对方的.h,不用前置声明。

即在StrBlob.h文件中 #include "StrBlobPtr.h" 在StrBlobPtr.h文件中 #include "StrBlob.h"

在main.cpp中 #include "StrBlob.h" ,会出现什么样的错误呢?

error C2027: 使用了未定义类型“StrBlobPtr”,即该类未定义。
因为在头文件中使用了保护符,那么编译器只对每个头文件编译一次,那么在StrBlob.h中就不再编译StrBlobPtr类,即StrBlob使用的是未定义的类。


2.如果对1进行前置声明 ,为了清晰明了,用类A和B表示。

 #pragma once 
#include "B.h" 
class B; 
 
class A 
{ 
public: 
        B* b; 
}; 
 
//文件B.h中的代码 
#pragma once 
#include "A.h" 
class B; 
 
class B 
{ 
public: 
        A* a; 
};  
这样,我们发现编译成功。即说明了,头文件不能替代前置声明。

但我们将各自对方的头文件去掉,再次编译,发现仍然编译成功。那么什么时候该包含对方的.h文件呢?


3.包含对方.h头文件的原则。

/***********************************此段来自网络*********************************/

头文件包含其实是一想很烦琐的工作,不但我们看着累,编译器编译的时候也很累,再加上头文件中常常出现的宏定义。感觉各种宏定义的展开是非常耗时间的,远不如自定义函数来得速度。这里仅就不同头文件、源文件间的句则结构问题提出两点原则,仅供参考:
    第一个原则应该是,如果可以不包含头文件,那就不要包含了。这时候前置声明可以解决问题。如果使用的仅仅是一个类的指针,没有使用这个类的具体对象(非指针),也没有访问到类的具体成员,那么前置声明就可以了。因为指针这一数据类型的大小是特定的,编译器可以获知。
    第二个原则应该是,尽量在CPP文件中包含头文件,而非在头文件中。假设类A的一个成员是是一个指向类B的指针,在类A的头文件中使用了类B的前置声明并编译成功,那么在A的实现中我们需要访问B的具体成员,因此需要包含头文件,那么我们应该在类A的实现部分(CPP文件)包含类B的头文件而非声明部分(H文件)。

/****************************************************************************************/

当我们的类只用到另一个类的的指针,引用等,没有去使用这个类的实例和他的具体成员,我们就直接使用前置声明,无需在自己的头文件.h中去包含对方的的.h,包含对方的.h可以放在自己的.cpp文件中去。


4.如果我们必须要使用另一个类的具体实例,或者成员函数。该怎么做?

一、使用全局变量+extern声明。

但实际上这样也破坏封装性。


二、使用基类指针。

...............之后复习基类在补充。


三、将两个类写在一个.h文件中,不要忘记前置声明。

按此题为例:

class StrBlobPtr;
class StrBlob{
friend class StrBlobPtr;
public:
	typedef vector<string>::size_type size_type;
	StrBlob();//默认构造函数
	StrBlob(initializer_list<string>);//接收1个参数的initializer_list作为形参
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	void push_back(const string &t) { return data->push_back(t); }
	void pop_back();
	string& front();
	string& back();
	//StrBlobPtr begin() { return StrBlobPtr(*this); }//返回一个指向首元素的迭代器
	//StrBlobPtr end(){ auto ret = StrBlobPtr(*this, this->size()); return ret; }//返回尾后的迭代器
	StrBlobPtr begin();//注意,这两个函数必须在类StrBlobPtr定义后,才能定义,因为它使用了StrBlobPtr的具体实例
	StrBlobPtr end();
private:
	shared_ptr<vector<string>> data;//一个指向vector<string>的智能指针
	void check(size_type i, const string &msg) const;//检查data[i]是否合法,抛出异常
};


class StrBlobPtr{

public:
	StrBlobPtr() :curr(0) { }//默认构造函数,令curr为0
	StrBlobPtr(StrBlob &a, size_t t = 0) :wptr(a.data), curr(t)  { }//带参数的构造函数
	string & deref() const;//解引用操作
	StrBlobPtr & incr();//递增操作
private:
	shared_ptr<vector<string>> check(size_t, const string&) const;//检查下标是否过界
	weak_ptr<vector<string>> wptr;//定义一个空的weakptr
	size_t curr;//定义元素下标
};

然后在.cpp中定义各个函数即可。

 值得注意的是,写在一个.h文件中时,前置声明之后,那个类是不完全的类,我们同样只能使用他的指针或者引用等,如红色的两行只能先声明,在StrBlobPtr定义之后在定义。


四、如果用到对方类,只用其类的指针或者引用,然后在自己的.h里前置声明对方类,不用包含对方的.h,在自己的.cpp实现时,再包含对方的.h

如下:

StrBlob.h中:

#ifndef STRBLOB_H_
#define STRBLOB_H_
#include <iostream>
#include <vector>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using namespace std;

class StrBlobPtr;
class StrBlob{

StrBlob.cpp中

#include "StrBlob.h"
#include "StrBlobPtr.h"

StrBlobPtr.h中

#ifndef STRBLOBPTR_H_
#define STRBLOBPTR_H_
#include <iostream>
#include <vector>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using namespace std;

class StrBlob;
class StrBlobPtr{

StrBlobPtr.cpp中

#include "StrBlobPtr.h"
#include "StrBlob.h"

这样,程序可以正常编译。


12.20

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include "StrBlob.h"
#include "StrBlobPtr.h"

using namespace std;
int main()
{
    ifstream input("F:\\test\\input.txt");
    string temp;
    StrBlob sb;
    if (getline(input, temp))
    {
        sb.push_back(temp);
        while (getline(input, temp))
        {
            sb.push_back(temp);
        }
    }
    else
        cerr << "No data!" << endl;

    for (auto i = sb.begin(); neq(i, sb.end()); i.incr()) //neq即!= ,incr()即++i
        cout << i.deref() << endl;
    system("pause");
    return 0;
}



可以得到:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值