More information: www.itspiritclub.

net

Structures, Unions and Enumerations
Lời nói đầu: Đây là một trong các loạt bài về C\C++ của site: itspiritclub.net . Chúng tôi sẽ đưa ra những khái niệm từ cơ bản tới nâng cao về structure,union, và enum để các bạn tiện tham khảo . Các bạn có thể tra cứu nhanh thông qua mục lục ở dưới dây: Mục lục: 1/ Biến Structure ...................................................................................................................................... 2 + Khai báo kiểu dữ liệu Structure .................................................................................... 2 .. + Khời gán giá trị cho biến cấu trúc: ............................................................................... 3 + Các phép toán trên biến cấu trúc: ................................................................................. 3 2/ Kiểu dữ liệu Structure: ................................................................................................................................ 4 + Khai báo một Structure Tags ........................................................................................ 4 + Structures như đối số và giá trị trả về ........................................................................... 4 3/ Cấu trúc lồng nhau array và structure .......................................................................................................... 5 +Nested Structure ( Cấu trúc lồng nhau) ........................................................................ 5 +Mảng của structure ...................................................................................................... 6 +Khởi gán một mảng của structure.................................................................................. 6 +PROGRAM: Quản lý một cơ sở dữ liệu parts ................................................................ 7 4/ Unions .......................................................................................................................................13 +Khái niệm ....................................................................................................................13 +Dùng union để tiết kiệm bộ nhớ ...................................................................................14 +Dùng unions để xây dựng cấu trúc dữ liệu hỗn hợp .....................................................16 +Thêm một “TAG FIELD” vào một union ......................................................................16 5/ Enumeration ...................................................................................................................................... 17 +Enum như những biến nguyên ......................................................................................18 +Dùng enum để khai báo “Tag Fields” ..........................................................................19 6/ Những câu hỏi thường gặp .................................................................................................................. 19-21

1

More information: www.itspiritclub.net Chương này sẽ giới thiệu về 3 kiểu dữ liệu mới: structures, unions và enumerations. Một structure là một tập hợp các giá trị (value ), có thể khác kiểu dữ liệu. Union thì tương tự như structure, ngoại trừ là các thành phần của một union thì chia sẻ cùng 1 vùng nhớ; và do đó, union có thể chứa 1 thành phần trong 1 lúc, không phải là đồng thời tất cả các thành phần. Một enumeration là 1 kiểu dữ liệu liệt kê, nó giúp bạn tổ chức dữ liệu khoa học hơn.

1/ Structure Variables
Không giống như kiểu chuỗi (array), các thành phần của structure không nhất thiết phải có cùng kiểu. Hơn nữa, các thành phần của structure có tên để phân biệt với nhau không phải là vị trí như chuỗi.

Khai báo kiểu dữ liệu Structure
Trong C++, một cấu trúc do người dùng tự định nghĩa được khai báo thông qua từ khoá struct: Struct < tên cấu trúc> { <Kiểu dữ liệu 1> < Tên thuộc tính 1 > ; <Kiểu dữ liệu 2> < Tên thuộc tính 2 > ; … }; Ví dụ, giả sử chúng ta cần lưu thông tin các phần trong 1 cửa hàng. Thông tin mà chúng ta sẽ lưu cho mỗi phần có thể bao gồm : number ( biến nguyên), name ( chuỗi) , và on_hand ( số lượng các phần) ( biến nguyên). Ta khai báo như sau: Struct { int number; char name[NAME_LEN + 1 ]; int on_hand; } part1, part2; Các thành phần của một structure được lưu trữ trong bộ nhớ theo thứ tự mà chúng được khai báo. Để có thể chỉ ra biến part1 lưu như thế nào trong bộ nhớ, ta hãy giả sử là: a/ part1 bắt đầu ở ô nhớ 2000 b/ 1 integer chiếm 4 bytes, c/ NAME_LEN có giá trị là 25 d/ không có khoảng trống giữa các biến

2

More information: www.itspiritclub.net

2000 2001 2002 2003 2004

...

Number

name

2029 2030 2031 2032 2033

on_hand

Khời gán giá trị cho biến cấu trúc: Struct { int number; char name[NAME_LEN + 1 ]; int on_hand; } part1 = {528,”disk drive”, 10}, part2 = {914,” printer cable”, 5};  Khởi gán có chỉ định: mỗi giá trị thì được gán tên tương ứng với thành phần o Ví dụ:  {.number = 528, .name = ”disk drive”, .on_hand = 10} o Thứ tự không quan trọng đối với kiểu khởi gán này. Người lập trình không nhất thiết phải nhớ thứ tự.  {.name = ”disk drive”, .number = 528, .on_hand = 10} o Không nhất thiết tất cả các thành phần phải có tiền tố:  {”disk drive”, .number = 528, .on_hand = 10}

Các phép toán trên biến cấu trúc: Để truy cập đến một thành phần của biến cấu trúc, ta viết tên của biến cấu trúc + dấu chấm + tên thành phần. Ví dụ: Các câu lệnh sau sẽ xuất ra giá trị của các thành phần part1: printf (“Part number: %d \n”, part1.number); printf (“Part name: %s \n”, part1.name); printf (“Quantity on hand: %d \n”, part1.on_hand); Xem xét ví dụ sau: 3

More information: www.itspiritclub.net Scanf(“%d”, &part1.on_hand); Biểu thức &part1.on_hand chứa 2 toán tử ( & và . ). Toán tử . được ưu tiên hơn toán tử & , nên & đưa ra địa chỉ của part1.on_hand như ta mong đợi

part2 = part 1; Phép toán = sẽ copy các thành phần tương ứng của part1 vào part2. Lưu ý: C không hỗ trợ phép toán trên toàn structure. Cụ thể hơn là ta không thể dùng phép toán = = hoặc ! = để kiểm tra 2 structure giống nhau hay không.

2/ Structure types.
Khai báo một Structure Tags Ví dụ sau sẽ khai báo một structure tag tên là part: Struct part { int number; char name[NAME_LEN + 1 ]; int on_hand; }; Lưu ý: Thiếu dấu chấm phẩy (;) sẽ gây ra lỗi Sau khi đã tạo part tag, ta có thể dùng nó để khai báo biến: struct part part1,part2; Ta không thể rút ngắn cú pháp bằng cách bỏ đi từ struct part part1,part2; //****WRONG ***// Structures như đối số và giá trị trả về Hàm có thể dùng structure như đối số và giá trị trả về. Hãy xem xét 2 ví dụ sau. Ví dụ 1: Đưa part structure ở bài trước như 1 đối số và in các phần tử ra: Void print_Part ( struct part p ) { Printf(“Part number: %d \n”, p.number); 4

More information: www.itspiritclub.net Printf(“Quantity on hand: %d \n”, p.on_hand); }; Gọi hàm như sau: print_Part (part1); Ví dụ 2: Hàm trả về một part structure Struct part build_part ( int number , const char * name, int on_hand) { Struct part p; p.number = number; strcpy ( p.name , name); p.on_hand = on_hand; return p; }; Gọi hàm: part1 = build_part( 528, “Disk Drive”,10 );

3/ Nested Arrays and Structures
Nested Structure ( Cấu trúc lồng nhau) Các cấu trúc có thể được định nghĩa lồng nhau khi một thuộc tính của một cấu trúc cũng cần có kiểu là một cấu trúc khác. Ví dụ : Giả sử ta đang định nghĩa 1 cấu trúc như sau, để lưu tên một người struct person_name { char first [FIRST_NAME_LEN + 1 ]; char middle_initial; char last[LAST_NAME_LEN + 1]; }; Chúng ta có thể dùng cấu trúc person_name trên như một phần của cấu trúc lớn hơn: Struct student { Struct person_name name; Int id, age; Char sex; 5

More information: www.itspiritclub.net } student1, student2; Để truy xuất vào student1 > first name … ta dùng 2 dấu chấm Strcpy ( student1.name.first, “ Fred”); Mảng của structure Một trong các sự kết hợp thông dụng nhất của mảng và cấu trúc là một mảng mà có các phần tử là cấu trúc( structure). Một mảng như vậy có thể phục vụ như là một cơ sở dữ liệu đơn giản. Ví dụ, mảng sau có thể chứa dữ liệu 100 phần tử part ( đã khai báo trong ví dụ trước). Struct part inventory[100]; Để truy cập tới một phần tử trong mảng, ta dùng các chỉ số dưới dòng. Để in phần tử tại vị trí I, ta dùng: print_part( inventory[i]); Để truy xuất vào phần tử của mỗi part ta cần kết hợp việc sử dụng chỉ số dưới dòng và dấu chấm. Ví dụ: inventory[i].number = 883; Khởi gán một mảng của structure Việc khởi gán một mảng của các structure cũng giống như khởi gán một mảng chiều. Lý do của khởi gán mảng các structure là khi chúng ta dự tính xử lý nó giống như một cơ sở dữ liệu mà không bị thay đổi trong quá trình chạy chương trình. Ví dụ: giả sử chúng ta đang làm việc trên một chương trình sẽ truy xuất tới mã vùng quốc gia khi thực hiện cuộc gọi quốc tế. Đầu tiên ta tạo 1 structure để lưu tên của quốc gia kèm mã vùng. struct dialing_code { char * country; int code; }; Tiếp theo, chúng ta sẽ khai báo mảng và khởi gán giá trị mã vùng: const struct dialing_code country_codes[] = { {“Argentina”, {“Brazil”, }; 54}, {“Bangladesh”, 55}, {“Myanmar”, 880}, 95},

