C++——string类

1.初识string

string属于C++标准库,而不属于STL,STL也属于C++标准库

string是管理字符的顺序表,用来管理字符数组

string是模板,只是库直接给它typedef了,直接实例化

string的接口设计的非常的繁杂

自定义类型实现流插入和流提取要重载,但这里在库里已经实现了

void TestString1()
{
	string s1;//构造无参的

	cin >> s1;
	cout << s1 << endl;
}

指向的空间在堆上,可以动态增长

2.string的构造函数

2.1无参数构造函数

void TestString1()
{
	char arr[10];
	//C语言的问题是,没办法很好地按需去申请空间

	string s1;//构造无参的

	cin >> s1;
	cout << s1 << endl;
}

2.2字符串构造

void TestString1()
{
	string s2("hello");
}

2.3拷贝构造函数

void TestString1()
{
	string s1;//构造无参的
	string s2("hello");

	string s3(s2);
	cout << s3 << endl;
}

2.3.1补充

void TestString2()
{
	string s1("hello");

	string s2="hello";
	//为什么支持这种写法?

	//单参数的构造函数支持隐式类型转换
	//这里是构造加拷贝构造,然后优化

}

2.4substring(3)

void TestString6()
{
	string s1("hello world");

	string s2(s1);
	cout << s2 << endl;

	string s3(s1, 6, 3);
	cout << s3 << endl;//wor

	string s4(s1, 6, 5);
	cout << s4 << endl;//world

}

void TestString6()
{
	string s5("hello worldxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyy");
	
	string s6(s5, 6);
//想把第6个位置及其后面的字符全部取到,怎么做?难道一个个地数吗?
//此时就可以使用缺省参数npos
	cout << s6 << endl;

	string s7(s5, 6,s5.size()-6);//使用size也可以
	cout << s7 << endl;
	//注意:strlen不可以对自定义类型使用

	//strlen(s5._str);//这里不可行,_str是私有变量
	//而且STL有很多版本,你无法得知对象中的私有变量名称

}

npos是里面的静态成员变量,给的值是-1,但npos的类型是size_t(unsigned integer:无符号整型)
在底层存储的是补码,-1的补码是全1,类型提升、转换,转换成无符号整型就是整型的最大值:42亿9千万左右
1G是 2^30   4G就是 2^32 byte ,也就是42亿9千万左右
一个string对象不会有4G那么大,所以npos完全够用
32位下new 1G还可以,2G就new不出来了,开不了那么大的连续空间,内存不够

如果len太长,字符串太短,此时也不会越界去访问,有多少取多少,直到字符串结束

2.5from sequence(5)

使用前n个去初始化

void TestString8()
{
	string s1("hello world",5);
	cout << s1 << endl;//hello

	string s2("hello world", 9);
	cout << s2 << endl;//hello wor

}

2.6fill(6)

使用n个c去初始化

void TestString7()
{
	string s1(10,'a');
	cout << s1 << endl;//aaaaaaaaaa
	
	string s2(3,'x');
	cout << s2 << endl;//xxx


}

2.7range(7)

void TestString9()
{
	//使用迭代器区间去初始化
	string s1("hello world");
	string s2(s1.begin(), s1.end());
	cout << s2 << endl;//hello world

	string s3(++s1.begin(), --s1.end());//第一个和最后一个不要
	cout << s3 << endl;//ello worl
}

2.8赋值运算符重载

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

	string s2("world");
	cout << s2 << endl;

	//一、
	s1 = s2;
	cout << s1 << endl;

	//二、
	s1 = "hello world";
	cout << s1 << endl;

	//三、
	s1 = 'x';
	cout << s1 << endl;

}

3.string的非成员函数重载(全局函数)

3.1 +

void TestString1()
{
	string s1;//构造无参的
	string s2("hello");

	string ret1 = s1 + s2;//实现字符串的连接
	cout << ret1 << endl;

	string ret2 = s1 + "world";
	cout << ret2 << endl;

	//C语言实现需要strcat
	//缺点:1.可读性差
	//2.需要考虑扩容问题
	//3.需要找到'\0',如果字符串很长,找'\0'也需要很长的时间

	string ret3 = s1 + s1;//这里是拷贝而不是往s1后面添加字符串
	cout << ret3 << endl;
}
void TestString4()
{
	string s2("hello");

	string s3 = s2 + 'a';
	string s4 = s2 + "abcd";

	//能不使用+就不要使用,因为+是传值返回
}

