目录
<1>string类和char*转换:—————补充————————
———————————————————————————————
string类对象的输入——getline(cin, a); 新增!
三.string类对象的赋值 std::string::operator=
(1)用 std::string::operator[](解决“遍历对象的每一个字符”①)
(1)做法一:利用下标+[] (已知物理空间是连续的,如果是链表就只能用迭代器)
3.std::string::operator[] 和 std::string::at
4.push_back; append operator+=
STL简介:
![](https://i-blog.csdnimg.cn/blog_migrate/bff116a1ac98565173fccc299a571b52.png)
stl容器的头文件和std标准库的关系:
头文件:#include<string> (不加.h,C语言中有string.h,防止和C语言的重复)<iostream>头文件除输入输入函数以外也包含<utility>和stl的所有容器的定义和部分容器的声明。部分容器的声明解释:有的容器在std标准库中有声明,有的stl容器在std标准库中没声明。例如string在std标准库中有声明,unordered_map在std标准库中无声明,即:<iostream>头文件包含了string的声明,但不包含unordered_map的声明,所以不加#include<string>,不加using namespace std;不放开标准库,直接用#include<iostream> ,只需要加上std这个域:std::string就可以正常使用,因为<iostream>头文件有string的声明;但是对于unordered_map,因为<iostream>不包含unordered_map的声明,所以你必须加上#include<unordered_map>里面有声明,才能正常使用unordered_map:
在加上<iostream>前提下,如果加#include<unordered_map>声明,但不加using namespace std; 不把标准库打开,就需要std::限定才能用否则不能用;如果加using namespace std; 打开了标准库,则std::加不加都可以。
若不加#include<unordered_map>,则没有声明,即使加上 std::限定 也用不了。
string的介绍:
typedef basic_string<char> string; (string是一个类模板)
1.解释:Strings are objects that represent sequences of characters. (strings 是管理字符串的一个类)
2.作用:管理动态增长字符数组,这个字符串以\0结尾
3.底层:类似于下面这样:底层是new一个新空间,再拷贝常量字符串进去,为什么不能直接
_str=str ? 这样做不行,他们指向同一块空间,都是指向常量字符串了,常量字符串无法实现增删查改
namespace bit
{
template<class T>
class basic_string
{
public:
basic_string(const T* str)
{
// 开空间存储字符串,方便增删查改
size_t len = strlen(str);
_str = new T[len + 1];
strcpy(_str, str);
}
private:
const T* _str;
size_t _size;
size_t _capacity;
};
}
typedef basic_string<char> string;
int main()
{
string s("hello world");
}
<1>string类和char*转换:—————补充————————
(1)string转char*
std::string str = "string";
const char *cstr = str.c_str();
(2)char*转string
可以直接赋值。
char *cstr = “hello”;
string sss;
sss = cstr;
<2>char*转int
(1)atoi:
(2)stoi
#include <cstdlib>
int main()
{
//int a=atoi("123");
int a=stoi("123");
printf("%d\n",a);
return 0;
}
(3)strtol
<3>int转char*
(1)to_string
———————————————————————————————
string类对象大小
string类对象的输入——getline(cin, a); 新增!
cin和cout写输入输出非常方便,但是用cin输入string字符串时,默认遇到空格回车制表符等空白字符即字符串输入结束。
#include <iostream>
#include <string>
using namespace std;
int main() {
string a, b;
cin >> a >> b;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
return 0;
}
/*
输入:
abc 123
输出:
a:abc
b:123
*/
但是遇到带空格字符的字符串就不是很友好了,则需要用到getline函数,getline()是遇回车符输入结束。
#include <iostream>
#include <string>
using namespace std;
int main() {
string a, b;
getline(cin, a);
getline(cin, b);
cout << "a:" << a << endl;
cout << "b:" << b << endl;
return 0;
}
/*
输入:
abc 123
123 abc
输出:
a:abc 123
b:123 abc
*/
一.string类对象的常见构造函数
(1)
1.string(); 是无参的构造函数。
2.string (const char* s); 是有参构造函数。
3.string (const string& str); 是拷贝构造函数。
4.string (const char* s, size_t n); 是用字符串s的前n个字符初始化
5.string(size_t n, char c);初始化成n个字符c
6.string (const string& str, size_t pos,size_ t len = npos);用string类型对象str的第pos个位置后的len个字符初始化(pos是position位置的缩写,缺省参数npos是 static const size_t npos = -1; 也就是正数里面的最大值,是4294967295:如果pos后面不够取,str太短了,那就取完为止,npos意思是把pos位置后面的取完)
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1; // 1.string(); 相当于string s1("");
string s2("hello world"); // 2.string (const char* s);
string s3(s2); // 3.string (const string& str);
string s4 = s2; // 3.string (const string& str);
string s5("aaabbbcccddd",3);// 4.string (const char* s, size_t n);
string s6(10, 'x'); // 5.string(size_t n, char c);
// 6.string (const string& str, size_t pos,size_ t len = npos);
string s7(s2, 6, 3);
string s8(s2, 6, 100);
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
cout << s7 << endl;
cout << s8 << endl;
}
(2)单参数的构造函数支持隐式类型转换
string s="hello"; 这句,字符串"hello"会先进行隐式类型转换,直接构造一个string类型的"hello",然后这个string类的"hello"拷贝构造给s,但会被编译器优化成直接构造,先构造再拷贝构造——>通过编译器优化就成了直接构造
string s("hello");
string s="hello";
详情见:此文章的大标题三——>小标题2
(88条消息) C++:1.流插入与流提取的运算符重载,2.初始化列表,3.explicit关键字,4.静态成员变量与非静态成员变量用法 详解_beyond.myself的博客-CSDN博客_流插入流提取
二.string类对象的常见析构函数
用途是清理动态资源,不用管,自动调用
三.string类对象的赋值 std::string::operator=
string& operator= (const string& str); 把string类对象赋值成另一个string类对象str(这个是最常用的)
string& operator= (const char* s); 把string类对象赋值成字符串s
string& operator= (char c); 把string类对象赋值成字符c
void test_string2()
{
string s1("hello");
string s2("xxx");
s1 = s2; //string& operator= (const string& str);
s1 = "yyy"; //string& operator= (const char* s);
s1 = "y"; //string& operator= (char c);
}
int main()
{
test_string2();
return 0;
}
四.std::string::operator[]
s1[i] 就相当于 s1.operator[](i); string类对象s1的第i个字符
1.有两个函数重载:
char& operator[](size_t pos) 可读可写
const char& operator[](size_t pos) const 只读
底层逻辑:
namespace bit
{
class string
{
public:
// 值修改返回对象
char& operator[](size_t pos)
{
assert(pos <= _size); //检查是否越界
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos <= _size);
return _str[pos];
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
void test_string4()
{
string s1("hello"); //调用可读可写
const string s2("hello"); //调用只读
}
int main()
{
test_string4();
return 0;
}
2.使用
以前的内置类型[]:
const char* s2 = "world";
s2[i]; // *(s2+i)
string中的自定义类型用 std::string::operator[]:
【1】题目1:遍历对象的每一个字符,三种做法:
第一种方式,下标+[]
第二种方式,迭代器
第二种方式,范围for
( s1.size()是计算对象中的字符个数,不包含\0)
(1)用 std::string::operator[](解决“遍历对象的每一个字符”①)
void test_string3()
{
// 遍历string的每一个字符
string s1("hello");
cout << s1.size() << endl;
// 第一种方式,下标+[]
for (size_t i = 0; i < s1.size(); i++)
{
// s1.operator[](i);
cout << s1[i] << " ";
}
cout << endl;
}
int main()
{
test_string3();
return 0;
}
(2)迭代器(解决“遍历对象的每一个字符”②)
例1:遍历对象的每一个字符:第二种方式,迭代器
介绍:迭代器是什么? -像指针一样的东西或者就是指针(像指针一样的访问数据结构的东西)
各个类都有独自的迭代器 iterator ,所以要指定是哪个类的,string类中的迭代器写作
string: : iterator
s1.begin()返回第一个位置的指针(或者说第一个位置的迭代器)
s1.end()返回最后一个数据的下一个位置,下面这里的 s1.end() 就是\0的位置
void test_string3()
{
// 遍历string的每一个字符
string s1("hello");
cout << s1.size() << endl;
// 迭代器
string::iterator it = s1.begin(); //it是s1的首元素地址
while (it != s1.end()) //遍历打印,直到it为 \0 停止
{
cout << *it << " ";
++it;
}
cout << endl;
}
int main()
{
test_string3();
return 0;
}
小疑问:这里 while (it != s1.end()) 可以写成 while (it < s1.end())吗?
答:这里是可以,因为是顺序表,存储空间连续,如果像后面的链表形式存储空间不连续就只能用标准的 while (it != s1.end()) 。
例2:vector顺序表
#include<iostream>
#include<vector>
#include<string>
using namespace std;
void test_string3()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator vit = v.begin();
while (vit != v.end())
{
cout << *vit << " ";
++vit;
}
cout << endl;
}
int main()
{
test_string3();
return 0;
}
(3)范围for—语法糖(解决“遍历对象的每一个字符”③)
范围for -- 底层:编译器替换成了迭代器 (这里简答了解一下)
void test_string3()
{
// 遍历string的每一个字符
string s1("hello");
cout << s1.size() << endl;
// 范围for -- 原理:替换成迭代器
for (auto ch : s1) //自动取s1中的数据赋值给ch,自动判断结束
{
cout << ch << " ";
}
cout << endl;
}
int main()
{
test_string3();
return 0;
}
【2】相关练习题目:917. 仅仅反转字母
给你一个字符串 s ,根据下述规则反转字符串:
所有非英文字母保留在原有位置。
所有英文字母(小写或大写)位置反转。
返回反转后的 s 。
示例 1:
输入:s = "ab-cd"
输出:"dc-ba"
示例 2:
输入:s = "a-bC-dEf-ghIj"
输出:"j-Ih-gfE-dCba"
示例 3:
输入:s = "Test1ng-Leet=code-Q!"
输出:"Qedo1ct-eeLg=ntse-T!"
(1)做法一:利用下标+[] (已知物理空间是连续的,如果是链表就只能用迭代器)
class Solution {
public:
bool isletter(char ch)
{
if(ch>='a'&&ch<='z')
return true;
else if(ch>='A'&&ch<='Z')
return true;
else
return false;
}
string reverseOnlyLetters(string s) {
int left=0,right=s.size()-1;
while(left<right)
{
while(left<right && !isletter(s[left]))
left++;
while(left<right && !isletter(s[right]))
right--;
swap(s[left],s[right]);
left++;
right--;
}
return s;
}
};
(2)迭代器做法
class Solution {
public:
bool isletter(char ch)
{
if(ch>='a'&&ch<='z')
return true;
else if(ch>='A'&&ch<='Z')
return true;
else
return false;
}
string reverseOnlyLetters(string s) {
string::iterator leftIt=s.begin();
string::iterator rightIt=s.end()-1; //end()是\0, s.end()-1 是最后一个字符
while(leftIt<rightIt)
{
while(leftIt<rightIt && !isletter(*leftIt))
leftIt++;
while(leftIt<rightIt && !isletter(*rightIt))
rightIt--;
swap(*leftIt,*rightIt);
leftIt++;
rightIt--;
}
return s;
}
};
3.std::string::operator[] 和 std::string::at
用法一样,处理方式不一样,越界时 operator[]通过断言抛异常,at抛越界异常
void test_string4()
{
string s1("hello");
const string s2("hello");
s1[0];
s1.at(0);
s1[0] = 'x';
//s2[0] = 'x';
s1.at(0) = 'y';
}
int main()
{
test_string4();
return 0;
}
五.迭代器(共四种迭代器)
介绍:迭代器是什么? -像指针一样的东西或者就是指针
各个类都有独自的迭代器 iterator ,所以要指定是哪个类的,string类中的迭代器写作
string: : iterator
s1.begin()返回第一个位置的指针(或者说第一个位置的迭代器)
s1.end()返回最后一个数据的下一个位置
1.正向迭代器
(1)正向迭代器
还是以 题目1:遍历对象的每一个字 为例
void test_string5()
{
// 遍历string的每一个字符
string s1("hello");
cout << s1.size() << endl;
// 迭代器
string::iterator it = s1.begin(); //it是s1的首元素地址
while (it != s1.end()) //遍历打印,直到it为 \0 停止
{
cout << *it << " ";
++it;
}
cout << endl;
}
int main()
{
test_string5();
return 0;
}
2.反向迭代器
(1)反向迭代器
void test_string5()
{
// 正向迭代器
string s("hello world");
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 反向迭代器
string::reverse_iterator rit = s.rbegin(); // s.rbegin() 表示最后一个字母d的指针
while (rit != s.rend()) // s.rend() 表示首字母h的指针
{
cout << *rit << " ";
++rit; //反向迭代器也是++
}
cout << endl;
}
int main()
{
test_string5();
return 0;
}
3.const正向迭代器 和 4. const反向迭代器
常规的正向迭代器和反向迭代器是可以修改string类对象的内容的:
void test_string5()
{
// 正向迭代器
string s("hello world");
string::iterator it = s.begin();
while (it != s.end())
{
(*it) += 1; //修改string类对象的内容
cout << *it << " ";
++it;
}
cout << endl;
cout << s << endl;
// 反向迭代器
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
(*rit) -= 1; //把string类对象的内容改回去
cout << *rit << " ";
++rit;
}
cout << endl;
cout << s << endl;
}
int main()
{
test_string5();
return 0;
}
const正向迭代器和const反向迭代器:
const正向迭代器:
此处调用只读类型的 const_iterator begin() const;
只读类型的 const_iterator end() const;
const反向迭代器:
调用只读类型的 const_reverse_iterator begin() const;
只读类型的 const_reverse_iterator rend() const;
void Func(const string& rs) //传参时转为const类型
{
string::const_iterator it = rs.begin();
while (it != rs.end())
{
//*it += 1; × 不可以修改
cout << *it << " ";
++it;
}
cout << endl;
//string::const_reverse_iterator rit = rs.rbegin(); 前面类型太长可以考虑用auto
auto rit = rs.rbegin();
while (rit != rs.rend())
{
//(*rit) -= 1; × 不可以修改
cout << *rit << " ";
++rit;
}
cout << endl;
}
void test_string6()
{
// 正向迭代器
string s("hello world");
Func(s);
}
六.string类中的其他用法
string类可以用==判断相等
#include<iostream>
using namespace std;
#include<string>
int main()
{
string s1("hello");
string s2("hello");
if (s1 == s2)
{
cout << "相等";
}
return 0;
}
1.几个小用法
length() 和 size() 一样,都是显示对象中字符串长度,size常用(length是早起写法,不常用)
max_ size() 显示字符串最大能开多长,没什么用
capacity() 显示对象的总容量
2.std::string::reserve
reserve:提前开好n字节空间,不一定是正好n个字节,为了对齐还要多开几个
利用s.push_back( ); 向对象中添加字符,每次容量满了就会扩容1.5倍,很麻烦,已知要添加1000个字符,所以用 reserve:提前开好1000字节空间
void test_string6()
{
string s;
size_t sz = s.capacity();
cout << "making s grow:\n";
cout << "capacity changed: " << sz << '\n';
for (int i = 0; i < 1000; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
int main()
{
test_string6();
return 0;
}
用 reserve:提前开好1000字节空间
void test_string6()
{
string s;
size_t sz1 = s.capacity();
cout << "capacity changed: " << sz1 << '\n'; //查看初始容量
s.reserve(1000); //提前开好1000字节空间
size_t sz = s.capacity();
cout << "making s grow:\n";
cout << "capacity changed: " << sz << '\n'; //查看reserve后的容量
for (int i = 0; i < 1000; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
int main()
{
test_string6();
return 0;
}
3.std::string::resize
resize是开空间并初始化
(1)例一
void test_string6()
{
string s1;
string s2;
//s.reserve(1000); // 扩空间
s1.resize(1000); // 扩空间+初始化
s2.resize(1000, 'x'); // 扩空间+初始化
}
int main()
{
test_string6();
return 0;
}
(2)例二
VS下:reverse不能缩小容量;resize也不能缩小容量,但能去掉前10个后面所有的内容
void test_string6()
{
string s1;
string s2;
s1.resize(100, 'c');
s2.resize(100, 'x');
s1.reserve(10);
s2.resize(10);
}
int main()
{
test_string6();
return 0;
}
4.push_back; append operator+=
插入一个字符用push_back;插入字符串用append;最好用的还是+=
void test_string6()
{
string s;
s.push_back('x');
s.append("hello");
string str("world");
s.append(str);
cout << s << endl;
s += 'x';
s += "hello";
s += str;
cout << s << endl;
}
int main()
{
test_string6();
return 0;
}
+=:
void test_string8()
{
string s("hello");
s += " ";
s += "world";
cout << s << endl;
}
int main()
{
test_string8();
return 0;
}
5.插入数据: insert()
void test_string8()
{
string s("hello");
s.insert(0, 1, 'a'); //在第0个位置插入一个字符a
s.insert(s.begin(), 'b'); //在第0个位置插入一个字符a
cout << s << endl;
s.insert(3, 1, 'x');
s.insert(s.begin()+3, 'y'); //里面放迭代器
cout << s << endl;
}
int main()
{
test_string8();
return 0;
}
insert 如果头插的话,第二次头插数据把后面1个挪动1次,第三次头插数据把后面2个挪动1次,……,第n次头插数据把后面n-1个挪动1次,等差数列相加后时间复杂度是O(N²),不如使用+=后逆置reverse( retStr.begin(), retStr.end()); 可以使时间复杂度降低到O(N) :
题目:415. 字符串相加
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)
{
carry=1;
ret-=10;
}
else
carry=0;
retStr+=ret+'0'; //这里如果用retStr.insert(retStr.begin(),ret+'0')效率低
end1--;
end2--;
}
if(carry==1)
{
retStr+='1';
}
reverse( retStr.begin(), retStr.end());
return retStr;
}
};
6.删除数据: erase()
void test_string8()
{
string s("hello world");
cout << s << endl;
s.erase(s.begin()); //删除迭代器所在位置的字符,删除首字符
cout << s << endl;
s.erase(s.begin() + 3); //删除第3个字符
cout << s << endl;
s.erase(3, 2); //删除包括第3个字符开始后面的2个字符
cout << s << endl;
//s.erase(3); //删除包括第3个字符开始后面所有值,因为缺省值npos是最大正数
s.erase(3, 100); //超出就有多少删多少
cout << s << endl;
}
int main()
{
test_string8();
return 0;
}
当用第三种时,删除区间是左闭右开
void test_string14()
{
string s("hello world");
cout << s << endl;
s.erase(s.begin(),s.begin()+2);
cout << s << endl;
}
int main()
{
test_string14();
return 0;
}
7.交换对象:swap()
void test_string9()
{
string s1("hello world");
string s2("string");
// C++98
s1.swap(s2); // 效率高:直接交换指针
swap(s1, s2); // 效率低:库中的交换函数,是深拷贝交换
}
int main()
{
test_string9();
return 0;
}
STL中的swap函数底层是深拷贝交换:
8.返回C格式字符串 c_str
const char* c_str() const;
获取C字符串等效
返回指向数组的指针,该数组包含以空结尾的字符序列(即C字符串),表示字符串对象的当前值。
此数组包含构成字符串对象值的相同字符序列,最后添加一个终止空字符(“\0”)。
string s1("hello world");
cout << s1 << endl;
cout << s1.c_str() << endl;
注意:他返回的是指针,例如下面的对象a和b就算值一样,但他们的返回值是指针,即a.c_str()==b.c_str()比较的是存储字符串位置的地址,不相同,a.c_str() == b.c_str()为假。
int main(int argc, char* argv[])
{
string a = "hello world";
string b = a;
if (a.c_str() == b.c_str())
{
cout << "true" << endl;
}
else cout << "false" << endl;
return 0;
}
9.std::string::find 和 rfind
(1)find
返回找的第一个字符的起始位置,找不到就返回npos(npos=42亿9千万...)
file.find('.'); 对应第四种用法:size_t find (char c, size_t pos = 0) const; ,'.'对应char c,size_t pos = 0 缺省值是0说明默认从0开始找字符c
void test_string10()
{
// 要求取出文件的后缀
string file("string.cpp");
size_t pos = file.find('.'); //size_t find (char c, size_t pos = 0) const;
if (pos != string::npos)
{
//string suffix = file.substr(pos, file.size() - pos);
string suffix = file.substr(pos); //和下面一样,都是取完后面所有的字符
cout << file << "后缀:" << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
}
int main()
{
test_string10();
return 0;
}
(2)rfind 从后往前取
void test_string10()
{
string file("string.c.tar.zip");
size_t pos = file.rfind('.');
if (pos != string::npos)
{
string suffix = file.substr(pos);
//string suffix = file.substr(pos,file.size()-pos);
cout << file << "后缀:" << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
}
int main()
{
test_string10();
return 0;
}
10. std::string::substr
string substr (size_t pos = 0, size_t len = npos) const;
从pos位置往后取(包括pos位置字符)len个字符,返回取出的这一串string类型的字符。默认缺省参数npos是最大正值。
int main()
{
std::string str("Please, replace the vowels in this sentence by asterisks.");
std::cout << str.substr(6,8) << '\n';
return 0;
}
void test_string11()
{
// 取出url中的域名
string url1("http://www.cplusplus.com/reference/string/string/find/");
string url2("https://leetcode.cn/problems/design-skiplist/solution/tiao-biao-probabilistic-alternative-to-b-0cd8/");
string& url = url1;
// 协议 域名 uri
string protocol;
size_t pos1 = url.find("://");
if (pos1 != string::npos)
{
protocol = url.substr(0, pos1);
cout << "protocol:" << protocol << endl;
}
else
{
cout << "非法url" << endl;
}
string domain;
size_t pos2 = url.find('/', pos1 + 3);
if (pos2 != string::npos)
{
domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << "domain:" << domain << endl;
}
else
{
cout << "非法url" << endl;
}
string uri = url.substr(pos2 + 1);
cout << "uri:" << uri << endl;
}
int main()
{
test_string11();
return 0;
}
11.std::getline (string)
用法是:cin遇到空格或换行就会停下,getline只认换行,遇到空格不会停
题目:HJ1 字符串最后一个单词的长度
#include <iostream>
#include <string>
using namespace std;
int main(){
string str;
getline(cin,str); //getline用法
size_t pos = str.rfind(' ');
cout << str.size()-pos-1;
return 0;
}
12.std::string::find_first_of
在string类对象中找到第一个出现的字符c,返回此字符的下标值。
int main()
{
std::string str("Please, replace the vowels in this sentence by asterisks.");
std::size_t found = str.find_first_of("e");
int a = 4;
while (a--)
{
str[found] = '*';
found = str.find_first_of("e", found + 1);
}
std::cout << str << '\n';
return 0;
}
如果找字符串的话,找到包含字符中出现的第一个,并返回下标值
int main()
{
std::string str("Please, replace the vowels in this sentence by asterisks.");
std::size_t found = str.find_first_of("aeiou");
int a = 4;
while (a--)
{
str[found] = '*';
found = str.find_first_of("aeiou", found + 1);
}
std::cout << str << '\n';
return 0;
}
题目:387. 字符串中的第一个唯一字符
class Solution {
public:
int firstUniqChar(string s) {
int count[26]={0};
for(char ch:s)
{
count[ch-'a']++;
}
for(int i=0;i<s.size();i++)
{
if(count[s[i]-'a']==1)
return i;
}
return -1;
}
};