6

More information: www.itspiritclub.net PROGRAM: Quản lý một cơ sở dữ liệu parts Để mô tả cách mà mảng dữ liệu có cấu trúc được dùng như thế nào, chúng ta sẽ phát triển một chương trình để quản lý cơ sở dữ liệu về các món đồ được chứa trong cửa hàng. Chương trình sẽ xây dựng quanh một mảng dữ liệu có cấu trúc, với mỗi cấu trúc lưu trữ các thông tin – part number , name và quantity . Chương trình sẽ thực hiện các chức năng sau:      Thêm một part mới: Chương trình sẽ phải in ra thông báo lỗi nếu part đã có ở trong CSDL hoặc nếu CSDL đã đầy Nhận một part number và in ra tên của part và số lượng (quantity on hand): Chương trình phải in thông báo nếu part number không tồn tại trong CSDL Nhận một part number và thay đổi số lượng ( quantity on hand): Chương trình phải in thông báo nếu part number không tồn tại trong CSDL In một bảng hiển thị tất cả thông tin trong CSDL: các parts phải hiển thị trong thứ tự mà chúng được nhập Tắt chương trình

Chúng ta sẽ dùng mã i (insert), s ( search) , u (update) , p ( print), và q(quit) để đại diện các chức năng. Một chương trình sẽ như sau: Enter operation code: i Enter part number: 528 Enter part name: Disk drive Enter quantity on hand: 10 Enter operation code: s Enter operation code: s Enter part number: 528 Part name: Disk drive Quantity on hand: 10 Enter operation code: p Enter operation code: s Enter part number: 914 Part not found. Part number 528 914 Part Name Disk drive Printer cable Quantity on hand 8 5 Enter part number: 528 Part name: Disk drive Quantity on hand: 8 Enter operation code: u Enter part number: 528 Enter change in quantity on hand: -2

Enter operation code: i Enter part number: 914 Enter part name: Printter cable Enter quantity on hand: 5

Enter operation code: q

7

More information: www.itspiritclub.net Chương trình sẽ lưu thông tin về mỗi part trong một structure. Ta sẽ giới hạn kích cỡ CSDL tới 100 parts và lưu trong một chuỗi gọi là inventory. Để theo dõi số lượng parts hiện có trong chuỗi, ta dùng một biến tên là num_parts. Mã giả: For( ; ; ) { Yêu cầu người dùng nhập operation code; Đọc code; Switch( code) { case ‘ i ‘ : thực hiện lệnh insert ; break; case ‘ s ‘ : thực hiện lệnh search ; break; case ‘ u ‘ : thực hiện lệnh update ; break; case ‘ p ‘ : thực hiện lệnh print ; break; case ‘ q ‘ : thực hiện lệnh update ; break; default: hiện thị thông báo lỗi; } } Lưu ý: sẽ thuận tiện hơn để chia hàm ra thành insert, search, update và print. Do các hàm cần phải truy cập tới inventory và num_parts, chúng ta sẽ cần cho các biến này là toàn cục. Một lựa chọn khác là khai báo cục bộ trong main sau đó truyền vào các hàm. Đứng dưới góc nhìn lập trình viên thì sẽ tốt hơn nếu dùng biến cục bộ. Nhưng trong chương trình này, ta sẽ dùng biến toàn cục để giảm độ phức tạp. Tôi sẽ tách chương trình ra thành 3 files: inventory.c để chưa chương trình; readline.h để chưa prototype cho hàm read_line ; và readline.c chứa định nghĩa của read_line. Chúng ta sẽ thảo luận 2 file cuối sau trong mục này. Bây giờ hãy tập trung vào inventory.c ( đính kèm theo tài liệu này)
/* inventory.c */ /* Maintains a parts database (array version) */ #include <stdio.h> #include "readline.h" #define NAME_LEN 25 #define MAX_PARTS 100 struct part { int number; char name[NAME_LEN+1]; int on_hand; } inventory[MAX_PARTS];

