Berbagi teknologi

Kelas dan Objek (Bagian 1)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Pada bab sebelumnya, kita telah menyebutkan secara singkat beberapa konsep kelas dan objek. Sekarang, mari kita tinjau bersama.

Definisi kelas

Format definisi kelas

class adalah kata kunci kelas yang mendefinisikan, diikuti dengan nama kelas yang mendefinisikan, {} adalah isi utama kelas, diikuti dengan ";", dan fungsi anggota yang didefinisikan dalam kelas diperluas secara default (inline)

kualifikasi akses

Format enkapsulasi, dibagi menjadi tiga jenis: publik, pribadi, dan dilindungi. Cakupan izin akses dimulai dari posisi munculnya qualifier akses hingga muncul qualifier akses berikutnya berakhir di } yaitu kelas.

domain kelas

Kelas mendefinisikan cakupan baru. Semua anggota kelas berada dalam cakupan kelas. Saat mendefinisikan anggota di luar kelas, Anda perlu menggunakan operator cakupan :: untuk menunjukkan domain kelas mana yang dimiliki anggota tersebut.

Berikut ini adalah film unggulannya ///

Memberi contoh

konsep

• Proses pembuatan objek dalam memori fisik menggunakan tipe kelas disebut instantiasi kelas.
• Kelas adalah deskripsi abstrak suatu objek, seperti model, yang membatasi variabel anggota kelas saja
Ini adalah deklarasi dan tidak ada ruang yang dialokasikan. Ruang hanya akan dialokasikan ketika suatu objek dipakai menggunakan kelas.
• Suatu kelas dapat membuat instance beberapa objek. Objek yang dibuat menempati ruang fisik aktual dan menyimpan variabel anggota kelas.

Singkatnya dalam satu kalimat: hanya ketika objek dipakai dengan kelas akan mengalokasikan ruang (Sama seperti membangun rumah dengan gambar akan menempati ruang).

  1. class Date
  2. {
  3. public:
  4. void Init(int year, int month, int day)
  5. {
  6. _year = year;
  7. _month = month;
  8. _day = day;
  9. }
  10. void Print()
  11. {
  12. cout << _year << "/" << _month << "/" << _day << endl;
  13. }
  14. private:
  15. // 这⾥只是声明,没有开空间
  16. int _year;
  17. int _month;
  18. int _day;
  19. };

Dalam kode di atas, variabel anggota di private hanya didefinisikan dan tidak ada spasi baru yang dibuka dan

  1. // Date类实例化出对象d1和d2
  2. Date d1;
  3. Date d2;

Kemudian dua objek baru dibuat instance-nya, sehingga ruang baru terbuka.

Aturan penyelarasan memori

definisi

• Anggota pertama berada pada alamat offset 0 dari struktur.
• Variabel anggota lainnya harus dipetakan ke alamat yang merupakan kelipatan bilangan bulat dari bilangan tertentu (logaritma).
• Catatan: logaritma = yang lebih kecil dari logaritma default compiler dan ukuran anggota.
• Logaritma default di VS adalah 8
• Ukuran total struktur adalah: kelipatan bilangan bulat dari bilangan berpasangan maksimum (yang terbesar dari semua jenis variabel dan parameter berpasangan default terkecil).
• Jika sebuah struktur disarangkan, maka struktur yang disarangkan tersebut akan sejajar dengan kelipatan bilangan bulat dari logaritma maksimumnya, dan ukuran keseluruhan dari struktur tersebut
Ini adalah kelipatan bilangan bulat dari semua logaritma terbesar (termasuk logaritma struktur bersarang).

Inilah yang kita ketahui dalam bahasa C. Ambil pertanyaan berikut sebagai contoh:

  1. // 计算⼀下A实例化的对象是多⼤?
  2. class A
  3. {
  4. public:
  5. void Print()
  6. {
  7. cout << _ch << endl;
  8. }
  9. private:
  10. char _ch;
  11. int _i;
  12. };

Tidak ada fungsi yang dipanggil, jadi lihat saja variabel anggotanya. _ch bertipe karakter char, dengan ukuran 1 byte; _i bertipe int, dengan ukuran 4 byte nomor penyelarasan, jadi _ Letakkan ch pada posisi dengan subskrip 0, kosongkan tiga spasi, lalu masukkan _i. Ini menempati total 8 byte ruang dari 0 hingga 7, yang merupakan kelipatan bilangan bulat dari nomor penyelarasan maksimum .Memenuhi syarat, jadi jawaban pertanyaan ini adalah 8.

