您的位置:首页 > 编程语言 > C语言/C++

C++中的“include”,“namespace”以及“前置声明”

2017-03-17 14:25 253 查看
1.include
“#include***”表示包含C/C++的头文件。其包含的指令不仅仅限于“.h”的头文件,还可以包含任何编译器能识别的C/C++代码文件,包括“.c”,“.cpp”,“.txt”,“.abc”都可以。
我们注意到include后面有两种表示形式,一个是“#include<***>”,另一个是“#include"***"”。
“<***>”引用的是编译器的类库路径里面的头文件;
““***””引用的是程序目录的相对路径中的头文件,在程序目录的相对路径中找不到该头文件时会继续在类库路径里搜寻该头文件。
但一般而言,我们在编程的时候,一般用“***”来include自己定义的头文件。

2.namespace
命名空间的定义格式为:named-namespace-definition:
namespace identifier { namespace-body }
unnamed-namespace-definition:
namespace { namespace-body }
namespace-body:
declaration-seqopt

namespace实际上就是一个由程序设计者命名的内存区域,程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体区分开来。

从上面一段从标准文档中摘取的代码可以看到,namespace可以声明为有名的命名空间,也可以声明为无名的命名空间。
所谓的C++中的namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个为std的namespace中。
具体用法可以通过下面的代码来阐明(来源:Namespaces in C++
#include <iostream>
using namespace std;

// first name space
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}

// second name space
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}

using namespace first_space;
int main () {

// This calls function from first name space.
func();

return 0;
}运行结果可想而知。我们在两个不同的namespace里面定义了名字相同的函数,main函数中使用了第一个命名空间,故调用的是第一个namespace中的fun()

3.前置声明(该部分转载自【作者:清林,博客名:飞空静渡】)
定义一个类 class A,这个类里面使用了类B的对象b,然后定义了一个类B,里面也包含了一个类A的对象a,就成了这样:

//a.h
#include "b.h"
class A
{
....
private:
B b;
};
//b.h
#include "a.h"
class B
{
....
private:
A a;
};
一编译,就出现了一个互包含的问题了,这时就有人跳出来说,这个问题的解决办法可以这样,在a.h文件中声明类B,然后使用B的指针:
//a.h
//#include "b.h"
class B;
class A
{
....
private:
B b;
};
//b.h
#include "a.h"
class B
{
....
private:
A a;
};
然后,问题就解决了。
但是,有人知道问题是为什么就被解决的吗,也就是说,加了个前置声明为什么就解决了这样的问题。下面,让我来探讨一下这个前置声明。
 
类的前置声明是有许多的好处的。
我们使用前置声明的一个好处是,从上面看到,当我们在类A使用类B的前置声明时,我们修改类B时,只需要重新编译类B,而不需要重新编译a.h的(当然,在真正使用类B时,必须包含b.h)。
另外一个好处是减小类A的大小,上面的代码没有体现,那么我们来看下://a.h
class B;
class A
{
....
private:
B *b;
....
};
//b.h
class B
{
....
private:
int a;
int b;
int c;
};
我们看上面的代码,类B的大小是12(在32位机子上)。
如果我们在类A中包含的是B的对象,那么类A的大小就是12(假设没有其它成员变量和虚函数)。如果包含的是类B的指针*b变量,那么类A的大小就是4,所以这样是可以减少类A的大小的,特别是对于在STL的容器里包含的是类的对象而不是指针的时候,这个就特别有用了。
 
在前置声明时,我们只能使用的就是类的指针和引用(因为引用也是居于指针的实现的)。
 
那么,我问你一个问题,为什么我们前置声明时,只能使用类型的指针和引用呢?
 
如果你回答到:那是因为指针是固定大小,并且可以表示任意的类型,那么可以给你80分了。为什么只有80分,因为还没有完全回答到。
 
想要更详细的答案,我们看下面这个类:class A
{
public:
A(int a):_a(a),_b(_a){} // _b is new add

int get_a() const {return _a;}
int get_b() const {return _b;} // new add
private:
int _b; // new add
int _a;
}; 我们看下上面定义的这个类A,其中_b变量和get_b()函数是新增加进这个类的。
那么我问你,在增加进_b变量和get_b()成员函数后这个类发生了什么改变,思考一下再回答。
 
好了,我们来列举这些改变:
第一个改变当然是增加了_b变量和get_b()成员函数;
 
第二个改变是这个类的大小改变了,原来是4,现在是8。
 
第三个改变是成员_a的偏移地址改变了,原来相对于类的偏移是0,现在是4了。
 
上面的改变都是我们显式的、看得到的改变。还有一个隐藏的改变,想想是什么。。。
 
这个隐藏的改变是类A的默认构造函数和默认拷贝构造函数发生了改变。
 
由上面的改变可以看到,任何调用类A的成员变量或成员函数的行为都需要改变,因此,我们的a.h需要重新编译。
如果我们的b.h是这样的://b.h
#include "a.h"
class B
{
...
private:
A a;
}; 那么我们的b.h也需要重新编译。
如果是这样的://b.h
class A;
class B
{
...
private:
A *a;
}; 那么我们的b.h就不需要重新编译。
 
像我们这样前置声明类A:
class A;
是一种不完整的声明,只要类B中没有执行需要了解类A的大小或者成员的操作,则这样的不完整声明允许声明指向A的指针和引用。
而在前一个代码中的语句
A a;
是需要了解A的大小的,不然是不可能知道如果给类B分配内存大小的,因此不完整的前置声明就不行,必须要包含a.h来获得类A的大小,同时也要重新编译类B。
 
再回到前面的问题,使用前置声明只允许的声明是指针或引用的一个原因是只要这个声明没有执行需要了解类A的大小或者成员的操作就可以了,所以声明成指针或引用是没有执行需要了解类A的大小或者成员的操作的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: