Giá trị mặc định của tham số hàm


130

1.

int Add (int a, int b = 3);
int Add (int a, int b)
{

}

2.

int Add (int a, int b);
int Add (int a, int b = 3)
{

}

Cả hai đều làm việc; Đó là cách tiêu chuẩn và tại sao ?

Câu trả lời:


203

Nếu bạn đặt khai báo trong một tệp tiêu đề và định nghĩa trong một .cpptệp riêng biệt và #includetiêu đề từ một .cpptệp khác , bạn sẽ có thể thấy sự khác biệt.

Cụ thể, giả sử:

lib.h

int Add(int a, int b);

lib.cpp

int Add(int a, int b = 3) {
   ...
}

test.cpp

#include "lib.h"

int main() {
    Add(4);
}

Việc biên dịch test.cppsẽ không thấy khai báo tham số mặc định và sẽ bị lỗi.

Vì lý do này, định nghĩa tham số mặc định thường được chỉ định trong khai báo hàm :

lib.h

int Add(int a, int b = 3);

Sau đó, bsẽ được xác định nhiều lần, một lần cho mỗi đơn vị biên dịch bao gồm lib.h, điều đó có đúng không?
httpinterpret

@httpinterpret: theo một nghĩa có, giá trị mặc định bđược xác định một lần cho mỗi .cpp tệp bao gồm tiêu đề. Nhưng không sao, vì bạn chỉ có một tuyên bố về Addchức năng.
Greg Hewgill

1
@httpinterpret Trình biên dịch sẽ thêm tham số không được chỉ định bởi tham số mặc định khi mã người gọi được xác định. Đó là lý do tại sao giá trị mặc định PHẢI nằm trong nguyên mẫu hàm chứ không phải trong triển khai hàm. Tham số không được xác định theo nghĩa định nghĩa biến vì nguyên mẫu không xác định biến.
harper

1
Câu trả lời này có thể được chỉnh sửa vì phân tích cú pháp nhanh (chỉ nhìn vào mã và không đi cho đến khi "Vì lý do này") khiến tôi hiểu điều ngược lại với ý của bạn.
Gabriel Devillers

44

Trong C ++, các yêu cầu áp đặt cho các đối số mặc định liên quan đến vị trí của chúng trong danh sách tham số như sau:

  1. Đối số mặc định cho một tham số đã cho phải được chỉ định không quá một lần. Chỉ định nó nhiều lần (ngay cả với cùng một giá trị mặc định) là bất hợp pháp.

  2. Các tham số với các đối số mặc định phải tạo thành một nhóm liền kề ở cuối danh sách tham số.

Bây giờ, hãy nhớ rằng, trong C ++, bạn được phép "phát triển" tập hợp các tham số có đối số mặc định từ một khai báo của hàm sang tiếp theo, miễn là các yêu cầu trên được thỏa mãn liên tục.

Ví dụ: bạn có thể khai báo một hàm không có đối số mặc định

void foo(int a, int b);

Để gọi hàm đó sau khi khai báo như vậy, bạn sẽ phải chỉ định rõ ràng cả hai đối số.

Sau này (tiếp tục xuống) trong cùng một đơn vị dịch, bạn có thể khai báo lại lần nữa, nhưng lần này với một đối số mặc định

void foo(int a, int b = 5);

và từ thời điểm này, bạn có thể gọi nó chỉ bằng một đối số rõ ràng.

Xa hơn nữa bạn có thể khai báo lại một lần nữa thêm một đối số mặc định

void foo(int a = 1, int b);

và từ thời điểm này, bạn có thể gọi nó mà không có đối số rõ ràng.

Ví dụ đầy đủ có thể trông như sau

void foo(int a, int b);

int main()
{
  foo(2, 3);

  void foo(int a, int b = 5); // redeclare
  foo(8); // OK, calls `foo(8, 5)`

  void foo(int a = 1, int b); // redeclare again
  foo(); // OK, calls `foo(1, 5)`
}

void foo(int a, int b)
{
  // ...
}

Đối với mã trong câu hỏi của bạn, cả hai biến thể đều hoàn toàn hợp lệ, nhưng chúng có nghĩa là những thứ khác nhau. Biến thể đầu tiên khai báo một đối số mặc định cho tham số thứ hai ngay lập tức. Biến thể thứ hai ban đầu khai báo hàm của bạn không có đối số mặc định và sau đó thêm một đối số cho tham số thứ hai.

Hiệu ứng ròng của cả hai khai báo của bạn (tức là cách mã được nhìn thấy sau khai báo thứ hai) hoàn toàn giống nhau: hàm có đối số mặc định cho tham số thứ hai của nó. Tuy nhiên, nếu bạn quản lý để nén một số mã giữa khai báo thứ nhất và khai báo thứ hai, hai biến thể này sẽ hoạt động khác nhau. Trong biến thể thứ hai, hàm không có đối số mặc định giữa các khai báo, vì vậy bạn sẽ phải chỉ định rõ ràng cả hai đối số.


Tôi không nghĩ mã của bạn được xác định void foo (int a = 1, int b) sẽ hoạt động. Bạn cần có tất cả các tham số tùy chọn sau một tham số tùy chọn. Đó là lỗi cú pháp (ít nhất là với g ++ 4.5.3 trên hệ thống của tôi).
Nilesh

@Nilesh: Như tôi đã nói rõ ràng ở trên (và đó là toàn bộ điểm của ví dụ này) void foo(int a = 1, int b)để làm việc, nó phải được khai báo sau void foo(int a, int b = 5) . Vâng, nó sẽ làm việc. Và không, nó không phải là một lỗi cú pháp. g ++ 4.5.3 sẽ biên dịch nó hoàn toàn tốt.
AnT

Được rồi, hàm lấy giá trị của b từ khai báo trước. Bắt điều này ngay bây giờ. Cảm ơn :-)
Nilesh

1
@Nilesh: Có, các khai báo đối số mặc định được tích lũy trên tất cả các khai báo trước đó trong đơn vị dịch.
AnT

1
Tôi thích viết các nguyên mẫu hàm của tôi mà không có tên biến, như int foo(int). Tôi thấy rằng tôi có thể viết int foo(int=5)lại, bỏ qua các tên tham số. Không ai có vẻ đã đề cập đến điều đó.
Victor Eijkhout

5

Cách thứ nhất sẽ được ưu tiên cho cách thứ hai.

Điều này là do tệp tiêu đề sẽ hiển thị rằng tham số là tùy chọn và giá trị mặc định của nó sẽ là gì. Ngoài ra, điều này sẽ đảm bảo rằng giá trị mặc định sẽ giống nhau, bất kể việc triển khai tệp .cpp tương ứng.

Theo cách thứ hai, không có gì đảm bảo giá trị mặc định cho tham số thứ hai. Giá trị mặc định có thể thay đổi, tùy thuộc vào cách tệp .cpp tương ứng được triển khai.


4

Các đối số mặc định phải được chỉ định với lần xuất hiện đầu tiên của tên hàm, thông thường, trong nguyên mẫu hàm. Nếu nguyên mẫu hàm bị bỏ qua vì định nghĩa hàm cũng đóng vai trò là nguyên mẫu, thì các đối số mặc định sẽ được chỉ định trong tiêu đề hàm.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.