C++ 문자열 클래스(1) - 초기화, 용량 연산, 반복자

목차

머리말

1. 문자열 클래스

2. 초기화

1. 인삼이 없거나 인삼이 있는 경우

2. 문자열 변수로 초기화

3. 문자열로 초기화

4. 문자 수를 지정하십시오

3. 용량운영

1, 크기

2、푸시백

3. 추가편집

4. += 연산자

5. vs 아래의 문자열 구조

6. g++의 문자열 구조

7, 예약

8, 크기 조정

4. 반복자

1. 정방향 반복자

2. 역방향 반복자 

3. const 반복자(정방향 및 역방향)

5. OJ 연습

반전 문자

문자열에 한 번 나타나는 문자 찾기 


머리말

문자열 클래스 템플릿은 다음과 같은데, 왜 여러 개가 있는 걸까요?

이러한 다양한 문자열 클래스 템플릿은 다양한 문자 인코딩과 문자 집합을 처리하도록 설계되었습니다. 각 템플릿은 특정 유형의 문자 데이터를 처리하도록 설계되었습니다.

  1. std::string: 이것은 ASCII 문자 집합을 처리하는 데 사용되는 가장 일반적인 문자열 클래스 템플릿입니다. 단일 바이트 문자 표현을 사용하며 대부분의 일반 문자열 작업에 적합합니다.

  2. std::wstring: 유니코드 문자를 처리하는 데 사용되는 문자열 클래스 템플릿의 와이드 문자 버전입니다. wchar_t 유형을 사용하여 문자를 표현하며 다국어 문자 세트를 처리해야 하는 상황에 적합합니다.

  3. std::u16string: UTF-16으로 인코딩된 문자열을 처리하기 위한 템플릿입니다. UTF-16은 16비트 인코딩을 사용하여 문자를 표현하며 대부분의 유니코드 문자와 같은 더 큰 문자 집합을 처리하는 데 적합합니다.

  4. std::u32string: UTF-32로 인코딩된 문자열을 처리하기 위한 템플릿입니다. UTF-32는 32비트 인코딩을 사용하여 문자를 표현하며 모든 유니코드 문자가 포함된 문자 집합을 처리하는 데 적합합니다.

이러한 다양한 문자열 클래스 템플릿은 다양한 유형의 텍스트 데이터로 작업할 때 문자를 올바르게 표현하고 조작할 수 있도록 다양한 문자 인코딩 및 문자 집합을 지원합니다. 적절한 문자열 클래스 템플릿을 선택하면 다양한 애플리케이션 시나리오에서 문자 데이터가 올바르게 처리되고 조작되도록 할 수 있습니다.

 

먼저 컴퓨터가 문자를 저장하는 방법을 이해해 보겠습니다. 

컴퓨터는 모두 바이너리 형태로 되어 있어 문자나 기호를 직접 저장할 수 없는데, 이때 매핑 테이블이 필요하게 되면서 ASCll이 탄생하게 되었습니다.

 여러 나라의 텍스트를 컴퓨터에 표시하기 위해 유니코드 탄생

 유니코드(유니버설 코드 및 유니코드라고도 함)는Unicode Alliance에 의해 개발되었으며등. 코딩 체계를 포함한 컴퓨터 과학 분야의 표준, 문자 집합

유니코드는 기존문자 인코딩 체계의 한계를 해결하기 위해 만들어졌으며 각 언어의 각 문자에 맞게 설계되었습니다. 고유한바이너리 인코딩은 교차 언어 및 교차 플랫폼

유니코드 문자 인코딩 체계는 UTF-8, UTF-16 및 UTF-32로 구분되며 컴퓨터 시스템에서 다양한 범위의 유니코드 문자를 표현하고 처리하도록 설계되었습니다.

  1. UTF-8: UTF-8은 1~4바이트를 사용하여 다양한 유니코드 문자를 나타내는 가변 길이 인코딩 체계입니다. ASCII 문자 집합과 호환되고 일반 문자를 표현할 때 공간을 절약하기 때문에 가장 일반적으로 사용되는 유니코드 인코딩 체계 중 하나입니다. UTF-8은 특히 인터넷과 컴퓨터 네트워크에서 널리 사용되는 경우 텍스트 데이터를 저장하고 전송할 때 공간을 절약하는 데 적합합니다.

  2. UTF-16: UTF-16은 유니코드 문자를 표현하기 위해 16비트 인코딩을 사용하는 고정 길이 또는 가변 길이 인코딩 체계입니다. 가장 일반적인 유니코드 문자의 경우 UTF-16은 16비트 인코딩을 사용하지만 덜 일반적으로 사용되는 일부 문자의 경우 두 개의 16비트 인코딩이 필요합니다. UTF-16은 다중 언어 텍스트 처리 및 국제화 애플리케이션과 같이 더 큰 문자 집합을 처리해야 하는 상황에 적합합니다.

  3. UTF-32: UTF-32는 유니코드 문자를 표현하기 위해 32비트 인코딩을 사용하는 고정 길이 인코딩 체계입니다. 모든 유니코드 문자는 해당 문자가 일반적인지 여부에 관계없이 32비트 인코딩을 사용하여 표현됩니다. UTF-32는 일부 특정 필드의 텍스트 처리 및 문자 수준 작업과 같이 모든 유니코드 문자가 포함된 문자 집합을 처리해야 하는 상황에 적합합니다.

이러한 다양한 유니코드 인코딩 체계는 서로 다른 장단점과 적용 가능성을 제공합니다.특정 요구 사항과 응용 프로그램 시나리오에 따라 적절한 인코딩 체계를 선택하여 유니코드 문자를 표현하고 처리할 수 있습니다.

그 중 UTF-8이 가장 많이 사용된다.

중국에는 한자에 대한 GBK 인코딩도 있습니다. 이는 일부 희귀 문자를 지원합니다.

1. 문자열 클래스

문서 소개 

  • String은 일련의 문자를 나타내는 클래스입니다. 표준 문자열 클래스는 이러한 객체에 대한 지원을 제공합니다. 해당 인터페이스는 표준 문자 컨테이너의 인터페이스와 유사하지만 특히 단일 바이트 문자열을 작동하기 위한 디자인 기능을 추가합니다.
  • string 클래스는 기본 char_traits 및 할당자 유형을 사용하여 char를 문자 유형으로 사용합니다(템플릿에 대한 자세한 내용은 basic_string 참조).
  • string 클래스는 basic_string 템플릿 클래스의 인스턴스로, basic_string 템플릿 클래스를 인스턴스화하기 위해 char을 사용하고, basic_string의 기본 매개변수로 char_traits 및 할당자를 사용합니다(자세한 템플릿 정보는 basic_string을 참조하세요).
  • 이 클래스는 사용된 인코딩과 관계없이 바이트를 처리합니다. 멀티바이트 또는 가변 길이 문자(예: UTF-8)의 시퀀스를 처리하는 데 사용되는 경우 이 클래스의 모든 멤버(예: 길이 또는 크기) 및 해당 반복자는 프로세서에서 (실제 인코딩된 문자가 아닌) 여전히 바이트 단위로 작동합니다.
요약하다:
  • string은 문자열을 나타내는 문자열 클래스입니다.
  • 이 클래스의 인터페이스는 기본적으로 문자열을 작동하는 데 사용되는 일부 일반 작업이 추가된 일반 컨테이너의 인터페이스와 동일합니다.
  • 맨 아래 수준에서 string은 실제로 basic_string 템플릿 클래스의 별칭, typedef basic_string<char, char_traits, allocator> string입니다.
  • 멀티바이트 또는 가변 길이 문자 시퀀스는 작동할 수 없습니다.
문자열 클래스를 사용할 때는 #include 헤더 파일 문자열을 포함하고 네임스페이스 std를 사용해야 합니다.;

2. 초기화

  1. 기본 생성자 string(): 빈 문자열 객체를 생성합니다.

  2. 생성자 복사 string(const string& str): 다른 문자열 객체를 복사하여 새 문자열 객체를 만듭니다 str .

  3. 하위 문자열 생성자 string(const string& str, size_t pos, size_t len = npos): 문자열 객체의 지정된 위치에서 시작하여 str pos , 새 A 문자열을 만듭니다. 길이가 있는 개체 lenlen 인수가 제공되지 않으면 기본적으로 문자열 끝에 하위 문자열이 생성됩니다.

  4. C-String 생성자에서 string(const char* s): null로 끝나는 C 문자열에서 새 문자열 객체를 생성합니다 s .

  5. 시퀀스의 생성자 string(const char* s, size_t n): C 문자열 s 문자열 개체의 첫 번째 n 문자에서 새 문자를 생성합니다. .

  6. 채우기 생성자 string(size_t n, char c)n 문자 c 를 포함하는 새 문자열 개체를 만듭니다.

  7. 범위 생성자 template <class InputIterator> string(InputIterator first, InputIterator last): 반복자 범위의 문자를 통해 새 문자열 객체를 생성합니다 [first, last) . 이 생성자는 포인터, 컨테이너 반복자 등과 같은 다양한 유형의 반복자를 허용할 수 있습니다.

1. 인삼이 없거나 인삼이 있는 경우

매개변수 없이 초기화하거나 매개변수를 사용하여 초기화할 수 있습니다.

int main()
{
	string s1;
	string s2("hello world");
	string s3 = "hello";
	return 0;
}

 [ ] 연산자를 통해 문자열의 특정 위치에 접근할 수도 있습니다.​ 

#include<string>
int main()
{
	string s2("hello world");
	for (size_t i = 0; i < s2.size(); ++i) {
		s2[i]++;
	}
	cout << s2 << endl;
	return 0;
}

스트림 삽입 연산자 <<를 통해 문자열 클래스 객체를 출력할 수 있습니다.​ 

이것은 스트림 삽입 연산자<<가 문자열 클래스에 오버로드되어 있기 때문입니다. Stream 추출도 다시 로드되었습니다.

int main()
{
	string s2;
	cin >> s2;
	for (size_t i = 0; i < s2.size(); ++i) {
		s2[i]++;
	}
	cout << s2 << endl;
	return 0;
}

 

2. 문자열 변수로 초기화

한 문자의 지정된 위치부터 지정된 문자 수로 다른 문자를 초기화합니다.

int main()
{
	string s3 = "hello";
	string s4(s3, 2, 3);
	cout << s4 << endl;
	return 0;
}

가져올 문자 수가 전체 문자 길이를 초과하는 경우 끝까지 가져옵니다.​ 

int main()
{
	string s3 = "hello";

	string s4(s3, 2, 3);
	cout << s4 << endl;

	string s5(s3, 2, 10);
	cout << s5 << endl;
	return 0;
}

 

세 번째 매개변수는 기본적으로 문자 정수일 수 있으며, 지정된 위치부터 끝까지 가져옵니다.​ 

int main()
{
	string s3 = "hello";

	string s4(s3, 2, 3);
	cout << s4 << endl;

	string s5(s3, 2);
	cout << s5 << endl;
	return 0;
}

3. 문자열로 초기화

첫 번째 파라미터 위치에 직접 할당할 문자열을 넣을 수도 있고, 두 번째 파라미터는 할당된 문자 개수입니다.​ 

int main()
{
	string s7("hello world", 5);
	cout << s7 << endl;
	string s8("hello world", 5 , 6);
	cout << s8 << endl;

	return 0;
}
  • 첫 번째 생성자에서 문자열을 제외한 매개변수가 하나만 있는 경우 첫 번째 매개변수의 크기와 길이는 기본적으로 문자열의 첫 번째 문자부터 지정됩니다.
  • 두 번째 생성자에서 문자열에 두 개의 매개변수가 있는 경우 첫 번째 매개변수는 지정된 시작 위치, 두 번째 매개변수는 지정된 초기화 길이이며, 길이가 실제 문자열의 길이보다 길면 다음으로 잘립니다. 실제 문자열 길이.

 

4. 문자 수를 지정하십시오

int main()
{
	string s9(10, '$');
	cout << s9 << endl;

	return 0;
}

3. 용량운영

1、크기

