Fitur C ++ modern yang perlu diketahui oleh semua programmer

Penulis materi, terjemahan yang kami terbitkan hari ini, mengatakan bahwa C ++, dalam bentuk modernnya, jika dibandingkan dengan apa yang beberapa tahun lalu, telah berubah secara signifikan menjadi lebih baik. Tentu saja, perubahan ini tidak terjadi segera. Misalnya, di masa lalu, C ++ tidak memiliki dinamisme. Tidak mudah menemukan seseorang yang dapat mengatakan bahwa ia memiliki perasaan yang lembut terhadap bahasa ini. Semuanya berubah ketika mereka yang bertanggung jawab untuk membakukan bahasa memutuskan untuk memberikan jalan kepada inovasi. Pada tahun 2011, C ++ menjadi bahasa yang dinamis, bahasa yang terus berkembang dan menyebabkan programmer lebih banyak emosi positif.

Jangan berpikir bahwa bahasa menjadi lebih mudah. Itu masih bisa disebut salah satu bahasa pemrograman yang paling kompleks digunakan, jika bukan yang paling kompleks. Tapi C ++ modern telah menjadi jauh lebih ramah dari sebelumnya.



Hari ini kita akan berbicara tentang beberapa fitur baru bahasa (dimulai dengan C ++ 11, yang, sudah, sudah berusia 8 tahun), yang akan diketahui oleh programmer mana pun.

Kata kunci otomatis


Sejak kata kunci auto muncul di C ++ 11, kehidupan programmer menjadi lebih mudah. Berkat kata kunci ini, kompiler dapat menampilkan tipe variabel pada waktu kompilasi, yang menyelamatkan kita dari selalu harus menentukan jenis sendiri. Ini ternyata sangat nyaman, misalnya, dalam kasus di mana Anda harus bekerja dengan tipe data seperti map<string,vector<pair<int,int>>> . Saat menggunakan kata kunci auto , ada beberapa hal yang perlu dipertimbangkan. Pertimbangkan sebuah contoh:

 auto an_int = 26; //    ,     - int auto a_bool = false; //   bool auto a_float = 26.04; //   float auto ptr = &a_float; //       auto data; // #1     ?    - . 

Perhatikan baris terakhir dalam contoh ini, komentar yang ditandai sebagai #1 (selanjutnya, dengan cara yang sama kita akan menandai baris yang akan kita uraikan setelah contoh). Tidak ada penginisialisasi di baris ini, Anda tidak bisa melakukan ini. Kode yang terletak pada baris ini mencegah kompiler mengetahui jenis variabel yang sesuai.

Awalnya, kata kunci auto dalam C ++ cukup terbatas. Kemudian, dalam versi bahasa yang lebih baru, fitur yang ditambahkan secara auto . Ini adalah contoh lain:

 auto merge(auto a, auto b) //            auto! {   std::vector<int> c = do_something(a, b);   return c; } std::vector<int> a = { ... }; // #1 -  std::vector<int> b = { ... }; // #2  -  auto c = merge(a,b); //       

Baris #1 dan #2 menggunakan inisialisasi variabel menggunakan kurung kurawal - fitur baru lainnya di C ++ 11.

Ingatlah bahwa ketika menggunakan kata kunci auto , kompiler harus memiliki beberapa cara untuk menyimpulkan jenis variabel.

Sekarang, pertanyaan yang menarik. Apa yang terjadi jika Anda menggunakan desain seperti auto a = {1, 2, 3} ? Apa ini Vektor, atau penyebab kesalahan kompilasi?

Bahkan, konstruksi bentuk std::initializer_list<type> muncul di C ++ 11. Daftar nilai inisialisasi yang di dalam tanda kurung akan diperlakukan sebagai wadah menggunakan kata kunci auto .

