125
Chương 1. CHƯƠNG TRÌNH C ĐƠN GIN Bài 1. CÁC KHÁI NIM CƠ BN 1. Các phn tcơ bn ca ngôn ngC Tp ký tTên gi, tkhoá - Tkhoá dùng để khai báo chung: asm, pascal, cdecl, typedef, const, enum, extern, static - Tkhoá dùng để khai báo kiu: struct, double, float, int, char, long, far - Tkhoá điu khin: if, else, do, while, for, return, break, continue, swith, case - Các tkhoá khác: interrupt, sizeof, void 2. Ví dchương trình C đơn gin Ví d1: #include <stdio.h> /* su dung thu vien vao/ra chuan */ void main(){ printf(“\n Hello! \n Glad to meet you”); getch(); } (Ngt lnh xung dòng thì thêm du / trước dòng trên Ví d2 #include <stdio.h> void main() { float a,cv,dt; printf(“\n nhap canh hinh vuong :”); scanf(“%f”,&a); cv=a*4; dt=a*a; prinf(“\n chu vi hinh vuong la: %5.1f \n / dien tich hinh vuong la: %5.1f “, cv,dt); getch();}

Giao Trinh Lap Trinh C Tom Tat

Embed Size (px)

DESCRIPTION

Giao Trinh Lap Trinh C Tom Tat

Citation preview

Page 1: Giao Trinh Lap Trinh C Tom Tat

Chương 1. CHƯƠNG TRÌNH C ĐƠN GIẢN

Bài 1. CÁC KHÁI NIỆM CƠ BẢN

1. Các phần tử cơ bản của ngôn ngữ C

� Tập ký tự � Tên gọi, từ khoá

- Từ khoá dùng để khai báo chung: asm, pascal, cdecl, typedef, const, enum, extern, static

- Từ khoá dùng để khai báo kiểu: struct, double, float, int, char, long, far

- Từ khoá điều khiển: if, else, do, while, for, return, break, continue, swith, case

- Các từ khoá khác: interrupt, sizeof, void

2. Ví dụ chương trình C đơn giản

Ví dụ 1:

#include <stdio.h> /* su dung thu vien vao/ra chuan */ void main(){ printf(“\n Hello! \n Glad to meet you”); getch(); } (Ngắt lệnh xuống dòng thì thêm dấu / trước dòng trên

Ví dụ 2

#include <stdio.h> void main() { float a,cv,dt; printf(“\n nhap canh hinh vuong :”); scanf(“%f”,&a); cv=a*4; dt=a*a; prinf(“\n chu vi hinh vuong la: %5.1f \n / dien tich hinh vuong la: %5.1f “, cv,dt); getch();}

Page 2: Giao Trinh Lap Trinh C Tom Tat

3. Vào ra đơn giản

3.1. Đưa dữ liệu lên màn hình

printf(dòng điều khiển, bt1, bt2, ...); Dòng điều khiển gồm:

- Ký tự điều khiển: \n, \t - Ký tự đặc tả : %f, %10.3f, %d, %8d ... - Các ký tự thông báo.

3.2. Nhập dữ liệu từ bàn phím

scanf(“dãy ký tự đặc tả kiểu”, dãy các địa chỉ biến);

Page 3: Giao Trinh Lap Trinh C Tom Tat

Bài 2. KIỂU DỮ LIỆU, HẰNG , BIẾN VÀ BIỂU THỨC.

1. Kiểu dữ liệu

stt Kiểu Phạm vi

Kích thước Ghi chú

char signed char -128-127

1

unsigned char

1B

0-255

0-31: điều khiển 32-127:hiển thị tren màn hình và máy in 128-255:trên màn hình

int 2B -32768-32767 unsigned int 0-65535 long (int) -2.147.483.648 ->

2.147.483.657

2

unsigned long (int)

4B

0->4.294.967.295 float 4B 3.4E-38-3.4E+38 độ chính xác đơn(8) double 8B 1.7E-308-1.7E+308 độ chính xác kép(16)

3

long double 10B 3.4E-4932-1.1E4932 số chữ số có nghĩa(18)

2. Hằng

#define tên_hằng giá_trị Ví dụ: #define heso 10 #define PI 3.14 #define longint 12345678 #define he8 0347 #define he16 0xA9 � Chú ý về hằng ký tự 1) ‘a’ là hằng ký tự được lưu trữ trong 1B

- Giá trị của ‘a’ là mã ASCCI của chữ a, vậy giá trị của ‘a’ là 97

Page 4: Giao Trinh Lap Trinh C Tom Tat

- Giá trị của ‘9’ là ‘9’-‘0’ = 57-48

2) Hằng ký tự ‘a’ còn có thể viết dưới dạng ‘\141’ dấu \ để khai báo một ký tự đặc biệt. ‘a’ có giá trị 97 ở hệ 10, có giá trị 0141 ở hệ 8 3) Các hằng ký tự đặc biệt: ‘\n’: xuống dòng ‘\0’:ký tự NULL: \0 ‘\t’:Tab ‘\b’:Back space ‘\r’: CR về đầu dòng ‘\f’:LF sang trang 4) Phân biệt ‘0’ là hằng ký tự ứng với chữ số 0 , có giá trị 97; ‘\0’ là hằng

ký tự ứng với ký tự NULL có giá trị 0 5) Hằng ký tự thực chất là số nguyên

printf(“%c, %c”, 65,66); sẽ in ra AB

6) Phân biệt hằng ký tự và hằng xâu ký tự ‘a’ : là hằng ký tự gồm một ký tự a “a” : hằng xâu ký tự gồm 2 ký tự a và ký tự NULL (tức \0). Ký tự \0 được C tự động gán vào cuối hằng xâu ký tự để đánh dấu kết thúc xâu “Ha noi” là hằng xâu ký tự gồm 7 ký tự 5 chữ, 1 cách và null “” là hằng xâu ký tự rỗng, chỉ gồm một ký tự NULL

3. Kiểu enum

Mục đích: tạo các macro như define 1. enum tk {pt1, pt2, ... } tb1, tb2, ... ; 2. enum tk {pt1, pt2, ... } ; 3. enum {pt1, pt2, ... } tb1, tb2, .... ; 4. enum {pt1, pt2, ...} ;

Page 5: Giao Trinh Lap Trinh C Tom Tat

Dạng (1) có 3 chức năng:

a. Định nghĩa các macro pt1, pt2, ... làm lượt có các giá trị là 0,1,... b. Định nghĩa kiểu enum có tên là tk, sau này có thể định nghĩa biến

kiểu enum dạng: enum tk x,y,z; c. Khai báo các biến kiểu enum có tên là tb1, tb2, …. - Các biến kiểu enum thực chất là các biến nguyên được cấp phát

2B, nên có thể nhận giá trị nguyên bất kỳ. - Mục đích của enum là tạo các macro (thay thế) các kiểu và các

biến gợi nhớ

Dạng (2) : có các chức năng (a) và (b) Dạng (3) : có các chức năng (a) và (c) Dạng (4) : có chức năng (a) Ví dụ: #include <stdio.h> main() { enum {T0,T1,T2}; enum DAY {cn,t2,t3,t4,t5,t6} ng1; enum DAY ng2; int i,j =2000, k = T2; i=t7; ng1=-1000; ng2=j; printf(“\n ng1 =%d ng2 = %d i = %d”, ng1,ng2,i); printf(“\n k =%d T1 = %d “, k,T1); }

4. Biến

tên_kiểu tên_biến;

Page 6: Giao Trinh Lap Trinh C Tom Tat

1) Vị trí khai báo : ngay sau { của main, trước mọi câu lệnh 2) Khởi tạo cho các biến 3) Lấy địa chỉ của biến : &tên_biến

5. Mảng

1) Khai báo mảng gồm các thành phần: kiểumảng, tên mảng, số chiều, kích thước mỗi chiều: kiểu_mảng tên_mảng[kíchthước] kiểu_mảng tên_mảng[kíchthước1] [kíchthước2] 2) Ví dụ int a[10]; float b[5][7]; 3) Chú ý: - Các phần tử của mảng được cấp phát trong vùng bộ nhớ có địa chỉ liên tục - &a[i] là địa chỉ của phần tử thứ i - viết: a hoặc @a[0] : là tên mảng biểu thị địa chỉ phần tử đầu tiên của mảng, cũng là địa chỉ của mảng.

6. Định nghĩa kiểu

- C dùng từ khoá typedef để định nghĩa kiểu: - Khai báo: typedef kiểu_chuẩn tên_kiểu ; - Sử dụng: tên_kiểu tên_biến1, tên_biến2; - Ví dụ: Khai báo: typedef int mang[50]; typedef float mt53[5][3]; typedef enum {T1,T2,T3} T; Sử dụng: mang x,y; mt53 a,b;

Page 7: Giao Trinh Lap Trinh C Tom Tat

T t;

7. Biểu thức

7.1. Phép toán số học

- 2 ngôi: +, -, *, / , % (chia lấy phần dư) - 1 ngôi: - (phép lấy đối)

7.2. Phép toán bít

& : and | : or ^ : xor (đồng tính bị huỷ, khác tính thì tồn tại) 1 ^ 1 = 0 ^ 0 = 0; 1 ^ 0 = 0 ^ 1 = 1)

7.3. Phép toán quan hệ

==, !=, > , <, >=, <=

7.4. Phép toán logic

! : not && : and || : or

7.5. Phép toán tăng giảm

++n hoặc n++ : tăng 1 đơn vị cho n --n hoặc n-- : giảm n 1 đơn vị ++n : tăng n trước khi dùng n++: dùng n trước khi tăng. ví dụ: int n=5; int x = n++ ; vậy x == 5, n ==6 int y =++n; vậy y==6, n==6

7.6. Thứ tự ưu tiên các phép toán

1) () [] -> . 2) ! ~ & - ++ -- 3) * / %

Page 8: Giao Trinh Lap Trinh C Tom Tat

4) + - 5) << >> 6) < <= > >= 7) == != 8) & 9) ^ 10) | 11) && 12) || 13) ?: 14) = += -= *= /= <<= >>= &= ^= |=

7.7. Biểu thức điều kiện

varible = condition? value1:value2

7.8. Phép chuyển kiểu

Ví dụ int a; float b; a = (int)b; - Để tính chính xác giá trị của phép chia 2 số nguyên m và n , ta viết ((float) m)/n - Để đổi giá trị float sang giá trị int ta áp dụng dạng (int)(r+0.5)

Page 9: Giao Trinh Lap Trinh C Tom Tat

Bài 3. KHỐI LỆNH, HÀM VÀ PHẠM VI HOẠT ĐỘNG CỦA BIẾN.

1. Khối lệnh và phạm vi hoạt động của biến bên trong và bên ngoài khối

1) Khối lệnh: { lệnh_1; lệnh_2; } 2) Biến trong và biến ngoài - Biến trong một khối chỉ có phạm vi hoạt động trong khối chứa nó, kể cả

việc cấp phát bộ nhớ - Biến ngoài một khối có phạm vi hoạt động trong khối đó và kể cả trong

khối con của khối đó (nếu trong khối con có tên biến đó). - Bên ngoài một khối không dùng được các biến trong khối con của nó kể

cả trong các khối khác. Ví dụ: #include <stdio.h> #include <conio.h> void main(){ int x=5; {int y=x+1; printf("\nx = %d, y = %d",x,y); } printf("\nx = %d",x); /* printf(" y = %d",y); lenh nay sai*/ getch(); }

2. Hàm

1) Hàm là đơn vị độc lập trong chương trình. Không có hàm con trong một hàm. Trong hàm có thể có các biến cục bộ.

2) Hàm main() là hàm bắt buộc và để gọi thực hiện các hàm khác. 3) Xen giữa các hàm có thể có khai báo dẫn hướng biên dịch

Page 10: Giao Trinh Lap Trinh C Tom Tat

4) Việc truyền dữ liệu và kết quả từ hàm này sang hàm khác thực hiện bằng các cách:

- Sử dụng đối - Sử dụng biến ngoài, mảng ngoài, biến tĩnh ngoài, mảng tĩnh ngoài.

Ví dụ : Minh hoạ việc xây dựng hàm tính giá trị đa thức f = ax2 + bx + c và sử dụng hàm này trong main(). Cách 1: Sử dụng đối

#include <stdio.h> #include <conio.h> long f(int,int,int,int ); void main(){ int a,b,c,x; long p; a=c=1; b=2; x=100; printf("\nf(2) = %ld",f(a,b,c,x)); getch(); } long f(int a,int b,int c,int x) { long z=a*x*x + b*x + c; return z; }

Chú ý: Các hàm có thể định nghĩa trước hàm main(), xem cách 2. Nếu các hàm định nghĩa sau hàm main() thì phải khai báo tên hàm và danh sách kiểu đối trước hàm main(), như cách 1 nói trên. Cách 2: Sử dụng biến ngoài.

#include <stdio.h> #include <conio.h> int a,b,c,x; long p; long f() { long z=a*x*x + b*x + c; return z; }

Page 11: Giao Trinh Lap Trinh C Tom Tat

void main(){ a=c=1; b=2; x=100; printf("\nf(2) = %ld",f()); getch(); }

3. Biến và mảng tự động

- Định nghĩa: Biến, mảng khai báo bên trong thân hàm hoặc đối của hàm gọi là biến mảng tự động.

- Phạm vi hoạt động: Biến, mảng tự động chỉ có tác dụng trong bản thân hàm, kể cả việc cấp phát bộ nhớ.

4. Biến và mảng ngoài và từ khoá extern

+ Định nghĩa: Biến, mảng khai báo bên ngoài các hàm gọi là biến, mảng ngoài .

+ Phạm vi hoạt động: Biến, mảng ngoài chỉ có tác dụng cho đến cuối tệp chương trình hiện tại. Muốn biến, mảng ngoài có tác dụng trong nhiều file thì tiền khai báo là extern.

Ví dụ: Ta có tệp MyLib.C, nội dung như sau: int x=10; long f(int n) {int i; long t=1; for(i=1;i<n;i++) t=t*i; return t; } Chú ý: - File Mylib.C phải đặt trong thư mục INCLUDE - File này không thể thực hiện vì không có hàm main(), do đó chỉ có thể

dịch sang file MyLib.OBJ, từ menu Compile \ Compile to OBJ Bây giờ ta sẽ viết chương trình USEMyLib.C , sử dụng các biến và hàm

trong Mylib.C. File chương trình này có nội dung như sau: #include <stdio.h> #include <conio.h>

Page 12: Giao Trinh Lap Trinh C Tom Tat

#include <ham3.c> extern int; extern long f(int); void main(){ printf("\n%d",x); printf("\n%ld",f(20)); getch(); } Lưu ý là các tiền tố extern có thể khai báo trong tệp “thư viện riêng”. + Về việc khởi đầu cho các biến , mảng ngoài: . Các biến, mảng ngoài có thể khởi đầu một lần. . Các mảng khi khởi đầu có thể không cần chỉ ra kích thước. Ví dụ float a[] = { 2.5, 7.0, 8.5 } int b[][4] = { {2,6,14,7}, {0,0,3,45} } . Khi khởi đầu cho các mảng kiểu ký tự, có thể khởi đầu bằng một danh

sách các ký tự hoặc xâu ký tự. Ví dụ: char name[] = { ‘h’,’a’,32,’n’,’o’,’i’,’\0’} char name[] =”ha noi” * Chú ký, kích thước của một biến, mảng được tính nhờ hàm sizeof()

5. Biến tĩnh và mảng tĩnh.

- Khai báo: dùng tiền khai báo static, - Có thể khởi đầu một lần. - Vẫn được cấp phát bộ nhớ. - Phạm vi hoạt động: Chỉ hoạt động trong thân hàm mà tại đó nó được khai

báo. Giá trị của chúng được giữ lại khi ra khỏi hàm và khi hàm hoạt động trở lại thì giá trị cũ được tiếp tục sử dụng.

Ví dụ: #include <stdio.h> #include <conio.h> void f(){ static int x=9; x+=10; printf("\n%d",x);

Page 13: Giao Trinh Lap Trinh C Tom Tat

} void main(){ int x=1; f(); printf("\n%d",x); f(); getch(); } Kết quả khi chạy chương trình là 19 1 29 Khi bỏ tiền khai báo static trong hàm f() thì kết quả là 19 1 19

Page 14: Giao Trinh Lap Trinh C Tom Tat

Bài 4. VÀO VÀ RA

1. Hàm printf và hàm scanf

1.1. Hàm printf

int printf(const char *dòng_điều_khiển, danh_sách_đối); - Hàm trả lại -1 khi có lỗi. Khi không lỗi, hàm trả lại giá trị là số lượng ký

tự được in ra. - Dòng điều khiển bao gồm: . Các ký tự điều khiển . Các ký tự đặc tả . Các ký tự bình thường - Các ký tự điều khiển bao gồm :

\n \t \r \b \f

sang dòng mới sang 1 tab CR: về đầu dòng back space LF: line feed

\0 \’ \” \\

NULL dấu ‘ dấu “ dấu \

- Các ký tự đặc tả có dạng tổng quát: %[-][độ_rộng][phần_thập_phân_đối_với_float]ký_tự_chuyển_dạng Dấu trừ để quy định kết quả in ra canh biên trái (mặc định là canh biên

phải) stt Ký tự

chuyển dạng Kiểu

dữ liệu Cách chuyển dạng

1. c char ký tự 2. d int số nguyên thường hệ 10, có thể có dấu 3. ld long số nguyên dài hệ 10, có thể có dấu 4. u int số nguyên hệ 10, không dấu 5. o int số nguyên hệ 8 không dấu (ở đầu không có số 0) 6. lo long số nguyên dài, hệ 8, không dấu 7. x int số nguyên hệ 16, không dấu

Page 15: Giao Trinh Lap Trinh C Tom Tat

8. lx long số nguyên dài hệ 16 không dấu.

1.2. Hàm scanf

int scanf(“const char *dòng_điều_khiển, danh_sách_địa_chỉ_đối); Ví dụ 1 int n; float r; scanf(“%d%f”,&n,&r); Ví dụ 2 int n; float x,y; char s[6], st[6]; scanf(“%f%5f%3d%3s%s”,&x,&y,&n,s,st); nếu nhập dòng vào là: 54.32e-1 25 12345678a Kết quả là: x==5.432 y==25.0 n=123 s=”456” bao gồm cả ký tự ‘\0’ st=”78a” bao gồm cả ký tự ‘\0’

2. So sánh 2 nhóm hàm trong stdio và conio

<stdio.h> : thư viện dòng vào và ra của stdio, gồm stdi và stdo - dòng vào stdi gồm scanf, gets, getchar (1) - dòng ra stdo gồm printf, puts, putchar (2) <conio.h> : thư viện dòng vào/ra trên màn hình và bàn phím gồm : getch, getche, putch, putche, kbhit, clrscr, gotoxy (3)

Page 16: Giao Trinh Lap Trinh C Tom Tat

2.1. Dòng vào stdi và hàm scanf

- stdi là dòng vào chuẩn - tức là bàn phím - Nếu trên stdi chưa đủ dữ liệu thì scanf yêu cầu nhập cho đến khi đủ dữ

liệu. - Nếu trên stdi đủ dữ liệu thì nhóm hàm (1) sẽ nhận một phần dữ liệu tương

ứng với yêu cầu của chúng. Phần dữ liệu còn lại vẫn còn ở trên stdi. - Hơn nữa, phần dữ liệu còn lại sẽ tự động gán cho biến được nhập bởi

scanf (nếu có lệnh scanf tiếp theo) và không có yêu cầu cần thực hiện lệnh scanf đó. Tuy nhiên nếu phần dữ liệu còn lại vẫn chưa đủ dữ liệu cho các biến tiếp theo thì yêu cầu nhập đối với các biến đó vẫn được nêu ra.

Ví dụ 1 #include <stdio.h> void main(){ int a,b,c; printf("\n a,b = "); scanf("%d%d",&a,&b); printf("\n a=%d, b=%d",a,b); printf("\n c = "); scanf("%d",&c); printf("\n a=%d, b=%d, c=%d",a,b,c); getch(); } . Nếu ta nhập a, b = 10 20 ↵ thì vẫn xuất hiện yêu cầu nhập c, tức là kết quả thực hiện là: a=10, b=20 c= 30 ↵ a=10, b=20, c=30 . Nếu ta nhập a,b = 10 20 30 40 ↵ thì không có yêu cầu nhập c, tức là kết quả thực hiện là: a=10, b=20 c= a=10, b=20, c= 30 giá trị 40 vẫn còn lại trên stdi.

Page 17: Giao Trinh Lap Trinh C Tom Tat

Ví dụ 2 #include <stdio.h> void main(){ int a,b,c,d; printf("\n a,b = "); scanf("%d%d",&a,&b); printf("\n a=%d, b=%d",a,b); printf("\n c,d = "); scanf("%d%d",&c,&d); printf("\n a=%d, b=%d, c=%d, d=%d",a,b,c,d); getch(); } . Theo dõi kết quả khi chạy chương trình thấy: a,b = 2 3 4 ↵ a=2, b=3 c,d = 5 6 ↵ a=2, b=3, c=4, d=5 Và giá trị 6 bị bỏ qua, vẫn còn lại trên stdi

2.2. Dòng vào stdi với các hàm gets và getchar

- Khi nhập xâu ký tự s, ít khi dùng hàm scanf vì hàm scanf không có khả năng nhập xâu ký tự chứa dấu cách. Nói cách khác phím space cũng là kết thúc nhập một xâu ký tự (đối với scanf).

Ví dụ 1 char s[25]; scanf(“%s”,s); printf(“s=%s”,s) nếu nhập thu ha ↵ kết quả là s=thu - Trong trường hợp này ta sẽ dùng gets thay cho scanf Ví dụ 2 char s[25]; gets(“%s”,s); printf(“s=%s”,s) nếu nhập le thu ha ↵

Page 18: Giao Trinh Lap Trinh C Tom Tat

kết quả là s=le thu ha - Hàm gets(s) để nhập một xâu ký tự cho biến s ; hàm puts(s) để in ra xâu

ký tự trong biến s. - Tổng quát, hàm gets có cú pháp: char *gets(char *s) s là biến con trỏ kiểu char trả về địa chỉ vùng nhớ chứau ký tự nhận được.

Hàm gets nhận một dãy ký từ stdi cho đến khi nhấn ↵ , tức là đến khi nhận được ký tự ‘\n’ thì gets dừng lại và ‘\n’ nằm lại trên stdi. Như vậy, thay vì đặt ký tự ‘\n’ vào cuối xâu ký tự s thì hàm gets đặt ký tự ‘\0’ (ký tự null) vào cuối xâu để đánh dấu kết thúc xâu.

Chú ý rằng hàm gets không trả về giá trị xâu ký tự mà trả về địa chỉ của xâu ký tự đó.

- Hàm getchar(void) có cú pháp tổng quát như sau: int getchar(void) có tác dụng nhập một ký tự từ bàn phím. Ví dụ char c, kt; kt=getchar(); . Nếu nhập A ↵ thì kt = ‘A’ , ký tự ‘\n’ vẫn nằm lại trên stdi . Nếu nhập ↵ thì kt =’\n’ và trên ‘\n’ bị loại khỏi stdi. - Hàm putchar() đưa một ký tự ra stdo chuẩn - màn hình.

2.3. Hiện tượng trôi dữ liệu (ảnh hưởng của scanf và getchar đối với gets)

