SỬ DỤNG HÀM TRONG C
CHƯƠNG 7 KIỂU DỮ LIỆU CẤU TRÚC
7.1 KHÁI NIỆM KIỂU DỮ LIỆU CẤU TRÚC
Từ chương 2 và chương 5 ở phần trước chúng ta đã biết khái niệm biến được
dùng để lưu
một phần tử dữ liệu cụ thể. Mảng được sử dụng để lưu một tập các phần tử có
cùng kiểu dữ
liệu. Tuy nhiên, trong thực tế có rất nhiều bài toán ứng dụng mà nếu chỉ dùng biến
hoặc mảng
sẽ không thể giải quyết được hoặc giải quyết được nhưng không hiệu quả,
chẳng hạn như
chúng ta viết một chương trình quản lý thư viện. Chương trình đòi hỏi phải nhập
và lưu trữ
thông tin về các cuốn sách như mã sách (kiểu số hoặc chữ), tên sách (kiểu xâu
ký tự), năm
xuất bản (kiểu số nguyên), giá thành (kiểu số thực) hoặc thông tin về bạn đọc như
mã bạn đọc
(kiểu số hoặc chữ), tên bạn đọc (kiểu xâu ký tự), số điện thoại (kiểu số nguyên),
ngày mượn
(kiểu ngày/tháng/năm),… Trong trường hợp này, sử dụng kiểu dữ liệu cấu trúc sẽ
làm cho mọi
việc đơn giản và hiệu quả hơn.
Một cấu trúc bao gồm nhiều thành phần dữ liệu, không cần phải cùng kiểu được nhóm lại với nhau. Trong ví dụ trên, cấu trúc bạn đọc sẽ bao gồm các thành phần dữ liệu như mã bạn đọc, tên bạn đọc, số điện thoại, ngày mượn, cấu trúc sách sẽ bao gồm các thành phần dữ liệu như mã sách, tên sách, tên tác giả, năm xuất bản, nhà xuất bản, lần xuất xuất bản, giá thành…Sự khác nhau giữa các khái niệm biến, mảng, cấu trúc được thể hiện cụ thể trên Hình 7. 1.
131
Hình 7. 1: Sự khác nhau giữa biến, mảng, cấu trúc.
7.1.1 Định nghĩa cấu trúc
Việc định nghĩa cấu trúc sẽ tạo ra kiểu dữ liệu mới cho phép người dùng sử dụng chúng để khai báo các biến kiểu cấu trúc. Các biến trong cấu trúc được gọi là các phần tử hay các thành phần của cấu trúc.
Cú pháp địnhnghĩa một kiểu kiểu cấu trúc như sau:
struct<Tên cấu trúc>
{
Khai báo các thành phần
dữ liệu;
};
Trong đó struct là một từ khóa trong C. Một định nghĩa cấu trúc luôn được bắt đầu bằng từ khóa struct và kết thúc bằng dấu (;). Cặp dấu ngoặc nhọn {} là bắt buộc, tất cả các thành phần dữ liệu của một cấu trúc đều được khai báo trong cặp dấu ngoặc này.
Ví dụ:
struct Sach{
char MaSach;
char TenSach[
50];
char TenTG[20 ];
int NamXB;
float GiaThanh;
};
Câu lệnh trên định nghĩa một kiểu dữ liệu mới tên là Sach. Mỗi biến của kiểu dữ liệu này bao gồm 5 thành phần dữ liệu MaSach, TenSach, TenTG, NamXB, GiaThanh. Lệnh này chỉ định nghĩa cấu trúcSach mà không khai báo bất kỳ biến nào và vì vậy chương trình không để dành bất kỳ vùng nhớ nào trong bộ nhớ. Từ khóa struct báo cho trình biên dịch biết rằng một cấu trúcđược định nghĩa. Nhãn Sach không phải là tên biến mà là một tên kiểu.
Ví dụ sau đây định nghĩa một kiểu dữ liệu mới là kiểu phân số (PhanSo), bao
gồm 2 thành phần dữ liệu là tử số (TuSo) và mẫu số (MauSo):
struct Fraction{
int Numerator;
int Denominat or;
};
132
7.1.2 Khai báo biến cấu trúc
Khi một cấu trúc đã được định nghĩa, chúng ta có thể khai báo một hoặc nhiều biến kiểu này bằng một trong các cách sau đây.
Cách 1: Khai báo đồng thời cấu trúc và biến cấu trúc theo mẫu như sau:
struct <tên cấu trúc>
{
Khai báo các thành phần của cấu trúc; }danh sách biến kiểu cấu trúc;
Ví dụ:
stru ct Book { char Book ID;
char BookName[
50]; char Author[20 ]; int PubYear;
flo at Pric e;
}bk1 , bk2;
Danh sách các biến được cách nhau bằng dấu phẩy (,). Khi khai báo theo dạng này tương đương với việc khai báo danh sách các biến có cùng kiểu dữ liệu vừa được định nghĩa.
Cách 2: Khai báo các biến sau khi đã định nghĩa cấu trúc theo cú pháp như sau:
Struct <Tên cấu trúc> Danh sách biến cấu trúc;
Ví dụ:
Struct Book bk1, bk2;
Các câu lệnh trên sẽ dành đủ vùng nhớ để lưu giữ tất cả các thành phần dữ liệu trong cấu trúcBook. Khai báo trên thực hiện chức năng tương tự như các khai báo biến thông thường, chẳng hạn int x hay char c. Lệnh này sẽ báo với trình biên dịch dành ra một vùng lưu trữ cho một biến với kiểu dữ liệu đã được định nghĩa.
Đặt tên kiểu dữ liệu bằng typedef
Từ khóa typedef có thể được dùng để định nghĩa một tên mới cho một kiểu đã
có. Cú pháp tổng quát của câu lệnh typedef là:
typedef <Kiểu dữ liêu> Tên mới;
Tên mới được định nghĩa là một tên thêm vào chứ không phải thay thế tên cho kiểu dữ liệu đã có. Ví dụ, câu lệnh sau đây sẽ định nghĩa một tên mới cho một kiểu số nguyên:
typedef int Int16;
Câu lệnh này báo cho trình biên dịch biết để nhận dạng Int16là một tên khác của kiểu
int. Một biến kiểu nguyên có thể định nghĩa thông qua tên mới Int16như sau:
Int16 x;
Ở đây xlà một biến kiểu Int16,là một tên khác của kiểu int. Sau khi được định nghĩa Int16 lại có thể được sử dụng như một kiểu dữ liệu trong câu lệnh typedef để gán một tên khác cho kiểu int, chẳng hạn:
133
typedef Int16 Int32;
Câu lệnh này báo cho trình biên dịch để nhận dạng Int32là một tên khác củaInt16, cũng chính là một tên khác của int.
Thực tế từ khóa typedefcũng rất thuận tiện trong việc định nghĩa một tên mới cho các kiểu con trỏ, mảng, ví dụ:
typedef int* ptr_int;
Câu lệnh này báo cho trình biên dịch biết để nhận dạngptr_intlà một tên mới cho kiểu dữ liệu int*(con trỏ kiểu nguyên). Khi đó, muốn khai báo một mảng kiểu nguyên Ary dưới dạng một con trỏ, ta có thể sử dụng câu lệnh:
ptr_int Ary;
Đặc biệt, typedef rất tiện dụng khi định nghĩa các cấu trúc, vì ta không cần
nhắc lại từ
khóastruct mỗi khi một sử dụng cấu trúc. Khi đó việc sử dụng cấu trúc sẽ thuận
tiện hơn.
Ngoài ra, tên một kiểu cấu trúc do người dùng định nghĩa thường gợi nhớ đến
mục đích của
cấu trúc trong chương trình. Một cách tổng quát, một cấu trúc do người dùng
định nghĩa có
thể được viết như sau:
typedef struct{
Khai báo các thành phần của cấu trúc; } <Tên kiểu dữ liêu cấu trúc>;
Ví dụ sau đây sẽ định nghĩa cấu trúcFraction là một kiểu dữ liệu mới (gọi là kiểu dữ liệu phân số), gồm 2 thành phần dữ liệu tử số (numerator), và mẫu số (denominator) kiểu int:
typedef struct{
int numerator int
denomin ator; } Fractio n;
Sau khi được định nghĩa với từ khóa typedef, Fraction trở thành một kiểu dữ liệu mới và chúng ta có thể dùng nó như các kiểu dữ liệu thông thường, ví dụ:
Fraction fract;
Giống như các khai báo biến thông thường, lệnh này báo cho trình biên dịch biết fractlà một biến có kiểu dữ liệu Fraction.
7.1.3 Truy cập tới các thành phần của một cấu trúc
Bản thân cấu trúc khi ta định nghĩa và khai báo nó được coi như một kiểu dữ liệu mới. Tuy nhiên, điểm khác biệt của kiểu dữ liệu cấu trúc so với các kiểu dữ liệu thông thường là chúng có những biến thành phần và để thao tác với các biến cấu trúc (gán hoặc lấy giá trị), chúng ta phải truy cập đến từng thành phần trong đó.
Các thành phần của cấu trúc được truy cập thông qua việc sử dụng toán tử chấm (.), toán tử này còn được gọi là toán tử thành viên membership. Cú pháp tổng quát
dùng để truy cập một phần tử của cấu trúc là:
<Tên của biến cấu trúc>. <Thành phần>
Chú ý rằng , việc truy cập tới các thành phần của một kiểu cấu trúc phải được thực hiện thông qua tên biến chứ không phải thông qua tên cấu trúc.
134
Ví dụ, để truy cập đến các thành phần tử số và mẫu số của kiểu dữ liệu phân số ở trên, phải thực hiện như sau:
Fractionfract; /*khai báo biến cấu trúc Fraction*/
fract.numerator; /*truy xuất tới thành phần tử số*/
fract.denominator; /*truy xuất tới thành phần mẫu số*/
Để gán giá trị cho các thành phần cấu trúc được thực hiện như sau:
fract.numerator = 2;
dt.denominator = 3;
Có thể sử dụng lệnh scanf() để nhập giá trị cho các thành phần cấu trúc:
scanf(“%d
%d”,&fract.numerator,&fract.denominator) Để hiển thị giá trị của các thành phần cấu trúc có thể sử dụng lệnh printf:
printf(“%d, %d”,fract.numerator,fract.denominator);
Ví dụ 7.1 sau đây định nghĩa cấu trúc dữ liệu sinh viên (Student) bao gồm các thành phần họ tên (Name), địa chỉ (Address), năm sinh (Birth_Year). Từ đó nhập dữ liệu cho hai sinh viên rồi hiển thị thông tin đã nhập ra màn hình.
Ví dụ7.1:
#include<stdio.h>
struct Student{
char Name[20];
char Address[1 0];
int Birth_Yea r; } main(){
struct Student st1, st2;
printf("Input the first student:\n");
printf("\nName:"); gets(st1.Name);
printf("\nAddress:"); gets(st1.Address );
printf("\nBirth_Year: ");
scanf("%d",&st1.Birth_Year ); fflush(stdin);
printf("\nInput the second student:\n");
printf("\nName:");
gets(st2.Name);
printf("\nAddress:");
gets(st2.Address );
printf("\nBirth_Year: ");
scanf("%d",&st2.Birth_Year );
printf("\nDisplay information of two students:\n");
printf("%s\n%s", st1.Name, st1.Address);
printf("\n%d", st1.Birth_Year);
printf("\n%s", st2.Name);
printf("\n%s:", st2.Address);
printf("\n%d", st2.Birth_Year);
}
Kết quả thực hiện của chương trình như sau:
Input the first student:
Name: Nguyen Van Hung
Address: Ha Noi
Birth_Year: 1995
Input the
second student:
Name: Nguyen Thi Huong
Address: Ha Tay
135
Birth_Year: 1996
Display information of two students: Nguyen Van Hung Ha Noi
1995 Nguyen Thi Huong Ha Tay 1996
7.1.4 Khởi tạo biến cấu trúc
Giống như các biến và mảng, các biến kiểu cấu trúc có thể được khởi tạo tại
thời điểm khai
báo. Hình thức khởi tạo biến cấu trúc tương tự như khởi tạo mảng: Các giá trị
gán cho các
thành phần của cấu trúc được phân cách nhau bằng dấu phẩy (,) trong hai dấu
ngoặc nhọn{}, ví
dụ:
struct Student st1 = {“Nguyen Van Hung”, “Ha Noi”, 1995}
Kết quả của lệnh khởi tạo này như sau:
st1.Name = “Nguyen Van Hung”
st1.Adress = “Ha Noi”
st1.Birth_Year = 1995
Chương trình sau đây sẽ khai báo và khởi tạo giá trị cho hai biến st1, st2. Sau đó hiển thị các thông tin đã khởi tạo ra màn hình.
Ví dụ7.3:
#includ e<stdio .h>
#includ e<conio .h>
struct Student {
char Name[20];
char Address[1 0];
Birth_Yeaint r; };
int main(){
struct Student st1 = {"Nguyen Van Hung","Ha Noi",1995}; struct Student st2 = {"Nguyen Thi Huong","Ha Tay",1996}; printf("\nDisplay information of two students:\n");
printf("%s", st1.Name);
printf("\n%s",st1.Address);
printf("\n%d", st1.Birth_Year);
printf("\n%s", st2.Name);
printf("\n%s", st2.Address);
printf("\n%d", st2.Birth_Year); }
Kết quả thực hiện của chương trình như sau:
Display information of two students: Nguyen Van Hung Ha Noi
1995 Nguyen Thi Huong Ha Tay 1996
136
7.1.5 Phép gán đối với biến cấu trúc
Có thể gán giá trị của một biến cấu trúc cho một biến khác cùng kiểu bằng cách sử dụng câu lệnh gán đơn giản. Chẳng hạn, nếu st1 và st2là hai biến cấu trúc Student thì câu lệnh gán st1 = st2 là hợp lệ.
Trong trường hợp không thể dùng câu lệnh gán trực tiếp thì có thể sử dụng hàm tạo sẵn memcpy() để sao chép các thành phần dữ liệu của hai biến cấu trúc. Khuôn mẫu của hàm memcpy() như sau:
memcpy (char * dest, char &source, int nbytes);
Hàm này thực hiện sao chép nbyte được lưu trữ bắt đầu từ địa chỉ source đến một vùng nhớ khác có địa chỉ bắt đầu từ dest. Hàm đòi hỏi người sử dụng phải chỉ ra kích cỡ của cấu trúc (nbytes), kích cỡ này có thể đạt được bằng cách sử dụng toán tử sizeof(). Sử dụng hàm memcpy() để sao chép nội dung của st1 sang st2 như sau:
memcpy (&st2, &st1, sizeof(struct Student));
7.1.6 Cấu trúc lồng trong cấu trúc
C cho phép một cấu trúc có thể lồng trong một cấu trúc khác. Khái niệm cấu trúc lồng trong cấu trúc ở đây có thể hiểu một cách đơn giản nhất là trong một kiểu cấu trúc được định nghĩa có chứa một thành phần dữ liệu là một kiểu cấu trúc khác. Ví dụ, trong lập trình đồ họa, để biểu diễn một đoạn thẳng, ta sẽ định nghĩa một cấu trúc Line như sau:
Struct Line {
struct Point pt1; // điểm đầu struct Point pt2; // điểm cuối }
Câu lệnh này khai báo pt1, pt2 là các thành phần dữ liệu của cấu trúc Line.
Bản thân pt1 và pt2 lại là một kiểu cấu trúc Point, được định nghĩa như sau:
Struct Point {
intx; // hoành độ inty; // tung độ }
Như vậy, cấu trúc Point được coi là lồng trong cấu trúc Line.Tuy nhiên, cần chú ý rằng một cấu trúc không thể lồng trong chính nó.
Trong thực tế, rất nhiều trường hợp thực tế đòi hỏi có một cấu trúc nằm trong
một cấu trúc
khác. Chẳng hạn, trong bài toán quản lý nhân sự của một công ty, thông tin cần lưu
trữ của một
nhân viên thường bao gồm mã nhân viên (kiểu số hoặc chuỗi ký tự), tên nhân viên
(kiểu chuỗi
ký tự), ngày sinh (gồm ngày/tháng/năm), số điện thoại (kiểu số hoặc chuỗi ký tự).
Do trong C
không có kiểu dữ liệu ngày/tháng/năm nên để thuận tiệnta có thể định nghĩa một
kiểu dữ liệu
mới Date với bathành phần dữ liệu ngày (day), tháng (month), năm (year) như sau:
typedef struct{
int day, month, year;
}Date;
Khi đó, kiểu dữ liệu nhân viên (Employee)có thể được định nghĩa:
typedef struct
137
{
char empl_id[5]; //mã nhân viên char name[30]; //tên nhân viên
Date birthday; //ngày sinh
char phone[12]; //số điện thoại }Employee;
Câu lệnh này khai báo birthdaylà một thành phần của cấu trúc
Employee. Bản thân birthday lại là một kiểu cấu trúc Date. Biến cấu trúc này có thể được khởi tạo như sau:
Employee empl = {“101”,“Nguyen Vu”, {15/11/1985},”091xxx”}
Đối với biến cấu trúc có thành phần là một cấu trúc khác, việc truy cập các thành phần của biến này hoàn toàn tương tự đối với một biến cấu trúc thông thường. Chẳng hạn, để truy cập các thành phần dữ liệu day, month, year của trường birthday trongcấu trúc Employee, ta sử dụng câu lệnh:
empl.birthday.day empl.birth
day.month empl.birth day.year
vớiempllà một biến cấu trúc Employee.
Tương tự, để nhậpgiá trị cho các thành phần dữ liệu này, ta có thể sử dụng câu lệnh scanf (đối với kiểu số) hoặc câu lệnh gets (đối với kiểu xâu kýtự) như sau:
scanf(“%d”,&empl.birthday.day);
scanf(“%d”,&empl.birthda y.month);
scanf(“%d”,&empl.birthda y.year);
Chương trình sau đây sẽ khai báo và nhập thông tin cho một nhân viên từ bàn phím, sau đó hiển thị ra màn hình các thông tin đã nhập ra màn hình:
Ví dụ7.4:
#includ e<stdio .h>
typedef struct{
int day, month, year;
}Date;
typedef struct {
char empl_i d[5];
char name[3 0]; Date
birthd ay;
char phone[12];
}
Employee;
int main(){
Employee empl1, empl2;
/*Nhập thông tin*/
printf("Please enter the information:\n");
printf("\nEmployee ID: "); scanf("%s", empl1.empl_id); fflush(stdin);
printf("\nName: ");
gets(empl1.name);
printf("\nBirthday: ");
scanf("%d%d
%d",&empl1.birthday.day,&empl1.birthday.month,
&empl1.birthday.year);
printf("\nTelephone
Number: ");
scanf("%s",
&empl1.phone);
138
/*Hiển thi ra màn hình*/
printf("\nDisplay entered information:\n"); printf("%s\n", empl1.empl_id);
printf("%s\n",empl1.name);
printf("%d/%d/
%d\n",empl1.birthday.day,empl1.birthday.month, empl1.birthday.year);
printf("%s\n",empl1.phone);
}
Kết quả thực hiện của chương trình như sau:
Please enter the information:
Employee ID:101
Name: Nguyen Hoang Anh
Birthday: 15 11 1982
Telephone
Number:123456789
Display entered information:
101 Nguyen Hoang Anh 15/11/
1982 123456789