Diễn Đàn Teen Việt -Thế Giới 9x pro
Bạn có muốn phản ứng với tin nhắn này? Vui lòng đăng ký diễn đàn trong một vài cú nhấp chuột hoặc đăng nhập để tiếp tục.

giáo trình ngôn ngữ lập trinh C ++ <phần 3>

Go down

giáo trình ngôn ngữ lập trinh C ++ <phần 3> Empty giáo trình ngôn ngữ lập trinh C ++ <phần 3>

Bài gửi by luongbv93 25th November 2011, 09:14

CHƯƠNG 2

CON TRỎ VÀ MẢNG

Nội dung của chương này tập trung trình bày các vấn đề cơ bản liên quan đến các thao tác trên
kiểu dữ liệu con trỏ và mảng trong C++:

• Khái niệm con trỏ, cách khai báo và sử dụng con trỏ.

• Mối quan hệ giữa con trỏ và mảng

• Con trỏ hàm

• Cấp phát bộ nhớ cho con trỏ

2.1 KHÁI NIỆM CON TRỎ

2.1.1 Khai báo con trỏ

Con trỏ là một biến đặc biệt chứa địa chỉ của một biến khác. Con trỏ có cùng kiểu dữ liệu với kiểu
dữ liệu của biến mà nó trỏ tới. Cú pháp khai báo một con trỏ như sau:

<Kiểu dữ liệu> *<Tên con trỏ>;

Trong đó:

• Kiểu dữ liệu: Có thể là các kiểu dữ liệu cơ bản của C++, hoặc là kiểu dữ liệu có cấu trúc,
hoặc là kiểu đối tượng do người dùng tựđịnh nghĩa.

• Tên con trỏ: Tuân theo qui tắc đặt tên biến của C++:

- Chỉđược bắt đầu bằng một kí tự (chữ), hoặc dấu gạch dưới “_”.

- Bắt đầu từ kí tự thứ hai, có thể có kiểu kí tự số.

- Không có dấu trống (space bar) trong tên biến.

- Có phân biệt chữ hoa và chữ thường.

- Không giới hạn độ dài tên biến.

Ví dụ, để khai báo một biến con trỏ có kiểu là int và tên là pointerInt, ta viết như sau:

int *pointerInt;

Lưu ý

• Có thể viết dấu con trỏ “*” ngay sau kiểu dữ liệu, nghĩa là hai cách khai báo sau là tương
đương:

int *pointerInt;

int* pointerInt;

• Các cách khai báo con trỏ như sau là sai cú pháp:

*int pointerInt; // Khai báo sai con trỏ

int pointerInt*; // Khai báo sai con trỏ

2.1.2 Sử dụng con trỏ

Con trỏđược sử dụng theo hai cách:

• Dùng con trỏđể lưu địa chỉ của biến để thao tác

• Lấy giá trị của biến do con trỏ trỏđến để thao tác

Dùng con trỏđể lưu địa chỉ của biến

Bản thân con trỏ sẽđược trỏ vào địa chỉ của một biến có cùng kiểu dữ liệu với nó. Cú pháp của
phép gán như sau:

<Tên con trỏ> = &<tên biến>;

Lưu ý

• Trong phép toán này, tên con trỏ không có dấu “*”.

Ví dụ:

int x, *px;

px = &x;

sẽ cho con trỏ px có kiểu int trỏ vào địa chỉ của biến x có kiểu nguyên. Phép toán &<Tên biến>
sẽ cho địa chỉ của biến tương ứng.

Lấy giá trị của biến do con trỏ trỏđến

Phép lấy giá trị của biến do con trỏ trỏđến được thực hiện bằng cách gọi tên:

*<Tên con trỏ>;

Lưu ý

• Trong phép toán này, phải có dấu con trỏ “*”. Nếu không có dấu con trỏ, sẽ trở thành
phép lấy địa chỉ của biến do con trỏ trỏ tới.

Ví dụ:

int x = 12, y, *px;

px = &y;

*px = x;

Quá trình diễn ra như sau:

int x = 12, y, *px; x = 12 y = 0 px null

px = &y; x = 12 y = 0 px

px = x; x = 12 y=x =12 px

con trỏ px vẫn trỏ tới địa chỉ biến y và giá trị của biến y sẽ là 12.

Phép gán giữa các con trỏ

Các con trỏ cùng kiểu có thể gán cho nhau thông qua phép gán và lấy địa chỉ con trỏ:

<Tên con trỏ 1> = <Tên con trỏ 2>;

Lưu ý

• Trong phép gán giữa các con trỏ, bắt buộc phải dùng phép lấy địa chỉ của biến do con trỏ
trỏ tới (không có dấu “*” trong tên con trỏ) mà không được dùng phép lấy giá trị của
biến do con trỏ trỏ tới.
Hai con trỏ phải cùng kiểu. Trong trường hợp hai con trỏ khác kiểu, phải sử dụng các
phương thức ép kiểu tương tự như trong phép gán các biến thông thường có kiểu khác
nhau.

Ví dụ:

int x = 12, *px, *py;

px = &x;

py = px;

int x = 12, *px, *py; x = 12 px null py null

px = &x; x = 12 px py null

py = px; x = 12 px py

con trỏ py cũng trỏ vào địa chỉ của biến x như con trỏ px. Khi đó *py cũng có giá trị 12 giống như
*px và là giá trị của biến x.

Chương trình 2.1 minh hoạ việc dùng con trỏ giữa các biến của một chương trình C++.

Chương trình 2.1

#include <stdio.h>

#include <conio.h>

void main(void){

int x = 12, *px, *py;

cout << ”x = ” << x << endl;

px = &x; // Con trỏ px trỏ tới địa chỉ của x

cout << ”px = &x, *px = ” << *px << endl;

*px = *px + 20; // Nội dung của px là 32

cout << ”*px = *px+20, x = ” << x << endl;

py = px; // Cho py trỏ tới chỗ mà px trỏ: địa chỉ của x

*py += 15; // Nội dung của py là 47

cout << ”py = px, *py +=15, x = ” << x << endl;

}

Trong chương trình 2.1, ban đầu biến x có giá trị 12. Sau đó, con trỏ px trỏ vào địa chỉ của biến x
nên con trỏ px cũng có giá trị 12. Tiếp theo, ta tăng giá trị của con trỏ px thêm 20, giá trị của con
trỏ px là 32. Vì px đang trỏđến địa chỉ của x nên x cũng có giá trị là 32. Sau đó, ta cho con trỏ py
trỏđến vị trí mà px đang trỏ tới (địa chỉ của biến x) nên py cũng có giá trị 32. Cuối cùng, ta tăng
giá trị của con trỏ py thêm 15, py sẽ có giá trị 37. Vì py cũng đang trỏđến địa chỉ của x nên x
cũng có giá trị 37. Do đó, ví dụ 2.1 sẽ in ra kết quả như sau:

x = 12

px = &x, *px = 12

*px = *px + 20, x = 32

py = px, *py += 15, x = 37

2.2 CON TRỎ VÀ MẢNG

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

Mảng một chiều

Trong C++, tên một mảng được coi là một kiểu con trỏ hằng, được định vị tại một vùng nhớ xác
định và địa chỉ của tên mảng trùng với địa chỉ của phần tửđầu tiên của mảng.

Ví dụ khai báo:

int A[5];

thì địa chỉ của mảng A (cũng viết là A) sẽ trùng với địa chỉ phần tửđầu tiên của mảng A (là
&A[0]) nghĩa là:

A = &A[0];

Quan hệ giữa con trỏ và mảng

Vì tên của mảng được coi như một con trỏ hằng, nên nó có thểđược gán cho một con trỏ có cùng
kiểu.

Ví dụ khai báo:

int A[5] = {5, 10, 15, 20, 25};

int *pa = A;

int A[5] = {5, 10, 15, 20, 25}; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

int *pa = A; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

pa

thì con trỏ pa sẽ trỏđến mảng A, tức là trỏđến địa chỉ của phần tử A[0], cho nên hai khai báo sau
là tương đương:

pa = A;

pa = &A[0];

Với khai báo này, thì địa chỉ trỏ tới của con trỏ pa là địa chỉ của phần tử A[0] và giá trị của con trỏ
pa là giá trị của phần tử A[0], tức là *pa = 5;

Phép toán trên con trỏ và mảng

Khi một con trỏ trỏđến mảng, thì các phép toán tăng hay giảm trên con trỏ sẽ tương ứng với phép
dịch chuyển trên mảng.

Ví dụ khai báo:

int A[5] = {5, 10, 15, 20, 25};


int *pa = &A[2];

thì con trỏ pa sẽ trỏđến địa chỉ của phần tử A[2] và giá trị của pa là: *pa = A[2] = 15.

Khi đó, phép toán:

pa = pa + 1;

sẽđưa con trỏ pa trỏđến địa chỉ của phần tử tiếp theo của mảng A, đó là địa chỉ của A[3]. Sau đó,
phép toán:

pa = pa – 2;

sẽđưa con trỏ pa trỏđến địa chỉ của phần tử A[1].

int A[5] = {5, 10, 15, 20, 25}; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

int *pa = &A[2]; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

pa

pa = pa + 1; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

pa

pa = pa - 2; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

pa

Lưu ý:

• Hai phép toán pa++ và *pa++ có tác dụng hoàn toàn khác nhau trên mảng, pa++ là thao
tác trên con trỏ, tức là trên bộ nhớ, nó sẽđưa con trỏ pa trỏđến địa chỉ của phần tử tiếp
theo của mảng. *pa++ là phép toán trên giá trị, nó tăng giá trị hiện tại của phần tử mảng
lên một đơn vị.

Ví dụ:

int A[5] = {5, 10, 15, 20, 25};

int *pa = &A[2];

thì pa++ là tương đương với pa = &A[3] và *pa = 20.

nhưng *pa++ lại tương đương với pa = &A[2] và *pa = 15+1 = 16, A[2] = 16.
int A[5] = {5, 10, 15, 20, 25}; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

int *pa = &A[2]; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

pa

pa ++; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

pa

Nhưng *pa ++; A A[0]=5 A[1]=10 A[2]=16 A[3]=20 A[4]= 25

pa

• Trong trường hợp:

int A[5] = {5, 10, 15, 20, 25};

int *pa = &A[4];

thì phép toán pa++ sẽđưa con trỏ pa trỏđến một địa chỉ không xác định. Lí do là A[4] là
phần tử cuối của mảng A, nên pa++ sẽ trỏđến địa chỉ ngay sau địa chỉ của A[4], địa chỉ
này nằm ngoài vùng chỉ số của mảng A nên không xác định. Tương tự với trường hợp
pa=&A[0], phép toán pa-- cũng đưa pa trỏđến một địa chỉ không xác định.

int A[5] = {5, 10, 15, 20, 25}; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

int *pa = &A[2]; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

pa

pa ++; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 null

pa

pa = &A[0]; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

pa

pa --; null A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25

pa

• Vì mảng A là con trỏ hằng, cho nên không thể thực hiện các phép toán trên A mà chỉ có
thể thực hiện trên các con trỏ trỏđến A: các phép toán pa++ hoặc pa--là hợp lệ, nhưng các
phép toán A++ hoặc A--là không hợp lệ.

Chương trình 2.2a minh hoạ việc cài đặt một thủ tục sắp xếp các phần tử của một mảng theo cách
thông thường.

Chương trình 2.2a

void SortArray(int A[ ], int n){

int temp;

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

for(int j=i+1; j<n; j++)

if(A[i] > A[j]){

temp = A[i];

A[i] = A[j];

A[j] = temp;

}

}

Chương trình 2.2b cài đặt một thủ tục tương tự bằng con trỏ. Hai thủ tục này có chức năng hoàn
toàn giống nhau.

Chương trình 2.2b

void SortArray(int *A, int n){

int temp;

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

for(int j=i+1; j<n; j++)

if(*(A+i) > *(A+j)){

temp = *(A+i);

*(A+i) = *(A+j);

*(A+j) = temp;

}

}

Trong chương trình 2.2b, thay vì dùng một mảng, ta dùng một con trỏđể trỏđến mảng cần sắp
xếp. Khi đó, ta có thể dùng các thao tác trên con trỏ thay vì các thao tác trên các phần tử mảng.

2.2.2 Con trỏ và mảng nhiều chiều

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

Một câu hỏi đặt ra là nếu một ma trận một chiều thì tương đương với một con trỏ, vậy một mảng
nhiều chiều thì tương đương với con trỏ như thế nào?

Xét ví dụ:

int A[3][3] = {

{5, 10, 15},

{20, 25, 30},

{35, 40, 45}

};

Khi đó, địa chỉ của ma trận A chính là địa chỉ của hàng đầu tiên của ma trận A, và cũng là địa chỉ
của phần tửđầu tiên của hàng đầu tiên của ma trận A:

