目录
4、C++标准库中string类的类体外已显示实现的常见的非类成员函数(全局函数)接口
4.2、全局的加法运算符重载函数 operator+ (string)
3.7、类成员函数 capacity
在C++标准库中的string类的类体中已经显式实现了类成员函数 capacity 的函数接口,具体如下所示:
size_t capacity() const; //size_t capacity(const string* const this);
该类成员函数返回的是:某一个自定义string类型的对象中的类成员变量所指向的在堆区上动态开辟的内存空间的容量,此处所谓的容量不需要考虑该自定义string类型的对象中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的常量字符串中末尾位置隐藏的无效字符 '\0',即,某一个自定义string类型的对象中的类成员变量所指向的在堆区上动态开辟的总的内存空间的个数应等于该类成员函数 capacity 的返回值再加上1、
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s("hello world");
cout << s.capacity() << endl; //s.capacity(&s); 15、
return 0;
}
其次,在C++标准库中的string类的类体中已经显式实现了类成员函数 push_back 的函数接口,具体如下所示:
void push_back (char c); //void push_back (string* const this,char c);
该类成员函数主要功能是在某一个自定义string类型的对象中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的常量字符串中末尾位置隐藏的无效字符 '\0' 的前面插入一个有效字符或着是一个显式的无效字符 '\0',使得该自定义string类型的对象中的类成员变量 _size 的值增加一个,这和该类成员函数的底层逻辑有关,具体在后期的模拟实现中会有体现,由于我们通常在任何情况下,只考虑最常规的常量字符串,所以此处,最常用的就是在某一个自定义string类型的对象中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规常量字符串中末尾位置隐藏的无效字符 '\0' 的前面与该最常规的常量字符串中的最后一个有效字符的后面尾插一个有效字符,不再考虑无效字符 '\0' 、
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s("hello world");
cout << s << endl;
s.push_back('A');
cout << s << endl;
s.push_back('B');
cout << s << endl;
s.push_back('C');
cout << s << endl;
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
//不太常用、
int main()
{
string s("hello world");
//此时,自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的常量字符串为:
//"hello world" ,此时的自定义string类型的对象s中的类成员变量_size是通过在自动调用该自定义string类型的
//对象s所对应的在C++标准库中的自定义的string类的类体中的已经显式实现的构造函数时,在该构造函数的函数体
//中通过strlen库函数对常量字符串"hello world"求出来的长度,然后再赋值给该自定义string类型的对象s中的类
//成员变量_size,所以是11、
cout << s << endl;
cout << s.size() << endl;//11
s.push_back('\0'); //转义字符、
//不管此处是在自定义string类型的对象中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的常量字符串
//中末尾位置隐藏的无效字符 '\0' 的前面插入一个有效字符或着是一个显式的无效字符 '\0',则此处都是调用自定义
//string类型的对象s所对应的在C++标准库中的自定义的string类的类体中的已经显式实现的类成员函数push_back的函
//数接口,在该类成员函数的函数体中对该自定义string类型的对象s中的类成员变量_size增加了1,此时就变成了12,与该
//类成员函数的底层逻辑有关,具体在后期的模拟实现中会有体现,并不是在该类成员函数的函数体中通过strlen库函数对
//常量字符串"hello world\0"求出来的长度,再赋值给该自定义string类型的对象s中的类成员变量_size的,或者可以理解
//为:编译器会把非常规的常量字符串:"hello world\0"处理成常规的常量字符串:"hello world",若此时是通过在该类成员
//函数的函数体中使用strlen库函数对常字符串"hello world"求长度,然后再赋值给该自定义string类型的对象s中的类
//成员变量_size的话,那么结果应该还是11、
cout << s << endl;
cout << s.size() << endl; //12
//在某一个自定义string类型的对象对应在C++标准库中的自定义的string类的类体中已经显式实现的类成员函数的函数接口
//若复用了该类体中的已经显式实现的类成员函数push_back,那么原理和上述所讲的一样、
return 0;
}
综合上述内容可得:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s; //自动调用C++标准库中的string类的类体中已经显式实现的类成员函数接口: string() ;
size_t sz = s.capacity(); //s.capacity(&s);
cout << "making s grow:\n";
cout << "capacity changed: " << sz << '\n'; //15
for (int i = 0; i < 200; ++i)
{
s.push_back('c'); //s.push_back(&s,'c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
return 0;
} //15 31 47 70 105
//由结果可以看出在VS(Windows)环境下,除了第一次,后面大概是1.5倍扩容,在Linux环境下大概是2倍扩容、
拓展知识点:
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int main() { //1、 char arr[] = "abc\0\0\0"; //注意,当我们在""或''中写入\0时,编译器不会把他们当做是两个字符,此处均会进行转义,所以上述常量字符串中 //一共存在4个无效字符'\0',但是当我们在输入终端(黑框)里面输入\0时,此时编译器则会默认他们是两个字符,不 //会对他们进行转义,这一点要记住、 cout << strlen(arr) << endl; //3、 cout << arr << endl; //注意,此时,arr代表的是字符数组首元素的地址,它的类型为char*类型,由于该类型为内置类型,所以编译器会先去 //C++标准库中的ostream类模板的类体中寻找与该类型最匹配的且已经显式实现的类成员函数接口,此时就找到了已 //经显式实现的类成员函数接口:ostream& operator<< (void* val); 但这不是最匹配的选择,所以编译器还会去C++ //标准库中的ostream类模板的类体外的全局区域寻找与该类型最匹配且已经显式实现的全局函数的函数接口,此时就 //找到了已经显式实现的全局函数接口: ostream& operator<< (ostream& os, const char* s);且与该类型最匹配, //那么此时,cout<<arr;就会被编译器替换为: operator<<(cout,arr); 调用的就是上述这个已经显式实现且与该类型 //最匹配的全局函数的函数接口,打印出来的就是字符串、 cout << (void*)arr << endl; //cout.operator<<(&cout,(void*)arr); //若此处就想打印出来字符数组首元素的地址,可以对arr进行强制类型转换,使得这里的arr强制转换为其他类型的指针变量,我们最常使用的就是void* //若强制类型转为void*的话,那么编译器会先去C++标准库中的ostream类模板的类体中寻找与该void*类型最匹配且已经显式实现的类成员函数的函数接口 //此时找到了最合适且已经显示实现的类成员函数接口:ostream& operator<< (void* val);所以会打印出这里的字符数组首元素的地址、 cout << (int*)arr << endl; //cout.operator<<(&cout,(int*)arr); //若把这里的arr强制类型转换为其他非void*类型的指针变量,比如:int*类型等等,那么编译器会先去C++标准库中的ostream类模板的类体中寻找与该int*类 //型最匹配且已经显示实现的类成员函数接口,此时就找到了已经显式实现的类成员函数接口:ostream& operator<< (void* val); 但这不是最匹配的选择 , //所以编译器还会去C++标准库中的ostream类模板的类体外的全局区域寻找与该int*类型最匹配且已经显式实现的全局函数的函数接口,但是还是没找到,所以 //编译器才会选择已经显式实现的类成员函数接口:ostream& operator<< (void* val);打印出这里的字符数组首元素的地址,注意,这里的void*代表无具体类型 //的指针变量、 //总结:当使用自定义类型的全局对象cout打印地址时,若地址的类型为char*类型,则打印出来的是该char*类型的地址对应的字符串,若地址的类型为除char*类型 //以外的其他的类型,则打印出来的就是地址,若已知某一个地址为char*类型,但又想打印出这个char*类型对应的地址,那么就强制类型转换为其他非char*类型的 //地址,比如void*,int*,short*类型的地址等等都可以、 //2、 char a = '\0'; //转义字符、 cout << a << endl; //空字符 cout << (int)a << endl; //0 //3、 int brr[] = { 1, 2, 3 }; cout << brr << endl; //此处的brr代表的是整型数组首元素的地址,即,int*类型的地址,打印出来的是整型数组首元素的地址、 return 0; }
3.8、类成员函数 reserve
注意:
编译器看到是非常规的常量字符串时,编译器会对它进行处理,把该非常规的常量字符串中显式写出来的第一个无效字符 '\0',以及其后面,直到到达该非常规的常量字符串末尾隐藏的无效字符 '\0' 前面的这些内容全部删除,比如:非常规的常量字符串:"abc\0def\0abd",(转义字符),则会被编译器处理成:常规的常量字符串: "abc" ,,但我们通常是在任何情况下,只考虑最常规的常量字符串,上述只做简单的了解即可,不必深究、
扩容是有代价的,尤其是当进行频繁扩容时,会导致整体的效率较低,在C++标准库中的string类的底层逻辑中,C++标准库中的string类的类体中的类成员变量所指向的在堆区上动态开辟的内存空间的个数为容量个数再加1个,这1个在堆区上动态开辟的内存空间是专门用来存储C++标准库中的string类的类体中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的常量字符串中末尾位置隐藏的无效字符 '\0' 的,所以这里的容量不需要考虑该常量字符串末尾位置隐藏的无效字符 '\0',若已知某一个自定义string类型的对象中的类成员变量 _size 最终的大小为100,我们可以直接使用在C++标准库中的string类的类体中已经显式实现好的类成员函数 reserve 来预留100个在堆区上动态开辟的内存空间容量,此时就不需要再进行扩容了,从而使得整体效率大为提高、
void reserve (size_t n = 0); //void reserve (string* const this,size_t n = 0);
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s; //自动调用C++标准库中的string类的类体中已经显式实现的类成员函数接口: string() ;
s.reserve(100);
size_t sz = s.capacity();
cout << "making s grow:\n";
cout << "capacity changed: " << sz << '\n'; //111
//此处涉及到内存对齐的问题,自定义string类型的对象s中的真实的容量会比100大一点,至少等于100、
for (int i = 0; i < 100; ++i)
{
s.push_back('\0');
//此时,当调用C++标准库中的string类的类体中已经显式实现的类成员函数push_back时,即使在自定义string类型的对象s中的类成员变量
//所指向的在堆区上动态开辟的内存空间中所存储的常量字符串中末尾位置隐藏的无效字符'\0'的前面插入显式的无效字符'\0',那么该自
//定义string类型的对象s中的类成员变量 _size 也会自增1,这与该类成员函数push_back的底层实现有关,具体请见模拟实现、
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
//程序执行到此,对于自定义string类型的对象s中的类成员变量 _size 的大小为100,我们可以在最初时直接预留100个在堆区上动态开辟的内存空间容量
//这样就可以使得上述程序中不进行扩容操作,大大提高了整体的效率、
return 0;
}
3.9、类成员函数 resize
在C++标准库中的string类的类体中已经显式实现了类成员函数 resize 的函数接口,具体如下所示:
void resize (size_t n); //void resize (string* const this,size_t n);
void resize (size_t n, char c); //void resize (string* const this,size_t n,char c);
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<string> using namespace std; int main() { string s; //自动调用C++标准库中的string类的类体中已经显式实现的类成员函数接口: string() ; cout<<s.size() << endl; //0 //1、 s.resize(100); //先预留100个在堆区上动态开辟的内存空间容量,但是涉及到内存对齐的问题,自定义string类型的对象s中的真实的容量会比100大一点,至少等于100, //除此之外,还会把自定义string类型的对象s中的类成员变量 _size 置为100,并且还会把自定义string类型的对象s中的类成员变量所指向的在堆区上 //动态开辟的内存空间中前100个内存空间中都存储上无效字符'\0'、 //对于代码:s.reserve(100);而言,只会预留100个在堆区上动态开辟的内存空间容量,但是涉及到内存对齐的问题,自定义string类型的对象s中的真实的 //容量会比100大一点,至少等于100,不会把自定义string类型的对象s中的类成员变量 _size 置为100,更不会把自定义string类型的对象s中的类成员变 //量所指向的在堆区上动态开辟的内存空间中前100个内存空间中都存储上无效字符'\0'、 cout << s.size() << endl; //100 2、 //s.resize(100, 'A'); 先预留100个在堆区上动态开辟的内存空间容量,但是涉及到内存对齐的问题,自定义string类型的对象s中的真实的容量会比100大一点,至少等于100, 除此之外,还会把自定义string类型的对象s中的类成员变量 _size 置为100,并且还会把自定义string类型的对象s中的类成员变量所指向的在堆区上 动态开辟的内存空间中前100个内存空间中都存储上有效字符'A'、 size_t sz = s.capacity(); cout << "making s grow:\n"; cout << "capacity changed: " << sz << '\n';//111 //此处涉及到内存对齐的问题,自定义string类型的对象s中的真实的容量会比100大一点,至少等于100、 //此处所涉及到的内存对齐的问题和内存申请有关,系统在申请内存时,一般要按照2的整数倍对齐, //和内存申请的一些内存碎片,效率和系统的内存管理,操作系统有关,了解一下即可、 for (int i = 0; i < 100; ++i) { s.push_back('c'); if (sz != s.capacity()) { sz = s.capacity(); cout << "capacity changed: " << sz << '\n'; } } return 0; }
3.10、类成员函数 clear
在C++标准库中的string类的类体中已经显式实现了类成员函数 clear 的函数接口,具体如下所示:
void clear(); //void clear(string* const this);
该类成员函数的功能是:将自定义string类型的对象s中的类成员变量 _size 置为0,并在该自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中下标为0的位置上存储无效字符 '\0' ,具体操作请见模拟实现,此时自定义string类型的对象s中的类成员变量 capacity 的值仍不变、
3.11、类成员函数 empty
在C++标准库中的string类的类体中已经显式实现了类成员函数 empty 的函数接口,具体如下所示:
bool empty() const; //bool empty(const string* this);
该类成员函数的功能是:如果自定义string类型的对象s中的类成员变量 _size 等于0的话,则该类成员函数返回true,否则返回的则是false,由于我们通常在任何情况下,只考虑最常规的常量字符串,即,不管怎么操作,编译器得到的都是最常规的常量字符串,所以,该类成员函数的功能也可以说是:只要编译器得到的最常规的常量字符串中不存在任何的有效字符,只有其末尾位置隐藏的无效字符 '\0' ,则该类成员函数返回true,否则返回的则是false,下面的内容只需要简单了解即可,不常用、
3.12、类成员函数 append
在C++标准库中的string类的类体中已经显式实现了类成员函数 append 的函数接口,具体如下所示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
//只考虑最常规的常量字符串、
string s("hello");
cout << s << endl; //hello
s.append(" world");
//在自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的
//最常规的常量字符串中的末尾位置隐藏的无效字符'\0'的前面与该常量字符串中最后一个有效
//字符的后面尾插一个最常规的常量字符串,具体底层逻辑请见模拟实现、
cout << s << endl; //hello world
string s1("!!!");
s.append(s1);
//在自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的
//最常规的常量字符串中的末尾位置隐藏的无效字符'\0'的前面与该常量字符串中最后一个有效
//字符的后面尾插一个自定义string类型的对象s1所指向的在堆区上动态开辟的内存空间中所存储的
//最常规的常量字符串,具体底层逻辑请见模拟实现、
cout << s << endl; //hello world!!!
//具体底层逻辑请见模拟实现、
return 0;
}
3.13、类成员函数 operator+=
在C++标准库中的string类的类体中已经显式实现了类成员函数 operator+= 的函数接口,具体如下所示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
//注意:只考虑最常规的常量字符串、
string s("hello");
cout << s << endl; //hello
//1、
s += ' ';
//在自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的
//最常规的常量字符串中的末尾位置隐藏的无效字符'\0'的前面与该常量字符串中最后一个有效
//字符的后面尾插一个字符(只考虑有效字符,不再考虑无效字符'\0'),具体底层逻辑请见模拟实现、
cout << s << endl; //hello空白
//2、
s += "world";
//在自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的
//最常规的常量字符串中的末尾位置隐藏的无效字符'\0'的前面与该常量字符串中最后一个有效
//字符的后面尾插一个最常规的常量字符串,具体底层逻辑请见模拟实现、
cout << s << endl; //hello world
//3、
string s1("!!!");
s += s1;
//在自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的
//最常规的常量字符串中的末尾位置隐藏的无效字符'\0'的前面与该常量字符串中最后一个有效
//字符的后面尾插一个自定义string类型的对象s1所指向的在堆区上动态开辟的内存空间中所存储的
//最常规的常量字符串,具体底层逻辑请见模拟实现、
cout << s << endl; //hello world!!!
//具体底层逻辑请见模拟实现、
return 0;
}
3.14、类成员函数 insert
在C++标准库中的string类的类体中已经显式实现了类成员函数 insert 的函数接口,具体如下所示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
//由于自定义string类型的某一个对象中的类成员变量所指向的在堆区上动态开辟的内存空间在本质上是顺序表,所以该C++标准库中的
//string类的类体中或者是显式实现在某一个命名空间中的string类的类体中的已经显式实现的类成员函数 insert 在使用时会进行挪动数据导致效率较低、
string s("hello world");
cout << s << endl; //hello world
//1、
s.insert(0, 1, 'x'); //string& insert (size_t pos, size_t n, char c);
//在最常规的常量字符串"hello world"中下标为0的位置上插入1个字符'x'、
cout << s << endl; //xhello world
//2、
s.insert(s.begin(), 'x'); //iterator insert(iterator p, char c);
//在迭代器p所对应的位置上插入字符'x'、
cout << s << endl; //xxhello world
//3、
s.insert(0, "!!!");//string& insert (size_t pos, const char* s);
//在最常规的常量字符串"hello world"中下标为0的位置上插入最常规的常量字符串"!!!"、
cout << s << endl; //!!!xxhello world
//4、
string st("sort ");//string& insert (size_t pos, const string& str);
s.insert(0,st);
//在最常规的常量字符串"hello world"中下标为0的位置上插入自定义string类型的对象st中的
//类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串"sort " 、
cout << s << endl; //sort !!!xxhello world
return 0;
}
3.15、类成员函数 erase
在C++标准库中的string类的类体中已经显式实现了类成员函数 erase 的函数接口,具体如下所示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
//由于自定义string类型的某一个对象中的类成员变量所指向的在堆区上动态开辟的内存空间在本质上是顺序表,所以该C++标准库中的
//string类的类体中或者是显式实现在某一个命名空间中的string类的类体中的已经显式实现的类成员函数erase在使用时会进行挪动数据导致效率较低、
string s("hello world");
cout << s << endl; //hello world
//1、
s.erase(s.begin());//iterator erase(iterator p);
//删除迭代器p所对应位置上的一个字符,此处就是删除最常规的常量字符串"hello world"中的首字符h、
cout << s << endl; //ello world
//2、
s.erase(3,1);//string& erase (size_t pos = 0, size_t len = npos);
//npos是C++标准库中的string类的类体中或者是显式实现在某一个命名空间中的string类的类体中的静态类成员变量、
//从最常规的常量字符串"hello world"下标为3的位置处开始,往后删除1个字符、
cout << s << endl; //ell world
3、
//s.erase();//string& erase (size_t pos = 0, size_t len = npos);
从最常规的常量字符串"ell world"下标为0的位置处开始,往后把后面所有的有效字符全部删除,即一直删除到该最常规
的常量字符串"ell world"末尾位置隐藏的无效字符'\0'的前面,此处等价于:s.erase(0,100);
//cout << s << endl;//空白
//4、
s.erase(s.begin(),s.end()-1);//iterator erase (iterator first, iterator last);
//把从迭代器 frist 到迭代器 last-1 处所对应位置上的字符全部删除, [frist,last) 左闭右开 、
cout << s << endl;//d
//上述所示的具体底层逻辑请见模拟实现、
return 0;
}
3.16、类成员函数 swap
在C++标准库中的string类的类体中已经显式实现了类成员函数 swap 的函数接口,具体如下所示:
void swap (string& str); //void swap (string* const this,string& str);
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
//此处只考虑C++98下的情况,至于C++11中,又会出现右值引用,还会存在一些其他的情况,暂时不考虑,目前只考虑C++98、
string s1("hello");
string s2("world");
//1、
s1.swap(s2);
//调用自定义string类型的对象s1所对应的在C++标准库中的string类的类体中已经显式实现的类成员函数swap的函数接口、
//效率高,本质上是对两个自定义string类型的对象s1和s2中的类成员变量中的内容进行的交换、
//2、
swap(s1, s2);
//调用C++标准库中的库函数模板(全局函数)swap、
//效率低,属于深拷贝交换,因为,C++标准库中的库函数模板(全局函数)swap如下所示:
//template <class T> void swap(T& a, T& b)
//{
// T c(a); a = b; b = c;
//}
//template <class string> void swap(string& a, string& b)
//{
// string c(a); a = b; b = c;
// //此处自动调用的分别是C++标准库中的string类的类体中已经显式实现的类成员函数:拷贝构造函数和赋值运算符重载函数
// //在这两个函数接口中进行的都是深拷贝,而深拷贝的代价比较大,从而导致整体的效率较低、
//}
//上述两个函数由于不在同一个作用域中,所以可以同名,不会造成歧义、
//就现阶段而言,上述最好使用方法1,效率较高,但是,若这两个自定义string类型的对象s1和s2中的类成员变量不涉及到深拷贝的话,则使用
//方法2也是可以的,此时两种方法的效率差不多、
//当想对两个自定义类型的对象进行交换操作的话,如果这两个自定义类型的对象中的类成员变量涉及到了深拷贝,则最好选择方法1,若不涉及深拷贝
//则使用哪种方法都是可以的,此处所说的自定义类型的对象可以是我们使用模拟实现在某一个命名空间中的类或类模板(先实例化出具有特定类型的
//具体的类)实例化出来的自定义类型的对象,也可以是使用C++标准库中的类或类模板(先实例化出具有特定类型的具体的类)实例化出来的自定义类型
//的对象、
//如果想对两个内置类型的变量进行交换操作的话,则只能使用方法2、
return 0;
}
3.17、类成员函数 c_str
在C++标准库中的string类的类体中已经显式实现了类成员函数 c_str 的函数接口,具体如下所示:
const char* c_str() const; //const char* c_str(const string* const this);
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s("hello");
cout << s << endl; //hello
cout << s.c_str() << endl; //hello 虽然这两者打印出来的结果是一样的,但在其他方面还存在不同,具体会在底层逻辑中进行阐述、
//此时调用的是自定义string类型的对象s所对应在C++标准库中的string类的类体中的已经显式实现的类成员函数c_str,该类成员函数
//返回的是自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串的首字符的
//地址,故该类成员函数的返回类型为const char* ,由于该类成员函数返回类型为char*(const char*),属于内置类型,所以这里的运算符
//<<的运算符重载,并没有调用自定义string类型的对象s所对应在C++标准库中的string类的类体外的已经显式实现的流插入运算符重载函
//数(全局函数),并且此处不会打印出地址,而是会把该地址指向的最常规的常量字符串打印出来、
return 0;
}
3.18、类成员函数 find
在C++标准库中的string类的类体中已经显式实现了类成员函数 find 的函数接口,具体如下所示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
string file("string.cpp");
size_t pos = file.find('.'); //size_t find (char c, size_t pos = 0) const;
//从自定义string类型的对象file中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串
//中的首元素开始,依次往后查找第一次出现字符 '.' 的位置并返回出去,若找不到的话,则返回的是: string::npos
if (pos != string::npos)
{
//自定义string类型的对象file对应在C++标准库中的string类的类体中的静态类成员变量npos的类型是size_t,所以
//静态类成员变量npos是一个非常大的正数,理论上,某一个自定义string类型的对象中的类成员变量所指向的在堆区
//上动态开辟的内存空间中所存储的最常规的常量字符串中的有效长度小于这个很大的正数,即,不会大于等于这个很
//大的正数,所以,如果能够找到的话,则pos一定小于string::npos,不会大于或等于,只有当找不到时,pos才会等于
//string::npos,所以,此处可以使用这个条件进行判断是否能够找到、
//方法1、
string suffix = file.substr(pos);
//在一个步骤中连续自动调用两次C++标准库中的string类的类体中已经显式实现的拷贝构造函数,VS编译器会进行优化,二合一
//只自动调用一次C++标准库中的string类的类体中已经显式实现的拷贝构造函数、
//把自定义string类型的对象file中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串中从pos
//位置开始,一直取,直到把该最常规的常量字符串中的有效字符全部取完,然后使用该子串构造一个自定义string类型的对象并返
//回(自定义类型的对象传值返回)出去、
方法2
//string suffix = file.substr(pos,file.size()-pos);
cout << file << "后缀为:" << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
//在C++标准库中的string类的类体中已经显式实现了类成员函数 substr 的函数接口,具体如下所示:
//string substr(size_t pos = 0, size_t len = npos) const; string substr (const string* const this, size_t pos = 0, size_t len = npos);
//该类成员函数是从某一个自定义string类型的对象中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串中取出该最常规的
//常量字符串的一个子串,即,从该最常规的常量字符串的pos位置开始,往后取len长度个字符,并且使用这个子串构造一个自定义string类型的对象返回出去,
//由于是在该类成员函数的函数体内使用该子串构造的一个自定义string类型的对象,属于局部对象,所以在返回时只能使用传值返回,不可使用传引用返回、
return 0;
}
3.19、类成员函数 rfind
在C++标准库中的string类的类体中已经显式实现了类成员函数 rfind 的函数接口,具体如下所示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
//1、
//string file("string.cpp");
//size_t pos = file.find('.'); //size_t find (char c, size_t pos = 0) const;
从自定义string类型的对象file中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串
中的首元素开始,依次往后查找第一次出现字符 '.' 的位置并返回出去,若找不到的话,则返回的是: string::npos
//2、
string file("string.cpp.tar.zip");
//如果自定义string类型的对象file中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串
//中的有效字符中存在多个字符 '.',比如:"string.cpp.tar.zip" ,此时若想要取出来后缀 .zip 的话,再使用上述方法就不
//行了,因此,可以进行以下操作:
size_t pos = file.rfind('.'); //size_t rfind (char c, size_t pos = 0) const;
//从自定义string类型的对象file中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串
//中最后一个有效字符开始,依次往前查找第一次出现字符 '.' 的位置,并返回出去,若找不到的话,则返回的是: string::npos
if (pos != string::npos)
{
//自定义string类型的对象file对应在C++标准库中的string类的类体中的静态类成员变量npos的类型是size_t,所以
//静态类成员变量npos是一个非常大的正数,理论上,某一个自定义string类型的对象中的类成员变量所指向的在堆区
//上动态开辟的内存空间中所存储的最常规的常量字符串中的有效长度小于这个很大的正数,即,不会大于等于这个很
//大的正数,所以,如果能够找到的话,则pos一定小于string::npos,不会大于或等于,只有当找不到时,pos才会等于
//string::npos,所以,此处可以使用这个条件进行判断是否能够找到、
//方法1、
string suffix = file.substr(pos);
//在一个步骤中连续自动调用两次C++标准库中的string类的类体中已经显式实现的拷贝构造函数,VS编译器会进行优化,二合一
//只自动调用一次C++标准库中的string类的类体中已经显式实现的拷贝构造函数、
//把自定义string类型的对象file中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串中从pos
//位置开始,一直取,直到把该最常规的常量字符串中的有效字符全部取完,然后使用该子串构造一个自定义string类型的对象并返
//回(自定义类型的对象传值返回)出去、
方法2
//string suffix = file.substr(pos,file.size()-pos);
cout << file << "后缀为:" << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
//在C++标准库中的string类的类体中已经显式实现了类成员函数 substr 的函数接口,具体如下所示:
//string substr(size_t pos = 0, size_t len = npos) const; string substr (const string* const this, size_t pos = 0, size_t len = npos);
//该类成员函数是从某一个自定义string类型的对象中的类成员变量所指向的在堆区上动态开辟的内存空间中所存储的最常规的常量字符串中取出该最常规的
//常量字符串的一个子串,即,从该最常规的常量字符串的pos位置开始,往后取len长度个字符,并且使用这个子串构造一个自定义string类型的对象返回出去,
//由于是在该类成员函数的函数体内使用该子串构造的一个自定义string类型的对象,属于局部对象,所以在返回时只能使用传值返回,不可使用传引用返回、
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
//url是网址、
string url1("http://www.cplusplus.com/reference/string/string/find/");
string url2("https://mp.csdn.net/mp_blog/creation/editor/126266709");
//注意:
//网络协议: http和https
//域名: www.cplusplus.com和mp.csdn.net 域名最后会转换成IP、
//目录: reference/string/string/find/和mp_blog/creation/editor/126266709
//如何将一个网址分离出:网络协议,域名和目录?
string& url = url1;
//不再自动调用C++标准库中的string类的类体中已经显式实现的类成员函数:拷贝构造函数、
//1、网络协议:
size_t pos1 = url.find("://");//size_t find (const char* s, size_t pos = 0) const;
//此时无符号整型size_t变量pos1是自定义string类型的对象url1或url2中的类成员变量所指向的
//在堆区上动态开辟的内存空间中所存储的最常规的常量字符串中的子串://中起始位置的字符':'
//的下标、
if (pos1 != string::npos)
{
string protocol = url.substr(0, pos1);
cout << "protocol:" << protocol << endl;
}
else
{
cout << "非法的url" << endl;
}
//2、域名:
size_t pos2 = url.find('/',pos1+3);
if (pos2 != string::npos)
{
string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << "domain:" << domain << endl;
}
else
{
cout << "非法的url" << endl;
}
//3、目录
string uri = url.substr(pos2+1);
cout << "uri:" << uri << endl;
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s("hello world");
size_t pos = s.find("worxxx",0,3);//size_t find (const char* s, size_t pos, size_t n) const;
//此时无符号整型size_t变量pos是自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟
//的内存空间中所存储的最常规的常量字符串"hello world"中的子串wor中起始位置的字符'w'的下标、
if (pos != string::npos)
{
string A = s.substr(pos);
cout << A << endl;
}
else
{
cout << "Fail" << endl;
}
return 0;
}
4、C++标准库中string类的类体外已显示实现的常见的非类成员函数(全局函数)接口
4.1、全局的关系运算符重载函数(string)
在C++标准库中的string类的类体外已经显式实现了常见的全局的关系运算符重载函数的函数接口,具体如下所示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1("hello world");
string s2("string");
//1、
//运算符<的运算符重载、
s1 < s2; //operator<(s1,s2);
//bool operator< (const string& lhs, const string& rhs);
//2、
//运算符<的运算符重载、
s1 < "string"; //operator<(s1,"string");
//bool operator< (const string& lhs, const char* rhs);
//3、
//运算符<的运算符重载、
"hello world" < s2; //operator<("hello world",s2);
//bool operator< (const char* lhs, const string& rhs);
//由于2和3可以转换为如下所示:
s1 < string("hello");//匿名对象,运算符<的运算符重载、
string("hello world") < s2;//匿名对象,运算符<的运算符重载、
//除此之外,2和3中的最常规的常量字符串"string"和"hello world",会进行隐式类型转换,
//使用这两个最常规的常量字符串先自动调用C++标准库中的string类的类体中已经显式实现
//的构造函数,从而构造出两个临时的自定义string类型的对象,再在2和3中与自定义string类型
//的对象s1和s2进行比较(运算符<的运算符重载),故,由此可知,在C++标准库中的string类的类外
//只显式实现非类成员函数的函数接口:bool operator< (const string& lhs, const string& rhs);
//即可满足要求、
return 0;
}
4.2、全局的加法运算符重载函数 operator+ (string)
在C++标准库中的string类的类体外已经显式实现了全局的加法运算符重载函数 operator+ (string)的函数接口,具体如下所示:
缺点就是:自定义string类型的对象传值返回,会自动调用C++标准库中的string类的类体中的已经显式实现的类成员函数:拷贝构造函数,涉及到深拷贝,导致效率较低、
4.3、例题1
字符串最后一个单词的长度 字符串最后一个单词的长度_牛客题霸_牛客网
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str;
//cin >> str;
//由于自定义类型的全局对象cin和自定义string类型的对象str都属于自定义类型的对象,所以此处是运算符>>的运算符重载,编译器会自动调用C++标准
//库中的string类的类体外已经显式实现的全局函数operator>>、
//此时在黑框里输入一个字符集(除了回车字符'\n'),除了回车字符'\n'之外的所有的字符,包括除了回车字符'\n'之外的空白字符:空格,Tab制表符等等
//则编译器认为都是该字符集中的内容,但若输入回车字符'\n',就代表着本次字符集的输入到此结束,此时,缓冲区内有:编译器认为是字符集中的内容以
//及最后面的一个回车字符'\n',但是编译器在缓冲区内读取时候,不会读取到任何空白字符包括:空格,Tab制表符,回车字符'\n'等等,只能读取到缓冲区
//中第一个空白字符前面的所有的非空白字符,并且缓冲区内第一个空白字符也不会被读取到,然后再在本次从缓冲区中读取到的所有的非空白字符的后面
//自动加上一个无效字符'\0',从而构成一个最常规的常量字符串,用来构造这里的自定义string类型的对象str、
//若在此处使用C++标准库中的string类的类体外已经显式实现的全局函数getline的话,那么编译器在缓冲区内,能够读取到除了回车字符'\n'之外的所有
//的字符,编译器会从缓冲区的起始位置开始读取,一直往后读取,直到遇到缓冲区内的回车字符'\n',才停止读取,并且不会把这个回车字符'\n'读取到,
//然后再在从缓冲区内读取到的所有的字符的末尾自动加上一个无效字符'\0'从而构成一个最常规的常量字符串用来构造这里的自定义string类型的对象
//str、
//若在黑框里输入ABCDEF T,然后敲回车,则缓冲区内有ABCDEF T\n,而对于代码cin>>str;而言,黑框中,在输入没结束前,默认以回车,Tab制表符和空格进行分割,当输入要结束时,则回车字符,Tab制表符和空格字符,
//均充当结束的标志,只不过,此时若敲回车,则黑框直接消失,而若敲空格字符或者Tab制表符,则黑框不会消失,当编译器在缓冲区内进行读取时,当整体的读取还没完成的时候,则编译器会认为回
//车字符,Tab制表符和空格字符均是分隔符,当读取将要完成的时候,则编译器会认为回车,Tab制表符和空格字符都是结束标志,此时,缓冲区内有:ABCDEF T\n,编译器读取到ABCDEF,此时遇到了空格字符,又因此时只进
//行了cin>>str,并未进行连续输入,所以编译器已经整体读取完毕,则编译器会认为这个空格字符为结束标志,就不再读取它,所以编译器在缓冲区中只读取到了ABCDEF
//此时可以使用C++标准库中的string类的类体外已经显式实现的全局函数getline,该全局函数在黑框中输入时,若没结束,则认为回车字符'\n'为分割标志,当输入要结束时,则认为回车字符'\n'为结束标志,此时敲回车
//,则黑框直接消失,当编译器在缓冲区内进行读取时,若整体的读取还没完成的时候,则编译器会认为回车字是分割符,当整体的读取将要完成的时候,则编译器会认为回车字符是结束标志,此时,缓冲区内有:ABCDEF T\n,
//编译器读取到ABCDEF T,此时遇到了回车字符,又因此时只进行了getline(cin, str);,并未进行连续输入,所以编译器已经整体读取完毕,则编译器则认为这个回车字符为结束标志,就不再读取它,所以编译器在缓冲区
//中读取到了ABCDEF T
//在C++标准库中的string类的类体外已经显式实现了全局函数getline,具体如下所示:
//C++98
//(1)istream& getline(istream& is, string& str, char delim);//不常用、
//(2)istream& getline(istream& is, string& str);
getline(cin, str);//istream& getline(istream& is, string& str);
size_t pos = str.rfind(' ');
if (pos != string::npos)
{
cout << str.size() - pos-1 <<endl;
}
else
{
cout << str.size() << endl;
}
return 0;
}
注意:
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int main() { char a = 0, b = 0; cin >> a >> b; //第一种解释:等价于:scanf("%c %c", &a, &b); 并非 scanf("%c%c", &a, &b); cout << a << " " << b << endl; return 0; //第二种解释见上述例题1中的文字: //当在黑框中输入A B,再敲回车,则缓冲区内有:A B\n,对于代码cin>>a>>b;而言,黑框中, 在输入没结束前,默认以回车, Tab制表符和空格进行分割,当输入要结束时,则回车字符,Tab制表符和空格字符 //均充当结束的标志,只不过,此时若敲回车,则黑框直接消失,而若敲空格字符或者Tab制表符,则黑框不会消失,当编译器在缓冲区内进行读取时,当整体的读取还没完成的时候,则编译器会认为回车字符, //Tab制表符和空格字符均是分隔符,当读取将要完成的时候,则编译器会认为回车,Tab制表符和空格字符都是结束标志,此时,缓冲区内有:A B\n,编译器读取到A,并把字符A赋值给字符变量a,然后编译器 //再读取时,遇到了空格字符,但此时进行了cin >> a >> b;,连续输入,所以编译器整体读取并未完毕,则编译器会认为这个空格字符为分割标志,所以编译器会自动跳过这个分割标志(空格字符),再读取 //时,就读取到了字符B,并把字符B赋值给了字符变量b,然后编译器遇到了回车字符'\n',而此时,编译器已经整体读取完毕,则编译器会认为这个回车字符为结束标志,就不再读取它,所以字符变量a读取到 //了字符A,字符变量b读取到了字符B、 }
4.4、例题2
//暴力求解:依次拿自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中 //所存储的最常规的常量字符串中的每一个有效字符去与其他所有的有效字符去比较,观察两者是否相同 //此时若该最常规的常量字符串的有效长度为N的话,则时间复杂度就是:O(N^2),效率太低,不选择该方法、 class Solution { public: int firstUniqChar(string s) { //哈希映射: //方法一: //统计该最常规的常量字符串中的每个有效字符出现的次数、 int count[26] = { 0 }; for (auto ch : s) { count[ch - 'a'] += 1; } for (size_t i = 0; i < s.size(); i++) { if (count[s[i]-'a'] == 1) { return i; } } return -1; } //时间复杂度:O(N)、 };
//暴力求解:依次拿自定义string类型的对象s中的类成员变量所指向的在堆区上动态开辟的内存空间中 //所存储的最常规的常量字符串中的每一个有效字符去与其他所有的有效字符去比较,观察两者是否相同 //此时若该最常规的常量字符串的有效长度为N的话,则时间复杂度就是:O(N^2),效率太低,不选择该方法、 class Solution { public: int firstUniqChar(string s) { //哈希映射: //方法二: //统计该最常规的常量字符串中的每个有效字符出现的次数、 int count[256] = { 0 }; size_t size = s.size(); for (size_t i = 0; i < size; i++) { count[s[i]] += 1; } for (size_t i = 0; i < size; i++) { if (count[s[i]] == 1) { return i; } } return -1; } //时间复杂度:O(N)、 };
4.5、例题3
字符串相加(大数运算) 力扣
//CPU的指令支持整数的加减乘除、
//当进行大数运算(整数)时,可以使用内建的用于处理大整数的库比如(BigInteger),这属于第三方库,C++的标准库中不存在这些库,对于大整数运算使用的比较少,只在一些特殊的场景下使用,所以C++标准库中没有设置、
class Solution {
public:
string addStrings(string num1, string num2)
{
int end1=num1.size()-1;
int end2=num2.size()-1;
//进位、
int carry=0;
string retstr;
while(end1>=0 || end2>=0)
{
int val1=end1>=0?num1[end1]-'0':0;
int val2=end2>=0?num2[end2]-'0':0;
int ret=val1+val2+carry;
if(ret>9)
{
ret -=10;
carry=1;
}
else
{
carry=0;
}
方法1:
//retstr.insert(retstr.begin(),'0'+ret);
此时,效率较低,头插(持续头插)需要挪动数据,此处的执行次数为:1+2+3+...+N ,此处的时间复杂度是:O(N^2),不管进不进下面的if语句,则总的时间复杂度都是O(N^2),所以此处尽量选择使用尾插,最后再进行逆置、
//方法2:
retstr += ('0'+ret);
end1--;
end2--;
}
if(carry == 1)
{
方法1:
//retstr.insert(retstr.begin(),'1');
//方法2:
retstr += '1';
}
方法1:
//return retstr;
//方法2:
//算法中有逆置,不需要自己写,在头文件<algorithm>中,存在逆置函数reserve,具体在后期再进行阐述,也可以自己实现逆置、
//template<class BidirectionalIterator>
//void reverse (BidirectionalIterator frist,BidirectionalIterator last);
//此处的迭代器有要求,后期再进行具体的阐述,现在会使用就行、
reverse(retstr.begin(),retstr.end());//C++中的迭代器区间一定要给 左闭右开 、
//总执行次数为:2N,则时间复杂度为:O(N)、
return retstr;
}
};