Bagaimana cara melewatkan objek ke fungsi di C ++?


249

Saya baru mengenal pemrograman C ++, tetapi saya memiliki pengalaman di Java. Saya perlu panduan tentang cara meneruskan objek ke fungsi di C ++.

Apakah saya perlu meneruskan nilai pointer, referensi, atau non-pointer dan non-referensi? Saya ingat di Jawa tidak ada masalah seperti itu karena kita hanya melewati variabel yang memegang referensi ke objek.

Alangkah baiknya jika Anda juga bisa menjelaskan di mana harus menggunakan masing-masing opsi tersebut.


6
Dari buku mana Anda belajar C ++?

17
Buku itu sangat tidak disarankan. Gunakan C ++ Primer oleh Stan Lippman.
Prasoon Saurav

23
Nah, ada masalah Anda. Schildt pada dasarnya adalah cr * p - dapatkan Accelerated C ++ oleh Koenig & Moo.

9
Saya bertanya-tanya bagaimana tidak ada yang menyebutkan Bahasa Pemrograman C ++ oleh Bjarne Stroustrup. Bjarne Stroustrup adalah pencipta C ++. Buku yang sangat bagus untuk belajar C ++.
George

15
@ George: TC ++ PL bukan untuk pemula, tetapi dianggap sebagai Alkitab untuk C ++. XD
Prasoon Saurav

Jawaban:


277

Aturan praktis untuk C ++ 11:

Lewati nilai , kecuali saat

  1. Anda tidak perlu kepemilikan objek dan alias sederhana akan dilakukan, dalam hal ini Anda melewati constreferensi ,
  2. Anda harus bermutasi objek, dalam hal ini, gunakan lewat constreferensi non- nilai ,
  3. Anda melewatkan objek dari kelas turunan sebagai kelas dasar, dalam hal ini Anda harus melewati referensi . (Gunakan aturan sebelumnya untuk menentukan apakah akan lulus dengan constreferensi atau tidak.)

Melewati oleh pointer hampir tidak pernah disarankan. Parameter opsional paling baik dinyatakan sebagai std::optional( boost::optionaluntuk ld std yang lebih tua), dan aliasing dilakukan dengan baik dengan referensi.

Semantik bergerak C ++ 11 membuat passing dan pengembalian dengan nilai jauh lebih menarik bahkan untuk objek yang kompleks.


Aturan praktis untuk C ++ 03:

Lewati argumen dengan constreferensi , kecuali saat

  1. mereka harus diubah di dalam fungsi dan perubahan tersebut harus tercermin di luar, dalam hal ini Anda melewati non- constreferensi
  2. fungsi harus dapat dipanggil tanpa argumen, dalam hal ini Anda melewati pointer, sehingga pengguna dapat melewati NULL/ 0/ nullptrsebagai gantinya; menerapkan aturan sebelumnya untuk menentukan apakah Anda harus melewati pointer ke constargumen
  3. mereka adalah tipe built-in, yang dapat dikirimkan melalui salinan
  4. mereka harus diubah di dalam fungsi dan perubahan tersebut tidak boleh dicerminkan di luar, dalam hal ini Anda dapat melewati salinan (alternatifnya adalah untuk lulus sesuai dengan aturan sebelumnya dan membuat salinan di dalam fungsi)

(di sini, "pass by value" disebut "pass by copy", karena lewat nilai selalu membuat salinan dalam C ++ 03)


Ada lebih dari ini, tetapi beberapa aturan pemula ini akan membuat Anda cukup jauh.


17
+1 - Saya juga akan mencatat bahwa beberapa (yaitu Google) merasa bahwa objek yang akan diubah dalam fungsi harus dilewatkan melalui pointer, bukan referensi non-const. Alasannya adalah bahwa ketika alamat objek dilewatkan ke suatu fungsi, lebih jelas bahwa fungsi tersebut dapat mengubahnya. Contoh: Dengan referensi, panggilannya adalah foo (bar); apakah rujukannya adalah const atau tidak, dengan pointer itu foo (& bar); dan lebih jelas bahwa foo sedang melewati objek yang bisa berubah.
RC.

19
@RC Masih tidak memberi tahu Anda jika pointer adalah const atau tidak. Panduan Google telah masuk untuk banyak kritik di berbagai komunitas online C ++ - benar demikian, IMHO.

