Dữ liệu kiểu cấu trúc - Lập trình C : Bài 15

Dữ liệu kiểu cấu trúc - Lập trình C : Bài 15
access_time 10/12/2019 12:00:00 AM
person Đào Minh Giang

Dữ liệu kiểu cấu trúc - Lập trình C : Bài 15

1. Giới thiệu

Trong quá trình xây dựng ứng dụng, ngoài các kiểu dữ liệu do ngôn ngữ lập trình C hỗ trợ, thì C còn cho phép các lập trình viên có thể tự định nghĩa các kiểu dữ liệu riêng của mình. Các kiểu dữ liệu này được gọi là kiểu dữ liệu do người dùng định nghĩa “Kiểu dữ liệu người dùng” hay kiểu dữ liệu hướng người dùng.

Về bản chất kiểu dữ liệu do người dùng định nghĩa là được xây dựng hay dẫn xuất từ các kiểu dữ liệu nguyên thủy của ngôn ngữ C. Ví dụ như dữ liệu mảng (array) là kiểu dữ liệu dẫn xuất từ các kiểu dữ liệu có sẵn như integer, char ... array cho phép lưu trữ dữ liệu dưới dạng một dãy liên tiếp dữ liệu trong bộ nhớ, sử dụng tên mảng để đại diện cho mảng, các phần tử trong mảng bắt buộc phải có cùng kiểu dữ liệu.

Tuy nhiên trên thực tế điều gì xẩy ra nếu khi xây dựng chương trình cần định nghĩ một kiểu dữ liệu, nó tập dữ liệu mà mỗi thành phần có kiểu dữ liệu khác nhau ví dụ như SinhVien là một tập dữ liệu bao gồm : Tên, ngày sinh, giới tính và số điện thoại. Lúc đó  các lập trình viên không thể sử dụng mảng để giải quyết vấn đề này được.

Để giải quyết vấn đề này một cách phù hợp, C cung cấp tính năng để đóng gói cụm dữ liệu không vào trong một nhóm, dữ liệu đó sẽ được định danh thông qua tên và tạo thành kiểu dữ liệu kết hợp và dữ liệu đó gọi là kiểu dữ liệu cấu trúc(structure).

2. Khai báo cấu trúc và Biến cấu trúc.

Một cấu trúc được khai báo thông qua từ khóa struct đi kèm với nó là tên cấu trúc (struct tag) và nội dung cấu trúc. Trong nội dung struct sẽ có chứa các thành phần của nó.

Cũng giống như các kiểu dữ liệu khác struct là kiểu dữ liệu do vậy chúng ta có thể khai báo các kiến có kiểu cấu trúc và các biến đó được gọi là biến cấu trúc.

Cú pháp:

struct <structure_tag_name > {

          <data_type member_name_1>;

<data_type member_name_i>;

    . .

    } <structure_variable1>,<structure_variable2>,...;

Trong đó :

structure_tag_name : Tên cấu trúc

data_type member_name_i : với  data_type là kiểu dữ liệu của các thành phần trực thuộc structure. member_name_i là tên thành phần thứ i của struct.

structure_variable: Tên biến cấu trúc

Vậy giống như các kiểu dữ liệu khác, một struct do người dùng định nghĩa sẽ bao gồm các thành phần sau :

+ Tên struct

+ Các thành phần trực thuộc struct

+ biến struct.

Ví dụ :

struct toado

{

          int x;

          int y

} a, b;

Vậy trong đoạn code ví dụ ta thấy :

+ Tên struct là toado

+ Các thành phần struct là x, y

+ Các biến struct là a, b;

Các thức thực hiện trong ví dụ trên người ta gọi là phương pháp gộp có nghĩa là định nghĩa cấu trúc và các biến cấu trúc vào trong một đoạn code. Các lập trình viên hoàn toàn có thể định nghĩa struct trước và khai báo biến cấu trúc sau.

Cú pháp khai báo biến cấu trúc :

struct tag_name variable1, variable2, …;

­trong đó :

tag_name :  Tên cấu trúc.

variable1, variable2: Biến cấu trúc

Ví dụ:

struct toado a, b;

Các phép toán có thể được thực hiện với struct bao gồm :

+ Phép gán, gán một biến struct cho một biến struct khác cùng kiểu.

+ Phép tham chiếu để lấy địa chỉ của một biến struct.

+ Truy vấn tới các thành phần của struct từ biến struct.

+ Sử dụng toán tử sizeof để lấy kích cỡ của biến struct.