8

More information: www.itspiritclub.net
int num_parts = 0; /* number of parts currently stored */

int find_part(int number); void insert(void); void search(void); void update(void); void print(void); /********************************************************** * main: Yêu cầu người dùng nhập vào operation code , * * Sau đó gọi 1 hàm để thực hiẹn chức năng tương * * ứng . Lặp lại cho tới khi người dùng nhập * * 'q'. In ra lỗi nếu người dùng nhập vào một giá * * trị không hợp lệ * **********************************************************/ int main(void) { char code; for (;;) { printf("Enter operation code: "); scanf(" %c", &code); while (getchar() != '\n') /* skips to end of line */ ; switch (code) { case 'i': insert(); break; case 's': search(); break; case 'u': update(); break; case 'p': print(); break; case 'q': return 0; default: printf("Illegal code\n"); } printf("\n"); } } /********************************************************** * find_part: Tìm một part_number ở trong mảng dữ liệu * * Trả về một nếu tìm thấy. Mặc khác trả về - * * * **********************************************************/ int find_part(int number) { int i; for (i = 0; i < num_parts; i++) if (inventory[i].number == number) return i; return -1; } /********************************************************** * insert: Prompts the user for information about a new * * part and then inserts the part into the *

9

More information: www.itspiritclub.net
* database. Prints an error message and returns * * prematurely if the part already exists or the * * database is full. * **********************************************************/ void insert(void) { int part_number; if (num_parts == MAX_PARTS) { printf("Database is full; can't add more parts.\n"); return; } printf("Enter part number: "); scanf("%d", &part_number); if (find_part(part_number) >= 0) { printf("Part already exists.\n"); return; } inventory[num_parts].number = part_number; printf("Enter part name: "); read_line(inventory[num_parts].name, NAME_LEN); printf("Enter quantity on hand: "); scanf("%d", &inventory[num_parts].on_hand); num_parts++; } /********************************************************** * search: Prompts the user to enter a part number, then * * looks up the part in the database. If the part * * exists, prints the name and quantity on hand; * * if not, prints an error message. * **********************************************************/ void search(void) { int i, number; printf("Enter part number: "); scanf("%d", &number); i = find_part(number); if (i >= 0) { printf("Part name: %s\n", inventory[i].name); printf("Quantity on hand: %d\n", inventory[i].on_hand); } else printf("Part not found.\n"); } /********************************************************** * update: Prompts the user to enter a part number. * * Prints an error message if the part doesn't * * exist; otherwise, prompts the user to enter * * change in quantity on hand and updates the * * database. * **********************************************************/ void update(void) { int i, number, change;

10

More information: www.itspiritclub.net
printf("Enter part number: "); scanf("%d", &number); i = find_part(number); if (i >= 0) { printf("Enter change in quantity on hand: "); scanf("%d", &change); inventory[i].on_hand += change; } else printf("Part not found.\n"); } /********************************************************** * print: Prints a listing of all parts in the database, * * showing the part number, part name, and * * quantity on hand. Parts are printed in the * * order in which they were entered into the * * database. * **********************************************************/ void print(void) { int i; printf("Part Number Part Name " "Quantity on Hand\n"); for (i = 0; i < num_parts; i++) printf("%7d %-25s%11d\n", inventory[i].number, inventory[i].name, inventory[i].on_hand); }

Trong hàm main, cú pháp “ %c” cho phép scanf để lờ đi những khoảng trắng trước khi đọc operation code. Dấu cách trong cú pháp trên là cốt yếu; nếu không có nó, scanf có thể sẽ đọc kí tự xuống dòng của dòng trước. Chương trình chứa một hàm, find_part, hàm này không dược gọi từ main. Hàm này hỗ trợ chúng ta tránh việc lặp lại code và đơn giản hoá các hàm quan trọng. Bằng việc gọi find_part, hàm insert, search và update có thể xác định một part trong CSDL. Chúng ta vẫn còn một hàm còn lại: hàm read_line, hàm này chương tình sẽ dùng để đọc part name. Xem xét chuyện gì sẽ xảy khi người dùng nhập 1 part: Enter part number: 528 Enter part name: Disk drive Người dùng nhấn phím Enter sau khi nhập vào part number và part name, mỗi lần sẽ để lại một kí tự xuống dòng vô hình mà chương trình phải đọc. Hãy giả sử là ta có thể hình thấy kí tự xuống dòng đó: Enter part number: 528↓ Enter part name: Disk drive↓ Khi hàm scanf đọc part number, nó nhận 5 2 và 8 nhưng bỏ qua kí tự ↓ . Nếu chúng ta dùng hàm read_line để đọc part name, nó sẽ gặp kí tự ↓ và ngừng quá trình đọc. Vẫn đề này thường xảy ra khi nhập input dạng số và theo sau bởi kí tự 11

