C++类,详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++类,详解相关的知识,希望对你有一定的参考价值。

详细一点。

推荐先看书系统学习下吧《C++程序设计》

一、C++类的定义
C++中使用关键字 class 来定义类, 其基本形式如下:
class 类名

public:
//行为或属性
protected:
//行为或属性

private:
//行为或属性
;

示例:
定义一个点(Point)类, 具有以下属性和方法:
■ 属性: x坐标, y坐标
■ 方法: 1.设置x,y的坐标值; 2.输出坐标的信息。
实现代码:
class Point

public:
void setPoint(int x, int y);
void printPoint();

private:
int xPos;
int yPos;
;
代码说明:
上段代码中定义了一个名为 Point 的类, 具有两个私密属性, int型的xPos和yPos, 分别用来表示x点和y点。
在方法上, setPoint 用来设置属性, 也就是 xPos 和 yPos 的值; printPoint 用来输出点的信息。

1 数据抽象和封装
抽象是通过特定的实例抽取共同特征以后形成概念的过程。一个对象是现实世界中一个实体的抽象,一个类是一组对象的抽象。
封装是将相关的概念组成一个单元,然后通过一个名称来引用它。面向对象封装是将数据和基于数据的操作封装成一个整体对象,对数据的访问或修改只能通过对象对外提供的接口进行。

2 类定义
几个重要名词:
(1) 类名
遵循一般的命名规则; 字母,数字和下划线组合,不要以数字开头。
(2) 类成员
类可以没有成员,也可以定义多个成员。成员可以是数据、函数或类型别名。所有的成员都必须在类的内部声明。
没有成员的类是空类,空类也占用空间。

class People

;
sizeof(People) = 1;
(3) 构造函数
构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。
(4) 成员函数
成员函数必须在类内部声明,可以在类内部定义,也可以在类外部定义。如果在类内部定义,就默认是内联函数。

3 类定义补充
3.1 可使用类型别名来简化类
除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。
使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
class People

public:
typedef std::string phonenum; //电话号码类型

phonenum phonePub; //公开号码
private:
phonenum phonePri;//私人号码
;

3.2 成员函数可被重载
可以有多个重载成员函数,个数不限。
3.3 内联函数
有三种:
(1)直接在类内部定义。
(2)在类内部声明,加上inline关键字,在类外部定义。
(3)在类内部声明,在类外部定义,同时加上inline关键字。注意:此种情况下,内联函数的定义通常应该放在类定义的同一头文件中,而不是在源文件中。这是为了保证内联函数的定义在调用该函数的每个源文件中是可见的。
3.4 访问限制
public,private,protected 为属性/方法限制的关键字。
3.5 类的数据成员中不能使用 auto、extern和register等进行修饰, 也不能在定义时进行初始化
如 int xPos = 0; //错;

例外:
静态常量整型(包括char,bool)数据成员可以直接在类的定义体中进行初始化,例如:
static const int ia= 30;

4 类声明与类定义
4.1 类声明(declare)
class Screen;
在声明之后,定义之前,只知道Screen是一个类名,但不知道包含哪些成员。只能以有限方式使用它,不能定义该类型的对象,只能用于定义指向该类型的指针或引用,声明(不是定义)使用该类型作为形参类型或返回类型的函数。
void Test1(Screen& a);
void Test1(Screen* a);
4.2 类定义(define)
在创建类的对象之前,必须完整的定义该类,而不只是声明类。所以,类不能具有自身类型的数据成员,但可以包含指向本类的指针或引用。
class LinkScreen

public:
Screen window;
LinkScreen* next;
LinkScreen* prev;
; //注意,分号不能丢
因为在类定义之后可以接一个对象定义列表,可类比内置类型,定义必须以分号结束:
class LinkScreen /* ... */ ;
class LinkScreen /* ... */ scr1,scr2;

5 类对象
定义类对象时,将为其分配存储空间。
Sales_item item; //编译器分配了足以容纳一个 Sales_item 对象的存储空间。item 指的就是那个存储空间。