• Địa chỉ của ma trận A: A = A[0] = *(A+0) = &A[0][0];

• Địa chỉ của hàng thứ nhất: A[1] = *(A+1) = &A[1][0];

• Địa chỉ của hàng thứ i: A[i] = *(A+i) = &A[i][0];

• Địa chỉ phần tử &A[i][j] = (*(A+i)) + j;

• Giá trị phần tử A[i][j] = *((*(A+i)) + j);

Như vậy, một mảng hai chiều có thể thay thế bằng một mảng một chiều các con trỏ cùng kiểu:

int A[3][3];

có thể thay thế bằng:

int (*A)[3];

Con trỏ trỏ tới con trỏ

Vì một mảng hai chiều int A[3][3] có thể thay thế bằng một mảng các con trỏ int (*A)[3]. Hơn
nữa, một mảng int A[3] lại có thể thay thế bằng một con trỏ int *A. Do vậy, một mảng hai chiều
có thể thay thế bằng một mảng các con trỏ, hoặc một con trỏ trỏđến con trỏ. Nghĩa là các cách
viết sau là tương đương:

int A[3][3];

int (*A)[3];

int **A;

2.3 CON TRỎ HÀM

Mặc dù hàm không phải là một biến cụ thể nên không có một địa chỉ xác định. Nhưng trong khi
chạy, mỗi một hàm trong C++ cũng có một vùng nhớ xác định, do vậy, C++ cho phép dùng con
trỏđể trỏđến hàm. Con trỏ hàm được dùng để truyền tham số có dạng hàm.

Khai báo con trỏ hàm

Con trỏ hàm được khai báo tương tự như khai báo nguyên mẫu hàm thông thường trong C++,
ngoại trừ việc có thêm kí hiệu con trỏ “*” trước tên hàm. Cú pháp khai báo con trỏ hàm như sau:

<Kiểu dữ liệu trả về> (*<Tên hàm>)([<Các tham số>]);

Trong đó:

• Kiểu dữ liệu trả về: là các kiểu dữ liệu thông thường của C++ hoặc kiểu do người dùng
tựđịnh nghĩa.

• Tên hàm: tên do người dùng tựđịnh nghĩa, tuân thủ theo quy tắc đặt tên biến trong C++.

• Các tham số: có thể có hoặc không (phần trong dấu “[ ]” là tuỳ chọn). Nếu có nhiều tham
số, mỗi tham sốđược phân cách nhau bởi dấu phẩy.

Ví dụ khai báo:

int (*Calcul)(int a, int b);

là khai báo một con trỏ hàm, tên là Calcul, có kiểu int và có hai tham số cũng là kiểu int.

Lưu ý:

• Dấu “()” bao bọc tên hàm là cần thiết để chỉ ra rằng ta đang khai báo một con trỏ hàm.
Nếu không có dấu ngoặc đơn này, trình biên dịch sẽ hiểu rằng ta đang khai báo một hàm
thông thường và có giá trị trả về là một con trỏ.

Ví dụ, hai khai báo sau là khác nhau hoàn toàn:

// Khai báo một con trỏ hàm

int (*Calcul)(int a, int b);

// Khai báo một hàm trả về kiểu con trỏ

int *Calcul(int a, int b);

Sử dụng con trỏ hàm

Con trỏ hàm được dùng khi cần gọi một hàm như là tham số của một hàm khác. Khi đó, một hàm
được gọi phải có khuôn mẫu giống với con trỏ hàm đã được khai báo.

Ví dụ, với khai báo:

int (*Calcul)(int a, int b);

thì có thể gọi các hàm có hai tham số kiểu int và trả về cũng kiểu int như sau:

int add(int a, int b);

int sub(int a, int b);

nhưng không được gọi các hàm khác kiểu tham số hoặc kiểu trả về như sau:

int add(float a, int b);

int add(int a);

char* sub(char* a, char* b);

Chương trình 2.3 minh hoạ việc khai báo và sử dụng con trỏ hàm.

Chương trình 2.3

#include <ctype.h>

#include <string>

// Hàm có sử dụng con trỏ hàm như tham số

void Display(char[ ] str, int (*Xtype)(int c)){

int index = 0;

while(str[index] != ‘\0’){

cout << (*Xtype)(str[index]); // Sử dụng con trỏ hàm

index ++;

}

return;

}