More information: www.itspiritclub.net input. Giải pháp là sẽ viết một dạng hàm read_line để bỏ qua khoảng trắng trước khi lưu kí tự. Không chỉ là giải quyết vấn đề xuống dòng, mà còn cho phép ta tránh lưu bất kì khoảng trắng nào trước part name. Do read_line thì không liên quan tới các hàm khác trong inventory.c và do nó có khả năng tái sử dụng cho chương trình khác. Nên tôi quyết định tách riêng ra khỏi inventory.c .
/* readline.h */ #ifndef READLINE_H #define READLINE_H /********************************************************** * read_line: Skips leading white-space characters, then * * reads the remainder of the input line and * * stores it in str. Truncates the line if its * * length exceeds n. Returns the number of * * characters stored. * **********************************************************/ int read_line(char str[], int n); #endif

/* readline.c */ #include <ctype.h> #include <stdio.h> #include "readline.h" int read_line(char str[], int n) { int ch, i = 0; while (isspace(ch = getchar())) ; while (ch != '\n' && ch != EOF) { if (i < n) str[i++] = ch; ch = getchar(); } str[i] = '\0'; return i; }

12

More information: www.itspiritclub.net

4/ Unions
Một union, thì giống với một structure, bao gồm một hoặc nhiều thành phần, có thể khác kiểu. Tuy nhiên, trình biên dịch chỉ cấp phát bộ nhớ đủ cho thành phần lớn nhất, cái mà bao phủ những cái khác. Do đó, khi chỉ định giá trị cho một thành phần thì sẽ cũng thay đổi giá trị của thành phần khác. Để minh hoạ cho tính chất của union, hãy thử khai báo một union, biến u với 2 thành phần: union{ int i; double d; }u; Cách khai báo union cũng tương tự như cách khai báo struct: struct{ int i; double d; }s; Thật tế thì union và struct chỉ khác nhau ở cách lưu trữ dữ liệu trong bộ nhớ: struct lưu các thành phần ở các ô nhớ khác n hau. Trong khi đó, các thành phần union chia sẻ cùng 1 vùng nhớ trong bộ nhớ. Dưới đây là hình minh hoạ cho cách sắp xếp: Structure i i d Union

d

13

More information: www.itspiritclub.net Trong cấu trúc s, i và d chiếm vị trí khác nhau trong vùng nhớ ; tổng kích thước của s là 12 bytes. Trong union u, I và d chiếm cùng vị trí ô nhớ( i thật sự là 4 bytes đầu của d), do đó u chỉ chiếm 8 bytes. Và i , d có cùng địa chỉ. Thành phần trong union được truy xuất giống như thành phần của 1 structure. Để chứa một số 82 vào trong I của u. ta có thể viết u.i = 82; Để lưu giá trị 74.8 vào d ta có thể viết u.d = 74.8; Lưu ý: Nếu ta lưu giá trị trong u.d , thì giá trị lưu trước đây ở u.i sẽ bị mất. Đơn giản là, thay đổi u.i sẽ làm mất u.d và ngược lại. Bời vì tính chất này, ta có thể nghĩ u như là 1 nơi để lưu trữ i hoặc d, không phải cả 2. Các cách khai báo tag, kiểu union, khởi gán … của union tương tự như structure Dùng union để tiết kiệm bộ nhớ Chúng ta thường dùng union như một các để tiết kiệm bộ nhớ trong structure. Giả sử rằng chúng ta đang thiết kế một structure để chứa thông tin về một món đồ đã được bán qua một danh mục quà ( gift catalog). Danh mục này chỉ chứa 3 loại hàng hoá: sách, ly , áo sơ mi. Mỗi loại có số thứ tự ( stock number) và giá ( price), cũng như các thông tin khác dựa trên từng loại hàng: Sách ( Books) : Tên sách ( title), tác giả(author) , số trang ( number of pages). Ly ( mugs) : Kiểu thiết kế ( design) Áo sơ mi ( shirts): Kiểu thiết kế ( design), màu sắc hiện có ( color available), kích thước hiện có ( sizes available). Chúng ta có thể sử dụng 1 struct như sau. Tuy nhiên sẽ rất phí bộ nhớ vì chỉ một phần của struct thì phù hợp với mỗi loại hàng hoá Struct catalog_item { Int stock_number; Double price; Int item_type; Char title[TITLE_LEN + 1 ]; Char author[AUTHOR_LEN + 1]; Int num_pages; Char design[DESIGN_LEN + 1 ]; Int colors; Int sizes; };

