互联网公司常见面试题之string实现
要求实现一个自定义的string类,比如命名为MyString,实现构造/拷贝构造/连接/比较等基本函数。
这道题很简单,考察的关键点是返回值和函数的参数,这个非常关键。仅仅简单的以为把函数里面的逻辑写得差不多就ok那是远远不够的。另外一个关键点是内存的申请和释放。
在此我忽略头部的include,挨个分析类结构及几个关键函数。(代码是微软风格,成员m_开头)
1. 基本的类定义:
class MyString {
private:
char *m_data;
//uint32_t m_size; /*如果定义的类要支持二进制数据,则需要定义这个成员*/
public:
MyString();
MyString(const char* ptr);
MyString(const MyString& rhs);
~MyString();
MyString& operator=(const MyString& rhs);
MyString operator+(const MyString& rhs);
char operator[](const unsigned int index);
bool operator==(const MyString& rhs);
friend ostream& operator<<(ostream& output, const MyString &rhs);
};
类里定义里m_data变长数组,用来存储数据。demo是仅仅考虑字符串的string类。如果要支持binary数据,需要m_size,并且在构造/拷贝构造等地方需要稍微调整一下,实现很简单,在此提醒一下,不给最终代码。)
2. 构造函数
2.1 默认的构造函数,用来构造一个空的MyString
//默认的构造函数
MyString::MyString() {
m_data = new char[1];
*m_data = '\0';
}
2.2 常用的构造函数
//使用const char* 来初始化
MyString::MyString(const char* ptr) {
if (NULL == ptr) {
m_data = new char[1];
*m_data = '\0';
} else {
int len = strlen(ptr);
m_data = new char[len + 1];
//strcpy(m_data, ptr); /*请废弃strcpy*/
snprintf(m_data, len, "%s", ptr);
}
}
2.3 拷贝构造函数
//拷贝构造函数
MyString::MyString(const MyString& rhs) {
int len = strlen(rhs.m_data);
m_data = new char[len + 1];
//strcpy(m_data, rhs.m_data); /*请废弃strcpy*/
snprintf(m_data, len, "%s", ptr);
}
3. 析构函数
//析构函数
MyString::~MyString() {
if(m_data){
delete[] m_data;
m_data = NULL;
}
}
4. 判断相等(判断数据一样)
bool MyString::operator ==(const MyString& rhs) {
return 0==strcmp(m_data, rhs.m_data);
}
5. 赋值操作符
//赋值操作符
MyString& MyString::operator =(const MyString& rhs) {
if (this != &rhs) {
delete[] m_data;
m_data = new char[strlen(rhs.m_data) + 1];
//strcpy(m_data, rhs.m_data); /*请废弃strcpy*/
snprintf(m_data, len, "%s", ptr);
}
return *this;
}
6. 重载+运算,连接字符串
//重载运算符+
MyString MyString::operator+(const MyString &rhs) {
MyString newString;
if (!rhs.m_data){
newString = *this;
}else if (!m_data){
newString = rhs;
}else {
newString.m_data = new char[strlen(m_data) + strlen(rhs.m_data) + 1];
//strcpy(newString.m_data, m_data); /*请废弃strcpy*/
snprintf(m_data, len, "%s", ptr);
strcat(newString.m_data, rhs.m_data);
}
return newString;
}
7. 重载下标运算符
//重载下标运算符
char MyString::operator [](const unsigned int index) {
return m_data[index];
}
8. 重载<<运算
//重载<<
ostream& operator<<(ostream& output, const MyString &rhs) {
output << rhs.m_data;
return output;
}
9. 测试demo
int main() {
const char* p = "hello, toutiao";
MyString s0 = "hello,toutiao";
MyString s1(p);
MyString s2 = s1;
MyString s3;
s3 = s1;
MyString s4 = s3 + s1;
bool flag(s1 == s2);
cout << s0 << endl;
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << flag << endl;
char result = s3[1];
cout << result << endl;
cout << s4 << endl;
return 0;
}
最后提一下,一直劝大家废弃strcpy函数,因为这个函数存在问题。
char s1[]="toutiao";
char s2[2]="1";
strcpy(s2,s1);会报什么错误?
这个问题很容易让我们产生误解,就是一般情况下,第一个参数的存储空间要大于第二个参数的存储空间,题中明显的s2的存储空间为2字节,s1的空间为8字节,s1>s2,当然会报错的了,至少会报告越界错误的吧。这就是这个题目的陷阱所在,但是实际实现过程中,strcpy函数却不会报错(linux gcc和window vc++6.0都没有报错)。
我们来看看strcpy的源代码:
char *strcpy(char *strDestination, const char *strSource){
assert(strDestination && strSource);
char *strD=strDestination;
while ((*strDestination++=*strSource++)!='\0')
NULL;
return strD;
}
最重要一条语句为:while ((*strDestination++=*strSource++)!='\0') ,意思是先将第二参数当前指针所指内容赋值给第一个参数当前指针所指空间,直到第二参数当前指针所指内容为'\0',将'\0'赋值给第一个参数后结束,因为第一个参数当前指针也是不断自加的,跟第一个参数所具有的存储空间的大小没有关系,他们会一直赋值,超过第一参数存储空间也没有关系,直到第二参数遇到'\0'为止。
那我为什么不推荐strncpy呢,因为strncpy只负责拷贝,不会负责给最后一个字节赋值'\0'操作,因此会需要2句话。btw,snprintf在此没有去判断返回值,真实的线上代码,最好加上返回值判断。snprintf返回写入的字节数。其实每个公司都有自己的编程规范,包括google,百度,腾讯,阿里巴巴等都有自己的规范,都有提到各种不应该使用的库函数等。
感谢支持,下期专门整理c语言各库函数的坑给大家。