C++字符串拼接有多种方式,不同方式原理和性能会有一些差异,本文进行一个小小归纳对比,如有疏漏错误,欢迎指出。
本文源码解析使用glibc-2.32版本, C ++ 11。
C语言字符串拼接
1. 手工拼接操作
char *(char *dest, const char *src, size_t, n)
{
// 保存头部指针
char *s = dest;
// 找到字符串末尾位置
s1 += strlen (dest);
// 计算拼接长度
size_t ss = strnlen (src, n);
// 末尾置 '\0'
s1[ss] = '\0';
// 复制字符串
while (*s1++ !='\0')
{
*s1++ = *src++;
}
return s;
}
2. 使用strcat/strncat进行字符串拼接
在glibc中,strcat 使用了 strcpy 实现拼接
char *
STRCAT (char *dest, const char *src)
{
strcpy (dest + strlen (dest), src);
return dest;
}
strncat使用了 memcpy方法
char *
STRNCAT (char *s1, const char *s2, size_t n)
{
char *s = s1;
/* Find the end of S1. */
s1 += strlen (s1);
size_t ss = __strnlen (s2, n);
s1[ss] = '\0';
memcpy (s1, s2, ss);
return s;
}
strcpy也是调用memcpy,strcpy不需传入拷贝长度,内部通过strlen找到’\0’为结尾。
char *
STRCPY (char *dest, const char *src)
{
return memcpy (dest, src, strlen (src) + 1);
}
在较早版本的glibc(至少2.11之前)中,strcpy使用类似手工方式进行了字符串拼接,以’\0’为结尾。
因此网上通常会看到memcpy与strcpy的区别说明:
a. 复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意类型的内容。strcpy只用于字符串复制,并且还会复制字符串的结束符。memcpy对于复制的内容没有限制,用途更广。
b. 复制的方法不同。strcpy不需要指定长度,遇到结束符’\0’才会结束,所以容易溢出。memcpy则是根据第三个参数决定复制的长度
c. 用途不同。通常在复制字符串时用strcpy,在复制其他类型数据时一般用memcpy。
// glibc 2.11
/* Copy SRC to DEST. */
char *
strcpy (dest, src)
char *dest;
const char *src;
{
reg_char c;
char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);
const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;
size_t n;
do
{
c = *s++;
s[off] = c;
}
while (c != '\0');
n = s - src;
(void) CHECK_BOUNDS_HIGH (src + n);
(void) CHECK_BOUNDS_HIGH (dest + n);
return dest;
}
在glibc 2.32中,memcpy拼接有两种形式:
a. 长度小于16字节的内容,会通过数组赋值进行一个个字节拷贝
b. 对于大于16字节的内容,会进行对齐,整体内存虚拟页拷贝操作
memcpy的实现源码可以在memcpy.c中看到,也比较好理解,其中关于虚拟内存页拷贝也涉及一些对齐细节,此处省略相关代码内容。关于memcpy的细节可以看:
https://programmer.group/learning-notes-of-memcpy-source-code-under-glibc.html
https://blog.csdn.net/xiao_huocai/article/details/103198250
通常认为memcpy与strcpy的区别
3. 使用sprintf进行字符串拼接
printf(sFilePath, "%s/%s", "c://", "a.txt");
sprintf的源码实现较为复杂,暂时无法清晰捋出来,后续可以新开文章进一步学习。
C++语言字符串拼接
1. 使用+号或+=拼接
std::string l_czTempStr;
std::string s1="Test data1";
std::string s2="Test data2";
std::string s3="Test data3";
l_czTempStr += s1 + s2 + s3;
其源码是对+号进行重载,调用append方法
template<typename _CharT, typename _Traits, typename _Alloc>
basic_string<_CharT, _Traits, _Alloc>
operator+(const _CharT* __lhs,
const basic_string<_CharT, _Traits, _Alloc>& __rhs)
{
__glibcxx_requires_string(__lhs);
typedef basic_string<_CharT, _Traits, _Alloc> __string_type;
typedef typename __string_type::size_type __size_type;
const __size_type __len = _Traits::length(__lhs);
__string_type __str;
__str.reserve(__len + __rhs.size());
__str.append(__lhs, __len);
__str.append(__rhs);
return __str;
}
2. 使用append拼接
std::string l_czTempStr;
std::string s1="Test data1";
l_czTempStr.append(s1);
append有四个重载方法,对应char*和string以及append范围限定
append(size_type __n, _CharT __c)
append(const _CharT* __s, size_type __n)
append(const basic_string& __str)
append(const basic_string& __str, size_type __pos, size_type __n)
append(const basic_string& __str) 方法源码:
template<typename _CharT, typename _Traits, typename _Alloc>
basic_string<_CharT, _Traits, _Alloc>&
basic_string<_CharT, _Traits, _Alloc>::
append(const basic_string& __str)
{
const size_type __size = __str.size();
if (__size)
{
const size_type __len = __size + this->size();
if (__len > this->capacity() || _M_rep()->_M_is_shared())
this->reserve(__len);
// 内存拷贝
_M_copy(_M_data() + this->size(), __str._M_data(), __size);
_M_rep()->_M_set_length_and_sharable(__len);
}
return *this;
}
可以看出append方法的具体行为(暂时不挖掘STL的string实现细节):
a. 首先计算了要拼接的长度,__size
b. __size对比当前拥有的内存可容纳的长度capacity(),如果空间不足,通过reverse方法请求更多的内存空间
c. 使用_M_copy进行内存拷贝
d. _M_rep进行内存空间设置
3. 使用stringstream拼接
std::ostringstream oss;
std::string s1="Test data1";
oss << s1;
l_czTempStr = oss.str();
stringstream源码也尚未吃透,坑留在这里
小结
此篇文章最初是想解惑,C++项目中字符串拼接使用什么方法更优,其原理、源码实现是什么。目前发现坑越挖越大,先尽量吃透其源码实现(还剩下很多坑),后续再总结其实现原理导致的性能不同问题。