C语言中的字符串

C语言的字符串很好理解,本身是一个数组,只不过对字符串的最后一个元素有特别的要求,最后一个元素必须是null。但是C语言也提供了一系列的库函数来操作字符串,避免自己去实现相关的操作。

库函数

C语言的库函数放在一个叫cstring的头文件中,其实它是对string.h的封装,因为C++有命名空间的概念,所以ctring就是加了一层命名空间。对于写C程序的人来说,只需要包含string.h即可。

// 精确复制num个字节
void *memcpy(void *destination, const void *source, size_t num);
// memmove更强大的地方在于两个内存段可以有交叉,
// memcpy则不能保证在有交叉的情况还能正确工作。
void *memmove(void *destination, const void *source, size_t num);
// 目标数组要能够装得下新的字符串,也就是需要预先保证有足够的空间
char *strcpy(char *destination, const char *source);
// 最多复制num个字符,具体长度有源字符串的长度决定
char *strncpy(char *destination, const char *source, size_t num);
// 调用者首先要保证目标有足够的空间,其次还需要保证没有交叉
char *strcat(char *destination, const char *source);
char *strncat(char *destination, const char *source, size_t num);
// 简单的逐字节比较
int memcmp(const void *ptr1, const void *ptr2, size_t num);
// 字符串比较
int strcmp(const char *str1, const char *str2);
// 也是字符串比较,但行为依赖于LC_COLLATE
int strcoll(const char *str1, const char *str2);
// 只比较前num个字符
int strncmp(const char *str1, const char *str2, size_t num);
// 根据LC_COLLATE将源字符串转换为目标字符串
size_t strxfrm(char *destination, const char *source, size_t num);
// 用于搜索value的第一次出现位置,如果没有找到返回NULL
const void *memchr(const void *ptr, int value, size_t num);
      void *memchr(void *ptr, int value, size_t num);
// 用来搜索指定字符的第一次出现位置
const char *strchr(const char *str, int character);
      char *strchr(char *str, int character);
// 查找最后一次出现,如果没有找到返回NULL,注意可以用来查找null
const char *strrchr(const char *str, int character);
      char *strrchr(char *str, int character);
// 用来搜索str2中任意一个字符的出现,返回值是前面没有在str2中出现的字符个数
// 因为搜索包含null字符,所以即便没有出现,也会返回str1的长度
size_t strcspn(const char *str1, const char *str2);
// 和strcspn的区别有两点,第一是查找从str1开头有多少个字符属于str2,
// 第二是不包括对null字符的搜索。
size_t strspn(const char *str1, const char *str2);
// 也是搜索str2中任意字符的出现,只不过换成了找到的指针,
// 如果没有找到就返回NULL
const char *strpbrk(const char *str1, const char *str2);
      char *strpbrk(char *str1, const char *str2);
// 用来查找整个str2在str1第一次出现,如果没有找到就返回NULL
const char *strstr(const char *str1, const char *str2);
      char *strstr(char *str1, const char *str2);
// 将str分割,分割字符由delimiters决定,其中出现的任意字符都作为分割符
char *strtok(char *str, const char *delimiters);
void *memset(void *ptr, int value, size_t num);
char *strerror(int errnum);         // 将错误码转换为字符串
size_t strlen(const char *str);     // 计算字符串长度
long int strtol(const char *nptr, char **endptr, int base);
long long int strtoll(const char *nptr, char **endptr, int base);
unsigned long int strtoul(const char *nptr, char **endptr, int base);
unsigned long long int strtoull(const char *nptr, char **endptr, int base);

调用之后通常需要检查两个值的有效性,一个是nptr,需要确保其第一个字符不能为null,一个是endptr,要确保*endptr为null,也就是说完整的将nptr转换为数字,如果nptr本身不需要完整转换,也可以检查endptr是否走到指定位置。

C++中的字符串

C++中string是以一个库的形式提供的,由于C++引入了命名空间的概念,所以string实际处于命名空间std中。为了避免累赘,这里忽略命名空间的概念,直接引用string。

要使用string,必须要要包含头文件<string>。接下来可以看一下string类的定义:

typedef basic_string<char> string;

从这里可以看到,实际上string类是模板类basic_string的一个实例,用char字符来实例化,如果熟悉basic_string可以利用它来实现其他类型的string,也就是除了字符串以外还能轻松实现其他类型的串。

既然提到basic_string不妨看一下它长什么样子:

template < class charT,
           class traits = char_traits<charT>,
           class Alloc = allocator<charT>
           > class basic_string;
charT

设计者对这个模板参数提出了一个要求,必须是非数组的POD类型。

因为我们要实现一个串,所以不让串的元素本身是串,这是比较容易理解的。另一方面要求是POD类型,称之为Plain Old Type,就是源于C的那些数据类型。

traits
定义一组特征。
Alloc
提供分配内存的方法。

一般情况不会去修改traits和Alloc,因为默认的就实现的很好了,如果我们想使用其他类型的串,就可以简单添加typedef实现。例如标准库里面已经提供了如下类型:

typedef basic_string<wchar_t> wstring;
typedef basic_string<char16_t> u16string;
typedef basic_string<char32_t> u32string;

数据成员

我们可以认为数据成员已经被完全封装,虽然有一个npos可以访问,但它是一个全局静态常量,其含义是size_type能表示的最大值。

static const size_type npos = -1;

函数成员

string();
string(const string &str);
// 从给定str的下标pos开始复制最多len个字符。
string(const string &str, size_t pos, size_t len = npos);
string(const char *s);
// 从字符串复制最多n个字符。
string(const char *s, size_t n);
string(size_t n, char c);
// 从迭代器复制。
template <class InputIterator>
string(InputIterator first, InputIterator last);

