我要好offer之 字符串相關大總結

1. str*系列手寫代碼

a. 必定要注意末尾'\0'的處理,切記切記html

b. 必定要對輸入作有效性判斷,多用斷言就是了c++

int Strlen(const char* str) {
    assert(str != NULL);
    const char* tmp = str;
    while (*tmp != '\0') {
        ++tmp;
    }
    return tmp - str;
}

char* Strcpy(char* dst, const char* src) {
    assert(dst != NULL && src != NULL);
    char* tmp = dst;
    while (*src != '\0') {
        *tmp++ = *src++;
    }
    *tmp = '\0';
    return dst;
}

char* Strncpy(char* dst, const char* src, int len) {
    assert(dst != NULL && src != NULL && len >= 0);
    char* tmp = dst;
    for (; len > 0 && *src != '\0'; --len) {
        *tmp++ = *src++;
    }
    for (; len > 0; --len) {
        *tmp ++ = '\0';
    }
    return dst;
}

char* Strcat(char* dst, const char* src) {
    assert(dst != NULL && src != NULL);
    char* tmp = dst;
    while (*tmp != '\0') {
        ++tmp;
    }
    while (*src != '\0') {
        *tmp++ = *src++;
    }
    *tmp = '\0';
    return dst;
}

char* Strncat(char* dst, const char* src, int len) {
    assert(dst != NULL && src != NULL && n >= 0);
    char* tmp = dst;
    while (*tmp != '\0') {
        ++tmp;
    }
    for (; len > 0 && *src != '\0'; --len) {
        *tmp++ = *src++;
    }
    *tmp = '\0';
    return dst;
}

int Strcmp(const char* str1, const char* str2) {
    assert(str1 != NULL && str2 != NULL);
    for (; *str1 == *str2; ++str1, ++str2) {
        if (*str1 == '\0') {
            return 0;
        }
    }
    if (*(unsigned char*)str1 < *(unsigned char*)str2) {
        return -1;
    } else {
        return 1;
    }
}

int Strncmp(const char* str1, const char* str2, int len) {
    assert(str1 != NULL && str2 != NULL && len >= 0);
    for (; len > 0; ++str1, ++str2) {
        if (*str1 != *str2) {
            return ((*(unsigned char*)str1) < (*(unsigned char*)str2) ? -1 : 1);
        } else if (*str1 == '\0') {
            return 0;
        }
    }
    return 0;
}

char* Strchr(const char* str, int c) {
    assert(str != NULL);
    const char* tmp = str;
    const char ch = (const char)c;
    for (; *tmp != ch; ++tmp) {
        if (*tmp == '\0') {
            return NULL;
        }
    }
    return (char*)tmp;
}

char* Strstr(const char* str1, const char* str2) {
    assert(str1 != NULL && str2 != NULL);
    if (*str2 == '\0') return str1;
    const char* tmp1 = str1;
    const char* tmp2 = str2;
    int len1 = Strlen(str1);
    int len2 = Strlen(str2);
    int i = 0;
    for (tmp1 = str1; i <= len1 - len2 && *tmp1 != '\0'; ++tmp1, ++i) {
        if (*tmp1 != *tmp2)
            continue;
        const char* cur1 = tmp1;
        const char* cur2 = tmp2;
        
        while (*cur1 == *cur2) {
            ++cur1;
            ++cur2;
            if (*cur2 == '\0') {
                return (char*)tmp1;
            }
        }
    }
    return NULL;
}

 

2. mem*系列手寫代碼

必定要對輸入作有效性判斷,多用斷言就是了git

void* Memchr(const void* src, int c, int len) {
    assert(src != NULL && len >= 0);
    const unsigned char ch = c;
    const unsigned char* tmp = (const unsigned char*)src;
    for (; len > 0; --len, ++tmp) {
        if (*tmp == ch) {
            return (void*)tmp;
        }
    }
    return NULL;
}

int Memcmp(const void* s1, const void* s2, int len) {
    assert(s1 != NULL && s2 != NULL);
    const unsigned char* tmp1 = (const unsigned char*)s1;
    const unsigned char* tmp2 = (const unsigned char*)s2;
    for (; len > 0; --len, tmp1++, tmp2++) {
        if (*tmp1 != *tmp2) {
            return ((*tmp1 < *tmp2) ? -1 : 1);
        }
    }
    return 0;
}

void* Memcpy(void* dst, const void* src, int len) {
    assert(dst != NULL && src != NULL && len >= 0);
    char* dstTmp = (char*)dst;
    const char* srcTmp = (const char*)src;
    for (; len > 0; --len, ++dstTmp, ++srcTmp) {
        *dstTmp = *srcTmp;
    }
    return dst;
}

void* Memmove(void* dst, const void* src, int len) {
    assert(dst != NULL && src != NULL && len >= 0);
    char* dstTmp = (char*)dst;
    const char* srcTmp = (const char*)src;
    if (dstTmp > srcTmp && dstTmp < srcTmp + len) {
        for (srcTmp += n, dstTmp += n; len > 0 ; --len, --srcTmp, --dstTmp) {
            *dstTmp = *srcTmp;
        }
    } else {
        for (; len > 0; --len, ++srcTmp, ++dstTmp) {
            *dstTmp = *srcTmp;
        }
    }
    return dst;
}

 

3. atoi函數

atoi函數的實現shell

class Solution {
public:
    int atoi(const char *str) {
        assert(str != NULL);
        const char* curr = str;
        const int maxRange = 10; int tmp = 0;
        int num = 0;
        while(isspace(*curr)) {
            ++curr;
        }
        const char* start = nullptr;
        char sign;
        if (*curr == '-' || *curr == '+') {
            sign = *curr;
            ++curr;
            start = curr;
        }  else {
            start = curr;
        }
        
        while (isdigit(*curr)) {
            tmp = num;
            num = num * 10 + (*curr - '0');
            ++curr;
        }
        int len = 0;
        if (!isdigit(*curr)) {
            len = curr - start;
        }
        --curr;
        if (len > maxRange || num < num - *curr) {
            if (sign == '-') {
                return INT_MIN;
            } else {
                return INT_MAX;
            }
        }
        if (sign == '-')  num = -num;
        return num;
    }
};

 

4. std::string實現

c++ std::string的簡易實現數組

class Mystring {
    public:
        Mystring() : data_(new char[1]) {
            *data = '\0';
        }

        explicit Mystring(const char* str) : data_(new char[strlen(str) + 1]) {
            strcpy(data_, str);
        }

        explicit Mystring(const Mystring& str) : data_(new char[str.size() + 1]) {
            strcpy(data_, str.c_str());
        }

        ~Mystring() {
            delete[] data_;
        }
        
        // 重載賦值,採用copy and swap手法,舊式寫法
        Mystring& operator=(const Mystring& str) {
            Mystring tmp(str);
            swap(tmp);
            return *this;
        }

        // 重載賦值,採用copy and swap手法,新式寫法
        Mystring& operator=(Mystring& str) {
            swap(str);
            return *this;
        }

        int size() const {
            return (int)strlen(data_);
        }
        const char* c_str() const {
            return data_;
        }

        void swap(Mystring& str) {
            std::swap(data_, str.data_);
        }
    private:
        char* data_;
};

a. Mystring類可以相似內置類型int同樣,能夠定義變量,能夠複製,賦值安全

b. Mystring能夠用做函數參數以及返回值類型函數

c. Mystring能夠用做標準庫容器的元素類型,即 vector/list/deque 的 value_type學習

d. 利用RAII正確管理資源,只在構造函數裏調用 new char[],只在析構函數裏調用 delete[]this

e. 重載賦值運算符使用copy and swap 手法spa

 

5. Str進行大數計算

博文:大數相乘

class Solution {
public:
    string multiply(string num1, string num2) {
        if (num1.size() == 0 || num2.size() == 0) return "";
        reverse(num1.begin(), num1.end());
        reverse(num2.begin(), num2.end());
        
        vector<int> result(num1.size() + num2.size(), 0);
        int count = 0;
        for (int i = 0; i < num1.size(); ++i) {
            for (int j = 0; j < num2.size(); ++j) {
                result.at(i+j) += (num1.at(i) - '0') * (num2.at(j) - '0');
            }
        }
        for (int i = 0; i < result.size(); ++i) {
            int tmp = result.at(i) + count;
            result.at(i) = tmp % 10;
            count = tmp / 10;
        }
        
        int zeroPos = 0;
        for (zeroPos = result.size() - 1; zeroPos >= 0; --zeroPos) {
            if (result.at(zeroPos) != 0) break;
        }
        result.erase(result.begin() + zeroPos + 1, result.end());
        reverse(result.begin(), result.end());
        
        string res(result.size(), '0');
        for (int i = 0; i < result.size(); ++i) {
            res.at(i) += result.at(i);
        }
        
        
        if (res == "") {
            return "0";
        } else {
            return res;
        }
    }
};

 

6. 爲何要禁止 char* p = "hello"這種寫法?

學習C語言的同窗確定見過 char* p = "hello"這種寫法的,如今我想說的是:千萬不要這樣寫

int main() {
    char* p1 = "hello";
    char* p2 = "hello";
    char p3[] = "hello";
    char p4[] = "hello";
    fprintf(stdout, "%p:%p\n", p1, p2);
    fprintf(stdout, "%p:%p\n", p3, p4);
    return 0;
}

程序結果顯示:p1等於p2,p3不等於p4

p1等於p2:"hello"爲字符串常量,位於全局的const區域段,第一,它是常量const,不能被修改  第二,它是全局的,即全部指向"hello"的指針的地址值全都是同樣的

p3不等於p4:p3和p4是字符數組,位於棧上,而且字符數組裏的字符是能夠被修改的

 

小結一下:

    char* p1 = "hello";    
    char p3[] = "hello";

p1:所指向內容不可修改(全局const),p1指針能夠修改(能夠更改指向)

p3:所指元素能夠修改(普通數組),p3不可修改(數組名做爲指針時表示數組的首地址,確定不能修改)

 

回到  char* p = "hello" 

前面咱們解釋了,p所指向的內容不可修改,即 p是一個指向const的指針

const char* p1 = "hello";

爲何要加上const呢?

由於 char* p = "hello" 把 實際的 const char* 隱含轉換爲 char*,萬惡的轉型啊,且看一段代碼:

    char* p1 = "this is wrong";
    char* p2 = "hello world";
    strcpy(p1, p2);

編譯經過了,運行呢? core dump,哈哈

前面說過了,p1實際上 const char*, 你如今想經過p1來修改const,必須來一個core dump

可是,若是咱們這樣寫呢?

    const char* p1 = "this is wrong";
    const char* p2 = "hello world";
    strcpy(p1, p2);

編譯錯誤!!!  (注:我使用的是 g++ -Wall 編譯)

 

小結一下:

char* p1 = "this is wrong"; // 將字符串常量的const特性隱式轉型了,經過p1修改字符串時將產生 運行時錯誤
const char* p1 = "this is right"; //加上const明確表示字符串的const特性,經過p1修改字符串時將產生 編譯時錯誤

 

既然上面用了 strcpy函數作例子,那就再說說strcpy的問題

假如對上述所提的問題都理解了,那就是如下的代碼:

int main() {
    char dst[] = "this is right";
    const char* src = "hello world";
    strcpy(dst, src);
    fprintf(stdout, "%s", dst);
    return 0;
}

 

運行都挺好的,可是,我想說的是:不要使用strcpy這類函數

咱們知道C語言標準庫有: strcpy、strcat、strcmp

C標準庫也有以下函數:strncpy、strncat、strncmp

以 strcpy strncpy爲例

strcpy只有遇到src的'\0'才結束複製,根本無論dst的空間是否是足以容納src,很是容易形成緩衝區溢出,各類××攻擊紛至沓來

因此纔有了 strcpy對應的「安全」版本--strncpy,strncpy本來想解決strcpy的不安全性,可是它的語義真是讓人蛋疼

strncpy僅僅複製 src的前n個字節,若是src的前n個字節不包括結束符'\0',問題就出來了,根本不復制src的結束符.....真讓人無語

 

使用strncpy通常是以下方式:

strncpy(dst, src, strlen(dst));
相關文章
相關標籤/搜索