Trong ngôn ngữ lập trình C, kiểu cấu trúc là một thành phần quan trọng giúp tổ chức và lưu trữ các biến có cùng một tên trong một đối tượng duy nhất. Trên cơ sở đó, chúng ta có thể tạo các biến thuộc kiểu cấu trúc và truy cập vào thành viên của chúng.
1. Kiểu cấu trúc trong C
Để hiểu về kiểu cấu trúc trong C, chúng ta cần tìm hiểu cách định nghĩa và sử dụng kiểu cấu trúc thông qua các ví dụ cụ thể.
1.1 Cách định nghĩa cấu trúc trong C
Trước khi tạo các biến thuộc kiểu cấu trúc trong C, chúng ta cần xác định kiểu dữ liệu của nó. Để định nghĩa một kiểu cấu trúc trong C, ta sử dụng từ khóa struct
.
Cú pháp của cấu trúc trong C như sau:
struct structureName
{
dataType member1;
dataType member2;
...
};
Ví dụ:
struct Person
{
char name[50];
int citNo;
float salary;
};
Trong ví dụ trên, kiểu struct Person
được định nghĩa. Lúc này, chúng ta có thể tạo ra các biến thuộc kiểu này.
1.2 Tạo các biến cấu trúc trong C
Khi một kiểu cấu trúc trong C được khai báo, chúng ta cần tạo ra các biến để làm việc với kiểu cấu trúc đó. Chúng ta có thể tạo các biến cấu trúc theo cách sau:
struct Person
{
char name[50];
int citNo;
float salary;
} person1, person2, p[20];
Trong ví dụ trên, 2 biến person1
và person2
, cùng với một mảng p
gồm 20 phần tử thuộc kiểu struct Person
, được tạo ra.
1.3 Truy cập vào các thành viên của một cấu trúc
Để truy cập vào các thành viên của một cấu trúc trong C, chúng ta sử dụng hai loại toán tử sau:
.
- Toán tử thành viên dot->
- Toán tử thành viên arrow (sẽ được giới thiệu trong phần sau)
Ví dụ, để truy cập vào salary
của person2
, ta phải sử dụng person2.salary
.
// Program to add two distances (feet-inch)
#include
struct Distance
{
int feet;
float inch;
};
int main()
{
struct Distance dist1, dist2, sum;
printf("1st distance\n");
printf("Enter feet: ");
scanf("%d", &dist1.feet);
printf("Enter inch: ");
scanf("%f", &dist1.inch);
printf("2nd distance\n");
printf("Enter feet: ");
scanf("%d", &dist2.feet);
printf("Enter inch: ");
scanf("%f", &dist2.inch);
// adding feet
sum.feet = dist1.feet + dist2.feet;
// adding inches
sum.inch = dist1.inch + dist2.inch;
// changing to feet if inch is greater than 12
while (sum.inch >= 12)
{
++sum.feet;
sum.inch = sum.inch - 12;
}
printf("Sum of distances = %d'- %.1f\"", sum.feet, sum.inch);
return 0;
}
Trong ví dụ này, chúng ta sử dụng hai biến cấu trúc dist1
và dist2
để lưu trữ hai khoảng cách. Chúng ta cộng hai khoảng cách lại với nhau và in ra tổng của chúng.
1.4 Từ khóa typedef
trong C
Chúng ta có thể sử dụng từ khóa typedef
để tạo tên bí danh cho các kiểu dữ liệu trong C, bao gồm cả các kiểu cấu trúc. Việc này giúp đơn giản hóa cú pháp khai báo biến.
Ví dụ:
struct Distance
{
int feet;
float inch;
};
typedef struct Distance distances;
int main()
{
distances d1, d2;
}
Cách khai báo biến d1
và d2
trong ví dụ trên tương đương với cách khai báo biến dist1
và dist2
trong các ví dụ trước.
1.5 Cấu trúc lồng nhau
Trong C, chúng ta có thể tạo một cấu trúc lồng trong một cấu trúc khác. Ví dụ:
struct complex
{
int imag;
float real;
};
struct number
{
struct complex comp;
int integers;
};
struct number num1, num2;
Giả sử chúng ta muốn gán giá trị 11 cho thành viên imag
của biến num2
. Ta có thể thực hiện như sau: num2.comp.imag = 11
.
1.6 Lợi ích của việc sử dụng cấu trúc trong C
Giả sử chúng ta muốn lưu trữ thông tin của một người bao gồm tên, số CMND và mức lương. Nếu sử dụng các biến riêng lẻ, chúng ta có thể tạo ra các biến name
, IdNo
và salary
riêng biệt để lưu trữ thông tin này.
Tuy nhiên, nếu chúng ta muốn lưu trữ thông tin của nhiều người, việc tạo ra các biến riêng lẻ sẽ trở nên khó khăn và không hiệu quả. Trong trường hợp này, chúng ta cần tạo một bộ sưu tập chứa các thông tin liên quan trong một cấu trúc đơn lẻ, ví dụ như Person
, và sử dụng nó cho tất cả mọi người.
2. Cấu trúc và con trỏ trong C
Trong phần này, chúng ta sẽ tìm hiểu cách sử dụng con trỏ để truy cập vào các thành viên của cấu trúc trong C. Chúng ta cũng sẽ biết cách cấp phát bộ nhớ tự động cho các kiểu cấu trúc trong C.
Tuy nhiên, trước đó, chúng ta cần xem qua các phần dưới đây để tìm hiểu cách sử dụng con trỏ với các biến cấu trúc và cấu trúc trong C:
- Biến con trỏ trong C
- Cấu trúc trong C
2.1 Tạo biến con trỏ trỏ đến cấu trúc trong C
Để tạo một biến con trỏ trỏ đến cấu trúc trong C, ta sử dụng dấu *
để khai báo biến con trỏ và tên cấu trúc.
Ví dụ:
struct name {
member1;
member2;
...
};
int main() {
struct name *ptr, Harry;
}
Trong ví dụ này, ptr
là một biến con trỏ trỏ đến cấu trúc name
.
Ví dụ: Truy cập vào các thành viên bằng biến con trỏ
Để truy cập vào các thành viên của một cấu trúc bằng biến con trỏ, chúng ta sử dụng toán tử ->
.
#include
struct person
{
int age;
float weight;
};
int main()
{
struct person *personPtr, person1;
personPtr = &person1;
printf("Enter age: ");
scanf("%d", &(personPtr->age));
printf("Enter weight: ");
scanf("%f", &(personPtr->weight));
printf("Displaying:\n");
printf("Age: %d\n", personPtr->age);
printf("Weight: %.2f", personPtr->weight);
return 0;
}
Trong ví dụ này, địa chỉ của person1
được lưu trữ trong biến con trỏ personPtr
bằng cách sử dụng personPtr = &person1;
. Bây giờ, chúng ta có thể truy cập vào các thành viên của person1
bằng biến con trỏ personPtr
.
2.2 Cấp phát bộ nhớ động cho cấu trúc
Trong một số trường hợp, chúng ta cần cấp phát bộ nhớ động cho một kiểu cấu trúc trong quá trình chạy. Để làm điều này, chúng ta có thể sử dụng hàm malloc()
để cấp phát bộ nhớ cho các biến cấu trúc.
Ví dụ: Cấp phát bộ nhớ động cho cấu trúc
#include
#include
struct person {
char name[50];
int age;
};
int main()
{
struct person *ptr;
int n;
printf("Enter the number of persons: ");
scanf("%d", &n);
// allocating memory for n numbers of struct person
ptr = (struct person*) malloc(n * sizeof(struct person));
for (int i = 0; i < n; ++i) {
printf("Enter name and age respectively: ");
scanf("%s %d", (ptr+i)->name, &(ptr+i)->age);
}
printf("Displaying Information:\n");
for (int i = 0; i < n; ++i)
printf("Name: %s\nAge: %d\n\n", (ptr+i)->name, (ptr+i)->age);
return 0;
}
Trong ví dụ này, chúng ta sử dụng hàm malloc()
để cấp phát bộ nhớ cho n
biến cấu trúc thuộc kiểu struct person
. Sau đó, chúng ta nhập thông tin cho các biến cấu trúc và hiển thị thông tin đó.
3. Cấu trúc và hàm trong lập trình C
Trong phần này, chúng ta sẽ tìm hiểu cách truyền biến cấu trúc vào hàm và làm việc với kết quả trả về của hàm.
3.1 Truyền cấu trúc vào hàm
Trong ngôn ngữ lập trình C, chúng ta có thể truyền cấu trúc vào một hàm. Để làm điều này, chúng ta cần khai báo biến đối số của hàm là một cấu trúc. Ví dụ:
#include
struct student {
char name[50];
int age;
};
void display(struct student s);
int main() {
struct student s1;
printf("Enter name: ");
scanf("%[^\n]%*c", s1.name);
printf("Enter age: ");
scanf("%d", &s1.age);
display(s1); // passing struct as an argument
return 0;
}
void display(struct student s) {
printf("\nDisplaying information:\n");
printf("Name: %s\n", s.name);
printf("Age: %d\n", s.age);
}
Trong ví dụ này, chúng ta khai báo một biến s1
thuộc kiểu struct student
, rồi nhập thông tin cho biến đó. Sau đó, chúng ta truyền biến s1
vào hàm display()
. Hàm này sẽ hiển thị thông tin của biến cấu trúc.
3.2 Lấy lại cấu trúc từ một hàm
Chúng ta cũng có thể lấy lại cấu trúc từ một hàm trong ngôn ngữ lập trình C. Để làm điều này, chúng ta cần khai báo kiểu trả về của hàm là một cấu trúc. Ví dụ:
#include
struct student
{
char name[50];
int age;
};
struct student getInformation();
int main()
{
struct student s;
s = getInformation();
printf("\nDisplaying information:\n");
printf("Name: %s\n", s.name);
printf("Age: %d\n", s.age);
return 0;
}
struct student getInformation()
{
struct student s1;
printf("Enter name: ");
scanf("%[^\n]%*c", s1.name);
printf("Enter age: ");
scanf("%d", &s1.age);
return s1;
}
Trong ví dụ này, chúng ta gọi hàm getInformation()
và gán kết quả trả về cho biến s
. Hàm này sẽ trả về một biến cấu trúc thuộc kiểu struct student
, và chúng ta sẽ hiển thị thông tin của biến đó.
3.3 Truyền cấu trúc bằng cách tham chiếu
Chúng ta cũng có thể truyền cấu trúc bằng cách tham chiếu, tương tự như cách chúng ta truyền các biến thuộc kiểu xây dựng sẵn. Trong quá trình truyền bằng cách tham chiếu, chúng ta truyền địa chỉ bộ nhớ của biến cấu trúc vào hàm.
#include
typedef struct Complex
{
float real;
float imag;
} complex;
void addNumbers(complex c1, complex c2, complex *result);
int main()
{
complex c1, c2, result;
printf("For first number,\n");
printf("Enter real part: ");
scanf("%f", &c1.real);
printf("Enter imaginary part: ");
scanf("%f", &c1.imag);
printf("For second number, \n");
printf("Enter real part: ");
scanf("%f", &c2.real);
printf("Enter imaginary part: ");
scanf("%f", &c2.imag);
addNumbers(c1, c2, &result);
printf("\nresult.real = %.1f\n", result.real);
printf("result.imag = %.1f", result.imag);
return 0;
}
void addNumbers(complex c1, complex c2, complex *result)
{
result->real = c1.real + c2.real;
result->imag = c1.imag + c2.imag;
}
Trong ví dụ này, ba biến cấu trúc c1
, c2
và result
cùng nhau được truyền vào hàm addNumbers()
. Tại đây, biến result
được truyền bằng cách tham chiếu. Khi biến result
trong hàm addNumbers()
thay đổi, biến result
trong hàm main()
cũng thay đổi theo.
4. Liên minh trong C
Liên minh (union) là một kiểu dữ liệu do người dùng định nghĩa trong ngôn ngữ lập trình C. Liên minh có chức năng tương tự như cấu trúc, nhưng có một số điểm khác biệt. Cấu trúc cấp phát đủ không gian để lưu trữ tất cả các thành viên của nó, trong khi liên minh chỉ cấp phát không gian để lưu trữ thành viên lớn nhất.
4.1 Cách định nghĩa liên minh
Chúng ta sử dụng từ khóa union
để định nghĩa một liên minh trong C.
Ví dụ:
union car
{
char name[50];
int price;
};
Trong ví dụ này, kiểu union car
được định nghĩa. Lúc này, chúng ta có thể tạo ra các biến thuộc kiểu này.
4.2 Tạo các biến liên minh
Khi một liên minh được định nghĩa, nó tạo ra một kiểu do người dùng định nghĩa. Tuy nhiên, liên minh không được cấp phát bộ nhớ. Để cấp phát bộ nhớ cho một kiểu liên minh cụ thể và làm việc với nó, chúng ta tạo các biến như sau:
union car
{
char name[50];
int price;
} car1, car2, *car3;
Trong ví dụ này, biến liên minh car1
và car2
, cùng với biến con trỏ car3
thuộc kiểu union car
, được tạo ra.
4.3 Truy cập vào các thành viên của một liên minh
Để truy cập vào các thành viên của một liên minh trong C, chúng ta sử dụng toán tử .
. Để truy cập vào các thành viên qua biến con trỏ, chúng ta sử dụng toán tử ->
.
Ví dụ, để truy cập price
của car1
, ta sử dụng car1.price
.
#include
union Job
{
float salary;
int workerNo;
} j;
int main()
{
j.salary = 12.3;
j.workerNo = 100;
printf("Salary = %.1f\n", j.salary);
printf("Number of workers = %d", j.workerNo);
return 0;
}
Trong ví dụ này, chúng ta gán giá trị 12.3 cho thành viên salary
của biến j
. Khi j.workerNo
được gán giá trị, j.salary
sẽ không còn giữ giá trị 12.3 nữa.
4.4 Sự khác biệt giữa liên minh và cấu trúc
Trong ngôn ngữ lập trình C, có sự khác biệt về kích thước giữa biến liên minh và biến cấu trúc. Điều này do cấu trúc cấp phát đủ không gian để lưu trữ tất cả các thành viên của nó, trong khi liên minh chỉ cấp phát không gian để lưu trữ thành viên lớn nhất.
Ví dụ: So sánh kích thước của biến liên minh và biến cấu trúc
#include
union unionJob
{
char name[32];
float salary;
int workerNo;
} uJob;
struct structJob
{
char name[32];
float salary;
int workerNo;
} sJob;
int main()
{
printf("size of union = %ld bytes\n", sizeof(uJob));
printf("size of structure = %ld bytes\n", sizeof(sJob));
return 0;
}
Trong ví dụ này, chúng ta so sánh kích thước của biến liên minh uJob
và biến cấu trúc sJob
. Kích thước của uJob
là 32 byte, trong khi kích thước của sJob
là 40 byte.
Sự khác biệt kích thước này xuất phát từ việc rằng kích thước của biến liên minh thường sẽ bằng kích thước của thành viên lớn nhất. Trong ví dụ này, kích thước của thành viên lớn nhất, name[32]
, là 32 byte. Tất cả các thành viên trong cùng một liên minh chia sẻ cùng một vùng nhớ.