Dan akhirnya, seperti yang telah disebutkan, ketik inferensi oleh kompiler dapat sangat berguna jika Anda harus bekerja dengan struktur data yang kompleks. Berikut ini sebuah contoh:

 void populate(auto &data) { //    !   data.insert({"a",{1,4}});   data.insert({"b",{3,1}});   data.insert({"c",{2,3}}); } auto merge(auto data, auto upcoming_data) { //         auto result = data;   for(auto it: upcoming_data) {       result.insert(it);   }   return result; } int main() {   std::map<std::string, std::pair<int,int>> data;   populate(data);   std::map<std::string, std::pair<int,int>> upcoming_data;   upcoming_data.insert({"d",{5,3}});   auto final_data = merge(data,upcoming_data);   for(auto itr: final_data) {       auto [v1, v2] = itr.second; // #1               std::cout << itr.first << " " << v1 << " " << v2 << std:endl;   }   return 0; } 

Lihatlah baris #1 . Ekspresi auto [v1,v2] = itr.second mewakili fitur baru C ++ 17. Ini adalah apa yang disebut dekomposisi ketika mendeklarasikan variabel. Dalam versi bahasa sebelumnya, setiap nilai harus diekstraksi secara terpisah. Berkat mekanisme ini, melakukan operasi seperti itu menjadi jauh lebih nyaman.

Selain itu, jika Anda perlu bekerja dengan data menggunakan tautan, cukup menambahkan hanya satu karakter ke konstruksi ini, mengonversinya ke bentuk berikut: auto &[v1,v2] = itr.second .

Ekspresi Lambda


C ++ 11 memperkenalkan dukungan untuk ekspresi lambda. Mereka menyerupai fungsi anonim dalam JavaScript, mereka dapat dibandingkan dengan objek fungsional tanpa nama. Mereka menangkap variabel dalam berbagai lingkup tergantung pada deskripsi mereka, yang konstruksi sintaksis kompak digunakan. Selain itu, mereka dapat ditugaskan ke variabel.

Ekspresi Lambda adalah alat yang sangat berguna untuk kasus-kasus ketika Anda perlu melakukan beberapa operasi kecil dalam kode, tetapi Anda tidak ingin menulis fungsi terpisah untuk ini. Contoh umum lain dari penggunaannya adalah penciptaan fungsi yang digunakan dalam membandingkan nilai. Sebagai contoh:

 std::vector<std::pair<int,int>> data = {{1,3}, {7,6}, {12, 4}}; //        std::sort(begin(data), end(data), [](auto a, auto b) { //   - auto!   return a.second < b.second; }); 

Anda dapat menemukan banyak hal menarik dalam contoh singkat ini.

Pertama, perhatikan betapa nyamannya menggunakan inisialisasi variabel menggunakan kurung kurawal. Selanjutnya, kita dapat melihat konstruksi standar begin() dan end() , yang juga muncul dalam C ++ 11. Kemudian muncul fungsi lambda, yang digunakan sebagai mekanisme untuk membandingkan data. Parameter fungsi ini dideklarasikan menggunakan kata kunci auto , fitur ini muncul di C ++ 14. Sebelumnya, kata kunci ini tidak dapat digunakan untuk menggambarkan parameter fungsi.

Sekarang perhatikan bahwa ekspresi lambda dimulai dengan tanda kurung siku - [] . Inilah yang disebut topeng variabel. Ini menentukan ruang lingkup ekspresi, yaitu, memungkinkan Anda untuk mengontrol hubungan ekspresi lambda dengan variabel dan objek lokal.

Berikut adalah kutipan dari repositori yang didedikasikan untuk fitur C ++ modern ini:

  • [] - ekspresi tidak menangkap apa pun. Ini berarti bahwa dalam ekspresi lambda tidak mungkin untuk menggunakan variabel lokal dari ruang lingkup yang eksternal untuk itu. Hanya parameter yang dapat digunakan dalam ekspresi.
  • [=] - ekspresi menangkap nilai objek lokal (yaitu, variabel lokal, parameter). Ini berarti bahwa mereka dapat digunakan, tetapi tidak dimodifikasi.
  • [&] - ekspresi menangkap referensi ke objek lokal. Mereka dapat dimodifikasi, seperti yang ditunjukkan pada contoh berikut.
  • [this] - ekspresi menangkap nilai dari pointer this .
  • [a, &b] - ekspresi menangkap nilai objek a dan referensi ke objek b .

Akibatnya, jika di dalam fungsi lambda Anda perlu mengkonversi data ke beberapa format lain, Anda dapat menggunakan mekanisme di atas. Pertimbangkan sebuah contoh:

 std::vector<int> data = {2, 4, 4, 1, 1, 3, 9}; int factor = 7; for_each(begin(data), end(data), [&factor](int &val) { //    factor     val = val * factor;   factor--; // #1   - ,  -    factor   }); for(int val: data) {   std::cout << val << ' '; // 14 24 20 4 3 6 9 } 

Di sini, jika variabel factor diakses oleh nilai (maka variabel mask [factor] akan digunakan untuk menggambarkan ekspresi lambda), maka di baris #1 nilai factor tidak dapat diubah - hanya karena kita tidak akan memiliki hak untuk melakukan operasi seperti itu. Dalam contoh ini, kami memiliki hak untuk melakukan tindakan tersebut. Dalam situasi seperti itu, penting untuk tidak menyalahgunakan kemampuan yang diakses oleh referensi variabel.

Selain itu, perhatikan bahwa val juga diakses dengan referensi. Ini memastikan bahwa perubahan data yang terjadi pada fungsi lambda memengaruhi vector .

Ekspresi inisialisasi variabel di dalam if dan switch constructs


Saya sangat menyukai inovasi C ++ 17 ini setelah saya mengetahuinya. Pertimbangkan sebuah contoh:

 std::set<int> input = {1, 5, 3, 6}; if(auto it = input.find(7); it==input.end()){ //   - ,  -    std::cout << 7 << " not found" << std:endl; } else {   //    else      it   std::cout << 7 << " is there!" << std::endl; } 

Ternyata sekarang Anda dapat menginisialisasi variabel dan membandingkannya dengan penggunaannya dalam satu if atau switch blok. Ini membantu menulis kode yang akurat. Berikut ini adalah deskripsi skematis dari struktur yang sedang dipertimbangkan:

 if( init-statement(x); condition(x)) {   //    } else {   //     x   //    } 

Melakukan perhitungan waktu kompilasi menggunakan constexpr


constexpr memberi kita peluang besar. Misalkan kita memiliki semacam ekspresi yang perlu dihitung, sedangkan nilainya, setelah menginisialisasi dengan variabel yang sesuai, tidak akan berubah. Ungkapan seperti itu dapat dihitung di muka dan digunakan sebagai makro. Atau, apa yang menjadi mungkin di C ++ 11, gunakan constexpr .

Programmer berusaha untuk meminimalkan jumlah perhitungan yang dilakukan selama eksekusi program. Akibatnya, jika operasi tertentu dapat dilakukan selama proses kompilasi dan dengan demikian menghapus beban dari sistem selama eksekusi program, ini akan memiliki efek yang baik pada perilaku program selama eksekusi. Berikut ini sebuah contoh:

 #include <iostream> constexpr long long fact(long long n) { //       constexpr return n == 1 ? 1 : (fact(n-1) * n); } int main() { const long long bigval = fact(20); std::cout<<bigval<<std::endl; } 

Ini adalah contoh yang sangat umum menggunakan constexpr .

Karena kami mendeklarasikan fungsi untuk menghitung faktorial sebagai constexpr , kompiler dapat melakukan pra-perhitungan nilai fact(20) pada saat kompilasi program. Akibatnya, setelah dikompilasi, string const long long bigval = fact(20); dapat diganti dengan const long long bigval = 2432902008176640000; .

Perhatikan bahwa argumen yang diteruskan ke fungsi diwakili oleh konstanta. Ini adalah fitur penting menggunakan fungsi yang dideklarasikan menggunakan constexpr . Argumen yang disampaikan kepada mereka juga harus dideklarasikan dengan constexpr atau dengan kata kunci const . Jika tidak, fungsi tersebut akan berperilaku seperti fungsi biasa, yaitu, selama kompilasi, nilainya tidak akan dihitung sebelumnya.

Variabel juga dapat dideklarasikan menggunakan constexpr . Dalam hal ini, seperti yang Anda duga, nilai-nilai variabel ini harus dihitung pada waktu kompilasi. Jika ini tidak dapat dilakukan, pesan kesalahan kompilasi akan ditampilkan.

Sangat menarik untuk dicatat bahwa kemudian, dalam C ++ 17, konstruksi constexpr-if dan constexpr-lambda muncul.

Struktur data Tuple


Seperti struktur data pair , struktur data tuple (tuple) adalah kumpulan nilai dari berbagai jenis ukuran tetap. Berikut ini sebuah contoh:

 auto user_info = std::make_tuple("M", "Chowdhury", 25); //  auto     //    std::get<0>(user_info); std::get<1>(user_info); std::get<2>(user_info); //  C++ 11     tie std::string first_name, last_name, age; std::tie(first_name, last_name, age) = user_info; //  C++ 17, ,       auto [first_name, last_name, age] = user_info; 

Terkadang, alih-alih struktur data tuple , lebih nyaman menggunakan std::array . Struktur data ini mirip dengan array sederhana yang digunakan dalam bahasa C, dilengkapi dengan fitur tambahan dari pustaka standar C ++. Struktur data ini muncul di C ++ 11.

Secara otomatis menyimpulkan tipe argumen templat kelas


Nama fitur ini terlihat agak panjang dan kompleks, tetapi sebenarnya tidak ada yang rumit di sini. Gagasan utama di sini adalah bahwa dalam C ++ 17, output dari tipe argumen template juga dilakukan untuk templat kelas standar. Sebelumnya, ini hanya didukung untuk templat fungsional. Alhasil, ternyata mereka biasa menulis seperti ini:

 std::pair<std::string, int> user = {"M", 25}; 

Dengan rilis C ++ 17, konstruksi ini sekarang dapat diganti dengan ini:

 std::pair user = {"M", 25}; 

Jenis inferensi dilakukan secara implisit. Mekanisme ini bahkan lebih nyaman digunakan untuk tupel. Yaitu, sebelum saya harus menulis yang berikut:

 std::tuple<std::string, std::string, int> user ("M", "Chy", 25); 

Sekarang hal yang sama terlihat seperti ini:

 std::tuple user2("M", "Chy", 25); 

Perlu dicatat bahwa fitur-fitur ini tidak akan terlihat menarik bagi mereka yang tidak terlalu mengenal template C ++.

Pointer pintar


Bekerja dengan pointer di C ++ bisa menjadi mimpi buruk yang nyata. Berkat kebebasan yang diberikan oleh bahasa kepada programmer, kadang-kadang sangat sulit baginya, seperti yang mereka katakan, "untuk tidak menembak diri sendiri." Dalam banyak kasus, pointer mendorong "tembakan" programmer.

Untungnya, C ++ 11 memperkenalkan pointer pintar yang jauh lebih nyaman daripada pointer biasa. Mereka membantu programmer menghindari kebocoran memori dengan membebaskan sumber daya bila memungkinkan. Selain itu, mereka memberikan jaminan keamanan untuk pengecualian.

Ringkasan


Berikut adalah repositori yang baik, yang kami percaya, akan menarik untuk dikunjungi oleh mereka yang mengikuti inovasi C ++. Sesuatu yang baru terus muncul dalam bahasa ini. Di sini kami menyentuh hanya beberapa fitur modern bahasa. Padahal, ada banyak dari mereka. Mungkin saja kita masih membicarakannya.

Pembaca yang budiman! Apa fitur C ++ modern yang menurut Anda paling menarik dan berguna?

Source: https://habr.com/ru/post/id451870/


All Articles