14

More information: www.itspiritclub.net Nếu một món hàng là một quyển sách (book), chúng ta không cần lưu design, colors và sizes. Bằng việc đặt một union vào trong structure catalog_item, chúng ta có thể giảm ô nhớ cho struct. Thành phần của mỗi union sẽ là một structure cần thiết để lưu từng loại hàng hoá tương ứng. Struct catalog_item{ int stock_number; double price; int item_type; union { struct { char title[TITLE_LEN + 1]; char author[AUTHOR_LEN + 1 ]; int num_pages; } book; Struct { char design[DESIGN_LEN + 1]; } mug; Struct { char design[DESIGN_LEN + 1]; int colors; int sizes; }shirt; } item; }; Lưu ý rằng union ( tên là item) là một thành phần của catalog_item structure, và các struct book, mug, shirt là thành phần của item. Giả sử c là một catalog_item structure và đại diện 1 quyển sách ( book), chúng ta có thể in ra tựa đề của sách bằng cách: printf(“%s”, c.item.book.title);

15

More information: www.itspiritclub.net

Dùng unions để xây dựng cấu trúc dữ liệu hỗn hợp Unions có một chức năng quan trọng: xây dựng một cấu trúc dữ liệu hỗn hợp. Giả sử rằng chúng ta cần một mảng mà các phần tử là một sự pha trộn giữa int và double. Vì các phần tử của mảng phải cùng dạng nên chúng ta dường như không thể tạo một mảng như vậy. Bằng cách dùng unions, bài toán đã được giải quyết. Đầu tiên chúng ta khai báo một kiểu dữ liệu union mà các phần tử đại diện cho kiểu dữ liệu khác nhau sẽ dược lưu trong mảng: Typedef union { int i; double d; }Number; Tiếp theo chúng ta tạo một mảng mà các phần tử là các Number. Number number_array[1000]; Mỗi phần tử của number_array là một union Number. Một Number union có thể chứa hoặc là giá trị int hoặc double. Ví dụ, giả sử chúng ta muốn phần tử thứ 0 lưu giá trị 5 và phần tử thứ 1 lưu 8.395. Cú pháp sau sẽ thực hiện đúng: Number_array[0].i = 5; Number_array[1].d = 8.395; Thêm một “TAG FIELD” vào một union Unions gặp phải một vấn đề quan trong: chúng ta không dễ dàng chỉ ra rằng thành phần nào đã được thay đổi vào chứa dữ liệu như thế nào. Thử suy nghĩ viết một hàm để hiển thị giá trị hiện tại được lưu trong union Number ở trên. Hàm sau có thể sẽ giải quyết được: Void print_number( Number n ) { if( n chứa một biên nguyên) Printf(“%d”, n.i); else Printf(“%d”, n.d); } Nhưng thật không may rằng không có cách nào để print_number xác định được liệu n chứa một biên nguyên hay một biến thực.

16

More information: www.itspiritclub.net Để theo giải quyết vấn đề này, ta cần thêm union vào trong một struct chứa thành phần khác: một “tag field” với mục đính là để nhắc nhở chúng ta rằng loại dữ liệu nào đang được lưu trong union. Trong structure catalog_item được thảo luận ở trước thì biến item_type đảm nhiệm nhiệm vụ này. Bây giờ hãy thêm “tag field” vào struct Number: #define INT_KIND 0 #define DOUBLE_KIND 1 typedef struct { int kind; /* tag field */ union { int i; double d; } u; }Number; Number cos 2 thành phần : kind và u. Giá trị của kind sẽ là hoặc INT_KIND hoặc DOUBLE_KIND Mỗi lần chúng ta gán giá trị cho một phần tử của u, chúng ta cũng sẽ thay đổi giá trị kind để nhắc chúng ta về kiểu dữ liệu mà chúng ta đã thay đổi. Ví dụ, nếu n là một biến Number, và việc gán giá trị cho phần thử i của u sẽ như sau: n.kind = INT_KIND; n.u.i = 82; /* Lưu ý để gán giá trị thì đầu tiên phải chọn thành phần u Khi ta cần lấy dữ liệu được lưu trong Number, biến kind sẽ giúp ta: Void print_number( Number n ) { if( n.kind == INT_KIND) Printf(“%d”, n.u.i); else Printf(“%d”, n.u.d); } sau đó là i của u */

