#include #include "kompleks.h"
menggunakan namespace std;
ostream& operator << (ostream& os, const kompleks& x) { return os << '(' << nyata (x) << ',' << imag (x) << ')' }
int main() { kompleks c1(2, 1); kompleks c2(4, 0);
cout<<c1<<endl;cout<<c2<<endl;
cout<<c1+c2<<endl; cout<<c1-c2<<endl;cout<<c1*c2<<endl;<c1/2<<endl;
cout << konj(c1) << endl; cout << norma(c1) << endl; cout << kutub(10,4) << endl;
cout<<(c1+=c2)<<endl;
cout << (c1 == c2) << endl; cout << (c1 != c2) << endl; cout << +c2 << cout << -c2 << endl;
cout<<(c2 - 2)<<endl;cout<<(5+c2)<<endl;
kembali 0; }
Seperti yang ditunjukkan pada gambar di atas, penugasan mandiri perlu diperiksa saat membebani operator penugasan "=", karena alasan berikut:
Jika tidak ada deteksi penugasan mandiri, maka m_data dari objeknya sendiri akan dilepaskan, dan konten yang ditunjuk oleh m_data tidak akan ada, sehingga akan ada masalah dengan penyalinannya.
Berikut dua kelas untuk penjelasannya:
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex*,
const complex&);
};
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
Setelah membuat dua objek ini, compiler (VC) mengalokasikan memori ke dua objek tersebut sebagai berikut:
Dua di sebelah kiri adalah alokasi memori kompiler dari kompleks kelas dalam mode debug dan mode rilis. Dalam mode debug, kompiler memasukkan bagian informasi ukuran kepala dan ekor (bagian merah), 4*8 + 4 (bagian abu-abu) ke dalam memori objek kompleks. hanya ada 52 kata. bagian, tetapi VC disejajarkan dengan 16 byte, jadi kelipatan 16 terdekat dari 52 adalah 64, dan celah 12 byte harus diisi (bagian cyan pad). Untuk objek kompleks di bagian rilis, hanya header informasi dan bagian header yang ditambahkan. Analisis kelas string pada dasarnya sama.
Selanjutnya, mari kita lihat bagaimana kompiler VC mengalokasikan objek array:
Demikian pula, kompiler menambahkan beberapa informasi berlebihan ke objek. Untuk objek kelas kompleks, karena array memiliki tiga objek, ada 8 objek ganda. Kemudian kompiler menyisipkan "3" di depan tiga objek kompleks untuk menandai nomor objek individual . Metode analisis kelas String juga serupa.
Gambar di bawah mengilustrasikan mengapa menghapus objek array memerlukan metode delete[]:
String.h
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = ' ';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
string_test.cpp
#include "string.h"
#include <iostream>
using namespace std;
int main()
{
String s1("hello");
String s2("world");
String s3(s2);
cout << s3 << endl;
s3 = s1;
cout << s3 << endl;
cout << s2 << endl;
cout << s1 << endl;
}
Dalam C++, konstruktor satu parameter (atau konstruktor multi-parameter dengan nilai default untuk semua parameter kecuali parameter pertama) memainkan dua peran. 1 adalah konstruktor, 2 adalah operator konversi tipe default dan implisit.
Oleh karena itu, terkadang ketika kita menulis kode seperti AAA = XXX, dan tipe XXX merupakan tipe parameter dari konstruktor parameter tunggal AAA, kompiler akan secara otomatis memanggil konstruktor ini untuk membuat objek AAA.
Ini terlihat keren dan sangat nyaman. Namun dalam beberapa kasus (lihat contoh resmi di bawah), hal ini bertentangan dengan niat awal kami (pemrogram). Saat ini, Anda perlu menambahkan modifikasi eksplisit di depan konstruktor untuk menentukan bahwa konstruktor ini hanya dapat dipanggil/digunakan secara eksplisit dan tidak dapat digunakan secara implisit sebagai operator konversi tipe.
Konstruktor eksplisit digunakan untuk mencegah konversi implisit. Silakan lihat kode di bawah ini:
class Test1
{
public:
Test1(int n)
{
num=n;
}//普通构造函数
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
}//explicit(显式)构造函数
private:
int num;
};
int main()
{
Test1 t1=12;//隐式调用其构造函数,成功
Test2 t2=12;//编译错误,不能隐式调用其构造函数
Test2 t2(12);//显式调用成功
return 0;
}
Konstruktor Test1 mengambil parameter int, dan baris 23 kode akan dikonversi secara implisit untuk memanggil konstruktor Test1. Konstruktor Test2 dinyatakan eksplisit, artinya konstruktor tidak dapat dipanggil melalui konversi implisit, sehingga kesalahan kompilasi akan terjadi pada baris 24 kode.
Konstruktor biasa dapat dipanggil secara implisit. Konstruktor eksplisit hanya dapat dipanggil secara eksplisit.
Ketika Pecahan apa pun perlu dikonversi ke tipe ganda, fungsi double() secara otomatis dipanggil untuk konversi. Seperti yang ditunjukkan pada gambar di atas, kompiler menentukan bahwa 4 adalah bilangan bulat selama analisis double d = 4 + f, dan kemudian melanjutkan untuk menentukan f. Diamati bahwa f menyediakan fungsi double(), dan kemudian melakukan double () operasi pada f, dan menghitung 0.6 , lalu menambahkannya ke 4, dan akhirnya mendapatkan tipe ganda 4.6.
Sebuah kelas didefinisikan pada gambar di atas, yang disebut Pecahan. Operator "+" kelebihan beban di kelas Selama operasi f+4, "4" secara implisit diubah (konstruktor) oleh kompiler menjadi objek Pecahan, dan kemudian diteruskan. melalui Pecahan Operator "+" yang kelebihan beban berpartisipasi dalam operasi.
Seperti yang ditunjukkan pada gambar di atas, fungsi double() ditambahkan ke Fraction, yang membagi dua variabel anggota Fraction, dan kemudian secara paksa mengubahnya menjadi tipe ganda dan mengembalikan hasilnya. Selama proses kelebihan beban f+4, kompiler akan melakukannya melaporkan kesalahan. Anda dapat melakukan analisis berikut:
1. Pertama, 4 secara implisit diubah (konstruktor) menjadi objek Pecahan, dan kemudian operator "+" yang kelebihan beban dioperasikan dengan "f" untuk mengembalikan objek Pecahan;
2. Pertama, 4 secara implisit diubah (konstruktor) menjadi objek Pecahan, dan kemudian operasi ganda dilakukan pada pasangan melalui operator "+" dan operasi "f" yang kelebihan beban, dan akhirnya objek Pecahan dikembalikan;
3. . .
Jadi kompiler memiliki setidaknya dua cara untuk melakukannya, yang menciptakan ambiguitas dan melaporkan kesalahan. Seperti yang ditunjukkan pada gambar di atas, jika kata kunci eksplisit ditambahkan sebelum konstruktor Pecahan, konversi implisit akan dibatalkan. Oleh karena itu, selama eksekusi d2 = f + 4, f akan memanggil fungsi ganda untuk mengkonversi ke 0,6, dan kemudian tambahkan ke 4 menjadi 4.6. Karena konstruktor membatalkan konversi rumus implisit, 4.6 tidak dapat dikonversi ke Pecahan, sehingga kesalahan akan dilaporkan.
Gambar berikut menunjukkan penerapan fungsi kelebihan operator dan konversi di C++ stl:
Gambar berikut menggambarkan dengan baik struktur internal dan penggunaan smart pointer: Ada tiga poin penting dalam sintaks smart pointer. Yang pertama adalah pointer eksternal yang disimpan, yang sesuai dengan T* px pada gambar di atas terkait dengan penunjuk masuk akan dilakukan alih-alih penunjuk masuk; yang kedua adalah membebani operator "*", melakukan dereferensi, dan mengembalikan objek yang ditunjuk oleh penunjuk; yang ketiga adalah membebani operator "->", untuk return Sebuah pointer yang sesuai dengan gambar di atas adalah px.
Iterator juga merupakan jenis smart pointer. Ada juga tiga elemen smart pointer yang disebutkan di atas, yang sesuai dengan font merah dan bagian bertanda kuning pada gambar di bawah:
Karakter kelebihan beban "*" dan "->" dari kelebihan beban iterator akan dianalisis dengan cermat di bawah ini:
Buat objek daftar iterator, list::iterator ite; daftar di sini digunakan untuk menyimpan objek Foo, yang merupakan kelas dalam definisi templat daftar T, operator*() mengembalikan objek (*node).data, node bertipe __link_type, tetapi __link_type bertipe __list_node<T>*. T di sini adalah Foo, jadi node bertipe __list_node<Foo >*, jadi ( *node).data mendapatkan objek bertipe Foo, dan &(operator*()) akhirnya mendapatkan alamat objek Foo, yaitu mengembalikan Foo* Sebuah penunjuk ke tipe.
Seperti yang Anda lihat dari gambar di atas, setiap fungsi adalah kelas yang membebani operator "()", dan kemudian menjadi "fungsi". Ini sebenarnya adalah kelas, tetapi tampaknya memiliki properti fungsi. Setiap fungsi sebenarnya mengintegrasikan kelas aneh di belakangnya, seperti yang ditunjukkan pada gambar di bawah. Kelas ini tidak perlu dideklarasikan secara manual secara eksplisit oleh programmer. Fungsi di perpustakaan standar juga mewarisi kelas aneh: Konten kelas ini ditunjukkan pada gambar di bawah. Itu hanya mendeklarasikan beberapa hal, dan tidak ada variabel atau fungsi aktual di dalamnya. Konten spesifik akan dibahas di STL.
Berbeda dengan templat kelas, templat fungsi tidak perlu mendeklarasikan tipe parameter masuk secara eksplisit saat menggunakannya, kompiler akan secara otomatis menyimpulkan tipenya.
Templat anggota sering digunakan dalam pemrograman generik. Untuk mendapatkan skalabilitas yang lebih baik, ambil gambar di atas sebagai contoh. T1 sering kali merupakan kelas dasar U1, dan T2 sering kali merupakan kelas dasar U2. Melalui cara ini, selama kelas induk atau nenek moyang dari U1 dan U2 yang diteruskan adalah T1 dan T2, pewarisan dan polimorfisme dapat dimanfaatkan dengan cerdik melalui metode ini, tetapi tidak sebaliknya. Metode ini banyak digunakan di STL:
Seperti namanya, parsialisasi templat mengacu pada penentuan tipe data tertentu dalam templat, yang berbeda dari generalisasi: Tentu saja, parsialisasi templat juga memiliki derajat, dan tipe parsial dapat ditentukan, yang disebut spesialisasi parsial:
Terlalu banyak konten yang akan dijelaskan dalam kursus C++11, jadi saya hanya akan memperkenalkannya di sini untuk saat ini.
Terlalu banyak konten yang akan dijelaskan dalam kursus C++11 Saya hanya akan memperkenalkannya di sini untuk saat ini.
Referensi dapat dilihat sebagai alias untuk variabel yang direferensikan.
Seperti yang ditunjukkan pada gambar di atas, tiga kelas didefinisikan, A, B dan C. B mewarisi dari A, dan C mewarisi dari B. Ada dua fungsi virtual di A, satu di B, dan satu di C. Kompiler mengalokasikan objek a dari A ke dalam memori seperti yang ditunjukkan pada gambar di atas. Hanya ada dua variabel anggota m_data1 dan m_data2 Pada saat yang sama, karena kelas A memiliki fungsi virtual, kompiler akan mengalokasikan ruang ke objek a untuk menyimpan tabel fungsi virtual, tabel ini mempertahankan alamat fungsi virtual (fungsi dinamis) kelas ini Pengikatan stateful), karena kelas A memiliki dua fungsi virtual, ada dua spasi (spasi kuning dan biru) di tabel fungsi virtual a yang masing-masing menunjuk ke A::vfunc1() dan A::vfunc2(); adalah objek kelas B, karena kelas B menulis ulang fungsi vfunc1() kelas A. , jadi tabel fungsi virtual B (bagian cyan) akan menunjuk ke B::vfunc1(), dan B mewarisi vfunc2() dari kelas A, jadi tabel fungsi virtual B (bagian biru) akan menunjuk ke A dari kelas induk A: :vfunc2 () fungsi; demikian pula, c adalah objek kelas C, diwakili oleh Kelas C menulis ulang fungsi vfunc1() dari kelas induk, sehingga tabel fungsi virtual C (bagian kuning) akan menunjuk ke C::vfunc1(). Pada saat yang sama, C mewarisi vfunc2() dari superkelas A, jadi fungsi virtual B Tabel (bagian biru) akan menunjuk ke fungsi A::vfunc2(). Pada saat yang sama, gambar di atas juga menggunakan kode bahasa C untuk menggambarkan bagaimana lapisan bawah kompiler memanggil fungsi-fungsi ini. Ini adalah inti dari polimorfisme pewarisan berorientasi objek.
Pointer this sebenarnya dapat dianggap sebagai pointer yang menunjuk ke alamat memori objek saat ini. Seperti yang ditunjukkan pada gambar di atas, karena ada fungsi virtual di kelas dasar dan subkelas, this->Serialize() akan terikat secara dinamis, yang setara dengan (*(ini- >vptr)[n])(ini). Hal tersebut dapat dipahami dengan menggabungkan pointer virtual dan tabel fungsi virtual pada bagian sebelumnya. Adapun alasan mengapa penulisan seperti ini pada akhirnya benar, berikut akan dijelaskan secara ringkas.