Bagaimana untuk membandingkan struktur dalam c. Struktur. Teks penuh makro

Bahasa C++ menjana pembina salinan dan pengendali tugasan salinan secara lalai untuk semua kelas dan struktur yang ditentukan pengguna. Oleh itu, untuk beberapa kes yang penting, pengaturcara dibebaskan daripada menulis fungsi yang ditentukan secara manual. Sebagai contoh, pengendali lalai berfungsi dengan baik untuk struktur yang mengandungi data. Dalam kes ini, data boleh disimpan dalam kedua-dua jenis ringkas dan dalam bekas kompleks, seperti std::vector atau std::string.

Memandangkan ini, adalah mudah untuk mempunyai operator perbandingan struktur == dan != secara lalai, tetapi pengkompil C++, mengikut piawaian, tidak menjananya.

Tidak sukar untuk menulis operator untuk perbandingan struktur yang bijak, tetapi organisasi program sedemikian menyusahkan dan berbahaya dari sudut pandangan ralat. Sebagai contoh, jika pengaturcara menambah ahli baharu pada struktur, tetapi terlupa untuk menambah perbandingan yang sepadan dalam pengendali perbandingan tersuai, maka ralat yang agak sukar untuk didiagnosis dijana dalam atur cara. Selain itu, pengisytiharan struktur dan pengendali perbandingan pengguna biasanya dipisahkan antara satu sama lain, kerana ia terletak dalam fail yang berbeza (*.h dan *.cpp).

Mengautomasikan penulisan operator perbandingan memberwise dalam C++ bukanlah mudah, kerana bahasa ini tidak mempunyai alat yang membolehkan anda mengetahui semasa pelaksanaan program berapa ramai dan ahli yang terkandung dalam struktur.

Pada pertengahan tahun 2000-an, semasa mengerjakan projek besar yang sentiasa berkembang dan memerlukan perubahan yang kerap pada struktur data, saya berusaha untuk menyelesaikan isu operator perbandingan sekali dan untuk semua. Akibatnya, pembinaan telah dibuat dalam C++ menggunakan makro, yang memungkinkan untuk mengisytiharkan struktur dengan penjanaan automatik pengendali perbandingan ahli demi ahli seterusnya. Reka bentuk yang sama memungkinkan untuk melaksanakan operasi ahli demi ahli lain secara automatik: memuatkan dan menyimpan data ke fail. Saya menyampaikannya kepada perhatian anda.

Penyelesaian lain yang sedia ada

Pada masa ini saya mengetahui penyelesaian alternatif berikut untuk masalah yang diterangkan:

  1. Menggunakan struktur dinamik. Daripada struktur C++ biasa, bekas unsur heterogen digunakan, yang dikurangkan kepada satu jenis. Contohnya, jenis VARIANT daripada Windows OLE. Ia juga menggunakan bekas rentetan untuk menyimpan nama ahli. Ini menjadikan nama ahli, jenis dan nombor tersedia untuk program semasa masa jalan. Walau bagaimanapun, pendekatan ini memperkenalkan kos masa jalan untuk mengakses ahli struktur sedemikian. Sintaks akses seperti object.member_name atau pObject->member_name menjadi tidak tersedia dan perlu ditukar kepada sesuatu seperti object.at(“member_name”). Di samping itu, terdapat peningkatan linear dalam penggunaan ingatan: setiap contoh struktur mengambil lebih banyak ruang memori daripada struktur biasa (statik).
  2. Menggunakan perpustakaan rangsangan, iaitu boost::fusion::map container. Di sini kami berjaya meletakkan semua kos di bahu pengkompil, tetapi sintaks tradisional untuk mengakses ahli tidak dapat dikekalkan. Anda perlu menggunakan binaan seperti: at_key (objek).
  3. Penjanaan kod C++. Perihalan struktur dalam C++ dan pengendali perbandingannya tidak ditulis secara manual oleh pengaturcara, tetapi dijana oleh skrip berdasarkan penerangan struktur dalam beberapa bahasa input lain. Pendekatan ini, dari sudut pandangan saya, adalah ideal, tetapi saya belum melaksanakannya pada masa ini, jadi artikel ini bukan mengenainya.