// Hàm main, dùng lời gọi hàm đến con trỏ hàm

void main(){

char input[500];

cout << “Enter the string: ”;

cin >> input;

char reply;

cout << “Display the string in uppercase or lowercase (u,l): ”;

cin >> reply;

if(reply == ‘l’) // Hiển thị theo dạng lowercase

Display(str, tolower);

else // Hiển thị theo dạng uppercase

Display(str, toupper);

return;

}

Chương trình 2.3 khai báo hàm Display() có sử dụng con trỏ hàm có khuôn mẫu

int (*Xtype)(int c);

Trong hàm main, con trỏ hàm này được gọi bởi hai thể hiện là các hàm tolower() và hàm
toupper(). Hai hàm này được khai báo trong thư viện ctype.h với mẫu như sau:

int tolower(int c);

int toupper(int c);

Hai khuôn mẫu này phù hợp với con trỏ hàm Xtype trong hàm Display() nên lời gọi hàm
Display() trong hàm main là hợp lệ.

2.4 CẤP PHÁT BỘ NHỚĐỘNG

Xét hai trường hợp sau đây:

• Trường hợp 1, khai báo một con trỏ và gán giá trị cho nó:

int *pa = 12;

• Trường hợp 2, khai báo con trỏđến phần tử cuối cùng của mảng rồi tăng thêm một đơn vị
cho nó:

int A[5] = {5, 10, 15, 20, 25};

int *pa = &A[4];

pa++;

Trong cả hai trường hợp, ta đều không biết thực sự con trỏ pa đang trỏđến địa chỉ nào trong bộ
nhớ: trường hợp 1 chỉ ra rằng con trỏ pa đang trỏ tới một địa chỉ không xác định, nhưng lại chứa
giá trị là 12 do được gán vào. Trường hợp 2, con trỏ pa đã trỏđến địa chỉ ngay sau địa chỉ phần tử
cuối cùng của mảng A, đó cũng là một địa chỉ không xác định. Các địa chỉ không xác định này là
các địa chỉ nằm ở vùng nhớ tự do còn thừa của bộ nhớ. Vùng nhớ này có thể bị chiếm dụng bởi
bất kì một chương trình nào đang chạy.

Do đó, rất có thể các chương trình khác sẽ chiếm mất các địa chỉ mà con trỏ pa đang trỏ tới. Khi
đó, nếu các chương trình thay đổi giá trị của địa chỉđó, giá trị pa cũng bị thay đổi theo mà ta
không thể kiểm soát được. Để tránh các rủi ro có thể gặp phải, C++ yêu cầu phải cấp phát bộ nhớ
một cách tường minh cho con trỏ trước khi sử dụng chúng.

2.4.1 Cấp phát bộ nhớđộng cho biến

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

Thao tác cấp phát bộ nhớ cho con trỏ thực chất là gán cho con trỏ một địa chỉ xác định và đưa địa
chỉđó vào vùng đã bị chiếm dụng, các chương trình khác không thể sử dụng địa chỉđó. Cú pháp
cấp phát bộ nhớ cho con trỏ như sau:

<tên con trỏ> = new <kiểu con trỏ>;

Ví dụ, khai báo:

int *pa;

pa = new int;

sẽ cấp phát bộ nhớ hợp lệ cho con trỏ pa.

Lưu ý:

• Ta có thể vừa cấp phát bộ nhớ, vừa khởi tạo giá trị cho con trỏ theo cú pháp sau:

int *pa;

pa = new int(12);

sẽ cấp phát cho con trỏ pa một địa chỉ xác định, đồng thời gán giá trị của con trỏ *pa = 12.

Giải phóng bộ nhớđộng

Địa chỉ của con trỏ sau khi được cấp phát bởi thao tác new sẽ trở thành vùng nhớđã bị chiếm
dụng, các chương trình khác không thể sử dụng vùng nhớđó ngay cả khi ta không dùng con trỏ
nữa. Để tiết kiệm bộ nhớ, ta phải huỷ bỏ vùng nhớ của con trỏ ngay sau khi không dùng đến con
trỏ nữa. Cú pháp huỷ bỏ vùng nhớ của con trỏ như sau:

delete <tên con trỏ>;

Ví dụ:

int *pa = new int(12); // Khai báo con trỏ pa, cấp phát bộ nhớ

