cslt 1 chuong 5 kieu du lieu co cau truc

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

Bài tập chương 4

1.  Viết một chương trình hiển thị bảng cửu chương, mỗi chương là một lần gọi hàm.

2.  Viết các hàm để :

a.   Tính và cho giá trị là phần dư của phép chia hai số nguyên khá lớn.

b.  Tính và cho giá trị là số nguyên kiểu long int bằng phần nguyên của một số thực.

c.   Tính và cho giá trị là số thực bằng phần nguyên của một số thực kiểu double.

d.  Tính và cho giá trị là phần phân của một số thực.

e.   Cho giá trị là xâu bằng cách cắt ra n kí tự trái (hoặc phải) nhất của một xâu s.

3.  Trong một chương trình, viết các hàm tìm ước số chung lớn nhất và bội số chung nhỏ nhất của hai số a, b và sử dụng trong các bài toán:

a.     Tối giản một phân số

b.    Tìm ước số chung lớn nhất và bội số chung nhỏ nhất của dãy n số nguyên dương nhập từ bàn phím

4.  Lập hàm kiểu int để kiểm tra một số tự nhiên n khá lớn có là số nguyên tố không. Nếu là số nguyên tố hàm cho giá trị 1, ngược lại, hàm cho giá trị 0.

5.  Viết chương trình phân tích số tự nhiên n ra thừa số nguyên tố có sử dụng hàm xác định một số có là số nguyên tố hay không.

6.  Trong một chương trình, viết các hàm để giải phương trình bậc nhất, hệ phương trình bậc nhất, phương trình bậc hai và một hàm main() để điều hành.

7.  Lập một hàm tính giá trị một đa thức bậc n rồi dùng hàm main() gọi đến hàm đó và hiển thị giá trị của đa thức trong [a,b] với bước nhảy là h chạy từ a.

8.  Giả sử hàm f(x) bậc n đơn điệu trong [a, b] và có f(a) x f(b) <0. Viết chương trình tính nghiệm xấp xỉ của f(x) trong đoạn [a, b].

Gợi ý: Liên tục chia đoạn [a, b] làm hai phần và giữ lại để xét nửa nào có giá trị hàm ở hai đầu mút là trái dấu. Khi tại điểm chia, hàm  f(x) ≈ 0 hoặc đoạn chia đủ nhỏ thì lấy điểm chia làm nghiệm.

9.  Viết hai hàm để so sánh và đổi chỗ hai xâu s1 và s2 có cùng độ dài rồi sử dụng nó để sắp xếp một mảng xâu.

10.      Dùng hàm đệ quy để hiển thị 1000 số trong dãy Fibonacy được xác định theo quy luật: Hai số đầu của dãy bằng 1, các số tiếp theo có giá trị bằng tổng hai số đứng trước trong dãy.

Chương 5: Kiểu dữ liệu có cấu trúc 

5.1. Khái niệm về kiểu dữ liệu có cấu trúc

Từ đầu chương trình, ta đã làm việc với các kiểu dữ liệu cơ sở (còn gọi là kiểu dữ liệu đơn giản), như kiểu: int , char, float, .... Trong thực tế, có những bài toán, các kiểu dữ liệu cơ sở không đủ khả năng mô tả đầy đủ các thuộc tính của dữ liệu khi các dữ liệu có mối liên hệ với nhau. Chẳng hạn, trong ví dụ:

Dữ liệu về điểm thi của một lớp sinh viên, gồm các thuộc tính: Họ tên, số báo danh, ngày sinh, điểm thi môn 1, điểm thi môn 2, điểm thi môn 3, phân loại. Nếu chúng ta sử dụng kiểu dữ liệu cơ sở sẽ vô cùng khó khăn trong việc xác định dữ liệu của mỗi sinh viên, gồm:

·    Thông tin của sinh viên viên nào;

