C++ 文件include规则 常量定义

总结一句话就是: C++的函数声明,变量声明,类定义写在头文件里,而函数实现,变量定义,类方法实现写在.cpp文件中;但是对于内联函数和模版类,函数的实现也要写在头文件里!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

1. 将类的成员变量、类方法的定义写在.h中,将类方法的实现写在.cpp中,不要include .cpp文件,不要在.h文件中只写class MyClass; ,一定要写类成员变量和方法的全部定义!!!类方法的实现写在.cpp文件中。

2. 类模版或者模版的定义一定要写在同一个.h中,不要写在.cpp中,不能分开写!!!可以参考 http://blog.csdn.net/ixsea/article/details/6695496 中的解释:对于模板函数来说,只有被调用的模板函数才被实例化,这里的被调用并不要求它必须被main函数调用。某个普通函数调用了模板函数,该模板函数就将对应产生一个实例,而调用它的普通函数可能并不被main调用,也即有可能并不被执行。而普通函数.h
和 .cpp可以分开的原因是已经实例化好的,因此可以根据.h中的定义找到函数实现的位置!!!全文为:

----------------------------------------------------------------------------------------------------------------------------------------

观点

包含模型是C++模板源代码的一种组织方式,它鼓励将模板代码全部放在一个.h头文件中,这样可以避免莫名其妙的链接错误。

莫名其妙的链接错误

一般而言,程序员习惯将函数和类的声明放在.h文件、把它们的实现放在.cpp文件,这种多文件组织方式一直被倡导。一方面,这种分离使得代码逻辑清晰,想要了解程序用到哪些全局函数和类,只要查看.h文件就可以。如果把声明和实现都揉在一起,带来的麻烦可想而知,要在一堆乱糟糟的代码中寻找函数名、类名、成员名是一种折磨。另一方面,在构建动态链接库时,这种组织方式是必需的。因为动态链接库是二进制级别上的代码复用,它的一大优点就是具体的实现过程被隐藏起来,全部揉在一个.h文件中显然不符合要求。

然而不幸的是,当程序员仍然按照这种好的习惯编写模板代码时,却出现了问题。比如下面这个简单的例子:

  1. // Bigger.h
  2. template<typename T>
  3. T Bigger(T,T);
  4. //Bigger.cpp
  5. #include"Bigger.h"
  6. template<typename T>
  7. T Bigger(T a,T b)
  8. {
  9. return a>b?a:b;
  10. }
  11. //main.cpp
  12. #include"Bigger.h"
  13. #include<iostream>
  14. using namespace std;
  15. int main()
  16. {
  17. cout<<Bigger(10,20)<<endl;
  18. system("pause");
  19. return 0;
  20. }

这几行代码很简单,分成了三个文件Bigger.h、Bigger.cpp以及main.cpp,分别对应模板函数Bigger的声明、定义和使用。看起来结构清晰,符合好的编码习惯,编译链接却得到这样的错误提示:

Error 1 error LNK2019: unresolved external symbol "int __cdecl Bigger<int>(int,int)" ([email protected]@@[email protected])
referenced in function _main E:\Codes\Chapter3Lab\includeModel\main.obj

意思是链接器找不到main.obj里Bigge<int>函数的实现。这种看起来毫无道理的链接错误,也很好的体现了模板的实例化规则。

模板的实例化规则

对于模板函数来说,只有被调用的模板函数才被实例化,这里的被调用并不要求它必须被main函数调用。某个普通函数调用了模板函数,该模板函数就将对应产生一个实例,而调用它的普通函数可能并不被main调用,也即有可能并不被执行。

模板类也有类型的实例化规则,特别的是即使显式实例化了类模板,类模板的成员函数也未必被实例化,这是模板类的“不完全”实例化规则,读者可以点击这里了解更多。

链接错误的解释

