Từ 1 tới 1 trên tổng số 1 kết quả

Đề tài: Dịch Series Tutorial C++ từ LearnCPP.com

  1. #1
    Ngày gia nhập
    11 2010
    Bài viết
    2

    Mặc định Dịch Series Tutorial C++ từ LearnCPP.com

    7.1 - Tham số và đối số của hàm
    Nhằm mục đích thảo luận sâu hơn, chúng ta sẽ phân biệt 2 khái niệm: tham số của hàm và đối số của hàm:
    Một tham số của hàm là một biến được khai báo trong nguyên mẫu hàm hay trong header của hàm:

    C++ Code:
    1. void foo(int x); // prototype -- x is a parameter
    2. void foo(int x) // declaration -- x is a parameter
    3. {
    4. }

    Một đối số là một giá trị được truyền cho hàm ở chỗ của tham số

    C++ Code:
    1. foo(6); // 6 is the argument passed to parameter x
    2. foo(y+1); // the value of y+1 is the argument passed to parameter x

    Khi một hàm được gọi, tất cả các tham số của hàm được tạo ra với tư cách là các biến, và giá trị của đối số được copy vào các tham số này. Ví dụ:

    C++ Code:
    1. void foo(int x, int y)
    2. {
    3. }
    4.  
    5. foo(6, 7);

    Khi foo() được gọi với các đối số 6 và 7, tham số x của foo() được tạo ra và được gán giá trị 6, và tham số y của foo() được tạo ra và được gán giá trị 7.

    Mặc dù các tham số không được khai báo bên trong thân hàm, nhưng các tham số của hàm có phạm vi cục bộ. Điều này có nghĩa là chúng được tạo ra khi hàm được gọi, và sẽ bị mất đi khi hàm kết thúc:

    C++ Code:
    1. void foo(int x, int y) // x và y được tạo ra ở đây
    2. {
    3. } // x và y bị xóa bỏ ở đây

    Có 3 phương pháp chính đê truyền đối số cho hàm: truyền giá trị, truyền tham chiếu, truyền địa chỉ. Các mục tiếp theo sẽ giải quyết từng trường hợp truyền tham số trên.

    7.2 Truyền tham số bằng giá trị

    Mặc định, tham số trong C++ được truyền theo giá trị. Khi đối số được truyền bằng giá trị , một bản sao của đối số được truyền cho hàm. Xem xét đoạn mã sau:
    C++ Code:
    1. void foo(int y)
    2. {
    3.     using namespace std;
    4.     cout << "y = " << y << endl;
    5. }
    6.  
    7. int main()
    8. {
    9.     foo(5); // first call
    10.  
    11.     int x = 6;
    12.     foo(x); // second call
    13.     foo(x+1); // third call
    14.  
    15.     return 0;
    16. }
    Trong lần đầu tiên gọi hàm foo(), đối số là một trực hằng 5. Khi foo() được gọi, biến y được tạo ra và giá trị 5 được chép vào y. Biến y sau đó bị phá hủy khi foo() kết thúc.

    Trong lần thứ 2 gọi hàm foo(), đối số là biến x. x được tính toán để tạo ra giá trị 6. Khi foo() được gọi lần thứ 2, biến y lại được tạo ra, và giá trị 6 được chép vào y. Biến y sau đó bị hủy khi foo() kết thúc.

    Trong lần gọi thứ 3, đối số là biểu thức x+1. x+1 được tính để tạo ra giá trị 7, rồi được truyền cho biến y. Biến y một lần nữa, lại bị hủy khi foo() kết thúc.

    Vì thế, chương trình in ra :
    C++ Code:
    1. y = 5
    2. y = 6
    3. y = 7

    Bởi vì một bản sao của đối số được truyền cho hàm, đối số ban đầu sẽ không bị hàm sửa đổi giá trị. Điều này được chỉ ra trong ví dụ sau:
    C++ Code:
    1. void foo(int y)
    2. {
    3.     using namespace std;
    4.     cout << "y = " << y << endl;
    5.  
    6.     y = 6;
    7.  
    8.     cout << "y = " << y << endl;
    9. } // y is destroyed here
    10.  
    11. int main()
    12. {
    13.     using namespace std;
    14.     int x = 5;
    15.     cout << "x = " << x << endl;
    16.  
    17.     foo(x);
    18.  
    19.     cout << "x = " << x << endl;
    20.     return 0;
    21. }
    Kết quả in ra:
    C++ Code:
    1. x = 5
    2. y = 5
    3. y = 6
    4. x = 5
    Ở lần đầu tiên, x =5. Khi foo() được gọi, giá trị của x (5) được truyền cho biến y bên trong foo(). y được gán giá trị 6, và sau đó bị hủy. Giá trị của x không thay đổi, mặc dù y thay đổi.

    Thuận lợi của việc truyền theo giá trị:
    • Đối số được truyền theo giá trị có thể là biến, trực hằng (vd:5), biểu thức (eg:x+1)

    • Đối số không bị sửa đổi bởi lời gọi hàm, điều này sẽ ngăn chặn các hiệu ứng biên xảy ra.


    Nhược điểm của truyền theo giá trị:
    • Copy một struct hay lớp kích thước lớn mất nhiều thời gian, ảnh hưởng tới hiệu năng chương trình khi hàm được gọi nhiều lần.

    Trong hầu hết các trường hợp, truyền theo giá trị là cách tốt nhất để truyền đối số cho hàm - nó linh hoạt và an toàn.

    7.3 Truyền đối số bằng tham chiếu

    Khi truyền đối số theo giá trị, cách duy nhất để trả về một giá trị cho lời gọi hàm là thông qua giá trị trả về của hàm. Nhưng nhiều khi ta cần viết một hàm có khả năng sửa đổi giá trị của một mảng (vd: sắp xếp mảng). Trong trường hợp này, ta cần khả năng sửa đổi mảng thật sự được truyền vào hàm, thay vì trả lại giá trị cho lời gọi hàm.

    Một trong những cách cho phép hàm sửa đổi giá trị của đối số là truyền bằng tham chiếu. Trong truyền bằng tham chiếu, ta khai báo các tham số hàm là các tham chiếu thay vì các biến thông thường:
    C++ Code:
    1. void AddOne(int &y) // y is a reference variable
    2. {
    3.     y = y + 1;
    4. }
    Khi hàm này được gọi, y sẽ trở thành một tham chiếu tới đối số. Vì một tham chiếu tới một biến được đối xử y như bản thân biến đó, bất cứ thay đổi này được thực hiện trên tham chiếu cũng sẽ là thực hiện trên đối số!
    Đoạn code sau chỉ ra điều này:
    C++ Code:
    1. void foo(int &y) // y is now a reference
    2. {
    3.     using namespace std;
    4.     cout << "y = " << y << endl;
    5.     y = 6;
    6.     cout << "y = " << y << endl;
    7. } // y is destroyed here
    8.  
    9. int main()
    10. {
    11.     int x = 5;
    12.     cout << "x = " << x << endl;
    13.     foo(x);
    14.     cout << "x = " << x << endl;
    15.     return 0;
    16. }
    Chương trình này giống hệt như chương trình ta đã dùng trong vd truyền theo giá trị, ngoại trừ tham số của foo() bây giờ là một tham chiếu thay cho một biến thông thường. Khi chúng ta gọi foo(x), y trở thành một tham chiếu tới x. Kết quả in ra :
    C++ Code:
    1. x = 5
    2. y = 5
    3. y = 6
    4. x = 6
    Chú ý rằng giá trị của x đã bị thay đổi sau lời gọi hàm.
    Đây là một ví dụ khác:
    C++ Code:
    1. void AddOne(int &y)
    2. {
    3.     y++;
    4. }
    5.  
    6. int main()
    7. {
    8.     int x = 5;
    9.  
    10.     cout << "x = " << x << endl;
    11.     AddOne(x);
    12.     cout << "x = " << x << endl;
    13.  
    14.     return 0;
    15. }
    Ví dụ này in ra :
    C++ Code:
    1. x = 5
    2. y = 6
    Bạn thấy đấy, hàm có thể thay đổi giá trị của đối số.
    Thỉnh thoảng ta cần một hàm có thể trả về nhiều giá trị. Tuy nhiên, hàm chỉ có thể có một giá trị trả về. Một các để trả về nhiều giá trị là dùng tham số là tham chiếu
    C++ Code:
    1. #include <iostream>
    2. #include <math.h>    // for sin() and cos()
    3.  
    4. void GetSinCos(double dX, double &dSin, double &dCos)
    5. {
    6.     dSin = sin(dX);
    7.     dCos = cos(dX);
    8. }
    9.  
    10. int main()
    11. {
    12.     double dSin = 0.0;
    13.     double dCos = 0.0;
    14.  
    15.     // GetSinCos will return the sin and cos in dSin and dCos
    16.     GetSinCos(30.0, dSin, dCos);
    17.  
    18.     std::cout << "The sin is " << dSin << std::endl;
    19.     std::cout << "The cos is " << dCos << std::endl;
    20.     return 0;
    21. }
    Hàm này lấy một tham số (truyền giá trị) làm input, và "trả về" 2 tham số (truyền tham chiếu) là output.

    Truyền const reference
    Một trong các nhược điểm của truyền theo giá trị là tất cả các đối số được truyền theo giá trị sẽ được sao chép để gán cho các tham số. Khi đối số là một struct hay lớp có kích thước lớn, điều này có thể mất nhiều thời gian. Tham chiếu cung cấp một các để tránh điều này. Khi một đối số được truyền theo tham chiếu, một tham chiếu được tạo ra để tham chiếu tới đối số thật sự (cần ít thời gian) mà không thực hiện sao chép giá trị. Điều này cho phép ta truyền những lớp và struct kích thước lớn mà không làm ảnh hưởng nhiều đến hiệu năng chương trình.

    Tuy nhiên điều này cũng dẫn tới một vấn đề tiềm tàng. Tham chiếu cho phép hàm thay đổi giá trị của đối số, mà trong nhiều trường hợp ta không muốn. Nếu ta biết rằng một hàm không nên thay đổi giá trị của đối số, nhưng cũng không muốn truyền theo giá trị, giải pháp tốt nhất là truyền bằng tham chiếu hằng.

    Bạn đã biết rằng tham chiếu hằng (const reference) là một tham chiếu không cho phép thay đổi giá trị của biến mà nó tham chiếu tới (thông qua tham chiếu đó). Do đó , nếu ta dùng một tham chiếu hằng làm tham số, chúng ta đảm bảo rằng lời gọi hàm đó sẽ không (và không thể) thay đổi giá trị đối số!
    Hàm sau sẽ sinh ra một lỗi biên dịch:
    C++ Code:
    1. void foo(const int &x)
    2. {
    3.     x = 6;  // x is a const reference and can not be changed!
    4. }
    Dùng const hữu ích vì:
    • Nó tranh thủ sự giúp đỡ của trình biên dịch để đảm bảo rằng những giá trị không nên thay đổi sẽ không bị thay đổi

    • Nó bảo cho coder rằng họ không cần lo lắng về việc hàm có thể thay đổi giá trị của đối số

    • Giúp đỡ coder dễ dàng debug hơn

    Rule: Luôn truyền bằng tham chiếu hằng trừ khi cần thay đổi giá trị của đối số.
    Tóm tắt
    Ưu điểm của truyền theo tham chiếu

    • Vì không cần tạo ra bản sao của đối số, nó sẽ chạy nhanh hơn đặc biệt với các struct hay class kích thước lớn
    • Cho phép hàm thay đổi giá trị của đối số, đôi khi ta cần phải làm thế

    • Ta có thể truyền bằng tham chiếu hằng để tránh những sự thay đổi không mong muốn

    • Có thể trả về nhiều giá trị từ một hàm


    Nhược điểm của truyền bằng tham chiếu:
    • Vì một non-const reference không thể tham chiếu tới một trực hằng hoặc một biểu thức, đối số tham chiếu phải là một biến
    • Khó xác định rằng một tham số được truyền theo tham chiếu sẽ đóng vai trò input hay output, hay cả hai
    • Từ lời gọi hàm không biết được đối số có thể bị thay đổi hay không, vì một đối số được truyền theo giá trị và theo tham chiếu trong lời gọi hàm trông đều giống nhau. Chúng ta chỉ có thể biết được biến được truyền theo tham chiếu hay giá trị khi xem khai báo hàm. Điều này có thể dẫn đến tình huống lập trình viên không nhận thức được rằng hàm có thể thay đổi giá trị của đối số.
    • Tham chiếu thường được cài đặt bởi con trỏ trong C++, và giải tham chiếu một con trỏ chậm hơn truy cập trực tiếp nó, truy cập biến được truyền theo tham chiếu chậm hơn biến được truyền theo giá trị.


    7.4 Truyền theo địa chỉ
    Có một cách nữa để truyền biến cho hàm, đó là bằng địa chỉ. Truyền một đối số bằng địa chỉ liên quan đến việc truyền địa chỉ của biến đối số thay vì bản thân biến đó.Bởi vì đối số là một địa chỉ, tham số hàm phải là một con trỏ. Sau đó hàm có thể giải tham chiếu con trỏ đó để truy cập hoặc thay đổi giá trị đang được tham chiếu tới.
    Đây là một ví dụ của hàm lấy một tham số được truyền bởi địa chỉ:
    C++ Code:
    1. void foo(int *pValue)
    2. {
    3.     *pValue = 6;
    4. }
    5.  
    6. int main()
    7. {
    8.     int nValue = 5;
    9.  
    10.     cout << "nValue = " << nValue << endl;
    11.     foo(&nValue);
    12.     cout << "nValue = " << nValue << endl;
    13.     return 0;
    14. }
    Đoạn code trên in ra:
    C++ Code:
    1. nValue = 5
    2. nValue = 6
    Bạn thấy đấy, hàm foo() thay đổi giá trị của nValue thông qua tham số con trỏ pValue.

    Truyền theo địa chỉ thường được dùng với biến cấp phát động và mảng. Ví dụ, hàm sau sẽ in tất cả giá trị trong một mảng:
    C++ Code:
    1. void PrintArray(int *pnArray, int nLength)
    2. {
    3.     for (int iii=0; iii < nLength; iii++)
    4.         cout << pnArray[iii] << endl;
    5. }
    Còn đây là chương trình gọi hàm này:
    C++ Code:
    1. int main()
    2. {
    3.     int anArray[6] = { 6, 5, 4, 3, 2, 1 };
    4.     PrintArray(anArray, 6);
    5. }
    Chương trình in ra như sau:
    C++ Code:
    1. 6
    2. 5
    3. 4
    4. 3
    5. 2
    6. 1
    Chú ý rằng độ dài của mảng phải được truyền vào cho một tham số của hàm, bởi vì bản thân mảng không lưu giữ độ dài của nó. Nếu không hàm PrintArray() sẽ không biết cần in ra bao nhiêu phần tử.

    Tốt nhất ta nên kiểm tra tham số được truyền bởi con trỏ không NULL trước khi giải tham chiếu chúng. Giải tham chiếu một con trỏ NULL thường sẽ làm chương trình crash. Đây là hàm PrintArray() có kiểm tra con trỏ NULL:
    C++ Code:
    1. void PrintArray(int *pnArray, int nLength)
    2. {
    3.     // if user passed in a null pointer for pnArray, bail out early!
    4.     if (!pnArray)
    5.         return;
    6.  
    7.     for (int iii=0; iii < nLength; iii++)
    8.         cout << pnArray[iii] << endl;
    9. }
    Ưu điểm truyền theo địa chỉ:
    • Cho phép hàm thay đổi giá trị của đối số
    • Vì truyền theo địa chỉ, nên tốc độ nhanh, không phải copy lượng dữ liệu lớn
    • Có thể trả về nhiều giá trị từ một hàm


    Nhược điểm:
    • Vì trực hằng và biểu thức không có địa chỉ, đối số con trỏ phải là biến thông thường
    • Tất cả các biến đều cần kiểm tra xem có NULL không. Cố giải tham chiếu một con trỏ null sẽ gây ra scrash. Ta thường hay quyên điều này
    • Giải tham chiếu một con trỏ chậm hơn truy cập trực tiếp, truy cập vào một đối số được truyền theo địa chỉ chậm hơn truy cập đối số được truyền theo giá trị

    Bạn có thể thấy, truyền theo địa chỉ và truyền theo tham chiếu có những ưu điểm và nhược điểm giống nhau.
    Bởi vì truyền theo tham chiếu thường an toàn hơn truyền theo địa chỉ, hầu hết các trường hợp nên truyền theo tham chiếu

    Truyền theo tham chiếu, địa chỉ và giá trị thật sự cũng không quá khác nhau
    Bây giờ bạn đã hiểu sự khác nhau cơ bản giữa truyền theo giá trị, theo tham chiếu và theo địa chỉ, giờ hãy làm rắc rối thêm bằng cách đơn giản hóa chúng

    Ta đã đề cập ngắn gọn rằng tham chiếu thông thường được cài đặt trong các trình biên dịch bằng cách dùng con trỏ. Vì thế, khác biệt thực sự duy nhất giữa con trỏ và tham chiếu là tham chiếu có cú pháp dễ hiểu và bị giới hạn hơn con trỏ. Điều này làm tham chiếu dễ dùng và an toàn hơn, nhưng cũng kém linh hoạt hơn. Nó cũng có nghĩa là truyền bằng tham chiếu và con trỏ cơ bản là giống hệt nhau về mặt hiệu suất.

    Đây là một điểm có thể làm bạn ngạc nhiên: khi bạn truyền một địa chỉ tới hàm, địa chỉ đó thật ra được truyền bằng giá trị! Bởi vì địa chỉ được truyền bằng giá trị, nếu bạn thay đổi giá trị của địa chỉ đó trong hàm, bạn thực ra đang thay đổi một bản copy tạm của nó. Vì thế, địa chỉ của con trỏ gốc sẽ không thay đổi !

    Sau đây là ví dụ minh họa:
    C++ Code:
    1. #include <iostream>
    2.  
    3. int nFive = 5;
    4. int nSix = 6;
    5.  
    6. // Function prototype so we can define
    7. // SetToSix below main()
    8. void SetToSix(int *pTempPtr);
    9.  
    10. int main()
    11. {
    12.     using namespace std;
    13.  
    14.     // First we set pPtr to the address of nFive
    15.     // Which means *pPtr = 5
    16.     int *pPtr = &nFive;
    17.  
    18.     // This will print 5
    19.     cout << *pPtr;
    20.  
    21.     // Now we call SetToSix (see function below)
    22.     // pTempPtr receives a copy of the address of pPtr
    23.     SetToSix(pPtr);
    24.  
    25.     // pPtr is still set to the address of nFive!
    26.     // This will print 5
    27.     cout << *pPtr;
    28.  
    29.     return 0;
    30. }
    31.  
    32. // pTempPtr copies the value of pPtr!
    33. void SetToSix(int *pTempPtr)
    34. {
    35.     using namespace std;
    36.  
    37.     // This only changes pTempPtr, not pPtr!
    38.     pTempPtr = &nSix;
    39.  
    40.     // This will print 6
    41.     cout << *pTempPtr;
    42. }
    Bởi vì pTempPtr nhận một bản sao địa chỉ của pPtr, mặc dù chúng ta thay đổi pTempPtr, nó không thay đổi giá trị mà pPtr trỏ tới. Vì thế chương trình này in ra:
    C++ Code:
    1. 565
    Mặc dù địa chỉ được truyền theo giá trị, ta có thể giải tham chiếu địa chỉ đó để thay đổi hẳn giá trị ở địa chỉ mà nó trỏ tới. Đây là điểm khác biệt giữa truyền bằng tham chiếu, địa chỉ so với truyền bằng giá trị.

    Câu hỏi hợp lý tiếp theo là : "Làm sao thay đổi được địa chỉ mà đối số con trỏ trỏ tới". Câu trả lời thật đơn giản: truyền con trỏ đó theo tham chiếu. Lúc đó ta có thể thay đổi được địa chỉ mà con trỏ đó (đối số) trỏ tới. Cú pháp hơi lạ, và rất dễ viết ngược lại : int *&pPtr. Tuy nhiên nếu bạn viết ngược lại thành &*, trình biên dịch sẽ báo lỗi cho bạn.
    Chương trình sau minh họa cách dùng tham chiếu tới một con trỏ:
    C++ Code:
    1. // pTempPtr is now a reference to a pointer to pPtr!
    2. // This means if we change pTempPtr, we change pPtr!
    3. void SetToSix(int *&pTempPtr)
    4. {
    5.     using namespace std;
    6.  
    7.     pTempPtr = &nSix;
    8.  
    9.     // This will print 6
    10.     cout << *pTempPtr;
    11. }
    Chú ý rằng bạn cũng cần phải cập nhận lại nguyên mẫu hàm của hàm SetToSix() cho phù hợp:
    C++ Code:
    1. // Function prototype so we can define
    2. // SetToSix below main()
    3. void SetToSix(int *&pTempPtr);
    Khi chạy chương trình với phiên bản này của hàm ta nhận được:
    C++ Code:
    1. 566
    kết quả cho thấy lời gọi hàm SetToSix() đã thực sự làm thay đổi địa chỉ của pPtr!

    7.4a Hàm trả về giá trị, tham chiếu và địa chỉ
    Trong 3 bài học trước, bạn đã học cách truyền đối số cho hàm bằng giá trị, tham chiếu và địa chỉ. Trong mục này, chúng ta sẽ xem xét vấn đề trả về giá trị cho lời gọi hàm thông qua tất cả 3 phương pháp.

    Trả về giá trị

    Trả về một giá trị là kiểu trả về đơn giản và an toàn nhất. Khi hàm return một giá trị, một bản sao của giá trị đó được trả về cho lời gọi hàm. Giống như truyền tham số bằng giá trị, ta có thể trả về một giá trị trực hằng (vd : 5), biến (vd: x), hay biểu thức (vd: x+1), nên nó rất linh hoạt.

    Ưu điểm khác của việc trả về một giá trị là bạn có thể trả về một biến hay biểu thức liên quan đến biến cục bộ được khai báo bên trong hàm. Bởi vì những biến này được tính toán trứoc khi hàm kết thúc, và một bản sao của giá trị được trả về cho lời gọi hàm, nên những biến cục bộ này có bị hủy khi kết thúc hàm cũng chẳng sao.
    C++ Code:
    1. int DoubleValue(int nX)
    2. {
    3.     int nValue = nX * 2;
    4.     return nValue; // Một bản sao của nValue sẽ được trả về ở đây
    5. } // nValue bị hủy ở đây
    Trả về giá trị rất thích hợp khi cần trả về một biến được khai báo bên trong hàm, hay trả về các tham số của hàm được truyền theo giá trị. Nhưng cũng giống như truyền tham số bằng giá trị, trả về giá trị cũng chậm khi trả về các struct hay class

    Trả về một tham chiếu

    Cũng giống như truyền tham chiếu, kiểu trả về phải là tham chiếu đến một biến. Ta không thể trả về một tham chiếu đến một hằng hay một biểu thức. Khi một biến được trả về theo tham chiếu, một tham chiếu tới biến đó được trả về cho lời gọi hàm. Sau đó lời gọi hàm có thể dùng tham chiếu này để sửa đổi giá trị của biến. Trả về tham chiếu cũng nhanh, hữu ích khi trả về một struct hay class

    Tuy nhiên trả về một tham chiếu còn một điểm yếu nữa: bạn không thể trả về một tham chiếu tới một biến cục bộ của hàm. Xem ví dụ sau:
    C++ Code:
    1. int& DoubleValue(int nX)
    2. {
    3.     int nValue = nX * 2;
    4.     return nValue; // Trả về một tham chiếu tới nValue
    5. } // nValue bị giải phóng ở đây
    Có vấn đề gì thế? Hàm này cố trả về một tham chiếu tới một biến cục bộ mà sẽ bị giải phóng khi hàm kết thúc. Điều này có nghĩa là lời gọi hàm nhận được một tham chiếu tới một đối tượng không còn tồn tại. May mắn là trình biên dịch của bạn sẽ báo lỗi khi bạn cố làm thế.

    Trả về một tham chiếu thường được dùng để trả về đối số được truyền bằng tham chiếu cho hàm ngược lại cho lời gọi hàm. Trong ví dụ sau, ta trả về (bằng tham chiếu) một phần tử của mảng được truyền cho hàm bằng tham chiếu
    C++ Code:
    1.  
    2. // struct này chứa một mảng 25 số nguyên
    3. struct FixedArray25
    4. {
    5.     int anValue[25];
    6. };
    7.  
    8. // trả về một tham chiếu tới phần tử nIndex của rArray
    9. int& Value(FixedArray25 &rArray, int nIndex)
    10. {
    11.     return rArray.anValue[nIndex];
    12. }
    13.  
    14. int main()
    15. {
    16.     FixedArray25 sMyArray;
    17.  
    18.     // Thiết lập phần tử thứ 10 của sMyArray thành 5
    19.     Value(sMyArray, 10) = 5;
    20.  
    21.     cout << sMyArray.anValue[10] << endl;
    22.     return 0;
    23. }
    Kết quả in ra :
    C++ Code:
    1. 5
    Khi chúng ta gọi Value(sMyArray, 10), Value() trả về một tham chiếu tới phần tử thứ 10 của mảng trong sMyArray. Sau đó main() dùng tham chiếu này để gán phần tử đó thành 5.

    Dù đây chỉ là ví dụ mang tính minh họa vì bạn có thể truy cập sMyArray.anValue trực tiếp, nhưng khi bạn học về lớp, bạn sẽ thấy trả về tham chiếu có nhiều điểm hữu ích

    Trả về một địa chỉ

    Trả về một địa chỉ liên quan đến việc trả về địa chỉ của một biến cho lời gọi hàm. Giá trị trả về chỉ có thể là địa chỉ của một biến, chứ không phải hằng hay biểu thức. Giống như trả về tham chiếu, trả về địa chỉ cũng nhanh nhưng không thể trả về địa chỉ của một biến cục bộ:
    C++ Code:
    1. int* DoubleValue(int nX)
    2. {
    3.     int nValue = nX * 2;
    4.     return &nValue; // trả về địa chỉ của nValue
    5. } // nValue bị giải phóng
    Bạn thấy đấy, nValue bị giải phóng sau khi địa chỉ của nó được trả về cho lời gọi hàm. Kết quả là lời gọi hàm nhận được địa chỉ của một vùng nhớ không được cấp phát, sẽ gây ra nhiều vấn đề khi sử dụng. Đây là một trong những lỗi lập trình phổ biến mà những lập trình viên mới hay mắc phải. Nhiều trình biên dịch mới sẽ đưa ra một cảnh báo (warning), không phải là một lỗi, khi LTV cố trả về địa chỉ của một biến cục bộ.

    Trả về địa chỉ thường dùng để trả về địa chỉ của vùng nhớ mới được cấp phát cho lời gọi hàm:
    C++ Code:
    1. int* AllocateArray(int nSize)
    2. {
    3.     return new int[nSize];
    4. }
    5.  
    6. int main()
    7. {
    8.     int *pnArray = AllocateArray(25);
    9.     // do stuff with pnArray
    10.  
    11.     delete[] pnArray;
    12.     return 0;
    13. }

    Kết luận

    Nói chung, trả về giá trị là đủ cho nhu cầu của bạn. Nó cũng là cách linh hoạt và an toàn nhất để trả về thông tin cho lời gọi hàm. Tuy nhiên trả về tham chiếu hay địa chỉ cũng có ích, đặc biệt khi làm việc với các lớp hoặc struct được cấp phát động. Khi trả về tham chiếu hoặc giá trị, hãy đảm bảo là bạn không trả về một tham chiếu tới, hay một địa chỉ của, một biến cục bộ trong hàm

    7.5 Inline Function

    Dùng hàm có nhiều thuận lợi:
    • Đoạn code trong hàm có thể dùng lại
    • Thay vì update code ở mọi nơi hàm xuất hiện, ta chỉ cần update code một lần trong hàm. Giảm code trùng lặp
    • Dễ đọc, dễ hiểu code, vì ta có thể hiểu hàm làm gì mà không cần hiểu nó làm điều đó như thế nào
    • Hàm cho phép kiểm tra kiểu

    Tuy nhiên, khi gọi hàm, ta phải thực hiện vài chi phí phụ như lưu lại địa chỉ của chỉ lệnh hiện tại để biết nơi quay về khi hàm kết thúc, tạo ra các tham số và gán giá trị, sau đó lại giải phóng chúng, etc... Code được viết trực tiếp tại chỗ chạy nhanh hơn hẳn

    Với những hàm lớn hay thực hiện tác vụ phức tạp, chi phí phụ khi gọi hàm có thể không đáng kể so với thời gian hàm chạy. Tuy nhiên với những hàm nhỏ, thường xuyên được dùng, thời gian cần thiết để thực hiện lời gọi hàm thường nhiều hơn hẳn thời gian thực sự chạy đoạn code trong thân hàm. Điều này sẽ ảnh hưởng đến hiệu năng chương trình.

    C++ đưa ra một cách để tổ hợp ưu điểm của hàm và tốc độ của code được viết trực tiếp tại chỗ: hàm inline. Từ khóa inline được dùng để yêu cầu trình biên dịch đối xử với hàm của bạn như một inline function. Khi trình biên dịch biên dịch code của bạn, tất cả các hàm inline được "expanded in-place", đó là, lời gọi hàm sẽ được thay thế bằng bản sao nội dung hàm, bỏ đựoc chi phí phụ khi thực hiện lời gọi hàm. Điểm yếu là vì hàm inline được explanded in-place cho mọi lời gọi hàm, nó sẽ làm cho chương trình sau khi biên dịch lớn hơn đặc biệt khi hàm inline dài hay được gọi nhiều lần
    Xem xét đoạn code sau:
    C++ Code:
    1. int min(int nX, int nY)
    2. {
    3.     return nX > nY ? nY : nX;
    4. }
    5.  
    6. int main()
    7. {
    8.     using namespace std;
    9.     cout << min(5, 6) << endl;
    10.     cout << min(3, 2) << endl;
    11.     return 0;
    12. }
    chương trình này gọi hàm min() hai lần, chịu chi phí phụ khi gọi hàm 2 lần. Vì min() là một hàm ngắn, nó là ứng viên hoàn hảo cho inlining:
    C++ Code:
    1. inline int min(int nX, int nY)
    2. {
    3.     return nX > nY ? nY : nX;
    4. }
    Bây giờ khi chương trình biên dịch hàm main(), nó sẽ tạo ra mã máy như thể main() được viết như sau:
    C++ Code:
    1. int main()
    2. {
    3.     using namespace std;
    4.     cout << (5 > 6 ? 6 : 5) << endl;
    5.     cout << (3 > 2 ? 2 : 3) << endl;
    6.     return 0;
    7. }
    Chương trình sau chạy nhanh hơn một chút, code đã biên dịch ra cũng lớn hơn một chút

    Do kích thước chương trình sẽ bị tăng lên, nên hàm inline phù hợp nhất cho các hàm ngắn, chỉ có một vài dòng code, thường đựoc gọi bên trong vòng lặp và không có lệnh rẽ nhánh. Cũng lưu ý rằng từ khóa inline chỉ là một yêu cầu, trình biên dịch sẽ cố gắng biên dịch theo kiểu inline nếu nó thấy phù hợp (nó có thể bỏ qua yêu cầu inline nếu thấy hàm quá dài).

    7.6 Chồng hàm

    continue...
    Đã được chỉnh sửa lần cuối bởi ngocdaibk49 : 25-10-2011 lúc 11:27 PM.