Các biến struct không sử dụng các phép toán == và != bởi vì các thành phần trực thuộc struct không nhất thiết phải được lưu trữ trong các byte bộ nhớ liên tiếp.

3. Truy vấn đến các thành phần của struct.

Các thành phần của struct có thể được truy vấn thông qua 3 cách. Một trong 3 cách đó là sử dụng dấu ‘.’

Cú pháp:

< structure_variable >.< member_name > ;

Trong đó :

structure_variable : Tên biến struct

member_name : Tên thành phần trực thuộc struct.

Ví dụ:

struct myStruct

{

          int a;

int b;

int c;

 } s1, s2;

Để truy vấn chúng là sử dụng : s1.a; s1.b

Xét theo kía cạnh lưu trữ trên bố nhớ (Giả sử int lưu trữ 2 bytes “thực tế có thể 4 bytes” và a, b, c được lưu trữ tại các ô nhớ liên tiếp “a, b, c không nhất thiết phải lưu trữ tại các ô nhớ liên tiếp” khi đó hình ảnh thể hiện như sau:


Hình số 1 : Truy vấn tới các thành phần struct

Có thể thiết lập các giá trị cho các thành phần của struct.

s1.b = 12;

Để in giá trị các thành phần thì tùy thuộc vào kiểu dữ liệu của các thành ví dụ chúng ta muốn in thông tin của thành phần b thì chúng ta sử dụng câu lệnh :

printf(“%d”, s1.b);

Các thành phần có kiểu string, kiểu mảng thì chúng ta xử lý tương tự như chúng ta đã làm.

4. Khởi tạo giá trị của struct

Struct có thể được khởi tạo qua nhiều cách khác nhau, điều này bao gồm cả việc như gán các hằng số giá trị cho các thành phần. Ngoài ra nếu không khởi tạo giá trị thì các biến có kiểu struct cũng sẽ khởi tạo theo kiểu không tường minh. Ví dụ với các thành phần có kiểu integer và float sẽ tự thiết lập giá trị là 0, với char và string sẽ là ‘\0’.

Cú pháp khởi tạo giá trị struct:

struct <structure_tag_name>

{

 <data_type member_name1>;

 <data_type member_name2>;  

}<structure_variable1> = {constant1,constant2, . .};

Hoặc

struct <structure_tag_name> <structure_variable>

    = {constant1,constant2,..};

Ví dụ minh họa

struct Ngaythang {

int ng ;

int th ;

int nam ;

} holiday = { 1, 5, 2000 } ;

một biến holiday cũng được khai báo kèm cùng kiểu này và được khởi tạo bởi bộ số 1. 5. 2000. Các giá trị khởi tạo này lần lượt gán cho các thành phần theo đúng thứ tự trong khai báo, tức ng = 1, th = 5 và nam = 2000.

Code minh họa một ví dụ khác:


Hình số 2 : Code minh họa khai báo và khởi tạo giá trị struct

 

Lưu ý : Với struct thì các member không được khởi tạo giá trị trực tiếp. Ví dụ :

struct games_ticket

  {

int value = 500; /* Lỗi vì khởi tạo giá trị trực tiếp cho member */

int seat_num = 52; /* Lỗi vì khởi tạo giá trị trực tiếp cho member */

int date, month, year;

  } fan1;

Ngoài cách thức khởi tạo trên, thì trong quá trình khởi tạo giá trị thì có thể truy vấn trực tiếp tới từng thành phần của struct.

Ví dụ :

struct {

float p, q,

int r;

  } k = { .p = 3.0, .q = 7.9, .r = 5};  

Biến struct k có thể được khởi tạo bằng cách truy vấn trực tiếp thông quan các thành phần của struct như p, q, r;

Lưu ý: trong ví dụ trên, khai báo struct không cần sử dụng tên vẫn được, lúc đó biến sẽ được khai báo trực tiếp.

Hoặc

struct employee

{

  int emp_num;

  char designation[40];

  char kind_of_leave_applied[30];

  int number_of _days;

  int begin_date;

  };

  struct employee mangal_singh = {.kind_of_leave_applied = “Medical leave”, .begin_date = 230910, .emp_num = 0691};

Vậy có nhiều cách để khởi tạo giá trị cho các biến struct.

Code minh họa


Hình số 3 : khởi tạo giá trị struct thông qua các thành phần

5. Copy và so sánh Struct

Một biến được khai báo có kiểu struct có thể gán cho một biến khác có cùng kiểu struct.

Ví dụ:

struct employee

{

char grade;

int basic;

float allowance;

 };

struct employee nam={‘b’, 6500, 812.5};

struct employee hung;

hung = nam; // Thực hiện phép gán biến hung = nam

Thực chất khi thực hiện phép gán hung = nam đó chính là quá trình sao chép dữ liệu các thành phần tương ứng của nam cho hung.

Code minh họa:


Hình số 4 : Gán giá trị của biến struct

 

Trong C không hỗ trợ so sánh 2 biến có kiểu struct.

6. typedef định nghĩa kiểu dữ liệu mới

Từ khóa typedef cho phép lập trình viên định nghĩa kiểu dữ liệu mới từ kiểu dữ liệu đã có trong chương trình.

Cú pháp

typedef <existing data type> <new data type ,….>;

Trong đó

existing data type : Kiểu dữ liệu đã tồn tại trong chương trình

new data type : Kiểu dữ liệu mới

Ví dụ:

typedef int id_number;

typedef float weight;

typedef char lower_case;

Trong ví dụ trên id_number là kiểu dữ liệu mới, nó sẽ được sử dụng như int.

Ví dụ áp dụng với từ khóa typedef với dữ liệu có kiểu struct;

typedef struct point

{

int x;

int y;

 } Dot;

Dot left,right;

Khi đó left và right là biến cấu trúc với kiểu dữ liệu mới là point.

Code minh họa


Hình số 5 : Khai báo kiểu dữ liệu mới với typedef

7. Struct lồng nhau

Một struct có thể chứa một struct khác. Trong trường hợp này có nghĩa là struct có chứa một hoặc nhiều thành viên có kiểu struct khác, cấu trúc như vậy người ta gọi là struct lồng nhau.

Trong các trường hợp như vậy, toán tử dấu chấm kết hợp với biến cấu trúc được sử dụng để truy cập các thành viên của trong cùng cấu trúc cũng như các cấu trúc phía bên ngoài.

Ví dụ:


Hình số 6 : struct lồng nhau

Trong ví dụ trên chúng ta có : struct outer chứa struct inner; Có nghĩa là struct outer có biến thành phần invar có kiểu inner.

Để truy vấn đến các thành phần trực thuộc các thành phần của struct, chúng ta sử dụng toán tử ‘.’ để truy cập.

8. Mảng struct

8.1 Khai báo mảng struct

Giống như các kiểu dữ liệu khác, struct hoàn toàn có thể sử dụng array để lưu trữ. Để khai báo mảng struct, chúng ta sử dụng cú pháp khai báo sau :

Cú pháp:

struct <structure_tag_name >

{

<data_type member_name1>;

<data_type member_name2>;

.

.

} <structure_variable>[index];

Hoặc

struct <structure_tag_name> <structure_variable>[index];

trong đó :

structure_tag_name : Tên struct

data_type member_name: Các thành phần trực thuộc mảng

structure_variable: Tên biến mảng struct

index: số lượng các thành phần mảng

Ví dụ

struct test1

{

char a;

int i;

float u;

} m[3];
Khi đó mảng m[3] sẽ lưu trữ 3 phần tử có kiểu cấu trúc test1.

Hình ảnh mô tả mảng cấu trúc:


Hình số 7 : Hình ảnh mảng chứa struct

 

8.2 Khởi tạo mảng struct

Quá trình khởi tạo giá trị ban đầu cho mảng struct được thực hiện giống như việc thiết lập giá trị khởi đầu của mảng có chứa các dữ liệu cơ bản khác.

Cú pháp:

struct <structure_tag_name >  

/* structure declaration */

  {

  <data_type member_name_1>;

  <data_type member_name_2>;

    .

    .

  <data_type member_name_n>;

 };

 /* Khai báo mảng struct và thiết lập giá trị */

 struct <structure_tag_name> <structure_variable>[N]=

{

  {constant01,constant02,……………………….constant0n},

  {constant11,constant12,……………………….constant1n},

      .

      .

  {constantN1,constantN2,…constantNn}

};

Ví dụ minh họa

struct boat       /** Khai báo mảng **/

{

  char name[20];

  int seatnum;

    float fare;

  }

Khai báo biến mảng struct và khởi tạo giá trị.

struct boat ticket[4]= {{“Vikram”, 1,15.50}, {“Krishna”, 2,15.50}, {“Ramu”, 3,25.50},

{“Gouri”, 4,25.50}};

Code minh họa:


Hình số 8 : Mảng struct

 

9. Mảng trong struct

Trong struct có thể có một hoặc nhiều thành phần có dữ liệu kiểu mảng ví dụ:

struct Lop {

char tenlop[10],      

int soluong;

} ;

struct Lop daihoc = {"K41C", 54} ;

Trong cấu trúc Lớp có chứa tên lớp có kiểu string (hay mảng char). Khi đó khai báo biến daihoc và khởi tạo giá trị cho biến daihoc như câu lệnh ở trên.

10. Cấu trúc và con trỏ

Một con trỏ trỏ đến cấu trúc hay biến con trỏ có kiểu cấu trúc (struct) chỉ đơn thuần là con trỏ đó trỏ đến địa chỉ của cấu trúc đó. Lưu ý con trỏ có kiểu struct không thể tự biến đổi biến con trỏ đó thành struct được.

Cú pháp:

struct <structure_tag_name >   /* Khai báo cấu trúc */

{

  <data_type member_name_1>;

  <data_type member_name_2>;

    .

    .

  <data_type member_name_n>;

} *ptr;

Hoặc

struct <structure_tag_name >   /* Khai báo cấu trúc */

{

  <data_type member_name_1>;

  <data_type member_name_2>;

    .

    .

  <data_type member_name_n>;

};

struct <structure_tag_name > *ptr;

Con trỏ ptr có thể được gán cho một con trỏ khác có cùng kiểu struct.

Để truy vấn đến các thành phần của cấu trúc. Chúng ta sử dụng cú pháp sau :

Cú pháp

(*ptr).member_name;

Hoặc

ptr-> member_name;

Để thiết lập cho các thành phần của struct thông qua biến con trỏ, chúng ta sử dụng cú pháp sau :

Cú pháp

(*ptr).member_name_x = constant;

Hoặc

ptr-> member_name_x = constant;

Ví dụ minh họa

struct test1  /** Khai báo struct “test” */

{

  char a;

  int i;

  float f;

};

struct test1 pt; /* Khai báo biến con trỏ có kiểu cấu truct “test”*/

pt->a=‘K’;   /* Thiết lập giá trị cho char a */

pt->i=15;    /* Thiết lập giá trị cho  int i */

pt->f=27.89; /* Thiết lập giá trị cho float f */

11. Cấu trúc và hàm

Struct có thể làm tham đối để truyền cho hàm, giống như các tham đối có kiểu dữ liệu của hàm. Khi một struct đóng vai trò là tham đối truyền vào của hàm thì mỗi thành phần trực thuộc struct sẽ được sao chép thông tin. Thực tế mỗi thành phần của struct được truyền giá trị vào cho hàm bằng cơ chế truyền tham đối thông qua giá trị. Khi thành phần của struct là array thì sẽ thực hiện bằng cơ chế copy mảng để truyền giá trị.

Với các struct có kích cỡ lớn thì việc sử dụng truyền tham đối của hàm thông qua con trỏ sẽ tỏ ra hiệu quả hơn.

Cú pháp khai báo tham đối của hàm với struct như sau :

struct structure_tag function_name(struct structure_tag structure_variable);

Trong đó

structure_tag : Tên struct

function_name: Tên hàm

structure_variable: Tên tham đối truyền vào cho hàm (Tên tham số hình thức).

Ví dụ:

struct A // Khai báo cấu trúc A

{

char ch;

int in;

float f;

 };

void show(struct A b); // Khai báo hàm show thực hiện vai trò hiển thị thông tin của struct A với b tham số hình thức truyền vào của A;

Code minh họa


Hình số 9: Hàm có tham đối có kiểu struct

Trong ví dụ trên, hàm show(struct A b) là hàm có sử dụng b làm tham đối truyền vào.

Với tham đối là con trỏ, thay vì chúng ta sử dụng hàm :

scanf("%c %d %f",&a.ch,&a.in,&a.f);

Để nhập giá trị cho biến struct A a; thì chúng ta viết hàm với tham đối có kiểu con trỏ như sau :

void read(struct A *p){

printf(“\n Enter ch, in and f:”);  /* Nhập thông tin các thành phần struct  */

        fflush(stdin); /* Xóa các dòng dữ liệu khi nhập thông tin */

scanf(“%c %d %f”,&p->ch,&p->in,&p->f); /* Nhập giá trị */

}

Hàm read có tham số p truyền vào có kiểu con trỏ. Việc sử dụng con trỏ sẽ làm tăng tính hiệu quả khi lập trình.

 


vertical_align_top
share
Chat...