// và gán giá trị ban đầu cho pa là 12.

delete pa; // Giải phóng vùng nhớ vừa cấp cho pa.

Lưu ý:

• Một con trỏ, sau khi bị giải phóng địa chỉ, vẫn có thểđược cấp phát một vùng nhớ mới
hoặc trỏđến một địa chỉ mới:

int *pa = new int(12); // Khai báo con trỏ pa, cấp phát bộ nhớ
// và gán giá trị ban đầu cho pa là 12.

delete pa; // Giải phóng vùng nhớ vừa cấp cho pa.

int A[5] = {5, 10, 15, 20, 25};

pa = A; // Cho pa trỏ đến địa chỉ của mảng A

• Nếu có nhiều con trỏ cùng trỏ vào một địa chỉ, thì chỉ cần giải phóng bộ nhớ của một con
trỏ, tất cả các con trỏ còn lại cũng bị giải phóng bộ nhớ:

int *pa = new int(12); // *pa = 12

int *pb = pa; // pb trỏ đến cùng địa chỉ pa.

*pb += 5; // *pa = *pb = 17

delete pa; // Giải phóng cả pa lẫn pb

• Một con trỏ sau khi cấp phát bộ nhớđộng bằng thao tác new, cần phải phóng bộ nhớ trước
khi trỏđến một địa chỉ mới hoặc cấp phát bộ nhớ mới:

int *pa = new int(12); // pa được cấp bộ nhớ và *pa = 12

*pa = new int(15); // pa trỏ đến địa chỉ khác và *pa = 15.

// địa chỉ cũ của pa vẫn bị coi là bận

2.4.2 Cấp phát bộ nhớ cho mảng động một chiều

Cấp phát b ộ nhớ cho mảng động một chiều

Mảng một chiều được coi là tương ứng với một con trỏ cùng kiểu. Tuy nhiên, cú pháp cấp phát bộ
nhớ cho mảng động một chiều là khác với cú pháp cấp phát bộ nhớ cho con trỏ thông thường:

<Tên con trỏ> = new <Kiểu con trỏ>[<Độ dài mảng>];

Trong đó:

• Tên con trỏ: tên do người dùng đặt, tuân thủ theo quy tắc đặt tên biến của C++.

• Kiểu con trỏ: Kiểu dữ liệu cơ bản của C++ hoặc là kiểu do người dùng tựđịnh nghĩa.

• Độ dài mảng: số lượng các phần tử cần cấp phát bộ nhớ của mảng.

Ví dụ:

int *A = new int[5];

sẽ khai báo một mảng A có 5 phần tử kiểu int được cấp phát bộ nhớđộng.

Lưu ý:

• Khi cấp phát bộ nhớ cho con trỏ có khởi tạo thông thường, ta dùng dấu “()”, khi cấp phát
bộ nhớ cho mảng, ta dùng dấu “[ ]”. Hai lệnh cấp phát sau là hoàn toàn khác nhau:

// Cấp phát bộ nhớ và khởi tạo cho một con trỏ int

int *A = new int(5);

// Cấp phát bộ nhớ cho một mảng 5 phần tử kiểu int

int *A = new int[5];

Giải phóng bộ nhớ của mảng động một chiều

Để giải phóng vùng nhớđã được cấp phát cho một mảng động, ta dùng cú pháp sau:

delete [ ] <tên con trỏ>;

Ví dụ:

// Cấp phát bộ nhớ cho một mảng có 5 phần tử kiểu int

int *A = new int[5];

// Giải phóng vùng nhớ do mảng A đang chiếm giữ.

delete [] A;

Chương trình 2.4 minh hoạ hai thủ tục khởi tạo và giải phóng một mảng động một chiều.

Chương trình 2.4

void InitArray(int *A, int length){

A = new int[length];

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

A[i] = 0;

return;

}

void DeleteArray(int *A){

delete [ ] A;

return;

}

2.4.3 Cấp phát bộ nhớ cho mảng động nhiều chiều

Cấp phát b ộ nhớ cho mảng động nhiều chiều

Một mảng hai chiều là một con trỏđến một con trỏ. Do vậy, ta phải cấp phát bộ nhớ theo từng
chiều theo cú pháp cấp phát bộ nhớ cho mảng động một chiều.

Ví dụ:

int **A;

const int length = 10;

A = new int*[length]; // Cấp phát bộ nhớ cho số dòng của ma trận A

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