5/Enumerations
Trong nhiều chương tình, chúng ta sẽ cần những biến mà chỉ có một số nhỏ giá trị có nghĩa. Ví dụ như biên Boolean, chỉ có 2 giá trị có nghĩa là true hoặc false. Một biến lưu giá trị phù hợp với lá bài tây chỉ có 4 giá trị có nghĩa “chuồn”(clubs), 17

More information: www.itspiritclub.net “Rô” (diamonds), “Cơ”(hearts), “Bích”(spades). Các thực tế để xử lý những biến như vậy là khai báo một biến nguyên và có một bộ mã tương ứng với từng biến. Int s; /* s lưu giá trị hình của lá bài */ s = 2; /* 2 đại diện cho “Cơ”, (hearts) */ Mặc dù kĩ thuật này hoạt động tốt, nhưng nó bỏ lại nhiều hơn mong đợi. Ai đó đọc chương trình không thể nói rằng s chỉ có 4 giá trị và định nghĩa của 2 cũng không xuất hiện. Một cách khác là sử dụng #define: #define SUIT int #define CLUBS 0 #define DIAMONDS 1 #define HEARTS 2 #define SPADES 3 Chương trình của ta sẽ trở nên dễ đọc hơn SUIT s; s = HEARTS; Kĩ thuật này đã được cải tiến so với kĩ thuật trước, nhưng nó vẫn chưa phải là giải pháp tốt nhất. Vẫn chưa có số chỉ nào để ai đó đọc chương trình biết rằng các giá trị “macro” đại diện cho một loại dữ liệu nào đó. Và nếu số lượng của biến có thể nhiều hơn, việc khai báo #define sẽ trở thành một vấn đề rắc rối. C cung cấp một kiểu dữ liệu đặc biệt được thiết kế cho những biến mà có một số nhỏ các giá trị có thể. Một kiểu dữ liệu enum là một kiểu dữ liệu liệt kệ. Ví dụ sau sẽ liệt kê các biến ( CLUBS, DIAMONDS, HEARTS VÀ SPADES) mà sẽ được gán vào biến s1 và s2; Enum {CLUBS, DIAMONDS, HEARTS, SPADES } s1 ,s2; Enum thì gần giống như các khai báo #define nhưng chúng không giống nhau. Nếu enum được khai báo ở trong một hàm thì những hằng số enum đó sẽ được khả dụng ở ngoài hàm tương ứng. Enum như những biến nguyên C xử lý những biến enum như những số nguyên. Mặc định thì trình biên dịch sẽ gán giá trị 0, 1 ,2 …cho từng giá trị của biến được liệt kê. Chúng ta cũng có thể thay đổi giá trị theo ý muốn. Ví dụ như chúng ta muốn CLUBS, DIAMONDS, HEARTS và SPADES đại diện cho 1, 2, 3, 4. Ta có thể viết như sau: Enum suit { CLUBS = 1 , DIAMONDS = 2, HEARTS = 3, SPADES = 4}; Các giá trị của enum có thể tuỳ ý, liệt kệ không nhất thiết phải theo thứ tự: 18

More information: www.itspiritclub.net Enum suit { RESEARCH = 20 , PRODUCTION = 10, SALES = 25}; Khi không có giá trị nào được khai báo cho một hằng số trong enum thì giá trị của nó sẽ lớn hơn giá trị của biến hằng trước nó 1 dơn vị. Trong ví dụ sau thì: BLACK có giá trị 0; LT_GRAY giá trị 7; DK_GRAY giá trị 8 và WHITE là 15 Enum EGA_colors { BLACK, LT_GRAY = 7, DK_GRAY, WHITE = 15}; Dùng enum để khai báo “Tag Fields” Enum là cách tuyệt nhất để giải quyết vấn đề mà chúng ta gặp phải ở mục Tag Field trước đây. Ví dụ như trong structure Number, chúng ta có thể tạo thành phần kind như một enum thay vì int typedef struct { enum {INT_KIND, DOUBLE_KIND} kind; /* tag field */ union { int i; double d; } u; }Number; Structure mới này dùng hoàn toàn giống như structure cũ. Ưu điểm của cách này là chỉ rõ rằng kind chỉ có 2 giá trị khả dụng : INT_KIND và DOUBLE_KIND

