การใช้คลาสสตริง
ฉันกำลังทำโปรเจ็กต์ (เป็นภาษา) และสำหรับโปรเจ็กต์นั้นฉันตัดสินใจใช้สไตล์ C ++ ระดับต่ำนั่นหมายถึงการสร้างชนิดข้อมูลของฉันเอง ฉันเพิ่งทำการติดตั้งคลาสสตริงของตัวเองเสร็จแล้ว นี่เป็นครั้งแรกที่ฉันสร้างคลาสสตริงมาก่อนดังนั้นฉันอาจจะทำหลายอย่างผิดพลาด เท่าที่ฉันรู้ฉันทดสอบแล้วและได้ผลและทำในสิ่งที่ตั้งใจไว้ แต่ฉันอาจมองข้ามบางสิ่งหรือบางอย่างอาจไม่ใช่แนวทางปฏิบัติที่ดีที่สุด
เป้าหมายของฉันคือการสร้างคลาสสตริง "C ++ ระดับต่ำ" ซึ่งหมายความว่าฉันจะสร้างทุกอย่างด้วยตัวเองโดยไม่ต้องใช้ส่วนหัวใด ๆ
ฉันมีทุกอย่างใน.hไฟล์เดียวและฉันรู้ว่านั่นไม่ใช่วิธีที่ดีที่สุดที่จะทำ แต่ฉันไม่ใช่แฟนตัวยงของการแยกรหัสของฉันเมื่อมันเป็นเพียงคลาสเล็ก ๆ
นี่คือภาพรวมของวิธีการและสิ่งที่พวกเขาทำและเหตุผลที่ฉันเลือก (โปรดทราบว่านี่ไม่ใช่.hไฟล์จริงฉันแค่แสดงสิ่งนี้เพื่อระบุบริบทและภาพรวมของสิ่งที่ฉันกำลังทำ):
class string
{
public:
string();
string(const char* buffer);
string(const string& buffer);
~string();
public:
string& operator=(const char* buffer);
string& operator=(const string& buffer);
void operator+=(char buffer);
void operator+=(const char* buffer);
void operator+=(const string& buffer);
bool operator==(const char* buffer) const;
bool operator==(const string& buffer) const;
bool operator!=(const char* buffer) const;
bool operator!=(const string& buffer) const;
char operator[](int index) const;
char& operator[](int index);
public:
int length() const;
// returns the actual string
const char* get() const;
private:
int str_len(const char* buffer) const;
// given a block of memory 'dest', fill that with characters from 'buffer'
void str_cpy(char* dest, int dest_size, const char* buffer);
void str_cpy(char* dest, int dest_size, const string& buffer);
// allocate a given size of memory
char* str_alc(int size);
private:
int size;
char* str;
};
อย่างที่คุณเห็นมันไม่ได้มีอะไรพิเศษเลยมีแค่ฟังก์ชันพื้นฐานบางอย่างที่น่าจะเพียงพอสำหรับโปรเจ็กต์ของฉัน ความคิดเห็นเล็กน้อยเกี่ยวกับรหัส:
ฉันเลือกที่จะเพิ่มget()เมธอดแทนสิ่งที่ต้องการoperator const char*()เนื่องจากฉันรู้สึกว่าโอเปอเรเตอร์โอเวอร์โหลดจะเพียงพอและฉันต้องการให้การเข้าถึงสตริงจริงชัดเจนยิ่งขึ้น
นอกจากนี้ยังทราบเกี่ยวกับวิธีการที่เอกชนผู้มีพื้นคล้ายกับวิธีการที่สามารถพบได้ใน<string.h>หัวเหมือนและstrncpy()str_len()
นี่คือstring.hไฟล์จริง:
#pragma once
namespace night { // 'night' is the project I'm working on
class string
{
public:
string()
{
size = 0;
str = str_alc(1);
}
string(const char* buffer)
{
size = str_len(buffer);
str = str_alc(size + 1);
str_cpy(str, size + 1, buffer);
}
string(const string& buffer)
{
size = buffer.size;
str = str_alc(size + 1);
str_cpy(str, size + 1, buffer);
}
~string()
{
delete[] str;
}
public:
string& operator=(const char* buffer)
{
delete[] str;
size = str_len(buffer);
str = str_alc(size + 1);
str_cpy(str, size + 1, buffer);
return *this;
}
string& operator=(const string& buffer)
{
delete[] str;
size = buffer.size;
str = str_alc(size + 1);
str_cpy(str, size + 1, buffer);
return *this;
}
void operator+=(char buffer)
{
char* temp = str_alc(size + 2);
str_cpy(temp, size + 2, str);
temp[size] = buffer;
temp[size + 1] = '\0';
delete[] str;
size += 1;
str = temp;
}
void operator+=(const char* buffer)
{
size += str_len(buffer);
char* temp = str_alc(size + 1);
str_cpy(temp, size + 1, str);
str_cpy(temp, size + 1, buffer);
delete[] str;
str = temp;
}
void operator+=(const string& buffer)
{
size += buffer.size;
char* temp = str_alc(size + 1);
str_cpy(temp, size + 1, str);
str_cpy(temp, size + 1, buffer);
delete[] str;
str = temp;
}
bool operator==(const char* buffer) const
{
if (size != str_len(buffer))
return false;
for (int a = 0; a < size; ++a)
{
if (str[a] != buffer[a])
return false;
}
return true;
}
bool operator==(const string& buffer) const
{
return operator==(buffer.str);
}
bool operator!=(const char* buffer) const
{
return !operator==(buffer);
}
bool operator!=(const string& buffer) const
{
return !operator==(buffer.str);
}
char operator[](int index) const
{
if (index < 0 || index >= size)
throw "[error] index is out of range";
return str[index];
}
char& operator[](int index)
{
if (index < 0 || index >= size)
throw "[error] index is out of range";
return str[index];
}
public:
int length() const
{
return size;
}
const char* get() const
{
return str;
}
private:
int str_len(const char* buffer) const
{
int length = 0;
for (int a = 0; buffer[a] != '\0'; ++a)
length += 1;
return length;
}
void str_cpy(char* dest, int dest_size, const char* buffer)
{
int start = 0;
while (dest[start] != '\0')
start += 1;
if (dest_size - start < str_len(buffer))
throw "[fatal error] function 'void str_cpy(char* dest, const char* buffer)' does not have enough space";
for (int a = 0; a < str_len(buffer); ++a)
dest[start + a] = buffer[a];
dest[start + str_len(buffer)] = '\0';
}
void str_cpy(char* dest, int dest_size, const string& buffer)
{
int start = 0;
while (dest[start] != '\0')
start += 1;
if (dest_size - start < buffer.size)
throw "[fatal error] function 'void str_cpy(char* dest, const string& buffer)' does not have enough space";
for (int a = 0; a < buffer.size; ++a)
dest[start + a] = buffer.str[a];
dest[start + buffer.size] = '\0';
}
char* str_alc(int size)
{
char* buffer;
try {
// set the new string to contain null-terminators by default
buffer = new char[size]{ '\0' };
}
catch (...) {
throw "[fatal error] function 'char* str_alc(int size)' cannot allocate enough memory";
}
return buffer;
}
private:
int size;
char* str;
};
} // namespace night
และเพื่อเป็นตัวอย่างนี่คือวิธีที่คุณจะใช้:
int main()
{
night::string test = "class";
test += ' ';
test += "string";
std::cout << test.get() << '\n';
night::string test1 = "string class";
test = test1;
test[0] = 'S';
test[7] = 'C';
std::cout << test.get() << '\n';
night::string test2 = "String Class";
std::cout << (test == test2) << '\n';
std::cout << (test != test2) << '\n';
}
นี่คือประเด็นสำคัญหลักของฉัน:
ฉันต้องการตัวสร้างการย้ายและตัวดำเนินการกำหนดย้ายหรือไม่? ฉันรู้ว่าสิ่งเหล่านี้ไม่จำเป็น แต่จะสร้างความแตกต่างอย่างมากในกรณีนี้หรือไม่?
วิธีส่วนตัวมีประสิทธิภาพหรือไม่? สามารถปรับปรุงได้หรือไม่?
วิธีการ
str_alc()ดีหรือไม่? เช่นเดียวกับวิธีปฏิบัติที่ดีในการสรุปnewคำสั่งลองจับ? และฉันควรเติมสตริงด้วย\0s ตามค่าเริ่มต้นหรือไม่? หรือว่าก่อให้เกิดอันตรายมากกว่าดี?
คำถามเล็กน้อยที่ฉันมีคือถ้าชื่อพารามิเตอร์bufferเป็นตัวเลือกที่ถูกต้อง? ฉันไม่แน่ใจจริงๆว่าจะเรียกพารามิเตอร์ว่าอะไร ...
ข้อเสนอแนะอื่น ๆ ก็ชื่นชมอย่างมาก!
ขอบคุณ!
คำตอบ
เหตุใดคุณจึงแปลงข้อยกเว้นที่มีความหมายเป็นสตริงที่ไม่มีความหมาย
char* str_alc(int size)
{
char* buffer;
try {
// set the new string to contain null-terminators by default
buffer = new char[size]{ '\0' };
}
catch (...) {
throw "[fatal error] function 'char* str_alc(int size)' cannot allocate enough memory";
}
return buffer;
}
วิธีการรายงานข้อผิดพลาดท้ายที่สุดขึ้นอยู่กับตำแหน่งที่ถูกจับ คุณไม่ควรโยนสตริงซ้ำ ตรวจจับข้อยกเว้น ณ จุดที่คุณรายงานข้อผิดพลาดและแปลงเป็นข้อความแสดงข้อผิดพลาดที่เหมาะสม ณ จุดนั้น หรือโยนประเภทข้อยกเว้นที่มีความหมายมากกว่า (ไม่ใช่สตริง)
นอกจากนี้หากคุณกำลังจะทำการตรวจสอบนี้ให้ใช้เวอร์ชันใหม่ที่ไม่ใช่การขว้างปาจากนั้นตรวจสอบว่าบัฟเฟอร์ไม่เป็นโมฆะและโยนข้อยกเว้นใหม่ของคุณ
อย่าสร้างฟังก์ชันการออกจากฟังก์ชันใหม่:
int str_len(const char* buffer) const
มีฟังก์ชั่น C สำหรับสิ่งนี้อยู่แล้วและฉันรับประกันว่าจะไม่ช้ากว่าเวอร์ชันของคุณและมีแนวโน้มที่จะเรียงลำดับความสำคัญได้เร็วกว่า
void str_cpy(char* dest, int dest_size, const char* buffer)
อีกครั้งมีฟังก์ชันคัดลอก C-String อยู่แล้ว หากคุณกำลังจะประดิษฐ์ใหม่ให้ใช้อัลกอริทึม C ++ เพื่อคัดลอกไบต์รอบ ๆ แทนที่จะเขียนลูปด้วยตนเอง
หากคุณกำลังเปรียบเทียบออบเจ็กต์สตริงสองรายการ คุณมุ่งมั่นที่จะเปรียบเทียบวัตถุสตริงกับ C-String เป็นกรณีทั่วไป
bool operator==(const char* buffer) const
{
if (size != str_len(buffer))
return false;
for (int a = 0; a < size; ++a)
{
if (str[a] != buffer[a])
return false;
}
return true;
}
bool operator==(const string& buffer) const
{
return operator==(buffer.str);
}
bool operator!=(const char* buffer) const
{
return !operator==(buffer);
}
bool operator!=(const string& buffer) const
{
return !operator==(buffer.str);
}
เป็นผลให้คุณคำนวณความยาวสตริงสำหรับวัตถุที่คุณทราบความยาวสตริงแล้ว!
คุณได้ดำเนินการตรวจสอบแล้วoperator[]:
char operator[](int index) const
{
if (index < 0 || index >= size)
throw "[error] index is out of range";
return str[index];
}
char& operator[](int index)
{
if (index < 0 || index >= size)
throw "[error] index is out of range";
return str[index];
}
ใน C ++ operator[]มักจะไม่เลือกและใช้ในสถานการณ์ที่คุณได้กำหนดไว้แล้วว่าการเข้าถึงนั้นอยู่ในขอบเขตดังนั้นการตรวจสอบจึงซ้ำซ้อน
ใน C ++ โดยปกติเราจะมีเวอร์ชันที่ไม่ได้ตรวจสอบดังนั้นคุณจึงไม่ต้องทำการตรวจสอบด้วยตนเอง ใน C ++ เราเรียกเวอร์ชันat()นี้
for(int loop = 0; loop < str. length(); ++loop) {
std::cout << str[loop]; // Why do I need the index
// checked here (every loop)
// I have already established that
// loop is within bounds by checking
// it against the length of the string.
}
คุณยังไม่ได้ใช้ความหมายการย้าย
คุณยังไม่ได้ใช้ขนาดสำรอง มีความแตกต่างระหว่างความยาวปัจจุบันและความยาวสูงสุดก่อนที่จะต้องปรับขนาด
ผู้ดำเนินการมอบหมายของคุณไม่มีข้อยกเว้นที่ปลอดภัย
string& operator=(const char* buffer)
{
delete[] str; // you have modified the object here
size = str_len(buffer);
str = str_alc(size + 1); // This can throw. If it does
// your object is in a bad state
// the member str is pointing at
// memory that has been released
// back to the runtime. Any
// use of this will be broken.
//
// You have to hope that that exception
// is not caught and the application
// exits.
str_cpy(str, size + 1, buffer);
return *this;
}
วิธีที่ถูกต้องคือการใช้สำนวนการคัดลอกและสลับ
string& operator=(const char* buffer)
{
string tmp(buffer); // safely create a copy.
// Now that you have done the copy swap this with tmp
std::swap(size, tmp.size)
std::swap(buffer, tmp.buffer);
return *this;
}
// destructor of tmp is called here.
// it will release the buffer that you just placed into the object
เวอร์ชันไลบรารีมาตรฐานนี้std::stringใช้การเพิ่มประสิทธิภาพสตริงสั้น ๆ ที่ดีที่ด้านบนของเวอร์ชันการจัดสรรหน่วยความจำแบบไดนามิกพื้นฐานที่คุณได้นำไปใช้
ฉันอาจจะขาดอะไรไป แต่ทำไมไม่std::string? ฉันไม่เข้าใจว่าทำไมคุณไม่สามารถใช้ STL หรือพูดไลบรารีโอเพนซอร์สอื่น ๆ ได้ คุณให้คำอธิบายบางอย่าง แต่ฉันไม่เข้าใจ การใช้ STL และไลบรารีโอเพนซอร์สจะช่วยให้คุณประหยัดเวลาในการพัฒนาและดีบักได้มาก
สำหรับการใช้งานสตริงของคุณ - ประเด็นสำคัญ:
การเพิ่มอักขระตัวเดียวทำให้เกิดการจัดสรรใหม่ซึ่งแย่มากในแง่ของหน่วยความจำและประสิทธิภาพ โดยปกติหนึ่งจะมีขนาดสำรองและเพิ่มขึ้นแบบทวีคูณ (x2 หรือ x1.5 ในแต่ละครั้ง) ดังนั้นคุณไม่จำเป็นต้องใช้การจัดสรรใหม่ทุกครั้งที่มีคนเพิ่มอักขระเดี่ยวหรือมากกว่าในบางครั้ง
ไม่มีการเพิ่มประสิทธิภาพสตริงสั้น ๆ เมื่อสตริงสั้นพอให้พูดน้อยกว่า 32 อักขระคุณไม่ควรทำการจัดสรรแบบไดนามิกและเก็บข้อมูลไว้ในเครื่องแทน เพื่อจุดประสงค์นี้คุณอาจต้องใช้บัฟเฟอร์เพิ่มเติมในคลาสสตริง สิ่งนี้สำคัญเนื่องจากสตริงส่วนใหญ่ค่อนข้างสั้น
นอกจากนี้ปัญหาเหล่านี้คุณควรสนับสนุนคุณสมบัติเดียวกันที่std::stringรองรับไม่มากก็น้อย ลองดูที่ของ API บนcppreference
การมีสตริงของคุณทั้งที่สิ้นสุดด้วยค่าว่างและมีขนาดที่ชัดเจนเป็นความคิดที่ไม่ดี C ++ std :: string ไม่ได้ตั้งใจโดยสิ้นเชิงไม่ได้ทำเช่นนั้น
คุณสามารถจัดสรรอักขระพิเศษและตั้งค่าเป็นศูนย์เพื่อความสะดวกในการแปลงเป็นสตริงสไตล์ C ในขณะที่แปลงจากหรือเปรียบเทียบกับสตริง C คุณสามารถ (และควร) ทดสอบสำหรับเทอร์มิเนเตอร์ว่างในสตริง C อย่ามองหาตัวยุติโมฆะในที่อื่น ๆ ของโค้ดของคุณ ใช้size.
คุณลืมใช้ความหมายการย้ายด้วย