14
Sementara dalam konteks lain google mungkin memimpin, di C ++ panduan gaya mereka tidak terlalu bagus.
David Rodríguez - dribeas

4
@ArunSaha: Sebagai panduan gaya murni, Stroustrup memiliki panduan yang dikembangkan untuk perusahaan dirgantara. Saya melihat-lihat panduan google dan tidak menyukainya karena beberapa alasan. Sutter & Alexandrescu C ++ Standar Pengkodean adalah buku yang bagus untuk dibaca dan Anda bisa mendapatkan beberapa saran bagus, tetapi ini bukan panduan gaya . Saya tidak tahu ada pemeriksa otomatis untuk gaya , selain manusia dan akal sehat.
David Rodríguez - dribeas

3
@anon Namun, Anda mendapatkan jaminan bahwa jika argumen tidak dikirimkan melalui pointer, maka itu TIDAK diubah. Itu IMHO yang cukup berharga, jika tidak ketika mencoba melacak apa yang terjadi pada variabel dalam suatu fungsi, Anda harus memeriksa file header dari semua fungsi yang diteruskan untuk menentukan apakah itu diubah. Dengan cara ini, Anda hanya perlu melihat yang dilewatkan melalui pointer.
smehmood

107

Ada beberapa perbedaan dalam konvensi pemanggilan di C ++ dan Java. Dalam C ++, secara teknis hanya ada dua konvensi: pass-by-value dan pass-by-reference, dengan beberapa literatur termasuk konvensi pass-by-pointer ketiga (yang sebenarnya pass-by-value dari tipe pointer). Di atas semua itu, Anda bisa menambahkan konstanta pada tipe argumen, meningkatkan semantik.

Lewati dengan referensi

Melewati dengan referensi berarti bahwa fungsi tersebut secara konseptual akan menerima instance objek Anda dan bukan salinannya. Referensi secara konseptual merupakan alias untuk objek yang digunakan dalam konteks panggilan, dan tidak bisa nol. Semua operasi yang dilakukan di dalam fungsi berlaku untuk objek di luar fungsi. Konvensi ini tidak tersedia di Jawa atau C.

Pass by value (dan pass-by-pointer)

Kompiler akan menghasilkan salinan objek dalam konteks panggilan dan menggunakan salinan itu di dalam fungsi. Semua operasi yang dilakukan di dalam fungsi dilakukan untuk menyalin, bukan elemen eksternal. Ini adalah konvensi untuk tipe primitif di Jawa.

Versi khusus itu lewat pointer (alamat-objek) ke fungsi. Fungsi menerima pointer, dan setiap dan semua operasi yang diterapkan pada pointer itu sendiri diterapkan ke copy (pointer), di sisi lain, operasi yang diterapkan pada pointer dereferenced akan berlaku untuk instance objek di lokasi memori itu, sehingga fungsi dapat memiliki efek samping. Efek menggunakan nilai pass-by-pointer ke objek akan memungkinkan fungsi internal untuk memodifikasi nilai-nilai eksternal, seperti dengan pass-by-reference dan juga akan memungkinkan untuk nilai-nilai opsional (melewati pointer nol).

Ini adalah konvensi yang digunakan dalam C ketika suatu fungsi perlu memodifikasi variabel eksternal, dan konvensi yang digunakan di Jawa dengan tipe referensi: referensi disalin, tetapi objek yang dirujuk adalah sama: perubahan pada referensi / penunjuk tidak terlihat di luar fungsi, tetapi perubahan memori menunjuk.

Menambahkan const ke persamaan

Dalam C ++ Anda dapat menetapkan konstan-objek untuk objek ketika mendefinisikan variabel, pointer dan referensi pada level yang berbeda. Anda dapat mendeklarasikan variabel sebagai konstan, Anda dapat mendeklarasikan referensi ke instance konstan, dan Anda dapat mendefinisikan semua pointer ke objek konstan, pointer konstan ke objek yang bisa berubah dan pointer konstan ke elemen konstan. Sebaliknya di Jawa, Anda hanya dapat menentukan satu tingkat kekenyalan (kata kunci akhir): variabel (contoh untuk tipe primitif, referensi untuk tipe referensi), tetapi Anda tidak dapat menentukan referensi ke elemen yang tidak dapat diubah (kecuali kelas itu sendiri adalah kekal).

Ini banyak digunakan dalam konvensi pemanggilan C ++. Ketika objek kecil, Anda bisa melewati objek dengan nilai. Kompiler akan menghasilkan salinan, tetapi salinan itu bukan operasi yang mahal. Untuk jenis lain, jika fungsi tidak akan mengubah objek, Anda dapat meneruskan referensi ke instance konstan (biasanya disebut referensi konstan) dari tipe tersebut. Ini tidak akan menyalin objek, tetapi meneruskannya ke fungsi. Tetapi pada saat yang sama kompiler akan menjamin bahwa objek tidak berubah di dalam fungsi.

Aturan praktis

Ini adalah beberapa aturan dasar yang harus diikuti:

  • Lebih suka pass-by-value untuk tipe primitif
  • Lebih suka pass-by-reference dengan referensi ke konstanta untuk tipe lain
  • Jika fungsi perlu mengubah argumen, gunakan pass-by-reference
  • Jika argumennya opsional, gunakan pass-by-pointer (untuk konstan jika nilai opsional tidak boleh dimodifikasi)

Ada penyimpangan kecil lainnya dari aturan ini, yang pertama adalah menangani kepemilikan suatu objek. Ketika suatu objek dialokasikan secara dinamis dengan yang baru, itu harus dialokasikan dengan menghapus (atau versi [] daripadanya). Objek atau fungsi yang bertanggung jawab atas penghancuran objek dianggap sebagai pemilik sumber daya. Ketika objek yang dialokasikan secara dinamis dibuat dalam sepotong kode, tetapi kepemilikan ditransfer ke elemen yang berbeda itu biasanya dilakukan dengan semantik pass-by-pointer, atau jika mungkin dengan smart pointer.

Catatan samping

Penting untuk menekankan pentingnya perbedaan antara C ++ dan referensi Java. Dalam C ++ referensi secara konseptual adalah instance dari objek, bukan accessor untuk itu. Contoh paling sederhana adalah menerapkan fungsi swap:

// 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 ); 
}

Fungsi swap di atas mengubah kedua argumennya melalui penggunaan referensi. Kode terdekat di Jawa:

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
   }
}

Versi kode Java akan memodifikasi salinan referensi secara internal, tetapi tidak akan memodifikasi objek aktual secara eksternal. Referensi Java adalah pointer C tanpa aritmatika pointer yang diteruskan oleh nilai ke dalam fungsi.


4
@ david-rodriguez-dribea Saya menyukai aturan bagian praktis, khususnya "Lebih suka pass-by-value untuk tipe primitif"
yadab

Menurut saya, ini adalah jawaban yang jauh lebih baik untuk pertanyaan itu.
unrealsoul007

22

Ada beberapa kasus yang perlu dipertimbangkan.

Parameter yang dimodifikasi ("keluar" dan "masuk / keluar" parameter)

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

Kasus ini sebagian besar tentang gaya: apakah Anda ingin kode tersebut terlihat seperti panggilan (obj) atau panggilan (& obj) ? Namun, ada dua titik di mana perbedaannya penting: kasing opsional, di bawah, dan Anda ingin menggunakan referensi saat membebani operator.

... dan opsional

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

Parameter tidak diubah

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

Ini adalah kasus yang menarik. Aturan praktisnya adalah "murah untuk menyalin" jenis disahkan oleh nilai - ini umumnya jenis kecil (tetapi tidak selalu) - sementara yang lain disahkan oleh const ref. Namun, jika Anda perlu membuat salinan di dalam fungsi Anda, Anda harus memberikan nilai . (Ya, ini memperlihatkan sedikit detail implementasi. C'est le C ++. )

... dan opsional

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

Ada sedikit perbedaan di sini antara semua situasi, jadi pilihlah yang membuat hidup Anda lebih mudah.

Nilai konstan adalah detail implementasi

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

Deklarasi ini sebenarnya adalah fungsi yang sama persis! Ketika melewati nilai, const sepenuhnya merupakan detail implementasi. Cobalah:

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 Saya tidak tahu tentang constmenjadi implementasi saat melewati nilai.
balki

20

Lewati nilai:

void func (vector v)

Lewati variabel dengan nilai ketika fungsi tersebut membutuhkan isolasi lengkap dari lingkungan yaitu untuk mencegah fungsi dari memodifikasi variabel asli serta untuk mencegah utas lain dari memodifikasi nilainya saat fungsi sedang dieksekusi.

Kelemahannya adalah siklus CPU dan memori tambahan yang dihabiskan untuk menyalin objek.

Lewati referensi const:

void func (const vector& v);

Formulir ini mengemulasi perilaku pass-by-value sambil menghapus overhead salin. Fungsi mendapat akses baca ke objek asli, tetapi tidak dapat mengubah nilainya.

Kelemahannya adalah keamanan utas: setiap perubahan yang dilakukan pada objek asli oleh utas lain akan muncul di dalam fungsi saat masih dijalankan.

Lewati referensi non-const:

void func (vector& v)

Gunakan ini ketika fungsi harus menulis kembali beberapa nilai ke variabel, yang pada akhirnya akan digunakan oleh pemanggil.

Sama seperti kasus referensi const, ini bukan thread-safe.

Lewati oleh pointer pointer:

void func (const vector* vp);

Secara fungsional sama dengan pass by const-reference kecuali untuk sintaks yang berbeda, ditambah fakta bahwa fungsi pemanggilan dapat melewati NULL pointer untuk menunjukkan ia tidak memiliki data yang valid untuk dilewati.

Tidak aman untuk benang.

Lewati dengan pointer non-const:

void func (vector* vp);

Mirip dengan referensi non-const. Penelepon biasanya mengatur variabel ke NULL ketika fungsi tidak seharusnya menulis kembali nilai. Konvensi ini terlihat di banyak API glibc. Contoh:

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

Sama seperti semua yang dilewati oleh referensi / pointer, bukan thread-safe.


0

Karena tidak ada yang disebutkan saya menambahkannya, Ketika Anda melewatkan objek ke fungsi di c ++, copy constructor default objek dipanggil jika Anda tidak memiliki satu yang membuat klon objek dan kemudian meneruskannya ke metode, jadi ketika Anda mengubah nilai objek yang akan mencerminkan pada salinan objek, bukan objek asli, itu adalah masalah dalam c ++, Jadi jika Anda membuat semua atribut kelas menjadi pointer, maka copy constructor akan menyalin alamat dari atribut pointer, jadi ketika metode invokasi pada objek yang memanipulasi nilai-nilai yang disimpan dalam alamat atribut pointer, perubahan juga mencerminkan dalam objek asli yang dilewatkan sebagai parameter, jadi ini bisa berperilaku sama dengan Java tetapi jangan lupa bahwa semua kelas Anda atribut harus berupa pointer, Anda juga harus mengubah nilai pointer,akan lebih jelas dengan penjelasan kode.

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
}

Tapi ini bukan ide yang baik karena Anda akan berakhir dengan menulis banyak kode yang melibatkan pointer, yang rentan terhadap kebocoran memori dan jangan lupa untuk memanggil destruktor. Dan untuk menghindari hal ini c ++ memiliki copy constructor di mana Anda akan membuat memori baru ketika objek yang berisi pointer diteruskan ke argumen fungsi yang akan berhenti memanipulasi data objek lain, Java melewati nilai dan nilai referensi, sehingga tidak memerlukan copy constructor.


-1

Ada tiga metode untuk meneruskan objek ke fungsi sebagai parameter:

  1. Lewati dengan referensi
  2. lewat nilai
  3. menambahkan parameter konstan

Pergi melalui contoh berikut:

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;
}

Keluaran:

Say i am in someFunc
Nilai dari pointer adalah -17891602
Nilai dari variabel adalah 4


Hanya ada 2 metode (2 pertama yang Anda sebutkan). Tidak tahu apa yang Anda maksud dengan "melewati parameter konstan"
MM

-1

Berikut ini adalah cara untuk meneruskan argumen / parameter agar berfungsi di C ++.

1. berdasarkan nilai.

// passing parameters by value . . .

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

2. dengan referensi.

// 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. oleh objek.

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
"lewat objek" bukanlah sesuatu. Hanya ada pass by value, dan pass by reference. "Case 3" Anda sebenarnya menunjukkan satu case pass by value dan satu case pass by reference.
MM
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.