TRƯỜNG ĐẠI HỌC CÔNG NGHIỆP THÀNH PHỐ HỒ CHÍ MINH

Phương pháp lập trình Kiểu con trỏ TS. Ngô Hữu Dũng

Kiến trúc máy tí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.

 RAM dùng để chứa một phần hệ điều hành, các lệnh chương

trình, các dữ liệu…

 Mỗi ô nhớ có địa chỉ duy nhất và địa chỉ này được đánh số từ 0

trở đi.  Ví dụ

 RAM 512MB được đánh địa chỉ từ 0 đến 229 – 1  RAM 2GB được đánh địa chỉ từ 0 đến 231 – 1

Phương pháp lập trình - Con trỏ

Khai báo biến trong C

 Quy trình xử lý của trình biên dịch

 Dành riêng một vùng nhớ với địa chỉ duy nhất để lưu biến đó.  Liên kết địa chỉ ô nhớ đó với tên biến.  Khi gọi tên biến, nó sẽ truy xuất tự động đến ô nhớ đã liên kết

với tên biến.

 Ví dụ: int a = 0x1234;

// Giả sử địa chỉ 0x0B

34 12 00 00

a

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Khái niệm con trỏ

 Khái niệm

 Địa chỉ của biến là một con số.  Ta có thể tạo biến khác để lưu địa chỉ của biến này  Con trỏ.

34

12

00

00

0B 00 00 00

a

pa

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

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ụ

char *ch1, *ch2; int *p1, p2;

 ch1 và ch2 là biến con trỏ, trỏ tới vùng nhớ kiểu char (1 byte).  p1 là biến con trỏ, trỏ tới vùng nhớ kiểu int (4 bytes) còn p2 là

biến kiểu int bình thường.

Phương pháp lập trình - Con trỏ

Khai báo con trỏ

 Sử dụng từ khóa typedef

typedef *;

;

 Ví dụ

typedef int *pint; int *p1; pint p2, p3;

 Lưu ý khi khai báo kiểu dữ liệu mới

 Giảm bối rối khi mới tiếp xúc với con trỏ.  Nhưng dễ nhầm lẫn với biến thường.

Phương pháp lập trình - Con trỏ

Con trỏ NULL

 Khái niệm

 Con trỏ NULL là con trỏ không trỏ và đâu cả.  Khác với con trỏ chưa được khởi tạo.

// unreferenced local varialbe

int n; int *p1 = &n; int *p2; int *p3 = NULL;

NULL

Phương pháp lập trình - Con trỏ

Khởi tạo kiểu con trỏ

 Khởi tạo

 Khi mới khai báo, biến con trỏ được đặt ở địa chỉ nào đó

(không biết trước).  chứa giá trị không xác định  trỏ đến vùng nhớ không biết trước.

 Đặt địa chỉ của biến vào con trỏ (toán tử &)

 Ví dụ

= &;

int a, b; int *pa = &a, *pb; pb = &b;

Phương pháp lập trình - Con trỏ

Sử dụng con trỏ

 Truy xuất đến ô nhớ mà con trỏ trỏ đến  Con trỏ chứa một số nguyên chỉ địa chỉ.  Vùng nhớ mà nó trỏ đến, sử dụng toán tử *.

 Ví dụ

int a = 5, *pa = &a; printf(“%d\n”, pa); // Giá trị biến pa printf(“%d\n”, *pa); // Giá trị vùng nhớ pa trỏ đến printf(“%d\n”, &pa); // Địa chỉ biến pa

05 00 00 00

0B 00 00 00

a

pa

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Kích thước của con trỏ

 Kích thước của con trỏ

char *p1; int *p2; float *p3; double *p4; …

 Con trỏ chỉ lưu địa chỉ nên kích thước của mọi con trỏ là như

nhau:  Môi trường MD-DOS (16 bit): 2 bytes  Môi trường Windows (32 bit): 4 bytes

Phương pháp lập trình - Con trỏ

Các cách truyền đối số

 Truyền giá trị (tham trị)

1. #include

2. void hoanvi(int x, int y);

int a = 5; b = 6; hoanvi(a, b); printf(“a = %d, b = %d”, a, b);

int t = x; x = y; y = t;

3. void main() 4. { 5. 6. 7. 8. } 9. void hoanvi(int x, int y) 10.{ 11. 12.}

Phương pháp lập trình - Con trỏ

Truyền giá trị (tham trị)

int x

int y

05 00 00 00 06 00 00 00

hoanvi

int x

int y

int t = x; x = y; y = t;

18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25

05 00 00 00 06 00 00 00

int b = 6

int a = 5

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Các cách truyền đối số

 Truyền địa chỉ (con trỏ)

1. #include

2. void hoanvi(int *x, int *y);

int a = 2912; b = 1706; hoanvi(&a, &b); printf(“a = %d, b = %d”, a, b);

int t = *x; *x = *y; *y = t;

3. void main() 4. { 5. 6. 7. 8. } 9. void hoanvi(int *x, int *y) 10.{ 11. 12.}

Phương pháp lập trình - Con trỏ

Truyền địa chỉ (con trỏ)

int *x

int *y

0B 00 00 00 0F 00 00 00

hoanvi

int *x

int *y

int t = *x; *x = *y; *y = *t;

18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25

05 00 00 00 06 00 00 00

int a = 5

int b = 6

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Các cách truyền đối số

 Truyền tham chiếu (C++)

1. #include

2. void hoanvi(int &x, int &y);

int a = 2912; b = 1706; hoanvi(a, b); printf(“a = %d, b = %d”, a, b);

int t = x; x = y; y = t;

3. void main() 4. { 5. 6. 7. 8. } 9. void hoanvi(int &x, int &y) 10.{ 11. 12.}

Phương pháp lập trình - Con trỏ

Truyền tham chiếu (C++)

hoanvi

int &x

int &y

int t = x; x = y; y = t;

05 00 00 00 06 00 00 00

int a = 5

int b = 6

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Một số lưu ý

 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;

Phương pháp lập trình - Con trỏ

Con trỏ và mảng một chiều

 Mảng một chiều int array[3];

 Tên mảng array là một hằng con trỏ

 không thể thay đổi giá trị của hằng này.

 array là địa chỉ đầu tiên của mảng

array == &array[0]

array

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Con trỏ và mảng một chiều

 Con trỏ đến mảng một chiều

1. int array[3], *parray;

// Cách 1 2. parray = array; 3. parray = &array[0]; // Cách 2

0B 00 00 00

parray

18 19 1A 1B 1C 1D 1E 1F

array

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Phép toán số học trên con trỏ

 Phép cộng (tăng)

 + n  + n * sizeof()  Có thể sử dụng toán tử gộp += hoặc ++

p = array

+2

+1

int array[3];

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Phép toán số học trên con trỏ

 Phép trừ (giảm)

 – n  – n * sizeof()  Có thể sử dụng toán tử gộp –= hoặc – –

p = &array[2]

–2

–1

int array[3];

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

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)

p2 = &array[2]

p1 = array

p1 – p2= (0B – 13)/sizeof(int) = –2

p2 – p1= (13 – 0B)/sizeof(int) = +2

int array[3];

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Phép toán số học trên con trỏ

 Các phép toán khác

 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: * / %

Phương pháp lập trình - Con trỏ

Con trỏ và mảng một chiều

 Truy xuất đến phần tử thứ n của mảng (không sử dụng

biến mảng)  array[n] == p[n] == *(p + n)

*

(

p

+ 2

)

