1. Trang chủ
  2. » Cao đẳng - Đại học

Bài giảng kỹ thuật lập trình chương 1 Pointer

101 5 0

Đ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 101
Dung lượng 1,54 MB

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

Nội dung

Con Trỏ (Pointer) Trịnh Tấn Đạt Khoa CNTT Đại Học Sài Gòn Email trinhtandatsgu edu vn Website https sites google comsitettdat88 Nội dung ▪ Biến tĩnh vs Biến động ▪ Con trỏ ▪ Các phép toán trên con trỏ ▪ Con trỏ và mảng một chiều ▪ Cấp phát vùng nhớ động ▪ Con trỏ cấp 2 ▪ Con trỏ và mảng nhiều chiều ▪ Mảng con trỏ ▪ Con trỏ hằng, void ▪ Con trỏ hàm (option) 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 đị.

Trang 1

Con Trỏ (Pointer)

Trịnh Tấn Đạt

Khoa CNTT - Đại Học Sài Gòn

Email: trinhtandat@sgu.edu.vn

Website: https://sites.google.com/site/ttdat88/

Trang 3

Khai báo biến trong C

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

▪ 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 = 5; // Giả sử địa chỉ lưu trữ biến a là 0x6

int B[3]; // Giả sử địa chỉ lưu trữ biến mảng B là 0x2

0x2 0x6 0xA

0x2 0x6 0xA 0xE

Trang 4

o Sau khi sử dụng xong có thể giải phóng để tiết kiệm chỗ trong bộ nhớ.

▪ Biến (variable): Biến là một ô nhớ đơn lẻ hoặc một vùng nhớ được hệ điều hành cấpphát cho chương trình C++ nhằm để lưu trữ giá trị vào bên trong vùng nhớ đó

Ví dụ: int m; // một vùng nhớ có kích thước 4 bytes sẽ được cấp phát

Trang 5

Virtual memory & Physical memory

❖ Virtual memory & Physical

memory

▪ Chúng ta chỉ có thể trỏ đến

memory) trên máy tính, còn

việc truy xuất đến bộ nhớ vật

lý (physical memory) từ bộ

nhớ ảo phải được thực hiện

bởi thiết bị phần cứng có tên

là Memory management unit

(MMU) và một chương trình

định vị địa chỉ bộ nhớ gọi

là Virtual address space

Trang 6

Variable address & address-of operator

▪ Địa chỉ của biến (variable address) mà chúng ta nhìn thấy thật ra chỉ là những giá

trị đã được đánh số thứ tự đặt trên Virtual memory

▪ Để lấy được địa chỉ ảo của biến trong chương trình, chúng ta sử dụng toán tử lấy

địa chỉ (address-of operator) ‘ &’ đặt trước tên biến

Ví dụ:

int x = 5;

cout << x; // print the value of variable x

cout << &x; // print the memory address of variable x

Trang 7

▪ Tham chiếu (Reference): Mục đích của tham chiếu trong C++ là tạo ra một biến

khác có cùng kiểu dữ liệu nhưng sử dụng chung vùng nhớ với biến được tham

chiếu đến

▪ Ví dụ:

Như vậy, mọi hành vi thay đổi giá trị của i_ref đều tác động trực tiếp đến i1.

Lưu ý: Biến tham chiếu sẽ có địa chỉ cố định sau khi khởi tạo Chúng ta không thể tham chiếu lại lần nữa.

Lưu ý: address-of operator và reference dùng chung 1 ký hiệu

&

Trang 8

Dereference operator

▪ Toán tử trỏ đến (dereference operator) hay còn gọi là indirection operator (toán tử

điều hành gián tiếp) được kí hiệu bằng dấu sao "*" cho phép chúng ta lấy ra giá trị của vùng nhớ có địa chỉ cụ thể.

▪ Ví dụ:

Trang 9

Dereference operator

▪ Ngoài việc truy xuất giá trị trong vùng nhớ của một địa chỉ cụ thể, toán tử trỏ đến

(dereference operator) còn có thể dùng để thay đổi giá trị bên trong vùng nhớ đó.

Trang 10

Dereference operator

▪ Toán tử trỏ đến cho phép chúng ta thao tác trực tiếp trên Virtual memory mà không

cần thông qua định danh (tên biến)

▪ Khác với tham chiếu (reference), toán tử trỏ đến (dereference operator) không tạo

ra một tên biến khác, mà nó truy xuất trực tiếp đến vùng nhớ có địa chỉ cụ thểtrên Virtual memory

Trang 11

Con trỏ (Pointer)

▪ Địa chỉ của biến là một con số, có thể tạo biến khác để lưu địa chỉ của biến này.

Con trỏ (pointer) là một biến được dùng để lưu trữ địa chỉ của biến khác.

Khác với tham chiếu, con trỏ là một biến có địa chỉ độc lập so với vùng nhớ mà nó trỏ đến,

nhưng giá trị bên trong vùng nhớ của con trỏ chính là địa chỉ của biến (hoặc địa chỉ ảo) mà nó trỏ tới.

❖ Kiểu con trỏ cho phép:

o Truyền tham số kiểu địa chỉ

o Biểu diễn các kiểu, cấu trúc dữ liệu động

o Lưu trữ dữ liệu trong vùng nhớ heap

Trang 12

Khai báo biến con trỏ

▪ Khai báo con trỏ trong C/C++: Kiểu con trỏ phải được định nghĩa trên một kiểu cơ

sở đã được định nghĩa trước đó (theo kiểu dữ liệu của biến mà con trỏ trỏ tới)

▪ Ví dụ:

int x; // x là kiểu int

int * px; // px là con trỏ đến vùng nhớ kiểu int

int * p1 , * p2 ; // // p1 và p2 là con trỏ đến vùng nhớ kiểu int

float * pf; // pf là con trỏ đến vùng nhớ kiểu float

double * pd; // pd là con trỏ đến vùng nhớ kiểu double

char c, d, * pc; // c và d kiểu char ; pc là con trỏ đến vùng nhớ kiểu char

<kiểu_cơ_sở> * <Tên_con_trỏ> ;

operator) , nó là cú pháp được ngôn ngữ C/C++ qui định

Trang 13

Con trỏ (Pointer)

▪ Lưu ý: Kiểu dữ liệu của con trỏ không mô tả giá trị địa chỉ được lưu trữ bên trong

con trỏ, mà kiểu dữ liệu của con trỏ dùng để xác định kiểu dữ liệu của biến mà nó trỏ đến trên bộ nhớ ảo.

Trang 14

Con trỏ và toán tử lấy địa chỉ

❖ Con trỏ và toán tử lấy địa chỉ & (address-of operator)

▪ “&”: toán tử lấy địa chỉ của 1 biến

▪ Địa chỉ của tất cả các biến trong chương trình đều đã được chỉ định từ khi khai báo

▪ Gán giá trị cho con trỏ: Giá trị mà biến con trỏ lưu trữ là địa chỉ của biến khác có

cùng kiểu dữ liệu với biến con trỏ

Do đó, chúng ta cần sử dụng address-of operator để lấy ra địa chỉ ảo của biến rồi mới

gán cho con trỏ được Lúc này, biến ptr sẽ lưu trữ địa chỉ ảo của biến value

Trang 15

Con trỏ và toán tử lấy địa chỉ

▪ Ví dụ:

int a = 5;

int *p; // khai báo biến con trỏ p

p = &a; // gán địa chỉ của biến a cho p ( hay p trỏ tới a)

int *q = &a ; // khởi tạo giá trị cho con trỏ q trỏ tới vùng nhớ của a

Trang 16

Con trỏ (Pointer)

▪ Ví dụ:

Trang 17

Con trỏ (Pointer)

▪ Ví dụ:

Lý do mà chúng ta gán được địa chỉ của biến value cho con trỏ kiểu int (int *) là vì address-of operator của một biến kiểu int trả về giá trị kiểu con trỏ kiểu int (int *).

Trang 18

Con trỏ và toán tử trỏ đến

❖ Con trỏ và toán tử trỏ đến *

(dereference operator)

▪ “*”: toán tử truy xuất giá trị của

vùng nhớ được quản lý bởi con trỏ

#include <stdio.h>

#include <iostream> using namespace std;

char g = 'z';

int main() {

}

Trang 19

Ví dụ:

int a = 5; // giả sử biến a được lưu trữ tại địa chỉ 0xB

int *p; // giả sử biến con trỏ p được lưu trữ tại địa chỉ 0xF

Trang 20

Con trỏ và toán tử gán

❖ Con trỏ và toán tử gán “=”

▪ Khi có hai con trỏ cùng kiểu thì chúng ta có thể gán trực tiếp mà không cần sử

