C++ 的一个常见面试题是让你实现一个 String 类,限于时间,不可能要求具备 std::string 的功能,但至少要求能正确管理资源。具体来说:
1.能像 int 类型那样定义变量,并且支持赋值、复制。
2.能用作函数的参数类型及返回类型。
3.能用作标准库容器的元素类型,即 vector/list/deque 的 value_type。
本文给出我认为适合面试的答案,强调正确性及易实现(白板上写也不会错),不强调效率。某种意义上可以说是以时间(运行快慢)换空间(代码简洁)。
首先选择数据成员,最简单的 String 只有一个 char* 成员变量。好处是容易实现,坏处是某些操作的复杂度较高(例如 size() 会是线性时间)。为了面试时写代码不出错,本文设计的 String 只有一个 char* data_成员。而且规定 invariant 如下:一个 valid 的 string 对象的 data_ 保证不为 NULL,data_ 以 ‘\0’ 结尾,以方便配合 C 语言的 str*() 系列函数。
其次决定支持哪些操作,构造、析构、拷贝构造、赋值这几样是肯定要有的(以前合称 big three,现在叫 copy control)。如果钻得深一点,C++11的移动构造和移动赋值也可以有。为了突出重点,本文就不考虑 operator[] 之类的重载了。
这样代码基本上就定型了:
#include <utility>
#include <string.h>
class String
{
public:
String()
: data_(new char[1])
{
*data_ = '\0';
}
String(const char* str)
: data_(new char[strlen(str) + 1])
{
strcpy(data_, str);
}
String(const String& rhs)
: data_(new char[rhs.size() + 1])
{
strcpy(data_, rhs.c_str());
}
/* Delegate constructor in C++11
String(const String& rhs)
: String(rhs.data_)
{
}
*/
~String()
{
delete[] data_;
}
/* Traditional:
String& operator=(const String& rhs)
{
String tmp(rhs);
swap(tmp);
return *this;
}
*/
String& operator=(String rhs) // yes, pass-by-value
{
swap(rhs);
return *this;
}
// C++ 11
String(String&& rhs)
: data_(rhs.data_)
{
rhs.data_ = nullptr;
}
String& operator=(String&& rhs)
{
swap(rhs);
return *this;
}
// Accessors
size_t size() const
{
return strlen(data_);
}
const char* c_str() const
{
return data_;
}
void swap(String& rhs)
{
std::swap(data_, rhs.data_);
}
private:
char* data_;
};
注意代码的几个要点:
1.只在构造函数里调用 new char[],只在析构函数里调用 delete[]。
2.赋值操作符采用了《C++编程规范》推荐的现代写法。
3.每个函数都只有一两行代码,没有条件判断。
4.析构函数不必检查 data_ 是否为 NULL。
5.构造函数 String(const char* str) 没有检查 str 的合法性,这是一个永无止境的
争论话题。这里在初始化列表里就用到了 str,因此在函数体内用 assert() 是无意义的。
这恐怕是最简洁的 String 实现了。
接下来我们模拟实现较完备的string类
成员变量:
private:
char* _str;
size_t _capacity;
size_t _size;
class String
{
public:
typedef char* Iterator;
public:
String(const char* str = "")
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
if (nullptr == str)
{
assert(false);
return;
}
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity+1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[s._capacity + 1])
, _size(s._size)
, _capacity(s._capacity)
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[s._capacity + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
/////////////////////////////////////////////////////////////////
// Iterator
Iterator Begin()
{
return _str;
}
Iterator End()
{
return _str + _size;
}
/////////////////////////////////////////////////////////////////
// modify
void PushBack(char c)
{
if (_size == _capacity)
Reserve(_capacity*2);
_str[_size++] = c;
_str[_size] = '\0';
}
void Append(size_t n, char c)
{
for (size_t i = 0; i < n; ++i)
PushBack(c);
}
String& operator+=(char c)
{
PushBack(c);
return *this;
}
void Clear()
{
_size = 0;
_str[_size] = '\0';
}
void Swap(String& s)
{
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
}
const char* C_Str()const
{
return _str;
}
/////////////////////////////////////////////////////////////////
// capacity
size_t Size()const
{
return _size;
}
size_t Capacity()const
{
return _capacity;
}
bool Empty()const
{
return 0 == _size;
}
void Resize(size_t newSize, char c = char())
{
if (newSize > _size)
{
// 如果newSize大于底层空间大小,则需要重新开辟空间
if (newSize > _capacity)
{
Reserve(newSize);
}
memset(_str + _size, c, newSize - _size);
}
_size = newSize;
_str[newSize] = '\0';
}
void Reserve(size_t newCapacity)
{
// 如果新容量大于旧容量,则开辟空间
if (newCapacity > _capacity)
{
char* str = new char[newCapacity + 1];
strcpy(str, _str);
// 释放原来旧空间,然后使用新空间
delete[] _str;
_str = str;
_capacity = newCapacity;
}
}
////////////////////////////////////////////////////////////////////
// access
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
//输出运算符的重载
friend ostream& operator<<(ostream& _cout, const bit::String& s);
private:
char* _str;
size_t _capacity;
size_t _size;
};
ostream& operator<<(ostream& _cout, String& s)
{
cout << s._str;
return _cout;
}
这个功能比较完备的版本啦,需要提醒的是:
1.迭代器iterator实质上是一个char*的封装
typedef char* iteraotor;
2.resize()和reserve()的区分:
当申请的空间大于原空间的时候,两者都会改变string对象的大小;不同的是resize()会将多余的空间初始化为’\0’或者指定字符,reserve()只是申请了指定空间而已;
当申请的空间小于原空间的时候,reserve()不会产生什么影响,但是resize()却会将原空间大小改为申请大小,并且实际字符串内容也会改变。
//test1();
string s("abcdefg");
cout << s.capacity() << endl;
cout << s.size() << endl;
cout << s << endl;
s.resize(3);
cout << s.capacity() << endl;
cout << s.size() << endl;
cout << s << endl;
输出结果 :