Giáo trình Turbo C nâng cao và C++

pdf 244 trang hapham 2990
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Turbo C nâng cao và C++", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfgiao_trinh_turbo_c_nang_cao_va_c.pdf

Nội dung text: Giáo trình Turbo C nâng cao và C++

  1. Giỏo trỡnh Turbo C nõng cao và C++
  2. Phần 1 : turbo c nâng cao và c++ Ch−ơng 1 : Biến con trỏ Đ1. Khái niệm chung Một con trỏ là một biến chứa địa chỉ của một biến khác. Nếu một biến chứa địa chỉ của một biến khác tthì ta nói biến thứ nhất trỏ đến biến thứ hai . Cũng nh− mọi biến khác, biến con trỏ cũng phải đ−ợc khai báo tr−ớc khi dùng. Dạng tổng quát để khai báo một biến con trỏ là : type * Trong đó : type là bất kì kiểu dữ liệu cơ bản thích hợp nào đ−ợc chấp nhận trong C và là tên của một biến con trỏ. Kiểu dữ liệu cơ bản xác định kiểu của những biến mà con trỏ có thể chỉ đến. Ví dụ khai báo biến con trỏ chỉ đến các biến nguyên và biến kiểu kí tự: char *p; int *x,*y; Con trỏ có một trị đặc biệt gọi là NULL. Trị này có nghĩa là con trỏ ch−a trỏ tới một địa chỉ hợp lệ nào cả. Để dùng đ−ợc trị này chúng ta phải dùng #include đầu ch−ơng trình Đ2. Các phép toán về con trỏ C có hai phép toán đặc biệt đối với con trỏ : * và & . Phép toán & là phép toán trả về địa chỉ trong bộ nhớ của biến sau nó. Ví dụ : p = &a; sẽ đặt vào biến p địa chỉ trong bộ nhớ của biến a. Địa chỉ này không có liên quan gì đến trị số của biến a. Nói cách khác địa chỉ của biến a không liên quan gì đến nội dung của biến a. Phép toán * là phép toán trả về trị của biến đặt tại địa chỉ đ−ợc mô tả bởi biến đi sau nó. Ví dụ nếu biến a chứa địa chỉ của biến b thì p = *a sẽ đặt trị số của biến b vào biến p Ch−ơng trình 1-1 : Lập ch−ơng trình in số 100 lên màn hình main() { int *p,a,b; clrscr(); a=100; p=&a; b=*p; printf("%d",b); getch(); } Đ3. Tầm quan trọng của dữ liệu khi khai báo con trỏ 1
  3. Cần phải bảo đảm là con trỏ luôn luôn trỏ đến một kiểu dữ liệu phù hợp. Ví dụ khi khai báo con trỏ kiểu int , trình biên dịch sẽ hiểu là con trỏ bao giờ cũng chỉ đến một biến có độ dài là 2 byte . Ta xét một ch−ơng trình nh− sau Ch−ơng trình 1-2 main() { float x=10.1,y; int *p; clrscr(); p=&x; y=*p; printf("%f",y); getch(); } Ch−ơng trình này nhằm gán trị của x cho biến y và in ra trị đó. Khi biên dịch ch−ơng trình không báo lỗi mà chỉ nhắc nhở : Suspencious pointer conversion in function main Tuy nhiên ch−ơng trình không gán trị x cho y đ−ợc. Lí do là ta khai báo một con trỏ int và cho nó trỏ tới biến float x. Nh− vậy trình biên dịch sẽ chỉ chuyển 2 byte thông tin cho y chứ không phải 4 byte để tạo ra một số dạng float . Đ4. Các biểu thức con trỏ 1. Các phép gán con trỏ : Cũng giống nh− bất kì một biến nào khác , ta có thể dùng một con trỏ ở về phải của một phép gán để gán trị của một con trỏ cho một con trỏ khác. Ví dụ ta viết Ch−ơng trình 1-3 : main() { int x; int *p1,*p2; clrscr(); p1 = &x; p2 = p1; printf(“ %p”,p2); getch(); } Ch−ơng trình này hiện lên địa chỉ của biến x ở dạng hex bằng cách dùng một mã định dạng khác của hàm printf() . %p mô tả rằng sẽ hiện lên một trị chứa trong một biến con trỏ theo dạng reg:xxxx với reg là tên của một trong các thanh ghi segment của CPU còn xxxx là địa chỉ offset tính từ đầu segment . 2. Các phép toán số học của con trỏ : Trong C , ta chỉ có thể dùng hai phép toán số học tác động lên con trỏ là phép + và - . Để hiểu đ−ợc cái gì sẽ xảy ra khi thực hiện một phép toán số học lên con trỏ ta giả sử p1 là một con trỏ chỉ đến một số nguyên có địa chỉ là 2000 . Sau khi thực hiện biểu thức 2
  4. p1++ ; con trỏ sẽ chỉ đến số nguyên nằm ở địa chỉ 2002 vì mỗi khi tăng con trỏ lên 1 nó sẽ chỉ đến số nguyên kế tiếp mà mỗi số nguyên lại có độ dài 2 byte . Điều này cũng đúng khi giảm . Ví dụ : p1 ; sẽ trỏ tới số nguyên ở địa chỉ 1998 . Nh− vậy mỗi khi con trỏ tăng lên 1 , nó sẽ chỉ đến dữ liệu kế tiếp tại địa chỉ nào đó tuỳ theo độ dài của kiểu dữ liệu. C còn cho phép cộng hay trừ một số nguyên với một con trỏ . Biểu thức : p1 = p1 + 9; sẽ làm cho con trỏ chỉ tới phần tử thứ 9 có kiểu là kiểu mà p1 trỏ tới và nằm sau phân tử hiện thời nó đang trỏ đến . Ngoài các phép toán trên , con trỏ không chấp nhận một phép toán nào khác . 3. So sánh các con trỏ : Chúng ta có thể so sánh 2 con trỏ trong một biểu thức quan hệ . Ví dụ cho hai p và q , phát biểu sau đây là hợp lệ : if (p<q) printf(“p tro den mot vi tri bo nho thap hon q\n”); Tuy nhiên cần nhớ rằng phép toán trên là so sánh hai địa chỉ chứa trong p và q chứ không phải nội dung của hai biến mà p và q trỏ tới . 4. Các ví dụ về việc dùng con trỏ : Ch−ơng trình 1-4 : Phân tích ch−ơng trình sau : main() { int i,j,*p; i=5; p=&i; j=*p; *p=j+2; } Trong ch−ơng trình trên ta khai báo hai biến nguyên là i và j và một biến con trỏ p trỏ tới một số nguyên . Ch−ơng trình sẽ phân phối bộ nhớ cho 3 biến này ví dụ tại các địa chỉ 100 , 102 và 104 vì mỗi số nguyên dài 2 byte và con trỏ mặc nhiên cũng đ−ợc mã hoá bằng 2 byte . 100 i 102 j 104 p lệnh i=5 cho trị số của biến i là 5 100 5 i 102 j 104 p lệnh p= &i làm cho con trỏ chỉ tới biến i nghĩa là con trỏ p chứa địa chỉ của biến i . Bây giờ p chỉ đến biến i . 100 5 i 102 j 104 100 p 3
  5. lệnh j=*p đặt nội dung của biến do p chỉ tới (biến i) vào biến j nghĩa là gán 5 cho j 100 5 i 102 5 j 104 100 p Một trong những vấn đề lí thú khi dùng con trỏ là xem nội dung bộ nhớ của máy tính . Ch−ơng trình sau đây cho phép ta vào địa chỉ bắt đầu của RAM mà ta muốn khảo sát và sau đó hiện lên nội dung mỗi byte ở dạng số hex . Trong ch−ơng trình có từ khoá far dùng để tham khảo đến các vị trí không nằm trong cùng một segment . Ch−ơng trình 1-5 : main() { unsigned long int start; char *p; int t; clrscr(); printf("Nhap vao dia chi bat dau ma ban muon xem : "); scanf("%lu",&start); p = (char far *) start; for(t=0;;t++,p++) if(!(t%16)) { printf("%2x\n",*p); getch(); } } Trong ch−ơng trình ta dùng định dạng %x trong hàm printf() để in ra số dạng hex . Dòng p = (char far *) start; dùng biến đổi số nhập vào thành một con trỏ . Đ5. Con trỏ và mảng Trong ch−ơng tr−ớc chúng ta đã thấy các ví dụ về mảng . Con trỏ th−ờng đ−ợc dùng khi xử lí mảng . Chúng ta xét ch−ơng trình sau : Ch−ơng trình 1-6 : main() { int a[10],*pa,x; a[0]=11; a[1]=22; a[2]=33; a[3]=44; clrscr(); pa=&a[0]; x=*pa; pa++; x=*pa; 4
  6. x=*pa+1; x=*(pa+1); x=*++pa; x=++*pa; x=*pa++; } int a[10] , *pa , x; khai báo một bảng gồm 10 phần tử kiểu int , đ−ợc liệt kê là a[0],a[1], ,a[9] , một con trỏ để chỉ đến một biến kiểu int và một biến kiểu int là x. a[0] = 11. . .; từ a[4] đến a[9] ch−a đ−ợc khởi gán . Nh− vậy chúng sẽ chứa trị ngẫu nhiên đã có tại những vị trí bộ nhớ đã phân phối cho chúng . pa=&a[0]; đặt vào pa địa chỉ của phần tử đầu tiên của mảng . Biểu thức này có thể viết đơn giản là pa = a ; vì tên của một mảng luôn luôn đ−ợc trình biên dịch coi là địa chỉ của phần tử đầu tiên của mảng . Tên của mảng không có chỉ số kèm theo có thể đ−ợc dùng trong ch−ơng trình nh− một hằng địa chỉ . x=*pa; đặt nội dung của biến nguyên mà pa trỏ đến vào (tức là a[0]) vào x . Nh− vậy x = 11 pa++; pa đ−ợc tăng lên 1 và bây giờ trỏ vào phần tử thứ 2 của mảng tức là chứa địa chỉ của phần tử a[1] x=*pa ; pa trỏ đến phần tử a[1] nên x = 22 x = *pa +1 ; x =23 x = *(pa+1) ; tr−ớc hết pa+1 đ−ợc thực hiện , nghĩa là pa trỏ vào a[2] , sau đó nội dung của a[2] đ−ợc gán cho x nên x= 33 .Tuy pa tham gia vào phép toán nh−ng trị số của nó không thay đổi . x = *++pa; ++ đ−ợc thực hiện tr−ớc nên pa trỏ tới a[2] . Sau đó trị của a[2] đ−ợc gán cho x nên x =33 x= ++*pa; *pa đ−ợc thực hiện tr−ớc . Do pa chỉ đến a[2] nên *pa=33 và ++*pa=34 . Nh− vậy x = 34 và a[2]=34 x=*pa++; nội dung của pa (tức 34) đ−ợc đặt vào x . Sau đó nó đ−ợc tăng lên 1 nên chỉ vào a[3]. Ch−ơng trình 1-7: main() { static int num[]={92,81,70,69,58}; int dex; clrscr(); for(dex=0;dex<5;dex++) printf("%d\n",num[dex]); getch(); } Ch−ơng trình 1-8 : main() { static int num[]={92,81,70,69,58}; int dex; clrscr(); for(dex=0;dex<5;dex++) printf("%d\n",*(num+dex)); 5
  7. getch(); } Hai ch−ơng trình chỉ khác nhau ở biểu thức : *(num+dex) . Cách viết này t−ơng đ−ơng với num[dex] .Nói cách khác truy cập đến phần tử có chỉ số dex trong mảng num . Chúng ta hiểu *(num+dex) nh− sau : đầu tiên num là địa chỉ của phần tử đầu tiên của mảng num và ta muốn biết trị số của phần tử có chỉ số dex . Vì vậy num+dex sẽ là địa chỉ của phần tử thứ dex . *(num+dex) xác định nội dung của phần tử (num+dex) . Tóm lại : *(array+index) t−ơng tự array(index) Có hai cách truy cập mảng là : theo kí hiệu mảng &array[index] theo kí hiệu con trỏ array+index Ch−ơng trình 1-9 : Tính nhiệt độ trung bình bằng cách dùng con trỏ main() { float temp[40]; float sum=0.0; int num,day=0; clrscr(); do { printf("Cho nhiet do ngay thu %d: ",day+1); scanf("%f",temp+day); } while(*(temp+day++)>0); num = day-1; for(day=0;day 0) vì temp là hằng con trỏ chứ không phải biến con trỏ . Nh− vậy chỉ đ−ợc phép thay đổi trị của biến con trỏ chứ không đ−ợc thay đổi trị của hằng con trỏ . Chúng ta viết lại ch−ơng trình nh− sau : Ch−ơng trình 1-10 : main() { float temp[40]; float sum=0.0; int num,day=0; float *p; clrscr(); p=temp; do { printf("Cho nhiet do ngay thu %d: ",day+1); 6
  8. scanf("%f",p); day++; } while(*(p++)>0); p=temp; num=day-1; for(day=0;day main() { char ch,line[81],*ptr; clrscr(); printf("Cho mot cau : "); gets(line); printf("Cho ki tu can tim : "); ch=getche(); ptr=strchr(line,ch); printf("\nChuoi bat dau tai dia chi %u.\n",line); printf("Ki tu xuat hien lan dau tai %u.\n",ptr); printf("Do la vi tri %d",(ptr-line+1)); getch(); } Chuỗi cũng có thể đ−ợc khởi tạo bằng con trỏ . Ta xét ví dụ sau Ch−ơng trình 1-11 : main() { char *chao="Xin chao !"; 7
  9. char ten[30]; clrscr(); printf("Cho ten cua ban : "); gets(ten); printf(chao); puts(ten); getch(); } Trong ch−ơng trình trên ta đã khởi tạo chuỗi bằng phát biểu char *chao = “ Xin chao !” thay cho static char chao[]=” Xin chao !” Cả hai cách đều cho cùng một kết quả . Trong ph−ơng án dùng con trỏ , chao là biến con trỏ nên có thể thay đổi đ−ợc . Ví dụ phát biểu : puts(++chao) sẽ cho kết quả : in chao ! Nếu ta có một mảng chuỗi ta cũng có thể dùng mảng con trỏ trỏ tới mảng chuỗi này . Ta khởi tạo chúng giống nh− khởi tạo biến con trỏ đơn . Ch−ơng trình 1-12 : #define max 5 main() { int dex; int enter=0; char name[40]; static char *list[max]= { "Hung", "Ngan", "Van", "Hoa", "Tien" }; clrscr(); printf("Cho ten cua ban : "); gets(name); for(dex=0;dex<max;dex++) if (strcmp(list[dex],name)==0) enter=1; if (enter==1) printf("Ban da dang ki hoc lop C"); else printf("Ban chua dang ki vao lop"); getch(); } Phát biểu char *list[max] nói rằng list là một mảng con trỏ gồm max phần tử chỉ tới các kí tự . Chúng ta xét tiếp một ví dụ nh− sau : 8
  10. Ch−ơng trình 1-13 : Nhập vào một dãy tên và sắp xếp lại đúng thứ tự a,b,c #define maxnum 38 #define maxlen 81 main() { static char name[maxnum][maxlen]; char *ptr[maxnum]; char *temp; int count = 0; int in,out; clrscr(); while (count 0) { temp=ptr[in]; ptr[in]=ptr[out]; ptr[out]=temp; } printf("Danh sach da sap xep :\n"); for(out=0;out<count;out++) printf("Ten thu %d : %s\n",out+1,ptr[out]); getch(); } Ch−ơng trình này dùng cả mảng chuỗi và mảng con trỏ chuỗi . Con trỏ nằm trong mảng đ−ợc khai báo nh− sau : char *ptr[maxnum] chuỗi nằm trong mảng hai chiều static char name[maxnum][maxlen] Do ta không biết một chuỗi dài bao nhiêu nên phải dùng mảng chuỗi name có tối đa maxnum phần tử , mỗi phần tử có maxlen kí tự . Khi nhập chuỗi phát biểu ptr[count++] = name[count sẽ gán địa chỉ của mỗi chuỗi đ−ợc cất giữ trong mảng name[][] vào phần tử con trỏ ptr . Sau đó mảng con trỏ này đ−ợc sắp xếp dựa trên mảng name[][] nh−ơng mảng name[][] không thay đổi gì cả . Ngôn ngữ C có thể xử lí các thành phần của mảng nh− một mảng . Cụ thể C có thể xem một dòng của mảng hai chiều nh− là một mảng một chiều. điều này rất tiện lợi nh− ta đẫ thấy trong ch−ơng trình trên . Câu lệnh ptr[count++] = name[count hoàn toàn hợp lí vì vế 9
  11. phải chính là địa chỉ của mảng name[count] và mảng này là một thành phần của mảng name[][] là một mảng hai chiều . Ta xem lại khai báo : static char name[maxnum][maxlen] rõ ràng ta có thể xem đây là một mảng một chiều có maxnum chuỗi và tham khảo tới phần tử của mảng một chiều bằng 1 chỉ số . Ví dụ : name[count] với count<=maxnum nh− thế name[0] : địa chỉ của chuỗi 1 name[1] : địa chỉ của chuỗi 2 Đ7. Con trỏ trỏ đến con trỏ Chúng ta có một ch−ơng trình in ra một bảng số đ−ợc viết nh− sau : Ch−ơng trình 1-14: #define row 4 #define col 5 main() { static int table[row][col]={ {13,15,17,19,21}, {20,22,24,26,28}, {31,33,35,37,39}, {40,42,44,46,48} }; int c=10; int i,j; clrscr(); for(i=0;i<row;i++) for(j=0;j<col;j++) table[i][j]+=c; for(i=0;i<row;i++) { for(j=0;j<col;j++) printf("%5d",table[i][j]); printf("\n"); } getch(); } Trong ch−ơng trình trên ta dùng kí hiệu mảng. Bây giờ ta muốn viết ch−ơng trình dùng kí hiệu con trỏ thay cho kí hiệu mảng. Vậy thì làm thế nào để mô tả table[i][j] bằng con trỏ . Ta thấy rằng : - table là địa chỉ của phần tử đầu tiên của toàn bộ mảng , giả định là 1000 - do đây là mảng nguyên nên mỗi phần tử chiếm 2 byte và mỗi dòng chiếm 10 byte vì có 5 phần tử . Nh− vậy địa chỉ của hai dòng liền nhau cách nhau 10 byte - do có thể xem mỗi dòng là một mảng một chiều nên các mảng một chiều liền nhau cách nhau 10 byte - trình biên dịch biết số cột trong mảng qua khai báo nên nó sẽ hiểu table+1 là đem table ( trị 1000 ) cộng với 10 byte thành 1010 . T−ơng tự table+2 cho ta 1020 . 1000 13 15 17 19 21 table[0] 10
  12. table==1000 1010 20 22 24 26 28 table[1] 1020 31 33 35 37 39 table[2] 1030 40 42 44 46 48 table[3] Để tham khảo đến từng phần tử của dòng tr−ớc hết ta l−u ý địa chỉ của mảng cũng là địa chỉ của phần tử đầu tiên của mảng . Ví dụ với mảng một chiều a[size] thì a và a[0] là nh− nhau . Trở lại mảng hai chiều địa chỉ của mảng một chiều tạo bởi dòng thứ 3 của mảng table[][] là table[2] hay table+2 .Trong kí hiệu con trỏ địa chỉ của phần tử đầu tiên của mảng một chiều này là &table[2][0] hay *(table+2) . Cả hai cách viết table+2 và *(table+2) đều tham khảo nội dung của cùng một ô nhớ (1020) . Nếu cộng 1 vào table +3 để có table+3 thì ta nhận đ−ợc địa chỉ của dòng thứ 4 trong mảng table[][] . Nếu cộng 1 vào *(table+2) để có *(table+2)+1 thì có địa chỉ của phần tử thứ 2 trong dòng thứ 3 của mảng table[][] . Tóm lại : table[i] = *(table+i) &table[i] = table+i table[i][j] = *(*table+i)+j) &table[i][j] = (*(table+i)+j) Nh− vậy ch−ơng trình trên đ−ợc viết lại nh− sau : Ch−ơng trình 1-15 : #define row 4 #define col 5 main() { static int table[row][col]={ {13,15,17,19,21}, {20,22,24,26,28}, {31,33,35,37,39}, {40,42,44,46,48} }; int c=10; int i,j; clrscr(); for(i=0;i<row;i++) for(j=0;j<col;j++) *(*(table+i)+j)+=c; for(i=0;i<row;i++) { for(j=0;j<col;j++) printf("%5d",*(*(table+i)+j)); printf("\n"); } getch(); } Bài tập : Lập ch−ơng trình tính hiệu độ dài hai chuỗi nhập vào từ bàn phím Lập ch−ơng trình xác định giá trị cực đại của n số nhập vào từ bàn phím Lập ch−ơng trình quản lí hàng gồm ngày , l−ợng nhập ,l−ợng xuất và hàng tồn kho 11
  13. ch−ơng 2 : Bàn phím và cursor Đ1. Các mã phím mở rộng Chúng ta đã thấy bàn phím tạo các mã thông th−ờng cho các chữ cái, các số và dấu chấm câu. Các phím này đều tạo mã ASCII dài 1 byte. Tuy nhiên có nhều phím và tổ hợp phím không đ−ợc biểu diễn bằng bộ kí tự dài một byte này ví dụ nh− các phím chức năng từ F1 đến F10 hay các phím điều khiển cursor . Các phím này đ−ợc mô tả bằng một mã dài 2 byte. Byte đầu tiên có trị số là 0 và byte thứ hai là trị số mã của phím này . 1. Nhận biết các mã mở rộng : Một mã mở rộng phải có 2 byte và byte đầu tiên là 0 nên ch−ơng trình cần phải đọc 2 byte này . Sau đây là đoạn ch−ơng trình nhận biết các mã mở rộng Ch−ơng trình 2-1: #include main() { char key,key1; clrscr(); while ((key=getche())!='x') if (key==0) { key1=getch(); printf("%3d%3d",key,key1); } else printf("%3d",key); } Ch−ơng trình này sẽ hiện thị các mã của các phím đ−ợc gõ cho dù chúng là mã một byte hay 2 byte . Ta dùng hàm getch() để không hiển thị kí tự vừa gõ lên màn hình . Trong biểu thức kiểm tra của while ch−ơng trình đọc mã đầu tiên . Nếu mã này là 0 , ch−ơng trình biết đó là mã mở rộng và đọc tiếp phần thứ hai của mã bằng hàm getch() . Sau đó nó hiển thị cả hai phần . Nếu phần đầu khác không ch−ơng trình sẽ cho rằng đây không phải là mã mở rộng và hiện thị mã này . 2. Đoán nhận mã mở rộng : Một cách đoán nhận mã mở rộng là dùng phát biểu switch nh− trong ch−ơng trình sau : Ch−ơng trình 2-2 : main() { int key,key1; clrscr(); while ((key=getche())!='X') if (key==0) { key1=getch(); switch (key1) { case 59 : printf("Phim F1 duoc nhan\n"); break; case 60 : printf("Phim F2 duoc nhan\n"); 12
  14. break; case 75 : printf("Phim left arrow duoc nhan\n"); break; default : printf("Phim mo rong khac duoc nhan\n"); break; } } else printf("%3d",key); getch(); } Đ2. Điều khiển cursor và ansi.sys 1.Khái niệm chung :Tập tin ansi.sys cung cấp tập đã chuẩn hoá các mã điều khiển cursor . ANSI - America National Standards Institut. Để bảo đảm sự cài đặt của tập tin ansi.sys trong tập tin config.sys ta đặt dòng lệnh : device = ansi.sys 2. Điều khiển cursor bằng ansi.sys : ansi.sys dùng dãy escape để điều khiển con nháy . Chuỗi escape gồm nhiều kí tự đặc biệt . Ansi.sys tìm chuỗi escape này qua thành phần của chuỗi trong hàm prinft() và giải mã các lệnh theo sau nó . Chuỗi escape luôn luôn giống nhau , gồm kí tự không in đ−ợc “\x1B”(là mã của kí tự escape) sau đó là dấu [ . Sau chuỗi escape có thể có một hay nhiều kí tự . Nhờ chuỗi này con nháy có thể đi lên , xuóng , sang trái , phải hay định vị tại một vị trí nào đó . Ví dụ để di chuyển con nháy xuống d−ới ta dùng chuỗi “\x1B[B” Ch−ơng trình 2-3 : Viết ch−ơng trình in một chuỗi theo đ−ờng chéo : main() { clrscr(); printf("Cho mot chuoi tan cung bang dau .:"); while (getche()!='.') printf("\x1B[B"); getch(); } 3. Dùng #define và chuỗi escape : Chuỗi “\x1B[B” đ−ợc mã hoá và rất khó đọc . Khi dùng các ch−ơng trình phức tạp nên ghi chú rõ ràng bằng cách dùng dẫn h−ớng #define . Ch−ơng trình 2-4 : #define c_down "\x1B[B" main() { while (getche()!='.') printf(c_down); getch(); } Tóm tắt các lệnh điều khiển con nháy Mã Công dụng 13
  15. “[2J” Xoá màn hình và đ−a con nháy về home “[K” Xoá đến cuối dòng “[A” Đ−a con nháy lên một dòng “[B” Đ−a con nháy xuống một dòng “[C” Đ−a con nháy sang phải một cột “[D” Đ−a con nháy sang trái một cột “[%d;%df Đ−a con nháy đến vị trí nào đó “[s” Cất giữ vị trí con nháy “[u” Khôi phục vị trí con nháy “[%dA” Đ−a con nháy lên một số dòng “[%dB” Đ−a con nháy xuống một số dòng “[%dC” Đ−a con nháy sang phải một số cột “[%dD” Đ−a con nháy sang trái một dòng và nhiều cột 4. Điều khiển con nháy từ bàn phím : Sau đây là ch−ơng trình cho phép bạn vẽ các hình đơn giản trên màn hình Ch−ơng trình 2-5 : #define clear "\x1B[2J" #define c_left "\x1B[D" #define c_right "\x1B[C" #define c_up "\x1B[A" #define c_down "\x1B[B" #define l_arrow 75 #define r_arrow 77 #define u_arrow 72 #define d_arrow 80 #define across 205 #define updown 186 main() { int key; printf(clear); while ((key=getch())==0) { key=getche(); switch (key) { case l_arrow : printf(c_left); putch(across); break; case r_arrow : printf(c_right); putch(across); break; case u_arrow : printf(c_up); putch(updown); break; case d_arrow : printf(c_down); putch(updown); break; } 14
  16. printf(c_left); } getch(); } 5. Đ−a con nháy đến vị trí bất kì : Chuỗi escape dạng sau sẽ đ−a con nháy đến vị trí bất kì trên màn hình Số hex 1B của kí tự escape Số hiệu dòng Số hiệu cột Chữ cái f “ \ x 1 B [ 10 ; 40 f ” Sau đây là một ch−ơng trình ví dụ về cách dùng chuỗi đó Ch−ơng trình 2-6 : #define true 1 #define clear "\x1B[2J" #define erase "\x1B[K" main() { int row=1,col=1; printf(clear); while(true) { printf("\x1B[23;1f"); printf(erase); printf("Nhap vao so dong va so cot dang(20,40)"); scanf("%d%d",&row,&col); printf("\x1B[%d;%df",row,col); printf("*(%d,%d)",row,col); } } Đ6. Trình bày chỗ bất kì trên màn hình Sau đây là ch−ơng trình dùng chuỗi định vị cursor .Ch−ơng trình cung cấp hai menu định vị dọc theo màn hình . Ch−ơng trình 2-7 : #define size1 5 #define size2 4 #define clear "\x1B[2J" main() { static char *menu1[]= { "Open", "Close" "Save" "Print" "Quit" 15
  17. }; static char *menu2[]= { "Cut", "Copy", "Paste", "Reformat" }; void display(char *[],int ,int); printf(clear); display(menu1,size1,20); display(menu2,size2,20); getch(); } void display(char *arr[],int size,int hpos) { int j; for (j=0;j<size;j++) { printf("\x1B[%d",j+1,hpos); printf("%s\n",*(arr+j)); } } Các mục cho từng menu đ−ợc cất giữ trong mảng các con trỏ trỏ tới chuỗi . Sau đó ch−ơng trình dùng hàm để hiển thị menu . Hàm định vị con nháy nhờ dãy định vị ANSI.SYS , lấy số hiệu dòng từ số hiệu của mục trên menu và số hiệu cột đ−ợc ch−ơng trình chính truyền sang . Đ7. Các thuộc tính của kí tự Mỗi kí tự hiển thị trên màn hình đ−ợc cất giữ trong hai byte bộ nhớ . Một byte là mã thông th−ờng của kí tự và byte kia là thuộc tính của nó . Byte thuộc tính ấn định diện mạo của kí tự nh− chớp nháy , đậm , gạch d−ới , đảo màu . Ta có thể dùng chuỗi escape của ANSI để ấn định thuộc tính của kí tự . Theo sau chuỗi kí tự escape và ngoặc vuông là con số và chữ m . Sau đây là danh sách các số tạo hiệu ứng trên màn hình : 2,3,6 màu tối 0 tắt thuộc tính , th−ờng là màu trắng trên nền đen 1 đậm 4 gạch d−ới 5 chớp nháy 7 đảo màu 8 không thấy đ−ợc Chuỗi escape có dạng nh− sau : Số hex 1B của kí tự escape Số cho biết kiểu thuộc tính 16
  18. “ \ x 1 B [ 10 m” Chuỗi này đ−ợc gởi trong tiến trình hiển thị . Mỗi khi bật một thuộc tính , tất cả các kí tự sẽ hiển thị theo thuộc tính mới cho đến khi nó tắt đi . Sau đây là ch−ơng trình biểu diễn các thuộc tính của kí tự Ch−ơng trình 2-8 : #define NORMAL "\x1B[Om" #define BOLD "\x1B[1m" #define UNDER "\x1B[4m" #define BLINK "\x1B[5m" #define REVERSE "\x1B[7m" main() { printf("normal%s blink %s normal \n\n",BLINK,NORMAL); printf("normal%s bold %s normal \n\n",BOLD,NORMAL); printf("normal%s underline %s normal \n\n",UNDER,NORMAL); printf("normal%s reversed %s normal \n\n",REVERSE,NORMAL); printf("%s%s reversed and blink %s \n\n",BLINK,REVERSE,NORMAL); } Đ8. Menu Ta xây dựng một ch−ơng trình gồm 5 mục menu là Open ,Close,Save,Print,Quit . Các phím mũi tên lên xuống sẽ di chuyển vệt sáng đến các mục cần chọn.Phím INS để chọn và thực hiện công việc t−ơng ứng . Mục Quit sẽ kết thúc ch−ơng trình . Ch−ơng trình 2-9 : #define true 1 #define num 5 #define clear "\x1B[2J" #define erase "\x1B[K" #define normal "\x1B[Om" #define reverse "\x1B[7m" #define home "\x1B[1;1f" #define bottom "\x1B[20:1f" #define u_arro 72 #define color "\x1B[4m" /*#define l_arro 75 #define r_arro 77*/ #define d_arro 80 #define insert 83 main() { static char *item[num]= { "Open", "Close", "Save", "Print", "Quit" }; 17
  19. int curpos; int code; void display(char *[],int,int); int getcode(void); void action(int); printf(clear); curpos=0; while(true) { display(item,num,curpos); code=getcode(); switch (code) { case u_arro:if (curpos>0) curpos; break; case d_arro:if (curpos<num-1) ++curpos; break; case insert:action(curpos); break; } } } void display(char *arr[],int size,int pos) { int j; printf(home); for (j=0;j<size;j++) { if (j==pos) printf(reverse); printf("%s\n",*(arr+1)); printf("%s%5s",color,*(arr+j)); printf(normal); printf("%s"," "); printf(home); } } int getcode() { int key; while(getch()!=0) ; return (getch()); } void action(int pos) 18
  20. { switch(pos) { case 0: printf("Open"); break; case 1: printf("Close"); break; case 2: printf("Save"); break; case 3: printf("Print"); break; case 4: exit(); } } Đ9. Gán phím chức năng bằng ansi.sys Nhờ gán chuỗi vào phím chức năng ta có thể cấu hình lại bàn phím đamg dùng . Dạng thức của chuỗi gán phím chức năng nh− sau : mã escape gồm 1xB[ byte thứ nhất của mã mở rộng cho phím chức năng dấu ; byte thứ hai của mã mở rộng cho phím chức năng dấu ; chuỗi cần gán dấu ; xuống dòng chữ p \ x 1 B [ 0 ; 68 ; “s” ; 13 p Ch−ơng trình 2-10: main() { char str[81]; int key; clrscr(); printf("Nhap vao mot so cua phim chuc nang :"); gets(str); key=atoi(str); printf("Nhap vao mot chuoi de gan phim nay : "); gets(str); printf("\x1B[0;%d;\"%s\";13p",key+58,str); } 19
  21. Ch−ơng 3 : Nhập và xuất dữ liệu Đ1. Khái niệm chung 1. Khái niệm :Tr−ớc đây chúng ta đã xét việc nhập dữ liệu từ bàn phím. Trong nhiều tr−ờng hợp thực tế , để thuận lợi , chúng ta phải nhập dữ liệu từ các tập tin trên đĩa . Các hàm th− viện của C cho phép truy cập tập tin và chia là 2 cấp khác nhau : - các hàm cấp 1 là các hàm ở cấp thấp nhất , truy cập trực tiếp đến các tập tin trên đĩa.C không cung cấp vùng nhớ đệm cho các hàm này - các hàm cấp 2 là các hàm truy xuất tập tin cao hơn , do chúng đ−ợc C cung cấp vùng nhớ đệm Đối với các hàm cấp 1 , tập tin đ−ợc xem là khối các byte liên tục do đó khi muốn truy cập mẫu tin cụ thể thì phải tính toán địa chỉ của mẫu tin và nh− vậy công việc vất vả hơn . Ngoài ra phải cung cấp vùng nhớ đệm cho kiểu đọc ghi này. Đối với các hàm cấp hai công việc nhẹ nhàng hơn do : - trình biên dịch tự động cung cấp vùng kí ức đệm cho chúng - có thể truy xuất các mẫu tin mà không gặp khó khăn nh− với các hàm cấp 1 Trong C , các thông tin cần thiết cho các hàm xuất nhập cấp 2 đ−ợc đặt trong tập tin stdio.h còn các thông tin về hàm nhập xuất cấp 1 thì ở trong tập tin io.h 2. Stream và các tập tin : Ta phải phân biệt hai thuật ngữ là stream và file .Hệ thống xuất nhập của C cung cấp một không gian t−ởng t−ợng giữa ng−ời lập trình và các thiết bị đ−ợc dùng . Cấp trung gian t−ởng t−ợng này gọi là stream và thiết bị cụ thể là tập tin . a. Các streams : Trong máy tính ta dùng 2 loại stream : văn bản và nhị phân . Một stream văn bản là một loạt kí tự đ−ợc tổ chức thành dòng mà mỗi dòng đ−ợc kết thúc bằng kí tự xuống dòng newline(“\n”) . Khi ghi , một kí tự chuyển dòng LF(mã 10) đ−cợ chuyển thành 2 kí tự CR( mã 13) và LF . Khi đọc 2 kí tự liên tiếp CR và LF trên tập tin chỉ cho ta một kí tự LF . Một stream nhị phân là một loạt các byte . a. Các tập tin : Trong C ,một tập tin là một khái niệm logic mà hệ thống có thể áp dụng cho mọi thứ từ các tập tin trên đĩa cho đến các terminal . Khi bắt đầu thực hiện ch−ơng trình , máy tính mở 3 stream văn bản đã đ−ợc định nghĩa tr−ớc là stdin , stdout và stderr . Đối với hầu hết các hệ thống , các thiết bị này là console Đ2. Nhập xuất chuẩn 1. Nhập xuất kí tự , chuỗi kí tự , định dạng và bản ghi : Nhập xuất cấp 2(nhập xuất chuẩn ) cung cấp 4 cách đọc và ghi dữ liệu khác nhau (ng−ợc lại nhập xuất câp1 chỉ dùng 1 trong 4 cách này) . Tr−ớc hết dữ liệu có thể đọc ghi mỗi lần một kí tự , t−ơng tự nh− cách làm việc của putchar() và getche() để đọc dữ liệu từ bàn phím và hiển thị lên màn hình . Thứ hai , dữ liệu có thể nhập xuất theo chuỗi bằng các dùng các hàm gets() và puts() Thứ ba , dữ liệu có thể đ−ợc nhập và xuất theo khuôn dạng bằng các hàm fprintf() và fscanf() Thứ t− , dữ liệu đ−ợc đọc và ghi theo khối có chiều dài cố định th−ờng dùng l−u trữ mảng hay cấu trúc bằng các hàm fread() và fwrite() . Tóm lại : Các hàm dùng chung cho hai kiểu nhị phân và văn bản fopen : dùng mở tập tin 21
  22. fclose : đóng tập tin fclose : đóng tất cả các tập tin fflush : dùng làm sạch vùng đệm của tập tin flushall : dùng làm sạch vùng đệm của tất cả tập tin ferror : cho biết có lỗi (khác không) hay không có lỗi ( bằng 0) perror : thong báo lỗi trên màn hình foef : cho biết cuối tập tin hay ch−a unlink và remove : dùng để loại tập tin trên đĩa fseek : di chuyển con trỏ đến vị trí bất kì trên tập tin ftell : cho biết vị trí hiện tại của con trỏ Các hàm nhập xuất kí tự putc và fputc : nhập kí tự vào tập tin getc và fgetc : đọc kí tự từ tập tin fprintf : dùng ghi dữ liệu định dạng lên tập tin fscanf : dùng đọc dữ liệu định dạng từ tập tin fputs : dùng ghi chuỗi lên tập tin fgets : dùng đọc chuỗi từ tập tin Các hàm dùng cho kiểu xuất nhập nhị phân putw : dùng ghi một số nguyên hai byte lên tập tin gets : dùng đọc một số nguyên hai byte từ tập tin fwrite : dùng ghi một mẫu tin lên tập tin fread : dùng đọc một mẫu tin từ tập tin 2.Dạng văn bản và dạng nhị phân : Cách khác để phân loại các thao tác nhập xuất tập tin là nó đ−ợc mở theo kiểu văn bản hay nhị phân . Điểm khác biệt giữa hai loại này là kí tự newline và end of line . Điểm thứ hai để phân biệt hai kiểu tập tin là là cách l−u trữ các số vào đĩa . Đối với dạng văn bản thì các số đ−ợc l−u trữ thành chuỗi các kí tự còn dạng nhị phân thì các số đ−ợc l−u nh− trong bộ nhớ , nghĩa là dùng hai byte cho một số nguyên và 4 byte cho một số float . 3. Nhập xuất chuẩn : Ch−ơng trình dùng các hàm nhập xuất cấp 2 th−ờng dễ hiểu hơn nên chúng ta sẽ nghiên cứu tr−ớc . a. Nhập xuất kí tự : Để nhập kí tự vào tập tin ta dùng hàm putc() hay fputc().Để đọc kí tự từ tập tin ta dùng hàm getc() hay fgetc() . Ch−ơng trình ví dụ này là tạo lập các kí tự bằng cách gõ vào bàn phím mỗi lần một kí tự và ghi vào một tập tin trên đĩa . Ch−ơng trình dùng hàm fopen() để mở một tập tin , dùng hàm putc() để ghi lên tập tin , dùng kí tự enter để kết thúc ch−ơng trình . Ch−ơng trình 3-1 : #include #include void main() { FILE *fp; char ch; printf(“Nhap cac ki tu : “); fp=fopen("textfile","w"); while ((ch=getche())!='\r') putc(ch,fp); fclose(fp); } 22
  23. b. Mở một tập tin : Tr−ớc khi ghi một tập tin lên đĩa ta phải mở tập tin đó đã . Để mở tập tin , tr−ớc hết ta phải khai báo một con trỏ chỉ tới FILE . FILE là một structure chứa đựng các thông tin về cấu trúc của tập tin ví dụ nh− kích th−ớc , vị trí của bộ đệm dữ liệu hiện hành . Cấu trúc FILE đ−ợc khai báo trong stdio.h nên ta cần include tập tin này . Ngoài ra stdio.h còn xác định các tên và các biến khác đ−ợc dùng trong ch−ơng trình h−ớng đến các tập tin . Do vậy trong ch−ơng trình ta có câu lệnh : FILE *fp ; Sau đó ta mở tập tin bằng lệnh : fopen(“textfile”,”w”); Khi viết nh− vậy sẽ làm cho hệ điều hành biết là mở một tập tin tên là textfile trong th− mục hiện hành để viết lên tập tin đó (nhờ “w”) . Ta có thể cho tên đ−ờng dẫn đầy đủ nếu muốn mở tập tin ở th− mục bất kì . Hàm fopen() trả về một con trỏ chỉ đến cấu trúc FILE cho tập tin và con trỏ này đ−ợc cất giữ trong biến fp . Chuỗi “w” đ−ợc gọi là kiểu , nó có nghĩa là ghi lên tập tin . Các kiểu mở tập tin là : “r”,”rt” mở để đọc , tập tin phải có trên đĩa “w”,”wt” mở để ghi , nếu trên đĩa đã có tập tin thì nội dung bị ghi đè , nếu ch−a có thì tập tin đ−ợc tạo lập “a”,”at” mở để nối thêm, thông tin đ−ợc ghi vào cuối tập tin cũ nếu đã có tập tin hay tạo mới tập tin “r+”,’’r+t” mở để vừa đọc và ghi , tập tin phải có trên đĩa “rb” mở một tập tin để đọc theo kiểu nhị phân . Tập tin phải có sẵn trên đĩa “r+b” mở một tập tin để đọc theo kiểu nhị phân . Tập tin phải có sẵn trên đĩa “w+”,”w+t” mở để vừa đọc và ghi , nội dung tập tin đã có trên đĩa sẽ bị ghi đè lên “wb” mở để ghi theo kiểu nhị phân , nếu trên đĩa đã có tập tin thì nội dung bị ghi đè , nếu ch−a có thì tập tin đ−ợc tạo lập “a+”,”a+t” mở để đọc và nối thêm , nếu tập tin ch−a có thì nó sẽ đ−ợc tạo ra “ab” mở để đọc và nối thêm theo kiểu nhị phân , nếu tập tin ch−a có thì nó sẽ đ−ợc tạo ra c. Ghi lên tập tin : Khi tập tin đã đ−ợc mở , ta có thể ghi lên tập tin từng kí tự một bằng cách dùng hàm : putc(ch,fp) Hàm putc() t−ơng tự các hàm putch() và putchar() . Hàm putc() ghi lên tập tin có cấu trúc FILE đ−ợc ấn định bởi biến fp nhận đ−ợc khi mở tập tin . Tiến trình ghi đ−ợc tiến hành cho đến khi nhấn enter . d. Đóng tập tin : Khi không đọc ghi nữa ta cần đóng tập tin . Câu lệnh đóng tập tin là : fclose(fp); Ta báo cho hệ thống biết là cần đóng tập tin chỉ bởi fp . e. Đọc tập tin : Nếu ta có thể ghi lên tập tin thì ta cũng có thể đọc từ tập tin . Ta có ví dụ sau : Ch−ơng trình 3-2 : #include #include main() { FILE *fp; int ch; clrscr(); fp=fopen("textfile","r"); while ((ch=getc(fp))!=EOF) 23
  24. printf("%c",ch); fclose(fp); getch(); } f. Kết thúc tập tin : Sự khác nhâu chủ yếu giữa ch−ơng trình đọc và ghi là ch−ơng trình đọc phải phân biệt đ−ợc đâu là kí tự EOF . Nó không phải là một kí tự àm là một số nguyên do hệ điều hành gửi tới . Khi hết tập tin ta gặp mã kết thúc tập tin EOF (định nghĩa trong stdio.h bằng -1 ) và hàm foef() cho trị khác không . Ng−ời ta chọn -1 làm mã kết thúc vì nếu ch−a gặp cuối tập tin thì sẽ đọc đ−ợc một byte mà mã sẽ nằm trong khoảng 0-255 . Nh− vậy giá trị -1 không trùng với bất kì kí tự nào nào đ−ợc đọc từ tập tin . Trong khi ch−ơng trình đang đọc và hiển thị các kí tự thì nó tìm kiếm mộ giá trị -1 hay EOF . Khi thấy giá trị này , ch−ơng trình sẽ kết thúc . Chúng ta dùng một biến nguyên cất giữ một kí tự đọc đ−ợc , do đó ta có thể hiểu dấu EOF nh− là một trị nguyên có trị là -1 . Nếu dùng một biến kiểu char , chúg ta có thể dùng tất cả các kí tự từ 0 255 - đó là tổ hợp 8 bit . Do đó nếu dùng biến nguyên , ta bảo đảm rằng chỉ có một giá trị 16 bit là -1 , đó là dấu EOF . g. Sự phiền phức khi mở tập tin : Hai ch−ơng trình ta trình bày trên có một lỗi tiểm ẩn . Nếu tập tin đã đ−ợc chỉ định không mở đ−ợc thì ch−ơng trình không chạy . Lỗi này có thể là do tập tin ch−a có (khi đọc) hay đĩa không còn đủ chỗ(khi ghi). Do đó vấn đề là phải kiểm tra xem tập tin có mở đ−ợc hay không , nếu tập tin không mở đ−ợc thì hàm fopen() trả về trị 0(0 là NULL trong stdio.h) . Khi này C coi đây không phải là địa chỉ hợp lệ . Nh− vậy ta viết lại ch−ơng trình trên nh− sau Ch−ơng trình 3-3 : #include #include #include void main() { FILE *fp; int ch; clrscr(); if ((fp=fopen("file","r"))==NULL) { printf("Khong mo duoc tap tin\n"); getch(); exit(1); } while ((ch=getc(fp))!=EOF) printf("%c",ch); fclose(fp); } h. Đếm số kí tự : Khả năng đọc và ghi tập tin trên cơ sở các kí tự cho phép triển khai một số ứng dụng . Chúng ta xem xét ch−ơng trình đếm số kí tự sau : Ch−ơng trình 3-4 : #include #include main(int argc,char *argv) { FILE *fp; 24
  25. char string[8]; int count = 0; clrscr(); if (argc!=2) { printf("Format c:\ "); getch(); exit(1); } if ((fp=fopen(argv[1],"r"))==NULL) { printf("Khong mo duoc tap tin\n"); getch(); exit(1); } while (getc(fp)!=EOF) count++; fclose(fp); printf("Tap tin %s co %d ki tu",argv[1],count); getch(); } i. Đếm số từ : Ta có thể sửa ch−ơng trình trên thành ch−ơng trình đếm số từ . Ch−ơng trình 3-5 : #include #include #include main(int argc,char *argv[]) { FILE *fp; char ch,string[81]; int count = 0; int white=1; clrscr(); if (argc!=2) { printf(" Format c:\ \n"); getch(); exit(1); } if ((fp=fopen(argv[1],"r"))==NULL) { printf("Khong mo duoc tap tin\n"); getch(); exit(1); } while ((ch=getc(fp))!=EOF) switch(ch) { case ' ': /*nếu có dấu trống , dòng mới hay tab*/ 25
  26. case '\t': case '\n': white++; break; default:if(white) { white=0; count++; } } fclose(fp); printf("Tap tin %s co %d tu",argv[1],count); getch(); return 0; } k.Vào ra chuỗi : Đọc hay ghi chuỗi trên tập tin cũng t−ơng tự nh− đọc hay ghi từng kí tự riêng lẻ . Ta xét một ch−ơng trình ghi chuỗi Ch−ơng trình 3-6 : #include #include #include #include void main() { FILE *fp; char string[8]; clrscr(); if ((fp=fopen("a.txt","w"))==NULL) { printf("Khong mo duoc tap tin\n"); getch(); exit(1); } while (strlen(gets(string))>0) { fputs(string,fp); fputs("\n",fp); } fclose(fp); } Trong ch−ơng trình mỗi chuỗi kết thúc bằng cách gõ enter và kết thúc ch−ơng trình bằng cách gõ enter ở đầu dòng mới . Do fputs() không tự động thêm vào mã kết thúc để chuyển dòng mới nên ta phải thêm vào tập tin mã này . Ch−ơng trình đọc một chuỗi từ tập tin : Ch−ơng trình 3-7 : #include #include #include #include void main() 26
  27. { FILE *fp; char string[81]; clrscr(); if ((fp=fopen("a.txt","r"))==NULL) { printf("Khong mo duoc tap tin\n"); getch(); exit(1); } while (fgets(string,81,fp)!=NULL) printf("%s",string); fclose(fp); getch(); } Hàm fgets() nhận 3 đối số : địa chỉ nơi đặt chuỗi , chiều dài tối đa của chuỗi , và con trỏ chỉ tới tập tin . l. Vấn đề sang dòng mới : Trong ch−ơng trình đếm kí tự ta thấy số kí tự đếm đ−ợc bao giờ cũng nhỏ hơn số byte có trong tập tin này nhận đ−ợc bằng lệnh dir của DOS . Khi ta ghi một tập tin văn bản vào đĩa , C tự động ghi vào đĩa cả hai mã CR và LF khi gặp mã sang dòng mới “\n” . Ng−ợc lại khi đọc tập tin từ đĩa , các mã CR và LF đ−ợc tổ hợp thành mã sang dòng mới . Ch−ơng trình sau minh hoa thêm về kĩ thuật vào ra chuỗi , nội dung t−ơng tự lệnh type của DOS Ch−ơng trình 3-8 : #include #include #include main(int argc,char *argv[]) { FILE *fp; char string[81]; clrscr(); if (argc!=2) { printf("Format c:\ "); getch(); exit(1); } if ((fp=fopen(argv[1],"r"))==NULL) { printf("Khong mo duoc tap tin\n"); getch(); exit(1); } while (fgets(string,81,fp)!=NULL) printf("%s",string); fclose(fp); getch(); return 0; 27
  28. } m. Các tập tin chuấn và máy in : Trên đây ta đã nói đến cách thức tiếp nhận một con trỏ tham chiếu dến một tập tin trên đĩa của hàm fopen() , C định nghĩa lại tê chuẩn của 5 tập tin chuẩn nh− sau : Tên Thiết bị in Thiết bị vào chuẩn (bàn phím) out Thiết bị ra chuẩn (màn hình) err Thiết bị lỗi chuẩn (màn hình) aux Thiết bị phụ trợ chuẩn(cổng nối tiếp) prn Thiết bị in chuẩn (máy in) Ta có thể dùng các tên này để truy cập đến các thiết bị . Ch−ơng trình sau dùng hàm fgets(0 và fputs() để in nội dung một tập tin ra máy in Ch−ơng trình 3-9 : #include #include #include main(int argc,char *argv[]) { FILE *fp1,*fp2; char string[81]; clrscr(); if (argc!=2) { printf("Format c:\ "); getch(); exit(1); } if ((fp1=fopen(argv[1],"r"))==NULL) { printf("Khong mo duoc tap tin\n"); getch(); exit(1); } if ((fp2=fopen("prn","w"))==NULL) { printf("Khong mo duoc may in\n"); getch(); exit(1); } while (fgets(string,81,fp1)!=NULL) fputs(string,fp2); fclose(fp1); fclose(fp2); getch(); return 0; } Trong ch−ơng trình trên máy in đ−ợc coi là tập tin có tên là prn 28
  29. n. Nhập xuất định dạng : Tr−ớc đây ta đã đề cập đến nhập xuất kí tự . Những số có định dạng cũng có thể ghi lên đĩa nh− các kí tự . Ta xét ch−ơng trình sau Ch−ơng trình 3-10 : #include #include main() { FILE *p; int i,n; float x[4],y[4]; clrscr(); p=fopen("test.txt","w"); printf("Cho so cap so can nhap n = "); scanf("%d",&n); fprintf(p,"%d\n",n); printf("Cho cac gia tri x va y\n"); for (i=0;i #include #include void main() { FILE *fp; char name[40]; int code; float height; int n,i; clrscr(); fp=fopen("b.txt","w"); printf("Cho so nguoi can nhap : "); scanf("%d",&n); for (i=0;i #include 29
  30. void main() { FILE *p; int i,n; float x[4],y[4]; clrscr(); p=fopen("test.txt","r"); fscanf(p,"%d",&n); for (i=0;i #include #include void main() { FILE *fp; char name[2]; int code,n,i; float height; clrscr(); fp=fopen("b.txt","r"); fscanf(fp,"%d",&n); for (i=0;i #include 30
  31. #include void main(int argc,char *argv[]) { FILE *fp; char string[81]; int count=0; clrscr(); if (argc!=2) { printf("Format c:\ "); getch(); exit(1); } if ((fp=fopen(argv[1],"rb"))==NULL) { printf("Khong mo duoc tap tin\n"); getch(); exit(1); } while (getc(fp)!=EOF) count++; fclose(fp); printf("Tap tin %s co %d ki tu",argv[1],count); getch(); } 2. Mã kết thúc tập tin theo 2 kiểu : Sự khác biệt thứ hai khi mở tập tin theo kiểu nhị phân hay kiểu kí tự còn là ở chỗ nhìn nhận kí tự kết thúc tập tin . Nói chung các tập tin đều đ−ợc quản lí theo kích th−ớc của nó và khi đọc hết số byte đã chỉ ra trong kích th−ớc tập tin thì dấu hiệu EOF sẽ đ−ợc thông báo , dấu hiệu đó ứng với mã 1Ah(hay 26 ở hệ 10) . Khi đóng tập tin văn bản , mã 1A sẽ đ−ợc tự động chèn vào cuối tập tin để làm dấu hiệu kết thúc tập tin (t−ơng đ−ơng mã Ctrl-Z) . Do vậy nếu bằng cáh nào đó ta chèn mã 1A vào một vị trí giữa tập tin , thì khi mở tập tin theo kiểu văn bản và đọc đến mã này ch−ơng trình đọc sẽ ngừng hẳn vì chính lúc đó hàm đọc phát sinh giá trị -1 để báo cho ch−ơng trình là đã kết thúc tập tin . Nếu đã l−u số vào tập tin theo dạng nhị phân thì khi mở tập tin cần phải mở theo dạng nhị phân . Nếu không sẽ có một số nào đó là 1A và việc đọc tập tin theo kiểu văn bản sẽ kết thúc ngoài ý định . T−ơng tự , với tập tin mở theo kiểu nhị phân mã 10 không đ−ợc nhìn nhận là mã sang dòng mới vì không đ−ợc xem là t−ơng ứng với tổ hợp CR/LF nữa. 3. Ch−ơng trình minh hoạ : Chúng ta xét một ch−ơng trình dùng kiểu nhị phân để khảo sát tập tin . Ch−ơng trình 3-13 : #include #include #include #define length 10 #define true 0 #define false -1 void main(int agrc,char *argv[]) { 31
  32. FILE *fp; int ch; int j,noteof; unsigned char string[length+1]; clrscr(); if (agrc!=2) { printf("Dang c:\ "); getch(); exit(1); } if ((fp=fopen(argv[1],"rb"))==NULL) { printf("Khong mo duoc tap tin\n"); getch(); exit(1); } noteof=true; do { for (j=0;j 31) *(string+j)=ch;/* ki tu in duoc*/ else *(string+j)='.';/* ki tu khong in duoc*/ } *(string+j)='\0'; printf(" %s\n",string); } while (noteof==true); fclose(fp); getch(); } 4. Các hàm fread và fwrite : a. Ghi cấu trúc bằng fwrite : Ta xét một ch−ơng trình ghi cấu trúc lên đĩa . Trong ch−ơng trình ta dùng hàm fread() . Hàm này có 4 đối số : địa chỉ để ghi cấu trúc , kích th−ớc của cấu trúc , số cấu trúc sẽ ghi và con trỏ chỉ tới tập tin . Ch−ơng trình 3-14 : #include #include #include void main() { char chso[10]; FILE *fp; 32
  33. struct nguoi { char ten[30]; int so; float cao; }nv; clrscr(); if((fp=fopen("nhanvien.rec","wb"))==NULL) { printf("Khong mo duoc file\n"); getch(); exit(1); } do { printf("\nCho ten : "); gets(nv.ten); printf("Cho ma so : "); gets(chso); nv.so=atoi(chso); printf("Cho chieu cao :"); gets(chso); nv.cao=atof(chso); fwrite(&nv,sizeof(nv),1,fp); printf("Tiep tuc khong y/n?"); } while(getch()=='y'); fclose(fp); } b. Đọc cấu trúc bằng fread : Ta dùng hàm fread() để đọc cấu trúc ghi trên một tập tin . Các đối số của fread() cũng giống nh− fwrite() . Hàm fread() trả về số của những mục đã đ−ợc đọc tới . Nếu tập tin đã kết thúc nó cho trị âm . Ta xét ví dụ sau : Ch−ơng trình 3-15 : #include #include #include void main() { FILE *fp; struct nguoi { char ten[30]; int so; float cao; }nv; clrscr(); if((fp=fopen("nhanvien.rec","rb"))==NULL) { printf("Khong mo duoc file\n"); getch(); exit(1); } 33
  34. do { printf("\nTen :%s\n",nv.ten); printf("Ma so :%03d\n",nv.so); printf("Chieu cao :%.2f\n",nv.cao); } while (fread(&nv,sizeof(nv),1,fp)==1); fclose(fp); getch(); } c. Ghi mảng bằng fwrite() : Hàm fwrite() cũng dùng ghi mảng lên đĩa . Ta xét ví dụ sau : Ch−ơng trình 3-16 : #include #include #include int table[10]={1,2,3,4,5,6,7,8,9,10}; void main() { FILE *fp; clrscr(); if((fp=fopen("table.rec","wb"))==NULL) { printf("Khong mo duoc file\n"); getch(); exit(1); } fwrite(table,sizeof(table),1,fp); fclose(fp); } d. Đọc mảng bằng fread() : Sau khi ghi mảng lên đĩa ta có thể đọc các phần tử của mảng từ đĩa bằng hàm fread(). Ch−ơng trình 3-17 : #include #include #include void main() { FILE *fp; int a[10]; int i; clrscr(); if((fp=fopen("table.rec","rb"))==NULL) { 34
  35. printf("Khong mo duoc file\n"); getch(); exit(1); } for (i=0;i #include #include #define true 1 struct nguoi { char ten[30]; int so; float cao; }; struct nguoi nv[10]; int n=0; char numstr[10]; void main() { char ch; void newname(void); void listall(void); void wfile(void); void rfile(void); clrscr(); while (true) { printf("\nGo 'e' de nhap nhan vien moi\n"); printf("Go 'l'de liet ke nhan vien\n"); printf("Go 'w' de ghi len dia\n"); printf("Go 'r'de doc file tu dia\n"); printf("Go 'q' de ket thuc chuong trinh\n\n"); ch=getch(); switch (ch) { case 'e':newname(); break; case 'l':listall(); 35
  36. break; case 'w':wfile(); break; case 'r':rfile(); break; case 'q': exit(1); default : printf("Nhap sai ki tu , chon lai!"); } } } void newname() { char numstr[81]; printf("\nBan ghi so %d\nCho ten : ",n+1); gets(nv[n].ten); printf("Cho ma so co 3 chu so : "); gets(numstr); nv[n].so=atoi(numstr); printf("Cho chieu cao :"); gets(numstr); nv[n++].cao=atof(numstr); } void listall() { int j; if (n<1) printf("Danh sach rong\n"); for (j=0;j<n;j++) { printf("\nBan ghi so %d\n",j+1); printf("Ten :%s\n",nv[j].ten); printf("Ma nhan vien : %3d\n",nv[j].so); printf("Chieu cao :%4.2f\n",nv[j].cao); } } void wfile() { FILE *fp; if (n<1) { printf("Danh sach rong , khong ghi\n"); getch(); exit(1); } if ((fp=fopen("nv.rec","wb"))==NULL) { printf("Khong mo duoc file\n"); getch(); 36
  37. exit(1); } else { fwrite(nv,sizeof(nv[0]),n,fp); fclose(fp); printf("Da ghi %3d ban ghi len dia\n",n); } } void rfile() { FILE *fp; if ((fp=fopen("nv.rec","rb"))==NULL) { printf("Khong mo duoc file\n"); getch(); exit(1); } else { while(fread(&nv[n],sizeof(nv[n]),1,fp)==1) { clrscr(); printf("Ban ghi so %3d\n",n+1); printf("Ten nhan vien :%s\n",nv[n].ten); printf("Ma nhan vien :%3d\n",nv[n].so); printf("Chieu cao cua nhan vien :%.2f\n",nv[n].cao); getch(); n++; } fclose(fp); printf("Xong ! Tong so ban ghi da doc %3d\n",n); } } Đ4. Các file ngẫu nhiên Các tập tin đề cập tr−ớc đây là các tập tin tuần tự , nghĩa là tập tin mà khi đọc hay ghi đề theo chế độ tuần tự từ đầu đến cuối tập tin . Đối với tập tin tuần tự ta không thể đọc hay ghi một cách trực tiếp tại một vị trí bất kì trên tập tin . Tập tin ngẫu nhiên cho phép ta truy cập ngẫu nhiên vào những vị trí cần thiết trên tập tin . Các hàm dùng khi truy cập tập tin ngẫu nhiên là : rewind() : di chuyển con trỏ tập tin về đầu tập tin Cú pháp : void rewind(FILE *fp); fseek() : di chuyển con trỏ tập tin về vị trí mong muốn Cú pháp : int fseek(FILE *fp , long sb , int xp) fp - con trỏ tập tin sb - số byte cần di chuyển 37
  38. xp - vị trí xuất phát mà việc dịch chuyển đ−cợ bắt đầu từ đó . xp có thể có các giá trị sau : xp=SEEK_SET hay 0 : xuất páht từ đầu tập tin xp=SEEK_CUR hay 1 : xuất phát từ vị trí con trỏ hiện tại xp=SEEK_END hay 2 : xuất páht từ cuối tập tin ftell() : cho biết vị trí hiện tại của con trỏ tập tin Ta xét ch−ơng trình ví dụ sau : Ch−ơng trình 3-19 : #include #include #include void main() { struct nguoi { char ten[30]; int so; float cao; }nv; int recno; FILE *fp; long int offset; clrscr(); if ((fp=fopen("nhanvien.rec","r"))==NULL) { printf("Khong mo duoc file\n"); getch(); exit(1); } printf("Ban muon doc ban ghi thu may : "); scanf("%d",&recno); recno ; offset=recno*sizeof(nv); if (fseek(fp,offset,0)!=0) { printf("Khong di chuyen duoc con tro file toi do\n"); getch(); exit(1); } fread(&nv,sizeof(nv),1,fp); printf("Ten :%s\n",nv.ten); printf("Ma nhan vien : %3d\n",nv.so); printf("Chieu cao :%4.2f\n",nv.cao); getch(); } Đ5. Lỗi vào ra Nói chung , khi mở tập tin thành công ta có thể ghi lên nó . Tuy nhiên , nhiều tr−ờng hợp không mở đ−ợc tập tin nh−ng ta không biết lỗi do đâu . Để xác định llõi ta dùng hàm 38
  39. ferror() . Hàm này có đối số là con trỏ tập tin . Hàm sẽ có giá trị không nếu không có lỗi gì . Ng−ợc lại hàm cho giá trị khác không . Ta cũng có thể dùng hàm perror() để chỉ nội dung lỗi . Ch−ơng trình 3-20 : #include #include #include #include void main() { FILE *fp; char name[40],numstr[10]; int code; float height; int n,i; clrscr(); fp=fopen("a:\newfile.txt","w"); printf("Cho so nguoi can nhap : "); gets(numstr); n=atoi(numstr); for (i=0;i<n;i++) { printf("Nhap ten : "); gets(name); printf("Nhap ma so : "); gets(numstr); code=atoi(numstr); printf("Nhap chieu cao : "); gets(numstr); height=atof(numstr); fprintf(fp,"%s %d %f",name,code,height); if (ferror(fp)) { perror("Loi ghi file "); getch(); exit(1); } } fclose(fp); } Sau lỗi do ta ghi , trình biên dịch sẽ thông báo lỗi cụ thể trong câu “ Loi ghi file : no such file ỏ directory” Đ6. Vào ra ở mức hệ thống 1.Các tập tin tiêu đề và biến chuẩn : Trong cách vào ra ở mức hệ thống , ta phải khởi tạo bộ đệm rồi đặt dữ liệu vào đó tr−ớc ghi hay đọc . Vào ra ở mức hệ thống có lợi ở chỗ l−ợng mã ít hơn vào ra chuẩn và tốc độ sẽ nhanh hơn . Để dùng các hàm cấp 1 ta phải cần các tập tin tiêu đề sau : 39
  40. io.h - chứa các prototype của các hàm cấp 1 fcntl.h - chứa các định nghĩa quyền truy cập sys/stat.h - chá các định nghĩa thuộc tính dó.h - chứa các thuộc tính theo DOS 2. Tóm tắt các hàm : creat - tạo tập tin mới _creat - tạo tập tin mới theo kiểu nhị phân open - mở tập tin _open - mở tập tin đã tồn tại close và _close - đóng tập tin chmod - thay đổi thuộc tính của tập tin _chmode - thay đổi thuộc tính của tập tin theo kiểu DOS perror - thông báo lỗi (stdlib.h) write - ghi một dãy các byte read - đọc một dãy các byte lseek - dùng di chuyển con trỏ vị trí 3. Đọc tập tin theo cách vào ra hệ thống : Ta có ch−ơng trình đọc tập tin từ đĩa và hiển thị lên màn hình theo cách vào ra hệ thống . Ch−ơng trình 3-21 : #include #include #include #include #include #define BUFFSIZE 512 char buff[BUFFSIZE]; void main(int argc,char *argv[]) { int inhandle,bytes,i; clrscr(); if (argc!=2) { printf("Dang "); getch(); exit(1); } if ((inhandle=open(argv[1],O_RDONLY|O_BINARY)) 0) for (i=0;i<bytes;i++) putch(buff[i]); close(inhandle); } 4. Khởi tạo bộ đệm : Trong ch−ơng trình ta phải định nghĩa bộ đệm bằng phát biểu #define BUFFSIZE 512 char buff[BUFFSIZE] 40
  41. Nhờ đó ta đọc đ−ợc dữ liệu từ đĩa vào bộ đệm buff . Với DOS , kích th−ớc bộ đệm nên chọn là bội số của 512. 5. Mở một tập tin : Cũng giống nh− vào ra bằng hàm cấp 2 , ta phải mở tập tin tr−ớc khi đọc hay ghi bằng phát biểu : inhandle=open(argv[1],O_RDONLY | O_BINARY); Biểu thức này thiết lập sự liên lạc giữa tập tin và hệ điều hành . Trong biểu thức ta cần một hằng lag oflag là dấu hiệu cho biết mức độ dùng tập tin . oflag ý nghĩa O_APPEND Đặt con trỏ ở cuối tập tin O_CREAT Tạo tập tin mới để ghi(không có hiệu quả nếu tập tin đã có ) O_RDONLY Mở một tập tin để chỉ đọc O_RDWR Mở một tập tin để chỉ đọc hay ghi O_TRUNC Mở và cắt bỏ bớt tập tin O_WRONLY Mở tập tin để ghi O_BINARY Mở tập tin kiểu nhị phân O_TEXT Mở tập tin kiểu văn bản 6. Danh số của tập tin : Trong vào ra chuẩn , con trỏ tập tin sẽ nhận đ−ợc ngay khi gọi hàm fopen() còn trong nhập xuất bằng hàm cấp 1 ta nhậ đ−ợc giá trị nguyên gọi là danh số của tập tin . Đây là số gán cho một tập tin cụ thể để tham chiếu đến tập tin này . Nếu hàm open() cho ta giá trị -1 nghĩa là danh số không đúng và phát sinh lỗi . 7. Đọc tập tin vào bộ đệm : Để đọc tập tin vào bộ đệm ta dùng lệnh : byte = read(inhandle , buff , BUFSIZE); Hàm này có 3 đối số : danh số của tập tin , địa chỉ của bộ đệm và số byte cực đại cần đọc . Giá trị của hàm read() chỉ ra số byte đã đọc đ−ợc . 8. Đóng tập tin : Để đóng tập tin ta dùng lệnh close(inhandle); 9. Thông báo lỗi : Khi hàm open() cho giá trị -1 , nghĩa là có lỗi . Dạng lỗi sẽ đ−ợc đọc bằng perror() . Ta có ch−ơng trình ví dụ Ch−ơng trình 3-22 : #include #include #include #include #include #define BUFFSIZE 512 char buff[BUFFSIZE]; void main(int argc,char *argv[]) { int inhandle,bytes,i; clrscr(); if (argc!=2) { printf("Dang "); getch(); exit(1); } if ((inhandle=open(argv[1],O_RDONLY|O_BINARY))<0) 41
  42. { perror("Khong mo duoc file\n"); getch(); exit(1); } while ((bytes=read(inhandle,buff,BUFFSIZE))>0) for (i=0;i #include #include #include #include #include #define BUFFSIZE 1024 char buff[BUFFSIZE]; void main(int argc,char *argv[]) { int inhandle,bytes; void search(char *,int); clrscr(); if (argc!=3) { printf("Dang "); getch(); exit(1); } if ((inhandle=open(argv[1],O_TEXT)) 0) search(argv[2],bytes); close(inhandle); printf("Khong tim thay"); getch(); } void search(char *cau,int buflen) { 42
  43. char *p,*ptr; ptr=buff; while ((ptr=memchr(ptr,cau[0],buflen))!=NULL) if (memcmp(ptr,cau,strlen(cau))==0) { printf("Tu xuat hien lan dau trong cau tai vi tri %d:\n",ptr-buff+1); for (p=ptr;p #include #include #include #include #include #define BUFFSIZE 4096 char buff[BUFFSIZE]; void main(int argc,char *argv[]) { int inhandle,outhandle,bytes; clrscr(); if (argc!=3) { printf("Dang "); getch(); exit(1); } if ((inhandle=open(argv[1],O_RDWR|O_BINARY))<0) { 43
  44. printf("Khong mo duoc file %s\n",argv[1]); getch(); exit(1); } if ((outhandle=open(argv[2],O_CREAT|O_WRONLY|O_BINARY,S_IWRITE)) 0) write(outhandle,buff,bytes); close(inhandle); close(outhandle); printf("Da chep xong"); getch(); } Trong ví dụ trên ta mở một lúc 2 tập tin với danh số là inhamdle và outhandle Biểu thức mở tập tin nguồn không có gì đặc biệt còn biểu thức mở tập tin đích có dạng : outhandle = open(argv[2] ,O_CREAT | O_WRONLY | O_BINARY , S_IWRITE) với O_CREAT để tạo tập tin trên đĩa O_WRONLY để chỉ ghi lên tập tin O_BINARY để mở tập tin theo kiểu nhị phân Khi mở tập tin với O_CREAT , đối thứ 3 của open() là một trong 3 trị : S_IWRITE : chỉ cho phép ghi lên tập tin S_IREAD : chỉ cho phép đọc từ tập tin S_IWRITE | S_IREAD : cho phép đọc và ghi lên tập tin Để dùng các trị này phải khai báo #include sau khai báo #include . Hàm write() có đối t−ơng tự nh− read() . Trong vòng lặp while hàm read() báo số byte đọc đ−ợc qua biến bytes và hàm write() sẽ biết số bytes cần ghi vào tập tin đích . Trong ch−ơng trình ta dùng bộ đệm với kích th−ớc khá lớn để ch−ơng trình chạy nhanh . 44
  45. Ch−ơng 4 : Bộ nhớ và hiển thị kí tự Đ1. Khái niệm chung Trong phần này ta sẽ xem xét việc xử lí hiển thị kí tự bằng cách xâm nhập trực tiếp vào bộ nhớ (direc memory access-DMA) . Ta sẽ tìm hiểu cách xâm nhập trực tiếp màn hình . Cách này nhanh hơn là dùng các hàm của C . Đ2. Các toán tử bitwise 1. Toán tử bitwise and (&) : C dùng 6 toán tử bitwise đ−ợc tóm tắt trong bảng sau Phép toán Kí hiệu and & or | xor ^ dịch phải >> dịch trái > : Toán tử này làm việc trên một biến duy nhất . Toán tử này dịch từng bit trong toán hạng sang phải . Sô bit dịch chuyển đ−ợc chỉ định trong số đi theo sau toán tử . Việc dịch phải một bit đồng nghĩa với việc chia toán hạng cho 2 . Ví dụ : 0 1 1 1 0 0 1 0 dịch sang phải 1 bit sẽ là 0 0 1 1 1 0 0 1 4. Đổi từ số hex sang số nhị phân : Ta dùng các toán tử bitwise để đổi một số từ hệ hex sang hệ 2 . Ch−ơng trình nh− sau : Ch−ơng trình 4-1 : #include #include void main() { int i,num,bit; unsigned int mask; char string[10],ch; clrscr(); 46
  46. do { mask=0x8000; printf("\nBan cho mot so : "); scanf("%x",&num); printf("Dang nhi phan cua so %x la : ",num); for (i=0;i >= 1; } printf("\nBan muon tinh tiep khong(c/k)?"); ch=getch(); } while (ch=='c'); getch(); } Trong ch−ơng trình trên ta dùng vòng lặp for để duyệt qua 16 bit của biến nguyên từ trái qua phải . Lõi của vấn đề là các phát biểu : bit = (mask&num)? 1 : 0; mask >>=1 Trong phát biểu đầu tiên mask là biến chỉ có một bit 1 duy nhất ở phía trái nhất . Mask này đ−ợc & với num để xem bit trái nhất của num có là 1 hay là 0 . Nếu kết quả khác 0 (true) bit t−ơng ứng của num là 1 còn ng−ợc lại bit t−ơng ứng là 0 . Sau mỗi lần & mask đ−ợc dịch trái 1 bit để xác định bit tiếp theo của num là 0 hay 1 . 5. Các toán tử bitwise khác : a. Toán tử xor ^ : Toán tử xor trả về trị 1 khi chỉ có 1 bit chứ không phải 2 bit có trị là 1 000 011 101 110 Toán tử xor cần khi lật bit nghĩa là hoán chuyển giữa 1 và 0 vì 1 xor với 1 là 0 và 1 xor với 0 là 1 . Ví dụ để lật bit thứ 3 trong biến ch ta dùng : ch ^ 0x08 b. Toán tử dịch phải << : Toán tử này t−ơng tự toán tử dịch trái . Giá trị của bit chèn vào bên phải luôn luôn bằng 0 . Dịchphải t−ơng ứng với việc nhân số đó cho 2 . c. Toán tử đảo : Toán tử này là toán tử một ngôi . Nó tác động lên các bit của toán hạng và đảo trị của bit từ 1 sang 0 và từ 0 sang 1 . Đảo 2 lần một số ta lại nhận đ−ợc số cũ . 47
  47. Đ3. Bộ nhớ màn hình 1. Khái niệm chung : Màn hình thông th−ờng có 25 dòng và mỗi dòng có 80 kí tự . Nh− vậy toàn bộ màn hình có 2000 kí tự . Mỗi kí tự trên màn hình t−ơng ứng với một địa chỉ cụ thể trong bộ nhớ màn hình . Mỗi kí tự dùng 2 byte của bộ nhớ này : byte thứ nhất chứa mã ASCII của kí tự (từ 0 đến 255 nay từ 0 đến ff)và byte thứ 2 chứa thuộc tính của nó . Nh− vậy để hiển thị 2000 kí tự , bộ nhớ màn hình phải cần 4000 byte . Bộ nhớ màn hình đơn sắc bắt đầu tại B000h và kết thúc tại B0F9F . Nếu ta có màn hình màu thì vùng nhớ cho chế độ văn bản sẽ bắt đầu từ B8000h và kết thúc tại B8F9F . Khi muốn hiển thị kí tự trên màn hình , đoản trình th− vện C sẽ gọi đoản trình ROM-BIOS để đặt kí tự cần hiển thị vào địa chỉ t−ơng ứng trong bộ nhớ nàm hình. Nếu muốn có tốc độ nhanh , hãy chèn trực tiếp các giá trị trên vào vùng nhớ màn hình . 2. Con trỏ far : Khi biết địa chỉ , cách thông dụng để đ−a giá trị vào bộ nhớ là dùng con trỏ . Do vậy nếu ta cần đ−a kí tự vào vị trí đầu tiên của vùng nhớ màn hình thì ta viết : int *ptr ; ptr = 0xB800; *(ptr)=ch; Đoạn ch−ơng trình trên có vẻ hợp lí nh−ng lại không làm việc vì biến con trỏ thông th−ờng có hai byte trong khi địa chỉ B0000h lại dài 5 chữ số (2,5 byte) . Lí do dẫn đến tình trạng này là do con trỏ th−ờng dùng để chứa đại chỉ nằm trong một đoạn duy nhất mà thôi . Trong họ 8086 , một đoạn dài 10000h hay 65535 byte . Bên trong các đoạn địa chạy từ 0h đến FFFFh . Thông th−ờng các dữ liệu của ch−ơng trình C nằm trong một đoạn nên để thâm nhập các địa chỉ nằm ngoài đoạn ta phải dùng một cơ chế khác . Bên trong 8086 , tình trạng này đ−ợc khắc phục bằng cách dùng các thanh ghi gọi là thanh ghi đoạn . Các địa chỉ nằm ngoài đoạn đ−cợ tạo lập bằng tổ hợp địa chỉ đoạn và địa chỉ offset . B 0 0 0 0 7 D 0 B 0 7 D 0 Trong hình trên địa chỉ đoạn B000h đ−ợc dịch trái 4 bit rồi cộng với địa chỉ offset 07D0 tạo ra địa chỉ tuyệt đối B07D0h. 3. Dùng địa chỉ đoạn : offset trong C : Nh− vậy khi địa chỉ nằm bên ngoài đoạn dữ liệu , C dùng tổ hợp đoạn : offset và yêu cầu dạng biểu diễn 32 bit(4 byte , 8 chữ số hex) với 4 chữ số cho địa chỉ đoạn và 4 chữ số cho địa chỉ offset . Do vậy C coi địa chỉ tuyệt đối B07D0 là 0xB00007D0 (B000 và theo sau là 07D0) . Trong C con trỏ 32 đ−ợc tính bằng cách dịch địa chỉ đoạn sang trái 16 bit và cộng với địa chỉ offset . Do con trỏ thông th−ờng không thể cất giữ địa chỉ dài 32 bit nên ta phải dùng con trỏ far Con trỏ này cất giữ địa chỉ dài 4 byte . Vì vậy ch−ơng trình sẽ là : int far *ptr ; ptr = 0xB8000000; *(ptr)=ch; 4. Dùng một kí tự để tô màn hình : Chúng ta dùng con trỏ far để ghi lên màn hình 2000 bản sao của một kí tự . Điều này t−ơng tự nh− dùng putch() . Ch−ơng trình kết thúc ghi gõ x Ch−ơng trình 4-2 : #include 48
  48. #include #define length 2000 void main() { int far *fptr; int add; char ch; clrscr(); printf("Go vao mot ki tu , go lai de thay doi"); fptr=(int far*)0xB8000000; while((ch=getche())!='x') for (add=0;add #include #define rowmax 25 #define colmax 80 void main() { int far *fptr; int row,col; char ch; clrscr(); printf("Go vao mot ki tu , go lai de thay doi"); fptr=(int far*)0xB8000000; while((ch=getche())!='x') for (row=0;row<rowmax;row++) for (col=0;col<colmax;col++) *(fptr+row*colmax+col)=ch|0x0700; } 5.Trình xử lí văn bản theo dòng: Để biết thên về sự tiện lợi của con trỏ far ta xét thêm một trình xử lí văn bản theo dòng . Trình xử lí này chỉ làm việc trên một dòng văn bản . Ta sẽ tiến hành theo 2 b−ớc : đầu tiên là một ch−ơng trình cho phép ng−ời dùng gõ vào một dòng 49
  49. và di chuyển con nháy tới lui . Có thể xoá kí tự nhờ di chuyển con nháy tới đó và ghi đè lên nó . Ch−ơng trình nh− sau : Ch−ơng trình 4-4 : #include #include #define colmax 80 #define rarrow 77 #define larrow 75 #define video 0x10 #define ctrlc '\x03' int col=0; int far *fptr; union REGS reg; void main() { char ch; void clear(void); void cursor(void); void insert(char); fptr=(int far*)0xB8000000; clear(); cursor(); while((ch=getch())!=ctrlc) { if (ch==0) { ch=getch(); switch (ch) { case rarrow : if (col 0) col; break; } } else if (col<colmax) insert(ch); cursor(); } } void cursor() { reg.h.ah=2; reg.h.dl=col; reg.h.dh=0; 50
  50. reg.h.bh=0; int86(video,®,®); } void insert(char ch) { *(fptr+col)=ch|0x0700; ++col; } void clear() { int j; for (j=0;j #include #define rowmax 25 #define colmax 80 void main() { int far *fptr; int row,col; char ch; clrscr(); printf("Go vao mot ki tu , go lai de thay doi"); fptr=(int far*)0xB8000000; while((ch=getche())!='x') for (row=0;row<rowmax;row++) 51
  51. for (col=0;col #include #define rowmax 25 #define colmax 80 void main() { int far *fptr; char ch,attr=0x07; void fill(char,char); clrscr(); printf("Go n cho chu binh thuong,\n"); printf("Go b cho chu xanh nuoc bien,\n"); printf("Go i cho chu sang,\n"); printf("Go c cho chu chop nhay,\n"); printf("Go r cho chu dao mau\n"); while((ch=getche())!='x') { switch (ch) { case 'n':attr=0x07; break; case 'b':attr=attr&0x88; attr=attr|0x01; break; case 'i':attr=attr^0x08; break; case 'c':attr=attr^0x80; break; case 'r':attr=attr&0x88; attr=attr|0x70; break; } fill(ch,attr); } } void fill(char ch,char attr) { int far *fptr; int row,col; fptr=(int far*)0xB8000000; for (row=0;row<rowmax;row++) 52
  52. for (col=0;col #include #define colmax 80 #define rarrow 77 #define larrow 75 #define video 0x10 #define norm 0x07 #define blue 0x01 #define bkspc 8 #define altu 22 #define ctrlc '\x03' int col=0; int length=0; int far *fptr; union REGS reg; void main() { char ch,attr=norm; void clear(void); void cursor(void); void insert(char,char); void del(void); fptr=(int far*)0xB8000000; clear(); cursor(); while((ch=getch())!=ctrlc) { if (ch==0) { ch=getch(); switch (ch) { case rarrow : if (col 0) col; break; case altu : attr=(attr==norm)? blue:norm; } 53
  53. } else switch (ch) { case bkspc: if (length>0) del(); break; default : if (length col;i ) *(fptr+i)=*(fptr+i-1); *(fptr+col)=ch|attr<<8; ++length; ++col; } void del() { int i; for (i=col;i<=length;i++) *(fptr+i-1)=*(fptr+i); length; col; } void clear() { int j; for (j=0;j<2000;j++) *(fptr+j)=0x0700; } Khi gõ tổ hợp phím Alt+U sẽ lật biến attr qua lại giữa norm(thuộc tính 07) và blue (cho chữ màu xanh - thuộc tính 01) . Hàm insert(0 có vòng lặp for dùng để thâm nhập trực 54
  54. tiếp bộ nhớ và con trỏ far để dịch các kí tự sang trái khi cần chèn . Tiến trìmh dịch phải bắt đầu từ cuối câu để tránh ghi đè lên . Đ4. Các kiểu bộ nhớ trong C 1. Địa chỉ đoạn và offset : Trong C kiểu bộ nhớ là khái niệm để chỉ về l−ợng các phần bộ nhớ khác nhau mà ch−ơng trình có thể chiếm . C cho phép 6 kiểu bộ nhớ là tiny , small , compact , medium , large và huge . Kiểu bộ nhớ mặc định là small . Bộ vi xử lí dùng các thanh ghi 16 bit để ghi địa chỉ . Thanh ghi 16 bit l−u đ−ợc ffffh byte hay 65536 hay 64 Kb địa chỉ . Vùng nhớ có kích th−ớc này gọi là đoạn . Để truy cập địa chỉ nằm ngoài đoạn , bộ vi xử lí phải dùng hai thanh ghi là thanh ghi đoạn và thanh ghi offset . Địa chỉ thực đ−ợc tính bằng cách dịch địa chỉ của thanh ghi đoạn sang trái 4 bit rồi cộng với thanh ghi offset . Làm nh− vậy ta đánh địa chỉ đ−ợc fffffh hay 1048576 = 1Mb . 2. Hai loại chỉ thị của bộ vi xử lí : Bộ vi xử lí dùng hai kĩ thuật khác nhau để tham chiếu dữ liệu trong bộ nhớ . Nếu vị trí cần tham chiếu nằm trong đoạn 64Kb và đoạn này đã đ−ợc chỉ định trong thanh ghi đoạn thì bộ vi xử lí chỉ cần dùng một lệnh duy nhất để truy cập dữ liệu . Cách này t−ơng ứng với việc dùng con trỏ near trong C và thực hiện rất nhanh . Trái lại nếu bộ vi xử lí cần tham chiếu ô nhớ nằm ngoài đoạn thì đầu tiên nó phải thay đổi địa chỉ đoạn và sau đoa là địa chỉ offset . Điều này t−ơng ứng với việc dùng con trỏ far trong C và thực hiện khá chậm . 3. Các kiểu Compact , small , medium và large : Có 4 loại chỉ thị của bộ vi xử lí ứng với 4 kiểu bộ nhớ trong C Kiểu Mã Dữ liệu small near near medium far near compact near far large far far Nếu mã ch−ơng trình nằm gọn trong một đoạn 64 K và mã dữ liệu nằm gọn trong một đoạn 64 K khác thì kiểu bộ nhớ small là thích hợp . Nếu mã ch−ơng trình lớn hơn 64 K và mã dữ liệu nằm gọn trong một đoạn 64 K khác thì hãy dùng kiểu bộ nhớ medium. Nếu mã ch−ơng trình nhỏ hơn 64 K và mã dữ liệu lớn hơn 64 K thì hãy dùng kiểu bộ nhớ compact. Nếu cả mã ch−ơng trình và mã dữ liệu lớn hơn 64 K thì hãy dùng kiểu bộ nhớ large . 4. Kiểu tyni và kiểu huge : Kiểu tyni đ−ợc dùng trong các tr−ờng hợp đặc biệt với l−ợng bộ nhớ cho cả mã ch−ơng trình lẫn mã dữ liệu nằm gọn trong một đoạn . Kiểu này đ−ợc dùng để tạo ra tập tin dạng *.com . Kiểu huge đ−ợc dùng chô một mục dữ liệu (th−ờng là mảng ) mà bản thân nó lớn hơn 64K . Đ5. Từ chứa danh mục thiết bị Đây là một vùng bộ nhớ dài 2 byte nằm trong vùng nhớ thấp có địa chỉ tuyệt đối là 410h chứa thông tin về thiết bị đ−ợc nối với máy tính. Để truy cậo từ này ta dùng con trỏ far . Con trỏ sẽ chỉ tới đoạn 0000 , địa xhỉ offset là 0410h và đ−ợc biểu diễn trong C là 00000410 hay 0x410 55
  55. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 có Số máy in ổ đĩa đang có Không Có lắp máy dùng in nối tiếp RAM mạch Có lắp game hệ thống adaptor 00=16K Số cổng nối tiếp 01=32K Có lắp chíp 11=64K DMA Kiểu màn hình 01=màu 40 cột Số ổ đĩa 10=màu 80 cột 00 = 1 ổ 11=đơn sắc 80 cột 01 = 2 ổ 10 = 3 ổ 11 = 4 ổ Để xem xét từng bit và nhóm bit trong từ này chúng ta sẽ dùng các toán tử bitweise . Nói chung ta sẽ dịch từ chứa danh mục thiết bị sang phải và đ−a các bit cần quan tâm vào phía phải của từ và che các bit không quan tâm ở phái trái bằng toán tử and . Ngoài từ chứa danh mục thiết bị ta có thể đọc từ chứa kích th−ớc bộ nhớ tại địa chỉ 413h . Ch−ơng trình 4-8 : #define eqlist 0x410 #define memsiz 0x413 #include #include void main() { int far *fptr; unsigned int eq,data; clrscr(); fptr=(int far *)eqlist; eq=*(fptr); data=eq>>14; printf("So may in la : %d\n",data); if (eq&0x2000) printf("Co may in noi tiep\n"); data=(eq>>9)&7; printf("So cong noi tiep la :%d\n",data+1); if (eq&1) { data=(eq>>6)&3; printf("So dia mem la :%d\n",data); } else 56
  56. printf("Khong co dia mem\n"); data=(eq>>4)&3; switch (data) { case 1: printf("Man hinh mau 40 cot\n"); break; case 2: printf("Man hinh mau 80 cot\n"); break; case 3: printf("Man hinh don sac 80 cot\n"); } fptr=(int far *)memsiz; printf("Dung luong bo nho :%dKbyte\n",*(fptr)); getch(); } 57
  57. Ch−ơng 5 : truy cập trực tiếp bộ nhớ Đ1.Các hàm truy cập theo địa chỉ phân đoạn 1. Hàm pokeb() : Dùng để gửi một kí tự value vào bộ nhớ tại địa chỉ phân đoạn off . Nguyên mẫu của hàm trong dos.h là : void pokeb(unsigned seg,unsigned off , char value) 2. Hàm peekb() : Nhận một byte tại địa chỉ seg:off . Nguyên mẫu của hàm trong dos.h là : char peekb(unsigned seg,unsigned off) 3. Hàm poke() : Gửi một số nguyên value vào bộ nhớ tại địa chỉ seg:off . Nguyên mẫu của hàm trong dos.h là : void poke(unsigned seg,unsigned off , int value) 4. Hàm peek() : Nhận một word tại địa chỉ seg:off . Nguyên mẫu của hàm trong dos.h là : void peek(unsigned seg,unsigned off ) 5. Hàm movedata() : Sao n byte từ địa chỉ seg_gui:off_gui đến địa chỉ seg_nhan:off_nhan . Nguyên mẫu của hàm trong menu.h là : void movedata(unsigned seg_gui,unsigned off_gui , unsigned seg_nhan,unsigned off_nhan , int n) Đ2. Đổi từ địa chỉ phân đoạn sang địa chỉ thực 1. Đổi từ địa chỉ thực : Để đổi từ địa chỉ thực sang địa chỉ phân đoạn ta dùng macro sau : unsigned FP_SEG(địa chỉ thực) unsigned FP_OFF(địa chỉ thực) 2. Đổi từ địa chỉ phân đoạn : Để đổi từ địa chỉ phân đoạn sang địa chỉ thực ta dùng macro : void far *MK_FP(seg:off) Ví dụ : Sau khi thực hiện các câu lệnh: char buf[100] unsigned ds,dx; ds= FP_SEG(buf) dx= FP_OFF(buf) thì ds:dx chứa địa chỉ của nảmg buf . Sau khi thực hiện câu lệnh : char *pchar; pchar = (char *) MK_FP(0xb800:0) thì pchar trỏ tới đầu bộ nhớ màn hình . Khi đó ta có thể dùng các lệnh gán để truy cập trực tiếp tới bộ nhớ màn hình . Ch−ơng trình 5-1 : Lập ch−ơng trình xác định địa chỉ của một ngắt . #include #include #include void main() { unsigned char far *p; int n,k; unsigned seg,off; clrscr(); p=(unsigned char far*)MK_FP(0,0); 58
  58. while(1) { printf("\nSo hieu ngat(Bam 0 de ket thuc): "); scanf("%d",&n); if (n==0) break; k=(n-1)*4; off=p[k]+256*p[k+1]; seg=p[k+2]+256*p[k+3]; printf("\nDia chi cua ngat %x : %x",seg,off); } } Số hiệu của ngắt đ−ợc đánh số từ 0 nh−ng n đ−ợc nhập từ 1 , mỗi ngắt chiếm 4 byte nên ta có k=(n-1)*4; 59
  59. Ch−ơng 6 : đồ hoạ trong c Đ1. Khái niệm chung Turbo C có khoảng 100 hàm đồ hoạ . Các hàm này đ−ợc chia làm hai kiểu : Loại theo kiểu văn bản ( ví dụ hàm tạo cửa sổ ) Loại theo kiểu đồ hoạ Đ2. Hàm theo kiểu văn bản Các hàm này đ−ợc dùng với màn hình đơn sắc hay màn hình đồ hoạ . Ta phải đặt vào đầu ch−ơng trình dòng #include . 1. Cửa sổ : Mục đích của các hàm đồ hoạ theo kiểu văn bản là tạo ra các cửa sổ . Cửa sổ là vubgf hình chữ nhật trên màn hình dùng để giới hạn vùng xuất dữ liệu . Nếu ta soạn thảo văn bản trong cửa sổ thì con nháy chỉ di chuyển trong phạm vi của cửa sổ chứ không phải toàn bộ màn hình . Ta xét một ch−ơng trình tạo ra cửa sổ và điền đầy vào đó dòng “ Xin chao “ Ch−ơng trình 6-1 : #include #include #define left 10 #define top 8 #define right 52 #define bot 21 void main() { int i; clrscr(); window(left,top,right,bot); textcolor(RED); textbackground(GREEN); for (i=0;i<100;i++) { cputs(" Xin chao "); delay(100); } gotoxy(15,8); cputs("Ket thuc"); getche(); } Trong ch−ơng trình ta có hàm : window(x1,y1,x2,y2) dùng để ấn định một cửa sổ có toạ độ góc trên trái là x1,y1 và góc d−ới phải là x2,y2 textcolor(RED) để ấn định màu chữ là đỏ textbackcolor(GREEN) để ấn định màu nền văn bản là xanh lá cây gotoxy(x,y) để di chuyển con nháy về toạ độ x,y 60
  60. cputs(string) để đặt chuỗi string trong một cửa sổ . Khi gặp biên của cửa sổ chuỗi sẽ đ−ợc xuống dòng . Màu trong chế độ đồ hoạ đ−ợc quy định nh− sau : Số Màu 0 BLACK 1 BLUE 2 GREEN 3 CYAN 4 RED 5 MAGENTA 6 BROWN 7 LIGHTGRAY 8 DARKGRAY 9 LIGHTBLUE 10 LIGHTGREEN 11 LIGHTCYAN 12 LIGHTRED 13 LIGHTMAGENTA 14 YELLOW 15 WHITE 2. Dời chỗ văn bản : Muốn dời chỗ một vùng hình chữ nhật của văn bản từ nơi này sang nơi khác ta dùng hàm movetext() . Ta xét ch−ơng trình sau tạo ra một cửa sổ , điền đầy cửa sổ bằng một đoạn văn bản và dời cửa sổ sang vị trí khác trên màn hình Ch−ơng trình 6-2 : #include #include #define left 26 #define top 7 #define right 65 #define bot 20 #define desleft 1 #define destop 1 #define numcolor 16 void main() { int i; clrscr(); window(left,top,right,bot); textbackground(GREEN); for (i=0;i<100;i++) { textcolor(i%numcolor); cputs(" Xin chao "); delay(200); } delay(2000); movetext(left,top,right,bot,desleft,destop); 61
  61. getche(); } Hàm movetext(x1,y1,x2,y2,x0,y0) dùng di chuyển cửa sổ x1,y1,x2,y2 đến vị trí mới mà toạ độ góc trên trái bây giờ là x0,y0 . 3.L−u trữ và phục hồi màn hình văn bản : Ta có thể l−u trữ một vùng văn bản hình chữ nhật trên màn hình và sau đó phục hồi lại tại một vị trí nào đó trên màn hình . Nhờ vậy ta có thể tạo một cửa sổ nhỏ trên đầu văn bản hiện hành . Ta xét ví dụ sau Ch−ơng trình 6-3 : #include #include #define left 1 #define top 1 #define right 80 #define bot 25 int buf[80][25]; void main() { int i,x,y; clrscr(); for (i=0;i<300;i++) cputs(" Turbo C "); getche(); gettext(left,top,right,bot,buf); clrscr(); getch(); puttext(left,top,right,bot,buf); getch(); } Ch−ơng trình l−u toàn bộ màn hình vào vùng đệm có tên là buf nhớ hàm gettext(x1,y1,x2,y2,buf) l−u vn trong hình chữ nhật x1,y1,x2,y2 vào biến buf . Hàm puttext(x1,y1,x2,y2,buf) đặt lại văn bản trong hình chữ nhật x1,y1,x2,y2 l−u bởi biến buf ra màn hình . 3. Một số hàm đồ hoạ văn bản khác : • void clreol(void) : xoá đến cuối dòng • int cprintf(const char *format) đ−a kí tự ra một cửa sổ • void textattr(int newattr) ấn định màu cùng lúc cho văn bản và nền • void gettextinfo(struct text_info *r) : đọc các thông tin nh− kiểu màn hình , vị trí và kích th−ớc cửa sổ , màu nền và văn bản ,vị trí con nháy • void normvideo(void) trả lại độ sáng cũ • void insline(void) : chèn thêm một dòng • void delline(void) xoá một dòng • void hightvideo(void) tăng độ sáng • void lowvideo(void) : giảm độ sáng • void textmode(int newmode) chuyển đổi giữa các kiểu văn bản . Hàm dùng các đối số sau : Trị Hằng ý nghĩa 62
  62. -1 LASTMODE Kiểu văn bản tr−ớc đó 0 BW40 Đen trắng 40 cột 1 C40 Màu 40 cột 2 BW80 Đen trắng 80 cột 3 C80 Màu 80 cột 7 MONO Đơn sắc 80 cột Đ3. Các hàm đồ hoạ 1. Khởi tạo kiểu đồ hoạ : Để khởi tạo đồ hoạ ta dùng hàm initgraph() đ−ợc khai báo trong graphics.h với cú pháp : void far initgraph(int *graphdrive , int *graphmode , char *path); với các biến graphdrive chứa trình điều khiển đồ hoạ graphmode kiểu đồ hoạ path đ−ờng dẫn đến th− mục chứa các drive đồ hoạ . Trong phần này ta phải dùng hai dấu \\ vì dấu \ đã đ−ợc dùng cho kí tự escape . Để thuận tiện ta khởi tạo đồ hoạ tự động bằng cách viết : graphdrive = detect; initgraph(graphdrive , graphmode , path); Ta có ch−ơng trình vẽ đ−ờng thẳng và đ−ờng tròn nh− sau : Ch−ơng trình 6-4 : #include #include void main() { int gd,gm; gd=DETECT; initgraph(&gd,&gm,"c:\\bc\\bgi"); line(0,0,100,100); circle(100,100,50); getche(); closegraph(); } 2. Lỗi đồ hoạ : Để biết lỗi đồ hoạ ta dùng hàm int far graphresult(void) . Sau khi biết mã lỗi ta chuyển nó sang cho hàm grapherrormsg() . Hàm này trả về con trỏ chỉ đén lỗi . Sau đây là ch−ơng trình minh hoạ Ch−ơng trình 6-5 : #include #include #include #include void main() { int gd,gm,ge; char *ep; gd=DETECT; initgraph(&gd,&gm,"c:\\bc\\bgi"); ge=graphresult(); 63
  63. if (ge) { printf("Ma loi %d",ge); ep=grapherrormsg(ge); puts(ep); getch(); exit(1); } line(0,0,100,100); circle(100,100,50); getche(); closegraph(); } 3. Đ−ờng thẳng và màu sắc : Để thiết lập dạng , mẫu và bề dày của đ−ờng thẳng ta dùng hàm void far setlinestyle(int style,int pattern, int thickness) . Tham biến style có thể là : Trị Hằng Y nghĩa 0 SOLID_LINE Đ−ờng đặc 1 DOTTED_LINE Đ−ờng chấm 2 CENTER_LINE Đ−ờng gạch 3 DASHED_LINE Đ−ờng gạch dài 4 USERBIT_LINE Đ−ờng tự tạo Tham biến thickness có thể nhân một trong hai giá trị sau : Trị Hằng Y nghĩa 1 NORM_WIDTH dãy 1 điểm ảnh 2 THICK_WIDTH dãy 3 điểm ảnh Để xác định màu cho đ−ờng thẳng ta dùng hàm void setcolor(int color) . Ta có ch−ơng trình sau Ch−ơng trình 6-6 : #include #include #include #include void main() { int gd,gm,ge; int x1=0,y1=0; int x2=199,y2=199; int xc=100,yc=100; int r=90; char *ep; gd=DETECT; initgraph(&gd,&gm,"c:\\bc\\bgi"); ge=graphresult(); if (ge) 64
  64. { printf("Ma loi %d",ge); ep=grapherrormsg(ge); puts(ep); getch(); exit(1); } setlinestyle(1,1,1); setcolor(LIGHTGREEN); line(x1,y1,x2,y2); circle(xc,yc,r); getche(); closegraph(); } 4. Ellipse và đa giác : Để vẽ ellipse ta dùng hàm void far ellipse(int x,int y , int gd,int gc,int xr , int yr) x,y - toạ độ tâm ellipse gd,gc - góc bắt đầu vẽ và góc kết thúc vẽ xr,yr - toạ độ tâm ellipse Ch−ơng trình 6-7 : Vẽ một loạt ellipse #include #include #include #include void main() { int gd,gm,ge; int x=150,y=150; int g1=0,g2=360; int xr=150,yr; char *ep; gd=DETECT; initgraph(&gd,&gm,"c:\\bc\\bgi"); ge=graphresult(); if (ge) { printf("Ma loi %d",ge); ep=grapherrormsg(ge); puts(ep); getch(); exit(1); } setcolor(RED); for (yr=0;yr<100;yr+=10) ellipse(x,y,g1,g2,xr,yr); getche(); closegraph(); } Để vẽ đa giác ta dùng hàm 65
  65. void far drawpoly(int number , int far *addrlist) number - số đỉnh đa giác cộng thêm 1 addrlist - mảng chứa toạ độ các đỉnh , toạ độ điểm đầu và cuối phải trùng nhau Ch−ơng trình 6-8 : Vẽ một hình hộp chữ nhật #include #include #define left 50 #define top 50 #define right 150 #define bot 180 int a[]={150,50,180,20,180,135,150,180}; int b[]={50,47,150,47,180,17,95,17,50,47}; void main() { int gd,gm; gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); setcolor(RED); rectangle(left,top,right,bot); setcolor(2); drawpoly(4,a); drawpoly(5,b); getche(); closegraph(); } 5. Tô màu và mẫu tô : Turbo C có nhiều hàm để tô màu . Hàm thông dụng nhất để tô bên trong một đa giác và mẫu tô hiện hành là void far fillpoly(int number , int far * addlist) . Màu và mẫu tô đ−ợc thiết lập nhờ hàm void far setfillstyle(int pattern , int color) . Biến pattern có thể nhận một trong các trị sau : Trị Hằng ý nghĩa 0 EMPTY_FILL Rỗng 1 SOLID_FILL Màu đặc 2 LINE_FILL Đ−ờng ngang 3 LTSLASH_FILL //// chéo mảnh 4 SLASH_FILL //// chéo dày 5 BKSLASH_FILL \\\\ chéo ng−ợc 6 LTBKSLASH_FILL \\\\ chéo ng−ợc mảnh 7 HATCH_FILL Sọc d−a th−a 8 XHATCH_FILL Sọc d−a dày 9 INTERLEAVE_FILL Đ−ờng xen kẽ 10 WIDE_DOT_FILL Chấm th−a 11 CLOSE_DOT_FILL Chấm dày 12 USER_FILL Mẫu tự do 66
  66. Biến color đ−ợc chọn theo danh sách đã liệt kê trong phần setcolor(). Nếu dùng giá trị không hợp lệ cho pattern và color thì hàm graphresult() sẽ trả về mã lỗi là -11 . Hàm floodfill() dùng để to màu một hình kín . Nó cần biết điểm bắt đầu tô . Hàm sẽ tô cho đến khi gặp đ−ờng biên có màu xác định bằng biến border . Có thể tô bên trong hay ngoài hình vẽ tuỳ điểm bắt đầu . Nếu tô một vùng không kín thì màu tô sẽ lan ra trong lẫn ngoài vật thể . Sau đây là ch−ơng trình tô vòng tròn . Ch−ơng trình 6-9 : #include #include #define x 200 #define y 200 #define r 150 void main() { int gd,gm; gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); setcolor(RED); circle(x,y,r); setfillstyle(CLOSE_DOT_FILL,BLUE); floodfill(x,y,RED); getche(); closegraph(); } Màu dùng để tô có thể giống hay khác với màu dùng cho đ−ờng viền của vùng . Tuy vậy màu dùng cho tham biến border của floodfill() phải giống màu vè vật thể (trong ch−ơng trình là màu RED) 6. Đồ thị : Turbo C có nhiều hàm giúp đơn giản hoá việc vẽ đồ thị các hàm là bar() , bar3d() và pieslice() . void bar (int top , int left , int right , int bottom) void far bar3d(int left , int top , int right , int right , int bottom , int depth , int topflag) topflag = 0 - có nắp , topflag = 1 - không có nắp void far pieslice(int x , int y , int startangle , int endangle , int r) Ta có ch−ơng trình minh hoạ Ch−ơng trình 6-10 : #include #include #define n 10 #define bwidth 10 #define sep 12 #define di (bwidth+sep) #define shft 15 #define width ((n+1)*di) #define left 5 #define depth 3 #define topflag 1 #define bot 170 67
  67. #define top 5 #define ppd (float)(bot-top)/100 void main() { int gd,gm,i; int data[n]={41,47,54,62,63,59,75,83,89,96}; gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); setcolor(RED); rectangle(top,left,left+width,bot); for (i=0;i #include #define n 6 #define r 90 int data[n]={11,19,44,32,15,7}; void main() { int gd,gm,i,x,y; float datasum,startangle,endangle,relangle; gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); x=getmaxx()/2; y=getmaxy()/2; setcolor(RED); for (i=0,datasum=0;i<n;i++) datasum+=data[i]; endangle=0; relangle=10; for (i=0;i<n;i++) { startangle=(i+1)*relangle; setfillstyle(SOLID_FILL,i%4); pieslice(x,y,startangle,endangle,r); getch(); } 68
  68. getche(); closegraph(); } 7. Viewport : Viewport là một vùng nhì thấy đ−ợc của màn hình . Khi mới khởi động viewport là toàn bộ màn hình . Để xác định một viewport ta dùng hàm setviewport() có cú pháp : void far setviewport(int left , int top , int right , int bot , int clip) Tham biến clip cho biết hình vẽ có hiện ra ngoài viewport hay không . Nếu clip #include void main() { int gd,gm,i; int left=0,top=0,right=150,bot=150; int x1=0,y1=0,x2=199,y2=199; int x=100,y=100; int clip=1; int r=90; gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); setviewport(left,top,right,bot,clip); setcolor(RED); rectangle(left,top,right,bot); line(x1,y1,x2,y2); circle(x,y,r); getch(); closegraph(); } 8. Vẽ theo toạ độ t−ơng đối : Trong C ta có thể dùng toạ độ t−ơng đối so với điểm hiện hành CP-current point . Để vẽ đ−ờng thẳng ta dùng hàm void far lineto(int x, int y) . Hàm này vẽ đ−ờng thẳng từ điểm CP đến điểm mới có toạ độ là x,y . Hàm void far linerel(int dx , int dy) vẽ đ−ờng thẳng từ CP(xc,yc) đến điểm có toạ độ (xc+dx,yc+dy) . Th−ờng ta hay kết hợp với hàm void far moveto(int x, int y) để di chuyển điểm hiện hành tới điểm mới có toạ độ (x,y) Ch−ơng trình 6-13 : Vẽ một bàn cờ #include #include #define max 160 #define grid 20 #define size 18 void main() { int gd,gm,i,j; void square(int ); 69
  69. gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); for (i=0;i #include #include void main() { int gd,gm,x,y; double g,sg; gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); line (1,100,200,100); for (x=0;x<200;x++) { g=((double)x/200)*(2*3.14159); sg=sin(g); y=100-100*sg; putpixel(x,y,RED); } getch(); closegraph(); } Để xác định màu của một điểm ta dùng hàm int getpixel(int x,int y) 10 . ảnh bit và làm ảnh chuyển động : Để cất gữi một hình ảnh vào bộ nhớ ta dùng hàm : void far getimage(int left , int top , int right , int bot , void far * addbuf) 70
  70. left , top , right , bot - các góc của hình chữ nhật chứa ảnh addbuf - địa chỉ bộ nhớ dùng chứa ảnh Hàm này cần biết kích th−ớc của hình . Kích th−ớc này đ−ợc xác định theo hàm : unsigned far imagesize(int left , int top , int right , int bot) Giá trị của hàm đ−ợc truyền cho hàm malloc() để cấp phát bộ nhớ . Con trỏ do hàm malloc() trả về đ−ợc truyền cho hàm putimage để khôi phục lại hình đã cất . Cú pháp của putimage() là : void far putimage(int left , int top , void far * addbuf,int putop) left,top là góc trên trái của vùng sẽ đ−a ảnh ra addbuf - địa chỉ bộ nhớ dùng chứa ảnh putop là các đ−a ảnh ra . Các hằng putop là : Trị Hằng ý nghĩa 0 COPY_PUT Thay hình cũ bằng hình mới 1 XOR_PUT XOR hình cũ bằng hình mới 2 OR_PUT OR hình cũ bằng hình mới 3 AND_PUT AND hình cũ bằng hình mới 5 NOT_PUT Thay hình cũ bằng đảo hình mới Ch−ơng trình 6-15 : Lập ch−ơng trình thể hiện quả bóng dội #include #include #include #define left 0 #define top 0 #define right 639 #define bottom 479 #define r 8 void main() { int gd,gm,x,y; int dx,dy,oldx,oldy; void far *buf; unsigned size; gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); rectangle(left,top,right,bottom); x=y=r+10; setcolor(LIGHTGREEN); setfillstyle(SOLID_FILL,LIGHTGREEN); circle(x,y,r); floodfill(x,y,LIGHTGREEN); size=imagesize(x-r,y-r,x+r,y+r); buf=malloc(size); getimage(x-r,y-r,x+r,y+r,buf); dx=1; dy=1; 71
  71. while (!kbhit()) { putimage(x-r,y-r,buf,COPY_PUT); delay(5); oldx=x; oldy=y; x=x+dx; y=y+dy; if (x =right-r-2) dx=-dx; if (y =bottom-r-2) dy=-dy; putimage(oldx-r,oldy-r,buf,XOR_PUT); delay(5); } closegraph(); } Đ4. Văn bản trong đồ hoạ 1. Các fonts : Để chọn fonts chữ ta dùng hàm : void far settextstyle(int font , int direction , int charsize) Các fonts chứa trong các tập tin trong bảng sau Trị Hằng Tập tin 0 DEFAULT_FONT Cài sẵn 1 TRIPLEX_FONT trip.chr 2 SMALL_FONT litt.chr 3 SANSERIF_FONT sans.chr 4 GOTHIC_FONT goth.chr 5 SCRIPT_FONT scrip.chr 6 SIMPLEX_FONT simp.chr 7 TRIPLEX_SCR_FONT tscr.chr 8 COMPLEX_FONT lcom.chr 9 EUROPEAN_FONT euro.chr 10 BOLD_FONT bold.chr Đối direction có thể nhận một trong hai trị : 0 (HORIZ_DIR) - từ trái sang phải 1 (VERT_DIR) - từ trên xuống d−ới Khi đối charsize có trị là 1 , kích th−ớc chữ là nhỏ nhất . Khi kích th−ớc là 2 , chữ sẽ tăng gấp đôi v.v. Để in chuỗi ra màn hình trong chế độ đồ hoạ ta dùng các hàm : void far outtext( char far * string); void far outtextxy(int x , int y , char far *string); Ch−ơng trình 6-16 : Dùng hàm settextstyle() để viết chữ #include #include 72
  72. #define FONTSIZE 4 void main() { int gd,gm; gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); settextstyle(GOTHIC_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,0,"Gothic"); settextstyle(TRIPLEX_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,40,"Triplex"); settextstyle(SMALL_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,80,"Small"); settextstyle(SANS_SERIF_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,100,"Sanserif"); settextstyle(DEFAULT_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,160,"Default"); settextstyle(EUROPEAN_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,200,"Euro"); settextstyle(BOLD_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,240,"Bold"); settextstyle(COMPLEX_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,300,"Complex"); settextstyle(SCRIPT_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,340,"Script"); settextstyle(SIMPLEX_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,370,"Simplex"); settextstyle(TRIPLEX_SCR_FONT,HORIZ_DIR,FONTSIZE); outtextxy(0,420,"Triplex script"); getch(); closegraph(); } 2. Justify và định kích th−ớc văn bản : Hàm định vị trí văn bản là ; void far settextjustify(int horiz , int vert); Đối horiz nhận các biến trong bảng sau Trị Hằng ý nghĩa 0 LEFT_TEXT CP nằm bên trái văn bản 1 CENTER_TEXT CP nằm bên chính giữa văn bản 2 RIGHT_TEXT CP nằm bên phải văn bản Đối vert nhận một trong các giá trị sau : Trị Hằng ý nghĩa 0 BOTTOM_TEXT CP nằm ở đáy văn bản 1 CENTER_TEXT CP nằm bên chính giữa văn bản 2 TOP_TEXT CP nằm ở đỉnh văn bản 73
  73. Ch−ơng trình 6-17 : #include #include #define cl 150 #define lead 40 #define fontsize 3 void main() { int gd,gm,i; gd=DETECT; clrscr(); initgraph(&gd,&gm,"c:\\bc\\bgi"); settextstyle(TRIPLEX_FONT,HORIZ_DIR,fontsize); line(cl,0,cl,200); for (i=0;i #include #include #define n 12 #define bwidth 20 #define sep 24 #define shft 30 #define left 5 74
  74. #define depth 6 #define topflag 1 #define bot 300 #define top 5 #define ticks 10 #define twidth 10 #define maxdata 100 #define xtitle 40 #define ytitle 40 #define font SANS_SERIF_FONT #define di (bwidth+sep) #define width (((n+1)*di)) #define pbt ((float)(bot-top)) #define ppd ((float)(bot-top)/maxdata) void main() { int gd,gm,i; float a,b,c,d; int data[n]={41,47,54,62,63,59,75,83,89,96,55,2}; char month[12][4]={"Jan","Feb","Mar","Apr","May","Jun","Jul", "Aug","Sep","Oct","Nov","Dec"}; char string[40]; clrscr(); gd=DETECT; initgraph(&gd,&gm,"c:\\bc\\bgi"); rectangle(left,top,left+width,bot); setusercharsize(4,3,4,3); settextstyle(font,HORIZ_DIR,0); moveto(xtitle,ytitle); outtext("1998 Sales"); setusercharsize(2,3,2,2); settextstyle(font,HORIZ_DIR,0); for (i=0;i<ticks;i++) { line(left,bot-i*pbt/10,left+twidth,bot-i*pbt/10); line(left+width-twidth,bot-i*pbt/10,left+width,bot-i*pbt/10); moveto(left+width+sep,bot-(i+1)*pbt/10); itoa(i*(maxdata/ticks),string,10); outtext(string); } setusercharsize(2,3,2,2); settextstyle(font,VERT_DIR,0); for (i=0;i<n;i++) { setfillstyle(SOLID_FILL,i); bar3d(left+shft+i*di,bot-data[i]*ppd,left+shft+i*di+bwidth,bot,depth,topflag); moveto(left+i*di+bwidth-1,bot+5); outtext(month[i]); } 75
  75. getch(); closegraph(); } Đ5. Ví dụ kết thúc Ch−ơng trình 6-19 : Lập ch−ơng trình vẽ một mặt Mallbrot #include #include #include #define ymax 400 #define xmax 400 #define maxcount 16 void main() { int gd,gm; int x,y,count; float xscale,yscale; float left,top,xside,yside,zx,zy,cx,cy,tempx; clrscr(); gd=DETECT; initgraph(&gd,&gm,"c:\\bc\\bgi"); left=-2.0; top=1.25; xside=2.5; yside=-2.5; xscale=xside/xmax; yscale=yside/ymax; rectangle(0,0,xmax+1,ymax+1); for (y=1;y<=ymax;y++) { for (x=1;x<=xmax;x++) { cx=x*xscale+left; cy=y*yscale+top; zx=zy=0; count=0; while((zx*zx+zy*zy<4) && (count<maxcount)) { tempx=zx*zx-zy*zy+cx; zy=2*zx*zy+cy; zx=tempx; count++; } putpixel(x,y,count); if (kbhit()) exit(0); } 76
  76. } getch(); closegraph(); } 77
  77. Ch−ơng 7 : một số vấn đề về đa thức và hàm số Đ1. Một số khái niệm chung 1. Khái niệm về ph−ơng pháp tính : Ph−ơng pháp tính là môn học về những lí luận cơ bản và các ph−ơng pháp giải gần đúng,cho ra kết quả bằng số của các bài toán th−ờng gặp trong toán học cũng nh− trong kĩ thuật. Chúng ta thấy rằng hầu hết các bài toán trong toán học nh− giải các ph−ơng trình đại số hay siêu việt,các hệ ph−ơng trình tuyến tính hay phi tuyến,các ph−ơng trình vi phân th−ờng hay đạo hàm riêng,tính các tích phân, th−ờng khó giải đúng đ−ợc,nghĩa là khó tìm kết quả d−ới dạng các biểu thức. Một số bài toán có thể giải đúng đ−ợc nh−ng biểu thức kết quả lại cồng kềnh,phức tạp khối l−ợng tính toán rất lớn.Vì những lí do trên,viẹc giải gần đúng các bài toán là vô cùng cần thiết. Các bài toán trong kĩ thuật th−ờng dựa trên số liệu thực nghiệm và các giả thiết gần đúng.Do vậy việc tìm ra kết quả gần đúng với sai số cho phép là hoàn toàn có ý nghĩa thực tế. Từ lâu ng−ời ta đã nghiên cứu ph−ơng pháp tính và đạt nhiều kết quả đáng kể. Tuy nhiên để lời giải đạt đ−ợc độ chính xác cao,khối l−ợng tính toán th−ờng rất lớn.Với các ph−ơng tiện tính toán thô sơ,nhiều ph−ơng pháp tính đã đ−ợc đề xuất không thể thực hiện đ−ợc vì khối l−ợng tính toán quá lớn.Khó khăn trên đã làm ph−ơng pháp tính không phát triển đ−ợc. Ngày nay nhờ máy tính điện tử ng−ời ta đã giải rất nhanh các bài toán khổng lồ,phức tạp,đã kiểm nghiệm đ−ợc các ph−ơng pháp tính cũ và đề ra các ph−ơng pháp tính mới. Ph−ơng pháp tính nhờ đó phát triển rất mạnh mẽ.Nó là cầu nối giữa toán học và thực tiễn.Nó là môn học không thể thiếu đối với các kĩ s−. Ngoài nhiệmvụ chính của ph−ơng pháp tính là tìm các ph−ơng pháp giải gần đúng các bài toán,nó còn có nhiệm vụ khác nh− nghiên cứu tính chất nghiệm,nghiên cứu bài toán cực trị,xấp xỉ hàm v.v. Trong phần này chúng ta sẽ nghiên cứu một loạt bài toán th−ờng gặp trong thực té và đ−a ra ch−ơng trình giải chúng. 2. Các đặc điểm của ph−ơng pháp tính : Đặc điểm về ph−ơng pháp coả môn học này là hữu hạn hoá và rời rạc hoá. Ph−ơng pháp tính th−ờng biến cái vô hạn thành cái hữu hạn,cái liên tục thành cái rời rạc và sau cùng lại trở về với cái vô hạn,cái liên tục.Nh−ng cần chú ý rằng quá trình trở lại cái vô hạn,cái liên tục phải trả giá đắt vì khối l−ợng tính toán tăng lên rất nhiều.Cho nên trong thực tế ng−ời ta dừng lại khi nghiệm gần đúg sát với nghiệm đúng ở một mức độ nào đó. Đặc diểm thứ hai của môn học là sự tiến đến kết quả bằng quá trình liên tiếp.Đó là quá trình chia ngày càng nhỏ hơn,càng dày đặc hơn hoặc quá trình tính toán b−ớc sau dựa vào các kết quả của các b−ớc tr−ớc.Công việc tính toán lặp đi lặp lại này rất thích hợp với máy điện toán. Khi nghiên cứu ph−ơng pháp tính ng−ời ta th−ờng triệt để lợi dụng các kết quả đạt đ−ợc trong toán học.Cùng một bài toán có thể có nhiều ph−ơng pháp tính khác nhau.Một ph−ơng pháp tính đ−ợc coi là tốt nếu nó đạt các yêu cầu sau : - ph−ơng pháp tính đ−ợc biểu diễn bằng một dãy hữu hạn các b−ớc tính cụ thể.Các b−ớc tính toán cụ thể này của ph−ơng pháp tính đ−ợc gọi là thuật toán. Thuật toán càng đơn giản càng tốt. - đánh giá đ−ợc sai số và sai số càng nhỏ càng tốt. - thuật toán thực hiện đ−ợc trên máy điện toán và thời gian chạy máy ít nhất 78