string——C++

1. 为什么学习string类?

1.1 C语言中的字符串

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

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

 

2. 标准库中的string类

2.1 string类(了解)

string类的文档介绍

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信
    息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits
    和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结

  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>
    string;
  4. 不能操作多字节或者变长字符的序列。
    在使用string类时,必须包含#include头文件以及using namespace std;

 

2.2 string类的常用接口说明

2.2.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)拷贝构造函数
#include <iostream>
#include <string>

using namespace std;

void test_string1()
{
string s1;//构造空的string类对象s1

//string s2("hello world!!!");
// string (const char* s);
string s2 = "hello world!!!";// 用C格式字符串构造string类对象s2

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

string s3(s2);//拷贝构造s3
cout << s3 << endl;

string s4(s2, 6, 5);//拷贝字符串s2中第六个字母开始拷贝五个字母到s4中
cout << s4 << endl;

// 第三个参数len大于后面字符长度,有多少拷贝多少拷贝到结尾
string s5(s2, 6, 15);
cout << s5 << endl;
string s6(s2, 6);
cout << s6 << endl;

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

string s8(100, 'x');
cout << s8 << endl;

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

 
 

2.2.2 string类对象的容量操作

函数名称功能说明
size返回字符串的有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty简则字符串释放为空串,是返回true,否则返回false
clear清空有效字符
reserve为字符串预留空间n
resize将有效字符的个数改成你个,多出的空间用字符c填充

注意

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一
    致,一般情况下基本都是用size()。
  2. clear()只是将string中有效字符清空,不改变底层空间大小。
  3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

 

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

  1. [ ]遍历
    std::string::operator[]
    让类像数组一样访问
void test_string3()
{
string s1("hello world");
cout << s1[0] << endl;
s1[0] = 'x';
cout << s1[0] << endl;
cout << s1 << endl;

// 要求遍历string,每个字符+1
for (size_t i = 0; i < s1.size(); ++i)
//for (size_t i = 0; i < s1.length(); ++i)
{
s1[i]++;
}
cout << s1 << endl;

const string s2("world");//只能遍历不能修改
for (size_t i = 0; i < s2.size(); ++i)
{
//s2[i]++;
cout << s2[i] << " ";
}
cout << endl;
cout << s2 << endl;

//s2[6];  内部会检查越界
}
s1[0] = 'x';
s1.opertor[](0)
char& operator[](size_t pos)
{
    assert(pos < _size);
    return _str[pos];//返回的是别名
}

不是传值返回而是引用返回,好处:可读可修改

  • 减少拷贝
  • 修改返回对象

 

std::string::at
at与[]的功能一致
区别:越界之后会抛异常

虽然迭代器是通用类型,但是string中会更喜欢用[]

  1. 迭代器 遍历 内嵌类型
    四种:
  • 正向 iteratior
  • const 正向 const_iteration
  • 反向 reverse_iterator
  • const 反向 const_reverse_iterator

iteratior像指针一样的类型,但不一定是指针
只是用法像指针

区间是左闭右开:
[s.begin() , s.end())

不是传值返回而是引用返回,好处:可读可修改
减少拷贝
修改返回对象

std::string::at
at与[]的功能一致
区别:越界之后会抛异常

虽然迭代器是通用类型,但是string中会更喜欢用[]

2.迭代器 遍历 内嵌类型
四种:
正向 iteratior
const 正向  const_iteration
反向 reverse_iterator
const 反向  const_reverse_iterator

iteratior像指针一样的类型,但不一定是指针
只是用法像指针

区间是左闭右开
[s.begin() , s.end())

srting 不喜欢用iteratior,因为[]更好用
vector 不喜欢用iteratior,因为[]更好用
list/map/set…只能用迭代器访问
iteratior是所有容器通用的访问方式,用法都是类似的

 

void test_string5()
{
string s("hello");
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;

PrintString(s);
}

 
 
3. 范围for 智能
但底层就是迭代器
只能正向

void test_string6()
{
// 范围for -- 自动迭代,自动判断结束
// 依次取s中每个字符,赋值给ch
/*for (auto ch : s)
{
ch++;
cout << ch << " ";
}*/
for (auto& ch : s)//加了&引用才能修改
{
ch++;
cout << ch << " ";
}
cout << endl;

cout << s << endl;

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

// 范围for底层其实就是迭代器
}
 

 
 

2.2.4. string类对象的修改操作

  1. 插入
    push_back 单个 如果空间不够就扩容
    append 多个
    ++最好用 都行 底层其实就是push_back
void test_string6()
{
string s("hello");
 
s.push_back('-');
s.push_back('-');
 
s.append("world");
cout << s << endl;

string str("我来了");
s += '@';
s += str;
s += "!!!";
cout << s << endl;

s.append(++str.begin(), --str.end());
cout << s << endl;

//string copy(++s.begin(), --s.end());
string copy(s.begin() + 5, s.end() - 5);
cout << copy << endl;
} test_string5()
{
string s("hello");
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;

PrintString(s);
}

 

  1. insert 插入
    erase 删除
    repace 替换

空格后插入20%

void test_string8()
{
string str("wo lai le");
for (size_t i = 0; i < str.size();)
{
if (str[i] == ' ')
{
str.insert(i, "20%");
i += 4;
}
else
{
++i;
}
}
cout << str << endl;
}

将空格替换成20%

  1. 先插再删(效率不高)
  2. 双指针
  3. 以空间换时间
  4. replace
void test_string8()
{
    //1.
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == ' ')
{
str.insert(i, "20%");
i += 3;
}
}
cout << str << endl;

