在C++开发中,字符串处理是最基础也最常遇到的任务之一。很多初学者在掌握了基本语法后,第一个尝试实现的类往往就是字符串类。今天我要分享的是一个从简陋的StringBad类逐步完善为功能完备的String类的完整过程,这个案例几乎涵盖了C++类设计的核心知识点。
这个String类最初版本存在严重的内存管理问题,经过多次迭代后,它现在具备了:
让我们从最关键的改进点开始,逐步剖析这个类的设计思路和实现细节。
原始版本的默认构造函数存在两个问题:一是会产生不必要的调试输出,二是初始化内容不必要地复杂。改进后的版本简洁而安全:
cpp复制String::String()
{
len = 0;
str = new char[1];
str[0] = '\0'; // 空字符串
}
这里有几个值得注意的技术细节:
delete[]操作保持兼容len明确记录字符串长度,避免反复调用strlen关键经验:在C++中,构造函数和析构函数的内存管理操作必须严格匹配。new/delete、new[]/delete[]必须成对使用。
原始类最大的问题就是缺少正确的拷贝控制,导致浅拷贝问题。改进版本实现了深拷贝:
cpp复制// 拷贝构造函数
String::String(const String& st)
{
num_strings++;
len = st.len;
str = new char[len + 1];
std::strcpy(str, st.str);
}
// 赋值运算符
String& String::operator=(const String& st)
{
if (this == &st) // 自赋值检查
return *this;
delete[] str; // 释放原有内存
len = st.len;
str = new char[len + 1];
std::strcpy(str, st.str);
return *this;
}
这里有几个关键点:
为了让String类用起来像内置类型一样自然,我们重载了比较运算符:
cpp复制bool operator<(const String& st1, const String& st2)
{
return (std::strcmp(st1.str, st2.str) < 0);
}
bool operator>(const String& st1, const String& st2)
{
return st2 < st1; // 巧妙地复用<运算符
}
bool operator==(const String& st1, const String& st2)
{
return (std::strcmp(st1.str, st2.str) == 0);
}
实现技巧:
为了支持类似数组的访问方式,我们实现了两个版本的下标运算符:
cpp复制// 普通版本,允许修改
char& String::operator[](int i)
{
return str[i];
}
// const版本,用于const对象
const char& String::operator[](int i) const
{
return str[i];
}
这种成对出现的运算符重载是C++的常见模式:
为了让String类完美融入C++IO系统,我们重载了<<和>>运算符:
cpp复制ostream& operator<<(ostream& os, const String& st)
{
os << st.str;
return os;
}
istream& operator>>(istream& is, String& st)
{
char temp[String::CINLIM];
is.get(temp, String::CINLIM);
if (is)
st = temp;
while (is && is.get() != '\n')
continue;
return is;
}
实现要点:
String类使用静态成员跟踪创建的字符串数量:
cpp复制static int num_strings; // 类内声明
int String::num_strings = 0; // 类外初始化
static int HowMany() { return num_strings; } // 静态方法
静态成员的特点:
为了避免从C字符串创建临时String对象的开销,我们增加了直接赋值运算符:
cpp复制String& String::operator=(const char* s)
{
delete[] str;
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str, s);
return *this;
}
这种优化在性能敏感的场景特别有用,它消除了临时对象的构造和析构开销。
cpp复制int main()
{
String name;
cout << "Enter your name: ";
cin >> name;
String sayings[3];
cout << "Enter 3 sayings:\n";
for (int i = 0; i < 3; i++) {
cin >> sayings[i];
}
// 找出最短的和字典序最小的
int shortest = 0, first = 0;
for (int i = 1; i < 3; i++) {
if (sayings[i].length() < sayings[shortest].length())
shortest = i;
if (sayings[i] < sayings[first])
first = i;
}
cout << "Shortest: " << sayings[shortest] << endl;
cout << "First alphabetically: " << sayings[first] << endl;
cout << "Total String objects: " << String::HowMany() << endl;
}
内存管理黄金法则:
运算符重载最佳实践:
const正确性:
性能优化技巧:
这个String类虽然比标准库的string简单很多,但它完整展示了C++类设计的核心概念。在实际项目中,除非有特殊需求,否则应该优先使用std::string。但实现这样一个类无疑是理解C++对象模型的绝佳练习。