C++String类

前言

大家好,我是jiantaoyab,本篇文章将给大家介绍String类的常用法,和模拟实现String类。

String介绍

在cplusplus中,对String有着下面的介绍。

The standard string class provides support for such objects with an interface similar to that of a standard container of bytes, but adding features specifically designed to operate with strings of single-byte characters.

The string class is an instantiation of the basic_string class template that uses char (i.e., bytes) as its character type, with its default char_traits and allocator types (see basic_string for more info on the template).

可以看到string类不是STL的容器,string类是basic_string类模板的实例化。

为什么basic_string要设计成类模板呢?

因为不同的语言有不同的编码,有gbk,unicode,ascii等,basic_string独立于所使用的编码来处理字节,如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

String类常用接口

目前的标 string 类有 106 个接口函数(包括构造和析构函数),如果考虑上默认参数,那么就一共有 134 不同的接口。其中有 5 个函数模板还会产生无穷多个各种各样的函数,所以我们只需要知道常用的接口就够了。

string类常见构造

string() 构造空的string类对象,即空字符串
string(const char* s) 用C-string来构造string类对象
string(size_t n, char c) string类对象中包含n个字符c
string(const string&s) 拷贝构造函数

string类对象的容量操作

size 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
empty 检测字符串释放为空串,是返回true,否则返回false
clear 清空有效字符,不改变底层空间的大小
reserve 为字符串预留空间
resize 将有效字符的个数该成n个,多出的空间用字符’\0’填充
int main()
{
    string s1("hello");
    s1.reserve(2); //hello 不变
	s1.reserve(1000);//申请1000个空间
	string s2("xxxxxxxxxxxxx");
	s2.resize(1000);//申请1000个空间+初始化为0
	s2.resize(5);//xxxxx只剩下5个,capacity不变
}

注意

  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在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

  1. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

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

operator[] 返回pos位置的字符,const string类对象调用
begin+ end begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rend begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
范围for

string类对象的修改操作

push_back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= 在字符串后追加字符串str
c_str 返回C格式字符串
find + npos 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置(包括pos)
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置(包括pos)
substr 在str中从pos位置开始(包括pos),截取n个字符,然后将其返回
void test()
{
    //取文件的后缀名
    size_t pos = file.find('.');
    if(pos != npos)
    {
        string suffix = file.substr(pos);
    }  
    //取address
    string url("http://www.cplusplus.com/reference/string/string/find/");
    size_t start = url.find(':');
    string protocol = url.substr(0, start - 0);
    start += 3; //从www开始找
    size_t end = url.find('/', start);
    string address = url.substr(start, finish - start);
    
    //删除
    url.erase(0, npos);
}

string类非成员函数

operator+ 尽量少用,因为传值返回,导致深拷贝效率低
operator>>
operator<<
getline 获取一行字符串
relational operators 大小比较

String题目练习

字符串最后一个单词的长度

#include <iostream>
using namespace std;
int main() {
    string s;
    while(getline(cin, s))
    {
        size_t pos = s.rfind(' ');
        cout<<s.size() - pos -1 <<endl;
    }
}

验证回文串

class Solution {
public:
    bool isletter(char a)
    {
        if(a>='a'&&a<='z')
        return true;
        if(a>='A'&&a<='Z')
        return true;
        if(a>='0'&&a<='9')
        return true;
     
        return false;
    }
    bool isPalindrome(string s) {
        int left=0;
        int right=s.size()-1;
        while(left<right)
        {
            while(left<right&&!isletter(s[left]))
            {
                left++;
            }
             while(left<right&&!isletter(s[right]))
            {
                right--;
            }


            if(tolower(s[left])!=tolower(s[right]))
            {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
};

字符串相加

class Solution {
public:
    string addStrings(string num1, string num2) {
        string ret;
        int size1=num1.size()-1;
        int size2=num2.size()-1;    
        int carry=0;
        //nums1 和 nums2 其中一个还没走完
        while(size1 >= 0 || size2 >= 0)
        {
            int x = 0;
            if(size1 >= 0)
            {
                x = num1[size1]-'0';
                --size1;
            }
            int y = 0;
            if(size2 >= 0)
            {
                y = num2[size2]-'0';
                --size2;
            }
            int add_sum = x + y + carry;
            if(add_sum > 9)
            {
                carry = 1;
                add_sum -= 10;
            }
            else{
                carry=0;
            }
            ret += add_sum + '0';     
        }
        if(carry==1)
        {
            ret += '1';
        }
        reverse(ret.begin(), ret.end());
        return ret;    
    }
};

反转字符串 II

image-20240327213914377

class Solution {
public:
    void reverse(string& s,int x,int y)
    {
        int left=x;
        int right=y;
        while(left<right)
        {
            swap(s[left],s[right]);
            left++;
            right--;
        }
    }
    string reverseStr(string s, int k) {
        for(int i=0;i<s.size();i+=(2*k))
        {
            if(i+k<=s.size())
            {
                reverse(s,i,i+k-1);
                continue;
            }
            else
            {
                reverse(s,i,s.size()-1);
            }
        }
       return s;
    }
};

反转字符串中的单词 III

image-20240327214053527

class Solution {
public:
	string reverseWords(string s)
	{
        int Word_begin=0;
        int Word_end=0;
        int i=0;
        int lenth=s.length();
        while(i<lenth)
        {
            Word_begin=i;
            while(i<lenth&&s[i]!=' ')
            {
                 i++;//找空格位置
            }
            Word_end=i-1;
            while(Word_begin<Word_end)
            {
                swap(s[Word_begin],s[Word_end]);
                Word_begin++;
                Word_end--;
            }
            while(i<lenth&&s[i]==' ')
            {
                i++;
            }

        }
        return s;
    }

};

字符串相乘

image-20240327214237696

class Solution {
public:
    string multiply(string num1, string num2) {
        int size1=num1.length();
        int size2=num2.length();
        vector<int>v(size1 + size2);
        for(int i=0;i<size1;i++)
        {
            for(int j=0;j<size2;j++)
            {
                int x=num1[size1-i-1]-'0';
                int y=num2[size2-j-1]-'0';
                v[i+j] += x*y;
            }
        }
        for(int i=0,carry=0;i<v.size();i++)
        {
            v[i]+=carry;
            carry=v[i]/10;
            v[i]%=10;
        }


        string ret;
        for(int i=v.size()-1;i>=0;i--)
        {
            if(ret.empty()&&v[i]==0) continue;
            ret+=v[i]+'0';
        }

        return ret.empty()? "0" :ret;
    }
};

找出字符串中第一个只出现一次的字符

#include <iostream>
#include<string>
using namespace std;
 
int main() {
    string ch;
    cin >> ch;
    char c;
    int sum_ch[256] = { 0 };
    int size = ch.size();
    for (int i = 0; i<size; i++)
    {
        sum_ch[ch[i]]++;
    } 
    for (int i = 0; i<size; i++)
    {
        if (sum_ch[ch[i]] == 1)
        {
            cout << ch[i] << endl;
            return 0;
        }
    }
    cout<<-1;
    return 0;
}

String模拟实现

详细的代码大家可以看string模拟实现具体代码,下面我拆分成每个模块给大家介绍。

迭代器部分

typedef char* iterator;
typedef const char* const_iterator;
	const_iterator begin() const
	{
		return _str;
	}
	const_iterator end()const
	{
		return _str + _size;
	}
	iterator begin()
	{
		return _str;
	}
	iterator end()
	{
		return _str + _size;
	}

范围for的是调用迭代器实现的

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

image-20240328214516411

构造、拷贝构造、赋值拷贝

string(const char* str = " ") //字符串默认给个'\0'
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)

		{
			string tmp(s._str);
			this->Swap(tmp);
		}

string& operator=(string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				this->Swap(tmp);				
			}
			return *this;
		}

空间容量

void reserve(size_t n)
{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;

			}
}

void resize(size_t n, char ch = '\0')
{
			if (n <= _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				if (n > _capacity)	reserve(n);				
				memset(_str + _size, ch, n - _size);
				_size = n;
				_str[_size] = '\0';
			}
}

增删改查


string& insert(size_t pos, const char* str)
{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (len + _size > _capacity) reserve(len + _size);
			size_t end = _size + len;
			//整体向后移动len位
			while (end > pos + len) _str[end--] = _str[end - len];
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;
}

string& erase(size_t pos = 0, size_t len = npos)
{
			assert(pos < _size);
			if (len == npos || pos + len > _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		
			return *this;
}

void append(const char* str)
{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str); //包括'\0' 也拷贝过来
			_size += len;
}

void push_bach(char ch)
{
			//空间不够增容
			if (_size == _capacity)
			{
				reserve(_size == 0 ? 4 : 2 * _capacity);
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
}
size_t find(char ch)
{
			for (size_t i; i < _size; i++)
			{
				if (_str[i] == ch) return i;
				
			}
			return npos;
}

size_t find(const char* str, size_t pos = 0)
{
			char* p = strstr(pos + _str, str);
			if (p == nullptr) return npos;
			else return p - _str; //找到返回子串第一个字符的下标
}

char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
string& operator+=(char ch)
		{
			push_bach(ch);
			return *this;
		}
string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

c语言几个常用的字符操作函数

可以看到上面实现的代码经常运用了c的字符操作函数,我在这里做一个总结。

strcpy

image-20240330115049708

  • 源字符串必须以 ‘\0’ 结束。
  • 会将源字符串中的 ‘\0’ 拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变

模拟实现

char *my_strcpy(char* dest, const char*src)
{
	char *ret = dest;
	assert(dest != NULL);
	assert(src != NULL);
	while ((*dest++ = *src++));
	return ret;
}

strncpy

image-20240330202805944

拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个 。

strcmp

比较的是字符串的内容,对应的ASCLL值

image-20240330202932874

  • 第一个字符串大于第二个字符串,则返回大于0的数字
  • 第一个字符串等于第二个字符串,则返回0
  • 第一个字符串小于第二个字符串,则返回小于0的数字

模拟实现

int my_strcmp (const char * src, const char * dst)
{
	int ret = 0 ;
	assert(src != NULL);
	assert(dest != NULL);
	while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
	++src, ++dst;
	if ( ret < 0 )
	ret = -1 ;
	else if ( ret > 0 )
	ret = 1 ;
	return( ret );
}

strstr

字符串查找,在str1里找str2,找到就返回第一次找到的位置,找不到返回NULL

image-20240330203313354

模拟实现

char* my_strstr(const char*str1, const char* str2)
{
	assert(str1 && str2);
	char* s1;
	char* s2;
	char* cp = str1;
	
	if (*str2 == '\0')
		return str1;
	//cp是来记录从这个字母开始比较的,如果这次比较失败就++,下个字母开始重新比较。
	while (*cp)
	{
		s1 = cp;
		s2 = str2;

		//while (*s1!='\0'  && *s2 != '\0' && *s1 == *s2)
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return cp;
		}
		cp++;
	}

	//找不到
	return NULL;
}