Các đề tài tương tự

  1. XML Tutorial, Basic to Advanced
    Gửi bởi luc13aka47 trong diễn đàn Thủ thuật, Tutorials Database
    Trả lời: 1
    Bài viết cuối: 10-10-2016, 04:46 PM
  2. [TUTORIAL] Cách bảo vệ phần mềm .Net bằng HWDI
    Gửi bởi dunggttn trong diễn đàn Tutorials và Thủ thuật lập trình C#, ASP.NET
    Trả lời: 0
    Bài viết cuối: 28-04-2013, 11:38 PM
  3. C# Tutorial - Từ cơ bản tới nâng cao
    Gửi bởi luc13aka47 trong diễn đàn Tutorials và Thủ thuật lập trình C#, ASP.NET
    Trả lời: 7
    Bài viết cuối: 19-03-2013, 12:21 PM
  4. Lập trình Objective C Trên Ubuntu [Tutorial]
    Gửi bởi AlexF trong diễn đàn Tutorials và Thủ thuật lập trình Objective-C
    Trả lời: 2
    Bài viết cuối: 28-06-2011, 11:34 AM
  5. [Tutorial] Công cụ phát triển ứng dụng C/C++
    Gửi bởi Kevin Hoang trong diễn đàn Thắc mắc chung
    Trả lời: 2
    Bài viết cuối: 07-10-2009, 04:46 PM

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn