http://blog.csdn.net/silvergingko/article/details/6070138
在编程中,经常需要将一个字符串中包含的数字转换成整数进行处理。标准C函数atoi及Unicode版本的wtoi都可以用来实现该任务。然而,在很多情况下,我并不喜欢使用这个函数,因为它无法满足我对转换结果的要求。
以下,先给出一段对atoi进行测试的代码。测试结果使用注释的形式给出:
- //C++ code
- #include <Windows.h>
- #include <errno.h>
- #include <tchar.h>
- int _tmain(int argc, _TCHAR* argv[])
- {
- //-----------------------------------------------------------------------------
- // 测试用例。
- //-----------------------------------------------------------------------------
- _TCHAR* s1 = _T("345");
- _TCHAR* s2 = _T("-345");
- _TCHAR* s3 = _T("0");
- _TCHAR* s4 = _T("2147483647");//INT_MAX。
- _TCHAR* s5 = _T("2147483648");//INT_MAX+1。
- _TCHAR* s6 = _T("-2147483648");//INT_MIN。
- _TCHAR* s7 = _T("-2147483649");//INT_MIN-1。
- _TCHAR* s8 = _T("9876543210");
- _TCHAR* s9 = _T("");
- _TCHAR* s10 = _T("q");
- _TCHAR* s11 = _T("345q");
- _TCHAR* s12 = NULL;
- _TCHAR ch = _T('/0');
- //-----------------------------------------------------------------------------
- // _ttoi function test.
- //-----------------------------------------------------------------------------
- int num = 0;
- errno_t err = 0;
- _set_errno(0);
- num = _ttoi(s1);//num = 345。
- _get_errno(&err);//err = 0。
- _set_errno(0);
- num = _ttoi(s2);//num = -345。
- _get_errno(&err);//err = 0。
- _set_errno(0);
- num = _ttoi(s3);//num = 0。
- _get_errno(&err);//err = 0。
- _set_errno(0);
- num = _ttoi(s4);//num = 2147483647。
- _get_errno(&err);//err = 0。
- _set_errno(0);
- num = _ttoi(s5);//num = 2147483647。
- _get_errno(&err);//err = 34。
- _set_errno(0);
- num = _ttoi(s6);//num = -2147483648。
- _get_errno(&err);//err = 0。
- _set_errno(0);
- num = _ttoi(s7);//num = -2147483648。
- _get_errno(&err);//err = 34。
- _set_errno(0);
- num = _ttoi(s8);//num = 2147483647。
- _get_errno(&err);//err = 34。
- _set_errno(0);
- num = _ttoi(s9);//num = 0。
- _get_errno(&err);//err = 0。
- _set_errno(0);
- num = _ttoi(s10);//num = 0。
- _get_errno(&err);//err = 0。
- _set_errno(0);
- num = _ttoi(s11);//num = 345。
- _get_errno(&err);//err = 0。
- //_set_errno(0);
- //num = _ttoi(s12);//抛出异常。
- //_get_errno(&err);
- _set_errno(0);
- num = _ttoi(&ch);//num = 0。
- _get_errno(&err);//err = 0。
- //-----------------------------------------------------------------------------
- // End of _ttoi function test.
- //-----------------------------------------------------------------------------
从以上测试的结果中,我比较关心的是第41行和第69行代码的结果,当字符串为s3,函数处理返回结果为0,这是理所当然的,
可是,字符串s10返回的结果也是0,且atoi函数并不会为遇上非数字字符而调用_set_errno,对于这样的行为我感到非常诧异,因为当s3和s10放在一起时,显然这两个字符串是完全不同的,作为atoi的调用者,将无法判断传给函数的是s3还是s10。如果程序需要针对用户给出的一个非数字字符串,提示用户字符串内容不正确,首先需要自己扫描字符串内容来判断字符串内容是否符号要求,然后才能调用atoi函数。这样对整个字符串的转换工作至少需要扫描字符串两次才能完成。并且,根据MSDN介绍,StrToIntXXX系列函数的工作方式和atoi类似。基于以上的情况,只能自己动手写一个字符串转换函数。
以下给出自定义函数Str2iNum和测试用例的完整代码:
- //C++ code
- #include <Windows.h>
- #include <errno.h>
- #include <tchar.h>
- //函数前导声明。
- BOOL __stdcall Str2iNumW(LPCWSTR, int *, BOOL);
- BOOL __stdcall Str2iNumA(LPCSTR, int *, BOOL);
- //宏定义。
- #undef Str2iNum
- #ifdef _UNICODE
- #define Str2iNum Str2iNumW
- #else
- #define Str2iNum Str2iNumA
- #endif
- int _tmain(int argc, _TCHAR* argv[])
- {
- //-----------------------------------------------------------------------------
- // 测试用例。
- //-----------------------------------------------------------------------------
- _TCHAR* s1 = _T("345");
- _TCHAR* s2 = _T("-345");
- _TCHAR* s3 = _T("0");
- _TCHAR* s4 = _T("2147483647");//INT_MAX。
- _TCHAR* s5 = _T("2147483648");//INT_MAX+1。
- _TCHAR* s6 = _T("-2147483648");//INT_MIN。
- _TCHAR* s7 = _T("-2147483649");//INT_MIN-1。
- _TCHAR* s8 = _T("9876543210");
- _TCHAR* s9 = _T("");
- _TCHAR* s10 = _T("q");
- _TCHAR* s11 = _T("345q");
- _TCHAR* s12 = NULL;
- _TCHAR ch = _T('/0');
- //-----------------------------------------------------------------------------
- // Str2iNum function test.
- //-----------------------------------------------------------------------------
- BOOL bRet = FALSE;
- DWORD dwErr = ERROR_SUCCESS;
- //在 WinError.h 定义的常量宏。
- //#define ERROR_BAD_FORMAT 11L
- //#define ERROR_INVALID_DATA 13L
- //#define ERROR_INVALID_PARAMETER 87L
- bRet = Str2iNum(s1, &num, FALSE);//num = 345, bRet = TRUE。
- bRet = Str2iNum(s2, &num, FALSE);//num = -345, bRet = TRUE。
- bRet = Str2iNum(s3, &num, FALSE);//num = 0, bRet = TRUE。
- bRet = Str2iNum(s4, &num, FALSE);//num = 2147483647, bRet = TRUE。
- SetLastError(NO_ERROR);
- bRet = Str2iNum(s5, &num, FALSE);//num = 2147483647, bRet = FALSE。
- dwErr = GetLastError();//dwErr = 13。
- bRet = Str2iNum(s6, &num, FALSE);//num = -2147483648, bRet = TRUE。
- SetLastError(NO_ERROR);
- bRet = Str2iNum(s7, &num, FALSE);//num = -2147483648, bRet = FALSE。
- dwErr = GetLastError();//dwErr = 13。
- SetLastError(NO_ERROR);
- bRet = Str2iNum(s8, &num, FALSE);//num = -2147483648, bRet = FALSE。
- dwErr = GetLastError();//dwErr = 13。
- SetLastError(NO_ERROR);
- bRet = Str2iNum(s9, &num, FALSE);//num = -2147483648, bRet = FALSE。
- dwErr = GetLastError();//dwErr = 11。
- SetLastError(NO_ERROR);
- bRet = Str2iNum(s10, &num, FALSE);//num = -2147483648, bRet = FALSE。
- dwErr = GetLastError();//dwErr = 11。
- SetLastError(NO_ERROR);
- bRet = Str2iNum(s11, &num, FALSE);//num = -2147483648, bRet = FALSE。
- dwErr = GetLastError();//dwErr = 11。
- SetLastError(NO_ERROR);
- bRet = Str2iNum(s12, &num, FALSE);//num = -2147483648, bRet = FALSE。
- dwErr = GetLastError();//dwErr = 87。
- SetLastError(NO_ERROR);
- bRet = Str2iNum(&ch, &num, FALSE);//num = -2147483648, bRet = FALSE。
- dwErr = GetLastError();//dwErr = 11。
- bRet = Str2iNum(_T(" /t 123"), &num, TRUE);//num = 123, bRet = TRUE。
- //-----------------------------------------------------------------------------
- // End of Str2iNum function test.
- //-----------------------------------------------------------------------------
- return 0;
- }
- #pragma region Str2iNum2W
- // 函数功能: 将字符串转换成对应的int型整数(Unicode版本)。
- // 参数:
- // pszNum: 源字符串。
- // pnNum: 转换后的int型整数的存储指针。
- // bSpaceLead: 前导空格。
- // 返回值: 成功返回TRUE,失败返回FALSE。
- BOOL __stdcall Str2iNumW(LPCWSTR pszNum, int *pnNum, BOOL bSpaceLead)
- {
- __int64 num = 0;
- int sign = 1;
- size_t len = 0;
- BOOL bRet = FALSE;
- WCHAR ch = 0;
- if (pszNum == NULL || pnNum == NULL)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- return bRet;
- }
- //跳过前导空格。
- if (bSpaceLead == TRUE)
- {
- while (*pszNum == _T(' ') || *pszNum == _T('/t'))
- pszNum++;
- }
- //跳过符号位。
- ch = *pszNum;
- if (ch == _T('-'))
- {
- sign = -1;
- pszNum++;
- } else if (ch == _T('+'))
- {
- pszNum++;
- }
- //非数字字符。
- if (*pszNum < _T('0') || *pszNum > _T('9'))
- {
- SetLastError(ERROR_BAD_FORMAT);
- return bRet;
- }
- //第一个字符是数字字符"0"。
- if (*pszNum == _T('/0'))
- {
- if (pszNum[1] == _T('/0'))
- {
- *pnNum = 0;
- return TRUE;
- }
- else
- {
- SetLastError(ERROR_BAD_FORMAT);
- return bRet;
- }
- }
- while (TRUE)
- {
- ch = *pszNum++;
- //数字字符。
- if (ch >= _T('0') && ch <= _T('9'))
- {
- len++;
- //int型整数最多十位。
- if (len > 10)
- {
- SetLastError(ERROR_INVALID_DATA);
- break;
- }
- num = num * 10 + (ch - _T('0'));
- continue;
- }
- //字符串终止符。
- else if (ch == _T('/0'))
- {
- num = sign * num;
- if (num <= INT_MAX && num >= INT_MIN)
- {
- *pnNum = (int)num;
- bRet = TRUE;
- break;
- }
- //溢出。
- else
- {
- SetLastError(ERROR_INVALID_DATA);
- break;
- }
- }
- //非数字字符。
- else
- {
- SetLastError(ERROR_BAD_FORMAT);
- break;
- }
- }
- return bRet;
- }
- #pragma endregion
- #pragma region Str2iNumA
- // 参数功能:Ansi版本的Str2iNumW。
- BOOL __stdcall Str2iNumA(LPCSTR pszNum, int *pnNum, BOOL bSpaceLead)
- {
- //int类型的数字最多是10位,大于10的最小2次幂是16。
- WCHAR szNum[16] = { 0 };
- int nRet = 0;
- size_t cbLen = 0;
- if (pszNum == NULL || pnNum == NULL)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- return FALSE;
- }
- //跳过前导空格。
- if (bSpaceLead == TRUE)
- {
- while (*pszNum == _T(' ') || *pszNum == _T('/t'))
- pszNum++;
- }
- cbLen = strnlen_s(pszNum, 16);
- //过滤较长的恶意字符串。
- if (cbLen == 16)
- {
- SetLastError(ERROR_BAD_FORMAT);
- return FALSE;
- }
- nRet = MultiByteToWideChar(CP_ACP, 0, pszNum, -1, szNum, 16);
- if (nRet == 0)
- {
- return FALSE;
- }
- return Str2iNumW(szNum, pnNum, bSpaceLead);
- }
- #pragma endregion