size()와 length()의 기본 구현 원칙은 정확히 동일하며 둘 다 문자열의 유효 문자 길이를 얻는 데 사용됩니다., 는 size()를 도입합니다. 이유는 다른 컨테이너의 인터페이스와 일관성을 유지하기 위함이며, 일반적으로 size()를 사용합니다.
int main()
{
	string s1("hello world");
	cout << s1.size() << endl;
	cout << s1.length() << endl;

	return 0;
}

다음 두 가지를 이해할 수 있습니다.

  • max_size()이 함수는 문자열 객체가 보유할 수 있는 최대 문자 수를 나타내는 부호 없는 정수를 반환합니다. 이 값은 일반적으로 시스템 제한 사항에 따라 달라지므로 운영 체제와 컴파일러에 따라 다를 수 있습니다.
  • capacity()이 함수는 문자열 객체에 현재 할당된 메모리 공간의 크기를 나타내는 부호 없는 정수를 반환합니다. 문자열 클래스는 일반적으로 확장을 위해 추가 공간을 예약하므로 이 값은 문자열에 포함된 실제 문자 수보다 클 수 있습니다.​ 
int main()
{
	string s1("hello world");
	cout << s1.max_size() << endl;
	cout << s1.capacity() << endl;

	return 0;
}

 64비트 미만의 출력 결과:

2、푸시백

문자열 끝에 단일 문자 추가 

int main()
{
	string s1("hello");
	s1.push_back(' ');
	s1.push_back('!');
	cout << s1 << endl;

	return 0;
}

3、추가

가장 일반적으로 사용되는 형식인 문자열 끝에 단일 문자나 문자열을 추가하기 위해 추가를 사용할 수도 있습니다.

int main()
{
	string s1("hello");
	s1.push_back(' ');
	s1.push_back('!');
	cout << s1 << endl;

	s1.append("world");
	cout << s1 << endl;
}

 

4. += 연산자

+= 연산자를 사용하여 문자열 끝에 문자나 문자열을 추가할 수도 있습니다. +=의 맨 아래 레이어는 push_back 또는 추가를 호출하는 것입니다. 문자열 끝에 문자를 추가하는 경우 s.push_back(c) / s.append(1, c) / s += 'c'의 세 가지 구현 방법은 유사합니다. 일반적으로 문자열 클래스의 += 연산이 더 자주 사용됩니다. += 연산은 단일 문자뿐만 아니라 문자열도 연결합니다.

int main()
{
	string s1("hello");
	s1 += ' ';
	s1 += '!';
	s1 += "world";
	cout << s1 << endl;
}

 

5, vs 아래의 문자열 구조

먼저 다음 코드를 살펴보세요. 

