Lap trinh huong doi tuong C7

Màu nền
Font chữ
Font size
Chiều cao dòng

Chương 7

Các dòng tập tin (Stream)

C đã cung cấp một thư viện các hàm nhập xuất như printf, scanf, gets, getch(), puts, puch(), fprintf, fscanf, fopen, fwite, fread,... . Các hàm này làm việc khá hiệu quả nhưng không thích ứng với cách tổ chức chương trình hướng đối tượng.

C++ sử dụng khái niệm dòng tin (stream) và đưa ra các lớp dòng tin để tổ chức việc nhập xuất. Dòng tin có thể xem như một dẫy các byte. Thao tác nhập là lấy (đọc) các byte từ dòng tin (khi đó gọi là dòng nhập - input) vào bộ nhớ. Thao tác xuất là đưa các byte từ bộ nhớ ra dòng tin (khi đó gọi là dong xuất - output). Các thao tác này là độc lập thiết bị. Để thực hiện việc nhập, xuất lên một thiết bị cụ thể, chúng ta chỉ cần gắn dòng tin với thiết bị này.

§ 1. các lớp stream

Có 4 lớp quan trọng cần nhớ là:

+ Lớp cơ sở ios

+ Từ lớp ios dẫn xuất đến 2 lớp istream và ostream

+ Hai lớp istream và ostream lại dẫn xuất tới lớp iostream

Sơ đồ kế thừa giữa các lớp như sau:

istream ostream

iostream

Lớp ios

+ Thuộc tính của lớp: Trong lớp ios định nghĩa các thuộc tính được sử dụng làm các cờ định dạng cho việc nhập xuất và các cờ kiểm tra lỗi (xem bên dưới).

+ Các phương thức: Lớp ios cung cấp một số phương thức phục vụ việc định dạng dữ liệu nhập xuất, kiểm tra lỗi (xem bên dưới).

Lớp istream

Lớp này cung cấp toán tử nhập >> và nhiều phương thức nhập khác (xem bên dưới) như các phương thức: get, getline, read, ignore, peek, seekg, tellg,...

Lớp ostream

Lớp này cung cấp toán tử xuất << và nhiều phương thức xuất khác (xem bên dưới) như các phương thức: put, write, flush, seekp, tellp,...

Lớp iostream

Lớp này thừa kế các phương thức nhập xuất của các lớp istream và ostream.

§ 2. Dòng cin và toán tử nhập

Dòng cin là một đối tượng kiểu istream đã định nghĩa trong C++ . Đó là dòng vào (input) chuẩn gắn với bàn phím (tương tự như stdin của C). Các thao tác nhập trên dòng cin đồng nghĩa với nhập dữ liệu từ bàn phím.

Do cin là một đối tượng của lớp istream nên với cin chung ta có thể sử dụng toán tử nhập >> và các phương thức nhập của các lớp ios và istream.

Cách dùng toán tử nhập để đọc dữ liệu từ dòng cin như sau:

cin >> Tham_số ;

Trong đó Tham_số có thể là:

- Biến hoặc phần tử mảng nguyên để nhận một số nguyên

- Biến hoặc phần tử mảng thực để nhận một số thực

- Biến hoặc phần tử mảng ký tự để nhận một ký tự

- Con trỏ ký tự để nhận một dẫy các ký tự khác trống

Chú ý: Các toán tử nhập có thể viết nối đuôi để nhập nhiều giá trị trên một dòng lệnh như sau:

cin >> Tham_số_1 >> Tham_số_2 >> ... >> Tham_số_k ;

Cách thức nhập như sau: Bỏ qua các ký tự trắng (dấu cách, dấu tab, dấu chuyển dòng) đứng trước nếu có và sau đó đọc vào các ký tự tương ứng với kiểu yêu cầu. Cụ thể đối với từng kiểu như sau:

Khi nhập số nguyên sẽ bỏ qua các ký tự trắng đứng trước nếu có, sau đó bắt đầu nhận các ký tự biểu thị số nguyên. Việc nhập kết thúc khi gặp một ký tự trắng hoặc một ký tự không thể hiểu là thành phần của số nguyên. Ví dụ nếu trên dòng vào (gõ từ bàn phím) chứa các ký tự <space><space>123X2 và Tham_số (bên phải cin) là biến nguyên n thì n sẽ nhận giá trị 123. Con trỏ nhập sẽ dừng tại ký tự X.

Phép nhập một số thực cũng tiến hành tương tự: Bỏ qua các khoảng trắng đứng trước nếu có, sau đó bắt đầu nhận các ký tự biểu thị số Thực. Việc nhập kết thúc khi gặp một ký tự trắng hoặc một ký tự không thể hiểu là thành phần của số thực.

Phép nhập một ký tự cũng vậy: Bỏ qua các khoảng trắng đứng trước nếu có, sau đó nhận một ký tự khác ký tự trắng. Ví dụ nếu gõ <space><space>XY thì ký tự X được nhận và con trỏ nhập dừng tại ký tự Y.

Phép nhập một dẫy ký tự: Bỏ qua các khoảng trắng đứng trước nếu có, sau đó bắt đầu nhận từ một ký tự khác ký tự trắng. Việc nhập kết thúc khi gặp một ký tự trắng.

Ví dụ 1: Xét đoạn chương trình:

char ten[10], que[12];

char ch;

float x;

cin >> n >> x >> ch >> ten >> que ;

Nếu gõ các ký tự:

123<s>3.14<s><s>ZHONG<s>HAI<s>PHONG<Enter>

(để cho gọn sẽ ký hiệu <s> là <space>)

thì kết quả nhập như sau:

n=123

x=3.14

ch='Z'

ten="HONG"

que = "HAI"

Con trỏ nhập sẽ dừng tại ký tự <space> trước từ PHONG. Các ký tự còn lại sẽ được nhận trong các câu lệnh nhập tiếp theo.

Ví dụ 2: Xét đoạn chương trình:

float y;

cin >> m >> y;

Nếu gõ:

<s><s>456<s><s>4.5<Enter>

thì kết quả nhập là:

m = 456

y = 4.5

Ký tự <Enter> vẫn còn lại trên dòng nhập.

§ 3. Nhập ký tự và chuỗi ký tự từ bàn phím

Chúng ta nhận thấy toán tử nhập >> chỉ tiện lợi khi dùng để nhập các giá trị số (nguyên, thực). Để nhập ký tự và chuỗi ký tự nên dùng các phương thức sau (định nghĩa trong lớp istream):

cin.get cin.getline cin.ignore

3.1. Phương thức get có 3 dạng (thực chất có 3 phương thức cùng có tên get):

Dạng 1:

int cin.get() ;

dùng để đọc một ký tự (kể cả khoảng trắng). Cách thức đọc của cin.get có thể minh hoạ qua ví dụ sau: Xét các câu lệnh

char ch;

ch = cin.get()

+ Nếu gõ

ABC<Enter>

thì biến ch nhận mã ký tự A, các ký tự BC<Enter> còn lại trên dòng vào.

+ Nếu gõ

A<Enter>

thì biến ch nhận mã ký tự A, ký tự <Enter> còn lại trên dòng vào.

+ Nếu gõ

<Enter>

thì biến ch nhận mã ký tự <Enter> (bằng 10) và dòng vào rỗng.

Dạng 2:

istream& cin.get(char &ch) ;

dùng để đọc một ký tự (kể cả khoảng trắng) và đặt vào một biến kiểu char được tham chiếu bởi ch.

Chú ý:

+ Cách thức đọc của cin.get dạng 2 cũng giống như dạng 1

+ Do cin.get() dạng 2 trả về tham chiếu tới cin, nên có thể sử dụng các phương thức get() dạng 2 nối đuôi nhau. Ví dụ 2 nếu khai báo

char ch1, ch2;

thì 2 câu lệnh:

cin.get(ch1);

cin.get(ch2);

có thể viết chung trên một câu lệnh sau:

cin.get(ch1).get(ch2);

Dạng 3:

istream& cin.get(char *str, int n, char delim = '

');

dùng để đọc một dẫy ký tự (kể cả khoảng trắng) và đưa vào vùng nhớ do str trỏ tới. Quá trình đọc kết thúc khi xẩy ra một trong 2 tình huống sau:

+ Gặp ký tự giới hạn (cho trong delim). Ký tự giới hạn mặc định là '

' (Enter)

+ Đã nhận đủ (n-1) ký tự

Chú ý:

+ Ký tự kết thúc chuỗi '\0' được bổ sung vào dẫy ký tự nhận được

+ ký tự giới hạn vẫn còn lại trên dòng nhập để dành cho các lệnh nhập tiếp theo.

Chú ý:

+ Cũng giống như get() dạng 2, có thể viết các phương thức get() dạng 3 nối đuôi nhau trên một dòng lệnh.

+ Ký tự <Enter> còn lại trên dòng nhập có thể làm trôi phương thức get() dạng 3. Ví dụ xét đoạn chương trình:

char ht[25], qq[20], cq[30];

cout << "

Họ tên: " ;

cin.get(ht,25);

cout << "

Quê quán: " ;

cin.get(qq,20);

cout << "

Cơ quan: " ;

cin.get(cq,30);

cout <<"

" <<ht<<" "<<qq<<" "<<cq

Đoạn chương trình dùng để nhập họ tên, quê quán và cơ quan. Nếu gõ:

Pham Thu Huong<Enter>

thì câu lệnh get đầu tiên sẽ nhận được chuỗi "Pham Thu Huong" cất vào mảng ht. Ký tự <Enter> còn lại sẽ làm trôi 2 câu lệnh get tiếp theo. Do đó câu lệnh cuối cùng sẽ chỉ in ra Pham Thu Huong.

Để khắc phục tình trạng trên, có thể dùng một trong các cách sau:

+ Dùng phương thức get() dạng 1 hoặc dạng 2 để lấy ra ký tự <Enter> trên dòng nhập trước khi dùng get (dạng 3).

+ Dùng phương thức ignore để lấy ra một số ký tự không cần thiết trên dòng nhập trước khi dùng get dạng 3. Phương thức này viết như sau:

cin.ignore(n) ; // Lấy ra (loại ra hay bỏ qua) n ký tự trên

// dòng nhập.

Như vậy để có thể nhập được cả quê quán và cơ quan, cần sửa lại đoạn chương trình trên như sau:

char ht[25], qq[20], cq[30];

cout << "

Họ tên: " ;

cin.get(ht,25);

cin.get(); // Nhận <Enter>

cout << "

Quê quán: " ;

cin.get(qq,20);

ignore(1); // Bỏ qua <Enter>

cout << "

Cơ quan: " ;

cin.get(cq,30);

cout <<"

" <<ht<<" "<<qq<<" "<<cq

3.2. Phương thức getline

Tương tự như get dạng 3, có thể dùng getline để nhập một dẫy ký tự từ bàn phím. Phương thức này được mô tả như sau:

istream& cin.getline(char *str, int n, char delim = '

');

Phương thức đầu tiên làm việc như get dạng 3, sau đó nó loại <Enter> ra khỏi dòng nhập (ký tự <Enter> không đưa vào dẫy ký tự nhận được). Như vậy có thể dùng getline để nhập nhiều chuối ký tự (mà không lo ngại các câu lệnh nhập tiếp theo bị trôi).

Ví dụ đoạn chương trình nhập họ tên, quê quán và cơ quan bên trên có thể viết như sau (bằng cách dùng getline):

char ht[25], qq[20], cq[30];

cout << "

Họ tên: " ;

cin.getline(ht,25);

cout << "

Quê quán: " ;

cin.getline(qq,20);

cout << "

Cơ quan: " ;

cin.get(cq,30);

cout <<"

" <<ht<<" "<<qq<<" "<<cq

Chú ý: Cũng giống như get() dạng 2 và get() dạng 3, có thể viết các phương thức getline() nối đuôi nhau trên một dòng lệnh. Ví dụ đoạn chương trình trên có thể viết lại như sau:

char ht[25], qq[20], cq[30];

cout << "

Họ tên, Quê quán và Cơ quan: " ;

cin.getline(ht,25).getline(qq,20).get(cq,30);

cout <<"

" <<ht<<" "<<qq<<" "<<cq

3.3. Phương thức ignore

Phương thức ignore dùng để bỏ qua (loại bỏ) một số ký tự trên dòng nhập. Trong nhiều trường hợp, đây là việc làm cần thiết để không làm ảnh hưởng đến các phép nhập tiếp theo.

Phương thức ignore được mô tả như sau:

istream& cin.ignore(int n=1);

Phương thức sẽ bỏ qua (loại bỏ) n ký tự trên dòng nhập.

3.4. Nhập đồng thời giá trị số và ký tự

Như đã nói trong §2, toán tử nhập >> bao giờ cũng để lại ký tự <Enter> trên dòng nhập. Ký tự <Enter> này sẽ làm trôi các lệnh nhập ký tự hoặc chuỗi ký tự bên dưới. Do vậy cần dùng:

hoặc ignore()

hoặc get() dạng 1

hoặc get() dạng 2

để loại bỏ ký tự <Enter> còn sót lại ra khỏi dòng nhập trước khi thực hiện việc nhập ký tự hoặc chuỗi ký tự.

3.5. Ví dụ: Chương trình dưới đây sử dụng lớp TSINH (Thí sinh) với 2 phương thức xuat và nhap.

//CT7_04.CPP

// Nhập dữ liêu số và ký tự

#include <iostream.h>

#include <conio.h>

struct TS

{

int sobd;

char ht[25];

float dt,dl,dh,td;

} ;

class TSINH

{

private:

TS *ts;

public:

TSINH()

{

ts=NULL;

sots=0;

}

TSINH(int n)

{

ts=new TS[n+1];

sots=n;

}

~TSINH()

{

if (sots)

{

sots=0;

ts = NULL;

}

}

void nhap();

void xuat();

} ;

void TSINH::nhap()

{

if (sots)

for (int i=1; i<=sots; ++i)

{

cout << "

Thi sinh "<< i << ": " ;

cout << "

So bao danh: " ;

cin >> ts[i].sobd;

cin.ignore();

cout << "Ho ten: " ;

cin.get(ts[i].ht,25);

cout << "Diem toan, ly , hoa: " ;

cin >> ts[i].dt >> ts[i].dl >> ts[i].dh;

ts[i].td = ts[i].dt + ts[i].dl + ts[i].dh;

}

}

void TSINH::xuat()

{

if (sots)

{

cout << "

Danh sach thi sinh:" ;

for (int i=1; i<=sots; ++i)

cout << "

Ho ten: " << ts[i].ht << " So BD: "<< ts[i].sobd

<<" Tong diem: "<< ts[i].td;

}

}

void main()

{

clrscr();

cout << "

So thi sinh: ";

cin>>n;

TSINH *t = new TSINH(n);

t->nhap() ;

t->xuat();

getch();

delete t;

}

§ 4. Dòng cout và toán tử xuất

4.1. Dòng cout

Dòng cout là một đối tượng kiểu ostream đã định nghĩa trong C++. Đó là dòng xuất (output) chuẩn gắn với màn hình (tương tự như stdout của C). Các thao tác xuất trên dòng cout đồng nghĩa với xuất dữ liệu ra màn hình.

Do cout là một đối tượng của lớp ostream nên với cout chung ta có thể sử dụng toán tử xuất << và các phương thức xuất của các lớp ios và ostream.

4.2.Toán tử xuất

C++ định nghĩa chồng toán tử dịch trái << để gửi các ký tự ra dòng xuất.

Cách dùng toán tử xuất để xuất dữ liệu từ bộ nhớ ra dòng cout như sau:

cout << Tham_số ;

Trong đó Tham_số biểu thị một giá trị cần xuất ra màn hình. Giá trị sẽ được biến đổi thành một dẫy ký tự trước khi đưa ra dòng xuất. Kiểu của Tham_số có thể như sau:

- Nguyên (xuất giá trị nguyên)

- Thực (xuất giá trị thực)

- ký tự - char (xuất một ký tự)

- con trỏ ký tự - char* (xuất chuỗi ký tự)

Chú ý: Các toán tử xuất có thể viết nối đuôi nhau (để xuất nhiều giá trị) trên một dòng lệnh như sau:

cout << Tham_số_1 << Tham_số_2 << ... << Tham_số_k ;

Chú ý: Toán tử xuất được định nghĩa chồng (trùng tên) với toán tử dịch trái và nó cùng có mức độ ưu tiên như toán tử dịch trái. Xem phụ lục 1 chúng ta thấy toán tử xuất có thứ tự ưu tiên lớn hơn các toán tử trong biểu thức điều kiện. Vì vậy nếu dùng toán tử xuất để in một biểu thức điều kiện như sau:

int a=5, b=10;

cout << "

Max= " << a>b?a:b ;

thì Trình biên dịch sẽ báo lỗi. Để tránh lỗi cần dùng các dấu ngoặc tròn để bao biểu thức điều kiện như sau:

int a=5, b=10;

cout << "

Max= " << (a>b?a:b) ;

Tóm lại: Nên bao các biểu thức trong 2 dấu ngoặc tròn.

4.3. Định dạng (tạo khuôn dạng cho) dữ liệu xuất

Việc định dạng dữ liệu xuất hay tạo khuôn dạng cho dữ liệu xuất là một việc cần thiết. Ví dụ cần in các giá trị thực trên 10 vị trí trong đó có 2 vị trí dành cho phần phân.

Bản thân toán tử xuất chưa có khả năng định dạng, mà cần sử dụng các công cụ sau:

+ Các phương thức định dạng

+ Các các cờ định dạng

+ Các hàm và bộ phận định dạng

Mục sau sẽ trình bầy cách định dạng giá trị xuất.

§ 5. Các phương thức định dạng

5.1. Nội dung định dạng giá trị xuất

Nội dung định dạng là xác định các thông số:

- Độ rộng quy định

- Độ chính xác

- Ký tự độn

- Và các thông số khác

+ Độ rộng thực tế của giá trị xuất: Như đã nói ở trên, C++ sẽ biến đổi giá trị cần xuất thành một chuỗi ký tự rồi đưa chuỗi này ra màn hình. Ta sẽ gọi số ký tự của chuỗi này là độ rộng thực tế của giá trị xuất. Ví dụ với các câu lệnh:

int n=4567, m=-23 ;

float x = -3.1416 ;

char ht[] = "Tran Van Thong" ;

thì:

Độ rộng thực tế của n là 4, của m là 3, của x là 7, của ht là 14.

+ Độ rộng quy đinh là số vị trí tối thiểu trên màn hình dành để in giá trị. Theo mặc định, độ rộng quy định bằng 0. Chúng ta có thể dùng phương thức cout.width() để thiết lập rộng này. Ví dụ câu lệnh:

cout.width(8);

sẽ thiết lập độ rộng quy định là 8.

+ Mối quan hệ giữa độ rộng thực tế và độ rộng quy định

- Nếu độ rộng thực tế lớn hơn hoặc bằng độ rộng quy định thì số vị trí trên màn hình chứa giá trị xuất sẽ bằng độ rộng thực tế.

- Nếu độ rộng thực tế nhỏ hơn độ rộng quy định thì số vị trí trên màn hình chứa giá trị xuất sẽ bằng độ rộng quy định. Khi đó sẽ có một số vị trí dư thừa. Các vị trí dư thừa sẽ được độn (lấp đầy) bằng khoảng trống.

+ Xác định ký tự độn: Ký tự độn mặc định là dấu cách (khoảng trống). Tuy nhiên có thể dùng phương thức cout.fill() để chọn một ký tự độn khác. Ví dụ với các câu lệnh sau:

int n=123; // Độ rộng thực tế là 3

cout.fill('*'); // Ký tự độn là *

cout.width(5); // Độ rộng quy định là 5

cout << n ;

thì kết quả in ra là:

**123

+ Độ chính xác là số vị trí dành cho phần phân (khi in số thực). Độ chính xác mặc định là 6. Tuy nhiên có thể dùng phương thức cout.precision() để chọn độ chính xác. Ví dụ với các câu lệnh:

float x = 34.455 ; // Độ rộng thực tế 6

cout.precision(2) ; // Độ chính xác 2

cout.width(8); // Độ rộng quy ước 8

cout.fill('0') ; // Ký tự độn là số 0

cout << x ;

thì kết quả in ra là:

0034.46

5.2. Các phương thức định dạng

1. Phương thức

int cout.width()

cho biết độ rộng quy định hiện tại.

2. Phương thức

int cout.width(int n)

Thiết lập độ rộng quy định mới là n và trả về độ rộng quy định trước đó.

Chú ý: Độ rộng quy định n chỉ có tác dụng cho một giá trị xuất. Sau đó C++ lại áp dụng độ rộng quy định bằng 0.

Ví dụ với các câu lệnh:

int m=1234, n=56;

cout << "

AB"

cout.width(6);

cout << m ;

cout << n ;

thì kết quả in ra là:

AB 123456

(giữa B và số 1 có 2 dấu cách).

3. Phương thức

int cout.precision()

Cho biết độ chính xác hiện tại (đang áp dụng để xuất các giá trị thức).

4. Phương thức

int cout.precision(int n)

Thiết lập độ chính xác sẽ áp dụng là n và cho biết độ chính xác trước đó. Độ chính xác được thiết lập sẽ có hiệu lực cho tới khi gặp một câu lệnh thiết lập độ chính xác mới.

5. Phương thức

char cout.fill()

Cho biết ký tự độn hiện tại đang được áp dụng.

6. Phương thức

char cout.fill(char ch)

Quy định ký tự độn mới sẽ được dùng là ch và cho biết ký tự độn đang dùng trước đó. Ký tự độn được thiết lập sẽ có hiệu lực cho tới khi gặp một câu lệnh chọn ký tự độn mới.

Ví dụ xét chương trình:

//CT7_06.CPP

// Cac phuong thuc dinh dang

#include <iostream.h>

#include <conio.h>

void main()

{

clrscr();

float x=-3.1551, y=-23.45421;

cout.precision(2);

cout.fill('*');

cout << "

" ;

cout.width(8);

cout << x;

cout << "

" ;

cout.width(8);

cout << y;

getch();

}

Sau khi thực hiện, chương trình in ra màn hình 2 dòng sau:

***-3.16

**-23.45

§ 6. Cờ định dạng

6.1. Khái niệm chung về cờ

Mỗi cờ chứa trong một bit. Cờ có 2 trạng thái:

Bật (on) - có giá trị 1

Tắt (off) - có giá trị 0

(Trong 6.3 sẽ trình bầy các phương thức dùng để bật, tắt các cờ)

Các cờ có thể chứa trong một biến kiểu long. Trong tệp <iostream.h> đã định nghĩa các cờ sau:

ios::left ios::right ios::internal

ios::dec ios::oct ios::hex

ios::fixed ios::scientific ios::showpos

ios::uppercase ios::showpoint ios::showbase

6.2. Công dụng của các cờ

Có thể chia các cờ thành các nhóm:

Nhóm 1 gồm các cờ định vị (căn lề) :

ios::left ios::right ios::internal

Cờ ios::left: Khi bật cờ ios:left thì giá trị in ra nằm bên trái vùng quy định, các ký tự độn nằm sau, ví dụ:

35***

-89**

Cờ ios::right: Khi bật cờ ios:right thì giá trị in ra nằm bên phải vùng quy định, các ký tự độn nằm trước, ví dụ:

***35

**-89

Chú ý: Mặc định cờ ios::right bật.

Cờ ios::internal: Cờ ios:internal có tác dụng giống như cờ ios::right chỉ khác là dấu (nếu có) in đầu tiên, ví dụ:

***35

-**89

Chương trình sau minh hoạ cách dùng các cờ định vị:

//CT7_06.CPP

// Cac phuong thuc dinh dang

// Co dinh vi

#include <iostream.h>

#include <conio.h>

void main()

{

clrscr();

float x=-87.1551, y=23.45421;

cout.precision(2);

cout.fill('*');

cout.setf(ios::left); // Bật cờ ios::left

cout << "

" ;

cout.width(8);

cout << x;

cout << "

" ;

cout.width(8);

cout << y;

cout.setf(ios::right); // Bật cờ ios::right

cout << "

" ;

cout.width(8);

cout << x;

cout << "

" ;

cout.width(8);

cout << y;

cout.setf(ios::internal); // // Bật cờ ios::internal

cout << "

" ;

cout.width(8);

cout << x;

cout << "

" ;

cout.width(8);

cout << y;

getch();

}

Sau khi thực hiện chương trình in ra 6 dòng như sau:

-87.16**

23.45***

**-87.16

***23.45

-**87.16

***23.45

Nhóm 2 gồm các cờ định dạng số nguyên:

ios::dec ios::oct ios::hex

+ Khi ios::dec bật (mặc định): Số nguyên được in dưới dạng cơ số 10

+ Khi ios::oct bật : Số nguyên được in dưới dạng cơ số 8

+ Khi ios::hex bật : Số nguyên được in dưới dạng cơ số 16

Nhóm 3 gồm các cờ định dạng số thực:

ios::fĩxed ios::scientific ios::showpoint

Mặc định: Cờ ios::fixed bật (on) và cờ ios::showpoint tắt (off).

+ Khi ios::fixed bật và cờ ios::showpoint tắt thì số thực in ra dưới dạng thập phân, số chữ số phần phân (sau dấu chấm) được tính bằng độ chính xác n nhưng khi in thì bỏ đi các chữ số 0 ở cuối.

Ví dụ nếu độ chính xác n = 4 thì:

Số thực -87.1500 được in: -87.15

Số thực 23.45425 được in: 23.4543

Số thực 678.0 được in: 678

+ Khi ios::fixed bật và cờ ios::showpoint bật thì số thực in ra dưới dạng thập phân, số chữ số phần phân (sau dấu chấm) được in ra đúng bằng độ chính xác n.

Ví dụ nếu độ chính xác n = 4 thì:

Số thực -87.1500 được in: -87.1500

Số thực 23.45425 được in: 23.4543

Số thực 678.0 được in: 678.0000

+ Khi ios::scientific bật và cờ ios::showpoint tắt thì số thực in ra dưới dạng mũ (dạng khoa học). Số chữ số phần phân (sau dấu chấm) của phần định trị được tính bằng độ chính xác n nhưng khi in thì bỏ đi các chữ số 0 ở cuối.

Ví dụ nếu độ chính xác n = 4 thì:

Số thực -87.1500 được in: -8.715e+01

Số thực 23.45425 được in: 2.3454e+01

Số thực 678.0 được in: 6.78e+02

+ Khi ios::scientific bật và cờ ios::showpoint bật thì số thực in ra dưới dạng mũ. Số chữ số phần phân (sau dấu chấm) của phần định trị được in đúng bằng độ chính xác n.

Ví dụ nếu độ chính xác n = 4 thì:

Số thực -87.1500 được in: -8.7150e+01

Số thực 23.45425 được in: 2.3454e+01

Số thực 678.0 được in: 6.7800e+01

Nhóm 4 gồm các hiển thị:

ios::showpos ios::showbase ios::uppercase

Cờ ios::showpos

+ Nếu cờ ios::showpos tắt (mặc định) thì dấu cộng không được in trước số dương.

+ Nếu cờ ios::showpos bật thì dấu cộng được in trước số dương.

Cờ ios::showbase

+ Nếu cờ ios::showbase bật thì số nguyên hệ 8 được in bắt đầu bằng ký tự 0 và số nguyên hệ 16 được bắt đầu bằng các ký tự 0x. Ví dụ nếu a = 40 thì:

dạng in hệ 8 là: 050

dạng in hệ 16 là 0x28

+ Nếu cờ ios::showbase tắt (mặc định) thì không in 0 trước số nguyên hệ 8 và không in 0x trước số nguyên hệ 16. Ví dụ nếu a = 40 thì:

dạng in hệ 8 là: 50

dạng in hệ 16 là 28

Cờ ios::uppercase

+ Nếu cờ ios::uppercase bật thì các chữ số hệ 16 (như A, B, C, ...) được in dưới dạng chữ hoa.

+ Nếu cờ ios::uppercase tắt (mặc định) thì các chữ số hệ 16 (như A, B, C, ...) được in dưới dạng chữ thường.

6.3. Các phương thức bật tắt cờ

Các phương thức này định nghĩa trong lớp ios.

+ Phương thức

long cout.setf(long f) ;

sẽ bật các cờ liệt kê trong f và trả về một giá trị long biểu thị các cờ đang bật. Thông thường giá trị f được xác định bằng cách tổ hợp các cờ trình bầy trong mục 6.1.

Ví dụ câu lệnh:

cout.setf(ios::showpoint | ios::scientific) ;

sẽ bật các cờ ios::showpoint và ios::scientific.

+ Phương thức

long cout.unsetf(long f) ;

sẽ tắt các cờ liệt kê trong f và trả về một giá trị long biểu thị các cờ đang bật. Thông thường giá trị f được xác định bằng cách tổ hợp các cờ trình bầy trong mục 6.1.

Ví dụ câu lệnh:

cout.unsetf(ios::showpoint | ios::scientific) ;

sẽ tắt các cờ ios::showpoint và ios::scientific.

+ Phương thức

long cout.flags(long f) ;

có tác dụng giống như cout.setf(long). Ví dụ câu lệnh:

cout.flags(ios::showpoint | ios::scientific) ;

sẽ bật các cờ ios::showpoint và ios::scientific.

+ Phương thức

long cout.flags() ;

sẽ trả về một giá trị long biểu thị các cờ đang bật.

§ 7. Các bộ phận định dạng và các hàm định dạng

7.1. Các bộ phận định dạng (định nghĩa trong <iostream.h>)

Các bộ phận định dạng gồm:

dec // như cờ ios::dec

oct // như cờ ios::oct

hex // như cờ ios::hex

endl // xuất ký tự '

' (chuyển dòng)

flush // đẩy dữ liệu ra thiết bị xuất

Chúng có tác dụng như cờ định dạng nhưng được viết nối đuôi trong toán tử xuất nên tiện sử dụng hơn.

Ví dụ xét chương trình đơn giản sau:

//CT7_08.CPP

// Bo phan dinh dang

#include <iostream.h>

#include <conio.h>

void main()

{

clrscr();

cout.setf(ios::showbase)

cout << "ABC" << endl << hex << 40 << " " << 41;

getch();

}

Chương trình sẽ in 2 dòng sau ra màn hình:

ABC

0x28 0x29

7.2. Các hàm định dạng (định nghĩa trong <iomanip.h>)

Các hàm định dạng gồm:

setw(int n) // như cout.width(int n)

setpecision(int n) // như cout.pecision(int n)

setfill(char ch) // như cout. fill(char ch)

setiosflags(long l) // như cout.setf(long f)

resetiosflags(long l) // như cout.unsetf(long f)

Các hàm định dạng có tác dụng như các phương thức định dạng nhưng được viết nối đuôi trong toán tử xuất nên tiện sử dụng hơn.

Chú ý 1: Các hàm định dạng (cũng như các bộ phận định dạng) cần viết trong các toán tử xuất. Một hàm định dạng đứng một mình như một câu lệnh sẽ không có tác dụng định dạng.

Chú ý 2: Muốn sử dụng các hàm định dạng cần bổ sung vào đầu chương trình câu lệnh:

#include <iomanip.h>

Ví dụ có thể thay phương thức

cout.setf(ios::showbase) ;

trong chương trình của mục 7.1 bằng hàm

cout << setiosflags(ios::showbase);

(chú ý hàm phải viết trong toán tử xuất)

Như vậy chương trình trong 7.1 có thể viết lại theo các phương án sau:

Phương án 1:

#include <iostream.h>

#include <iomanip.h>

#include <conio.h>

void main()

{

clrscr();

cout << setiosflags(ios::showbase) ;

cout << "ABC" << endl << hex << 40 << " " << 41;

getch();

}

Phương án 2:

#include <iostream.h>

#include <iomanip.h>

#include <conio.h>

void main()

{

clrscr();

cout << "ABC" << endl << setiosflags(ios::showbase)

<< hex << 40 << " " << 41;

getch();

}

Dưới đây là ví dụ khác về việc dùng các hàm và bộ phận định dạng. Các câu lệnh:

int i = 23;

cout << i << endl << setiosflags(ios::showbase)

<< hex << i << dec << setfill('*')

<< endl << setw(4) << i << setfill('0')

<< endl << setw(5) << i ;

sẽ in ra màn hình như sau:

23

0x17

**23

00023

7.3. Ví dụ: Chương trình dưới đây minh hoạ cách dùng các hàm định dạng và phương thức định dạng để in danh sách thí sinh dưới dạng bảng với các yêu cầu sau: Số báo danh in 4 ký tự (chèn thêm số 0 vào trước ví dụ 0003), tổng điểm in với đúng một chữ số phần phân.

//CT7_08.CPP

// Bo phan dinh dang

// Ham dinh dang

// Co dinh dang

#include <iostream.h>

#include <iomanip.h>

#include <conio.h>

struct TS

{

int sobd;

char ht[25];

float dt,dl,dh,td;

};

class TSINH

{

private:

TS *ts;

public:

TSINH()

{

ts=NULL;

sots=0;

}

TSINH(int n)

{

ts=new TS[n+1];

sots=n;

}

~TSINH()

{

if (sots)

{

sots=0;

ts = NULL;

}

}

void nhap();

void sapxep();

void xuat();

} ;

void TSINH::nhap()

{

if (sots)

for (int i=1; i<=sots; ++i)

{

cout << "

Thi sinh "<< i << ": " ;

cout << "

So bao danh: " ;

cin >> ts[i].sobd;

cin.ignore();

cout << "Ho ten: " ;

cin.get(ts[i].ht,25);

cout << "Diem toan, ly , hoa: " ;

cin >> ts[i].dt >> ts[i].dl >> ts[i].dh;

ts[i].td = ts[i].dt + ts[i].dl + ts[i].dh;

}

}

void TSINH::sapxep()

{

int i,j;

for (i=1; i< sots; ++i)

for (j=i+1; j<= sots; ++j)

if (ts[i].td < ts[j].td)

{

TS tg;

tg=ts[i];

ts[i]=ts[j];

ts[j]=tg;

}

}

void TSINH::xuat()

{

if (sots)

{

cout << "

Danh sach thi sinh:" ;

cout.precision(1);

cout << setiosflags(ios::left);

cout << "

" << setw(20) << "Ho ten" << setw(8)

<< "So BD" << setw(10) << "Tong diem";

for (int i=1; i<=sots; ++i)

cout << "

" << setw(20)<<setiosflags(ios::left) << ts[i].ht

<< setw(4) << setfill('0') << setiosflags(ios::right)

<< ts[i].sobd << " " << setfill(32)

<< setiosflags(ios::left|ios::showpoint)

<< setw(10) << ts[i].td;

}

}

void main()

{

clrscr();

cout << "

So thi sinh: ";

cin>>n;

TSINH *t = new TSINH(n);

t->nhap() ;

t->sapxep();

t->xuat();

getch();

delete t;

}

§ 8. Các dòng tin chuẩn

Có 4 dòng tin (đối tượng của các lớp Stream) đã định nghĩa trước, được cài đặt khi chương trình khởi động. Hai trong số đó đã nói ở trên là:

cin dòng input chuẩn gắn với bàn phím, giống như stdin của C.

cout dòng output chuẩn gắn với màn hình, giống như stdout của C.

Hai dòng tin chuẩn khác là:

cerr dòng output lỗi chuẩn gắn với màn hình, giống như stderr của C.

clog giống cerr nhưng có thêm bộ đệm.

Chú ý 1: Có thể dùng các dòng cerr và clog để xuất ra màn hình như đã dùng đối với cout.

Chú ý 2: Vì clog có thêm bộ đệm, nên dữ liệu được đưa vào bộ đệm. Khi đầy bộ đệm thì đưa dữ liệu từ bộ đệm ra dòng clog. Vì vậy trước khi kết thúc xuất cần dùng phương thức:

clog.flush();

để đẩy dữ liệu từ bộ đệm ra clog.

Chương trình sau minh hoạ cách dùng dòng clog. Chúng ta nhận thấy, nếu bỏ câu lệnh clog.flush() thì sẽ không nhìn thấy kết quả xuất ra màn hình khi chương trình tạm dừng bởi câu lệnh getch().

// Dùng clog và flush

#include <iostream.h>

#include <conio.h>

void main()

{

clrscr();

float x=-87.1500, y=23.45425,z=678.0;

clog.setf(ios::scientific);

clog.precision(4);

clog.fill('*');

clog << "

";

clog.width(10);

clog << x;

clog << "

";

clog.width(10);

clog << y;

clog << "

";

clog.width(10);

clog << z;

clog.flush();

getch();

}

§ 9. Xuất ra máy in

Trong số 4 dòng tin chuẩn không dòng nào gắn với máy in. Như vậy không thể dùng các dòng này để xuất dữ liệu ra máy in. Để xuất dữ liệu ra máy in (cũng như nhập, xuất trên tệp) cần tạo ra các dòng tin mới và cho nó gắn với thiết bị cụ thể. C++ cung cấp 3 lớp stream để làm điều này, đó là các lớp:

ifstream dùng để tạo dòng nhập

ofstream dùng để tạo dòng xuất

fstream dùng để tạo dòng nhập, dòng xuất hoặc dòng nhập-xuất

Mỗi lớp có 4 hàm tạo dùng để khai báo các dòng tin (đối tượng dòng tin). Trong mục sau sẽ nói thêm về các hàm tạo này.

Để tạo một dòng xuất và gắn nó với máy in ta có thể dùng một trong các hàm tạo sau:

ofstream Tên_dòng_tin(int fd) ;

ofstream Tên_dòng_tin(int fd, char *buf, int n) ;

Trong đó:

+ Tên_dòng_tin là tên biến đối tượng kiểu ofstream hay gọi là tên dòng xuất do chúng ta tự đặt.

+ fd (file descriptor) là chỉ số tập tin. Chỉ số tập tin định sẵn đối với stdprn (máy in chuẩn) là 4.

+ Các tham số buf và n xác định một vùng nhớ n byte do buf trỏ tới. Vùng nhớ sẽ được dùng làm bộ đệm cho dòng xuất.

Ví dụ 1 câu lệnh:

ofstream prn(4) ;

sẽ tạo dòng tin xuất prn và gắn nó với máy in chuẩn. Dòng prn sẽ có bộ đệm mặc định. Dữ liệu trước hết chuyển vào bộ đệm, khi đầy bộ đệm thì dữ liệu sẽ được đẩy từ bộ đệm ra dòng prn. Để chủ động yêu cầu đẩy dữ liệu từ bộ đệm ra dòng prn có thể sử dụng phương thức flush hoặc bộ phận định dạng flush. Cách viết như sau:

prn.flush(); // Phương thức

prn << flush ; // Bộ phận định dạng

Các câu lệnh sau sẽ xuất dữ liệu ra prn (máy in) và ý nghĩa của chúng như sau:

prn << "

Tong = " << (4+9) ; // Đưa một dòng vào bộ đệm

prn << "

Tich =" << (4*9); // Đưa tiếp dòng thứ 2 vào bộ đệm

prn.flush(); // Đẩy dữ liệu từ bộ đệm ra máy in (in 2 dòng)

Các câu lệnh dưới đây cũng xuất dữ liệu ra máy in nhưng sẽ in từng dòng một:

prn << "

Tong = " << (4+9) << flush ; // In một dòng

prn << "

Tich = " << (4*9) ; << flush // In dòng thứ hai

Ví dụ 2: Các câu lệnh

char buf[1000] ;

ofstream prn(4,buf,1000) ;

sẽ tạo dòng tin xuất prn và gắn nó với máy in chuẩn. Dòng xuất prn sử dụng 1000 byte của mảng buf làm bộ đệm. Các câu lệnh dưới đây cũng xuất dữ liệu ra máy in:

prn << "

Tong = " << (4+9) ; // Đưa dữ liệu vào bộ đệm

prn << "

Tich = " << (4*9) ; // Đưa dữ liệu vào bộ đệm

prn.flush() ; // Xuất 2 dòng (ở bộ đệm) ra máy in

Chú ý: Trước khi kết thúc chương trình, dữ liệu từ bộ đệm sẽ được tự động đẩy ra máy in.

Chương trinh minh hoạ: Chương trình dưới đây tương tự như chương trình trong mục 7.3 (chỉ sửa đổi phương thức xuất) nhưng thay việc xuất ra màn hình bằng xuất ra máy in.

//CT7_08B.CPP

// Xuat ra may in

// Bo phan dinh dang

// Ham dinh dang

#include <iostream.h>

#include <iomanip.h>

#include <conio.h>

struct TS

{

int sobd;

char ht[25];

float dt,dl,dh,td;

} ;

class TSINH

{

private:

TS *ts;

public:

TSINH()

{

ts=NULL;

sots=0;

}

TSINH(int n)

{

ts=new TS[n+1];

sots=n;

}

~TSINH()

{

if (sots)

{

sots=0;

ts = NULL;

}

}

void nhap();

void sapxep();

void xuat();

} ;

void TSINH::nhap()

{

if (sots)

for (int i=1; i<=sots; ++i)

{

cout << "

Thi sinh "<< i << ": " ;

cout << "

So bao danh: " ;

cin >> ts[i].sobd;

cin.ignore();

cout << "Ho ten: " ;

cin.get(ts[i].ht,25);

cout << "Diem toan, ly , hoa: " ;

cin >> ts[i].dt >> ts[i].dl >> ts[i].dh;

ts[i].td = ts[i].dt + ts[i].dl + ts[i].dh;

}

}

void TSINH::sapxep()

{

int i,j;

for (i=1; i< sots; ++i)

for (j=i+1; j<= sots; ++j)

if (ts[i].td < ts[j].td)

{

TS tg;

tg=ts[i];

ts[i]=ts[j];

ts[j]=tg;

}

}

void TSINH::xuat()

{

ostream prn(4);

if (sots)

{

prn << "

Danh sach thi sinh:" ;

prn.precision(1);

prn << setiosflags(ios::left);

prn << "

" << setw(20) <<"Ho ten" << setw(8)

<< "So BD"<< setw(10) << "Tong diem";

for (int i=1; i<=sots; ++i)

prn << "

" << setw(20)<<setiosflags(ios::left) <<ts[i].ht <<

setw(4) << setfill('0')<<setiosflags(ios::right)<< ts[i].sobd

<< " " << setfill(32) <<setiosflags(ios::left|ios::showpoint)

<<setw(10)<< ts[i].td;

}

}

void main()

{

clrscr();

cout << "

So thi sinh: ";

cin>>n;

TSINH *t = new TSINH(n);

t->nhap() ;

t->sapxep();

t->xuat();

getch();

delete t;

}

§ 10. Làm việc với tệp

10.1. Các lớp dùng để nhập, xuất dữ liệu lên tệp

Như đã nói ở trên, C++ cung cấp 4 dòng tin chuẩn để làm việc với bàn phím và màn hình. Muốn nhập xuất lên tệp chúng ta cần tạo các dòng tin mới (khai báo các đối tượng Stream) và gắn chúng với một tệp cụ thể. C++ cung cấp 3 lớp stream để làm điều này, đó là các lớp:

ofstream dùng để tạo các dòng xuất (ghi tệp)

ifstream dùng để tạo các dòng nhập (đọc tệp)

fstream dùng để tạo các dòng nhập, dòng xuất hoặc dòng nhập-xuất

Sơ đồ dẫn xuất các lớp như sau:

ostream fstreambase istream

ofstream ifstream

fstream

10.2. Ghi dữ liệu lên tệp

Thủ tục ghi dữ liệu lên tệp như sau:

1. Dùng lớp ofstream để tạo ra một dòng xuất và gắn nó với một tệp cụ thể. Khi đó việc xuất dữ liệu ra dòng này đồng nghĩa với việc ghi dữ liệu lên tệp.

2. Thực hiện xuất dữ liệu ra dòng xuất vừa tạo như thể xuất dữ liệu ra dòng xuất chuẩn cout.

10.3. Đọc dữ liệu từ tệp

Thủ tục đọc dữ liệu từ tệp như sau:

1. Dùng lớp ifstream để tạo ra một dòng nhập và gắn nó với một tệp cụ thể. Khi đó việc nhập dữ liệu từ dòng này đồng nghĩa với việc đọc dữ liệu từ tệp.

2. Thực hiện nhập dữ liệu từ dòng nhập vừa tạo như thể nhập dữ liệu từ dòng nhập chuẩn cin.

10.4. Đọc - ghi dữ liệu đồng thời trên tệp

Thủ tục đọc-ghi dữ liệu đồng thời trên tệp như sau:

1. Dùng lớp fstream để tạo ra một dòng nhập-xuất và gắn nó với một tệp cụ thể.

2. Thực hiện nhập dữ liệu từ dòng nhập-xuất vừa tạo như thể nhập dữ liệu từ dòng nhập chuẩn cin.

3. Thực hiện xuất dữ liệu ra dòng nhập-xuất vừa tạo như thể xuất dữ liệu ra dòng xuất chuẩn cout.

Nhận xét: Như vậy:

1. Việc xuất dữ liệu ra máy in hoặc lên tệp được thực hiện hoàn toàn giống như xuất dữ liệu ra dòng xuất chuẩn cout (màn hình).

2. Việc đọc dữ liệu từ tệp được thực hiện hoàn toàn giống như nhập dữ liệu từ dòng nhập chuẩn cin (bàn phím).

§ 11. Ghi dữ liệu lên tệp

11.1. Lớp ofstream

Để ghi dữ liệu lên tệp chúng ta sử dụng lớp ofstream. Lớp ofstream thừa kế các phương thức của các lớp ios và ostream. Nó cũng thừa kế phương thức:

close

của lớp fstreambase. Ngoài ra lớp ofstream có thêm các hàm tạo và các phương thức sau:

1. Hàm tạo:

ofstream() ; // Không đối

dùng để tạo một đối tượng ofstream (dòng xuất), chưa gắn với tệp.

2. Hàm tạo:

ofstream(const char *fn, int mode = ios::out,

int prot = filebuf::openprot);dùng để tạo một đối tượng ofstream, mở tệp có tên fn để ghi và gắn đối tượng vừa tạo với tệp được mở.

+ Tham số fn cho biết tên tệp.

+ Tham số mode có giá trị mặc định là ios::out (mở để ghi). Tham số này có thể là một hợp của các giá trị sau:

ios::binary ghi theo kiểu nhị phân (mặc định theo kiểu văn bản)

ios::out ghi tệp, nếu tệp đã có thì nó bị xoá

ios::app ghi bổ sung vào cuối tệp

ios::ate chuyển con trỏ tệp tới cuối tệp sau khi mở tệp

ios::trunc xoá nội dung của tệp nếu nó tồn tại

ios::nocreate nếu tệp chưa có thì không làm gì (bỏ qua)

ios::noreplace nếu tệp đã có thì không làm gì (bỏ qua)

+ Tham số thứ ba prot quy định cấp bảo vệ của dòng tin, tham số này có thể bỏ qua vì nó đã được gán một giá trị mặc định.

3. Hàm tạo:

ofstream(int fd);

dùng để tạo một đối tượng ofstream và gắn nó với một tệp có chỉ số fd đang mở.

(Để mở và lấy chỉ số (số hiệu) tệp có thể dùng hàm _open, xem cuốn Kỹ thuật Lập trình C của tác giả).

4. Hàm tạo:

ofstream(int fd, char *buf, int n);

dùng để tạo một đối tượng ofstream , gắn nó với một tệp có chỉ số fd đang mở và sử dùng một vùng nhớ n byte do buf trỏ tới làm bộ đệm.

5. Phương thức:

void open(const char *fn, int mode = ios::out,

int prot = filebuf::openprot);dùng để mở tệp có tên fn để ghi và gắn nó với đối tượng ofstream. Các tham số của phương thức có cùng ý nghĩa như trong hàm tạo thứ 2.

11.2. Các cách ghi tệp

Có 2 cách chính sau:

+ Cách 1: Dùng hàm tạo 2 để xây dựng một dòng xuất, mở một tệp để ghi và gắn tệp với dòng xuất. Sau đó dùng toán tử xuất << và các phương thức để xuất dữ liệu ra dòng xuất vừa tạo như thể xuất dữ liệu ra cout (xem các mục trên).

+ Cách 2: Dùng hàm tạo 1 để xây dựng một dòng xuất. Sau đó dùng phương thức open để mở một tệp cụ thể và cho gắn với dòng xuất vừa xây dựng. Khi không cần làm việc với tệp này nữa, chúng ta có thể dùng phương thức close để chấm dứt mọi ràng buộc giữa dòng xuất và tệp. Sau đó có thể gắn dòng xuất với tệp khác. Theo cách này, có thể dùng một dòng xuất (đối tượng ofstream) để xuất dữ liệu lên nhiều tệp khác nhau.

11.3. Ví dụ

Chương trình 1: Chương trình dưới đây sẽ nhập danh sách n thí sinh. Thông tin thí sinh gồm: Họ tên, tỉnh hoặc thành phố cư trú, số báo danh, các điểm toán lý hoá. Dữ liệu thí sinh được ghi trên 2 tệp: Tệp DS1.DL ghi thí sinh theo thứ tự nhập từ bàn phím, tệp DS2.DL ghi thí sinh theo thứ tự giảm của tổng điểm. Cấu trúc của 2 tệp như sau:

Dòng đầu ghi một số nguyên bằng số thí sinh.

Các dòng tiếp theo ghi dữ liệu của thí sinh. Mỗi thí sinh ghi trên 2 dòng, dòng 1 ghi họ tên trên 24 vị trí và tên tỉnh trên 20 vị trí. Dòng 2 ghi số báo danh (6 vị trí), các điểm toán, lý , hoá và tổng điểm (mỗi điểm ghi trên 6 vị trí trong đó một vị trí chứa phần phân). Chương trình sử dụng lớp TS (Thí sinh) có 3 phương thức: Nhập, sắp xếp và ghi tệp. Cách ghi tệp sử dụng ở đây là cách 1: Dùng hàm tạo dạng 2 của lớp ofstream.

Chương trình 2 ngay bên dưới cũng giải quyết cùng bài toán nêu trên nhưng sử dụng cách ghi tệp thứ 2 (dùng hàm tạo 1 và phương thức open)

Một điều đáng nói ở đây là việc nhập một chuỗi ký tự (như họ tên và tên tỉnh) bằng các phương thức get hoặc getline chưa được thuận tiện, vì 2 lý do sau: thứ nhất là các phương thức này có thể bị ký tự chuyển dòng (còn sót trên cin) làm trôi. Thứ hai là các phương thức này có thể để lại một số ký tự trên dòng cin (nếu số ký tự gõ nhiều hơn so với quy định) và các ký tự này sẽ gây ảnh hưởng đến các phép nhập tiếp theo. Để khắc phục các nhược điểm trên, chúng ta đưa vào 2 chương trình trên hàm getstr để nhập chuỗi ký tự từ bàn phím.

//CT7_10.CPP

// Ghi Tep

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

#include <ctype.h>

void getstr(char *str,int n)

{

char tg[21];

while(1) // Bỏ qua Enter và nhập tối đa n-1 ký tự

{

cin.get(str,n);

if (str[0])

break;

else

cin.ignore();

}

while(1) // Loại các ký tự còn lại ra khỏi dòng nhập cin

{

cin.get(tg,20);

if (tg[0]==0)

{

cin.ignore();

break;

}

}

}

struct TSINH

{

char ht[25];

char ttinh[21];

int sobd;

float dt,dl,dh,td;

} ;

class TS

{

private:

TSINH *ts;

public:

TS()

{

sots=0;

ts = NULL;

}

void nhap();

void sapxep();

void ghitep(char *ttep);

};

void TS::nhap()

{

cout << "

So thi sinh: " ;

cin >> sots ;

int n=sots;

ts = new TSINH[n+1];

for (int i=1; i<=n; ++i)

{

cout << "

Nhap thi sinh thu: " << i << endl;

cout << "Ho ten: " ;

getstr(ts[i].ht,25);

cout << "Tinh hoac thanh pho: " ;

getstr(ts[i].ttinh,21);

cout << "So bao danh: " ;

cin >> ts[i].sobd ;

cout << "Cac diem toan, ly, hoa: " ;

cin >> ts[i].dt >> ts[i].dl >> ts[i].dh ;

ts[i].td =ts[i].dt + ts[i].dl + ts[i].dh ;

}

}

void TS::sapxep()

{

int n = sots;

for (int i=1; i< n; ++i)

for (int j=i+1; j<= n; ++j)

if (ts[i].td < ts[j].td)

{

TSINH tg = ts[i];

ts[i] = ts[j];

ts[j] = tg;

}

}

void TS::ghitep(char *ttep)

{

ofstream f(ttep);

f << sots ;

f << setprecision(1) << setiosflags(ios::showpoint);

for (int i=1; i<=sots; ++i)

{

f << endl << setw(24) << ts[i].ht << setw(20) << ts[i].ttinh ;

f << endl << setw(6) << ts[i].sobd

<< setw(6) << ts[i].dt

<< setw(6) << ts[i].dl

<< setw(6) << ts[i].dh

<< setw(6) << ts[i].td ;

}

f.close();

}

void main()

{

clrscr();

TS t;

t.nhap();

t.ghitep("DS1.DL");

t.sapxep();

t.ghitep("DS2.DL");

cout << "

Hoan thanh";

getch();

}

Chương trình 2:

//CT7_11.CPP

// Ghi Tep

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

#include <ctype.h>

void getstr(char *str,int n)

{

char tg[21];

while(1)

{

cin.get(str,n);

if (str[0])

break;

else

cin.ignore();

}

while(1)

{

cin.get(tg,20);

if (tg[0]==0)

{

cin.ignore();

break;

}

}

}

struct TSINH

{

char ht[25];

char ttinh[21];

int sobd;

float dt,dl,dh,td;

} ;

class TS

{

private:

TSINH *ts;

public:

TS()

{

sots=0;

ts = NULL;

}

void nhap();

void sapxep();

void ghitep(char *ttep);

};

void TS::nhap()

{

cout << "

So thi sinh: " ;

cin >> sots ;

int n=sots;

ts = new TSINH[n+1];

for (int i=1; i<=n; ++i)

{

cout << "

Nhap thi sinh thu: " << i << endl;

cout << "Ho ten: " ;

getstr(ts[i].ht,25);

cout << "Tinh hoac thanh pho: " ;

getstr(ts[i].ttinh,21);

cout << "So bao danh: " ;

cin >> ts[i].sobd ;

cout << "Cac diem toan, ly, hoa: " ;

cin >> ts[i].dt >> ts[i].dl >> ts[i].dh ;

ts[i].td =ts[i].dt + ts[i].dl + ts[i].dh ;

}

}

void TS::sapxep()

{

int n = sots;

for (int i=1; i< n; ++i)

for (int j=i+1; j<= n; ++j)

if (ts[i].td < ts[j].td)

{

TSINH tg = ts[i];

ts[i] = ts[j];

ts[j] = tg;

}

}

void TS::ghitep(char *ttep)

{

ofstream f;

f.open(ttep,ios::out|ios::noreplace);

if (f.bad())

{

cout << "

Tep " << ttep << " da ton tai";

cout << "

Co ghi de? - C/K";

int ch=getch();

if (toupper(ch)=='C')

{

f.close();

f.open(ttep) ;

}

else

exit(1);

}

f << sots ;

f << setprecision(1) << setiosflags(ios::showpoint);

for (int i=1; i<=sots; ++i)

{

f << endl << setw(24) << ts[i].ht << setw(20) << ts[i].ttinh ;

f << endl << setw(6) << ts[i].sobd

<< setw(6) << ts[i].dt

<< setw(6) << ts[i].dl

<< setw(6) << ts[i].dh

<< setw(6) << ts[i].td ;

}

f.close();

}

void main()

{

clrscr();

TS t;

t.nhap();

t.ghitep("DS1.DL");

t.sapxep();

t.ghitep("DS2.DL");

cout << "

Hoan thanh";

getch();

}

§ 12. Đọc dữ liệu từ tệp

12.1. Lớp ifstream

Để đọc dữ liệu từ tệp chúng ta sử dụng lớp ifstream. Lớp ifstream thừa kế các phương thức của các lớp ios và istream. Nó cũng thừa kế phương thức:

close

của lớp fstreambase. Ngoài ra lớp ifstream có thêm các hàm tạo và các phương thức sau:

1. Hàm tạo:

ifstream() ; // Không đối

dùng để tạo một đối tượng ifstream (dòng nhập), chưa gắn với tệp.

2. Hàm tạo:

ifstream(const char *fn, int mode = ios::in,

int prot = filebuf::openprot);

dùng để tạo một đối tượng ifstream, mở tệp có tên fn để đọc và gắn đối tượng vừa tạo với tệp được mở.

+ Tham số fn cho biết tên tệp.

+ Tham số mode có giá trị mặc định là ios::in (mở để đọc). Tham số này có thể là một hợp của các giá trị sau:

ios::binary đọc theo kiểu nhị phân (mặc định theo kiểu văn bản)

ios::ate chuyển con trỏ tệp tới cuối tệp sau khi mở tệp

+ Tham số thứ ba prot quy định cấp bảo vệ của dòng tin, tham số này có thể bỏ qua vì nó đã được gán một giá trị mặc định.

3. Hàm tạo:

ifstream(int fd);

dùng để tạo một đối tượng ifstream và gắn nó với một tệp có chỉ số fd đang mở.

(Để mở và lấy chỉ số (số hiệu) tệp có thể dùng hàm _open, xem cuốn Kỹ thuật Lập trình C của tác giả)

4. Hàm tạo:

ifstream(int fd, char *buf, int n);

dùng để tạo một đối tượng ifstream , gắn nó với một tệp có chỉ số fd đang mở và sử dùng một vùng nhớ n byte do buf trỏ tới làm bộ đệm.

5. Phương thức:

void open(const char *fn, int mode = ios::in,

int prot = filebuf::openprot);

dùng để mở tệp có tên fn để đọc và gắn nó với đối tượng ifstream. Các tham số của phương thức có cùng ý nghĩa như trong hàm tạo thứ 2.

12.2. Các cách đọc tệp

Có 2 cách chính sau:

+ Cách 1: Dùng hàm tạo 2 để xây dựng một dòng nhập, mở một tệp để đọc và gắn tệp với dòng nhập. Sau đó dùng toán tử nhập >> và các phương thức để nhập dữ liệu từ dòng nhập vừa tạo như thể nhập dữ liệu từ cin (xem các mục trên)

+ Cách 2: Dùng hàm tạo 1 để xây dựng một dòng nhập. Sau đó dùng phương thức open để mở một tệp cụ thể và cho gắn với dòng nhập vừa xây dựng. Khi không cần làm việc với tệp này nữa, chúng ta có thể dùng phương thức close để chấm dứt mọi ràng buộc giữa dòng nhập và tệp. Sau đó có thể gắn dòng nhập với tệp khác. Theo cách này, có thể dùng một dòng nhập (đối tượng ifstream) để nhập dữ liệu từ nhiều tệp khác nhau.

12.3. Kiểm tra sự tồn tại của tệp, kiểm tra cuối tệp

+ Khi mở một tệp để đọc mà tệp không tồn tại thì sẽ phát sinh lỗi, khi đó phương thức bad trả về giá trị khác không. Ví dụ để kiểm tra xem tệp DSTS (Danh sách thí sinh) đã tồn tại hay không có thể dùng đoạn chương trình:

ifstream fin("DSTS");

if (fin.bad())

{

cout << "

Tep DSTS không tồn tai";

exit(1);

}

+ Trong quá trình đọc, con trỏ tệp sẽ chuyển dần về cuối tệp. Khi con trỏ tệp đã ở cuối tệp (hết dữ liệu) mà vẫn thực hiện một lệnh đọc thì phương thức eof sẽ cho giá trị khác không. Chương trình dưới đây dùng phương thức eof để xác định độ dài (số byte) của tệp TC.EXE (chú ý cần dùng kiểu đọc nhị phân):

//CT7_14.CPP

// Do dai tep

#include <iostream.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

void main()

{

clrscr();

long dd=0; char ch;

ifstream f("TC.EXE",ios::in | ios::binary);

if (f.bad())

{

cout << "

Tep TC.EXE khong ton tai";

getch();

exit(1);

}

while(f.get(ch),!f.eof()) ++dd;

cout << "

Do dai TC.EXE: " << dd;

getch();

}

12.4. Ví dụ

Chương trình dưới đây sẽ:

+ Đọc danh sách thí sinh từ tệp DS1.DL do chương trình trong muc §11 tạo ra.

+ In danh sách thí sinh vừa đọc.

+ Sắp xếp dẫy thí sinh (vừa nhập từ tệp) theo thứ tự giảm của tổng điểm.

+ Ghi danh sách thí sinh sau khi sắp xếp lên tệp DS3.DL

+ Đọc danh sách thí sinh từ tệp DS3.DL

+ In danh sách thí sinh đọc từ tệp DS3.DL

Chương trình sử dụng lớp TS (Thí sinh) có 4 phương thức:

void xuat();

void sapxep();

void ghitep(char *ttep);

void doctep(char *ttep);

//CT7_12.CPP

// Doc tep

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

#include <ctype.h>

struct TSINH

{

char ht[25];

char ttinh[21];

int sobd;

float dt,dl,dh,td;

} ;

class TS

{

private:

TSINH *ts;

public:

TS()

{

sots=0;

ts = NULL;

}

void xuat();

void sapxep();

void ghitep(char *ttep);

void doctep(char *ttep);

};

void TS::xuat()

{

cout << "

So thi sinh: " << sots;

cout << setprecision(1) << setiosflags(ios::showpoint);

for (int i=1; i<=sots; ++i)

{

cout << "

Thi sinh thu: " << i ;

cout << "

Ho ten: " << ts[i].ht ;

cout << "

Tinh - thanh pho: " << ts[i].ttinh ;

cout << "

So bao danh: " << ts[i].sobd ;

cout << "

Cac diem toan, ly, hoa: "

<< setw(5) << ts[i].dt

<< setw(5) << ts[i].dl

<< setw(5) << ts[i].dh ;

cout << "

Tong diem: " << ts[i].td ;

}

}

void TS::sapxep()

{

int n = sots;

for (int i=1; i< n; ++i)

for (int j=i+1; j<= n; ++j)

if (ts[i].td < ts[j].td)

{

TSINH tg = ts[i];

ts[i] = ts[j];

ts[j] = tg;

}

}

void TS::ghitep(char *ttep)

{

ofstream f;

f.open(ttep,ios::out|ios::noreplace);

if (f.bad())

{

cout << "

Tep " << ttep << " da ton tai";

cout << "

Co ghi de? - C/K";

int ch=getch();

if (toupper(ch)=='C')

{

f.close();

f.open(ttep) ;

}

else

exit(1);

}

f << sots ;

f << setprecision(1) << setiosflags(ios::showpoint);

for (int i=1; i<=sots; ++i)

{

f << endl << setw(24) << ts[i].ht << setw(20) << ts[i].ttinh ;

f << endl << setw(6) << ts[i].sobd

<< setw(6) << ts[i].dt

<< setw(6) << ts[i].dl

<< setw(6) << ts[i].dh

<< setw(6) << ts[i].td ;

}

f.close();

}

void TS::doctep(char *ttep)

{

ifstream f;

f.open(ttep);

if (f.bad())

{

cout << "

Tep " << ttep << " khong ton tai";

getch();

exit(1);

}

f >> sots ;

f.ignore();

if (ts!=NULL) delete ts;

ts = new TSINH[sots+1];

for (int i=1; i<=sots; ++i)

{

f.get(ts[i].ht,25).get(ts[i].ttinh,21); ;

f >> ts[i].sobd >> ts[i].dt >> ts[i].dl

>> ts[i].dh >> ts[i].td ;

f.ignore();

}

f.close();

}

void main()

{

clrscr();

TS t;

t.doctep("DS1.DL");

t.xuat();

t.sapxep();

t.ghitep("DS3.DL");

t.doctep("DS3.DL");

t.xuat();

cout << "

Hoan thanh";

getch();

}

§ 13. Đọc ghi đồng thời trên tệp

13.1. Lớp fstream

Để đọc ghi đồng thời trên tệp, chúng ta sử dụng lớp fstream. Lớp fstream thừa kế các phương thức của các lớp ofstream và ifstream. Ngoài ra lớp fstream có các hàm tạo và phương thức sau:

1. Hàm tạo:

fstream() ; // Không đối

dùng để tạo một đối tượng fstream (dòng nhập-xuất), chưa gắn với tệp.

2. Hàm tạo:

fstream(const char *fn, int mode,

int prot = filebuf::openprot);

dùng để tạo một đối tượng fstream, mở tệp có tên fn và gắn đối tượng vừa tạo với tệp được mở.

+ Tham số fn cho biết tên tệp.

+ Tham số mode quy định các kiểu truy nhập và có thể là tổ hợp của các giá trị sau:

ios::binary đọc-ghi theo kiểu nhị phân (mặc định theo kiểu văn bản).

ios::out ghi tệp, nếu tệp đã có thì nó bị xoá

ios::in đọc tệp

ios::app ghi bổ sung vào cuối tệp

ios::ate chuyển con trỏ tệp về cuối sau khi mở

ios::trunc xoá nội dung của tệp nếu nó tồn tạI

ios::nocreate nếu tệp chưa có thì không làm gì (bỏ qua)

ios::noreplace nếu tệp đã có thì không làm gì (bỏ qua)

Chú ý:

+ Tham số mode không có giá trị mặc định.

+ Tham số thứ ba prot quy định cấp bảo vệ của dòng tin, tham số này có thể bỏ qua vì nó đã được gán một giá trị mặc định.

3. Hàm tạo:

fstream(int fd);

dùng để tạo một đối tượng fstream và gắn nó với một tệp có chỉ số fd đang mở.

(Để mở và lấy chỉ số (số hiệu) tệp có thể dùng hàm _open, xem cuốn Kỹ thuật Lập trình C của tác giả)

4. Hàm tạo:

fstream(int fd, char *buf, int n);

dùng để tạo một đối tượng fstream , gắn nó với một tệp có chỉ số fd đang mở và sử dùng một vùng nhớ n byte do buf trỏ tới làm bộ đệm.

5. Phương thức:

void open(const char *fn, int mode,

int prot = filebuf::openprot);

dùng để mở tệp có tên fn và gắn nó với đối tượng fstream. Các tham số của phương thức có cùng ý nghĩa như trong hàm tạo thứ 2.

Chú ý: Tham số mode không có giá trị mặc định.

13.2. Các cách đọc-ghi đồng thời trên tệp

Có 2 cách chính sau:

+ Cách 1: Dùng hàm tạo 2 để xây dựng một dòng nhập-xuất, mở một tệp để đọc-ghi và gắn tệp với dòng nhập-xuất. Sau đó dùng toán tử nhập >> , toán tử xuất >> và các phương thức nhập, xuất để nhập, xuất dữ liệu ra dùng nhập-xuất vừa tạo (như đối với các dòng chuẩn cin và cout). Ví dụ:

fstream f("DU_LIEU", ios::in | ios::out) ;

+ Cách 2: Dùng hàm tạo 1 để xây dựng một dòng nhập-xuất. Sau đó dùng phương thức open để mở một tệp cụ thể (để đọc và ghi) và cho gắn với dòng nhập-xuất vừa xây dựng. Khi không cần làm việc với tệp này nữa, chúng ta có thể dùng phương thức close để chấm dứt mọi ràng buộc giữa dòng nhập-xuất và tệp. Sau đó có thể gắn dòng nhập-xuất với tệp khác. Theo cách này, có thể dùng một dòng nhập-xuất (đối tượng fstream) để đọc-ghi dữ liệu từ nhiều tệp khác nhau.

Ví dụ:

fstream f;

f.open("DU_LIEU", ios::in | ios::out) ;

13.3. Di chuyển con trỏ tệp

13.3.1. Để di chuyển con trỏ tệp trên dòng xuất, chúng ta sử dụng các phương thức sau (của lớp ostream) :

1. Phương thức

ostream& seekp(long n) ;

sẽ chuyển con trỏ tệp tới vị trí (byte) thứ n (số thứ tự các byte tính từ 0).

2. Phương thức

ostream& seekp(long offset, seek_dir dir) ;

sẽ chuyển con trỏ tệp tới vị trí offset kể từ vị trí xuất phát dir. Giá trị của offset có thể âm, còn dir có thể nhận một trong các giá trị sau:

ios::beg xuất phát từ đầu tệp

ios::end xuất phát từ cuối tệp

ios::cur xuất phát từ vị trí hiện tại của con trỏ tệp

3. Phương thức

long teelp() ;

cho biết vị trí hiện tại của con trỏ tệp.

13.3.2. Để di chuyển con trỏ tệp trên dòng nhập, chúng ta sử dụng các phương thức sau (của lớp istream):

4. Phương thức

istream& seekg(long n) ;

sẽ chuyển con trỏ tệp tới vị trí (byte) thứ n (số thứ tự các byte tính từ 0)

5. Phương thức

istream& seekg(long offset, seek_dir dir) ;

sẽ chuyển con trỏ tệp tới vị trí offset kể từ vị trí xuất phát dir. Giá trị của offset có thể âm, còn dir có thể nhận một trong các giá trị sau:

ios::beg xuất phát từ đầu tệp

ios::end xuất phát từ cuối tệp

ios::cur xuất phát vị trí hiện tại của con trỏ tệp

6. Phương thức

long teelg() ;

cho biết vị trí hiện tại của con trỏ tệp.

13.3.3. Để di chuyển con trỏ tệp trên dòng nhập-xuất, chúng ta có thể sử dụng cả 6 phương thức nêu trên.

13.4. Ví dụ

Ví dụ 1. Trong §12 đã viết chương trình xác định độ dài của tệp TC.EXE. Dưới đây là một phương án khác đơn giản hơn:

fstream f("TC.EXE");

f.seekg(0,ios::end);

do_dai = f.teelg();

Ví dụ 2. Chương trình dưới đây sẽ nhập danh sách n thí sinh từ bàn phím và ghi lên tệp. Sau đó đưa con trỏ tệp về đầu tệp và bắt đầu đọc dữ liệu thí sinh từ tệp để in ra màn hình. Thông tin thí sinh gồm: Họ tên, tỉnh hoặc thành phố cư trú, số báo danh, các điểm toán lý hoá.

//CT7_13.CPP

// ghi - đọc đồng thời

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

#include <ctype.h>

#include <stdio.h>

void main()

{

char ht[25], ttinh[21], ttep[40];

int sobd,stt ;

float dt, dl, dh, td;

fstream f;

cout << "

Ten tep: " ;

cin >> ttep;

f.open(ttep,ios::out|ios::in|ios::noreplace);

if (f.bad())

{

cout << "

Tep " << ttep << " da ton tai";

cout << "

Co ghi de? - C/K";

int ch=getch();

if (toupper(ch)=='C')

{

f.close();

f.open(ttep,ios::out|ios::in|ios::trunc) ;

}

else

exit(1);

}

stt=0 ;

f << setprecision(1) << setiosflags(ios::showpoint);

while(1)

{

++stt;

cout << "

Nhap thi sinh thu: " << stt ;

cout << "

Ho ten (neu rong thi ket thuc nhap) : ";

cin.ignore();

cin.getline(ht,25);

if (ht[0]==0) break;

cout << "Tinh - thanh pho: ";

cin.getline(ttinh,21);

cout << "SoBD, diem toan, diem ly, diem hoa: " ;

cin >> sobd >> dt>> dl >> dh ;

td = dt + dl + dh ;

if (stt>1) f << endl;

f << setw(24) << ht << setw(20) << ttinh ;

f << endl << setw(6) << sobd

<< setw(6) << dt

<< setw(6) << dl

<< setw(6) << dh

<< setw(6) << td ;

}

f.seekg(0);

stt=0;

clrscr();

cout << "

Danh sach thi sinh

";

cout << setprecision(1) <<

setiosflags(ios::showpoint);

while(1)

{

f.getline(ht,25).getline(ttinh,21);

if (f.eof()) break;

++stt;

f >> sobd >> dt >> dl >> dh >> td;

f.ignore();

cout << "

Thi sinh thu: " << stt ;

cout << "

Ho ten: "<< ht;

cout << "

Tinh - thanh pho: " << ttinh;

cout << "

So bao danh: " << sobd;

cout << "

Diem toan, ly, hoa va tong diem: "

<<setw(6)<< dt << setw(6) <<dl << setw(6) << dh

<< setw(6) << td ;

}

f.close();

cout << "

Hoan thanh";

getch();

}

§ 14. Xử lý lỗi

Khi làm việc với tệp không phải mọi việc đều trôi chẩy mà thường xẩy ra nhiều điều trục trặc, chẳng hạn:

1. Mở một tệp để đọc nhưng tệp không tồn tại.

2. Đọc dữ liệu nhưng con trỏ tệp đã ở cuối tệp

3. Ghi dữ liệu nhưng hết không gian đĩa (đĩa đầy).

4. Tạo tệp nhưng đia hỏng, hoặc đĩa cấm ghi hoặc đĩa đầy.

5. Dùng tên tệp không hợp lệ

6. Định thực hiện một thao tác nhưng tệp lại không được mở ở mode phù hợp để thực hiện thao tác đó.

Tóm lại khi làm việc với tệp thường gặp nhiều lỗi khác nhau, nếu không biết cách phát hiện xử lý thì chương trình sẽ dẫn đến rối loạn hoặc cho kết quả sai. Trong lớp ios của C++ có nhiều phương thức cho phép phát hiện lỗi khi làm việc với tệp. Đó là:

1. Phương thức

int eof() ;

Nếu con trỏ tệp đã ở cuối tệp mà lại thực hiện một lệnh đọc dữ liệu thì phương thức eof() trả về giá trị khác không, trái lại phương thức có giá trị bằng 0.

2. Phương thức

int fail() ;

Phương thức fail() trả về giá trị khác không nếu lần nhập xuất cuối cùng có lỗi, trái lại phương thức có giá trị bằng 0.

3. Phương thức

int bad() ;

Phương thức bad() trả về giá trị khác không khi một phép nhập xuất không hợp lệ hoặc có lỗi mà chưa phát hiện được, trái lại phương thức có giá trị bằng 0.

4. Phương thức

int good() ;

Phương thức good() trả về giá trị khác không nếu mọi việc đều tốt đẹp ( không có lỗi nào xẩy ra). Khi có một lỗi nào đó thì phương thức có giá trị bằng 0.

5. Phương thức

void clear() ;

dùng để tắt tất cả các bit lỗi.

Ví dụ 1. Khi mở tệp để đọc cần kiểm tra xem tệp có tồn tại không. Để làm điều đó, chúng ta có thể dùng đoạn chương sau:

char ten_tep[40] ;

cout << "

Cho biết tên tệp: " ;

cin >> ten_tep ;

ifstream f(ten_tep);

if (f.bad())

{

cout << "

Tệp << ten_tep << "không tồn tại" ;

exit(1) ;

}

Ví dụ 2. Khi tạo tệp mới để ghi cần kiểm tra xem có tạo được tệp hay không. Để làm điều đó, chúng ta có thể dùng đoạn chương sau:

char ten_tep[40] ;

cout << "

Cho biết tên tệp: " ;

cin >> ten_tep ;

ofstream f(ten_tep);

if (f.bad())

{

cout << "

Không tạo được tệp << ten_tep ;

exit(1) ;

}

Ví dụ 3. Để xác định độ dài của tệp, có thể dùng phương thức eof() và thuật toán sau:

+ Đọc một byte (chú ý phải đọc theo kiểu nhị phân)

+ Nếu việc đọc thành công ( eof()=0 ) thì cộng thêm một vào bộ đếm. Nếu việc đọc không thành ( eof() != 0 ) thì kết thúc vùng lặp.

Thuật toán trên được thể hiện bằng đoạn chương trình sau:

ifstream f(ten_tep, ios::in | ios::binary) ;

long dem = 0 ; char ch;

while (1)

{

f.get(ch) ;

if (!eof()) dem++ ;

else break;

}

§ 15. Nhập xuất nhị phân

15.1. Chọn kiểu nhập xuất nhị phân

Kiểu nhập xuất mặc định là văn bản. Để chọn kiểu nhập xuất nhị phân, thì trong tham số mode (của hàm tạo dạng 2 và phương thức open) cần chứa giá trị:

ios::binary

Ví dụ muốn mở tệp "DSTS.DL" để đọc-ghi theo kiểu nhị phân và gắn tệp với dòng nhập-xuất fs , ta dùng câu lệnh sau:

fstream fs("DSTS.DL" , ios::in | ios::out | ios::binary) ;

15.2. Đọc, ghi ký tự

+ Để ghi một ký tự lên tệp có thể dùng phương thức:

ostream & put(char) ;

+ Để đọc một ký tự từ tệp có thể dùng phương thức:

istream & get(char &) ;

Cần chú ý rằng: Cách đọc ghi ký tự theo kiểu văn bản khác với cách đọc ghi ký tự theo kiểu nhị phân (xem chương 10, cuốn Kỹ thuật lập trình C của tác giả)

Ví dụ để sao tệp có thể dùng thuật toán đơn giản sau:

+ Đọc một ký tự từ tệp nguồn

+ Nếu đọc thành công ( phương thức eof() = 0) thì ghi lên tệp đích và lại tiếp tục đọc ký tự tiếp theo.

+ Nếu đọc không thành công ( phương thức eof() != 0) thì kết thúc.

Chú ý là phải dùng kiểu nhập xuất nhị phân thì thuật toán mới cho kết quả chính xác. Chương trình sao tệp dưới đây viết theo thuật toán trên.

//CT7_15.CPP

// Sao tep

#include <iostream.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

void main()

{

clrscr();

char tep_nguon[40], tep_dich[40] ;

char ch;

fstream fnguon, fdich;

cout << "

Ten tep nguon: " ; cin >> tep_nguon;

cout << "

Ten tep dich: " ; cin >> tep_dich;

fnguon.open(tep_nguon,ios::in | ios::binary);

fdich.open(tep_dich,ios::out | ios::binary);

if (fnguon.bad() || fdich.bad() )

{

cout << "

Loi mo tep nguon hoac dich " ;

getch();

exit(1);

}

while(fnguon.get(ch),!fnguon.eof())

fdich.put(ch) ;

fnguon.close();

fdich.close();

cout << "

Hoan thanh" ;

getch();

}

15.3. Đọc, ghi một dẫy ký tự theo kiểu nhị phân

+ Phương thức:

ostream & write(char *buf, int n) ;

sẽ xuất n ký tự (byte) chứa trong buf ra dòng xuất.

+ Phương thức:

istream & read(char *buf, int n) ;

sẽ nhập n ký tự (byte) từ dòng nhập và chứa vào buf.

+ Phương thức

int gcount

cho biết số ký tự thực sự đọc được trong phương thức read.

Chú ý: Các phương thức write, read chỉ làm việc một cách chính xác trong kiểu nhập-xuất nhị phân.

Dưới đây là chương trình sao tệp sử dụng các phương thức write, read và gcount.

//CT7_16.CPP

// Sao tep dung write, read va gcount

#include <iostream.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

void main()

{

clrscr();

char tep_nguon[40], tep_dich[40] ;

char buf[5000];

fstream fnguon, fdich;

cout << "

Ten tep nguon: " ; cin >> tep_nguon;

cout << "

Ten tep dich: " ; cin >> tep_dich;

fnguon.open(tep_nguon,ios::in | ios::binary);

fdich.open(tep_dich,ios::out | ios::binary);

if (fnguon.bad() || fdich.bad() )

{

cout << "

Loi mo tep nguon hoac dich " ;

getch();

exit(1);

}

while(fnguon.read(buf,5000),(n=fnguon.gcount()))

fdich.write(buf,n) ;

fnguon.close();

fdich.close();

cout << "

Hoan thanh" ;

getch();

}

§ 16. Đọc ghi đồng thời theo kiểu nhị phân

Chương trình dưới đây minh hoạ cách đọc ghi đồng thời trên tệp theo kiểu nhị phân. Chương trình sử dụng các phương thức write, read, các phương thức di chuyển con trỏ tệp và các phương thức kiểm tra lỗi. Chương trình gồm 3 chức năng:

1. Nhập một danh sách thí sinh mới và ghi vào tệp TS.DL

2. Bổ sung thí sinh vào tệp TS.DL

3. Xem sửa thí sinh trên tệp TS.DL

//CT7_18.CPP

// Doc tep

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

#include <ctype.h>

#include <string.h>

#include <stdio.h>

struct TSINH

{

char ht[25];

int sobd;

float td;

};

class TS

{

private:

TSINH ts;

char ten_tep[40];

static int size;

public:

TS(char *ttep);

void tao_ds();

void bo_sung();

void xem_sua();

};

int TS::size = sizeof(TSINH);

TS::TS(char *ttep)

{

strcpy(ten_tep,ttep);

fstream f;

f.open(ten_tep,ios::binary|ios::in|ios::ate);

if (!f.good())

sots = 0 ;

else

{

sots=f.tellg()/size ;

}

}

void TS::tao_ds()

{

fstream f;

f.open(ten_tep,ios::binary|ios::out|ios::noreplace);

if (!f.good())

{

cout << "

Danh sach da ton tai" ;

cout << "

Co tao lai khong? - C/K" ;

char ch=getch();

if (toupper(ch) != 'C')

return;

else

{

f.close();

f.open(ten_tep,ios::binary|ios::out|ios::trunc);

}

}

sots=0;

while(1)

{

cout << "

Thi sinh thu: " << (sots+1) ;

cout << "

Ho ten (Bam Enter de ket thuc): ";

fflush(stdin);

gets(ts.ht);

if (ts.ht[0]==0) break;

cout << "

So bao danh: ";

cin >> ts.sobd;

cout << "

Tong diem: ";

cin >> ts.td;

f.write((char*)(&ts),size) ;

sots++ ;

}

f.close();

}

void TS::bo_sung()

{

fstream f;

f.open(ten_tep,ios::binary|ios::app|ios::nocreate);

if (!f.good())

{

cout << "

Danh sach chua tao" ;

cout << "

Co tao moi khong? - C/K" ;

char ch=getch();

if (toupper(ch) != 'C')

return;

else

{

f.close();

f.open(ten_tep,ios::binary|ios::out);

}

}

int stt=0;

while(1)

{

cout << "

Bo sung thi sinh thu: " << (stt+1);

cout << "

Ho ten (Bam Enter de ket thuc): ";

fflush(stdin);

gets(ts.ht);

if (ts.ht[0]==0) break;

cout << "

So bao danh: ";

cin >> ts.sobd;

cout << "

Tong diem: ";

cin >> ts.td;

f.write((char*)(&ts),size) ;

++stt;

}

sots += stt ;

f.close();

}

void TS::xem_sua()

{

fstream f; int ch;

f.open(ten_tep,ios::binary|ios::out|ios::in|ios::nocreate);

if (!f.good())

{

cout << "

Danh sach chua tao" ;

getch();

return ;

}

cout << "

Danh sach gom: " << sots << "thi sinh" ;

while(1)

{

cout << "

Can xem-sua thi sinh thu (Bam 0 de ket thuc): " ;

cin >> stt ;

if (stt<1 || stt > sots) break;

f.seekg((stt-1)*size,ios::beg);

f.read((char*)(&ts),size);

cout << "

Ho ten : " << ts.ht;

cout << "

So ba danh: " << ts.sobd ;

cout << "

Tong diem: " << ts.td ;

cout << "

Co sua khong? - C/K" ;

ch=getch();

if (toupper(ch)=='C')

{

f.seekg(-size,ios::cur) ;

cout << "

Ho ten: ";

fflush(stdin);

gets(ts.ht);

cout << "

So bao danh: ";

cin >> ts.sobd;

cout << "

Tong diem: ";

cin >> ts.td;

f.write((char*)(&ts),size) ;

}

}

f.close();

}

void main()

{

int chon;

clrscr();

TS t("TS.DL");

while(1)

{

clrscr();

cout << "

1. Tao danh sach thi sinh moi" ;

cout << "

2. Bo sung danh sach thi sinh" ;

cout << "

3. Xem-sua danh sach thi sinh" ;

cout << "

4. Ket thuc chuong trinh " ;

chon = getch();

chon = chon - 48;

clrscr();

if (chon==1) t.tao_ds();

else if(chon==2) t.bo_sung();

else if(chon==3) t.xem_sua();

else break;

}

clrscr();

cout << "

Hoan thanh";

getch();

}

§ 17. Xây dựng toán tử nhâp xuất đối tượng trên tệp

Trong các mục trên đã trình bầy cách dùng các toán tử nhập >> và xuất << để ghi dữ liệu kiểu chuẩn (nguyên, thực, ký tự, chuỗi ký tự) trên tệp. Mục này trình bầy cách xây dựng các toán tử dùng để đọc ghi các đối tượng của một lớp bất kỳ do người dùng định nghĩa.

Giả sử chúng ta muốn sử dụng các toán tử nhập xuất để đọc ghi các đối tượng của lớp TS. Khi đó ta đưa vào các hàm bạn toán tử nhập xuất như sau:

class TS

{

private:

// Khai báo các thuộc tính

public:

friend fstream& operator<<(fstream& fs,const TS &t);

friend fstream& operator>>(fstream& fs,TS &t);

...

} ;

Về kiểu ghi: Có thể xây dựng các toán tử để thực hiện các phép đọc ghi theo kiểu văn bản cũng như nhị phân.

Ví dụ 1: Ghi theo kiểu văn bản

Chương trình dưới đây minh hoạ cách xây dựng và sử dụng các toán tử nhập xuất đối tượng trên màn hình, bàn phím và tệp. Chương trình đưa vào lớp TS (Thí sinh) và các hàm toán tử cho phép nhập xuất các đối tượng TS trên màn hình, bàn phím và tệp. Chương trình gồm các nội dung sau:

+ Tạo tệp TS.DL dùng để đọc và ghi theo kiểu văn bản.

+ Nhập 3 thí sinh từ bàn phím và chứa vào 3 biến đối tượng t1, t2, t3.

+ Ghi nội dung của 3 biến đối tượng t1, t2, t3 lên tệp TS.DL

+ Đọc các đối tượng từ tệp TS.DL và chứa vào 3 biến t4, t5, t6

+ In các biến đối tượng t4, t5, t6 ra màn hình

+ Chuyển con trỏ về đầu tệp, dùng chu trình while để lần lượt đọc các đối tượng từ tệp và in ra màn hình. Dùng phương thức eof để kiểm tra xem đã đọc hết dữ liệu hay chưa.

//CT7_17.CPP

// Cac toan tu doc ghi doi tuong tren Tep

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

#include <ctype.h>

class TS

{

private:

char ht[25];

float td;

public:

friend ostream& operator<<(ostream& os,const TS &t);

friend istream& operator>>(istream& is,TS &t);

friend fstream& operator<<(fstream& fs,const TS &t);

friend fstream& operator>>(fstream& fs,TS &t);

};

fstream& operator>>(fstream& fs,TS &t)

{

fs.getline(t.ht,25);

fs >> t.td;

fs.ignore();

return fs;

}

ostream& operator<<(ostream& os,const TS &t)

{

os << "

Ho ten: " << t.ht ;

os << "

Tong diem: " << t.td;

return os;

}

fstream& operator<<(fstream& fs,const TS &t)

{

fs << t.ht << endl;

fs << t.td << endl;

return fs;

}

istream& operator>>(istream& is,TS &t)

{

cout << "

Ho ten: " ;

is.get(t.ht,25);

cout << "Tong diem: " ;

is >> t.td ;

is.ignore();

return is;

}

void main()

{

clrscr();

fstream f("TS.DL",ios::out | ios::in | ios::trunc);

TS t1,t2,t3,t4,t5,t6,t;

cin >> t1 >> t2 >> t3;

f << t1 << t2 <<t3;

f.seekg(0);

f>>t4>>t5>>t6;

cout << t4 << t5 << t6;

f.seekg(0);

while (f>>t ,!f.eof())

cout << t;

f.close();

cout << "

Xong";

getch();

}

Ví dụ 2 : Ghi theo kiểu nhị phân

Chương trình dưới đây cũng có các chức năng như chương trình trong ví dụ 1 bên trên, nhưng cách ghi đọc tệp theo kiểu nhị phân.

//CT7_19.CPP

// Cac toan tu doc ghi doi tuong tren Tep

// Kieu nhi phan

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <conio.h>

#include <stdlib.h>

#include <ctype.h>

class TS

{

private:

char ht[25];

float td;

static int size;

public:

friend ostream& operator<<(ostream& os,const TS &t);

friend istream& operator>>(istream& is,TS &t);

friend fstream& operator<<(fstream& fs,const TS &t);

friend fstream& operator>>(fstream& fs,TS &t);

};

int TS::size= sizeof(TS);

fstream& operator>>(fstream& fs,TS &t)

{

fs.read( (char*)(&t) , t.size);

return fs;

}

fstream& operator<<(fstream& fs,const TS &t)

{

fs.write( (char*)(&t) , t.size);

return fs;

}

ostream& operator<<(ostream& os,const TS &t)

{

os << t.ht << endl;

os << t.td << endl;

return os;

}

istream& operator>>(istream& is,TS &t)

{

cout << "

Ho ten: " ;

is.get(t.ht,25);

cout << "Tong diem: " ;

is >> t.td ;

is.ignore();

return is;

}

void main()

{

clrscr();

fstream f("THU.DL",ios::binary | ios::out|ios::in|ios::trunc);

TS t1,t2,t3,t4,t5,t6,t;

cin >> t1 >> t2 >> t3;

f << t1 << t2 <<t3;

f.seekg(0);

f>>t4>>t5>>t6;

cout << t4 << t5 << t6;

f.seekg(0);

while( f>>t ,!f.eof() )

cout << t;

f.close();

cout << "

Xong";

getch();

}

§ 18. Hệ thống các lớp stream

Mục này hệ thống lại các lớp stream mà chúng ta đã sử dụng bên trên để tổ chức xuất nhập trên màn hình, bàn phím, máy in và tệp

18.1. Sơ đồ quan hệ giữa các lớp

istream fstreambase ostream

ifstream ofstream

fstream

18.2. Các phương thức của lớp ios

1. int bad()

2. void clear(int=0)

3. int eof()

4. int fail()

5. int fill()

6. int fill(char)

7. long flags()

8. long flags(long)

9. int good()

10. int precision()

11. int precision(int)

12. long setf(long)

13. long setf(long setbits, long field)

14. long unsetf(long)

15. int width()

16. int width(int)

18.3. Các phương thức của lớp istream

1. operator>>

2. int gcount()

3. int get()

4. istream& get(char*, int, char = '

')

5. istream& get(char&)

6. istream& getline(char*, int, char = '

')

7. istream& ignore(int n = 1, int delim = EOF)

8. int peek()

9. istream& putback(char)

10. istream& read(char*, int)

11. istream& seekg(long)

12. istream& seekg(long, seek_dir)

13. long tellg()

18.4. Các phương thức của lớp ostream

1. operator<<

2. ostream& flush()

3. ostream& put(char)

4. ostream& seekp(long)

5. ostream& seekp(long, seek_dir)

6. long tellp()

7. ostream& write(char*, int)

18.5. Các phương thức của lớp fstreambase

void close()

18.6. Các phương thức của lớp ifstream

1. ifstream()

2. ifstream(const char*, int = ios::in, int = filebuf::openprot)

3. ifstream(int )

4. ifstream(int , char*, int)

5. void open(const char*, int = ios::in, int = filebuf::openprot)

18.7. Các phương thức của lớp ofstream

1. ofstream()

2. ofstream(const char*, int = ios::out, int = filebuf::openprot)

3. ofstream(int )

4. ofstream(int , char*, int)

5. void open(const char*, int = ios::out, int = filebuf::openprot)

18.8. Các phương thức của lớp fstream

1. fstream()

2. fstream(const char*, int, int = filebuf::openprot)

3. fstream(int )

4. fstream(int , char*, int)

5. void open(const char*, int, int = filebuf::openprot)

Bạn đang đọc truyện trên: Truyen2U.Pro