Giáo trình Lập trình căn bản (Phần 2)

Chương VI

KIỂU MẢNG

Học xong chương này, sinh viên sẽ nắm được các vấn đề sau:

• Khái niệm về kiểu dữ liệu mảng cũng như ứng dụng của nó.

• Cách khai báo biến kiểu mảng và các phép toán trên các phần tử của mảng.

I. GIỚI THIỆU KIỂU DỮ LIỆU “KIỂU MẢNG”

TRONG C

Mảng là một tập hợp các phần tử cố định có cùng một kiểu, gọi là kiểu phần tử.

Kiểu phần tử có thể là có các kiểu bất kỳ: ký tự, số, chuỗi ký tự ; cũng có khi ta sử

dụng kiểu mảng để làm kiểu phần tử cho một mảng (trong trường hợp này ta gọi là

mảng của mảng hay mảng nhiều chiều).

Ta có thể chia mảng làm 2 loại: mảng 1 chiều và mảng nhiều chiều.

Mảng là kiểu dữ liệu được sử dụng rất thường xuyên. Chẳng hạn người ta cần

quản lý một danh sách họ và tên của khoảng 100 sinh viên trong một lớp. Nhận thấy

rằng mỗi họ và tên để lưu trữ ta cần 1 biến kiểu chuỗi, như vậy 100 họ và tên thì cần

khai báo 100 biến kiểu chuỗi. Nếu khai báo như thế này thì đoạn khai báo cũng như

các thao tác trên các họ tên sẽ rất dài dòng và rắc rối. Vì thế, kiểu dữ liệu mảng giúp

ích ta trong trường hợp này; chỉ cần khai báo 1 biến, biến này có thể coi như là tương

đương với 100 biến chuỗi ký tự; đó là 1 mảng mà các phần tử của nó là chuỗi ký tự.

Hay như để lưu trữ các từ khóa của ngôn ngữ lập trình C, ta cũng dùng đến một mảng

để lưu trữ chúng.

II. MẢNG 1 CHIỀU

Nếu xét dưới góc độ toán học, mảng 1 chiều giống như một vector. Mỗi phần tử của mảng

một chiều có giá trị không phải là một mảng khác.

II.1. Khai báo

II.1.1. Khai báo mảng với số phần tử xác định (khai báo tường minh)

Cú pháp: <[số phần="" tử]="">

Ý nghĩa:

- Tên mảng: đây là một cái tên đặt đúng theo quy tắc đặt tên của danh biểu. Tên này

cũng mang ý nghĩa là tên biến mảng.

- Số phần tử: là một hằng số nguyên, cho biết số lượng phần tử tối đa trong mảng là

bao nhiêu (hay nói khác đi kích thước của mảng là gì).

- Kiểu: mỗi phần tử của mảng có dữ liệu thuộc kiểu gì.

- Ở đây, ta khai báo một biến mảng gồm có số phần tử phần tử, phần tử thứ nhất là

tên mảng [0], phần tử cuối cùng là tên mảng[số phần tử -1]

pdf 42 trang yennguyen 4980
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Lập trình căn bản (Phần 2)", để tải tài liệu gốc về máy hãy click vào nút Download ở trên

Tóm tắt nội dung tài liệu: Giáo trình Lập trình căn bản (Phần 2)

Giáo trình Lập trình căn bản (Phần 2)
Lập trình căn bản 
Chương VI 
KIỂU MẢNG 
 Học xong chương này, sinh viên sẽ nắm được các vấn đề sau: 
• Khái niệm về kiểu dữ liệu mảng cũng như ứng dụng của nó. 
• Cách khai báo biến kiểu mảng và các phép toán trên các phần tử của mảng. 
 I. GIỚI THIỆU KIỂU DỮ LIỆU “KIỂU MẢNG” 
TRONG C 
Mảng là một tập hợp các phần tử cố định có cùng một kiểu, gọi là kiểu phần tử. 
Kiểu phần tử có thể là có các kiểu bất kỳ: ký tự, số, chuỗi ký tự; cũng có khi ta sử 
dụng kiểu mảng để làm kiểu phần tử cho một mảng (trong trường hợp này ta gọi là 
mảng của mảng hay mảng nhiều chiều). 
Ta có thể chia mảng làm 2 loại: mảng 1 chiều và mảng nhiều chiều. 
Mảng là kiểu dữ liệu được sử dụng rất thường xuyên. Chẳng hạn người ta cần 
quản lý một danh sách họ và tên của khoảng 100 sinh viên trong một lớp. Nhận thấy 
rằng mỗi họ và tên để lưu trữ ta cần 1 biến kiểu chuỗi, như vậy 100 họ và tên thì cần 
khai báo 100 biến kiểu chuỗi. Nếu khai báo như thế này thì đoạn khai báo cũng như 
các thao tác trên các họ tên sẽ rất dài dòng và rắc rối. Vì thế, kiểu dữ liệu mảng giúp 
ích ta trong trường hợp này; chỉ cần khai báo 1 biến, biến này có thể coi như là tương 
đương với 100 biến chuỗi ký tự; đó là 1 mảng mà các phần tử của nó là chuỗi ký tự. 
Hay như để lưu trữ các từ khóa của ngôn ngữ lập trình C, ta cũng dùng đến một mảng 
để lưu trữ chúng. 
 II. MẢNG 1 CHIỀU 