6/Những câu hỏi thường gặp
Q: Khi tôi cố sử dụng sizeof để xác định số lượng byte của một struct, Tôi nhận được một số lớn hơn tổng của từng phần tử. Tại sao lại như vậy? A: Hãy cùng xem ví dụ sau: Struct{ Char a; Int b; }s; Nếu char chiếm 1 byte là int chiếm 4 byte thì s sẽ chiếm bao ? Câu trả lời rõ ràng là 5 bytes thì có thể không đúng. Một số máy tính yêu cầu địa chỉ của dữ liệu phải là bội số của một số byte nào đó( có thể là 2 ,4 hoặc 8 ). Để thoả mãn yêu cầu này, trình biên dịch sẽ sắp xếp (align) các thành phần của structure bằng cách tạo ra những khoảng trống (holes) ( byte không dùng tới) giữa các thành phần. Nếu chúng ta giả sử dữ liệu phải bắt đầu với bội số của 4, thì thành phần a sẽ được theo sau bởi 3 byte trống. Và do đó, kết quả sizeof(s) sẽ là 8 19

More information: www.itspiritclub.net Bên cạnh đó, structure cũng có thể có khoảng trống ở cuối structure. Ví dụ: Struct{ Int a; Char b; }s; Kết quả sizeof vẫn là 8 Q: Khoàng trống (hole) có thể ở đầu structure hay không? A: Không. C chuẩn chỉ cho phép khoảng trống ở giữa hoặc sau mỗi phần tử. Q: Tại sao ta không thể sử dụng phép == để kiểm tra liệu hai structure bằng nhau hay không? A: Phép toán này không được hỗ trợ bởi C vì không có cách nào để thực hiện nó một cách phù hợp . Nếu so sánh các phần tử của struct từng đôi một sẽ không hiệu quả. So sánh tất cả bytes trong structure có thể sẽ tốt hơn. Nếu structure chứa các khoảng trống (holes) thì việc so sánh byte có thể sẽ mang lại kết quả không đúng. Vấn đề có thể giải quyết bằng cách khởi gán để trình biên dịch chắc rằng khoảng trống chứa cùng giá trị ( 0 chẳng hạn). Nhưng việc khởi gán khoảng trống sẽ lạm dụng hiệu năng của chương trình dùng structure. Điều đó không khả thi Q: Tại sao trong C lại có 2 cách để khai báo structure ( dùng tags và typedef ) ? A: C chuẩn thì thiếu đi typedef, nên tags là kĩ thuật duy nhất để khai báo structure. Khi mà typedef được thêm vào C thì đã quá muộn để loại bỏ tags. Bên cạnh đó, tag thì vẫn cần thiết khi một thành phần của struct trỏ tới một structure khác cùng kiểu. Q: Liệu có thể dùng đồng thời cả tag và typedef? A: Có thể. Trong thực tế, tag và typedef có thể dùng như nhau. typedef struct part{ int number; char name[NAME_LEN + 1]; int on_hand; } part; Q: Làm sao chúng ta có thể chia sẽ một loại structure giữa nhiều file khác nhau trong một chương trình? A: Đặt một định nghĩa của structure tags( hoặc là typedef nếu bạn thích) trong một file header, sau đó include file header vào nơi mà structure cần dùng. Ví dụ để chia sẻ part structure, chúng ta đặt những dòng sau vào một file header: Struct part{ Int number; 20

More information: www.itspiritclub.net Char name[NAME_LEN + 1]; Int on_hand; }; Chú ý rằng chúng ta chỉ mới khai báo structure tag, không phải là biến của kiểu dữ liệu này. Việc khai báo một tag hoặc typedef name hai lần trong cùng một file sẽ dẫn tới lỗi. Điều này cũng tương tự cho unions và enum Q: Nếu tôi include khai báo của part structure ở trong hai file khác nhau, liệu biến part trong một file sẽ giống y hệt với biến part ở file còn lại? A: Về mặt kĩ thuật thì không.. Tuy nhiên, C chuẩn nói rằng biến part ở 1 file có một kiểu dữ liệu mà nó tương thích với kiểu dữ liệu part của file còn lại. Các biến mà tương thích với nhau thì có thể gán cho nhau, đây chỉ là một sự khác biệt giữa “tương thích” và “ y hệt” …. Còn nhiều câu hỏi nữa.

21

Sign up to vote on this title
UsefulNot useful