C++中定义自己的头文件

由多个文件组成的程序需要一种方法连接名字的使用和声明,在C++中是通过头文件实现的。头文件一般包括类的定义,extern 变量的声明和函数的声明。使用或定义这些实体的文件要包含适当的头文件。
头文件的正确使用有两个好处:保证所有文件使用给定实体的同一声明;当声明需要修改时,只有头文件需要更新。在定义头文件时要注意头文件中所做的声明在逻辑上应该是适于放在一起的,否则,编译头文件会花费较长时间。

使用头文件时需要注意以下几点:
1 头文件是用于声明而不是定义。当定义头文件时,记住定义和声明的区别是很重要的。定义只可以出现一次,而声明可以出现多次。因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义,否则会造成在同一个程序对同一个变量或函数的多次定义。下列语句是定义,所以不应该放在头文件中:
extern int ival = 10;
double ix;
对头文件不应该含有定义这一规则,有三个例外。头文件可以定义类,值在编译时就已经知道的const对象和inline函数。这些实体可以在多个源文件中定义,只要每个源文件中的定义是相同的。
在头文件中定义这些实体,是因为编译器需要它们的定义来产生代码。例如:为了产生能定义或使用类的对象的代码,编译器需要知道组成该类型的数据成员,同样还需要知道能够在这些对象上执行的操作。对于const对象,作以下说明:
除非特别说明,在全局作用域声明的const变量默认为是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。通过指定const变量为extern,就可以在整个程序中访问const对象。例如:
//在file1中定义变量ix
extern const int ix = 5;
//在file2中我们可以这样使用它
extern const int ix;
for (int index = 0; index != ix; ++index)
{...}
通 过对const变量进行为编译器可见的初始化式初始化后,const变量成为常量表达式。为了能够让const变量和它的初始化式在每个文件中都可见,一 般都把这样的const变量定义在头文件中,从而使包含该头文件的文件都可以使用相同的常量值。无论该const变量何时使用,编译器都可以看见其初始化 式。在C++中任何变量都只能定义一次,定义会分配存储空间,而所有对该变量的使用都关联到同一存储空间,因为const对象默认为定义它的文件的局部变量,所以把它们定义放在头文件中是合法的。
如果const变量不是用常量表达式初始化,那么它就不应该在头文件中定义,相反,它应该和其他变量一样,该const变量应该在一个源文件中定义并初始化,然后在头文件中为它添加extern声明,以使其能被多个文件共享。

2 预处理器的简单介绍
#include设施是C++预处理器的一部分,它只接受一个参数:头文件名。预处理器用指定的头文件的内容替代每个#include.

3 头文件保护符
头 文件经常#include其他头文件,头文件定义的实体经常使用其他头文件的设施,在同一源文件中还可能多次包含同一头文件。比如我们自己定义了一个 Query.h的头文件,该头文件中包含了string的头文件。同时,我们在编写源代码时也使用了string库,这样string 库就包含了两次:一次是通过程序本身直接包含,一次是通过包含Query头文件间接包含。
设计头文件时,应使其可以多次包含在同一源文件中,我们必须保证多次包含同一头文件不会引起该头文件中定义的类和对象被多次定义,使得头文件安全的通用做法,是使用预处理器定义头文件保护符来避免在已经见到头文件的情况下重新处理该头文件的内容。

4 定义预处理变量及头文件
为 了避免名字冲突,预处理变量经常用全大写字母表示。预处理变量有两种状态:已定义或未定义。定义预处理器变量和检测其状态所用的预处理指示不 同。#define指示接受一个名字并定义该名字为预处理变量。#ifndef指示检测指定的预处理器变量是否未定义。如果预处理器变量未定义,那么跟在 其后的所有指示都被处理,直到出现#endif。可以使用这种方法来预防多次包含同一头文件。
#ifndef  QUERY_H
#define QUERY_H
//定义query.h中的内容
#endif

条件指示
#ifndef  QUERY_H
测 试 QUERY_H预处理器变量是否未定义。如果 QUERY_H未定义,那么#ifndef测试成功,跟在#ifndef后面的所有行都被执行,直到发现#endif.相反,如果QUERY_H已定义, 那么#infdef指示测试为假,该指示和#endif指示之间的代码都被忽略。通常情况下,我们用定义在头文件中的实体(如类)来命名预处理器变量来避 免预处理器变量重名的问题。通过使用类名来组成头文件和预处理器变量的名字,可以使得很可能只有一个文件将会使用该预处理器变量。

5 使用自定义的头文件
#include指示接受以下两种形式:
#include<iostream>
#include"query.h"
如 果头文件名括在尖括号< >里,那么认为该头文件是标准头文件。编译器会在预定义的位置集查找该头文件,这些预定义的位置可以通过设置查找路径环境变量或者通过命令行选项来 修改。如果头文件名括在一对引号里,那么认为它是非系统头文件,非系统头文件的查找通常开始于源文件所在的路径。

posted @ 2012-04-13 14:10 章涛 阅读(622) | 评论 (0)编辑 收藏

ofstream和ifstream详细用法

ofstream是从内存到硬盘,ifstream是从硬盘到内存,其实所谓的流缓冲就是内存空间

ofstream是从内存到硬盘,ifstream是从硬盘到内存,其实所谓的流缓冲就是内存空间;

  在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:

  1、插入器(<<)

  向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<"Write Stdout"<<'\n';就表示把字符串"Write Stdout"和换行字符('\n')输出到标准输出流。

  2、析取器(>>)

  从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。

  在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h。下面就把此类的文件操作过程一一道来。

  一、打开文件

  在fstream类中,有一个成员函数open(),就是用来打开文件的,其原型是:

  void open(const char* filename,int mode,int access);参数:

  filename:  要打开的文件名

  mode:    要打开文件的方式

  access:   打开文件的属性

  打开文件的方式在类ios(是所有流式I/O类的基类)中定义,常用的值如下:

  ios::app:   以追加的方式打开文件

  ios::ate:   文件打开后定位到文件尾,ios:app就包含有此属性

  ios::binary: 以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文

  ios::in:    文件以输入方式打开(文件数据输入到内存)

  ios::out:   文件以输出方式打开(内存数据输出到文件)

  ios::nocreate: 不建立文件,所以文件不存在时打开失败

  ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败

  ios::trunc:  如果文件存在,把文件长度设为0

  可以用“或”把以上属性连接起来,如ios::out|ios::binary

  打开文件的属性取值是:

  0:普通文件,打开访问

  1:只读文件

  2:隐含文件

  4:系统文件

  可以用“或”或者“+”把以上属性连接起来,如3或1|2就是以只读和隐含属性打开文件。

  例如:以二进制输入方式打开文件c:\config.sys

  fstream file1;

  file1.open("c:\\config.sys",ios::binary|ios::in,0);

  如果open函数只有文件名一个参数,则是以读/写普通文件打开,即:

  file1.open("c:\\config.sys"); <=> file1.open("c:\\config.sys",ios::in|ios::out,0);

  另外,fstream还有和open()一样的构造函数,对于上例,在定义的时侯就可以打开文件了:

  fstream file1("c:\\config.sys");  特别提出的是,fstream有两个子类:ifstream(input file stream)和ofstream(outpu file stream),ifstream默认以输入方式打开文件,而ofstream默认以输出方式打开文件。

  ifstream file2("c:\\pdos.def");//以输入方式打开文件

  ofstream file3("c:\\x.123");//以输出方式打开文件  所以,在实际应用中,根据需要的不同,选择不同的类来定义:如果想以输入方式打开,就 用ifstream来定义;如果想以输出方式打开,就用ofstream来定义;如果想以输入/输出方式来打开,就用fstream来定义。

  二、关闭文件

  打开的文件使用完成后一定要关闭,fstream提供了成员函数close()来完成此操作,如:file1.close();就把file1相连的文件关闭。

  三、读写文件

  读写文件分为文本文件和二进制文件的读取,对于文本文件的读取比较简单,用插入器和析取器就可以了;而对于二进制的读取就要复杂些,下要就详细的介绍这两种方式

  1、文本文件的读写

  文本文件的读写很简单:用插入器(<<)向文件输出;用析取器(>>)从文件输入。假设file1是以输入方式打开,file2以输出打开。示例如下:

  file2<<"I Love You";//向文件写入字符串"I Love You"

  int i;

  file1>>i;//从文件输入一个整数值。

  这种方式还有一种简单的格式化能力,比如可以指定输出为16进制等等,具体的格式有以下一些

  操纵符 功能 输入/输出

  dec 格式化为十进制数值数据 输入和输出

  endl 输出一个换行符并刷新此流 输出

  ends 输出一个空字符 输出

  hex 格式化为十六进制数值数据 输入和输出

  oct 格式化为八进制数值数据 输入和输出

  setpxecision(int p) 设置浮点数的精度位数 输出

  比如要把123当作十六进制输出:file1<

  2、二进制文件的读写

  ①put()

  put()函数向流写入一个字符,其原型是ofstream &put(char ch),使用也比较简单,如file1.put('c');就是向流写一个字符'c'。

  ②get()

  get()函数比较灵活,有3种常用的重载形式:

  一种就是和put()对应的形式:ifstream &get(char &ch);功能是从流中读取一个字符,结果保存在引用ch中,如果到文件尾,返回空字符。如file2.get(x);表示从文件中读取一个字符,并把读取的字符保存在x中。

  另一种重载形式的原型是: int get();这种形式是从流中返回一个字符,如果到达文件尾,返回EOF,如x=file2.get();和上例功能是一样的。

  还有一种形式的原型是:ifstream &get(char *buf,int num,char delim='\n');这种形式把字符读入由 buf 指向的数组,直到读入了 num 个字符或遇到了由 delim 指定的字符,如果没使用 delim 这个参数,将使用缺省值换行符'\n'。例如:

  file2.get(str1,127,'A'); //从文件中读取字符到字符串str1,当遇到字符'A'或读取了127个字符时终止。

  ③读写数据块

  要读写二进制数据块,使用成员函数read()和write()成员函数,它们原型如下:

  read(unsigned char *buf,int num);

  write(const unsigned char *buf,int num);

  read()从文件中读取 num 个字符到 buf 指向的缓存中,如果在还未读入 num 个字符时就到了文件尾,可以用成员函数 int gcount();来取得实际读取的字符数;而 write() 从buf 指向的缓存写 num 个字符到文件中,值得注意的是缓存的类型是 unsigned char *,有时可能需要类型转换。

  例:

  unsigned char str1[]="I Love You";

  int n[5];

  ifstream in("xxx.xxx");

  ofstream out("yyy.yyy");

  out.write(str1,strlen(str1));//把字符串str1全部写到yyy.yyy中

  in.read((unsigned char*)n,sizeof(n));//从xxx.xxx中读取指定个整数,注意类型转换

  in.close();out.close(); 四、检测EOF

  成员函数eof()用来检测是否到达文件尾,如果到达文件尾返回非0值,否则返回0。原型是int eof();

  例:  if(in.eof()) ShowMessage("已经到达文件尾!");

  五、文件定位

  和C的文件操作方式不同的是,C++ I/O系统管理两个与一个文件相联系的指针。一个是读指针,它说明输入操作在文件中的位置;另一个是写指针,它下次写操作的位置。每次执行输入或输出时, 相应的指针自动变化。所以,C++的文件定位分为读位置和写位置的定位,对应的成员函数是seekg()和seekp()。seekg()是设置读位置, seekp是设置写位置。它们最通用的形式如下:

  istream &seekg(streamoff offset,seek_dir origin);

  ostream &seekp(streamoff offset,seek_dir origin);

  streamoff定义于 iostream.h 中,定义有偏移量 offset 所能取得的最大值,seek_dir 表示移动的基准位置,是一个有以下值的枚举:

  ios::beg:  文件开头

  ios::cur:  文件当前位置

  ios::end:  文件结尾

  这两个函数一般用于二进制文件,因为文本文件会因为系统对字符的解释而可能与预想的值不同。例:

  file1.seekg(1234,ios::cur); //把文件的读指针从当前位置向后移1234个字节

  file2.seekp(1234,ios::beg); //把文件的写指针从文件开头向后移1234个字节