4.string的元素访问

4.1  []

4.2at

at[]的功能是一样的,只是说它不是运算符重载,它就是一个普通的函数

void TestString3()
{	
    string s1;
	s1.resize(10, '#');

	s1[0]++;//使用起来更加形象
	cout << s1 << endl;

	s1.at(0)++;//使用起来比较别扭
	cout << s1 << endl;
}

5.遍历数组

遍历数组的方式有三种:

5.1 []

//遍历数组

void TestString2()//访问
{
	//一、
	for (size_t i = 0; i < s1.size(); i++)
	{
		//读
		cout << s1[i] << " ";
	}
	cout << endl;
	for (size_t i = 0; i < s1.size(); i++)
	{
		//写
		s1[i]++;
	}
	cout << s1;
}

5.2迭代器

迭代器分为两大类:有无const、正向与反向,组合起来就是4种

5.2.1正向迭代器

//正向迭代器
void TestString3()
{
	string s1("hello");

	//二、迭代器
//迭代器是遍历数据结构的一种方式,可以认为它类似一个指针
	string::iterator it = s1.begin();//一般在类里面定义或是typedef
	//迭代器定义在类域里面,所以要指定类域
	//it是对象

	//begin()是成员函数,一般begin就是返回开始位置的迭代器,返回开始位置的指针

	//迭代器何时终止?它不等于end就截止了,end是最后一个数据的下一个位置,o是最后一个数据
	//C++中对字符串是有要求的,为了满足兼容C语言等需求,除了存储有效字符,还要存储'\0'
	while (it != s1.end())
	{
		cout << *it << " ";//只读
		++it;
	}
	cout << endl;
	//_size是5,与strlen相同,不算'\0','\0'在这里面是一个标识字符,标识结束
	//所以最后一个有效字符是o,end就指向'\0'

}

5.2.2补充

void TestString3()
{
	string s1("hello");

	string::iterator it = s1.begin();

	//while (it != s1.end())
	while (it < s1.end())//这里可以这样使用,但不建议
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;



	//实际上迭代器才是主流的遍历方式,它是通用的,可以遍历所有容器
	//同时屏蔽了底层的实现细节,也体现了面向对象的封装
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	list<int>::iterator lit = lt.begin();
	while (lit != lt.end())//所以推荐使用!=,因为它是通用的
	//while (lit < lt.end())//物理空间大概率不连续,是一个个的小块内存,end未必一定比begin大
	{
		cout << *lit << " ";
		lit++;//底层也是cur=cur->next
	}
}

5.2.3反向迭代器

//反向迭代器
void TestString4()
{

	string s1("hello");

	//倒着遍历
	//string::reverse_iterator rit = s1.rbegin();
	auto rit = s1.rbegin();//因为rbegin的返回值就是反向迭代器,右边有一个对象,会自动推导左边对象的类型

	while (rit != s1.rend())
	{
		cout << *rit << " ";
		rit++;//这里++就是反着走的
	}


//这样也可以实现,但是非常别扭
	string::iterator rit = s1.end();
	rit--;
	while (rit != s1.begin())
	{
		cout << *rit << " ";//只读
		--rit;
	}
	cout << endl;


}

5.2.4有无const

void func(const string& s)
//不推荐传值传参	
// 要进行拷贝,并且这里要去调用拷贝构造,同时拷贝构造要使用深拷贝,否则会出现多次析构的问题
{
	//string::iterator it = s.begin();//这样写就不支持了
	string::const_iterator it = s.begin();

	while (it != s.end())
	{
		//const迭代器不支持写
		//*it = 'a';
        
        //读
		cout << *it << " ";
		it++;
	}
	cout << endl;


	//string::const_reverse_iterator rit = s.rbegin();
	auto rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
}

void TestString6()
{
	string s1("hello");

	func(s1);

}

5.3范围for

void TestString5()
{//三、

	string s1("hello");

	//自动判断结束、解引用、++
	//原理:编译时编译器替换成迭代器
	
    //读
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	//范围for的底层和第二种遍历方式是完全类似的,把*it赋值给ch
	//看似代码很短,实际是交给编译器去生成代码了 
	//范围for不支持倒着遍历


	for (auto ch : s1)
	{
		ch++;//s1不会修改
	}
	cout <<s1<< endl;

	//写
	for (auto& ch : s1)//ch就是*it的别名,这样就可以修改s1
	{
		ch++;
	}
	cout << s1 << endl;
}

6.Capacity

6.1size

size用来看字符串的有效字符是多少个,不算‘\0’,'\0'是标识字符,标识结束

void TestString11()
{
	string s1("hello");
	cout << s1.size() << endl;
	//不算'\0',打印的是5

}

底层在数组上存储的时候要存储‘\0’

为什么要存储‘\0’?明明是个可以动态增长的数组,不要‘\0’也是可以的。

因为要兼容C语言,有些场景下必须要调用C的接口,Linux是用C写的,Linux的接口也是使用C写的,比如有个地方需要传字符串,此时传string是不认识的

void TestString14()
{
	string filename;
	cin >> filename;

	//FILE* fout = fopen(filename, "r");//这里不能编译通过
	//怎么打开文件?
	//首先,第一个要传一个常量字符串,C语言规定常量字符串要给'\0'

	//c_str,即返回C形式的字符串,会返回底层指向字符数组的指针
	FILE* fout = fopen(filename.c_str(), "r");
	 
	//C语言对字符串、字符数组的规定是结尾要有'\0'

	//如果结尾没有'\0',就算提供了c_str,C语言的那些接口也不能很好地兼容,因为不知道字符串的结尾
	//C语言字符串为什么要加'\0',因为'\0'是标识字符,就知道它结束了,那就可以拿到名字的长度了
	//这样C++的string就可以和C语言的接口进行完美的配合
}

6.2length

与size作用相同,string产生的比STL要早,C语言最开始给字符串取名字叫strlen,length(长度)明显比size(大小)更为形象,早期的时候字符个数设计的就叫做length,在STL出现后,就开始使用size了,因为size具有通用性,而length不具有,同时其它的数据结构只有size

6.3capacity

计算数组的容量大小

6.4clear

会清掉数据,但不会释放空间

void TestString12()
{
	string s1("hello world");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.clear();
	cout << endl;

	//如何判断是否释放了空间?
	//看capacity,capacity变小就说明释放了
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	//二者都是15,证明没有释放空间

	cout << endl;

	//STL并未进行严格的规定,但一般来说,clear是不释放空间的
	//clear一般都是把之前的数据清掉,然后继续插入一些数据
	//空间比较廉价,没有必要把空间释放掉
	//最终空间肯定会被释放的,有析构函数

	s1 += "jxyxtla";
	cout << s1 << endl;
	cout << s1.size() << endl;

	cout << s1.capacity() << endl;

}

6.5empty

用来判空

6.6max_size

设计初衷是想知道string最大有多长,最长能到多少

但实际上,1.不同平台下实现不同

2.它非常无用,因为获取的信息也未必准确,比如假设现在内存已经不足了,已经创建了很多对象,开了很多的空间,它就不准确了。这个值是写死的而不是实时变化的。

void TestString13()
{
	string s1("hello world");
	cout << s1.max_size() << endl;
	//内存很足时,可以开21亿(32位)
}

6.7reserve

6.7.1引入

void TestString1()
{
	//string的扩容
	//不断插入数据,看capacity的变化,来观察string的扩容

	string s;
	size_t old = s.capacity();
	cout << "初始是" << old << endl;
	for (size_t i = 0; i < 100; i++)
	{
		s.push_back('a');
		if (s.capacity() != old)
		{
			cout << "扩容为" << s.capacity() << endl;
			old = s.capacity();
		}
	}

}

6.7.2应用

// reverse 反转
// reserve 保留
void TestString2()
{
	//reserve可以提前开空间
	string s;
	s.reserve(100);//要100的空间,肯定会去开100,但同时也可能会比100还大

	size_t old = s.capacity();
	cout << "初始是" << old << endl;
}

6.7.3补充

void TestString2()
{
	//那么reserve会不会缩容?
	s.reserve(10);
//一般是不会缩容,但STL没有严格规定,这只是一般惯例的实现,不同的平台实现都可能不同  
//核心原因还是缩空间这件事不符合我们目前的设计,空间是足够大的
	cout << "缩容为" << s.capacity() << endl;
}

6.8resize

6.8.1大于15

void TestString3()
{
	string s("hello world");
	cout << s << endl;
	cout << "初始长度为" << s.size() << endl;
	cout << "初始容量为" << s.capacity() << endl;

	s.resize(20, 'a');//此时就会扩容
	cout << s << endl;
	cout << "改变size后长度为" << s.size() << endl;
	cout << "改变size后容量为" << s.capacity() << endl;
}

6.8.2大于11小于15

void TestString3()
{
	string s("hello world");
	cout << s << endl;
	cout << "初始长度为" << s.size() << endl;
	cout << "初始容量为" << s.capacity() << endl;

	//s.resize(13);
	s.resize(13,'a');//打印hello worldaa

}

6.8.3小于11

void TestString3()
{
	string s("hello world");
	cout << s << endl;
	cout << "初始长度为" << s.size() << endl;
	cout << "初始容量为" << s.capacity() << endl;

	s.resize(5);//此时就会删除数据,只保留前5个字符
	cout << s << endl;
	cout << "改变size后长度为" << s.size() << endl;
	cout << "改变size后容量为" << s.capacity() << endl;
}

6.8.4使用场景

void TestString3()
{
	//使用场景
	//比如要开一个字符串,要开10个空间,且每个字符都是#
	string s1;
	cout << s1 << endl;
	cout << "初始长度为" << s1.size() << endl;
	cout << "初始容量为" << s1.capacity() << endl;

	s1.resize(10, '#');//此时的作用就是开空间+初始化
	cout << s1 << endl;
	cout << "使用size后为" << s1.size() << endl;
	cout << "使用size后为" << s1.capacity() << endl;

	//核心特点就是要把size改变为所需的值
}

6.9shrink_to_fit

缩容的接口,缩容以后还要插入数据,代价太大,尽量不要使用

7.Modifiers

7.1push_back

void TestString4()
{
	string s;
	s.push_back('a');//push_back是插入一个字符
	s.append("hello world");//插入字符串要使用append

	string s1("hello");
	s.append(s1);

}

7.2append

7.3+=

void TestString4()
{
	string s;
	string s1("hello");

	s += s1;
	s += "helloc";
	s += 'x';

}

7.4assign

assign有赋值的意思

void TestString5()
{
	string str;
    //string str("hello world");
    //如果已经有一些数据,会把这些数据覆盖掉

	string base = "The quick brown fox jumps over a lazy dog.";

	str.assign(base);//把base assign给str
	std::cout << str << endl;

	str.assign(base,5,10);//从第5个位置开始,取10个字符
	std::cout << str << endl;

}

7.5insert

在指定位置前,进行插入数据的操作

void TestString6()
{
	//之前的数据结构,像顺序表,都是插入一个值
	//string的特点就是可以插入一个值,也可以插入多个值
	//因为它的多个值非常好表达,像之前的只能使用数组来表达
	//而它可以使用字符串来表达,有常量字符串这个概念	
	
    string s("hello");
	cout << s << endl;
	//头插一个字符只能这样写

	s.insert(0, 1, 'b');//一、
	s.insert(s.begin(), 'a');//二、
	cout << s << endl;

	//头插需要挪动数据,尽量少使用头插
}

7.6erase

void TestString7()
{
	string s("hello world");
	cout << s << endl;

	s.erase(7);//删除第7个位置及其以后的数据
	cout << s << endl;

}

7.7replace 替换

void TestString8()
{
	string s("hello world");
	cout << s << endl;
	
	string s1("hello");
	s.replace(5, 5, s1);
	cout << s << endl;

	s.replace(5, 1, "123");
	cout << s << endl;

	s.replace(5, 3, "456");
	//平替覆盖时效率尚可,但凡多一个少一个都要挪动数据
	cout << s << endl;

}

注意:insert、erase和replace尽量不要使用,因为它们都涉及到挪动数据,效率不高
它们的接口设计都是复杂繁多,需要使用时查看文档即可

7.8补充

void TestString8()
{
	//题目:将空格替换为%10

	string s2("askjd n nasdnkla lk sknkl nlk");
	cout << s2 << endl;

	//以空间换时间的方式
	string s3;
	for (auto ch : s2)
	{
		if (ch != ' ')
		{
			s3 += ch;
		}
		else
		{
			s3 += "%10";
		}
	}
	cout << s3 << endl;

}

那如果就是要s2改变怎么呢?

7.8.1赋值

void TestString8()
{
	//一、赋值  开空间,拷贝数据过去
    
    s2 = s3;
	//s2.assign(s3);

	cout << s2 << endl;

}

7.8.2 string的Modifiers::swap

void TestString8()
{
	//二、string自己提供的swap
	//本质是交换二者的成员变量  _str、_size、_capacity

	printf("s2:%p\n", s2.c_str());
	printf("s3:%p\n", s3.c_str());

	s2.swap(s3);
	cout << s2 << endl;

	printf("s2:%p\n", s2.c_str());
	printf("s3:%p\n", s3.c_str());
	//证明
}

