Tuesday, December 29, 2009

e. Kiểu con trỏ

Khái niệm:
Kiểu con trỏ là kiểu cơ sở dùng lưu địa chỉ bộ nhớ đã cấp phát cho một đối tượng dữ liệu khác.
Biến con trỏ có kiểu cơ sở T là biến mà giá trị của nó là địa chỉ vùng nhớ đã cấp phát cho một biến kiểu T (có thể là biến con trỏ) hoặc là giá trị NULL để chỉ định biến con trỏ chưa chứa địa chỉ vùng nhớ nào.
Khai báo kiểu con trỏ: typedef kiểucơsởT *tênkiểucontrỏ;
Kiểucơsở: là kiểu đã định nghiã trước, cũng có thể là kiểu con trỏ.
Kiểu con trỏ void: là kiểu con trỏ có thể chứa địa chỉ của bất kỳ kiểu nào. Nhưng khi sử dụng phải ép về 1 kiểu dữ liệu cụ thể.
Khai báo biến con trỏ:
Mẫu 1: tênkiểucontrỏ tênbiếncontrỏ;
Mẫu 2: kiểucơsởT *tênbiếncontrỏ;


Ví dụ: typedef int *intpointer;
intpointer pt;
hay int *pt;
Cấu trúc lưu trữ: Biến con trỏ được lưu trữ trong vùng nhớ cấp phát động (Heap) của chương trình. Kích thước của biến con trỏ tùy thuộc quy ước số byte địa chỉ trong từng mô hình bộ nhớ của từng ngôn ngữ lập trình
- Trong Pascal: 4 bytes(2 bytes địa chỉ Segment + 2 bytes địa chỉ offset)
- Trong C: 2 bytes cho con trỏ NEAR (chỉ lưu offset); 4 byte cho con trỏ FAR
Các thao tác trên biến con trỏ:
  • Gán 1 địa chỉ cho biến con trỏ:
Biến con trỏ có thể được khởi gán khi khai báo hay sử dụng câu lệnh gán tường minh.
tênbiếncontrỏ = &tênbiếncầnlấyđịachỉ;
tênbiếncontrỏ = NULL;
Ví dụ: Chứa địa chỉ của mảng 1 chiều: int *P , A[10]; --> P = A
Chứa địa chỉ của mảng 2 chiều: float *P, B[3][4]; --> P = (float*) B
Chứa địa chỉ của 1 biến cấu trúc: struct HocSinh *P, hs; --> P = &hs
  • o Truy xuất nội dung 1 biến do biến con trỏ trỏ đến: *tênbiếncontrỏ
Lưu ý: Toán tử * và & cùng độ ưu tiên
  • o Phép toán số học trên kiểu con trỏ:
Chỉ có ý nghĩa khi thực hiện trên mảng
pt + i ==> Cộng địa chỉ vùng nhớ lưu trong pt với i*sizeof(T)
pt1 – pt2 ==> số phần tử có kích thước sizeof(T) giữa 2 địa chỉ.
  • o Phép so sánh con trỏ: so sánh địa chỉ lưu trong 2 biến con trỏ: ==, !=
f. Kiểu tập tin
Các cấu trúc dữ liệu đã xét, lưu trữ và xử lý các mục dữ liệu ở bộ nhớ trong. Do đó có thể truy nhập đến chúng rất nhanh. Tuy nhiên dung lượng lưu trữ của bộ nhớ trong có thể quá nhỏ đối với những danh sách dữ liệu lớn, như danh sách nhân viên và không thể lưu trữ lâu dài. Những nhóm dữ liệu như vậy phải được lưu trữ ở bộ nhớ ngoài, gọi là tập tin (file).
Có 2 loại tập tin :
  • File văn bản
  • File có cấu trúc truy nhập ngẫu nhiên (File nhị phân)