了解了模板的实例化规则,就可以对上面的链接错误做出解释了。main.cpp中调用了Bigger(10,20),按理说这将引起模板函数Bigger(T,T)被实例化为普通函数,然而在main.cpp所属的翻译单元里并没有Bigger(T,T)的实现,对main.cpp所属的翻译单元来说,Bigger(T,T)的实现是不可见的。因此,由main.cpp所属翻译单元编译得到main.obj时,编译器假设Bigger<int>(int,int)在其它翻译单元中。

Bigger.cpp虽然有Bigger(T,T)的实现,但是由于在Bigger.cpp所属翻译单元中Bigger并没有被调用,因此Bigger.cpp就没有义务对模板函数Bigger(T,T)进行实例化,于是由它产生的Bigger.obj中也找不到的Bigger<int>(int,int)。

本文前述例子中的链接错误信息正是表达的这个意思。

链接错误的进一步探讨

既然是因为Bigger.cpp没有义务对Bigger(T,T)进行实例化,那么在Bigger.cpp中增加一个调用Bigger<int>(int,int)函数的普通函数是否就可以了呢?在Bigger.cpp文件中添几行代码,如下所示:

  1. //Bigger.cpp
  2. #include"Bigger.h"
  3. template<typename T>
  4. T Bigger(T a,T b)
  5. {
  6. return a>b?a:b;
  7. }
  8. void g()  //增加一个调用Bigger<int>(int,int)的普通函数g()
  9. {
  10. Bigger(1,2);
  11. }

编译、链接成功,允许结果正确,进一步验证了上述观点。

解决方法 - 包含模型

本文列出的例子很简单,规模小,所以按照模板的实例化规则,“人为”地介入到模板函数的实例化过程中并让程序成功运行。但是,在规模较大的程序里,想要人为介入加以控制几乎是不可能的,应该使用C++推荐的包含模型。

具体做法并不复杂:把模板的声明和定义放在一个.h文件中,凡是用到该模板的.cpp文件包含它所在的.h文件就可以了。上面的例子使用包含模型改写,最终是代码是这样的:

  1. // Bigger.h
  2. template<typename T>
  3. T Bigger(T a,T b)
  4. {
  5. return a>b?a:b;
  6. }
  7. //main.cpp
  8. #include"Bigger.h"
  9. #include<iostream>
  10. using namespace std;
  11. int main()
  12. {
  13. cout<<Bigger(10,20)<<endl;
  14. system("pause");
  15. return 0;
  16. }

不过仍然有一个问题值得思考:当多个.cpp文件同时包含Bigger.h时,就有可能产生多份相同类型的实例化,这样是否会造成最终生成的.exe文件变得庞大?这个问题理论上是存在的,不过现在大多数编译器都对此作了一定的优化,一个模板的相同类型有多份实例化体时,编译器最终只保留一个,这样就避免了“代码膨胀”的问题。

下面给一个例子:

//main.cpp

#include "person.h"
#include "SmartPointer.h"

using namespace std;
int test() {
  //auto_ptr<person> p(new person("Cici"));
  //SmartPointer<person> p(new person("Cici"));
  //p -> tell();
  SmartPointer<person> r(new person("taoqi"));
  SmartPointer<person> p(new person("Cici"));  

  p -> tell();
  {
	SmartPointer<person> q = p;
    q -> tell();
    r = q;
	SmartPointer<person> s(r);
    s -> tell();
  }
  r -> tell();  

  return 0;
}
int main(){
  test();
  return 0;
}

//SmartPointer.h

#ifndef SMARTPOINTER_H
#define SMARTPOINTER_H

template<typename T>
class SmartPointer
{
public:
  SmartPointer(T* ptr);
  ~SmartPointer();
  SmartPointer(SmartPointer<T>& sptr);
  T* operator->();
  T& operator*();
  SmartPointer<T>& operator=(SmartPointer<T>& sptr);
  T getValue();
protected:
  T* ref;
  unsigned* ref_count;
};

