#include #include "complex.h"
ใช้เนมสเปซมาตรฐาน;
ostream& โอเปอเรเตอร์ << (ostream& os, const complex& x) { return os << '(' << จริง (x) << ',' << imag (x) << ')';
int main() { ซับซ้อน c1(2, 1); ซับซ้อน c2(4, 0);
ศาล << c1 << endl; ศาล << c2 << endl;
ศาล << c1+c2 << endl; ศาล << c1-c2 << endl;
ศาล << conj (c1) << endl; ศาล << บรรทัดฐาน (c1) << endl;
ศาล << (c1 += c2) << endl;
ศาล << (c1 == c2) << endl; ศาล << (c1 != c2) << endl; ศาล << +c2 << endl;
ศาล << (c2 - 2) << endl; ศาล << (5 + c2) << endl;
กลับ 0; }
ดังแสดงในรูปด้านบน จะต้องตรวจสอบการกำหนดตัวเองเมื่อมีการโอเวอร์โหลดตัวดำเนินการกำหนด "=" ด้วยเหตุผลดังต่อไปนี้:
หากไม่มีการตรวจจับการกำหนดตนเอง m_data ของอ็อบเจ็กต์ของตัวเองจะถูกปล่อยออกมา และเนื้อหาที่ชี้ไปโดย m_data จะไม่มีอยู่ ดังนั้นจะเกิดปัญหากับการคัดลอก
ต่อไปนี้เป็นสองคลาสสำหรับคำอธิบาย:
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;
};
หลังจากสร้างวัตถุทั้งสองนี้แล้ว คอมไพลเลอร์ (VC) จะจัดสรรหน่วยความจำให้กับวัตถุทั้งสองดังนี้:
สองทางด้านซ้ายคือการจัดสรรหน่วยความจำคอมไพเลอร์ของคลาสคอมเพล็กซ์ในโหมดดีบักและโหมดรีลีส ในโหมดดีบัก คอมไพเลอร์จะแทรกส่วนหัวและส่วนท้าย (ส่วนสีแดง) ส่วนข้อมูลขนาด 4*8 + 4 (ส่วนสีเทา) ลงในหน่วยความจำอ็อบเจ็กต์ที่ซับซ้อน ส่วนสีเขียวคือพื้นที่ว่างจริง ๆ โดยอ็อบเจ็กต์ที่ซับซ้อน หลังจากการคำนวณ มีเพียง 52 คำ แต่ VC ถูกจัดแนวเป็น 16 ไบต์ ดังนั้น 16 ผลคูณที่ใกล้ที่สุดของ 52 คือ 64 และควรเติมช่องว่าง 12 ไบต์ (ส่วนแผ่นสีฟ้า) สำหรับออบเจ็กต์ที่ซับซ้อนในส่วน release จะมีการเพิ่มเฉพาะส่วนหัวข้อมูลและส่วนหัวเท่านั้น การวิเคราะห์คลาสสตริงนั้นโดยพื้นฐานแล้วจะเหมือนกัน
ต่อไป มาดูกันว่าคอมไพเลอร์ VC จัดสรรอ็อบเจ็กต์อาร์เรย์อย่างไร:
ในทำนองเดียวกัน คอมไพเลอร์จะเพิ่มข้อมูลที่ซ้ำซ้อนให้กับอ็อบเจ็กต์ เนื่องจากอาร์เรย์มีอ็อบเจ็กต์ 3 ตัว คอมไพเลอร์จะแทรก "3" ไว้ด้านหน้าอ็อบเจ็กต์ที่ซับซ้อน 3 ตัวเพื่อทำเครื่องหมายแต่ละอ็อบเจ็กต์ . วิธีการวิเคราะห์ของคลาส String ก็คล้ายกันเช่นกัน
รูปภาพด้านล่างแสดงให้เห็นว่าเหตุใดการลบวัตถุอาร์เรย์จึงต้องใช้วิธีลบ []:
สตริง.เอช
#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;
}
ใน C ++ ตัวสร้างพารามิเตอร์เดียว (หรือตัวสร้างหลายพารามิเตอร์ที่มีค่าเริ่มต้นสำหรับพารามิเตอร์ทั้งหมดยกเว้นพารามิเตอร์แรก) มีบทบาทสองประการ 1 เป็นตัวสร้าง 2 คือตัวดำเนินการแปลงประเภทเริ่มต้นและโดยนัย
ดังนั้น บางครั้งเมื่อเราเขียนโค้ด เช่น AAA = XXX และประเภทของ XXX เกิดขึ้นเป็นประเภทพารามิเตอร์ของตัวสร้างพารามิเตอร์เดียว AAA คอมไพลเลอร์จะเรียกตัวสร้างนี้โดยอัตโนมัติเพื่อสร้างวัตถุ AAA
มันดูเท่และสะดวกมาก แต่ในบางกรณี (ดูตัวอย่างที่เชื่อถือได้ด้านล่าง) มันขัดแย้งกับความตั้งใจเดิมของเรา (โปรแกรมเมอร์) ในขณะนี้ คุณต้องเพิ่มการแก้ไขที่ชัดเจนที่ด้านหน้าของ Constructor เพื่อระบุว่า Constructor นี้สามารถเรียก/ใช้งานอย่างชัดเจนเท่านั้น และไม่สามารถใช้โดยปริยายเป็นตัวดำเนินการแปลงประเภทได้
ตัวสร้างที่ชัดเจนถูกใช้เพื่อป้องกันการแปลงโดยนัย โปรดดูรหัสด้านล่าง:
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;
}
ตัวสร้างของ Test1 รับพารามิเตอร์ int และบรรทัดที่ 23 ของโค้ดจะถูกแปลงเป็นการเรียกตัวสร้างของ Test1 โดยปริยาย ตัวสร้างของ Test2 ได้รับการประกาศว่าชัดเจน ซึ่งหมายความว่าตัวสร้างไม่สามารถถูกเรียกผ่านการแปลงโดยนัย ดังนั้นข้อผิดพลาดในการคอมไพล์จะเกิดขึ้นที่บรรทัดที่ 24 ของโค้ด
ตัวสร้างสามัญสามารถเรียกได้โดยปริยาย ตัวสร้างที่ชัดเจนสามารถเรียกได้อย่างชัดเจนเท่านั้น
เมื่อเศษส่วนใดๆ จำเป็นต้องแปลงเป็นประเภทคู่ ฟังก์ชัน double() จะถูกเรียกสำหรับการแปลงโดยอัตโนมัติ ดังแสดงในรูปด้านบน คอมไพเลอร์พิจารณาว่า 4 เป็นจำนวนเต็มในระหว่างการวิเคราะห์ double d = 4 + f จากนั้นจึงพิจารณา f ต่อไป สังเกตว่า f จัดเตรียมฟังก์ชัน double() แล้วจึงดำเนินการ double () การดำเนินการบน f และคำนวณ 0.6 จากนั้นบวกเข้ากับ 4 และสุดท้ายก็ได้ double type 4.6
คลาสถูกกำหนดไว้ในรูปด้านบน เรียกว่าเศษส่วน ตัวดำเนินการ "+" ถูกโอเวอร์โหลดในคลาส ในระหว่างการดำเนินการ f+4 "4" จะถูกแปลงโดยปริยาย (ตัวสร้าง) โดยคอมไพเลอร์เป็นวัตถุเศษส่วน จากนั้นจึงส่งต่อ ผ่านเศษส่วน ตัวดำเนินการ "+" ที่โอเวอร์โหลดเข้าร่วมในการดำเนินการ
ดังแสดงในรูปด้านบน ฟังก์ชัน double() จะถูกเพิ่มเข้าไปใน Fraction ซึ่งจะแบ่งตัวแปรสมาชิกของ Fraction สองตัว จากนั้นบังคับให้แปลงตัวแปรให้เป็น double type และส่งกลับผลลัพธ์ในระหว่างกระบวนการโอเวอร์โหลด f+4 คอมไพลเลอร์จะทำ รายงานข้อผิดพลาด คุณสามารถทำการวิเคราะห์ต่อไปนี้:
ขั้นแรก 4 จะถูกแปลงโดยปริยาย (ตัวสร้าง) เป็นวัตถุเศษส่วน จากนั้นตัวดำเนินการ "+" ที่โอเวอร์โหลดจะดำเนินการด้วย "f" เพื่อส่งคืนวัตถุเศษส่วน
2. ขั้นแรก 4 จะถูกแปลงโดยปริยาย (ตัวสร้าง) เป็นวัตถุเศษส่วน จากนั้นมีการดำเนินการสองครั้งกับคู่นี้ผ่านตัวดำเนินการ "+" ที่โอเวอร์โหลดและการดำเนินการ "f" และสุดท้ายวัตถุเศษส่วนจะถูกส่งกลับ
3. - -
ดังนั้นคอมไพลเลอร์จึงมีอย่างน้อยสองวิธี ซึ่งสร้างความคลุมเครือและรายงานข้อผิดพลาด ดังแสดงในรูปด้านบน หากเพิ่มคีย์เวิร์ด explict ก่อนตัวสร้าง Franction การแปลงโดยนัยจะถูกยกเลิก ดังนั้น ในระหว่างการดำเนินการ d2 = f + 4 f จะเรียกใช้ฟังก์ชัน double เพื่อแปลงเป็น 0.6 จากนั้น เพิ่มเป็น 4 ให้เป็น 4.6 เนื่องจาก Constructor ยกเลิกการแปลงสูตรโดยนัย จึงไม่สามารถแปลง 4.6 เป็นเศษส่วนได้ ดังนั้น จะมีการรายงานข้อผิดพลาด
รูปต่อไปนี้แสดงการประยุกต์ใช้ฟังก์ชันโอเวอร์โหลดและการแปลงใน C++ stl:
รูปภาพด้านล่างแสดงให้เห็นถึงโครงสร้างภายในและการใช้งานพอยน์เตอร์อัจฉริยะ: มีจุดสำคัญสามจุดในไวยากรณ์ของพอยน์เตอร์อัจฉริยะ จุดแรกคือ พอยน์เตอร์ภายนอกที่บันทึกไว้ ซึ่งสอดคล้องกับ T* px ในภาพด้านบน การดำเนินการ ที่เกี่ยวข้องกับตัวชี้ที่เข้ามาจะดำเนินการแทนตัวชี้ที่เข้ามา ประการที่สองคือการโอเวอร์โหลดตัวดำเนินการ "*" การอ้างอิงและส่งคืนวัตถุที่ชี้ไปโดยตัวชี้ ประการที่สามคือการโอเวอร์โหลดตัวดำเนินการ "->" ถึง return ตัวชี้ที่สอดคล้องกับภาพด้านบนคือ px
ตัววนซ้ำยังเป็นพอยน์เตอร์อัจฉริยะประเภทหนึ่งอีกด้วย นอกจากนี้ยังมีสามองค์ประกอบของพอยน์เตอร์อัจฉริยะที่กล่าวถึงข้างต้น ซึ่งสอดคล้องกับแบบอักษรสีแดงและส่วนที่ทำเครื่องหมายสีเหลืองในรูปด้านล่าง:
อักขระโอเวอร์โหลด "*" และ "->" ของการโอเวอร์โหลดตัววนซ้ำจะได้รับการวิเคราะห์อย่างระมัดระวังด้านล่าง:
สร้างวัตถุตัววนซ้ำรายการ list::iterator ite รายการที่นี่ใช้เพื่อบันทึกวัตถุ Foo ซึ่งเป็นคลาสในคำจำกัดความเทมเพลตรายการ T, ตัวดำเนินการ*() ส่งคืน (*node).data object โหนดเป็นประเภท __link_type แต่ __link_type เป็นประเภท __list_node<T>* T นี่คือ Foo ดังนั้นโหนดจึงเป็นประเภท __list_node<Foo >* ดังนั้น ( *node).data ได้รับอ็อบเจ็กต์ประเภท Foo และในที่สุด &(operator*()) ก็ได้รับที่อยู่ของอ็อบเจ็กต์ Foo นั่นคือส่งคืน Foo* ตัวชี้ไปยังประเภท
ดังที่คุณเห็นจากรูปด้านบน แต่ละ functor เป็นคลาสที่โอเวอร์โหลดตัวดำเนินการ "()" จากนั้นจึงกลายเป็น "functor" จริงๆ แล้วมันคือคลาส แต่ดูเหมือนว่าจะมีคุณสมบัติของฟังก์ชัน ฟังก์ชันแต่ละตัวจะรวมคลาสแปลกๆ ไว้เบื้องหลัง ดังแสดงในรูปด้านล่าง โปรแกรมเมอร์ไม่จำเป็นต้องประกาศคลาสนี้ด้วยตนเอง functor ในไลบรารีมาตรฐานยังสืบทอดคลาสแปลก ๆ อีกด้วย เนื้อหาของคลาสนี้จะแสดงในรูปด้านล่าง มันแค่ประกาศบางสิ่ง และไม่มีตัวแปรหรือฟังก์ชันที่แท้จริงในนั้น เนื้อหาเฉพาะจะถูกกล่าวถึงใน STL
แตกต่างจากเทมเพลตคลาส เทมเพลตฟังก์ชันไม่จำเป็นต้องประกาศประเภทของพารามิเตอร์ขาเข้าอย่างชัดเจนเมื่อใช้งาน คอมไพลเลอร์จะอนุมานประเภทโดยอัตโนมัติ
เทมเพลตสมาชิกมักใช้ในการเขียนโปรแกรมทั่วไป เพื่อให้มีความสามารถในการขยายขนาดได้ดีขึ้น ให้ดูตัวอย่างข้างต้น T1 มักเป็นคลาสพื้นฐานของ U1 และ T2 มักเป็นคลาสพื้นฐานของ U2 ด้วยวิธีนี้ ตราบใดที่คลาสพาเรนต์หรือคลาสบรรพบุรุษของ U1 และ U2 ที่ส่งผ่านคือ T1 และ T2 การสืบทอดและความหลากหลายสามารถนำไปใช้อย่างชาญฉลาดผ่านวิธีนี้ แต่ไม่ใช่ในทางกลับกัน วิธีนี้ใช้บ่อยมากใน STL:
ตามชื่อที่แนะนำ การแบ่งส่วนของเทมเพลตหมายถึงการระบุประเภทข้อมูลเฉพาะในเทมเพลต ซึ่งแตกต่างจากลักษณะทั่วไป: แน่นอนว่า การแบ่งส่วนของเทมเพลตก็มีระดับเช่นกัน และสามารถระบุประเภทบางส่วนได้ ซึ่งเรียกว่าความเชี่ยวชาญบางส่วน:
จะมีการอธิบายเนื้อหามากเกินไปในหลักสูตร C++11 ดังนั้นฉันจะแนะนำที่นี่เพียงตอนนี้เท่านั้น
จะมีการอธิบายเนื้อหามากเกินไปในหลักสูตร C++11 ฉันจะแนะนำที่นี่เท่านั้น
การอ้างอิงสามารถเห็นเป็นนามแฝงสำหรับตัวแปรอ้างอิง
ดังแสดงในรูปด้านบน มีการกำหนดคลาสไว้สามคลาส A, B และ C โดย B สืบทอดจาก A และ C สืบทอดจาก B มีฟังก์ชันเสมือนสองฟังก์ชันใน A หนึ่งฟังก์ชันใน B และอีกหนึ่งฟังก์ชันใน C คอมไพเลอร์จัดสรรอ็อบเจ็กต์ a ของ A ในหน่วยความจำดังแสดงในรูปด้านบน มีตัวแปรสมาชิกเพียงสองตัวเท่านั้น m_data1 และ m_data2 ในเวลาเดียวกัน เนื่องจากคลาส A มีฟังก์ชันเสมือน คอมไพเลอร์จะจัดสรรพื้นที่ให้กับอ็อบเจ็กต์ a เพื่อบันทึกฟังก์ชันเสมือน ตารางนี้จะรักษาที่อยู่ฟังก์ชันเสมือน (ฟังก์ชันไดนามิก) ของคลาสนี้ การรวมสถานะ) เนื่องจากคลาส A มีฟังก์ชันเสมือนสองฟังก์ชัน จึงมีช่องว่างสองช่อง (ช่องว่างสีเหลืองและสีน้ำเงิน) ในตารางฟังก์ชันเสมือนของการชี้ไปที่ A::vfunc1() และ A::vfunc2() ตามลำดับ ในทำนองเดียวกัน b It เป็นอ็อบเจ็กต์ของคลาส B เนื่องจากคลาส B จะแทนที่ฟังก์ชัน vfunc1() ของคลาส A ดังนั้นตารางฟังก์ชันเสมือนของ B (ส่วนสีฟ้า) จะชี้ไปที่ B::vfunc1() และ B สืบทอด vfunc2() ของคลาส A ดังนั้นตารางฟังก์ชันเสมือนของ B (ส่วนสีน้ำเงิน) จะชี้ไปที่ A ของคลาสพาเรนต์ A: :vfunc2 () ฟังก์ชั่นในทำนองเดียวกัน c เป็นวัตถุของคลาส C ซึ่งแสดงโดย คลาส C เขียนฟังก์ชัน vfunc1() ของคลาสพาเรนต์ ดังนั้นตารางฟังก์ชันเสมือนของ C (ส่วนสีเหลือง) จะชี้ไปที่ C::vfunc1() ในเวลาเดียวกัน C จะสืบทอด vfunc2() ของซูเปอร์คลาส A ดังนั้นฟังก์ชันเสมือนของ B ตาราง (ส่วนสีน้ำเงิน) จะชี้ไปที่ฟังก์ชัน A::vfunc2() ในเวลาเดียวกัน รูปด้านบนยังใช้โค้ดภาษา C เพื่อแสดงให้เห็นว่าชั้นล่างสุดของคอมไพเลอร์เรียกฟังก์ชันเหล่านี้อย่างไร นี่คือแก่นแท้ของความหลากหลายทางมรดกเชิงวัตถุ
ตัวชี้นี้ถือได้ว่าเป็นตัวชี้ที่ชี้ไปยังที่อยู่หน่วยความจำของวัตถุปัจจุบัน ดังแสดงในรูปด้านบน เนื่องจากมีฟังก์ชันเสมือนในคลาสพื้นฐานและคลาสย่อย ดังนั้น Serialize() จะถูกผูกไว้แบบไดนามิก ซึ่งเทียบเท่ากับ (*(this- >vptr)[n])(this) สามารถเข้าใจได้โดยการรวมตัวชี้เสมือนและตารางฟังก์ชันเสมือนไว้ในส่วนก่อนหน้า ส่วนเหตุใดจึงถูกต้องในการเขียนเช่นนี้ในตอนท้าย บทสรุปต่อไปนี้จะอธิบาย