【C++】深入理解string类(1)

目录

一  认识了解string类

 1 为什么学习string类?

2. 标准库中的string类 

二  string类的常用接口说明

1. string类对象的常见构造

2 赋值

3 遍历和修改

4 数据个数

5 迭代器

6 迭代器的分类

(1)begin

(2)rbegin

(3)cbegin

7 查找算法

三 auto和范围for

auto关键字

范围for

四 完整代码


 在使用string类的时候,要加上头文件#include<string>


一  认识了解string类

 1 为什么学习string类?

1.1string类的定义

在 C++ 中,string 类是标准库(STL 的一部分)提供的用于处理字符串的模板类,定义在 <string> 头文件中,属于 std 命名空间。它封装了字符串的存储和操作,相比 C 语言中的字符数组(char*),string 类提供了更安全、便捷的字符串处理方式。

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

1.3 两个面试题(暂不做讲解) 

https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&&tqId=11202&rp=6&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking

https://leetcode-cn.com/problems/add-strings

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、 快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

2. 标准库中的string类 


2.1 string类

string类的文档介绍:

   http://www.cplusplus.com/reference/string/string/?kw=string

当我们在看这个文档的时候发现,里面几乎是全英文的界面,我们要自己学会看文档,当然也可以使用翻译软件,但是翻译出来的意思总是和原本要表达的意思差点“味道”,所以更推荐自己阅读英语的文档(上半年C++网站的官方中文版下线了)

在使用string类时,必须包含#include头文件以及using namespace std;

二  string类的常用接口说明


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) (重点)拷贝构造函数
void Teststring()
{
    string s1;                // 构造空的string类对象s1
    string s2("hello bit");   // 用C格式字符串构造string类对象s2
    string s3(s2);            // 拷贝构造s3
}

我们来学习一下第三个:

使用方法翻译过来是这样的:复制从pos位置开始并跨越len字符的str部分,如果str太短或len是string::npos,则直到str的末尾。

我们来使用一下:

void test_string1()
{

	string s4(s2, 0, 5);
	cout << s4 << endl;

	// pos位置拷贝结尾
	string s5(s2, 6, 15);
	cout << s5 << endl;

	string s6(s2, 6);
	cout << s6 << endl;

}

如果要复制的长度超过了字符串的长度,编译器不会报错也不会越界,只会拷贝到字符串的末尾。

如果确定了要拷贝到末尾,可以直接省去第三个参数,如上面所示的s6。

但是这个接口我们并不常用,只是通过这个接口,我们了解到如何去阅读文档。

再引入一下第五个:

void test_string1()
{
   //只取前六个字符
    string s7("hello world", 6);
	cout << s7 << endl;

	//给s8赋值为十个*
    string s8(10, '*');
	cout << s8 << endl;

    s7 = "xxxx";
	cout << s7 << endl;
}

运行结果为:

Hello
**********

2 赋值

可以用字符赋值,也可以用字符串赋值

3 遍历和修改

pos是下标,返回的是pos对应位置的引用,意味着既可以读也可以修改。

void test_string2()
{
	string s1("hello world");
	cout << s1 << endl;

	s1[0] = 'x';
	cout << s1 << endl;
	cout << s1[0] << endl;

	// 越界有严格断言检查
	//s1[12];  // 断言
	//s1.at(12); // 抛异常

	cout << s1.size() << endl; // 推荐
	cout << s1.length() << endl;

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

我们发现,[]的使用和数组下标的使用很相似

同样的:

at的使用和[]的使用类似,区别在于越界时at会抛异常

4 数据个数

这两个相比,我们更推荐使用size,有些时候例如链表,无法使用length,但是size在任何情况都适用。

cout << s1.size() << endl; // 推荐
	cout << s1.length() << endl;

5 迭代器

迭代也就是遍历的意思,迭代器是一种通用的访问所有容器的方式

迭代器的常见类型:iterator

在 C++ 中,iterator(迭代器)是一种行为类似指针的对象,用于遍历容器(如 vectorliststring 等)中的元素。它是连接容器和算法的桥梁,允许我们在不暴露容器内部实现细节的情况下访问元素。

iterator 的核心作用:

  • 提供统一的接口遍历不同容器(无论容器底层是数组、链表还是树结构)
  • 支持通过 * 运算符访问元素(类似指针解引用)
  • 支持通过 ++ 运算符移动到下一个元素
  • 支持比较操作(如 ==!= 判断是否到达容器末尾)

注意:行为像指针,但是底层不一定是指针

使用实例:

// 行为像指针一样的东西
	string::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		// (*it1)--; 修改值的内容
		cout << *it1 << " ";//解引用输出对应的值
		++it1;向下一位挪动
	}
	cout << endl;

使用范围:左闭右开[begin , end)

这一点和python中的range的使用范围很像

这个时候有些同学就有疑问了,那这个迭代器和下标+[ ]不是一样的吗?为什么还要多此一举用迭代器。

// 下标+[]
	// 遍历 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;

是的,在这个应用场景我们使用下标会更方便简洁一点,但是 迭代器作为STL的一大组件,它是作为通用的访问所有容器的方式。底层是数据的结构(如顺序表)用下标+[ ],但是链表没有[ ],就需要借助迭代器。

我们来实现一下链表的迭代器:

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())
	{
		cout << *lit << " ";
		++lit;
	}
	cout << endl;

