Làm thế nào để truyền đối tượng cho các chức năng trong C ++?


249

Tôi chưa quen với lập trình C ++, nhưng tôi có kinh nghiệm về Java. Tôi cần hướng dẫn về cách truyền đối tượng cho các hàm trong C ++.

Tôi có cần truyền con trỏ, tham chiếu hoặc giá trị không tham chiếu và không tham chiếu không? Tôi nhớ trong Java không có vấn đề nào như vậy vì chúng ta chỉ chuyển biến chứa tham chiếu đến các đối tượng.

Sẽ thật tuyệt nếu bạn cũng có thể giải thích nơi sử dụng từng tùy chọn đó.


6
Bạn đang học C ++ từ cuốn sách nào?

17
Cuốn sách đó không được khuyến khích. Đi cho C ++ Primer của Stan Lippman.
Prasoon Saurav

23
Vâng, có vấn đề của bạn. Schildt về cơ bản là cr * p - lấy Gia tốc C ++ của Koenig & Moo.

9
Tôi tự hỏi làm thế nào không ai đề cập đến Ngôn ngữ lập trình C ++ của Bjarne Stroustrup. Bjarne Stroustrup là người tạo ra C ++. Một cuốn sách thực sự tốt để học C ++.
George

15
@George: TC ++ PL không dành cho người mới bắt đầu, nhưng nó được coi là Kinh thánh cho C ++. XD
Prasoon Saurav

Câu trả lời:


277

Quy tắc ngón tay cái cho C ++ 11:

Truyền theo giá trị , trừ khi

  1. bạn không cần quyền sở hữu đối tượng và một bí danh đơn giản sẽ làm, trong trường hợp đó bạn chuyển qua consttham chiếu ,
  2. bạn phải thay đổi đối tượng, trong trường hợp đó, sử dụng vượt qua bởi một consttham chiếu không có giá trị ,
  3. bạn truyền các đối tượng của các lớp dẫn xuất dưới dạng các lớp cơ sở, trong trường hợp đó bạn cần truyền bằng tham chiếu . (Sử dụng các quy tắc trước đó để xác định xem có vượt qua consttham chiếu hay không.)

Đi qua con trỏ hầu như không bao giờ được khuyên. Các tham số tùy chọn được thể hiện tốt nhất dưới dạng std::optional( boost::optionalđối với lib std cũ) và việc khử răng cưa được thực hiện tốt bằng cách tham chiếu.

Ngữ nghĩa di chuyển của C ++ 11 làm cho việc vượt qua và trở lại theo giá trị hấp dẫn hơn nhiều ngay cả đối với các đối tượng phức tạp.


Quy tắc ngón tay cái cho C ++ 03:

Truyền tham số bằng consttham chiếu , trừ khi

  1. chúng phải được thay đổi bên trong hàm và những thay đổi như vậy sẽ được phản ánh ra bên ngoài, trong trường hợp bạn chuyển qua không consttham chiếu
  2. hàm nên có thể gọi được mà không có bất kỳ đối số nào, trong trường hợp đó bạn chuyển qua con trỏ, để người dùng có thể vượt qua NULL/ 0/ nullptrthay vào đó; áp dụng quy tắc trước để xác định xem bạn có nên chuyển con trỏ tới constđối số không
  3. chúng là các loại tích hợp, có thể được thông qua bản sao
  4. chúng phải được thay đổi bên trong hàm và những thay đổi đó không nên được phản ánh ra bên ngoài, trong trường hợp đó bạn có thể chuyển qua bản sao (một cách thay thế sẽ là chuyển theo các quy tắc trước đó và tạo một bản sao bên trong hàm)

(ở đây, "truyền theo giá trị" được gọi là "truyền theo bản sao", bởi vì truyền theo giá trị luôn tạo ra một bản sao trong C ++ 03)


Có nhiều hơn thế, nhưng một vài quy tắc của người mới bắt đầu này sẽ giúp bạn đi khá xa.