Việc nhập xuất File văn bản chỉ khác file nhị phân khi xử lý ký tự chuyển dòng (mã 10, ký hiệu ‘\n’):
• Khi ghi, 1 ký tự xuống dòng ‘\n’ được chuyển thành 2 ký tự CR (Carriage Return_13) và LF (Line Feed_10).
• Khi đọc 2 ký tự liên tiếp CR và LF trên file chỉ cho ta 1 ký tự LF.
Quy tắc làm việc với tập tin:
- Khai báo biến tập tin
- Mở tập tin
- Truy xuất tập tin
- Đóng tập tin
Trong C, việc truy xuất đến 1 file trên đĩa có thể thực hiện bằng các hàm trong STDIO.H thông qua biến con trỏ file.

Khai báo biến con trỏ file: FILE *TrỏFile;
Mỗi file đều được đánh dấu kết thúc bởi ký tự có mã 26. Khi sử dụng các hàm để đọc File, nếu gặp cuối File thì hàm sẽ trả về giá trị –1, có tên là EOF.

Mở file: TrỏFile = fopen (char *têntậptin, char *kiểu);
• Têntậptin: Bao gồm cả đường dẫn. Nếu có lỗi sẽ cho giá trị NULL.
• Kiểu: Kết hợp bởi các mã sau nhằm khai báo kiểu tập tin và cách thức truy xuất tập tin.
Kiểu Ý nghiã
“b” Mở tập tin kiểu nhị phân
“t” Mở tập tin kiểu văn bản
“r” Mở để đọc
. Nếu không có File sẽ báo lỗi
“r
+” Mở để sửa
“w” Mở file mới để ghi
. Nếu file đã có sẽ bị xóa
“w
+” Mở file mới đọc hoặc ghi.
“a” Mở file để ghi thêm vào cuối file
“a
+” Mở file để đọc ghi. Nếu file cũ thì nối thêm,nếu không thì tạo mới


Ví dụ:
FILE *fp;
fp = fopen(“C:\\DSHS.DBF, “wb”);
if ( fp == 0 )
{
perror(“Loi Mo File:);
exit(1);
}


Đóng file:
- int fclose (FILE *fp); Nếu có lỗi hàm cho EOF ngược lại cho giá trị 0.
- int fcloseall ( ); Nếu có lỗi cho EOF nếu không cho số file được đóng.
File văn bản
  1. Ghi dữ liệu từ biến ra file văn bản:
  • int putc ( int ch, FILE *fp); Ghi lên file 1 ký tự có mã m = ch % 256.
Nếu không ghi được sẽ trả về EOF ngược lại trả về ký tự đã ghi.
  • int fputc (int ch, FILE *fp); như trên
Ví dụ: Xây dựng chương trình tạo tập tin văn bản, trong đó tên tập tin được nhập trên dòng lệnh. Kết thúc khi ấn Ctrl-Z hay F6
int main(int n, char * a[]) //n là số đối số trên dòng lệnh kể cả tên chương trình a[0]
{ FILE *fp; char c; int sobyte=0;
if (n != 2) { puts("Loi cu phap: "); exit(1); }
fp = fopen(a[1], "wt");
while (( c = getchar()) != EOF) //hàm getchar() trả về EOF khi có lỗi hoặc ấn F6
{ putc( c, fp); sobyte++; }
printf("\n\t 1 file copy \t\t %d bytes ", sobyte);
fclose(fp); }

Chú thích: Biên dịch thành file EXE, trở về dấu nhắc Dos: Taofile D:\LuuTru\Thoca.txt
  • int fputs (const char *Str, FILE *fp); Ghi chuỗi Str vào file.
Khi thành công hàm trả về ký tự cuối cùng được ghi, ngược lại trả về EOF.
Ví dụ:
int main(int n, char * a[]) //n là số đối số trên dòng lệnh kể cả tên chương trình a[0]
{ FILE *fp; char st[80]; int sobyte=0;
if (n != 2) { puts("Loi cu phap: "); exit(1); }
fp = fopen(a[1], "wt");
while ( gets(st)) != NULL) //hàm gets(st) trả về NULL khi có lỗi hoặc ấn F6
{ fputs( st, fp); fputs("\n", fp);
sobyte+=strlen(st);
}
printf("\n\t 1 file copy \t\t %d bytes ", sobyte);
fclose(fp);
}

  • int fprintf (FILE *fp, const char *dk, các biểu thức);
Ghi dữ liệu theo khuôn dạng. Nếu thành công hàm trả về số byte ghi lên điã, ngược lại trả về giá trị EOF (-1). Ký tự ‘\n’ được ghi thành 2 ký tự CR và LF.
Ví dụ: fprintf(fp,”so dinh %d \n”, n);
Ví dụ: Tạo file văn bản chứa một bản nhạc: Lời, tần số, thời gian nghỉ.
Ví dụ: Tạo file lưu bảng lượng giác của các góc từ 1 đến 89
  1. Đọc dữ liệu vào biến bộ nhớ:
  • int getc (FILE *fp) hoặc int fgetc (FILE *fp)
Đọc 1 ký tự. Nếu thành công hàm trả về mã đọc được (0 - 255). Nếu gặp ký tự cuối file (26) hay có lỗi đọc file thì trả về EOF.
Chú ý: Trong kiểu văn bản,
- Hàm đọc 1 lượt cả 2 mã 13, 10 và chỉ trả về mã 10
- Khi đọc ký tự kết thúc file (26) hàm trả về EOF
Ví dụ: Đếm số từ xuất hiện trong 1 file văn bản.
#include #include #include
#include #include
void main()
{ FILE *fp; char ch, Tu[8], fin[30];int i ;
clrscr(); textmode(C40); strcpy(fin,"d:\\thu.cpp");
fp = fopen(fin,"rt");
if (fp == NULL)
{ printf("Khong tim thay File: %s ", fin); getch(); exit(1);
}
while (!feof(fp))
{ memset(Tu,0,8);
while ((ch = getc(fp)) != EOF) //Tim ky tu dau tu
if (!isspace(ch))
{
Tu[0] = ch;
break;
}
if (Tu[0] != 0)
{ i = 1;
while ((ch = getc(fp)) != EOF)
if (isspace(ch)) { break; }
else Tu[i++] = ch;
printf("\n%s",Tu);
}
}
fclose(fp);
}

  • char *fgets (char * Str, int n, FILE *fp);
Đọc 1 chuỗi tối đa n-1 ký tự. Hàm trả về địa chỉ vùng chứa chuỗi. Nếu có lỗi hoặc gặp cuối file hàm cho giá trị NULL.
Việc đọc kết thúc khi:
- Đọc hết n-1 ký tự;
- Gặp dấu xuống dòng (cặp mã 13 10): khi đó mã 10 (‘\n’) được ghi vào xâu kết quả
- Gặp dấu kết thúc file
Hàm sẽ thêm ký tự '\0' vao cuối chuỗi.
Ví dụ: Đọc file lượng giác bằng hàm fgets vào một mảng và cho phép sử dụng các phím mũi tên để duyệt trên danh sách trên màn hình.
  • int fscanf (FILE *fp, const char *dk, địa chỉ các biến);
Đọc dữ liệu theo khuôn dạng. Nếu thành công hàm trả về số Fields đọc được, ngược lại trả về giá trị EOF (-1).
Ví dụ: Đọc file DAGIAC.DAT chứa toạ độ các đỉnh của đa giác. Dòng đầu ghi số đỉnh các dòng sau mỗi dòng ghi tọa độ của 1 đỉnh của đa giác.

typedef struct pointtype Polygol[10];
Polygol P;
void main(void)
{
FILE *fp;
char c; int n,i;
fp = fopen("c:\\dagiac.dat","rt");
fscanf(fp,"%d",&n);
printf("%d\n",n);*/
for (i=0; i<n; i++)
fscanf(fp,"%d %d",&P[i].x,&P[i].y);
fclose(fp);
}