- Như ở trên suy ra , hàm gets dừng lại khi trên stdi có ký tự ‘\n’ (CR) - Từ đó, các hàm scanf và getchar có thể làm cho hàm gets mất tác dụng.

Nghĩa là mặc dù trong chương trình có viết lệnh gets nhưng khi chạy chương trình thì lệnh này không được thực hiện. Hiện tượng này gọi là hiện tượng trôi dữ liệu.

Ví dụ 1 #include <stdio.h> void main(){ int tuoi; char hoten[25]; printf("\n tuoi: "); scanf("%d,&tuoi); printf("\n ho ten: "); getsf(hoten);

Page 19: Giao Trinh Lap Trinh C Tom Tat

printf("\n tuoi: %d, ho ten: %s",tuoi,hoten); getch(); } . Nếu ta nhập 45 ↵ thì kết quả là tuoi:45, ho ten: nghĩa là việc nhập giá trị cho biến hoten không có tác dụng. . Nếu ta nhập 45 nguyen thanh thuy ↵ thì kết quả là tuoi:45, ho ten: nguyen thanh thuy - Nói chung để nhập dữ liệu cho đúng ta nên sử dụng lệnh làm sạch stdi , vì

khi đó trên stdi không còn ký tự ‘\n’ , do đó không bị trôi các hàm nhập dữ liệu khác, như hàm gets.

Hàm fflush(stdi); sẽ làm sạch stdi Ví dụ 2 #include <stdio.h> void main(){ int tuoi; char hoten[25]; printf("\n tuoi: "); scanf("%d,&tuoi); printf("\n ho ten: "); fflush(stdi); getsf(hoten); printf("\n tuoi: %d, ho ten: %s",tuoi,hoten); getch(); }

2.4. Dòng ra stdo với nhóm hàm 2

- Thư viện conio.h chứa các hàm kiểu int sau đây: . getch() : nhận một ký tự từ bộ đệm bàn phím, không hiện lên màn hình . getche(): nhận một ký tự từ bộ đệm bàn phím, có hiện lên màn hình . putch(): Đưa một ký tự lên màn hình văn bản, có mầu sắc quy định bởi

hàm textcolor(), trong khi đó, hàm putchar() luôn hiện lên mầu trắng. . kbhit(): Cho biết trong bộ đệm bàn phím có còn ký tự nào không. kbhit()=0 (tức FALSE) khi bộ đệm bàn phím rỗng

Page 20: Giao Trinh Lap Trinh C Tom Tat

kbhit()=1 (tức TRUE) khi bộ đệm bàn phím có các ký tự Chú ý: - Nếu gõ phím trong khi máy dừng chờ kết thúc nhập dữ liệu bởi các hàm

scanf, gets, getchar thì các ký tự sẽ gửi vào stdi - Nếu gõ phím trong các trường hợp khác, ví dụ đối với hàm getch, getche

thì các ký tự sẽ gửi vào bộ đệm bàn phím. Ví dụ 1 (chạy trong mode MS-DOS) Lặp quá trình nhập một số ký tự vào bộ đệm bộ đệm bàn phím đến khi nhập

ký tự 27. Lấy các ký tự từ bộ đệm bàn phím đã nhập đó để in lên màn hình: #include <stdio.h> #include <conio.h> void main(){ int c; clrscr(); while (getch()!=27) putch(7); while(kbhit()) { c=getch(); putch(c); } }

Page 21: Giao Trinh Lap Trinh C Tom Tat

Chương 2. CÁC TOÁN TỬ ĐIỀU KHIỂN

Bài 1. TOÁN TỬ RẼ NHÁNH IF VÀ CHUYỂN SWITCH

1. Toán tử rẽ nhánh if

- Dạng khuyết: if(biểu_thức) { nhóm_lệnh; }

- Dạng đủ: if(biểu_thức) { nhóm_lệnh_1; } else { nhóm_lệnh_2;}

- Các ví dụ : Ví dụ 1 : Giải phương trình bậc hai: ax2 + bx + c (a<>0)

#include <stdio.h> #include <conio.h> #include <math.h> main() { float a,b,c,d,x1,x2; printf("\n nhap a,b,c: "); scanf("%f%f%f",&a,&b,&c); d=b*b-4*a*c; if(d<=0) printf("\n phuong trinh vo nghiem"); else if(d==0) printf("\n phuong trinh co nghiem kep %10.2f",b/(2*a)); else { x1=(-b+sqrt(d))/(2*a); x2=(-b-sqrt(d))/(2*a); printf("\n phuong trinh co 2 nghiem phan biet:"); printf("\n x1= %10.2f\n x2= %10.2f",x1,x2); } getch(); }

Ví dụ 2 : Nhập từ bàn phím điểm trung bình môn của một sinh viên, in lên màn hình xếp loại học lực sinh viên đó. . Dưới 5.0 : xếp loại yếu . Từ 5.0 đến dưới 6.5: xếp loại trung bình . Từ 6.5 đến dưới 8.0: xếp loại khá . Từ 8.0 trở lên : xếp loại giỏi

Page 22: Giao Trinh Lap Trinh C Tom Tat

#include <stdio.h> #include <conio.h> main() { float dtb; char *xl; printf("\n nhap diem trung binh: "); scanf("%f",&dtb); if(dtb<5.0) xl="YEU"; else if(dtb<6.5) xl="TRUNG BINH"; else if(dtb<8.0) xl="KHA"; else xl="GIOI"; printf("\n xep loai sinh vien do la %s",xl); getch(); }

Ví dụ 3: Nhập từ bàn phím mức lương của 4 cán bộ nhân viên. In lên màn hình mức lương cao nhất ? thấp nhất.

#include <stdio.h> #include <conio.h> main() { float a,b,c,d,max,min; printf("\n nhap muc luong cua 4 nhan vien: "); scanf("%f%f%f%f",&a,&b,&c,&d); max=a>b?a:b; if(max<c) max=c; if(max<d) max=d; min=a>b?b:a; if(min>c) max=c; if(min>d) max=d; printf("\n luong cao nhat la %0.1f\n luong thap nhat la %0.1f", max,min); getch(); }

Page 23: Giao Trinh Lap Trinh C Tom Tat

2. Toán tử chuyển switch

- Cú pháp tổng quát: switch(biểu_thức_nguyên){ case 1: nhóm_lệnh_1; case 2: nhóm_lệnh_2; ... case n: nhóm_lệnh_n; [default : nhóm_lệnh_mặc_định; ] }

- Hoạt động của toán tử switch: + Khi biểu thức có giá trị k (k<=n) thì bắt đầu từ lệnh có nhãn k được thực hiện. Các lệnh có nhãn bên dưới vẫn tiếp tục được thực hiện nếu như trước đó không có lệnh ngắt, đây là điểm lưu ý vì nó khác biệt so với cấu trúc case của pascal. Vậy swith không được hiểu là thay thế cấu trúc if .. else lồng nhau. + Khi biểu thức không có giá trị nào trong đoạn [1..k] thì nhóm lệnh phần default được thực hiện (nếu có) hoặc ra ngay khỏi swith (nếu không có default) + Tổng quát, toán tử swith kết thúc trong các trường hợp sau đây: . Thực hiện xong phần default nếu có hoặc gặp dấu } cuối cùng của khối switch . Gặp câu lệnh ngắt break. Từ đó nếu sau mỗi trường hợp của swith có một câu lệnh break thì chỉ một trong các trường đó được thực hiện và sau đó ra khỏi switch, lúc này switch sẽ giống với cấu trúc case của pascal. . Gặp câu lệnh nhảy goto ra một nhãn ngoài swith * Nói chung để biện luận các trường hợp cho một bài toán, ta nên thêm một câu lệnh ngắt break sau mỗi trường hợp của swith - Các ví dụ Ví dụ 1

#include <stdio.h> main() {

Page 24: Giao Trinh Lap Trinh C Tom Tat

int i; printf("\n i= "); scanf("%d",&i); switch (i) { case 1 : printf("\n lenh 1"); case 2 : printf("\n lenh 2"); case 3 : printf("\n lenh 3"); case 4 : printf("\n lenh 4"); default : printf("\n lenh default"); } getch(); }

Nếu ta nhập i=1 thì kết quả là lenh 1 lenh 2 lenh 3 lenh 4 lenh default Nếu ta nhập i=3 thì kết quả là lenh 3 lenh 4 lenh default * Từ đó biến i chỉ có tác dụng quyết định bắt đầu từ lệnh nào được thực hiện. Các giá trị nhãn bên dưới (cho dù khác i) vẫn được thực hiện, không phụ thuộc vào giá trị của i nữa !. Ví dụ 2

#include <stdio.h> #include <conio.h> main() { int i; printf("\n i= "); scanf("%d",&i); switch (i) { case 1 : printf("\n%d",i); i+=4; case 3 : printf("\n%d",i); i+=2; case 5 : printf("\n%d",i); i+=9; case 14 : printf("\n%d",i); i+=2; default : printf("\n%d",i); i=100; }

Page 25: Giao Trinh Lap Trinh C Tom Tat

printf("\n%d",i); getch(); }

Nếu ta nhập i = 1, kết quả là Kết quả Giải thích 1 5 7 16 18 100

i=1, lệnh nhãn 1 thực hiện, in ra i=1 và i=5 lệnh nhãn 3 thực hiện, in ra 5 và i=7 lệnh nhãn 5 thực hiện, in ra 7 và i=16 lệnh nhãn 14 thực hiện, in ra 16 và i=18 lệnh default thực hiện, in ra 18 và i=100 lệnh sau switch thực hiện, in ra 100

Như vậy, sau khi lệnh nhãn 1 thực hiện, i = 5. Nhưng không phải vì thế mà swith sẽ “nhẩy tới ” nhãn 5. Nhãn 3 ngay sau đó vẫn được thực hiện. Như vậy, một lần nữa chứng tỏ giá trị của i chỉ có tác dụng quyết định lệnh nào được thực hiện đầu tiên. Các nhãn sau đó không liên quan gì đến biến i. Nếu ta nhập i = 3 kết quả là: 3 5 14 16 100 Ví dụ 3 Một tháng có 26 ngày công. Một công nhân có thể làm thêm 1 hoặc 2 hoặc 3 hoặc 4 ngày nữa. Số tiền thưởng quy định như sau: Nếu số ngày làm thêm là: 1: thưởng 30.000 đ 2: thưởng 100.000 đ 3: thưởng 150.000 đ 4: thưởng 210.000 đ Lương gốc của một công nhân là 350.000 đồng.

Page 26: Giao Trinh Lap Trinh C Tom Tat

Hãy viết chương trình tính tổng lương của một công nhân khi biết số ngày làm thêm của họ.

#include <stdio.h> #include <conio.h> main() { int sn,thuong; printf("\n so ngay lam them = "); scanf("%d",&sn); switch (sn) { case 1 : thuong=30; break; case 2 : thuong=100; break; case 3 : thuong=150; break; case 4 : thuong=210; break; default : thuong=0; } printf("\n tong luong la %ld dong",1000*((long)thuong+310)); getch(); }

Chú ý: ta phải chuyển biến thuong kiểu int thành kiểu long thông qua phép chuyển là (long)thuong trước khi thực hiện phép nhân với 1000. Vì nếu không kết quả phép tính các biến nguyên sẽ cho giá trị nguyên, bị tràn số. Ví dụ 4 Một máy M gồm 5 van k1, k2, k3, k4,k5 như mô tả ở hình vẽ:

Các van quy ước 2 trạng thái

Trạng thái 2

Trạng thái 1

1 0

B Máy M gồm 5 van

A

k3 k4 k5

k1 k2

Page 27: Giao Trinh Lap Trinh C Tom Tat

Một trạng thái của máy là một dãy 5 trạng thái của 5 van. Theo hình vẽ, trạng thái của máy là (10011). Mỗi trạng thái của máy có tác dụng điều khiển một bộ phận nào đó trong một hệ thống tổng thể mà ta không nói tới ở đây.

Máy này có 2 nút điều khiển A và B và hoạt động như sau: nếu nút A được nhấn thì một quả cầu sẽ lăn vào đường ống A và chạy qua các ống trong máy cuối cùng thoát ra một trong 4 đường ống bên dưới theo nguyên tắc:

. Chỉ đi qua ống không bị van nào đó ngăn lại

. Khi đi qua một van thì làm cho van đó đổi trạng thái ngược lại. Máy hoạt động hoàn toàn tương tự khi nhấn nút B. Chẳng hạn nếu ta nhấn nút A thì từ trạng thái (10011) sẽ biến thành trạng

thái (00111); nhấn tiếp nút B, sẽ biến thành trạng thái (01110),... Yêu cầu: Gọi (k1,k2,k3,k4,k5) là trạng thái xuất phát của máy, hãy xác định trạng

thái mới của máy sau khi nhấn một nút nào đó. Chương trình: #include <stdio.h> #include <conio.h> main() { int k1,k2,k3,k4,k5; char nut; int next; printf("\n nhap day trang thai cua 5 van : "); scanf("%d%d%d%d%d",&k1,&k2,&k3,&k4,&k5); fflush(stdin); printf("\n nhan nut A hay nut B: "); nut=getchar(); printf("\n ban dau: %d%d%d%d%d",k1,k2,k3,k4,k5); switch (nut) { case 'A' : if(k1) next=3; k1=!k1; case 34 : if(next==3) k3=!k3; else k4=!k4; break; case 'B' : if(k2) next=4; k2=!k2; case 45 : if(next==4) k4=!k4; else k5=!k5; } printf("\n sau nhan: %d%d%d%d%d",k1,k2,k3,k4,k5); getch(); }

Page 28: Giao Trinh Lap Trinh C Tom Tat

Lưu ý: Các nhãn 34, 45 chỉ giả lập, không có ý nghĩa gì đối với biện luận của biến nut.

Page 29: Giao Trinh Lap Trinh C Tom Tat

Bài 2. CÁC TOÁN TỬ LẶP FOR VÀ WHILE

1. Toán tử lặp for

- Cú pháp for(biểu_thức1;biểu_thức2;biểu_thức3) { nhóm_lệnh; }

- Hoạt động của toán tử (lặp) for: Bước 1: Tính giá trị biểu thức 1 Bước 2: Nếu biểu thức 2 sai thì thực hiện Bước 5 (kết thúc) Bước 3: /* ngầm hiểu biểu thức 2 đúng */ - Thực hiện Nhóm_lệnh. - Tính giá trị biểu thức 3. Bước 4: Quay về Bước 2. Bước 5: Kết thúc. - Chú ý: . Trong for gồm 3 thành phần biểu_thức1, biểu_thức2, biểu_thức3. Các thành phần này ngăn cách bởi dấu “chấm phẩy” ( “;” ). . Trong từng thành phần có thể chứa nhiều biểu thức con và các biểu thức con trong một thành phần ngăn cách bởi dấu phẩy ( “,” ). . Các biểu thức 1,2,3 trong for có thể vắng mặt. . Khi biểu thức 2 vắng mặt thì ngầm định luôn đúng. Khi đó, vòng lặp for sẽ kết thúc khi gặp một trong các lệnh break / goto/ return. Nếu vắng mặt biểu_thức2 thì giữa biểu_thức 1 và biểu_thức2 phải có 2 dấu chấm phẩy. . Biểu thức 1 gồm những lệnh khởi đầu cho biến điều khiển vòng lặp và các biến cần phải tính toán, do vậy có thể khởi đầu trước for và khi đó khuyết biểu thức 1. . Trong bước 3 gồm 2 việc : thực hiện nhóm lệnh (thân vòng lặp for ) và tính giá trị biểu thức 3. Vậy có thể để cho các lệnh tính giá trị biểu thức 3 gộp chung với nhóm lệnh. Trong trường hợp này biểu thức 3 sẽ vắng mặt. Ngược lại, cũng có thể gộp chung nhóm lệnh (nhất là khi ít lệnh) với biểu thức 3, khi đó thân vòng for là rỗng! . Trong cả 2 cách gộp chung như trên, thứ tự viết nhóm lệnh trước hay biểu thức 3 trước là khá quan trọng , nó ảnh hưởng đến việc tăng hay giảm một bước lặp trong

Page 30: Giao Trinh Lap Trinh C Tom Tat

vòng lặp for, do đó có thể phải điều chỉnh lại các lệnh trong biểu thức 2 một cách hợp lý. . Trong thân vòng lặp for {...} nếu gặp lệnh continue thì các lệnh còn lại được bỏ qua và chuyển đến xét biểu thức 3. - Ví dụ : Viết chương trình đảo ngược dãy số thực chứa trong mảng x.

float x[] = {6.3, -4.7, 70.2, 14.5 , -22.1} int n = sizeof(x) / sizeof(float); void main() { int i,j; float c; ( ) getch(); }

Trong đó ( ) gồm các cách sau đây: Cách 1 ( ) Đủ các thành phần và thân for for(i=0,j=n-1 ; i<j ; i++, j--) { c=x[i]; x[i] =x[j]; x[j]=c;} Cách 2 ( ) Khuyết thân for: for(i=0,j=n-1 ; i<j ; c=x[i], x[i] =x[j], x[j]=c, i++, j--); Cách 3 ( ) Khuyết biểu thức 1 i=0; j=n-1; for( ; i<j ; i++,j--) {c=x[i]; x[i] =x[j]; x[j]=c;} Cách 4 ( ) Khuyết biểu thức 2 for(i=0, j=n-1; ; i++, j--) {c=x[i]; x[i] =x[j]; x[j]=c; if (i>j) break;} Cách 5 ( ) Khuyết biểu thức 3 for(i=0, j=n-1; i<j ;) {c=x[i]; x[i] =x[j]; x[j]=c; i++; j--;} Cách 6 ( ) Khuyết cả 3 biểu thức i=0; j=n-1; for(;;) {c=x[i]; x[i] =x[j]; x[j]=c; if(i++> j--) break;}

Page 31: Giao Trinh Lap Trinh C Tom Tat

Còn nhiều cách khác i=0; j=n-1; for(;i<j;){c=x[i]; x[i] =x[j]; x[j]=c; i++; j--;} Hoặc : for(i=0, j=n-1;;) {c=x[i]; x[i] =x[j]; x[j]=c; if(i++> j--) goto next;} next: Một số bài tập về toán tử (lặp) for Bài 1 Viết chương trình tính an với a>0, n>0

#include <stdio.h> void main() { int a,n,i; long t=1; printf("\n nhap a,n : "); scanf("%d%d",&a,&n); for(i=1;i<=n;t*=a,i++); printf("\n %d^%d = %ld",a,n,t); getch(); }

Bài 2 Viết chương trình tính an với a<>0, n<>0

#include <stdio.h> void main() { int a,n,b,m,i; float t=1; printf("\n nhap a,n : "); scanf("%d%d",&a,&n); b=abs(a); m=abs(n); for(i=1;i<=m;t*=b,i++); if(n<0) t=1/t; if(a<0 && (n%2!=0)) t=-t; printf("\n %d^%d = %0.2f",a,n,t); getch(); }

Bài 3 Viết chương trình tính n! với số n nguyên dương nhập từ bàn phím .

#include <stdio.h> void main() {

Page 32: Giao Trinh Lap Trinh C Tom Tat

int n,i; long t=1; printf("\n nhap n : "); scanf("%d",&n); for(i=1;i<=n;t*=i,i++); printf("\n %d! =%ld",n,t); getch(); }

Bài 4 Viết chương trình tìm tất cả các số nguyên trong khoảng 1000 đến 9999 thỏa mãn số đó bằng tổng của trùng phương của 4 chữ số của nó. (kết quả có 2 số : 1634, 8208)

#include <stdio.h> long f(int x) {return (long)x*x*x*x; } void main() { int a,b,c,d; for(a=1;a<9;a++) for(b=0;b<9;b++) for(c=0;c<9;c++) for(d=0;d<9;d++) if((long)(a*1000+b*100+c*10+d)==f(a)+f(b)+f(c)+f(d)) printf("\n%d%d%d%d",a,b,c,d); getch();}

Bài 5 : Lập trình in lên màn hình các hình sau đây Hình 1 1 12 123 1234 123456 123456 12345 1234 123 12

Hình 2 1 121 12321 1234321 123454321 12345654321

Hình 3 1 232 34543 4567654 567898765 67890109876 7890123210987 890123454321098 90123456765432109 0123456789876543210

Page 33: Giao Trinh Lap Trinh C Tom Tat

1 HINH1.C

#include <stdio.h> void main() { int line,i; clrscr(); for(line=1;line<=6;line++) {for(i=1;i<=line;i++) printf("%d",i); printf("\n"); } printf("\n"); for(line=6;line>=1;line--) {for(i=1;i<=line;i++) printf("%d",i); printf("\n"); } getch();}

HINH2.C

#include <stdio.h> void main() { int line,i,j,k=5; clrscr(); for(line=1;line<=6;line++) {for(j=1;j<=k;j++) printf("%c",32);k--; for(i=1;i<=line;i++) printf("%d",i); for(i=line-1;i>=1;i--) printf("%d",i); printf("\n"); } getch();}

HINH3.C

#include <stdio.h> void main() { int line,i,j,k=10; clrscr(); for(line=1;line<=10;line++) { for(j=1;j<=k;j++) printf("%c",32);k--; j=line; for(i=1;i<=line;i++) {if(j<10) printf("%d",j);else printf("%d",j%10); j++;}

Page 34: Giao Trinh Lap Trinh C Tom Tat

j-=2; for(i=line-1;i>=1;i--) {if(j<10) printf("%d",j);else printf("%d",j%10); j--;} printf("\n"); } getch(); }

Bài 6 Tìm tất cả các số nguyên tố trong đoạn [a,b]; a<b là các số nguyên nhập từ bàn phím.

#include <stdio.h> #include <conio.h> main() { int a,b; int d,i,k; do{ printf("\n nhap cac gia tri a,b: "); scanf("%d%d",&a,&b); } while ( (a<=0)||(a>=b)); for(k=a;k<b;k++) {for(d=0,i=1;i<=k/2;i++) if(k%i==0) d++; if(d==1) printf("\t%d",k); } getch(); }

Bài 7 Tìm tất cả các số hoàn thiện trong đoạn [a,b]; a<b là các số nguyên nhập từ bàn phím.

#include <stdio.h> #include <conio.h> main() { int a,b; int d,i,k; do{ printf("\n nhap cac gia tri a,b: "); scanf("%d%d",&a,&b); } while ( (a<=0)||(a>=b)); for(k=a;k<b;k++) {d=0; for(i=1;i<=k/2;i++) if(k%i==0) d+=i; if(d==k) printf("\t%d",k); } printf("\n xong !");

Page 35: Giao Trinh Lap Trinh C Tom Tat

getch(); }

2. Toán tử while

- Cú pháp: có 2 dạng Dạng 1

while(biểu_thức) { nhóm_lệnh;} Dạng 2 do { nhóm_lệnh } while (biểu_thức); + Giống nhau, nhóm lệnh lặp còn thực hiện khi biểu thức còn đúng (biểuthức == 1) + Khác nhau, dạng 1 kiểm tra điều kiện trước còn dạng 2 kiểm tra điều kiện sau. Vì vậy, do while cho phép nhóm lệnh lặp thực hiện ít nhất một lần. Ví dụ 1 Viết đoạn chương trình nhập số nguyên n > 0. - Dùng do while

do { printf(“\n nhap n:”); scanf(“%n”,&n); } while(n<=0);

- Dùng while printf(“\n nhap n:”); scanf(“%n”,&n); while(n<=0) { printf(“\n nhap n:”); scanf(“%n”,&n);}

hoặc while(1) { printf(“\n nhap n:”); scanf(“%n”,&n); if(n>0) break;}

Một số bài tập về toán tử (lặp) while Bài 1

Page 36: Giao Trinh Lap Trinh C Tom Tat

Viết 2 chương trình với 2 cách khác nhau tìm USCLN của 2 số nguyên dương a,b nhập từ bàn phím. Phương pháp trừ liên tiếp

#include <stdio.h> #include <conio.h> main() { int a,b,a1,b1; printf("\n nhap 2 so a,b :"); scanf("%d%d",&a,&b); a1=a;b1=b; while(a!=b) if(a>b) a-=b; else b-=a; printf("uscln(%d,%d)=%d",a1,b1,b); getch(); }

Phương pháp chia liên tiếp #include <stdio.h> #include <conio.h> main() { int a,b,a1,b1,r; printf("\n nhap 2 so a,b :"); scanf("%d%d",&a,&b); a1=a;b1=b; while((r=a%b)!=0) {a=b;b=r;} printf("uscln(%d,%d)=%d",a1,b1,b); getch(); }

Bài 2 Viết chương trình tính giá trị gần đúng y = ex , biết hàm này có thể khai triển dưới dạng chuỗi Taylor như sau: ex = 1+x/1! + x2/2! + ... + xi /i! + ... với i không vượt quá N hoặc với độ chính xác E = 0.001 , tức là | xi /i! | < E

#include <stdio.h> #include <conio.h> #define Epxilon 0.001 #define N 100 long gt(int n) {long t; int i; for(t=1,i=1;i<=n;t*=i,i++); return t; }

Page 37: Giao Trinh Lap Trinh C Tom Tat

float Ex(float x) { float E=1.0; int i=1; while( ( x/gt(i)>Epxilon) && (i<N) ) {E+=x/gt(i); i++;} return E; } void main() { float x; printf("\n x= "); scanf("%f",&x); printf("\n E(%0.2f)= %0.3f",x,Ex(x)); getch(); }

Bài 2 Viết chương trình tính giá trị các hàm sin(x) và cos(x) theo khai triển Taylor như sau: (với i không vượt quá N hoặc với độ chính xác E = 0.001.) sin(x) = x - x3/3! + x5/5! - ... + (-1)i x(2i+1)/(2i+1)! cos(x) = 1 - x2/2! + x4/4! - .... + (-1)i x2i/(2i)! Sincos.C

#include <stdio.h> #include <conio.h> #define Epxilon 0.0001 #define N 100 long gt(int n) {long t; int i; for(t=1,i=1;i<=n;t*=i,i++); return t;} float mu(float a,int n) {float t; int i; for(t=1.0,i=1;i<=n;t*=a,i++); return t;} float sin(float x) { float k,S=x; int i=3,d=-1; while ( (i<N) && ( (k=mu(x,i)/gt(i)) > Epxilon) ) {S+=d*k; i+=2; d=-d;} return S; } float cos(float x)

Page 38: Giao Trinh Lap Trinh C Tom Tat

{ float k,S=1; int i=2,d=-1; while ( (i<N) && ( (k=mu(x,i)/gt(i)) > Epxilon) ) {S+=d*k; i+=2; d=-d;} return S;} void main() { float x; printf("\n x= "); scanf("%f",&x); printf("\n sin(%0.2f)= %0.9f",x,sin(x)); printf("\n cos(%0.2f)= %0.9f",x,cos(x)); getch(); }

Page 39: Giao Trinh Lap Trinh C Tom Tat

Chương 3. CON TRỎ VÀ MẢNG

Bài 1. BIẾN CON TRỎ

1. Tác dụng của biến con trỏ

Biến con trỏ có 2 tác dụng: - Dùng để chứa địa chỉ của biến. - Xin cấp phát bộ nhớ động. Mục đích của việc chứa địa chỉ của biến là để truyền địa chỉ của đối cho

hàm, và nói chung là để trực tiếp làm việc với địa chỉ của biến, chẳng hạn như các xâu ký tự. Mục đích của việc cấp phát bộ nhớ động là để tối ưu hóa việc sử dụng bộ nhớ cho các biến trong chương trình hoặc tính đến việc tổ chức tốt dữ liệu để phù hợp với một ý định (thuật toán) nào đó.

2. Khai báo biến con trỏ

Kiểu_dữ_liệu *tên_biến_con_trỏ; Ví dụ

int *x; float *r; long *l; unsigned long *w; char *name;

Biến con trỏ cũng phải có kiểu dữ liệu và có kiểu dữ liệu trùng với kiểu dữ liệu của biến mà nó chứa địa chỉ. Trong trường hợp cấp phát bộ nhớ cho các biến động thì biến con trỏ có kiểu dữ liệu trùng với kiểu dữ liệu của các biến động đó.

Ví dụ Nếu ta có biến nguyên x và một biến con trỏ px để chứa địa chỉ của x thì ta

có khai báo: int x; int *px; và có lệnh px=&x;

Page 40: Giao Trinh Lap Trinh C Tom Tat

3. Sử dụng biến con trỏ khi biến con trỏ chứa địa chỉ của biến

Giả sử ta có khai báo dạng (1) Khi đó int x; int *px; px=&x;

viết px là chỉ &x, tức là px==&x viết *px là chỉ x , tức là *px ==x

Như vậy sau khai báo dạng (1) (cách 2):

int x; int *px = &x;

Sau lệnh: *px = 10; thì x ==10; x=20; thì *px=20;

Các lệnh sau đây sai: px=x; sai vì px là biến con trỏ, không chứa được giá trị của biến x x==px; sai vì giá trị của x không bằng địa chỉ của nó lưu trong px

Những quy tắc này được ghi nhớ để vận dụng khi truyền đối cho hàm kiểu con trỏ và nói chung trong các vấn đề mà ta làm việc gián tiếp với biến thông qua con trỏ chứa địa chỉ của biến đó.

Ví dụ #include <stdio.h> #include <conio.h> void main(){ int x; int *px; px=&x; printf(“\n nhap x: “); scanf(“%d”,px); printf(“\n x=%d”, x); x+=10; printf(“\n x=%d”, *px); *px+=11; printf(“\n x=%d”, x); getch(); }

Page 41: Giao Trinh Lap Trinh C Tom Tat

4. Biến con trỏ và hằng địa chỉ

4.1. Về phép toán đối với biến con trỏ

Nếu có khai báo dạng (1): int x, *px=&x; Thì : - Tất cả các phép toán áp dụng được cho x cũng áp dụng được cho *px - &x thì không có phép toán nào. - px thì có các phép toán tăng, giảm địa chỉ. Có nghĩa là mặc dù px==&x Nhưng lệnh px++ ; là đúng (1) Còn lệnh (&x)++; là sai. (2) - Lệnh (2) sai vì địa chỉ của biến x là một địa chỉ vật lý của bộ nhớ được

cấp phát và cố định ngay từ lúc dịch chương trình. Như vậy, địa chỉ của biến là một hằng địa chỉ, không được phép thay đổi trong chương trình.

- Lệnh (1) đúng vì px hoàn toàn độc lập với x, nó có khả năng không những chỉ vào địa chỉ của x mà còn có thể trỏ vào các địa chỉ khác (trong trường hợp này là các địa chỉ kiểu int, 2B). Trước khi thực hiện lệnh (1) con trỏ px trỏ vào địa chỉ của x, sau lệnh (1) thì px sẽ trỏ vào địa chỉ của vùng nhớ là 2 byte tiếp theo, ngay sau 2 Byte địa chỉ của x. Khi px là con trỏ kiểu nguyên thì sau mỗi một lần lệnh tăng địa chỉ thực hiện, px sẽ chỉ vào 2 Byte tiếp theo trong bộ nhớ. Tương tự như thế , nếu px là con trỏ kiểu long , px sẽ chi vào các địa chỉ 4 Bte; nếu px kiểu float, nó sẽ chỉ vào các địa chỉ 6 Byte.

4.2. Phân biệt hằng địa chỉ với biến con trỏ trong trường hợp xâu ký tự

Một xâu ký tự có thể có 2 cách khai báo, hay nói một cách chính xác là 2 cách biểu diễn:

- Sử dụng một mảng các ký tự char name[30]; (cách 1)

- Sử dụng một con trỏ kiểu char char *pname; (cách 2)

Vấn đề là khi nào sử dụng cách nào, phân biệt 2 cách đó khác nhau như thế nào?

(a) Trước hết , về cách sử dụng con trỏ kiểu char

Page 42: Giao Trinh Lap Trinh C Tom Tat

Nếu ta có char *name;

thì phép gán : name=”le thu ha”

nghĩa là gán con trỏ name bằng địa chỉ của hằng xâu ký tự “le thu ha”, do đó, 2 lệnh sau có tác dụng giống nhau để cho hiện lên màn hình dòng chữ le thu ha:

puts(“le thu ha”); và puts(name); (b) Phân biệt cách dùng mảng kiểu char và con trỏ kiểu char.

Ví dụ 1: Đúng Ví dụ 2: Sai char *name, s[30]; name=”le thu ha”; gets(s);

char *name, s[30]; (1) s=”le thu ha”; (2) gets(name); (3)

Ví dụ 1 đúng như đã giải thích trong mục (a). Ví dụ 2 sai: - Câu lệnh (2) sai vì s là một mảng kiểu char mà đã là mảng thì tên mảng là

một hằng địa chỉ, do đó vế trái là một hằng địa chỉ, vế phải là một hằng địa chỉ khác, vậy, không thể gán hằng địa chỉ này bằng một hằng địa chỉ khác được.

- Câu lệnh (3) không sai về mặt cú pháp, nhưng trong trường hợp này là vô nghĩa. Bởi vì name là một con trỏ (kiểu char) cho nên nó phải trỏ vào một địa chỉ (kiểu char) nào đó. Chẳng hạn, nếu name trỏ vào địa chỉ của s , tức là nếu có lệnh:

char *name, s[30]; name=s; (cũng giống như lệnh px = &x) thì 2 lệnh sau đây là có tác dụng như nhau: gets(name); gets(s);

5. Sử dụng con trỏ để xin cấp phát bộ nhớ động

- Trong mục 3 và 4 đã trình bầy về nguyên tắc sử dụng con trỏ để chứa địa chỉ của biến và phân biệt giữa con trỏ và hằng địa chỉ (của biến). Trong mục 5 này sẽ nghiên cứu về cách sử dụng biến con trỏ để xin cấp phát bộ nhớ động.

Page 43: Giao Trinh Lap Trinh C Tom Tat

5.1. Cấp phát và giải phóng bộ nhớ cho biến động

- Trong C, hàm malloc để cấp phát bộ nhớ động. - Xét một biến con trỏ px có kiểu T nào đó: T *px; - Giả sử kiểu T chiếm k byte, tức k=sizeof(T). - Để cấp phát k byte cho một biến động kiểu T ta dùng lệnh: px = (T*) malloc (sizeof(T)); Ví dụ

Ví dụ 1: vô nghĩa Ví dụ 2: có nghĩa Ví dụ 3: có nghĩa int *px; *px=10;

int x,*px=&x; *px=10;

int *px; px = (int*) malloc (sizeof(int)); *px=10;

Ví dụ 1 không sai về mặt cú pháp lệnh nhưng vô nghĩa vì con trỏ px không rõ đang chỉ vào địa chỉ nào. Giả thiết với một địa chỉ “vu vơ “ nào đó mà px đang trỏ vào thì ô nhớ có địa chỉ này này nhận nội dung là 10. Trái lại, ở ví dụ 2, px đang trỏ vào địa chỉ của x, do đó ô nhớ dành cho x sẽ nhận giá trị 10. Còn ở ví dụ 3, mặc dù không có biến tĩnh nào, nhưng một biến động (không có tên - đương nhiên) được tạo ra và chứa giá trị 10.

- Để cấp phát n*k byte cho một dãy n biến động kiểu T ta dùng lệnh: px = (T*) malloc (n*sizeof(T)); Ta sẽ xem xét dãy biến động này trong mục 5.2 dưới đây. - Để giải phóng cho bộ nhớ cho biến động (hoặc dãy biến động) được quản

lý bởi con trỏ px (tức là được cấp phát nhờ px) ta dùng hàm free free(px);

5.2. Quản lý dãy biến động

Theo mục 5.1, dãy n biến động kiểu T ( k byte) được cấp phát và giải phóng như sau:

px = (T*) malloc (n*sizeof(T)); và free(s); Khi đó dãy biến động này được sử dụng như một mảng một chiều các phần

tử kiểu T. Các vấn đề về xử lý mảng đều đưa về việc duyệt mảng, và ở đây là duyệt dãy n biến động. Ta sẽ sử dụng triệt để các phép tăng, giảm địa chỉ đối với

Page 44: Giao Trinh Lap Trinh C Tom Tat

con trỏ để duyệt trên dãy n biến động. Nói chung nên có thêm một con trỏ phụ py nào đó (kiểu T) để duyệt trên dãy biến động, con trỏ px được giữ nguyên để xác định địa chỉ đầu của dãy n biến động. Để duyệt xuôi, ta cho py bắt đầu từ px và tăng địa chỉ con trỏ py n-1 lần đến phần tử thứ n của dãy; để duy: duyệt ngược, ta giảm dần địa chỉ con trỏ py đến khi py chạm vào px thì dừng lại

Ví dụ: Nhập dãy n biến động kiểu nguyên, in dãy đó lên màn hình. #include <stdio.h> #include <conio.h> main(){ int *px,*py; int n,i; printf("\n n= "); scanf("%d",&n); px = (int*) malloc (n*sizeof(int)); py=px; for(i=1;i<=n;i++) { printf("a[%d]= ",i); scanf("%d",py); py++; } printf("\n day n so da nhap la:"); py=px; for(i=1;i<=n;i++) { printf("%d ",*py); py++; } getch(); free(px); }

5.3. Con trỏ cấp phát bộ nhớ cho xâu ký tự

Tổng kết lại những vấn đề đã trình bầy về xâu ký tự , đặc biệt là mục ở mục 4. Ta thấy:

(1) Nhóm lệnh sau đây là bình thường char s[30]; gets(s); puts(s); Nhưng lệnh sau là sai: s = “le van binh” (2) Nhóm lệnh sau đây là bình thường char *s; s=”le van binh”; puts(s);

Page 45: Giao Trinh Lap Trinh C Tom Tat

Nhưng lệnh sau là vô nghĩa: gets(s); (3) Trong trường hợp (2), trường hợp biểu diễn xâu ký tự bằng con trỏ kiểu

char và muốn nhập một xâu ký tự từ bàn phím mà không muốn khởi tạo một hằng xâu ký tự ( như (2) ), tức là làm cho lệnh gets(s) có nghĩa, ta có 2 cách:

Cách 1: Dùng thêm một biến mảng kiểu char, rồi để cho con trỏ s trỏ vào hằng địa chỉ tg

char *s, tg[30]; s=tg; gets(tg); hoặc gets(s); Cách này xem ra trở thành “thừa biến” ! Cách 2: Xin cấp phát bộ nhớ động cho n ký tự để chứa xâu ký tự và dùng

con trỏ (kiểu char) để xác định: char *s; int n = 30; s = (char*) malloc (n*sizeof(char)); Vì xâu ký tự thực sự thì có ký tự ‘\0’ ở cuối nên với xâu n ký tự sẽ cần n+1

byte , vì thế nên viết là: char *s; int n = 30; s = (char*) malloc ((n+1)*sizeof(char)); Ví dụ #include <stdio.h> #include <conio.h> main(){ char *s; int n=30; s= (char*) malloc (n*sizeof(char)); puts("nhap s: "); gets(s); printf("\n%s",s); { char x[30]; strcpy(x,s); printf("\n%s",x); } getch(); }

Page 46: Giao Trinh Lap Trinh C Tom Tat

Khối lệnh “lơ lửng” được đưa vào là vì ta muốn khai báo thêm một biến giữa chừng ở main(). Nếu khai báo này không được đưa vào khối lệnh thì sẽ lỗi.

Bài 2. TRUYỀN ĐỐI CHO HÀM. CON TRỎ TRỎ TỚI HÀM

Ngoài việc hàm sử dụng biến toàn cục, hàm còn được giao nhiệm vụ với các đối (tham số) truyền cho hàm. C sử dụng 2 cách truyền tham số cho hàm trong C là truyền theo giá trị và truyền theo con trỏ. (C++ có thêm một cách mới để truyền tham số cho hàm là truyền tham số kiểu tham chiếu.)

1. Truyền tham số kiểu giá trị cho hàm

Theo cách truyền theo giá trị thì chỉ có bản sao của đối được hàm sử dụng. Trong thân hàm, các giá trị này có thể bị thay đổi nhưng ra khỏi hàm thì giá trị ban đầu của đối vẫn giữ nguyên. Đương nhiên giá trị đầu vào của đối được giữ nguyên vì không phải là chính bản thân đối truyền cho hàm mà là bản sao của đối được truyền cho hàm. Sự thay đổi các giá trị trong hàm là chỉ thay đổi giá trị của bản sao của nó mà thôi.

Ví dụ 1 (thso1.cpp) #include <stdio.h> #include <conio.h> void swap(int,int); main() {

Kết quả x = 10 y=5

int x=10,y=5; clrscr(); swap(x,y); printf("x=%d y=%d\n",x,y); getch(); } void swap(int x,int y) {int z=x; x=y; y=z;}

2. Truyền tham số kiểu con trỏ cho hàm

Khi đối truyền cho hàm kiểu con trỏ thì lúc gọi hàm các con trỏ này sẽ thay thế bằng địa chỉ của các biến để truyền vào hàm hoặc là một con trỏ hoàn toàn xác định nghĩa là đang chứa một địa chỉ của một biến nào đó. Như vậy về bản chất thì bản gốc của đối được truyền cho hàm chứ không phải là bản sao của nó. Vì vậy, những giá trị của đối bị thân hàm làm thay đổi sẽ được giữ lại khi hàm kết thúc.

Ví dụ 2 (thso2.cpp)

Page 47: Giao Trinh Lap Trinh C Tom Tat

#include <stdio.h> Kết quả x = 5 y=10

#include <conio.h> void swap(int*,int*); main() { int x=10,y=5; clrscr(); swap(&x,&y); printf("x=%d y=%d\n",x,y); getch();} void swap(int *x,int *y){int z=*x; *x=*y; *y=z;} Chú ý - Khi khai báo: void swap(int*,int*) - Khi định nghĩa: void swap(int *x,int *y) { // dùng *x và *y; } - Khi sử dụng: swap(&x,&y)

3. Con trỏ trỏ tới hàm

3.1. Cú pháp chung

- Khai báo con trỏ tới hàm (con trỏ trỏ tới hàm) kiểu_dữ_liệu_của_hàm (*tên_con_trỏ_hàm) (kiểu_của_các_đối) { thân_hàm;} - Sử dụng hàm trong main() tên_con_trỏ_hàm = tên_hàm_nào_đó_cùng_kiểu_dữ_liệu; Ví dụ 1: xét khai báo sau, có 2 con trỏ hàm kiểu float: float (*f)(float), (*mf[50])(int); . f là tên con trỏ hàm (gọi tắt là con trỏ hàm) kiểu float gồm một đối kiểu

float. . mf là mảng con trỏ hàm ( mảng 50 phần tử) cũng có kiểu float , có một đối

kiểu int. Ví dụ 2: xét khai báo sau , có 2 con trỏ hàm cùng có kiểu double double (*g)(int,double), (*mg[30])(double,float); . g là con trỏ hàm có kiểu double, gồm 2 đối có kiểu int và double . mg là mảng con trỏ hàm (30 phần tử) kiểu double, gồm 2 đối kiểu double

và float.

Page 48: Giao Trinh Lap Trinh C Tom Tat

3.2. Tác dụng của con trỏ hàm. Đối của hàm là con trỏ hàm

- Con trỏ hàm dùng để chứa địa chỉ của hàm (kiểu của hàm và kiểu của con trỏ hàm phải giống nhau). Do đó, có thể truyền đối cho hàm là một con trỏ hàm, tức là đối của một hàm có thể là một hàm khác.

- Khi khai báo đối của hàm là con trỏ hàm thì ta vẫn khai báo con trỏ hàm như khai báo bên ngoài.

- Khi sử dụng đối (là con trỏ hàm) trong thân hàm mẹ ta có 3 cách sử dụng trong đó có 1 cách như là lúc khai báo: kiểu_hàm_mẹ (...,kiểu_hàm_con(*tên_con_trỏ_hàm)(kiểu_các_đối), ...)

{ /* trong thân hàm mẹ , việc truy nhập tới các đối là con trỏ hàm */ /* có 3 cách như nhau để gọi sử dụng các con trỏ hàm*/ (tên_con_trỏ_hàm) (ds_các_đối_thực_sự); (*tên_con_trỏ_hàm) (ds_các_đối_thực_sự); tên_con_trỏ_hàm (ds_các_đối_thực_sự); } - Ví dụ Danh sách các đối của hàm mẹ

kiểu_hàm_mẹ tên hàm mẹ đối 1 đối 2 đối 3 float f( (*g)(int,int) ,float a ,float b) { ... dùng (*g)(x,y) dùng (g)(x,y) dùng g(x,y) }

- Các con trỏ hàm được sử dụng một cách hình thức trong thân hàm mẹ và

chỉ có ý nghĩa khi có lời gọi hàm mẹ. Lời gọi hàm mẹ sẽ thay thế con trỏ hàm bằng một hàm thực sự nào đó có nghĩa, chẳng hạn đó là các hàm sin, cos, hay một hàm đã xác định từ trước, chẳng hạn hàm f(x)=ax2+bx+c. Điều đó có nghĩa là chỉ cần biết cách mô tả dạng khai báo một con trỏ hàm phù hợp với các hàm thực tế chứ không cần xây dựng nội dung cụ thể cho con trỏ hàm đó.

Page 49: Giao Trinh Lap Trinh C Tom Tat

Ví dụ 1 Ví dụ sau đây xét một hàm xác định f(x) = ax2 và ta định dùng hàm này làm

đối của hàm g nào đó , chẳng hạn g(x) = (ax2 )2. Khi đó , đối của hàm g trước hết phải là một đối hình thức là một con trỏ

hàm *pf nào đó. Khi gọi hàm g thì đối f thực sự mới thay thế *pf. #include <stdio.h> #include <conio.h> double f(double a,double x) {return a*x*x;} double g(double (*pf)(double,double),double a, double x) {double z = (*pf)(a,x)*(pf)(a,x); return z; } main() { double a,x; a=2; x=3; printf("\n g(%0.1f,%0.1f,%0.1f) = %0.1f", f(a,x), a, x, g(f,a,x) ); getch(); } - Ngoài ra, con trỏ hàm (là đối của hàm mẹ) không được khai báo danh

sách đối kể cả hình thức. Chẳng hạn khai báo trên mà sửa như sau là sai: double g(double (*pf)(double a,double x)) {double z = (*pf)(a,x)*(pf)(a,x); return z; } Và khi gọi hàm g, tên hàm thực sự được thay thế đối hình thức và chỉ là tên

hàm, chứ không cần chỉ rõ đối! Trừ trường hợp con trỏ hàm được sử dụng để thay thế cho một hàm thực sự và con trỏ hàm không là đối của hàm mẹ nào (xem ví dụ 3)

printf("\n g(%0.1f) = %0.1f " , x, g(f,a,x) ); (chỉ viết là f , chứ không viết là f(a,x) , như bản thân hàm f đã định nghĩa)

Ví dụ 2 Phương pháp hình thang để tính gần đúng tích phân của hàm f(x) trên đoạn

[a,b] với khoảng chia N mô tả như sau:

Page 50: Giao Trinh Lap Trinh C Tom Tat

Trên đoạn [a,b], gồm N khoảng chia, mỗi khoảng có độ lớn: h = (b-a)/N gọi s là giá trị trung bình của hàm f(x) giữa đoạn [a,b], tức là: s = ( f(a) + f(b) ) /2 Khi đó, giá trị tích phân cần tính là giá trị gần đúng của chuỗi khai triển s = h* [ f(a+h) + f(a+2h) + ... + f(a+N*h);] Vận dụng phương pháp hình thang để tính tích phân như mô tả ở trên với

số khoảng chia là N = 1000, hãy tính tích phân gần đúng cho các hàm: 1) sin(x) trên đoạn [0,pi/2] 2) cos(x) trên đoạn [0,pi/2] 3) ex trên đoạn [0,1] 4) g(x) = ex - 2sin(x2) / (1 + x4) trên đoạn [-1.2, 3.5] #include <stdio.h> #include <conio.h> #include <math.h> const N = 1000; const pi = 3.1416; double g(double x) { double s; s=(exp(x)-2*sin(x*x)) / (1+pow(x,4)); return s; } double tichphan(double (*pf)(double), double a,double b) {double h = (b-a)/N; double s =((*pf)(a) + (*pf)(b))/2; int i; for(i=1;i<=N;i++) s+=(*pf)(a+i*h); return h*s; } main(){ printf("\n Tich phan cua sin(x)= %0.1f", tichphan(sin, 0, pi/2) ); printf("\n Tich phan cua cos(x)= %0.1f", tichphan(cos, 0, pi/2) ); printf("\n Tich phan cua exp(x)= %0.1f", tichphan(exp, 0, pi/2) ); printf("\n Tich phan cua g(x)= %0.1f", tichphan(g, 0, pi/2) ); getch(); }

Page 51: Giao Trinh Lap Trinh C Tom Tat

Ví dụ 3 Sử dụng mảng con trỏ hàm để lập bảng giá trị cho các hàm : x2, sin(x), cos(x), ex và sqrt(x) với x trong đoạn [1,10] với số gia bước tăng

là 0.5. - Ví dụ 2 là một chứng minh rằng việc sử dụng con trỏ hàm có vai trò quan

trọng khi muốn xây dựng một ứng dụng mà trong đó có nhiều hàm khác nhau được sử dụng cùng một cách như nhau.

- Đôi khi để cho thuận tiện, các hàm thực sự được biểu thị bởi một mảng các con trỏ hàm. Chẳng hạn như ví dụ 3 dưới đây, mảng con trỏ hàm (*mf[5])(double) sẽ lần lượt biểu thị các hàm thực sự, nghĩa là trước khi gọi hàm mẹ (với đối là con trỏ hàm), các hàm thực sự được gán cho các phần tử của mảng con trỏ hàm. Ví dụ 3 cũng minh họa cách sử dụng con trỏ hàm độc lập (không là đối của hàm mẹ nào) : khi đó vẫn không được chỉ rõ tên tham số hình thức trong đối của con trỏ hàm trừ lúc gọi hàm; và không cần xây dựng nội dung của con trỏ hàm.

#include <stdio.h> #include <conio.h> #include <math.h> double g(double x) {return x*x;} main(){ double (*mf[6])(double); mf[1]=g; mf[2]=sin; mf[3]=cos; mf[4]=exp; mf[5]=sqrt; clrscr(); { int i; double x=1.0; while(x<=10.0){ printf("\n"); for(i=1;i<5;i++) printf("%10.2f",mf[i](x)); x+=0.5;} } getch();

Page 52: Giao Trinh Lap Trinh C Tom Tat

} Ví dụ 4 Viết chương trình nhập một số nguyên trong đoạn [1..3] để tương ứng cho

tính đạo hàm của các hàm số y=x2, y=x3, y=x4. Việc tính đạo hàm được thực hiện bằng một lời gọi tới một phần tử mảng của hàm lấy mục chọn làm chỉ số. Viết 3 hàm thành phần của mảng để tính đạo hàm cho từng trường hợp.

#include <stdio.h> #include <conio.h> #include <math.h> float f(float x) {return 2*x;} float g(float x) {return 3*x*x;} float h(float x) {return 4*x*x*x;} main(){ float (*mf[4])(float); mf[1]=f; mf[2]=g; mf[3]=h; clrscr(); { int i; float x; printf("\n nhap thu tu ham can tinh dao ham: "); scanf("%d",&i); printf("\n can tinh dao ham tai diem x= "); scanf("%f",&x);

printf("gia tri dao ham can tinh la: %0.1f",mf[i](x)); } getch(); }

Page 53: Giao Trinh Lap Trinh C Tom Tat

Bài 3. CON TRỎ VÀ MẢNG MỘT CHIỀU

1. Địa chỉ và giá trị của các phần tử của mảng

- Một vài lý luận dưới đây sẽ là cơ sở lô gich cho hệ thống các quy ước về địa chỉ của mảng và các phần tử của mảng:

Một cách hợp lý thấy rằng địa chỉ của một cơ quan X gồm dãy nhà 34, 35, 36 nằm trên phố Y sẽ là địa chỉ của ngôi nhà đầu tiên , nghĩa là cơ quan X ở địa chỉ “34 phố Y”. Tương tự như thế nếu px là một con trỏ nguyên vừa xin cấp phát n vùng bộ nhớ n*2 byte thì địa chỉ của biến động đầu tiên chính là địa chỉ chung của toàn dãy (về thói quen quy ước) , nghĩa là địa chỉ bộ nhớ của 2 byte đầu tiên trong dãy n các 2 byte bộ nhớ vừa xin cấp phát. Cũng tương tự như thế, một mảng nguyên a thì &a[0] là địa chỉ của phần tử đầu tiên của mảng đồng thời cũng là địa chỉ của toàn mảng , nghĩa là a = &a[0]

Mặt khác, nếu px là một con trỏ nguyên của dãy n các 2 bye bộ nhớ thì phép toán tăng con trỏ px = px + i nghĩa là cho px trỏ vào 2 byte thứ i trong dãy n các 2 byte bộ nhớ đó. Tương tự như thế, mảng a kiểu nguyên có thể coi là một con trỏ: trỏ vào dãy các 2 byte bộ nhớ, và , khi đó thì phép toán tăng địa chỉ a + i chính là để truy nhập tới địa chỉ của phần tử thứ i trong mảng a, do đó (a+i) = &a[i], và cũng vì vậy ta có *(a+i) = a[i]

- Tóm lại ta có hệ thống các biểu diễn đồng nhất như sau: hệ thống (1)

a==&a[0] a+1== &a[1] a+2==&a[2] ... a+i==&a[i] *(a+i)==a[i]

- Một cách gián tiếp, nếu ta có khai báo float a[n]; float *pa; pa=a; thì pa có thể thay thế a trong hệ thống (1)

Page 54: Giao Trinh Lap Trinh C Tom Tat

nghĩa là ta có: Hệ thống (2) a[i] có địa chỉ &a[i] *(a+i) có địa chỉ a+i *(pa+i) có địa chỉ pa+i pa[i] có địa chỉ &pa[i] Trong đó: a[i] == *(a+i) == *(pa+i)==pa[i] &a[i]== (a+i)== (pa+i)==&pa[i]

Ví dụ 1 Nhập một mảng float và sắp xếp tăng dần các phần tử trong mảng #include <stdio.h> #include <conio.h> main(){ float a[10]; int n,i,j; float tg; printf("\n nhap n: "); scanf("%d",&n); for(i=0;i<n;i++) {printf("a[%d]=",i+1); scanf("%f",(a+i)); } for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(a[j]<a[i]) {tg=*(a+i); *(a+i)=*(a+j); *(a+j)=tg; } printf("\n sau khi sap xep :"); for(i=0;i<n;i++) printf("%0.1f ",a[i]); getch(); } - Lệnh scanf(“%f”, (a+i)); có thể viết là scanf(“%f”, &a[i]); - Các lệnh tg=*(a+i); *(a+i)=*(a+j); *(a+j)=tg; có thể viết đơn giản là: tg=a[i]; a[i]=a[j]; a[j]=tg; vv...

Page 55: Giao Trinh Lap Trinh C Tom Tat

2. Đối của hàm (đối ra) là mảng thì đó là một con trỏ

- Hệ thống 1 cho thấy bản thân mảng và con trỏ đã có quan hệ chặt chẽ: các phần tử của mảng được xác định nhờ chỉ số hoặc thông qua con trỏ.

- Hệ thống 2 là cách sử dụng con trỏ mảng một cách gián tiếp nếu muốn truyền đối cho hàm là một mảng.

- Giả sử a là một mảng, maxN là một hằng số đã xác định, *pa là một biến con trỏ có kiểu trùng với kiểu của mảng (để sau này thay tham số hình thức pa bởi tham số thực sự (cùng kiểu) là a), n là số phần tử cụ thể của mảng a. Ta có các quy định sau đây về cú pháp:

- Khai báo: . kiểu_dl_mảng a[maxN]; . kiểu_dl_hàm tên_hàm( kiểu_mảng *pa, int n) { Thân hàm: - địa chỉ của phần tử thứ i là (pa+i) - giá trị của phần tử thứ i là *(pa+i) hoặc pa[i] } - Sử dụng: tên_hàm(a,n); nghĩa là thay tham số hình thức *pa bằng a. (a là một con trỏ mảng, *pa là

một con trỏ kiểu_dl_mảng) Ví dụ 2 Xây dựng các hàm thực hiện các nhiệm vụ sau đây: - Nhập một mảng n số thực - In mảng đã nhập lên màn hình - Sắp xếp các phần tử của mảng theo thứ tự tăng dần trong hàm main, gọi thực hiện các hàm nói trên. #include <stdio.h> #include <conio.h> void nhapmang(float *pa,int *n)

Page 56: Giao Trinh Lap Trinh C Tom Tat

{ int i; printf("\n nhap n: "); scanf("%d",n); for(i=0;i<*n;i++) { printf("a[%d]=",i+1); scanf("%f",(pa+i)); } } void sapxep(float *pa, int n) { int i,j; float tg; for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(pa[j]<pa[i]){tg=pa[i]; pa[i]=pa[j]; pa[j]=tg; } } void inmang(float *pa,int n) { int i; for(i=0;i<n;i++) printf("%0.1f ",pa[i]); } main(){ float a[10]; int n; nhapmang(a,&n); printf("\n truoc khi sap xep: "); inmang(a,n); printf("\n sau khi sap xep: "); sapxep(a,n); inmang(a,n); getch(); }

Page 57: Giao Trinh Lap Trinh C Tom Tat

Bài 4. CON TRỎ VÀ MẢNG 2 CHIỀU

1. Nguyên tắc viết địa chỉ và giá trị của các phần tử mảng của 2 chiều

- Xét một mảng a, địa chỉ của phần tử a[i][j] viết là &a[i,j] hay &a[i][j] đều sai. Do vậy, để sử dụng giá trị của phần tử a[i,j] ta vẫn có thể viết a[i][j] , nhưng nếu để nhập thì không thể viết scanf(“%f”,&a[i][j]); Đôi khi để đỡ phức tạp (như sơ đồ dưới đây), ta dùng thêm biến trung gian x để nhập cho &x và sau đó gán a[i][j]=x;

- Nguyên tắc viết địa chỉ và giá trị của các phần tử của mảng 2 chiều được tóm tắt như sơ đồ sau đây:

float a[][n]; (1) hoặc float a[m][n]; (1’)

a[i,j]

(float*)a +i*n+j * ( (float*)a +i*n+j )

float *pa; (2) và gán pa = (float*)a; (3)

a[i,j]

pa + i*n + j *(pa + i*n + j)

có địa chỉ có giá trị

nếu khai báo thêm (2) và lệnh trao địa chỉ (3)

có địa chỉ có giá trị

Page 58: Giao Trinh Lap Trinh C Tom Tat

Lưu ý: nếu trong (3), chỉ gán pa=a sẽ sai vì vế trái là biến con trỏ float, vế phải là con trỏ float[m]. So sánh 3 cách trong 3 ví dụ sau đây để nhập và in mảng 2 chiều vừa nhập. Ví dụ 1

#include <stdio.h> #include <conio.h> #define maxm 100 #define maxn 100 main() { float a[maxm][maxn]; int i,j,m,n; printf("\n nhap m,n : "); scanf("%d%d",&m,&n); for(i=0;i<m;i++) for(j=0;j<n;j++) { printf("a[%d,%d]= ",i,j); scanf("%f", (float*)a+i*n+j); } printf("\n mang vua nhap la:\n "); for(i=0;i<m;i++) { for(j=0;j<n;j++) printf("%3.1f\t", *( (float*)a +i*n +j) ); printf("\n"); } getch(); }

Các cú pháp : scanf("%f", (float*)a+i*n+j); (1) và printf("%3.1f\t", *( (float*)a +i*n +j) ); (2) có vẻ phức tạp. Để khắc phục, ta dùng thêm một biến trung gian x như ví dụ 2 sau đây: Ví dụ 2

#include <stdio.h> #include <conio.h> #define maxm 100 #define maxn 100

Page 59: Giao Trinh Lap Trinh C Tom Tat

main() { float a[maxm][maxn]; int i,j,m,n; float x; printf("\n nhap m,n : "); scanf("%d%d",&m,&n); for(i=0;i<m;i++) for(j=0;j<n;j++) { printf("a[%d,%d]= ",i,j); scanf("%f", &x); a[i][j]=x; } printf("\n mang vua nhap la:\n "); for(i=0;i<m;i++) { for(j=0;j<n;j++) printf("%3.1f\t", a[i][j]); printf("\n"); } getch(); }

Việc sử dụng thêm biến trung gian x đã thay được phức tạp (1) và (2) thành

scanf("%f", &x); a[i][j]=x; (1b) và

printf("%3.1f\t", a[i][j]); (2b) Lưu ý rằng (1) không thể đi cùng với (2b) và (1b) cũng không thể đi cùng với (2) Một cách thứ 3 là ta dùng thêm một con trỏ mảng như ví dụ sau đây: Ví dụ 3

#include <stdio.h> #include <conio.h> #define maxm 100 #define maxn 100 main() { float a[maxm][maxn]; int i,j,m,n; float *pa; pa=(float*)a; printf("\n nhap m,n : "); scanf("%d%d",&m,&n); for(i=0;i<m;i++) for(j=0;j<n;j++) { printf("a[%d,%d]= ",i,j);

Page 60: Giao Trinh Lap Trinh C Tom Tat

scanf("%f", pa+i*n+j); } printf("\n mang vua nhap la:\n "); for(i=0;i<m;i++) { for(j=0;j<n;j++) printf("%3.1f\t", *(pa+i*n+j) ) ; printf("\n"); } getch(); } Đúng như sơ đồ: việc sử dụng thêm con trỏ

float *pa ; với lệnh gán

pa=(float*)a; là một ép kiểu thể cho phép diễn tả đơn giản (1) và (2)

Để nhập: scanf("%f", pa+i*n+j); Để in giá trị : printf("%3.1f\t", *(pa+i*n+j) ) ;

* Từ đó ta thấy rằng, nếu thao tác với mảng một cách trực tiếp trong hàm

main() hoặc với một mảng tự động trong một hàm thì nên sử dụng cú pháp của ví dụ 2. Nếu muốn truyền mảng vào hàm như một đối của hàm đó thì phải sử dụng cú pháp của ví dụ 3, nghĩa là sử dụng biến con trỏ, xem mục 2 dưới đây.

2. Đối của hàm (đối ra) là một mảng 2 chiều thì phải là con trỏ mảng

- Khai báo: kiểu_mảng a[m][n]; kiểu_hàm tên_hàm( kiểu_mảng *pa, int m, int n)

- Thân hàm { + địa chỉ của a[i,j] là: (pa+i*n+j) + giá trị của a[i,j] là: *(pa+i*n+j) }

- Khi sử dụng: tên_hàm( (kiểu_mảng*)a, m, n )

Page 61: Giao Trinh Lap Trinh C Tom Tat

- Có thể thực hiện cách thứ 2 : ta dùng thêm một biến con trỏ trung gian ,

giả sử kiểu mảng a là float float a[m][n]; float *pa; pa = (float*)a;

. Khai báo kiểu_hàm tên_hàm ( float (*pa)[n] )

hoặc kiểu_hàm tên_hàm ( float pa[][n] )

. Thân hàm Cách viết pa[i][j] để sử dụng giá trị. Nếu muốn sử dụng địa chỉ , chẳng hạn

để nhập thì có thể dùng thêm một biến trung gian, như ví dụ 1. Ví dụ 4 Viết các hàm: - Nhập ma trận các phần tử của một ma trận là các số thực biết kích thước

mxn. - In lên màn hình ma trận kích thước mxn. - Tính ma trận tích C[mxn]= A[mxp]*B[pxn] Vận dụng các hàm trên, trong hàm main() thực hiện các công việc sau đây: - Nhập các số nguyên m,p,n . - Nhập các ma trận A[mxp], B[pxn] các phần tử là các số thực - In lên màn hình các ma trận A, B, C = A*B #include <stdio.h> #include <conio.h> #define size 100 void nhapmatran(float*,int,int); void inmatran(float*,int,int); void tinhtich(float*,float*,float*,int,int,int); void main() { float a[size][size], b[size][size], c[size][size]; int m,p,n; printf("\n nhap m,p,n : "); scanf("%d%d%d",&m,&p,&n); printf("\n nhap ma tran a:\n"); nhapmatran((float*)a,m,p); printf("\n nhap ma tran b:\n"); nhapmatran((float*)b,p,n); tinhtich((float*)a,(float*)b,(float*)c,m,p,n);

Page 62: Giao Trinh Lap Trinh C Tom Tat

inmatran((float*)a,m,p); inmatran((float*)b,p,n); inmatran((float*)c,m,n); getch(); } void nhapmatran(float *pa,int m,int n) { int i,j; for(i=0;i<m;i++) for(j=0;j<n;j++) {printf("item[%d,%d]= ",i+1,j+1); scanf("%f",(pa+i*n+j)); } } void inmatran(float *pa,int m,int n) { int i,j; for(i=0;i<m;i++) { for(j=0;j<n;j++) printf("%10.1f",*(pa+i*n+j)); printf("\n"); } printf("\n\n"); } void tinhtich(float *pa,float *pb,float *pc,int m,int p,int n) { int i,j,k; for(i=0;i<m;i++) for(j=0;j<n;j++) { *(pc+i*n+j)=0; for(k=0;k<p;k++) *(pc+i*n+j) += (*(pa+i*p+k)) * (*(pb+k*n+j)); } }

Page 63: Giao Trinh Lap Trinh C Tom Tat

Chương 4. CON TRỎ, KÝ TỰ VÀ XÂU KÝ TỰ

Bài 1. Mảng các phần tử là các xâu ký tự Có 3 mẫu để nhập và xử lý một danh sách mà mỗi phần tử của danh sách là một xâu ký tự. - Hai mẫu đầu tiên (mục 1 và 2) biểu diễn danh sách các xâu ký tự bằng mảng con trỏ kiểu char. Do đó mỗi phần tử của mảng là một con trỏ kiểu char có khả năng biểu diễn một xâu ký tự. Mẫu 3 (mục 3) biểu diễn danh sách các xâu ký tự bằng một mảng 2 chiều mà mỗi một hàng của mảng là một dãy các ký tự. Với cách biểu diễn này ít nhiều có những hạn chế khi muốn xử lý từng phần tử (xâu ký tự) của danh sách. - Mẫu (1) được áp dụng khi xử lý trực tiếp trong chương trình một danh sách các xâu ký tự, có thể đổi chỗ các phần tử của danh sách cho nhau, có thể xử lý từng ký tự trên từng phần tử. - Mẫu (2) có tác dụng như mẫu (1) nhưng được áp dụng trong trường hợp muốn truyền đối là danh sách các xâu ký tự cho hàm xử lý.

1. Mảng các phần tử là xâu ký tự trong chương trình (hàm main())

1.1. Khai báo

# define maxn 100; # define maxlen 50; char *s[maxn]; int n; char tg[maxlen]; int len,i,j;

1.2. Nhập

for(i=0;i<n;i++) { printf(“s[%d] =”,i); gets(tg); len=strlen(tg); s[i]= (char*) malloc ((len+1)*sizeof(char));

}

1.3. In danh sách

for(i=0;i<n;i++) printf(“%s\n”,s[i]);

1.4. Xử lý

- Đảo ngược danh sách, sử dụng các phép gán for(i=0,j=n-1; i<j ; i++, j--) {

strcpy(tg,s[i]); strcpy(s[i], s[j]); strcpy(s[j],tg); } - Mỗi phần tử thứ i của danh sách s là một xâu ký tự, ta có thể xử lý từng ký

tự thứ j trong xâu ký tự s[i] đó , ta viết s[i][j] for(i=0;i<n;i++){ for(j=0;j<strlen(s[i]);j++) <xử lý s[i][j]>}

Page 64: Giao Trinh Lap Trinh C Tom Tat

Xem các ví dụ 1,2

2. Đối (đối ra) của hàm là mảng các xâu ký tự

2.1. Khai báo

# define maxn 100; # define maxlen 50; char *s[maxn]; int n;

2.2. Viết hàm

kiểu_hàm tên_hàm (char *ps[], int n) { thân hàm:

phần tử thứ i của mảng là ps[i] , ps[i] là một xâu ký tự }

2.3. Gọi hàm

nhap_ds(s,&n); in_ds(s,n); dao_ds(s,n); in_ds(s,n); ...

Xem các ví dụ 3,4

3. Mảng các xâu ký tự biểu diễn bằng mảng 2 chiều các ký tự

Trong trường hợp mảng các xâu ký tự được sử dụng : - Không để truyền đối cho hàm - Không có nhu cầu thay đổi giá trị của các xâu ký tự trong danh sách thì ta nên biểu diễn nó bằng một mảng 2 chiều các phần tử là các ký tự và sử dụng như dạng sau đây:

3.1. Khai báo

# define maxn 100; # define maxlen 50; char s[maxn][maxlen]; int n,i,j;

3.2. Nhập

for(i=0;i<n;i++) {... gets(s[i]);}

3.3. Xử lý

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

Page 65: Giao Trinh Lap Trinh C Tom Tat

... Dùng (s[i]) /* hạn chế xử lý trên xâu s[i] */ ...} Ví dụ 1 - Nhập từ bàn phím danh sách gồm tên của n cán bộ. - Đảo ngược danh sách và in kết quả lên màn hình. - Sắp xếp danh sách theo alphabe và in kết quả lên màn hình

#include <stdio.h> #include <conio.h> #include <string.h> #define maxn 100 #define maxlen 50 main() { char s[maxn][maxlen]; int n; int i,j; char tg[maxlen]; printf("\n nhap n: "); scanf("%d",&n); fflush(stdin); printf("\n nhap n ten n can bo:\n"); for(i=0;i<n;i++) {printf("\n s[%d] = ",i); gets(s[i]);} for(i=0,j=n-1; i<j; i++,j--) {strcpy(tg,s[i]); strcpy(s[i],s[j]); strcpy(s[j],tg);} printf("\n danh sach duoc dao nguoc:\n"); for(i=0;i<n;i++) puts(s[i]); for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(strcmp(s[j],s[i])<0) {strcpy(tg,s[i]); strcpy(s[i],s[j]); strcpy(s[j],tg);} printf("\n danh sach duoc sap xep:\n"); for(i=0;i<n;i++) puts(s[i]); getch(); }

Ví dụ 2 - Nhập từ bàn phím danh sách gồm tên của n sinh viên - Sắp xếp danh sách theo alphabe và in kết quả lên màn hình

#include <stdio.h> #include <conio.h> #include <string.h> #define maxn 100 #define maxlen 50

Page 66: Giao Trinh Lap Trinh C Tom Tat

main() { char *s[maxn]; char tg[maxlen]; int n; int i,j; printf("\n nhap n: "); scanf("%d",&n); fflush(stdin); printf("\n nhap n xau ky tu:\n"); for(i=0;i<n;i++) { int len; printf("\n s[%d] = ",i); gets(tg); len=strlen(tg); s[i]=(char*) malloc ((len+1)*sizeof(char)); strcpy(s[i],tg); } for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(strcmp(s[j],s[i])<0) { strcpy(tg,s[i]); strcpy(s[i],s[j]); strcpy(s[j],tg);} printf("\n danh sach sau khi sap xep la:\n"); for(i=0;i<n;i++) printf("%s\t",s[i]); getch();}

Ví dụ 3 - Nhập từ bàn phím danh sách gồm họ và tên của n sinh viên - Chữa lại các chữ cái đầu của họ và tên mỗi sinh viên là một chữ hoa - In kết quả lên màn hình

#include <stdio.h> #include <conio.h> #include <string.h> #define maxn 100 #define maxlen 50 main() { char *s[maxn]; char tg[maxlen]; int n; int i,j; printf("\n nhap n: "); scanf("%d",&n); fflush(stdin); printf("\n nhap n xau ky tu:\n"); for(i=0;i<n;i++) { int len; printf("\n s[%d] = ",i); gets(tg); len=strlen(tg); s[i]=(char*) malloc ((len+1)*sizeof(char)); strcpy(s[i],tg); } for(i=0;i<n;i++) for(j=0;j<strlen(s[i]);j++)

Page 67: Giao Trinh Lap Trinh C Tom Tat

if(j==0 || (s[i][j-1]==32 && s[i][j]!=32)) { s[i][j]-=32;} printf("\n danh sach sau khi sua:\n"); for(i=0;i<n;i++) printf("\n%s",s[i]); getch(); }

Ví dụ 3 Viết chương trình minh họa sử dụng các hàm sau đây: - Nhập một danh sách tên các mặt hàng - In danh sách các tên mặt hàng. - Tìm kiếm một mặt hàng biết tên mặt hàng nhập từ bàn phím

#include <stdio.h> #include <conio.h> #include <string.h> #define maxn 100 #define maxlen 50 void nhap_ds(char *ps[],int *n) { int i; char tg[maxlen]; printf("\n nhap n: "); scanf("%d",n); fflush(stdin); printf("\n nhap n ten mat hang:\n"); for(i=0;i<*n;i++) { int len; printf("\n s[%d] = ",i); gets(tg); len=strlen(tg); ps[i]=(char*) malloc ((len+1)*sizeof(char)); strcpy(ps[i],tg); } } void in_ds(char *ps[],int n) { int i; printf("\n danh sach cac mat hang:\n"); for(i=0;i<n;i++) printf("%s\t",ps[i]); } int timthay(char *s,char *ps[],int n) { int i,j=0; for(i=0;i<n;i++) if(strcmp(s,ps[i])==0) j=1; return j; }

Page 68: Giao Trinh Lap Trinh C Tom Tat

main() { char *s[maxn]; int n; char tenh[maxlen]; nhap_ds(s,&n); in_ds(s,n); printf("\n nhap ten mot mat hang can tim : "); scanf("%s",tenh); if (timthay(tenh,s,n)) printf("\n co ten hang nay trong danh sach"); else printf("\n khong co ten hang nay trong danh sach"); getch(); }

Ví dụ 4 Xây dựng các hàm sau và minh họa việc sử dụng chúng trong main() - Nhập từ bàn phím tên của n sinh viên - Sắp xếp danh sách tên các sinh vên theo thứ tự giảm dần - In danh sách sinh viên lên màn hình

#include <stdio.h> #include <conio.h> #include <string.h> #define maxn 100 #define maxlen 50 void nhap_ds(char *ps[],int *n) { int i; char tg[maxlen]; printf("\n nhap n: "); scanf("%d",n); fflush(stdin); printf("\n nhap n ten n nhan vien:\n"); for(i=0;i<*n;i++) { int len; printf("\n s[%d] = ",i); gets(tg); len=strlen(tg); ps[i]=(char*) malloc ((len+1)*sizeof(char)); strcpy(ps[i],tg); } } void in_ds(char *ps[],int n) { int i; printf("\n danh sach cac nhan vien:\n"); for(i=0;i<n;i++) printf("%s\t",ps[i]); } void sapxep(char *ps[],int n) { int i,j;

Page 69: Giao Trinh Lap Trinh C Tom Tat

char tg[maxlen]; for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(strcmp(ps[j],ps[i])>0) {strcpy(tg,ps[i]); strcpy(ps[i],ps[j]); strcpy(ps[j],tg);} } main() { char *s[maxn]; int n; nhap_ds(s,&n); sapxep(s,n); in_ds(s,n); getch(); }

Bài 2. Mảng các phần tử là ký tự

1. Nhắc lại về vấn đề cấp phát bộ nhớ cho biến động và dãy biến động

Nếu p là biến con trỏ thì p có 2 tác dụng - Để chứa địa chỉ của biến, mảng. - Để cấp phát bộ nhớ cho biến động: Giả sử p có kiểu TypeP

TypeP *p; 1) Cấp phát bộ nhớ cho n biến động kiểu TypeP và xác định bằng con trỏ p, ta viết lệnh

p = (TypeP*) malloc ((n+1)*sizeof(TypeP) 2) Khi đó truy nhập tới dãy n biến động bằng lệnh:

for (i=0; i<n ; i++) < su dung p[i]> 3) Để giải phóng bộ nhớ cho dãy biến động này , ta dùng lệnh

for (i=0; i<n ; i++) free(p[i]);

2. Mảng các phần tử là ký tự, sử dụng con trỏ kiểu char.

2.1. Khai báo

char *s; char st[20]; int len;

2.2. Nhập mảng ký tự , cấp phát bộ nhớ động

gets(st); len=strcpy(st); s=(char*) malloc (len+1);

Page 70: Giao Trinh Lap Trinh C Tom Tat

strcpy(s,st);

2.3. Duyệt mảng để in

for(i=0;i<len;i++) printf("%c",*(s+i))

2.4. Sắp xếp các ký tự trong mảng ký tự

Thêm một biến char *p; p = (char*) malloc (2)

Và áp dụng thuật toán sắp xếp for(i=0;i<len;i++) for(j=len-1;j>i;j--) if(*(s+j)<*(s+j-1)) { *p=*(s+j); *(s+j)=*(s+j-1); *(s+j-1)=*(s+j); }

3. Tìm hiểu về con trỏ kiểu char

void main() { char *s; char *p; char st[25]; int len; int i,j; printf("\ns = "); gets(st); len=strlen(st); s=(char*) malloc (len+1); strcpy(s,st); for(i=0;i<len;i++) printf("%c",*(s+i)); printf("\n"); p=s; for(i=0;i<len;i++) {printf("%c",*s); s++;} ; printf("\n"); s=p; for(i=0;i<len;i++) {printf("%c",*s); s++;} ; printf("\n"); getch(); }

Ví dụ Nhập một xâu ký tự từ bàn phím gồm các chữ cái. Hãy chuyển các chữ hoa lên đầu, các chữ thường về cuối dãy sao cho thứ tự trước sau của từng loại chữ hoa, chữ thường trong dãy ban đầu được giữ nguyên.

Page 71: Giao Trinh Lap Trinh C Tom Tat

Cách 1/* Dung con tro kieu char */ #include <stdio.h> #include <string.h> #include <ctype.h> void main() { char *s; char *p; char st[25]; int len; int i,j; clrscr(); printf("\ns = "); gets(st); len=strlen(st); s=(char*) malloc (len+1); strcpy(s,st); for(i=0;i<len;i++) printf("%c",*(s+i)); printf("\n"); p=(char*) malloc (1); for(i=1;i<len;i++) for(j=len-1;j>=i;j--) if( isupper(*(s+j)) && islower(*(s+j-1)) ) { *p=*(s+j); *(s+j)=*(s+j-1); *(s+j-1)=*p; } for(i=0;i<len;i++) printf("%c",*(s+i)); printf("\n"); getch(); } Cách 2 /* Dung mang kieu char */ #include <stdio.h>#include <string.h>#include <ctype.h>void main(){ char s[200]; int i,j; int len,k; clrscr(); printf("\ns = "); gets(s); printf("\ns= "); puts(s); len=strlen(s); for(i=1;i<len;i++)

for(j=len-1;j>=i;j--) if( isupper(s[j]) && islower(s[j-1]) ) { k=s[j]; s[j]=s[j-1]; s[j-1]=k; } printf("\ns= "); puts(s); getch(); }

Page 72: Giao Trinh Lap Trinh C Tom Tat

Bài 3. Xây dựng các phép toán tiện ích trên xâu ký tự

1. Xem xét các hàm có sẵn trong C

1.1. Các hàm chuyển đổi kiểu

1. Chuyển đổi kiểu tổng quát #include <stdio.h> • int sprintf(char *s,char *dkhien, danh_sach_doi);

Ví dụ sprintf(s,"%s %s %s",s1,s2,s3); 2. Trong <ctype.h>

• int tolower(int c); Đổi 1 chữ hoa thành chữ thường. • int toupper(int c); Đổi 1 chữ thường thành chữ hoa. • double atof(char *s); Chuyển chuỗi s sang giá trị double. • int atoi(char *s); Chuyển chuỗi s sang giá trị int. • long atol(char *s); Chuyển chuỗi s sang giá trị long. • char *itoa(int x,char *s,int cs); Chuyển số nguyên x hệ cs thành chuỗi s .

Hàm trả về địa chỉ của s. • char *ltoa(long x, char *s,int cs); Tương tự như hàm itoa(x,s,cs) • char *gcvt(double x, int n, char *s);

- Chuyển double sang chuỗi s gồm các chữ số, có cả dấu chấm thập phân và dấu - cho số âm :

- Vào + x : giá trị double cần chuyển. + n : số chữ số có nghĩa cần lưu giữ. - Ra + Chuỗi kết quả ( ở dạng thập phân hoặc dạng mũ ) chứa trong vùng s. + Hàm trả về địa chỉ vùng s.

1.2. Các hàm kiểm tra ký tự

• int isalnum(int kt); • int isalpha(int kt); • int islower(int kt); • int upper(int kt); • int isspace(int kt); • int isdigit(int kt); • int isxdigit(int kt); kt có phải là chữ số hệ 16 không • int ispunct(int kt); kt có phải dấu chấm câu. • int isgraph(int kt); kt có phải là ký tự in được (khác ký tự trống 0x21 đến 0x7E) • int isprint(int kt); kt có phải là ký tự in được (kể cả ký tự trống 0x20 đến 0x7E)

Page 73: Giao Trinh Lap Trinh C Tom Tat

1.3. Các hàm trên xâu ký tự

1. int strlen(char *s) Độ dài chuỗi 2. char *strcat(char *s1, char *s2) Bổ sung chuỗi s2 vào sau chuỗi s1. 3. char *strncat(char *s1, char *s2) Ghép n ký tự đầu tiên của s2 vào s1 4. char strcpy(char *s1, char *s2) Sao chép chuỗi s2 vào vùng s1. 5. char strncpy(char *s1,char *s2,int n) Sao chép n ký tự đầu của s2 sang s1. 6. int strcspn(char *s1, char *s2)

Cho đoạn đầu dài nhất của s1 mà không ký tự nào của s1 có mặt trong s2

7. int strspn(char *s1, char *s2) Cho đoạn đầu dài nhất của s1 mà mọi ký tự trong đoạn đó có mặt trong s2

8. char strstr(char *s1, char *s2) Tìm vị trí xuất hiện của chuỗi s2 trong s1. Nếu thấy, hàm trả lại địa chỉ của chuỗi con đó trong s1. Trái lại, hàm cho giá trị NULL.

9. char strchr(char *s, int kt)

Tìm vị trí đầu tiên của ký tự kt trong s , hàm cho địa chỉ của kt trong chuỗi.

10. char strrchr(char *s, int kt)

Tìm vị cuối cùng của ký tự kt trong s : , hàm cho địa chỉ của kt trong chuỗi.

11. char *strpbrk(char *s1,char *s2) Tìm sự xuất hiện đầu tiên của một ký tự của s2 trong s1. Nếu có xuất hiện, hàm cho địa chỉ của ký tự tìm thấy trong s1. Ngược lại , hàm cho giá trị NULL.

12. int strcmp(char *s1, char *s2) So sánh 2 chuỗi. Hàm cho giá trị âm nếu s1<s2, 0 nếu s1=s2, dương nếu s1>s2

13. int strcmpi(char *s1, char *s2) So sánh 2 chuỗi không phân biệt chữ hoa và chữ thường.

14. int stricmp(char *s1, char *s2) So sánh 2 chuỗi không phân biệt chữ hoa và chữ thường. ( như hàm strcmpi)

15. int strncmp(char *s1, char *s2, int n) So sánh n ký tự đầu tiên của 2 chuỗi . 16. int strnicmp(char *s1,char *s2, int n) So sánh n ký tự đầu tiên của 2 chuỗi ,

không phân biệt chữ hoa , chữ thường. 17. char *strlwr(char *s) Đổi chữ hoa thành chữ thường 18. char *strupr(char *s) Đỗi chữ thường thành chữ hoa 19. char *strdup(char *s) Gấp đôi một chuỗi. 20. char *strrev(char *s) Đảo ngược chuỗi s. 21. char *strnset(char *s,int c,int n) Kết quả s sẽ gồm n ký tự c. 22. char *strset(char *s,int kt) Thay mọi ký tự trong s bằng kt

1.4. Phân biệt một số hàm

Trong stdio.h Trong conio.h char *gets( char *s) Nhập một chuỗi ký tự từ stdin , trả về địa chỉ của chuỗi, s là biến con

getch() : Nhận ký tự từ bộ đệm bàn phím, không hiện lên màn hình.

Page 74: Giao Trinh Lap Trinh C Tom Tat

trỏ ( kiểu char) trỏ tới vùng nhớ chứa dãy ký tự nhận được. int puts(const char *s) Đưa chuỗi s lên màn hình và đưa cả ký tự '\n' lên màn hình , do đó sau khi in xong s thì con trỏ màn hình chuyển xuống dòng dưới . int getchar(void) : Nhận một ký tự từ stdin và hàm trả về ký tự đó. int putchar(int ch) :Đưa giá trị ký tự ch lên màn hình .

getche() : Nhận ký tự từ bộ đệm bàn phím, có hiện lên màn hình. putch() : Đưa một ký tự lên màn hình văn bản, có mầu sắc theo lệnh textcolor, trong khi đó, hàm putchar() luôn hiện ký tự có mầu trắng kbhit() : Cho biết trong bộ đệm bàn phím còn kýtự nào không

2. Xây dựng các hàm tiện ích trên xâu ký tự

2.1. Hàm copy

/* tao ham copy(s,i,n) trong tc */ /* van dung : dem so tu x trong cau van s */ #include <stdio.h> #define max 200 char *copy(char *s,int k,int n); void main() { char x[max],s[max]; int k,lx,d=0; clrscr(); printf("nhap chuoi s : "); gets(s); printf("nhap chuoi x : "); gets(x); lx=strlen(x); for(k=0;s[k]!='\0';k++) if(strcmp(x,copy(s,k,lx))==0) d++; printf("\n%d",d); getch(); } char *copy(char *s,int k,int n) { int i,j=0; char s1[max]; for(i=0;s[i]!='\0';i++) if((i>=k)&&(j<n)) {s1[j]=s[i];j++;} s1[j]='\0'; return(s1); }

2.2. Hàm clean

/* Xoa cac dau cach vo nghia trong cau */ /* Xay dung ham clean(s) */

Page 75: Giao Trinh Lap Trinh C Tom Tat

#include <stdio.h> #include <ctype.h> #define max 100 char *clean(char *s); void myprintf(char *s); void main() { char s[max]; clrscr(); printf("nhap chuoi s : "); gets(s); myprintf(clean(s)); getch(); } char *clean(char *s) { int t,p,i,j=0; char s1[max]; t=0; while (isspace(s[t])) t++; p=strlen(s)-1; while (isspace(s[p])) p--; for(i=t;i<=p;i++) if( !isspace(s[i]) || (isspace(s[i]) && !isspace(s[i+1]) )) {s1[j]=s[i];j++;} s1[j]='\0'; return(s1); } void myprintf(char *s) { int i; for(i=0;s[i]!='\0';i++) if (isspace(s[i])) putchar('_'); else putchar(s[i]); printf("\n"); }

2.3. Các hàm insert, delete, pos

/* xay dung cac ham - insert(x,s,k) */ /* - delete(s,k,n) */ /* - copy(s,k,n) */ /* - pos(x,s) */ /* van dung, sua loi cu phap ve dau phay va dau cham */ /* - sytax(s,symbol) */ #include <stdio.h> #include <ctype.h> #define max 100 #define sperator ','

Page 76: Giao Trinh Lap Trinh C Tom Tat

#define pointstop '.' char *insert(char *x,char *s,int k); char *delete(char *s,int k,int n); char *copy(char *s,int k,int n); int pos(char *x,char *s); char *sytax(char *s,char symbol); void myprintf(char *s); void main() { char s[max],x[max]; printf("nhap chuoi s : "); gets(s); myprintf(s); sytax(s,sperator); sytax(s,pointstop); myprintf(s); getch(); } char *insert(char *x,char *s,int k) { int i,j1=0,j2=0; char s1[max],s2[max]; for(i=0;i<k;i++) {s1[j1]=s[i];j1++;} for(i=k;s[i]!='\0';i++) {s2[j2]=s[i];j2++;} s1[j1]='\0'; s2[j2]='\0'; sprintf(s,"%s%s%s",s1,x,s2); return(s); } char *delete(char *s,int k,int n) { int i,j1=0,j2=0; char s1[max],s2[max]; for(i=0;i<k;i++) {s1[j1]=s[i];j1++;} for(i=k+n;s[i]!='\0';i++) {s2[j2]=s[i];j2++;} s1[j1]='\0'; s2[j2]='\0'; sprintf(s,"%s%s",s1,s2); return(s); } char *copy(char *s,int k,int n) { int i,j=0; char s1[max]; for(i=0;s[i]!='\0';i++) if((i>=k)&&(j<n)) {s1[j]=s[i];j++;} s1[j]='\0'; return(s1); } int pos(char *x,char *s) {

Page 77: Giao Trinh Lap Trinh C Tom Tat

int lx,i,d=-1; lx=strlen(x); for(i=0;s[i]!='\0';i++) if (strcmp(copy(s,i,lx),x)==0) {d=i; break; } return(d); } char *sytax(char *s,char symbol) { #define space ' ' #define error1 " ," #define error2 ", " #define error3 " ." #define error4 ". " int k,ls,i=0; ls=strlen(s); while(i<ls) { if(s[i]==symbol) insert(" ",s,++i); i++;} if (symbol==sperator) { while((k=pos(error1,s))>-1) delete(s,k,1); while((k=pos(error2,s))>-1) delete(s,k+1,1);} else { while((k=pos(error3,s))>-1) delete(s,k,1); while((k=pos(error4,s))>-1) delete(s,k+1,1);} return(s); } void myprintf(char *s) { int i; for(i=0;s[i]!='\0';i++) if (isspace(s[i])) putchar('*'); else putchar(s[i]); printf("\n"); }

2.3. Các hàm insert, delete, sytax nên có kiểu void

/* xay dung cac ham: /* insert(x,s,k), delete(s,k,n), copy(s,k,n), pos(x,s), /* sua loi cu phap ve dau phay va dau cham: sytax(s,symbol) */

#include <stdio.h> #include <ctype.h> #define max 100 #define sperator ',' #define pointstop '.' void insert(char *x,char *s,int k); void delete(char *s,int k,int n); char *copy(char *s,int k,int n);

Page 78: Giao Trinh Lap Trinh C Tom Tat

int pos(char *x,char *s); void sytax(char *s,char symbol); void myprintf(char *s); void main() { char s[max],x[max]; printf("nhap chuoi s : "); gets(s); myprintf(s); sytax(s,sperator); sytax(s,pointstop); myprintf(s); getch(); } void insert(char *x,char *s,int k) { int i,j1=0,j2=0; char s1[max],s2[max]; for(i=0;i<k;i++) {s1[j1]=s[i];j1++;} for(i=k;s[i]!='\0';i++) {s2[j2]=s[i];j2++;} s1[j1]='\0'; s2[j2]='\0'; sprintf(s,"%s%s%s",s1,x,s2); } void delete(char *s,int k,int n) { int i,j1=0,j2=0; char s1[max],s2[max]; for(i=0;i<k;i++) {s1[j1]=s[i];j1++;} for(i=k+n;s[i]!='\0';i++) {s2[j2]=s[i];j2++;} s1[j1]='\0'; s2[j2]='\0'; sprintf(s,"%s%s",s1,s2); } char *copy(char *s,int k,int n) { int i,j=0; char s1[max]; for(i=0;s[i]!='\0';i++) if((i>=k)&&(j<n)) {s1[j]=s[i];j++;} s1[j]='\0'; return(s1); } int pos(char *x,char *s) { int lx,i,d=-1; lx=strlen(x); for(i=0;s[i]!='\0';i++) if (strcmp(copy(s,i,lx),x)==0) {d=i; break; } return(d);

Page 79: Giao Trinh Lap Trinh C Tom Tat

} void sytax(char *s,char symbol) { #define space ' ' #define error1 " ," #define error2 ", " #define error3 " ." #define error4 ". " int k,ls,i=0; ls=strlen(s); while(i<ls) { if(s[i]==symbol) insert(" ",s,++i); i++;} if (symbol==sperator) { while((k=pos(error1,s))>-1) delete(s,k,1); while((k=pos(error2,s))>-1) delete(s,k+1,1);} else { while((k=pos(error3,s))>-1) delete(s,k,1); while((k=pos(error4,s))>-1) delete(s,k+1,1);} } void myprintf(char *s) { int i; for(i=0;s[i]!='\0';i++) if (isspace(s[i])) putchar('*'); else putchar(s[i]); printf("\n");}

Page 80: Giao Trinh Lap Trinh C Tom Tat

Chương 5. CẤU TRÚC VÀ HỢP

Bài 1. KIỂU DỮ LIỆU KIỂU CẤU TRÚC - STRUCT

1. Định nghĩa kiểu cấu trúc

1.1. Khai báo

struct tên_kiểu_cấu_trúc { /*thân cấu trúc bao gồm: */ kiểu_dữ_liệu_1 trường_1; kiểu_dữ_liệu_2 trường_2; kiểu_dữ_liệu_3 trường_3; ...

} [biến_cấu_trúc1, biến_cấu_trúc2, .... ] ; - Khai báo trên có tác dụng như sau: + Khai báo một tên kiểu dữ liệu. + Khai báo các thành phần của kiểu cấu trúc , mỗi thành phần gọi là trường.

Mỗi trường có thể có kiểu dữ liệu khác nhau. + Các kiểu dữ liệu của các trường: Có kiểu dữ liệu nói chung là tùy ý, nghĩa

là bao gồm các kiểu dữ liệu đơn giản có sẵn và kể cả kiểu cấu trúc nếu trước đó đã định nghĩa kiểu cấu trúc đó.

+ Các biến có kiểu dữ liệu cấu trúc (gọi tắt là các biến có kiểu cấu trúc) có thể khai báo trực tiếp như trên hoặc khai báo sau như dưới đây:

struct tên_kiểu_cấu_trúc tên_biến1, tên_biến2, ... - Có thể khai báo một mảng cấu trúc như sau struct tên_kiểu_cấu_trúc tên_biến_mảng[số_phtử_mảng]; - Một kiểu dữ liệu mới có “tên_kiểu_cấu_trúc” có thể khai báo nhờ typedef

: typedef struct { /* thân cấu trúc */ } tên_kiểu_dữ_liệu; Và khi đó các biến có kiểu dữ liệu (cấu trúc) này có thể khai báo như các

kiểu dữ liệu bình thường:

Page 81: Giao Trinh Lap Trinh C Tom Tat

tên_kiểu_dữ_liệu tênbiến, tênmảng[sốphầntử];

1.2. Ví dụ về khai báo cấu trúc

Ví dụ 1 Để khai báo các biến sv1, sv2, sv3, sv4, mảng sv[100] có kiểu sinhvien,

trong đó sinhvien là một kiểu dữ liệu cấu trúc gồm các trường: - họ tên - năm sinh - địa chỉ ta có 2 cách: Cách 1: struct sinhvien { char *hoten; int namsinh; char *diachi; } sv1, sv2; struct sinhvien sv3, sv4, sv[100]; Cách 2: typedef struct { char *hoten; int namsinh; char *diachi; } sinhvien; sinhvien sv1, sv2, sv3, sv4, sv[100]; Ví dụ 2 Yêu cầu “Khai báo trọn vẹn lý lịch của một sinh viên.” Ví dụ 2 minh họa

trường hợp khi nào nên dùng typedef, khi nào thì không nên dùng: typedef struct { unsigned int ngay; unsigned int thang;

Page 82: Giao Trinh Lap Trinh C Tom Tat

unsigned int nam; } ngaysinh; typedef struct { char *tenpho; unsigned sonha; unsigned long dienthoai; } diachi; struct sinhvien { char *hoten; ngaysinh ns; diachi dc; }; struct sinhvien sv1, sv2, sv[100];

2. Cách sử dụng cấu trúc

(1)- Đối với một biến cấu trúc chỉ có các phép so sánh, phép gán 2 cấu trúc cùng kiểu.

(2)- Các trường của cấu trúc có thể có các phép toán toán tùy thuộc vào kiểu dữ liệu của nó. Nếu trường đó có kiểu dữ liệu đơn giản thì thậm chí có thể lấy địa chỉ của nó.

(3)- Các trường của cấu trúc trường sử dụng thông qua toán tử xác định (dấu chấm)

tên_kiểu_cấu_trúc.tên_trường (4)- Khi truy nhập vào trường thì phải truy nhập đến trường nhỏ nhất , tức

là trường có kiểu dữ liệu đơn giản có sẵn, chẳng hạn không thể dừng lại khi mới truy nhập đến là một trường lại là một biến cấu trúc.

(5)- Khi có nhiều trường của một biên cấu trúc cùng được truy nhập thì để tránh viết dài dòng tên biến cấu trúc, ta có thể định nghĩa qua một macro (ít dùng!):

#define tên_macro tên_biến_cấu_trúc;

Page 83: Giao Trinh Lap Trinh C Tom Tat

sau đó một trường của biến cấu trúc có thể sử dụng thông qua quy tắc: tên_macro.tên_trường (6) - Các kiểu cấu trúc cũng có thể khởi đầu một cách bình thường, ví dụ: struct address { char street[30]; unsinged int number; unsigned long phone; } A[] = { { “thuy khue”, 14, 7540199}, { “hoang hoa tham”, 174, 8949199}, { “cat linh”, 231, 8520009} }; Bài tập minh họa Viết chương trình - Nhập lý lịch của n sinh viên. (Lý lịch sinh viên được mô tả ở ví dụ 2) - In lên màn hình danh sách các sinh viên vừa nhập được sắp xếp theo chiều

tăng dần. #include <stdio.h> #include <conio.h> #include <string.h> main() { typedef struct { unsigned int d,m,y; } date; typedef struct { char *street; unsigned int number; unsigned long phone; } address; struct sinhvien { char *ht; date ns; address dc; };

Page 84: Giao Trinh Lap Trinh C Tom Tat

struct sinhvien sv[100]; int n; int i,j; struct sinhvien tg; printf("\n nhap so sinh vien: "); scanf("%d",&n); fflush(stdin); for(i=0;i<n;i++) { char tg[30]; unsigned int len; unsigned int d1,m1,y1; fflush(stdin); printf("\n sv[%d]:\n",i+1); printf("ho ten : "); gets(tg); len = strlen(tg); sv[i].ht = (char*) malloc (len+1); strcpy(sv[i].ht, tg); printf("ngay sinh: "); scanf("%d%d%d",&d1, &m1, &y1); sv[i].ns.d=d1; sv[i].ns.m=m1; sv[i].ns.y=y1; fflush(stdin); printf("dia chi:\n "); printf("ten pho: "); gets(tg); len=strlen(tg); sv[i].dc.street = (char*) malloc (len+1); strcpy(sv[i].dc.street, tg); printf("so nha: "); scanf("%d", &sv[i].dc.number); printf("so dien thoai: "); scanf("%d", &sv[i].dc.phone); } for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if (strcmp(sv[j].ht,sv[i].ht)<0) { tg=sv[i]; sv[i]=sv[j]; sv[j]=tg; } printf("\n danh sach sinh vien duoc sap xep theo ho ten:"); for(i=0;i<n;i++) { printf("\n %s : %d/%d/%d", sv[i].ht, sv[i].ns.d, sv[i].ns.m, sv[i].ns.y); printf("\n pho: %s, so nha: %d, dien thoai: %d", sv[i].dc.street, sv[i].dc.number, sv[i].dc.phone); }

Page 85: Giao Trinh Lap Trinh C Tom Tat

getch(); }

Bài 2. CẤU TRÚC TỰ TRỎ VÀ DANH SÁCH LIÊN KẾT

1. Con trỏ cấu trúc và địa chỉ cấu trúc

Cú pháp về con trỏ và địa chỉ

- Biến con trỏ cấu trúc được khai báo như sau: struct tên_kiểu_cấu_trúc *tên_biến_con_trỏ; - Cũng như các biến con trỏ khác , con trỏ cấu trúc có thể sử dụng để chứa

địa chỉ của biến kiểu cấu trúc hoặc xin cấp phát bộ nhớ cho các biến động có kiểu cấu trúc. Trường hợp cấp phát bộ nhớ động để tạo ra danh sách liên kết là một vấn đề quan trọng ta sẽ nghiên cứu ở mục 2.

- Về nguyên tắc cú pháp chung, con trỏ cấu trúc cũng như các con trỏ kiểu dữ liệu khác.

- Ví dụ struct hoadon { int sohd; char tenhang[30]; };

struct hoadon hd1, hd2, *p, *q, *r, ds[100]; trong khai báo trên ta có:

. hd1, hd2 là tên các biến cấu trúc kiểu hoadon

. *p, *q , *r là tên các biến con trỏ có kiểu hoadon

. ds[100] là một biến mảng mà các phần tử có kiểu hoadon. - Bây giờ ta có thể viết các lệnh:

p=&hd1; /* p chứa địa chỉ của hd1 */ q=&ds[5]; /* p chứa địa chỉ của ds[5] */ r=ds; /* r chứa địa chỉ của ds[0] */

và khi đó *p == hd1

Page 86: Giao Trinh Lap Trinh C Tom Tat

*q == ds[5] r[i] == ds[i] == *(r+i)

- Để truy nhập tới một trường của biến cấu trúc ta có 2 cách: tên_con_trỏ -> tên_trường (*tên_con_trỏ).tên_trường Như vậy dùng toán tử ‘->’ khi vế trái là con trỏ biểu thị địa chỉ biến cấu

trúc, dùng toán tử ‘.’ khi vế trái là con trỏ biểu thị giá trị biến cấu trúc. Ví dụ:

p->sohd=24; hoặc (*p).sohd=24; q->sohd=5; hoặc (*q).sohd=5; (tức là ds[5].sohd = 5;) r[i].sohd=i; hoặc (r+i)->sohd = i; hoặc *(r+i).sohd = i; và địa chỉ &ds[i].sohd == &p[i].sohd;

- Trường hợp con trỏ mảng cấu trúc (như ví dụ trên) có thể có các phép toán tăng giảm địa chỉ con trỏ để chỉ định vị trí của các phần tử trong mảng.

Ví dụ Khi r =ds; Thì (r+i) là địa chỉ phần tử thứ i &r[i].tên_trường là địa chỉ của trường đó của phần tử thứ i. *(r+i) là giá trị của phần tử thứ i (*(r+i)).tên_trường == r[i].tên_trường == (r+i)->tên_trường =

ds[i].tên_trường : là giá trị của trường đó của phần tử thứ i. Bài tập minh họa Viết chương trình sử dụng con trỏ cấu trúc trỏ tới mảng các cấu trúc: - Nhập vào một mảng n mặt hàng: gồm số hóa đơn, tên hàng - Sắp xếp danh sách các mặt hàng theo chiều giảm dần của số hóa đơn. #include <stdio.h> #include <conio.h> #include <string.h> main() { struct mathang

Page 87: Giao Trinh Lap Trinh C Tom Tat

{ unsigned int sohd; char tenh[30]; }; struct mathang *p, tg, ds[100]; int n; int i,j; p=ds; printf("\n nhap so mat hang: "); scanf("%d",&n); for(i=0;i<n;i++) { printf("\n mh[%d]:\n",i+1); printf("ten hang: "); fflush(stdin); gets((p+i)->tenh); printf("so hoa don: "); scanf("%d", &p[i].sohd); } for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if (p[j].sohd>p[i].sohd) {tg=p[i]; p[i]=p[j]; p[j]=tg;} printf("\n danh sach hang duoc sap xep giam theo so hoa don :\n"); for(i=0;i<n;i++) printf("%s %d , ", (*(p+i)).tenh, p[i].sohd); getch(); }

2. Truyền cấu trúc cho hàm

2.1. Nguyên tắc chung

- Đối của hàm có thể là: + Biến cấu trúc. Khi đó tham số thực sự là một giá trị cấu trúc (truyền kiểu

tham trị) + Con trỏ cấu trúc. Khi đó tham số thực sự của hàm là địa chỉ của biến cấu

trúc: . Trực tiếp dùng biến với địa chỉ của nó. (nên dùng cách này nếu không có

gì đặc biệt về việc phải xử lý gián tiếp một biến cấu trúc.) . Hoặc dùng gián tiếp qua một con trỏ thực sự mà trước đó đã gán bằng địa

chỉ của biến cấu trúc.

Page 88: Giao Trinh Lap Trinh C Tom Tat

+ Mảng cấu trúc hình thức hoặc con trỏ cấu trúc. Khi đó tham số thực tương ứng là tên mảng cấu trúc.

- Hàm có thể trả về giá trị: + Giá trị cấu trúc. + Con trỏ cấu trúc.

2.2. Ví dụ

Xét kiểu cấu trúc nhanvien gồm 3 thành phần: + ht (họ tên) kiểu mảng char + ns (ngày tháng năm sinh) kiểu struct date + bl (bậc lượng) kiểu float Xây dựng 6 hàm sau đây: + Hàm 1: nhanvien *ptr_tim(char *ht, nhanvien h[], int n); có tác dụng tìm trong danh sách n nhân viên trong mảng h người có tên cho

trong ht. Hàm trả về con trỏ trỏ vào nhân viên tìm thấy hoặc trỏ vào null nếu không có nhân viên này.

+ Hàm 2: nhanvien struct_tim(char *ht, nhanvien h[], int n); cũng có tác dụng như hàm *ptr_tìm nhưng nó trả về giá trị cấu trúc chứa

thông tin của người tìm được. Các thông tin này bằng 0 nếu không tìm thấy. + Hàm 3: void hoanvi(nhanvien *p, nhanvien *q); để hoán vị 2 cấu trúc + Hàm 4: void sapxep_ns(nhanvien *p, int n); để sắp xếp một mảng các cấu trúc tăng dần theo trường năm sinh. Mảng

được xác định bởi con trỏ p. + Hàm 5: void nhap_nv(nhanvien *p); để nhập từ bàn phím thông tin cho một nhân viên , nhân viên này được xác

định bởi con trỏ p.

Page 89: Giao Trinh Lap Trinh C Tom Tat

+ Hàm 6: void in_nv(nhanvien nv); để in ra thôngtin của một nhân viên. Chương trình vận dụng các hàm trên để: - Nhập danh sách n nhân viên. - Sắp xếp danh sách các nhân viên theo năm sinh tăng dần. - In danh sách nhân viên sau khi sắp xếp. - Hai cách tìm kiếm một nhân viên ( dùng hàm ptr_tim và struct_tim) #include <stdio.h> #include <conio.h> #include <string.h> struct date {int d,m,y;}; typedef struct { char ht[30]; struct date ns; float bl; } nhanvien; void nhap_nv(nhanvien *p) { nhanvien nvtg; printf("\n ho ten: "); fflush(stdin); gets(nvtg.ht); printf(" ngay thang nam sinh: "); scanf("%d%d%d%*c",&nvtg.ns.d,&nvtg.ns.m,&nvtg.ns.y); printf(" bac luong: "); scanf("%f%*c",&nvtg.bl); *p=nvtg; } void in_nv(nhanvien nv) { printf("\n %s: %d/%d/%d , bac luong: %0.1f", nv.ht, nv.ns.d, nv.ns.m, nv.ns.y, nv.bl); } void hoanvi(nhanvien *p,nhanvien *q) { nhanvien nvtg; nvtg=*p; *p=*q; *q=nvtg; } void sapxep_ns(nhanvien *p, int n) { int i,j; for(i=0;i<n-1;i++) for(j=i+1;j<n;j++)

Page 90: Giao Trinh Lap Trinh C Tom Tat

if(p[j].ns.y < p[i].ns.y) hoanvi(&p[i],(p+j)); } nhanvien* ptr_tim(char *s, nhanvien h[], int n) {int i; for(i=0;i<n;i++) if (strcmp(s,h[i].ht)==0) return (&h[i]); return (NULL); } nhanvien struct_tim(char *s, nhanvien h[], int n) { int i; nhanvien nvtg; nvtg.ns.d=nvtg.ns.m=nvtg.ns.y=0; nvtg.bl=0.0; nvtg.ht[0]=0; for(i=0;i<n;i++) if(strcmp(s,h[i].ht)==0) return (h[i]); return (nvtg); } main() { nhanvien *p, ds[100]; int n; char s[30]; int i,j; clrscr(); printf("\n nhap so nhan vien: "); scanf("%d",&n); for(i=0;i<n;i++) { printf("\n nhap nhan vien thu %d :\n",i+1); nhap_nv(&ds[i]); } sapxep_ns(ds,n); printf("\n danh sach sau khi sap xep tang theo nam sinh la:\n"); for(i=0;i<n;i++) in_nv(ds[i]); while(1) { printf("\n nhap ho ten sinh vien can tim (cach 1) : "); fflush(stdin); gets(s); if(s[0]==0) break;

Page 91: Giao Trinh Lap Trinh C Tom Tat

if((p=ptr_tim(s,ds,n))==NULL) printf("\n khong co sinh vien nay."); else { printf("\n tim thay cach 1 : "); in_nv(*p) ; } } while(1) { printf("\n nhap ho ten sinh vien can tim (cach 2) : "); fflush(stdin); gets(s); if(s[0]==0) break; if(struct_tim(s,ds,n).ht[0]==0) printf("\n khong co sinh vien nay."); else { printf("\n tim thay cach 2: "); in_nv( struct_tim(s,ds,n) ) ; } } printf("\n bam enter de ket thuc chuong trinh"); getch(); }

3. Cấu trúc tự trỏ và danh sách liên kết

3.1. Khai báo cấu trúc tự trỏ

- Một kiểu cấu trúc mà trong đó có một trường là một con trỏ có kiểu dữ liệu là chính kiểu cấu trúc đó thì gọi là cấu trúc tự trỏ.

- Một cách hình thức nếu x là một cấu trúc có kiểu T thì trong x có một trường là con trỏ *f có kiểu là T.

- Bản ghi tự trỏ dùng để xây dựng danh sách liên kết-một cấu trúc dữ liệu có nhiều ứng dụng thực tế.

Cách 1 typedef struct T { kiểu_dữ_liệu tên_trường; ... struct T *f; } x;

Cách 2

struct T { kiểu_dữ_liệu tên_trường;

Page 92: Giao Trinh Lap Trinh C Tom Tat

... struct T *f; } x;

Cách 3

typedef struct T x { kiểu_dữ_liệu tên_trường; x *f; };

3.2. Các phép toán trên danh sách liên kết

Xét một danh sách liên kết tạo bởi các cấu trúc tự trỏ mà mỗi một nút lưu trữ thông tin của một cán bộ , giả sử gồm 2 mục thông tin là họ tên và năm sinh. Và, đương nhiên mỗi một nút có thêm một trường là con trỏ *next có khả năng trỏ vào các nút kề trong danh sách, do đó *next có kiểu dữ liệu là chính kiểu của nút. Các mục dưới đây sẽ thể hiện cách cài đặt danh sách liên kết và các phép toán cơ bản trên danh sách liên kết

Danh sách liên kết dưới đây dự định tổ chức kiểu danh sách liên kết ngược. Nghĩa là nút sau sẽ trỏ vào nút trước. Danh sách sẽ được quản lý bởi con trỏ last trỏ vào nút cuối cùng trong danh sách.

nút 1 nút 2 nút 3 NULL last

Định nghĩa cấu trúc tự trỏ

typedef struct node { char ht[30]; unsigned int ns; struct node *next; } canbo; canbo *last;

Khởi tạo danh sách liên kết ngược (nút sau trỏ vào nút trước)

Page 93: Giao Trinh Lap Trinh C Tom Tat

last =NULL;

Bổ sung một mục dữ liệu vào cuối danh sách

- Tạo một nút mới canbo *p; int t; p=(canbo*) malloc (sizeof(canbo)); p->next=NULL; printf("\n nhap can bo thu %d\n",++i); printf("ho ten: "); fflush(stdin); gets(p->ht); if(p->ht[0]==0) break; printf("nam sinh: "); scanf("%d",&t); p->ns=t; - Thêm p vào danh sách if(last==NULL) last=p; else { p->next=last; last=p; }

In danh sách lên màn hình

p=last; while(p!=NULL) {printf("\n %s %d", (*p).ht,(*p).ns); p=p->next; }

Tìm kiếm một mục dữ liệu trên danh sách

printf("\n nhap ho ten mot can bo can tim: "); fflush(stdin); gets(s); found=0; p=last; while(!found && p!=NULL) {if(strcmp(s,p->ht)==0) {found=1;break;} else p=p->next; } if(found) printf("\n co can bo do trong danh sach"); else printf("\n khong co can bo trong danh sach");

Xóa một mục dữ liệu ra khỏi danh sách

Page 94: Giao Trinh Lap Trinh C Tom Tat

printf("\n nhap ho ten mot can bo can xoa: "); fflush(stdin); gets(s); found=0; p=last; q=NULL; while(!found && p!=NULL) { if(strcmp(s,p->ht)==0) found=1; else {q=p; p=p->next;} } if(found) { if(q==NULL) {last=last->next;} else {q->next=p->next; free(p);} }

Chèn một mục dữ liệu vào trước một mục dữ liệu nào đó trong danh sách

printf("\n nhap thong tin mot can bo can chen:\n "); r=(canbo*) malloc (sizeof(canbo)); r->next=NULL; printf("ho ten: "); fflush(stdin); gets(r->ht); printf("nam sinh: "); scanf("%d",&t); r->ns=t; printf("\n Can chen vao truoc can bo co ten la gi "); printf("(tinh tu can bo cuoi nguoc ve dau danh sach): "); fflush(stdin); gets(s); found=0; p=last; q=NULL; while(!found && p!=NULL) { if(strcmp(s,p->ht)==0) found=1; else {q=p; p=p->next;} } if(found) { if(q==NULL) {r->next=last; last=r;} else {q->next=r; r->next=p;} }

4. Các phương pháp cài đặt các phép toán

Việc tổ chức chương trình cài đặt các phép toán trên danh sách liên kết có thể có 3 cách thực hiện:

- Cách 1: Chương trình không tổ chức các hàm (pheptoan1.c). Cách tổ chức này ưu điểm là dễ thực hiện, nhưng nhược điểm rất lớn là chương trình sẽ dài, cồng kềnh, không tiện lời khi một phép toán (chẳng hạn như phép tìm kiếm) phải vận dụng nhiều lần.

Page 95: Giao Trinh Lap Trinh C Tom Tat

- Cách 2: Chương trình tổ chức thành các hàm, trong đó mỗi hàm đảm nhận một phép toán (pheptoan2.c). Cách tổ chức này khắc phục nhược điểm của cách thức nhất, nhưng có nhược điểm là sẽ khó hơn , nhất là việc truyền vào hàm các đối là các con trỏ cấu trúc.

- Cách 3: (pheptoan3.c- mục 4) Giống như cách thứ 3, nhưng ở đây sẽ sử dụng tới kỹ thuật cài đặt con trỏ trỏ tới một con trỏ khác, tức là con trỏ của con trỏ.

Pheptoan1.C

#include <stdio.h> #include <conio.h> #include <string.h> void main(){ typedef struct node { char ht[30]; unsigned int ns; struct node *next; } canbo; canbo *last; canbo *p; int i,t; char s[30]; int found; canbo *q; canbo *r; /*------------------------------------*/ /* tao danh sach can bo */ /*------------------------------------*/ last=NULL; i=0; while(1) { p=(canbo*) malloc (sizeof(canbo)); p->next=NULL; printf("\n nhap can bo thu %d\n",++i); printf("ho ten: "); fflush(stdin); gets(p->ht); if(p->ht[0]==0) break; printf("nam sinh: "); scanf("%d",&t); p->ns=t; if(last==NULL) last=p; else { p->next=last;

Page 96: Giao Trinh Lap Trinh C Tom Tat

last=p; } } /*------------------------------------*/ /* in danh sach */ /*------------------------------------*/ p=last; while(p!=NULL) {printf("\n %s %d", (*p).ht,(*p).ns); p=p->next; } /*-----------------------------------------------*/ /* tim kiem tren danh sach theo ten */ /*-----------------------------------------------*/ while(1) { printf("\n nhap ho ten mot can bo can tim: "); fflush(stdin); gets(s); if(s[0]==0) break; found=0; p=last; while(!found && p!=NULL) {if(strcmp(s,p->ht)==0) {found=1;break;} else p=p->next; } if(found) printf("\n co can bo do trong danh sach"); else printf("\n khong co can bo trong danh sach"); } /*-----------------------------------------------*/ /* xoa mot can bo khoi danh sach */ /*-----------------------------------------------*/ printf("\n nhap ho ten mot can bo can xoa: "); fflush(stdin); gets(s); found=0; p=last; q=NULL; while(!found && p!=NULL) { if(strcmp(s,p->ht)==0) found=1; else {q=p; p=p->next;} } if(found) { if(q==NULL) {last=last->next;} else {q->next=p->next; free(p);} printf("\n can bo do da duoc xoa");

Page 97: Giao Trinh Lap Trinh C Tom Tat

p=last; while(p!=NULL) {printf("\n %s %d", (*p).ht,(*p).ns); p=p->next; } } else printf("\n khong co can bo trong danh sach"); /*----------------------------------------------------*/ /* chen mot can bo vao giua danh sach */ /*----------------------------------------------------*/ printf("\n nhap thong tin mot can bo can chen:\n "); r=(canbo*) malloc (sizeof(canbo)); r->next=NULL; printf("ho ten: "); fflush(stdin); gets(r->ht); if(r->ht[0]==0) goto end; printf("nam sinh: "); scanf("%d",&t); r->ns=t; printf("\n Can chen vao truoc can bo co ten la gi "); printf("(tinh tu can bo cuoi nguoc ve dau danh sach): "); fflush(stdin); gets(s); if(s[0]==0) goto end; found=0; p=last; q=NULL; while(!found && p!=NULL) { if(strcmp(s,p->ht)==0) found=1; else {q=p; p=p->next;} } if(found) { if(q==NULL) {r->next=last; last=r;} else {q->next=r; r->next=p;} printf("\n can bo do da chen"); p=last; while(p!=NULL) {printf("\n %s %d", (*p).ht,(*p).ns); p=p->next; } } else printf("\n khong co can bo trong danh sach"); end: printf("\n bam enter de ket thuc chuong trinh."); getch();

Page 98: Giao Trinh Lap Trinh C Tom Tat

}

Pheptoan2.C

#include <stdio.h> #include <conio.h> #include <string.h> typedef struct node { char ht[30]; unsigned int ns; struct node *next; } canbo; canbo *last; /*---------------------------------------------*/ /* ham nhap du lieu cho mot nut */ /*--------------------------------------------*/ void nhapmotcb(canbo *p) { unsigned int t; printf("ho ten: "); fflush(stdin); gets(p->ht); if(p->ht[0]!=0) {printf("nam sinh: "); scanf("%d",&t); p->ns=t;} } /*------------------------------------------------------------*/ /* ham ket nap mot nut moi vao cuoi danh sach*/ /*-------------------------------------------------------------*/ void bosung_ds(canbo *p) { if(last==NULL) last=p; else {p->next=last;last=p;} } /*--------------------------------------------------------*/ /* ham in mot can bo va danh sach can bo */ /*--------------------------------------------------------*/ void in_cb(canbo *p) {printf("\n %s %d", (*p).ht,(*p).ns); } void in_ds(canbo *last) { canbo *p; p=last;

Page 99: Giao Trinh Lap Trinh C Tom Tat

while(p!=NULL) {in_cb(p); p=p->next;} } /*------------------------------------------------------------*/ /* ham tim kiem mot can bo */ /* ket qua tra lai la con tro tro vao nut tim thay */ /*------------------------------------------------------------*/ canbo* timkiem(canbo *last,char s[]) /* tra lai con tro q tro vao nut tim thay. q=NULL neu khong co*/ { canbo *p; p=last; while(p!=NULL) {if(strcmp(s,p->ht)==0) {return(p);} else p=p->next; } return (NULL); } /*-----------------------------------------------------------------------*/ /* ham tim kiem mot can bo */ /* ket qua tra lai la con tro tro vao cha cua nut tim thay */ /*-----------------------------------------------------------------------*/ canbo* timkiem2(canbo *last, char s[]) /* tra lai con tro p tro vao cha cua nut tim thay. p=NULL neu nut tim thay la last */ { canbo *p,*q; q=last;p=NULL; while(q!=NULL) { if(strcmp(s,q->ht)==0) break; p=q; q=q->next; } return (p); } /*------------------------------------------------------------------*/ /* ham xoa mot nut co biet truoc mot muc du lieu */ /*------------------------------------------------------------------*/ void xoa_cb(char s[]) { canbo *p,*q; q=timkiem(last,s); p=timkiem2(last,s); if(q==NULL) printf("\n khong thay can bo nay.");

Page 100: Giao Trinh Lap Trinh C Tom Tat

else { if(p==NULL) {last=last->next;} else {p->next=q->next; free(q);} printf("\n can bo do da duoc xoa"); in_ds(last); } } /*-----------------------------------------------------------------*/ /* ham chen mot nut moi truoc mot nut nao do */ /*-----------------------------------------------------------------*/ void chen_cb(canbo *r,char s[]) { canbo *p,*q; q=timkiem(last,s); p=timkiem2(last,s); if(q==NULL) printf("\nkhong thay can bo %s",s); else { if(p==NULL) {r->next=last; last=r;} else {p->next=r; r->next=q;} printf("\n da chen xong can bo do"); in_ds(last); } } /*------------------------------------*/ /* chuong trinh chinh */ /*------------------------------------*/ void main(){ int i=0; canbo *p, *q, *r; char s[30]; last=NULL; while(1) { printf("\n nhap can bo thu %d: \n",++i); p=(canbo*) malloc (sizeof(canbo)); p->next=NULL; nhapmotcb(p); if (p->ht[0]==0) break; bosung_ds(p); } in_ds(last);

Page 101: Giao Trinh Lap Trinh C Tom Tat

while(1) { printf("\n nhap ho ten mot can bo can tim: "); fflush(stdin); gets(s); if(s[0]==0) break; p=timkiem(last,s); if(p==NULL) printf("\n khong co can bo do."); else in_cb(p); } printf("\n nhap ho ten mot can bo can xoa: "); fflush(stdin); gets(s); if(s[0]!=0) xoa_cb(s); printf("\n nhap thong tin mot can bo can chen:\n "); r=(canbo*) malloc (sizeof(canbo)); r->next=NULL; nhapmotcb(r); if(r==NULL) goto end; printf("\n Tinh tu can bo cuoi nguoc ve dau danh sach"); printf("\n Can chen vao truoc can bo co ten la gi: "); fflush(stdin); gets(s); if(s[0]==0) goto end; chen_cb(r,s); end: printf("\n bam enter de ket thuc chuong trinh."); getch(); }

5. Con trỏ của “con trỏ cấu trúc tự trỏ”

Nhận xét

Trong chương trình 3.3.2 (pheptoan2.c) ta thấy có một vài điểm hơi không bình thường, đúng ra là hơi bất tiện.

- Thứ nhất là con trỏ last được cố tình để là biến toàn cục để các hàm thao tác trên biến toàn cục này. Chẳng hạn việc nhập vào thêm, xóa đi hay hủy bỏ một nút trong danh sách đương nhiên làm cho danh sách thay đổi , nghĩa là con trỏ last (dùng để xác định toàn danh sách) sẽ thay đổi theo. Nhưng điều này ta không còn lo lắng vì last đã là biến toàn cục rồi.

- Thứ hai là tất cả các hàm có ý định làm thay đổi “giá trị” của một con trỏ cấu trúc tự trỏ nào đó đều không truyền thành đối của hàm. Có nghĩa là có vấn đề xảy ra khi định truyền một con trỏ cấu trúc tự trỏ trong hàm như một tham số biến (bởi vì lúc này chúng đều bị coi là tham số giá trị).

Ví dụ 1:

Page 102: Giao Trinh Lap Trinh C Tom Tat

Nếu hàm void bosung_ds(canbo *p) sửa thành void bosung_ds(canbo *last, canbo *p) thì việc kết nạp thêm một nút p vào last chỉ có tác dụng trong thân hàm. Khi

hàm kết thúc, last coi như không có thêm p. Và, như vậy đoạn trình trong main để tạo danh sách là không có tác dụng.

Ví dụ 2: Cũng từ đó sinh ra chuyện phải viết riêng rẽ 2 hàm tìm kiếm và bản thân 2

hàm tìm kiếm cũng lảng tránh việc trả lại kết quả tìm kiếm nhờ một đối là con trỏ nào đó mà lại nhờ chính bản thân tên hàm.

Đáng lý ra 2 hàm tìm kiếm phải thay bằng một hàm: void timkiem(canbo *last,char s[], canbo *p, canbo *q); nghĩa là tìm cán bộ tên trong s của danh sách last. Nếu tìm thấy thì q trỏ

vào nút có tên cán bộ này còn p thì trỏ vào cha của nút đó... Nhưng viết như trên thì *p, *q đều được coi chỉ là các tham số giá trị, trong

thân hàm dẫu cho p và q làm được điều đó thì ra khỏi hàm, p và q vẫn không xác định.

Ví dụ 3: Xét hàm nhập cán bộ: void nhapmotcb(canbo *p) thực chất chỉ là việc nhập dữ liệu đơn thuần cho biến động trỏ bởi p và biến

động này phải được cấp phát ngoài main(), nếu cấp phát trong hàm thì kết quả chương trình không còn đúng nữa!

Con trỏ của con trỏ

- Khái niệm về con trỏ của con trỏ sẽ khắc phục các bất tiện trên. - Để một con trỏ cấu trúc tự trỏ truyền vào hàm với tư cách như một tham

số biến, nghĩa là giá trị của nó kể cả việc cấp phát bộ nhớ được thân hàm làm thay đổi sẽ được giữ lại khi ra khỏi hàm thì con trỏ đó phải được khai báo là con trỏ của con trỏ. Trong trường hợp này là con trỏ của “con trỏ cấu trúc tự trỏ”.

- Một chương trình sử dụng các phép toán cài đạt bằng hàm với đối là con trỏ của “con trỏ cấu trúc tự trỏ” sẽ cho cảm giác tự nhiên và dễ chịu hơn rất nhiều vì tính tiện lợi của nó.

Page 103: Giao Trinh Lap Trinh C Tom Tat

kiểu_hàm tên_hàm ( kiểu_cấu_trúc_tự_trỏ ** tên_biến_con_trỏ)

{ /* thân hàm sử dụng :*/ *tên_biến_con_trỏ }

Khi gọi hàm tên_hàm(&tên_biến_con_trỏ)

Ví dụ 1 : hàm nhập cán bộ và hàm bổ sung cán bộ có thể chuyển thành int nhapmotcb(canbo **p) { unsigned int t; (*p)=(canbo*) malloc (sizeof(canbo)); (*p)->next=NULL; printf("ho ten: "); fflush(stdin); gets((*p)->ht); if((*p)->ht[0]==0) return(0); else {printf("nam sinh: "); scanf("%d",&t); (*p)->ns=t;} return(1); } void bosung_ds(canbo **start,canbo *p) { canbo *q; if(*start==NULL) *start=p; else { q=*start; while(q->next!=NULL) q=q->next; q->next=p;} } Vậy khi đó đoạn trình trong main sẽ tạo danh sách liên kết (thuận) như sau: start=NULL; while(1) { printf("\n nhap can bo thu %d: \n",++i); if(nhapmotcb(&p)==0) break; bosung_ds(&start,p); } Pheptoan3.C Xây dựng một danh sách liên kết thuận cùng với các phép toán cơ bản của

một danh sách liên kết.

nút 3 nút 2 nút 1 NULL Start

#include <stdio.h> #include <conio.h>

Page 104: Giao Trinh Lap Trinh C Tom Tat

#include <string.h> typedef struct node { char ht[30]; unsigned int ns; struct node *next; } canbo; canbo *start; /*---------------------------------------*/ /* nhap du lieu cho mot nut */ /*--------------------------------------*/ int nhapmotcb(canbo **p) { unsigned int t; (*p)=(canbo*) malloc (sizeof(canbo)); (*p)->next=NULL; printf("ho ten: "); fflush(stdin); gets((*p)->ht); if((*p)->ht[0]==0) return(0); else {printf("nam sinh: "); scanf("%d",&t); (*p)->ns=t;} return(1); } /*-----------------------------------------------------*/ /*ket nap mot nut moi vao cuoi danh sach*/ /*----------------------------------------------------*/ void bosung(canbo **start,canbo *p) { canbo *q; if(*start==NULL) *start=p; else { q=*start; while(q->next!=NULL) q=q->next; q->next=p;} } /*---------------------------------------------------*/ /* in mot can bo va danh sach can bo */ /*---------------------------------------------------*/ void in_cb(canbo *p) {printf("\n %s %d", (*p).ht,(*p).ns); } void in_ds(canbo *start) { canbo *p; p=start; while(p!=NULL) {in_cb(p); p=p->next;} } /*------------------------------------------------------------------------*/ /* tim kiem mot can bo co ten chua trong mang ky tu s */ /* neu khong tim thay thi ham tra gia tri 0 */ /* neu tim thay: */ /* - ham tra gia tri 1 */ /* - *q tro vao nut tim thay */

Page 105: Giao Trinh Lap Trinh C Tom Tat

/* - *p tro vao cha cua *q */ /* - neu *q tro vao nut start thi ham tra gia tri 2 */ /*-------------------------------------------------------------------------*/ int timkiem(canbo *start,char s[],canbo **p, canbo **q) { *q=start; while(*q!=NULL) { if(strcmp(s,(*q)->ht)==0) {if(*q==start) return 2; else return 1;} else {*p=*q; *q=(*q)->next; } } return 0; } /*----------------------------------------------------------------------*/ /* xoa mot can bo co ten chua trong mang ky tu s */ /*---------------------------------------------------------------------*/ void xoacanbo(canbo **start,char s[]) {canbo *p,*q; int k=timkiem(*start,s,&p,&q); if(k==0) {printf("khong co can bo %s ",s); return;} if(k==2) (*start)=(*start)->next; if(k==1) p->next=q->next; free(q); } /*--------------------------------------------------------------------------*/ /* Chen mot can bo co ten chua trong nut r */ /* vao sau mot can bo trong danh sach ma can bo nay */ /* co ten chua trong mang ky tu s. */ /*-------------------------------------------------------------------------*/ void chencanbo(canbo **start,canbo *r,char s[]) { canbo *p,*q; if(timkiem(*start,s,&p,&q)==0) {printf("khong co can bo %s ",s); return;} r->next=q->next; q->next=r; } /*=============================*/ /* chuong trinh chinh */ /*=============================*/ void main(){ int i=0; canbo *p, *q, *r; char s[30]; start=NULL; while(1) { printf("\n nhap can bo thu %d: \n",++i); if(nhapmotcb(&p)==0) break; bosung(&start,p); }

Page 106: Giao Trinh Lap Trinh C Tom Tat

in_ds(start); while(1) { printf("\n nhap ho ten mot can bo can tim: "); fflush(stdin); gets(s); if(s[0]==0) break; if(timkiem(start,s,&p,&q)==0) printf("\n khong co can bo do."); else { printf("\n tim thay can bo do."); in_cb(q); } } printf("\n nhap ho ten mot can bo can xoa: "); fflush(stdin); gets(s); xoacanbo(&start,s); in_ds(start); printf("\n nhap thong tin mot can bo can chen:\n "); if(nhapmotcb(&r)==0) goto end; printf("\n Can chen vao sau can bo co ten la gi: "); fflush(stdin); gets(s); if(s[0]==0) goto end; chencanbo(&start,r,s); in_ds(start); end: printf("\n bam enter de ket thuc chuong trinh."); getch(); }

Bài 3. KIỂU DỮ LIỆU HỢP - UNION

1. Cấu trúc kiểu bit

- Một cấu trúc đặc biệt gọi là cấu trúc bit vì các trường của nó được quy định cụ thể là chiếm bao nhiêu bit trong bộ nhớ, dù trường đó có kiểu là int, char, unsigned.

- Đơn vị cấp phát nhỏ nhất cho cấu trúc là 2B (tức là 16bit) và cứ tiếp tục từng 2B một như thế. Chẳng hạn một cấu trúc gồm các trường mà tổng cộng chiếm 5 bit thì cầu trúc đó vẫn được cấp phát đủ 16 bit (nghĩa là thừa 11bit), nếu tổng cộng các trường chiếm 20 bít thì cấu trúc đó vẫn được cấp phát đủ 32bit (nghĩa là thừa ra 10 bit).

- Các trường với số bít của chúng theo thứ tự khai báo sẽ chiếm các bít trong các từ máy (2B) tương ứng theo thứ tự từ phải sang trái, nghĩa là từ byte thấp đến byte cao.

- Khai báo cấu trúc này như sau: struct tên_kiểu_cấu_trúc { kiểu_dữ_liệu1 tên_trường1 : số_bít; kiểu_dữ_liệu2 tên_trường2 : số_bít;

Page 107: Giao Trinh Lap Trinh C Tom Tat

...} [tên_biến_cấu_trúc]; Ví dụ: struct date { unsigned ngay : 5; /* chiếm 5 bít, số lớn nhất là 31 */ unsigned thang : 4; /* chiếm 4 bít, số lớn nhất là 16 */ int : 2; /* chiếm 2 bít, bỏ trống unsigned nam : 5 ; /* chiếm 5 bít, số lớn nhất là 31 (quy ước là 2031) */ } d; Trong bộ nhớ, chuỗi các bít cho các trường của cấu trúc d như sau:

byte cao byte thấp

năm tháng ngày Một số quy định đối với các trường kiểu bit: . Một trường nếu có quy định số bit, phải có kiểu int (gọi là trường kiểu bit) . Độ dài của mỗi trường kiểu bit dài không quá 2B . Khi muốn bỏ qua một số bit, ta không viết tên trường. . Không cho phép lấy địa chỉ của trường kiểu bit . Không xây dựng các mảng kiểu trường kiểu bit . Không xây dựng hàm trả về một thành phần kiểu trường kiểu bit, có thể

dùng mẹo, chẳng hạn nếu t là một trường kiểu bit của cấu trúc x thì: int k = x.t; return k; * ứng dụng của cấu kiểu bit là tiết kiệm bộ nhớ và kết hợp với kiểu dữ liệu

union để lấy ra các bit của một từ.

2. Kiểu union

2.1. Nhận xét

- Việc định nghĩa một biến kiểu union , việc khai báo một biến kiểu union, mảng union, con trỏ union cũng giống như cấu trúc. Việc truy nhập đến các thành phần kiểu union cũng giống như truy nhập đến các thành phần của cấu trúc. Thành

Page 108: Giao Trinh Lap Trinh C Tom Tat

phần của cấu trúc có thể có kiểu union và ngược lại, thành phần của union có thể có kiểu cấu trúc. (Trong cú pháp, từ khóa struct tương ứng sẽ thay bằng từ khóa union)

- Cũng giống như struct, union cũng gồm nhiều thành phần, nhưng chúng khác nhau ở chỗ, các thành phần của cấu trúc thường có những vùng nhớ khác nhau, còn các thành phần của union được cấp phát một vùng nhớ chung. Độ dài của union bằng độ dài của thành phần lớn nhất.

Ví dụ typedef union { unsigned int n; /*chiếm 2B */ float r; /*chiếm 4B */ unsigned char s[2]; /*chiếm 2B */ } U; Vậy U chiếm 4B, trong đó n,r, s đều dùng chung 4 B, nghĩa là: r chiếm 4B, n chiếm 2B đầu tiên, s[2] cũng chiếm 2B đầu tiên Nếu ta gán: U.n=0xa1b2; thì do n và s[2] cùng chiếm 2 byte đầu tiên nên U.s[1]=0xa1, U.s[2]=0xb2.

2.2. Lấy ra các bit của các trường

Cấu trúc kiểu bit thường được sử dụng trong kiểu union để lấy ra các bye và các bit của các số nguyên.

Ví dụ #include <stdio.h> #include <conio.h> #include <string.h> #include <dos.h> void main(){ struct date{ unsigned ngay:5; unsigned thang:4; unsigned nam:7; }; union { struct {unsigned a; } A;

Page 109: Giao Trinh Lap Trinh C Tom Tat

struct { unsigned bit0:1; unsigned bit1:1; unsigned bit2:1; unsigned bit3:1; unsigned :4; unsigned :7; unsigned bit15:1; } B; }U; U.A.a=11; printf("\n%d %d%d%d%d",U.B.bit7, U.B.bit3,U.B.bit2,U.B.bit1,U.B.bit0); printf("\n bam enter de ket thuc chuong trinh."); getch(); }

Nếu khi chạy chương trình, kết quả là 0 1001 Giải thích union U chiếm 2 B và các thành phần U.A và U.B đều chung nhau 2B này.

Cho nên khi U.A.a=11; (tức là nội dung của ô nhớ 2B này là 0000.1001) thì các trường kiểu bit của cấu trúc B (như nói trong mục 1) sẽ lần lượt chiếm các bit từ phải sang trái của 2B đó. Các bít sau 0,1,2,3 được gán tương cho các trường có tên là bit0, bit1,bit2,bit3. Chúng sẽ lần lượt nhận giá trị của 4 bit đầu tiên của ô nhớ 2B đó tức là 1001. 4 bít tiếp theo của bye thấp và 7 bít nữa tiếp theo của byte cao bị bỏ qua không dùng. Còn 1 bit thứ 16 cuối cùng thì trường có tên bit15 sẽ nhận giá trị của bit thứ 15 trong số nguyên 11 , nghĩa bit15=0.

2.3. Thông tin hệ thống tại địa chỉ 410H

Tại địa chỉ 410H dài 2 byte , theo thứ tự các bit từ byte thấp đến byte cao gồm các thiết bị sau đây:

- Bit 0: Cho biết có ổ đĩa mềm (1) hay không (0) - Bit 1: Có bộ đồng xử lý toán học (1) hay không (0) - Bit 2,3: Số lượng RAM trên mother board: 00: 16K, 01:32K, 10:48K,

11:64K. Trong máy AT hai bit này không được dùng đến. - Bit 4,5: Cho biết kiểu màn hình đang dùng: 01:PCjr mầu 40 cột, 10: mầu

80 cột, 11: đơn sắc 80 cột, 00: không rõ kiểu màn hình.

Page 110: Giao Trinh Lap Trinh C Tom Tat

- Bit 6,7: Cho biết số lượng ổ đĩa mềm (trừ đi 1) (nghĩa là lúc in ra giá trị của 2 bít này phải cộng thêm 1)

- Bít 8: Có lắp chip DMA (0) hay không lắp (1) - Bít 9,10,11: Cho biết số các cổng nối tiếp RS232 - Bit 12: Máy có cắm bộ phối ghép trò chơi (1) hay không (0) - Bit 13: Máy in có cắm cổng nối tiếp (1) hay không (0). Bit này không sử

dụng đối với máy PC. - Bít 14,15 : Số máy in được cắm vào máy tính. Vậy để xem các thông tin trên ta có thể sử dụng struct và union để lấy ra

giá trị các bit và in lên màn hình. #include <stdio.h> #include <conio.h> #include <string.h> #include <dos.h> char *MaHieu(); typedef struct { unsigned FloppyDisk :1; /* Co it nhat 1 o dia mem =1*/ unsigned Coprocessor:1; /* Co bo dong xu ly toan hoc=1 */ unsigned RAM :2; /* So luong RAM tren mainboard */ unsigned Monitor :2; /* Kieu man hinh */ unsigned NFloppyDisk :2; /* So luong o mem */ unsigned DMA :1; /* Co lap chip DMA (0) */ unsigned NRS232 :3; /* So cac cong noi tiep RS232 */ unsigned Joystic :1; /* Co lap bo phoi ghep tro choi (1) */ unsigned SerialPrt :1; /* Co cam may in noi tiep khong */ unsigned NPrinter :1; /* So may in duoc cam */ } devices ; devices far *list = (devices far*) MK_FP (0, 0x410); void display(){ if(list->FloppyDisk) printf("\n Co it nhat mot o dia mem."); if(list->Coprocessor) printf("\n Co bo dong xu ly toan hoc."); if(strcmp("AT",MaHieu())) printf("\n Co %dK tren main board ",16 << list->RAM); printf("\n Kieu man hinh: "); switch (list->Monitor) { case 0: printf("Khong ro kieu man hinh."); break; case 1: printf("PCjt mau 40 cot."); break; case 2: printf("80 cot mau."); break; case 3: printf("Don sac 80 cot."); break; } printf("\n so o dia mem: %d.",list->NFloppyDisk+1);

Page 111: Giao Trinh Lap Trinh C Tom Tat

if(!list->DMA) printf("\n Co chip DMA."); printf("\n So cong noi tiep: %d.",list->NRS232); if(list->Joystic) printf("\n Co bo phoi ghep tro choi."); if(list->SerialPrt) printf("\n Co may in noi tiep."); printf("\n So cong may in: %d.",list->NPrinter); } char *MaHieu(){ char far *MaHieu = (char far *) MK_FP (0xF000, 0xFFFE); char *s= (char*) malloc (15); unsigned char m = *MaHieu; switch (m){ case 0xFF: strcpy(s,"PC IBM"); break; case 0xFE: strcpy(s,"XT/PC portable"); break; case 0xFD: strcpy(s,"PCjr"); break; case 0xFC: strcpy(s,"AT"); break; case 0x2D: strcpy(s,"Compaq"); break; case 0x9A: strcpy(s,"Compaq Plus"); break; } return s; } void main() { clrscr(); display(); printf("\n Bam phim bat ky de ket thuc chuong trinh."); getch(); }

Page 112: Giao Trinh Lap Trinh C Tom Tat

Chương 6. DỮ LIỆU KIỂU TỆP - FILE

Bài 1. TỆP VÀ CÁC HÀM CẤP 1

1. Tổng quan so sánh về tệp nhị phân và tệp văn bản.

- Một tệp lưu trên đĩa có bản chất là một dãy các byte có giá trị từ 0..255. Cũng vì lý do đó khi gặp một byte -1 thì C lấy đó làm dấu hiệu kết thúc tệp. Mã EOF có giá trị -1.

- C quản lý 2 loại tệp: tệp nhị phân và tệp văn bản. + Tệp kiểu nhị phân ghi và đọc trung thành mọi ký tự trên tệp. Việc ghi tệp

được thực hiện ghi theo các byte nhị phân như trong bộ nhớ. Việc đọc tệp dừng lại khi đọc xong ký tự mã EOF. Tệp kiểu nhị phân không tương thích với cấu trúc của DOS.

+ Tệp văn bản khác tệp văn bản khi xử lý đến ký tự LF (mã 10) và ký tự mã 26. Cụ thể là, khi ghi: một ký tự LF sẽ chuyển thành cặp ký tự (CR,LF) = (13,10) để ghi chúng vào tệp. Khi đọc, gặp cặp ký tự (CR,LF) thì ký tự CR được loại bỏ và ta chỉ nhận được một ký tự LF. Tệp văn bản (khi đọc) nhận ra ký tự kết thúc tệp là EOF (-1) hoặc mã 26. Điều này phù hợp với các tệp văn bản chuẩn , nghĩa là phù hợp với cấu trúc của DOS , vì DOS sử dụng ký tự mã 26 làm ký tự kết thúc tệp.

Ví dụ: Minh họa việc chuyển ký tự 10 thành (13,10) khi ghi vào tệp văn bản.

#include <stdio.h> #include <conio.h> main() { FILE *fvb, *fnp, *f; /* mo cac tep van ban va tep nhi phan de ghi */ fvb=fopen("vb.txt","wt"); fnp=fopen("np.bin","wb"); f=fopen("vd.txt","wt"); /*ghi cac ky tu len tep van ban fvb */

fputc('A',fvb); fputc(26,fvb); fputc(10,fvb); fputc('B',fvb); /*ghi cac ky tu len tep nhi phan fnp */

fputc('A',fnp); fputc(26,fnp); fputc(10,fnp); fputc('B',fnp);

Page 113: Giao Trinh Lap Trinh C Tom Tat

/*ghi 3 dong len tep van ban*/ fprintf(f,"%2d\n%2d\n%2d",56,7,8); /* Dong cac tep*/ fclose(fvb); fclose(fnp); fclose(f); printf("\n Da ghi xong."); getch(); }

Kết quả: Tệp vb.txt nhận được chuỗi 5 ký tự có mã :

65 26 13 10 66 A 26 CR,LF B

Tệp np.txt nhận được chuỗi 4 ký tự có mã :

65 26 10 66 A 26 LF B

Tệp văn bản vd.txt nhận được một chuỗi 10 ký tự có mã sau đây:

53 54 13 10 32 55 13 10 32 56 số 5 số 6 CR,LF space số 7 CR,LF space số 8

Nhưng nếu thay lệnh: f=fopen(“vd.txt”, “wt”) bằng lệnh: f=fopen(“vd.txt”, “wb”) thì kết quả tệp nhị f đó sẽ nhận được 8 ký tự có mã sau đây:

53 54 13 32 55 10 32 56 số 5 số 6 LF space số 7 LF space số 8

Cấu trúc ghi trong tệp nhị phân f không cho phép lệnh Type của DOS hiển

thị 3 dòng như mong muốn.

Page 114: Giao Trinh Lap Trinh C Tom Tat

2. Các ký tự mở tệp

Trong hàm fopen có 2 đối - Đối thứ nhất là tên tệp - Đối thứ hai gồm 2 ký tự (gọi chung là “kiểu”): . Ký tự đầu quy định kiểu mở tệp: mở để ghi (w), để đọc (r), hay để bổ

sung vào cuối tệp (a). . Ký tự thứ hai quy định loại tệp: tệp văn bản (t) hay tệp nhị phân (b) - Nói riêng, nếu có dấu + xen giữa 2 ký tự đó thì có thể vừa ghi vừa đọc,

nếu bắt đầu là “r+t” hoặc “r+b” thì việc ghi đọc có kiểm tra lỗi tồn tại tệp , nếu bắt đầu là “w+t” hoặc “w+b” thì không kiểm tra lỗi tồn tại tệp (tệp không có trên đĩa sẽ tự động được tạo mới). Nếu trong 2 ký hiệu, chỉ viết ký hiệu đầu thì ngầm định là tệp văn bản.

Tóm lại ta có thể có: tệp văn bản tệp nhị phân “r” hoặc “rt” “rb” “w” hoặc “wt” “wb” “a” hoặc “at” “ab” “r+” hoặc “r+t” “r+b” “w+” hoặc “w+t” “w+t” “a+” hoặc “a+t” “a+b”

Tổng quan về các hàm cấp 1 và các hàm cấp 2

- Thao tác với tệp bao gồm các công việc: mở tệp; đọc dữ liệu từ tệp chuyển ra biến; xử lý trên biến để giải bài toán; kết quả xử lý lại có thể ghi vào tệp; cuối cùng đóng tệp. Thao tác trên tệp được thực hiện bởi 2 nhóm hàm:

+ Nhóm hàm cấp 1: là các hàm đọc/ghi hệ thống thực hiện việc đọc ghi như DOS. Đặc điểm của các hàm này là:

. Không đọc ghi theo từng kiểu dữ liệu mà chỉ có đọc ghi từng dãy byte, bao gồm các dịch vụ đọc/ghi 2 byte đối với mỗi số nguyên, đọc/ghi 4 byte đối với mỗi số thực.

. Mỗi tệp có một số hiệu (gọi là thẻ - handle) và các hàm làm việc với tệp thông quan số hiệu tệp (là các số nguyên)

Page 115: Giao Trinh Lap Trinh C Tom Tat

+ Nhóm hàm cấp 2: Được xây dựng từ các hàm cấp 1, dễ sử dụng và có nhiều khả năng hơn:

. Có dịch vụ đọc ghi từng kiểu dữ liệu.

. Có một biến con trỏ tệp để xác định các tệp.

. Sử dụng vùng đệm trung gian trong quá trình đọc/ghi để tăng tốc độ truy xuất dữ liệu.

3. Thao tác với tệp (giới thiệu các hàm cấp 2)

Bao gồm thứ tự các công việc trong bài toán: - Mở tệp - Đọc/ghi dữ liệu với biến bộ nhớ - Xử lý dữ liệu trên biến - Đóng tệp

3.1. Mở tệp

FILE *fopen(const char *tên_tệp, const char *kiểu); hàm cho giá trị NULL nếu mở tệp không thành công.

3.2. Đọc và ghi tệp

- Các hàm ký tự: + Ghi:

int putc(inc c, FILE *f); int fputc(int c, FILE *f);

+ Đọc: int getc(FILE *f); int fgetc(FILE *f);

- Các hàm chuỗi ký tự (thích hợp với tệp văn bản) + Ghi: (trả về EOF khi lỗi)

int fputs(const char *s,FILE f); không ghi ‘\n’, trả về ký tự cuối ghi tệp int fprintf(FILE *f, const char *dk,...); trả về số byte ghi tệp

+ Đọc: int *fgets(char *s,int n,FILE *f); int fscanf(FILE *f, const char *dk, ...);

- Các hàm đọc ghi kiểu nhị phân (thích hợp tệp nhị phân)

Page 116: Giao Trinh Lap Trinh C Tom Tat

+ Ghi: int putw(int n, FILE *f); int fwrite(void *p, int size, int n, FILE *f); đặc trị cho struct

+ Đọc: int getw(FILE *f); int fread(void *p,int size, int n, FILE *f); đặc trị cho struct

3.3. Đóng tệp

int fclose(FILE *f); Đóng tệp sau khi đã đẩy hết dữ liệu từ vùng đệm lên đĩa. Hàm bằng

0 nếu đúng.

3.4. Các hàm hỗ trợ khác

Các hàm thông dụng:

. int feof(FILE *f): Kiểm tra cuối tệp, chưa kết thúc tệp = 0.

. int fflush(FILE *f) : làm sạch vùng đệm, đúng= 0, sai = EOF

. int ferror (FILE *f): lỗi tao tác trên tệp, đúng =0

. void perror(const char *s): in chuỗi và thông báo lỗi hệ thống

. int unlink(const char *s): xóa tệp s. Đúng = 0

. void remove(const char *s): xóa tệp s

Các hàm làm việc với nhiều tệp

. int fflushall(void) : làm sạch vùng đệm của các tệp đang mở.

. int fcloseall(void): đóng tất cả các tệp đang mở.

Các hàm di chuyển con trỏ định vị

void rewind(FILE *f); chuyển về đầu tệp int fseek(FILE *f,long sb,int xp); từ vị trí xp, chuyển sb byte (+,-)

xp có thể là: SEEK_SET, SEEK_CUR, SEEK_END long ftell(FILE *f); vị trí hiện tại của con trỏ định vị. Ví dụ tính độ dài của tệp fseek(f,0,SEEK_END); n=ftell(f); nếu mỗi phần tử của tệp có kích thước size thì số phần tử là n=ftell(f)/size;

Page 117: Giao Trinh Lap Trinh C Tom Tat

* Chú ý: Khi biến con trỏ tệp là stdprn thì đó là máy in.

4. Một số bài tập

Bài 1 Viết chương trình sao chép các ký tự từ tệp source.dat sang tệp dest.dat * Tác dụng: - Sao chép trung thành, dùng tệp nhị phân. - Minh họa sử dụng các hàm đọc/ghi ký tự: fgetc, fputc #include <stdio.h> #include <conio.h> main() { FILE *f1, *f2; char c; f1=fopen("source.dat","rb"); f2=fopen("dest.dat","wb"); if(f1==NULL) {printf("\n source file not exist"); getch(); exit();} while((c=fgetc(f1))!=EOF) fputc(c,f2); fclose(f1); fclose(f2); printf("\n Nhan phim bat ky de ket thuc chuong trinh."); getch(); } Bài 2 Tạo tệp văn bản prims.txt chứa n số nguyên tố đầu tiên (n <= 500). Lọc ra

các số nguyên tố đối xứng và ghi vào tệp văn bản prim2.txt. Trên mỗi dòng của các tệp nói trên ghi 10 số, trừ dòng cuối cùng có thể ít

hơn. * Tác dụng: Cách 1- Dùng các hàm đọc/ghi dữ liệu lên tệp text theo khuôn dạng: fscanf,

fprintf Cách 2- Dùng các hàm đọc/ ghi theo số nguyên (2B) getw, putw Cách 1 #include <stdio.h> #include <conio.h> #define fo1 "prims.txt" #define fo2 "prim2.txt"

Page 118: Giao Trinh Lap Trinh C Tom Tat

long doixung(long x) {unsigned long y=0; while(x>0){y=y*10+x%10;x/=10;} return (y); } int prim(long x) {int d=0; long i=1; for(;i<=x/2;i++) if(x%i==0) d++; return(d==1?1:0); } main() { FILE *f1, *f2; long x; int c=0; char kt; const n = 200; f1=fopen(fo1,"wt"); for(x=2;c<n;x++) if(prim(x)) { c++; if(c%10==0) fprintf(f1,"%d\n",x); else fprintf(f1,"%d ",x); } fclose(f1); f1=fopen(fo1,"rt"); f2=fopen(fo2,"wt"); c=0; while(!feof(f1)) { fscanf(f1,"%ld",&x); if(!feof(f1)) if(x==doixung(x)) { fprintf(f2,"%ld ",x);c++; if(c%10==0) fprintf(f2,"\n"); } } fclose(f1); fclose(f2); } Cách 2 #include <stdio.h> #include <conio.h> #define fo1 "prims.txt" #define fo2 "prim2.txt" long doixung(long x)

Page 119: Giao Trinh Lap Trinh C Tom Tat

{unsigned long y=0; while(x>0){y=y*10+x%10;x/=10;} return (y); } int prim(long x) {int d=0; long i=1; for(;i<=x/2;i++) if(x%i==0) d++; return(d==1?1:0); } void main() { FILE *f1, *f2; long x; char kt; int c=0; const n = 200; f1=fopen(fo1,"wb"); for(x=2;c<n;x++) if(prim(x)) { c++; putw(x,f1);} fclose(f1); f1=fopen(fo1,"rb"); f2=fopen(fo2,"wb"); while(!feof(f1)) { x=getw(f1); if(!feof(f1)) if(x==doixung(x)) {putw(x,f2); printf("%d\t",x);} } fclose(f1); fclose(f2); } Bài 3 Mô phỏng chương trình soạn thảo văn bản. Nhận và in lên màn hình và tệp văn bản (có tên nhập từ bàn phím) các ký tự

bất kỳ có mã > 31 (trừ ký tự mã 13). Chương trình kết thúc khi nhấn Ctrl-C. * Tác dụng - Cách 1: Sử dụng các hàm getche() nhận và hiện ký tự bất kỳ, hàm fputc - Cách 2: Sử dụng các hàm fgets và fputs Cách 1 #include <stdio.h> #include <conio.h> void main() {

Page 120: Giao Trinh Lap Trinh C Tom Tat

FILE *f; char c; char namef[11]; clrscr(); printf("File name: "); scanf("%s",namef); for(c=0;c<79;c++) printf("-"); printf("\n"); f=fopen(namef,"wt"); while((c=getche())!=3) {if(c>31||c==13) fputc(c,f); if(c==13) printf("\n"); } fclose(f); printf("\n File is copied."); getch(); } Cách 2 Thể hiện ở đoạn trình sau đây: char d[256]; while (1){

gets(d); if(d[0]==’\0’) break; fputc(10,f); fputs(d,f);

} Để đọc nội dung của tệp văn bản và in lên màn hình, ta dùng đoạn chương

trình char d[256]; while (1){

fgets(d,256,d); printf(“%s”,d);

} Bài 4 Viết chương trình tạo tệp (nhị phân) chứa các cấu trúc mô tả thông tin về

các sinh viên (gồm họ tên và năm sinh). Sau đó đọc danh sách sinh viên từ tệp này để in ra màn hình. * Tác dụng

Page 121: Giao Trinh Lap Trinh C Tom Tat

- Sử dụng các hàm fwrite, fread - ôn lại cách truyền con trỏ cấu trúc cho hàm. #include <stdio.h> #include <conio.h> #define fname "sinhvien.dat" typedef struct{ char ht[30]; int ns; } sinhvien; const int size = sizeof(sinhvien); int nhapsv(sinhvien *sv) { sinhvien svtg; printf("\n ho ten: "); fflush(stdin); gets(svtg.ht); if(svtg.ht[0]=='\0') return (0); printf("\n nhap nam sinh: "); scanf("%d",&svtg.ns); *sv=svtg; return 1; } void xemsv(sinhvien sv) { printf("\n%s: %d ",sv.ht,sv.ns);} void main(){ FILE *f; sinhvien sv; f=fopen(fname,"wb"); while(1) {if(nhapsv(&sv)==0) break; fwrite(&sv,size,1,f); } fclose(f); f=fopen(fname,"rb"); while(fread(&sv,size,1,f)>0) xemsv(sv); fclose(f); getch(); }

Page 122: Giao Trinh Lap Trinh C Tom Tat

Bài 2. CÁC HÀM NHẬP XUẤT CẤP 1

1. Các tệp tiêu đề và biến chuẩn

io/h chứa các nguyên mẫu của các hàm cấp 1 fcntl.h chứa các định nghĩa quyền truy nhập (access) sys/stat.h chứa các định nghĩa thuộc tính (amode) dos.h chứa các định nghĩa thuộc tính (attribute) theo DOS

2. Tóm tắt các hàm

Các hàm tạo, mở đóng tệp

. creat và _creat: tạo tệp mới

. open và _open: mở tệp mới hoặc đã có

. close và _close: đóng tệp

. chmod và _chmode : thay đổi thuộc tính tệp ( _ là theo kiểu DOS)

. perror: để thông báo lỗi (stdio.h)

. write, read : ghi/đọc dãy các byte

. lseek: di chuyển con trỏ định vị tệp. * UNIX và DOS - Kiểu truy nhập mặc định của các hàm trên là tệp văn bản. Nếu thêm _ thì

mặc định là tệp nhị phân. - Thuộc tính trong các hàm create, chmod là các hằng có đặc trưng của

UNIX, định nghĩa trong sys/stat. Thuộc tính trong các hàm _create, _chmod là các hằng có đặc trưng của DOS định nghĩa trong dos.h

3. Cú pháp của các hàm

3.1. Tạo tệp

int creat(const char *fname, int amode); - Trả về -1 khi lỗi, ngược lại, trả về số hiệu tệp. Trong sys/stat.h - amode là một trong 2 hằng: S_IREAD: chỉ đọc. Không xóa ,sửa, bổ sung. S_IWRITE: để ghi. Được xóa,sửa,bổ sung. Kiểu ghi được gán trong biến:

Page 123: Giao Trinh Lap Trinh C Tom Tat

fmode ( mặc định là OTEXT, nhị phân là O_BINARY) int _creat(const char *fname, int attrib); - Trả về -1 khi lỗi, ngược lại, trả về số hiệu tệp. Trong dos.h amode là một trong 2 hằng: - attrib là một trong các hằng hoặc tổ hợp có nghĩa các hằng: FA_RONLY: chỉ đọc. FA_ARCH: để ghi. FA_HIDDEN: tệp ẩn.

3.2. Mở tệp

int open(const char *fname, int access, [, unsigned amode]); - Mở tệp và truy nhập theo quy định của access. Trong fcntl.h - access là một trong các hằng hoặc tổ hợp các hằng: O_RONLY: Đọc O_RDWR: Đọc/ghi O_APPEND: Ghi bổ sung O_TRUNC: Xóa tệp nếu tồn tại, giữ lại thuộc tính O_TEXT: truy nhập kiểu tệp văn bản O_BINARY: truy nhập kiểu tệp nhị phân O_CREATE: nếu tệp chưa có thì tạo mới. int _open(const char *fname, int oflags); - Mở tệp thành công = 0, lỗi = -1. - oflags xác định quyền và kiểu truy nhập giống như access của hàm open

và cũng có các giá trị hằng như trên. Tuy nhiên mặc định là tệp nhị phân (O_BINARY) và hằng O_CREATE không có tác dụng tạo tệp mới.

3.3. Đóng tệp

int close(int fn); int _close(fn); - Đóng tệp có số hiệu fn. Hàm thành công cho giá trị 0, lỗi cho giá trị -1.

3.4. Thay đổi thuộc tính tệp

int chmod(const char *fname, int amode);

Page 124: Giao Trinh Lap Trinh C Tom Tat

- Thành công=0, lỗi = -1 - amode là một trong các hằng đã nêu. int _chmod(const char *fname, int func, [,int attrib]); - Thành công=0, lỗi = -1 - Nếu func=0, hàm cho biết thuộc tính tệp, không cần đối thứ 3. - Nếu func=1, hàm thay đổi thuộc tính tệp theo quy định trong attrib. Giá trị

của attrib như đã nêu.

3.5. Ghi/ đọc và di chuyển vị trí trên tệp

unsigned write(int fn, void *ptr, unsigned n); unsigned read(int fn, void *ptr, unsigned n); - Ghi/đọc n byte giữa vùng nhớ ptr và tệp có số hiệu fn. - Thành công, hàm trả số byte ghi/đọc được, lỗi hàm trả về -1 long lseek(int fn, long sb, int xp); di chuyển con trỏ định vị của tệp số hiệu fn từ vị trí xp đi sb byte. Dấu của

sb là chiều di chuyển.

4. Một số bài tập

Bài 1 Thực hiện việc sao chép tệp bằng các hàm cấp 1 - Sử dụng các hàm _open, _creat, read và write #include <stdio.h> #include <conio.h> #include <io.h> #include <dos.h> #include <fcntl.h> void main(){ int n; char tep1[11], tep2[11]; char copy[1000]; int fn1, fn2; printf("\n nhap ten tep nguon: "); gets(tep1); printf("\n nhap ten tep dich: "); gets(tep2); fn1=_open(tep1,O_RDONLY); if(fn1==-1) { printf("\n tep nguon khong co."); return; } fn2=_creat(tep2,FA_ARCH); if(fn2==-1) { printf("\n loi tao tep dich."); return; } while((n=read(fn1,copy,1000))>0) write(fn2,copy,n);

Page 125: Giao Trinh Lap Trinh C Tom Tat

close(fn1); close(fn2); printf("\n copied."); getch(); } Bài 2 Thực hiện việc thay đổi thuộc tính của tệp #include <stdio.h> #include <conio.h> #include <io.h> #include <dos.h> #include <fcntl.h> void main(){ char tep[11]; int chon; int attrib; printf("\n nhap ten tep can thay doi thuoc tinh: "); gets(tep); printf("\n chon thuoc tinh: \ 0: ghi, 1: chi doc, Cong them 2 neu muon them hidden: "); chon=getche()-'0'; if(chon==0) attrib=FA_ARCH; else if(chon==1) attrib=FA_RDONLY; if(chon>=2) attrib=attrib | FA_HIDDEN; if(_chmod(tep,1,attrib)==-1) perror("\nloi thuc hien thay doi thuoc tinh"); else printf("\n attributed."); getch(); }