17
+1 - Tôi cũng sẽ lưu ý rằng một số (ví dụ Google) cảm thấy rằng các đối tượng sẽ được thay đổi trong hàm nên được truyền qua một con trỏ thay vì tham chiếu không phải là hằng số. Lý do là khi địa chỉ của một đối tượng được truyền cho một chức năng thì rõ ràng hơn là chức năng đã nói có thể thay đổi nó. Ví dụ: Với các tham chiếu, cuộc gọi là foo (bar); cho dù tham chiếu có là const hay không, với một con trỏ, nó là foo (& bar); và rõ ràng hơn là foo đang được thông qua một đối tượng có thể thay đổi.
RC.

19
@RC Vẫn không cho bạn biết nếu con trỏ là const hay không. Các nguyên tắc của Google đã xuất hiện rất nhiều trong cộng đồng trực tuyến C ++ khác nhau - chính xác là vậy, IMHO.

14
Mặc dù trong các bối cảnh khác, google có thể dẫn đầu, trong C ++, hướng dẫn về phong cách của họ không thực sự tốt.
David Rodríguez - dribeas

4
@ArunSaha: Là một hướng dẫn phong cách thuần túy, Stroustrup có một hướng dẫn được phát triển cho một công ty hàng không vũ trụ. Tôi đã duyệt qua hướng dẫn của google và không thích nó vì một vài lý do. Sutter & Alexandrescu C ++ Tiêu chuẩn mã hóa là một cuốn sách tuyệt vời để đọc và bạn có thể nhận được khá nhiều lời khuyên tốt, nhưng nó không thực sự là một hướng dẫn phong cách . Tôi không biết về bất kỳ trình kiểm tra tự động nào về kiểu dáng , ngoài con người và lẽ thường.
David Rodríguez - dribeas

3
@anon Tuy nhiên, bạn có được một đảm bảo rằng nếu một đối số không được truyền qua một con trỏ, thì nó KHÔNG bị thay đổi. IMHO khá có giá trị, nếu không, khi cố gắng theo dõi điều gì xảy ra với một biến trong hàm, bạn phải kiểm tra các tệp tiêu đề của tất cả các hàm được truyền để xác định xem nó có thay đổi hay không. Bằng cách này, bạn chỉ cần nhìn vào những cái nó được truyền qua con trỏ.
smehmood

107

Có một số khác biệt trong cách gọi các quy ước trong C ++ và Java. Trong C ++, về mặt kỹ thuật chỉ có hai quy ước: pass-by-value và pass-by-Reference, với một số tài liệu bao gồm quy ước truyền qua con trỏ thứ ba (đó thực sự là giá trị truyền của loại con trỏ). Trên hết, bạn có thể thêm const-ness vào loại đối số, nâng cao ngữ nghĩa.

Đi qua tham khảo

Chuyển qua tham chiếu có nghĩa là hàm sẽ nhận được khái niệm đối tượng của bạn và không phải là bản sao của nó. Tham chiếu về mặt khái niệm là bí danh cho đối tượng được sử dụng trong ngữ cảnh gọi và không thể là null. Tất cả các hoạt động được thực hiện bên trong chức năng áp dụng cho đối tượng bên ngoài chức năng. Quy ước này không có sẵn trong Java hoặc C.

Truyền theo giá trị (và truyền qua con trỏ)

Trình biên dịch sẽ tạo một bản sao của đối tượng trong ngữ cảnh gọi và sử dụng bản sao đó bên trong hàm. Tất cả các hoạt động được thực hiện bên trong chức năng được thực hiện để sao chép, không phải phần tử bên ngoài. Đây là quy ước cho các kiểu nguyên thủy trong Java.

Một phiên bản đặc biệt của nó là chuyển một con trỏ (địa chỉ - của đối tượng) vào một hàm. Hàm nhận con trỏ, và bất kỳ và tất cả các hoạt động được áp dụng cho chính con trỏ đều được áp dụng cho bản sao (con trỏ), mặt khác, các hoạt động được áp dụng cho con trỏ bị hủy đăng ký sẽ áp dụng cho thể hiện đối tượng tại vị trí bộ nhớ đó, do đó hàm có thể có tác dụng phụ. Hiệu quả của việc sử dụng giá trị truyền của con trỏ tới đối tượng sẽ cho phép hàm bên trong sửa đổi giá trị bên ngoài, như với tham chiếu qua và cũng sẽ cho phép các giá trị tùy chọn (truyền con trỏ null).

Đây là quy ước được sử dụng trong C khi một hàm cần sửa đổi một biến ngoài và quy ước được sử dụng trong Java với các kiểu tham chiếu: tham chiếu được sao chép, nhưng đối tượng được tham chiếu giống nhau: các thay đổi đối với tham chiếu / con trỏ không hiển thị bên ngoài chức năng, nhưng thay đổi bộ nhớ nhọn là.

Thêm const vào phương trình

Trong C ++, bạn có thể gán hằng số cho các đối tượng khi xác định các biến, con trỏ và tham chiếu ở các mức khác nhau. Bạn có thể khai báo một biến là hằng số, bạn có thể khai báo một tham chiếu đến một thể hiện hằng và bạn có thể định nghĩa tất cả các con trỏ tới các đối tượng không đổi, các con trỏ không đổi cho các đối tượng có thể thay đổi và các con trỏ không đổi thành các phần tử không đổi. Ngược lại, trong Java, bạn chỉ có thể định nghĩa một mức không đổi (từ khóa cuối cùng): đó là biến số (ví dụ cho các kiểu nguyên thủy, tham chiếu cho các kiểu tham chiếu), nhưng bạn không thể định nghĩa một tham chiếu đến một phần tử bất biến (trừ khi chính lớp đó là bất biến).

Điều này được sử dụng rộng rãi trong các quy ước gọi C ++. Khi các đối tượng nhỏ, bạn có thể truyền đối tượng theo giá trị. Trình biên dịch sẽ tạo ra một bản sao, nhưng bản sao đó không phải là một hoạt động đắt tiền. Đối với bất kỳ loại nào khác, nếu hàm sẽ không thay đổi đối tượng, bạn có thể chuyển tham chiếu đến một thể hiện không đổi (thường được gọi là tham chiếu không đổi) của loại. Điều này sẽ không sao chép đối tượng, nhưng chuyển nó vào hàm. Nhưng đồng thời trình biên dịch sẽ đảm bảo rằng đối tượng không bị thay đổi bên trong hàm.

Quy tắc của ngón tay cái

Đây là một số quy tắc cơ bản cần tuân theo:

  • Thích pass-by-value cho các kiểu nguyên thủy
  • Thích tham chiếu qua với tham chiếu đến hằng số cho các loại khác
  • Nếu hàm cần sửa đổi đối số, hãy sử dụng tham chiếu qua
  • Nếu đối số là tùy chọn, hãy sử dụng con trỏ qua (thành hằng nếu giá trị tùy chọn không được sửa đổi)

Có những sai lệch nhỏ khác so với các quy tắc này, đầu tiên là xử lý quyền sở hữu đối tượng. Khi một đối tượng được phân bổ động với mới, nó phải được giải quyết bằng cách xóa (hoặc các phiên bản [] của chúng). Đối tượng hoặc chức năng chịu trách nhiệm cho việc phá hủy đối tượng được coi là chủ sở hữu của tài nguyên. Khi một đối tượng được phân bổ động được tạo trong một đoạn mã, nhưng quyền sở hữu được chuyển sang một phần tử khác, nó thường được thực hiện với ngữ nghĩa của con trỏ hoặc nếu có thể với con trỏ thông minh.

Lưu ý bên