Nếu xét dưới góc độ toán học, mảng 1 chiều giống như một vector. Mỗi phần tử của mảng 
một chiều có giá trị không phải là một mảng khác. 
II.1. Khai báo 
II.1.1. Khai báo mảng với số phần tử xác định (khai báo tường minh) 
Cú pháp: 
Ý nghĩa: 
- Tên mảng: đây là một cái tên đặt đúng theo quy tắc đặt tên của danh biểu. Tên này 
cũng mang ý nghĩa là tên biến mảng. 
- Số phần tử: là một hằng số nguyên, cho biết số lượng phần tử tối đa trong mảng là 
bao nhiêu (hay nói khác đi kích thước của mảng là gì). 
- Kiểu: mỗi phần tử của mảng có dữ liệu thuộc kiểu gì. 
- Ở đây, ta khai báo một biến mảng gồm có số phần tử phần tử, phần tử thứ nhất là 
tên mảng [0], phần tử cuối cùng là tên mảng[số phần tử -1] 
Trang 72 
Lập trình căn bản 
Ví dụ: 
int a[10]; /* Khai báo biến mảng tên a, phần tử thứ nhất là a[0], phần tử 
cuối cùng là a[9].*/ 
Ta có thể coi mảng a là một dãy liên tiếp các phần tử trong bộ nhớ như sau: 
Vị trí 0 1 2 3 4 5 6 7 8 9 
Tên phần tử a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] 
Hình 1: Hình ảnh mảng a trong bộ nhớ 
II.1.2. Khai báo mảng với số phần tử không xác định (khai báo không tường 
minh) 
Cú pháp: 
Khi khai báo, không cho biết rõ số phần tử của mảng, kiểu khai báo này thường được 
áp dụng trong các trường hợp: vừa khai báo vừa gán giá trị, khai báo mảng là tham số hình 
thức của hàm. 
a. Vừa khai báo vừa gán giá trị 
Cú pháp: 
 []= {Các giá trị cách nhau bởi dấu phẩy} 
 Nếu vừa khai báo vừa gán giá trị thì mặc nhiên C sẽ hiểu số phần tử của mảng là số 
giá trị mà chúng ta gán cho mảng trong cặp dấu {}. Chúng ta có thể sử dụng hàm sizeof() 
để lấy số phần tử của mảng như sau: 
Số phần tử=sizeof(tên mảng)/ sizeof(kiểu) 
 b. Khai báo mảng là tham số hình thức của hàm, trong trường hợp này ta không cần 
chỉ định số phần tử của mảng là bao nhiêu. 
II.2 Truy xuất từng phần tử của mảng 
Mỗi phần tử của mảng được truy xuất thông qua Tên biến mảng theo sau là chỉ số 
nằm trong cặp dấu ngoặc vuông [ ]. Chẳng hạn a[0] là phần tử đầu tiên của mảng a được khai 
báo ở trên. Chỉ số của phần tử mảng là một biểu thức mà giá trị là kiểu số nguyên. 
 Với cách truy xuất theo kiểu này, Tên biến mảng[Chỉ số] có thể coi như là một biến 
có kiểu dữ liệu là kiểu được chỉ ra trong khai báo biến mảng. 
Ví dụ 1: 
int a[10]; 
Trong khai báo này, việc truy xuất các phần tử được chỉ ra trong hình 1. Chẳng hạn 
phần tử thứ 2 (có vị trí 1) là a[1] 
Ví dụ 2: Vừa khai báo vừa gán trị cho 1 mảng 1 chiều các số nguyên. In mảng số 
nguyên này lên màn hình. 
Giả sử ta đã biết số phần tử của mảng là n; việc hiển thị 1 giá trị số nguyên lên màn 
hình ta cần sử dụng hàm printf() với định dạng %d, tổng quát hóa lên nếu muốn hiển thị lên 
màn hình giá trị của n số nguyên, ta cần gọi hàm printf() đúng n lần. Như vậy trong trường 
hợp này ta sử dụng 1 vòng lặp để in ra giá trị các phần tử. 
Ta có đoạn chương trình sau: 
#include 
#include 
int main() 
{ 
 int n,i,j,tam; 
 int dayso[]={66,65,69,68,67,70}; 
 clrscr(); 
Trang 73 
Lập trình căn bản 
 n=sizeof(dayso)/sizeof(int); /*Lấy số phần tử*/ 
 printf("\n Noi dung cua mang "); 
 for (i=0;i<n;i++) 
 printf("%d ",dayso[i]); 
 return 0; 
} 
Ví dụ 3: Đổi một số nguyên dương thập phân thành số nhị phân. Việc chuyển đổi này 
được thực hiện bằng cách lấy số đó chia liên tiếp cho 2 cho tới khi bằng 0 và lấy các số dư 
theo chiều ngược lại để tạo thành số nhị phân. Ta sẽ dùng mảng một chiều để lưu lại các số dư 
đó. Chương trình cụ thể như sau: 
#include 
#include 
int main() 
{ 
 unsigned int N; 
 unsigned int Du; 
 unsigned int NhiPhan[20],K=0,i; 
 printf("Nhap vao so nguyen N= ");scanf("%d",&N); 
 do 
 { 
 Du=N % 2; 
 NhiPhan[K]=Du; /* Lưu số dư vào mảng ở vị trí K*/ 
 K++; /* Tăng K lên để lần kế lưu vào vị trí kế*/ 
 N = N/2; 
 } while(N>0); 
 printf("Dang nhi phan la: "); 
 for(i=K-1;i>=0;i--) 
 printf("%d",NhiPhan[i]); 
 getch(); 
 return 0; 
} 
 Ví dụ 4: Nhập vào một dãy n số và sắp xếp các số theo thứ tự tăng. Đây là một bài 
