Bài giảng lập trình hướng đối tượng - Thầy Cường Học viện bưu chính viễn thông TP HCM
Trang 1Chương 3
Mảng , Con trỏ và Tham chiếu
• Mảng các đối tượng
• Con trỏ đối tượng
• Con trỏ this
• Toán tử new và delete
• Truyền tham chiếu cho đối tượng
• Trả về các tham chiếu
• Các tham chiếu độc lập và các hạn chế
Trang 3I/ Mảng các đối tượng
Các đối tượng chính là các biến , có các khả năng và thuộc tính như các biến thông thường khác Do đó, các đối tượng có thể tổ chức thành mảng
Cú pháp khai báo một mảng các đối tượng hoàn toàn giống như ngôn ngữ C
Việc truy cập mảng các đối tượng cũng giống như mảng của các loại biến khác
Ví dụ 1.1 Mảng các đối tượng
for(i=0; i<4; i++) ob[i].set_a(i); // truy cập mảng đối tượng
for(i=0; i<4; i++) cout << ob[i].get_a( );
Trang 4int get_a() { return a; }
@ Một cách viết khác (dài hơn)
samp ob[4] = { samp(-1), samp(- 2), samp(-3), samp(- 4) };
@ Cách khởi đầu ở trên chỉ làm việc với các mảng có hàm tạo chỉ nhận một đối số
• Khởi đầu mảng đối tượng nhiều chiều
Trang 5for(i=0; i<4; i++) {
• Khi khởi đầu một mảng đối tượng có hàm tạo nhận nhiều đối số , cần phải dùng
dạng khởi đầu khác
int get_a() { return a; }
int get_b() { return b; }
for(i=0; i<4; i++) {
Trang 6squares(int a, int b) { num = a; sqr = b; }
void show() {cout << num << ' ' << sqr << "\n"; }
};
II/ Con trỏ đối tượng
Các đối tượng có thể được truy cập thông qua con trỏ, toán tử -> sẽ được dùng
Khai báo một con trỏ đối tượng giống như khai báo một con trỏ hướng về kiểu biến bất kỳ Ví dụ samp *p;
Để có điạ chỉ của một đối tượng, dùng toán tử & đặt trước đối tượng Ví dụ
Trang 7myclass ob(120); // create object
myclass *p; // create pointer to object
p = &ob; // put address of ob into p
cout << "Value using object: " << ob.get() << "\n" ;
cout << "Value using pointer: " << p->get();
return 0;
}
@ Việc tạo ra một con trỏ đối tượng không tạo ra một đối tượng, nó chỉ tạo ra một
con trỏ trỏ về đối tượng
Trang 8• Số học con trỏ :
+ Khi tăng con trỏ đối tượng, nó sẽ trỏ đến đối tượng tiếp theo
+ Khi giảm con trỏ đối tượng, nó sẽ trỏ đến đối tượng đứng trước
int get_a() { return a; }
int get_b() { return b; }
p = ob ; // get starting address of array
for(i=0; i<4; i++) {
Trang 9Bài tập II
1 Hãy viết lại ví dụ 2.2 chương 3 để cho nó hiển thị nội dung của mảng ob theo thứ tự ngược lại
2 Hãy viết lại ví dụ 1.3 chương 3 để truy cập mảng hai chiều qua con trỏ
III/ Con trỏ this
this là con trỏ được truyền tự động cho bất kỳ hàm thành viên nào khi được gọi và nó là con trỏ tới đối tượng tạo ra lời gọi hàm
Ví dụ, cho câu lệnh ob.f1() ; // ob là đối tượng
Hàm f1() tự động được truyền con trỏ ob là đối tượng tạo ra lời gọi hàm Con trỏ này
được xem là this
Chỉ có các hàm thành viên được truyền con trỏ this Hàm friend không có con trỏ
this
Con trỏ this có nhiều sử dụng, kể cả việc giúp quá tải các toán tử
• Khi một hàm thành viên tham chiếu một hàm thành viên khác của lớp, nó thực
hiện mà không xác định tham chiếu với hoặc một lớp hoặc một đặc tả đối tượng
Trang 10• Khi một hàm thành viên được gọi, nó tự động được truyền con trỏ this trỏ về đối
tượng tạo ra lời gọi Chương trình có thể viết lại :
Trang 11{
strcpy(this->item, i); // access members
this->cost = c; // through the this
Bài tập III
Hãy chuyển tất cả các tham chiếu thích hợp đối với các thành viên của lớp thành tham chiếu con trỏ this
Trang 12IV/ Toán tử new và delete
1/ Toán tử new dùng để cấp phát bộ nhớ động và toán tử delete dùng giải phóng bộ
nhớ đã cấp phát
Cú pháp p_var = new data_type;
delete p_var;
data_type chỉ định kiểu đối tượng muốn cấp phát bộ nhớ
p_var con trỏ tới kiểu đó
Giống như hàm malloc(), nếu không đủ bộ nhớ theo yêu cầu cấp phát thì toán tử new sẽ trả về con trỏ NULL Toán tử delete được gọi chỉ với một con trỏ đã được cấp phát trước đó qua toán tử new Nếu gọi delete với một con trỏ không hợp lệ, hệ thống cấp phát sẽ bị hủy, và có thể làm hỏng chương trình
Các ưu điểm
+ toán tử new tự động cấp phát bộ nhớ để giữ một đối tượng có kiểu được chỉ rõ + toán tử new tự động trả về một con trỏ có kiểu được chỉ rõ
+ toán tử new và delete có thể được quá tải
+ có thể khởi đầu đối tượng được cấp phát động
Trang 13+ không cần nạp thư viện malloc.h hoặc stdlib.h vào trong chương trình
• Cấp phát bộ nhớ động để giữa một số nguyên
cout << "Here is integer at p: " << *p << "\n";
delete p; // release memory
Trang 14int get_product() { return i*j; }
Bài tập IVa
1 Hãy viết chương trình sử dụng new để cấp phát động một float, một long và một char Cho các biến động này những giá trị và hiển thị Sau đó, hãy dùng delete giải phóng tất cả bộ nhớ
2 Hãy tạo một lớp có chứa tên và số điện thoại của một người Dùng new để cấp phát động một đối tượng của lớp này, lưu tên và số điện thoại vào đối tượng đó rồi hiển thị
2/ Các đặc điểm của new và delete
+ các đối tượng được cấp phát động một giá trị đầu
p_var = new data_type(initial_value) ;
Trang 15
+ các mảng được cấp phát động có thể được tạo ra
p_var = new data_type[size] ;
p_var trỏ tới đầu mảng có phần tử size có kiểu được chỉ rõ
Vì lý do kỹ thuật, không thể khởi đầu một mảng đã được cấp phát động
+ để hủy bỏ mảng đã được cấp phát động
delete [] p_var ; p_var sẽ được giải phóng một lần
• cấp phát bộ nhớ cho một số nguyên và khởi đầu bộ nhớ đó
cout << "Here is integer at p: " << *p << "\n";
delete p; // release memory
return 0;
}
• truyền các giá trị đầu cho một đối tượng được cấp phát động
Trang 16samp(int a, int b) { i=a; j=b; }
int get_product() { return i*j; }
Trang 17p = new int [5]; // allocate room for 5 integers
// always make sure that allocation succeeded
for(i=0; i<5; i++) p[i] = i;
for(i=0; i<5; i++) {
cout << "Here is integer at p[" << i << "]: ";
void set_ij(int a, int b) { i=a; j=b; }
int get_product() { return i*j; }
};
int main()
Trang 18for(i=0; i<10; i++) p[i].set_ij(i, i);
for(i=0; i<10; i++) {
cout << "Product [" << i << "] is: ";
void set_ij(int a, int b) { i=a; j=b; }
~samp() { cout << "Destroying \n"; }
int get_product() { return i*j; }
};
int main()
Trang 19for(i=0; i<10; i++) {
cout << "Product [" << i << "] is: ";
@ Kết quả của chương trình có gì khác so với ví dụ 4.6 ?
Bài tập IVb
1 Hãy chuyển đổi đoạn chương trình sau để dùng toán tử new
char *p;
p = (char *) malloc(100);
//
strcpy(p, "This is a test");
2/ Sử dụng toán tử new, cấp phát một double và cho gaí trị đầu là -3.1416
V/ Tham chiếu (reference)
Trang 201/ Một tham chiếu là một con trỏ ẩn tác động như một tên khác đối với một biến
Khai báo : data_type &var
Cách sử dụng :
+ Một tham chiếu có thể được truyền cho hàm (gọi là tham số tham chiếu)
+ Một tham chiếu có thể được trả về bởi hàm
+ Một tham chiếu độc lập có thể được tạo ra
• Hoạt động của tham số tham chiếu (reference parameter)
Chương trình này chứng tỏ cách con trỏ được dùng như một tham số để tạo ra một cơ
chế truyền tham số gọi bằng tham chiếu
• Quá trình trên có thể thực hiện tự động bằng cách dùng tham số tham chiếu
Trang 21// f() now uses a reference parameter
void f(int &n)
{
// notice that no * is needed in the following statement
n = 100; // put 100 into the argument used to call f()
}
n là một biến tham chiếu
f() được khai báo như một tham số tham chiếu, điạ chỉ đối với số được truyền tự động cho f()
2/ Đặc tính
• Khi sử dụng tham số tham chiếu , trình biên dịch sẽ tự động truyền địa chỉ của biến được dùng như đối số Không cần tạo ra điạ chỉ của đối số bằng cách đặt trước nó ký tự &
• Bên trong hàm, trình biên dịch tự động dùng biến được trỏ tới bởi tham số tham chiếu Không cầân dùng ký tự * Do đó, một tham số tham chiếu hoàn toàn thực hiện tự động cơ chế truyền đối số bằng cách gọi tham chiếu
• Không thể thay đổi những gì mà một tham chiếu trỏ tới
3/ Ưu điểm của tham số tham chiếu
Trang 22+ Từ quan điểm thực hành, không cần nhớ truyền điạ chỉ của một đối số
+ Tham số tham chiếu tạo ra một giao diện đẹp hơn so với cách dùng cơ chế con trỏ + Khi một đối tượng được truyền cho một hàm như một tham chiếu thì không có bản sao được thực hiện
• Dùng các tham chiếu để trao đổi 2 đối số nguyên
Nếu dùng con trỏ thì hàm swapargs() được viết lại như sau :
void swapargs(int *x, int *y)
Trang 24// decompose num into whole and fractional parts
frac = modf(num, &val);
if(frac < 0.5) num = val;
else num = val+1.0;
}
Bài tập V
1 Hãy viết hàm neg() để đảo ngược dấu của các tham số nguyên Theo hai cách :
+ dùng tham số con trỏ
+ dùng tham số tham chiếu
2 Tìm lỗi sai trong chương trình sau :
// This program has an error
// Triple num's value
void triple(double &num)
Trang 25Trong chương 2, khi một đối tượng được truyền cho một hàm bằng cách dùng cơ chế truyền tham số gọi giá trị, một bản sao đối tượng được tạo ra Khi hàm trả về, hàm hủy của bản sao đối tượng được gọi, điều này có thể sinh ra những vấn đề nghiêm trọng, chẳng hạn hàm hủy giải phóng bộ nhớ động
Khi truyền đối số bằng tham chiếu, không có bản sao đối tượng được tạo ra, do đó hàm hủy của nó không được gọi khi các hàm trả về
Tuy nhiên, những thay đổi đối với đối tượng ở bên trong hàm có ảnh hưởng đến đối tượng được dùng như đối số
Khi một đối tượng được truyền bởi tham chiếu thì toán tử truy cập thành viên vẫn là
toán tử điểm (.) chứ không phải toán tử mũi tên (->)
• Truyền đối tượng bằng giá trị cho hàm được gọi f()
~myclass() { cout << "Destructing " << who << "\n"; }
int id() { return who; }
Trang 26Giải thích kết quả ?
• Truyền đối tượng bằng tham chiếu cho hàm được gọi f()
~myclass() { cout << "Destructing " << who << "\n"; }
int id() { return who; }
};
// Now, o is passed by reference
void f(myclass &o)
{
// note that operator is still used!!!
cout << "Received " << o.id() << "\n";
Trang 29Một hàm có thể trả về một tham chiếu Điều này có ích khi quá tải một số loại toán tử nào đó
Một hàm cũng có thể được sử dụng ở bên trái của câu lệnh gán
• Hàm trả về một tham chiếu
Trang 30Ví duï 7.2
// Return an int reference
int &f()
{
int x; // x is now a local variable
return x; // returns a reference to x
char &put(int i);
char get(int i);
Trang 32Bài tập VII
1 Hãy viết chương trình tạo mảng an toàn hai chiều 2x3 các số nguyên
2 Đoạn chương trình sau có đúng không ? Tại sao ?
VIII/ Các tham chiếu độc lập và các hạn chế
1/ Tham chiếu độc lập (independent reference) là một biến tham chiếu có tác dụng chỉ là một tên khác cho một biến khác
Tham chiếu độc lập phải được khởi đầu khi khai báo, do các tham chiếu không thể được gán những giá trị mới
Các lập trình viên thường ít sử dụng tham chiếu độc lập
2/ Một số hạn chế
+ không thể tham chiếu đến một tham chiếu khác
+ không thể có điạ chỉ của tham chiếu
+ không thể tạo một mảng các tham chiếu
+ không thể tham chiếu một trường bit
+ các tham chiếu phải được khởi đầu trừ khi chúng là các thành viên của lớp, là các giá trị trả về hoặc là các tham số của hàm
• Chương trình có một tham chiếu độc lập
Trang 33int &ref = x; // create an independent reference
x = 10; // these two statements
ref = 10; // are functionally equivalent
const int &ref = 10;
Bài tập chương 3
Trang 341 Cho lớp sau, hãy tạo một mảng hai chiều 3x4 và cho mỗi đối tượng trong mảng một giá trị đầu