文章目錄
前言
在實際項目開發中,字符串拷貝是個很常見用法。方式有很多種,在我們使用過程中,一般不會出現什么問題,或者說是一般編譯器不會編譯報錯,甚至運行報錯。但一些潛在的陷阱是經常存在的,如在使用VS 2017開發時,會發現很多語法編譯器檢測會更嚴謹,以前如vs2010不會報錯的,vs2017會編譯不過,或編譯成功了,運行報錯。
廢話:在剛參加工作時,覺得只要功能能實現,便是完成任務了。隨着經驗的積累,很多東西就喜歡去深究,去思考,為什么要這么用,這么用有什么好處,效率會不會更高,性能會不會更優。
好了,廢話多說無益,下面正式開討論今天的主題。
拷貝方式
字符串拷貝的方式有很多種,下面我主要說說memcpy、strcpy、string::copy這三種。
內存拷貝memcpy
函數原型
void* __cdecl memcpy(
_Out_writes_bytes_all_(_Size) void* _Dst,
_In_reads_bytes_(_Size) void const* _Src,
_In_ size_t _Size
);
簡單點:
void *memcpy(void *dest, const void *src, size_t count);
參數這里不細說。
例子:
std::string CopyString(const std::string &strBody)
{
int nLen = strBody.length();
char *cBody = new char(nLen);
memcpy(cBody, strBody.c_str(), nLen);
std::cout << "FONCTION:" << cBody << std::endl;
return cBody;
}
int main()
{
std::string strBody = "This is a Test!";
std::string strReturn = CopyString(strBody);
std::cout << "MAIN:" << strReturn.c_str() << std::endl;
system("pause");
return 0;
}
上面函數,大多數情況下都不會出現報錯的,但存在一個潛在陷阱。
陷阱
內存拷貝不會對字符串結束符'\0'進行檢查
結果
拷貝結束后,在字符串末尾會出現亂碼。
解決方案
申請內存時多申請一個字節內存,以保證將字符串結束符拷貝進去。
std::string CopyString(const std::string &strBody)
{
int nLen = strBody.length();
char *cBody = new char(nLen + 1);
memcpy(cBody, strBody.c_str(), nLen + 1);
std::cout << "FONCTION:" << cBody << std::endl;
return cBody;
}
int main()
{
std::string strBody = "This is a Test!";
std::string strReturn = CopyString(strBody);
std::cout << "MAIN:" << strReturn.c_str() << std::endl;
delete strReturn.c_str();
system("pause");
return 0;
}
字符串拷貝strcpy
函數原型
char* __cdecl strcpy(
_Out_writes_z_(_String_length_(_Source) + 1) char* _Dest,
_In_z_ char const* _Source
);
簡單點:
char *strcpy(char *dst, const char *src);
例子
std::string CopyString(const std::string &strBody)
{
int nLen = strBody.length();
char *cBody = new char(nLen);
strcpy(cBody, strBody.c_str());
std::cout << "FONCTION:" << cBody << std::endl;
return cBody;
}
int main()
{
std::string strBody = "This is a Test!";
std::string strReturn = CopyString(strBody);
std::cout << "MAIN:" << strReturn.c_str() << std::endl;
delete strReturn.c_str();
system("pause");
return 0;
}
說明
strcpy是專用於字符串拷貝的函數,與memcpy的區別就是,它會檢測結束符'\0',所以在申請內存時不用做多余申請。
string方法拷貝string::copy
原型
size_type copy(_Out_writes_(_Count) _Elem * const _Ptr,
size_type _Count, const size_type _Off = 0) const
{ // copy [_Off, _Off + _Count) to [_Ptr, _Ptr + _Count)
auto& _My_data = this->_Get_data();
_My_data._Check_offset(_Off);
_Count = _My_data._Clamp_suffix_size(_Off, _Count);
_Traits::copy(_Ptr, _My_data._Myptr() + _Off, _Count);
return (_Count);
}
還是簡單點:
size_t copy (char* s, size_t len, size_t pos = 0) const;
這里解釋一下這幾個參數:
s
指向一組字符的指針。
該數組應包含足夠的存儲空間用於復制的字符。
len
要復制的字符數(如果字符串較短,則復制盡可能多的字符)。
pos
要復制的第一個字符的位置。
如果這大於字符串長度,則拋出out_of_range。
注意:字符串中的第一個字符由值0(不是1)表示。
例子
// string::copy
#include <iostream>
#include <string>
int main ()
{
char buffer[20];
std::string str ("This is a Test");
std::size_t length = str.copy(buffer,6,5);
std::cout << "buffer contains: " << buffer << '\n';
return 0;
}
陷阱&結果
因為這里copy最終調用的函數仍然是memcopy,所以陷阱一樣,拷貝完成后,會在字符串后面帶上一串亂碼
解決方案
在拷貝結束后,加上結束符'\0'。
// string::copy
#include <iostream>
#include <string>
int main ()
{
char buffer[20];
std::string str ("This is a Test");
std::size_t length = str.copy(buffer,6,5);
buffer[length]='\0';
std::cout << "buffer contains: " << buffer << '\n';
return 0;
}
這里還有一種方式,但是這種方式待討論,因為我目前的結果是正確的。那就是在申請內存后,第一時間進行初始化,這樣拷貝也不會出現亂碼。
例如:
std::string CopyString(const std::string &strBody)
{
char *cBody = NULL;
int nLen = strBody.size();
cBody = (char *)malloc(nLen);
memset(cBody, 0, nLen);
strBody.copy(cBody, nLen, 0);
std::cout << "FONCTION:" << cBody << std::endl;
return cBody;
}
int main()
{
std::string strBody = "This is a Test!";
std::string strReturn = CopyString(strBody);
std::cout << "MAIN:" << strReturn.c_str() << std::endl;
system("pause");
return 0;
}
后語
在我們開發過程中,很多細節需要我們去推敲,不注意細節,不出問題倒好,一出問題,連問題都不好找,特別是在大型項目中。
