การใช้คลาสสตริง

Sep 01 2020

ฉันกำลังทำโปรเจ็กต์ (เป็นภาษา) และสำหรับโปรเจ็กต์นั้นฉันตัดสินใจใช้สไตล์ 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';
}

นี่คือประเด็นสำคัญหลักของฉัน:

  1. ฉันต้องการตัวสร้างการย้ายและตัวดำเนินการกำหนดย้ายหรือไม่? ฉันรู้ว่าสิ่งเหล่านี้ไม่จำเป็น แต่จะสร้างความแตกต่างอย่างมากในกรณีนี้หรือไม่?

  2. วิธีส่วนตัวมีประสิทธิภาพหรือไม่? สามารถปรับปรุงได้หรือไม่?

  3. วิธีการstr_alc()ดีหรือไม่? เช่นเดียวกับวิธีปฏิบัติที่ดีในการสรุปnewคำสั่งลองจับ? และฉันควรเติมสตริงด้วย\0s ตามค่าเริ่มต้นหรือไม่? หรือว่าก่อให้เกิดอันตรายมากกว่าดี?

คำถามเล็กน้อยที่ฉันมีคือถ้าชื่อพารามิเตอร์bufferเป็นตัวเลือกที่ถูกต้อง? ฉันไม่แน่ใจจริงๆว่าจะเรียกพารามิเตอร์ว่าอะไร ...

ข้อเสนอแนะอื่น ๆ ก็ชื่นชมอย่างมาก!

ขอบคุณ!

คำตอบ

5 MartinYork Sep 01 2020 at 02:56

เหตุใดคุณจึงแปลงข้อยกเว้นที่มีความหมายเป็นสตริงที่ไม่มีความหมาย

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ใช้การเพิ่มประสิทธิภาพสตริงสั้น ๆ ที่ดีที่ด้านบนของเวอร์ชันการจัดสรรหน่วยความจำแบบไดนามิกพื้นฐานที่คุณได้นำไปใช้

4 ALX23z Sep 01 2020 at 01:57

ฉันอาจจะขาดอะไรไป แต่ทำไมไม่std::string? ฉันไม่เข้าใจว่าทำไมคุณไม่สามารถใช้ STL หรือพูดไลบรารีโอเพนซอร์สอื่น ๆ ได้ คุณให้คำอธิบายบางอย่าง แต่ฉันไม่เข้าใจ การใช้ STL และไลบรารีโอเพนซอร์สจะช่วยให้คุณประหยัดเวลาในการพัฒนาและดีบักได้มาก

สำหรับการใช้งานสตริงของคุณ - ประเด็นสำคัญ:

  1. การเพิ่มอักขระตัวเดียวทำให้เกิดการจัดสรรใหม่ซึ่งแย่มากในแง่ของหน่วยความจำและประสิทธิภาพ โดยปกติหนึ่งจะมีขนาดสำรองและเพิ่มขึ้นแบบทวีคูณ (x2 หรือ x1.5 ในแต่ละครั้ง) ดังนั้นคุณไม่จำเป็นต้องใช้การจัดสรรใหม่ทุกครั้งที่มีคนเพิ่มอักขระเดี่ยวหรือมากกว่าในบางครั้ง

  2. ไม่มีการเพิ่มประสิทธิภาพสตริงสั้น ๆ เมื่อสตริงสั้นพอให้พูดน้อยกว่า 32 อักขระคุณไม่ควรทำการจัดสรรแบบไดนามิกและเก็บข้อมูลไว้ในเครื่องแทน เพื่อจุดประสงค์นี้คุณอาจต้องใช้บัฟเฟอร์เพิ่มเติมในคลาสสตริง สิ่งนี้สำคัญเนื่องจากสตริงส่วนใหญ่ค่อนข้างสั้น

นอกจากนี้ปัญหาเหล่านี้คุณควรสนับสนุนคุณสมบัติเดียวกันที่std::stringรองรับไม่มากก็น้อย ลองดูที่ของ API บนcppreference

1 n.'pronouns'm. Sep 01 2020 at 14:34

การมีสตริงของคุณทั้งที่สิ้นสุดด้วยค่าว่างและมีขนาดที่ชัดเจนเป็นความคิดที่ไม่ดี C ++ std :: string ไม่ได้ตั้งใจโดยสิ้นเชิงไม่ได้ทำเช่นนั้น

คุณสามารถจัดสรรอักขระพิเศษและตั้งค่าเป็นศูนย์เพื่อความสะดวกในการแปลงเป็นสตริงสไตล์ C ในขณะที่แปลงจากหรือเปรียบเทียบกับสตริง C คุณสามารถ (และควร) ทดสอบสำหรับเทอร์มิเนเตอร์ว่างในสตริง C อย่ามองหาตัวยุติโมฆะในที่อื่น ๆ ของโค้ดของคุณ ใช้size.

คุณลืมใช้ความหมายการย้ายด้วย