dụng toán tử lấy địa chỉ address-of operator.

Trang 21

Tham chiếu (reference) không thể thay đổi địa chỉ sau lần tham chiếu đầu tiên.

Trang 22

Địa chỉ của biến mảng

▪ Ví dụ :

Trang 23

Lưu ý

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

▪ 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

▪ Con trỏ trong ngôn ngữ C/C++ vốn không an toàn Nếu sử dụng con trỏ không

hợp lý có thể gây lỗi chương trình

Trang 24

Lưu ý

❖ Con trỏ chưa được gán địa chỉ

▪ Biến con trỏ có thể không cần khởi tạo giá trị ngay khi khai báo Nhưng thực hiện

truy xuất giá trị của con trỏ bằng dereference operator khi chưa gán địa chỉ cụ thểcho con trỏ, chương trình có thể bị đóng bởi hệ điều hành

Trang 25

Con trỏ NULL

▪ Do đó, khi khai báo con trỏ nhưng chưa có địa chỉ khởi tạo cụ thể, chúng ta nên gán

cho con trỏ giá trị NULL (giá trị NULL trong thư viên <stdlib.h>).

▪ Giá trị đặc biệt NULL để chỉ rằng con trỏ không quản lý vùng nào Giá trị này

thường được dùng để chỉ một con trỏ không hợp lệ

#include <stdlib.h>

#include <iostream>

using namespace std;

int main() {

Trang 27

Biến Giá trị i

j k p q

Trang 28

cout << *(&n) << endl;

cout << &p <<endl;

Trang 29

Truyền đối số cho hàm:

▪ Các cách truyền đối số cho hàm:

int a = 1; b = 2;

hoanvi_2(a, b);

cout<<a << b;

}

Trang 30

Con trỏ (Pointer)

Con trỏ - Truyền tham số địa chỉ (cho hàm)

#include <iostream>

using namespace std;

void change_2(int &); // dung tham chieu

void change_1(int *); // con tro truyen tham so dia chi

int main()

{

int var1 = 5;

int var2 = 5;

change_1(&var1); // dung con tro truyen tham so dia chi

cout << "main: change_1: var = " << var1 <<endl;

change_2(var2); // dung tham chieu

cout << "main: change_1: var = " << var2 <<endl;

return 0;

}

void change_1(int *v) {

Trang 32

Phép toán trên con trỏ

❖ Con trỏ và toán tử “+, - ” với số nguyên

cout <<"p+2 = " << p+2 << endl;cout <<"p+3 = " << p+3 << endl;cout <<"q - p = " << q-p << endl;return 0;

}

Trang 33

Con trỏ và Mảng 1 chiều

▪ Biến kiểu mảng là địa chỉ tĩnh của một vùng nhớ, được xác định khi khai báo,

không thay đổi trong suốt chu kỳ sống

▪ Biến con trỏ là địa chỉ động của một vùng nhớ, được xác định qua phép gán địa chỉ

khi chương trình thực thi

Trang 34

Con trỏ và Mảng 1 chiều

❖ Mảng 1 chiều: int arr[10];

▪ Tên mảng arr là một hằng con trỏ

➔ arr  &arr[0]

Trang 35

Con trỏ và Mảng 1 chiều

▪ Địa chỉ của

mảng một chiều

và các phần tửtrong mảng mộtchiều

Ví dụ:

int arr[] = { 32, 13, 66, 11, 22 };

//show address of arr in virtual memory

cout << &arr << endl;

//show address of the first element of arr

cout << &arr[0] << endl;

cout << arr << endl;

// address

cout << arr << endl;

cout << arr + 1 << endl;

cout << arr + 2 << endl;

//value

cout << *(arr) << endl;

cout << *(arr + 1) << endl;

cout << *(arr + 2) << endl;

▪ Các cách viết sau đây

là tương đương

o arr  &arr[0]  &arr

o arr+i  &arr[i]

o *(arr+i)  arr[i]

Trang 36

cout << *(ptr - 1) << endl; //access the second element of arr

cout << *(ptr + 2) << endl; //access the last element of arr

// sai : cout << *(arr-1) ; không dùng phép toán “-” cho biến kiểu mảng

• Lưu ý: Truy xuất đến phần tử thứ n của mảng (không sử dụng biến mảng)

Trang 37

Con trỏ và Mảng 1 chiều

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

int arr[] = { 3, 5, 65, 23, 11 };

int *ptr = arr; //ptr point to &arr[0] // arr = ptr : SAI

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

Trang 38

Lưu ý:

▪ 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

Trang 39

Con trỏ cấu trúc

▪ Con trỏ có thể dùng để trỏ tới một kiểu dữ liệu kiểu cấu trúc (struct)

▪ Để truy xuất các thành phần trong cấu trúc thông qua con trỏ có 02 cách:

▪ Ví dụ:

typedef struct PHANSO {

int tu, mau;

Trang 41

Cấp phát bộ nhớ

▪ Vùng nhớ stack:

o Khai báo các biến, mảng hay các tham số trong

hàm, kể cả hàm main (gọi là biến cục bộ) ->

được lưu trong vùng nhớ stack

o Khi kết thúc hàm, vùng nhớ của biến này sẽ tự

động bị thu hồi

o Kích thước vùng nhớ stack khá hạn chế, giả sử

bạn muốn khai báo một mảng có kích thước 1

triệu phần tử hay lớn hơn, chương trình có thể

sẽ bị crash tuỳ theo dung lượng RAM mà bạn

char ch_array_1[1024 * 1000];

char ch_array_2[1024 * 10000]; // tràn vùng nhớ Stack báo lỗi

Trang 42

sẽ gây ra hiện tượng “rò rỉ” bộ nhớ (memory-leaking).

o Kích thước vùng nhớ heap tương đối thoải mái, có thể khai báo được mảng mới kíchthước lớn (tuy nhiên vẫn phụ thuộc vào dung lượng của RAM)

Trang 43

Cấp phát bộ nhớ

❖ Cấp phát bộ nhớ tĩnh vs cấp phát bộ nhớ động

▪ Cấp phát bộ nhớ tĩnh: được áp dụng cho biến static và biến toàn cục.

o Vùng nhớ của các biến này được cấp phát ngay khi chạy chương trình

o Kích thước của vùng nhớ được cấp phát phải được cung cấp tại thời điểm biên dịchchương trình

o Đối với việc khai báo mảng một chiều, đây là lý do tại sao số lượng phần tử là hằng số

Ví dụ : int i=10;

double Arr[1000]; // có thể dẫn đến dư thừa khi không dùng hết 1000 ô nhớ hoặckhông thể mổ rộng khi muốn dùng hơn 1000 phần tử

Trang 44

o Các vùng nhớ được cấp phát sẽ được thu hồi khi chương trình đi ra khỏi một khối lệnh.

o Kích thước vùng cần cấp phát cũng phải được cung cấp rõ ràng.

Trang 45

Cấp phát bộ nhớ động

▪ Có thể chỉ định vùng mới cho 1 con trỏ quản lý bằng các lệnh hàm malloc , calloc

(của C) hoặc toán tử new (của C++)

▪ Vùng nhớ do lập trình viên chỉ định phải được giải phóng bằng hàm free (của C)

hoặc toán tử delete (của C++))

Ví dụ: int *i = new int;

double *Arr = new double[n]; // nhập vào giá trị n cụ thể

Trang 46

Cấp phát bộ nhớ động

▪ Hàm malloc (memory allocation) và calloc (contiguous allocation) cho phép cấp phát

các vùng nhớ ngay trong lúc chạy chương trình

Trang 47

trả về kích cỡ của các kiểu dữ liệu trong C

trả về kích cỡ của các con trỏ tới các kiểu dữ liệu khác nhau

cout<< sizeof(char*); // 4

cout<< sizeof(int*); // 4

cout<< sizeof(float*); // 4

cout<< sizeof(double*); // 4

Trang 48

Ép kiểu dữ liệu cho con trỏ

Trang 49

Cấp phát bộ nhớ động

▪ calloc: hàm calloc() thực hiện cấp phát bộ nhớ gồm num phần tử và khởi tạo tất cả

các ô nhớ có giá trị bằng 0 (trả về NULL nếu không đủ bộ nhớ)

void* calloc (size_t num, size_t size);

Ví dụ:

int *b = (int *) calloc(10, sizeof( int ));

int *a = (int *) calloc(1, sizeof( int ));

char *s = (char *) calloc(100, sizeof(char ));

Trang 50

Cấp phát bộ nhớ động

▪ Giải phóng vùng nhớ: dùng hàm free() trong C.

▪ Khi thoát khỏi hàm, các biến khai báo trong hàm sẽ “biến mất” Tuy nhiên các vùng

nhớ được cấp phát động vẫn còn tồn tại và được “đánh dấu” là đang “được dùng”

→ bộ nhớ của máy tính sẽ hết

▪ Cú pháp : free(ptr); // ptr là con trỏ

Trang 51

ptr = (int *)malloc(n * sizeof(int)); // cap phat dong

// ptr = (int *)calloc(n, sizeof(int)); // dung calloc

// check if cap phat ko thanh cong

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

cin >> *(ptr + i); // hoac dung cin >> ptr[i]; sum += *(ptr + i); // hoac dung sum += ptr[i];

} cout<<"Tong = " << sum ;

// thu hoi vung nho

free(ptr);

return 0;

}

Trang 52

Cấp phát bộ nhớ động

▪ Mở rộng vùng nhớ dùng realloc()

▪ Nếu việc cấp phát bộ nhớ động không

đủ hoặc cần nhiều hơn mức đã cấp

phát, bạn có thể thay đổi kích thước

của bộ nhớ đã được cấp phát trước đó

int *ptr, i , n1, n2;

cin >> n1; // nhap n1

ptr = (int*) malloc(n1 * sizeof(int));

cout << "Dia chi cua vung nho vua cap phat:" << ptr; cout << "\nNhap lai so luong phan tu: " ;

cin >> n2;

// cap nhat lai vung nho

ptr = (int*) realloc(ptr, n2 * sizeof(int));

cout << "Dia chi cua vung nho vua cap phat:" << ptr; // thu hoi vung nho

Trang 53

Cấp phát bộ nhớ động

▪ Cấp phát động trong C++

o Cấp phát bộ nhớ động dùng toán tử new (Toán tử new được dùng để xin cấp phát vùng nhớ trên

phân vùng Heap của bộ nhớ ảo) (trả về NULL nếu không đủ bộ nhớ).

Cú pháp : một vùng nhớ

<data_type> * tencontro = new <data_type> ;

dãy vùng nhớ liên tục nhau (mảng một chiều)

<data_type> * tencontro = new <data_type> [num_of _elements];

Ví dụ:

int *ptr = new int; // dynamically allocate an integer

double *p_double = new double;

int *p_arr = new int[10]; // cho mảng 10 phần tử

char *c_str = new char [100]; // cho mảng 100 phần tử

❖ Điều khiến cho kỹ thuật Dynamic memory allocation khác với Static memory allocation là số lượng phần tử có thể được cung cấp trong khi chương trình đang chạy Ví dụ:

Trang 54

Cấp phát bộ nhớ động

▪ Cấp phát động trong C++

o Giải phóng bộ nhớ dùng toán tử delete

• Khi không muốn sử dụng tiếp vùng nhớ đã được cấp phát cho chương trình

trên Heap, chúng ta nên trả lại vùng nhớ đó cho hệ điều hành.

• Thật ra khi chương trình kết thúc, tất cả vùng nhớ của chương trình đều bị hệ điềuhành thu hồi, nhưng chúng ta nên giải phóng vùng nhớ không cần thiết càng sớmcàng tốt

• Để xóa một vùng nhớ, chúng ta cần có một địa chỉ cụ thể, địa chỉ đó được giữ bởicon trỏ sau khi gán địa chỉ cấp phát cho nó:

• Đối với dãy vùng nhớ liên tục được cấp phát trên Heap, chúng ta cần thêm vào toán

tử [ ] để báo với hệ điều hành rằng vùng nhớ đã được cấp phát không dùng cho mộtbiến đơn

int *p = new int;

delete p;

int *p_arr = new int[10];

delete[] p_arr;

Trang 55

Cấp phát bộ nhớ động

▪ Lưu ý:

Cấp phát bộ nhớ tĩnh Cấp phát bộ nhớ động

int Arr [100]; // khai báo mảng Arr

Nhập vào n và các phân tử của mảng

-Không thề nhập n > 100

-Nếu n < 100 - > dư thừa tài nguyên

int Arr[n]; // Wrong

Nhập vào n và các phân tử của mảng int *Arr = new int[n];

- Hạn chế sự lãng phí tài nguyên

- Có thể mở rộng vùng nhớ khi thực thi chương trình.

Ngày đăng: 28/04/2022, 06:26

TỪ KHÓA LIÊN QUAN

TRÍCH ĐOẠN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w