int array[3];

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Con trỏ và mảng một chiều

 Ví dụ nhập mảng void main() {

int a[10], n = 10, *pa; pa = a;

// hoặc pa = &a[0];

for (int i = 0; i

}  &a[i]  (a + i)  (p + i)  &p[i]

Phương pháp lập trình - Con trỏ

Con trỏ và mảng một chiều

 Ví dụ xuất mảng

void main() {

// hoặc pa = &a[0];

int a[10], n = 10, *pa; pa = a; … for (int i = 0; i

}  a[i]  *(a + i)  *(p + i)  p[i]

Phương pháp lập trình - Con trỏ

Truyền mảng 1 chiều cho hàm

 Chú ý!

 Mảng một chiều truyền cho hàm là địa chỉ của phần tử đầu tiên

chứ không phải toàn mảng.

xuất

int a[3]

int n

int a[]

int *a

int array[3];

Phương pháp lập trình - Con trỏ

10 11 13 14 15 16 17 18 19 20 21 22 23 24

Con trỏ và mảng một chiều

 Ví dụ

void xuat(int a[10], int n) {

for (int i = 0; i

printf(“%d”, *(a++));

// OK

}

void main() {

int a[10], n = 10;

for (int i = 0; i

printf(“%d”, *(a++));

// Lỗi

}  Đối số mảng truyền cho hàm không phải hằng con trỏ.

Phương pháp lập trình - Con trỏ

Con trỏ và mảng một chiều

 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ó.  Đối số mảng một chiều truyền cho hàm là địa chỉ

phần tử đầu tiên của mảng.

Phương pháp lập trình - Con trỏ

Con trỏ cấu trúc

 Truy xuất bằng 2 cách

-> (*).

 Ví dụ

struct PHANSO {

int tu, mau;

}; PHANSO ps1, *ps2 = &p1; // ps2 là con trỏ

ps1.tu = 1; ps1.mau = 2; ps2->tu = 1; ps2->mau = 2; (*ps2).tu = 1; (*ps2).mau = 2;

Phương pháp lập trình - Con trỏ

Con trỏ cấu trúc

 Gán hai cấu trúc

struct PHANSO {

int tu, mau;

}; PHANSO ps1, *ps2;

ps1.tu = 1; ps1.mau = 2;

// ps1 = 1/2

ps2 = &ps1; ps2->tu = 3; ps2->mau = 4;

// ps1 = 3/4

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Bài 1: 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

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Bài 2: Tìm lỗi

#include #include

void main() {

int *x, y = 2;

*x = y; *x += y++;

printf("%d %d",*x,y); getch();

}

Phương pháp lập trình - Con trỏ

Bài tập 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: Các phần tử trong mảng được sắp xếp trong bộ

nhớ như thế nào?

 Bài 5: 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.

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Bài 6: Nếu ta truyền cho hàm đối số là mảng một chiều. Trình bày hai cách nhận biết phần tử cuối của mảng?  Bài 7: Trình bày 6 phép toán có thể thực hiện trên con

trỏ?

 Bài 8: 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. p2 – p1 = ?  Bài 9: Giống như câu trên nhưng đối với mảng float?

Phương pháp lập trình - Con trỏ

Bài tập

 Bài 10: Trình bày khai báo con trỏ pchar trỏ đến kiểu

char.

 Bài 11: Cho biến cost kiểu int. Khai báo và khởi tạo con

trỏ pcost trỏ đến biến này.

 Bài 12: Gán giá trị 100 cho biến cost sử dụng hai cách

trực tiếp và gián tiếp.

 Bài 13: In giá trị của con trỏ và giá trị của biến mà nó trỏ

tới.

 Bài 14: Sử dụng con trỏ để làm lại các bài tập về mảng

một chiều.

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Bài 15: Cho đoạn chương trình sau:

int *pint; float a; char c; double *pd;

Hãy chọn phát biểu sai cú pháp:

a. a = *pint; b. c = *pd; c. *pint = *pd; d. pd = a;

Phương pháp lập trình - Con trỏ

Bài tập thực hành

 Bài 16: Viết chương trình nhập số nguyên dương n gồm k chữ số (0 < k ≤ 5) , sắp xếp các chữ số của n theo thứ tự tăng dần. Ví dụ:  Nhập n = 1536  Kết quả sau khi sắp xếp: 1356.

Phương pháp lập trình - Con trỏ

Con trỏ cấp 2 (con trỏ đến con trỏ)

 Đặt vấn đề

void CapPhat(int *p, int n) {

p = (int *)malloc(n * sizeof(int));

}

void main() {

int *a = NULL; CapPhat(a, 2); // a vẫn = NULL

}

Làm sao thay đổi giá trị của con trỏ (không phải giá trị mà nó trỏ đến) sau khi gọi hàm?

Phương pháp lập trình - Con trỏ

Con trỏ cấp 2 int *p

int n

22 00 00 00 N U L L 02 00 00 00

CapPhat

int *p

int n

NULL

2

18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25

N U L L

int *a = NULL

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Con trỏ cấp 2

 Giải pháp

 Sử dụng tham chiếu int *&p (trong C++) void CapPhat(int *&p, int n) {

p = (int *)malloc(n * sizeof(int));

}

 Không thay đổi trực tiếp tham số mà trả về

int* CapPhat(int n) {

int *p = (int *)malloc(n * sizeof(int)); return p;

}

Phương pháp lập trình - Con trỏ

Con trỏ cấp 2

 Giải pháp

 Sử dụng con trỏ p trỏ đến con trỏ a này. Hàm sẽ thay đổi giá trị

của con trỏ â gián tiếp thông qua con trỏ p.

void CapPhat(int **p, int n) {

*p = (int *)malloc(n * sizeof(int));

}

void main() {

int *a = NULL; CapPhat(&a, 4);

}

Phương pháp lập trình - Con trỏ

Con trỏ cấp 2 int **p

int n

18 21 22 23 24 25

CapPhat

int **p

int n

0B

2

19 1A 1B 1C 1D 1E 1F 20 02 00 00 00 0B 00 00 00

22 00 00 00 N U L L

int *a = NULL

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Con trỏ cấp 2

 Lưu ý

int x = 12; int *ptr = &x; int k = &x; ptr = k;

// OK // Lỗi

int **ptr_to_ptr = &ptr; int **ptr_to_ptr = &x;

// OK // Lỗi

**ptr_to_ptr = 12; *ptr_to_ptr = 12;

// OK // Lỗi

printf(“%d”, ptr_to_ptr); printf(“%d”, *ptr_to_ptr); printf(“%d”, **ptr_to_ptr);

// Địa chỉ ptr // Giá trị ptr // Giá trị x

Phương pháp lập trình - Con trỏ

Con trỏ và mảng 2 chiều

int a[3][4];

0 1 2 3 4 5 6 7 8 9 10 11

0

a

1

int

2

0 1 2 3 1 2

0

a

1

int[4]

Phương pháp lập trình - Con trỏ

2

Con trỏ và mảng 2 chiều

 Hướng tiếp cận 1

 Các phần tử tạo thành mảng 1 chiều  Sử dụng con trỏ int * để duyệt mảng 1 chiều

int *p = (int *)a

+1

0 1 2 3 4 5 6 7 8 9 10 11

int a[3][4]

Phương pháp lập trình - Con trỏ

Hướng tiếp cận 1

 Nhập / Xuất theo chỉ số mảng 1 chiều

#define D 3 #define C 4 void main() {

int a[D][C], i; int *p = (int *)a; for (i = 0; i < D*C; i++) {

printf(“Nhap phan tu thu %d: ”, i); scanf(“%d”, p + i);

}

for (i = 0; i < D*C; i++)

printf(“%d ”, *(p + i));

}

Phương pháp lập trình - Con trỏ

Hướng tiếp cận 1

 Liên hệ giữa chỉ số mảng 1 chiều và chỉ số mảng 2 chiều

(d, c)  i ?

i = d*C + c

0 1 2 3 4 5 6 7 8 9 10 11

0

aCxD

1

i  (d, c) ?

2

Phương pháp lập trình - Con trỏ

d = i / C c = i % C

Hướng tiếp cận 1

 Nhập / Xuất theo chỉ số mảng 2 chiều

int a[D][C], i, d, c; int *p = (int *)a;

for (i = 0; i < D*C; i++) {

printf(“Nhap a[%d][%d]: ”, i / C, i % C); scanf(“%d”, p + i);

} for (d = 0; d < D; d++) {

for (c = 0; c < C; c++)

printf(“%d ”, *(p + d * C + c));// *p++

printf(“\n”;

}

Phương pháp lập trình - Con trỏ

Con trỏ và mảng 2 chiều

 Hướng tiếp cận 2

 Mảng 1 chiều, mỗi phần tử là mảng 1 chiều

 a chứa a[0], a[1], …  a = &a[0]  a[0] chứa a[0][0], a[0][1], …  a[0] = &a[0][0]

a

+1

1 2 0

int a[3][4]

+1

a[0]

Phương pháp lập trình - Con trỏ

Hướng tiếp cận 2

 Kích thước của mảng

void main() {

int a[3][4]; printf(“KT của a = %d”, sizeof(a)); printf(“KT của a[0] = %d”, sizeof(a[0])); printf(“KT của a[0][0] = %d”, sizeof(a[0][0]));

}

0 1 2

a

0 1 2 3

a[0]

a[0][0]

Phương pháp lập trình - Con trỏ

Hướng tiếp cận 2

 Nhận xét

 a là con trỏ đến a[0], a[0] là con trỏ đến a[0][0]  a là con trỏ

cấp 2.

 Có thể truy xuất a[0][0] bằng 3 cách:

void main() {

int a[3][4]; a[0][0] = 1; *a[0] = 1; **a = 1;

a[1][0] = 1; *a[1] = 1; **(a+1) = 1; a[1][2] = 1; *(a[1]+2) = 1; *(*(a+1)+2) = 1;

}

Phương pháp lập trình - Con trỏ

Hướng tiếp cận 2

 Truyền mảng cho hàm

 Truyền địa chỉ phần tử đầu tiên cho hàm.  Khai báo con trỏ rồi gán địa chỉ mảng cho con trỏ này để nó trỏ

đến mảng.

 Con trỏ này phải cùng kiểu với biến mảng, tức là con trỏ đến

vùng nhớ n phần tử (mảng)

 Cú pháp

 Ví dụ

(*)[];

int (*ptr)[4];

Phương pháp lập trình - Con trỏ

Hướng tiếp cận 2

 Truyền mảng cho hàm

void Xuat_1_Mang_C1(int (*ptr)[4]) // ptr[][4] {

int *p = (int *)ptr; for (int i = 0; i < 4; i++)

printf(“%d ”, *p++);

} void main() {

int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int (*ptr)[4]; ptr = a; for (int i = 0; i < 3; i++)

Xuat_1_Mang_C1(ptr++); // hoặc ptr + i // sai => a + i Xuat_1_Mang_C1(a++);

}

Phương pháp lập trình - Con trỏ

Hướng tiếp cận 2

 Truyền mảng cho hàm

// ptr[]

void Xuat_1_Mang_C2(int *ptr, int n) {

for (int i = 0; i < n; i++)

printf(“%d ”, *ptr++);

} void main() {

int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int (*ptr)[4]; ptr = a; for (int i = 0; i < 3; i++)

Xuat_1_Mang_C2((int *)ptr++); Xuat_1_Mang_C2((int *)(a + i));// a++ sai

}

Phương pháp lập trình - Con trỏ

Hướng tiếp cận 2

 Truyền mảng cho hàm

void Xuat_n_Mang_C1(int (*ptr)[4], int n) {

int *p = (int *)ptr; for (int i = 0; i < n * 4; i++) printf(“%d ”, *p++);

} void main() {

int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int (*ptr)[4]; ptr = a;

Xuat_n_Mang_1(ptr, 3); Xuat_n_Mang_1(a, 3);

}

Phương pháp lập trình - Con trỏ

Hướng tiếp cận 2

 Truyền mảng cho hàm

void Xuat_n_Mang_C2(int (*ptr)[4], int n) {

int *p; for (int i = 0; i < n; i++) {

p = (int *)ptr++;

for (int i = 0; i < 4; i++)

printf(“%d ”, *p++);

printf(“\n”);

}

}

Phương pháp lập trình - Con trỏ

Mảng con trỏ

 Đặt vấn đề

 Sử dụng cấu trúc dữ liệu nào để lưu trữ thông tin sau?

2 0 1 3 4 5 6 7

6

1

5

0

1

2

9

2

1

7

0

6

1

0

2

 Giải pháp?

 Cách 1: Mảng 2 chiều 3x8 (tốn bộ nhớ)

Phương pháp lập trình - Con trỏ

2

Mảng con trỏ

 Cách 2: Mảng 1 chiều các con trỏ

1

5

6

18 19 1A 1B 1C 1D 1E 1F

2

9

1

2

1

7

0

6

28 29 2A 2B 2C 2D 2E 2F

0

2

3A 3B 3C

19 00 00 00 28 00 00 00 3A 00 00 00

array

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Mảng con trỏ

 Ví dụ

void print_strings(char *p[], int n) {

for (int i = 0; i

printf(“%s ”, p[i]);

}

void main() {

char *message[4] = {“Tin”, “Hoc”, “Co”, “So”};

print_strings(message, 4);

}

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 Khái niệm

 Hàm cũng đuợc lưu trữ trong bộ nhớ, tức là cũng có địa chỉ.  Con trỏ hàm là con trỏ trỏ đến vùng nhớ chứa hàm và có thể gọi

hàm thông qua con trỏ đó.

11 00 00 00

p

int Cong(int, int)

Phương pháp lập trình - Con trỏ

0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17

Con trỏ hàm

 Khai báo tường minh

(* )(ds tham số);

 Ví dụ

// Con trỏ đến hàm nhận đối số int, trả về int int (*ptof1)(int x);

// Con trỏ đến hàm nhận 2 đối số double, không trả về void (*ptof2)(double x, double y);

// Con trỏ đến hàm nhận đối số mảng, trả về char char (*ptof3)(char *p[]);

// Con trỏ đến không nhận đối số và không trả về void (*ptof4)();

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 Khai báo không tường minh (thông qua kiểu)

typedef (* )(ds tham số); ;

 Ví dụ

int (*pt1)(int, int);

// Tường minh

typedef int (*PhepToan)(int, int);

PhepToan pt2, pt3;

// Không tường minh

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 Gán giá trị cho con trỏ hàm

= ; = &;

 Hàm được gán phải cùng dạng (vào, ra)

 Ví dụ

int Cong(int x, int y); int Tru(int x, int y); int (*tinhtoan)(int x, int y);

// Hàm // Hàm // Con trỏ hàm

tinhtoan = Cong; // Dạng ngắn gọn tinhtoan = &Tru; // Dạng sử dụng địa chỉ tinhtoan = NULL; // Không trỏ đến đâu cả

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 So sánh con trỏ hàm

if (tinhtoan != NULL) {

if (tinhtoan == &Cong)

printf(“Con trỏ đến hàm Cong.”);

else

if (tinhtoan == &Tru)

printf(“Con trỏ đến hàm Tru.”);

else

printf(“Con trỏ đến hàm khác.”);

} else

printf(“Con trỏ chưa được khởi tạo!”);

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 Gọi hàm thông qua con trỏ hàm

 Sử dụng toán tử lấy nội dung “*” (chính quy) nhưng trường

hợp này có thể bỏ

int Cong(int x, int y); int Tru(int x, int y);

int (*tinhtoan)(int, int);

tinhtoan = Cong; int kq1 = (*tinhtoan)(1, 2); // Chính quy int kq2 = tinhtoan(1, 2);

// Ngắn gọn

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 Truyền tham số là con trỏ hàm

int Cong(int x, int y); int Tru(int x, int y); int TinhToan(int x, int y, int (*pheptoan)(int, int)) {

// Gọi hàm

int kq = (*pheptoan)(x, y); return kq;

}

void main() {

int (*pheptoan)(int, int) = &Cong; int kq1 = TinhToan(1, 2, pheptoan); int kq2 = TinhToan(1, 2, &Tru);

}

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 Trả về con trỏ hàm

int (*LayPhepToan(char code))(int, int) {

if (code == ‘+’)

return &Cong;

return &Tru;

}

void main() {

int (*pheptoan)(int, int) = NULL; pheptoan = LayPhepToan(‘+’); int kq2 = pheptoan(1, 2, &Tru);

}

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 Trả về con trỏ hàm (khai báo kiểu)

typedef (*PhepToan)(int, int); PhepToan LayPhepToan(char code) {

if (code == ‘+’)

return &Cong;

return &Tru;

}

void main() {

PhepToan pheptoan = NULL; pheptoan = LayPhepToan(‘+’); int kq2 = pheptoan(1, 2, &Tru);

}

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 Mảng con trỏ hàm

typedef (*PhepToan)(int, int); void main() {

int (*array1[2])(int, int); PhepToan array2[2];

// tường minh // kô tường minh

array1[0] = array2[1] = &Cong; array1[1] = array2[0] = &Tru;

printf(“%d\n”, (*array1[0])(1, 2)); printf(“%d\n”, array1[1](1, 2)); printf(“%d\n”, array2[0](1, 2)); printf(“%d\n”, array2[1](1, 2));

}

Phương pháp lập trình - Con trỏ

Con trỏ hàm

 Lưu ý

 Không được quên dấu () khi khai báo con trỏ hàm

 int (*PhepToan)(int x, int y);  int *PhepToan(int x, int y);

 Có thể bỏ tên biến tham số trong khai báo con trỏ hàm

 int (*PhepToan)(int x, int y);  int (*PhepToan)(int, int);

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Câu 1: Ta có thể khai báo và sử dụng biến con trỏ đến cấp

thứ mấy?

 Câu 2: Có sự khác nhau giữa con trỏ đến một chuỗi và con

trỏ đến một mảng ký tự không?

 Câu 3: Nếu không sử dụng các kiến thức nâng cao về con trỏ, ta có thể giải quyết một số bài toán nào đó không?

 Câu 4: Hãy nên một số ứng dụng của con trỏ hàm.

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Câu 5: Viết đoạn lệnh khai báo biến x kiểu float, khai báo và khởi tạo con trỏ px đến biến x và khai báo và khởi tạo con trỏ ppx đến con trỏ px.

 Câu 6: Ta muốn gán 100 cho x thông qua con trỏ ppx bằng

biểu thức gán “ppx = 100;” có được không?

 Câu 7: Giả sử ta khai báo mảng array 3 chiều int

array[2][3][4]. Cho biết cấu trúc của mảng này đối với trình biên dịch C.

 Câu 8: Cho biết array[0][0] có nghĩa là gì?

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Câu 9: Xét xem biểu thức so sánh nào sau đây đúng

 array[0][0] == & array[0][0][0];  array[0][1] == array[0][0][1];  array[0][1] == &array[0][1][0];

 Câu 10: Viết nguyên mẫu của một hàm nhận một mảng con trỏ đến kiểu char làm đối số, và giá trị trả về có kiểu void.

 Câu 11: Theo cách viết của câu 10, ta có thể biết được số

phần tử của mảng được truyền kô?

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Câu 12: Con trỏ đến hàm là gì?  Câu 13: Viết khai báo con trỏ đến một hàm mà hàm đó có giá trị trả về kiểu char, nhận đối số là một mảng con trỏ đến kiểu char.

 Câu 14: Ta viết khai báo con trỏ ở câu 12 như vậy có đúng

không? char *ptr(char *x[]);

 Câu 15: Cho biết ý nghĩa của các khai báo sau:

 int *var1;  int var2;  int **var3;

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Câu 16: Cho biết ý nghĩa của các khai báo sau:

 int a[3][12];  int (*b)[12];  int *c[12];

 Câu 17: Cho biết ý nghĩa của các khai báo sau:

 char *z[10];  char *y(int field);  char (*x)(int field);

Phương pháp lập trình - Con trỏ

Bài tập lý thuyết

 Câu 18: Viết khai báo con trỏ func đến một hàm nhận đối

số là một số nguyên và trả về giá trị kiểu float.

 Câu 19: Viết khai báo một mảng con trỏ đến hàm. Các hàm nhận một chuỗi ký tự làm tham số và trả về giá trị kiểu nguyên. Ta có thể sử dụng mảng này để làm gì?  Câu 20: Viết câu lệnh khai báo một mảng 10 con trỏ đến

kiểu char.

Phương pháp lập trình - Con trỏ

Bài tập thực hành

 Câu 21: Tìm lỗi sai trong đoạn lệnh sau

 int x[3][12];  int *ptr[12];  ptr = x;

 Câu 22: 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.

Phương pháp lập trình - Con trỏ

Bài tập thực hành

 Câu 23: 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.

 Câu 24: Sửa lại bài tập 22 để người sử dụng có thể lựa

chọn cách sắp xếp theo thứ tự tăng hay giảm dần.

Phương pháp lập trình - Con trỏ

Bài tập thực hành

 Câu 25: 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.

 Câu 26: Sử dụng con trỏ hàm để viết các hàm sắp xếp sau

 Tăng dần  Giảm dần  Dương giảm rồi âm tăng, cuối cùng là số 0  …

Phương pháp lập trình - Con trỏ