·    Từng thuộc tính cần biết của sinh viên đó (mỗi thuộc tính sẽ có kiểu dữ liệu khác nhau: Họ tên, số báo danh, phân loại: kiểu char; Điểm thi 3 môn: kiểu int; Ngày sinh gồm 3 dữ liệu (ngày, tháng, năm): kiểu int.

Để tiện lợi cho người lập trình và cũng để tăng khả năng ứng dụng của ngôn ngữ, nhà thiết kế ngôn ngữ C cho phép người lập trình có thể tự định nghĩa lấy một kiểu dữ liệu mới cho chương trình của mình, trong đó, một kiểu dữ liệu có thể bao gồm các phần tử dữ liệu có kiểu khác nhau và có mỗi liên hệ với nhau, gọi là dữ liệu có cấu trúc.

Vậy, kiểu dữ liệu có cấu trúc là một kiểu dữ liệu phức hợp, được tạo thành từ các phần tử dữ liệu khác nhau nhưng có mối liên hệ với nhau. Mỗi phần tử dữ liệu có thể là một dữ liệu có cấu trúc khác hay một dữ liệu cơ sở.

5.2. Khai báo và truy nhập vào phần tử STRUCT

Trong các kiểu dữ liệu có cấu trúc, dữ liệu kiểu struct là một kiểu đặc trưng nhất, nó hoàn toàn giống kiểu record trong Pascal.

5.2.1. Khai báo kiểu struct

Struct là một kiểu dữ liệu trong C nhưng là kiểu dữ liệu tự tạo, vì thế, trước khi sử dụng, phải khai báo (định nghĩa) để C hiểu nó chứa các phần tử dữ liệu nào, mỗi phần tử lại chứa phần tử nào, ... Mẫu khai báo như sau:

struct  tên_cấu_trúc

{

Kiểu_1    biến_1;

Kiểu_2    biến_2;

.......

Kiểu_n    biến_n;

} Danh_sách_biến;

Trong đó:

·    struct: Là từ khoá;

·    Tên_cấu_trúc: Là một tên hợp lệ để xác định kiểu dữ liệu của người sử dụng tự xác định. Thành phần này có thể có khoặc không;

·    Kiểu_i (i=1-n): Là một trong các kiểu cơ sở hay kiểu struct, nếu là kiểu struct thì kiểu cấu trúc đó đã phải được khai báo trước đó, ta gọi đó là các cấu trúc lồng nhau;

·    Biến_i  (i=1-n): Là tên các biến thành phần của kiểu struct; Nếu ở cùng mức, chúng phải có tên khác nhau trong một cấu trúc.

·    Danh_sách_biến: Là tập hợp các tên biến hoặc con trỏ. Thành phần này có thể có hoặc không.

·    Chỗ nào có thể đặt được lời khai báo biến thì có thể đặt khai báo dữ liệu kiểu cấu trúc.

Khi đó, máy sẽ hiểu là: Có một kiểu dữ liệu mới có cấu trúc struct là tên_cấu_trúc gồm các phần tử là biến_1, biến_2, ..., biến_n và các biến trong danh_sách_biến đều có kiểu dữ liệu mới đó.

Ví dụ1:

struct  date

{

int  ngay;

int  thang;

int  nam;

} NGAY_DEN, NGAY_DI ;

Ví dụ 1 khai báo hai biến NGAY_DEN, NGAY_DI  đều có kiểu struct  date gồm 3 biến thành phần: ngay, thang, nam đều thuộc kiểu int.

Ví dụ 2:

struct  danh_sach

{

char  ho_ten[30] ;

char  sbd[6] ;

struct date ngay_sinh;   /*cấu trúc lồng nhau */

int  diem_1;

int  diem_2;

int  diem_3;

char  phan_loai[4];

} sv_a, sv_b ;

Cố nhiên, trong ví dụ này, kiểu dữ liệu struct date đã phải khai báo trước (chẳng hạn như ví dụ 1).

Chú ý:

[1]  Mỗi cấu trúc là một kiểu dữ liệu tự khai báo, nó sẽ gắn với một hay nhiều biến. Nếu trong khai báo struct có danh_sách_biến thì không nhất thiết phải có tên_cấu_trúc; trường hợp không có danh_sách_biến thì nhất thiết phải có tên_cấu_trúc để thực hiện bước tiếp theo là: Gán cấu trúc đã khai báo với một hay một số biến nào đó, theo mẫu sau:

struct Tên_cấu_trúc  Danh_sách_biến ;

Ví dụ:  Có thể khai báo như ở ví dụ 1 trên đây hoặc sử dụng một trong hai cách khai báo sau:

Cách 1: Khai báo kiểu đồng thời với khai báo biến.

struct 

{

int  ngay;

int  thang;

int  nam;

} NGAY_DEN, NGAY_DI ;

Cách 2: Khai báo kiểu rồi khai báo biến.

struct  date

{

int  ngay;

int  thang;

int  nam;

} ;

struct date NGAY_DEN, NGAY_DI ;

Sự khác nhau giữa khai báo có và không có tên_cấu_trúc ở chỗ: Nếu có, ta có thể sử dụng cấu trúc đã khai báo với nhiều biến khác nhau, ở nhiều chỗ khác nhau, chẳng hạn ở cách 2, có thể có thêm khai báo biến “struct date ngay_sinh;”; Nếu không, chỉ các biến có tên trong danh_sách_biến  sử dụng được cấu trúc này.

 [2] Sau khi khai báo biến cấu trúc, ta có thể khởi đầu giá trị cho các phần tử của biến cấu trúc tương tự như với một biến mảng. Ví dụ;

struct ten_tuoi

{ char  ten[10];

int  tuoi;

};

struct  ten_tuoi  x = { “BAC QUANG”, 40};

[3]  Nếu chỉ có khai báo kiểu struct, chưa có khai báo biến, máy chưa cấp phát bộ nhớ. Trường hợp đã khai báo cả kiểu struct, cả biến, máy sẽ cấp phát bộ nhớ cho từng biến theo thứ tự; Mỗi biến có độ dài bằng tổng độ dài của các phần tử thành phần.

5.2.2. Truy nhập vào phần tử struct

Khác với các biến thông thường, các phần tử struct, muốn truy nhập đến nó, ngoài việc phải chỉ ra tên biến phần tử, ta còn phải chỉ ra nó gắn với biến nào có cấu trúc struct đó. Cách viết như sau:

Trường hợp 1: Đối với biến có cấu trúc struct, ta viết:

                      tên_biến . biến_phần_tử

Trường hợp 2: Đối với biến con trỏ có cấu trúc struct, ta viết:

                      tên_biến_con_trỏ -> biến_phần_tử

  (dấu mũi tên -> tạo thành từ hai kí tự: gạch ngang “-” và lớn hơn “>”)

Sau khi biết cách chỉ ra các phần tử struct, ta hoàn toàn có thể coi mỗi phần tử này như một biến bình thường để sử dụng.

Ví dụ 1: Yêu cầu nhập họ tên, số báo danh, ngày sinh, điểm thi hai môn rồi hiển thị lên màn hình thông tin sau:

THONG BAO KET QUA HOC TAP

Ho ten: ...            So bao danh: ...

Ngay sinh:  ...

Diem trung binh cong: ...

Văn bản chương trình như sau:

#include  <stdio.h>

#include  <conio.h>

main()

{

struct  date

{

int  ngay;

int  thang;

int  nam;

} ;

struct  danh_sach

{

char  ho_ten[30] ;

char  sbd[6] ;

struct date ngay_sinh;

int  diem_1;

int  diem_2;

 } bien1 ;

clrscr();

/* Phần nhập */

printf(“ Ho ten sinh vien: ”); gets(bien1.ho_ten);

printf(“ So bao danh: ” ); gets(bien1.sbd);

printf(“ Ngay sinh: ”); scanf(“%d”, &bien1.ngay_sinh.ngay);

printf(“ Thang sinh: ”); scanf(“%d”, &bien1.ngay_sinh.thang);

printf(“ Nam sinh: ”); scanf(“%d”, &bien1.ngay_sinh.nam);

printf(“ Diem 1: ”); scanf(“%d”, &bien1.diem_1);

printf(“ Diem 2: ”); scanf(“%d”, &bien1.diem_2);

/* phần xuất */

clrscr();

printf(“THONG BAO KET QUA HOC TAP

”);

printf(“Ho ten sinh vien: %s ”,  bien1.ho_ten);

printf(“So bao danh: %s

”,  bien1.sbd);

printf(“Ngay sinh: %d-%d-%d

”,bien1.ngay_sinh.ngay,

 bien1.ngay_sinh.thang, bien1.ngay_sinh.nam);

printf(“Diem trung binh cong: %.2f

”,  (bien1.diem_1

 + bien1.diem_2)/2.0 );

getch();

}

Ví dụ 2: Sử dụng con trỏ có cấu trúc để truy nhập các phần tử struct.

#include  <stdio.h>

#include  <conio.h>

struct  date

{  int  ngay;

int  thang;

int  nam;

} ;   /* Có thể viết dòng này là: } x = {20,11,1960};

   thay cho viết dòng (1) trong hàm main */

main()

{

struct date x = { 20, 11, 1960};     /*dòng (1)*/

struct date *pd;

pd=&x;

clrscr();

printf(“Anh ay sinh ngay %2d ”, pd->ngay);

printf(“ thang %2d ”, pd->thang);

printf(“ nam %4d ”, pd->nam);

getch();

}

Lưu ý rằng, khi khai báo pd là một biến con trỏ kiểu cấu trúc, nó mới chỉ được cấp một địa chỉ và ghi nhận là một biến con trỏ chỉ đến cấu trúc date, còn biến cấu trúc mà con trỏ chỉ đến vẫn chưa có. Để pd chỉ đến một biến cụ thể, phải gán địa chỉ biến đó cho pd bằng phép gán:  pd = &x;

5.3. Mảng cấu trúc

5.3.1. Khai báo biến mảng cấu trúc

Biến cấu trúc được sử dụng giống như một biến bình thường: Có thể sử dụng dạng biến đơn hay biến mảng. Để được phép sử dụng các biến mảng cấu trúc, phải tạo ra mảng cấu trúc. Mảng cấu trúc là mảng mà mỗi phần tử của nó là một biến cấu trúc.

Để sử dụng một kiểu cấu trúc đã khai báo vào việc khai báo một mảng cấu trúc, ta sử dụng cú pháp tương tự như khai báo mảng, như sau;

struct  tên_cấu_trúc  tên_mảng[kích thước] ;

Ví dụ:

struct  date

{

int  day;

int  month;

int  year;

};

struct  date ngay[50];

Khi đó, C sẽ tạo ra một mảng ngay là mảng một chiều, gồm 50 biến mảng kiểu struct date ngay[0], ngay[1], ..., ngay[49]; Mỗi biến mảng đều có ba phần tử là: day, month, year.

Tương tự như vậy, có thể tạo ra các mảng cấu trúc nhiều chiều.

5.3.2. Truy nhập phần tử mảng cấu trúc

Việc truy nhập các phần tử của một mảng cấu trúc hoàn toàn tương tự như truy nhập tới các biến mảng thông thường. Có thể truy nhập trực tiếp qua tên (xem ví dụ 1 của 5.2.2) hay thông qua con trỏ  (xem ví dụ 2 của 5.2.2). Ví dụ:

Ví dụ 1: Gán giá trị ban đầu cho mảng cấu trúc chứa tên và ngày sinh của một số sinh viên, sử dụng truy nhập phần tử mảng cấu trúc qua tên.

#include  “Stdio.h”

#include  “Conio.h”

main()

{

struct date

{

char ten[40];

int ngay;

int thang;

int nam;

};

struct  date  ngay_sinh[ ] = { {“ANH”, 20, 11, 1960},

{“MAI” ,21,12,1967}, “TUAN”, 2,8,1991};

clrscr();

for  (i = 0; i < 4; i++)

          printf(“Ho ten:  %s  Sinh ngay: %2d - %2d - %4d \ n ”,

   ngay_sinh[i].ten, ngay_sinh[i].ngay, ngay_sinh[i].thang,

   ngay_sinh[i].nam );

getch();

}

Ví dụ 2: Một mảng cấu trúc để chứa danh sách lương 50 cán bộ, sử dụng con trỏ để truy nhập tới các phần tử cấu trúc trong mảng.

#include  “Stdio.h”

#include  “Conio.h”

struct date

{

int ngay;

int thang;

int nam;

};

struct danh_sach

{

char  hoten[30];

char  maso[6];

struct  date ngaysinh;

float hsluong;

float phucap;

float  tienluong;

};

struct danh_sach  nhan_su[50];

main()

{

char  menu;

int  lap=1;

while (lap)

{

clrscr();

printf(“

-  1. Nhap du lieu luong”);

printf(“

-  2. Xem danh sach luong ”);

printf(“

-  3. Xem kich thuoc cau truc”);

printf(“

-  4. Thoat khoi chuong trinh”);

printf(“

Go 1 / 2 / 3 / 4 de chon cong viec”);

menu = getche();

switch  (menu)

{

case ‘1’: NHAP();

               break;

case ‘2’: XEM_DS();

               break;

case ‘3’: XEM_KT();

               break;

case ‘4’: lap = 0;

               break;

default :  clrscr();

                puts(“

Ban chon sai ! Nhan ENTER de chon lai ...”);

  getch();

  break;

 }

}

}

NHAP()                      /* Hàm nhập */

{

int  i, n;

struct  danh_sach  *p;

p = nhan_su;

clrscr();

printf(“So can bo n = ”); scanf(“%d”, &n);

for  (i = 0 ; i<n ; i++ )

{ fflush(stdin);

  printf(“Ho ten can bo thu %d : ”, i+1); gets(p->hoten);

printf(“ Ngay sinh : ”); scanf(“%d ”, &p->ngaysinh.ngay);

printf(“ Thang sinh : ”); scanf(“%d ”, &p->ngaysinh.thang);

printf(“ Nam sinh : ”); scanf(“%d ”, &p->ngaysinh.nam);

fflush(stdin);

printf(“ Ma so : ”); gets(p->maso);

printf(“ He so luong : ”); scanf(“%f ”, &p->hsluong);

printf(“ Tien phu cap : ”); scanf(“%f ”, &p->phucap);

p->tienluong = p->hsluong*290000 + p->phucap;

p++;

}

puts(“

Da nhap xong ! Nhan ENTER de quay ve .”);

getch();

}

XEM_DS(void)                      /* Hàm xem danh sách*/

{

int  i, m, n;

struct  danh_sach  *p;

p = nhan_su;

printf(“

Xem từ số : ”); scanf(“%d”, &m);

printf(“

Đến số:  ”); scanf(“%d”, &n);

clrscr();

puts(“          DANH SACH LUONG CAN BO”);

for  ( i = m-1 ; i<n ; i++ )

{ printf(“

%d- %-30s : ”, i+1, nhan_su[i].hoten);

printf(“ %2d - %2d - %4d ”, (*(p+i)).ngaysinh.ngay,

(*(p+i)).ngaysinh.thang, (*(p+i)).ngaysinh.nam);

printf(“ %-7s ”, (*(p+i)).maso);

printf(“%5.2f ”, (p+i)->hsluong);

printf(“%6.0f ”, (p+i)->phucap);

printf(“%10.2f ”, (p+i)->tienluong );

}

puts(“

Nhan ENTER de quay ve .”);

getch();

return;

}

XEM_KT()              /* Hàm xem kích thước cấu trúc*/

{

int  kt;

clrscr();

kt = sizeof(struct danh_sach);

printf(“

Kich thuoc kieu cau truc = %d bytes”, kt);

getch();

return;

}

Ta nhận thấy: có thể dùng 4 cách để truy nhập một phần tử thứ i của mảng cấu trúc nhan_su, đó là:

nhan_su[i].phần_tử

p[i].phần_tử

(*(p+i)).phần_tử

(p+i)-> phần_tử

5.4. Dữ liệu kiểu enum và kiểu typedef

5.4.1. Kiểu enum (liệt kê)

Tương tự như dữ liệu kiểu struct, dữ liệu kiểu enum là một kiểu dữ liệu mới, có cấu trúc, do người sử dụng tự tạo. Kiểu dữ liệu enum dùng để liệt kê các giá trị mà các biến có thể nhận thông qua các tên. Cách khai báo kiểu enum như sau:

enum  tên_kiểu

bảng_liệt_ kê

} danh_sách_biến; 

