Bài giảng lập trình hướng đối tượng - Thầy Cường Học viện bưu chính viễn thông TP HCM
Trang 1Chương 8
Nhập/Xuất C++ nâng cao
• Tạo Bộ thao tác Nhập/Xuất
• Nhập/Xuất File
• Nhập/Xuất File nhị phân không định dạng
• Các hàm Nhập/Xuất nhị phân
• Truy cập ngẫu nhiên
• Kiểm tra trạng thái Nhập/Xuất
• Nhập/Xuất theo đơn đặt hàng và các File
• Nhập/Xuất theo mảng
Trang 3I/ Tạo Bộ thao tác Nhập/Xuất
Ngoài bộ chèn và bộ chiết, có thể sửa đổi hệ thống nhập/xuất của C++ bằng cách
tạo ra bộ thao tác nhập/xuất tự tạo Nó có hai lý do :
* một bộ thao tác có thể hợp nhất một dãy các thao tác nhập/xuất thành một bộ thao tác tự tạo
* một bộ thao tác nhập/xuất tự tạo cần thiết khi nhập/xuất trên các thiết bị không chuẩn (máy in đặc biệt hay hệ thống nhận dạng quang học)
Có hai loại bộ thao tác, một dùng cho các stream nhập, một dùng cho các stream
xuất Ngoài ra người ta còn phân thành hai nhóm : bộ thao tác có tham số và bộ thao
tác không có tham số, chúng khác nhau về cách tạo ra bộ thao tác Việc tạo ra bộ thao tác có tham số không thuộc phạm vi bài giảng này
• Dạng tổng quát của các bộ thao tác xuất không có tham số :
ostream &manip_name(ostream &stream)
{
// program codes
return stream
}
manip_name tên của bộ thao tác được tạo ra
Lưu ý : bộ thao tác trên có đối số là một tham chiếu đến stream mà nó thao tác, khi gọi bộ này, không được kèm theo tên bộ thao tác bất kỳ một đối số nào
• Dạng tổng quát của các bộ thao tác nhập không có tham số :
istream &manip_name(istream &stream)
{
// program codes
return stream
}
Trang 4Một bộ thao tác nhập nhận một tham chiếu đến stream mà nó thao tác Stream này phải được trả về bởi bộ thao tác
Ví dụ 1.1 Chương trình tạo ra bộ thao tác xuất setup() Khi được gọi, bộ này sẽ thiết lập độ rộng trường là 10, độ chính xác là 4 và dấu "* " là ký tự lấp đầy
Trang 5cout << atn << "High voltage circuit\n";
cout << note << "Turn off all lights\n";
// A simple input manipulator
istream &getpass(istream &stream)
{
cout << '\a'; // sound bell
cout << "Enter password: ";
Trang 63 Hãy tạo một bộ thao tác mới tên là skipchar() dùng để đọc và bỏ qua mười ký tự liên tiếp từ một stream nhập
Trang 7II/ Nhập/Xuất File
Trong C++, một file được mở bằng cách liên kết nó với một stream Có ba loại stream
: nhập, xuất và nhập/xuất Trước khi mở file phải tạo ra stream tương ứng
Để tạo một stream nhập, cần khai báo stream đó thuộc lớp ifstream
Để tạo một stream xuất, cần khai báo stream đó thuộc lớp ofstream
Đối với stream thực hiện cả thao tác nhập và xuất, cần khai báo stream đó thuộc lớp
fstream
Ví dụ
ifstream in; // input stream
ofstream out; // output stream
fstream io; // input and output stream
2/ Mở file
a/ Sau khi tạo ra một stream, có thể mở file gắn với stream đó bằng hàm open()
Hàm này là hàm thành phần của ba stream nói trên, dạng tổng quát :
void open(const char *filename, int mode, int access);
filename tên của file cần mở, có thể đi kèm với đường dẫn
mode xác định chế độ của file được mở
access xác định cách truy cập file
@ Các giá trị nguyên của mode, có thể là một hay nhiều giá trị (được kế thừa bởi
fstream.h)
• Chế độ ios::app làm cho kết xuất được thêm vào cuối của file Dùng cho các
file xuất
Trang 8• Chế độ ios::ate cho phép chuyển đến vị trí cuối file ngay sau khi file được mở
Lưu ý, mặc dù đang ở vị trí cuối file, các thao tác nhập/xuất vẫn có thể xuất hiện ở bất cứ vị trí nào của file
• Chế độ ios::in xác định file được mở là file nhập Chế độ ios::out xác định file
được mở là file xuất
Tuy nhiên, việc tạo ra một stream bằng các khai báo nó thuộc lớp ifstream ngầm định nó là một stream nhập, cũng như khai báo stream thuộc lớp ofstream ngầm định nó là một stream xuất Trong hai trường hợp trên, không cần phải xác định các giá trị ios::in và ios::out khi mở file
• Chế độ ios::binary làm cho file được mở ở chế độ nhị phân
Theo mặc định, tất cả các file được mở ở chế độ văn bản (text mode) Trong chế độ
văn bản, sẽ có một số trường hợp ký tự sẽ bị chuyển đổi Ví dụ, ký tự tạo dòng mới ( '\n' ) sẽ được chuyển thành chuổi liên tiếp nhau của hai ký tự xuống dòng (carriage
return, mã 13), và thêm một dòng (line-feed, mã 10)
Khi hoạt động ở chế độ nhị phân, sẽ không có bất kỳ một trường hợp chuyển đổi ký tự
nào Bất kỳ một file nào đều có thể hoạt động ở hai chế độ văn bản hay nhị phân
• Chế độ ios::nocreate làm cho việc gọi hàm open() bị thất bại nếu file được mở là một file mới (chưa có sẵn trên điã) Còn ios::noreplace làm cho việc gọi hàm
open() bị thất bại nếu file được mở đã có sẵn trên điã
• Chế độ ios::trunc làm cho khi file có sẵn trùng tên với file được mở sẽ bị xoá
sạch nội dung
Có thể sử dụng kết hợp hai hay nhiều chế độ ở trên bằng phép toán OR
@ Các giá trị của đối số access xác định cách truy cập file
Giá trị ngầm định của đối số này là filebuf::openprot (filebuf là lớp cha của các
lớp stream) Trong môi trường UNIX nó có giá trị 0x644 đối với các file thông thường
Trong môi trường DOS/WINDOWS giá trị của đối số access qui định mã thuộc tính của file, bao gồm :
Trang 9Thuộc tính Ý nghiã
0 Archive File bình thường : ghi / đọc (mặc định)
1 Read only File chỉ đọc
Có thể sử dụng kết hợp hai hay nhiều thuộc tính ở trên bằng phép toán OR
• Ví dụ đoạn chương trình sau minh hoạ việc mở file bình thường trong DOS/WINDOWS
ofstream out ; // khai báo stream xuất
out.open ("test.txt", ios::out, 0) ;
hoặc out.open("test.txt") ; // lời gọi ngắn gọn, thông thường
• Để mở file nhập/xuất, cần phải xác định cả hai chế độ ios::in và ios::out cho đối số mode, được minh hoạ sau đây Lưu ý, không có giá trị mặc định cho trường hợp này
fstream mystream; // khai báo stream xuất
mystream.open ("test.txt", ios::in | ios::out) ;
Nếu hàm open() không thực thi được, stream được trả về sẽ mang giá trị không Do đó, cần kiểm tra lại việc mở file có thành công hay không, trước khi truy xuất nó Ví dụ :
Trang 10Thông thường, không cần dùng hàm open() để mở file, vì các lớp ifstream, ofstream và fstream đã có sẵn các hàm tạo dùng để mở file một cách tự động Các
hàm tạo này có các đối số giống hệt như của hàm open() Dạng thông thường mở file
để nhập :
ifstream mystream("test.txt");
Cần kiểm tra lại việc mở file có thành công hay không, trước khi truy xuất nó
3/ Đóng file
Dùng hàm void close() Hàm này không có đối số và không trả về giá trị nào cả
Ví dụ đóng một file đang gắn với stream mystream, cần gọi :
mystream.close();
• Để kiểm tra tình trạng hết file dùng hàm eof()
int eof();
Hàm trả về giá trị ≠ 0 khi đang ở tình trạng cuối file
giá trị = 0 ngược lại
Ví dụ mystream.eof();
4/ Truy xuất nội dung trên file
Toán tử xuất << và toán tử nhập >> được sử dụng để truy xuất nội dung trên file, cần phải thay các stream cin và cout bằng stream liên kết với file
Thông tin lưu trên file có cùng định dạng như khi nó được trình bày trên màn hình
Do đó, các file tạo ra bằng toán tử xuất << và các file được đọc vào bằng toán tử nhập >> đều là các file văn bản có định dạng
Ví dụ 2.1 Tạo một file xuất, ghi lên file, đóng file Mở file đó như một file nhập, đọc nội dung của file
#include <iostream.h>
#include <fstream.h>
Trang 12Hello!
100 64
Ví dụ 2.2 Chương trình đọc từ bàn phím một chuổi ký tự, ghi lên file, đóng file Chương trình kết thúc khi nhập một dòng trống Mở file đó như một file nhập, đọc nội dung của file
// WRITE.CPP
#include <iostream.h>
#include <fstream.h>
#include <iomanip.h>
int main(int argc, char *argv[]) // argc : số các tham số ,
// *argv[] : chứa nội dung các tham số {
if(argc!=2) {
cout << "Usage: WRITE <filename> \n";
// argv[0] = '\<path>\WRITE.EXE' // argv[1] = '<filename>'
Trang 13ifstream fin(argv[1]); // open input file to read
ofstream fout(argv[2]); // create output file to write
Trang 142 Viết chương trình để ghi bảng dữ liệu sau đây vào một file có tên là phone.txt
Nguyễn Công Trứ 08.8980168
Phạm Ngũ Lão 04.2325678
Trần Nguyên Hãn 04.6781234
3 Viết chương trình đếm số lượng từ có trong một text file bất kỳ
III/ Nhập/Xuất File nhị phân không định dạng
Việc nhập/xuất file nhị phân không định dạng linh động hơn nhập/xuất text file
Trang 151/ Các hàm nhập/xuất cấp thấp
• Đọc ghi từng ký tự
Dạng phổ biến của chúng :
istream &get (char &ch);
ostream &put(char ch);
Hàm get() đọc một ký tự từ stream tương ứng và gán ký tự đó vào nội dung của ch Hàm trả về một tham chiếu đến stream, nó sẽ là NULL nếu đến cuối file
Hàm put() ghi (xuất) nội dung của ch ra một stream và trả về một stream
• Đọc ghi các khối dữ liệu nhị phân
istream &read(unsigned char *buf, int num);
ostream &write(const unsigned char *buf, int num);
Hàm read() đọc một số lượng num các byte từ stream tương ứng và ghi lên một vùng đệm được trỏ bởi buf
Hàm write() ghi một số lượng num các byte ở vùng đệm được trỏ bởi buf đến stream
tương ứng
Nếu file được đọc hết trước khi đủ num ký tự, hàm read() hoàn tất bình thường và vùng đệm chứa hết số ký tự của lần đọc cuối Để biết lần cuối này có bao nhiêu ký tự được đưa ra vùng đệm, dùng hàm gcount() có dạng
int gcount();
Hàm sẽ trả về giá trị là tổng số ký tự đọc được ở lần đọc cuối ngay trước khi hết file
Lưu ý : việc thiết lập chế độ ios::binary chỉ nhằm mục đích ngăn ngừa việc chuyển
đổi ký tự Khi sử dụng các hàm nhập/xuất file nhị phân không định dạng, không cần
mở file ở chế độ ios::binary
Ví dụ 3.1 Chương trình xuất nội dung của một file bất kỳ ra màn hình
// PR.CPP
Trang 17Lưu ý : chương trình dùng hàm get() để đọc ký tự từ stream cin vào Nếu dùng toán
tử << các ký tự khoảng cách sẽ bị bỏ qua, trong khi sử dụng hàm get() các ký tự
khoảng cách vẫn được nhận vào
Ví dụ 3.3 Chương trình dùng hàm write() để ghi một số thực kiểu double và một
chuỗi ký tự lên một file có tên là test.txt
Trang 18char str[] = "This is a test";
out.write((char *) &num, sizeof(double));
out.write(str, strlen(str));
out.close();
return 0;
}
Lưu ý : việc khai báo linh hoạt (char *) khi gọi hàm write() là cần thiết khi vùng
đệm không được định nghiã là một dãy ký tự Do C++ kiểm tra kiểu rất kỹ, nên một con trỏ chỉ đến kiểu dữ liệu này không thể được tự động đổi thành một con trỏ của kiểu khác
Ví dụ 3.4 Chương trình dùng hàm read() để đọc nội dung text file test.txt từ ví dụ
Trang 19Ví dụ 3.5 Chương trình ghi một mảng các giá trị kiểu double một file và sau đó
đọc lại nội dung file Chương trình còn thông báo số lượng các ký tự đã đọc
Trang 20Bài tập III
1 Viết lại chương trình trong các bài tập 1 và 3 phần II chương 8, thay bằng các hàm get(), put(), read() hoặc write() sao cho phù hợp nhất
2 Cho lớp sau đây, viết chương trình ghi nội dung của lớp lên một file, sử dụng bộ chèn
Trang 21}
// create inserter here
};
IV/ Các hàm Nhập/Xuất nhị phân
Hai dạng quá tải hàm get() thường dùng :
• istream &get(char *buf, int num, char delim = '\n') ;
Hàm get() đọc các ký tự vào một dãy được chỉ bởi buf, với số lượng là num ký tự hoặïc khi đọc vào một ký tự phân cách delim
Nếu trong đối số của hàm không có đối số delim, giá trị mặc định của ký tự phân
cách là ký tự sang dòng mới '\n' Nếu ký tự phân cách xuất hiện trong stream nhập, ký tự sẽ không bị lấy ra, thay vào đó, ký tự này sẽ ở trong stream cho đến khi gặp thao tác nhập khác
• int get() ;
Hàm get() trả về ký tự kế tiếp trong stream Nếu gặp tình trạng hết file, hàm trả về giá trị EOF (giống như hàm getc() trong ngôn ngữ C)
• istream &getline(char *buf, int num, char delim = '\n') ;
Hàm getline() cũng thực hiện nhập dữ liệu, đọc và bỏ các ký tự phân cách ra khỏi
stream (nhập được một chuỗi) Các đối số tương tự như hàm get() ở trên
• int peek() ;
Hàm peek() có thể lấy ký tự tiếp theo trong stream nhập mà không cần phải đưa nó
ra khỏi stream Hàm trả về giá trị là ký tự tiếp theo của stream, nếu hết file, hàm trả về giá trị EOF
• istream &putback(char ch) ;
Hàm putback() trả lại giá trị vừa đọc vào stream Đối số ch là ký tự vừa được đọc
vào từ stream
Trang 22• ostream &flush() ;
Mỗi khi thao tác xuất được thực hiện, dữ liệu không được đưa ra ngay thiết bị vật lý liên kết với stream, mà thông tin đó được đưa vào vùng đệm Khi vùng đệm bị đầy,
dữ liệu của vùng đệm mới được xuất ra điã Tuy nhiên, nếu muốn thông tin xuất ra
điã trước khi đầy vùng đệm, có thể dùng hàm flush()
Nên dùng hàm này khi máy tính hoạt động với nguồn điện hoạt động không ổn định
Ví dụ 4.1 Dùng hàm getline() nhập vào một chuỗi bất kỳ
// Use getline() to read a string that contains spaces
cout << "Enter your name: ";
cin.getline(str, 79); // tương tự như hàm gets(str) ;
cout << str << '\n';
return 0;
}
Ví dụ 4.2 Dùng hàm peek() và putback() trợ giúp xử lý dễ dàng khi không biết
ược kiểu của thông tin nhập vào
Trang 23out << 123 << "this is a test" << 23;
out << "Hello there!" << 99 << "sdf" << endl;
while(isdigit(*p = in.get() ) ) p++; // read integer
in.putback(*p); // return char to stream
cout << "Integer: " << atoi(str);
}
else if(isalpha(ch)) { // read a string
while(isalpha(*p = in.get() ) ) p++;
cout << "String: " << str;
}
Trang 243 Viết chương trình minh hoạ hoạt động của hàm flush() ?
V/ Truy cập ngẫu nhiên
• C++ cung cấp việc truy cập ngẫu nhiên một file qua các hàm :
istream &seekg(streamoff offset, seek_dir origin) ;
ostream &seekp(streamoff offset, seek_dir origin) ;
streamoff là kiểu định nghiã trong iostream.h, nó có thể lưu trữ được giá trị hợp lệ
lớn nhất mà offset có thể nhận được
seek_dir là một kiểu hằng liệt kê với các giá trị như sau :
Giá trị Ý nghiã ios::beg tìm từ vị trí đầu file ios::cur tìm từ vị trí hiện hành ios::end tìm từ vị trí cuối file
Hàm seekg() dời con trỏ get của file tương ứng một đoạn bằng offset byte từ một vị
trí được xác định bằng origin
Trang 25Hàm seekp() dời con trỏ put của file tương ứng đi một đoạn bằng offset byte tính từ
vị trí được xác định bởi origin
• Hệ thống nhập/xuất của C++ sử dụng hai con trỏ để quản lý một file
Thứ nhất là con trỏ get để trỏ đến vị trí sẽ được đưa vào nếu file được thực hiện một
tác vụ nhập nữa
Thứ hai là con trỏ put xác định vị trí của file mà một tác vụ xuất tiếp theo sẽ đọc ra
từ đó
Mỗi khi thực hiện thao tác nhập/xuất file con trỏ sẽ được tự động tăng lên để chỉ vào
vị trí kế tiếp Tuy nhiên, vẫn có thể dùng hàm seekg() và seekp() để truy cập file một cách ngẫu nhiên
• Có thể xác định vi trí hiện hành của các con trỏ get và put bằng các hàm :
streampos tellg() ;
streampos tellp() ;
streampos là kiểu định nghiã trong iostream.h, nó có thể lưu trữ được giá trị
lớn nhất mà hàm có thể trả về được
Ví dụ 5.1 Dùng hàm seekp() cho phép đổi một ký tự xác định của file Tham số của chương trình gồm tên file, số thứ tự của byte cần thay thế và ký tự cần thay