Trường hợp không biết trước số đỉnh của đa giác:
i = 0;
while (fscanf(fp,"%d %d",&P[i].x, &P[i].y) != EOF)
printf("%d %d\n",P[i].x,P[i].y);
File truy nhập ngẫu nhiên
Tập tin truy nhập ngẫu nhiên thường dùng lưu trữ các mục dữ liệu cùng kiểu dưới dạng nhị phân như trong bộ nhớ. Cho phép truy nhập tuần tự các mục dữ theo thứ tự lưu trữ và có thể truy nhập trực tiếp đến một mục nào đó trong File bằng cách đặc tả vị trí của nó.

a. Ghi các mục dữ liệu vào File:
  • int fwrite(void *ptr, int sizeofItem, int n, FILE *fp);
Ghi n phần tử ở địa chỉ ptr, mỗi phần tử có kích thước size bytes. Hàm trả về số phần tử thật sự được ghi. Sau khi ghi xong đầu đọc sẽ dời đi (n*sizeof(Item)) byte.
Ví dụ: Tạo File lưu trữ hồ sơ nhân viên.

b. Đọc các mục dữ liệu trên File:
  • int fread (void *ptr, int sizeofItem, int n, FILE *fp);
Đọc n phần tử vào địa chỉ ptr , mỗi phần tử có kích thước size bytes. Hàm trả về số phần số phần tử thật sự đọc được.
Ví dụ: Liệt kê danh sách nhân viên.
Thuật toán: Duyệt tập tin
(1) Mở file để đọc
(2) Trong khi còn đọc được N nhân viên (đọc chừng 20 nhân viên)
while (
N = fread(&nv, sizeof(NhanVien), 20, fp)) > 0)
(
3) In N nhân viên
(4) Đóng file


void main()
{ FILE *f; item e; int N; const char *fname="e:\\DS.DBF";
f = fopen(fname, "rb");
while (N = fread(&e, sizeof(item), 1, f)) > 0 )
{ for (int i=1; i<=N; i++) printf("%6d", e.k);
getch();
}
fclose(f);
}

c. Kiểm tra cuối file:
  • int feof (FILE *fp);
Hàm cho giá trị khác 0 nếu gặp cuối file sau khi đọc
Ví dụ: while (1)
{ fread(&e,sizeof(Item), 1, fp);
if ( feof( f )) break;
}

d. Vị trí con trỏ byte:
  • long ftell (FILE *fp);
Cho biết con trỏ ở byte thứ mấy của file (tính từ 0). Khi có lỗi hàm trả về –1L.

e. Di chuyển đầu đọc File:
  • void rewind (FILE *fp); Chuyển đầu đọc về đầu file.
  • int fseek (FILE *fp, long sốbyte, int vịtríxuấtphát); chuyển đầu đọc từ vị trí xuất phát qua 1 số byte về hướng cuối file (sốbyte>0) hay hướng đầu file (sốbyte<0).>
Vị trí xuất phát có thể nhận các giá trị:
+ SEEK_SET hay 0: xuất phát từ đầu file
+ SEEK_CUR hay 1: xuất phát từ vị trí hiện hành
+ SEEK_END hay 2: xuất phát từ cuối file.


Ví dụ : Hàm tính số byte trên file (đối với file văn bản ký tự xuống dòng được tính 2 còn file nhị phân tính 1)
long filesize(FILE *stream) { long curpos, length;
curpos = ftell(stream);
fseek(stream, 0L, SEEK_END);
length = ftell(stream);
fseek(stream, curpos, SEEK_SET);
return length;
}

f. Đánh dấu vị trí đầu đọc ghi:
  • int fgetpos (FILE *fp, fpos_t *vịtrí);
g. Quay lại vị trí đã đánh dấu:
  • int fsetpos(FILE * fp, fpos_t *vịtrí);
Trong đó: fpos_t tương đương với kiểu long int. Nếu thành công trả về giá trị 0