7.8.3全局的swap模板

void TestString8()
{
	//三、全局的swap可以交换string

	swap(s2, s3);
	cout << s2 << endl;

	//3次深拷贝,要去调用一次构造、两次赋值,代价太大
}

7.8.4string的非成员函数重载的swap

实际上,上面的代码不会去实例化全局的swap模板
有可以直接去调用的函数,就不用实例化模板了

void TestString8()
{
	swap(s2, s3);
	cout << s2 << endl;

}

7.9pop_back

8.String operations

8.1 c_str

兼容C语言,有些接口是C的,想把数组传过去就使用c_str

8.2 data

与c_str功能类似

8.3copy

8.4

9.练习

387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

//索引就是下标
class Solution {
public:
    int firstUniqChar(string s) {
        int Array[26]={0};//计数排序可以很好地解决问题
        for(auto ch:s)
        {
            Array[ch-'a']++;//相对映射

        }
        for(size_t i=0;i<s.size();i++)
        {
            if(Array[s[i]-'a']==1)
            {
                return i;
            }
        }
        return -1;


    }
};

以下是一个简单的C++文字RPG游戏的示例代码,其中包括了基本的角色、战斗和物品系统: ```c++ #include <iostream> #include <string> #include <cstdlib> #include <ctime> using namespace std; // 角色 class Character { public: string name; int hp; int atk; int def; int gold; Character(string n, int h, int a, int d, int g) { name = n; hp = h; atk = a; def = d; gold = g; } // 攻击函数 void attack(Character& other) { int damage = atk - other.def; if (damage < 0) { damage = 0; } other.hp -= damage; cout << name << "攻击了" << other.name << ",造成了" << damage << "点伤害。" << endl; } // 是否死亡 bool isDead() { return hp <= 0; } }; // 物品 class Item { public: string name; int price; int hp; int atk; int def; Item(string n, int p, int h, int a, int d) { name = n; price = p; hp = h; atk = a; def = d; } }; // 商店 class Shop { public: Item items[3]; Shop() { items[0] = Item("草药", 10, 20, 0, 0); items[1] = Item("铁剑", 50, 0, 10, 0); items[2] = Item("铁甲", 100, 0, 0, 10); } // 显示商店物品 void showItems() { cout << "欢迎光临!以下是本店的物品:" << endl; for (int i = 0; i < 3; i++) { cout << i + 1 << ". " << items[i].name << " - " << items[i].price << "金币" << endl; } } // 购买物品 bool buy(Character& c, int choice) { if (c.gold < items[choice - 1].price) { cout << "金币不足,法购买!" << endl; return false; } c.gold -= items[choice - 1].price; c.hp += items[choice - 1].hp; c.atk += items[choice - 1].atk; c.def += items[choice - 1].def; cout << "购买成功!" << endl; return true; } }; // 战斗函数 void battle(Character& player, Character& enemy) { cout << "你遇到了一只" << enemy.name << ",准备战斗!" << endl; while (!player.isDead() && !enemy.isDead()) { player.attack(enemy); if (enemy.isDead()) { cout << enemy.name << "被你打败了!" << endl; player.gold += enemy.gold; return; } enemy.attack(player); if (player.isDead()) { cout << "你被" << enemy.name << "打败了!" << endl; return; } } } int main() { srand(time(NULL)); // 初始化随机数种子 // 初始化角色和商店 Character player("勇者", 100, 10, 5, 50); Character enemies[3] = { Character("史莱姆", 30, 5, 2, 10), Character("骷髅兵", 50, 10, 5, 20), Character("巨龙", 100, 20, 10, 50) }; Shop shop; // 游戏循环 while (true) { cout << "你的状态 - HP:" << player.hp << " ATK:" << player.atk << " DEF:" << player.def << " 金币:" << player.gold << endl; cout << "请选择操作:" << endl; cout << "1. 进入商店" << endl; cout << "2. 进行战斗" << endl; cout << "3. 离开游戏" << endl; int choice; cin >> choice; switch (choice) { case 1: shop.showItems(); cout << "请选择要购买的物品(输入编号):" << endl; cin >> choice; shop.buy(player, choice); break; case 2: battle(player, enemies[rand() % 3]); break; case 3: cout << "游戏结束,欢迎再次光临!" << endl; return 0; default: cout << "无效的操作!" << endl; break; } } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值