Lập trình cơ bản - Chương 4: Hàm

pdf 53 trang vanle 1890
Bạn đang xem 20 trang mẫu của tài liệu "Lập trình cơ bản - Chương 4: Hàm", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

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

  • pdflap_trinh_co_ban_chuong_4_ham.pdf

Nội dung text: Lập trình cơ bản - Chương 4: Hàm

  1. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái CHƯƠNG 4: HÀM 1. Khái niệm hàm 1.1Khái niệm và phân loại Một chương trình viết trong ngôn ngữ C là một dãy các hàm, trong đó có một hàm chính(hàm main). Hàm chia các bài toán lớn thành các công việc nhỏ, có thể có những đoạn chương trình viết lặp đi lặp lại nhiều lần, để tránh rườm rà và mất thời gian khi viết chương trình; người ta thường phân chia chương trình thành nhiều module, mỗi module giải quyết một công việc vào đó. Thứ tự các hàm trong chương trình là bất kỳ, song chương trình bao giờ cũng đi thực hiện từ main(). Trong C, chương trình con được gọi là hàm. Hàm trong C có thể trả về kết quả thông quan tên hàm hay có thể không trả về kết quả. Hàm có hai loại: Hàm chuẩn và hàm tự định nghĩa. Trong chương này ta chú trọng đến cách định nghĩa hàm và cách sử dụng các hàm đó. Hàm thư viện Hàm thư viện là những hàm đã được định nghĩa sẵn trong một thư viện nào đó, muốn sử dụng các hàm thư viện thì phải khai báo thư viện trước khi sử dụng bằng lệnh #inlcude Hàm người dùng Hàm người dùng là những hàm do người lập trình tự tạo ra nhằm đáp ứng nhu cầu xử lý của mình. Một hàm khi được định nghĩa thì có thể sử dụng bất cứ đâu trong chương trình. Trong C, một chương trình bắt đầu thực thi bằng hàm main. Chương trình con được dùng để tôí ưu hóa việc tổ chức chương trình, chia một chương trình lớn thành nhiều công việc độc lập nhỏ. Dùng chương trình con thực hiện các công việc nhỏ, tạo thành mô-đun. Khi đó nhiệm vụ chương trình chính chỉ là cung cấp dữ liệu đầu vào cho các mô-đun để hoàn thành công việc của mình. Một chương trình viết theo cách này gọi là chương trình cấu trúc. Có thể minh họa chương trình con như hình sau : Chương trình con thực hiện công việc A Dữ liệu đưa vào để thực Dữ liệu kết quả hiện công việc A của công việc A 52
  2. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Hình 4.1.1 : Hình ảnh minh họa nhiệm vụ của chương trình con Để nhận các dữ liệu đưa vào cho chương trình con và nếu có thể chứa dữ liệu kết quả ra chúng ta phải sử dụng tham số (parameters) của chương trình con. Tham số tồn tại dưới hai hình thức đó là tham số thực và tham số hình thức. Tham số hình thức là tham số để khai báo và xây dựng trương trình con, còn tham số thực để xác định dữ liệu đưa vào khi gọi chương trình con. Trong một số ngôn ngữ lập trình cung cấp hai loại chương trình con riêng biệt đó là hàm(function) và thủ tục (procedures) nhưng trong C chỉ cung cấp một loại đó là hàm Để tạo một hàm chúng ta cần xác định - Hàm tạo ra sẽ thực hiện công việc gì ? - Sau khi thưc hiện song có cần trả về một dữ liệu hay nhiều dữ liệu không ? - Để thực hiện được công việc đó ta cần những dữ liệu nào ? 1.2 Quy tắc hoạt động của hàm Khi gặp một lời gọi hàm, thì hàm sẽ được thực hiện theo trình tự sau: Cấp phát bộ nhớ cho các đối và các biến cục bộ. Gán giá trị của các tham số thực cho các đối tượng tương ứng. Thực hiện các câu lệnh trong thân hàm. Khi gặp câu lệnh return hoặc dấu } cuối cùng của thân hàm thì máy sẽ xóa các đối, các biến cục bộ và thoát khỏi hàm. Chú ý: Số tham số thực sự phải bằng số tham số hình thức (đối của hàm) và kiểu của tham số thực phải cùng kiểu với tham số hình thức tương ứng. Nếu trở về từ một câu lệnh return có chứa biểu thức thì giá trị của biểu thức được gán cho hàm. Giá trị của hàm sẽ được sử dụng trong biểu thức chứa nó. 2. Xây dựng hàm 2.1 Định nghĩa hàm  Khai báo hàm Khai báo hàm được thực hiện ở phần đầu chương trình, khai báo để chỉ cho máy biết các thông tin về hàm bao gồm : kiểu dữ liệu trả về có hay không, tên hàm, các tham số bao gồm kiểu và tên của từng tham số. Cú pháp khai báo như sau : 53
  3. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Tên_kiểu_trả_về tên_hàm(kiểu1 tham_số1, kiểu2 tham_số2 ) ; Trong đó : - Tên kiểu trả về : là một tên kiểu dữ liệu quy định kết quả trả về là khiểu gì. - Tên hàm : Tự đặt theo quy định đặt tên của ngôn ngữ C. - Kiểu1 tham_số1 : xác định tên của tham số thứ nhất và kiểu của tham số đó, Nếu có nhiều tham số, mỗi tham số phân cách nhau bởi dấu phẩy (,) Ví dụ: int sum(int a , int b) ; float max(float x, float y); Nếu trong hàm không có dữ liệu trả về thì viết kiểu trả về là void nếu không có tham số thì bỏ trống và phải có cặp dấu đóng mở ngoăc () sau tên hàm. Ví dụ: void hien();  Xây dựng hàm Sau khi khai báo xong hàm chúng ta có thể viết lệnh thực hiện công việc đặt ra cho chương trình con. Cú pháp: Tên_kiểu_trả_về tên_hàm(kiểu1 tham_số1,kiểu2 tham_số2 ) { Các câu lệnh thực hiện công việc đặt ra cho hàm. } Khi cần kết thúc thực hiện hàm và trả về dữ liệu nào đây, chúng ta viết lệnh return vào vị trí cần thiết nhất trong hàm. Cú pháp: return giá-trị-dữ-liệu-trả-về; Ví dụ: int sum(int a, int b) { return a+b; } Hoặc 54
  4. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái float max(float x, float y) { if(x>y) return x; else return y; } Đối với hàm có kiểu void có thể không cần lệnh return Ví dụ: void hien() { printf(“ho va ten: Nguyen Van Nam”); printf(“Ngay sinh :12\08\2014”); printf(“que quan : Yen Bai”); } Chú ý: Thông thường với những chương trình đơn giản có ít chương trình con thì người ta viết lệnh cho hàm ngay tại nơi khai báo. 2.2 Sử dụng hàm Một hàm khi định nghĩa thì chúng vẫn chưa được thực thi trừ khi ta có một lời gọi đến hàm đó. Khi thực hiện chương trình máy sẽ thực hiện các lệnh trong chương trình chính(hàm main), do đó để yêu cầu máy thực hiện công việc của chương trình con ta phải viết lệnh gọi chương trình con với cú pháp sau. Cú pháp: ([Danh sách các tham số]) Lời gọi này có thể là một câu lệnh độc lập hoặc đặt trong biểu thức, nếu đặt trong biểu thức thì hàm đó phải có giá trị trả về để thực hiện tính toán biểu thức đó. Ví dụ: printf(“Tong hai so 5 va 6 la: %d”,tong(5,6)); Hoặc: float x ; 55
  5. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái x = max(a,b) ; Hoăc: hien(); Chú ý: Thông thường lời gọi hàm được viết trong chương trình chính, tuy nhiên có thể viết trong chương trình con khác. Các chương trình con có thể gọi lẫn nhau. 3. Các tham số của hàm 3.1 Phân biệt các loại tham số Trong lập trình, tham số là biến được thu nhận bởi một chương trình con. Tại thời gian chạy, chương trình con sử dụng các giá trị được gán cho các tham số để thay đổi cách ứng xử của mình. Hầu hết các ngôn ngữ lập trình có thể định nghĩa các chương trình con không có tham số hoặc chấp nhận một vài tham số. Tham số hình thức: Là biến được liệt kê trong danh sách tham số (thường nằm tại phần đầu của định nghĩa chương trình con). Tham số thực sự : Là giá trị cụ thể của biến đó tại thời gian chạy. Để phân biệt rõ hai khái niệm trên, xét ví dụ dưới đây: int sum(int gt1, int gt2) { return (gt1+gt2); } Hàm sum nhận hai tham số hình thức: gt1và gt2. Nó lấy tổng của các giá trị được truyền vào các tham số này và trả về kết quả cho nơi gọi hàm (bằng cách sử dụng một kỹ thuật được cung cấp tự động bởi trình biên dịch C). Mã gọi hàm sum có thể trông như dưới đây: int a=40; int b=2; int c = sum(a,b); Các biến a và b được khởi tạo với các giá trị 40 và 2. Các biến này không phải tham số hình thức hay tham số thực sự. Tại thời gian chạy, giá trị đã được gán cho các biến này được truyền vào cho hàm sum . Trong hàm sum, các tham số hình thức gt1 và gt2 được tính giá trị và lần lượt cho kết quả là hai tham số thực 56
  6. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái sự 40 và 2. Giá trị của các tham số thực sự được cộng lại, kết quả được trả về cho nơi gọi hàm - nơi nó được gán cho biến c Tham số hình thức thường được gọi tắt là tham số. Tham số thực sự còn được gọi là tham số thực, tham đối hoặc đối số. 3.2 Cách truyền tham số Có hai cách để truyền tham số cho hàm: Truyền theo tham trị và truyền theo tham chiếu. Truyền bằng tham trị Trong cơ chế truyền tham số bằng giá trị (gọi là truyền tham trị), khi chương trình con được chạy, một bản sao của tham số thực sự được gán cho tham số hình thức (sao chép giá trị). Nghĩa là mọi sửa đổi của chương trình con đối với tham số hình thức không gây ảnh hưởng tới biến được truyền vào chương trình con theo kiểu truyền tham trị. Các tham số được truyền bằng giá trị được gọi là tham trị. Do chỉ có giá trị được truyền vào chương trình con, tham số thực sự không nhất thiết phải là một biến thông thường mà có thể là hằng giá trị, hằng biến, biểu thức trả về giá trị Truyền bằng biến Trong cơ chế truyền tham số bằng biến (gọi là truyền tham biến), khi chương trình con được chạy, tham số hình thức trở thành một tham chiếu tham số thực sự. Nghĩa là mọi sửa đổi của chương trình con đối với tham số hình thức sẽ có tác dụng với tham số thực sự. Đây được gọi là hiệu ứng phụ của chương trình con. Các tham số được truyền bằng biến được gọi là tham biến. Ngược lại với cơ chế truyền bằng giá trị, cơ chế truyền bằng biến đòi hỏi tham số thực sự phải là một biến. Các đối số mặc định Một số ngôn ngữ lập trình cho phép một đối số mặc định (default argument) được cho trước một cách tường mình hay ngầm định trong phần khai báo của một chương trình con. Điều này cho phép nơi gọi bỏ qua đối số này khi gọi chương trình con đó. Nếu đối số mặc định được cho một cách tường mình, thì giá trị đó được sử dụng mỗi khi nó không được cung cấp bởi nơi gọi. Nếu đối số mặc định là ngầm định (mà đôi khi được khai báo bởi từ khóa như là "Optional" (không bắt buộc)) thì ngôn ngữ sẽ cung ứng một giá trị "ban đầu" thông dụng (như là null, tập rỗng, giá trị 0, xâu kí tự rỗng, ) nếu giá trị (của tham số) không được nơi gọi cung cấp. Chiều dài biến đổi được của danh sách tham số 57
  7. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Một số ngôn ngữ cho phép các chương trình con được định nghĩa với số các đối số thay đổi được. Đối với các ngôn ngữ như vậy, các chương trình con phải duyệt qua danh mục của các đối số Các tham số danh định Một số ngôn ngữ lập trình cho phép các chương trình con có các tham số danh định (named parameter). Điều này cho phép mã nơi gọi chương trình con có tính tự mô tả (self-documenting) cao hơn. Nó cũng cung cấp cho nơi gọi khả năng uyển chuyển cao hơn, thường cho phép thay đổi thứ tự của các đối số hay bỏ qua một số đối số nếu cần thiết. 3.3. Biến toàn cục và biến cục bộ Biến toàn cục là biến nhớ được khai báo ở ngoài mọi hàm(thường được khai báo ở trên cùng sau khai báo thư viện), có tác dụng đến toàn bộ chương trình cả chương trình chính và chương trình con. Biến cục bộ là biến nhớ được khai báo bên trong một chương trình con, chỉ có tác dụng ở trong chương trình con đó. Bên ngoài chương trình con đó thì biến đó không được sử dụng nữa. Ví du: #include #include int a,b; int sum() { int c = a + b; return c; } int tich() { int c ; c =a*b; return c; } void main() { clrscr(); 58
  8. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái printf(“nhap vao so a = ”); scanf(“%d”,&a); printf(“nhap vao so b = ”);scanf(“%d”,&b); printf(“\n tong hai so: %d”,sum()); printf(“\n tich hai so la: %d”,tich()); getch(); } Trong ví dụ trên hai biến a và b là biến toàn cục được sử dụng trong cả 3 hàm sum, max và main. Trong hàm sum và max đều có biến cục bộ là c nhưng biến c trong hàm sum sẽ độc lập và riêng biệt so với c trong hàm max. 4. Hàm đệ quy 4.1 Khái niệm đệ quy Một hàm được gọi là đệ quy nếu bên trong thân hàm có lệnh gọi đến chính nó Ví dụ: Người ta định nghĩa giai thừa của một số nguyên dương n như sau: N!= 1*2*3* .*(n-1)*n=(n-1)! *n (với 0!=1) Như vậy, để tính n! ta thấy nếu n=0 thì n!=1 ngược lại thì n!=n*(n-1)! Với định nghĩa trên thì hàm đệ quy tính n! được viết: #include #include /*hàm tính n! bằng đệ quy*/ long giaithua_ dequy(int n) { if(n==0) Return 1; else return n*giaithua_dequy(n-1); } 4.2 Các bài toán dùng đệ quy Phương pháp đệ quy thường dùng phổ biến trong ứng dụng mà cách giải quyết có thể được thể hiện bằng việc áp dụng liên tiếp cùng giải pháp cho những tập hợp con của bài toán. Phương pháp đệ quy không phải bao giờ cũng là giải pháp hữu hiệu nhất. Giải pháp vòng lặp có hiệu quả về mặt thời gian và vùng nhớ. Còn với đệ quy mỗi lần 59
  9. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái gọi đệ quy máy phải dành một số vùng nhớ để trữ các trị, thông số và biến cục bộ. Do đó, đệ quy tốn nhiều vùng nhớ, thời gian truyền đối mục, thiết lập vùng nhớ trung gian và trả kết quả Nhưng sử dụng phương pháp đệ quy trông chương trình đẹp mắt hơn vòng lặp và tính thuyết phục của nó. Điều cốt lõi khi thiết đặt chương trình phải làm thế nào hàm đệ quy có thể chấm dứt thông qua điều kiện cơ bản Chính vì vậy, trong lập trình người ta cố tránh sử dụng thủ tục đệ quy nếu thấy không cần thiết. 4.3 Cách xây dựng hàm đệ quy Hàm đệ quy phải có 2 phần : + Phần dừng hay phải có trường hợp nguyên tố.Trong ví dụ ở trên thì trường hợp n=0 là trường hợp nguyên tố. + Phần đệ quy : Là phần có gọi lại hàm được định nghĩa. Trong ví dụ trên thì phần đệ quy là n>0 thì n !=n*(n-1) ! Sử dụng hàm đề quy trong chương trình sẽ làm chương trình dễ đọc, dễ hiểu và vấn đề được nêu bật rõ ràng hơn.Tuy nhiên trong đa số trường hợp thì hàm đệ quy tốn bộ nhớ nhiều hơn và tốc độ thực hiện chương trình chậm hơn không đệ quy. Tùy từng bài cụ thể mà người lập trình quyết định nên dùng đệ quy hay không(có trường hợp không dùng đệ quy thì không giải quyết được bài toán). 4.4 Các ví dụ về hàm đệ quy Ví dụ 1: Tính n! N!= 1*2*3* *(n-2)*(n-1)*n với n>=1 và 0!=1 long giaithua(int n) { if(n==0) Return 1; else return n*giaithua(n-1); } Giải thích hoạt động của hàm đệ quy giaithua Ví dụ giá trị truyền vào hàm giaithua qua biến n = 5 Thứ tự gọi thực hiện hàm giai thừa 60
  10. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái giaithua(n) return(n * giaithua(n-1)) 5 5*giaithua(4) = 5 *? 4 4*giaithua(3) = 4 *? 3 3*giaithua(2) =3 *? 2 2*giaithua(1) = 2*? 1 1*giaithua(0) = 1 *? Khi tham số n =0 thì return về giá trị (giá trị 1 kiểu long). Lúc này các giá trị? Bắt đầu định trị theo thứ tự ngược lại Giaithua(in) Return(in * giaithua(in-1)) 1 1*giaithua(0) = 1 *1 = 1 2 2*giaithua(1) = 2 *1=2 3 3*giaithua(2) =3 *2=6 4 4*giaithua(3) = 4*6=24 5 5*giaithua(4) = 5 *24=120 Kết quả sau cùng 5!= 120 Minh họa bằng hình ảnh. 61
  11. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Hình 4.4.4: Hình ảnh minh họa tính 5! bằng giải thuật đệ quy Ví dụ 2: dãy số Fibonaci 0,1,1,2,3,5,8,13,21,34 Bắt đầu bằng 0 và 1, các số tiếp theo bằng tổng hai số đi trước Dãy số fibonaci được khai báo đề quy như sau: fibonaci(0)=0 fibonaci(1)=1 fibonaci(n)= fibonaci(n-1) + fibonaci(n-2) /* tinh so fibonaci thu n*/ # include # include void main(void) { long n; long fibonaci(long); printf(“nhap vao so n:”); scanf(%ld”,&n); print(“fibonaci(%ld)=%ld\n”,n,fibonaci(n)); getch(); } long fibonaci(long in) { if (n==0|| n==1) Return n; else return fibonaci(n -1 )+ fibonaci(n – 2); } Ket qua in ra man hinh 62
  12. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Nhap vao so n:10 Fibonaci(10)=55 Ví dụ 3 : Tìm UCLN của 2 số a, b. Bài toán có thể được định nghĩa dưới dạng đệ qui như sau: − nếu a = b thì UCLN = a − nếu a > b thì UCLN(a, b) = UCLN(a-b, b) − nếu a 0 { if (a b) UCLN(a-b, b); } BÀI TẬP VẬN DỤNG Bài tập 1: Viết chương trình con tìm Max, Min 3 sô nguyên, chương trình chính nhập vào 3 số nguyên, hiện số lớn nhất và nhỏ nhất ra màn hình. #include #include int max(int a,int b,int c) { int max=a; if(max<b)max=b; if(max<c)max=c; return max; } int min(int a, int b, int c) { int min=a; 63
  13. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái if(min>b)min=b; if(min>c)min=c; return min; } void main() { int a,b,c; clrscr(); printf("\nnhap vao 3 so nguyen:"); printf("\nso thu nhat: ");scanf("%d",&a); printf("\nso thu hai: ");scanf("%d",&b); printf("\nso thu ba : ");scanf("%d",&c); printf("\n so lon nhat la: %d",max(a,b,c)); printf("\n so nho nhat la: %d",min(a,b,c)); getch() ; } Bài tập 2: Viết chương trình kiểm tra một số nguyên có phải là số nguyên tố hay không. #include #include #include int SNT(int n) { int i,kt=1; for(i=2; i<=sqrt(n);i++) { if(n%i==0) { kt=0; break; 64
  14. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái } } return kt; } void main() { int n; printf("Nhap So Can Kiem Tra: "); scanf("%d", &n); int kq; kq = SNT(n); if(kq==1) printf("So: %d là so nguyen to.", n); else printf("So: %d khong là so nguyen to.", n); printf("\n"); } Bài tập 3 Viết chương trình đếm số chữ số của một số nào đó, Ví dụ: 21432 trả về: 5 vì có 5 chữ số. //dem so chu so cua n vd: 12342 -> tra ve: 5 int Dem(int n) { int dem=0; while(n != 0) { n=n/10; dem ++; } return dem; } void main() { int n; printf("Nhap n: "); scanf("%d", &n); int dem = Dem(n); printf(".:|Chu so cua so n la: %d", dem); 65
  15. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái printf("\n"); } Bài tập 4 Viết chương trình đảo ngược của 1 số: ví dụ nhập 123 -> trả về 321. int DaoSo(int n) { int kq=0; while(n!=0) { kq=kq*10 + n%10; n=n/10; } return kq; } void main() { int n; printf("Nhap n: "); scanf("%d", &n); printf("\nSo Dao Nguoc La: %d", DaoSo(n)); } 66
  16. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Chương 5: MẢNG 1. Khái niệm mảng Mảng là cấu trúc dữ liệu cho phép quản lý một danh sách hữu hạn các dữ liệu cùng kiểu. Ví dụ: Để chứa 5 dữ liệu số nguyên sau 10, 12, 25, 30, 40 Chúng ta có thể sử dụng 5 biến nhớ khác nhau a = 10, b = 12, c = 25, d = 30, e = 40 Tuy nhiên nếu số lượng dữ liệu tăng thì cách này không phù hợp, và chúng ta sẽ sử dụng cấu trúc mảng, minh hoạ như sau: 10 12 25 30 40 Các dữ liệu trong mảng phải cùng kiểu (số nguyên, số thực, ký tự ). Mảng để chứa dữ liệu như trên là mảng 1 chiều, chúng ta có thể sử dụng mảng 2 chiều hoặc mảng nhiều chiều. Ví dụ về mảng 2 chiều: 10 2 5 45 23 56 8 5 20 Mảng hai chiều là một bảng các dữ liệu được xếp theo hàng và cột, mỗi dữ liệu sẽ chứa trong ô được xác định bằng cách chỉ ra ở hàng nào và cột nào. 2. Khai báo mảng  Mảng một chiều Mảng một chiều giống như một vector. Mỗi phần tử của mảng một chiều có giá trị không phải là một mảng khác. Khai báo với số phần tử xác định Cú pháp: Trong đó: + Tên_mảng: Đây là tên mảng đạt theo đúng quy tắc đặt tên của danh biểu. Tên này cũng mang ý nghĩa là tên biến mảng. + Số_phần_tử : Là một hằng số nguyên, cho biết số lượng phàn tử tối đa trong mảng là bao nhiêu (hay nói khác đi kích thước của mảng là gì). 67
  17. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái + Kiểu : Mỗi phần tử của mảng có dữ liệu thuộc kiểu gì. Ở đây, ta khai báo một biến mảng gồm có Số_phần_tử phần tử, phần tử thứ nhất là tên_mảng [0], phần tử cuối cùng là tên_mảng[Số_phần_tử -1] VD: int a[10]; /*Khai báo biến mảng tên a, phần tử thứ nhất là a[0], phần tử cuối cùng là a[9].*/ Ta có thể coi mảng a là một dãy liên tiếp các phần tử trong bộ nhớ như sau: Vị trí 0 1 2 3 4 5 6 7 8 9 Tên phần a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] tử Khai báo với số phần tử không xác định (khai báo không tường minh) Cú pháp: Khi khai báo, không cho biết rõ số phần tử của mảng, kiểu khai báo này thường được áp dụng trong các trường hợp: vừa khai báo vừa gán giá trị, khai báo mảng là tham số hình thức của hàm. Vừa khai báo vừa gán giá trị Cú pháp: [] = {Các giá trị cách nhau bởi dấu phẩy} Nếu vừa khai báo vừa gán giá trị thì mặc nhiên C sẽ hiểu số phần tử của mảng là số giá trị mà chúng ta gán cho mảng trong cặp dấu {}. Ví dụ: int S[] = {3,5,6};  Mảng nhiều chiều Mảng nhiều chiều là mảng có từ hai chiều trở lên. Điều đó có nghĩa là mỗi phần tử của mảng là một mảng khác. Người ta thường sử dụng mảng nhiều chiều để lưu các ma trận, các tọa độ 2 chiều, 3 chiều . Phần dưới đây là các vấn đề liên quan đến mảng 2 chiều; các mang 3, 4 chiều thì tương tự (chỉ cần tổng quát hóa lên). 68
  18. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Khai báo mảng hai chiều tường minh Cú pháp: VD: Người ta cần lưu trữ thông tin của một ma trận gồm các số thực. Lúc này ta có thể khai báo một mảng 2 chiều như sau: float m[8][9]; /*Khai báo mảng hai chiều có 8*9 phần tử là số thực*/ Trong trường hợp này, ta đã khai báo cho một ma trận có tối đa là 8 dòng, mỗi dòng có tối đa là 9 cột. 0 1 2 3 4 5 6 7 8 0 m[0][0] m[0][1] m[0][2] m[0][3] m[0][4] m[0][5] m[0][6] m[0][7] m[0][8] 1 m[1][0] m[1][1] m[1][2] m[1][3] m[1][4] m[1][5] m[1][6] m[1][7] m[1][8] 2 m[2][0] m[2][1] m[2][2] m[2][3] m[2][4] m[2][5] m[2][6] m[2][7] m[2][8] 3 m[3][0] m[3][1] m[3][2] m[3][3] m[3][4] m[3][5] m[3][6] m[3][7] m[3][8] 4 m[4][0] m[4][1] m[4][2] m[4][3] m[4][4] m[4][5] m[4][6] m[4][7] m[4][8] 5 m[5][0] m[5][1] m[5][2] m[5][3] m[5][4] m[5][5] m[5][6] m[5][7] m[5][8] 6 m[6][0] m[6][1] m[6][2] m[6][3] m[6][4] m[6][5] m[6][6] m[6][7] m[6][8] 7 m[7][0] m[7][1] m[7][2] m[7][3] m[7][4] m[7][5] m[7][6] m[7][7] m[7][8] Khai báo mảng 2 chiều không tường minh Để khai báo mảng 2 chiều không tường minh, ta vẫn phải chỉ ra số phần tử của chiều thứ hai (chiều cuối cùng). Cú pháp: Cách khai báo này cũng được áp dụng trong trường hợp vừa khai báo, vừa gán trị hay đặt mảng 2 chiều là tham số hình thức của hàm. 69
  19. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái 3. Truy xuất mảng 3.1 Truy xuất mảng 1 chiều Đối với mảng một chiều các phần tử chứa dữ liệu được sắp xếp liên tục trong bộ nhớ máy, và mỗi phần tử sẽ có một chỉ số (là số thứ tự) của phần tử đó trong mảng. Phần tử đầu tiên có chỉ số là 0, tiếp theo là 1 và tiếp tục cho đến hết, vậy sẽ dừng lại ở chỉ số bằng số lượng phần tử -1. Minh hoạ bằng hình sau: 10 25 15 30 0 1 2 3 Kích thước của mảng (tính bằng byte) sẽ là: số lượng phần tử của mảng nhân với kích thước của kiểu dữ liệu của mảng đó. Ví dụ: int a[20]; Thì mảng sẽ chiếm kích thước bộ nhớ là: 20 x 2 = 40 byte Để truy nhập đến các phần tử ta sử dụng tên và các chỉ số của phần tử đó theo cú pháp sau: Tên_mảng[chỉ_số_phần_tử_cần_truy_nhập]; Ví dụ: a[0]=10; Truy xuất đến phần tử đầu tiên của mảng a rồi gán giá trị cho phần tử đó bằng 10. 3.2 Truy xuất mảng 2 chiều Để truy nhập đến các phần tử của mảng hai chiều ta sử dụng tên mảng và chỉ số hàng và chỉ số cột theo cú pháp sau: Tên_mảng_hai_chiều[chỉ_số_hàng] [chỉ_số_cột]; Ví dụ: a[1][2] = 5; Chú ý: Số lượng dữ liệu lưu trong mảng có thể ít hơn số lượng phần tử đã khai báo, nhưng không vượt quá số lượng phần tử đã khai báo đó. Thông thường sử dụng 70
  20. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái mảng sẽ có một số nguyên n để lưu số lượng dữ liệu được lưu trong mảng tương ứng. Danh sách các dữ liệu lưu trong mảng phải liên tục nhau để quản lý và sử lý dễ dàng. 71
  21. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái BÀI TẬP THỰC HÀNH Bài tập 1: Viết chương trình nhập vào từ bàn phím một mảng gồm n số nguyên. Hiện mảng vừa nhập lên màn hình. #include #include void nhap(int n,int C[]) { for(int i=0;i #include void nhap(int n,int C[]) { 72
  22. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái for(int i=0;i tong(m,B)) printf("A"); else printf("B"); getch() ; } 73
  23. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Bài tập 3: Nhập ma trận A có m hàng n cột đưa ra màn hình ma trận A vừa nhập để kiểm tra. #include #include void main() { int n,m,i,j,A[100][100]; clrscr(); printf("nhap so hang cua ma tran: n= "); scanf("%d",&n); printf("nhap so cot cua ma tran: m= "); scanf("%d",&m); //nhap gia tri cho ma tran for(i=0;i #include void main() { int i,n,A[100],max=0,min=0; 74
  24. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái clrscr(); printf("nhap so luong phan tu cua day: "); scanf("%d",&n); //nhap gia tri cho day for(i=0;i A[i]) min=i; } printf("\nSo lon nhat trong day la: %d",A[max]); printf("\t Vi tri so do la: %d",max+1); printf("\nSo nho nhat trong day la: %d",A[min]); printf("\t Vi tri so do la: %d",min+1); getch(); } Bài tập 5 Nhập và từ bàn phím một dãy số nguyên, xắp xếp dãy đó theo chiều tăng dần và giảm dần. Hiện lên màn hình: dãy số vừa nhập, dãy đã sắp xếp. #include #include void nhap(int n,int C[]) { for(int i=0;i<n;i++) { printf("\n Nhap so thu %d ",i+1); scanf("%d",&C[i]); } } void hien(int n,int C[]) { for(int i=0;i<n;i++) { printf(" %d",C[i]); } } 75
  25. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái void tang(int n, int C[]) { int tg; for(int i=0;i C[j+1]) { tg=C[j]; C[j]=C[j+1]; C[j+1]=tg; } } void giam(int n, int C[]) { int tg; for(int i=0;i<n;i++) for(int j=0;j<n-1;j++) if(C[j] <C[j+1]) { tg=C[j]; C[j]=C[j+1]; C[j+1]=tg; } } void main() { clrscr(); int A[100],n; printf("\nnhap so luong phan tu day : n= "); scanf("%d",&n); printf("\n Nhap gia tri cho day :"); nhap(n,A); printf("\n Day vua nhap la:"); hien(n,A); //hien day da xap xep tang dan printf("\n Day da xap xep tang dan la:"); tang(n,A); hien(n,A); //hien day da xap xep giam dan printf("\n Day da xap xep giam dan:"); giam(n,A); hien(n,A); getch() ;} 76
  26. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái CHƯƠNG 6: CON TRỎ 1. Khái niệm về con trỏ và địa chỉ Một con trỏ (pointer) là một biến mà nội dung của nó là địa chỉ của một đối tượng khác. Đối tượng ở đây có thể là một biến hoặc một hàm và không phải là một hằng. Việc sử dụng các biến con trỏ sẽ giúp chúng ta khai thác đến 1 biến thông qua địa chỉ của nó. Khả năng đó cộng với khả năng tính toán linh hoạt trên các con trỏ của C đã khiến con trỏ trở thành một công cụ mạnh để thực hiện nhiều thao tác mà thiếu nó thì không thể làm được hoặc rất khó khăn. Bộ nhớ máy tính bao gồm một bảng các ô nhớ liên tiếp có thể đánh địa chỉ được. Chúng ta có thể thao tác trên từng ô nhớ riêng rẽ hoặc trên các ô nhớ liên tiếp. Khi chúng ta khai báo một biến, máy sẽ cấp phát cho biến đó một số ô nhớ liên tiếp đủ để chứa nội dung của biến, ví dụ một biến ký tự được cấp phát một byte, một biến nguyên được cấp phát 2 byte, một biến thực được cấp phát 4 byte Địa chi của biến được tính là số thứ tự của byte đầu tiên trong dãy các byte được cấp phát cho biến. Vì vậy người ta phân biệt các con trỏ theo kiểu địa chỉ chứa trong các con trỏ. Con trỏ được minh họa bằng hình sau. Địa chỉ vùng nhớ: 0100 Biến a Địa chỉ vùng nhớ Biến b Biến của biến a (0100) trỏ Hình 6.1: Hình ảnh mô tả giá trị của biến con trỏ Có hai kiểu biến trỏ: Biến trỏ có kiểu và biến trỏ không kiểu Biến trỏ có kiểu : loại biến trỏ này chỉ chứa địa chỉ vùng nhớ của một kiểu dữ liệu nào đó, kiểu dữ liệu này được xác định khi khai báo biến trỏ. Và kiểu của biến trỏ xác định kích thước của vùng nhớ mà biến trỏ đến Ví dụ biến trỏ kiểu int sẽ trỏ đến vùng nhớ có kích thước là 2 bytes, kiểu floast sẽ trỏ đến vùng nhớ có kích thước là 4 bytes. 77
  27. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Biến trỏ không kiểu: loại biến trỏ này có thể chứa địa chỉ vùng nhớ có kích thước bất kỳ. Vùng nhớ động: Là vùng nhớ được cấp phát khi cần và giải phóng khỏi bộ nhớ khi sử dụng song. Để truy cập đến vùng nhớ động chúng ta phải dùng biến trỏ. 2. Khai báo về sử dụng biến con trỏ 2.1 Khai báo biến con trỏ biến trỏ có kiểu Kiểu_dữ_liệu *tên_con_trỏ; Ví dụ: *pn /*Khai báo con trỏ pn chứa địa chỉ của biến nguyên*/ - biến trỏ không kiểu: void *tên_biến_trỏ ví dụ: void *i; Với câu lệnh khai báo trên, con trỏ chưa trỏ đến địa chỉ nào cả và nó có giá trị bằng NULL. NULL là một hằng số biểu thị giá trị của con trỏ khi chưa được gán địa chỉ nào. 2.2. Các thao tác trên con trỏ 2.2.1. Gán địa chỉ của biến cho con trỏ Toán tử & dùng để định vị con trỏ đến địa chỉ của một biến đang làm việc. Cú pháp : =& Giải thích: Ta gán địa chỉ của biến a cho con trỏ pa, Ta gán địa chỉ của biến b cho con trỏ pb pa=&a ; pb=&b ; Lúc này hình ảnh của các biến trong bộ nhớ được mô tả : Lưu ý : Khi gán địa chỉ của biến tĩnh cho con trỏ cần phải lưu ý kiểu dữ liệu của chúng. Ví dụ sau đây không đúng do không tương thích kiểu : int biennguyen ; float *controthuc ; 78
  28. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái . controthuc=&biennguyen ; Phép gán ở đây là sai vì controthuc là một con trỏ kiểu float(nó chỉ có thể chứa được địa chỉ của biến kiểu float) ; trong khi đó biến nguyên kiểu int. 2.2.2. Nội dung của ô nhớ con trỏ chỉ tới Để truy cập đến nọi dung của ô nhớ mà con trỏ chỉ tới, ta sử dụng cú pháp : * Với cách truy cập này thì * có thể coi là một biến có kiểu được mô tả trong phần khai báo biến con trỏ. Ví dụ : Cho phép khai báo, gán địa chỉ cũng như lấy nội dung vùng nhớ của biến con trỏ : Int x= 100 ; int *ptr ; ptr = &x ; int y= *ptr ; Lưu ý : Khi gán địa chỉ của một biến cho biến con trỏ, mọi sự thay đổi trên nội dung ô nhớ con trỏ chỉ tới sẽ làm giá trị của biến thay đổi theo( thực chất nội dung ô nhớ và biến là một) Ví dụ : Đoạn chương trình sau thấy rõ sự thay đổi #include #include int main () { int a,b, *pa, *pb ; a=2 ; b=3 ; clrscr() ; printf (‘\n gia tri cua bien a=%d \n gia tri cua bien b=%d’, a,b) ; pa=&a ; pb=&b ; printf (‘\n noi dung cua o nho con tro pa tro toi=%d’, *pa) ; printf (‘\n noi dung cua o nho con tro pa tro toi=%d’, *pb) ; *pa=20 ; 79
  29. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái *pb=20 ; printf (‘\n gia tri moi cua bien a=%d \n gia tri moi cua bien b=%d’, a,b) ; getch() ; return 0; } Kết quả thực hiện chương trình 2.2.3. Cấp phát và giải phóng vùng nhớ Trước khi sử dụng biến con trỏ, ta nên cấp phát vùng nhớ cho biến con trỏ này quản lý địa chỉ. Việc cấp phát được thực hiện nhờ các hàm malloc(), calloc() trong thư viện alloc.h Cú pháp các hàm : Void *malloc(size_t size) : Cấp phát vùng nhớ có kích thước là size. Void *calloc(size_t nitems,size_t size) : Cấp phát vùng nhớ có kích thước là nitems*size. Ví dụ: Giả sử ta có khai báo int a, *pa, *pb; pa=(int*)malloc(sizeof(int));/*cấp phát vùng nhớ có kích thước bằng với kích thước của một số nguyên */ pb=(int*)calloc(10,sizeof(int));/*cấp phát vùng nhớ có thể chứa được 10 số nguyên */ Lúc này hình ảnh trong bộ nhớ như sau: Lưu ý: 80
  30. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Khi sử dụng hàm malloc() hay calloc(), ta phải ép kiểu vì nguyên mẫu các hàm này trả về con trỏ kiểu void. Trong quá trình thao tác trên biến con trỏ, nếu ta cần cấp phát thêm vùng nhớ có kích thước lớn hon vùng nhớ đã cấp phát, ta sử dụng hàm realloc(). Cú pháp: void *realloc(void *block,size_t size) Ý nghĩa: Cấp phát lại 1 vùng nhớ cho con trỏ block quản lý, vùng nhớ này có kích thước mới là size; khi cấp phát lại thì nội dung vùng nhớ trước đó vẫn tồn tại. Kết quả trả về của hàm là địa chỉ đầu tiên của vùng nhớ mới. Địa chỉ này có thể khác với địa chỉ được chỉ ra khi cấp phát ban đầu. Ví dụ: Trong ví dụ trên ta có thể cấp phát lại vùng nhớ do con trỏ pa quả lý như sau: int a, *pa; pa=(int *)malloc(sizeof(int)) ;/*cấp phát vùng nhớ có kích thước 2 byte*/ pa=realloc(pa,6) ;/*cấp phát lại vùng nhớ có kích thước 6byte*/ Một vùng nhớ đã cấp phát cho biến con trỏ, khi không còn sử dụng nữa, ta sẽ thu hồi lại vùng nhớ này nhờ hàm free() Cú pháp: void free(void *block) Ý nghĩa: Giải phóng vùng nhớ được quản lý bởi con trỏ block. Ví dụ: Ở ví dụ trên, sau khi thực hiện xong, ta giải phóng vùng nhớ cho 2 biến con trỏ pa & pb: free(pa); free(pb); 2.2.4. Một số phép toán  Phép gán con trỏ: Hai con trỏ cùng kiểu có thể gán cho nhau: Ví dụ: int a, *p, *a; float *f; A=5; p =&a; q=p; /*đúng*/ f=p;/* sai do khác kiểu*/ 81
  31. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Ta cũng có thể ép kiểu con trỏ theo cú pháp: ( *) Chẳng hạn, ví dụ trên được viết lại: int a, *p, *q; float *f; a=5; p =&a; q=p; /*đúng*/ f=(float*)p;/* đúng nhờ ép kiểu*/  Cộng, trừ con trỏ với một số nguyên Ta có thể cộng (+), trừ (-) 1 con trỏ với 1 số nguyên N nào đó; kết quả trả về là 1 con trỏ. Con trỏ này chỉ đến vùng nhớ cách vùng nhớ cảu con trỏ hiện tại N phần tử. Ví dụ: Cho đoạn chương trình sau: int *pa; /*cấp phát vùng nhớ 20byte = 10 số nguyên*/ pa =(int*)malloc(20); int *pb, *pc; pb = pa +7; pc=pb -3 ; Lúc này hình ảnh của pa, pb, pc như sau :  Con trỏ Null : Là con trỏ không chứa địa chỉ nào cả. Ta có thể gán giá trị NULL cho 1 con trỏ có kiểu bất kỳ. Lưu ý : - Ta không thể cộng 2 con trỏ với nhau - Phép trừ 2 con trỏ cùng kiểu sẽ trả về 1 giá trị nguyên (int). Đây chính là khoảng cách(số phần tử) giữa 2 con trỏ đó. Chẳng hạn, trong ví dụ trên pc – pa = 4 3. Con trỏ và mảng 1 chiều Giữa mảng và con trỏ có một sự liên hệ rất chặt chẽ. Những phần tử của mảng có thể được xác định bằng chỉ số trong mảng, bên cạnh đó chúng cũng có thể được xác lập qua biến con trỏ. 82
  32. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái 3.1 Phép toán lấy địa chỉ Ta có các quy tắc sau: & [0] tương đương với & [ ] tương đương với + [ ] tương đương với *( + ) Ví dụ : Cho 1 mảng 1 chiều các số nguyên a có 5 phần tử, truy cập các phần tử theo kiểu mảng và theo kiểu con trỏ. #include #include /*nhap mang binh thuong*/ void nhapmang(int a[], int n) { int i; for ( i=0; i<n; i++) { printf (“phan tu thu %d:”, i); scanf(“%d”, & a[i]); } } /* nhap mang theo dang con tro*/ void nhapcontro(int a[], int n) { int i; for (i=0; i<n;i++) { printf(“phan tu thu %d:”,i); scanf(“%d”, a+i); } } int main() 83
  33. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái { int a[20], n, i; clrscr(); printf(“so phan tu n=”); scanf(“%d”,&n); nhapmang(a,n); printf(“truy cap theo kieu mang:”); for (i=0;i [ ] tương đương với *( + ) & [ ] tương đương với ( + ) Trong đó là biến con trỏ, là 1 biểu thức số nguyên. Ví dụ: giả sử có khai báo: #include #include #include int main() { int *a; 84
  34. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái int i; clrscr(); a=(int*)malloc(sizeof(int)*10); for (i=0;i #include #include 85
  35. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái int main() { int i, mangint[10]; int *controint; clrscr() for(i=0;i<=9;i++) mangint[i]=i*2; controint=&mangint[5]; printf(“\n noi dung cua mang int ban dau=”); for (i=0;i<=9;i++) printf(“%d”, mangint[i]); printf(“\n noi dung cua contro int ban dau=”); for (i=0;i<=5;i++) printf(“%d”, controint[i]); for i=0;i<5;i++) controint [i]++; printf(“\n “); printf(“\n noi dung cua mang int sau khi tang 1=”); for (i=0;i<=9;i++) printf(“%d”, mangint[i]); printf(“\n noi dung cua contro int sau khi tang 1=”); for (i=0;i<5;i++) printf(“%d”, controint[i]); for i=0;i<5;i++) if(controint !=null) free(controint); getch(); return 0; } Kết quả của chương trình 86
  36. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái 4. Con trỏ và hàm Khi tham số hình thức của hàm là một con trỏ thì theo nguyên tắc gọi hàm ta dùng tham số thực tế là 1 con trỏ có kiểu giống với kiểu của tham số hình thức. Nếu lúc thực thi hàm ta có sự thay đổi trên nội dung vùng nhớ được chỉ bởi con trỏ tham số hình thức thì lúc đó nội dung vùng nhớ được chỉ bởi tham số thực tế cũng sẽ bị thay đổi theo. Ví dụ: xét hàm hoán vị được viết như sau: #include #include void hoanvi(int *a, int *b) { int c=*a; *a=*b; *b=c; } int main() { int m=20, n=30; clrscr(); printf(“truoc khi goi ham m=%d, n=%d\n”,m,n); hoanvi(&m,&n); printf(“sau khi goi ham m=%d, n=%d”,m,n); getch(); return 0; } 87
  37. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Kết quả thực thi chương trình Truớc khi gọi hàm Khi gọi hàm Sau khi gọi hàm m=20 , n=30 a=&m; b=&n; Con trỏ a,b bị giải phóng Lúc này m,n đã thay đổi: *a=m; *b=n; m=30, n=20 &m, &n m=20 , n=30 Đổi chỗ ta được &m, &n a=30, b= 20 m = 30 , n= 20 &m, &n BÀI TẬP THỰC HÀNH Bài tập 1 Khai báo 1 biến nguyên a và biến trỏ kiểu nguyen p, cho p trỏ đến a. in ra màn hình địa chỉ của a, nội dung trong p, địa chỉ của biến trỏ p. #include #include #include void main() { int a, *p; clrscr(); p=&a; printf("\nDia chi cua bien nho a= %x",&a); printf("\nNoi dung cua bien tro p= %x",p); printf("\n dia chi cu bien tro p= %x",&p); getch(); } Bài tập 2 88
  38. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Viết chương trình nhập vào từ bàn phím n số nguyên, cấp phát một vùng nhớ động để lưu trữ n số nguyên đó, hiện các số vừa nhập, tính tổng, trung bình cộng, max, min của dãy số đó. #include #include #include void main() { clrscr(); int *a,n ,i; printf("\n nhap so luong phan tu n= "); scanf("%d",&n); // cap phat bo nho a=(int*)malloc(n*sizeof(int)); //nhap du lieu for(i=0;i<n;i++) { printf("\n Nhap so thu %d ",i+1); scanf("%d",a+i); } // hien du lieu printf("\n day so vua nhap la: "); for(i=0; i<n; i++) { printf(" %d",*(a+i)); } //tinh tong int tong=0; for(i=0; i<n; i++) { tong=tong + *(a+i); } printf("\n tong cua day so do la: %d",tong); 89
  39. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái //tinh tb cong printf("\n Trung binh cong cua day do la: %f",tong/(n*1.0)); // tim max int max= *a; for(i=0; i *(a+i)) min = *(a+i); } printf("\n so nho nhat la: %d", min); getch(); } 90
  40. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Chương 7 : CHUỖI KÝ TỰ 1. Khái niệm Trong C không có kiểu xâu, do vậy ngôn ngữ C sử dụng mảng để lưu trữ chuỗi ký tự, mỗi phần tử của mảng sẽ là một kiểu dữ liệu kiểu char Chuỗi ký tự là một dãy các ký tự hoặc một mảng các ký tự. Các ký tự được lưu theo thứ tự từ trái sang phải của chuỗi và bắt đầu bằng phần tử có chỉ số 0 cho đến hết chuỗi. Kết thúc chuỗi là ký tự ‘\0’ (còn được gọi là ký tự NULL trong bảng mã ASCII). Các hằng chuỗi ký tự được đặt trong cặp dấu nháy kép ””. 2. Khai báo 2.1 Khai báo theo mảng Cú pháp: char [Chiều dài tối đa] VD: Trong chương trình, ta có khai báo: char Ten[12]; Trong khai báo trên, bộ nhớ sẽ cung cấp 12+1 bytes để lưu trữ nội dung của chuỗi ký tự Ten; byte cuối cùng để lưu ký tự ‘\0’ để chấm dứt chuỗi. Ghi chú: Chiều dài tối đa của biến chuỗi là một hằng nguyên nằm trong khoẳng từ 1 đến 255 bytes. Chiều dài tối đa không nên khai báo thừa để tránh lẵng phí bộ nhớ, nhưng cũng không nên khai báo thiếu. 2.2 Khai báo theo con trỏ Cú pháp: char * ; VD: Trong chương trình, ta có khai báo: char *Ten; Trong khai báo trên, bộ nhớ sẽ dành 2 bytes để lưu trữ địa chỉ của biến con trỏ Ten đang chỉ đến, chưa cung cấp nơi để lưu trữ dữ liệu. Muốn có chỗ để lưu trữ dữ liệu, ta phải gọi đến hàm malloc() hoặc calloc có trong alloc.h, sau đó mới gán dữ liệu cho biến. 91
  41. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái 3. Các thao tác trên chuỗi 3.1 Nhập chuỗi từ bàn phím Để nhập một chuỗi ký tự từ nàn phím, ta sử dụng hàm gets() với cú pháp như sau: gets( ) VD: char Ten[20]; gets(Ten); Ta cũng có thể sử dụng hàm scanf() để nhập dữ liệu cho biến chuỗi, tuy nhiên lúc này ta chỉ có thể nhập được một chuỗi không có dấu khoẳng trắng. Ngoài ra, hàm cgets() (trong conio.h) cũng được sử dụng để nhập chuỗi. Chú ý: Nếu trước lệnh nhập chuỗi có lệnh nhập số thì phải xóa vùng đệm bàn phím trước khi nhập chuỗi bằng lệnh sau: fflush(stdin); Ví dụ int n; char s[50]; printf(“nhap so n =”); scanf(“%d”,&n); printf(“nhap chuoi s = ”); fflush(stdin); gets(s); 3.2 Xuất chuỗi ra màn hình Để xuất chuỗi (biểu thức chuỗi) lên màn hình, ta sử dụng hàm puts(), với cú pháp như sau: puts( ) VD: Nhập vào một chuỗi và hiển thị trên màn hình chuỗi vừa nhập. #include #include #include int main() { 92
  42. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái char Ten[12]; printf(“Nhap chuoi:”); gets(Ten); printf(“Chuoi vua nhap”);puts(Ten); getch(); return 0; } Ngoài ra, ta có thể sử dụng hàm printf(), cputs() (trong conio.h) để hiển thị chuỗi lên màn hình. 3.3 Một số hàm xử lý chuỗi  Hàm cho biết độ dài thực sự (số ký tự) của chuỗi ký tự lưu trong mảng int strlen(s); ví dụ: char s[100] = “Ha Noi Viet Nam”; int a = strlen(s); sẽ trả về độ dài thực của sâu s gán vào biến nhớ a, cụ thể là a = 15  Lệnh gán chuỗi từ sâu này vào sâu khác strcpy(s1, s2 ); sẽ thưc hiện chép chuỗi ký tự trong sâu s2 vào s1. Chú ý: Trong ngôn ngữ C không thể sử dung phép gán ( = ) để gán hai chuỗi do đó phải sử dụng lệnh strcpy này.  Lệnh nối hai sâu lại với nhau strcat( s1,s2); sẽ thưc hiện nối sâu S2 vào sâu S1 ví dụ: char s1[100] = “Ha Noi”; char s2[100] =”Viet Nam”; strcat (s1,s2); kết quả xâu s1 sẽ thay đổi và đó là:”Ha NoiViet Nam”  Lệnh chuyển sâu thành chữ hoa strupr(s); 93
  43. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái  Lệnh chuyển sâu thành chữ thường. strlwr(s); 3.4 Một số thao tác cơ bản trên chuỗi Đêm ký tự, đếm từ Cho một chuỗi s và đếm xem trong chuỗi có bao nhiêu chữ thỏa mãn một điều kiện nào đó: int dem = 0 for( i =0; i<strlen(s);i++) if(s[i] thỏa mãn điều kiện) dem ++; printf(“so ky tu dem duoc la: %d”,dem) ; Thuật toán đếm số từ co trong chuỗi s theo tiêu chuẩn nhận biết đầu từ( là cặp ký tự thỏa mãn ký tự trước là dấu cách, ký tự sau khác dấu cách). int dem; if(s[0] = = ‘ ’ ) dem = 0 ; else dem = 1; for( i = 1;i < strlen(s);i++ ) { if(s[i] = =’ ’&& s[i+1]!=’ ’) dem ++; } printf(“so tu dem duoc là: %d”,dem) ; Chuẩn hóa chuỗi ký tự Một chuỗi ký tự ở dạng chuẩn nếu không có dấu cách ở đầu, không có dấu cách ở cuối và giữa hai từ có duy nhất một dấu cách. Thuật toán chuẩn hóa được chia làm 3 bước như sau Bước 1: Xóa dấu cách ở đầu int i = 0 ; while (s[i] = =’ ’&&i < strlen(s)) { i++ ; strcpy(&s[0],&s[i]); 94
  44. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái } Bước 2: Xóa dấu cách thừa giữu hai từ int i = 0; while(i<strlen(s)-1) { if(s[i] == ‘ ‘&& s[i+1] == ‘ ‘ ) strcpy(&s[i],&s[i+1]); i++; } Bước 3: xóa dấu cách ở cuối chuỗi while(s[strlen(s)-1] ==’ ’ ) s[strlen(s)-1]= ‘\0’; Tách từ + Tách từ đầu tiên i=0; while(s[i] = =' ')i++; for(j=0;(i<strlen(s))&&(s[i]!=' ');i++,j++) tudau[j] = s[i]; tudau[j]='\0'; Tách từ cuối cùng i = strlen(s)-1; while(s[i] = =' ')i ; j=i; while(s[i]!=' ')i ; i++; for(int x = 0;i<=j;i++) { tucuoi[x] = s[i]; x++; } tucuoi[x] = '\0'; 95
  45. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái 96
  46. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái BÀI TẬP THỰC HÀNH Bài tập 1 Lập trình đọc vào một sâu. Đọc vào từ bàn phím một kí tự bất kì, rồi đếm xem số lần xuất hiện ký tự này trong sâu. Báo kết quả ra màn hình. #include #include #include int dem(char s[],char a) { int d=0;//so ki tu int l=strlen(s); for(int i=0;i<l;i++) { if(s[i] == a) d++; } return d; } void main() { int i; char s[100],ch; clrscr(); printf("\n nhap vao mot cau: "); fflush(stdin);gets(s); printf("\n Nhap vao mot ki tu bat ki:"); ch = getche(); //dem so lan xuat hien cua ki tu printf("\nki tu %c xuat hien %d lan trong xau ",ch,dem(s,ch)); getch(); } 97
  47. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Bài tập 2 Viết chương trình đọc vào một xâu. Đếm số từ của một sâu. Từ được hiểu là xâu khác rỗng và không có dấu cách. #include #include #include int sotu(char S[]) { int dem=0,n; n=strlen(S); if(S[0]!=' ')dem=1; for(int i=1;i<n;i++) { if(S[i]= =' '&& S[i+1]!=' ')dem++; } return dem; } void main() { int i; char s[100]; clrscr(); printf("\n nhap vao mot sau ki tu: "); fflush(stdin);gets(s); //dem so tu cua mot sau printf("sau ban vua nhap co: %d tu",sotu(s)); getch(); } 98
  48. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Bài tập 3 Lập trình đọc vào một câu từ bàn phím rồi đưa ra màn hình dưới dạng một cột. thí dụ đọc vào TIÊN HỌC LỄ HẬU HỌC VĂN kết quả đưa ra thành TIÊN HỌC LỄ HẬU HỌC VĂN #include #include #include void hien(char s[]) { int l=strlen(s),n=0; while(s[n]= =' ')n++; for(int i = n ; i<l ; i++) { if(s[i]!=' ') printf("% c",s[i]); else printf("\n"); } } void main() { int i; char s[100]; clrscr(); printf("\n nhap vao mot cau: "); fflush(stdin);gets(s); printf("hien cau moi tu tren mot dong la:\n"); hien(s); getch(); } 99
  49. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Bài tập 4 Nhập vào từ bàn phím họ tên của một người. Hiện tên và họ của người đó lên màn hình. #include #include #include void main() { char s[100],ten[10],ho[10]; int i=0,j=0; clrscr(); printf("\n nhap vao ten cua mot nguoi: "); fflush(stdin);gets(s); // tach ho while(s[i]= =' ')i++; do{ ho[j]=s[i]; i++; j++; }while(s[i]!=' '); ho[j]='\0'; printf("\n ho cua nguoi nay la: %s",ho); // tach ten j=0; i=strlen(s); while(s[i]= =' ')i ; int i1=i; while(s[i1]!=' ')i1 ; do{ ten[j]=s[i1]; i1++; j++; }while(i1<=i); ten[j]='\0'; printf("\n ten cua nguoi nay la: %s",ten); getch(); } 100
  50. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái PHỤ LỤC HƯỚNG DẪN DEBUG TRÊN MÔI TRƯỜNG BORLAND C Mặc dù chương trình không còn lỗi nhưng khi chạy chương trình vẫn ra kết quả sai, những lỗi đó có thể là: • Dùng chấm phẩy sau: if, else, for, while, mà chưa thực hiện lệnh. • Định dạng nhập xuất sai hay khai báo sai kiểu dữ liệu. • Chia cho 0. • Không có điều kiện dừng (điều kiện dừng sai). • Phân tích thuật toán thiếu (chưa vét hết các trường hợp) hoặc sai. Các thao tác debug: Nhấn F7 hoặc F8 để chạy từng bước (nếu không có lỗi khi biên dịch) - F7: Đi từng lệnh của hàm con nếu có gọi hàm. - F8: không vào chi tiết từng lệnh khi gọi đến hàm con (chỉ đưa ra kết quả của hàm con). Quan sát vệt sáng để biết chương trình đang thực hiện đến vị trí lệnh nào. - Nhấn Ctrl+F7 (hoặc nhấn phím Insert nếu đã có cửa sổ Watch): Nhập vào biến cần theo dõi giá trị các biến khi thực hiện xong lệnh hay hàm nào đó. 101
  51. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái - Có thể xóa biến trên cửa sổ Watch bằng cách chọn biến trên cửa sổ Watch và nhấn phím Delete. - Nếu không thấy cửa sổ hiển thị giá trị biến (Watch) nhấn Alt+W+W hoặc vào menu Window chọn Watch. Nếu muốn bỏ qua một đoạn nào đó (tức không cần kiểm tra đọan đó) thì nhấn F4 để chương trình thực thi tới vị trí dòng của dấu nháy rồi dừng lại đó (dấu nháy phải tại vị trí những dòng phía sau của vệt sáng, nhấn F6 để chuyển qua lại các cửa sổ). - Muốn thay đổi giá trị của biến ta dùng phím Ctrl+F4 để hiển thị cửa sổ. 102
  52. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Nhập vào tên biến ở ô Expression, chọn nút Evaluate (hoặ nhấn Enter), ô Result sẽ hiển thị kết quả tại thời điểm đó, sau đó nhập giá trị mới cho biến tại ô New Value Enter (dùng phím tab để di chuyển vị trí chọn). Ngoài ra có thể đánh dấu để chương trình thực thi đến vị trí đánh dấu (khi chưa chạy từng bước) dùng phím F8 để đánh dấu ngay vị trí dấu nháy. Vị trí đánh dấu sẽ có vệt sáng màu đỏ. Có thể đánh dấu nhiều vị trí khác nhau. Nhấn Ctrl+F9 để chương trình thực thi đến vị trí đánh dấu theo thứ tự từ trên xuống dưới, đồng thời cũng có thể dùng phím F7 hoặc F8 giống như trên để chạy từng bước. 103
  53. Giáo trình: Lập trình cơ bản Trường Cao đẳng nghề Yên Bái Ngoài ra, có thể dùng phím ALT+F5 để xem kết quả xuất trong quá trình debug (để kiểm tra nhập xuất). Trong quá trình chạy từng bước có thể kết thúc bằng cách nhấn Ctrl+F2. Các thao tác liên quan đến cửa sổ Watch - Di chuyển cửa sổ Watch: Chọn cửa sổ Watch, nhấn Ctrl+F5. Sau đó dùng phím mũi tên để di chuyển cửa sổ tới vị trí mới. Nhấn phím Enter. - Thay đổi kích thứơc cửa sổ Watch (khi đang chọn bằng Ctrl+F5 trên cửa sổ Watch) nhấn Shift + phím mũi tên rồi nhấn phím Enter. TÀI LIỆU THAM KHẢO PHẠM VĂN ẤT: “Kỹ thuật lập trình C: cơ sở và nâng cao”. Nhà Xuất BảnKhoa Học Kỹ Thuật – 1996. Lê Mạnh Thạnh, Giáo trình môn lập trình C, NXB Giáo dục, 2000; Nguyễn Linh Giang, Nguyễn Xuân Thực, Lê Văn Thái, Giáo trình kỹ thuật lập trình C, NXB Giáo dục, 2005; Ngô Trung việt, Giáo trình ngôn ngữ lập trình C và C++ , NXB Giao thông vận tải, 1995; B. Kernighan and D. Ritchie, The C programming language, Prentice Hall, 1990. 104