h. Thêm một số mục dữ liệu vào tập tin:
Thuật toán:
(1) Mở File để thêm
(2) Nhập dữ liệu vào biến bộ nhớ e
(3) Nếu (có nhập) thì ghi biến nhớ vào File
(4) Lặp lại (2) trong khi còn nhập.
(
5) Đóng File


i. Sửa Dữ liệu:
Thuật toán:
(1) Mở file để đọc và ghi
(2) Nhập thông tin nhận dạng mục cần sửa
(3) Dời đầu đọc đến mục cần sửa
(4) Ghi nhận vị trí đầu đọc
(5) Đọc mục cần sửa vào biến nhớ
(6) Hiển thi thông tin và cho Nhập dữ liệu mới
(7) Dời đầu đọc về lại vị trí đã ghi nhận
(8) Ghi dữ liệu vào File


j. Hủy một mục dữ liệu:
Phương án 1:
Thuật toán:
(1) Đọc các phần tử không bị hủy trong tập tin chính vào một tập tin tạm.
Item e;
ftemp = fopen(??, “wb”)
while (! feof( f ) )
{ fread( &e, sizeof(Item), 1, f );
if (e.khóa != Khóa_cần_tìm)
fwrite(&e, sizeof(Item), 1, ftemp);
}
(2) Đổi tên tập tin tạm thành tập tin chính
(2.1) Nếu có file ?.BAK thì xóa nó
remove(“ ?.BAK”);
(2.2) Đổi tên ? thành ?.BAK // rename( “? “, “?.BAK”)
(2.3) Đổi tên ?? thành ? // rename( “ ?? “, “?”)

k. Đổi tên / di chuyển file :
  • int rename (const char *OldFile, const char *NewFile);
Nếu thành công: Trả về giá trị 0, ngược lại trả về -1.

l. Xóa tập tin:
  • int remove (const char * path);
Nếu thành công: Trả về giá trị 0, ngược lại trả về -1.
Phương án 2:
Tổ chức thêm vùng tin để đánh dấu sự loại bỏ tạm thời.
(1) Tìm và đánh dấu các phần tử cần loại bỏ.
• Tìm phần tử cần hủy
• Nếu tìm thấy thì e.dauloaibo = 1
• Ghi trở lại vào File
fseek( f , (long) –sizeof(Item), SEEK_CUR);
fwrite( &e, sizeof(Item), 1, f );
(2) Sau đó mới copy các phần tử không đánh dấu loại bỏ qua tập tin mới.
Ưu điểm:
- An toàn dữ liệu
- Tiết kiệm thời gian
- Có thể khôi phục lại các phần tử đã được đánh dấu loại bỏ tạm thời.

m. Trộn File:
Giả sử, có 2 file chứa các mẫu tin, mỗi mẫu tin chứa những thông tin khác nhau về một đối tượng cần quản lý (Nhân viên). Và hiện đang lưu trữ theo thứ tự của một trường khóa nào đó (MsNV).
Để Trộn 2 file đã sắp thứ tự theo khóa của mẫu tin sao cho file nhận được cũng sắp thứ tự có thể dùng thuật toán sau:
Thuật toán:
input: File1, File2 đã sắp thứ tự
output: File3 cũng sắp thứ tự
Bắt đầu:
(1) Mở File1 và File2 để đọc; File3 để ghi
(2) Đọc phần tử X trong File1 và phần tử Y trong File2
(3) Lặp lại các bước sau cho đến khi kết thúc File1 hay File2
Nếu
( Khóa(X) < Khóa(Y) ) thì
Ghi X vào File3
Đọc mẫu tin mới của X trong File1
Ngược lại
Ghi Y vào File3
Đọc mẫu tin mới của Y trong File2
(4) Nếu chưa kết thúc trong File1, sao tất cả các phần tử còn lại từ File1 sang File3. Nếu chưa kết thúc trong File2, sao tất cả các phần tử còn lại từ File2 sang File3.
(
5) Đóng các file
Kết thúc
.


No comments:

Post a Comment