我要好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函数
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实现
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));