for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == ' ')
{
str.erase(i, 1);
}
}
cout << str << endl;
 
  //3.先遍历字符串

string newstr;
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] != ' ')//不是空格
{
newstr += str[i];//+= 字符串后加字符串
}
else//是空格
{
newstr += "20%";
}
}
cout << newstr << endl;
}

 

  1. c_str 兼容c的接口
    返回值是 const char*

以c的形式读文件

void test_string9()
{
string filename("test.cpp");
cout << filename << endl;
cout << filename.c_str() << endl;

FILE* fout = fopen(filename.c_str(), "r");
assert(fout);
char ch = fgetc(fout);
while (ch != EOF)
{
cout << ch;
ch = fgetc(fout);
}
 }

 

filename 与 filename.c_str()的区别
+= 的结果不一样 底层有区别

void test_string10()
{
string filename("test.cpp");
cout << filename << endl;
cout << filename.c_str() << endl;

filename += '\0';
filename += "string.cpp";
 
cout << filename << endl; // string 对象size为准
cout << filename.c_str() << endl; // 常量字符串对象\0

cout << filename.size() << endl;
 
string copy = filename;
cout << copy << endl << endl;

for (unsigned char ch = 0; ch < 128; ++ch)
{
cout << ch;
}
cout << endl;
cout << "\\0" << endl;
}

data与c_str 一样 但是习惯用后者

 

  1. find 查找
    默认从0开始找,但也可以从中间某个位置找
  • 找后缀
    substr 不用算长度 不给长度就有多少找多少
    rfind 适合用来取最后一个后缀

  • 分割网址
    网址(url):3部分构成
    协议名(protocol) + 域名(domain) + 路径(uri)

void DealUrl(const string& url)
{
size_t pos1 = url.find("://");
if (pos1 == string::npos)
{
cout << "非法url" << endl;
return;
}

string protocol = url.substr(0, pos1);
cout << protocol << endl;

size_t pos2 = url.find('/', pos1 + 3);
if (pos2 == string::npos)
{
cout << "非法url" << endl;
return;
}
string domain = url.substr(pos1 + 3, pos2 - pos1 - 3);
cout << domain << endl;

string uri = url.substr(pos2 + 1);
cout << uri << endl << endl;
}

void test_string11()
{
string filename("test.cpp.tar.zip");
// 后缀
//size_t pos = filename.find('.');
size_t pos = filename.rfind('.');
if (pos != string::npos)
{
//string suff = filename.substr(pos, filename.size() - pos);
string suff = filename.substr(pos);

cout << suff << endl;
}

string url1 = "https://cplusplus.com/reference/string/string/";
string url2 = "https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=ascall&step_word=&hs=0&pn=0&spn=0&di=7108135681917976577&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&istype=0&ie=utf-8&oe=utf-8&in=&cl=2&lm=-1&st=undefined&cs=2613959014%2C543572025&os=2740573600%2C1059518451&simid=2613959014%2C543572025&adpicid=0&lpn=0&ln=179&fr=&fmq=1660115697093_R&fm=&ic=undefined&s=undefined&hd=undefined&latest=undefined&copyright=undefined&se=&sme=&tab=0&width=undefined&height=undefined&face=undefined&ist=&jit=&cg=&bdtype=0&oriquery=&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fimg.php.cn%2Fupload%2Fimage%2F147%2F157%2F796%2F1593765739620093.png%26refer%3Dhttp%3A%2F%2Fimg.php.cn%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Dauto%3Fsec%3D1662707704%26t%3Da68cb238bbb3f99d0554098c785d526e&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Brir_z%26e3BvgAzdH3FuwqAzdH3F9c9amd_z%26e3Bip4s&gsm=1&rpstart=0&rpnum=0&islist=&querylist=&nojc=undefined&dyTabStr=MCwzLDIsNCw2LDEsNSw3LDgsOQ%3D%3D";
string url3 = "ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf";

DealUrl(url1);
DealUrl(url2);
DealUrl(url3);
}

find_first(last)_of 搜索第一个匹配的字符
与find的区别:find 全部匹配 of 只要出现的任意一个字符就返回

 
 
加餐
Functions
转型

void test_string12()
{
int ival;
double dval;
cin >> ival >> dval;
string istr = to_string(ival);
string dstr = to_string(dval);
cout << istr << endl;
cout << dstr << endl;

istr = "9999";
dstr = "9999.99";
ival = stoi(istr);
dval = stod(dstr);//有些浮点数无法精确存储,所以结果会有些不一样
}

2.2.5 string类非成员函数

函数功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率低
operator>>输入运算符重载
operator<<输出运算符重载
getline获取一行字符串
relational operators大小比较
以下是一个简单的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; } ```
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hey pear!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值