Trong đó:

§    enum : Là từ khoá

§    tên_kiểu: Là tên đặt cho kiểu enum

§    bảng_liệt_kê: Là danh sách các tên đại diện cho các giá trị có thể có, viết cách nhau dấu phảy

§    Danh_sách_biến: Là các biến được khai báo có kiểu enum tên_kiểu. Thành phần này có thể có hoặc không.

Khi đó, máy sẽ hiểu là: Có một kiểu dữ liệu mới là enum tên_kiểu, có các giá trị được đại diện bằng các tên trong bảng_liệt_kê và các biến trong danh_sách_biến đều có kiểu dữ liệu mới đó.

Ví dụ: Có thể định nghĩa một kiểu dữ liệu để chỉ các ngày trong tuần như sau:

enum  weekday

{

Sunday, Monday, Tuesday, Wedsday,

Thursday, Friday, Saturday

} day;

Trong ví dụ, các tên trong bảng_liệt_ kê sẽ đại diện lần lượt cho các số: 0, 1, 2, ..., 6; nghĩa là: tên sau tăng hơn tên trước một đơn vị. Chẳng hạn, viết:

day = Sunday;   tương đương với  day = 0;

day = Monday;   tương đương với  day = 1;

 ...

Trường hợp muốn một tên nào đó bắt đầu là một giá trị khác, trong bảng_liệt_kê có thể gán lại giá trị mong muốn cho tên đó, và, kể từ vị trí này, các tên sau sẽ tăng thêm 1 đơn vị.

Nếu không có danh_sách_biến trong khai báo kiểu, phải có khai báo biến và định kiểu cho nó là kiểu enum  tên_kiểu, mẫu khai báo như sau:

enum  tên_kiểu  danh_sách_biến;

 Ví dụ:

enum weekday  day, ngay;

Chú ý: Kiểu dữ liệu enum, bản chất là kiểu int, nó được cấp phát 2 bytes bộ nhớ và nó có thể nhận một giá trị nguyên bất kì.

Để nắm vững kiểu dữ liệu enum, xem ví dụ sau đây: Nhập số giờ làm việc của một người, từ thứ hai đến chủ nhật trong một tuần rồi tính tiền công theo đơn giá: Ngày thường tính đơn giá 1,2 usd /1 giờ, ngày thứ bảy và chủ nhật tính gấp 1,5 đơn giá.

#include  “Stdio.h”

#include  “Conio.h”

enum  tuan

{

thuhai, thuba, thutu, thunam, thusau, thubay, chunhat

};

char *thu[] = {“thu Hai”, “thu Ba”, “thu Tu”, “thu Nam”,

 “thu Sau”, “thu Bay”, “Chu nhat”};

main()

{

enum tuan  ngay;

char  ho_ten[30];

float  luong = 0;

clrscr();

printf(“ Vao ho ten:”); gets(ho_ten);

for  (ngay = thuhai; ngay<=chunhat; ngay++)

{

fflush(stdin);

printf(“Gio lam viec ngay %s la: ”, thu[ngay]);

scanf(“%d”, &gio);

if  (ngay= =thubay  | |  ngay= =chunhat)

      luong = luong + gio * 1.2 * 1.5;

else

      luong = luong + gio*1.2;

}

printf(“

Ong ( Ba) %s: ”, ho_ten);

printf(“

Co so tien cong la: %d  usd %0.2f cents ”,

(int) luong, luong - (int)luong);

getch();

}

5.4.2. Định nghĩa kiểu typedef

Tương tự như dữ liệu kiểu struct, dữ liệu kiểu typedef là một kiểu dữ liệu mới, có cấu trúc, do người sử dụng tự tạo. Kiểu dữ liệu typedef không thực sự tạo ra một kiểu dữ liệu mới nào cả mà chỉ khai báo một tên kiểu mới cho một kiểu dữ liệu đã có sẵn. Cách khai báo tổng quát như sau:

typedef  kiểu_đã_có  tên_kiểu_mới;

Trong đó:

·    kiểu_đã_có: Là kiểu dữ liệu mà ta muốn đổi tên, có thể là int, float, char, double, ...

·    tên_kiểu_mới: Là tên mới mà ta muốn đặt