Penyelesaian berasaskan makro

Penyelesaian yang saya dapat laksanakan menggunakan makro mempunyai kelebihan berikut:

  • Tiada overhed masa jalan untuk mengakses ahli struktur.
  • Ia adalah mungkin untuk mengekalkan sintaks standard untuk mengakses ahli struktur objek object.member_name atau pObject->member_name.
  • Beban memori ialah O(1). Dalam erti kata lain, setiap contoh struktur auto-perbandingan mengambil jumlah ruang memori yang sama seperti struktur biasa. Terdapat hanya kos ingatan malar (kecil) untuk setiap jenis struktur sedemikian yang diisytiharkan.

Kelemahannya termasuk yang berikut:

  • Kehadiran ahli perkhidmatan tambahan dalam struktur, yang mengurangkan kemudahan alat analisis seperti Intellisense atau DoxyGen.
  • Kemungkinan konflik antara nama ahli perkhidmatan dan pengguna.
  • Adalah mustahil untuk memulakan dengan senarai pemula seperti struct a = (1,2,3).
Contoh penggunaan

Katakan kita perlu mencipta struktur untuk menyimpan data tentang orang, bersamaan dengan struktur biasa berikut:

Struktur MANPARAMS ( std::string name; int age; std::vector nama_kawan; karma berganda; );

Berdasarkan perpustakaan saya, struktur dengan operasi memberwise automatik diisytiharkan seperti ini:

Kelas AUTO_MANPARAMS ( PARAMSTRUCT_DECLARE_BEGIN(AUTO_MANPARAMS); awam: DECLARE_MEMBER_PARAMSTRUCT(std::rentetan, nama); DECLARE_MEMBER_PARAMSTRUCT(int, umur); DECLARE_MEMBER_PARAMSTRUCT(std::vector , nama_kawan); DECLARE_MEMBER_PARAMSTRUCT(double, karma); );

Selepas ini, sekali untuk keseluruhan program, anda perlu menyusun panggilan makro berikut dalam salah satu fail *.cpp:

PARAMFIELD_IMPL(AUTO_MANPARAMS);

Semua! Kini anda boleh menggunakan struktur ini dengan selamat seperti biasa, dan membandingkannya untuk kesamarataan atau ketidaksamaan, tanpa perlu risau tentang menulis operator yang sepadan. Sebagai contoh:

Lelaki kosong(kosong) ( AUTO_MANPARAMS man1, man2; man1.name = "John Smith"; man1.age = 18; man1.karma = 0; man2.name = "John Doe"; man2.age = 36; man2.karma = 1; man2.friends.push_back(“Sergud Smith”); if(man1 == man2) printf(“Ku-ku!n”); )

Perlaksanaan

Seperti yang anda boleh lihat daripada di atas, pada permulaan takrifan setiap struktur, anda perlu memanggil makro PARAMSTRUCT_DECLARE_BEGIN(x), yang akan mentakrifkan beberapa jenis biasa dan ahli perkhidmatan statik untuk struktur tersebut. Selepas ini, apabila mengisytiharkan setiap ahli pengguna, anda perlu memanggil makro kedua, DECLARE_MEMBER_PARAMSTRUCT(jenis, nama), yang, sebagai tambahan kepada mengisytiharkan ahli itu sendiri dengan nama yang ditentukan, mentakrifkan ahli perkhidmatan struktur yang berkaitan dengannya.

Idea asas pelaksanaan:

  • Untuk setiap ahli struktur, fungsi perbandingan untuk ahli ini dijana secara automatik.
  • Penunjuk kepada fungsi perbandingan disimpan dalam tatasusunan statik. Operator perbandingan hanya melelang melalui semua elemen tatasusunan itu dan memanggil fungsi perbandingan pada setiap ahli.
  • Apabila program bermula, tatasusunan ini dimulakan supaya tidak menduplikasi kod untuk mengisytiharkan ahli struktur.
