BÀI 12: CON TRỎ SỐ HỌC�VÀ HÀM
Giáo viên: PHAN THIỆN HUỆ
Gmail: thayhuetnh@gmail.com
SĐT, Zalo: 0382.1682.32
Blog: thayhuetnh.blogspot.com
1
Chương III: NGÔN NGỮ LẬP TRÌNH C++
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
2
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.1 Địa chỉ, phép toán &
- Mọi chương trình trước khi chạy đều phải bố trí các biến do người sử dụng khai báo vào đâu đó trong bộ nhớ.
- Mỗi biến (với tên biến) được gắn với một số nguyên là địa chỉ của byte đầu tiên mà biến đó được phân phối.
- Cần biết tên biến hoặc địa chỉ của biến ta có thể đọc/viết dữ liệu vào/ra các biến đó.
- Biến, ô nhớ và địa chỉ có quan hệ chặt chẽ với nhau.
- C++ cung cấp một toán tử một ngôi & để lấy địa chỉ của các biến (ngoại trừ biến mảng và xâu kí tự). Nếu x là một biến thì &x là địa chỉ của x.
VD: VDDiaChiConTro
3
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.2. Con trỏ
a. Ý nghĩa
− Con trỏ là một biến chứa địa chỉ của biến khác.
− Nếu p là con trỏ chứa địa chỉ của biến x ta gọi p trỏ tới x và x được trỏ bởi p.
− Thông qua con trỏ ta có thể làm việc được với nội dung của những ô nhớ mà p trỏ đến.
− Để con trỏ p trỏ tới x ta phải gán địa chỉ của x cho p.
− Để làm việc với địa chỉ của các biến cần phải thông qua các biến con trỏ trỏ đến biến đó.
4
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.2. Con trỏ
b. Khai báo biến con trỏ
Cú pháp:
<kiểu dữ liệu> *<tên biến>;
Ví dụ:
int *p ; // khai báo biến p là biến con trỏ trỏ đến kiểu dữ liệu nguyên.
float *q, *r ; // hai con trỏ thực q và r.
5
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.2. Con trỏ
c. Sử dụng con trỏ, phép toán *
- Để con trỏ p trỏ đến biến x ta phải dùng phép gán p = địa chỉ của x.
+ Nếu x không phải là mảng ta viết: p = &x.
+ Nếu x là mảng ta viết: p = x hoặc p = &x[0].
- Không gán p cho một hằng địa chỉ cụ thể.
Ví dụ: viết p = 200 là sai.
6
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.2. Con trỏ
c. Sử dụng con trỏ, phép toán *
- Phép toán * cho phép lấy nội dung nơi p trỏ đến.
Ví dụ:
để gán nội dung nơi p trỏ đến cho biến f ta viết f = *p.
- Hai phép toán & và * là ngược nhau.
+ Cụ thể nếu p = &x thì x = *p.
+ Từ đó nếu p trỏ đến x thì bất kỳ nơi nào xuất hiện x đều có thể thay được bởi *p và ngược lại.
Ví dụ:
7
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.2. Con trỏ
c. Sử dụng con trỏ, phép toán *
Ví dụ 2.1:
int i, j ; // khai báo 2 biến nguyên i, j
int *p, *q ; // khai báo 2 con trỏ nguyên p, q
p = &i; // cho p trỏ tới i
q = &j; // cho q trỏ tới j
cout << &i ; // hỏi địa chỉ biến i
cout << q ; // hỏi địa chỉ biến j (thông qua q)
8
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.3. Các phép toán với con trỏ
Trên đây ta đã trình bày về 2 phép toán một ngôi liên quan đến địa chỉ và con trỏ là & và *.
a. Phép toán gán
− Gán con trỏ với địa chỉ một biến: p = &x ;
− Gán con trỏ với con trỏ khác: p = q ; (sau phép toán gán này p, q chứa cùng một địa chỉ, cùng trỏ đến một nơi).
Ví dụ 2.1:
9
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.3. Các phép toán với con trỏ
c. Phép toán tự tăng giảm
p++, p--, ++p, --p: tương tự p+1 và p-1, có chú ý đến tăng (giảm) trước, sau.
VD: int a[2] = {3, 7}, *p = a;
(*p)++ ; // tăng (sau) giá trị nơi p trỏ
++(*p) ; // tăng (trước) giá trị nơi p trỏ ≡ tăng a[0] thành 4
*(p++) ; // lấy giá trị nơi p trỏ (3) và tăng trỏ p (tăng sau), p → a[1]
*(++p) ; // tăng trỏ p (tăng trước), p → a[1] và lấy giá trị nơi p trỏ (7)
10
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.3. Các phép toán với con trỏ
Chú ý:
• Phân biệt p+1 và p++ (hoặc ++p):
• p+1 được xem như một con trỏ khác với p.
• *p++ là con trỏ p nhưng trỏ đến phần tử khác. *p++ trỏ đến phần tử đứng sau phần tử p trỏ đến ban đầu.
• Phân biệt *(p++) và *(++p):
Các phép toán tự tăng giảm cũng là một ngôi. Cụ thể:
*p++ ≡ *(p++) ; *++p ≡ *(++p); ++*p ≡ ++(*p)
11
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.3. Các phép toán với con trỏ
d. Hiệu của 2 con trỏ
Phép toán này chỉ thực hiện được khi p và q là 2 con trỏ cùng trỏ đến các phần tử của một dãy dữ liệu nào đó trong bộ nhớ (ví dụ cùng trỏ đến 1 mảng dữ liệu). Khi đó hiệu p
- q là số thành phần giữa p và q (chú ý p - q không phải là hiệu của 2 địa chỉ mà là số thành phần giữa p và q).
Ví dụ: giả sử p và q là 2 con trỏ nguyên, p có địa chỉ 200 và q có địa chỉ 208. Khi đó p - q = −4 và q - p = 4 (4 là số thành phần nguyên từ địa chỉ 200 đến 208).
12
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.3. Các phép toán với con trỏ
e. Phép toán so sánh
Các phép toán so sánh cũng được áp dụng đối số với con trỏ, thực chất là so sánh giữa địa chỉ của hai nơi được trỏ bởi các con trỏ này. Thông thường các phép so sánh <,<=, >, >=,== chỉ áp dụng cho hai con trỏ trỏ đến phần tử của cùng một mảng dữ liệu nào đó. Thực chất của phép so sánh này chính là so sánh chỉ số của 2 phần tử được trỏ bởi 2 con trỏ đó.
Ví dụ 5:
13
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.4. Cấp phát động, toán tử cấp phát, thu hồi new, delete
- Nếu ta khai báo một mảng nguyên chứa 1000 số thì trong bộ nhớ sẽ có một vùng nhớ liên tục 2000 bytes để chứa dữ liệu của mảng này. Khi đó dù trong chương trình ta chỉ nhập vào mảng và làm việc với một vài số thì phần mảng rỗi còn lại vẫn không được sử dụng vào việc khác. Đây là hạn chế thứ nhất của kiểu mảng.
14
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.4. Cấp phát động, toán tử cấp phát, thu hồi new, delete
- Một hướng khác, một lần nào đó chạy chương trình ta lại cần làm việc với hơn 1000 số nguyên. Khi đó vùng nhớ mà chương trình dịch đã dành cho mảng là không đủ để sử dụng. Đây chính là hạn chế thứ hai của mảng được khai báo trước.
- Hai công việc cấp phát và thu hồi này được thực hiện thông qua các toán tử new, delete và con trỏ p.
- Thông qua p ta có thể làm việc với bất kỳ địa chỉ nào của vùng được cấp phát.
15
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.4. Cấp phát động, toán tử cấp phát, thu hồi new, delete
Sau đây là cú pháp của câu lệnh new.
p = new <kiểu> ; // cấp phát 1 phần tử
p = new <kiểu>[n] ; // cấp phát n phần tử
Ví dụ:
int *p ;
p = new int ; // cấp phát vùng nhớ chứa được 1 số nguyên
p = float int[100] ; // cấp phát vùng nhớ chứa được 100 số thực
16
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.4. Cấp phát động, toán tử cấp phát, thu hồi new, delete
- Khi gặp toán tử new, chương trình sẽ tìm trong bộ nhớ một lượng ô nhớ còn rỗi và liên tục với số lượng đủ theo yêu cầu và cho p trỏ đến
- Nếu không có vùng nhớ với số lượng như vậy thì việc cấp phát là thất bại và p = NULL (NULL là một địa chỉ rỗng, không xác định)
- Do vậy ta có thể kiểm tra việc cấp phát có thành công hay không thông qua kiểm tra con trỏ p bằng hay khác NULL.
Ví dụ 6:
17
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.4. Cấp phát động, toán tử cấp phát, thu hồi new, delete
- Để giải phóng bộ nhớ đã cấp phát cho một biến (khi không cần sử dụng nữa) ta sử dụng câu lệnh delete.
delete p ; // p là con trỏ được sử dụng trong new
và để giải phóng toàn bộ mảng được cấp pháp thông qua con trỏ p ta dùng câu lệnh:
delete[] p ; // p là con trỏ trỏ đến mảng
Ví dụ 6.1:
18
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.2. Con trỏ
Ví dụ 6.1: Nhập dãy số (không dùng mảng). Sắp xếp và in ra màn hình. Trong ví dụ này chương trình xin cấp phát bộ nhớ đủ chứa n số nguyên và được trỏ bởi con trỏ head. Khi đó địa chỉ của số nguyên đầu tiên và cuối cùng sẽ là head và head+n-1. p và q là 2 con trỏ chạy trên dãy số này, so sánh và đổi nội dung của các số này với nhau để sắp thành dãy tăng dần và cuối cùng in kết quả.
Dữ liệu vào N (1<=N<=104) và dãy a1,a2,..an (ai<109)
19
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.5. Con trỏ và mảng, xâu kí tự
a. Con trỏ và mảng 1 chiều
Việc cho con trỏ trỏ đến mảng cũng tương tự trỏ đến các biến khác, tức gán địa chỉ của mảng (chính là tên mảng) cho con trỏ. Chú ý rằng địa chỉ của mảng cũng là địa chỉ của thành phần thứ 0 nên a+i sẽ là địa chỉ thành phần thứ i của mảng. Tương tự, nếu p trỏ đến mảng a thì p+i là địa chỉ thành phần thứ i của mảng a và do đó *(p+i) = a[i] = *(a+i).
20
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.5. Con trỏ và mảng, xâu kí tự
Ví dụ 7: In toàn bộ mảng thông qua con trỏ.
int a[5] = {1,2,3,4,5}, *p, i;
1: p = a; for (i=0; i<5; i++) cout << *(p+i);
// p không thay đổi
Trong phương án 1, con trỏ p không thay đổi trong suốt quá trình làm việc của lệnh for, để truy nhập đến phần tử thứ i của mảng a ta sử dụng cú pháp *(p+i).
21
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.5. Con trỏ và mảng, xâu kí tự
Ví dụ 7: In toàn bộ mảng thông qua con trỏ.
int a[5] = {1,2,3,4,5}, *p, i;
2. for (p=a; p<=a+4; p++) cout << *p ; // thay đổi p
Đối số với phương án 2 con trỏ sẽ dịch chuyển dọc theo mảng a bắt đầu từ địa chỉ a (phần tử đầu tiên) đến phần tử cuối cùng. Tại bước thứ i, p sẽ trỏ vào phần tử a[i], do đó ta chỉ cần in giá trị *p. Để kiểm tra khi nào p đạt đến phần tử cuối cùng, ta có thể so sánh p với địa chỉ cuối mảng chính là địa chỉ đầu mảng cộng thêm số phần tử trong a và trừ 1 (tức a+4 trong ví dụ trên).
22
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.5. Con trỏ và mảng, xâu kí tự
b. Con trỏ và xâu kí tự
Một con trỏ kí tự có thể xem như một biến xâu kí tự, trong đó xâu chính là tất cả các kí tự kể từ byte con trỏ trỏ đến cho đến byte '\0' gặp đầu tiên. Vì vậy ta có thể khai báo các xâu dưới dạng con trỏ kí tự như sau.
char *s ;
char *s = "Hello" ;
23
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.5. Con trỏ và mảng, xâu kí tự
b. Con trỏ và xâu kí tự
- Các hàm trên xâu vẫn được sử dụng như khi ta khai báo nó dưới dạng mảng kí tự. Ngoài ra khác với mảng kí tự, ta được phép sử dụng phép gán cho 2 xâu dưới dạng con trỏ, ví dụ: char *s, *t = "Tin học" ;
s = t; // thay cho hàm strcpy(s, t) ;
- Thực chất phép gán trên chỉ là gán 2 con trỏ với nhau, nó cho phép s bây giờ cũng được trỏ đến nơi mà t trỏ (tức dãy kí tự "Tin học" đã bố trí sẵn trong bộ nhớ)
24
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.5. Con trỏ và mảng, xâu kí tự
c. Con trỏ và mảng hai chiều
float *p; int a[2][3];
Cách sai:
p = a ; // sai vì khác kiểu
Các cách đúng:
p = (float*)a; // ép kiểu của a về con trỏ float (cũng là kiểu của p)
p = a[0]; // gán với địa chỉ của mảng a[0]
p = &a[0][0]; //]; // gán với địa chỉ số thực đầu tiên trong a
25
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
BÀI 12: CON TRỎ SỐ HỌC VÀ HÀM
12.5. Con trỏ và mảng, xâu kí tự
c. Con trỏ và mảng hai chiều
- Trong đó cách dùng p = (float*)a; là trực quan và đúng trong mọi trường hợp nên được dùng thông dụng hơn cả.
- Sau khi gán a cho p (p là con trỏ thực), việc tăng giảm p chính là dịch chuyển con trỏ trên từng phần tử (thực) của a. p trỏ tới a[0][0]; p+1 trỏ tới a[0][1]
p+2 trỏ tới a[0][2]; p+3 trỏ tới a[1][0]
Tổng quátvới mảng m x n phần tử:
p + i*n + j trỏ tới a[i][j]
hoặc đối số a[i][j] = *(p + i*n + j)
Ví dụ 9:
26
Trường THPT chuyên Thoại Ngọc Hầu Tin học 10 Chuyên
Tổ Tin học Chương III
27
Cám ơn quý thầy cô và
các bạn đã lắng nghe