【C++】string类基础知识

文章目录

  • 1.为什么学习string类?
    • [1.1 C语言中的字符串](#1.1 C语言中的字符串)
    • [1.2 引言(了解)](#1.2 引言(了解))
  • [2. auto和范围for auto关键字](#2. auto和范围for auto关键字)
    • [2.1 auto](#2.1 auto)
    • [2.2 范围for](#2.2 范围for)
  • 3.标准库中的string类
    • [3.1 string类(了解)](#3.1 string类(了解))
    • [3.2 string类的常用接口说明](#3.2 string类的常用接口说明)
    • [3.3 string类对象的容量操作](#3.3 string类对象的容量操作)
    • [3.4 string类对象的访问及遍历操作](#3.4 string类对象的访问及遍历操作)
    • [3.5 string类对象的修改操作](#3.5 string类对象的修改操作)
    • [3.6 string类非成员函数](#3.6 string类非成员函数)

1.为什么学习string类?

1.1 C语言中的字符串

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合面向对象的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

1.2 引言(了解)

ASCII不能表示全世界的文字,产生了Unicode

Unicode 是一种字符集,旨在为世界上所有的字符分配一个唯一的编号,解决不同编码方式之间的兼容性问题。

UTF-8 是 Unicode 的一种实现方式,属于变长编码。

但是中文里还有繁体字,于是又有了GBK,GBK用两个字节表示一个汉字

2. auto和范围for auto关键字

2.1 auto

在这里补充2个C++11的小语法,方便我们后面的学习。

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。

1.C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。(通过初始化表达式值类型自动推荐对象类型)

2.用auto声明指针类型时,用auto和auto*都可以 ,但用auto*时右边一定是指针,用auto声明引用类型时则必须加&

cpp 复制代码
int i=0;
int& r1=i;
auto r2=r1;//r2此时不是引用,是整型
auto& r3=r1;//此时r3是引用

3.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

4.auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

auto不能直接用来声明数组

cpp 复制代码
#include<iostream>
using namespace std;
int func1()
{
	 return 10;
}
// 不能做参数
void func2(auto a)
{}
// 可以做返回值,但是建议谨慎使用
auto func3()
{
 	return 3;
}
int main()
{
	 int a = 10;
	 //通过初始化表达式值类型自动推荐对象类型
	 auto b = a;
	 auto c = 'a';
	 auto d = func1();
	 // 编译报错:rror C3531: "e": 类型包含"auto"的符号必须具有初始值设定项
	 auto e;
	 cout << typeid(b).name() << endl;
	 cout << typeid(c).name() << endl;
	 cout << typeid(d).name() << endl;
	 int x = 10;
	 auto y = &x;
	 auto* z = &x;
	 auto& m = x;
	 cout << typeid(x).name() << endl;
	 cout << typeid(y).name() << endl;
	 cout << typeid(z).name() << endl;
	 auto aa = 1, bb = 2;
	 // 编译报错:error C3538: 在声明符列表中,"auto"必须始终推导为同一类型
	 auto cc = 3, dd = 4.0;
	 // 编译报错:error C3318: "auto []": 数组不能具有其中包含"auto"的元素类型
	 auto array[] = { 4, 5, 6 };
 	 return 0;
}
cpp 复制代码
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	 std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };
	 // auto的用武之地
	 //std::map<std::string, std::string>::iterator it = dict.begin();
	 auto it = dict.begin();
	 while (it != dict.end())
	 {
		 cout << it->first << ":" << it->second << endl;
		 ++it;
	 }
	 return 0;
}

2.2 范围for

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。

for循环后的括号由冒号" :"分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,++自动迭代++,自动取容器数据赋值,自动判断结束++ 。
++范围for可以作用到数组和容器对象上进行遍历++

范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到,支持迭代器的容器,都可以用范围for

cpp 复制代码
//auto后面的名字可以随便取
//也可以直接写成for(char ch:s1)
for(auto ch:s1)
{
	cout<<ch<<' ';
}
for(auto e:lt)
{
	cout<<e<<' ';
}
cpp 复制代码
//想要修改,给类型加上引用
for(auto& ch:s1)
{
	ch-=1;
}
cout<<endl;
//最好加上const
for(const auto& ch:s1)
{
	cout<<ch<' ';
}
cpp 复制代码
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
 int array[] = { 1, 2, 3, 4, 5 };
 // C++98的遍历
 for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
     array[i] *= 2;
}
 for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
     cout << array[i] << endl;
}
 // C++11的遍历
 for (auto& e : array)
     e *= 2;
 for (auto e : array)
     cout << e << " " << endl;
 string str("hello world");
 for (auto ch : str)
{
     cout << ch << " ";
}
 cout << endl;
 return 0;
}*

3.标准库中的string类

3.1 string类(了解)

ps:文章里的蓝色字体都是超链接,可以点击跳转!
string类的文档介绍

在使用string类时,必须包含#include<string>和using m=namespace

3.2 string类的常用接口说明

1.string类对象的常见构造

(constructor)函数名称 功能说明
string()(重点) 构造空的string类对象,即空字符串
string(const char* s)(重点) 用C-string来构造string类对象
string(size_t n,char c) string类对象中包含n个字符c
string(const string&s)(重点) 拷贝构造函数
  • 代码演示
cpp 复制代码
void Teststring()
{
 string s1;                // 构造空的string类对象s1
 string s2("hello world");   // 用C格式字符串构造string类对象s2
 string s3(s2);            // 拷贝构造s3

//第三种构造函数
 string s4(s2,0,5);      //从s2的首字符位置拷贝5个字符给s4
 string s5(s2,6);  //从s2第6个字符位置开始拷贝到结尾 
 //第五种
 string s6("hello world",6);//拷贝前6个字符
 //第六种
 string s7(10,'*');//拷贝构造10个*给s7
 
 string s8="xxxx";//也可以直接赋值
}

3.3 string类对象的容量操作

函数名称 功能说明
size (重点) 返回字符串有效字符长度(不包含结尾的'\0'
length 返回字符串有效字符长度(不包含结尾的'\0'
capacity 返回空间总大小(容量)(不包含结尾的'\0'
empty(重点) 检测字符串释放为空串,是返回true,否则返回false
clear(重点) 清空有效字符(对空间的容量没有影响)
reserve(重点) 为字符串预留空间
resize(重点) 将有效字符的个数改成n个,多出的空间用字符c填充



缩容,但是一般情况下不建议缩容,缩容是一种以时间换空间的操作,但实际上时间比空间宝贵

string容量相关方法使用代码演示

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;

void TestCapacity()
{
	string s("hello world");
	//max_size()返回字符串可达到的最大长度,但有时候不准
	cout << s.max_size() << endl;
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << "-----------------" << endl;

	s.clear();//会清理有效字符,但是不影响capacity大小
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << "-----------------" << endl;

	string s1("hello world");
	size_t old = s1.capacity();
	cout << s1.capacity() << endl;
	for (size_t i = 0; i < 100; i++)
	{
		s1.push_back('x');
		if (s1.capacity() != old)
		{
			cout << s1.capacity() << endl;
			old = s1.capacity();
		}
	}
	cout << "-----------------" << endl;

	string s2("hello world");
	//s2.reverse(200);//确定知道要插入多少字符,提前扩容
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	s2.reserve(20);//手动扩容
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	cout << "-----------------" << endl;

	//缩容,但是一般情况下不建议缩容
	s2.shrink_to_fit();
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	cout << "-----------------" << endl;

	string s3("helloworld");
	cout << s3 << endl;
	s3.resize(5);
	cout << s3 << endl;
	s3.resize(10, 'x');
	cout << s3 << endl;
	cout << "-----------------" << endl;
}
int main()
{
	TestCapacity();

	return 0;
}

注意:

1.size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。

2.clear()只是将string中有效字符清空,不改变底层空间(capacity)大小。

3.4 string类对象的访问及遍历操作

函数名称 功能说明
operator[](重点) 返回pos位置的字符 const string类对象调用
begin+ end begin 获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rend begin获取一个字符的迭代器 + end获取最后一个字符下一个置的迭代器
范围for C++11支持更简洁的范围for的新遍历方式

所以string有三种遍历

下标[]

迭代器

范围for




迭代器的特点

1.提供统一的方式遍历修改容器

2.算法可以泛型化,算法借助迭代器处理容器的数据

  • string中元素访问及遍历代码演示
cpp 复制代码
#include<iostream>
#include<string>
#include<list>
 
using namespace std;

void Print(const string& s)
{
	//const string::iterator it1=s.begin();
	//这个const修饰的是迭代器,那么it1不能++,也就不能迭代
	//所以我们应该修饰的是内容不能修改

	string::const_iterator it1 = s.begin();
	while (it1 != s.end())
	{
		//不能修改
		//*it1='x';
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;

	string::const_reverse_iterator it2 = s.rbegin();
	while (it2 != s.rend())
	{
		//不能修改
		//*it2='x';
		cout << *it2 << " ";
		++it2;
	}
	cout << endl;

}

//operator[]的内部逻辑
//class string
//{
//public:
//	char& operator[](size_t pos)
//	{
//		assert(pos<_size);
//		return _str[pos];
//	}
//private:
//	char* _str;
//	size_t _size;
//	size_t _capacity;
//};
void test_string()
{
	string s1("hello world");
	cout << s1 << endl;

	s1[0] = 'x';//像数组一样得益于operator[]函数
	cout << s1 << endl;
	cout << s1[0] << endl;

	//越界有严格的断言检查
	//s1[12];断言
	//s1.at(12);//抛异常
	
	//不包含'\0'
	cout << s1.size() << endl;//推荐用
	cout << s1.length() << endl;

	//遍历or修改
	for (size_t i = 0; i < s1.size(); i++)
	{
		s1[i]++;
	}
	cout << s1 << endl;

	//行为像指针一样的东西
	string::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		//可以读,也可以写
		(*it1)--;
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;
	
	//所有容器都适用迭代器
	// 列表
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	list<int>::iterator lit = lt.begin();
	while (lit != lt.end())
	{
		//可以读,也可以写
		(*lit)--;
		cout << *lit << " ";
		++lit;
	}
	cout << endl;
	Print(s1);

	
}

int main()
{
	try
	{
		test_string();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

3.5 string类对象的修改操作

函数名称 功能说明
push_back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= (重点) 在字符串后追加字符串 str
c_str(重点) 返回C格式字符串
find + npos(重点) 从字符串pos位置开始往后找字符c,返回该字符在字符串中的点) 位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 在str中从pos位置开始,截取n个字符,然后将其返回
  • string中查找代码演示
cpp 复制代码
#include<iostream>
#include<string>
#include<list>
#include<algorithm>  
using namespace std;

void test_string()
{
	string s1("hello world");
	cout << s1 << endl;
	
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	list<int>::iterator lit = lt.begin();
	while (lit != lt.end())
	{
		(*lit)--;
		cout << *lit << " ";
		++lit;
	}
	cout << endl;


	//查找
	string::iterator ret1 = find(s1.begin(),s1.end(), 'h');
	if (ret1 != s1.end())
	{
		cout << "找到了h" << endl;
	}
	list<int>::iterator ret2 = find(lt.begin(), lt.end(), 2);
	if (ret2 != lt.end())
	{
		cout << "找到了2" << endl;
	}
}

int main()
{
	test_string();
	return 0;
} 


补充:c_str()

  • 概念

c_str() 是 C++ 标准库中 std::string 类的一个成员函数,它返回一个指向以空字符('\0')结尾的字符数组的指针,该数组包含了与 string 对象相同的字符序列。简单来说,它将 C++ 风格的 string 转换为 C 风格(以 \0 结尾)的字符串。

  • 主要用途

与 C 语言函数交互:许多 C 库函数(如 printf、strlen、fopen 等)要求传入 const char* 类型的参数。通过 c_str() 可以方便地将 std::string 传递给这些函数。

cpp 复制代码
int main()
{
	string filename("Test.cpp");
	FILE* fout = fopen(filename.c_str(), "r");
	if (fout)
	{
		cout << "打开文件成功" << endl;
	}
	return 0;
}

需要以 \0 结尾的场景:某些底层接口或算法要求字符串必须以 \0 结尾使用 c_str() 可以确保满足此条件

  • 返回值类型

const char*:指向只读字符数组的指针。不可通过该指针修改字符串内容(若需修改,可使用 data() 的非 const 版本,但需谨慎)。

  • 为什么需要 c_str()?

1.保证以 \0 结尾:std::string 的内部存储不一定在末尾包含 \0。虽然大多数现代实现(尤其是 C++11 之后)会在字符串末尾额外存储一个 \0 以方便转换,但标准并未强制要求,且即使有,也可能在字符串修改后失效。c_str() 会确保返回的字符数组总是以 \0 结尾,并且内容与当前 string 对象一致。

2.与 C 语言接口兼容:许多系统调用、库函数(如 fopen、strcmp、printf)以及旧代码都要求传入 C 风格字符串。通过 c_str(),我们可以安全地将 std::string 传递给这些函数。

3.明确意图:即使 std::string 内部可能已经有 \0 结尾,使用 c_str() 能清晰地表明此处需要一个 C 风格字符串,而不是直接操作内部数据。



返回不是"abcdefghijklmnopqrstuvwxyz"的字符


  • string中修改代码演示
cpp 复制代码
#include<iostream>
#include<string>
using namespace std;

void test_string()
{
	string s1("hello world");
	s1.push_back('%');
	s1.append("hello bit");
	cout << s1 << endl;

	s1.append(10,'#');
	cout << s1 << endl;
	cout << "--------------" << endl;

	string s2(" hello bit!");
	//如果不想要" hello bit!"前面的空格和感叹号
	//左闭右开[begin,end)
	s1.append(++s2.begin(), --s2.end());
	//end指向'!',不会打印
	cout << s1 << endl;
	cout << "--------------" << endl;

	string s3("hello world");
	s3 += ' ';
	s3 += "hello bit";
	cout << s3 << endl;

	//s3本身不改变
	cout << s3 + "xxxx" << endl;
	cout << "xxxx" + s3 << endl;
	
	//赋新值
	s3.assign("yyy");
	cout << s3 << endl;
	cout << "--------------" << endl;

	//在第一个字符位置插入"xxx"
	string s4("hello world");
	s4.insert(0, "xxx");
	cout << s4 << endl;
	//不能这样写
	//s4. insert(5, 'x');
	//只能这样写
	//在第六个字符之前插入一个'x'
	s4.insert(5, 1, 'x');
	cout << s4 << endl;
	s4.insert(s4.begin(), '$');
	cout << s4 << endl;
	cout << "--------------" << endl;

	string s5("hello world");
	s5.erase(s5.begin());//头删
	cout << s5 << endl;
	s5.erase(0, 1);//头删
	cout << s5 << endl;
	s5.erase(5, 2);//删除从第五个字符后的两个字符
	cout << s5 << endl;
	s5.erase(5);//删除从第五个字符后所有的字符
	cout << s5 << endl;
	cout << "--------------" << endl;

	string s6("hello world");
	cout << s6 << endl;
	s6.replace(5, 1, "%%%");
	cout << s6 << endl;
	s6.replace(5, 3, "*");
	cout << s6 << endl;
	cout << "--------------" << endl;	

}
int main()
{
	test_string();
	return 0;
}

牛刀小试:将所有空格替换为%%

cpp 复制代码
string s7("hello world  hello bit");
size_t pos=s7.find(' ');
//没找到返回的是string类的npos
while (pos != string::npos)
{
	s7.replace(pos, 1, "%%");
	//找下一个空格   
	//此时' '替换为了"%%",pos要+2
	pos = s7.find(' ', pos + 2);
}
cout << s7 << endl;
cout << "--------------" << endl;

string s8;
//可以加上这句减少扩容
s8.reserve(s7.size());
for (auto ch : s7)
{
	if (ch != ' ')
		s8 += ch;
	else
		s6 += "%%";
}
cout << s8 << endl;
cout << "--------------" << endl;

注意:

在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好

cpp 复制代码
int main()
{
	string filename("Test.cpp");
	FILE* fout = fopen(filename.c_str(), "r");
	if (fout)
	{
		cout << "打开文件成功" << endl;
	}
	cout << "******************" << endl;
	size_t pos = filename.find('.');
	if(pos!=string::npos)
	{
		string suffix = filename.substr(4);
		cout << suffix << endl;
	}
	string ur1 = "https://cplusplus.com/reference/string/string/rfind/";
	//分离网络协议
	size_t i1 = ur1.find(':');
	if (pos != string::npos)
	{
		//protocol协议
		string protocol = ur1.substr(0, i1);
		cout << protocol << endl;
		//分离域名
		size_t i2 = ur1.find('/', i1 + 3);
		if (i2 != string::npos)
		{
			string domain = ur1.substr(i1 + 3, i2 - (i1 + 3));
			cout << domain << endl;
			//分离最后
			string uri = ur1.substr(i2 + 1);
			cout << uri << endl;
		}
	}
	cout << "******************" << endl;

	string str("hello world");
	cout << str + "xxxx"<<endl;
	cout << "xxxx" + str<<endl;
	//C++11以后,传值返回对象效率都很不错
	string ret = str + "****";
	cout << ret << endl;
	cout << "******************" << endl;

	string s2;
	cin >> str >> s2;
	cout << str << endl;
	cout << s2 << endl;
	cout << "******************" << endl;

	return 0;
}

3.6 string类非成员函数

函数 功能说明
operator+ 尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点) 输入运算符重载
operator<< (重点) 输出运算符重载
getline (重点) 获取一行字符串
relational operators (重点) 大小比较

string类中还有一些其他的操作,这里不一一列举,大家在需要用时查文档即可。

相关推荐
笑鸿的学习笔记2 小时前
qt-C++语法笔记之Qt中的delete ui、ui的本质与Q_OBJECT
c++·笔记·qt
PyAIGCMaster2 小时前
开发了一个全自动接入wordpress的saas发文章的网站,记录一下如何实现,有需要的朋友联系。
java·开发语言·数据库
研究点啥好呢2 小时前
3月21日GitHub热门项目推荐|攻守兼备,方得圆满
java·c++·python·开源·github
m0_528174452 小时前
ZLibrary反爬机制概述
开发语言·c++·算法
xiangpanf2 小时前
PHP vs Python:30字看透两大语言差异
开发语言·php
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——责任链模式
c++·笔记·设计模式·责任链模式
江沉晚呤时2 小时前
.NET 9 快速上手 RabbitMQ 直连交换机:高效消息传递实战指南
开发语言·分布式·后端·rabbitmq·.net·ruby
yunyun321232 小时前
嵌入式C++驱动开发
开发语言·c++·算法
左左右右左右摇晃2 小时前
Java笔记 —— 值传递与“引用传递”
java·开发语言·笔记