🧑💻作者: @情话0.0
📝专栏:《C++从入门到放弃》
👦个人简介:一名双非编程菜鸟,在这里分享自己的编程学习笔记,欢迎大家的指正与点赞,谢谢!

string
在学习C++的过程中,必定有一个非常重要的工具就是STL(标准模板库):C++标准库的重要组成部分,它不仅是一个可复用的组件库,而且还是一个包罗数据结构与算法的软件框架。接下来我会将STL中几个主要的容器进行总结。
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。而在C++中,正是因为STL库的存在,使得关于字符串的操作更加简单、方便、快捷。因此大家都喜欢去使用string类的相关算法,很少人去使用C库中的字符串操作函数。
总结:
| (constructor)函数名称 | 功能说明 |
|---|---|
| string(); | 构造一个空字符串,长度为0个字符。 |
| string (const char* s); | 通过C字符串构造string类对象 |
| string (const string& str); | 拷贝构造 |
| string (const string& str, size_t pos, size_t len = npos); | 复制str从pos位置开始往后npos个字符 |
| string (const char* s, size_t n); | 复制C字符串前n个字符 |
| string (size_t n, char c); | string类对象包含n个字符c |
| template < class InputIterator > string (InputIterator first, InputIterator last); | 通过一个字符串区间构造对象 |
void Teststring()
{
string s1;
string s2("hello world!");
string s3(s2);
string s4(s2,1,3);
string s5("abcdefg",3);
string s6(3,'a');
string s7(s2,s2+11);
}

| 函数名称 | 功能说明 |
|---|---|
| size | 返回字符串有效字符长度 |
| length | 返回字符串有效字符长度 |
| capacity | 返回空间总大小 |
| empty | 检测字符串释放为空串,是返回true,否则返回false |
| clear | 清空有效字符 |
| reserve | 为字符串预留空间** |
| resize | 将有效字符的个数该成n个,多出的空间用字符c填充 |
void Teststring()
{
string s("hello,world!");
cout << s.size() << endl;
cout << s.length() << endl;
cout << s.capacity() << endl;
cout << s << endl;
// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s.clear();
cout << s.size() << endl;
cout << s.capacity() << endl;
// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
// “aaaaaaaaaa”
s.resize(10, 'a');
cout << s.size() << endl;
cout << s.capacity() << endl;
// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
// "aaaaaaaaaa\0\0\0\0\0"
// 注意此时s中有效字符个数已经增加到15个
s.resize(15);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
// 将s中有效字符个数缩小到5个
s.resize(5);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
//将s的容量增加到10
s.reserve(10);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
// 底层空间容量会不会变小?
s.reserve(5);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
}
总结:
size() 与 length() 方法底层实现原理完全相同,引入size() 的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
clear() 只是将string中有效字符清空,不改变底层空间大小。
resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层空间的大小,关键取决于你增加后的整个字符串长度会不会超过当前string的底层空间大小;如果是将元素个数减少,底层空间总大小不变。
reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数大于string的底层空间总大小时,底层空间可能会发生变化,当reserve的参数小于string的底层空间总大小时,string的底层空间总大小不变。
string的底层空间大小的变化是有自己的规则,并不是说你想让其空间为多少,它的空间大小就是多少。
//string的增容规则
void TestPushBack()
{
string s;
size_t sz = s.capacity();
cout << "first capacity: " << sz << endl;
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << endl;
}
}
}

