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ếu là truyề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
__________________