strcat

字符串追加

image-20240330203632576

char *my_strcat(char *dest, const char *src) //字符串追加
{
	assert(dest != NULL);
	assert(src != NULL);
	char *ret = dest;
    //1.找尾
	while (*dest)
	{
		dest++;
	}
    //2.追加
	while ((*dest++ = *src++))
	{
		;
	}

	return ret;
}

自己给自己追加可以吗?

两个指针同时操作一个字符串,当*dest找到 ‘\0’ 之后,*src开始追加,当追加完一个字符后,*src往后走一位,*dest指向的 '\0’被覆盖,dest就必须往后走一位,找到下一个’\0’,找到之后src又开始追加,不断重复,导致程序崩溃。

C语言常用的内存函数

memcpy

image-20240330203910491

  • 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  • 这个函数在遇到 ‘\0’ 的时候并不会停下来。
  • 如果source和destination有任何的重叠,复制的结果都是未定义的

模拟实现

void *my_memcpy(void* dest, const void *str, size_t num)
{
	//一个字节一个字节拷贝
	void *ret = dest;
	assert(dest&&str);
	while (num--)
	{
		*(char*)dest = *(char*)str;
		dest = (char*)dest + 1;
		str = (char*)str + 1;
	}
	return ret;
}

memmove

image-20240330204109452

和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。如果源空间和目标空间出现重叠,就得使用memmove函数处理。

模拟实现

void*my_memove(void *dest, const void*str, size_t num)
{
	assert(dest&&str); 
	void *ret = dest;
	if (dest > str)
	{
		//后向前拷贝
		while (num--)
		{
			*((char*)dest+num) = *((char*)str+num);	
		}
	}
	else
	{
		//前向后拷贝
		while (num--)
		{
			*(char*)dest = *(char*)str;
			str = (char*)str+1;
			dest = (char*)dest + 1;
		}
	}
	return ret;
}

memset

从prt指向的字符串开始,插入num个 value

image-20240330204434127

memcmp

image-20240330204320840
比较从ptr1和ptr2指针开始的num个字节, 如果return <0 prt1<ptr2

相关推荐

  1. MFC中CString的用法及使用示例

    2024-03-31 15:28:03       39 阅读
  2. MFC中字符串string类型和CString类型互转方法

    2024-03-31 15:28:03       39 阅读
  3. File

    2024-03-31 15:28:03       56 阅读
  4. qmap

    2024-03-31 15:28:03       59 阅读
  5. Kotlin

    2024-03-31 15:28:03       61 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-31 15:28:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-31 15:28:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-31 15:28:03       87 阅读
  4. Python语言-面向对象

    2024-03-31 15:28:03       96 阅读

热门阅读

  1. 发挥ChatGPT潜力:高效撰写学术论文技巧

    2024-03-31 15:28:03       33 阅读
  2. 使用Spring的集成Quartz框架来管理定时任务

    2024-03-31 15:28:03       39 阅读
  3. -梦想-周游世界-论人生短暂-读书与写作-

    2024-03-31 15:28:03       37 阅读
  4. MySQL与SQLite区别

    2024-03-31 15:28:03       37 阅读
  5. MQ

    MQ

    2024-03-31 15:28:03      39 阅读
  6. 一.Git环境

    2024-03-31 15:28:03       32 阅读
  7. PowerShell数组

    2024-03-31 15:28:03       37 阅读
  8. Log4j远程代码执行

    2024-03-31 15:28:03       30 阅读
  9. Redisson兼容redis多模式部署的配置方式

    2024-03-31 15:28:03       38 阅读
  10. 【微服务篇】深入理解分布式消息队列系统

    2024-03-31 15:28:03       38 阅读
  11. mybatis EXISTS

    2024-03-31 15:28:03       32 阅读
  12. 深入理解zookeeper

    2024-03-31 15:28:03       40 阅读