Điều quan trọng là nhấn mạnh tầm quan trọng của sự khác biệt giữa các tham chiếu C ++ và Java. Trong các tham chiếu C ++ về mặt khái niệm là ví dụ của đối tượng, không phải là một trình truy cập vào nó. Ví dụ đơn giản nhất là thực hiện chức năng hoán đổi:

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

Hàm hoán đổi ở trên thay đổi cả hai đối số của nó thông qua việc sử dụng các tham chiếu. Mã gần nhất trong Java:

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

Phiên bản Java của mã sẽ sửa đổi các bản sao của các tham chiếu bên trong, nhưng sẽ không sửa đổi các đối tượng thực tế bên ngoài. Tham chiếu Java là con trỏ C không có số học con trỏ được truyền theo giá trị vào các hàm.


4
@ david-Rodriguez-dribeas Tôi thích các quy tắc của phần ngón tay cái, đặc biệt "Ưu tiên giá trị vượt qua cho các loại nguyên thủy"
yadab

Theo tôi, đây là câu trả lời tốt hơn cho câu hỏi.
unrealsoul007

22

Có một số trường hợp để xem xét.

Thông số được sửa đổi (tham số "ra" và "vào / ra")

void modifies(T &param);
// vs
void modifies(T *param);

Trường hợp này chủ yếu là về phong cách: bạn có muốn mã giống như cuộc gọi (obj) hoặc cuộc gọi (& obj) không? Tuy nhiên, có hai điểm khác biệt quan trọng: trường hợp tùy chọn, bên dưới và bạn muốn sử dụng tham chiếu khi quá tải toán tử.

... và tùy chọn

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

Thông số không được sửa đổi

void uses(T const &param);
// vs
void uses(T param);

Đây là trường hợp thú vị. Nguyên tắc chung là các loại "giá rẻ để sao chép" được truyền theo giá trị - đây thường là các loại nhỏ (nhưng không phải luôn luôn) - trong khi các loại khác được truyền bởi const ref. Tuy nhiên, nếu bạn cần tạo một bản sao trong chức năng của mình, bạn nên chuyển theo giá trị . (Vâng, điều này cho thấy một chút chi tiết thực hiện. C'est le C ++. )

... và tùy chọn

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

Có sự khác biệt ít nhất ở đây giữa tất cả các tình huống, vì vậy hãy chọn bất cứ điều gì làm cho cuộc sống của bạn dễ dàng nhất.

Const theo giá trị là một chi tiết thực hiện

void f(T);
void f(T const);

Những tuyên bố này thực sự là cùng một chức năng! Khi truyền theo giá trị, const hoàn toàn là một chi tiết thực hiện. Dùng thử:

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types

3
+1 Tôi không biết về constviệc thực hiện khi chuyển theo giá trị.
balki

20

Truyền theo giá trị:

void func (vector v)

Truyền biến theo giá trị khi hàm cần cách ly hoàn toàn khỏi môi trường tức là để ngăn hàm sửa đổi biến ban đầu cũng như ngăn các luồng khác sửa đổi giá trị của nó trong khi hàm đang được thực thi.

Nhược điểm là chu kỳ CPU và bộ nhớ thêm dành để sao chép đối tượng.

Đi qua tham chiếu const:

void func (const vector& v);

Biểu mẫu này mô phỏng hành vi vượt qua giá trị trong khi loại bỏ chi phí sao chép. Hàm được truy cập đọc vào đối tượng ban đầu, nhưng không thể sửa đổi giá trị của nó.

Nhược điểm là an toàn của luồng: mọi thay đổi được thực hiện cho đối tượng ban đầu bởi một luồng khác sẽ hiển thị bên trong hàm trong khi nó vẫn đang thực thi.

Đi qua tham chiếu không const:

void func (vector& v)

Sử dụng điều này khi hàm phải ghi lại một số giá trị cho biến, cuối cùng sẽ được người gọi sử dụng.

Cũng giống như trường hợp tham chiếu const, đây không phải là chủ đề an toàn.

Đi qua con trỏ const:

void func (const vector* vp);

Về mặt chức năng giống như truyền bằng tham chiếu const ngoại trừ cú pháp khác nhau, cộng với thực tế là hàm gọi có thể truyền con trỏ NULL để cho biết nó không có dữ liệu hợp lệ để truyền.

Không an toàn chủ đề.

Vượt qua con trỏ không const:

void func (vector* vp);

Tương tự như tham chiếu không const. Người gọi thường đặt biến thành NULL khi hàm không có nghĩa là ghi lại giá trị. Quy ước này được thấy trong nhiều API glibc. Thí dụ:

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

Giống như tất cả thông qua tham chiếu / con trỏ, không an toàn chủ đề.


0

Vì không ai đề cập đến tôi đang thêm vào nó, Khi bạn truyền một đối tượng cho một hàm trong c ++, hàm tạo sao chép mặc định của đối tượng được gọi nếu bạn không có cái nào tạo bản sao của đối tượng và sau đó truyền nó cho phương thức, vì vậy Khi bạn thay đổi các giá trị đối tượng sẽ phản ánh trên bản sao của đối tượng thay vì đối tượng ban đầu, đó là vấn đề trong c ++, vì vậy nếu bạn tạo tất cả các thuộc tính lớp thành con trỏ, thì các hàm tạo sao chép sẽ sao chép địa chỉ của các thuộc tính con trỏ, do đó, khi các phương thức gọi vào đối tượng thao tác các giá trị được lưu trữ trong các địa chỉ thuộc tính con trỏ, các thay đổi cũng phản ánh trong đối tượng ban đầu được truyền dưới dạng tham số, do đó, điều này có thể hành xử giống như một Java nhưng đừng quên rằng tất cả các lớp của bạn thuộc tính phải là con trỏ, bạn cũng nên thay đổi giá trị của con trỏ,sẽ được nhiều rõ ràng với giải thích mã.

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

Nhưng đây không phải là ý kiến ​​hay vì cuối cùng bạn sẽ viết rất nhiều mã liên quan đến con trỏ, vốn dễ bị rò rỉ bộ nhớ và đừng quên gọi hàm hủy. Và để tránh c ++ này có các hàm tạo sao chép, nơi bạn sẽ tạo bộ nhớ mới khi các đối tượng chứa con trỏ được truyền cho các đối số hàm sẽ dừng thao tác dữ liệu đối tượng khác, Java chuyển qua giá trị và giá trị là tham chiếu, do đó nó không yêu cầu các hàm tạo sao chép.


-1

Có ba phương thức truyền đối tượng cho hàm dưới dạng tham số:

  1. Đi qua tham khảo
  2. vượt qua giá trị
  3. thêm hằng số trong tham số

Đi qua ví dụ sau:

class Sample
{
public:
    int *ptr;
    int mVar;

    Sample(int i)
    {
        mVar = 4;
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }

    void PrintVal()
    {
        cout << "The value of the pointer is " << *ptr << endl
             << "The value of the variable is " << mVar;
   }
};

void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}


int main()
{

  Sample s1= 10;
  SomeFunc(s1);
  s1.PrintVal();
  char ch;
  cin >> ch;
}

Đầu ra:

Giả sử tôi đang ở someFunc
Giá trị của con trỏ là -17891602
Giá trị của biến là 4


Chỉ có 2 phương thức (2 phương thức đầu tiên bạn đề cập). Không biết ý của bạn là gì khi "truyền hằng số trong tham số"
MM

-1

Sau đây là các cách để truyền đối số / tham số cho hàm trong C ++.

1. theo giá trị.

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

2. bằng cách tham khảo.

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

3. theo đối tượng.

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}

1
"vượt qua đối tượng" không phải là một điều. Chỉ có chuyển qua giá trị, và vượt qua tham chiếu. "Trường hợp 3" của bạn thực sự hiển thị một trường hợp vượt qua theo giá trị và một trường hợp vượt qua theo tham chiếu.
MM
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.