Bản chất là, chỉ có một kiểu nhưng có hai tên. Kiểu typedef thường được sử dụng để định nghĩa lại các kiểu dữ liệu phức tạp thành một tên duy nhất, nhằm dễ dàng hơn khi khai báo biến.

Ví dụ:

typedef  unsigned char  byte;

Khi đó, có thể viết khai báo biến như sau:

byte  x = 10, y;

thì hai biến x, y đều có kiểu byte nhưng bản chất là kiểu unsigned char.

Đặc biệt, với kiểu dữ liệu có cấu trúc struct, typedef cho phép đơn giản hoá cách khai báo. Ví dụ:

typedef struct  sinh_vien

{ char  ho_ten[30];

float  diem;

} ten_sv, *tro_sv;

thì ten_sv và *tro_sv là tên mới của cấu trúc, chứ không phải là tên biến cấu trúc, đồng thời, có một kiểu con trỏ có tên tro_sv. Khi đó, có thể khai báo biến cấu trúc và con trỏ cấu trúc rất gọn ,như sau:

ten_sv  danh_sach[50];

tro_sv  p;      /* thay cho viết: struct  sinh_vien  *p      */

Lưu ý: Có thể xem kích thước (tính bằng byte) của một biến hay một dữ liệu có cấu trúc nhờ toán tử sizeof, cách viết như sau:

            sizeof(biến/kiểu)   hoặc   sizeof biến

ví dụ, khi có khai báo:  float  x;  

thì   sizeof(x) hay sizeof x có giá trị là 4 (độ dài trường nhớ chứa một biến kiểu float).

5.5. Dữ liệu kiểu file  (Tệp)

5.5.1. Khái niệm và phân loại tệp

Từ trước tới nay, khi chạy chương trình C, mọi dữ liệu mà người sử dụng nhập vào sẽ được đưa vào bộ nhớ RAM, nó sẽ bị mất khi thực hiện xong chương trình để giải phóng bộ nhớ. Trong thực tế, trong nhiều trường hợp, người ta muốn lưu trữ lại các dữ liệu đã nhập vào để sử dụng về sau. Muốn vậy, phải ghi dữ liệu lên đĩa thành tệp.

Tệp là một tập hợp các byte trên thiết bị nhớ ngoài của máy tính để chứa các thông tin có quan hệ với nhau. Để có thể truy nhập đến tệp (đọc hoặc ghi), phải biết tên tệp và địa chỉ của tệp (ghi trong thư mục nào). Trước khi làm việc với tệp nào, phải đọc tệp đó vào RAM - gọi là mở tệp; Sau khi làm việc xong với tệp, để không bị mất dữ liệu, phải ghi tệp trở lại đĩa - gọi là đóng tệp.

Trong C, Tệp là một kiểu dữ liệu có cấu trúc - cấu trúc FILE. Chỉ có một kiểu dữ liệu FILE, nhưng cấu trúc của mỗi tệp cụ thể lại có thể khác nhau; Cấu trúc đó được hình thành khi ghi dữ liệu vào tệp và được quyết định bởi hàm mà ta sử dụng để ghi dữ liệu lên đĩa. Phần này chỉ giới thiệu hai cấu trúc thường dùng nhất khi ghi tệp, đó là: Cấu trúc Văn bản (gọi tắt là tệp văn bản) và cấu trúc Nhị phân (gọi tắt là tệp nhị phân).

·  Tệp văn bản: Là tệp để ghi các dữ liệu kiểu char, nó có thể được xem như một dãy các kí tự được xử lí tuần tự theo chiều tiến từ đầu đến cuối tệp. Các kí tự ghi lên đĩa thành từng dòng với dấu hiệu kết thúc dòng là hai kí tự điều khiển CR (kí tự ‘\r’, có mã ASCII là 13) và LF  (kí tự ‘

’, có mã ASCII là 10). Khi gặp hai mã này, máy sẽ ngăn cách giữa hai dãy kí tự, tương ứng với hai dòng khác nhau. Hơn nữa, hai kí tự CR và LF được màn hình và máy in dùng làm kí tự điều khiển để xuống đầu dòng kế tiếp.

Có một thành phần nữa liên quan đến việc tổ chức tệp, đó là dấu hiệu kết thúc tệp (End Of File). Tệp văn bản có dấu hiệu kết thúc tệp là kí tự điều khiển CTRL+Z - mã ASCII là 26.

Ví dụ: Một văn bản gồm ba dòng có nội dung như sau:

            VAN BAN

               12345

            HET

sẽ được máy chứa trong một tệp văn bản gồm dãy các byte như sau:

V

A

N

B

A

N

\r

1

2

3

4

5

\r

H

E

T

^z

Chiều dài của các dòng văn bản không giống nhau nên tệp văn bản chỉ có thể đọc hoặc ghi theo phương pháp tuần tự từng dòng một và không thể thực hiện đồng thời cả đọc và ghi.

Màn hình, bàn phím và máy in là các tệp văn bản đặc biệt.

·  Tệp nhị phân: Là tập hợp các byte trên đĩa, trong lúc ghi, giữ nguyên dạng nhị phân như ở trong RAM. Thành phần của tệp nhị phân là các cấu trúc nên mỗi lần đọc hoặc ghi là một cấu trúc.

Nếu một xâu kí tự được ghi ra tệp nhị phân, dấu hiệu hết dòng của nó chỉ còn là LF = ‘

’, không có kí tự CR = ‘\r’.

Khác với tệp văn bản được lưu trữ dưới dạng mã ASCII nên có thể đọc được nội dung bằng bất cứ phần mềm xử lí văn bản nào; còn tệp nhị phân ghi dưới dạng mã máy nên không đọc được nội dung. Thí dụ, số 12345 là một số nguyên, không kể đến các bytes dành cho tổ chức tệp, nếu ghi dạng nhị phân thì chỉ mất 2 bytes, nếu ghi dạng văn bản, phải mất 5 bytes để biểu diễn 5 kí tự số.

Khi học các lệnh về tệp, phải xác định rõ lệnh đó xử lí tệp văn bản hay tệp nhị phân.

5.5.2. Các bước xử lí tệp

Để làm việc với tệp bất kì tệp nào, đều phải thực hiện theo một trình tự chung, gồm lần lượt bốn bước: Khai báo biến tệp; Mở tệp; Xử lí tệp (đọc/ ghi tệp, đánh dấu tệp, đặt kích thước vùng đệm,  xoá tệp, cài đặt thuộc tính cho tệp, ...); Đóng tệp.

Trong tệp tiêu đề Stdio.h, chứa rất nhiều hàm làm việc với tệp, có một số hàm dùng chung cho cả hai kiểu tệp, có một số hàm chỉ sử dụng với tệp nhị phân, có một số hàm chỉ sử dụng  với tệp văn bản.

a.    Bước 1: Khai báo biến tệp

Giống như mọi kiểu dữ liệu có cấu trúc, khi làm việc với dữ liệu kiểu tệp, trước tiên, phải khai báo biến tệp. Biến tệp bắt buộc phải là con trỏ. Cú pháp khai báo biến tệp như sau:

FILE   danh_sách_biến_con_trỏ;

Trong đó:

·  FILE : Là từ khoá - viết in hoa;

·  Danh_sách_biến_con_trỏ: Là các con trỏ để trỏ đến tệp cần thao tác, viết cách nhau dấu phảy.

Ví dụ:

FILE  *ghi, *doc;

b.    Bước 2: mở tệp

Một đặc điểm của C, khi mở tệp, phải khai báo ngay mở tệp để đọc hay ghi. Cú pháp của câu lệnh mở tệp như sau:

biến_tệp = fopen(“tên_tệp”, kiểu_truy_nhập);

Trong đó:

·   biến_tệp: Là tên một con trỏ đã khai báo ở bước 1.

·   fopen(): Là hàm mở tệp nằm trong tệp tiêu đề stdio.h

·   tên_tệp: Là tên của tệp được mở.

·   kiểu_truy_nhập: Là một tổ hợp các kí tự quy định cách thức truy nhập đối với tệp được mở, đặt trong cặp dấu nháy kép, gồm các kí tự và ý nghĩa như sau:

Đối với tệp văn bản:

Kiểu xử lí

ý nghĩa

“r”

Mở tệp văn bản đã có chỉ để đọc (read only), nếu tệp chưa có sẽ báo lỗi.

“w”

Mở tệp văn bản mới chỉ để ghi (write only), nếu tệp đã có thì ghi đè.

“a”

Mở tệp văn bản đã có để ghi thêm (append) dữ liệu vào cuối tệp, nếu tệp chưa có thì tạo ra tệp mới.

“r+”

Mở tệp văn bản đã có để cả đọc và ghi, nếu tệp chưa có sẽ báo lỗi.

“w+”

Mở tệp văn bản mới để cả đọc và ghi, nếu tệp đã có sẽ ghi đè.

“a+”

Mở tệp văn bản đã có để đọc và ghi thêm  dữ liệu vào cuối tệp, nếu tệp chưa có thì tạo ra tệp mới.

“t”

Mở tệp kiểu văn bản (text).

Đối với tệp nhị phân:

Kiểu xử lí

ý nghĩa

“rb”

Mở tệp nhị phân đã có chỉ để đọc (read only, binary), nếu tệp chưa có sẽ báo lỗi.

“wb”

Mở tệp nhị phân mới chỉ để ghi (write only, binary), nếu tệp đã có thì ghi đè.

“ab”

Mở tệp nhị phân đã có để ghi thêm (append, binary) dữ liệu vào cuối tệp, nếu tệp chưa có thì tạo ra tệp mới.

“r+b”

Mở tệp nhị phân đã có để cả đọc và ghi, nếu tệp chưa có sẽ báo lỗi.

“w+b”

Mở tệp nhị phân mới để cả đọc và ghi, nếu tệp đã có sẽ ghi đè.

“a+b”

Mở tệp nhị phân đã có để đọc và ghi thêm dữ liệu vào cuối tệp, nếu tệp chưa có thì tạo ra tệp mới.

Nếu mở thành công, hàm fopen() sẽ cho con trỏ kiểu FILE ứng với tệp vừa mở; Nếu có lỗi, hàm trả về giá trị NULL.

c.     Bước 3: xử lí tệp

Hai thao tác xử lí tệp quan trọng nhất là đọc dữ liệu từ tệp và ghi dữ liệu lên tệp. Các thao tác này có sự khác nhau trong từng kiểu tệp và các kiểu xử lí tệp.

Chú ý rằng, khi xử lí dữ liệu kiểu tệp, khác hẳn với dữ liệu kiểu mảng hay kiểu cấu trúc -  có thể truy nhập các phần tử một cách tuỳ ý thông qua tên biến, chỉ dẫn hoặc tên trường. Các phần tử của tệp không có tên, chúng được sắp xếp thành một dãy các ô nhớ, và, ở mỗi thời điểm, chỉ có thể truy nhập vào một phần tử của tệp thông qua giá trị của một biến đệm. Biến đệm là biến dùng để đánh dấu vị trí truy nhập của tệp (cửa sổ tệp) gọi là con trỏ chỉ vị (gọi tắt là con trỏ). 

. . . . . .

EOF (F)

Khi mới mở tệp, con trỏ chỉ vào phần tử đầu tiên của tệp.

Tệp trong C gắn liền với bộ nhớ đệm (buffer) để làm tăng tốc độ truy nhập bộ nhớ ngoài, trong các kiểu đọc hoặc ghi, ta cần làm sạch vùng nhớ đệm trước khi chuyển từ đọc sang ghi hoặc từ ghi sang đọc.

Ta sẽ xét các thao tác cụ thể này ở các mục sau.

d.    Bước 4: đóng tệp

Là bước cuối cùng để làm việc với tệp nhằm đảm bảo an toàn cho dữ liệu không bị mất. Có thể đóng tệp bằng một trong các hàm sau:

fclose(biến_tệp);

fcloseall();

Cả hai hàm trên đều nằm trong tệp Stdio.h, trong đó, hàm fclose() đóng một tệp đang mở, còn hàm fcloseall() sẽ đóng tất cả các tệp đang mở.

Như vậy, một chương trình xử lí tệp thường có dạng như sau:

#include <stdio.h>

main()

{

FILE *fp;   /*fp là biến con trỏ để chỉ đến một tệp */

fp = fopen(“van_ban.txt”, “w”);

              /*Mở tệp để ghi kiểu văn bản*/

if  (fp = = NULL)

    printf(“

ERROR: Không mở được tệp”);

else

  {

/* Các thao tác ghi hoặc đọc dữ liệu */

fclose(fp);  /* Đóng tệp */

  }

}

5.5.3. Một số hàm dùng chung cho cả hai kiểu tệp

Các hàm sau đây đều nằm trong tệp STDIO.H và cho phép sử dụng chung cho cả tệp văn bản và tệp nhị phân.

a.     Hàm fopen(“tên_tệp”, kiểu_truy_nhập): Mở một tệp với cách thức truy nhập được quy định bởi kiểu_truy_nhập (đã giới thiệu trong 5.5.2).

b.    Hàm fflush(biến_tệp): Làm sạch vùng nhớ đệm của tệp được trỏ bởi biến_tệp. Giá trị hàm là một số kiểu int, hàm cho giá trị 0 nếu thành công, ngược lại, hàm cho giá trị EOF. Để làm sạch vùng nhớ đệm bàn phím ta dùng hàm fflush(stdin).

c.     Hàm flushall(): Làm sạch vùng nhớ đệm của tất cả các tệp đang mở. Giá trị hàm là một số kiểu int; Nếu thành công, giá trị hàm bằng số tệp đang mở, ngược lại, cho giá trị EOF.

d.    Hàm fclose(biến_tệp): Đóng tệp đang mở được trỏ bởi biến_tệp. Giá trị hàm là một số kiểu int, hàm cho giá trị 0 nếu tệp được đóng, ngược lại - đóng có lỗi, hàm cho giá trị EOF.

e.     Hàm ferror(biến_tệp): Hàm kiểm tra lỗi của tệp. Giá trị hàm là một số kiểu int, nếu tệp không lỗi, hàm cho giá trị 0, ngược lại, cho giá trị 1.

f.     Hàm perror(s): Là một hàm kiểu void để hiển thị xâu s và thông báo lỗi hệ thống lên màn hình.

g.     Hàm feof(biến_tệp): Hàm kiểm tra xem con trỏ đã chỉ vào dấu hiệu kết thúc tệp hay chưa (end of file). Giá trị hàm là một số kiểu int, bằng 0 nếu con trỏ chưa ở eof, khác không nếu con trỏ chỉ vào eof.

h.    Hàm remove(“tên_tệp”): Là hàm kiểu void để xoá tệp.

i.      Hàm rename(“tên_tệp_cũ”, “tên_tệp_mới”): Là hàm kiểu int để đổi tên tệp trên đĩa. Nếu thành công, hàm cho giá trị 0, ngược lại, cho giá trị EOF.

j.      Hàm rewind(biến_tệp): Là một hàm kiểu void để di chuyển con trỏ về vị trí đầu tệp.

k.    Hàm fseek(biến_tệp, số_byte, bắt_đầu ): Là hàm kiểu int để di chuyển con trỏ đến một vị trí mong muốn. số_byte là một giá trị kiểu long để chỉ số byte mà con trỏ cần phải di chuyển tính từ vị trí bắt_đầu; bắt_đầu là một giá trị kiểu int để chỉ mốc xuất phát khi di chuyển con trỏ.

Giá trị bắt_đầu có thể là một trong các giá trị sau:

SEEK_SET = 0  là xuất phát từ đầu tệp;

SEEK_CUR = 1  là xuất phát từ vị trí hiện tại của con trỏ. Nếu số_byte dương thì di chuyển về phía cuối tệp, âm thì di chuyển về phía đầu tệp;

SEEK_END = 2  là xuất phát bắt đầu từ cuối tệp.

Hàm trả về giá trị 0 nếu di chuyển thành công, giá trị khác 0 nếu di chuyển có lỗi.

l.      Hàm ftell(biến_tệp): Hàm cho giá trị là một số kiểu long để chỉ vị trí hiện tại của con trỏ trong tệp đang mở (tệp được trỏ bởi biến_tệp) là byte thứ mấy. Các byte của tệp được đánh số thứ tự trong hệ 10, bắt đầu từ 0 cho byte đầu tiên. Nếu có lỗi, hàm cho giá trị -1.

m.   Hàm putc(c, biến_tệp): Giá trị của hàm là một số nguyên kiểu int. Hàm thực hiện ghi một kí tự vào cuối tệp. Kí tự được ghi  có mã là phần dư của phép chia c cho 256. Giá trị của hàm là mã ASCII của kí tự được ghi, trái lại hàm cho giá trị EOF. Với tệp văn bản, nếu kí tự được ghi có mã là 10, C sẽ tách thành hai mã 13 và 10 để ghi vào tệp.

n.    Hàm getc(biến_tệp): Hàm cho giá trị là một số nguyên kiểu int. Hàm thực hiện đọc một kí tự từ tệp. Nếu thành công, hàm cho giá trị là mã ASCII của kí tự đọc được, nếu gặp cuối tệp hoặc có lỗi, cho giá trị EOF. Với tệp văn bản, nếu đọc cả hai mã 13 và 10, hàm sẽ trả về mã 10, đọc mã 16 sẽ trả về EOF.

Ví dụ: Chương trình sao chép hai tệp nhị phân.

#include <Stdio.h>

#include <Conio.h>

void main()

{  FILE  *f1, *f2; char c, *nguon,*dich;

printf(“

Tên tệp nguồn: ”); gets(nguon);

printf(“

Tên tệp đích: ”); gets(dich);

f1 = fopen(nguon, “rb”); f2 = fopen(dich, “wb”);

if  (f1 = = NULL)

    { printf(“

Tệp nguồn không tồn tại!”);

       getch();

       exit(1);

    }

while  (c=getc(f1)  != EOF)

putc(c,f2);

fcloseall();

printf(“

Sao chép đã hoàn thành”);

getch();

}

5.5.4. Tệp dữ liệu nhị phân

          5.5.4.1. Tạo tệp nhị phân

Để tạo ra một tệp nhị phân, trước tiên, phải mở một tệp mới để ghi theo kiểu nhị phân (với kiểu truy nhập “wb”). Khi đó, tệp chưa có phần tử nào và con trỏ đặt vào EOF. Khi tạo tệp mới, cần chú ý tới các tên tệp đã có sẵn trên đĩa, nếu tên tệp cần tạo trùng với một tệp đã có, tệp cũ sẽ bị mất vì bị ghi đè lên.

Ví dụ:

FILE  *fp ;

fp = fopen(so_nguyen.dat, “wb”);

          5.5.4.2. Truy nhập tệp dữ liệu nhị phân

a.  Hàm putw(n; tên_biến): Là hàm để ghi số n (kiểu int) lên tệp dưới dạng 2 bytes. Giá trị của hàm trả về là số kiểu int đã được ghi vào tệp hoặc EOF nếu có lỗi ghi.

b.   Hàm getw(biến_tệp): Là hàm đọc một số nguyên ghi ở 2 bytes từ tệp. Giá trị của hàm trả về là số kiểu int đã đọc được hoặc EOF nếu có lỗi hay gặp cuối tệp.

Ví dụ: Chương trình nhập một dãy n số nguyên từ  bàn phím và ghi vào tệp có tên do người sử dụng nhập từ bàn phím rồi hiển thị dãy số nguyên đọc lại từ tệp đó ra màn hình.

#include <stdio.h>

#include <conio.h>

void main()

{

FILE  *fp ;  char  ten_tep[21];

int  i, n, x;

printf(“

Vào tên tệp để ghi dãy số:”); scanf(“%s”, ten_tep)

fp = fopen(ten_tep, “wb”);

printf(“

Số phần tử của dãy n = “); scanf(“%d”, &n);

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

{printf(“

Vào số nguyên thứ %d =”, i);

  scanf(“%d %*c”, &x);

  putw(x, fp);

}

fflush(fp);

fclose(fp);

fp = fopen(ten_tep, “rb”);

clrscr();

printf(“

Dãy số ghi trong tệp là :”);

while  ( ! feof(fp) )

{ x = getw(fp);  printf(“

%d “, x);}

fclose(fp);

getch();

}

c.  Hàm fwrite(con_trỏ, kích_thước, n, biến_tệp): Hàm ghi vào tệp biến_tệp n khối dữ liệu có địa chỉ xác định bởi con_trỏ và độ dài trường  nhớ xác định bởi kích_thước. Mỗi khối dữ liệu khi ghi vào tệp tạo thành một bản ghi. Giá trị trả về của hàm là số kiểu int chỉ số bản ghi ghi được.

d.  Hàm fread(con_trỏ, kích_thước, n, biến_tệp): Hàm đọc n bản ghi có số lượng byte bằng kích_thước từ tệp biến_tệp chứa vào vùng nhớ có địa chỉ chỉ bởi con_trỏ.

Ví dụ: Chương trình nhập một dãy n số thực từ  bàn phím và ghi vào tệp có tên là DULIEU.IN rồi hiển thị dãy số thực đọc lại từ tệp đó ra màn hình.

#include <stdio.h>

#include <conio.h>

void main()

{

FILE  *fp ; int  i, n;

float x;

fp = fopen(“Dulieu.in”, “wb”);

printf(“

Số phần tử của dãy n = “); scanf(“%d”, &n);

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

{printf(“

Vào số thực thứ %d =”, i);

  scanf(“%f %*c”, &x);

  fwrite(&x, sizeof(float), 1, fp);

}

fflush(fp);

fclose(fp);

printf(“

Đã ghi xong !!!”); getch();

fp = fopen(“Dulieu.in”, “rb”);

clrscr();

printf(“

Dãy số thực ghi trong tệp là :”);

while  ( ! feof(fp) )

{ fread(&x, sizeof(x), 1, fp);

   printf(“

%f “, x);}

fclose(fp);

getch();

}

          5.5.4.3. Truy nhập tệp dữ liệu struct

Muốn truy nhập đến các dữ liệu kiểu truct, nhất thiết phải sử dụng đến hai hàm fwite() và fread() để ghi hoặc đọc từng khối (bản ghi) đối với tệp nhị phân. Khi lấy ra các thành phần của struc, vẫn tham chiếu bình thường như đã giới thiệu ở phần  5.2.2 (tên_biến.phần_tử hoặc con_trỏ->phần tử).

Ví dụ: Chương trình quản lí số điện thoại của các nhân viên trong cơ quan được thiết kế thành  các hàm:

- Hàm nhap(): Để nhập bản ghi mới gồm hai thành phần họ tên và số điện thoại; Nếu tệp chưa có thì tạo mới, nếu đã có thì bổ sung bản ghi vào phía cuối tệp. Để kết thúc nhập, bấm Enter khi nhập họ tên.

- Hàm duyet(): Để đọc lần lượt từng bản ghi rồi hiển thị chúng lên màn hình thành từng cột;

- Hàm xem(): Để xem một bản ghi khi biết số thứ tự; Kết thúc xem bấm một số âm. Hàm này minh hoạ cho việc truy nhập ngẫu nhiên một bản ghi trong tệp.

- Hàm main(): Là hàm chính điều hành các công việc thông qua việc gọi các hàm liên kết với một thực đơn luôn được hiển thị trên màn hình.

#include <stdio.h>

#include <conio.h>

#include <string.h>

struct ban_ghi

  { char hoten[30];

     long int phone;};

typedef struct ban_ghi nhanvien;

FILE *fp;

long size = sizeoff(nhanvien);

/* *********************  */

void nhap()

{ nhanvien x;

fp = fopen(“Sophone.dat”, “ab”);

while  (1)

  { printf(“

Nhap ho ten : ”); gets(x.hoten);

     if  (strlen(x.hoten) = = 0)  break;

     printf(“

Nhap so dien thoai : ”

     scanf(“%ld%*c”, &x.phone);

     fwrite(&x, size, 1, fp);

  }

fclose(fp);

}

/* *********************  */

int duyet()

{ nhanvien  x;

fp = fopen(“Sophone.dat”, “rb”);

if  (fp = = NULL)

{  clrscr();

   printf(

Tep chua ton tai, bam ENTER de tiep tuc ...”);

   getch();

   return  1;

}

while  ( fread(&x, size, 1, fp) >0 )

    printf(“

%-30s            %ld”, x.hoten, x.phone);

fclose(fp);

printf(“

  Nhan ENTER de quay ve ...”);

getch();

retunr  0;

}

/* *********************  */

void xem()

{ nhanvien  x;

int  so_bg , kqua;

fp = fopen(“Sophone.dat”, “r+b”);

so_bg = 0;

while  (so_bg >=0)

{ clrscr();

printf(“

Nhap so ban ghi muon xem :  ”);

scanf(“%d%*c”, &so_bg);

fseek(fp, so_bg*size, SEEK_SET);

  kqua = fread(&x,  size, 1, fp);

  if  (kqua = = 1)

printf(“

%-30s       %ld”, x.hoten, x.phone);

          else

printf(“

Khong co ban ghi so %d”,so_bg);

printf(“

        Nhan ENTER de tiep tuc ...”);

getch();

          }

fclose(fp);

}

/* *********************  */

void main()

{ int ch, thoat = 1;

while  (thoat)

{ clrscr();

printf(“

  1. Nhap so dien thoai tung nguoi”);

printf(“

  2. Hien bang tra cuu so dien thoai ”);

printf(“

  3. Xem so dien thoai tung nguoi”);

printf(“

  4. Thoat khoi chuong trinh”);

printf(“

Hay bam 1 / 2/ 3/ 4 de chon cong viec ”);

ch = getchar();

fflush(stdin);

switch  (ch)

{    case ‘1’ : nhap(); break;

case ‘2’ : duyet(); break;

case ‘3’ : xem(); break;

case ‘4’ : thoat = 0; break;

}

}

}

5.5.5. Tệp văn bản

5.5.5.1. Một số hàm xử lí tệp văn bản

a.   Hàm fprintf(biến_tệp, xâu_điều_khiển,  danh_sách_đối): Là hàm tổng quát để đưa thông tin ra các thiết bị ngoại vi thông qua các luồng dữ liệu khác nhau. Cách sử dụng hàm này giống như hàm printf() nhưng không đưa ra màn hình mà đưa ra tệp văn bản được trỏ bởi biến_tệp. Nếu ghi thành công, hàm sẽ cho giá trị là một số nguyên kiểu int bằng số byte ghi được ra tệp, ngược lại cho giá trị EOF.

b.  Hàm fscanf(biến_tệp, xâu_điều_khiển,  danh_sách_đối): Tương tự như hàm scanf() nhưng không đọc dữ liệu từ bàn phím mà đọc từ tệp văn bản được trỏ bởi biến_tệp. Nếu đọc được, hàm trả về giá trị là một số kiểu int bằng số trường đọc được. Cách thức đọc giống như scanf() đọc dữ liệu từ  buffer.

Với mã %s, hàm fscanf() sẽ đọc một xâu từ tệp văn bản cho đến khi gặp kí tự khoảng trống hoặc kí tự xuống dòng (

).

Ví dụ 1: Chương trình nhập dữ liệu là một ma trận vuông cấp n từ bàn phím (với n≤100), ghi vào tệp văn bản MATRAN.TXT, theo quy cách:

a11   a12  a13   ....   a1n

a21   a22   a23   .... a2n

...........................    

an1   an2   an3   ...  ann

- Dòng đầu ghi số nguyên n là kích thước ma trận.

- n dòng tiếp theo trong tệp, mỗi dòng ghi một hàng của ma trận, mỗi phần tử cách nhau ít nhất một kí tự khoảng trống.

Phần việc tiếp theo là:

- Đọc lại dữ liệu từ tệp vừa nhập vào một mảng;

- Hiển thị lại mảng vừa đọc ra màn hình dưới dạng một ma trận.

#include <stdio.h>

#include <conio.h>

void main()

{

FILE *fp;

int  i , j , n;  float  temp, x[100][100];

fp = fopen(“MATRAN.TXT”, “w”);

/* Ghi vào tệp */

printf(“

Nhap n = ”); scanf(“%d”, &n);

fprintf(fp, “%d%c”, n, ‘

’);

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

{  clrscr(); printf(“

Nhap dong %d cua ma tran

”, i);

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

{ printf(“ a[%d,%d] = “, i, j);

   scanf(“%f”, &temp);

   fprintf(fp, “%f    “, temp);   /*     - khoảng trống  */

 }

fprintf(fp, “%c”, ‘

’);

}

fclose(fp);

/* Đọc từ  tệp  */

fp = fopen(“MATRAN.TXT”, “r”);

fscanf(fp, “%d”, &n);

for  (i=0; i < n; i++)

for  (j=0; j < n; j++)

{ fscanf(fp, “%f  ” , &temp);

x[i][j] = temp;

}

fclose(fp);

/* Hiển thị ma trận  */

clrscr();

for  (i=0; i < n; i++)

{ for  (j=0; j < n; j++)

printf(“%10.1f  ” ,  x[i][j]);

printf(“%c” ,  ‘

’);

}

getch();

}

c.  Hàm puts(s, biến_tệp): Hàm ghi xâu s vào tệp được trỏ bởi biến_tệp. Hàm sẽ: Không ghi kí tự ‘\0’ vào tệp; Cho giá trị là một số kiểu int bằng mã ASCII của kí tự cuối cùng vừa được ghi vào tệp, nếu không ghi được hàm cho giá trị EOF.

d.  Hàm fgets(s, n, biến_tệp): Hàm đọc một xâu có độ dài cực đại là n (n là số kiểu int) từ tệp được trỏ bởi biến tệp rồi chứa vào vùng nhớ s. Việc đọc xâu sẽ dừng khi: hoặc đã đọc được n-1 kí tự, hoặc gặp cặp mã 13 10, hoặc dấu hiệu kết thúc tệp. Khi gặp cặp mã 13 10, kí tự có mã 10  sẽ được bổ sung vào xâu đọc được.

Kết thúc quá trình đọc xâu, kí tự ‘\0’ sẽ được bổ sung vào cuối xâu kết quả. Hàm trả về địa chỉ vùng ghi kết quả, nếu đọc lỗi hoặc con trỏ chỉ vị đã ở dấu hiệu kết thúc tệp, hàm cho giá trị NULL.

Ví dụ 2: Chương trình thực hiện các công việc sau với tệp SAMPLE.TXT:

- Ghi 5 dòng văn bản vào tệp bằng hàm fprintf();

- Bổ sung 3 dòng văn bản vào cuối tệp bằng cách ghi từng kí tự nhờ hàm putc();

- Nhập bổ sung một văn bản nhờ hàm fputs();

- Đọc từng kí tự từ tệp nhờ hàm getc();

- Đọc từng từ nhờ hàm fscanf();

- Đọc từng dòng.

#include <stdio.h>

#include <conio.h>

#include <string.h>

void main()

{

FILE *fp ;

int  i, j ;

char  kitu, xau[50],  tu[10],  dong[100];

clrscr();

printf(“

1. Tao tep moi và ghi 5 dong van ban vao tep

”);

fp = fopen(“SAMPLE.TXT” , “w”);

strcpy(xau, “Dong van ban thu”);

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

fprintf(fp, “%s  %d

”, xau , i);

fclose(fp);

printf(“

Xong 1. Bam ENTER de tiep tuc ...”);

getch(); clrscr();

/*******************************/

printf(“

2. Ghi bo sung 3 dong van ban vao tep

”);

fp = fopen(“SAMPLE.TXT” , “a”);

strcpy(xau, “Dong van ban bo sung thu”);

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

{  for  (j =0; xau[j]; j++)  putc(xau[j], fp);

     fprintf(fp, “ %d”, i);

     putc(‘

’, fp);

}

fclose(fp);

printf(“

Xong 2. Bam ENTER de tiep tuc ...”);

getch(); clrscr();

/*******************************/

printf(“

3. Bo sung mot so dong tu ban phim vao tep

”);

fp = fopen(“SAMPLE.TXT” , “a”);

for  (    ;      ;     )

{ printf(“Go mot dong van ban / Enter de ket thuc

”); gets(dong);

if  (dong[0] = = ‘\0’)     break;

fputs(dong, fp);

putc(‘

’, fp);

}

fclose(fp);

printf(“

Xong 3. Bam ENTER de tiep tuc ...”);

getch(); clrscr();

/*******************************/

printf(“

4. Doc tung ki tu cua tep  van ban

”);

fp = fopen(“SAMPLE.TXT” , “r”);

if  (fp = = NULL)

printf(“Tep khong ton tai !!!”);

else

{  do

{  kitu = getc(fp);

putchar(c);

}

while  (kitu ! = EOF);

}

fclose(fp);

printf(“

Xong 4. Bam ENTER de tiep tuc ...”);

getch(); clrscr();

/*******************************/

printf(“

5. Doc tung tu cua tep  van ban

”);

fp = fopen(“SAMPLE.TXT” , “r”);

if  (fp = = NULL)

printf(“Tep khong ton tai !!!”);

else

{  do

{  kitu = fscanf(fp, “%s”, tu );

if  (kitu != EOF) printf(“%s  ”, tu);

}

while  (kitu != EOF);

}

fclose(fp);

printf(“

Xong 5. Bam ENTER de tiep tuc ...”);

getch(); clrscr();

/*******************************/

printf(“

6. Doc tung dong tep  van ban

”);

fp = fopen(“SAMPLE.TXT” , “r”);

do

{  kitu = fgets(dong, 100, fp);

    if  ( kitu!= NULL ) printf(“%s”, dong);

}

while  (kitu != NULL);

}

fclose(fp);

printf(“

Hoan tat cong viec. Bam ENTER de quay ve”);

getch(); clrscr();

}