Dan bagaimana jika kedua pertanyaan ini?

  1. class B
  2. {
  3. public:
  4. void Print()
  5. {
  6. //...
  7. }
  8. };
  9. class C
  10. {
  11. };

Kita dapat menemukan bahwa ada fungsi kosong di kelas B dan tidak ada anggota di kelas C. Dalam bahasa C, kita membuat kedua kelas menempati ruang 1 byte.

penunjuk ini

konsep

DAda dua fungsi anggota, Init dan Print, di kelas ate tidak ada perbedaan antara objek yang berbeda di badan fungsi.
Saat mencetak suatu fungsi, bagaimana fungsi tersebut mengetahui apakah ia harus mengakses objek d1 atau objek d2?Kemudian di sini kita akan melihat apa yang diberikan C++
Secara implisit penunjuk ini memecahkan masalah di sini
• Setelah kompiler dikompilasi, fungsi anggota kelas akan menambahkan pointer dari tipe kelas saat ini, yang disebut pointer ini, ke posisi pertama parameter formal secara default. Misalnya, prototipe sebenarnya dari kelas Init of Date adalah, void Init(Date* const this, int year,
int bulan, int hari)
• Ketika mengakses variabel anggota dalam fungsi anggota kelas, mereka pada dasarnya diakses melalui pointer ini. Misalnya, ketika memberikan nilai ke _year dalam fungsi Init, this-
&gt;_year = tahun;
• C++ menetapkan bahwa pointer ini tidak dapat ditulis secara eksplisit pada posisi parameter aktual dan parameter formal (kompiler akan menangani ini selama kompilasi), tetapi pointer ini dapat digunakan secara eksplisit dalam isi fungsi.

Misalnya

  1. class Date
  2. {
  3. public:
  4. // void Init(Date* const this, int year, int month, int day)
  5. void Init(int year, int month, int day)
  6. {
  7. // 编译报错:error C2106: “=”: 左操作数必须为左值
  8. // this = nullptr;
  9. // this->_year = year;
  10. _year = year;
  11. this->_month = month;
  12. this->_day = day;
  13. }
  14. void Print()
  15. {
  16. cout << _year << "/" << _month << "/" << _day << endl;
  17. }
  18. private:
  19. // 这⾥只是声明,没有开空间
  20. int _year;
  21. int _month;
  22. int _day;
  23. };
  24. int main()
  25. {
  26. // Date类实例化出对象d1和d2
  27. Date d1;
  28. Date d2;
  29. // d1.Init(&d1, 2024, 3, 31);this指针
  30. d1.Init(2024, 3, 31);
  31. // d1.Init(&d2, 2024, 7, 5);
  32. d2.Init(2024, 7, 5);
  33. //不能自己写
  34. return 0;
  35. }

Untuk penulisan parameter aktual dalam fungsi Init, C++ akan menambahkan konstanta tak kasat mata penunjuk ini (yang tidak dapat diubah) ke variabel pertama fungsi tersebut, seperti yang ditunjukkan dalam komentar di fungsi tersebut.

Melihat:

Selama penunjuk nol tidak direferensikan, tidak ada kesalahan yang akan dilaporkan.

  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << "A::Print()" << endl;
  7. }
  8. private:
  9. int _a;
  10. };
  11. int main()
  12. {
  13. A* p = nullptr;
  14. p->Print();
  15. return 0;
  16. }

Meskipun p diberi penunjuk nol dan menunjuk ke fungsi Cetak, p tidak direferensikan, jadi tidak ada kesalahan yang akan dilaporkan di sini.

  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << "A::Print()" << endl;
  7. cout << _a << endl;//这个地方多了一句
  8. }
  9. private:
  10. int _a;
  11. };
  12. int main()
  13. {
  14. A* p = nullptr;
  15. p->Print();
  16. return 0;
  17. }

Dibandingkan dengan program sebelumnya, hanya ada satu kalimat lagi cout &lt;&lt; _a &lt;&lt; endl, namun saat ini penunjuk nol menunjuk ke variabel anggota, dan penunjuk nol akan didereferensi, sehingga program akan melaporkan kesalahan.