posted @ 2012-04-12 13:34 章涛 阅读(288) | 评论 (0)编辑 收藏

在编程中学习很有用

不断实践才会学好一门语言,多用,不懂再看书

posted @ 2012-04-11 10:56 章涛 阅读(218) | 评论 (0)编辑 收藏

static用法小结

static关键字是C,   C++中都存在的关键字,   它主要有三种使用方式,   其中前两种只指在C语言中使用,   第三种在C++中使用(C,C++中具体细微操作不尽相同,   本文以C++为准).
(1)局部静态变量
(2)外部静态变量/函数
(3)静态数据成员/成员函数
下面就这三种使用方式及注意事项分别说明

一、局部静态变量
在C/C++中,   局部变量按照存储形式可分为三种auto,   static,   register
( <C语言程序设计(第二版)> 谭浩强,   第174-175页)
与auto类型(普通)局部变量相比,   static局部变量有三点不同
1.   存储空间分配不同
auto类型分配在栈上,   属于动态存储类别,   占动态存储区空间,   函数调用结束后自动释放,   而static分配在静态存储区,   在程序整个运行期间都不释放.   两者之间的作用域相同,   但生存期不同.
2.   static局部变量在所处模块在初次运行时进行初始化工作,   且只操作一次
3.   对于局部静态变量,   如果不赋初值,   编译期会自动赋初值0或空字符,   而auto类型的初值是不确定的.   (对于C++中的class对象例外,   class的对象实例如果不初始化,   则会自动调用默认构造函数,   不管是否是static类型)

特点:   static局部变量的”记忆性”与生存期的”全局性”
所谓”记忆性”是指在两次函数调用时,   在第二次调用进入时,   能保持第一次调用退出时的值.  
示例程序一
#include   <iostream>

using   namespace   std;

void   staticLocalVar()
{
  static   int   a   =   0;   //   运行期时初始化一次,   下次再调用时,   不进行初始化工作
  cout < < "a= " < <a < <endl;
  ++a;
}

int   main()
{
  staticLocalVar();   //   第一次调用,   输出a=0
  staticLocalVar();   //   第二次调用,   记忆了第一次退出时的值,   输出a=1
  return   0;
}

应用:
  利用”记忆性”,   记录函数调用的次数(示例程序一)
      利用生存期的”全局性”,   改善”return   a   pointer   /   reference   to   a   local   object”的问题.   Local   object的问题在于退出函数,   生存期即结束,.   利用static的作用,   延长变量的生存期.
示例程序二:
//   IP   address   to   string   format
//   Used   in   Ethernet   Frame   and   IP   Header   analysis
const   char   *   IpToStr(UINT32   IpAddr)
{
  static   char   strBuff[16];   //   static局部变量,   用于返回地址有效
  const   unsigned   char   *pChIP   =   (const   unsigned   char   *)&IpAddr;
  sprintf(strBuff,   "%u.%u.%u.%u ",     pChIP[0],   pChIP[1],   pChIP[2],   pChIP[3]);
  return   strBuff;
}