// Cấp phát bộ nhớ cho các phần tử của mỗi dòng

A[i] = new int[length];

sẽ cấp phát bộ nhớ cho một mảng động hai chiều, tương đương với một ma trận có kích thước
10*10.

Lưu ý:

• Trong lệnh cấp phát A = new int*[length], cần phải có dấu “*” để chỉ ra rằng cần cấp
phát bộ nhớ cho một mảng các phần tử có kiểu là con trỏ int (int*), khác với kiểu
int bình thường.

Giải phóng bộ nhớ của mảng động nhiều chiều

Ngược lại với khi cấp phát, ta phải giải phóng lần lượt bộ nhớ cho con trỏ tương ứng với cột và
hàng của mảng động.

Ví dụ:

int **A;

…; // cấp phát bộ nhớ



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

delete [ ] A[i]; // Giải phóng bộ nhớ cho mỗi dòng

delete [] A; // Giải phóng bộ nhớ cho mảng các dòng

sẽ giải phóng bộ nhớ cho một mảng động hai chiều.

Chương trình 2.5 minh hoạ việc dùng mảng động hai chiều để tính tổng của hai ma trận.

Chương trình 2.5

#include<stdio.h>

#include<conio.h>

/* Khai báo nguyên mẫu hàm */

void InitArray(int **A, int row, int colum);

void AddArray(int **A, int **B, int row, int colum);

void DisplayArray(int **A, int row, int colum);

void DeleteArray(int **A, int row);