toán có ứng dụng rộng rãi trong nhiều lĩnh vực. Có rất nhiều giải thuật sắp xếp. Một trong số 
đó được mô tả như sau: 
Đầu tiên đưa phần tử thứ nhất so sánh với các phần tử còn lại, nếu nó lớn hơn một 
phần tử đang so sánh thì đổi chỗ hai phần tử cho nhau. Sau đó tiếp tục so sánh phần tử thứ hai 
với các phần tử từ thứ ba trở đi ... cứ tiếp tục như vậy cho đến phần tử thứ n-1. 
Chương trình sẽ được chia thành các hàm Nhap (Nhập các số), SapXep (Sắp xếp) và 
InMang (In các số); các tham số hình thức của các hàm này là 1 mảng không chỉ định rõ số 
phần tử tối đa, nhưng ta cần có thêm số phần tử thực tế được sử dụng của mảng là bao nhiêu, 
đây là một giá trị nguyên. 
#include 
#include 
void Nhap(int a[],int N) 
{ 
 int i; 
 for(i=0; i< N; i++) 
 { 
 printf("Phan tu thu %d: ",i);scanf("%d",&a[i]); 
 } 
} 
Trang 74 
Lập trình căn bản 
void InMang(int a[], int N) 
{ 
 int i; 
 for (i=0; i<N;i++) 
 printf("%d ",a[i]); 
 printf("\n"); 
} 
void SapXep(int a[], int N) 
{ 
 int t,i; 
 for(i=0;i<N-1;i++) 
 for(int j=i+1;j<N;j++) 
 if (a[i]>a[j]) 
 { 
 t=a[i]; 
 a[i]=a[j]; 
 a[j]=t; 
 } 
} 
int main() 
{ 
 int b[20], N; 
 printf("So phan tu thuc te cua mang N= "); 
scanf("%d",&N); 
 Nhap(b,N); 
 printf("Mang vua nhap: "); 
 InMang(b,N); 
 SapXep(b,N); /* Gọi hàm sắp xếp*/ 
 printf("Mang sau khi sap xep: "); 
 InMang(b,N); 
 getch(); 
 return 0; 
} 
 Kết quả chạy chương trình có thể là: 
III. MẢNG NHIỀU CHIỀU 
 Mảng nhiều chiều là mảng có từ 2 chiều trở lên. Điều đó có nghĩa là mỗi phần 
tử của mảng là một mảng khác. 
 Người ta thường sử dụng mảng nhiều chiều để lưu các ma trận, các tọa độ 2 
chiều, 3 chiều 
 Phần dưới đây là các vấn đề liên quan đến mảng 2 chiều; các mảng 3, 4, chiều thì 
tương tự (chỉ cần tổng quát hóa lên). 
Trang 75 
Lập trình căn bản 
III.1 Khai báo 
III.1.1. Khai báo mảng 2 chiều tường minh 
Cú pháp: 
Ví dụ: Người ta cần lưu trữ thông tin của một ma trận gồm các số thực. Lúc này 
ta có thể khai báo một mảng 2 chiều như sau: 
 float m[8][9]; /* Khai báo mảng 2 chiều có 8*9 phần tử là số thực*/ 
Trong trường hợp này, ta đã khai báo cho một ma trận có tối đa là 8 dòng, mỗi 
dòng có tối đa là 9 cột. Hình ảnh của ma trận này được cho trong hình 2: 
Dòng\Cột 0 1 2 3 4 5 6 7 8 
0 m[0][0] m[0][1] m[0][2] m[0][3] m[0][4] m[0][5] m[0][6] m[0][7] m[0][8]
1 m[1][0] m[1][1] m[1][2] m[1][3] m[1][4] m[1][5] m[1][6] m[1][7] m[1][8]
2 m[2][0] m[2][1] m[2][2] m[2][3] m[2][4] m[2][5] m[2][6] m[2][7] m[2][8]
3 m[3][0] m[3][1] m[3][2] m[3][3] m[3][4] m[3][5] m[3][6] m[3][7] m[3][8]
4 m[4][0] m[4][1] m[4][2] m[4][3] m[4][4] m[4][5] m[4][6] m[4][7] m[4][8]
5 m[5][0] m[5][1] m[5][2] m[5][3] m[5][4] m[5][5] m[5][6] m[5][7] m[5][8]
6 m[6][0] m[6][1] m[6][2] m[6][3] m[6][4] m[6][5] m[6][6] m[6][7] m[6][8]
7 m[7][0] m[7][1] m[7][2] m[7][3] m[7][4] m[7][5] m[7][6] m[7][7] m[7][8]
Hình 2: Ma trận được mô tả là 1 mảng 2 chiều 
III.1.2. Khai báo mảng 2 chiều không tường minh 
Để khai báo mảng 2 chiều không tường minh, ta vẫn phải chỉ ra số phần tử của 
chiều thứ hai (chiều cuối cùng). 
Cú pháp: 
Cách khai báo này cũng được áp dụng trong trường hợp vừa khai báo, vừa gán 
trị hay đặt mảng 2 chiều là tham số hình thức của hàm. 
III.2 Truy xuất từng phần tử của mảng 2 chiều 
Ta có thể truy xuất một phần tử của mảng hai chiều bằng cách viết ra tên mảng 
theo sau là hai chỉ số đặt trong hai cặp dấu ngoặc vuông. Chẳng hạn ta viết m[2][3]. 
Với cách truy xuất theo cách này, Tên mảng[Chỉ số 1][Chỉ số 2] có thể coi là 1 
biến có kiểu được chỉ ra trong khai báo biến mảng. 
Ví dụ 1: Viết chương trình cho phép nhập 2 ma trận a, b có m dòng n cột, thực 
hiện phép toán cộng hai ma trận a,b và in ma trận kết quả lên màn hình. 
Trong ví dụ này, ta sẽ sử dụng hàm để làm ngắn gọn hơn chương trình của ta. 
Ta sẽ viết các hàm: nhập 1 ma trận từ bàn phím, hiển thị ma trận lên màn hình, cộng 2 
ma trận. 
#include 
#include 
void Nhap(int a[][10],int M,int N) 
{ 
 int i,j; 
 for(i=0;i<M;i++) 
Trang 76 
Lập trình căn bản 
 for(j=0; j<N; j++){ 
 printf("Phan tu o dong %d cot %d: ",i,j); 
scanf("%d",&a[i][j]); 
 } 
} 
void InMaTran(int a[][10], int M, int N) 
{ 
 int i,j; 
 for(i=0;i<M;i++){ 
 for(j=0; j< N; j++) 
 printf("%d ",a[i][j]); 
 printf("\n"); 
 } 
} 
/* Cong 2 ma tran A & B ket qua la ma tran C*/ 
void CongMaTran(int a[][10],int b[][10],int M,int N,int c[][10]){ 
 int i,j; 
 for(i=0;i<M;i++) 
 for(j=0; j<N; j++) 
 c[i][j]=a[i][j]+b[i][j]; 
} 
int main() 
{ 
 int a[10][10], b[10][10], M, N; 
 int c[10][10];/* Ma tran tong*/ 
 printf("So dong M= "); scanf("%d",&M); 
 printf("So cot M= "); scanf("%d",&N); 
 printf("Nhap ma tran A\n"); 
 Nhap(a,M,N); 
 printf("Nhap ma tran B\n"); 
 Nhap(b,M,N); 
 printf("Ma tran A: \n"); 
 InMaTran(a,M,N); 
 printf("Ma tran B: \n"); 
 InMaTran(b,M,N); 
 CongMaTran(a,b,M,N,c); 
 printf("Ma tran tong C:\n"); 
 InMaTran(c,M,N); 
 getch(); 
 return 0; 
} 
 Ví dụ 2: Nhập vào một ma trận 2 chiều gồm các số thực, in ra tổng của các phần tử 