注意事项:
1.   “记忆性”,   程序运行很重要的一点就是可重复性,   而static变量的”记忆性”破坏了这种可重复性,   造成不同时刻至运行的结果可能不同.
2.   “生存期”全局性和唯一性.   普通的local变量的存储空间分配在stack上,   因此每次调用函数时,   分配的空间都可能不一样,   而static具有全局唯一性的特点,   每次调用时,   都指向同一块内存,   这就造成一个很重要的问题   ----   不可重入性!!!
这样在多线程程序设计或递归程序设计中,   要特别注意这个问题.
(不可重入性的例子可以参见 <effective   C++   (2nd)> (影印版)第103-105页)
下面针对示例程序二,   分析在多线程情况下的不安全性.(为方便描述,   标上行号)
①   const   char   *   IpToStr(UINT32   IpAddr)
②   {
③     static   char   strBuff[16];   //   static局部变量,   用于返回地址有效
④     const   unsigned   char   *pChIP   =   (const   unsigned   char   *)&IpAddr;
⑤     sprintf(strBuff,   "%u.%u.%u.%u ",     pChIP[0],   pChIP[1],   pChIP[2],   pChIP[3]);
⑥     return   strBuff;
⑦   }
假设现在有两个线程A,B运行期间都需要调用IpToStr()函数,   将32位的IP地址转换成点分10进制的字符串形式.   现A先获得执行机会,   执行IpToStr(),   传入的参数是0x0B090A0A,   顺序执行完应该返回的指针存储区内容是:”10.10.9.11”,   现执行到⑥时,   失去执行权,   调度到B线程执行,   B线程传入的参数是0xA8A8A8C0,   执行至⑦,   静态存储区的内容是192.168.168.168.   当再调度到A执行时,   从⑥继续执行,   由于strBuff的全局唯一性,   内容已经被B线程冲掉,   此时返回的将是192.168.168.168字符串,   不再是10.10.9.11字符串.

二、外部静态变量/函数
在C中static有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。,   但为了限制全局变量/函数的作用域,   函数或变量前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函 数)。注意此时,   对于外部(全局)变量,   不论是否有static限制,   它的存储区域都是在静态存储区,   生存期都是全局的.   此时的static只是起作用域限制作用,   限定作用域在本模块(文件)内部.
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
示例程序三:
 
//file1.cpp

static   int   varA;
int   varB;
extern   void   funA()
{
……
}

static   void   funB()
{
……
}

//file2.cpp

extern   int   varB;   //   使用file1.cpp中定义的全局变量
extern   int   varA;   //   错误!   varA是static类型,   无法在其他文件中使用
extern   vod   funA();   //   使用file1.cpp中定义的函数
extern   void   funB();   //   错误!   无法使用file1.cpp文件中static函数

 

三、静态数据成员/成员函数(C++特有)
C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数.   这是与普通成员函数的最大区别,   也是其应用所在,   比如在对某一个类的对象进行计数时,   计数生成多少个类的实例,   就可以用到静态数据成员.   在这里面,   static既不是限定作用域的,   也不是扩展生存期的作用,   而是指示变量/函数在此类中的唯一性.   这也是”属于一个类而不是属于此类的任何特定对象的变量和函数”的含义.   因为它是对整个类来说是唯一的,   因此不可能属于某一个实例对象的.   (针对静态数据成员而言,   成员函数不管是否是static,   在内存中只有一个副本,   普通成员函数调用时,   需要传入this指针,   static成员函数调用时,   没有this指针.   )
请看示例程序四( <effective   c++   (2nd)> (影印版)第59页)
class   EnemyTarget   {
public:
    EnemyTarget()   {   ++numTargets;   }
    EnemyTarget(const   EnemyTarget&)   {   ++numTargets;   }
    ~EnemyTarget()   {   --numTargets;   }
    static   size_t   numberOfTargets()   {   return   numTargets;   }
    bool   destroy();       //   returns   success   of   attempt   to   destroy   EnemyTarget   object
private:
    static   size_t   numTargets;                               //   object   counter
};
//   class   statics   must   be   defined   outside   the   class;
//   initialization   is   to   0   by   default
size_t   EnemyTarget::numTargets;

在这个例子中,   静态数据成员numTargets就是用来计数产生的对象个数的.
另外,   在设计类的多线程操作时,   由于POSIX库下的线程函数pthread_create()要求是全局的,   普通成员函数无法直接做为线程函数,   可以考虑用Static成员函数做线程函数.
转:
http://topic.csdn.net/t/20060414/11/4686455.html

posted @ 2012-04-11 09:44 章涛 阅读(157) | 评论 (0)编辑 收藏

仅列出标题
共3页: 1 2 3 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