Truyền theo tham chiếu (pass-by-reference)

Một phần của tài liệu Lý thuyết lập trình C++ (Trang 28 - 33)

Như đã nói ở trên truyền theo tham trị không truyền bản thân biến vào mà chỉ truyền bản sao cho hàm. Do đó có những hạn chế nhất định của nó. Bây giờ mời bà con và cô bác ngâm cứu cách truyền thứ hai, truyền theo tham chiếu (passing-by-reference). Có hai cách để truyền theo tham chiếutruyền tham chiếu thông qua tham chiếu (pass-by- reference-with-references), và truyền tham chiếu thông qua con trỏ (pass-by-

reference-with-pointers). Nghe có vẻ hơi lằng nhằng nhưng mình sẽ giải thích ngay bây

giờ.

Truyền tham chiếu thông qua con trỏ

Chắc chắn các bạn đã quen thuộc với con trỏ rồi nên mình sẽ không nói nhiều về phần này. Tuy nhiên có thể mình sẽ dành ra một bài để viết riêng về mục con trỏ nếu thấy cần thiết để đảm bảo tính hệ thống. Nhắc lại, con trỏ là một biến đặc biệt lưu trữ địa chỉ của một biến mà nó trỏ tới. Cú pháp khai báo con trỏ cũng như cách sử dụng nó được mình họa trong chương trình sau:

Lựa chọn code | Ẩn/Hiện code

#include <iostream>

using namespace std;

int main(){

int x; // khai báo một biến nguyên

int *ptr; // khai báo một con trỏ kiểu nguyên

ptr=&x; // ptr trỏ tới x hay gán địa chỉ của x cho ptr

*ptr=10; // gán giá trị 10 cho vùng nhớ mà ptr trỏ tới, cụ thể ở đây là x

cout << x << endl; // in giá trị của x, bây giờ là 10

return 0; }

Chương trình trên nhắc lại những kiến thức hết sức cơ bản về con trỏ. Bây giờ ta sẽ xem xét cách truyền đối số cho hàm thông qua con trỏ như thế nào. Ví dụ chương trình sau thực hiện việc hoán đổi nội dung hai biến cho nhau, một chương trình hết sức cổ điển gần như lúc nào cũng được lôi ra làm ví dụ khi nói về truyền đối số bằng con trỏ:

C++ Code:

Lựa chọn code | Ẩn/Hiện code

#include <iostream>

using namespace std;

void swap(int* a, int* b){ // hoán đổi nội dung hai biến cho nhau

int temp; temp=*a; *a=*b; *b=temp; } int main(){ int x=5; int y=7;

// trước khi gọi swap

cout << "Before calling swap" << endl; cout << "x= " << x << endl;

cout << "y= " << y << endl;

// gọi swap

swap(&x, &y);

// sau khi gọi swap

cout << "After calling swap" << endl; cout << "x= " << x << endl;

cout << "y= " << y << endl; return 0;

}

Nhận thấy kết quả sẽ là Trích dẫn:

Before calling swap x= 5

y= 7

x=7 y=5

Mình sẽ giải thích về bản chất của cách truyền này. Để ý câu lệnh: C++ Code:

Lựa chọn code | Ẩn/Hiện code

swap(&x, &y);

Câu lệnh này truyền địa chỉ của x và y chi hàm swap, và hàm swap cứ thế mò thẳng đến vùng nhớ của x và y mà thao tác. Điều này nghĩa mọi mọi thao tác trong hàm swap có thể làm thay đổi biến ban đầu, và do đó nó cho phép hoán đổi nội dung của x, y cho nhau. Truyền tham chiếu thông qua con trỏ cũng có cái lợi và cái hại. Cái lợi thứ nhất là nó cho phép thao tác trực tiếp trên biến ban đầu nên có thể cho phép sửa đổi nội dung của biến nếu cần thiết (như ví dụ hàm swap trên). Thứ hai, cũng do thao tác trực tiếp trên biến gốc nên ta không phải tốn chi phí cho việc tạo biến phụ hay copy các giá trị sang biến phụ. Cái hại là làm giảm đi tính bảo mật của dữ liệu. Ví dụ trong trường hợp hàm min ở trên ta hoàn toàn không mong muốn thay đổi dữ liệu của biến gốc mà chỉ muốn biết thằng nào bé hơn. Nhưng nếu truyền theo kiểu con trỏ như thế này có khả năng ta “lỡ” sửa đổi biến gốc và do đó gây ra lỗi (sợ nhất vẫn là những lỗi logic, nó không chạy thì còn đỡ, nó chạy sai mới đểu).