trên đường chéo chính của ma trận này. 
 Ta nhận thấy rằng giả sử ma trận a có M dòng, N cột thì các phần tử của đường chéo 
chính là các phần tử có dạng: a[i][i] với i ∈ [0min(M,N)-1]. 
#include 
#include 
int main() 
{ 
 float a[10][10], T=0; 
 int M, N, i,j, Min; 
 clrscr(); 
Trang 77 
Lập trình căn bản 
 printf("Ma tran co bao nhieu dong? ");scanf("%d",&M); 
 printf("Ma tran co bao nhieu cot? ");scanf("%d",&N); 
 for(i=0;i<M;i++) 
 for(j=0; j<N; j++) 
 { 
 printf("Phan tu o dong %d cot %d: ",i,j); 
scanf("%f",&a[i][j]); 
 } 
 printf("Ma tran vua nhap: \n"); 
 for(i=0;i<M;i++) 
 { 
 for(j=0; j< N; j++) 
 printf("%.2f ",a[i][j]); 
 printf("\n"); 
 } 
 Min=(M>N) ? N: M; /* Tìm giá trị nhỏ nhất của M & N*/ 
 for(i=0;i<Min;i++) 
 T=T+a[i][i]; 
 printf("Tong cac phan tu o duong cheo chinh la: %f",T); 
 getch(); 
 return 0; 
} 
IV. BÀI TẬP 
IV.1 Mục đích yêu cầu 
Làm quen với kiểu dữ liệu có cấu trúc trong C, kiểu mảng. Thực hiện các bài tập trong 
phần nội dung bằng cách kết hợp kiểu dữ liệu mảng, các kiểu dữ liệu đã học và các phần đã 
học trong các bài tập trước. 
IV.2 Nội dung 
1. Viết chương trình nhập vào một dãy n số thực a[0], a[1],..., a[n-1], sắp xếp dãy số theo thứ 
tự từ lớn đến nhỏ. In dãy số sau khi sắp xếp. 
2. Viết chương trình sắp xếp một mảng theo thứ tự tăng dần sau khi đã loại bỏ các phần tử 
trùng nhau. 
3. Viết chương trình nhập vào một mảng, hãy xuất ra màn hình: 
- Phần tử lớn nhất của mảng. 
- Phần tử nhỏ nhất của mảng. 
- Tính tổng của các phần tử trong mảng . 
4. Viết chương trình nhập vào một dãy các số theo thứ tự tăng, nếu nhập sai quy cách thì yêu 
cầu nhập lại. In dãy số sau khi đã nhập xong. Nhập thêm một số mới và chèn số đó vào dãy đã 
có sao cho dãy vẫn đảm bảo thứ tự tăng. In lại dãy số để kiểm tra. 
5. Viết chương trình nhập vào một ma trận (mảng hai chiều) các số nguyên, gồm m hàng, n 
cột. In ma trận đó lên màn hình. Nhập một số nguyên khác vào và xét xem có phần tử nào của 
ma trận trùng với số này không ? Ở vị trí nào ? Có bao nhiêu phần tử ? 
Trang 78 
Lập trình căn bản 
6. Viết chương trình để chuyển đổi vị trí từ dòng thành cột của một ma trận (ma trận chuyển 
vị) vuông 4 hàng 4 cột. Sau đó viết cho ma trận tổng quát cấp m*n. 
Ví dụ: 
1 2 3 4 1 2 9 1 
2 5 5 8 2 5 4 5 
9 4 2 0 3 5 2 8 
1 5 8 6 4 8 0 6 
7. Viết chương trình nhập vào một mảng số tự nhiên. Hãy xuất ra màn hình: 
- Dòng 1 : gồm các số lẻ, tổng cộng có bao nhiêu số lẻ. 
- Dòng 2 : gồm các số chẵn, tổng cộng có bao nhiêu số chẵn. 
- Dòng 3 : gồm các số nguyên tố. 
- Dòng 4 : gồm các số không phải là số nguyên tố. 
8. Viết chương trình tính tổng bình phương của các số âm trong một mảng các số nguyên. 
9. Viết chương trình thực hiện việc đảo một mảng một chiều. 
Ví dụ : 1 2 3 4 5 7 9 10 đảo thành 10 9 7 5 4 3 2 1 . 
10. Viết chương trình nhập vào hai ma trận A và B có cấp m, n. In hai ma trận lên màn hình. 
Tổng hai ma trận A và B là ma trận C được tính bởi công thức: 
 cij= aij +bij ( i=0,1,2,...m-1; j=0,1,2...n-1) 
Tính ma trận tổng C và in kết quả lên màn hình. 
11. Viết chương trình nhập vào hai ma trận A có cấp m, k và B có cấp k, n. In hai ma trận lên 
màn hình. Tích hai ma trận A và B là ma trận C được tính bởi công thức: 
cij= ai1*b1j + ai2 *b2j + ai3 *b3j + ... + aik *bkj (i=0,1,2,...m-1;j=0,1,2...n-1) 
Tính ma trận tích C và in kết quả lên màn hình. 
12. Xét ma trận A vuông cấp n, các phần tử a[i, i] ( i= 1 ... n ) được gọi là đường chéo chính 
của ma trận vuông A. Ma trận vuông A được gọi là ma trận tam giác nếu tất cả các phần tử 
dưới đường chéo chính đều bằng 0. Định thức của ma trận tam giác bằng tích các phần tử trên 
đường chéo chính. 
Ta có thể chuyển một ma trận vuông bất kỳ về ma trận tam giác bằng thuật toán: 
- Xét cột i (i =0,1...n-2) 
- Trong cột i xét các phần tử a[k,i] ( k=i+1...n-1) 
+ Nếu a[k,i]=0 thì tăng k lên xét phần tử khác 
+ Nếu a[k,i] 0 thì làm như sau: 
Nhân toàn bộ hàng k với - a[i,i]/a[k,i] 
Lấy hàng i cộng vào hàng k sau khi thực hiện phép nhân trên. 
Đổi chỗ hai hàng i và k cho nhau 
Nhân toàn bộ hàng k với -1 sau khi đã đổi chỗ với hàng i 
Tăng k lên xét phần tử khác. 
Viết chương trình tính định thức cấp n thông qua các bước nhập ma trận, in ma trận, 
đưa ma trận về dạng tam giác, in ma trận tam giác, in kết quả tính định thức. 
13. Viết chương trình thực hiện việc trộn hai dãy có thứ tự thành một dãy có thứ tự. Yêu cầu 
không được trộn chung rồi mới sắp thứ tự. Khi trộn phải tận dụng được tính chất đã sắp của 
hai dãy con. 
Trang 79 
Lập trình căn bản 
Chương VII 
KIỂU CON TRỎ 
Học xong chương này, sinh viên sẽ nắm được các vấn đề sau: 
• Khái niệm  ... scii là 26 (xác định bởi tổ hợp phím Ctrl + 
Z). 
Tập tin văn bản chỉ có thể truy xuất theo kiểu tuần tự. 
o Tập tin định kiểu (Typed File): là loại tập tin bao gồm nhiều phần tử có 
cùng kiểu: char, int, long, cấu trúc và được lưu trữ trên đĩa dưới dạng một chuỗi các 
byte liên tục. 
o Tập tin không định kiểu (Untyped File): là loại tập tin mà dữ liệu của chúng 
gồm các cấu trúc dữ liệu mà người ta không quan tâm đến nội dung hoặc kiểu của nó, 
chỉ lưu ý đến các yếu tố vật lý của tập tin như độ lớn và các yếu tố tác động lên tập tin 
mà thôi. 
Biến tập tin: là một biến thuộc kiểu dữ liệu tập tin dùng để đại diện cho một 
tập tin. Dữ liệu chứa trong một tập tin được truy xuất qua các thao tác với thông số là 
biến tập tin đại diện cho tập tin đó. 
Con trỏ tập tin: Khi một tập tin được mở ra để làm việc, tại mỗi thời điểm, sẽ 
có một vị trí của tập tin mà tại đó việc đọc/ghi thông tin sẽ xảy ra. Người ta hình dung 
có một con trỏ đang chỉ đến vị trí đó và đặt tên nó là con trỏ tập tin. 
Trang 105 
Lập trình căn bản 
Sau khi đọc/ghi xong dữ liệu, con trỏ sẽ chuyển dịch thêm một phần tử về phía 
cuối tập tin. Sau phần tử dữ liệu cuối cùng của tập tin là dấu kết thúc tập tin EOF (End 
Of File). 
II. CÁC THAO TÁC TRÊN TẬP TIN 
Muốn thao tác trên tập tin, ta phải lần lượt làm theo các bước: 
o Khai báo biến tập tin. 
o Mở tập tin bằng hàm fopen(). 
o Thực hiện các thao tác xử lý dữ liệu của tập tin bằng các hàm đọc/ghi dữ liệu. 
o Đóng tập tin bằng hàm fclose(). 
Ở đây, ta thao tác với tập tin nhờ các hàm được định nghĩa trong thư viện stdio.h. 
II.1. Khai báo biến tập tin 
 Cú pháp: FILE 
 Các biến trong danh sách phải là các con trỏ và được phân cách bởi dấu phẩy(,). 
 Ví dụ: FILE *f1,*f2; 
II.2. Mở tập tin 
 Cú pháp: FILE *fopen(char *Path, const char *Mode) 
 Trong đó: 
 - Path: chuỗi chỉ đường dẫn đến tập tin trên đĩa. 
 - Type: chuỗi xác định cách thức mà tập tin sẽ mở. Các giá trị có thể của Mode: 
Chế độ Ý nghĩa 
r Mở tập tin văn bản để đọc 
w Tạo ra tập tin văn bản mới để ghi 
a Nối vào tập tin văn bản 
rb Mở tập tin nhị phân để đọc 
wb Tạo ra tập tin nhị phân để ghi 
ab Nối vào tập tin nhị phân 
r+ Mở một tập tin văn bản để đọc/ghi 
w+ Tạo ra tập tin văn bản để đọc ghi 
a+ Nối vào hay tạo mới tập tin văn bản để đọc/ghi 
r+b Mở ra tập tin nhị phân để đọc/ghi 
w+b Tạo ra tập tin nhị phân để đọc/ghi 
a+b Nối vào hay tạo mới tập tin nhị phân 
 - Hàm fopen trả về một con trỏ tập tin. Chương trình của ta không thể thay đổi 
giá trị của con trỏ này. Nếu có một lỗi xuất hiện trong khi mở tập tin thì hàm này trả về 
con trỏ NULL. 
 Ví dụ: Mở một tập tin tên TEST.txt để ghi. 
 FILE *f; 
 f = fopen(“TEST.txt”, “w”); 
 if (f!=NULL) 
{ 
 /* Các câu lệnh để thao tác với tập tin*/ 
Trang 106 
Lập trình căn bản 
 /* Đóng tập tin*/ 
} 
Trong ví dụ trên, ta có sử dụng câu lệnh kiểm tra điều kiện để xác định 
mở tập tin có thành công hay không?. 
Nếu mở tập tin để ghi, nếu tập tin đã tồn tại rồi thì tập tin sẽ bị xóa và 
một tập tin mới được tạo ra. Nếu ta muốn ghi nối dữ liệu, ta phải sử dụng chế 
độ “a”. Khi mở với chế độ đọc, tập tin phải tồn tại rồi, nếu không một lỗi sẽ 
xuất hiện. 
II.3. Đóng tập tin 
 Hàm fclose() được dùng để đóng tập tin được mở bởi hàm fopen(). Hàm này sẽ 
ghi dữ liệu còn lại trong vùng đệm vào tập tin và đóng lại tập tin. 
 Cú pháp: int fclose(FILE *f) 
 Trong đó f là con trỏ tập tin được mở bởi hàm fopen(). Giá trị trả về của hàm là 
0 báo rằng việc đóng tập tin thành công. Hàm trả về EOF nếu có xuất hiện lỗi. 
 Ngoài ra, ta còn có thể sử dụng hàm fcloseall() để đóng tất cả các tập tin lại. 
 Cú pháp: int fcloseall() 
 Kết quả trả về của hàm là tổng số các tập tin được đóng lại. Nếu không thành 
công, kết quả trả về là EOF. 
II.4. Kiểm tra đến cuối tập tin hay chưa? 
 Cú pháp: int feof(FILE *f) 
 Ý nghĩa: Kiểm tra xem đã chạm tới cuối tập tin hay chưa và trả về EOF nếu 
cuối tập tin được chạm tới, ngược lại trả về 0. 
II.5 Di chuyển con trỏ tập tin về đầu tập tin - Hàm rewind() 
 Khi ta đang thao tác một tập tin đang mở, con trỏ tập tin luôn di chuyển về phía 
cuối tập tin. Muốn cho con trỏ quay về đầu tập tin như khi mở nó, ta sử dụng hàm 
rewind(). 
 Cú pháp: void rewind(FILE *f) 
III. TRUY CẬP TẬP TIN VĂN BẢN 
III.1. Ghi dữ liệu lên tập tin văn bản 
III.1.1 Hàm putc() 
Hàm này được dùng để ghi một ký tự lên một tập tin văn bản đang được mở để 
làm việc. 
Cú pháp: int putc(int c, FILE *f) 
Trong đó, tham số c chứa mã Ascii của một ký tự nào đó. Mã này được ghi lên 
tập tin liên kết với con trỏ f. Hàm này trả về EOF nếu gặp lỗi. 
III.1.2 Hàm fputs() 
 Hàm này dùng để ghi một chuỗi ký tự chứa trong vùng đệm lên tập tin văn bản. 
 Cú pháp: int puts(const char *buffer, FILE *f) 
Trang 107 
Lập trình căn bản 
 Trong đó, buffer là con trỏ có kiểu char chỉ đến vị trí đầu tiên của chuỗi ký tự 
được ghi vào. Hàm này trả về giá trị 0 nếu buffer chứa chuỗi rỗng và trả về EOF nếu 
gặp lỗi. 
III.1.3 Hàm fprintf() 
 Hàm này dùng để ghi dữ liệu có định dạng lên tập tin văn bản. 
Cú pháp: fprintf(FILE *f, const char *format, varexpr) 
Trong đó: format: chuỗi định dạng (giống với các định dạng của hàm 
printf()), varexpr: danh sách các biểu thức, mỗi biểu thức cách nhau dấu phẩy (,). 
Định dạng Ý nghĩa 
%d Ghi số nguyên 
%[.số chữ số thập phân] f Ghi số thực có theo quy tắc làm 
tròn số. 
%o Ghi số nguyên hệ bát phân 
%x Ghi số nguyên hệ thập lục phân 
%c Ghi một ký tự 
%s Ghi chuỗi ký tự 
%e hoặc %E hoặc %g 
hoặc %G 
Ghi số thực dạng khoa học (nhân 10 mũ x) 
 Ví dụ: Viết chương trình ghi chuỗi ký tự lên tập tin văn bản D:\\Baihat.txt 
#include 
#include 
int main() 
{ 
FILE *f; 
clrscr(); 
 f=fopen("D:\\Baihat.txt","r+"); 
 if (f!=NULL) 
 { 
 fputs("Em oi Ha Noi pho.\n",f); 
 fputs("Ta con em, mui hoang lan; ta con em, mui hoa sua.",f); 
 fclose(f); 
 } 
 getch(); 
 return 0; 
} 
Nội dung tập tin Baihat.txt khi được mở bằng trình soạn thảo văn bản Notepad. 
Trang 108 
Lập trình căn bản 
III.2. Đọc dữ liệu từ tập tin văn bản 
III.2.1 Hàm getc() 
 Hàm này dùng để đọc dữ liệu từ tập tin văn bản đang được mở để làm việc. 
 Cú pháp: int getc(FILE *f) 
 Hàm này trả về mã Ascii của một ký tự nào đó (kể cả EOF) trong tập tin liên 
kết với con trỏ f. 
III.2.2 Hàm fgets() 
 Cú pháp: char *fgets(char *buffer, int n, FILE *f) 
Hàm này được dùng để đọc một chuỗi ký tự từ tập tin văn bản đang được mở ra 
và liên kết với con trỏ f cho đến khi đọc đủ n ký tự hoặc gặp ký tự xuống dòng ‘\n’ (ký 
tự này cũng được đưa vào chuỗi kết quả) hay gặp ký tự kết thúc EOF (ký tự này không 
được đưa vào chuỗi kết quả). 
Trong đó: 
- buffer (vùng đệm): con trỏ có kiểu char chỉ đến cùng nhớ đủ lớn chứa các ký 
tự nhận được. 
- n: giá trị nguyên chỉ độ dài lớn nhất của chuỗi ký tự nhận được. 
- f: con trỏ liên kết với một tập tin nào đó. 
- Ký tự NULL (‘\0’) tự động được thêm vào cuối chuỗi kết quả lưu trong vùng 
đêm. 
- Hàm trả về địa chỉ đầu tiên của vùng đệm khi không gặp lỗi và chưa gặp ký tự 
kết thúc EOF. Ngược lại, hàm trả về giá trị NULL. 
III.2.3 Hàm fscanf() 
 Hàm này dùng để đọc dữ liệu từ tập tin văn bản vào danh sách các biến theo 
định dạng. 
 Cú pháp: fscanf(FILE *f, const char *format, varlist) 
 Trong đó: format: chuỗi định dạng (giống hàm scanf()); varlist: danh sách các 
biến mỗi biến cách nhau dấu phẩy (,). 
Ví dụ: Viết chương trình chép tập tin D:\Baihat.txt ở trên sang tập tin 
D:\Baica.txt. 
#include 
#include 
int main() 
{ 
 FILE *f1,*f2; 
 clrscr(); 
 f1=fopen("D:\\Baihat.txt","rt"); 
 f2=fopen("D:\\Baica.txt","wt"); 
 if (f1!=NULL && f2!=NULL) 
 { 
 int ch=fgetc(f1); 
 while (! feof(f1)) 
 { 
 fputc(ch,f2); 
 ch=fgetc(f1); 
 } 
Trang 109 
Lập trình căn bản 
 fcloseall(); 
 } 
 getch(); 
 return 0; 
} 
IV. TRUY CẬP TẬP TIN NHỊ PHÂN 
IV.1 Ghi dữ liệu lên tập tin nhị phân - Hàm fwrite() 
 Cú pháp: size_t fwrite(const void *ptr, size_t size, size_t n, FILE *f) 
 Trong đó: 
 - ptr: con trỏ chỉ đến vùng nhớ chứa thông tin cần ghi lên tập tin. 
 - n: số phần tử sẽ ghi lên tập tin. 
 - size: kích thước của mỗi phần tử. 
 - f: con trỏ tập tin đã được mở. 
 - Giá trị trả về của hàm này là số phần tử được ghi lên tập tin. Giá trị này bằng n 
trừ khi xuất hiện lỗi. 
IV.2 Đọc dữ liệu từ tập tin nhị phân - Hàm fread() 
 Cú pháp: size_t fread(const void *ptr, size_t size, size_t n, FILE *f) 
Trong đó: 
- ptr: con trỏ chỉ đến vùng nhớ sẽ nhận dữ liệu từ tập tin. 
 - n: số phần tử được đọc từ tập tin. 
 - size: kích thước của mỗi phần tử. 
 - f: con trỏ tập tin đã được mở. 
- Giá trị trả về của hàm này là số phần tử đã đọc được từ tập tin. Giá trị này 
bằng n hay nhỏ hơn n nếu đã chạm đến cuối tập tin hoặc có lỗi xuất hiện.. 
IV.3 Di chuyển con trỏ tập tin - Hàm fseek() 
 Việc ghi hay đọc dữ liệu từ tập tin sẽ làm cho con trỏ tập tin dịch chuyển một số 
byte, đây chính là kích thước của kiểu dữ liệu của mỗi phần tử của tập tin. 
 Khi đóng tập tin rồi mở lại nó, con trỏ luôn ở vị trí ngay đầu tập tin. Nhưng nếu 
ta sử dụng kiểu mở tập tin là “a” để ghi nối dữ liệu, con trỏ tập tin sẽ di chuyển đến vị 
trí cuối cùng của tập tin này. 
 Ta cũng có thể điều khiển việc di chuyển con trỏ tập tin đến vị trí chỉ định bằng 
hàm fseek(). 
 Cú pháp: int fseek(FILE *f, long offset, int whence) 
 Trong đó: 
- f: con trỏ tập tin đang thao tác. 
- offset: số byte cần dịch chuyển con trỏ tập tin kể từ vị trí trước đó. Phần tử 
đầu tiên là vị trí 0. 
- whence: vị trí bắt đầu để tính offset, ta có thể chọn điểm xuất phát là: 
0 SEEK_SET Vị trí đầu tập tin 
Trang 110 
Lập trình căn bản 
1 SEEK_CUR Vị trí hiện tại của con trỏ tập tin 
2 SEEK_END Vị trí cuối tập tin 
- Kết quả trả về của hàm là 0 nếu việc di chuyển thành công. Nếu không 
thành công, 1 giá trị khác 0 (đó là 1 mã lỗi) được trả về. 
IV.4 Ví dụ 
Ví dụ 1: Viết chương trình ghi lên tập tin CacSo.Dat 3 giá trị số (thực, nguyên, 
nguyên dài). Sau đó đọc các số từ tập tin vừa ghi và hiển thị lên màn hình. 
#include 
#include 
int main() 
{ 
 FILE *f; 
 clrscr(); 
 f=fopen("D:\\CacSo.txt","wb"); 
 if (f!=NULL) 
 { 
 double d=3.14; 
 int i=101; 
 long l=54321; 
 fwrite(&d,sizeof(double),1,f); 
 fwrite(&i,sizeof(int),1,f); 
 fwrite(&l,sizeof(long),1,f); 
 /* Doc tu tap tin*/ 
 rewind(f); 
 fread(&d,sizeof(double),1,f); 
 fread(&i,sizeof(int),1,f); 
 fread(&l,sizeof(long),1,f); 
 printf("Cac ket qua la: %f %d %ld",d,i,l); 
 fclose(f); 
 } 
 getch(); 
 return 0; 
} 
 Ví dụ 2: Mỗi sinh viên cần quản lý ít nhất 2 thông tin: mã sinh viên và họ tên. 
Viết chương trình cho phép lựa chọn các chức năng: nhập danh sách sinh viên từ bàn 
phím rồi ghi lên tập tin SinhVien.dat, đọc dữ liệu từ tập tin SinhVien.dat rồi hiển thị 
danh sách lên màn hình, tìm kiếm họ tên của một sinh viên nào đó dựa vào mã sinh 
viên nhập từ bàn phím. 
 Ta nhận thấy rằng mỗi phần tử của tập tin SinhVien.Dat là một cấu trúc có 2 
trường: mã và họ tên. Do đó, ta cần khai báo cấu trúc này và sử dụng các hàm đọc/ghi 
tập tin nhị phân với kích thước mỗi phần tử của tập tin là chính kích thước cấu trúc đó. 
#include 
#include 
#include 
typedef struct 
{ 
 char Ma[10]; 
 char HoTen[40]; 
Trang 111 
Lập trình căn bản 
} SinhVien; 
void WriteFile(char *FileName) 
{ 
 FILE *f; 
 int n,i; 
 SinhVien sv; 
 f=fopen(FileName,"ab"); 
 printf("Nhap bao nhieu sinh vien? ");scanf("%d",&n); 
 fflush(stdin); 
 for(i=1;i<=n;i++) 
 { 
 printf("Sinh vien thu %i\n",i); 
 printf(" - MSSV: ");gets(sv.Ma); 
 printf(" - Ho ten: ");gets(sv.HoTen); 
 fwrite(&sv,sizeof(sv),1,f); 
 fflush(stdin); 
 } 
 fclose(f); 
 printf("Bam phim bat ky de tiep tuc"); 
 getch(); 
} 
void ReadFile(char *FileName) 
{ 
 FILE *f; 
 SinhVien sv; 
 f=fopen(FileName,"rb"); 
 printf(" MSSV | Ho va ten\n"); 
 fread(&sv,sizeof(sv),1,f); 
 while (!feof(f)) 
 { 
 printf(" %s | %s\n",sv.Ma,sv.HoTen); 
 fread(&sv,sizeof(sv),1,f); 
 } 
 fclose(f); 
 printf("Bam phim bat ky de tiep tuc!!!"); 
 getch(); 
} 
void Search(char *FileName) 
{ 
 char MSSV[10]; 
 FILE *f; 
 int Found=0; 
 SinhVien sv; 
 fflush(stdin); 
 printf("Ma so sinh vien can tim: ");gets(MSSV); 
 f=fopen(FileName,"rb"); 
 while (!feof(f) && Found==0) 
 { 
 fread(&sv,sizeof(sv),1,f); 
 if (strcmp(sv.Ma,MSSV)==0) Found=1; 
 } 
 fclose(f); 
 if (Found == 1) 
 printf("Tim thay SV co ma %s. Ho ten la: %s",sv.Ma,sv.HoTen); 
Trang 112 
Lập trình căn bản 
 else 
 printf("Tim khong thay sinh vien co ma %s",MSSV); 
 printf("\nBam phim bat ky de tiep tuc!!!"); 
 getch(); 
} 
int main() 
{ 
 int c; 
 for (;;) 
 { 
 clrscr(); 
 printf("1. Nhap DSSV\n"); 
 printf("2. In DSSV\n"); 
 printf("3. Tim kiem\n"); 
 printf("4. Thoat\n"); 
 printf("Ban chon 1, 2, 3, 4: "); scanf("%d",&c); 
 if(c==1) 
 WriteFile("d:\\SinhVien.Dat"); 
 else if (c==2) 
 ReadFile("d:\\SinhVien.Dat"); 
 else if (c==3) 
 Search("d:\\SinhVien.Dat"); 
 else break; 
 } 
 return 0; 
} 
 Ngoài ra thư viện stdio.h còn định nghĩa một số hàm khác cho phép thao tác 
với tập tin, sinh viên có thể tham khảo trong phần trợ giúp. 
V. BÀI TẬP 
V.1 Mục đích yêu cầu 
 Nắm vững cách sử dụng kiểu dữ liệu tập tin. Phân biệt nó với tất cả các kiểu dữ 
liệu có cấu trúc đã học. Làm quen và biết cách thao tác trên tập tin. Vận dụng các kiến 
thức đã học viết các chương trình trong phần nội dung. 
V.2 Nội dung 
1. Viết chương trình quản lý một tập tin văn bản theo các yêu cầu: 
a- Nhập từ bàn phím nội dung một văn bản sau đó ghi vào đĩa. 
b- Đọc từ đĩa nội dung văn bản vừa nhập và in lên màn hình. 
c- Đọc từ đĩa nội dung văn bản vừa nhập, in nội dung đó lên màn hình và cho 
phép nối thêm thông tin vào cuối tập tin đó. 
2. Viết chương trình cho phép thống kê số lần xuất hiện của các ký tự là chữ 
(‘A’..’Z’,’a’..’z’) trong một tập tin văn bản. 
3. Viết chương trình đếm số từ và số dòng trong một tập tin văn bản. 
4. Viết chương trình nhập từ bàn phím và ghi vào 1 tập tin tên là DMHH.DAT với mỗi 
phần tử của tập tin là 1 cấu trúc bao gồm các trường: Ma (mã hàng: char[5]), Ten (Tên 
Trang 113 
Lập trình căn bản 
hàng: char[20]).Kết thúc việc nhập bằng cách gõ ENTER vào Ma. Ta sẽ dùng tập tin 
này để giải mã hàng hóa cho tập tin DSHH.DAT sẽ đề cập trong bài 5. 
5. Viết chương trình cho phép nhập từ bàn phím và ghi vào 1 tập tin tên DSHH.Dat 
với mỗi phần tử của tập tin là một cấu trúc bao gồm các trường : mh (mã hàng: 
char[5]), sl (số lượng : int), dg ( đơn giá: float), st (Số tiền: float) theo yêu cầu: 
 - Mỗi lần nhập một cấu trúc 
 - Trước tiên nhập mã hàng (mh), đưa mh so sánh với Ma trong tập tin 
DMHH.DAT đã được tạo ra bởi bài tập 1, nếu mh=ma thì in tên hàng ngay bên cạnh 
mã hàng. 
 - Nhập số lượng (sl). 
 - Nhập đơn giá (dg). 
 - Tính số tiền = số lượng * đơn giá. 
 Kết thúc việc nhập bằng cách đánh ENTER vào mã hàng. Sau khi nhập xong 
yêu cầu in toàn bộ danh sách hàng hóa có sự giải mã về tên hàng theo mẫu sau: 
| STT | MA HANG| TEN HANG | SO LG |DON GIA|SO TIEN| 
| 1 | a0101 |Duong cat trang | 25 | 10000.00 |250000.00 | 
| 2 | b0101 |Sua co gai Ha Lan | 10 | 40000.00 |400000.00 | 
Trang 114 

File đính kèm:

  • pdfgiao_trinh_lap_trinh_can_ban_phan_2.pdf