| 函数名称 | 功能说明 |
|---|---|
| operator[] | 返回pos位置的字符,const string类对象调用 |
| begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
| rbegin + rend | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
| 范围for | C++11支持更简洁的范围for的新遍历方式 |
(1)可以通过[]+下标的方式来获取该字符串某个位置的字符,感觉和数组差不多,经常使用
void Teststring()
{
string s1("hello World!");
const string s2("Hello World!");
cout << s1[0] << " " << s2[0] << endl;
s1[0] = 'H';
cout << s1 << endl;
// s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}
(2)除了上面的方式可以访问字符串之外,还有一种非常特殊的方式——迭代器(非常重要),虽然很重要,但是在string里使用的并不多,做常用的方式还是下标访问的方式最多
void Teststring4()
{
string s("hello world!");
string::iterator it = s.begin();//begin代表字符串第一个位置
while (it != s.end())
{
cout << *it << endl;
++it;
}
// string::reverse_iterator rit = s.rbegin();
// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
auto rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << endl;
rit++;
}
//除了上面的两种普通迭代器,还有两种const修饰的迭代器
string::const_iterator it=s.begin();
string::const_reverse_iterator rit = s.rbegin();
}
(3)范围for,C++11新增,虽然看起来很厉害,但是没啥卵用,底层还是用的迭代器
void Teststring4()
{
string s("hello world!");
for (auto ch : s)
cout << ch << endl;
}
| 函数名称 | 功能说明 |
|---|---|
| push_back | 在字符串后尾插字符c |
| append | 在字符串后追加一个字符串 |
| operator+= | 在字符串后追加字符串str |
| insert | 在源字符串某一位置插入字符串str |
| erase | 在某一位置开始进行删除 |
| swap | 字符串交换 |
| c_str | 返回C格式字符串 |
| find + npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
| rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
| substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
| find_first_of | 搜索字符串中与参数中指定的任何字符匹配的第一个字符。 |
| find_last_of | 在字符串中搜索与参数中指定的任何字符匹配的最后一个字符。 |
| find_first_not_of | 搜索字符串中与参数中指定的任何字符不匹配的第一个字符。 |
| find_last_not_of | 在字符串中搜索不匹配其参数中指定的任何字符的最后一个字符。 |
void Teststring5()
{
string str;
str.push_back(' '); // 在str后插入空格
str.append("hello"); // 在str后追加一个字符"hello"
str += 'w'; // 在str后追加一个字符'b'
str += "orld"; // 在str后追加一个字符串"it"
cout << str << endl;
cout << str.c_str() << endl; // 以C语言的方式打印字符串
string s("world");
s.insert(0, "hello");
cout << s << endl;
s.insert(5, " ");
cout << s << endl;
cout << s.at(0) << endl;
s.insert(2, "dsfsdf", 3);//插入该字符串的三个字符在二号位置
cout << s << endl;
s.erase(2, 3);//从2号位置开始删除三个字符
cout << s << endl;
string s1("aaa");
string s2("bbb");
s1.swap(s2);
string s("abcd");
s.insert(s.find('b'),"sdfsf");//找到字符b的位置然后插入”sdfsf“
cout << s << endl;
s.replace(s.find('f'),1,"20%" );//找到f的位置用20%代替
cout << s << endl;
string s("hello world");
size_t pos = s.find_first_of("eo",0);//从位置0处找到字符e或字符o第一次出现的位置
while (pos != string::npos)//如果没有找到匹配项,函数返回string::npos。
{
s[pos] = '*';
pos = s.find_first_of("eo", pos + 1);//将整个字符串中的字符e或字符o换成*
}
//将不是e或o的字符都替换成*
string s("hello world");
size_t pos = s.find_first_not_of("eo", 0);
while (pos != string::npos)
{
s[pos] = '*';
pos = s.find_first_not_of("eo", pos + 1);
}
cout << s << endl;
string s("hello world");
size_t pos = s.find_last_of("eo", s.size()-1);//从后开始找
while (pos != string::npos)
{
s[pos] = '*';
pos = s.find_last_of("eo", pos - 1);
}
cout << s << endl;
}
当然,关于插入删除的函数有多种情况,我也就不多介绍,我们可以根据具体的要求去官网查找,在这里我只需要大家明白最基础的就行。
注意:


| 函数 | 功能说明 |
|---|---|
| operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
| operator>> | 输入运算符重载 |
| operator<< | 输出运算符重载 |
| getline | 获取一行字符串 |
| relational operators | 大小比较 |
上面的这几个非成员函数做一个了解即可,如果想知道具体该怎么实现可以看一下关于string的文档。
关于string学到这里我们感觉一个string对象应该占12个字节(一个指向字符串的指针,一个是整形的size,一个整形的capacity),但明显不是这样的,string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放;
当字符串长度大于等于16时,从堆上开辟空间。
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
}_Bx;
因为大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高;还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量;还有一个指针做一些其他事情。因此总共占16+4+4+4=28个字节。

在之前的学习类的六大成员函数时,关于拷贝构造函数存在一个问题:关于自定义类型的数据拷贝问题,那我们看一下下面的代码有什么问题?
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
void TestString()
{
String s1("hello world!");
String s2(s1);
}