Truyền tham chiếu thông qua tham chiếu

Tham chiếu (reference) là một khái niệm mới của C++ so với C. Nói nôm na nó là một biệt danh hay nickname của một biến. Chương trình sau minh họa đơn giản cách sử dụng tham chiếu trong C++

C++ Code:

Lựa chọn code | Ẩn/Hiện code

#include <iostream>

using namespace std;

int main(){

int x; // khai báo biến nguyên x

int &ref=x; // tham chiếu ref là nickname của x

ref=10; // gán ref=10, nghĩa là x cũng bằng 10

cout << x << endl; // in giá trị của x, tức là 10, lên màn hình

return 0; }

Một lưu ý về tham chiếu là nó phải được khởi tạo ngay khi khai báo. Câu lệnh như sau sẽ báo lỗi:

C++ Code:

Lựa chọn code | Ẩn/Hiện code

int &ref; // lỗi không khởi tạo ngay khi khai báo

Mọi thay đổi về trên tham chiếu cũng gây ra những thay đổi tương tự trên biến vì bản chất nó là hai cái tên cho cùng một biến (giống như thằng Bờm với con của bố thằng Bờm là một thằng, giả thiết bố thằng Bờm chỉ đẻ được một thằng ). Vì vậy ta cũng có thể dùng tham chiếu để truyền đối số cho hàm với tác dụng giống hệt con trỏ. Bây giờ ta sẽ cải tiến lại hàm swap bên trên bằng cách dùng tham chiếu.

C++ Code:

Lựa chọn code | Ẩn/Hiện code

#include <iostream>

// hàm swap

void swap(int& a, int& b){ int temp; temp=a; a=b; b=temp; } int main(){ … // gọi hàm swap swap(x,y); … }

Nhận xét: về cơ bản tác dụng của việc truyền theo tham chiếu và truyền theo con trỏ là

hòan toàn như nhau, tuy nhiên dùng tham chiếu sẽ tốt hơn vì nó làm cho “giao diện” của hàm thân thiện hơn. Hãy so sánh việc truyền tham số của hai cách:

C++ Code:

Lựa chọn code | Ẩn/Hiện code

// theo con trỏ

swap(&x, &y);

// theo tham chiếu

swap(x, y);

Rõ ràng thằng dưới nhìn “thân thiện” hơn thằng trên (tự dưng để cái dấu & ở trước trông nó chướng mắt ). Hơn nữa tham chiếu đã gắn với biến nào rồi thì cố định luôn, không thay đổi được, còn con trỏ không thích trỏ biến này nữa thì có thể trỏ sang biến khác, nên nếu lỡ tay mà ta cho nó “trỏ lung tung” thì không biết đằng nào mà lần.

Lợi ích của việc truyền tham chiếu hằng (const references)

Bây giờ ta lại đặt ra vấn đề: liệu có cách nào tận dụng được tính an toàn bảo mật của truyền theo tham trị nhưng lại tận dụng được lợi thế về chi phí bộ nhớ và thời gian như truyền theo tham chiếu không? Câu trả lời đói là dùng tham chiếu hằng. Chúng ta sẽ xem chương trình sau:

C++ Code:

Lựa chọn code | Ẩn/Hiện code

#include <iostream>

using namespace std;

int min(const int& a, const int& b){

return (a<b?a:b); // trả về giá trị nhỏ hơn

}

int main(){ int x=5; int y=7;

int minimum=min(x,y); // gọi hàm min tính giá trị nhỏ nhất rồi gán cho minimum

cout << "minimum= " << minimum << endl; return 0;

}

C++ Code:

Lựa chọn code | Ẩn/Hiện code

int min(const int& a, const int& b)

Việc đặt từ khóa const trước kiểu của tham số a và b như trên được gọi là truyền theo tham chiếu hằng. Với từ khóa const này, ta vẫn truyền trực tiếp biếnx, y vào cho hàm min nhưng hàm min không có quyền “sửa đổi” giá trị của x, y mà chỉ được dùng những thao tác không làm ảnh hưởng đến x, y như so sánh, lấy giá trị của x, y để tính toán, … Nếu cố tình sửa đổi x, y sẽ gây lỗi. Xét một ví dụ như sau:

C++ Code:

Lựa chọn code | Ẩn/Hiện code

int example(const int& a){

a=20; // lỗi vì cố tình sủa đổi tham chiếu hằng

return a; }

Việc sử dụng tham chiếu hằng như trên là một ví dụ về nguyên tắc “quyền ưu tiên tối thiểu” (the principle of least privilege), một nguyên tắc nền tảng trong lập trình. Trong trường hợp này nghĩa là chỉ trao cho hàm min những quyền ưu tiên tối thiểu thao tác trên dữ liệu để nó đủ thực hiện nhiệm vụ, không hơn. Rõ ràng hàm min chỉ cần so sánh hai đối số truyền vào để xem thằng nào nhỏ hơn rồi trả về giá trị. Vì vậy truyền theo tham chiếu hằng là phương án đảm bảo nguyên tắc trên.

Truyền cấu trúc dữ liệu (passing data structures)

Tạm thời mình chỉ giới thiệu cấu trúc đơn giản nhất là mảng (arrays). Còn những cấu trúc dữ liệu phức tạp hơn, nếu có điều kiện mình sẽ nói trong dịp khác. Như ta biết tên mảng là một con trỏ hằng, trỏ đến phần tử đầu tiên của mảng. Vì vậy truyền mảng giống như truyền con trỏ vậy. Chương trình sau gọi hàm input để nhập các phần tử vào một mảng, và output để xuất các phần tử của mảng:

C++ Code:

Lựa chọn code | Ẩn/Hiện code

#include <iostream>

using namespace std;

void input(int*, int); // nguyên mẫu hàm input

void output(int*, int); // nguyên mẫu hàm output

int main(){

int num; // biến lưu số lượng phần tử mảng

int *ptr; // con trỏ quản lý mảng

cout << "Enter number of elements: " << endl; cin >> num; // nhập số lượng phần tử mảng

ptr=new int[num]; // cấp phát bộ nhớ động cho con trỏ ptr

cout << "Enter elements: " << endl; input(ptr, num); //nhập mảng

cout << "Here are elements of the array: " << endl; output(ptr, num); // xuất mảng

return 0; }

// định nghĩa hàm input

for(int i=0; i<n; i++){ cout << "element "<< i+1 << "= "; cin >> a[i]; } } // định nghĩa hàm output

void output(int* a, int n){ for(int i=0; i<n; i++){ cout << a[i] << " "; }

}

nếu test thử kết quả sẽ như sau Trích dẫn:

Enter number of elements: 4 Enter elements: element 1= 1 element 2= 2 element 3= 0 element 4= 8

Here are elements of the array: 1 2 0 8

Lưu ý, do mảng tương tự con trỏ nên truyền mảng bao giờ cũng là truyền theo tham chiếu, không phải theo tham trị.

Hết bài 7a

__________________

Một phần của tài liệu Lý thuyết lập trình C++ (Trang 28 - 33)

Tải bản đầy đủ (DOCX)

(78 trang)
w