怎样在外贸网站上做土特产,什么是网络营销方案,邢台路桥建设总公司没有网站吗,庆元县住房和城乡建设局网站string类的模拟实现 文章目录 string类的模拟实现前言1. 类的框架设计2. 构造函数与析构函数3. 拷贝构造与重载赋值运算符函数4. 运算符重载5. 成员函数6. 迭代器的实现7. 非成员函数8. 单元测试总结 前言
在现代编程中#xff0c;字符串处理是每个程序员都会遇到的基本任…string类的模拟实现 文章目录 string类的模拟实现前言1. 类的框架设计2. 构造函数与析构函数3. 拷贝构造与重载赋值运算符函数4. 运算符重载5. 成员函数6. 迭代器的实现7. 非成员函数8. 单元测试总结 前言
在现代编程中字符串处理是每个程序员都会遇到的基本任务。C标准库提供了强大的std::string类但了解其背后的原理对于深入理解语言特性至关重要。本文将介绍如何从零开始实现一个简单的String类这不仅能够帮助我们更好地理解C还能够提升我们解决问题的能力。 1. 类的框架设计
首先需要确定成员变量有哪些结合“先描述再组织”的方针要对String类进行描述那么就必须先确定成员变量下一步才有资格谈论功能实现函数声明与定义。这里我们需要实现的string本质上是一个可实现自动扩容的字符数组所以成员变量采用char*声明的字符数组字符串长度大小字符数组容量通过字符数组实现存储字符串长度和字符数组容量共同作为控制扩容逻辑的参考值。
当然在确定成员变量后接着就需要定夺功能实现了首先必须要有的是构造、析构函数拷贝构造、重载复制运算符函数这两者拷贝函数有时需要显式delete避免调用特定的运算符重载基本功能函数等。直接一次性声明出所有的类内成员变量与函数是不现实的很难免会出现遗漏功能或逻辑不自洽。所以一般都是一边完善类的声明一边实现函数定义下面我便于后续描述直接给出了本文涉及的所有函数功能声明。 注仅挑选实现了std::string类中部分高使用率的函数 class String
{
public:// 基础构造函数String(const char* str );// 拷贝构造函数String(const String s);// 赋值运算符函数String operator(const String s);// 析构函数~String();// 重载运算符friend std::ostream operator(std::ostream out, const String s);friend std::istream operator(std::istream in, const String s);bool operator(const String s);char operator[](size_t index)const;char operator[](size_t index);String operator(const String s)const;String operator(const char c)const;String operator(const char* str)const;String operator(const String s);String operator(const char c);String operator(const char* str);// 成员函数size_t size()const;size_t capacity()const;const char* c_str()const;void reserve(size_t n);void resize(size_t n, char c \0);void push_back(const char c);void append(const char* str);void insert(size_t pos, const char c, size_t n 1);void insert(size_t pos, const char* str);void erase(size_t pos, size_t n npos);void swap(String s);size_t find(const String s, size_t pos 0)const;size_t find(const char* str, size_t pos 0)const;size_t find(const char c, size_t pos 0, size_t n 0)const;String substr(size_t pos 0, size_t len npos)const;// 仿迭代器typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator cbegin();const_iterator cend();private:char* m_str;size_t m_size;size_t m_capacity;
private:static const int npos;
};在实现具体函数功能前需要声明 任何类内非静态函数其参数列表第一个参数永远是类自身的self指针例如这里实现String类即为String* self 这是后面函数实现中有时显式无参却能操作类内对象、仅有一个参数却能实现两对象之间的对比亦或其他操作的理论基础 2. 构造函数与析构函数
String::String(const char* str):m_size(strlen(str)) // 注意缺省参数在声明和定义处仅出现一次
{m_capacity m_size;m_str new char[m_capacity 1]; // 多一个字节存放\0strcpy(m_str, str);
}String::~String()
{delete[] m_str;m_str nullptr;m_size m_capacity 0;
}注意这里的构造函数使用了全缺省参数实现通过这种方法可实现通过一个函数兼任无参构造与单参数构造函数。定义内容上在String类实例化对象时实现堆区创建指针数组以便后续对其扩容。 注意函数参数使用缺省值实现时缺省值定义的出现在声明和定义中仅能出现一次 析构函数中需要注意delete的对象是数组所以需要相应使用delete[] 数组名的方式对数组申请的堆区空间进行释放。
3. 拷贝构造与重载赋值运算符函数
String::String(const String s)
{m_size s.m_size;m_capacity s.m_capacity;m_str new char[m_capacity 1];strcpy(m_str, s.m_str);
}
String String::operator(const String s)
{char* tmp new char[s.m_capacity];strcpy(tmp, s.m_str);delete[] this-m_str;this-m_str tmp;this-m_size s.m_size;this-m_capacity s.m_capacity;return *this;
}实现类的拷贝功能类型函数时特别需要注意深浅拷贝的问题很好的辨别方法是观察成员变量是否有是在堆区创建的如果有一般都需要避免浅拷贝的问题。在复制给新对象时需要对该成员变量在堆区新开辟空间接着才能进行堆空间之间内容的值拷贝。 对于拷贝赋值运算符而言返回值一般以 self 形式存在很大一部分原因在于通过这种方法可以实现链式调用例如 String s1(haha);
String s2;
String s3;s3 s2 s1;由于赋值运算符的运算逻辑是自右向左的所以仅需一条语句就可将s1的值拷贝给s2、s3对象。 4. 运算符重载
流插入运算符和流提取运算符
std::ostream operator(std::ostream out, const String s)
{out s.m_str;return out;
}std::istream operator(std::istream in, String s)
{char ch;ch in.get();int i 0;char buff[128]{ 0 };while (ch ! \n ch ! ){buff[i] ch;if (i 127){buff[i] \0;s buff;i 0;}ch in.get();}if (i ! 127){buff[i] \0;s buff;}return in;
}当我们使用流提取时由于不会导致String对象的成员变量字符数组内数组大小发生改变并不会发生扩容所以可以采用简单的直接提取字符数组进行输出out s.m_str
然而使用流插入时 很难免传入的新字符串大小超过原字符数组的容量承载极限有可能导致扩容所以我们需要对插入的数据进行分批从缓冲区获取分批插入字符数组如此一来当我们调用插入之类的函数时就可以一边插入一边扩容这里采用了 运算符实现插入操作当然也可以使用 insert 、push_back之类的函数实现。当然后面我们会给出这类插入函数的实现。
双等号运算符
bool String::operator(const String s)
{return strcmp(m_str, s.m_str) 0;
}这里采用了C语言库中的strcmp函数通过对两个String对象的字符数组进行对比判断两对象是否等价。
下标运算符
char String::operator[](size_t index)const
{assert(index m_size);return m_str[index];
}
char String::operator[](size_t index)
{assert(index m_size);return m_str[index];
}提供了重载实现 如果const String对象调用[]运算符那么返回的 char 类型不会因其改变影响对象内的字符数组内值的变化。如果String对象non_const调用时返回指定下标位置的引用外部对其进行修改时可以引起字符数组内指定位置值同步发生改变。 加号运算符
String String::operator(const String s)const
{String tmp;size_t capacity_sum capacity() s.capacity();tmp.reserve(capacity_sum); // 开空间// 字符串拷贝strcpy(tmp.m_str, m_str);strcat(tmp.m_str, s.m_str);tmp.m_size size() s.size();return tmp;
}
String String::operator(const char c)const
{String tmp; // 初始化和定义tmp.reserve(capacity() 1);size_t size this-size();// 赋值strcpy(tmp.m_str, m_str);tmp.m_str[size] c;tmp.m_str[size 1] \0;tmp.m_size size 1;return tmp;
}
String String::operator(const char* str)const
{assert(str);String tmp;size_t size_1 size();size_t size_2 strlen(str);tmp.reserve(size_1 size_2 1);// 赋值strcpy(tmp.m_str, c_str());strcat(tmp.m_str, str);return tmp;
}可以看到重载了三种加号运算符分别实现了追加String对象、char字符、char*类型字符串从内部实现来看无一不是调用reserve()函数实现扩容具体的reserve()函数定义将会在后面给出。不过可以确定的是运算符也属于插入类型的运算符函数不可避免的存在扩容的问题所以模型都属于扩容赋值。
加等运算符
String String::operator(const String s)
{this-append(s.m_str);return *this;
}
String String::operator(const char c)
{this-push_back(c);return *this;
}
String String::operator(const char* str)
{this-append(str);return *this;
}由于operator的功能和append()函数基本相同所以采用代码复用的方式实现后面给出 append() 函数的具体实现。
5. 成员函数
size和capacity
size_t String::size()const
{return m_size;
}size_t String::capacity()const
{return m_capacity;
}出于对数据的保护使用接口对外提供访问。
c_str
const char* String::c_str()const
{return m_str;
}便于外部直接访问字符数组尤其是外部C语言函数调用时不接受String类型参数只能通过 c_str() 函数进行传递注意返回值类型为 const char* 可以避免外部对指针的操作引起内部数组变化。
reserve和resize
void String::reserve(size_t n) // 设定容量不影响size值
{if (n m_size){char* cmp new char[n 1];strcpy(cmp, m_str);delete[] m_str;m_str cmp;m_capacity n;}
}
void String::resize(size_t n, char c)
{if (n m_size){m_str[n] c;m_size n;}else{if (n m_capacity) // 考虑扩容{reserve(n);}for (size_t i m_size; i n; i) // 填充值{m_str[i] c;}m_str[n] \0;m_size n;}
}reserve()控制容量 resize() 控制size大小可能影响容量需要扩容 push_back和append
void String::push_back(const char c)
{if (m_size m_capacity) // 考虑扩容{m_capacity (m_capacity 0) ? (4) : (m_capacity * 2);reserve(m_capacity);}m_str[m_size] c;m_str[m_size] \0;
}void String::append(const char* str)
{assert(str);const size_t size strlen(str);if (size m_capacity - m_size) // 考虑扩容{m_capacity size m_size 1;reserve(m_capacity);}// 拷贝strcpy(m_str this-size(), str); // 从中间部分开始拷贝m_size size;
}都属于引起字符数组size增大的函数存在扩容的可能所以内部对size与capacity值进行判断如有需要先扩容再插入。
insert和erase
void String::insert(size_t pos, const char c, size_t n)
{assert(pos m_size);while(n m_capacity - m_size) // 避免n过大内存分配不够{m_capacity (m_capacity 0) ? (n1) : (m_capacity * 2);reserve(m_capacity);}size_t i m_size n;for (; i n pos i ! 0; --i) // i!0避免溢出 内置if语句拖慢运行时间{m_str[i] m_str[i - n];}if (i 1 pos 0) // 处理pos0或i0的情况{m_str[n] m_str[n - 1];}//m_str[m_size n] \0; // 注意结尾的\0 (如果上面for循环定义i m_size n - 1时)for (size_t j pos; j pos n; j){m_str[j] c;}m_size n; // 切记更新size值
}
void String::insert(size_t pos, const char* str)
{assert(str pos m_size);size_t length strlen(str);while (length m_capacity - m_size){m_capacity (m_capacity 0) ? (4) : (m_capacity * 2);reserve(m_capacity);}// 字符串右移size_t i m_size length;for (; i length pos i ! 0; --i) // i!0避免溢出 内置if语句拖慢运行时间{m_str[i] m_str[i - length];}if (i 1 pos 0) // 处理pos0或i0的情况{m_str[length] m_str[length - 1];}for (size_t j pos, i 0; j pos length; j, i){m_str[j] str[i];}m_size length;
}void String::erase(size_t pos, size_t n)
{assert(pos m_size);if (n npos || n m_size - pos){m_str[pos] \0;m_size pos;}else{for (size_t i pos; i m_size; i) //i pos n连带复制\0{m_str[i] m_str[i n];}m_size - n;}
}提供了两个重载的 Insert() 函数可实现指定位置插入n个char值或在指定位置插入字符串const char*。而 erase() 函数也实现了从pos位置向后删除n个字符的功能。两者都采用了缺省值参数如不指定就插入1个字符而 erase() 函数的n如果未指定代表极大的值相当于删除pos值以后的所有字符。
swap和find
void String::swap(String s)
{std::swap(m_str, s.m_str);std::swap(m_size, s.m_size);std::swap(m_capacity, s.m_capacity);
}size_t String::find(const String s, size_t pos)const
{assert(pos m_size);const char* sub_str strstr(m_str pos, s.m_str);if (sub_str ! nullptr){return sub_str - m_str;}return npos;
}
size_t String::find(const char* str, size_t pos)const
{assert(pos m_size str ! nullptr);const char* sub_str strstr(m_str pos, str);if (sub_str ! nullptr){return sub_str - m_str;}return npos;
}
size_t String::find(const char c, size_t pos, size_t n)const
{assert(pos m_size n m_size - pos);for (size_t i pos; i m_size - n; i){if (m_str[i] c){size_t j 0;for (; j n; j, i){if (m_str[i] ! c){break;}}if (j n) { return i - n; }}}return npos;
}使用最简单朴素的方法实现字符串查找。
substr
String String::substr(size_t pos, size_t len)const
{assert(pos m_size);String tmp;if (len m_size - pos) // 取到字符串结尾{for (size_t i pos; i m_size; i){tmp.push_back(m_str[i]);}}else{for (size_t i pos; i pos len; i){tmp.push_back(m_str[i]);}tmp.push_back(\0);}return tmp;
}实现字符串切割如果 len 值过大呢我们将其视为取pos 往后全部字符串的功能要求所以需要设置一个极大的值赋给 len我们在类内定义一个静态成员变量size_t npos -1由于 size_t 属于无符号整形所以 -1 代表的值极大一般字符串都不会超过这个长度。
6. 迭代器的实现
begin和end
char* String::begin()
{return m_str;
}
char* String::end()
{return m_str m_size;
}这里需要C语言的基础指针变量整数代表指针后移整数个指向的变量位置。
cbegin和cend
const char* String::cbegin()
{return m_str;
}
const char* String::cend()
{return m_str m_size;
}上面提供两种迭代器的原因是外部利用迭代器访问时如果是 const 对象就调用 “cbegin(), cend()” 函数反之调用 “begin(), end()” 函数。返回非const迭代器可以实现外部改变类内字符数组的值。
7. 非成员函数
getline与swap
istream getline(istream in, String s, char delim \n) // 空格不作为读取终止符
{char ch in.get();int i 0;char buff[128]{ 0 };while (ch ! delim){buff[i] ch;if (i 127){buff[127] \0;s buff;i 0; // 重置buff}ch in.get(); // 循环读取}if (i ! 127){buff[i] \0;s buff;}return in;
}void swap(String s1, String s2)
{s1.swap(s2);
}8. 单元测试
void test1()
{const String s1(xxxxx);String s2(s1);String s3 s1 s2;String s4 s1 w;String s5 s1 hahaha;cout s3 endl;cout s4 endl;cout s5 endl;// 测试扩容String ss1;ss1.push_back(s);ss1.push_back(s);ss1.push_back(s);ss1.push_back(s);ss1.push_back(s);cout ss1 endl;
}运行结果 void test2()
{String s1;s1.append(hahahaha);cout s1 endl;s1.insert(0, q, 50);cout s1 endl;String s2 s1;s2.insert(0, wwwwwwwwww);cout s2 endl;s2.erase(0, 10);cout s2 endl;
}运行结果 void test3()
{String s1(wewewewewe);String s2(hhhhh);s1 s2;s1 6;s1 vocal;cout s1 endl;
}运行结果 void test4()
{String s1;String s2;cin s1 s2;cout s1 endl;cout s2 endl;String s3;getline(cin, s3);cout s3 endl;
}运行结果 void test5()
{String s1(hahaha);String s2(jiarenmen);s1.swap(s2);cout s1: s1 \t s2: s2 endl;swap(s1, s2);cout s1: s1 \t s2: s2 endl;
}运行结果 void test6()
{String s(hawrwcrwwwwcrw);size_t found s.find(w, 0, 4);cout found endl;found s.find(wwcrw);cout found endl;String s2(wc);found s.find(s2);cout found endl;
}运行结果 void test7()
{String s(hawwwcrwwwwcrw);String s2 s.substr(2);cout s2 endl;String s3 s.substr(s.find(cr), 4);cout s3 endl;
}运行结果 总结
通过实现自己的String类我们不仅加深了对C内存管理、对象生命周期和运算符重载的理解还提高了我们的编程技能和问题解决能力。虽然std::string类在功能和性能上可能更加成熟但自己动手实现一个String类无疑是一次宝贵的学习经历。在未来的编程实践中我们可以继续探索更多的优化方法和高级特性使我们的String类更加健壮和高效。