B1: Mở tệp để đọc bằng hàm fopen()
fp = fopen(“Tên tệp”,”Kiểu truy nhập”);
trong đó: +) fp là con trỏ tệp được khai
báo trỏ tới kiểu FILE;
+) Tên tệp có thể là hằng xâu hoặc biến
xâu. Trong tên tệp có thể có đường dẫn.
+) Kiểu truy nhập tệp là hằng xâu diễn tả
cách truy nhập vào tệp.
64 trang |
Chia sẻ: huongthu9 | Lượt xem: 506 | Lượt tải: 0
Bạn đang xem trước 20 trang tài liệu Giáo trình Lập trình nâng cao, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
p trình nâng cao - Chương 06 - Ngô Công Thắng 5
2. Khai báo kiểu cấu trúc (tiếp)
² Ví dụ: Để lưu trữ thông tin về nhân sự của phòng tổ
chức với các thông tin về họ tên, ngày sinh, địa chỉ,
lương ta khai báo một kiểu cấu trúc như sau:
struct nhansu
{
char hoten[30];
char ngaysinh[10];
char diachi[40];
float luong;
};
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 6
2. Khai báo kiểu cấu trúc (tiếp)
² Sau khi khai báo kiểu cấu trúc ta có thể dùng tên
kiểu cấu trúc như tên các kiểu dữ liệu cơ bản.
² Kiểu của các thành phần của cấu trúc có thể là kiểu
cấu trúc, tức là trong cấu trúc có thể chứa cấu trúc
khác. Ví dụ:
struct ngaythang
{
int ngay,thang,nam;
};
struct nhansu
{
char hoten[30];
ngaythang ngaysinh;
char diachi[40];
float luong;
};
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 7
3. Khai báo biến cấu trúc
²Việc khai báo kiểu cấu trúc không tạo ra
vùng nhớ chứa cấu trúc mà chỉ mô tả về cấu
trúc xem có những gì.
²Muốn có vùng nhớ chứa cấu trúc ta phải khai
báo biến cấu trúc. Cú pháp:
struct Tên_kiểu_cấu_trúc Tên_biến_cấu trúc;
Ví dụ:
struct nhansu ng1,ng2;
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 8
4. Truy nhập các thành phần cấu trúc
² Để truy nhập các thành phần của cấu trúc ta dùng
toán tử chấm. Cú pháp:
Tên_biến_cấu_trúc.Tên_thành_phần
Ví dụ:
struct thisinh
{
char SBD[15];
float toan,ly,hoa;
} ts;
//Khai bao bien cau truc
thisinh ts;
//Nhap du lieu cho thi sinh
printf("So bao danh: “); scanf(“%s”,&ts.SBD);
printf(“Diem Toan: “); scanf(“%f”,&ts.toan);
printf(“Diem Ly: “); scanf(“%f”,&ts.ly);
printf(“Diem Hoa: “); scanf(“%f”,&ts.hoa);
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 9
5. Khởi tạo biến cấu trúc
² Khi khai báo biến cấu trúc ta có thể khởi tạo giá trị cho các
thành phần của cấu trúc như khởi tạo cho các phần tử của
mảng.
Ví dụ:
//Khai bao kieu cau truc
struct thisinh
{
char SBD[15];
float toan,ly,hoa;
};
//Khai bao va khoi tao bien cau truc
struct thisinh ts={"NNHA23456", 7, 8, 9};
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 10
6. Phép gán biến cấu trúc
² Ta có thể gán một biến cấu trúc cho một biến cấu
trúc cùng kiểu. Ví dụ:
//Khai bao kieu cau truc
struct thisinh
{
char SBD[15];
float toan,ly,hoa;
};
//Khai bao bien cau truc
struct thisinh ts1={"NNHA23456",7,8,9};
struct thisinh ts2;
ts2=ts1;
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 11
7. Mảng cấu trúc
² Sau khi khai báo kiểu cấu trúc thì tên kiểu cấu trúc
được dùng như các kiểu dữ liệu khác. Chẳng hạn,
dùng cấu trúc làm kiểu phần tử của mảng.
Ví dụ:
//Khai bao kieu cau truc
struct thisinh
{
char SBD[15];
float toan,ly,hoa;
};
//Khai bao bien cau truc
thisinh ds[100];
strcpy(ds[0].SBD,"NNHA23456");
ds[0].toan=8;
ds[0].ly=8;
ds[0].hoa=9;
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 12
Ví dụ
Viết chương trình quản lý điểm môn học của sinh
viên. Mỗi sinh viên có các thông tin về họ tên, lớp,
điểm kiểm tra 1, điểm kiểm tra 2, trung bình kiểm
tra, điểm thi, điểm môn học. Nhập vào một danh
sách n sinh viên. Hiện danh sách ra màn hình theo
dạng bảng với các cột STT, Họ và tên, Lớp, TB
KTra, Đ.Thi, Điểm MH. Khi nhập dữ liệu chỉ nhập
Họ tên, Lớp, Điểm Ktra1, Điểm Ktra2, còn TB
KTra = (Điểm KTra1 + Điểm Ktra2)/2, Điểm MH =
0,3xTB KTra + 0,7xĐiểm Thi.
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 13
II. Kiểu liệt kê
² Kiểu liệt kê là kiểu dữ liệu do người lập trình tự
định nghĩa bằng cách liệt kê tất cả các giá trị. Các
giá trị của kiểu liệt kê là các tên tự đặt.
² Để định nghĩa kiểu liệt kê ta dùng từ khóa enum
theo cú pháp sau:
enum Tên_kiểu_liệt_kê {Danh sách các tên tự đặt};
Ví dụ: enum boolean {TRUE, FALSE};
enum mausac {Xanh, Do, Tim, Vang};
enum days_of_week {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 14
II. Kiểu liệt kê (tiếp)
²Sau khi khai báo kiểu liệt kê ta có thể khai
báo các biến kiểu liệt như các biến kiểu khác:
Tên_kiểu_liệt_kê Danh_sách_các_biến;
Ví dụ: Giả sử các kiểu liệt kê đã được khai
báo ở trên, ta khai báo các biến liệt kê:
days_of_week day1, day2;
²Để đưa giá trị vào biến liệt kê ta dùng lệnh
gán:
Ví dụ: day1 = Mon; day2 = Sat;
Lập trình nâng cao - Chương 06 - Ngô Công Thắng 15
II. Kiểu liệt kê (tiếp)
² Các giá trị kiểu liệt kê được lưu trữ như các số
nguyên kiểu int, giá trị tên đầu tiên là 0, giá trị tên
tiếp theo là 1,
Ví dụ: Với kiểu liệt kê days_of_week ở trên thì Sun
có giá trị 0, Mon có giá trị 1, Tue có giá trị 3,
² Ta có thể thay đổi giá trị số của các giá trị tên
n Cho các giá trị tên có giá trị số bắt đầu từ một số khác 0
Ví dụ: enum mausac {Xanh=5, Do, Tim, Vang};
Với khai báo này Xanh có giá trị 5, Do có giá trị 6, Tim
có giá trị 7, Vàng có giá trị 8.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 1
Chương 7. Con trỏ
I. Địa chỉ và con trỏ
II. Con trỏ, mảng và xâu ký tự
III. Quản lý bộ nhớ với hàm malloc() và free()
IV. Bài tập chương 7
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 2
I. Địa chỉ và con trỏ
1. Địa chỉ (hằng con trỏ)
2. Toán tử địa chỉ &
3. Khai báo biến con trỏ
4. Truy nhập biến qua con trỏ
5. Con trỏ void và con trỏ NULL
6. Các phép toán trên con trỏ
7. Con trỏ trỏ tới con trỏ
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 3
1. Địa chỉ (hằng con trỏ)
²Mỗi byte trong bộ nhớ máy tính có một địa
chỉ. Các địa chỉ này là các số bắt đầu từ 0 trở
đi. Ví dụ có 1 MB bộ nhớ thì địa chỉ thấp
nhất là 0 và địa chỉ cao nhất là 1.048.575.
²Bất kỳ chương trình nào khi được nạp vào bộ
nhớ đều chiếm một khoảng địa chỉ. Điều đó
có nghĩa là mọi biến và mọi hàm trong
chương trình đều bắt đầu tại một địa chỉ cụ
thể. Hình 7.1 cho thấy các địa chỉ bộ nhớ.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 4
1. Địa chỉ (hằng con trỏ) tiếp
Hình 7.1 Địa chỉ bộ nhớ
0
chương
trình
655.359
var1
var2
var3
var4
int
char
float
int
314.810
314.809
314.808
314.807
314.806
314.805
314.804
314.803
314.802
314.801
314.800
314.799
var1 có địa chỉ 314.809
var2 có địa chỉ 314.808
var3 có địa chỉ 314.804
var4 có địa chỉ 314.802
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 5
2. Toán tử địa chỉ &
²Toán tử địa chỉ ký hiệu là &, được dùng
để lấy địa chỉ của một biến. Toán tử &
phải đặt trước tên biến muốn lấy địa chỉ.
Ví dụ: Chương trình sau sẽ đưa ra địa
chỉ của 3 biến nguyên a, b, c.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 6
3. Khai báo biến con trỏ
² Vì địa chỉ bộ nhớ là số nên nó cũng có thể lưu trữ
trong một biến giống như giá trị của các kiểu int,
char và float. Một biến mà chứa giá trị địa chỉ gọi là
biến con trỏ hay gọi tắt là con trỏ. Nếu một con trỏ
chứa địa chỉ của một biến thì ta nói rằng con trỏ trỏ
tới biến đó.
² Để khai báo các biến con trỏ ta dùng cú pháp sau:
Kiểu* Tên_biến_con_trỏ;
trong đó Kiểu là kiểu dữ liệu của đối tượng mà biến
con trỏ sẽ trỏ tới. Dấu * có nghĩa là trỏ tới. Nên để
dấu * bên cạnh tên kiểu để nhấn mạnh rằng nó là
một phần của kiểu chứ không phải của tên biến con
trỏ.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 7
3. Khai báo biến con trỏ (tiếp)
² Ví dụ:
int a;
int* ptr;
ptr = &a;
Lệnh này khai báo một biến con trỏ có tên là ptr trỏ tới các
số nguyên int. Nói cách khác con trỏ ptr có thể chứa địa chỉ
của các biến nguyên.
² Để khai báo nhiều biến con trỏ cùng trỏ tới một kiểu dữ liệu
ta viết:
Kiểu *Biến1, *Biến2, *Biến3,;
Mặc dù dấu * để cạnh tên biến con trỏ nhưng vẫn nên hiểu
nó là một phần của kiểu.
Ví dụ: int *p, *q;
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 8
3. Khai báo biến con trỏ (tiếp)
² Khi khai báo một biến con trỏ thì biến con trỏ này
sẽ chứa một giá trị vô nghĩa (trừ khi được khởi tạo).
Giá trị vô nghĩa này có thể là địa chỉ của một ô nhớ
nào đó nằm trong phần chương trình của ta hoặc hệ
điều hành. Điều này sẽ rất nguy hiểm nếu ta đưa giá
trị vào ô nhớ do con trỏ này trỏ tới. Bởi vậy, trước
khi sử dụng một con trỏ ta phải đưa địa chỉ vào nó.
² Con trỏ trỏ tới kiểu nào thì chỉ chứa được địa chỉ
của các biến kiểu đó. Không thể gán địa chỉ của
biến float tới một con trỏ trỏ tới int.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 9
4. Truy nhập biến qua con trỏ
²Một câu hỏi đặt ra là nếu không biết tên một
biến mà chỉ biết địa chỉ của nó thì có truy
nhập được vào biến đó không? Câu trả lời là
có. Con trỏ chứa địa chỉ của một biến nên ta
có thể truy nhập biến qua con trỏ.
²Để truy nhập tới biến do con trỏ ptr trỏ tới ta
dùng toán tử truy nhập gián tiếp * đặt trước
tên biến con trỏ: *ptr. *ptr tương đương với
tên của biến, chỗ nào dùng được tên biến thì
chỗ đó dùng được *ptr.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 10
4. Truy nhập biến qua con trỏ
² Toán tử truy nhập gián tiếp cũng ký hiệu là * nhưng
có nghĩa là giá trị của biến được trỏ tới bởi biến con
trỏ nằm bên phải nó, khác với dấu * khi khai báo
biến con trỏ có nghĩa là trỏ tới.
² Ví dụ:
int v; //Khai báo biến có kiểu int
int* p; //Khai báo biến con trỏ p trỏ tới int
p = &v; //Gán địa chỉ của biến v cho con trỏ p
v = 3; //Gán 3 vào v
*p = 3; //Gán 3 vào v gián tiếp qua con trỏ p
v
p
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 11
5. Con trỏ trỏ tới void và con trỏ NULL
² Ta biết rằng con trỏ trỏ tới kiểu nào thì chỉ chứa
được địa chỉ của các biến kiểu đó. Tuy nhiên trong
C++ còn có một loại con trỏ đa năng có thể trỏ tới
bất kỳ kiểu dữ liệu nào. Con trỏ đó gọi là con trỏ trỏ
tới void. Khai báo con trỏ trỏ tới void như sau:
void* ptr;
² Con trỏ NULL là con trỏ không trỏ tới bất cứ cái gì,
nó chứa giá trị rỗng (bằng 0). Để có con trỏ rỗng ta
gán giá trị 0 vào biến con trỏ. Ta có thể sử dụng tên
hằng này để tạo con trỏ rỗng.
int* ptr=NULL;
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 12
5. Con trỏ trỏ tới void và con trỏ NULL (tiếp)
² Ví dụ:
int ivar;
float fvar;
int* iptr;
float* fptr;
void* vptr;
iptr = &ivar;
//iptr = &fvar; //lỗi vì gán float* tới int*
fptr = &fvar;
//fptr = &ivar; //lỗi vì gán int* tới float*
vptr = &ivar; //được vì gán int* tới void*
vptr = &fvar; //được vì gán float* tới void*
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 13
6. Các phép toán trên con trỏ
²Các phép toán số học:
n Chỉ có 4 phép toán dùng được với con trỏ là +, -,
++, --.
n Khi cộng hoặc trừ biến con trỏ với một số thì số
đó phải nguyên.
n Các phép toán số học tác động trên con trỏ khác
với bình thường. Cụ thể là khi tăng biến con trỏ
lên 1 đơn vị thì địa chỉ chứa trong biến con trỏ
không tăng lên một mà tăng lên một lượng bằng
kích thước kiểu dữ liệu con trỏ trỏ tới (thường là
2 với kiểu int, 4 với kiểu float và 8 với kiểu
double).
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 14
6. Các phép toán trên con trỏ (tiếp)
n Ví dụ: giả sử p là con trỏ int chứa địa chỉ 200, sau khi
lệnh
++p;
được thực hiện thì p sẽ có giá trị là 202. Nếu p là con trỏ
float thì sau lệnh trên p sẽ có giá trị là 204.
²Các phép toán so sánh: có thể so sánh hai
biến con trỏ bằng các phép toán so sánh. Tuy
nhiên việc so sánh này chỉ có ý nghĩa trong
hai trường hợp sau:
n So sánh hai con trỏ để xem chúng có bằng con trỏ NULL
không.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 15
6. Các phép toán trên con trỏ (tiếp)
n So sánh hai con trỏ khi chúng cùng liên quan tới một đối
tượng, chẳng hạn là cùng trỏ tới một biến.
² Phép gán: Có thể gán một biến con trỏ cho một
biến con trỏ có cùng kiểu trỏ tới.
² Lưu ý: Khi dùng toán tử tăng hoặc giảm với biến do
con trỏ trỏ tới thì phải chú ý về thứ tự thực hiện các
phép toán. Ví dụ: nếu ta viết
*p++;
thì con trỏ sẽ tăng lên 1 chứ không phải biến do con
trỏ trỏ tới tăng lên 1, bởi vì phép toán * và ++ cùng
mức ưu tiên, được kết hợp từ phải qua trái. Muốn
tăng biến do con trỏ trỏ tới ta phải viết:
(*p)++;
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 16
7. Con trỏ trỏ tới con trỏ
²Trong C++, một con trỏ có thể trỏ tới một
con trỏ khác, tức là một con trỏ có thể chứa
địa chỉ của một biến con trỏ khác.
Giá trị
Biến
Địa chỉ
Con trỏ
Giá trị
Biến
Địa chỉ
Con trỏ
Địa chỉ
Con trỏ
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 17
7. Con trỏ trỏ tới con trỏ (tiếp)
² Để khai báo một biến con trỏ trỏ tới một con trỏ ta dùng
thêm dấu * nữa. Ví dụ:
int** p; //p là con trỏ trỏ tới một con trỏ int
² Để truy nhập tới biến qua con trỏ trỏ tới con trỏ ta phải dùng
hai lần toán tử truy nhập gián tiếp. Kiểu truy nhập này gọi là
truy nhập gián tiếp bội (Multiple Indirection). Ví dụ:
char ch;
char* p;
char** mp;
ch='A';
p=&ch;
mp=&p;
cout<<"Ky tu nam trong bien ch la: "<<**mp;
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 18
II. Con trỏ, mảng và xâu ký tự
1. Con trỏ và mảng
2. Con trỏ và xâu ký tự
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 19
1. Con trỏ và mảng
² Con trỏ được sử dụng để truy nhập vào các phần tử của
mảng và làm đối số truyền vào hàm. Và khi mảng làm đối số
truyền vào hàm thì con trỏ cũng rất hữu ích.
² Các phần tử của mảng có thể được truy nhập qua ký hiệu của
mảng ([]) hoặc ký hiệu của con trỏ (*). Ví dụ:
int a[5]={31,54,77,52,93};
int i;
//Dua ra bang ky hieu cua mang
for(i=0;i<5;i++) printf("%i ",a[i]);
//Dua ra bang ky hieu cua con tro
for(i=0;i<5;i++) printf("%i ",*(a+i));
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 20
1. Con trỏ và mảng (tiếp)
² Biểu thức *(a+i) tương đương với a[i]. Ví dụ, với
i=2 thì *(a+2) là phần tử thứ 3 (có giá trị là 77).
² Tại sao *(a+2) lại là phần tử thứ 3? Như ta đã biết,
tên biến mảng chính là địa chỉ của phần tử đầu tiên
của biến mảng. Khi ta viết (a+2) thì trình biên dịch
sẽ thực hiện cộng địa chỉ với 2. Khi cộng địa chỉ với
2 trình biên dịch lấy kích thước kiểu dữ liệu của
mảng nhân với 2 rồi mới cộng vào địa chỉ. Kết quả
(a+2) cho ta địa chỉ của phần tử thứ 3. Để truy nhập
tới phần tử thứ 3 khi biết địa chỉ phải sử dụng toán
tử truy nhập gián tiếp *(a+2).
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 21
1. Con trỏ và mảng (tiếp)
²Địa chỉ của các phần tử mảng
31
54
77
52
93
a[0]
a[1]
a[2]
a[3]
a[4]
a
a+1
a+2
a+3
a+4
Địa chỉ của
các phần tử
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 22
1. Con trỏ và mảng (tiếp)
²Hằng con trỏ và biến con trỏ: Tên biến
mảng là một địa chỉ cụ thể mà hệ thống đã
chọn để đặt mảng. Địa chỉ này không thể
thay đổi và nó được duy trì khi biến mảng
còn tồn tại. Người ta gọi các địa chỉ không
thay đổi được là các hằng con trỏ. Vì tên biến
mảng a ở ví dụ trên là hằng nên ta không thể
viết a++ hay a+=2.
Một địa chỉ thì không thể thay đổi nhưng
biến con trỏ chứa địa chỉ thì có thể thay đổi.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 23
1. Con trỏ và mảng (tiếp)
²Hằng con trỏ và biến con trỏ: (tiếp)
Ví dụ sau dùng biến con trỏ để đưa ra các phần
tử của mảng:
int a[5]={31,54,77,52,93};
int i;
int *p=a; //p tro toi phan tu dau tien cua mang a
//Dua ra bang bien con tro
cout<<"Dua ra bang bien con tro: "<<'\n';
for(i=0;i<5;i++) cout<<*p++<<' ';
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 24
2. Con trỏ và xâu ký tự
² Như ta đã biết, xâu ký tự thực chất là mảng ký tự. Bởi
vậy ta có thể dùng ký hiệu con trỏ để truy nhập vào các
ký tự của xâu giống như truy nhập vào các phần tử của
mảng. Ví dụ:
char s[6]=”DHNNI”;
cout<<*(s+1);//Dua ra ky tu thu 2 la H
² Con trỏ trỏ tới hằng xâu ký tự: Khi khai báo và khởi
tạo biến xâu ký tự ta có thể khai báo như một mảng ký
tự hoặc khai báo như một con trỏ trỏ tới kiểu ký tự. Ví
dụ:
char s1[] = ”Khai bao nhu mot mang”; s1[1], *(s1+1)
//char* s1 = ”Khai bao nhu con con tro”; *(s1+1), s1[1]
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 25
2. Con trỏ và xâu ký tự (tiếp)
Sau khai báo trên ta sẽ được hai biến xâu ký tự s1 và s2. Tuy
nhiên hai biến xâu này có một sự khác nhau: s1 là một địa
chỉ, một hằng con trỏ, s2 là một biến con trỏ; s2 có thể thay
đổi còn s1 không thể thay đổi. Ví dụ:
char s1[]="Khai bao nhu mot mang";
char* s2 ="Khai bao nhu mot con tro";
cout<<s1<<'\n';
cout<<s2<<'\n';
//s1++; //Bao loi, s1 la hang con tro
s2++; //Duoc
cout<<s2; //Chi hien: hai bao nhu mot con tro
Chú ý: Khi thay đổi s2 thì ký tự đầu tiên của xâu sẽ thay đổi.
Ở ví dụ trên, sau khi tăng s2 lên 1 thì ký tự đầu tiên của xâu
là h.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 26
2. Con trỏ và xâu ký tự (tiếp)
²Mảng con trỏ trỏ tới các hằng xâu ký tự:
n Giống như mảng các biến kiểu int hoặc float, ta
cũng có mảng con trỏ. Mảng con trỏ hay dùng
nhất là mảng con trỏ trỏ tới các hằng xâu ký tự.
n Ta xét hai cách khai báo sau đây:
//Dùng mảng hai chiều
char days[7][10]={"Sunday","Monday","Tuesday","Wednesday",
"Thursday","Friday","Saturday"};
//Dùng con trỏ
char* days[7]={"Sunday","Monday","Tuesday","Wednesday",
"Thursday","Friday","Saturday"};
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 27
2. Con trỏ và xâu ký tự (tiếp)
²Mảng con trỏ trỏ tới các hằng xâu ký tự: (tiếp)
w Nếu khai báo theo mảng hai chiều thì các mảng con
chứa các xâu ký tự phải có kích thước bằng nhau (10).
Do đó, với những xâu có số ký tự nhỏ hơn 10 sẽ gây
lãng phí bộ nhớ.
w Nếu khai báo theo con trỏ thì trình biên dịch C++ sẽ
để các xâu ký tự liên tiêp nhau trong bộ nhớ và dùng
một mảng con trỏ để trỏ tới các xâu này (Hình trang
sau cho thấy các xâu ký tự trong bộ nhớ). Một xâu ký
tự là một mảng kiểu char, do đó một mảng con trỏ trỏ
tới xâu ký tự thực chất là một mảng con trỏ trỏ tới
char. Đây chính là lý do tại sao ta khai báo là char*
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 28
2. Con trỏ và xâu ký tự (tiếp)
S
u
n
d
a
y
\0
M
o
n
d
a
y
\0
T
u
e
f200
f199
f198
f197
f196
f195
f194
f193
f192
f191
f190
f189
f188
f187
f186
f185
f184
f200
f193
f186
f178
f168
f168
f160
f153
f144
Mảng con trỏ
Các xâu ký tự
Địa chỉ của ký tự
đầu tiên chính là
địa chỉ của xâu.
Các địa chỉ này
được lưu trữ trong
mảng con trỏ.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 29
Bài tập
Viết chương trình nhập vào một họ tên. Tách
tên và đưa tên ra màn hình.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 30
III. Quản lý bộ nhớ với malloc và free
1. Cách sử dụng bộ nhớ của một chương trình C
2. Các loại biến trong chương trình C
3. Hạn chế của mảng
4. Hàm malloc() và free()
5. Mảng động
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 31
1. Cách sử dụng bộ nhớ của một chương trình C
²Một chương trình C khi
chạy sẽ chiếm một vùng
nhớ trong bộ nhớ. Vùng
nhớ này được chia thành 3
phần: phần chứa mã
chương trình, phần chứa
các biến tĩnh và biến ngoài
(gọi là Heap), phần chứa
các biến tự động (gọi là
Stack). Stack mở rộng từ
địa chỉ cao xuống địa chỉ
thấp, Heap mở rộng từ địa
chỉ thấp lên địa chỉ cao.
Địa chỉ cao
Địa chỉ thấp
Stack
Heap
Mã chương
trình
Biến toàn cục
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 32
2. Các loại biến trong chương trình C
a) Sự khác nhau giữa khai báo và định nghĩa
b) Thời gian tồn tại và phạm vi hoạt động của các loại biến
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 33
a) Sự khác nhau giữa khai báo và định nghĩa
²Một khai báo (declaration) chỉ xác định tên và kiểu
dữ liệu. Nhiệm vụ của khai báo là cung cấp thông
tin cho trình biên dịch, nó không yêu cầu trình biên
dịch làm bất cứ việc gì.
² Trái lại, một định nghĩa (definition) yêu cầu trình
biên dịch phải cấp phát bộ nhớ cho biến.
² Trong một số trường hợp khai báo cũng yêu cầu
trình biên dịch cấp phát bộ nhớ, chẳng hạn như khai
báo biến. Tuy nhiên, với định nghĩa thì trong bất kỳ
trường hợp nào cũng yêu cầu cấp phát bộ nhớ.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 34
b) Thời gian tồn tại và phạm vi hoạt động của các loại biến
² Các loại biến có hai đặc tính chính là phạm vi hoạt
động và thời gian tồn tại. Phạm vi hoạt động liên
quan đến phần chương trình nào có thể truy nhập
(sử dụng) biến. Thời gian tồn tại là khoảng thời gian
trong đó biến tồn tại. Phạm vi hoạt động của biến có
thể là trong một lớp, một hàm, một file hay một số
file. Thời gian tồn tại của một biến có thể trùng với
một đối tượng, một hàm hay toàn bộ chương trình.
² Có các loại biến sau: biến tự động, biến thanh ghi,
biến trong khối lệnh, biến ngoài, biến tĩnh.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 35
Các biến tự động (automatic variable)
²Các biến tự động là các biến được khai báo
trong một hàm. Sở dĩ gọi chúng là các biến tự
động bởi vì chúng được tự động tạo khi hàm
được gọi và bị hủy khi hàm kết thúc.
n Biến tự động có phạm vi hoạt động trong một
hàm. Do đó, một biến i được khai báo trong một
hàm hoàn toàn khác với một biến i được khai báo
trong một hàm khác.
n Mặc định các biến tự động không được khởi tạo,
bởi vậy ngay sau khi chúng được hình thành
chúng sẽ có một giá trị vô nghĩa.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 36
Các biến thanh ghi (register variable)
² Biến thanh ghi là một loại biến tự động đặc biệt. Nó
được đặt trong các thanh ghi của CPU chứ không
phải trong bộ nhớ. Việc truy nhập các biến thanh
ghi nhanh hơn các biến thông thường. Biến thanh
ghi có lợi nhất khi được dùng làm biến điều khiển
cho lệnh lặp bên trong nhất trong các lệnh lặp lồng
nhau. Ta chỉ nên dùng một đến hai biến thanh ghi
trong một hàm.
² Để khai báo biến thanh ghi ta dùng từ khóa register
trước khai báo biến thông thường.
Ví dụ: register int a;
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 37
Các biến trong khối lệnh
²Các biến tự động có thể được khai báo ở bất
kỳ đâu trong một hàm hoặc trong một khối
lệnh. Khối lệnh là phần chương trình nằm
giữa hai dấu ngoặc { và }, chẳng hạn như
thân lệnh if hay thân lệnh lặp. Các biến được
khai báo trong một khối lệnh có phạm vi hoạt
động chỉ trong khối lệnh đó.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 38
Các biến ngoài (external variable)
²Các biến ngoài là các biến được khai báo ở
bên ngoài tất cả các hàm. Các biến ngoài có
phạm vi hoạt động từ vị trí khai báo đến cuối
file khai báo chúng. Thời gian tồn tại của các
biến ngoài là thời gian tồn tại của chương
trình, tức là khi chương trình kết thúc thì các
biến ngoài mới bị hủy. Khác với các biến tự
động, các biến ngoài được tự động khởi tạo
bằng 0 nếu ta không khởi tạo.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 39
Các biến ngoài (tiếp)
//Bat dau file
int a; //a la bien ngoai
.....
void afunc();
.......
//Cuoi file
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 40
Các biến ngoài (tiếp)
²Nếu chương trình được chia thành nhiều file
thì các biến ngoài chỉ có thể dùng được trong
file khai báo chúng, không dùng được trong
các file khác. Để sử dụng một biến ngoài đã
được định nghĩa ở một file thì ta phải khai
báo biến đó dùng từ khóa extern.
²Để các biến ngoài chỉ truy nhập được trong
file khai báo chúng, không truy nhập được từ
file khác ta dùng từ khóa static. Từ khóa
static sẽ hạn chế phạm vi hoạt động của biến.
Ví dụ: (trang sau)
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 41
Các biến ngoài (tiếp)
Ví dụ 1: Truy nhập biến ngoài trên nhiều file
//Bat dau file 1
int a; //a la bien ngoai
//Cuoi file 1
//Bat dau file 2
extern int a; //khai bao su dung bien ngoai a o file 1
//Trong file 2 co the truy nhap bien a
//Cuoi file 2
//Bat dau file 3
//Khong khai bao su dung bien ngoai a nen trong file 3
// khong the truy nhap bien a
//Cuoi file 3
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 42
Các biến ngoài (tiếp)
Ví dụ 2: Hạn chế việc truy nhập biến ngoài
//Bat dau file 1
static int a; //dinh nghia bien ngoai a
//bien a chi truy nhap duoc trong file nay
//Cuoi file 1
//Bat dau file 2
extern int a; //Khong dung duoc khai bao nay
//Cuoi file 2
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 43
Các biến ngoài (tiếp)
²Có hai vấn đề khi sử dụng biến ngoài:
n Vì biến ngoài có thể truy nhập được từ bất kỳ
hàm nào trong chương trình nên rất dễ bị thay đổi
làm mất dữ liệu.
n Vì các biến ngoài có phạm vi hoạt động ở mọi
nơi trong chương trình nên ta phải quan tâm đến
vấn đề kiểm soát tên biến để sao cho không có
hai biến nào trùng tên.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 44
Các biến tĩnh cục bộ (local static)
²Các biến tĩnh cục bộ được sử dụng khi ta
muốn duy trì giá trị của một biến khai báo
trong hàm giữa các lời gọi hàm. Tức là khi
hàm kết thúc biến tĩnh vẫn còn và vẫn chứa
giá trị, khi hàm được gọi lần 2 lại có thể sử
dụng giá trị này. Phạm vi hoạt động của biến
tĩnh cục bộ là trong hàm nhưng thời gian tồn
tại của nó là suốt thời gian chương trình chạy.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 45
3. Hạn chế của việc lưu trữ bằng mảng
²Mảng rất hay được sử dụng khi cần lưu trữ một số
lượng lớn các biến hay đối tượng. Tuy nhiên tại thời
điểm viết chương trình ta phải xác định kích thước
của mảng chứ không đợi được đến khi chương trình
thực hiện. Đoạn chương trình sau sẽ sinh ra lỗi:
printf(“Nhap vao kich thuoc mang: ”);scanf(“%i”,&size);
int a[size]; //Lỗi, kích thước mảng phải là hằng
² Trong nhiều trường hợp, tại thời điểm viết chương
trình ta không biết được là cần bao nhiêu bộ nhớ. Nếu
dự trù nhiều mà không dùng hết thì lãng phí bộ nhớ,
nếu dự trù ít mà cần lưu trữ nhiều thì không có chỗ
chứa. Vấn đề này được khắc phục bằng cơ chế cấp
phát động bộ nhớ hàm malloc() và free().
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 46
4. Hàm malloc() và free()
²Trong C có 2 hàm thực hiện chức năng cấp phát
và giải phóng bộ nhớ, đó là hàm malloc() và
free(). Muốn sử dụng hai hàm này trong chương
trình ta phải khai báo sử dụng thư viện stdlib
(#include).
²Cú pháp cấp phát bộ nhớ động như sau:
Biến_con_trỏ = (Kiểu_dl_của_biến*) malloc(sizeof(Kiểu_dl_của_biến));
trong đó Biến con trỏ phải được khai báo trỏ
đến kiểu dữ liệu của biến.
Ví dụ: int* p=NULL;
p = (int*) malloc(sizeof(int));
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 47
4. Hàm malloc() và free()
²Hàm malloc() sẽ cấp phát một ô nhớ trong phần
nhớ Heap trong khi chương trình đang chạy, đủ
để chứa một giá trị có kiểu Kiểu_dl_của_biến
và trả về một con trỏ trỏ tới nó.
²Vì kích thước phần Heap có giới hạn nên có thể
sẽ hết. Nếu phần nhớ Heap đã hết mà ta vẫn cấp
phát thì hàm malloc() sẽ trả về con trỏ rỗng
(NULL). Bởi vậy, luôn luôn phải kiểm tra con
trỏ được trả về bởi hàm malloc() trước khi dùng
nó.
if(!Biến_con_trỏ) printf(“Cap phat bo nho bi loi!”);
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 48
4. Hàm malloc() và free() (tiếp)
²Cú pháp giải phóng bộ nhớ được cấp phát bởi
hàm malloc()
free(p);
Trong đó p là con trỏ trỏ tới vùng nhớ được
cấp phát động bởi hàm malloc().
²Hàm free(p) sẽ giải phóng vùng nhớ được trỏ
tới bởi biến con trỏ p. Chỉ nên dùng hàm
free() để giải phóng vùng nhớ được cấp phát
bởi hàm malloc(), calloc() hoặc realloc().
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 49
4. Hàm malloc() và free() (tiếp)
²Ví dụ về sử dụng hàm malloc() và free():
//Khai bao su dung thu vien chuong trinh
#include
#include
int main()
{
int* p;
p = (int*) malloc(sizeof(int)); //cap phat bo nho chua kieu int
if(p != NULL)
{
printf("Cap phat bo nho bi loi“);
return 1;
}
*p=100; //Gan 100 vao o nho vua duoc cap
printf(”*p = %i”,*p); //Hien thi noi dung cua o nho vua duoc cap
free(p); //Giai phong o nho vua duoc cap
return 0;
}
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 50
5. Cấp phát mảng động với calloc()
² Với cơ chế cấp phát động bộ nhớ ta có thể cấp phát bộ nhớ
cho cả một biến mảng. Điều này cho phép xác định số phần
tử của mảng trong khi chạy chương trình. Để cấp phát động
cho mảng một chiều ta dùng hàm calloc() với cú pháp như
sau:
Biến_con_trỏ = (KPT*) calloc(size, sizeof(KPT));
trong đó KTP là kiểu phần tử, size là số phần tử của mảng.
size có thể là hằng, biến hoặc biểu thức.
int a[100];
int *a = (int*) calloc(100,sizeof(int)); a[0]=25; a[1]=15;
² Để giải phóng vùng nhớ cấp phát cho mảng ta dùng hàm
free() với đối số là Biến con trỏ trỏ tới mảng:
free(Biến_con_trỏ);
Ví dụ
Nhập vào dãy số nguyên có n phần tử. Tìm
phần tử có giá trị lớn nhất.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 51
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 52
5. Mảng động (tiếp)
² Với mảng động ta có thể thay đổi kích thước của
mảng mà vẫn giữ được nội dung của mảng ban đầu.
² Để thay đổi kích thước một mảng động ta dùng hàm
realloc() với cú pháp sau:
p = (KPT*) realloc(p, size_new);
Trong đó KPT là kiểu phần tử, p là con trỏ trỏ tới
mảng động, size_new là kích thước mới của mảng
động.
Nếu thay đổi kích thước không thành công, hàm
realloc() sẽ trả về con trỏ rỗng (NULL).
Ví dụ
Nhập vào dãy số nguyên có n phần tử. Chèn
thêm phần tử x vào cuối dãy.
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 53
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 54
5. Mảng động (tiếp)
² Ví dụ về mảng động:
//Khai bao su dung thu vien chuong trinh
#include
#include
int main()
{
int* a;
int n,i;
printf("Nhap vao so phan tu cua mang: ");scanf("%i",&n);
a = (int*) calloc(n, sizeof(int));//Cap phat bo nho cho mang n phan tu nguyen
if(!a)
{
printf("Cap phat bo nho bi loi");
return 1;
}
//Tiếp trang sau
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 55
5. Mảng động (tiếp)
²Ví dụ về mảng động: (tiếp)
//Nhap cac gia tri vao mang
printf("Nhap vao mang so nguyen:\n" );
for(i=0;i<n;i++)
{
printf("Nhap vao so thu %i: ",i+1); scanf(“%i“,&a[i]);
}
//Mo rong kich thuoc mang them 10 phan tu
a = (int*) realloc(a, n + 10);
//Dua cac so nhap vao ra man hinh
printf("Cac so da nhap la:\n");
for(i=0;i<n;i++) printf("%i ",a[i]);
free(a); //Giai phong vung nho cap phat cho mang
return 0;
}
Lập trình nâng cao - Chương 07 - Ngô Công Thắng 56
Bài tập chương 7
² Bai 1. Cho dãy số nguyên a1, a2, a3,, an. Sắp xếp
dãy số tăng dần. Yêu cầu trong chương trình có sử
dụng mảng động để chứa dãy số.
² Bài 2. Viết chương trình nhập vào một dãy n số
nguyên, lưu dãy số này trong một danh sách liên kết
đơn P. Hãy tạo một danh sách liên kết đơn Q là đảo
ngược của P.
² Bài 3. Viết chương trình nhập vào một dãy n số
nguyên, lưu dãy số này trong một danh sách liên kết
đơn P. Hãy sắp xếp dãy số theo chiều không giảm
sử dụng phương pháp sắp xếp chọn.
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 1
Chương 8. Hàm trong C
I. Khai báo hàm
II. Định nghĩa hàm
III. Sử dụng hàm
IV. Con trỏ trỏ tới hàm
V. Xây dựng thư viện hàm
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 2
I. Khai báo hàm
1. Giới thiệu về hàm
2. Cú pháp khai báo hàm
3. Các tham số trong khai báo hàm
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 3
1. Giới thiệu về hàm
² Trong C tất cả các chương trình con đều gọi là hàm.
² Ngoài các hàm thư viện có sẵn, người lập trình có
thể tự tạo ra các hàm. Để tạo ra một hàm người lập
trình phải khai báo và định nghĩa nó.
² Khai báo hàm (function declaration or prototype) là
xác định tên của hàm, kiểu dữ liệu trả về, số lượng
tham số và kiểu của từng tham số.
² Định nghĩa hàm (function definition) là xác định
công việc mà hàm sẽ thực hiện thông qua các lệnh
của hàm.
² Các hàm trong C không lồng nhau, tức là trong một
hàm ta không thể định nghĩa một hàm khác.
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 4
2. Cú pháp khai báo hàm
²Cú pháp khai báo hàm nằm trên một dòng, kết
thúc bằng dấu chấm phẩy.
Kiểu_trả_về Tên_hàm(Kiểu_1 Tên_tham_số_1, Kiểu_2 Tên_tham_số_2,);
Ví dụ: float inchtomet(float x);
int cong(int a, int b);
²Một khai báo hàm không cho biết những gì có
trong thân hàm. Nó chỉ báo cho trình biên dịch
biết về tên hàm, kiểu của hàm, số lượng các tham
số và kiểu của các tham số.
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 5
2. Cú pháp khai báo hàm (tiếp)
² Khai báo hàm có thể đặt ở bất kỳ đâu trước khi gọi
hàm. Tốt nhất là để ở đầu tệp chứa chương trình chính
(chứa hàm main) hoặc để trước một hàm sẽ gọi nó.
Trong các chương trình nhiều file thì các khai báo hàm
thường để trong các file header có đuôi .h, còn các định
nghĩa hàm để trong các file thư viện có đuôi obj hoặc
lib.
² Nếu hàm được định nghĩa ở đâu đó trước khi gọi hàm
thì có thể không cần khai báo hàm. Tuy nhiên vẫn nên
có khai báo hàm nhất là trong các chương trình có
nhiều hàm lớn hay các chương trình nằm trên nhiều
file.
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 6
3. Các tham số trong khai báo hàm
²Nếu hàm không có tham số thì trong dấu
ngoặc đơn của khai báo hàm để trống. Ví dụ:
int xoa();
²Tên của các tham số trong khai báo hàm có
thể không cần xác định. Ví dụ:
float inchtomet(float, float);
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 7
II. Định nghĩa hàm
1. Cú pháp định nghĩa hàm
2. Lệnh return
3. Hàm không trả về giá trị
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 8
1. Cú pháp định nghĩa hàm
Kiểu_trả_về Tên_hàm(Kiểu_1 Tên_tham_số_1, Kiểu_2 Tên_tham_số_2,)
{
//Các lệnh của hàm để đây
}
Ví dụ:
int cong(int a, int b)
{
int z;
z = a + b;
return z;
}
Không có dấu
chấm phẩyThân hàm
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 9
1. Cú pháp định nghĩa hàm (tiếp)
²Dòng đầu tiên trong định nghĩa hàm giống
trong khai báo hàm, chỉ khác là không có dấu
chấm phẩy và các tham số bắt buộc phải có
tên.
²Khi đã có khai báo hàm thì định nghĩa hàm
thường để sau hàm main hoặc để trong một
tệp obj (lib). Để quen dần với việc viết các
chương trình lớn, khi thực hành chúng ta viết
các khai báo hàm trong tệp .h, còn các định
nghĩa hàm để trong tệp .obj (lib).
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 10
2. Lệnh return
² Lệnh return được sử dụng trong một hàm. Lệnh
return thực hiện hai chức năng:
n Làm cho một hàm trở về chương trình gọi nó.
n Được dùng để trả về một giá trị.
² Cú pháp dùng lệnh return như sau:
return Giá_trị_trả_về;
hoặc return;
² Lệnh return có thể dùng ở bất kỳ vị trí nào trong
hàm nhưng thường ở cuối hàm.
² Với các hàm có trả về giá trị thì lệnh return bắt buộc
phải có.
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 11
3. Hàm không trả về giá trị
²Với các hàm không trả về giá trị thì khi khai
báo và định nghĩa hàm ta phải khai báo kiểu
trả về là void. Ví dụ:
void chao();
²Nếu sử dụng lệnh return trong hàm không trả
về giá trị thì chỉ dùng được dạng:
return;
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 12
III. Sử dụng hàm
1. Lời gọi hàm
2. Truyền đối số theo giá trị
3. Truyền đối số theo tham chiếu
4. Truyền con trỏ tới hàm
5. Truyền mảng tới hàm
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 13
1. Lời gọi hàm
²Một hàm, sau khi được định nghĩa và khai báo, có
thể được thực hiện bằng một lệnh gọi hàm (lời gọi
hàm) ở đâu đó trong chương trình. Có thể gọi từ
hàm main, có thể gọi từ một hàm khác hoặc có thể
gọi từ một hàm thành viên của lớp.
² Cú pháp gọi hàm như sau:
Tên_hàm(Danh sách các đối số, nếu có);
² Nếu hàm được khai báo và định nghĩa là có các
tham số thì khi gọi hàm ta phải truyền giá trị cho
hàm qua các tham số. Các giá trị truyền cho hàm gọi
là các đối số. Các đối số có thể là hằng, biến, mảng,
con trỏ,
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 14
1. Lời gọi hàm (tiếp)
² Ví dụ: giả sử ta khai báo một hàm cộng hai giá trị
float
int cong(int a, int b);
Ta gọi hàm này như sau:
cong(7,8);
² Lời gọi một hàm có trả về giá trị có thể sử dụng
trong các biểu thức, còn lời gọi một hàm không trả
về giá trị không dùng được trong biểu thức. Khi
dùng trong biểu thức thì không có dấu chấm phẩy
sau lời gọi hàm. Ví dụ:
a = cong(7,8) +2; cout<<a;
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 15
1. Lời gọi hàm (tiếp)
²Hoạt động của lời gọi hàm
sdfghjkl
sdfghjkl
func1();
sdfghjkl
sdfghjkl
func1();
sdfghjkl
sdfghjkl
func1();
sdfghjkl
Chương trình
gọi hàm
Lời gọi hàm
void func1()
{
sdfghjkl
sdfghjkl
sdfghjkl
}
Cùng một mã được dùng
cho tất cả các lời gọi hàm
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 16
Ví dụ về hàm
²Viết chương trình tính số các chỉnh hợp chập k từ n
phần tử. Chương trình phải sử dụng hàm để tính giai
thừa.
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 17
2. Truyền đối số theo giá trị (tiếp)
²Trong C, các đối số truyền vào cho hàm là
truyền theo giá trị.
²Khi truyền đối số cho hàm theo giá trị thì
hàm sẽ tạo ra các biến mới (tên các biến này
là tên của các tham số), copy giá trị của các
đối số vào các biến mới và thao tác trên các
biến mới này. Bởi vậy sau khi gọi hàm các
đối số không bị thay đổi giá trị mặc dù bên
trong hàm giá trị của đối số bị thay đổi.
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 18
2. Truyền đối số theo giá trị (tiếp)
#include
void DoiCho(int a, int b);
int main()
{
int x=12,y=15;
clrscr();
printf("Truoc khi doi cho: x= %d, y= %d“,x,y);
DoiCho(x,y);
printf(“Sau khi doi cho: x= %d, y= %d“,x,y);
}
void DoiCho(int a,int b) //Khai bao de truyen doi so theo gia tri
{
int tmp=a;
a=b;
b=tmp;
}
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 19
3. Truyền con trỏ tới hàm
²Để truyền con trỏ tới hàm ta phải thực hiện
hai bước:
n Khai báo các tham số (khi khai báo và định
nghĩa) là con trỏ.
n Khi gọi hàm thì đối số truyền cho hàm là địa chỉ.
Ví dụ:
void DoiCho(int* a, int* b);
int x = 12,y = 15;
DoiCho(&x,&y);
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 20
3. Truyền con trỏ tới hàm (tiếp)
² Khi truyền con trỏ tới hàm thì biến do con trỏ trỏ tới
có thể bị thay đổi bởi hàm.
Ví dụ: Đổi chỗ giá trị của hai biến
void DoiCho(int* a,int* b);
. . . .
DoiCho(&x,&y);
. . . .
void DoiCho(int* a,int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 21
4. Truyền mảng tới hàm
² Khi tên mảng được sử dụng mà không có chỉ số
kèm theo thì nó là địa chỉ bắt đầu của mảng. Do đó,
nếu dùng mảng làm đối số truyền tới một hàm thì
chỉ có địa chỉ của mảng được truyền tới hàm chứ
không phải toàn bộ mảng. Điều này có nghĩa rằng
khi khai báo tham số của hàm thì tham số phải có
kiểu con trỏ.
² Bởi vì địa chỉ của mảng được truyền tới hàm nên
mọi thay đổi của hàm lên mảng sẽ giữ nguyên khi
hàm kết thúc.
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 22
4. Truyền mảng tới hàm (tiếp)
² Ví dụ: Viết một hàm đưa ra các phần tử của mảng
void print(int* m);
. . . . .
int x[7]={2,5,8,1,6,7,10};
. . . . .
print(x);
. . . . .
void print(int* m)
{
for(int i=0;i<7;i++) printf(“%d ”,m[i]);
}
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 23
IV. Con trỏ trỏ tới hàm
²Một đặc điểm rất mạnh của C là con trỏ hàm.
Mặc dù hàm không phải là biến nhưng nó
vẫn có địa chỉ trong bộ nhớ. Địa chỉ này có
thể chứa trong một con trỏ. Vì địa chỉ của
hàm chứa trong con trỏ nên ta có thể sử dụng
con trỏ thay cho tên hàm.
²Để có địa chỉ của hàm ta dùng tên hàm không
có đối số (giống như tên biến mảng là địa chỉ
của mảng).
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 24
IV. Con trỏ trỏ tới hàm (tiếp)
² Để có con trỏ có thể chứa địa chỉ của hàm ta khai
báo con trỏ trỏ tới kiểu giống kiểu trả về của hàm,
theo sau là các tham số của hàm đặt trong ngoặc
đơn. Ví dụ: giả sử hàm Tong có hai tham số kiểu
int, kiểu trả về cũng là int. Khi đó ta khai báo con
trỏ trỏ tới hàm này như sau:
int Tong(int a, int b); //Khai bao hàm
int (*p) (int a, int b); //Khai báo con trỏ hàm
p = Tong; //Gán địa chỉ của hàm Tong cho p
(*p)(10,15); //Gọi hàm Tong qua con trỏ
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 25
IV. Con trỏ trỏ tới hàm (tiếp)
²Ví dụ: Viết chương trình tính tổng, hiệu, tích
và thương của hai số nguyên nhập vào từ bàn
phím. Chương trình yêu cầu người sử dụng
lựa chọn một trong các cách tính.
V-Xây dựng thư viện hàm
1) Các khai báo hàm để trong tệp header (.h)
2) Các định nghĩa hàm để trong tệp .c
3) Biên dịch tệp định nghĩa hàm thành tệp .obj
4) Tạo thư viện hàm có đuôi .lib bằng trình tlib
5) Khai báo thư viện hàm trong tệp chương
trình chính: #include”thuvien.h”
Chú ý: Tệp header và tệp thư viện hàm để
trong cùng thư mục với tệp chương trình
chính.
Lập trình nâng cao - Chương 08 - Ngô Công Thắng 26
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 1
Chương 9. Kiểu dữ liệu tệp
I. Giới thiệu về tệp
II. Tệp nhị phân
III. Tệp văn bản
IV. Truy nhập trực tiếp các phần tử của tệp
V. Tệp không xác định kiểu dữ liệu
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 2
I. Giới thiệu về tệp
1. Khái niệm về tệp
2. Cấu trúc của tệp
3. Phân loại tệp
4. Khai báo tệp
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 3
1. Khái niệm về tệp
l Kiểu tệp bao gồm một tập hữu hạn các
phần tử có cùng kiểu dữ liệu.
l Số phần tử của tệp không cần xác định khi
khai báo biến tệp.
l Các phần tử của tệp được lưu trữ trên bộ
nhớ ngoài. Đây là đặc điểm khác với tất
cả các kiểu dữ liệu khác.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 4
2. Cấu trúc của tệp
l Các phần tử của tệp được sắp xếp thành một
dãy các byte liên tiếp nhau. Sau phần tử dữ liệu
cuối cùng là phần tử EOF. Phần tử này không
phải là dữ liệu mà là mã kết thúc tệp.
EOF45 12 20 25 15 72 81 8
0 2 4 6 8 10 12 14 16Vị trí byte
Con trỏ
chỉ vị
1 3 5 7 9 11 13 15
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 5
3. Phân loại tệp
l Dựa vào cách lưu trữ dữ liệu trên tệp ta có
các loại tệp sau:
§ Tệp nhị phân (binary): Dữ liệu ghi ra tệp nhị
phân có dạng các byte nhị phân giống như
trong bộ nhớ.
§ Tệp văn bản (text): Dữ liệu được ghi ra tệp
thành các ký tự trong bảng mã ASCII. Trên
tệp văn bản có mã xuống dòng gồm 2 ký tự
LF (mã 10) và CR (mã 13).
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 6
4. Khai báo tệp
l Kiểu tệp đã được trình biên dịch định
nghĩa với tên chuẩn là FILE.
l Khai báo tệp ta khai báo biến con trỏ trỏ
tới kiểu FILE.
Ví dụ: FILE *f;
l Con trỏ tệp sẽ trỏ tới vùng nhớ chứa các
thông tin về tệp trên bộ nhớ ngoài.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 7
II. Tệp nhị phân
1. Ghi dữ liệu ra tệp nhị phân
2. Đọc dữ liệu từ tệp nhị phân
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 8
1. Ghi dữ liệu ra tệp nhị phân
l B1: Mở tệp để ghi bằng hàm fopen()
fp = fopen(Tên tệp, Kiểu truy nhập);
trong đó: +) fp là con trỏ tệp được khai
báo trỏ tới kiểu FILE;
+) Tên tệp có thể là hằng xâu hoặc biến
xâu. Trong tên tệp có thể có đường dẫn.
+ Kiểu truy nhập tệp là hằng xâu diễn tả
cách truy nhập vào tệp.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 9
Các kiểu truy nhập tệp nhị phân
Kiểu Ý nghĩa
“wb” Mở tệp mới để ghi theo kiểu nhị phân. Nếu tệp đã có nó sẽ bị
xóa.
“rb” Mở tệp mới để đọc theo kiểu nhị phân. Nếu tệp không có sẽ
sinh ra lỗi.
“ab” Mở tệp theo kiểu nhị phân để ghi bổ sung vào cuối tệp. Nếu
tệp chưa có sẽ tạo tệp mới.
“r+b” Mở tệp mới để đọc/ghi theo kiểu nhị phân. Nếu tệp không có
sẽ sinh ra lỗi.
“w+b” Mở tệp mới để đọc/ghi theo kiểu nhị phân. Nếu tệp đã có nó sẽ
bị xóa.
“a+b” Mở tệp theo kiểu nhị phân để đọc/ghi bổ sung vào cuối tệp.
Nếu tệp chưa có sẽ tạo tệp mới.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 10
1. Ghi dữ liệu ra tệp nhị phân (tiếp)
l B2: Ghi dữ liệu ra tệp bằng hàm fwrite()
fwrite(ptr, size, n, fp);
trong đó: +) ptr là con trỏ trỏ tới vùng nhớ chứa các phần tử
dữ liệu cần ghi.
+) size là kích thước phần tử theo byte.
+) n là số phần tử cần ghi.
+) fp là con trỏ tệp.
Nếu có lỗi không ghi được, hàm trả về 0. Nếu không có lỗi
hàm trả về số phần tử ghi được.
Ví dụ:
FILE *f = fopen(“songuyen.dat”,”wb”);
int a=200;
fwrite(&a,sizeof(a),1,f);
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 11
1. Ghi dữ liệu ra tệp nhị phân (tiếp)
l B3: Đóng tệp
fclose(fp);
trong đó fp là con trỏ tệp.
Ví dụ:
fclose(fp);
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 12
2. Đọc dữ liệu từ tệp nhị phân
l B1: Mở tệp để đọc bằng hàm fopen()
fp = fopen(Tên tệp, Kiểu truy nhập);
trong đó: +) fp là con trỏ tệp được khai
báo trỏ tới kiểu FILE;
+) Tên tệp có thể là hằng xâu hoặc biến
xâu. Trong tên tệp có thể có đường dẫn.
+ Kiểu truy nhập tệp là hằng xâu diễn tả
cách truy nhập vào tệp.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 13
2. Đọc dữ liệu từ tệp nhị phân (tiếp)
l B2: Đọc dữ liệu từ tệp bằng hàm fread()
fread(ptr, size, n, fp);
trong đó: +) ptr là con trỏ trỏ tới vùng nhớ chứa
các phần tử dữ liệu đọc được.
+) size là kích thước phần tử theo byte.
+) n là số phần tử cần đọc.
+) fp là con trỏ tệp.
Hàm fread đọc n phần tử của tệp kể từ vị trí con
trỏ chỉ vị. Hàm trả về số phần tử đọc được.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 14
2. Đọc dữ liệu từ tệp nhị phân (tiếp)
l B2: Đọc dữ liệu từ tệp bằng hàm fread()
fread(ptr, size, n, fp);
Nếu con trỏ chỉ vị đã ở cuối tệp (EOF) mà
vẫn đọc sẽ sinh lỗi.
Trước khi đọc tệp cần kiểm tra con trỏ chỉ
vị đã ở cuối tệp chưa => dùng hàm
feof(con trỏ tệp)
Nên đọc từng phần tử tệp, trước khi đọc
cần kiểm tra vị trí con trỏ chỉ vị.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 15
2. Đọc dữ liệu từ tệp nhị phân (tiếp)
l B3: Đóng tệp
fclose(fp);
trong đó fp là con trỏ tệp.
Ví dụ:
fclose(fp);
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 16
Ví dụ
Viết chương trình tạo tệp “sonuyen.dat”
chứa n số nguyên nhập vào từ bàn phím.
Đọc lại các số nguyên từ tệp và đưa ra
màn hình.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 17
III. Tệp văn bản
1. Ghi dữ liệu ra tệp văn bản
2. Đọc lại dữ liệu từ tệp văn bản
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 18
1. Ghi dữ liệu ra tệp văn bản
B1: Mở tệp để ghi bằng hàm fopen()
fp = fopen(“Tên tệp”,”Kiểu truy nhập”);
trong đó: +) fp là con trỏ tệp được khai
báo trỏ tới kiểu FILE;
+) Tên tệp có thể là hằng xâu hoặc biến
xâu. Trong tên tệp có thể có đường dẫn.
+ Kiểu truy nhập tệp là hằng xâu diễn tả
cách truy nhập vào tệp.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 19
Các kiểu truy nhập tệp văn bản
Kiểu Ý nghĩa
“wt” Mở tệp mới để ghi theo kiểu văn bản. Nếu tệp đã có nó sẽ bị
xóa.
“rt” Mở tệp mới để đọc theo kiểu văn bản. Nếu tệp không có sẽ
sinh ra lỗi.
“at” Mở tệp theo kiểu văn bản để ghi bổ sung vào cuối tệp. Nếu tệp
chưa có sẽ tạo tệp mới.
“r+t” Mở tệp mới để đọc/ghi theo kiểu văn bản. Nếu tệp không có sẽ
sinh ra lỗi.
“w+t” Mở tệp mới để đọc/ghi theo kiểu văn bản. Nếu tệp đã có nó sẽ
bị xóa.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 20
1. Ghi dữ liệu ra tệp văn bản (tiếp)
B2: Ghi dữ liệu ra tệp văn bản
- Ghi dữ liệu theo định dạng ra tệp văn bản giống
như đưa dữ liệu ra màn hình.
fprintf(fp,dk,);
trong đó: +) fp là con trỏ tệp
+) dk là hằng xâu ký tự có chứa đặc tả chuyển
dạng dữ liệu.
+) là các đối mà giá trị của chúng cần ghi tệp.
Ví dụ: fprintf(fp,“x= %d y= %d”,x,y);
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 21
1. Ghi dữ liệu ra tệp văn bản (tiếp)
B2: Ghi dữ liệu ra tệp văn bản
- Ghi cả xâu ký tự ra tệp văn bản.
fputs(s,fp);
trong đó: +) fp là con trỏ tệp
+) s là hằng xâu hoặc biến xâu.
Hàm fputs() không ghi ký tự ‘\0’ ra tệp.
Ví dụ: fputs(“Hung Yen”,fp);
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 22
1. Ghi dữ liệu ra tệp văn bản (tiếp)
B3: Đóng tệp
fclose(fp);
trong đó fp là con trỏ tệp.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 23
2. Đọc lại dữ liệu từ tệp văn bản
B1: Mở tệp để đọc bằng hàm fopen()
fp = fopen(“Tên tệp”,”Kiểu truy nhập”);
trong đó: +) fp là con trỏ tệp được khai
báo trỏ tới kiểu FILE;
+) Tên tệp có thể là hằng xâu hoặc biến
xâu. Trong tên tệp có thể có đường dẫn.
+) Kiểu truy nhập tệp là hằng xâu diễn tả
cách truy nhập vào tệp.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 24
2. Đọc lại dữ liệu từ tệp văn bản (tiếp)
B2: Đọc lại dữ liệu từ tệp
- Đọc dữ liệu có định dạng
fscanf(fp, dk, )
trong đó: +) fp là con trỏ tệp
+) dk là hằng xâu ký tự có chứa đặc tả
chuyển dạng dữ liệu.
+) là các địa chỉ vùng nhớ chứa dữ liệu
đọc được.
Ví dụ: fscanf(fp,“%d”,&b);
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 25
2. Đọc lại dữ liệu từ tệp văn bản (tiếp)
B2: Đọc lại dữ liệu từ tệp
- Đọc một xâu ký tự từ tệp
fgets(s, n, fp);
trong đó: +) s là biến xâu ký tự.
+) n là số ký tự tối đa sẽ đọc.
+) fp là con trỏ tệp
Ví dụ: fgets(s,20,fp);
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 26
2. Đọc lại dữ liệu từ tệp văn bản (tiếp)
B3: Đóng tệp
fclose(fp);
trong đó fp là con trỏ tệp.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 27
IV. Truy nhập trực tiếp các phần tử
của tệp
1. Các hàm di chuyển con trỏ chỉ vị
2. Truy nhập một phần tử bất kỳ của tệp
3. Một số hàm thao tác trên tệp
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 28
1. Các hàm di chuyển con trỏ chỉ vị
l Hàm rewind(fp) chuyển con trỏ về phần tử đầu tiên
của tệp, fp là con trỏ tệp.
l Hàm fseek(fp, sb, xp)
trong đó: +) fp là con trỏ tệp
+) sb là số byte cần di chuyển
+) xp là vị trí xuất phát. xp chỉ có thể nhận một
trong 3 giá trị sau:
SEEK_SET hoặc 0: xuất phát từ đầu tệp
SEEK_CUR hoặc 1: xuất phát từ vị trí hiện tại của
con trỏ chỉ vị
SEEK_END hoặc 2: Xuất phát từ cuối tệp.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 29
1. Các hàm di chuyển con trỏ chỉ vị (tiếp)
Hàm fseek() di chuyển con trỏ chỉ vị của tệp
fp từ vị trí xác định bởi xp qua số byte sb.
Chiều di chuyển về cuối tệp nếu sb dương,
về đầu tệp nếu sb âm.
Hàm này trả về 0 nếu di chuyển thành công,
trả về giá trị khác không nếu di chuyển không
thành công.
Chú ý: Không nên dùng fseek cho tệp văn bản.
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 30
1. Các hàm di chuyển con trỏ chỉ vị (tiếp)
l Hàm ftell(fp) cho biết vị trí hiện tại của con
trỏ chỉ vị.
Ứng dụng: 1) Xác định kích thước tệp
2) Xác định số phần tử tệp
EOF20 15 8 10
0 1 2 3 4 5 6 7 8
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 31
2. Truy nhập một phần tử bất kỳ
của tệp
B1: Mở tệp với kiểu truy nhập là “r+b”
B2: Di chuyển con trỏ chỉ vị tới phần tử cần
đọc/ghi
B3: Đọc/ghi phần tử
B4: Đóng tệp
Lập trình nâng cao - Chương 09 - Ngô Công Thắng 32
3. Một số hàm thao tác trên tệp
l Hàm fcloseall() đóng tất cả các tệp đang mở.
l Hàm fflush(con trỏ tệp) làm sạch vùng đệm tệp.
l Hàm fflushall() làm sạch vùng đệm của tất cả
các tệp đang mở.
l Hàm ferror(con trỏ tệp) kiểm tra lỗi thao tác tệp,
nếu không có lỗi trả về 0, có lỗi trả về giá trị
khác 0.
l Hàm remove(Tên tệp) xóa tệp
Các file đính kèm theo tài liệu này:
- giao_trinh_lap_trinh_nang_cao.pdf