C++的中.h与.cpp⽂件介绍#include与#include区别
#include< >直接从编译器⾃带的函数库中寻⽂件
#include" "是先从⾃定义的⽂件中,如果不到在从函数库中寻⽂件
采⽤"< >"⽅式进⾏包含的头⽂件表⽰让编译器在编译器的预设标准路径下去搜索相应的头⽂件,如果不到则报错。
特别要注意的是,如果是标准库头⽂件,那么既可以采⽤<>的⽅式,⼜可以采⽤" "的⽅式,⽽⽤户⾃定义的头⽂件只能采⽤" "的⽅式。
例如,如下这种⽅式是正确的:
#include <iostream>效率较⾼(编译器⾃带的函数库中寻⽂件)include和contain
#include "iostream"效率较低(从⾃定义的⽂件中 ,如果不到在从函数库中寻⽂件)
如下这种⽅式是不正确的:
#include <hanli.h>
hanli.h是我们⾃⼰创建的头⽂件
c++中xx.h和xx.cpp之间的联系
不是很严格的讲,*.h⽂件做的是类的声明,包括类成员的定义和函数的声明,⽽*.cpp⽂件做的类成员函数的具体实现(定义)。
⼀个*.h⽂件和*.cpp⽂件⼀般是配对的。在*.cpp⽂件的第⼀⾏⼀般也是#include"*.h"⽂件,其实也相当于把*.h⽂件⾥的东西复制到*.cpp⽂件的开头。所以,你全部写在*.cpp⽂件其实也是⼀样的。
除了编程规范外,还涉及到类⼀个重要性质,就是封装性。⽐如现在我们公司和另⼀家软件公司合作,这样就必然要互相提供⼀些软件的信息(⽐如⼀些类,它到底是要做什么的),可是在提供这些信息的同时我们⼜不像让对⽅知道我们这些类的具体实现,毕竟这些是我们公司的算法核⼼和⼼⾎啊。所以这个时候就可以把类的接⼝(这个类是要做什么的)放在*.h⽂件中,⽽具体类的实现放在 *.cpp⽂件。这时候我们只要给对⽅公司*.h⽂件就⾏了。这样既提供了必要的信息,⼜保护了我们的核⼼代码。
头⽂件(.h):C++ 头⽂件仍然以.h为后缀,它们所包含的类、函数、宏等都是全局范围的。
写类的声明(包括类⾥⾯的成员和⽅法的声明)、函数原型、#define常数等,但⼀般来说不写出具体
的实现。
在写头⽂件时需要注意,在开头和结尾处必须按照如下样式加上预编译语句(如下):
#ifndef CIRCLE_H
#define CIRCLE_H
//你的代码写在这⾥
#endif
这样做是为了防⽌重复编译,不这样做就有可能出错。
⾄于CIRCLE_H这个名字实际上是⽆所谓的,你叫什么都⾏,只要符合规范都⾏。原则上来说,⾮常建议把它写成这种形式,因为⽐较容易和头⽂件的名字对应。
源⽂件(.cpp):
源⽂件主要写实现头⽂件中已经声明的那些函数的具体代码。需要注意的是,开头必须#include⼀下实现的头⽂件,以及要⽤到的头⽂件。那么当你需要⽤到⾃⼰写的头⽂件中的类时,只需要#include进来就⾏了。
下⾯举个最简单的例⼦来描述⼀下,咱就求个圆⾯积。
在头⽂件的⽂件夹⾥新建⼀个名为Circle.h的头⽂件,它的内容如下:
#ifndef CIRCLE_H
#define CIRCLE_H
class Circle
{
private:
double r;//半径
public:
Circle();//构造函数
Circle(double R);//构造函数
double Area();//求⾯积函数
};
#endif
注意到开头结尾的预编译语句。在头⽂件⾥,并不写出函数的具体实现。
第2步,要给出Circle类的具体实现,因此,在源⽂件夹⾥新建⼀个Circle.cpp的⽂件,它的内容如下:
#include "Circle.h"
Circle::Circle()
{
this->r=5.0;
}
Circle::Circle(double R)
{
this->r=R;
}
double Circle:: Area()
{
return 3.14*r*r;
}
需要注意的是:开头处包含了Circle.h,事实上,只要此cpp⽂件⽤到的⽂件,都要包含进来!这个⽂件的名字其实不⼀定要叫Circle.cpp,但⾮常建议cpp⽂件与头⽂件相对应。
最后,我们建⼀个main.cpp来测试我们写的Circle类,它的内容如下:
#include <iostream>
#include "Circle.h"
using namespace std;
int main()
{
Circle c(3);
cout<<"Area="<<c.Area()<<endl;
return 1;
}
注意到开头时有#include "Circle.h"的声明,证明我们使⽤到了我们刚才写的Circle类。
⾄此,我们⼯程的结构为:
运⾏⼀下,输出结果为:
说明我们写的Circle类确实可以⽤了。
1..h叫做头⽂件,它是不能被编译的。“#include”叫做编译预处理指令,可以简单理解成,在1.cpp中的#include"1.h"指令把1.h中的代码在编译前添加到了1.cpp的头部。每个.cpp⽂件会被编译,⽣成⼀个.obj⽂件(转化为汇编码,这种⽂件称为⽬标⽂件。后缀为.obj)然后所有的.obj⽂件链接起来你的可执⾏程序(.exe⽂件)就算⽣成了。
发现了没有,你要在.h⽂件中严格区分声明语句和定义语句。好的习惯是,头⽂件中应只处理常量、变量、函数以及类等等等等的声明,变量的定义和函数的实现等等等等都应该在源⽂件.cpp中进⾏。
⾄于.h和.cpp具有同样的主⽂件名的情况呢,对编译器来讲是没有什么意义的,编译器不会去匹配⼆者的主⽂件名,相反它很傻,只认
#include等语句。但是这样写是⼀种约定俗成的编程风格,⼀个类的名字作为其头⽂件和源⽂件的主
⽂件名⽐如Class1.h和Class1.cpp,这个类的声明在Class1.h中,实现在Class1.cpp中,我们⼈类看起来⽐较整齐,读起来⽅便,也很有利于模块化和源代码的重⽤。
为什么这个风格会约定俗成?有⼀句著名的话,叫“程序是为程序员写的”。
2.h⽂件和cpp⽂件也就是说,在h⽂件中声明Declare,⽽在cpp⽂件中定义Define。 “声明”向计算机介绍名字,它说,“这个名字是什么意思”。⽽“定义”为这个名字分配存储空间。⽆论涉及到变量时还是函数时含义都⼀样。⽆论在哪种情况下,编译器都在“定义”处分配存储空间。对于变量,编译器确定这个变量占多少存储单元,并在内存中产⽣存放它们的空间。对于函数,编译器产⽣代码,并为之分配存储空间。函数的存储空间中有⼀个由使⽤不带参数表或带地址操作符的函数名产⽣的指针。定义也可以是声明。如果该编译器还没有看到过名字A,程序员定义int A,则编译器马上为这个名字分配存储地址。声明常常使⽤于extern关键字。如果我们只是声明变量⽽不是定义它,则要求使⽤extern。对于函数声明, extern是可选的,不带函数体的函数名连同参数表或返回值,⾃动地作为⼀个声明。
概览
*申明:declaration
*定义:definition
头⽂件
头⽂件的所有内容,都必须包含在
#ifndef {Filename}
#define {Filename}
//{Content of head file}
#endif
这样才能保证头⽂件被多个其他⽂件引⽤(include)时,内部的数据不会被多次定义⽽造成错误
inline限定符
在头⽂件中,可以对函数⽤inline限定符来告知编译器,这段函数⾮常的简单,可以直接嵌⼊到调⽤定义之处。
当然inline的函数并不⼀定会被编译器作为inline来实现,如果函数过于复杂,编译器也会拒绝inline。
因此简单说来,代码最好短到只有3-5⾏的才作为inline。有循环,分⽀,递归的函数都不要⽤做inline。
对于在类定义内定义实现的函数,编译器⾃动当做有inline请求(也是不⼀定inline的)。因此在下边,我把带有inline限定符的函数成员和写在类定义体内的函数成员统称为“要inline的函数成员”
⾮模板类型
全局类型
就像前⾯笼统的话讲的:申明写在.h⽂件。
对于函数来讲,没有实现体的函数,就相当于是申明;⽽对于数据类型(包括基本类型和⾃定义类型)来说,其申明就需要⽤extern来修饰。
然后在.cpp⽂件⾥定义、实现或初始化这些全局函数和全局变量。
不过导师⼀直反复强调:不许使⽤全局函数和全局变量。⽤了之后造成的后果,⽬前就是交上去的作业项⽬会扣分。当然不能⽤⾃有不能⽤的理由以及解决⽅案,不过不在⽬前的讨论范围内。
⾃定义类型
对于⾃定义类型,包括类(class)和结构体(struct),它们的定义都是放在.h⽂件中。其成员的申明和定义就⽐较复杂了,不过看上边的表格,还是⽐较清晰的。
函数成员
函数成员⽆论是否带有static限定符,其申明都放在.h⽂件的类定义内部。
对于要inline的函数成员其定义放在.h⽂件;其他函数的实现都放在.cpp⽂件中。
数据成员
数据成员的申明与定义都是放在.h⽂件的类定义内部。对于数据类型,关键问题是其初始化要放在什么地⽅进⾏。
对于只含有static限定符的数据成员,它的初始化要放在.cpp⽂件中。因为它是所有类对象共有的,因此必须对它做合适的初始化。
对于只含有const限定符的数据成员,它的初始化只能在构造函数的初始化列表中完成。因为它是⼀经初始化就不能重新赋值,因此它也必须进⾏合适的初始化。
对于既含有static限定符,⼜含有const限定符的数据成员,它的初始化和定义同时进⾏。它也是必须进⾏合适的初始化
对于既没有static限定符,⼜没有const限定符的数据成员,它的值只针对本对象可以随意修改,因此我们并不在意它的初始化什么时候进⾏。
模板类型
C++中,模板是⼀把开发利器,它与C#,Java的泛型很相似,却⼜不尽相同。以前,我⼀直只觉得像泛型,模板这种东西我可能⼀辈⼦也不可能需要使⽤到。但是在导师的强制逼迫使⽤下,我才真正体会到模板的强⼤,也真正知道要如何去使⽤模板,更进⼀步是如何去设计模板。不过这不是三⾔两语可以讲完的,就不多说了。
对于模板,最重要的⼀点,就是在定义它的时候,编译器并不会对它进⾏编译,因为它没有⼀个实体可⽤。
只有模板被具体化(specialization)之后(⽤在特定的类型上),编译器才会根据具体的类型对模板进⾏编译。
所以才定义模板的时候,会发现编译器基本不会报错(我当时还很开⼼的:我写代码尽然会没有错误,
⼀⽓呵成),也做不出智能提⽰。但是当它被具体⽤在⼀个类上之后,错误就会⼤⽚⼤⽚的出现,却往往⽆法准确定位。
因此设计模板就有设计模板的⼀套思路和⽅式,但是这跟本⽂的主题也有偏。
因为模板的这种特殊性,它并没有⾃⼰的准确定义,因此我们不能把它放在.cpp⽂件中,⽽要把他们全部放在.h⽂件中进⾏书写。这也是为了在模板具体化的时候,能够让编译器可以到模板的所有定义在哪⾥,以便真正的定义⽅法。
⾄于模板类函数成员的定义放在哪⾥,导师的意见是放在类定义之外,因为这样当你看类的时候,⼀⽬了然地知道有那些⽅法和数据;我在⽤Visual Studio的时候查看到其标准库的实现,都是放在类内部的。
可能是我习惯了C#的风格,我⽐较喜欢把它们都写在类内部,也因为在开发过程中,所使⽤的编辑器都有⼀个强⼤的功能:代码折叠。
当然还有其他原因就是写在类外部,对于每⼀个函数成员的实现都需要把模板类型作为限定符写⼀遍,把类名限定符也要写⼀遍。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。