void InitArray(int **A, int row, int colum){

A = new int*[row];

for(int i=0; i<row; i++){

A[i] = new int[colum];

for(int j=0; j<colum; j++){

cout << “Phan tu [” << i << “,” << j << “] = ”;

cin >> A[i][j];

}

return;

}

void AddArray(int **A, int **B, int row, int colum){

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

for(int j=0; j<colum; j++)

A[i][j] += B[i][j];

return;

}

void DisplayArray(int **A, int row, int colum){

for(int i=0; i<row; i++){

for(int j=0; j<colum; j++)

cout << A[i][j] << “ ”;

cout << endl; // Xuống dòng

return;

}

void DeleteArray(int **A, int row){

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

delete [ ] A[i];

delete [ ] A;

return;

}


void main(){

clrscr();

int **A, **B, row, colum;

cout << “So dong: ”;

cin >> row;

cout << “So cot: ”;

cin >> colum;

/* Khởi tạo các ma trận */

cout << “Khoi tao mang A:” << endl;

InitArray(A, row, colum);

cout << “Khoi tao mang B:” << endl;

InitArray(B, row, colum);

// Cộng hai ma trận

AddArray(A, B, row, colum);

// Hiển thị ma trận kết quả

cout << “Tong hai mang A va mang B:” << endl;

DisplayArray(A, row, colum);

// Giải phóng bộ nhớ

DeleteArray(A, row);

DeleteArray(B, row);

return;

}

TỔNG KẾT CHƯƠNG 2

Nội dung chương 2 đã trình bày các vấn đề liên quan đến việc khai báo và sử dụng con trỏ và
mảng trong ngôn ngữ C++:

• Con trỏ là một kiểu biến đặc biệt, nó trỏđến địa chỉ của một biến khác. Có hai cách truy
nhập đến con trỏ là truy nhập đến địa chỉ hoặc truy nhập đến giá trị của địa chỉ mà con trỏ
trỏđến.

• Con trỏ có thể tham gia vào các phép toán như các biến thông thường bằng phép lấy giá
trị.

• Một con trỏ có sự tương ứng với một mảng một chiều có cùng kiểu.

• Một ma trận hai chiều có thể thay thế bằng một mảng các con trỏ hoặc một con trỏ trỏđến
con trỏ.

• Một con trỏ có thể trỏđến một hàm, khi đó, nó được dùng để gọi một hàm như là một
tham số cho hàm khác.
• Một con trỏ cần phải trỏ vào một địa chỉ xác định hoặc phải được cấp phát bộ nhớ qua
phép toán new và giải phóng bộ nhớ sau khi dùng bằng thao tác delete.

CÂU HỎI VÀ BÀI TẬP CHƯƠNG 2

1. Trong các khai báo con trỏ sau, những khai báo nào là đúng:

a. int A*;

b. *int A;

c. int* A, B;

d. int* A, *B;

e. int *A, *B;

2. Với khai báo:

int a = 12;

int *pa;

Các phép gán nào sau đây là hợp lệ:

a. pa = &a;

b. pa = a;

c. *pa = &a;

d. *pa = a;

3. Với khai báo:

int A[5] = {10, 20, 30, 40, 50};

int *pa = A+2;

Khi đó, *pa = ?

a. 10

b. 20

c. 30

d. 40

e. 50

4. Với đoạn chương trình:

int A[5] = {10, 20, 30, 40, 50};

int *pa = A;

*pa += 2;

Khi đó, *pa = ?

a. 10

b. 12

c. 30

d. 32

5. Với đoạn chương trình:

int A[5] = {10, 20, 30, 40, 50};

int *pa = A;

pa += 2;

Khi đó, *pa = ?

a. 10

b. 12

c. 30

d. 32

6. Với đoạn chương trình:

int A[5] = {10, 20, 30, 40, 50};

int *pa = A;

pa += 2;

Khi đó, pa = ?

a. &A[0]

b. A[2]

c. &A[2]

d. Không xác định

7. Với đoạn chương trình:

int A[5] = {10, 20, 30, 40, 50};

int *pa = A;

pa -= 2;

Khi đó, pa = ?

a. &A[0]

b. &A[2]

c. &A[4]

d. Không xác định

8. Với đoạn chương trình:

int A[3][3] = {

{10, 20, 30},

{40, 50, 60},

{70, 80, 90}

};

int *pa;

Khi đó, để có được kết quả *pa = 50, các lệnh nào sau đây là đúng?

a. pa = A + 4;

b. pa = (*(A+1)) + 1;

c. pa = &A[1][1];

d. pa = *((*(A+1)) + 1);

9. Giả sử ta khai báo một hàm có sử dụng con trỏ hàm với khuôn mẫu như sau:

int Calcul(int a, int b, int (*Xcalcul)(int x, int y)){}

Và ta có cài đặt một số hàm như sau

int add(int a, int b);

void cal(int a, int b);

int squere(int a);

Khi đó, lời gọi hàm nào sau đây là đúng:

a. Calcul(5, 10, add);

b. Calcul(5, 10, add(2, 3));

c. Calcul(5, 10, cal);

d. Calcul(5, 10, squere);

10. Ta muốn cấp phát bộ nhớ cho một con trỏ kiểu int và khởi đầu giá trị cho nó là 20. Lệnh
nào sau đây là đúng:

a. int *pa = 20;

b. int *pa = new int{20};

c. int *pa = new int(20);

d. int *pa = new int[20];

11. Ta muốn cấp phát bộ nhớ cho một mảng động kiểu int có chiều dài là 20. Lệnh nào sau
đây là đúng:

a. int *pa = 20;

b. int *pa = new int{20};

c. int *pa = new int(20);

d. int *pa = new int[20];

12. Xét đoạn chương trình sau:

int A[5] = {10, 20, 30, 40, 50};

int *pa = A;

pa = new int(2);

Khi đó, *pa = ?

a. 10

b. 30

c. 2

d. Không xác định

13. Xét đoạn chương trình sau:

1> int A[5] = {10, 20, 30, 40, 50};

2> int *pa = A;

3> pa += 15;

4> delete pa;

Đoạn chương trình trên có lỗi ở dòng nào?

a. 1

b. 2

c. 3

d. 4

14. Viết chương trình thực hiện các phép toán cộng, trừ, nhân, chia trên đa thức. Các đa thức
được biểu diễn bằng mảng động một chiều. Bậc của đa thức và các hệ số tương ứng được
nhập từ bàn phím.

15. Viết chương trình thực hiện các phép toán cộng, trừ, nhân hai ma trận kích thước m*n.
Các ma trận được biểu diễn bằng mảng động hai chiều. Giá trị kích cỡ ma trận (m, n) và
giá trị các phần tử của ma trận được nhập từ bàn phím.
luongbv93
luongbv93
Admin

Tổng số bài gửi : 117
Points : 354
Reputation : 0
Join date : 18/11/2011
Age : 30
Đến từ : Hà Nội

https://diendanteenviet.forumvi.com

Về Đầu Trang Go down

Về Đầu Trang

- Similar topics

 
Permissions in this forum:
Bạn không có quyền trả lời bài viết