结果发现这段代码是存在问题的,而问题是出现在析构函数处,为什么这样说呢?
因为上面的String类没有显示定义拷贝构造函数,所以在通过s1对象构造s2对象时采用了系统默认的构造函数,但是默认拷贝构造函数完成的是值拷贝,什么意思呢?就是把s1对象的地址给到了s2,并不是将内容给到s2,这也就导致了s1和s2共用一块空间,其实在这里都是ok的,问题就出现在当要调用析构函数的时候出现了问题,首先肯定先析构s2对象,那么就会将其回收并将它的内容释放掉,这就导致了当s1对象析构时出现了问题,进而就引发了同一块空间被多次释放而引起程序崩溃。而这种拷贝的方式被称为浅拷贝,也叫值拷贝。
对于浅拷贝的劣势,与其对应的有一个拷贝方式叫深拷贝:如果一个类中涉及到资源管理时,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。它能够给每个对象独立分配资源,保证多个对象之间不会因为共享资源而造成多次释放造成程序崩溃问题。
//string(const char* str = nullptr) 错误
//string(const char* str = '\0') 错误
string(const char* str = "")
:_size(strlen(str))
{
_str = new char[_size + 1];
strcpy(_str, str);
_capacity = _size;
}
关于构造函数的缺省参数该给什么是有说法的,比如上面的两种形式是不行的,第一种不行的原因在于给到 str 一个空指针,那么在strlen(str)时会对 str 解引用,因此会报错;第二种是因为类型不匹配导致的出错。
~String()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
关于析构函数没有什么难点,就把该释放的空间释放,参数置空赋0。
String(const string& s)
:_size(s._size)
{
_str = new char[_size + 1];
strcpy(_str, s._str);
_capacity = _size;
}
String(const string& s)
:_str(nullptr)
{
string str(s._str);//调用构造函数
swap(_str,str);
}
关于拷贝构造函数有两种方法,注意第二种一定要先构造一份对象然后再交换,要不然直接交换的话就把传过来的对象变空了。
String& operator=(const String& s)
{
char* tmp = new char[s._size + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_capacity = _size = s._size;
return *this;
}
//下面两种方式哪种更好
String& operator=(String s)
{
swap(_str,s._str);
return *this;
}
String& operator=(const String& s)
{
if(this!=&s)
{
String str(s);
swap(_str,str._str);
}
return *this;
}
关于赋值运算符重载我给了三种方式,主要看一下下面两种赋值方式哪种更好,可能有的人会认为第二种更好,认为第一种在传参时没有传引用导致了多一次的拷贝构造,在函数体内直接交换会导致传过来的对象为空了。其实并不然,可以再看一下代码,第一种通过直接传参会先通过拷贝构造构造一份临时对象,然后再用这个临时对象与被赋值的对象交换,这个只是将临时对象变空了,函数调用完也就自动释放了,并不会造成什么影响。
bool operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool operator>(const string& s)
{
return strcmp(_str, s._str) > 0;
}
bool operator>=(const string& s)
{
return *this > s || *this == s;
}
bool operator<(const string& s)
{
return !(*this >= s);
}
bool operator<=(const string& s)
{
return *this < s && *this == s;
}
bool operator!=(const string& s)
{
return !(*this == s);
}
比较运算符的重载可以先写一个大于和等于的函数,其他的比较可以直接用这两个进行复用。
void resize(size_t newsize,char c = '\0')
{
if (newsize > _size)
{
if (newsize > _capacity)
{
reserve(newsize);
}
memset(_str + _size, c, newsize - _size);
}
_size = newsize;
_str[newsize] = '\0';
}
void reserve(size_t cap)
{
if (cap > _capacity)
{
char* tmp = new char[cap + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = cap;
}
}
void push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity * 2);
}
_str[_size++] = c;
_str[_size] = '\0';
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void append(const char* str)
{
int len = strlen(str);
if (_size +len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string& insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = c;
++_size;
return *this;
}
string& insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end + len - 1] = _str[end - 1];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
string& erase(size_t pos, size_t len = npos)
{
if (pos + len >= _size || len == npos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator begin()const
{
return _str;
}
const iterator end()const
{
return _str + _size;
}
为什么写了前两个还要再写两个const型的迭代器,这是因为将一个const修饰的对象作为一个参数传过去,在那个参数内部要使用迭代器就必须使用 const 修饰的迭代器,否则就是权限的放大。
为了更好地学习string,将其模拟实现一次更好,可以更好地了解每个接口的实现原理等,在学之后的STL容器就更容易。此片博客如有不足还望指出,大家互相学习,万分感谢你的支持。

大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一
在Ruby1.9.3(可能还有更早的版本,不确定)中,我试图弄清楚为什么Ruby的String#split方法会给我某些结果。我得到的结果似乎与我的预期相反。这是一个例子:"abcabc".split("b")#=>["a","ca","c"]"abcabc".split("a")#=>["","bc","bc"]"abcabc".split("c")#=>["ab","ab"]在这里,第一个示例返回的正是我所期望的。但在第二个示例中,我很困惑为什么#split返回零长度字符串作为返回数组的第一个值。这是什么原因呢?这是我所期望的:"abcabc".split("a")#=>["bc"
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO