Tóm tắt ngôn ngữ lập trình C

if(kq==0) { if(strcmp(pd->ht,p1->ht)==0) { pd=pd->tiep; free(p1); return; } else { ps->tiep=p1->tiep; free(p1); } } else { printf("\n khong co nhuoi can tim:"); getch(); } } }

pdf51 trang | Chia sẻ: aloso | Lượt xem: 3526 | Lượt tải: 3download
Bạn đang xem trước 20 trang tài liệu Tóm tắt ngôn ngữ lập trình C, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
a. goi theo tên biến : Là cách gọi bằng chính cái tên của biến do bạn đặt. b. gọi theo địa chỉ : Là cách gọi theo chỗ nhớ của biến t−ơng ứng. Cách này rất có hiệu quả trong việc l−u trữ biến nhiều phần tử, tuy nhiên bạn phải sử dụng 1 con trỏ để l−u trữ địa chỉ (Tôi xẽ giới thiệu sau) VD nh− biến mảng, biến cấu trúc).Trong Pascal đã giới thiệu rất kĩ về mảng tuy nhiên Tôi xin nhắc lại định nghĩa mảng. Mảng là một số các phần tử phân bố kế tiếp nhau trong bộ nhớ. VD : với khai báo int a[20]; Ù Mảng a có 20 phần tử vì vậy a[0] : Là phần tử đầu tiên trong mảng a[1] : Là phần tử thứ 2 trong mảng. …… a[19] : Là phần tử cuối cùng trong mảng. 2. Câu lệnh điều kiện: Cú pháp : If { công việc; } Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 9 else { công việc; } VD: Giải ph−ơng trình bậc nhất ax2+b=c (điều kiện a#0)với a,b,c đ−ợc nhập từ bàn phím. Viết ch−ơng trình #include #include main() { float a,b,c,x; printf(“nhập a,b,c=”);scanf(“%f%f%f”,&a&b&c); x2=(c-b)/a; If((c-b)>0) { printf(“ph−ơng trình có 2 nghiệm”); printf(“x1=%4.2f”,sqrt(x)); printf(“x2=%4.2f”,-sqrt(x)); } if((c-b)=0) printf(“co 1 nghiem x=0.”); if((c-b)<0) printf(“phuong trinh vo nghiem.”); getch(); } 3. Vòng lặp xác định: Là vòng lặp cho bạn biết tr−ớc số lần lặp là bao nhiêu. Mục đích là để l−u trữ (nhập/xuất) các phần tử mảng cho thuận tiện Cú pháp : for(i=0;i<n;i++) { khối lệnh; } trong đó: i : Là biến chạy cho biết địa chỉ của phần tử thứ i, i++ là sau 1 vòng lặp i sẽ tự động tăng thêm 1 đơn vị. n : Là số phần tử của mảng. Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 10 VD: Hãy nhập một mảng (10 phần tử) số nguyên bất kì tiên sau đó cho hiện ra màn hình dãy số vừa nhập. Ch−ơng trình: #include #include main() { int a[10],i; for(i=0;i<10;i++) { printf(“nhap gia tri cho mang:\ n”); printf(“phan tu thu: %d \ n”,i);scanf(“%d”,&a[i]); } /*đến đây bạn đã có 1 mảng 10 phần tử*/ for(i=0;i<10;i++) { printf(“hiện gia tri mang vừa nhập:\ n”); printf(“phần tử thứ %d %d“,i,a[i]); } getch(); } T−ơng tự vậy bạn có thể làm ví dụ sau: Bài Tập : Nhập mảng 10 số thực bất kì sau đó: - Cho hiện lên màn hình dãy số vừa nhập. - Tính tổng của 10 số đó và cho hiện tổng tính đ−ợc lên màn hình . Gợi ý: Công thức tính tổng s=s+a[i] ; 4. Vòng lặp không xác định : Có 2 vòng cơ bản : a. Vòng lặp while….do : Cú pháp : while (điều kiện) { khối lệnh; } Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 11 Giải thích : Vòng lặp trên có ý nghĩa là: Trong khi (while) (điều kiện (còn đúng)) { thì còn làm các công việc; } VD: Tìm giá trị của n để có đ−ợc giai thừa lớn hơn 120. Ch−ơng trình: #include #include main() { int n,gt; gt=1; n=0; while(gt<120) { n=n+1; gt=gt*n; } printf(“gia tri n can tim la n= : %d”,n); printf(“giai thua tai n la:%d)”,gt; getch(); } Nhận xét : Khác với vòng lặp for bởi vì nó không biết nó phải lặp bao nhiêu lần. Nếu không có điều kiện(còn đúng) thì nó xẽ lặp mãi không dừng. Nh−ng vấn đề không phải ở đó. Bạn nên hiểu rằng khi ng−ời ta đ−a ra một vòng lặp thì chắc chắn nó sẽ có những công dụng khác nhau, giải quyết loại bài toán khác nhau.Tôi lấy ví dụ đơn giản bạn có thể dùng vòng for để nhập dữ liệu cho mảng, ng−ợc lại trong 1 phép tính nào đó VD :y=x/5 điều kiện là hãy nhập vào giá trị của x để có đ−ợc y<0.0004 và in giá trị của x t−ơng ứng với loại bài toán này thì vòng for tỏ ra không có hiệu quả. Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 12 Tóm lại: Để phân biệt đ−ợc sự khác nhau giữa các vòng lặp là vô cùng khó khăn. Thực ra mà nói thì giữa chúng gần nh− không có danh giới, mà chỉ tỏ ra −u việt hơn trong từng loại bài toán mà thôi. Vì vậy bạn cần nắm vững và phân biệt các loại bài toán để áp dụng nó một cách chính xác. Bạn có thể xem các vòng lặp nh− là những công cụ làm việc, còn dữ liệu là nguyên liệu. Tôi lấy ví dụ nh− có bao giờ bạn dùng dao để trặt đá không trong khi đó bạn có búa, tuy nhiên bạn cố tình thì vẫn có thể làm đ−ợc. b. Vòng lặp do…while: Cú pháp: do { công việc; } while ; Vòng lặp này chỉ khác vòng lặp trên ở chỗ while đ−ợc đặt ở cuối câu còn nội dung thì giống nhau. Ghi chú: Đối với 2 vòng lặp b, c bạn cần kết hợp thêm câu lệnh sau thì rất có hiệu quả. (tham khảo trong sách). - break ; mục đích để thoát khỏi vòng lặp không cần đến điều kiện. - Continue; Khi gặp lệnh này nó xẽ bỏ công việc đang thực hiện và quay lại thực hiện công việc tiếp theo. Chú ý: Bạn phải hết sức quan tâm đến phần này, vì nó là công cụ duy nhất để giải quyết bài toán. Nếu nh− không hiểu phần này coi nh− bạn chẳng biết gì cả. Cách tốt nhất bạn nên làm nhiều bài tập để hiểu cặn kẽ vấn đề. VI. Con trỏ và hàm: 1. Con trỏ: Con trỏ rất có lợi trong mọi ngôn ngữ vì nó rất tiện lợi sử dụng trong việc l−u trữ địa chỉ. Có những việc nếu nh− không có con trỏ bạn không thể thực hiện đ−ợc công việc nh− ý muốn. Ví dụ việc lấy kết quả từ một ch−ơng trình con sang ch−ơng trình chính, nếu không sử dụng con trỏ bạn không thể lấy kết quả ra khỏi ch−ơng trình đó Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 13 đ−ợc. Tuy nhiên việc sử dụng con trỏ làm cho chúng ta cảm thấy rất phức tạp.Thực ra nó cũng đơn giản thôi bạn thấy gì sự khác nhau giữa hai ch−ơng trình sau: Ch−ơng trình 1: #include #include main() { int a=34; printf(“gia tri của a la a= : %d”,a); getch(); } kết quả in a=34; Ch−ơng trình 2: #include #include main() { int a=34,*p; *p=a; printf(“gia tri của a la a= : %d”,p); getch(); } kết quả in a=844; Vì sao vậy bởi vì con trỏ chỉ l−u địa chỉ của biến a chứ nó không l−u giá trị của biến a. Mà địa chỉ trong bộ nhớ thì đ−ơng nhiên không phải là giá trị của biến nào đó. Đến đây Tôi tin chắc bạn đã hình dung ra rồi trứ, nếu ch−a Tôi xin đ−ợc lấy một ví dụ vui thế này:Bạn của bạn nhờ bạn cất giữ hộ một cái va li đ−ơng nhiên bạn không thể mở nó ra đ−ợc, chiếc va li đó chẳng may bị mất, khi trình báo với công an thì bạn chỉ có thể trình bày là chiếc va li để chỗ nào thôi chứ không biết đ−ợc trong đó có gì, còn bạn của bạn lại biết bên trong có gì nh−ng không biết nó nằm chỗ nào. Tức là gì: Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 14 “ Con trỏ chỉ l−u trữ địa chỉ chứ không l−u trữ giá trị của biến đó,đ−ơng nhiên giá trị phải nằm trong địa chỉ ” Hi vọng bạn không còn nhầm lẫn nữa. Và sau đây Tôi xẽ cho các bạn thấy sự tác dụng của con trỏ và việc sử dụng nó. Tôi xẽ nói tiếp về con trỏ sau, vì nó gắn liền với việc sử dụng hàm. 2.Hàm và ch−ơng trình: Bạn có phân biệt đ−ợc hai từ này không còn theo Tôi thì: + Hàm là 1 phần ch−ơng trình lớn, gồm có các hàm sau: 2.1. Hàm void() : Là hàm không chả lại kết quả trong tên hàm khi ch−ơng trình chính gọi đến nó. VD: Có hàm void tim(int n); Khi đó trong ch−ơng trình chính đ−a ra lời gọi hàm là printf(“kết quả tìm là%d”,tim(n)); thì chắc trắn bạn không có đ−ợc kết quả đúng. 2.2. Hàm main() : Hàm này bắt buộc cho mọi ch−ơng trình(còn gọi là hàm chính). 2.3. Hàm có kiểu : Trong đó có thể là các kiểu nguyên, thực,char. VD: int gt(int n); hoặc hàm float tbc(float a,float b); Loại hàm này sau khi thực hiện bạn đ−a ra câu lệnh sau return(biến tổng); thì tự khắc kết quả đ−ợc tự động chứa vào tên hàm là “tbc” , khi đó ở ch−ơng trình chính bạn gọi tới nó thì nó sẽ cho kết quả mà nó nhận đ−ợc. 2.4. Hàm tự định nghĩa: (Tôi xin giới thiệu hàm này trong phần dữ liệu có cấu trúc vì phần này ch−a cần sử dụng đến). + Ch−ơng trình : Là một dãy các hàm kết hợp lại với nhau trong đó có hàm chính đ−ợc định nghĩa là hàm main(). 3. Sử dụng hàm: Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 15 Từ đầu đến giờ cứ mỗi một ví dụ đều phải sử dụng hàm đó là hàm main(). Nh−ng trong phần này Tôi muốn trình bày cách kết hợp nhiều hàm trong 1 ch−ơng trình, kết hợp với việc sử dụng con trỏ nh− thế nào. a.Kết hợp hàm main và hàm có kiểu: Tôi sẽ xây dựng 1 ch−ơng trình có 2 hàm trong đó hàm main sẽ gọi đến hàm void VD (bt 4,trang 133,ch−ơng 5,sách của ông ất): Viết ch−ơng trình tính sinx=x-x3/3!+x5/5!- …..+(-1)nx2n+1/(2*n+1)! Với độ chính xác là 0.0001. Gơị ý: Ta thấy x luôn tỷ lệ với n, tức n tăng thì mũ của x cũng tăng theo, khi n tăng thêm 1 đơn vị thì ta lại có đ−ợc 1 th−ơng mới, nếu n lẻ thì th−ơng này mang dấu trừ và ng−ợc lại. Độ chính xác 0.0001 tức là khi có một th−ơng mới, nếu th−ơng này <0.0001 tức là điều kiện đ−ợc thỏa mãn Đầu vào : Nhập x bất kì. n: Là biến chạy nhằm thỏa mãn độ chính xác. Đầu ra : Giá trị của sinx ; Tôi sử dụng 2 hàm: - Hàm int gt(int n); để tính giai thừa n kiểu hàm là nguyên tên hàm là gt ,đối n kiểu nguyên. - Hàm main() là hàm chính làm nhiệm vụ nhập x và gọi tới hàm gt để lấy kết quả tính toàn và cuối cùng là in giá trị của sinx Để viết đ−ợc ch−ơng trình này bạn phải hình dung đ−ợc là: - Mẫu số là biểu thức tính giai thừa. Vì vậy để đơn giản bài toán ta xây dựng 1 hàm tính giai thừa là mẫu số, khi n tăng tự nó xẽ đ−a ra lời gọi giai thừa. - Tử số tuy nó phức tạp nh−ng rất may trong c đã có hàm pow(y,x) Ù yx để tính xn và (-1)n tức là (-1) n pow(-1,n);và xnpow(x,n) ; - Ch−ơng trình sử dụng 1 biến kt để chứa giá trị th−ơng nhận đ−ợc, đồng thời nó cũng là điều kiện để vòng lặp kết thúc. Nếu kt>0.0001.biến sinx để chứa kết quả nhận đ−ợc. Sau đây là ch−ơng trình: #include #include #include int gt(int n); Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 16 /*bắt đầu hàm gt*/ int gt(int n) { int i,tg; tg=1; for(i=1;i<=2*n+1; ) { i=i+2; tg=tg*i; } return tg; /*là câu lệnh chứa kết quả của tg vào tên hàm chú ý nếu là con trỏ thì phảI là return (&tg)*/ } /*bắt đầu hàm chính*/ main() { clrscr(); int n; float x,sinx,dk; printf("\n nhap gia tri cua x= ");scanf("%f",&x); n=1; do { dk=(pow(-1,n)* pow(x,n)) /gt(n); /*đây là lời gọi tới hàm gt*/ sinx=sinx+dk; n=n+1; } while (abs(dk)<0.0001); /*abs là lấy giá trị tuyệt đối*/ printf("\n gia tri cua sinx =%4.8f",sinx); printf("\n n=%d",n); getch(); } Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 17 Nếu cũng với bài toán này Tôi khai báo là: int *gt(int n);tức là sử dụng con trỏ thì điều gì xẽ xảy ra: Bạn hãy tự chạy ch−ơng trình này thì bạn xẽ hiểu những gì Tôi nói trong phần con trỏ. Sau đây là ch−ơng trình: #include #include #include int *gt(int n); /*bắt đầu hàm gt*/ int *gt(int n) { int i,tg; tg=1; for(i=1;i<=2*n+1; ) { i=i+2; tg=tg*i; } return (&tg); } /*bắt đầu hàm chính*/ main() { clrscr(); int n; float x,sinx,dk; printf("\n nhap gia tri cua x= ");scanf("%f",&x); n=1; do { dk=(pow(-1,n)* pow(x,n)) /gt(n);/*đây là lời gọi tới hàm gt*/ sinx=sinx+dk; n=n+1; } Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 18 while (abs(dk)<0.0001); /*abs là lấy giá trị tuyệt đối*/ printf("\n gia tri cua sinx =%4.8f",sinx); printf("\n n=%d",n); getch(); } Chú ý: Bạn có biết tại sao trong hàm gt Tôi lại sử dụng biến tg, mà lại không sử dụng trực tiếp biến gt không. Bởi vì không nên sử dụng lời gọi hàm lớn hơn 1 lần trong ch−ơng trình. T−ơng tự vậy bạn hãy làm bài tập sau: Tính giá trị của ex=1+x/1!+x2/2!+….+xn/n!; b. Hàm main và hàm void: - Nh− ta đã biết hàm void không trả lại kết quả cho tên hàm vì vậy muốn lấy kết quả ra khỏi ch−ơng trình thì bắt buộc bạn phải dùng đến con trỏ. - Tôi lại xin nhắc lại cho nhớ hai câu mà bạn không đ−ợc pháp quên đó là: 1. Con trỏ chỉ l−u địa chỉ của biến chứ không l−u giá trị. 2. Khi nào cần lấy giá trị của biến ra khỏi ch−ơng trình thì bắt buộc phải sử dụng con trỏ. Vì sao Tôi nhắc lại điều này vì từ bây giờ trở đi con trỏ là công cụ đắc lực khi giải quyết bài toán. Việc sử dụng hàm bạn đã quá quen thuộc vì vậy sau đây Tôi muốn bạn hiểu thông qua ví dụ sau: VD: Viết ch−ơng trình hoán vị hai số a,b cho nhau sủ sụng 2 hàm: - Hàm void vh(float *p1,float *p2) để hoán vị 2 số cho nhau. - Hàm main() để nhập dữ liệu và in kết quả hoán vị. Ch−ơng trình: #include #include void hv(float *p1,float *p2); /*khai báo nguyên mẫu*/ void hv(float *p1, float *p2); /*bắt đầu hàm void*/ { float tg; tg=*p1; *p1=*p2; *p2=tg; } Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 19 main() { float a,b; printf("\n nhap gia tri cua a= ");scanf("%f",&a); printf("\n nhap gia tri cua b= ");scanf("%f",&b); vh(&a,&b); printf("\n nhap gia tri cua a= %4.2f",a); printf("\n nhap gia tri cua b= %4.2f",b); getch(); } nhập a=2.4;b=3.0; kết quả chạy: a=3.00;b=2.40; Để tỏ rõ uy lực của con trỏ bạn thử bỏ * ở p1 và p2 mà xem. 4. Các lỗi th−ờng gặp trong khi sử dụng con trỏ: Đ−a ra phần này Tôi, bản thân cảm thấy rất xấu hổ bởi vì vốn tiếng anh của Tôi vô cùng khiêm tốn. Có gì mong bỏ quá cho. + Máy báo : inconpatible type conversion : lỗi này là do lời gọi hàm sai lẽ ra là tên hàm (&biến) bạn chỉ viết tênhàm(biến) + Máy báo : illegal use of floating point :bạn đã viết thiếu dấu “*” trong 1 biểu thức nào đó vì đây là con trỏ. + Máy báo : cannot covert int to int :thiếu dấu & trong return(tênhàm) + Máy báo : too few parameters in callto :đối trong lời gọi hàm không t−ơng thích với trong hàm. + Máy báo : linler error undefined symbol ….:sai đ−ờng dẫn giữa lời gọi hàm và hàm. Ngoài ra còn rất nhiều lỗi khác nữa mong tự tìm hiểu.Đặc biệt có 1 lỗi mà ch−ơng trình không báo lỗi, nh−ng ch−ơng trình cho kết quả sai, thì đó là do thuật toán của bạn sai hoặc là bạn sử dụng con trỏ không đúng, tức là bàn vẫn ch−a hiểu gì về con trỏ . VII. Mảng và con trỏ : Mảng là một số các phần tử có cùng kiểu dữ liệu đ−ợc l−u trữ kế tiếp nhau trong bộ nhớ. Việc truy nhập vào mảng bắt buộc phải thông qua chỉ số của mảng. Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 20 Chú ý: Trong C quy định chỉ số của 1 mảng bắt đầu từ 0. Và bạn đừng nghĩ mảng và biến là một (hi vọng Tôi nhắc điều này là không thừa).Nếu bạn lấy chỉ số là 1 thì khi truy nhập mảng xẽ truy nhập ra ngoài vùng nhớ của mảng=>kết quả sai. VD : for(i=1;i<n;i++) là sai tuy nhiên máy không báo lỗi. Còn for(i=0;i<n;i++) là đúng; Trong ngôn ngữ C có 2 loại mảng đó là mảng tĩnh và mảng động : 1. Mảng tĩnh: Là mảng cho ta biết tr−ớc số phần tử là bao nhiêu. VD: int a[10]; với khai báo này bạn đã có một mảng số nguyên có10 phần tử do chính bạn sẽ nhập vào trong ch−ơng trình bằng câu lệnh: for(i=0;i<10;i++) { printf(“nhập mảng a[%d]=”,i);scanf(“%d”,&a[i]); } Khi đó máy sẽ l−u trong bộ nhớ nh− sau:(giả thiết đây là 1 khoảng trong bộ nhớ). a[0] chiếm 8bit(=8 ô)=>……..vùng nhó liên tiếp………. =>a[9] chiếm 8bit. Công việc về mảng th−ờng có một số công việc sau: - Nhập mảng. - Sắp xếp mảng. - Tìm phần tử mảng. - Bổ sung phần tử cho mảng. - Loại phần tử mảng. - In mảng. VD: Lập một ch−ơng trình sử dụng 2 hàm . Hàm void làm nhiệm vụ sắp xếp mảng. Hàm main làm nhiệm vụ nhập mảng và hiển thị mảng. Nhận xét: Trong hàm void làm nhiệm vụ sắp xếp. Sau khi sắp xếp nó xẽ trả kết quả lại cho hàm main.Vì vậy để lấy đ−ợc kết quả này bắt buộc phải sử dụng con trỏ: Ch−ơng trình: #include #include Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 21 #include void sxep(float *p); float a[5]; void sxep(float *p) { int i,j; float tg; for(i=0;i<5;i++) for(j=i+1;j<=5;j++) if(p[i]>p[j]) { tg=p[i]; p[i]=p[j]; p[j]=tg; } } main() { int i; clrscr(); for(i=0;i<=5;i++) { printf("\n nhap phan tu thu: %d \n",i);scanf("%f",&a[i]); } else*/ printf("\n ket qua sap xep:\n"); sxep(a); for(i=0;i<5;i++) printf("\n a[i]=%4.2f",a[i]); getch(); } Sau khi chạy ch−ơng trình này bạn : 1.Thử bỏ * trong p xem điều gì xẽ sảy ra. 2.Trong lời gọi hàm bạn viết sxep(&a[i]) Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 22 3.Thử viết for(i=0;I<=5;i++) hoặc for(i=1;i<5;i++) 4. Thay lời gọi trên bằng for(i=0;i<5;i++) sxep(&a[i])đối chiếu với lời gọi trên và cho nhận xét. (Đây là hai cách gọi hàm đều đúng mà Tôi phải tìm hiểu mãi mới nhận ra đ−ợc quy luật của nó bạn phản thử mới nhớ đ−ợc). Chú ý: Bạn phải thử mới biết cái thú vị của nó.Nếu bạn không thử trong những h−ớng dẫn này, đến 1 lúc nào đó bạn làm ch−ơng trình nó không báo lỗi, mà lại cho kết quả sai, khi đó bạn xẽ tức điên lên cho mà xem. 2. Khai báo động: (mảng động) Cú pháp: a=(kiểu dữ liệu*)malloc(n*sizeof(a)); Trong đó: a : Là mảng phần tử(a phảI là con trỏ mảng). n : Là chỉ số lớn nhất của mảng. + Kiểu dữ liệu: Là kiểu dữ liệu của mảng. Thực ra phần này t−ơng tự với phần trên nó chỉ khác ở hai điểm sau: - Tên mảng phải là con trỏ, số l−ợng phần tử (n) phải nhập vào. - Không biết tr−ớc số l−ợng phần tử. Đây là lý do của việc phải sử dụng con trỏ,và vì n nhập từ bàn phím chứ không ấn định cho nên tiết kiệm đ−ợc bộ nhớ. - Tr−ớc khi sử dụng khai báo động phải khai báo th− viện là: #include Do vậy Tôi mong các bạn hiểu phần này thông qua ví dụ sau: VD: Viết CT nhập n phần tử từ bàn phím. CT sử dụng hai hàm: 1.Hàm float tbc(float p[],int n);để tính trung bình cộng của mảng 2.Hàm main() để nhập dữ liệu và in kết quả. Ch−ơng trình: #include #include #include float tbc(float p[],int n); float tbc(float p[],int n) { int i,d=0; float s; Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 23 for(i=0;i<n;i++) { s=s+p[i]; d=d+1; } return(s/d); } main() { int i,n; float *a; clrscr(); printf("\n nhap so phan tu cua mang n=:");scanf("%d",&n); a=(float*)malloc(n*sizeof(float)); for(i=0;i<n;i++) { printf("\n nhap phan tu thu: %d \n",i);scanf("%f",&a[i]); } tbc(a,n); printf("\n trung binh cong la %8.2f",tbc(a,n)); getch(); } Nhận xét: Bạn có thấy điều gì khác biệt trong phần này không . Trong phần con trỏ Tôi nói: Con trỏ chỉ l−u giữ địa chỉ chứ không l−u kết quả. Còn trong phần này thì nó lại l−u kết quả, lúc in ra vần hoàn toàn đúng: Tôi xin lấy tiếp ví dụ vui trong phần con trỏ nh− sau:Lần tr−ớc là chiếc va li của ng−ời bạn nhờ bạn cất giữ hộ. Còn bây giờ chiếc va li ấy là của bạn thì đ−ơng nhiên bạn phải biết trong đó có gì, và nó đ−ợc cất ở đâu. Có nghĩa là: Khi bạn nhập giá trị cho 1 biến a nào đó, bạn gán *(p)=a (tức là cất hộ),còn bạn nhập trực tiếp *p[i]=?(nh− ví dụ trên đã chứng minh) Thì thử hỏi hai điều này có khác nhau không. Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 24 Phần 2. cấu trúc I. Cách l−u trữ của máy tính: Phần này Tôi cứ loay hoay từ đầu CT đến giờ xem giới thiệu vào chỗ nào là hợp lý nhất, bạn biết vì sao không: Vì nó cực kì quan trọng, vì Tôi sợ bạn quên mất. Nếu bạn quên thì bạn mất đi cơ hội biết đ−ợc máy tính làm gì, và làm nh− thế nào, khi bạn tác động vào nó. Nếu không hiểu đ−ơng nhiên những việc bạn làm chỉ là mò mẫm mà thôi. Bạn chú ý câu nói này: Trong ngôn ngữ C quy định l−u trữ trong máy tính bằng 2 dạng đó là: L−u trữ kế tiếp và l−u trữ móc nối đối với dữ liệu có cấu trúc. Mô hình l−u trữ : 1.Dạng kế tiếp: Ví dụ có khai báo trong phần II(xem ở d−ới). Đầu ch−ơng trình bạn: Viết khai báo 1 biến cấu trúc tên là danh sách và 1 mảng cấu trúc tên là ds[10] . a. Với biến cấu trúc tên là danh sách: =>giả thiết các ô nhớ có thứ tự từ trái qua phải và từ trên xuống. ô 1 2 3 .. .. .. 150 ô 160 - 8 ô cho 1 kí tự=>mảng char họtên[20] cần 160 kí tự => l−u trữ xong tr−ờng họtên[20]. - T−ơng tự tiếp theo cần 160 kí tự cho tr−ờng quêquán[20]=>bắt đầu từ ô 161=>hết ô 320. ô 161 162 .. .. .. .. 319 320 - Tiếp theo đến tr−ờng ngày sinh có kiểu là int => cần 2 byte để l−u trữ=>bắt đầu từ ô 321=>hết ô thứ 336. 321 ô 336 ệ Hết 1 biến cấu trúc. b. Với mảng cấu trúc. Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 25 T−ơng tự trên bạn có thể suy ra. Một biến cấu trúc cần 336 ô(=336 bit) để l−u trữ, thì mảng ds[10] cần 10*336 ô = 3360 ô để l−u trữ, các ô này kế tiếp nhau. c.Kết luận: Bạn phải phân biệt rõ đâu là biến cấu trúc, đâu là mảng cấu trúc. Vì điều này còn liên quan tới việc sử dụng con trỏ. 2. L−u trữ móc nối : Khác với kế tiếp. L−u trữ móc nối thì các phần tử nằm không kế tiếp nhau trong bộ nhớ, nh−ng chúng đ−ợc liên kết với nhau thông qua con trỏ giữ địa chỉ của phần tử t−ơng ứng:Tức là mỗi một phần tử có 2 tr−ờng một tr−ờng để chứa dữ liệu, còn 1 tr−ờng để chứa con trỏ liên kết với phần tử kề sau nó. Mô hình chung: (giả thiết mỗi ô là 1 phần tử cấu trúc chứ không phải là 1 bit nh− trên.). + Với 1 phần tử có cấu trúc:=> Phần chứa dữ liệu Link=null Phần chứa dữ liệu : Bao gồm các tr−ờng kế tiếp nhau. Phần link : Là con trỏ kết nối phần tử liền sau nó. Vì có 1 phần tử nên nó không giữ địa chỉ của phần tử nào nữa=> chỉ vào null=0(rỗng); Với lớn hơn 1 phần tử có cấu trúc => (giả thiết đây là các vùng nhớ liên tục trong bộ nhớ.) Bắt đầu vùng nhớ => Phần tử 1 Link 1 Vùng nhớ không liên quan Vùng nhớ không liên quan p.tử 2 Link 2 Không liên quan ...... ........... Phần tử 3 Link 3 K.L.Q Phần tử 4 Link 4 KLQ Phần tử 5 Link 5 KLQ =>kết thúc vùng nhớ. Trong vùng nhớ trên có 5 phần tử có cấu trúc xen kẽ với các vùng nhớ khác, nh−ng chúng đ−ợc liên kết nhau bởi phần link: Link1 chứa phần tử 2,link 2 chứa phần tử 3..... cuối cùng là link 5, nó không chứa gì cả và nó chỉ vào null. Chú ý: Các tr−ờng của 1 phần tử cũng đ−ợc cấp phát liên tiếp nhau trong bộ nhớ.Tức là phải phân biệt rõ các tr−ờng và các phần tử. Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 26 II. Định nghĩa và khai báo một cấu trúc theo kiểu l−u trữ kế tiếp . A. Cấu trúc không lồng nhau : 1. Định nghĩa : Cấu trúc lầ tập hợp các biến, các mảng đ−ợc biểu diễn thông qua một tên. 2. Khai báo theo kiểu l−u trữ kế tiếp: Có rất nhiều cách khai báo khác nhau cho một cấu trúc. Chính vì vậy mà chúng ta khi bắt đầu tiếp xúc với phần này th−ờng rơi vào hai tr−ờng hợp : Một là khó nhớ, hai là khai báo lộn sộn không theo một cấu trúc nào cả. Để khắc phục tình trạng này Tôi khuyên bạn nên làm quen với một cách khai báo duy nhất sau, và đây cũng là cách khai báo có nhiều tính năng nhất: Đây là cách khai báo theo kiểu tự định nghĩa: Cú pháp: typedef struct { kiểu tr−ờng Tên tr−ờng 1; kiểu tr−ờng Tên tr−ờng 2 ............................... kiểu tr−ờng Tên tr−ờng n; } Tên cấu trúc; trong đó: - Tr−ờng 1,2...n: Là 1 cái tên do bạn đặt sao cho ý nghĩa gắn liền với kiểu tr−ờng. - Kiểu tr−ờng th−ờng là các kiểu int, float, char. - Tên cấu trúc: là cái tên cũng do bạn tự đặt sao cho có ý nghĩa. Tên này sẽ sử dụng để làm kiểu dữ liệu trong CT với 3 kiểu dữ liệu trên. Chú ý tên tr−ờng th−ờng không có kí tự trống VD: họ tên:là sai. Họtên:là đúng. VD: khai báo một cấu trúc gồm 3 tr−ờng họ tên, ngày sinh và quê quán: Typedef struct { Char họtên[20]; /*mảmg kí tự có 20 kí tự*/ Char quequan[20]; /*t−ơng tự trên*/ Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 27 int ngàysinh; } sinhviên; Với khai báo trên bạn có một cấu trúc tên là sinh viên có 3 tr−ờng là họ tên, quê quán và ngày sinh. 3. Cách truy nhập tới cấu trúc l−u trữ kế tiếp : Khi bạn có khai báo trên thì trong ch−ơng trình bạn có thể dùng nó nh− một kiểu dữ liệu thông dụng VD: Đầu ch−ơng trình sau dấu { bạn viết: danhsách:sinhviên, sv[10]; Với khai báo này bạn có 1 biến cấu trúc tên là danhsách có 10 phần tử và có kiểu là sinhviên. Mảng cấu trúc có kiểu là danh sách. Bạn nên nhớ là trong C phân biệt chữ hoa và th−ờng, cho nên khi đặt tên bạn phải thống nhất. Tốt nhất bạn nên viết th−ờng cả, và thêm 1 chú ý nữa là đặt tên nên ngắn gọn, dễ nhớ VD:ht họtên.qqquêquán... Cách truy nhập: Có một cách truy nhập duy nhất đó là: Tên biến cấu trúc.tên tr−ờng; (ngăn cách giữa tên biến và tr−ờng là dấu (.)). Còn một kiểu cấu trúc lồng nữa Tôi xẽ giới thiệu sau, vì Tôi muốn bạn làm quen dần dần, để chánh sự choáng ngợp kiến thức. 4. Ví dụ : Phần I Tôi đã cho bạn công cụ làm việc đó là các vòng lặp. Và phần II này bạn đã có nguyên liệu(là dữ liệu đầu vào). Bây giờ là lúc bạn có thể chế biến nó thành sản phẩm nh− ý muốn. Tôi xin làm mọt vài ví dụ mẫu, từ đó bạn có thể phát triển thành những gì mà bạn muốn. Chú ý: Khi nhập dữ liệu kiểu char, bạn nên dùng câu lệnh gets(); để nhập , tr−ớckhi nhập bạn phải xóa bộ đệm bàn phím bằng câu lệnh: fflush(stdin); Còn khi in thì vẫn dùng printf nh− bình th−ờng. VD1: Hãy nhập vào một danh sách cán bộ có 10 sinh viên mỗi cán bộ gồm 4 tr−ờng : họ tên, công, bậc l−ơng, và l−ơng(l−ơng = bậc l−ơng*công) .Sau khi nhập xong thì cuối cùng cho hiện danh sách đó ra màn hình. Với 1 chú ý là bạn phải dùng 1 biến trung gian để nhập giá trị cho các biến có kiểu là số thực nếu không khi nhập máy sẽ bị treo. Ch−ơng trình : #include Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 28 #include typedef struct { char ht[20]; int cong; float luong; float bluong; }cb; main() { clrscr(); cb a[10]; float l,bl; int c, i; for(i=0;i<3;i++) { printf(" nhap ho ten nguoi thu: %d \n",i); fflush(stdin); gets(a[i].ht); printf(" nhap ngay cong:\n"); scanf("%d",&c); a[i].cong=c; printf(" nhap bac luong:\n");scanf("%f",&bl); a[i].bluong=bl; a[i].luong=a[i].cong*a[i].bluong; } printf("\n ***************KET QUA*******************"); for(i=0;i<3;i++) { printf("\n ho ten %d nguoi thu: %s",i, a[i].ht ); printf("\n ngay cong: %4d",a[i].cong); printf("\n bac luong:%4.2f",a[i].bluong); printf("\n luong:% 4.2f",a[i].luong); } getch(); } Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 29 Có lẽ ch−ơng trình này không cần giải thích gì hơn, vì nó cũng khá đơn giản. Nh−ng một điều chú ý cho là: Khi bạn nhập giá trị cho biến thực bạn không đ−ợc nhập trực tiếp, mà phải nhập thông qua 1 biến trung gian vì nếu nhập trực tiếp nh− vậy máy xẽ bị treo. VD2: Ví dụ có kết hợp với việc sử dụng hàm và con trỏ: Hãy nhập vào từ bàn phím số liệu cho n sinh viên. Cấu trúc mỗi sinh viên gồm 3 tr−ờng là: họ tên, quê quán và tuổi. Yêu cầu sử dụng 3 hàm Hàm void sxếp(sinhviên *p; int n); để sắp xếp tăng dần theo thứ tự tăng dần của tuổi.Vì hàm này sau khi làm nhiệm vụ sắp xếp nó phải trả lại kết quả sắp xếp cho hàm main(), cho nên đối phải là con trỏ. Hàm void in(sinhviên p;int n);để in kết quả sau khi sắp xếp. Hàm main() là hàm chính để nhập dữ liệu và đ−a ra lời gọi đến các hàm khác khi cần thiết. Ch−ơng trình: #include #include #include typedef struct { char ht[40]; char qq[40] ; int t; }sinhvien; void sxep(sinhvien *p,int n); void in(sinhvien p); void sxep(sinhvien *p,int n) { int i,j; sinhvien tg; for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(p[i].t>p[j].t) { Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 30 tg=p[i]; p[i]=p[j]; p[j]=tg; } } void in(sinhvien p) { printf("\n ten nguoi: 5%s",p.ht); printf("\n que quan %s: ",p.qq); printf("\n tuoi:%d",p.t); } main() { clrscr(); sinhvien *p,ds[20]; int i,j,n; printf("nhap so sinh vien n:="); scanf("%d",&n); for(i=0;i<n;i++) { fflush(stdin); printf("\n nhap ten nguoi: %d",i);gets(ds[i].ht); printf("\n nhap que quan : "); gets(ds[i].qq); printf("\n nhap tuoi:"); scanf("%d",&ds[i].t); } sxep(ds,n); /* lời gọi hàm*/ for(i=0;i<n;i++) in(ds[i]); /* lời gọi hàm*/ getch(); } Giải thích quá trình làm việc: Sau khi soát lỗi xong, máy vào hàm chính bắt nhập vào n và thực hiện việc nhập dữ liệu vào biến cấu trúc có tên là danh sách. Nhập xong nó thấy có lời gọi hàm sxếp, biến t−ơng ứng là ds và nó đem biến này gán vào đối t−ơng ứng là con trỏ *p. Con trỏ p nhận địa chỉ của ds và thực hiện công việc sắp xếp, Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 31 sau khi sắp xếp nó lại chả kết quả về cho đối t−ơng ứng của nó là ds, tiếp theo nó gặp vòng for và cứ 1 giá trị của i nó lại gọi hàm in(ds[i]) một lần, và thực hiện việc in. Vì nó gửi(truyền) dữ liệu từ ds cho p, và p làm nhiệm vụ in ngay tại hàm in(p) mà không đem kết quả trở lại, cho nên ta không cần dùng con trỏ *p Chú ý: Có 2 cách gọi hàm đó là: 1. Truyền 1 lúc cả danh sách: khi đó lời gọi hàm t−ơng ứng là (lấy trên làm ví dụ): sxếp(ds); /*trong hàm in có vòng for*/ 2. Truyền từng phần tử 1: khi đó lời gọi là: for(i=0;i<n;i++) /* trong hàm in(ds) không có vòng for*/ in(ds[i]); /* lời gọi hàm*/ Bạn nên cố gắng phân biệt hai tr−ờng hợp này, vì nó thể hiện tính đúng dắn của thuật toán. Chứng minh cụ thể bằng ví dụ trên nếu bạn đ−a ra lời gọi hàm sxếp là: for(i=0;i<n;i++) Sxếp(ds[i]); vì truyền từng phần tử 1 nên nó lấy gì ra để so sánh. Nh−ng đối với hàm in(ds); thì khác, vì nó truyền 1 lần 1 phần tử, hay truyền 1 lần nhiều phần tử cũng đ−ợc. Đôi lúc bạn phải tính đến sự thuận tiện của giải thuật. Vì giả sử Tôi kết hợp hàm tìm(ds) (chỉ tìm 1 ng−ời) với hàm in(ds), khi đó bạn dùng lời gọi hàm in theo cách 1 là vô cùng rở hơi (bạn thử nghĩ mà xem tại sao vậy?) Kết luận: Trong phần này bạn chỉ cần hiểu 2 điều sau là bạn đã nắm khá vững vấn đề: 1. Bạn hiểu đ−ợc việc l−u trữ của máy tính => có mấy cách và phần này đang áp dụng cách nào.? 2. Bạn hiểu việc sử dụng hàm và con trỏ nh− thế nào?. Khi nào thì dùng con trỏ, và việc truyền giá trị cho đối t−ơng ứng ra sao?. Phần này Tôi không cho bài tập vì nếu nh− bạn hiểu đ−ợc các ví dụ trên thì bạn có thể tự ra đề cho mình đ−ợc. Hoặc là trong sách có rất nhiều mong tự tham khảo. Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 32 B. Cấu trúc lồng cấu trúc: 1. Khai báo cấu trúc lồng: Để cho rễ hiểu Tôi khai báo thông qua 1 ví dụ sau: có khai báo Typedef struct { int ns;(ngày sinh) int ts;(tháng sinh) int ns;(năm sinh) } ngay; Typedef struct { Char họtên[20]; /*mảmg kí tự có 20 kí tự*/ Char quequan[20]; /*t−ơng tự trên*/ ngay ngàysinh; }sinhviên; Khi đó việc truy nhập tới cấu trúc của tr−ờng ngày nh− sau: (với giả sử biến ds[i] là biến cấu trúc kiểu sinhviên) ds[i].ngàysinh.ngày Còn mọi công việc giống hệt nh− cấu trúc không lồng nhau. Tuy vậy Tôi vẫn lấy ví dụ để bạn tham khảo thêm. VD: typedef struct { int ngay,thang,nam; }ngsinh; typedef struct { char ht[25]; char qq[25]; ngsinh ns; int tuoi; }hocsinh; void vaosl(hocsinh *p,int n); Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 33 void tim(hocsinh *p,int n); void in(hocsinh p); void vaosl(hocsinh *p,int n) { int i; for(i=0;i<n;i++) { fflush(stdin); printf("\n nhap ho ten nguoi thu %d: ",i);fflush(stdin); gets(p[i].ht); printf("\n nhap que quan:");fflush(stdin); gets(p[i].qq); printf("\n ngay sinh:");scanf("%d",&p[i].ns.ngay); printf("\n thang sinh:");scanf("%d",&p[i].ns.thang); printf("\n nam sinh:");scanf("%d",&p[i].ns.nam); printf("\n nhap tuoi:");scanf("%d",&p[i].tuoi); } } void tim(hocsinh *p,int n) { int i; char ht[25],ch; while(1) { tt:printf("nhap nguoi can tim:");fflush(stdin); gets(ht); if(ht[0]==0) break; for(i=0;i<n;i++) if(strcmp(ht,p[i].ht)==0) in(ds[i]); /*lời gọi hàm in(p)*/ ch=getch(); if(ch=='c'||ch=='C')goto tt; } } void in(hocsinh p) Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 34 { printf("*****************Ket qua chay********************\n"); printf("\n Ho ten nguoi thu %d: %s ",i+1,p[i].ht); printf("\n Que quan: %s ",p[i].qq); printf("\n Ngay sinh : %d ",p[i].ns.ngay); printf("\n Thang sinh : %d ",p[i].ns.thang); printf("\n Nam sinh : %d ",p[i].ns.nam); printf("\n Tuoi : %d ",p[i].tuoi); printf("__________________________________________\n"); } main() { hocsinh ds[10]; int i,n; clrscr(); printf(“nhap n:=”);scanf(“%d”,&n); vaosl(ds,n); printf("*********Ket qua tim**********************\n"); tim(ds,n); for(i=0;i<n;i++) in(ds[i]); getch(); } C. Khai báo động: Khai báo động cũng là 1 hình thức l−u trữ kế tiếp. Nh−ng nó khác khai báo tĩnh ở trên là ở chỗ cần đến đâu khai báo đến đấy, với mục đích là tiết kiệm bộ nhớ. Nếu trong khai báo tĩnh là: sinhviên ds[20]; tức là bạn đã ấn định 20 chỗ để l−u trữ 1 danh sách có cấu trúc kiểu sinhviên. Còn trong khai báo động bạn không làm nh− vậy, mà bạn phải khai báo thêm 1 con trỏ kiểu sinhviên để nhập dữ liệu. Cú pháp : Tên con trỏ =(tên kiểu cấu trúc*)malloc(n+1)*sizeof(tên kiểu cấu trúc); trong đó: Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 35 - Tên con trỏ : Là tên do bạn đặt nó có kiểu là kiểu cấu trúc. - n : Là số phần tử của cấu trúc do bạn nhập vào. - Kiểu cấu trúc : Là kiểu mà bạn đã định nghĩa nó gồm các tr−ờng. VD: Có khai báo: typedef struct { char ht[25]; char qq[25]; int tuổi ; }hocsinh; Bạn hãy nhập số ng−ời khoảng 5 ng−ời vào và cho hiển thị lên màn hình số ng−ời vừa nhập. #include #include #include #include typedef struct { char ht[20]; char qq[20]; int tuoi; }sinhvien; main() { int songuoi,i; sinhvien *p; tt:printf("nhap so nguoi:");scanf("%d",&songuoi); if (songuoi>5) goto tt; /*nếu số ng−ời nhập >5 thì quay lại chỗ tt: để nhập lại*/ p=(sinhvien*)malloc((songuoi+1)*sizeof(sinhvien)); for(i=0;i<songuoi;i++) { fflush(stdin); printf("\n nhap ho ten:");gets(p[i].ht); fflush(stdin); Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 36 printf("\n nhap que quan:");gets(p[i].qq); printf("\n nhap tuoi:");scanf("%d",&p[i].tuoi); } for(i=0;i<songuoi;i++) { printf("\n ho ten:%s",p[i].ht); printf("\n que quan:%s",p[i].qq); printf("\n tuoi:%d",p[i].tuoi); } getch(); } Qua ví dụ trên cho thấy khai báo động và khai báo tĩnh chỉ khác nhau hai điểm sau: - Nhập dữ liệu cho danh sách trong khai báo động bắt buộc phải nhập bằng con trỏ. Còn trong khai báo tĩnh thì có thể là con trỏ hoặc không cần con trỏ. - Nếu là khai báo động thì có cú pháp khai báo động riêng. Còn sau đó tất cả mọi công việc và cách làm đều nh− nhau. Bây giờ Tôi xẽ lấy một ví dụ tổng hợp các công việc để bạn đ−ợc rõ hơn. Nếu bạn ch−a thành thạo việc truyền đối trong lời gọi hàm, thì hãy quan sát thật kĩ việc truyền đối trong ch−ơng trình này. Với một chú ý là tất cả các tham biến ,tham trị trong mỗi hàm là hoàn toàn khác nhau mặc dù chúng có tên giống nhau. Điển hình là biến con trỏ *p1 trong ch−ơng trình này. VD: Nhập vào một danh sách sinh viên có số ng−ời do bạn quyết định. Yêu cầu mỗi sinh viên đ−ợc nhập có đủ các thông tin :họ tên, quê quán và tuổi. Ch−ơng trình : #include #include #include #include typedef struct { char ht[20]; char qq[20]; Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 37 int tuoi; }sinhvien; void nhap(sinhvien *p1,int songuoi); void in(sinhvien p1); void sxep(sinhvien *p1,int songuoi); void tim(sinhvien *p1,int songuoi); void nhap(sinhvien *p1,int songuoi) { int i; for(i=0;i<songuoi;i++) { fflush(stdin); printf("\n nhap ho ten:");gets(p1[i].ht); fflush(stdin); printf("\n nhap que quan:");gets(p1[i].qq); printf("\n nhap tuoi:");scanf("%d",&p1[i].tuoi); } } void tim(sinhvien *p1,int songuoi) { int i; char hoten[20]; while(1) { fflush(stdin); printf("\n nhap ho ten:"); gets(hoten); if(hoten[0]==0) break; for(i=0;i<songuoi;i++) if(strcmp(p1[i].ht,hoten)==0) in(p1[i]); } } void sxep(sinhvien *p1,int songuoi) { Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 38 int i,j; sinhvien tg; for(i=0;i<songuoi-1;i++) for(j=i+1;j<songuoi;j++) if(p1[i].tuoi>p1[j].tuoi) { tg=p1[i]; p1[i]=p1[j]; p1[j]=tg; } } void in(sinhvien p1) { printf("\n ho ten:%s",p1.ht); printf("\n que quan:%s",p1.qq); printf("\n tuoi:%d",p1.tuoi); } main() { clrscr(); int songuoi,i; sinhvien *p; tt:printf("nhap so nguoi:");scanf("%d",&songuoi); if (songuoi>5) goto tt; p=(sinhvien*)malloc((songuoi+1)*sizeof(sinhvien)); nhap(p,songuoi); sxep(p,songuoi); for(i=0;i<songuoi;i++) in(p[i]); tim(p,songuoi); getch(); } Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 39 III. Cấu trúc l−u trữ móc nối : Đây là phần (theo Tôi đánh giá) khó nhất trong ch−ơng trình C vì nó rất phức tạp từ việc khai báo đến sử dụng. Phần này để hiểu đ−ợc yêu cầu bạn phải có một chút kiến thức môn giải thuật, nói vậy không phải để bạn nản lòng bạn phải biết đây là phần cực kì quan trọng và bao giờ nó cũng chiếm 50% số điểm của bài thi. Nh−ng không sao chúng ta xẽ cố gắng phơi bày bản chất của nó thông qua lời giải thích và những ví dụ cụ thể. 1. Định nghĩa cho một cấu trúc móc nối : Cấu trúc móc nối là cấu trúc có các tr−ờng thông tin và ít nhất một con trỏ trỏ vào chính nó. Và đ−ợc khai báo nh− sau: typedef struct (tên cấu trúc) { kiểu tr−ờng 1 tên tr−ờng; kiểu tr−ờng 2 tên tr−ờng; kiểu tr−ờng 3 tên tr−ờng; ........... struct tên cấu trúc *tên con trỏ; } tên cấu trúc; VD: typedef struct sinhvien { char ht[20]; char qq[20]; int tuoi; struct sinhvien *tiep; /*nhất thiết phải có */ }sinhvien; Sau Khai báo này bạn đã có một kiểu dữ liệu có tên là tên cấu trúc.Các tr−ờng thì hoàn toàn giống các tr−ờng trong cấu trúc l−u trữ kế tiếp. 2. Nhập dữ liệu cho một cấu trúc móc nối : Giả thiết bạn có khai báo sau: typedef struct sinhvien { Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 40 char ht[20]; char qq[20]; int tuoi; struct sinhvien *tiep; }sinhvien; Khai báo trên cho biết bạn. Có một cấu trúc sinhviên gồm 3 tr−ờng, và một con trỏ tên là tiếp. Bây giờ bạn có thể dùng cấu trúc này để khai báo các biến cấu trúc kiểu sinh viên. Với một chú ý là việc nhập dữ liệu hay bất cứ một thao tác nào trên cấu trúc đều phải sử dụng con trỏ. Khác với l−u trữ mảng, l−u trữ móc nối luôn có 1 con trỏ trỏ đến đầu danh sách, mục đích để dữ đầu mối, mỗi khi bạn thao tác trên cấu trúc đều bắt đầu từ đầu mối này.Nếu muốn duyệt qua danh sách bạn phải sử dụng thêm 1 con trỏ khác để duyệt. Chú ý Đây cũng là 1 hình thức khai báo động, cho nên đầu ch−ơng trình bạn phải khai báo th− viện “alloc.h”. Cú pháp khai báo động nh− sau: pd=(tên cấu trúc*)malloc(sizeof(tên cấu trúc)); Trong đó pd là con trỏ trỏ đầu danh sách. Nếu nh− từ phần tử thứ hai trở đi bạn phải dùng con trỏ khác để nhập dữ liệu. Tôi xẽ mô tả bằng lời quá trình nhập dữ liệu và sau đó là ví dụ điển hình: Với một cấu trúc đ−ợc định nghĩa nh− trên bạn cần hai con trỏ để phục vụ cho việc nhập dữ liệu Con trỏ 1 luôn nắm đầu danh sách. Nh−ng lúc đầu danh sách còn rỗng, vì vậy nó làm nhiệm vụ nhập phần tử đầu tiên. Sau đó nó nh−ờng lại quyền này cho con trỏ thứ 2 con trỏ này tiếp tục làm nhiệm vụ đ−ợc giao đồng thời nắm dữ cuối danh sách để nhập tiếp dữ liệu, đến khi thôi thì nó cũng coi nh− hết nhiệm vụ và đ−ơng nhiên nó không còn liên quan gì tới danh sách này nữa.Sau khi nhập xong bạn đã có 1 danh sách: Có con trỏ pd đang nắm đầu danh sách. VD: Hãy nhập từ bàn phím danh sách một số sinh viên gồm các thông tin sau: họ tên , quê quán, và tuổi. Ch−ơng trình : #include Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 41 #include #include #include typedef struct sinhvien { char ht[20]; char qq[20]; int tuoi; struct sinhvien *tiep; }sinhvien; main () { char hoten[20]; /* bắt buộc phải khai báo thêm vì bạn dùng vòng while(1)*/ sinhvien *pd,*p; /* hai biến trỏ trong thân hàm*/ pd=NULL; /*đầu tiên pd không trỏ vào đâu cả*/ while(1) /* gọi là vòng lặp vô tận để thoát khỏi vòng này phụ thuộc vào câu lệnh break */ { fflush(stdin); printf("nhap ho ten:");gets(hoten); if(hoten[0]==0)break; /* nếu hoten=0 thì thoát khỏi vòng lặp */ if(pd==NULL) /* nếu là phần tử đầu tiên của danh sách*/ { pd=(sinhvien*)malloc(sizeof(sinhvien)); /*xin bộ nhớ cho phần tử đầu tiên */ p1=pd; /* gán 1 con trỏ khác = pd để nó làm tiếp nhiệm vụ nhập */ } else { /* từ lần nhập thứ 2 trở đi */ p1->tiep=(sinhvien*)malloc(sizeof(sinhvien)); p1=p1->tiep; /*p1->tiêp link(p1) trong giải thuật */ } strcpy(p1->ht,hoten); /* câu lệnh nhập tr−ờng ht vào trong danh sách nó khác câu lệnh nhập qq ở d−ới vì nó đã đ−ợc nhập vào máy rồi*/ printf("\n nhap que quan:");gets(p1->qq); Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 42 printf("\n nhap tuoi:");scanf("%d",&(p1->tuoi)); p1->tiep=NULL; /*sau mỗi vòng lặp ấn định không còn phần tử tiếp theo vì có thể quay lại vòng khác bạn không nhập nữa*/ } } Chú ý: Bạn nên làm quen với những quy định sau: 1. p=NULL: tức là không trỏ vào đâu cả.Con trỏ chỉ có thể nắm địa chỉ của 1 phần tử trong danh sách. 2. p=p->tiêp p=link(p) a[i] =>a[i+1] :phần tử tiếp theo của danh sách. Nh−ng với chú ý p->tiếp bao giờ cũng chạy tr−ớc p một b−ớc. 3. p->tiếp=NULL:không trỏ vào đâu nữa và cũng là phần tử cuối cùng của 1 danh sách. 4. strcpy(p->ht,hoten); đẩy tr−ờng họtên vào trong danh sách bắt buộc hotên đã đ−ợc nhập vào trong máy rồi. Chú ý tr−ờng ht[20] hoàn toàn khác với hoten[20]. 3.Thao tác trên cấu trúc: Có rất nhiều thao tác trên danh sách ví dụ nh− hiển thị danh sách lên màn hình,tìm tên một ng−ời nào đó có hoặc không có trong danh sách, loại bỏ một ng−ời ra khỏi danh sách hoặc bổ sung......Nh−ng ở tr−ớc hết bạn hãy làm quen với hai công 3 công việc đơn giản nhất đó là nhập, hiển thị và tìm ng−ời nào đó có trong danh sách. Còn nếu bạn muốn tìm hiểu sâu hơn thì hãy xem ví dụ tổng hợp cuối phần này. ở trên Tôi đã giới thiệu cách nhập 1 danh sách bây giờ Tôi giới thiệu tiếp việc in 1 danh sách và tìm ng−ời. a. Hiển thị danh sách: Sau khi bạn nhập vào 1 danh sách và bạn ấn định 1 con trỏ(có tên) dữ đầu danh sách khi đó bạn muốn truy nhập vào danh sách đó bạn phải dùng 1 con trỏ khác có cùng kiểu dữ liệu gán bằng nó để làm nhiệm vụ(nếu không làm nh− vậy bạn sẽ mất đầu mối tức là mất địa chỉ), bằng câu lệnh: VD p1=pd; Và phép duyệt danh sách t−ơng ứng là: p1=p->tiếp xẽ cho phép bạn đi từ đầu đến cuối danh sách. Cùng với ví dụ trên Tôi xẽ làm tiếp phần còn lại của công việc là hiển thị ra màn hình. Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 43 Tiếp ví dụ trên: p1=pd; while(p1!=NULL) { printf("\n in ho ten:%s",p1->ht); printf("\n in que quan:%s",p1->qq); printf("\n in tuoi:%d",p1->tuoi); p1=p1->tiep; } } Cứ 1 vòng lặp thì in đ−ợc 1 ng−ời vì con trỏ chỉ dữ địa chỉ đ−ợc 1 ng−ời. Trong while() bạn thử thay =p1->tiep!=NULL xem điều gì xẽ sảy ra. b. Tìm ng−ời khi biết tên: Đầu tiên bạn phải nhập tên của 1 ng−ời bất kì sau đó kiểm tra xem ng−ời tên này có trong danh sách không bằng câu lệnh: strcmp(p->ht,hoten)= =0; Nếu đúng =0 thì là có trong danh sách còn ng−ợc lại thì không. Sau đó in ng−ời tìm đ−ợc ra. Vẫn tiếp ví dụ trên Tôi làm tiếp công việc tìm ng−ời trên danh sách đó: tt:printf("\n nhap ten nguoi can tim:");fflush(stdin);gets(hoten); if(hoten[0]==0) goto tt; p1=pd; while(p1!=NULL) { if(strcmp(hoten,p1->ht)==0) { printf("\n nguoi can tim la:%s",p1->ht); printf("\n que quan:%s",p1->qq); printf("\n tuoi:%d",p1->tuoi); } p1=p1->tiep; } Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 44 4. Sử dụng hàm: Để tiện so sánh Tôi chia ch−ơng trình trên thành 3 hàm với 3 công việc khác nhau là : Hàm void nhap() để nhập dữ liệu. Hàm void in() để in dữ liệu. Hàm void tim() để tìm và in thông tin ng−ời tìm đ−ợc. Ch−ơng trình : Đối với loại cấu trúc này th−ờng sử dụng hàm không đối. Nh−ng vì sao nó lại làm thay đổi đ−ợc kết quả: Vì chúng ta chỉ thao tác trên cùng 1 cấu trúc. Pd là biến toàn cục, trong mỗi hàm bất kì nào đó bạn đều gán: Ví dụ p1=pd, thì danh sách này đ−ợc đem ra làm việc và bạn có thể bỏ đi 1 phần tử nào đó(ví dụ phép loại bỏ).Khi đó danh sách ban đầu đã bị thay đổi không còn nguyên vẹn nh− ban đầu. Với *p1,*pd là hai biến toàn cục tức là sử dụng cho mọi hàm đ−ợc khai báo ở đầu ch−ơng trình. Xem CT ở d−ới. #include #include #include #include typedef struct sinhvien { char ht[20]; char qq[20]; int tuoi; struct sinhvien *tiep; }sinhvien; void nhap(); void tim(); void in(); sinhvien *pd,*p1; void nhap() { char hoten[20]; pd=NULL; Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 45 while(1) { fflush(stdin); printf("nhap ho ten:");gets(hoten); if(hoten[0]==0)break; if(pd==NULL) { pd=(sinhvien*)malloc(sizeof(sinhvien)); p1=pd; } else { p1->tiep=(sinhvien*)malloc(sizeof(sinhvien)); p1=p1->tiep; } strcpy(p1->ht,hoten); printf("\n nhap que quan:");gets(p1->qq); printf("\n nhap tuoi:");scanf("%d",&(p1->tuoi)); p1->tiep=NULL; } } void in() { p1=pd; while(p1!=NULL) { printf("\n in ho ten:%s",p1->ht); printf("\n in que quan:%s",p1->qq); printf("\n in tuoi:%d",p1->tuoi); p1=p1->tiep; } } void tim() Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 46 { char hoten[20]; tt:printf("\n nhap ten nguoi can tim:");fflush(stdin);gets(hoten); if(hoten[0]==0) goto tt; p1=pd; while(p1!=NULL) { if(strcmp(hoten,p1->ht)==0) { printf("\n nguoi can tim la:%s",p1->ht); printf("\n que quan:%s",p1->qq); printf("\n tuoi:%d",p1->tuoi); } p1=p1->tiep; } } main() { clrscr(); nhap(); in(); tim(); getch(); } VD2: Cũng với vấu trúc trên Tôi thiết kế 1 ch−ơng trình sử dụng ba hàm Hàm void làm nhiệm vụ loại bỏ 1 mg−ời có tên trong danh sách. Trình tự làm việc của hàm này nh− sau : Đầu tiên hàm bắt nhập vào một cái tên bất kì, sau đó nó so sánh (dùng 1con trỏ p1 để đi tìm. Cùng với p1 có 1 con trỏ *ps chạy liền sau nó, con trỏ này xẽ làm nhiệm vụ liên kết ng−ời đứng tr−ớc và ng−ời đứng sau p1 nếu p1 bị loại) xem có tên nào trùng với tên này không. Nếu thì nó xẽ in là không có. Ng−ợc lại, nếu nó kiểm tra đúng là có thì nó dừng lại nắm lấy điạ chỉ tại ng−ời đó và bắt đầu thực hiện loại, bằng cách lấy liên kết của ng−ời tr−ớc nó trỏ vào ng−ời sau Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 47 nó, chứ không trỏ vào nó nữa. Thoát khỏi ch−ơng trình nó đã làm mất liên kết của 1 phần tử trong danh sách . Hàm main làm nhiệm vụ nhập dữ liệu và gọi hàm. Hàm in kết quả nhập và kết quả tìm. Ch−ơng trình : #include #include #include #include typedef struct sinhvien { char ht[20]; char qq[20]; int tuoi; struct sinhvien *tiep; }sinhvien; void loai(); void in(); sinhviên *pd,*p1; /*hai biến toàn cục*/ void loai() /*bắt đầu hàm loại */ { char hoten[20]; int kq; sinhvien *ps; tt:printf("\n nhap ten nguoi bi loai:");fflush(stdin); scanf("%s",&hoten); if(hoten[0]==0) goto tt; if((hoten!=0)&&(pd!=0)) { p1=pd; ps=p1; while(kq= strcmp(p1->ht,hoten)!=0) /*nếu biến kq còn khác 0 còn làm*/ { Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 48 ps=p1; p1=p1->tiep; } /* thoát khỏi vòng lặp p1 đang trỏ đến ng−ời cần tìm,*ps trỏ ng−ời liền sau nó chú ý liền sau tức là đã đ−ợc duyệt rồi*/ if(kq==0) { if(strcmp(pd->ht,p1->ht)==0) { pd=pd->tiep; free(p1); return; } else { ps->tiep=p1->tiep; free(p1); } } else { printf("\n khong co nhuoi can tim:"); getch(); } } } void in()/*hàm in*/ { p1=pd; while(p1!=NULL) { printf("\n in ho ten:%s",p1->ht); printf("\n in que quan:%s",p1->qq); printf("\n in tuoi:%d",p1->tuoi); Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 49 p1=p1->tiep; } } main() { char hoten[20]; pd=NULL; while(1) { fflush(stdin); printf("nhap ho ten:");gets(hoten); if(hoten[0]==0)break; if(pd==NULL) { pd=(sinhvien*)malloc(sizeof(sinhvien)); p1=pd; } else { p1->tiep=(sinhvien*)malloc(sizeof(sinhvien)); p1=p1->tiep; } strcpy(p1->ht,hoten); printf("\n nhap que quan:");gets(p1->qq); printf("\n nhap tuoi:");scanf("%d",&(p1->tuoi)); p1->tiep=NULL; } in(); /* in danh sách vừa nhập*/ loai(); /*goị hàm loại*/ in(); /*gọi hàm in để in danh sách sau khi loại*/ getch(); } Đến dây Tôi xin kết thúc phần này ở đây. Hi vọng bạn hiểu thế nào là cấu trúc mảng và thế nào là cấu trúc móc nối và thao tác trên mỗi cấu trúc. Thanh Tùng KS 20_CNTT - ĐHBKHN Tóm tắt ngôn ngữ lập trình C 50 Phần 3. Tệp tin và Thao tác trên tệp tin. ( sẽ hoàn thiện sau. Mong các bạn thông cảm )

Các file đính kèm theo tài liệu này:

  • pdfTóm tắt ngôn ngữ lập trình C.pdf
Tài liệu liên quan