1. Penjanaan automatik fungsi perbandingan untuk setiap ahli

Setiap fungsi tersebut adalah ahli struktur dan membandingkan ahli data "nya". Ia dijana dalam makro DECLARE_MEMBER_PARAMSTRUCT(jenis, nama) seperti berikut:

Bool comp##name(const ThisParamFieldClass& a) const ( return name == a.name; )

Di mana ThisParamFieldClass ialah jenis struktur kami, yang diisytiharkan melalui typedef dalam makro kepala - lihat di bawah.

2. Tatasusunan dengan penunjuk kepada fungsi perbandingan

Makro kepala PARAMSTRUCT_DECLARE_BEGIN(x) mengisytiharkan tatasusunan statik yang menyimpan penunjuk kepada setiap fungsi perbandingan ahli. Untuk melakukan ini, mula-mula tentukan jenisnya:

#define PARAMSTRUCT_DECLARE_BEGIN(x) peribadi: typedef x ThisParamFieldClass; typedef bool (ThisParamFieldClass::*ComFun)(const ThisParamFieldClass& a) const; struct MEM_STAT_DATA ( std::string member_name; ComFun comfun; );

Dan kemudian tatasusunan diisytiharkan:

Std statik::vektor stat_data;

Pengendali perbandingan juga diisytiharkan di sini:

Awam: operator bool==(const ThisParamFieldClass& a) const; operator bool!=(const ThisParamFieldClass& a) const ( return !operator==(a); )

Pengendali perbandingan dilaksanakan oleh makro lain (PARAMFIELD_IMPL), tetapi pelaksanaannya adalah remeh jika anda mempunyai tatasusunan stat_data yang diisi: anda hanya perlu memanggil fungsi perbandingan untuk setiap elemen tatasusunan ini.
Untuk membandingkan struktur sahaja, tidak perlu menyimpan nama ahli struktur dalam tatasusunan. Walau bagaimanapun, menyimpan nama membolehkan konsep dilanjutkan melangkaui perbandingan ahli demi ahli kepada operasi lain, seperti menyimpan dan memuatkan dalam format teks yang boleh dibaca manusia.

3. Pengisian data tentang ahli struktur

Ia kekal untuk menyelesaikan isu mengisi tatasusunan stat_data. Memandangkan maklumat tentang ahli pada mulanya tidak tersedia di mana-mana kecuali makro DECLARE_MEMBER_PARAMSTRUCT, adalah mungkin untuk mengisi tatasusunan hanya dari sana (secara langsung atau tidak langsung). Walau bagaimanapun, makro ini dipanggil di dalam pengisytiharan struktur, yang bukan tempat yang paling mudah untuk memulakan std::vector. Saya menyelesaikan masalah ini menggunakan objek utiliti. Untuk setiap ahli struktur, kelas perkhidmatan dan objek kelas ini diisytiharkan. Kelas ini mempunyai pembina - ia menambah maklumat tentang elemen kepada stat_data tatasusunan statik:

Class cl##name ( public: cl##name(void) ( if(populate_statdata) ( MEM_STAT_DATA msd = ( #name, &ThisParamFieldClass::comp##name ); stat_data.push_back(msd); ) ) ); cl##nama ob##nama;

di mana populate_statdata ialah bendera statik yang diisytiharkan dalam makro kepala dan memberi isyarat sama ada tatasusunan stat_data harus diisi dengan nama ahli struktur dan fungsi perbandingannya. Apabila program bermula, mekanisme permulaan yang diterangkan di bawah menetapkan populate_statdata=true dan mencipta satu contoh struktur. Dalam kes ini, pembina objek perkhidmatan yang dikaitkan dengan setiap ahli struktur mengisi tatasusunan dengan data tentang ahli. Selepas ini, populate_statdata=false ditetapkan dan tatasusunan statik dengan maklumat ahli tidak lagi diubah suai. Penyelesaian ini membawa kepada kehilangan masa setiap kali program pengguna mencipta struktur, menyemak bendera populate_statdata. Walau bagaimanapun, penggunaan memori tidak meningkat: objek perkhidmatan tidak mengandungi ahli data, hanya pembina.

