Ngôn ngữ lập trình C - Chương 8: Cấu trúc
Cấu trúc là tập hợp của một hoặc nhiều biến, chúng có thể khác kiểu nhau,
được nhóm lại dưới một cái tên duy nhất để tiện sử lý. Cấu trúc còn gọi là bản ghi
trong một số ngôn ngữ khác, chẳng hạn như PASCAL.
Cấu trúc giúp cho việc tổ chức các dữ liệu phức tạp, đặc biệt trong những
chương trình lớn vì trong nhiều tình huống chúng cho phép nhóm các biến có liên
quan lại để xử lý như một đơn vị thay vì các thực thể tách biệt.
Một ví dụ được đề cập nhiều đến là cấu trúc phiếu ghi lương, trong đó mỗi
nhân viên được mô tả bởi một tập các thuộc tính chẳng hạn như : tên, địa chỉ, lương,
phụ cấp vv. một số trong các thuộc tính này lại có thể là cấu trúc bởi trong nó có thể
chứa nhiều thành phần : Tên ( Họ, đệm, tên ), Địa chỉ ( Phố, số nhà ) vv.
Trong chương này chúng ta sẽ minh hoạ cách sử dụng của các cấu trúc trong
chương trình.
8: Cấu trúc 103 Hoàn toàn tương tự như vậy : ta có thể sử dụng một kiểu cấu trúc đã mô tả để khai báo các cấu trúc và mảng cấu trúc. Cách khai báo mảng cấu trúc : struct tên_kiểu_cấu_trúc_đã_định_nghĩa tên_mảng_cấu_trúc[số phần tử của mảng]; Ví dụ : Ví dụ 1 : Giả sử kiểu cấu trúc canbo đã được định nghĩa như mục trên. Khi đó dòng khai báo : struct canbo cb1,cb2,nhom1[10],nhom2[7]; sẽ cho : Hai biến cấu trúc cb1 và cb2. Hai mảng cấu trúc nhom1 co 10 phần tử và nhom2 có 7 phần tử và mỗi phần tử của hai nhóm này có kiểu canbo. Ví dụ 2 : Đoạn chương trình sau sẽ tính tổng lương cho các phần tử nhóm 1: double tongluong=0; for (i=0;i<10;++i) tongluong+=nhom1[i].luong; Chú ý : Không cho phép sử dụng phép toán lấy địa chỉ đối với các thành phần của mảng cấu trúc khác kiểu nguyên. Chẳng hạn không cho phép sử dụng câu lệnh sau : scanf("%f",&nhom1[5].luong); Trong trường hợp này ta dùng biến trung gian. 8.5. Khởi đầu một cấu trúc : Có thể khởi đầu cho một cấu trúc ngoài, cấu trúc tĩnh, mảng cấu trúc ngoài và mảng cấu trúc tĩnh Chương 8: Cấu trúc 104 8.6. Phép gán cấu trúc : Có thể thực hiện phép gán trên các biến và phần tử mảng cấu trúc cùng kiểu như sau : Gán hai biến cấu trúc cho nhau Gán biến cấu trúc cho phần tử mảng cấu trúc Gán phần tử mảng cấu trúc cho biến cấu trúc Gán hai phần tử mảng cấu trúc cho nhau Mỗi một phép gán trên tương đương với một dãy phép gán các thành phần tương ứng. Ví dụ : Đoạn chương trình sau minh hoạ cách dùng phép gán cấu trúc để để sắp xếp n thí sinh theo thứ tự giảm của tổng điểm : struct thisinh { char ht[25]; float td; } tg,ts[100]; for (i=1;i<=n-1;++i) for (j=1;j<=n;++j) if (ts[i].td<ts[j].td) { tg=ts[i]; ts[i]=ts[j]; ts[j]=tg; } 8.7. Con trỏ cấu trúc và địa chỉ cấu trúc : 8.7.1. Con trỏ và địa chỉ : Ta xét ví dụ sau : struct ngay { Chương 8: Cấu trúc 105 int ngaythu; char thang[10]; int nam; }; struct nhancong { char ten[20]; char diachi[25]; double bacluong; struct ngay ngaysinh; }; Nếu khai báo : struct nhancong *p,*p1,*p2,nc1,nc2,ds[100]; ta có : p, p1, p2 là con trỏ cấu trúc nc1, nc2 là các biến cấu trúc ds là mảng cấu trúc Con trỏ cấu trúc dùng để lưu trữ địa chỉ của biến cấu trúc và mảng cấu trúc. Ví dụ : p1=&nc1; /* Gửi địa chỉ nc1 vào p1 */ p2=&ds[4]; /* Gửi địa chỉ ds[4] vào p2 */ p=ds; /* Gửi địa chỉ ds[0] vào p */ 8.7.2. Truy nhập qua con trỏ: Có thể truy nhập đến các thành phần thông qua con trỏ theo một trong hai cách sau : Cách một : Tên_con_trỏ->Tên_thành_phần Cách hai : Chương 8: Cấu trúc 106 (*Tên_con_trỏ).Tên_thành_phần Ví dụ : nc1.ngaysinh.nam p1-> ngaysinh.nam ds[4].ngaysinh.thang (*p2). ngaysinh.thang 8.7.3. Phép gán qua con trỏ: Giả sử ta gán : p1=&nc1; p2=&ds[4]; Khi đó có thể dùng : *p1 thay cho nc1 *p2 thay cho ds[4] Tức là viết: ds[5]=nc1; ds[4]=nc2; Tương đương với : ds[5]=*p1; *p2=nc2; 8.7.4. Phép cộng địa chỉ : Sau các phép gán : p=ds; p2=&ds[4]; thì p trỏ thới ds[[0]] và p2 trỏ tới ds[4]. Ta có thể dùng các phép cộng, trừ địa chỉ để làm cho p và p2 trỏ tới các thành phần bất kỳ nào khác. Ví dụ : Sau các lệnh : Chương 8: Cấu trúc 107 p=p+10; p2=p2-4; thì p trỏ tới ds[10] còn p2 trỏ tới ds[0] 8.7.5. Con trỏ và mảng : Giả sử con trỏ p trỏ tới đầu mảng ds, khi đó : Ta có thể truy nhập tới các thành phần cấu trúc bằng các cách sau : + ds[i].thành_phần ds[i].ngaysinh.nam + p[i].thành_phần p[i].ngaysinh.nam + (p+i)->thành_phần (p+i)->ngaysinh.nam Khi ta sử dụng cả cấu trúc thì các cách viết sau là tương đương : ds[i] p[i] *(p+i) 8.8. Cấu trúc tự trỏ và danh sách liên kết : Khi ta lập một chương trình quản lý mà bản thân số biến (cấu trúc) chưa được biết trước, nếu ta sử dụng mảng ( cấp phát bộ nhớ tĩnh ) thì ta phải sử dụng số các phần tử là tối đa. Như vậy sẽ có rất nhiều vùng nhớ được cấp phát mà không bao giờ dùng đến. Lúc đó ta có cách để cấp phát bộ nhớ động. Số vùng nhớ cấp ra đủ số biến cần dùng. Cấu trúc có ít nhất một thành phần là con trỏ kiểu cấu trúc đang định nghĩa gọi là cấu trúc tự trỏ. Ví dụ : Các cách để định nghĩa cấu trúc tự trỏ person: Cách 1 : typedef struct pp { char ht[20]; Chương 8: Cấu trúc 108 char qq[25]; int tuoi; struct pp *tiep; } person; Cách 2 : typedef struct pp person struct pp { char ht[20]; char qq[25]; int tuoi; person *tiep; }; Cách 3 : struct pp { char ht[20]; char qq[25]; int tuoi; struct pp *tiep; }; typedef pp person; Cấu trúc tự trỏ được dùng để xây dựng danh sách liên kết ( móc nối ), đó là một nhóm các cấu trúc có tính chất sau : ( Móc nối theo chiều thuận ). Biết địa chỉ cấu trúc đầu đang được lưu trữ trong một con trỏ nào đó. Trong mỗi cấu trúc ( trừ cấu trúc cuối ) chứa địa chỉ của cấu trúc tiếp sau của danh sách. Cấu trúc cuối chứa hằng NULL. Ví dụ : Chương 8: Cấu trúc 109 Với danh sách này, ta có thể lần lượt từ cấu trúc đầu đến cấu trúc cuối theo chiều từ trên xuống dưới. Nhóm cấu trúc móc nối theo chiều ngược có tính chất sau : Biết địa chỉ cấu trúc cuối. Trong mỗi cấu trúc ( trừ cấu trúc đầu ) đều chứ địa chỉ của cấu trúc trước. Cấu trúc đầu chứa hằng NULL. Với danh sách này, ta có thể lần lượt từ cấu trúc cuối lên cấu trúc đầu theo chiều từ dưới lên trên. Ngoài ra, ta có thể xây dựng các danh sách mà mỗi phần tử chứa hai địa chỉ của cấu trúc trước và cấu trúc sau. Với loại danh sách này, ta có thể truy nhập theo cả hai chiều trên. Khi làm việc với danh sách móc nối, ta thường phải tiến hành các công việc sau sau : ( Giả sử ta có con trỏ p, trỏ pdau chỉ cấu trúc đầu của danh sách, con trỏ tiep là thành phần con trỏ của cấu trúc ) Tạo danh sách mới : Cấp phát bộ nhớ cho một cấu trúc Nhập một biến cấu trúc vào vùng nhớ vừa cấp Gán địa chỉ của cấu trúc sau cho thành phần con trỏ của cấu trúc trước Duyệt qua tất cả các phần tử của danh sách : Đưa trỏ p về trỏ cùng cấu trúc với pdau bằng lệnh : p=pdau Để chuyển tiếp đến người tiếp theo ta dùng lệnh : p=p->tiep Dấu hiệu để biết đang xét cấu trúc cuối cùng của danh sách là : p->tiep==NULL ......... NULL Pdau Chương 8: Cấu trúc 110 Loại một cấu trúc ra khỏi danh sách : Lưu trữ địa chỉ của cấu trúc cần loại vào một con trỏ (Để giải phóng bộ nhớ của cấu trúc này) Sửa để cấu trúc trước đó có địa chỉ của cấu trúc cần loại Giải phóng bộ nhớ cấu trúc cần loại Bổ xung hoặc chèn một cấu trúc vào danh sách: Cấp phát bộ nhớ và nhập bổ xung Sửa thành phần con trỏ trong các cấu trúc có liên quan để đảm bảo mỗi cấu trúc chứa địa chỉ của cấu trúc tiếp theo Hàm cấp phát bộ nhớ : void *malloc(kichthuoc_t kichthuoc); Hàm lấy trong thư viện alloc.h hoặc stdlib.h. kichthuoc tính bằng số by te. Hàm sẽ đưa con trỏ về vị trí ô nhớ vừa được cấp hoặc về NULL nếu không đủ bộ nhớ cần thiết. Nếu kichthuoc == 0 thì nó trả về NULL. Ví dụ : #include "stdio.h" #include "string.h" #include "alloc.h" #include "process.h" int main() { char *str; /* Cấp phát bộ nhớ cho xâu ký tự */ if ((str = malloc(10)) == NULL) { printf("Not enough memory to allocate buffer\n"); exit(1); /* Kết thúc chương trình nếu thiếu bộ nhớ */ } Chương 8: Cấu trúc 111 /* copy "Hello" vào xâu */ strcpy(str, "Hello"); /* Hiển thị xâu */ printf("String is %s\n", str); /* Giải phóng bộ nhớ */ free(str); return 0; } Ví dụ : Tạo một danh sách liên kết. Các biến cấu trúc gồm các trường : Họ tên, Quê quán, tuổi, và một trường con trỏ là Tiếp. Móc nối theo chiều thuận (Vào trước ra trước FIFO first in first out ): #include "stdio.h" #include "alloc.h" #include "conio.h" #include "string.h" typedef struct pp { char ht[25]; char qq[20]; int tuoi; struct pp *tiep; } nhansu; main() { char tt; nhansu *pdau,*pcuoi,*p; char tam[10]; clrscr(); pdau=NULL; Chương 8: Cấu trúc 112 do { p=(nhansu*)malloc(sizeof(nhansu)); printf("\n Ho ten : "); gets(p->ht); printf(" Que quan : "); gets(p->qq); printf(" Tuoi: "); gets(tam); p->tuoi=atoi(tam); if (pdau==NULL) { pdau=p; pcuoi=p; p->tiep=NULL; } else { pcuoi->tiep=p; pcuoi=p; p->tiep=NULL; } printf("\nBam phim bat ky de tiep tuc, ESC de dung"); tt=getch(); } while(tt!=27) ; /* Đưa danh sách liên kết ra màn hình, trỏ pdau tro */ printf("\n Danh sach nhu sau :\n"); p=pdau; while (p!=NULL) { Chương 8: Cấu trúc 113 printf("\n Ho ten: %25s Que : %20s Tuoi : %d",(*p).ht,(*p).qq,(*p).tuoi); p=p->tiep; } getch(); } Móc nối theo chiều ngược (Vào sau ra trước LIFO last in first out ): #include "stdio.h" #include "alloc.h" #include "conio.h" #include "string.h" typedef struct pp { char ht[25]; char qq[20]; int tuoi; struct pp *tiep; } nhansu; main() { char tt; nhansu *pdau,*pcuoi,*p; char tam[10]; clrscr(); pdau=NULL; do { p=(nhansu*)malloc(sizeof(nhansu)); printf("\n Ho ten : "); gets(p->ht); printf(" Que quan : "); Chương 8: Cấu trúc 114 gets(p->qq); printf(" Tuoi: "); gets(tam); p->tuoi=atoi(tam); if (pdau==NULL) { pdau=p; pcuoi=p; p->tiep=NULL; } else { p->tiep=pcuoi; pcuoi=p; } printf("\nBam phim bat ky de tiep tuc, ESC de dung"); tt=getch(); } while(tt!=27) ; /* Đưa danh sách liên kết ra màn hình, trỏ pdau tro */ printf("\n Danh sach nhu sau :\n"); p=pcuoi; while (p!=NULL) { printf("\n Ho ten: %25s Que : %20s Tuoi : %d",(*p).ht,(*p).qq,(*p).tuoi); p=p->tiep; } getch(); }
File đính kèm:
- CHUONG_8.pdf