Fungsi anggota default kelas

konsep

Fungsi anggota default adalah fungsi anggota yang tidak diimplementasikan secara eksplisit oleh pengguna dan secara otomatis dihasilkan oleh kompiler. Ini disebut fungsi anggota default. Untuk sebuah kelas, jika kita tidak menulisnya, kompiler akan menghasilkan 6 fungsi anggota default berikut secara default, yaitu 6 berikut

Konstruktor

definisi

Konstruktor adalah fungsi anggota khusus. Perlu dicatat bahwa meskipun nama konstruktor disebut konstruktor, tugas utama konstruktor bukanlah membuka ruang untuk membuat objek (objek lokal yang sering kita gunakan adalah ruang saat tumpukan frame dibuat) ), sebagai gantinya objek diinisialisasi saat objek dibuat. Intinya adalah mengganti fungsi initalisasi Init.

Fitur

Lima fitur teratas

1. Nama fungsinya sama dengan nama kelas.
2. Tidak ada nilai kembalian. (Tidak perlu menulis batal)
3. Ketika objek dibuat, sistem akan secara otomatis memanggil konstruktor yang sesuai.
4. Konstruktor bisa kelebihan beban.
5. Jika tidak ada konstruktor yang didefinisikan secara eksplisit di kelas, kompiler C++ akan secara otomatis menghasilkan konstruktor default tanpa parameter. Setelah pengguna mendefinisikannya secara eksplisit, kompiler tidak akan lagi menghasilkannya.

Lima item pertama relatif sederhana, mari kita beri contoh:

  1. class Date
  2. {
  3. public:
  4. //1.⽆参构造函数,无需写void
  5. Date()//若不写,则构造函数会写出这一中无参构造函数
  6. {
  7. _year = 1;
  8. _month = 1;
  9. _day = 1;
  10. }
  11. //2.全缺省构造函数
  12. Date(int year = 1, int month = 1, int day = 1)
  13. {
  14. _year = year;
  15. _month = month;
  16. _day = day;
  17. }
  18. private:
  19. int _year;
  20. int _month;
  21. int _day;
  22. }
  23. int main()
  24. {
  25. Date d1;//此时相当于已经自动调用了Init函数
  26. Date d2;
  27. return 0;
  28. }

Dua konstruktor ditulis dalam program, satu adalah konstruktor tanpa parameter dan yang lainnya adalah konstruktor default penuh. Ketika kita tidak menulis suatu fungsi, program akan menulis hingga satu konstruktor tanpa parameter. Selain itu, kami telah mengatakan sebelumnya Namun, meskipun ini dua fungsi adalah fungsi yang kelebihan beban, karena ambiguitas pemanggilan, kedua fungsi tersebut tidak dapat ada secara bersamaan.

Dua karakteristik terakhir

6. Konstruktor tanpa parameter, konstruktor default penuh, dan konstruktor yang dihasilkan oleh kompiler secara default ketika kita tidak menulis konstruktor semuanya disebut konstruktor default. Namun, hanya satu dari ketiga fungsi ini yang ada dan tidak dapat ada secara bersamaan. Meskipun konstruktor tanpa parameter dan konstruktor default penuh merupakan fungsi yang kelebihan beban, akan ada ambiguitas saat memanggilnya. Perlu dicatat bahwa banyak siswa berpikir bahwa konstruktor default adalah konstruktor default yang dihasilkan oleh kompiler. Faktanya, konstruktor tanpa parameter dan konstruktor default penuh juga merupakan konstruktor default parameter. Ini disebut konstruksi default.

6

Fitur keenam lebih rumit: konstruktor tanpa parameter, konstruktor semua-default, dan konstruktor yang dihasilkan oleh kompiler secara default ketika kita tidak menulis konstruktor semuanya disebut konstruktor default, dan hanya ada satu sebagai konstruktor yang dihasilkan oleh kompiler secara default!

7


7. Jika kita tidak menulisnya, konstruktor yang dihasilkan oleh kompiler secara default tidak memiliki persyaratan untuk inisialisasi variabel anggota tipe bawaan. Dengan kata lain, diinisialisasi atau tidak tidak pasti, itu tergantung pada kompiler . Untuk variabel anggota tipe khusus, diperlukan untuk memanggil konstruktor default variabel anggota ini untuk menginisialisasinya. Jika variabel anggota ini tidak memiliki konstruktor default, kesalahan akan dilaporkan. Jika kita ingin menginisialisasi variabel anggota ini, kita perlu menggunakan daftar inisialisasi untuk menyelesaikan masalah. Kami akan menjelaskan daftar inisialisasi secara rinci di bab berikutnya .

penghancur

konsep

Fungsi destruktor berlawanan dengan fungsi konstruktor. Destruktor tidak menyelesaikan penghancuran objek itu sendiri, misalnya objek lokal memiliki bingkai tumpukan dirilis. Kita tidak perlu mengontrolnya. C++ menetapkan bahwa objek Ketika dihancurkan, destruktor akan secara otomatis dipanggil untuk menyelesaikan pembersihan dan pelepasan sumber daya di objek. Fungsi destructor mirip dengan fungsi Destroy yang kita implementasikan sebelumnya di Stack, misalnya Date tidak memiliki Destroy. Faktanya, tidak ada resource yang akan dirilis destruktor sama seperti fungsi Destroy. Ini harus digunakan untuk struktur data yang memerlukan objek non-lokal seperti tumpukan dan antrian untuk mengajukan sumber daya.

Fitur

1. Nama destruktor diawali dengan karakter ~ sebelum nama kelas. (Mirip dengan negasi bitwise dalam bahasa C)
2. Tidak ada parameter dan tidak ada nilai kembalian. (Ini mirip dengan strukturnya, dan tidak perlu menambahkan kekosongan)
3. Sebuah kelas hanya dapat memiliki satu destruktor. Jika tidak didefinisikan secara eksplisit, sistem akan secara otomatis menghasilkan destruktor default.
4. Ketika siklus hidup objek berakhir, sistem akan secara otomatis memanggil destruktor.
5. Mirip dengan konstruktor, jika kita tidak menulis destruktor yang dihasilkan secara otomatis oleh kompiler, ia tidak akan memproses anggota tipe bawaan akan memanggil destruktornya.
6. Perlu juga dicatat bahwa ketika kita menulis destruktor secara eksplisit, destruktor dari anggota tipe khusus juga akan dipanggil. Artinya, destruktor dari anggota tipe khusus akan dipanggil secara otomatis apa pun situasinya.
7. Jika tidak ada sumber daya yang diterapkan di kelas, destruktor tidak perlu ditulis, dan destruktor default yang dihasilkan oleh kompiler, seperti Tanggal, dapat digunakan secara langsung. tidak perlu menampilkannya. Tulis destruktornya, seperti MyQueue; tetapi bila ada aplikasi sumber daya, Anda harus menulis destruktornya sendiri, jika tidak maka akan menyebabkan kebocoran sumber daya, seperti Stack.
8. Untuk beberapa objek dalam domain lokal, C++ menetapkan bahwa objek yang ditentukan kemudian akan dimusnahkan terlebih dahulu.

Penggunaan destruktor

Sama seperti konstruktor di atas, destruktor default yang dibuat secara otomatis oleh C++ umumnya tidak akan banyak membantu kita, namun lebih nyaman dalam beberapa kasus yang jarang terjadi, seperti menggunakan dua tumpukan untuk mengimplementasikan antrian:

  1. public:
  2. Stack(int n = 4)//相当于Init
  3. {
  4. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  5. if (nullptr == _a)
  6. {
  7. perror("malloc fail!");
  8. return;
  9. }
  10. _capacity = n;
  11. _top = 0;
  12. }
  13. ~Stack()//相当于Destroy
  14. {
  15. cout << "~Stack()" << endl;
  16. free(_a);
  17. _a = nullptr;
  18. _top = _capacity = 0;
  19. }
  20. private:
  21. STDataType* _a;
  22. size_t _capacity;
  23. size_t _top;
  24. };
  25. class MyQueue
  26. {
  27. public:
  28. //编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
  29. // 显⽰写析构,也会⾃动调⽤Stack的析构
  30. private:
  31. Stack pushst;
  32. Stack popst;
  33. };

Pada saat ini, kompiler di MyQueue secara otomatis memanggil konstruktor (inisialisasi) dan destruktor (penghancuran) tumpukan.