Dan akhirnya, mekanisme untuk mengawal bendera populate_statdata: dilaksanakan menggunakan objek perkhidmatan statik dengan pembina, satu untuk keseluruhan struktur. Objek ini diisytiharkan dalam makro kepala:

Kelas VcfInitializer ( awam: VcfInitializer(void); ); statik VcfInitializer vcinit;

Pelaksanaan pembina adalah dalam makro PARAMFIELD_IMPL(x):

X::VcfInitializer::VcfInitializer(void) ( x::populate_statdata = benar; ThisParamFieldClass dummy; x::populate_statdata = false; )

Teks penuh makro
#define PARAMSTRUCT_DECLARE_BEGIN(x) peribadi: typedef x ThisParamFieldClass; typedef bool (ThisParamFieldClass::*ComFun)(const ThisParamFieldClass& a) const; struct MEM_STAT_DATA ( std::string member_name; ComFun comfun; ); std statik::vektor stat_data; bool statik populate_statdata; awam: operator bool==(const ThisParamFieldClass& a) const; operator bool!=(const ThisParamFieldClass& a) const ( return !operator==(a); ) private: class VcfInitializer ( public: VcfInitializer(void); ); statik VcfInitializer vcinit; #define DECLARE_MEMBER_PARAMSTRUCT(type,name) public: type name; private: bool comp##name(const ThisParamFieldClass& a) const ( return name == a.name; ) class cl##name ( public: cl##name(void) ( if(populate_statdata) ( MEM_STAT_DATA msd = ( #name , &ThisParamFieldClass::comp##name, );stat_data.push_back(msd); ) ) ); cl##nama ob##nama; #define PARAMFIELD_IMPL(x) std::vector x::stat_data; bool x::populate_statdata = palsu; x::VcfInitializer x::vcinit; x::VcfInitializer::VcfInitializer(void) ( x::populate_statdata = benar; ThisParamFieldClass dummy; x::populate_statdata = false; ) bool x::operator==(const x& a) const ( bool r = true; for( saiz_t i=0;r && i *stat_data[i].comfun)(a); ) pulangan r; )
Kesimpulan

Berdasarkan makro di atas, anda boleh mengisytiharkan struktur yang pengendali perbandingan dan operasi ahli lain dibuat secara automatik. Operasi lain seperti itu termasuk, sebagai contoh, memuatkan dan menyimpan ke fail teks dalam format XML. Ketiadaan pertindihan kod menjadikan kerja lebih mudah dan menghalang ralat. Hanya mengisytiharkan ahli struktur menambah ahli itu untuk membandingkan, menyimpan dan memuatkan operasi.

Struktur

Seperti yang anda sedia maklum, kelas ialah jenis data rujukan. Ini bermakna bahawa objek kelas tertentu boleh diakses melalui rujukan, berbanding dengan nilai jenis mudah yang boleh diakses secara langsung. Tetapi kadangkala akses terus ke objek sebagai nilai jenis mudah ternyata berguna, sebagai contoh, untuk meningkatkan kecekapan program. Lagipun, setiap akses kepada objek (walaupun yang terkecil) melalui rujukan dikaitkan dengan kos tambahan untuk penggunaan sumber pengkomputeran dan RAM.

Untuk menyelesaikan masalah tersebut, C# menyediakan struktur, yang serupa dengan kelas tetapi merupakan jenis nilai dan bukannya jenis data rujukan. Itu. struct berbeza daripada kelas dalam cara ia disimpan dalam ingatan dan cara ia diakses (kelas ialah jenis rujukan yang diperuntukkan pada timbunan, struct ialah jenis nilai yang diperuntukkan pada tindanan), dan juga dalam sifat tertentu (contohnya, struct tidak menyokong warisan ). Atas sebab prestasi, anda akan menggunakan struktur untuk jenis data kecil. Walau bagaimanapun, dari segi sintaks, struktur sangat serupa dengan kelas.

