1. Trang chủ
  2. » Thể loại khác

6. Tuan 9-10 Con tro va mang dong ppsx

55 483 1

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 55
Dung lượng 185,48 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Dữ liệu kiểu con trỏ• Bộ nhớ RAM là mỗi chuỗi các ô nhớ có địa chỉ xác định được đánh số từ 0x0000 • Địa chỉ của một ô nhớ là vị trí của nó trong bộ nhớ RAM • Địa chỉ tối đa phụ thuộc và

Trang 1

BÀI GIẢNG

KỸ THUẬT LẬP TRÌNH

Nguyễn Duy Đỉnh

-1

Trang 2

Tuần 9 - 10 – Con trỏ

• Dữ liệu kiểu con trỏ

• Các phép toán với con trỏ

• Các lệnh cấp phát bộ nhớ

• Mảng động

• Truyền tham số mảng

2

Trang 3

Dữ liệu kiểu con trỏ

• Bộ nhớ RAM là mỗi chuỗi các ô nhớ có địa chỉ xác định được đánh số từ 0x0000

• Địa chỉ của một ô nhớ là vị trí của nó trong bộ nhớ RAM

• Địa chỉ tối đa phụ thuộc vào dung lượng nhớ Ví dụ – RAM 256 Bytes có dải địa chỉ từ 0x00 đến 0xFF

– RAM 1M có dải địa chỉ từ 0x0 0000 đến 0xF FFFF – RAM 1G có dải địa chỉ từ 0x0000 0000 đến 0x3FFF FFFF –

• Bản đồ nhớ là cách bố trí các ô nhớ trong bộ nhớ RAM 3

Trang 4

 Ví dụ, bản đồ nhớ của RAM 256 Bytes

 Địa chỉ ô nhớ màu vàng - 0x0B; đỏ - 0x14; xanh – 0x29

 Mỗi ô nhớ chiếm 1 Byte bộ nhớ

Dữ liệu kiểu con trỏ

0x00 0x10 0x20

0xF0 4

Trang 5

Dữ liệu kiểu con trỏ

• Các biến khi được khai báo sẽ chiếm một ví trí nào đó trong bộ nhớ RAM

• Tên biến chính là cách mà ta gọi tên của ô nhớ

• Chẳng hạn, ô nhớ màu vàng, màu đỏ, màu xanh là cách mà ta gọi tên các

Trang 6

Do biến a, b kiểu short int là kiểu dữ liệu 2 bytes nên mỗi biến a, b

sẽ chiếm 2 bytes (2 ô) trong bản đồ nhớ của bộ nhớ RAM

 Chẳng hạn, giả sử bộ nhớ cấp cho a bắt đầu từ 0x0A

 bộ nhớ cấp cho b bắt đầu từ 0x12

Dữ liệu kiểu con trỏ

0x00 0x10 0x20

0xF0 6

Trang 7

 Do biến a, b kiểu short int là kiểu dữ liệu 2 bytes nên mỗi biến

a, b sẽ chiếm 2 bytes (2 ô) trong bản đồ nhớ của bộ nhớ RAM

 Chẳng hạn, giả sử bộ nhớ cấp cho a bắt đầu từ 0x0A

 bộ nhớ cấp cho b bắt đầu từ 0x12

Dữ liệu kiểu con trỏ

0x00 0x10 0x20

7

Trang 8

 Gán a = 0x1234, nghĩa là ghi các giá trị 12 và 34 vào các ô nhớ tương ứng: [0x0A] = 12 và [0x0B] = 34

 Tương tự, gán b = a<<1; //b=2468, nghĩa là ghi các giá trị 24

0xF0 8

Trang 9

 Trên thực tế, khi máy tính thực hiện chương trình, nó chỉ quan tâm đến địa chỉ ô nhớ và làm việc với địa chỉ ô nhớ

 a, b chẳng qua chỉ là tên mà người lập trình gọi ô nhớ sao cho thuận tiện khi lập trình

 Thông thường, ta chỉ quan tâm đến giá trị của các biến a, b mà không cần biết nó ở vị trí nào trong bản đồ nhớ

Dữ liệu kiểu con trỏ

9

Trang 10

 Tuy nhiên, trong rất nhiều trường hợp, địa chỉ và vùng bộ nhớ chiếm giữ của các biến này cũng rất quan trọng, giúp cho việc thực hiện thuật toán nhanh hơn, thuận tiện hơn, linh hoạt hơn

 Chẳng hạn,

 khi muốn thay đổi giá trị của tham số truyền vào một hàm

 khi muốn làm việc với các ô nhớ liên tiếp nhau trong bộ nhớ RAM (mảng) hoặc trên ổ cứng (tệp)

 khi muốn làm việc với danh sách liên kiết

 khi muốn sử dụng bộ nhớ một cách linh hoạt

Dữ liệu kiểu con trỏ

10

Trang 11

 Từ đó, Visual C định nghĩa kiểu dữ liệu con trỏ: là kiểu dữ liệu dùng để lưu địa chỉ của các đối tượng khác

 Khai báo: thêm dấu * trước tên biến Ví dụ

int a, *b;

 Khai báo biến a kiểu nguyên, biến b là biến con trỏ kiểu nguyên, lưu địa chỉ của ô nhớ nào đó mà giá trị của ô nhớ đó là kiểu int

Hay nói cách khác, biến b là một con trỏ, trỏ tới các phần tử có

kiểu int

Dữ liệu kiểu con trỏ

11

Trang 12

 Ví dụ khai báo biến kiểu con trỏ:

float *x, *y;

FILE *f;

char *s;

 x, y là các con trỏ kiểu float, lưu địa chỉ của các ô nhớ mà giá trị

của các ô nhớ đó có kiểu kiểu float, hay x, y trỏ tới các phần tử

kiểu float

f con trỏ kiểu file, trỏ tới các tệp

s con trỏ kiểu char, trỏ tới các phần tử kiểu char

Dữ liệu kiểu con trỏ

12

Trang 13

 Các biến thông thường khi được khai báo sẽ nằm trong bộ nhớ Stack (có dung lượng mặc định là 1M Bytes)

 Các biến con trỏ khi được khai báo sẽ nằm trong bộ nhớ Stack

 Bản thân mỗi biến con trỏ chiếm 4 Bytes bộ nhớ

 Tuy nhiên, vùng nhớ mà biến con trỏ quản lý là vùng nhớ nằm trong bộ nhớ Heap

 Bộ nhớ Heap thường lớn hơn nhiều so với bộ nhớ Stack

Dữ liệu kiểu con trỏ

13

Trang 14

 Toán tử & và toán tử *

 Toán tử &: chỉ địa chỉ của một biến trong bản đồ nhớ

 Toán tử *: chỉ giá trị của ô nhớ có địa chỉ mà biến con trỏ trỏ tới

 Ví dụ:

 int a, *b;

 &a: địa chỉ của ô nhớ mà biến a chiếm trong bộ nhớ

 *b: giá trị của ô nhớ mà con trỏ b trỏ tới

 &b: địa chỉ của ô nhớ mà biến con trỏ b chiếm

Các phép toán với con trỏ

14

Trang 15

 Ví dụ:

int a, b, *c;

a = 0x1234;

b = a << 1;

 Với bản đồ nhớ dưới đây thì ta có: &a = 0x0A; &b = 0x12;

 Giả sử con trỏ c đang trỏ tới ô nhớ 0x12 thì: *c = 2468;

Các phép toán với con trỏ

0x20

15

Trang 16

 Phép gán:

short int *a, *b, c, d, *e;

a = &c; //Con trỏ a trỏ tới địa chỉ của biến c

b = &d; //Con trỏ b trỏ tới địa chỉ của biến d

*a = 0x1234; //Giá trị của ô nhớ mà a trỏ tới = 0x1234

//Tương đương với lệnh gán c = 0x1234

*b = *a; //Giá trị của ô nhớ mà b trỏ tới bằng giá //trị ô nhớ mà a trỏ tới bằng 0x1234

//Tương đương với lệnh gán d = 0x1234

e = a;//Con trỏ e trỏ tới địa chỉ mà con trỏ a //đang trỏ tới, tức là trỏ tới địa chỉ của //biến c

a = &d; //Con trỏ a trỏ tới địa chỉ của biến d, //không trỏ tới địa chỉ của biến c nữa

*e = d+1; //Giá trị của ô nhớ mà con trỏ e đang trỏ //đến tăng 1 đơn vị, tương đương với việc //tăng c 01 đơn vị = 0x1235

Các phép toán với con trỏ

16

Trang 17

 Ví dụ: c ở địa chỉ 0x0A, d ở địa chỉ 0x12

short int *a, *b, c, d, *e;

17

Trang 18

Giả sử c ở địa chỉ 0x0A, d ở địa chỉ 0x12

short int *a, *b, c, d, *e;

0xF0 18

Trang 19

Giả sử c ở địa chỉ 0x0A, d ở địa chỉ 0x12

short int *a, *b, c, d, *e;

a = &c; b = &d;

 Dự đoán kết quả của phép toán sau?

a ; //a trỏ tới ô nhớ có địa chỉ 0x08

b ++; //b trỏ tới ô nhớ có địa chỉ 0x14

a -= 2; //a trỏ tới ô nhớ có địa chỉ 0x04

b += 3; //b trỏ tới ô nhớ có địa chỉ 0x1A

Phép tịnh tiến: a += i hoặc b -= i sẽ dịch chuyển con trỏ

a hoặc b đi (i*sizeof(*a)) ô nhớ trong bản đồ nhớ theo hướng tiến hoặc lùi phụ thuộc vào dấu của phép tịnh tiến

Các phép toán với con trỏ

19

Trang 20

Lưu ý: con trỏ không có phép nhân (*) hoặc chia (/) mà chỉ có

phép gán (=), phép lấy địa chỉ (&), phép lấy giá trị (*) và phép tịnh tiến (+ hoặc -)

Các phép toán với con trỏ

20

Trang 21

 Ta đã gặp một vài trường hợp hàm làm thay đổi giá trị của tham số truyền vào dòng lệnh gọi nó như:

Trang 22

Ví dụ 9.1: Lập trình hàm đổi giá trị 2 biến bất kỳ truyền vào

int a = 2, b = 5;

Swap(a, b);

Ứng dụng truyền tham số cho hàm

 Hàm Swap(x, y) thực hiện việc đảo giá trị của các biến x, y trong nội dung hàm Tuy nhiên, khi hàm kết thúc, giá trị của x, y vẫn không bị thay đổi trong toàn cục

  Nguyên nhân: Do hàm chỉ sử dụng

giá trị của các biến này mà không quan tâm đến nó là biến nào

22

Trang 23

Ví dụ 9.1: Lập trình hàm đổi giá trị 2 biến bất kỳ truyền vào

int a = 2, b = 5;

Ứng dụng truyền tham số cho hàm

 Hàm Swap2(&x, &y) không quan tâm đến tên biến mà quan tâm đến ô nhớ lưu giá trị của biến

 Hàm Swap2(&x, &y) thực hiện việc đảo giá trị của các ô nhớ mà con trỏ

x và y trỏ tới

23

Trang 24

Note:

Việc truyền tham số sẽ làm cho hàm trở nên linh hoạt hơn, không phụ thuộc vào biến toàn cục, do đó, có thể được dùng đi dùng lại trong các project khác nhau mà không phụ thuộc vào project cụ thể.

Việc truyền tham số sẽ đảm bảo tính đóng kín và tính kế

thừa cho hàm

Ứng dụng truyền tham số cho hàm

24

Trang 25

Ví dụ 9.2: Lập trình hàm GPTB2() giải phương trình bậc 2 với

các hệ số và các ẩn được truyền vào dưới dạng tham số

void GPTB2(float a, float b, float c, float *x1, float *x2)

float a, b, c, x1, x2;

GPTB2(a, b, c, &x1, &x2);

Ứng dụng truyền tham số cho hàm

25

Trang 26

Ví dụ 9.3: Lập trình hàm HeBac2() giải hệ 2 phương trình 2 ẩn

với các hệ số và các ẩn được truyền vào dưới dạng tham số

void HeBac2(float a1, float a2, float b1, float b2, float c1,

float c2, float *x, float *y)

float a1, a2, b1, b2, c1, c2, x1, x2;

HeBac2(a1, a2, b1, b2, c1, c2, &x1, &x2);

Ứng dụng truyền tham số cho hàm

26

Trang 27

 Trước khi dùng biến kiểu con trỏ, ta đã chỉ rõ nó trỏ tới phần tử nào, địa chỉ nào trong bộ nhớ  nói cách khác, ta đã khởi tạo cho nó

 Chẳng hạn, ở ví dụ 9.3 trên, bên trong hàm HeBac2() ta đã truyền địa chỉ của các biến x1, x2 tới các con trỏ *x và *y để chỉ

rõ rằng 2 con trỏ x, y trỏ tới ô nhớ của các biến x1 và x2 toàn cục

 Các biến x1, x2 là các biến toàn cục có địa chỉ xác định và không đổi trong bộ nhớ nên vùng nhớ mà con trỏ *x và *y trỏ tới đã xác định

Các lệnh cấp phát bộ nhớ

27

Trang 28

 Tuy nhiên, có những trường hợp người lập trình muốn

 Thêm vào các biến mới trong quá trình chương trình chạy và giải phóng chúng sau khi chúng hết nhiệm vụ

 Sử dụng vùng nhớ độc lập với các vùng nhớ chứa các biến đã khai báo

 Ví dụ, tìm lỗi sai trong đoạn chương trình sau:

int *a, *b, c;

a = &c; //Không có lỗi vì vùng nhớ chứa biến c //(địa chỉ của ô nhớ chứa giá trị biến //c) là đã xác định

*a = 0x1234;

*b = *a;

Các lệnh cấp phát bộ nhớ

28

Trang 29

 Tuy nhiên, có những trường hợp người lập trình muốn

 Thêm vào các biến mới trong quá trình chương trình chạy và giải phóng chúng sau khi chúng hết nhiệm vụ

 Sử dụng vùng nhớ độc lập với các vùng nhớ chứa các biến đã khai báo

 Ví dụ, tìm lỗi sai trong đoạn chương trình sau:

int *a, *b, c;

a = &c; //Không có lỗi vì vùng nhớ chứa biến c // (địa chỉ của ô nhớ chứa giá trị biến //c) là đã xác định

*a = 0x1234;

*b = *a; //Lỗi vì b chưa trỏ vào một vùng nhớ //xác

Các lệnh cấp phát bộ nhớ

29

Trang 30

 Khi mới khai báo biến con trỏ mà chưa chỉ rõ nó được trỏ tới

địa chỉ nào, biến con trỏ sẽ mang giá trị là NULL

 Khi đó, để dùng được con trỏ b, người lập trình cần phải thực

hiện cấp phát động bộ nhớ cho nó, theo nghĩa, cần phải chỉ rõ,

nó sẽ quản lý vùng nhớ nào, kích thước bao nhiêu, ?

 Để thực hiện cấp phát động, trong ANSI C (C chuẩn), ta dùng hàm malloc(), calloc() và realloc()

 Các hàm malloc() và calloc() nằm trong thư viện

stdlib.h nên khi dùng phải khai báo #include <stdlib.h>

Các lệnh cấp phát bộ nhớ

30

Trang 31

 Cú pháp:

void *malloc(size_t size);

void *calloc(size_t nitems, size_t size);

void *realloc(void *ptr, size_t size);

 malloc(): cấp phát vùng nhớ có kích thước size byte

 calloc(): cấp phát vùng nhớ có kích thước nitems*size bytes

 realloc(): cấp phát lại vùng nhớ có kích thước size byte Sau khi gọi lệnh realloc(), con trỏ sẽ chỉ quản lý vùng nhớ có kích thước do hàm realloc() cấp, bất kể trước đó nó được cấp vùng nhớ bao nhiêu

Do các hàm malloc(), calloc() và realloc() có kiểu void nên ta

phải ép nó về các kiểu tương ứng

Các lệnh cấp phát bộ nhớ

31

Trang 32

 Sau đoạn chương trình trên, con trỏ a sẽ trỏ tới một vùng nhớ

có kích thước 4 bytes (sizeof(int))

 Con trỏ b trỏ tới vùng nhớ có kích thước 40 Bytes gồm 10 block

4 Bytes

Các lệnh cấp phát bộ nhớ

32

Trang 35

 Để giải phóng vùng nhớ được cấp phát bằng hàm malloc(), calloc() và realloc() ta dùng hàm free()

Trang 36

 Hàm malloc(), calloc() và realloc() có cách dùng tương đối phức tạp và tương đối giống nhau

 Trong C++/Visual C++, để thực hiện việc cấp phát bộ nhớ, ta

Trang 37

Để thay đổi vùng nhớ đã cấp cho con trỏ, ta dùng lại lệnh new

theo sau bởi kích thước vùng nhớ mới:

new kiểu[kích thước vùng nhớ mới];

Trang 38

 Để thu hồi vùng nhớ cấp cho con trỏ, ta làm như sau:

Trang 39

 Ví dụ: tìm lỗi sai trong đoạn code sau:

int *a, *b = new int;

Trang 40

Mảng cũng là một con trỏ đặc biệt, gọi là con trỏ mảng

 Khi ta khai báo một mảng, ví dụ:

int a[10];

thì bản thân a là con trỏ mảng

Vùng nhớ mà con trỏ a quản lý sẽ là số phần tử * sizeof(int),

(trong trường hợp này, a sẽ quản lý 40 bytes bộ nhớ)

Con trỏ a khi đó sẽ luôn luôn trỏ tới phần tử đầu tiên trong

mảng (tức là *a luôn bằng a[0])

Mảng động

40

Trang 41

Mảng a[10] do đó gọi là mảng tĩnh, nghĩa là số phần tử của

mảng không thể thêm bớt trong quá trình chương trình chạy

Con trỏ mảng a gọi là con trỏ hằng, do địa chỉ mà nó trỏ tới

không thể thay đổi trong quá trình chạy (luôn luôn trỏ tới a[0])

 Do đó, lệnh gán con trỏ a để trỏ tới địa chỉ khác hay tịnh

tiến/lùi con trỏ a đều không hợp lệ

Mảng động

41

Trang 42

 Tuy nhiên, trong nhiều ứng dụng, chẳng hạn:

 khi không biết chắc chắn số phần tử của mảng

 khi số phần tử thường xuyên thay đổi

 Khi đó, nảy sinh nhu cầu thêm bớt một/một số phần tử của mảng, thậm chí xóa hẳn mảng ra khỏi bộ nhớ trong thời gian chạy chương trình (Run Time)

  Dùng cấu trúc mảng động

Mảng động

42

Trang 43

 Khai báo mảng động giống như khai báo một con trỏ bình thường:

kiểu * tên_con_trỏ;

 Sau đó, để cấp phát động cho con trỏ mảng, ta dùng lệnh:

tên_con_trỏ = new kiểu[số_phần_tử];

khi đó, tên_con_trỏ sẽ quản lý một vùng nhớ có kích thước bằng kiểu*số_phần_tử

Mảng động

43

Trang 44

 Ví dụ, sau đoạn lệnh sau:

 Lệnh sau sẽ gán khởi tạo các phần tử của mảng bằng 0

for (i = 0; i<10; i++) a[i] = 0;

Mảng động

44

Trang 45

 Ví dụ, sau đoạn lệnh sau:

int *a;

a = new int[10];

Ngoài ra, ngay sau khi khởi tạo bằng lệnh new, con trỏ mảng

động a sẽ trỏ tới phần tử đầu tiên của mảng

 Ta có thể thực hiện phép tịnh tiến/lùi trên con trỏ a Lưu ý, khi

đó, phần tử mà con trỏ a trỏ tới cũng sẽ tịnh tiến/lùi theo

Mảng động

45

Trang 46

 Ví dụ:

int *a, i;

a = new int[10]; //a trỏ tới phần tử thứ 1

for (i=0; i<10; i++) a[i] = i;

a++; //a trỏ tới phần tử thứ 2

a += 5; //a trỏ tới phần tử thứ 7

*(a – 3) = 15; //phần tử thứ 4 gán bằng 15

for (i = 0; i<3; i++)

{*a = 2*i; a++;} //Kết thúc vòng lặp a trỏ

Trang 47

Ví dụ 9.4: Viết chương trình trên đọc file Data.in có cấu trúc

gồm 2 dòng:

 Dòng đầu chứa tổng số phần tử n

 Các dòng tiếp theo chứa n số nguyên

 Số phần tử n là không biết trước và cũng không có giới hạn cụ thể

 Sau khi đọc, in ra màn hình các phần tử của mảng

Mảng động

47

Trang 48

Tìm lỗi sai trong chương trình :

Trang 49

Chương trình được sửa lại

Trang 50

 Khi lời gọi hàm được gọi, máy tính sẽ cấp phát bộ nhớ các tham

số và các biến địa phương của hàm

 Các tham số và biến của hàm đều được khai báo trong Stack

 Nếu số lượng tham số và biến quá nhiều sẽ nhanh chóng làm tràn Stack sinh ra lỗi chương trình

 Đặc biệt, nếu ta truyền tham số kiểu mảng thì ngoài biến mảng

đã có ở toàn cục, máy tính sẽ cấp phát thêm một vùng nhớ tương đương nữa cho tham số mảng truyền vào chương trình con  làm cạn kiệt Stack

Truyền tham số mảng

50

Trang 51

 Khi gọi hàm BubbleSort(a), máy tính ngoài 40000 Bytes đã cấp cho mảng a, sẽ phải cấp thêm 40000 Bytes nữa cho bộ nhớ đệm của hàm BubbleSort()

Truyền tham số mảng

51

Trang 52

 Trường hợp này, ta có thể truyền tham số kiểu con trỏ, sau đó lập trình với mảng động như bình thường Ví dụ:

BubbleSort(a);

 Lúc này, khi gọi hàm, chương trình chỉ phải cấp thêm 4 Bytes trong bộ nhớ Stack để lưu biến con trỏ X  Sử dụng bộ nhớ hiệu quả hơn

Truyền tham số mảng

52

Trang 53

Ví dụ 9.5: Hãy lập trình lại thuật toán Selection Sort dùng tham

số truyền vào là mảng cần sắp xếp và số phần tử của mảng

void SelectionSort(int *a, int n)

int X[10000], m;

Truyền tham số mảng

53

Trang 54

Ví dụ 9.6: Cho n dãy số a0, a1, , an Trọng số của một dãy là số có số lần

xuất hiện nhiều nhất lớn nhất trong dãy đó Ví dụ, cho 3 dãy:

 a0: 1, 3, 2, 3, 4, 2, 5, 6, 3, 2  trọng số là 3

 a1: 3, 2, 6, 4, 5, 1, 2, 1, 4, 2, 5, 6, 4 trọng số là 4

 a2: 6, 6, 7  trọng số là 6

 Hãy tìm dãy có trọng số lớn nhất

Input: file “TrongSo.in” gồm:

 Dòng đầu tiên ghi số dãy số n

 n dòng tiếp theo mỗi dòng ghi

 Số đầu tiên ghi số phần tử của dãy thứ i

 Các số còn lại ghi các số thuộc dãy thứ i

Output: file “TrongSo.out” gồm 1 dòng ghi 2 số cách nhau bởi dấu cách:

 Số thứ tự của dãy có trọng số lớn nhất

 Trọng số lớn nhất của dãy tìm được

Truyền tham số mảng

54

Ngày đăng: 10/08/2014, 13:22

TỪ KHÓA LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w