int main()
{
	size_t sz = s.capacity();
	cout << "making s grow:\n";
	cout << "capacity changed: " << sz << '\n';
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

위는push_back() 함수를 사용하여 문자열 객체에 문자를 추가하고 문자열 용량의 변화를 관찰하는 방법을 보여줍니다.

  • 먼저 유형의 변수가 main() 함수에서 선언되고 문자로 초기화됩니다. 문자열 객체 (가 이전에 정의된 문자열 객체라고 가정). size_tszss
  • 그런 다음 cout 개체와 << 연산자를 사용하면 "making s 성장:"이라는 프롬프트 메시지가 출력됩니다.
  • 다음으로 루프를 사용하여 문자열 객체에 'c' 문자를s 0부터 99까지 하나씩 추가합니다. 각 문자를 추가한 후 이전에 기록된 용량sz과 현재 문자열 개체의 용량s을 비교하여 용량이 변경되었는지 확인합니다.
  • 용량이 변경되면 새 용량 값을 sz에 할당하고 cout 개체 및 << 연산자를 사용합니다. "capacity selected:"라는 프롬프트 메시지와 새 용량 값을 출력합니다.

여기서 출력되는 용량에는 \0이 포함되지 않습니다. 즉, 실제 용량에 1을 더해야 함을 의미합니다.

모니터링에서 볼 수 있듯이 문자는 실제로 _Buf 배열에 저장됩니다.

        C++ 구현에서 std::string 클래스는 일반적으로 문자열의 문자를 저장하기 위해 두 개의 배열을 사용합니다. 문자열의 길이가 15자 이하인 경우(16번째 문자는 \0) 문자열의 문자는 _Buf라는 내부 고정 크기 배열에 저장됩니다. 배열은 15입니다. 이렇게 하면 동적 메모리 할당이 방지되고 성능이 향상됩니다.

        문자열의 길이가 15자를 초과하면 문자열의 문자는 _Ptr이라는 동적으로 할당된 배열에 저장되며, 배열의 길이는 필요에 따라 조정됩니다. 동적 조정. 이를 통해 더 긴 문자열을 수용할 수 있으며 필요에 따라 메모리를 동적으로 할당할 수 있습니다.

        이 디자인은 문자열이 짧을 때 메모리를 절약하고 문자열이 길 때 충분한 저장 공간을 제공할 수 있습니다. 구체적인 구현은 컴파일러와 표준 라이브러리에 따라 다를 수 있지만, 짧은 문자열과 긴 문자열을 구별하는 이러한 전략은 일반적인 최적화 기술 중 하나입니다.

vs: string 아래의 문자열 구조는 총 28바이트를 차지하며 내부 구조는 조금 더 복잡합니다.먼저 문자열에서 문자열의 저장 공간을 결정하는 데 사용되는 공용체가 있습니다.
  • 문자열 길이가 15보다 작거나 같으면 내부 고정 문자 배열을 사용하여 저장합니다.
  • 문자열 길이가 16보다 크거나 같으면 힙에서 공간이 할당됩니다.
union _Bxty
{ // storage for small buffer or pointer to larger one
	value_type _Buf[_BUF_SIZE];
	pointer _Ptr;
	char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
  • 이런 디자인도 의미가 있습니다.대부분의 경우 문자열의 길이는 16자 미만입니다.문자열 객체가 생성된 후 내부에는 이미 16자 배열을 위한 고정된 공간이 있으므로 이를 통해 생성할 필요가 없습니다. 힙은 매우 효율적입니다.
  • 두 번째: 문자열 길이를 저장하는 size_t 필드와 힙에 열린 공간의 총 용량을 저장하는 size_t 필드도 있습니다
  • 마지막으로, 다른 작업을 수행하는포인터도 있습니다.
  • 따라서 총 16+4+4+4=28바이트를 차지합니다.

문자열 클래스 객체의 크기를 확인하는 출력 

string s;

cout << sizeof(s) << endl;

 28이므로 힙의 시작 크기는 32임을 알 수 있습니다.

확장 상황을 살펴보면 32사이즈부터 매번 용량이 1.5배씩 확장되는 것을 알 수 있다.​ 

 

6. g++의 문자열 구조

G++에서는 string을 copy-on-write 방식으로 구현하는데, string 객체는 총 4바이트를 차지하며 내부적으로 하나의 포인터만 포함하고 있다.
바늘은 다음 필드를 포함하는 미래의 힙 공간을 가리킵니다.
  • 전체 공간 크기
  • 문자열의 유효 길이
  • 참조 카운팅
struct _Rep_base
{
    size_type _M_length;
    size_type _M_capacity;
    _Atomic_word _M_refcount;
};

문자열을 저장하는 데 사용되는 힙 공간에 대한 포인터입니다.

7, 예약

필요한 공간 크기가 알려지면 예비 공간을 사용하여 미리 공간을 확보하여 용량 확장을 줄이고 효율성을 높일 수 있습니다.​ 

 

한 번에 100자 공간을 열려면 reverse를 사용하세요.​ 

int main()
{
	string s;

	s.reserve(100);
	size_t sz = s.capacity();
	cout << "making s grow:\n";
	cout << "capacity changed: " << sz << '\n';
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

이때 용량을 계속해서 확장할 필요가 없습니다.​ 

 문자열을 연산할 때 대략적으로 몇 글자를 넣을지 짐작할 수 있다면 예약을 통해 먼저 공간을 확보할 수 있다.

8, 크기 조정

resize는 문자열의 크기를 n자 길이로 조정합니다.

  • n이 현재 문자열 길이보다 작으면 현재 값을 처음 n자로 줄이고 n번째 문자 이외의 문자를 삭제합니다.
  • n이 현재 문자열 길이보다 큰 경우 n 크기에 도달할 때까지 가능한 한 많은 문자 0을 끝에 삽입하여 현재 내용을 확장합니다.
  • 패딩 문자 c가 지정되면 새 요소는 c의 복사본으로 초기화되고, 그렇지 않으면 값이 초기화된 문자(널 문자)입니다.
int main()
{
	// 扩容
	string s1("hello world");
	s1.reserve(100);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	// 扩容+初始化
	string s2("hello world");
	s2.resize(100);
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;

	return 0;
}

resize(size_t n) 및 resize(size_t n, char c)는 모두 문자열의 유효한 문자 수를 n으로 변경합니다. 차이점은 문자 수가 증가할 때 resize(n)는 추가 문자를 0 요소 공간으로 채운다는 것입니다. , resize(size_t n, char c)는 문자 c를 사용하여 추가 요소 공간을 채웁니다. 참고: 크기 조정으로 요소 수가 변경되는 경우 요소 수가 증가하면 기본 용량의 크기가 변경될 수 있으며, 요소 수가 줄어들면 기본 공간의 전체 크기는 변경되지 않습니다.

Reserve(size_t res_arg=0): 유효한 요소의 수를 변경하지 않고 문자열을 위한 공간을 예약합니다. 예약의 매개변수가 문자열의 기본 공간의 전체 크기보다 작은 경우 예약은 용량을 변경하지 않습니다.

4. 반복자

1. 정방향 반복자

start는 문자열의 첫 번째 문자를 가리키고, end는 문자열의 마지막 문자 다음 위치를 가리키며 그 기능은 포인터와 유사합니다.

int main()
{
	string s1("hello world");
	string::iterator it = s1.begin();
	while (it != s1.end()) {
		cout << *it << " ";
		++it;
	}
    return 0;
}

 

범위의 맨 아래 레이어는 iterator를 호출하여 구현됩니다.​ 

int main()
{
	string s1("hello world");
	string::iterator it = s1.begin();
	while (it != s1.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto ch : s1) {
		cout << ch << " ";
	}
	cout << endl;
}

 

2. 역방향 반복자 

rbegin은 문자열의 마지막 문자를 가리키고, rend는 문자열의 첫 번째 문자 앞의 위치를 ​​가리킵니다.​ 

 

int main()
{
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend()) {
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
    return 0;
}

3. const 반복자(정방향 및 역방향)

  Const 정방향 및 역방향 반복자는 데이터를 탐색하고 읽을 수만 있습니다.

int main()
{
	string s1("hello world");

	string::const_iterator it = s1.begin();
	while (it != s1.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;

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

	cout << s1 << endl;

	return 0;
}

이때 auto를 사용하면 자동으로 유형을 추론할 수 있습니다.​ 

int main()
{
	//string::const_iterator it = s1.begin();
	auto it = s1.begin();
	while (it != s1.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;

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

	return 0;
}

 

5. OJ 연습

반전 문자

917. 그냥 역문자 - LeetCode

빠른 정렬 아이디어를 사용하십시오.​ 

class Solution {
public:
    string reverseOnlyLetters(string s) {
        size_t begin = 0, end=s.size()-1;
        while(begin<end){
            while(begin<end&&!isalpha(s[begin]))
                ++begin;
            while(begin<end&&!isalpha(s[end]))
                --end;
            swap(s[begin],s[end]);
            ++begin;
            --end;
        }
    return s;
    }
};

문자열에 한 번 나타나는 문자 찾기 

 387. 문자열의 첫 번째 고유 문자 - LeetCode

계산 정렬 아이디어를 사용하십시오.​ 

class Solution {
public:
    int firstUniqChar(string s) {
        int count[26]={0};
        for(auto ch:s){
            count[ch-'a']++;
        }
        for(int i=0;i<s.size();i++){
            if(count[s[i]-'a']==1)
                return i;
        }
        return -1;
    }
};

추천

출처blog.csdn.net/m0_73800602/article/details/134744293