Lưu ý: [1] Trong việc đọc và ghi tệp văn bản, tồn tại các cặp hàm thường đi đôi với nhau trong quá trình truy nhập tệp, đó là:

Đọc

ghi

getc()

Đọc từng kí tự

putc()

Ghi từng kí tự

fgets()

Đọc từng dòng

fputs()

Ghi từng dòng

fscanf()

Đọc dữ liệu theo quy cách

fprintf()

Ghi dữ liệu theo quy cách

[2] Các thiết bị vào ra chuẩn đều được coi là các tệp văn bản đặc biệt. Do vậy, tất  cả các hàm nhập xuất kiểu tệp văn bản đều có thể dùng với các thiết bị này. C đã sử dụng các tên chuẩn sau đây để làm biến tệp trỏ tới mỗi thiết bị:

Tên tệp

Con trỏ

Thiết bị

stdin

Bàn phím

out

stdout

Màn hình (ra chuẩn)

srderr

Màn hình (lỗi chuẩn)

stdprn

máy in.

5.5.5.2. Truyền tham số là tệp cho hàm

Về nguyên tắc, tệp là một kiểu dữ liệu có cấu trúc nên có thể truyền dữ liệu kiểu tệp cho hàm. Mặt khác, tệp là một dãy các bytes chứ không phải là các đơn vị dữ liệu độc lập như các biến. Vì thế, muốn truyền tham số là dữ liệu kiểu tệp cho hàm, ta chỉ có thể truyền tham số thực sự cho hàm một tên tệp, sau đó, hàm sẽ tự mở tệp để thực hiện các xử lí tệp.

Ngoài ra, một chương trình C đã được dịch thành tệp thực hiện (.EXE), chương trình sẽ chạy từ môi trường DOS như một lệnh ngoại trú và trong trường hợp này, lệnh chương trình được thực hiện bằng cách gõ tham số là tên tệp vào dòng lệnh. Ta minh hoạ điều đó bằng một ví dụ: Chương trình C:\DEM.EXE để đếm số kí tự của một tệp văn bản có tên do người sử dụng cung cấp. Văn bản chương trình như sau:

#include  <stdio.h>

#include  <stdlib.h>

main( int argc, char  *argv[])

{ int ch;    FILE *fp;

long count = 0;

if  (argc != 2)

{  printf(“Dùng: %s tên tệp

”, argv[0]);

exit(1);

}

if  ( (fp = fopen(argv[1], “r”)) = = NULL)

{  printf(“Không thể mở %s

”, argv[1]);

exit(1);

}

while  (ch=getc(fp)  !=EOF)

{  putc(ch,  stdout);

count++;

}

fclose(fp);

printf(“

Tệp %s có %ld kí tự.

”, argv[1], count);

return  0

}

Khi chạy chương trình DEM.EXE để đếm số kí tự của một tệp, chẳng hạn, tệp VANBAN.TXT, từ DOS ta gõ:    C:\> DEM  VANBAN.TXT

Thành phần VANBAN.TXT trong lệnh gọi là tham số dòng lệnh. Trong C có thể chuyển đối dòng lệnh cho chương trình khi nó bắt đầu chạy hàm main(). Hàm main() được gọi,  có thể có hai đối số là hai biến hệ thống, đó là:

Biến thứ nhất là argc (argument count): Là biến kiểu int để chỉ số lượng đối dòng lệnh;

Biến thứ hai *argv[ ] (argument vector): Là con trỏ để trỏ tới một mảng xâu chứa các đối dòng lệnh, mỗi đối là một xâu. Khi gõ các tham số dòng lệnh, các tham số phải cách nhau ít nhất một khoảng trống và chương trình coi đó như các dữ liệu vào.

Theo quy ước, argv[0] là tên chương trình nên argv[1] ít nhất bằng 1. Nếu argc = 1 thì sau tên chương trình sẽ không có đối dòng lệnh. Trong thí dụ trên, argv[0] = “DEM” , argv[1] = “VANBAN.TXT”.

Bài tập chương 5

1.  Để quản lí sinh viên ở một trường đại học, người ta cần biết các thông tin: Họ tên sinh viên, mã sinh viên, khoá, lớp, điểm thi năm môn, điểm trung bình, phân loại học tập.

Yêu cầu viết một chương trình gồm các hàm dùng để:

a.   Nhập dữ liệu cho n sinh viên.

b.  Tính điểm trung bình và phân loại sinh viên theo chế độ hiện hành

(Số học trình tương ứng của 5 môn là: 4, 5, 6, 7, 8).

c.   Sắp xếp danh sách sinh viên theo thứ tự điểm trung bình giảm dần.

d.  Hiển thị ra màn hình danh sách sinh viên một lớp bất kì nhập từ bàn phím

e.   Hàm main() để điều hành các hàm trên thông qua một thức đơn.

2.  Để thực hiện một bài toán tuyển sinh đơn giản trên máy tính, người ta sử dụng kiểu dữ liệu có cấu trúc gồm để chứa: Họ tên, số báo danh, ngày sinh (ngày, tháng, năm), điểm thi 3 môn, điểm ưu tiên, tổng điểm, kết quả ( 1- đỗ, 0 - trượt).  Dữ liệu ghi ở mảng. Hãy viết một chương trình để thực hiện bài toán thông qua một thực đơn, gồm các việc sau:

a.   Nhập dữ liệu

b.  Đánh số báo danh từ 1 đến hết theo thứ tự alphabet của tên

c.   Nhập điểm thi ba môn và tính tổng điểm.

d.  Xác định kết quả với chỉ tiêu tuyển là 10 người có điểm cao nhất.

e.   Hiển thị danh sách đỗ ra máy in.

3.  Có thể ghi một dãy xâu nhị phân vào tệp bằng cách chuyển nó về các số hệ 16. Viết chương trình để thực hiện sao cho khi hiển thị lại nó vẫn giữ nguyên là một dãy chữ số hệ 2.

4.  Làm bài 2 nhưng yêu cầu dữ liệu ghi vào tệp văn bản đồng thời bổ sung thêm các việc:

- Bổ sung thêm danh sách một thí sinh

- Xem kết quả thi một thí sinh theo số báo danh

5.  Có k dãy số bất kì được nhập thứ tự từ bàn phím. Viết chương trình ghi dãy số vào tệp, đọc lại từng dãy và sắp xếp tăng dần rồi lại ghi lại kết quả vào chính tệp đó.

6.  Sử dụng hai biến hệ thống argc, argv để viết chương trình tính tổng n số vào từ tham số dòng lệnh có dạng:

TONG    x1   x2   ...  xn

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