Hàm vớiđốisốbấtđịnhtrongCTrong các giáo trình C thường chỉ hướng dẫn cách xây dựng hàmvới các đối cố định. Mỗi đối cần có một tham số (cùng kiểu với nó) trong lời gọi hàm. Tuy nhiên một vài hàm chuẩn của C lại không như vậy, mà linh hoạt hơn, chẳng khi dùng hàm printf hay scanf thì số tham số mà ta cung cấp cho hàm là không cố định cả về số lượng lẫn kiểu cách. Ví dụ trong câu lệnh: printf(“\n Tổng = %d “ , 3+4+5) ; có 2 tham số, nhưng trong câu lệnh: printf(“\n Hà Nội“ ) ; chỉ có một tham số. Như vậy cần phân biệt các khái niệm sau: - Đốisố cố định được khai báo trong dòng đầu của hàm, nó có tên và kiểu - Tham số ứng vớiđốisố cố định gọi là tham số cố định - Đốibấtđịnh được khai báo bởi ba dấu chấm: bấtđịnh cả về số lượng và kiểu - Tham sốbấtđịnh (ứng vớiđốibất định) là một danh sách giá trị vớisố lượng và kiểu tuỳ ý (không xác định) Trong phụ lục này sẽ trình bầy cách xây dựng các hàmvớiđốisốbất định. Công cụ chủ yếu được dùng là con trỏ và danh sách. 1. Biến con trỏ Biến con trỏ (hay con trỏ) dùng để chứa địa chỉ của biến, mảng, hàm, . Có nhiều kiểu địa chỉ, vì vậy cũng có nhiều kiểu con trỏ. Biến con trỏ được khai báo theo mẫu: Kiểu *Tên_biến_con_trỏ ; Ví dụ: float px ; // px là con trỏ thực Các phép toán quan trọng trên con trỏ gồm: + Gán địa chỉ một vùng nhớ cho con trỏ (dùng toán tử gán, phép lấy địa chỉ, các hàm cấp phát bộ nhớ) + Truy nhập vào vùng nhớ thông qua con trỏ, dùng phép toán: *Tên_con_trỏ (Để ý ở đây có 2 vùng nhớ: vùng nhớ của biến con trỏ và vùng nhớ mà địa chỉ đầu của nó chứa trong biến con trỏ) + Cộng địa chỉ để con trỏ chứa địa chỉ của phần tử tiếp theo, dùng phép toán: ++ Tên_con_trỏ hoặc Tên_con_trỏ ++ Chú ý rằng các phép toán trên chỉ có thể thực hiện đốivới con trỏ có kiểu. 2. Danh sách không cùng kiểu Dùng con trỏ có kiểu chỉ quản lý được một danh sách giá trị cùng kiểu, ví dụ dẫy số thực, dẫy số nguyên, dẫy các cấu trúc, Khi cần quản lý một danh sách các giá trị không cùng kiểu ta phải dùng con trỏ không kiểu (void) khai báo như sau: void * Tên_con_trỏ ; 561 562 Con trỏ void có thể chứa các địa chỉ có kiểu bất kỳ, và dùng để trỏ đến vùng nhớ chứa danh sách cần quản lý. Một chú ý quan trọng là mỗi khi gửi vào hay lấy ra một giá trị từ vùng nhớ, thì tuỳ theo kiểu giá trị mà ta phải dùng phép chuyển kiểu thích hợp đốivới con trỏ. Ví dụ sau minh hoạ cách lập một danh sách gồm một số nguyên, một số thực và một chuỗi ký tự. Chúng ta cần một bộ nhớ để chứa số nguyên, số thực và địa chỉ chuỗi và dùng các con trỏ void để quản lý vùng nhớ này. void *list , *p ; // Con trỏ list trỏ tới đầu danh sách // p dùng để duyệt qua các phần tử của danh sách list=malloc(sizeof(int) + sizeof(float)+ sizeof(char*) ); p=list; *((int*)p) = 12; // Đưa số nguyên 12 vào danh sách ((int*)p)++ ; // Chuyển sang phần tử tiếp theo *((float*)p) = 3.14; // Đưa số thực 3.14 vào danh sách ((float*)p)++ ; // Chuyển sang phần tử tiếp theo *((char**)p) = “HA NOI”; // Đưa địa chỉ chuỗi “HA NOI” // vào danh sách // Nhận các phần tử trong danh sách p=list; // Về đầu danh sách int a = *((int*)p); // Nhận phần tử thứ nhất ((int*)p)++ ; // Chuyển sang phần tử tiếp theo float x= *((float*)p); // Nhận phần tử thứ hai ((float*)p)++ ; // Chuyển sang phần tử tiếp theo char *str = *((char**)p) ; // Nhận phần tử thứ ba 3. Hàm vớiđốisốbấtđịnh + Đốibấtđịnh bao giờ cũng đặt sau cùng và được khai báo bằng dấu ba chấm. Ví dụ ví dụ hàm void f(int n, char *s, .) ; có 2 đối cố định là n, s và đốibất định. + Để nhận được các tham sốbấtđịnhtrong lời gọi hàm ta cần lưu ý các điểm sau: - Các tham sốbấtđịnh chứa trong một danh sách. Để nhận được địa chỉ đầu danh sách ta dùng một con trỏ void và phép gán sau: void *list ; list = . ; - Dùng một tham số cố định kiểu chuỗi để quy địnhsố lượng và kiểu của mỗi tham sốtrong danh sách, ví dụ: “3i” hiểu là : tham sốbấtđịnh gồm 3 giá trị int “3f” hiểu là : tham sốbấtđịnh gồm 3 giá trị float “fiss” hiểu là có 4 tham sốbấtđịnh có kiểu lần lượt là float, int, char*, char* Một khi đã biết được địa chỉ đầu danh sách, biết được số lượng và kiểu của mỗi tham số , thì dễ dàng nhận được giá trị các tham số để sử dụng trong thân hàm. Ví dụ sau đây minh hoạ cách xây dựng các hàmvới tham sốbất định. Hàm dùng để in các 563 564 giá trị kiểu int, float và char. Hàm có một tham số cố định để cho biết có bao nhiêu giá trị và kiểu các giá trị cần in. Kiểu quy định như sau: i là int, f là float, s là char*. Tham số có 2 cách viết: lặp (gồm một hằng số nguyên và một chữ cái định kiểu) và liệt kê (một dẫy các chữ cái định kiểu). Ví dụ: “4s” có nghĩa in 4 chuỗi “siif” có nghĩa in một chuỗi, 2 giá trị nguyên và một giá trị thực: #include <stdio.h> #include <ctype.h> #include <string.h> #include <conio.h> #include <stdlib.h> #include <stdarg.h> void InDanhSachGiaTri(char *st, .) { void *list ; int gt_int ; float gt_float; char *gt_str; int n,i ; char kieu; int lap; list = . ; // list tro toi vung nho chua danh sach dia chi cac // tham so lap = isdigit(st[0]) ; if (lap) n=st[0] - '0' ; else n=strlen(st); printf("\n n= %d lap = %d",n,lap); getch(); for(i=0;i<n;++i) { if(lap) kieu=st[1]; else kieu = st[i]; printf("\nKieu= %c",kieu); getch(); switch(kieu) { case 'i' : gt_int = *((int*)list); if(!lap) ((int*)list)++ ; printf("\nGia tri %d = %d",i,gt_int); break; case 'f' : gt_float = (float) (*((double*)list)); if(!lap) ((double*)list)++ ; printf("\nGia tri %d = %0.2f",i,gt_float); break; case 's' : gt_str = *((char**)list) ; if(!lap) ((char**)list)++ ; printf("\nGia tri %d = %s",i,gt_str); } } } void main() { float x=3.14; int a=123; char *tp="HAI PHONG"; InDanhSachGiaTri("4i",a); InDanhSachGiaTri("4s","HA NOI"); InDanhSachGiaTri("ifsssffii", a, x, tp, tp,"QUY NHON", x, 6.28, a, 246); InDanhSachGiaTri("4f",6.28); getch(); } 4. Hàm không đối và hàm vớiđốibấtđịnh Nhiều người nghĩ hàm khai báo như sau void f(); là hàm không đốitrong C. Trong C++ thì hiểu như thế là đúng, còn trongC thì đó là hàm có đốibấtđịnh (hàm không đốitrongC khai báo như sau: f(void) ). Do không có đối cố định nào cho biết về số lượng và kiểu của các tham sốbất định, nên giải pháp ở đây là dùng các biến toàn bộ. Rõ ràng giải pháp này không không thuận tiện cho người dùng vì phải khai báo đúng tên biến toàn bộ và phải khởi gán giá trị cho nó trước khi gọi hàm. Ví dụ trình bầy một hàm chỉ có đốibấtđịnh dùng để tính max và min của các giá trị thực. Các tham sốbấtđịnh được đưa vào theo trình tự sau: Địa chỉ chứa max, địa chỉ chứa min, các giá trị nguyên cần tính max, 565 566 min. Chương trình dùng biến toàn bộ N để cho biết số giá trị nguyên cần tính max, min. int N; void maxmin() { void *lt = . ; float *max, *min , tg; int i; max = *((float**)lt)++; min = *((float**)lt)++; *max = *min = (float) *((double*)lt)++; for(i=1;i<N;++i) { tg= (float) *((double*)lt)++; if(tg > *max) *max = tg; if(tg < *min) *min = tg; } } . Hàm với đối số bất định trong C Trong c c giáo trình C thường chỉ hướng dẫn c ch xây dựng hàm với c c đối c định. Mỗi đối c n c một tham số (c ng. 2 đối c định là n, s và đối bất định. + Để nhận đư c c c tham số bất định trong lời gọi hàm ta c n lưu ý c c điểm sau: - C c tham số bất định chứa trong