Perbezaan utama ialah mereka diisytiharkan menggunakan kata kunci struct bukannya kelas. Berikut ialah bentuk umum pengisytiharan struktur:

nama struct: antara muka ( // pengisytiharan ahli )

di mana nama menandakan nama khusus struktur.

Seperti kelas, setiap struktur mempunyai ahlinya sendiri: kaedah, medan, pengindeks, sifat, kaedah pengendali dan peristiwa. Struktur juga boleh menentukan pembina, tetapi bukan pemusnah. Walau bagaimanapun, anda tidak boleh menentukan pembina lalai untuk struktur (iaitu, pembina tanpa parameter). Intinya ialah pembina lalai ditentukan secara automatik untuk semua struktur dan tidak boleh diubah. Pembina ini memulakan medan struktur dengan nilai lalai. Dan kerana struktur tidak menyokong warisan, ahlinya tidak boleh ditentukan sebagai abstrak, maya atau dilindungi.

Objek struktur boleh dibuat menggunakan operator baru dengan cara yang sama seperti objek kelas, tetapi ini tidak begitu diperlukan. Lagipun, apabila pengendali baru digunakan, pembina lalai dipanggil. Dan apabila pengendali ini tidak digunakan, objek masih dicipta, walaupun ia tidak dimulakan. Dalam kes ini, mana-mana ahli struktur perlu dimulakan secara manual.

Mari lihat contoh penggunaan struktur:

Menggunakan Sistem; namespace ConsoleApplication1 ( // Cipta struktur struktur UserInfo ( rentetan awam Nama; public byte Age; public UserInfo(string Name, byte Age) ( this.Name = Name; this.Age = Age; ) public void WriteUserInfo() ( Console. WriteLine ("Nama: (0), umur: (1)",Nama,Umur); ) ) kelas Program ( static void Main() ( UserInfo user1 = new UserInfo("Alexandr", 26); Console.Write(" user1 : "); user1.WriteUserInfo(); UserInfo user2 = new UserInfo("Elena",22); Console.Write("user2: "); user2.WriteUserInfo(); // Tunjukkan perbezaan utama antara struktur dan kelas user1 = user2 ; user2.Name = "Natalya"; user2.Umur = 25; Console.Write("\nuser1: "); user1.WriteUserInfo(); Console.Write("user2: "); user2.WriteUserInfo() ; Konsol. ReadLine(); ) ) )

Ambil perhatian bahawa apabila satu struktur diberikan kepada yang lain, salinan objeknya dibuat. Ini adalah salah satu perbezaan utama antara struktur dan kelas. Apabila rujukan kepada satu kelas diberikan kepada rujukan kepada kelas lain, hasilnya ialah rujukan di sebelah kiri penyataan tugasan menghala ke objek yang sama dengan rujukan di sebelah kanan. Dan apabila pembolehubah dalam satu struktur diperuntukkan kepada pembolehubah dalam struktur lain, salinan objek struktur di sebelah kanan pernyataan tugasan dicipta.

Oleh itu, jika contoh sebelumnya telah menggunakan kelas UserInfo dan bukannya struct, yang berikut akan menjadi hasilnya:

Tujuan struktur

Sehubungan dengan perkara di atas, persoalan yang munasabah timbul: mengapa struktur dimasukkan dalam C # jika ia mempunyai keupayaan yang lebih sederhana daripada kelas? Jawapan kepada soalan ini terletak pada meningkatkan kecekapan dan produktiviti program. Struktur ialah jenis nilai dan oleh itu boleh dimanipulasi secara langsung dan bukannya dengan rujukan. Akibatnya, untuk bekerja dengan struktur, pembolehubah jenis rujukan tidak diperlukan sama sekali, dan ini bermakna, dalam beberapa kes, penjimatan yang ketara dalam RAM.