析构函数对使用者来说其实没有什么需要注意的,只需要在动态申请是能记得释放就可以了。

string &operator=(const string &str);
string &operator=(const char *s);
string &operator=(char c);

迭代器方法和其他容器类几乎一样,如果熟悉vector就知道有哪些: begin(),end(),rbegin(),rend(),cbegin(),cend(),crbegin(),crend()。其中r的含义是reverse,而c的含义是const。

size_t size() const;                    // 就是总共多少个字符
size_t length() const;                  // 字符串长度,同size()
size_t max_size() const;                // 系统可以分配的最大长度
void resize(size_t n);
void resize(size_t n, char c);          // 增加的部分用c填充
size_t capacity() const;                // 当前分配到的内存长度
void clear();                           // 将字符串擦除,长度变为0
bool empty() const;                     // 用于检查是否为空字符串
void reserve(size_t n = 0);             // 设置预留长度
void shrink_to_fit();                   // 清除预留长度
resize()
如果往小的调整,实际上不会释放内存,只会减少字符串长度,如果要扩大,增加字符串长度,如果超出capacity则会重新分配内存。
reserve()
在请求减少容量的时候并不一定会真的减少容量,这取决于编译器的实现。 shrink_to_fit()同样如此。

下标操作有[index]和at(index)两种方法,后者会检查是否越界,而前者不会检查。 back()和front()用于返回最后一个和第一个字符。

string &operator+=(const string &str);
string &operator+=(const char *s);
string &operator+=(char c);

string &append(const string &str);
string &append(const string &str, size_t subpos, size_t sublen);
string &append(const char *s);
string &append(const char *s, size_t n);
string &append(size_t n, char c);
template <class InputIterator>
string &append(InputIterator first, InputIterator last);

void push_back(char c);

string &assign(const string &str);
string &assign(const string &str, size_t subpos, size_t sublen);
string &assign(const char *s);
string &assign(const char *s, size_t n);
string &assign(size_t n, char c);
template <class InputIterator>
string &assign(InputIterator first, InputIterator last);

string &insert(size_t pos, const string &str);
string &insert(size_t pos, const string &str,
               size_t subpos, size_t sublen);
string &insert(size_t pos, const char *s);
string &insert(size_t pos, const char *s, size_t n);
string &insert(size_t pos, size_t n, char c);
void insert(iterator p, size_t n, char c);
iterator insert(iterator p, char c);
template <class InputIterator>
void insert(iterator p, InputIterator first, InputIterator last);

string &erase(size_t pos = 0, size_t len = npos);
iterator erase(iterator p);
iterator erase(iterator first, iterator last);

string &replace(size_t pos,  size_t len,  const string &str);
string &replace(iterator i1, iterator i2, const string &str);
string &replace(size_t pos,  size_t len,  const string &str,
                 size_t subpos, size_t sublen);
string &replace(size_t pos,  size_t len,  const char *s);
string &replace(iterator i1, iterator i2, const char *s);
string &replace(size_t pos,  size_t len,  const char *s, size_t n);
string &replace(iterator i1, iterator i2, const char *s, size_t n);
string &replace(size_t pos,  size_t len,  size_t n, char c);
string &replace(iterator i1, iterator i2, size_t n, char c);
template <class InputIterator>
string &replace(iterator i1, iterator i2,
                InputIterator first, InputIterator last);

void swap(string &str);
void pop_back();

复合加法,也就是+=实际上是在尾部添加,和append()一样,push_back()也是向尾部添加。

const char *c_str() const;              // 获取C字符串
const char *data() const;
allocator_type get_allocator() const;
size_t copy(char *s, size_t len, size_t pos = 0) const;

size_t find(const string &str, size_t pos = 0) const;
size_t find(const char *s, size_t pos = 0) const;
size_t find(const char *s, size_t pos, size_t n) const;
size_t find(char c, size_t pos = 0) const;

size_t find_first_of(const string &str, size_t pos = 0) const;
size_t find_first_of(const char *s, size_t pos = 0) const;
size_t find_first_of(const char *s, size_t pos, size_t n) const;
size_t find_first_of(char c, size_t pos = 0) const;

string substr(size_t pos = 0, size_t len = npos) const;

int compare(const string &str) const;
int compare(size_t pos, size_t len, const string &str) const;
int compare(size_t pos, size_t len, const string &str,
             size_t subpos, size_t sublen) const;
int compare(const char *s) const;
int compare(size_t pos, size_t len, const char *s) const;
int compare(size_t pos, size_t len, const char *s,
            size_t n) const;
find()

如果没找到,返回npos。 rfind()和find()形式相同,默认参数是npos。

find_first_of()系列包括find_last_of()、find_first_not_of()、 find_last_not_of()。原型完全一致,find_first_of()用于查找指定串中的任意一个字符的首次出现, find_first_not_of()查找指定串以外任意一个字符的首次出现。

库函数

库函数用于操作string但是它不属于成员函数。

string operator+(const string &lhs, const string &rhs);
string operator+(const string &lhs, const char   *rhs);
string operator+(const char   *lhs, const string &rhs);
string operator+(const string &lhs, char          rhs);
string operator+(char          lhs, const string &rhs);

bool operator==(const string &lhs, const string &rhs);
bool operator==(const char   *lhs, const string &rhs);
bool operator==(const string &lhs, const char   *rhs);

void swap(string &x, string &y);

istream &operator>>(istream &is, string &str);
ostream &operator<<(ostream &os, const string &str);

istream &getline(istream &is, string &str, char delim);
istream &getline(istream &is, string &str);

比较操作符==、!=、<、<=、>、>=具有完全相同的函数原型和用法,因此只需要参考==即可。