template<typename T>
SmartPointer<T>::SmartPointer(T* ptr){
  ref = ptr;
  ref_count = (unsigned*)malloc(sizeof(unsigned));
  *ref_count = 1;
}

template<typename T>
SmartPointer<T>::~SmartPointer(){
  --*ref_count;
  if (*ref_count == 0) {
    delete ref;
    free(ref_count);

    ref = NULL;
    ref_count = NULL;
  }
}
template<typename T>
SmartPointer<T>::SmartPointer(SmartPointer<T>& sptr) {
  ref = sptr.ref;
  ref_count = sptr.ref_count;
  ++(*ref_count);
}
template<typename T>
T* SmartPointer<T>::operator->() {
  return ref;
}
template<typename T>
T& SmartPointer<T>::operator*() {
  return *ref;
}
template<typename T>
SmartPointer<T>& SmartPointer<T>::operator=(SmartPointer<T>& sptr){
  if (this != &sptr) {
    ref = sptr.ref;
    ref_count = sptr.ref_count;
    ++(*ref_count);
  }
  return *this;
}
template<typename T>
T getValue() {
  return *ref;
}

#endif

//person.h

#ifndef PERSON_H
#define PERSON_H

#include <string>
#include <iostream>
using namespace std;
class person
{
public:
  person(string name);
  ~person(void);
  void tell();
private:
  string name;
};

#endif

//person.cpp

#include "person.h"

person::person(string name):name(name){
}
void person::tell(){
  cout << "Hi! I am " << name << endl;
}
person::~person(){
  cout << "Bye!" << endl;
}

另外一篇文章关于内部链接和外部连接的解释:

http://www.cnblogs.com/magicsoar/p/3840682.html

内部链接与外部链接

那么什么内部链接和外部链接又是什么呢?

我们知道C++中声明和定义是可以分开的

例如在vs中,我们可以一个函数声明定义放在b.cpp中,在a.cpp只需再声明一下这个函数,就可以在a.cpp中使用这个函数了

a.cpp

void show();

int main()
{
    show();
return 0;
}

b.cpp

#include <iostream>
void show()
{
    std::cout << "Hello" << std::endl;
}

而通过之前的了解,我们知道每个编译单元间是相互独立不知道彼此的存在的

那么a.cpp又是如何知道show函数的定义的呢

其实在编译一个编译单元(.cpp)生成相应的obj文件过程中

编译器会将分析这个编译单元(.cpp)

将其所能提供给其他编译单元(.cpp)使用的函数,变量定义记录下来。

而将自己缺少的函数,变量的定义也记录下来。

所以可以认为a.obj和b.obj记录了以下的信息

然后在链接器连接的时候就会知道a.obj需要show函数定义,而b.obj中恰好提供了show函数的定义,通过链接,在最终的可执行文件中我们能看到show函数的运行

哪这些又和内部链接,外部链接有什么关系呢?

那些编译单元(.cpp)中能向其他编译单元(.cpp)展示,提供其定义,让其他编译单元(.cpp)使用的的函数,变量就是外部链接,例如全局变量

而那些编译单元(.cpp)中不能向其他编译单元(.cpp)展示,提供其定义的函数,变量就是内部链接,例如static函数,inline函数等

好了让我们看下编译单元,内部链接和外部链接比较正式的定义吧

编译单元:当一个c或cpp文件在编译时,预处理器首先递归包含头文件,形成一个含有所有 必要信息的单个源文件,这个源文件就是一个编译单元。

内部连接:如果一个名称对编译单元(.cpp)来说是局部的,在链接的时候其他的编译单元无法链接到它且不会与其它编译单元(.cpp)中的同样的名称相冲突。

外部连接:如果一个名称对编译单元(.cpp)来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互。

----------------------------------------------------------------------------------------------------------------------------------------

3. 使用下面两种方式防止重复include:

#ifndef PERSON_H

#define PERSON_H

#endif

或者

#pragma once

4. 给出在定义类内部可用常量,文件作用域常量,全局常量的写法:

a. 类成员变量是无法在成员变量定义的时候初始化的(除非是const static),因此在这个时候,成员变量初始化列表是const变量初始化的唯一机会了。。。写成

class MyClass {

private:

const int a1 = 3;

const char* s1 = "abc";

}

是大错而且特错的!!!!同时注意只能在构造函数的初始化列表里初始化

b. 定义全局变量时,extern int a; 只是声明,并没有定义,但是extern int a = 3却是在定义;当然可以在a.cpp中 extern int a = 3; 在b.cpp 中extern int a;来声明。但是比较规范的做法是可以把extern int a; 扔到b.cpp 的头文件b.h中,在b.cpp中只是定义int a = 3; 其他文件用的时候只是#include "b.h"即可。

c. static const int a = 3; 要写在.cpp中,因为只在这个文件中使用,.h文件是供其他人include用的:

因此正确的代码是:

//main.cpp

//main.cpp
#include"MyClass.h"
#include<iostream>
using namespace std;  

int main()
{
  MyClass myClass(30,"abc");

  cout << "a2 = " << a2 << endl;
  cout << "s2 = " << s2 << endl;

  return 0;
}  

//MyClass.cpp

//MyClass.cpp
#include "MyClass.h"
#include <iostream>
using namespace std;

const int a2 = 2;
const char* const s2 = "s2";

static const int a3 = 3;
static const char* const s3 = "s3";

MyClass::MyClass(const int a = 30, const char* const s = "abc"):a1(a),s1(s){
  cout << "a3 = " << a3 << endl;
  cout << "s3 = " << s3 << endl;
}

//MyClass.h

//MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

extern const int a2;
extern const char* const s2; 

class MyClass {
private:
  const int a1;
  const char* const s1;
public:
  MyClass(const int a, const char* const s);
};
#endif

5. 内联函数一定要写在头文件里:

inline函数的特征是在调用的地方插入相应函数的代码,所以编译之后的目标文件里是没有inline函数体的,因为在要调用的地方它都已经用相应的语句替换掉了(当然这只限于内联成功的情况)。

如果我们将inline函数写在cpp文件里,但是绝大多数情况下,在我们用第三方类库的时候,我们只有头文件和目标文件(没有cpp文件),当你调用那个内联函数时,编译器没办法找到它。所以说将inline函数写在cpp文件中是没什么用的

6. 最后附上一篇const, static等不同变量初始化的日志,引以为戒:

http://blog.csdn.net/gljseu/article/details/9750877

1、普通的变量:一般不考虑啥效率的情况下 可以在构造函数中进行赋值。考虑一下效率的可以再构造函数的初始化列表中进行。

class CA

{

public:

int data;

……

public:

CA();

……

};

CA::CA():data(0)//……#1……初始化列表方式

{

//data = 0;//……#1……赋值方式

};

2、static 静态变量:

static变量属于类所有,而不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有一个。在这种性质上理解,有点类似于全局变量的唯一性。

class CA

{

public:

static int sum;

……

public:

CA();

……

};

int CA::sum=0;//……#2……类外进行初始化

3、const 常量变量:

const常量需要在声明的时候即初始化。因此需要在变量创建的时候进行初始化。一般采用在构造函数的初始化列表中进行。

class CA

{

public:

const int max;

……

public:

CA();

……

};

CA::CA():max(100)

{

……

}

4、Reference 引用型变量:

引用型变量和const变量类似。需要在创建的时候即进行初始化。也是在初始化列表中进行。但需要注意用Reference类型。

class CA

{

public:

int init;

int& counter;

……

public:

CA();

……

};

CA::CA():counter(&init)

{

……

}

5、const static integral 变量:

对于既是const又是static 而且还是整形变量,C++是给予特权的(但是不同的编译器可能有会有不同的支持,VC
6好像就不支持)。可以直接在类的定义中初始化。short可以,但float的不可以哦。

// 例float类型只能在类外进行初始化

// const float CA::fmin = 3.14;

class CA

{

public:

//static const float fmin = 0.0;// only static const integral data members can be initialized within a class

const static int nmin = 0;

……

public:

……

};

总结起来,可以初始化的情况有如下四个地方:

1、在类的定义中进行的,只有const 且 static 且 integral 的变量。

2、在类的构造函数初始化列表中, 包括const对象和Reference对象。

3、在类的定义之外初始化的,包括static变量。因为它是属于类的唯一变量。

4、普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高。

类的定义体中只能初始化const integral data型的量。对于static型的量,那就放在.cpp文件中吧!当然了,还不能放在成员函数中(非静态成员函数可以使用静态数据成员的吧!
静态成员函数只能调用静态数据成员。),因为static量是类的,不是某个对象的。那样的话每个对象都来操作属于所有对象(类)的东西,岂不是会乱套,所以不能允许这种行为。

但是,static量可以在类的构造函数中赋值,当然是不可以放在初始化成员列表中的,可是在构造函数中赋值时不可以使用copy
construction,提示这样的错误

term does not evaluate to a function taking 1 arguments

那么,对于类里面的static函数的声明和定义是这样的:

static函数的声明可以像普通成员函数一样声明,只是在前面加上一个static关键字。

如:

private:

static int GetXYZ();

而在,定义时,像static变量那样,也是不可以加上static关键字,若写成:

static int A::GetXYZ()

{

…………

}

就会提示:

‘static‘ should not be used on member functions defined at file scope

所以应该写成是这样:

int A::GetXYZ()

{//他是static型函数的性质,就用其他方法来辨别吧,比如在这儿写上:this
is a static function

…………

}

至于static函数的使用,可以再你所编写的代码段中这样插入:

………………

A::GetXYZ(); //可以看出他是类的东东,不是对象的

………………

当然,对于public型的static量(假设叫CString
S_str),可以这样使用:

A::S_str = "Hello !";

CString str = A::S_str;

c++成员变量初始化问题 分类: c/c 小结 2009-11-03
17:19

C++为类中提供类成员的初始化列表

类对象的构造顺序是这样的:

1.分配内存,调用构造函数时,隐式/显示的初始化各数据成员

2.进入构造函数后在构造函数中执行一般计算

1.类里面的任何成员变量在定义时是不能初始化的。

2.一般的数据成员可以在构造函数中初始化。

3.const数据成员必须在构造函数的初始化列表中初始化。

4.static要在类的定义外面初始化。

5.数组成员是不能在初始化列表里初始化的。

6.不能给数组指定明显的初始化。

这6条一起,说明了一个问题:C++里面是不能定义常量数组的!因为3和5的矛盾。这个事情似乎说不过去啊?没有办法,我只好转而求助于静态数据成员。

到此,我的问题解决。但是我还想趁机复习一下C++类的初始化:

1.初始化列表:CSomeClass::CSomeClass() : x(0), y(1){}

2.类外初始化:int CSomeClass::myVar=3;

3.const常量定义必须初始化,C++类里面使用初始化列表;

4.C++类不能定义常量数组。

C++ 文件include规则 常量定义,布布扣,bubuko.com

时间: 2024-10-29 04:35:02

C++ 文件include规则 常量定义的相关文章

[ExtJS学习笔记]第六节 Extjs的类系统Class System命名规则及定义和调试

本文地址: http://blog.csdn.net/sushengmiyan/article/details/38479079 本文作者:sushengmiyan -------------------------------------------------------------资源链接----------------------------------------------------------------------- 翻译来源  Sencha Cmd官方网站:    http:

Class文件内容及常量池