代码解析:

  1. list<int> lt;定义了一个存储 int 类型的双向链表。

  2. lt.push_back(1); 等向链表尾部依次添加 1、2、3。

  3. list<int>::iterator lit = lt.begin();定义一个迭代器,指向链表的第一个元素。

  4. while (lit != lt.end())从当前位置开始遍历,直到到达链表末尾。

  5. cout << *lit << " "; ++lit;输出当前迭代器指向的值,然后移动到下一个元素。

迭代器的特点

1、提供统一的方式遍历容器;

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

6 迭代器的分类

(1)begin

分为普通的迭代器和const迭代器,普通的的迭代器我们前面已经讲过了,下面来学习const迭代器。

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

注意const的位置:在类名之后。这样修饰的是迭代器所指向的对象不被修改,如果放在类名之前,就是迭代器不能修改,这样是错误的。

(2)rbegin

反向迭代器和const反向迭代器

它的遍历是从后往前遍历的

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

是C++11之后定义的,就是const版本的begin

7 查找算法

注意 使用算法时必须包含头文件   #include<algorithm>

三 auto和范围for


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

auto关键字

1. 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个 不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型 指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期 推导而得

2. 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

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

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

5 . auto不能直接用来声明数组

// C++11
	int i = 0;
	// 通过初始化表达式值类型自动推荐对象类型
	auto j = i;
	auto k = 10;
	auto p1 = &i;
	// 指定一定是指针
	auto* p2 = &i;
	cout << p1 << endl;
	cout << p2 << endl;
	// 引用
	int& r1 = i;
	// r2不是int&引用,是int
	auto r2 = r1;
	//r3是int& 引用
	auto& r3 = r1;
	cout << &r2 << endl;
	cout << &r1 << endl;
	cout << &i << endl;
	cout << &r3 << endl;

范围for

1 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
2 范围for可以作用到数组和容器对象上进行遍历
3 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

// C++11
	// 范围for
	// 自动取容器数据赋值,自动迭代++,自动判断结束
	//for (auto ch : s1)
	for (char ch : s1)
	{
		cout << ch << ' ';
	}
	cout << endl;

	for (auto e : lt)
	{
		cout << e << ' ';
	}
	cout << endl;

for (auto ch : s1):就是把当前s1的值赋值给ch

范围for的特点:自动取容器数据赋值,自动迭代++,自动判断结束

支持迭代器的容器,都可以使用范围for

数组也支持(特殊处理)

​
int a[10] = { 1,2,3 };
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;
}

​
  1. 根据 C++ 规则,未显式初始化的剩余元素会被自动初始化为 0,所以数组实际存储为 [1,2,3,0,0,0,0,0,0,0]

  2. 范围 for 循环for (auto e : a)遍历数组a所有 10 个元素(包括未显式初始化的 0),依次输出每个元素的值。

四 完整代码

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<string>
#include<algorithm>
#include<list>
using namespace std;

//int main()
//{
//	// 净网行动
//	// 卧槽
//	// 10:48
//	char buff1[] = "abcA";
//	buff1[0]++;
//
//	char buff2[] = "比特abc";
//	cout << sizeof(buff2) << endl;
//	buff2[1]++;
//	cout << buff2 << endl;
//
//	buff2[1]++;
//	cout << buff2 << endl;
//
//	buff2[3]++;
//	cout << buff2 << endl;
//
//	buff2[3]++;
//	cout << buff2 << endl;
//
//	return 0;
//}

void test_string1()
{
	string s1;
	string s2("hello world");
	string s3(s2);

	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;

	string s4(s2, 0, 5);
	cout << s4 << endl;

	// pos位置拷贝结尾
	string s5(s2, 6, 15);
	cout << s5 << endl;

	string s6(s2, 6);
	cout << s6 << endl;

	string s7("hello world", 6);
	cout << s7 << endl;

	string s8(10, '*');
	cout << s8 << endl;

	s7 = "xxxx";
	cout << s7 << endl;
}

//class string
//{
//public:
//	char& operator[] (size_t pos)
//	{
//		return _str[pos];
//	}
//private:
//	char* _str;
//	size_t _size;
//	size_t _capacity;
//};

void Print(const string& s)
{
	//const string::iterator it1 = s.begin();
	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;
}

void test_string2()
{
	string s1("hello world");
	cout << s1 << endl;

	s1[0] = 'x';
	cout << s1 << endl;
	cout << s1[0] << endl;

	// 越界有严格断言检查
	//s1[12];  // 断言
	//s1.at(12); // 抛异常

	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())
	{
		cout << *lit << " ";
		++lit;
	}
	cout << endl;

	Print(s1);

	//string::iterator ret1 = find(s1.begin(), s1.end(), 'x');
	auto ret1 = find(s1.begin(), s1.end(), 'x');
	if (ret1 != s1.end())
	{
		cout<<"找到了x"<<endl;
	}

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

	// C++11
	int i = 0;
	// 通过初始化表达式值类型自动推荐对象类型
	auto j = i;
	auto k = 10;
	auto p1 = &i;
	// 指定一定是指针
	auto* p2 = &i;
	cout << p1 << endl;
	cout << p2 << endl;
	// 引用
	int& r1 = i;
	// r2不是int&引用,是int
	auto r2 = r1;
	//r3是int& 引用
	auto& r3 = r1;
	cout << &r2 << endl;
	cout << &r1 << endl;
	cout << &i << endl;
	cout << &r3 << endl;

	// C++11
	// 范围for
	// 自动取容器数据赋值,自动迭代++,自动判断结束
	//for (auto ch : s1)
	for (char ch : s1)
	{
		cout << ch << ' ';
	}
	cout << endl;

	for (auto e : lt)
	{
		cout << e << ' ';
	}
	cout << endl;
}

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

	return 0;
}

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值