6 隐含的 this 指针
成员函数具有一个附加的隐含形参,即 this指针,它由编译器隐含地定义。成员函数的函数体可以显式使用 this 指针。
6.1 何时使用 this 指针
当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用 this:该函数返回对调用该函数的对象的引用。
class Screen

...
public:
Screen& set(char);
;
Screen& Screen::set(char c)

contents[cursor] = c;
return *this;


7 类作用域
每个类都定义了自己的作用域和唯一的类型。
类的作用域包括:类的内部(花括号之内), 定义在类外部的成员函数的参数表(小括号之内)和函数体(花括号之内)。
class Screen

//类的内部
...
;
//类的外部
char Screen::get(index r, index c) const

index row = r * width; // compute the row location
return contents[row + c]; // offset by c to fetch specified character

注意:成员函数的返回类型不一定在类作用域中。可通过 类名::来判断是否是类的作用域,::之前不属于类的作用域,::之后属于类的作用域。例如

Screen:: 之前的返回类型就不在类的作用域,Screen:: 之后的函数名开始到函数体都是类的作用域。
class Screen

public:
typedef std::string::size_type index;
index get_cursor() const;
;
Screen::index Screen::get_cursor() const //注意:index前面的Screen不能少

return cursor;

该函数的返回类型是 index,这是在 Screen 类内部定义的一个类型名。在类作用域之外使用,必须用完全限定的类型名 Screen::index 来指定所需要的 index 是在类 Screen 中定义的名字。

二 构造函数
构造函数是特殊的成员函数,用来保证每个对象的数据成员具有合适的初始值。
构造函数名字与类名相同,不能指定返回类型(也不能定义返回类型为void),可以有0-n个形参。
在创建类的对象时,编译器就运行一个构造函数。

1 构造函数可以重载
可以为一个类声明的构造函数的数量没有限制,只要每个构造函数的形参表是唯一的。
class Sales_item;

public:
Sales_item(const std::string&);
Sales_item(std::istream&);
Sales_item(); //默认构造函数
;

2 构造函数自动执行
只要创建该类型的一个对象,编译器就运行一个构造函数:
Sales_item item1("0-201-54848-8");
Sales_item *p = new Sales_item();
第一种情况下,运行接受一个 string 实参的构造函数,来初始化变量item1。
第二种情况下,动态分配一个新的 Sales_item 对象,通过运行默认构造函数初始化该对象。

3 构造函数初始化式
与其他函数一样,构造函数具有名字、形参表和函数体。
与其他函数不同的是,构造函数可以包含一个构造函数初始化列表:
Sales_item::Sales_item(const string &book): isbn(book), units_sold(0), revenue(0.0)

构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。
构造函数可以定义在类的内部或外部。构造函数初始化只在构造函数的定义中指定。
构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。初始化列表属于初始化阶段(1),构造函数函数体中的所有语句属于计算阶段(2)。
初始化列表比构造函数体先执行。不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。
3.1 哪种类需要初始化式
const 对象或引用类型的对象,可以初始化,但不能对它们赋值,而且在开始执行构造函数的函数体之前要完成初始化。
初始化 const 或引用类型数据成员的唯一机会是构造函数初始化列表中,在构造函数函数体中对它们赋值不起作用。
没有默认构造函数的类类型的成员,以及 const 或引用类型的成员,必须在初始化列表中完成初始化。
class ConstRef

public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
;
ConstRef::ConstRef(int ii)

i = ii; // ok
ci = ii; // error
ri = i; //

应该这么初始化:
ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii)
3.2 成员初始化的次序
每个成员在构造函数初始化列表中只能指定一次。重复初始化,编译器一般会有提示。
成员被初始化的次序就是定义成员的次序,跟初始化列表中的顺序无关。
3.3 初始化式表达式
初始化式可以是任意表达式
Sales_item(const std::string &book, int cnt, double price): isbn(book), units_sold(cnt), revenue(cnt * price)
3.4 类类型的数据成员的初始化式
初始化类类型的成员时,要指定实参并传递给成员类型的一个构造函数,可以使用该类型的任意构造函数。
Sales_item(): isbn(10, '9'), units_sold(0), revenue(0.0)
3.5 类对象的数据成员的初始化
在类A的构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。
类类型的数据成员,运行该类型的默认构造函数来初始化。
内置或复合类型的成员的初始值依赖于该类对象的作用域:在局部作用域中不被初始化,在全局作用域中被初始化为0。假设有一个类A,
class A

public:
int ia;
B b;
;
A类对象A a;不管a在局部作用域还是全局作用域,b使用B类的默认构造函数来初始化,ia的初始化取决于a的作用域,a在局部作用域,ia不被初始化,a在全局作用域,ia初始化0。

4 默认构造函数
不含形参的构造函数就是默认构造函数。
只要定义一个对象时没有提供初始化式,就使用默认构造函数。如: A a;
为所有形参提供默认实参的构造函数也定义了默认构造函数。例如:

class A

public:
A(int a=1,char c ='')
private:
int ia;
char c1;
;
4.1 合成的默认构造函数
只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
一个类只要定义了一个构造函数,编译器也不会再生成默认构造函数。
建议:
如果定义了其他构造函数,也提供一个默认构造函数。
如果类包含内置或复合类型(如 int& 或 string*)的成员,它应该定义自己的构造函数来初始化这些成员。每个构造函数应该为每个内置或复合类型的成员提供初始化。

5 隐式类类型转换
5.1 只含单个形参的构造函数能够实现从形参类型到该类类型的一个隐式转换
class A

public:
A(int a)

ia =a;


bool EqualTo(const A& a)

return ia == a.ia;


private:
int ia;
;

A a(1);
bool bEq = false;
bEq = a.EqualTo(1);//参数为1,实现从int型到A的隐式转换

5.2抑制由构造函数定义的隐式转换
通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中使用构造函数:

class A

public:
explicit A(int a )

ia =a;


bool EqualTo(const A& a)

return ia == a.ia;


private:
int ia;
;
通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为 explicit。将构造函数设置为 explicit 可以避免错误。

三 复制控制
1 复制构造函数
1.1 几个要点
(1) 复制构造函数
复制构造函数是一种特殊构造函数,只有1个形参,该形参(常用 const &修饰)是对该类类型的引用。
class Peopel

public:
Peopel();//默认构造函数
Peopel(const Peopel&);//复制构造函数
~Peopel();//析构函数
;
当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。
Peopel a1; Peopel a2 = a1;
当将该类型的对象传递给函数或函数返回该类型的对象时,将隐式使用复制构造函数。
Peopel Func(Peopel b)...
(2)析构函数
析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。
析构函数可用于释放构造对象时或在对象的生命期中所获取的资源。
不管类是否定义了自己的析构函数,编译器都自动执行类中非 static 数据成员的析构函数。
(3) 复制控制
复制构造函数、赋值操作符和析构函数总称为复制控制。编译器自动实现这些操作,但类也可以定义自己的版本。
(4) 两种初始化形式
C++ 支持两种初始化形式:直接初始化和复制初始化。直接初始化将初始化式放在圆括号中,复制初始化使用 = 符号。
对于内置类型,例如int, double等,直接初始化和复制初始化没有区别。
对于类类型:直接初始化直接调用与实参匹配的构造函数;复制初始化先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。直接初始化比复制初始化更快。
(5)形参和返回值
当形参或返回值为类类型时,由该类的复制构造函数进行复制。
(6)初始化容器元素
复制构造函数可用于初始化顺序容器中的元素。例如:
vector<string> svec(5);
编译器首先使用 string 默认构造函数创建一个临时值,然后使用复制构造函数将临时值复制到 svec 的每个元素。
(7)构造函数与数组元素
如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素。
如果使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复制初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素:
Sales_item primer_eds[] = string("0-201-16487-6"),
string("0-201-54848-8"),
string("0-201-82470-1"),
Sales_item()
;

1.2 合成的复制构造函数
(1)合成的复制构造函数
如果没有定义复制构造函数,编译器就会为我们合成一个。
合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。
逐个成员初始化:合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。
例外:如果一个类具有数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。

1.3 定义自己的复制构造函数
(1) 只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义复制构造函数,也可以复制。
class Peopel

public:
std::string name;
unsigned int id;
unsigned int age;
std::string address;
;
(2) 有些类必须对复制对象时发生的事情加以控制。
例如,类有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定工作。这两种情况下,都必须定义自己的复制构造函数。
最好显式或隐式定义默认构造函数和复制构造函数。如果定义了复制构造函数,必须定义默认构造函数。

1.4 禁止复制
有些类需要完全禁止复制。例如,iostream 类就不允许复制。延伸:容器内元素不能为iostream
为了防止复制,类必须显式声明其复制构造函数为 private。

2 赋值操作符
与复制构造函数一样,如果类没有定义自己的赋值操作符,则编译器会合成一个。

(1)重载赋值操作符
Sales_item& operator=(const Sales_item &);
(2)合成赋值操作符
合成赋值操作符会逐个成员赋值:右操作数对象的每个成员赋值给左操作数对象的对应成员。除数组之外,每个成员用所属类型的常规方式进行赋值。对于数组,给每个数组元素赋值。
(3)复制和赋值常一起使用
一般而言,如果类需要复制构造函数,它也会需要赋值操作符。

3 析构函数
构造函数的用途之一是自动获取资源;与之相对的是,析构函数的用途之一是回收资源。除此之外,析构函数可以执行任意类设计者希望在该类对象的使用完毕之后执行的操作。
(1) 何时调用析构函数
撤销(销毁)类对象时会自动调用析构函数。
变量(类对象)在超出作用域时应该自动撤销(销毁)。
动态分配的对象(new A)只有在指向该对象的指针被删除时才撤销(销毁)。
撤销(销毁)一个容器(不管是标准库容器还是内置数组)时,也会运行容器中的类类型元素的析构函数(容器中的元素总是从后往前撤销)。
(2)何时编写显式析构函数
如果类需要定义析构函数,则它也需要定义赋值操作符和复制构造函数,这个规则常称为三法则:如果类需要析构函数,则需要所有这三个复制控制成员。
(3)合成析构函数
合成析构函数按对象创建时的逆序撤销每个非 static 成员,因此,它按成员在类中声明次序的逆序撤销成员。
对于每个类类型的成员,合成析构函数调用该成员的析构函数来撤销对象。
合成析构函数并不删除指针成员所指向的对象。 所以,如果有指针成员,一定要定义自己的析构函数来删除指针。

析构函数与复制构造函数或赋值操作符之间的一个重要区别:即使我们编写了自己的析构函数,合成析构函数仍然运行。

四 友元
友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。
友元可以出现在类定义的内部的任何地方。
友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。
建议:将友元声明成组地放在类定义的开始或结尾。

1 友元类
class Husband

public:
friend class Wife;
private:
double money;//钱是老公私有的,别人不能动,但老婆除外
;

class Wife

public:
void Consume(Husband& h)

h.money -= 10000;//老婆可以花老公的钱

;

Husband h;
Wife w;
w.Consume(h);

2 使其他类的成员函数成为友元
class Husband; //1.声明Husband

class Wife //2.定义Wife类

public:
void Consume(Husband& h);
;

class Husband //3.定义Husband类

public:
friend void Wife::Consume(Husband& h);//声明Consume函数。
private:
double money;//钱是老公私有的,别人不能动,但老婆除外
;

void Wife::Consume(Husband& h) //4.定义Consume函数。

h.money -= 10000;//老婆可以花老公的钱

注意类和函数的声明和定义的顺序:
(1)声明类Husband
(2)定义类Wife,声明Consume函数
(3)定义类Husband
(4)定义Consume函数。
参考技术A 好好看书。 参考技术B 详解什么

C++文件操作详解(ifstreamofstreamfstream)

转自:http://www.cnblogs.com/azraelly/archive/2012/04/14/2446914.html