当JVM运行Java程序的时候,它会加载对应的class文件,并提取class文件中的信息存放在JVM开辟出来的方法区内存中.那么这个class文件里面到底有些什么内容呢? 一.class文件内容概述 class文件是由8bits的字节流组成,全部字节构成了15个有意义的项目.这些项目之间没有任何无意义的字节,因此class文件非常紧凑.占据多字节空间的项目按照高位在前的顺序存放.下面我们详细讨论这些项目: ★ magic(魔数)    每个class文件的前4个字节称为魔数,值为0xCAFEB

C++预编译头文件(#include &quot;stdafx.h&quot;)

来源:http://blog.sina.com.cn/s/blog_4ac766c00100qsbd.html http://blog.csdn.net/txh0001/article/details/7031058 作为一个C++菜鸟,在预编译头文件(#include "stdafx.h")上纠结了很久,今天打算彻底弄明白它. 1.预编译头文件的概念 所谓的预编译头文件,其实我们很熟悉的,这里的头文件(Microsoft Visual C++中)一般的说就是我们常见的stdafx.h

Java常量定义需要注意事项及static作用(复习)

在任何开发语言中,都需要定义常量.在Java开发语言平台中也不例外.不过在Java常量定义的时候,跟其他语言有所不同.其有自己的特色.在这篇文章中,主要针对Java语言中定义常量的注意事项进行解析,帮助各位程序开发人员更好的掌握常量的定义与管理工作. 一.常量定义的基本注意事项 在Java语言中,主要是利用final关键字(在Java类中灵活使用Static关键字)来进行Java常量定义.当常量被设定后,一般情况下就不允许再进行更改.如可以利用如下的形式来定义一个常量:final double

引用头文件#include &lt;queue&gt;出错

在工程头文件中引用头文件 #include <queue> 莫名奇妙出错,其原因很可能是由于头文件引用问题. include/c++/4.7.1/bits/stl_vector.h:1308:40: error: expected unqualified-id before '(' token /lib/gcc/arm-linux-gnueabihf/4.7.1/../../../../arm-linux-gnueabihf/include/c++/4.7.1/vector:66:0,    

Class文件内容及常量池(转)

原文:http://hxraid.iteye.com/blog/687660 当JVM运行Java程序的时候,它会加载对应的class文件,并提取class文件中的信息存放在JVM开辟出来的方法区内存中.那么这个class文件里面到底有些什么内容呢? 一.class文件内容概述 class文件是由8bits的字节流组成,全部字节构成了15个有意义的项目.这些项目之间没有任何无意义的字节,因此class文件非常紧凑.占据多字节空间的项目按照高位在前的顺序存放.下面我们详细讨论这些项目: ★ mag

初级篇第十一期:学习使用常量定义和宏定义

学习建议:自己动手,丰衣足食 学习周期:1周 学习目的:熟练使用常量定义和宏定义 学习答疑:欢迎来技术群里提问并做分享 学习工具:Xcode开发环境 学习内容:熟悉项目开发中常用的两个定义 我们一般定义常量数字和字符串的时候一般会考虑用常量来定义   static CGFloat const kDefaultColorLayerOpacity = 0.4f; 一般定义宏的时候,都是用来定义方法,用宏的时候一定要多注意使用哦,会关系到括号的问题   #define SWH_RGBA(r, g, b

【Struts2学习笔记(4)】指定需要Struts 2处理的请求后缀和细说常量定义

一.指定需要Struts 2处理的请求后缀 前面我们都是默认使用.action后缀访问Action.其实默认后缀是可以通过常量"struts.action.extension"进行修改的,例如:我们可以配置Struts 2只处理以.do为后缀的请求路径: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software

php 常量定义

php常量定义及取值  常量在定义时赋值:  不能变 :不能销毁: 具有超全局作用于:常量只能储存标量数据(字符 整型 浮点 ): <?php define("hello","world");//变量名 hello 变量值 world echo hello; //变量输出 echo "<br/>"; $b = constant('hello');//使用函数 constant();取值echo $b; ?> 判断常量是否存