Nhập môn lập trình - Bài 14: Con trỏ
1. Cho biết ý nghĩa của các khai báo và câu lệnh; Tìm lỗi
sai trong đoạn code và giải thích (t.t) xem các bài
tập từ 1 đến 12 trong phần trước.
2. Viết chương trình nhập một dãy số hữu tỉ tùy ý (sử
dụng con trỏ và sự cấp phát động), xuất ra dãy gồm
tất cả các số nhỏ hơn 1 có trong dãy được nhập vào,
tính tổng và tích của dãy số hữu tỉ.
3. Viết chương trình khai báo mảng hai chiều có 12x12
phần tử kiểu char. Gán ký tự „X‟ cho mọi phần tử của
mảng này. Sử dụng con trỏ đến mảng để in giá trị các
phần tử mảng lên màn hình ở dạng lưới.
93 trang |
Chia sẻ: dntpro1256 | Lượt xem: 1586 | Lượt tải: 0
Bạn đang xem trước 20 trang tài liệu Nhập môn lập trình - Bài 14: Con trỏ, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
NHẬP MÔN LẬP TRÌNH
BÀI 14: CON TRỎ
CĐR buổi học
• Sau khi học xong buổi học, sinh viên có khả năng:
• Hiểu được khái niệm con trỏ, địa chỉ của biến và quản lý các biến trong C/C++.
• Sử dụng con trỏ trong lập trình
• Biết được một số thuật ngữ và tiếng Anh tương ứng
2
Bảng các thuật ngữ Việt- Anh liên quan nội dung con trỏ
Thuật ngữ tiếng Việt Thuật ngữ tiếng Anh
Con trỏ Pointer
Hằng con trỏ Constant pointer
Địa chỉ bộ nhớ Memory Address
Toán tử & Address-of Operator
Toán tử *
Dereferencing Operator, or: Indirection
Operator
Cấp phát bộ nhớ Memory Allocation
Giải phóng bộ nhớ De-Allocate Memory
Cấp phát tĩnh Static Memory Allocation
Cấp phát động Dynamic Memory Allocation
Biến động Dynamic Variable
Phép toán số học trên con trỏ Pointer Arithmetic
NMLT - CON TRỎ CƠ BẢN 3
Nội dung
1. Khái niệm và cách sử dụng con trỏ
2. Con trỏ và mảng 1 chiều
3. Bài tập
NMLT - Con trỏ và cấp phát động 4
1. Khái niệm và cách sử dụng
1.1 Biến và vùng nhớ
1.2 Khái niệm con trỏ
1.3 Khai báo con trỏ
1.4 Con trỏ và toán tử &, *
1.5 Con trỏ NULL
1.6 Kích cỡ con trỏ
1.7 Từ khóa const và con trỏ
1.8 Con trỏ và hàm
Bài tập
Một số lưu ý
NMLT - CON TRỎ CƠ BẢN 5
1.1 Biến và vùng nhớ
• Bộ nhớ máy tính
• Bộ nhớ RAM chứa rất nhiều ô nhớ,
mỗi ô nhớ có kích thước 1 byte.
• Mỗi ô nhớ có địa chỉ duy nhất và địa
chỉ này được đánh số từ 0 trở đi.
• RAM để lưu trữ mã chương trình và
dữ liệu trong suốt quá trình thực thi.
NMLT - CON TRỎ CƠ BẢN 6
Memory Layout (bytes)
.
.
.
0
1
2
3
4
5
6
7
.
.
.
Địa chỉ
ô nhớ
1 byte
1.1 Biến và vùng nhớ
• Khi khai báo biến, máy tính sẽ dành riêng một vùng
nhớ để lưu biến đó.
• Khi tên biến được gọi, máy tính sẽ thực hiện 2 bước sau:
• Tìm kiếm địa chỉ ô nhớ của biến.
• Truy xuất hoặc thiết lập giá trị của biến được lưu trữ tại ô
nhớ đó.
NMLT - CON TRỎ CƠ BẢN 7
1.1 Biến và vùng nhớ
NMLT - CON TRỎ CƠ BẢN 8
int main() {
char ch=‘x’;
int a = 7;
}
Memory Layout (bytes)
.
.
.
0
1
2
3
4
5
6
7
.
.
.
Địa chỉ
ô nhớ
ch
a
x
7
Toán tử & và *
• Toán tử & (Address-of Operator) đặt trước tên biến và
cho biết địa chỉ của vùng nhớ của biến.
• Toán tử * (Dereferencing Operator hay Indirection
Operator) đặt trước một địa chỉ và cho biết giá trị lưu
trữ tại địa chỉ đó.
• Ví dụ:
NMLT - CON TRỎ CƠ BẢN 9
Toán tử & và *
cout << " value = " << value;
=> value = 3200;
cout << " &value = " << &value;
=> &value = 0x50;
cout << " *(&value) = " << *(&value);
=> *(&value) = 3200;
NMLT - CON TRỎ CƠ BẢN 10
int value;
value = 3200; 3200
value
0x50
Memory Layout
1.2 Khái niệm con trỏ
• Khái niệm:
Con trỏ (Pointer) là một biến lưu trữ địa chỉ của một địa
chỉ bộ nhớ. Địa chỉ này thường là địa chỉ của một biến khác.
VD: Biến x chứa địa chỉ của biến y. Vậy ta nói biến x “trỏ
tới” y.
• Phân loại con trỏ:
Con trỏ kiểu int dùng để chứa địa chỉ của các biến kiểu int.
Tương tự ta có con trỏ kiểu float, double,
NMLT - CON TRỎ CƠ BẢN 11
1.3 Khai báo con trỏ
• Khai báo
• Giống như mọi biến khác, biến con trỏ muốn sử dụng cũng cần
phải được khai báo.
• Ví dụ
NMLT - CON TRỎ CƠ BẢN 12
*;
char char1;
int *ptrI;
float *ptrF;
char1
0x50
ptrF
0x10
ptrI
0x80
Memory Layout
1.4 Con trỏ và toán tử &, *
• Toán tử & dùng trong khởi tạo giá trị cho con trỏ
• Ví dụ:
NMLT - CON TRỎ CƠ BẢN 13
* = & ;
int a;
int *ptr = &a;
double a;
int *ptr = &a;?
0x34ptr
0x90
Memory Layout
a
0x34
1.4 Con trỏ và toán tử &, *
• Toán tử * đặt trước biến con trỏ cho phép truy xuất
đến giá trị ô nhớ mà con trỏ trỏ đến.
• Ví dụ
NMLT - CON TRỎ CƠ BẢN 14
int a = 1000;
int *ptr = &a;
cout << ptr << ‚ ‛ << *ptr;
// a = 3200
*ptr = 3200;
cout << *ptr;
(*ptr) ++;
1000
0x34
a
ptr
0x34
0x90
Memory Layout
32 1
Ví dụ
#include
using namespace std;
int main() {
int a;
int *ptr;
int value;
a = 3200;
ptr = &a;
value = --(*ptr);
}
NMLT - CON TRỎ CƠ BẢN 15
Memory Layout
Ví dụ
#include
using namespace std;
int main() {
int a;
int *ptr;
int value;
a = 3200;
ptr = &a;
value = --(*ptr);
}
NMLT - CON TRỎ CƠ BẢN 16
3200
3199
0x34
a
ptr
value
0x34
0x90
0x50
Memory Layout
199
Ví dụ
NMLT - CON TRỎ CƠ BẢN 17
3199
3199
0x34
a
ptr
value
0x34
0x90
0x50
3199
0x34
3199
0x50
0x90
0x34
3199
0x34
error
3199 Memory Layout
value =
ptr =
a =
&value =
&ptr =
&a =
*ptr =
&(*ptr) =
*(*ptr) =
*(&(*ptr)) =
Phép gán con trỏ
• Có thể gán biến con trỏ:
int *p1, *p2;
p2 = p1;
Gán một con trỏ cho con trỏ khác
“Chỉ định p2 trỏ tới nơi mà p1 đang trỏ tới“
Dễ bị lẫn với: *p2 = *p1;
Gán “giá trị trỏ bởi p1” cho “giá trị trỏ bởi p2”
NMLT - CON TRỎ CƠ BẢN 18
Ví dụ
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 19
p1
p2 5
27 p1
p2 5
27
p1 = p2
p1
p2 5
27 p1
p2 5
5
*p1 = *p2
1.5 Con trỏ NULL
• Khái niệm
• Con trỏ NULL là con trỏ không trỏ vào đâu cả.
• Khác với con trỏ chưa được khởi tạo.
int n;
int *p1 = &n;
int *p2; // unreferenced local variable
int *p3 = NULL;
NMLT - CON TRỎ CƠ BẢN 20
NULL
1.6 Toán tử sizeof
• Để xác định kích thước (bytes) của một kiểu dữ liệu ta dùng toán
tử sizeof. Cú pháp: sizeof (type) hoặc sizeof value
Trong đó type là kiểu dữ liệu, value là tên biến
• Kích thước của kiểu dữ liệu không giống nhau cho tất cả máy
tính. Nên dùng toán tử sizeof để biết chính xác kích thước của dữ
liệu.
• Con trỏ chỉ lưu địa chỉ nên kích thước của mọi con trỏ là như nhau.
(Kết quả sau mang tính chất tham khảo)
NMLT - CON TRỎ CƠ BẢN 21
int a;
double b;
char c;
int *pa;
double *pb;
char *pc;
sizeof a = 4
sizeof(b) = 8
sizeof(c) = 1
sizeof pa = 4
sizeof pb = 4
sizeof(pc)= 4
sizeof(int) = 4
sizeof(double) = 8
sizeof(char) = 1
sizeof(int*) = 4
sizeof(double*)= 4
sizeof(char*) = 4
1.7 Từ khóa const và con trỏ
• Hằng số dùng trong khai báo một biến cho biết giá trị
của biến không được phép thay đổi trong quá trình thực
hiện chương trình.
• Tùy thuộc vào vị trí đặt từ khóa const dùng trong khái
báo biến con trỏ, mà quy định giá trị hằng cho con trỏ
hay cho vùng nhớ con trỏ trỏ tới.
• Có 3 trường hợp trong khai báo biến con trỏ và từ khóa
const.
NMLT - CON TRỎ CƠ BẢN 22
1.7 Con trỏ và hàm
• Xét ví dụ sau:
Hãy viết hàm để nhập giá trị cho 1 biến.
Cách viết nội dung hoàn toàn ở hàm main như sau:
int main() {
int a;
cout << "Nhap gia tri vao";
cin >> a;
cout << a;
}
NMLT - CON TRỎ CƠ BẢN 23
a
0x50
1.7 Con trỏ và hàm
// Cách 1:
int NhapGiaTri(){
int b;
cout << "Nhap gia tri vao";
cin >> b;
return b;
}
int main() {
int a;
a = NhapGiaTri();
cout << a;
}
NMLT - CON TRỎ CƠ BẢN 24
a
0x50
5
b
0x100
int NhapGiaTri()
1.7 Con trỏ và hàm
? Hỏi cách này có đúng không
void NhapGiaTri(int b) {
cout << "Nhap gia tri vao";
cin >> b;
}
int main() {
int a;
NhapGiaTri(a);
cout << a;
}
NMLT - CON TRỎ CƠ BẢN 25
1.7 Con trỏ và hàm
// Cách 2
void NhapGiaTri(int *b) {
cout << "Nhap gia tri vao";
cin >> *b;
}
int main() {
int a;
NhapGiaTri(&a);
cout << a;
}
NMLT - CON TRỎ CƠ BẢN 26
1.7 Con trỏ và hàm
// Cách 3
void NhapGiaTri(int &b) {
cout << "Nhap gia tri vao";
cin >> b;
}
int main() {
int a;
NhapGiaTri(a);
cout << a;
}
NMLT - CON TRỎ CƠ BẢN 27
Bài tập 1
• Tìm lỗi sai trong đoạn chương trình sau:
int main() {
int x, *p;
x = 10;
*p = x;
return 0;
}
NMLT - CON TRỎ CƠ BẢN 28
Bài tập 2
#include
using namespace std;
int main() {
int i = 12;
int *p1;
// i = 24;
p1 = &i;
*p1 = 24;
cout << *p1 << " "
<< i << endl;
}
NMLT - CON TRỎ CƠ BẢN 29
Dùng C++ viết một đoạn
chương trình với 2 biến:
+ Biến i có kiểu int với
giá trị khởi đầu là 12
+ Biến p1 là một con trỏ
trỏ tới vùng nhớ kiểu int.
? Hãy dùng biến p1 để thay
đổi giá trị của biến i từ 12
sang 24.
Bài tập 3
• Hãy viết hàm hoán đổi giá trị của 2 tham số
• Giải:
NMLT - CON TRỎ CƠ BẢN 30
// Cách 1
void Swap(int *a, int *b) {
int temp = *b;
*b = *a;
*a = temp;
}
int main() {
int x=7, y=8;
Swap(&x, &y);
cout << "x= "<< x << ",
y= " << y;
}
//Cách 2
void Swap(int &a, int &b) {
int temp = b;
b = a;
a = temp;
}
int main() {
int x=7, y=8;
Swap(x, y);
cout << "x= "<< x << ",
y= " << y;
}
Một số lưu ý
• Con trỏ là khái niệm quan trọng và khó nhất trong C++.
Mức độ thành thạo C++ được đánh giá qua mức độ sử
dụng con trỏ.
• Nắm rõ quy tắc sau, ví dụ int a, *pa = &a;
• *pa và a đều chỉ nội dung của biến a.
• pa và &a đều chỉ địa chỉ của biến a.
• Không nên sử dụng con trỏ khi chưa được khởi tạo. Kết
quả sẽ không lường trước được.
int *pa; *pa = 1904; => sai
NMLT - CON TRỎ CƠ BẢN 31
2. Con trỏ và Mảng 1 chiều
2.1 Mảng 1 chiều và cách lấy địa chỉ
2.2 Mảng 1 chiều và hằng con trỏ
2.3 Các phép toán số học trên con trỏ
2.4 Con trỏ và mảng 1 chiều
Bài tập
Một số lưu ý
NMLT - CON TRỎ CƠ BẢN 32
2.1 Mảng 1 chiều và cách lấy địa chỉ
• Cho mảng 1 chiều: int arr[6] = {5, 6, 9, 4, 1,
2};
NMLT - CON TRỎ CƠ BẢN 33
Memory Layout
2
1
4
9
6
5
0x30
0x26
0x22
0x18
0x14
0x10
arr[5]
arr[4]
arr[3]
arr[2]
arr[1]
arr[0]
&arr[5]
&arr[4]
&arr[3]
&arr[2]
&arr[1]
&arr[0]
Lấy giá trịLấy địa chỉ
2.2 Mảng 1 chiều và hằng con trỏ
• Cho mảng 1 chiều
• Tên mảng arr là một hằng
con trỏ
không thể thay đổi giá
trị của hằng này.
• arr là địa chỉ đầu tiên của
mảng
arr == &arr[0]
NMLT - CON TRỎ CƠ BẢN 34
int arr[6] = {5,
6, 9, 4, 1, 2};
2
1
4
9
6
5
0x30
0x26
0x22
0x18
0x14
0x10 arr
== &arr[0]
= 0x10
Memory Layout
2.2 Mảng 1 chiều và hằng con trỏ
NMLT - CON TRỎ CƠ BẢN 35
2
1
4
9
6
5
0x30
0x26
0x22
0x18
0x14
0x10
arr[5]
arr[4]
arr[3]
arr[2]
arr[1]
arr[0]
= *(arr+5)
= *(arr+4)
= *(arr+3)
= *(arr+2)
= *(arr+1)
= * arr = *(arr+0)
Memory Layout
Lấy giá trị
2.3 Các phép toán số học trên con trỏ
Phép toán số học trên con trỏ- Pointer Arithmetics
• Phép cộng (tăng)
• + n + n * sizeof()
• Có thể sử dụng toán tử gộp += hoặc ++
• Phép trừ (giảm)
• – n – n * sizeof()
• Có thể sử dụng toán tử gộp –= hoặc – –
NMLT - CON TRỎ CƠ BẢN 36
2.3 Các phép toán số học trên con trỏ
NMLT - CON TRỎ CƠ BẢN 37
2
1
4
9
6
5
0x30
0x26
0x22
0x18
0x14
0x10
p+1 (=0x22)
arr
int *p = &arr[2]
Memory Layout
p+2 (=0x26)
2.3 Các phép toán số học trên con trỏ
NMLT - CON TRỎ CƠ BẢN 38
2
1
4
9
6
5
0x30
0x26
0x22
0x18
0x14
0x10
p-1 (=0x22)
arr
p-2 (=0x18)
Memory Layout
int *p = &arr[4]
2.3 Các phép toán số học trên con trỏ
• Phép toán tính khoảng cách giữa 2 con trỏ
• *p1, *p2;
• p1 – p2 cho ta khoảng cách (theo số phần tử) giữa hai con trỏ
(cùng kiểu)
• Các phép toán so sánh
• Phép so sánh: So sánh địa chỉ giữa hai con trỏ (thứ tự ô nhớ)
==, !=, >, >=, <, <=
• Không thể thực hiện các phép toán: * / %
NMLT - CON TRỎ CƠ BẢN 39
2.3 Các phép toán số học trên con trỏ
NMLT - CON TRỎ CƠ BẢN 40
2
1
4
9
6
5
0x30
0x26
0x22
0x18
0x14
0x10 arr
Memory Layout
int *p2 = &arr[5]
int *p1 = &arr[1]
p2 – p1 = 4
p1 – p2 = -4
2.4 Con trỏ và mảng 1 chiều
int arr[6] = {5, 6, 9, 4, 1, 2};
int *parr;
// Cách 1
parr = arr;
// Cách 2
parr = &arr[0];
NMLT - CON TRỎ CƠ BẢN 41
0x10
parr
0x90
2
1
4
9
6
5
0x30
0x26
0x22
0x18
0x14
0x10 arr
Memory Layout
Xét đoạn code sau:
2.4 Con trỏ và mảng 1 chiều
• Truy xuất tới giá trị của phần tử thứ i của mảng (xét i là
chỉ số hợp lệ của mảng):
• Truy xuất tới địa chỉ của phần tử thứ i của mảng (xét i
là chỉ số hợp lệ của mảng):
NMLT - CON TRỎ CƠ BẢN 42
arr[i] == *(arr+i) == parr[i] == *(parr + i)
&arr[i] == arr+i == &parr[i] == parr + i
2.4 Con trỏ và mảng 1 chiều
NMLT - CON TRỎ CƠ BẢN 43
2
1
4
9
6
5
0x30
0x26
0x22
0x18
0x14
0x10
arr[5]
arr[4]
arr[3]
arr[2]
arr[1]
arr[0]
= parr[5]
= parr[4]
= parr[3]
= parr[2]
= parr[1]
= parr[0]
Memory Layout
= *(parr+5)
= *(parr+4)
= *(parr+3)
= *(parr+2)
= *(parr+1)
= *(parr+0)
2.5 Truyền mảng 1 chiều cho hàm
• Xét 2 đoạn chương trình sau. Tìm lỗi sai và giải thích.
Lý do: Đối số mảng truyền cho hàm không phải hằng
con trỏ.
NMLT - CON TRỎ CƠ BẢN 44
int main() {
int a[] = {1,2,3,4,5,6};
for (int i = 0; i<6; i++)
printf("%d", *(a++));
}
void xuat(int *a, int n) {
for (int i = 0; i<n; i++)
printf("%d", *(a++));
}
int main() {
int a[] = {1,2,3,4, 5, 6};
xuat(a, 6);
}
SAI
ĐÚNG
Bài tập 1
Nhóm các lệnh sau lại thành 2 nhóm tương ứng nhóm lấy địa
chỉ biến và nhóm lấy giá trị biến:
*(parr+i)
arr[i]
&parr[i]
&arr[i]
parr[i]
arr+i
parr + i
*(arr+i)
NMLT - CON TRỎ CƠ BẢN 45
Bài tập 2
Cho mảng 1 chiều a có 10 phần tử, biến con trỏ p trỏ tới
mảng 1 chiều a.
a. Hãy dùng con trỏ p để gán giá trị 100 cho phần tử thứ 5 của
mảng.
b. Hãy viết chương trình nhập và xuất mảng 1 chiều thông qua
con trỏ p.
NMLT - CON TRỎ CƠ BẢN 46
Lời giải
#include
using namespace std;
const int n = 10;
int main() {
int a[n], *p = a;
*(p+5) =100; // câu a
for (int i = 0; i < n; i++) {// câu b
cin >> *(p + i);
}
for (int i = 0; i < n; i++) {// câu b
cout << *(p + i) << " ";
}
}
NMLT - CON TRỎ CƠ BẢN 47
Bài tập 2
• Tạo biến str lưu chuỗi “hello class”, sau đó tạo biến con
trỏ p lưu trữ địa chỉ đầu tiên của chuỗi. Hãy thực hiện
chuyển chuỗi str thành chuỗi ký tự in hoa “HELLO
CLASS” thông qua sử dụng con trỏ p.
NMLT - CON TRỎ CƠ BẢN 48
Lời giải
#include
using namespace std;
int main() {
char str[20] = "hello class";
char *p;
int n = strlen(str);
p = str;
for (int i = 0; i<=n; i++)
p[i] = toupper(p[i]);
cout << p;
}
NMLT - CON TRỎ CƠ BẢN 49
Một số lưu ý
• Không thực hiện các phép toán nhân, chia, lấy phần
dư.
• Tăng/giảm con trỏ n đơn vị có nghĩa là tăng/giảm giá
trị của nó n*sizeof ()
• Không thể tăng/giảm biến mảng. Hãy gán một con trỏ
đến địa chỉ đầu của mảng và tăng/giảm nó.
NMLT - CON TRỎ CƠ BẢN 50
Câu hỏi lý thuyết
• Bài 1: Toán tử nào dùng để xác định địa chỉ của một biến?
• Bài 2: Toán tử nào dùng để xác định giá trị của biến do con
trỏ trỏ đến?
• Bài 3: Phép lấy giá trị gián tiếp là gì?
• Bài 4: Cho biến daa kiểu int. Khai báo và khởi tạo con trỏ
pdaa trỏ đến biến này. Sau đó gán giá trị 100 cho biến daa
sử dụng hai cách trực tiếp và gián tiếp.
• Bài 5: Các phần tử trong mảng được sắp xếp trong bộ nhớ
như thế nào?
• Bài 6: Cho mảng một chiều data. Trình bày 2 cách lấy địa chỉ
phần tử đầu tiên của mảng này.
• Bài 7: Cho con trỏ p1 trỏ đến phần tử thứ 3 còn con trỏ p2
trỏ đến phần tử thứ 4 của mảng int. Tính p2 – p1 ?
NMLT - CON TRỎ CƠ BẢN 51
Câu hỏi lý thuyết
• Bài 8: Tìm lỗi trong đoạn code
sau:
#include
using namespace std;
int main() {
int *x, y = 2;
*x = y;
*x += y++;
cout << *x << y;
}
NMLT - CON TRỎ CƠ BẢN 52
• Bài 9: Cho đoạn chương trình
sau:
float pay;
float *ptr_pay;
pay = 2313.54;
ptr_pay = &pay;
Hãy cho biết giá trị của:
a. pay
b. *ptr_pay
c. *pay
d. &pay
Bài tập bắt buộc
1. Cho biết ý nghĩa của các khai báo và câu lệnh; Tìm lỗi sai
trong đoạn code và giải thích
2. Cho mảng 1 chiều a có 10 phần tử, biến con trỏ p trỏ tới
mảng 1 chiều a. Hãy dùng con trỏ p để gán giá trị 100 cho
phần tử thứ 5 của mảng. Hãy viết chương trình nhập và
xuất mảng 1 chiều thông qua con trỏ p.
3. Tạo biến str lưu chuỗi “hello class”, sau đó tạo biến con trỏ
p lưu trữ địa chỉ đầu tiên của chuỗi. Hãy thực hiện chuyển
chuỗi str thành chuỗi ký tự in hoa “HELLO CLASS” thông
qua sử dụng con trỏ p.
4. Viết hàm nhập một dãy số thực A tùy ý trong đó có sự cấp
phát động. Viết hàm sao chép dãy số thực A (được nhập
bởi hàm trên) sang một dãy B trong đó có sự giải phóng
vùng nhớ cấp phát động và cấp phát lại ở dãy B.
5. Làm lại các bài tập về mảng dùng con trỏ
NMLT - CON TRỎ CƠ BẢN 53
NHẬP MÔN LẬP TRÌNH
BUỔI 15: CON TRỎ VÀ CẤP PHÁT ĐỘNG
CĐR buổi học
• Sau khi học xong buổi học, sinh viên có khả năng:
• Hiểu được về con trỏ và cấp phát động.
• Áp dụng con trỏ trong cấp phát mảng.
• Áp dụng con trỏ và tham số của hàm.
• Áp dụng con trỏ và cấu trúc.
55
Nội dung
1. Cấp phát động
2. Cấp phát động mảng 1 chiều
3. Cấp phát động mảng 2 chiều
4. Con trỏ và hàm số
5. Con trỏ và cấu trúc
6. Một số vấn đề mở rộng
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 56
1. Cấp phát động
• Cấp phát bộ nhớ tĩnh (static memory allocation)
• Khai báo biến, cấu trúc, mảng,
• Bắt buộc phải biết trước cần bao nhiều bộ nhớ lưu trữ tốn bộ
nhớ, không thay đổi được kích thước,
• Cấp phát động (dynamic memory allocation)
• Cần bao nhiêu cấp phát bấy nhiêu.
• Có thể giải phóng nếu không cần sử dụng.
• Sử dụng vùng nhớ ngoài chương trình (cả bộ nhớ ảo virtual
memory).
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 57
Cấu trúc một CT C++ trong bộ nhớ
• Toàn bộ tập tin chương trình sẽ được nạp vào bộ nhớ tại
vùng nhớ còn trống, gồm 4 phần:
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 58
STACK
Last-In First-Out
Vùng cấp phát tĩnh
(kích thước cố định)
Vùng cấp phát động
(RAM trống và bộ nhớ ảo)
Gồm các lệnh và hằng
(kích thước cố định)
Lưu đối tượng cục bộ
khi thực hiện hàm
Vùng nhớ trống
HEAP
Đối tượng toàn cục
& tĩnh
Mã chương trình
Cấp phát bộ nhớ
• Cấp phát bộ nhớ
• Trong C: Hàm malloc, calloc, realloc ( hoặc
)
• Trong C++: Toán tử new
• Giải phóng bộ nhớ
• Trong C: Hàm free
• Trong C++: Toán tử delete
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 59
Biến cấp phát động và Biến tự động
• Biến cục bộ
• Khai báo bên trong định nghĩa hàm
• Sinh ra khi hàm được gọi
• Hủy đi khi hàm kết thúc
• Thường gọi là biến tự động nghĩa là được trình biên dịch quản lý một cách tự
động
• Biến cấp phát động
• Sinh ra bởi cấp phát động
• Sinh ra và hủy đi khi chương trình đang chạy
• Biến cấp phát động hay Biến động là biến con trỏ trước khi sử dụng
được cấp phát bộ nhớ.
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 60
Toán tử new
• Vì con trỏ có thể tham chiếu tới biến nhưng không thực sự
cần phải có định danh cho biến đó.
• Có thể cấp phát động cho biến con trỏ bằng toán tử new.
Toán tử new sẽ tạo ra biến “không tên” cho con trỏ trỏ tới.
• Cú pháp: * = new
Ví dụ: int *ptr = new int;
o Tạo ra một biến “không tên” và gán ptr trỏ tới nó
o Có thể làm việc với biến “không tên” thông qua *ptr
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 61
Kiểm tra việc cấp phát có thành công không
#include
using namespace std;
int main() {
int *p = new int;
if (p == NULL) {
cout << "Error: Khong
du bo nho.\n";
exit(1);
}
*p = 3199;
}
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 62
3199
0x34ptr
0x34
0x90
Khởi tạo giá trị trong cấp phát động
• Cú pháp: pointer = new (value)
• Ví dụ:
#include
using namespace std;
int main() {
int *p;
p = new int(99); // initialize with 99
cout << *p; // displays 99
return 0;
}
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 63
Ví dụ
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 64
?p1
?p2
1. int *p1, *p2;
p1
?p2
2. int *p1 = new int;
?
p1
?p2
3. *p1 = 30;
p1
p2
4. p2 = p1;
p1
p2
5. *p2 = 40;
p1
p2
6. p1 = new int;
30
30
40
40
?
p1
p2
7. *p1 = 50;
40
50
Toán tử delete
• Toán tử delete dùng để giải phóng vùng nhớ trong HEAP do
con trỏ trỏ tới (con trỏ được cấp pháp bằng toán tử new). Cú
pháp:
delete ;
• Ghi chú: Sau khi gọi toán tử delete thì con trỏ vẫn trỏ tới
vùng nhớ trước khi gọi hàm delete. Ta gọi là “con trỏ lạc”. Ta
vẫn có thể gọi tham chiếu trên con trỏ, tuy nhiên:
• Kết quả không lường trước được
• Thường là nguy hiểm
Hãy tránh con trỏ lạc bằng cách gán con trỏ bằng NULL sau khi delete.
• Ví dụ:
delete pointer;
pointer = NULL;
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 65
Từ khóa typedef
• Từ khóa typedef dùng để định nghĩa 1 tên mới hay gọi là
một biệt danh (alias) cho tên kiểu dữ liệu có sẵn.
Ví dụ: typedef int SONGUYEN;
Các khai báo sau tương đương:
int a;
SONGUYEN a;
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 66
Định nghĩa kiểu dữ liệu con trỏ
• Có thể đặt tên cho kiểu dữ liệu con trỏ
• Để có thể khai báo biến con trỏ như các biến khác
• Loại bỏ * trong khai báo con trỏ
Ví dụ: typedef int* IntPtr;
- Định nghĩa một tên khác cho kiểu dữ liệu con trỏ
- Các khai báo sau tương đương:
IntPtr p;
int *p;
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 67
Con trỏ và hàm
• Con trỏ là kiểu dữ liệu hoàn chỉnh có thể dùng nó như các
kiểu khác
• Con trỏ có thể là tham số của hàm
• Có thể là kiểu trả về của hàm
Ví dụ: int* findOtherPointer(int* p);
Hàm này khai báo:
- Có tham số kiểu con trỏ trỏ tới int
- Trả về biến con trỏ trỏ tới int
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 68
Ví dụ
typedef int *IntPointer;
void Input(IntPointer temp) {
*temp = 20;
cout << "Trong ham goi *temp = " << *temp << endl;
}
int main() {
IntPointer p = new int;
*p = 10;
cout << "Truoc khi goi ham, *p = " << *p << endl;
Input(p);
cout << "Sau khi ket thuc ham, *p = " << *p << endl;
}
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 69
Truoc khi goi ham, *p = 10
Trong ham goi *temp = 20
Sau khi ket thuc ham, *p = 20
Ví dụ
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 70
p
1. Trước khi gọi hàm Input
10
p 10
temp
2. Giá trị của p sẽ được truyền
vào temp
p 20
temp
3. Thay đổi giá trị *temp
p 20
4. Sau khi kết thúc gọi hàm Input
Nhắc lại
• Mảng lưu trong các ô nhớ liên tiếp trong bộ nhớ máy
tính
• Biến mảng tham chiếu tới phần tử đầu tiên
• Biến mảng là một biến hằng con trỏ
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 71
Nhắc lại
• Ví dụ:
int a[10];
typedef int* IntPtr;
IntPtr p;
a và p là các biến con trỏ.
• Phép gán hợp lệ p = a;
• p bây giờ sẽ trỏ tới nơi a trỏ, tức là tới phần tử đầu tiên của
mảng a
• Phép gán không hợp lệ a = p;
• Bởi con trỏ mảng là con trỏ hằng.
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 72
Hạn chế của mảng chuẩn
• Hạn chế của mảng chuẩn
• Bắt buộc phải biết trước cần bao nhiều bộ nhớ lưu trữ => tốn bộ
nhớ, không thay đổi được kích thước,
Dùng Mảng động
• Mảng động
• Kích thước không xác định ở thời điểm lập trình
• Mà xác định khi chạy chương trình
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 73
Tạo mảng động bằng toán tử new
• Cấp phát động cho biến con trỏ
• Sau đó dùng con trỏ như mảng chuẩn
• Cú pháp:
= new []
Ví dụ:
typedef double * doublePtr;
doublePtr d;
d = new double[10];
Tạo biến mảng cấp phát động d có 10 phần tử, kiểu cơ
sở là double.
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 74
Xóa mảng động
• Dùng toán tử delete[] để xóa mảng động.
Ví dụ:
double *d = new double[10];
//... Processing
delete[] d;
Giải phóng tất cả vùng nhớ của mảng động này
Cặp ngoặc vuông báo hiệu có mảng
Nhắc lại: d vẫn trỏ tới vùng nhớ đó. Vì vậy sau khi
delete, cần gán d = NULL;
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 75
Hàm trả về kiểu mảng
• Ta không được phép trả về kiểu mảng trong hàm.
Ví dụ:
int[] someFunction(); // Không hợp lệ!
• Có thể thay bằng trả về con trỏ tới mảng có cùng kiểu
cơ sở:
int* someFunction(); // Hợp lệ!
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 76
3. Mảng động 2 chiều
• Là mảng của mảng
• Sử dụng định nghĩa kiểu con trỏ giúp hiểu rõ hơn:
typedef int* IntArrayPtr;
IntArrayPtr *m = new IntArrayPtr[3];
Tạo ra mảng 3 con trỏ
Sau đó biến mỗi con trỏ này thành mảng 4 biến int
for (int i = 0; i < 3; i++)
m[i] = new int[4];
Kết quả là mảng động 3 x 4
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 77
Lời giải
int main() {
int row=2, col=3;
// Cấp phát vùng nhớ
int **p = new int*[row];
if (p == NULL) exit(1);
for (int i = 0; i < row; i++) {
p[i] = new int[col];
if (p[i] == NULL) exit(1);
}
// Giải phóng vùng nhớ
for (int i = 0; i < row; i++) {
delete[] p[i];
}
delete[] p;
return 0;
}
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 78
4. Con trỏ và hàm số
• Tham số của hàm là 1 biến con trỏ
• Trường hợp thay đổi giá trị của đối số
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 79
void Hoanvi(int *x, int *y)
{
int z = *x;
*x=*y;
*y=z;
}
void main()
{
int a=1,b=2;
cout<<a<<b; // 1 2
Hoanvi(&a,&b);
cout<<a<<b; // 2 1
}
4. Con trỏ và hàm số
• Tham số của hàm là 1 biến con trỏ
• Trường hợp không thay đổi giá trị của đối số
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 80
void Capphat(int *a)
{ a = new int[5];
for(int i=0; i<5; i++)
{ a[i]=i+1;
cout<< a[i];
}
}
void main()
{ int n=5;
int *b = &n;
cout<<*b; // 5
Capphat(b); // 1 2 3 4 5
cout<<*b; // 5
}
4. Con trỏ và hàm số
• Kiểu trả về của hàm là 1 con trỏ
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 81
int* GetArray()
{
int a = new int [5];
for(int i=0; i<5; i++)
a[i]=i+1;
return a;
}
4. Con trỏ và cấu trúc
• Truy xuất các các thuộc tính dùng con trỏ:
tên_biến_con_trỏ tên_thuộc_tính
• Ví dụ cấu trúc phân số
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 82
struct PhanSo
{ int TuSo;
int MauSo;
};
PhanSo x;
x.Tuso=1; x.MauSo=2;
PhanSo *p, *q;
p = &x;
PTuso=3; p MauSo=4; // x(3,4)
q = new PhanSo();
q->TuSo = 1; // giống (*q).TuSo=1
q->MauSo = 2;// giống (*q).MauSo=2
4. Con trỏ và cấu trúc
• Cấu trúc đệ quy (tự trỏ)
struct PERSON
{
char hoten[30];
struct PERSON *father, *mother;
};
struct NODE
{
int value;
struct NODE *pNext;
};
Bài tập
• Bài 1: Tại sao cần phải giải phóng khối nhớ được cấp phát
động?
Khối nhớ không tự giải phóng sau khi sử dụng nên sẽ làm
giảm tốc độ thực hiện chương trình hoặc tràn bộ nhớ nếu
tiếp tục cấp phát
• Bài 2: Điều gì xảy ra nếu ta nối thêm một số ký tự vào một
chuỗi (được cấp phát động trước đó) mà không cấp phát lại
bộ nhớ cho nó?
Nếu chuỗi đủ lớn để chứa thêm thông tin thì không cần cấp
phát lại. Ngược lại phải cấp phát lại để có thêm vùng nhớ.
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 84
Bài tập
• Bài 3: Ta thường dùng phép ép kiểu trong những trường hợp nào?
Lấy phần nguyên của số thực hoặc lấy phần thực của phép chia
hai số nguyên,
• Bài 4: Giả sử c kiểu char, i kiểu int, l kiểu long. Hãy xác định kiểu
của các biểu thức sau:
• (c + i + l)
• (i + ‘A’)
• (i + 32.0)
• (100 + 1.0)
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 85
Bài tập
• Bài 5: Việc cấp phát động nghĩa là gì?
Bộ nhớ được cấp phát động là bộ nhớ được cấp phát
trong khi chạy chương trình và có thể thay đổi độ lớn
vùng nhớ.
• Bài 6: Cho biết sự khác nhau giữa malloc và calloc?
malloc: cấp phát bố nhớ cho một đối tượng.
calloc: cấp phát bộ nhớ cho một nhóm đối tượng.
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 86
Bài tập
• Bài 7: Viết câu lệnh sử dụng hàm malloc để cấp phát 1000
số kiểu long.
long *ptr;
ptr = (long *)malloc(1000 * sizeof(long));
• Bài 8: Giống bài 7 nhưng dùng calloc
long *ptr;
ptr = (long *)calloc(1000, sizeof(long));
ptr = (long *)calloc(sizeof(long), 1000); !!!
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 87
Bài tập
• Bài 9: Kiểm tra kết quả
• Bài 10: Kiểm tra lỗi
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 88
void func() {
int n1 = 100, n2 = 3;
float ketqua = n1 / n2;
printf("%d / %d = %f", n1, n2, ketqua);
}
int main() {
void *p;
p = (float *)malloc(sizeof(float));
*p = 1.23;
}
Bài tập
• Bài 11: Cho biết kết quả chương trình sau. Giải thích tại
sao ta có được kết quả này.
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 89
void hamf(int*a)
{ a= new int[5];
for(int i=0; i<5; i++)
a[i]=i+1;
}
void main()
{ int n=5;
int *a= &n;
cout<<‚Giá trị *a = "<<*a;
hamf(a);
cout<<‚Giá trị *a = "<<*a;
}
Bài tập
• Bài 12: Cho biết kết quả chương trình sau. Giải thích tại
sao ta có được kết quả này.
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 90
void hamf(int*&a)
{ a= new int[5];
for(int i=0; i<5; i++)
a[i]=i+1;
}
void main()
{ int n=5;
int *a= &n;
cout<<‚Giá trị *a = "<<*a;
hamf(a);
cout<<‚Giá trị *a = "<<*a;
}
Bài tập bắt buộc (1/2)
1. Cho biết ý nghĩa của các khai báo và câu lệnh; Tìm lỗi
sai trong đoạn code và giải thích (t.t) xem các bài
tập từ 1 đến 12 trong phần trước.
2. Viết chương trình nhập một dãy số hữu tỉ tùy ý (sử
dụng con trỏ và sự cấp phát động), xuất ra dãy gồm
tất cả các số nhỏ hơn 1 có trong dãy được nhập vào,
tính tổng và tích của dãy số hữu tỉ.
3. Viết chương trình khai báo mảng hai chiều có 12x12
phần tử kiểu char. Gán ký tự „X‟ cho mọi phần tử của
mảng này. Sử dụng con trỏ đến mảng để in giá trị các
phần tử mảng lên màn hình ở dạng lưới.
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 91
Bài tập bắt buộc (2/2)
4. Viết chương trình khai báo mảng 10 con trỏ đến kiểu
float, nhận 10 số thực từ bàn phím, sắp xếp lại và in
ra màn hình dãy số đã sắp xếp.
5. Chương trình cho phép người dùng nhập các dòng văn
bản từ bàn phím đến khi nhập một dòng trống.
Chương trình sẽ sắp xếp các dòng theo thứ tự
alphabet rồi hiển thị chúng ra màn hình.
6. Làm lại các bài tập về ma trận dùng con trỏ
NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 92
Chúc các em học tốt!
Các file đính kèm theo tài liệu này:
- nhap_mon_lap_trinh_bai_11_con_tro_2582_2054390.pdf