C++ 通过以下几个类支持文件的输入输出:

  • ofstream: 写操作(输出)的文件类 (由ostream引申而来)
  • ifstream: 读操作(输入)的文件类(由istream引申而来)
  • fstream: 可同时读写操作的文件类 (由iostream引申而来)

 

打开文件(Open a file)

对这些类的一个对象所做的第一个操作通常就是将它和一个真正的文件联系起来,也就是说打开一个文件。被打开的文件在程序中由一个流对象(stream object)来表示 (这些类的一个实例) ,而对这个流对象所做的任何输入输出操作实际就是对该文件所做的操作。

要通过一个流对象打开一个文件,我们使用它的成员函数open():

void open (const char * filename, openmode mode);

这里filename 是一个字符串,代表要打开的文件名,mode 是以下标志符的一个组合:

ios::in

为输入(读)而打开文件

ios::out

为输出(写)而打开文件

ios::ate

初始位置:文件尾

ios::app

所有输出附加在文件末尾

ios::trunc

如果文件已存在则先删除该文件

ios::binary

二进制方式

这些标识符可以被组合使用,中间以”或”操作符(|)间隔。例如,如果我们想要以二进制方式打开文件"example.bin" 来写入一些数据,我们可以通过以下方式调用成员函数open()来实现:

ofstream file; file.open ("example.bin", ios::out | ios::app | ios::binary);

ofstream, ifstream 和 fstream所有这些类的成员函数open 都包含了一个默认打开文件的方式,这三个类的默认方式各不相同:

参数的默认方式

ofstream

ios::out | ios::trunc

ifstream

ios::in

fstream

ios::in | ios::out

只有当函数被调用时没有声明方式参数的情况下,默认值才会被采用。如果函数被调用时声明了任何参数,默认值将被完全改写,而不会与调用参数组合。

由于对类ofstream, ifstream 和 fstream 的对象所进行的第一个操作通常都是打开文件,这些类都有一个构造函数可以直接调用open 函数,并拥有同样的参数。这样,我们就可以通过以下方式进行与上面同样的定义对象和打开文件的操作:

ofstream file ("example.bin", ios::out | ios::app | ios::binary);

两种打开文件的方式都是正确的。

你可以通过调用成员函数is_open()来检查一个文件是否已经被顺利的打开了:

bool is_open();

它返回一个布尔(bool)值,为真(true)代表文件已经被顺利打开,假( false )则相反。

 

关闭文件(Closing a file)

当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。关闭文件需要调用成员函数close(),它负责将缓存中的数据排放出来并关闭文件。它的格式很简单:

void close ();

这个函数一旦被调用,原先的流对象(stream object)就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程(process)所有访问了。

为防止流对象被销毁时还联系着打开的文件,析构函数(destructor)将会自动调用关闭函数close。

 

文本文件(Text mode files)

类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。

一般来说,我们将使用这些类与同控制台(console)交互同样的成员函数(cin 和 cout)来进行输入输出。如下面的例题所示,我们使用重载的插入操作符<<:

// writing on a text file
      #include <fiostream.h>
     
      int main () {
          ofstream examplefile   ("example.txt");
          if (examplefile.is_open()) {
              examplefile <<   "This is a line.\\n";
              examplefile <<   "This is another line.\\n";
              examplefile.close();
          }
          return 0;
      }
   

file example.txt  

This is a line.  

This is another line.

从文件中读入数据也可以用与 cin的使用同样的方法:

    // reading a text file
      #include <iostream.h>
      #include <fstream.h>
      #include <stdlib.h>
     
      int main () {
          char buffer[256];
          ifstream examplefile   ("example.txt");
          if (! examplefile.is_open())
          { cout << "Error   opening file"; exit (1); }
          while (! examplefile.eof() ) {
              examplefile.getline   (buffer,100);
              cout << buffer   << endl;
          }
          return 0;
      }

This is a line.  

This is another line.

上面的例子读入一个文本文件的内容,然后将它打印到屏幕上。注意我们使用了一个新的成员函数叫做eof ,它是ifstream 从类 ios 中继承过来的,当到达文件末尾时返回true 。

 

状态标志符的验证(Verification of state flags)

