我要好offer之 字符串相关大总结

1. str*系列手写代码

a. 一定要注意末尾'\0'的处理,切记切记

b. 一定要对输入做有效性判断,多用断言就是了

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*系列手写代码

一定要对输入做有效性判断,多用断言就是了

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函数的实现

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[]

e. 重载赋值运算符使用copy and swap 手法

 

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));

 

posted @ 2014-08-12 19:32  skyline09  阅读(229)  评论(0编辑  收藏  举报