除了eof()以外,还有一些验证流的状态的成员函数(所有都返回bool型返回值):

  • bad()

如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。

  • fail()

除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。

  • eof()

如果读文件到达文件末尾,返回true。

  • good()

这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。

要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。

 

获得和设置流指针(get and put stream pointers)

所有输入/输出流对象(i/o streams objects)都有至少一个流指针:

  • ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
  • ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
  • fstream, 类似 iostream, 同时继承了get 和 put

我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:

  • tellg() tellp()

这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).

  • seekg() seekp()

这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:

seekg ( pos_type position ); seekp ( pos_type position );

使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。

seekg ( off_type offset, seekdir direction ); seekp ( off_type offset, seekdir direction );

使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是:

ios::beg

从流开始位置计算的位移

ios::cur

从流指针当前位置开始计算的位移

ios::end

从流末尾处开始计算的位移

流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。

以下例子使用这些函数来获得一个二进制文件的大小:

     // obtaining file size
      #include <iostream.h>
      #include <fstream.h>
     
      const char * filename =   "example.txt";
     
      int main () {
          long l,m;
          ifstream file (filename,   ios::in|ios::binary);
          l = file.tellg();
          file.seekg (0, ios::end);
          m = file.tellg();
          file.close();
          cout << "size of   " << filename;
          cout << " is "   << (m-l) << " bytes.\\n";
          return 0;
      }

size of example.txt is 40   bytes.

 

二进制文件(Binary files)

在二进制文件中,使用<< 和>>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。

文件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。第一个函数 (write) 是ostream 的一个成员函数,都是被ofstream所继承。而read 是istream 的一个成员函数,被ifstream 所继承。类 fstream 的对象同时拥有这两个函数。它们的原型是:

write ( char * buffer, streamsize size ); read ( char * buffer, streamsize size );

这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。

      // reading binary file
      #include <iostream>
      #include <fstream.h>
     
      const char * filename =   "example.txt";
     
      int main () {
          char * buffer;
          long size;
          ifstream file (filename,   ios::in|ios::binary|ios::ate);
          size = file.tellg();
          file.seekg (0, ios::beg);
          buffer = new char [size];
          file.read (buffer, size);
          file.close();
         
          cout << "the   complete file is in a buffer";
         
          delete[] buffer;
          return 0;
      }
    

The complete file is in a   buffer

 

缓存和同步(Buffers and Synchronization)

当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。

当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:

  • 当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
  • 当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
  • 控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。
  • 明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。
  • 在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<<hex<<123;要把3.1415926以5位精度输出:file1<<setpxecision(5)<<3.1415926。

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 *,有时可能需要类型转换。

例:

1 unsigned char str1[]="I Love You"; 
2 int n[5]; 
3 ifstream in("xxx.xxx"); 
4 ofstream out("yyy.yyy"); 
5 out.write(str1,strlen(str1));//把字符串str1全部写到yyy.yyy中 
6 in.read((unsigned char*)n,sizeof(n));//从xxx.xxx中读取指定个整数,注意类型转换 
7 in.close();out.close();

四、检测EOF

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

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

五、文件定位

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

1 istream &seekg(streamoff offset,seek_dir origin); 
2 ostream &seekp(streamoff offset,seek_dir origin);

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

ios::beg: 文件开头

ios::cur: 文件当前位置

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

例:

1 file1.seekg(1234,ios::cur);//把文件的读指针从当前位置向后移1234个字节 
2 file2.seekp(1234,ios::beg);//把文件的写指针从文件开头向后移1234个字节

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个字节

 

使用ifstreamgetline读取文件内容[c++]

  1 假设有一个叫 data.txt 的文件, 它包含以下内容:
  2 
  3                         Fry: One Jillion dollars.
  4  [Everyone gasps.]
  5  Auctioneer: Sir, thats not a number.
  6 数据读取, 测试 。
  7 
  8 
  9 以下就是基于 data.txt 的数据读取操作:
 10 
 11 #include <iostream>
 12  #include <fstream>
 13  #include <string>
 14 
 15  using namespace std;
 16 
 17  //输出空行
 18 void OutPutAnEmptyLine()
 19  {
 20      cout<<"\\n";
 21  }
 22 
 23  //读取方式: 逐词读取, 词之间用空格区分
 24 //read data from the file, Word By Word
 25  //when used in this manner, we‘ll get space-delimited bits of text from the file
 26  //but all of the whitespace that separated words (including newlines) was lost. 
 27  void ReadDataFromFileWBW()
 28  {
 29      ifstream fin("data.txt");  
 30      string s;  
 31      while( fin >> s ) 
 32       {    
 33          cout << "Read from file: " << s << endl;  
 34      }
 35  }
 36 
 37  //读取方式: 逐行读取, 将行读入字符数组, 行之间用回车换行区分
 38 //If we were interested in preserving whitespace, 
 39  //we could read the file in Line-By-Line using the I/O getline() function.
 40  void ReadDataFromFileLBLIntoCharArray()
 41  {
 42      ifstream fin("data.txt"); 
 43      const int LINE_LENGTH = 100; 
 44      char str[LINE_LENGTH];  
 45      while( fin.getline(str,LINE_LENGTH) )
 46       {    
 47          cout << "Read from file: " << str << endl;
 48      }
 49  }
 50 
 51  //读取方式: 逐行读取, 将行读入字符串, 行之间用回车换行区分
 52 //If you want to avoid reading into character arrays, 
 53  //you can use the C++ string getline() function to read lines into strings
 54  void ReadDataFromFileLBLIntoString()
 55  {
 56      ifstream fin("data.txt");  
 57      string s;  
 58      while( getline(fin,s) )
 59       {    
 60          cout << "Read from file: " << s << endl; 
 61      }
 62  }
 63 
 64  //带错误检测的读取方式
 65 //Simply evaluating an I/O object in a boolean context will return false 
 66  //if any errors have occurred
 67  void ReadDataWithErrChecking()
 68  {
 69      string filename = "dataFUNNY.txt";  
 70      ifstream fin( filename.c_str());  
 71      if( !fin ) 
 72       {   
 73          cout << "Error opening " << filename << " for input" << endl;   
 74          exit(-1);  
 75      }
 76  }
 77 
 78  int main()
 79  {
 80      ReadDataFromFileWBW(); //逐词读入字符串 
 81      OutPutAnEmptyLine(); //输出空行
 82 
 83     ReadDataFromFileLBLIntoCharArray(); //逐词读入字符数组
 84     OutPutAnEmptyLine(); //输出空行
 85 
 86     ReadDataFromFileLBLIntoString(); //逐词读入字符串
 87     OutPutAnEmptyLine(); //输出空行
 88 
 89     ReadDataWithErrChecking(); //带检测的读取
 90     return 0;
 91  }
 92 
 93 
 94 输出结果为:
 95 Read from file: Fry:
 96 Read from file: One
 97 Read from file: Jillion
 98 Read from file: dollars.
 99 Read from file: [Everyone
100 Read from file: gasps.]
101 Read from file: Auctioneer:
102 Read from file: Sir,
103 Read from file: thats
104 Read from file: not
105 Read from file: a
106 Read from file: number.
107 Read from file: 数据读取,
108 Read from file: 测试
109 Read from file: 。 
110 
111 Read from file: Fry: One Jillion dollars.
112 Read from file: [Everyone gasps.]
113 Read from file: Auctioneer: Sir, thats not a number.
114 Read from file: 数据读取, 测试 。
115 
116 Read from file: Fry: One Jillion dollars.
117 Read from file: [Everyone gasps.]
118 Read from file: Auctioneer: Sir, thats not a number.
119 Read from file: 数据读取, 测试 。
120 
121 Error opening  dataFUNNY.txt for input
122 Press any key to continue

 

































































以上是关于C++类,详解的主要内容,如果未能解决你的问题,请参考以下文章

C++ 类(class) 详解

C++类基础部分详解

c++ 文件操作详解

C++友元详解

C++之继承详解

C++ 模板详解(转)