Cách C ++ để phân tích một chuỗi (được cho là char *) thành một int? Xử lý lỗi mạnh mẽ và rõ ràng là một lợi thế (thay vì trả về 0 ).
Cách C ++ để phân tích một chuỗi (được cho là char *) thành một int? Xử lý lỗi mạnh mẽ và rõ ràng là một lợi thế (thay vì trả về 0 ).
Câu trả lời:
Trong C ++ 11 mới có các chức năng cho điều đó: stoi, stol, stoll, stoul, v.v.
int myNr = std::stoi(myString);
Nó sẽ ném một ngoại lệ về lỗi chuyển đổi.
Ngay cả các hàm mới này vẫn có cùng một vấn đề như Dan đã lưu ý: chúng sẽ vui vẻ chuyển đổi chuỗi "11x" thành số nguyên "11".
Xem thêm: http://en.cppreference.com/w/cpp/opes/basic_opes/stol
size_t
không bằng độ dài của chuỗi, thì nó dừng sớm. Nó vẫn sẽ trả về 11 trong trường hợp đó, nhưng pos
sẽ là 2 thay vì độ dài chuỗi 3. coliru.stacked-crooking.com/a/cabe25d64d2ffa29
Đây là lời khuyên đầu tiên của tôi: không sử dụng chuỗi ký tự cho việc này . Mặc dù lúc đầu có vẻ đơn giản để sử dụng, bạn sẽ thấy rằng bạn phải làm thêm rất nhiều việc nếu bạn muốn sự mạnh mẽ và xử lý lỗi tốt.
Đây là một cách tiếp cận có vẻ như trực quan nên hoạt động:
bool str2int (int &i, char const *s)
{
std::stringstream ss(s);
ss >> i;
if (ss.fail()) {
// not an integer
return false;
}
return true;
}
Điều này có một vấn đề lớn: str2int(i, "1337h4x0r")
sẽ vui vẻ trở lại true
và i
sẽ nhận được giá trị 1337
. Chúng tôi có thể giải quyết vấn đề này bằng cách đảm bảo không có thêm ký tự nào stringstream
sau khi chuyển đổi:
bool str2int (int &i, char const *s)
{
char c;
std::stringstream ss(s);
ss >> i;
if (ss.fail() || ss.get(c)) {
// not an integer
return false;
}
return true;
}
Chúng tôi đã sửa một vấn đề, nhưng vẫn còn một vài vấn đề khác.
Nếu số trong chuỗi không phải là cơ sở 10 thì sao? Chúng tôi có thể cố gắng điều chỉnh các cơ sở khác bằng cách đặt luồng về chế độ chính xác (ví dụ ss << std::hex
) trước khi thử chuyển đổi. Nhưng điều này có nghĩa là người gọi phải biết một tiên nghiệm số cơ sở là gì - và làm thế nào người gọi có thể biết điều đó? Người gọi không biết số này là gì. Họ thậm chí không biết rằng đó làmột số! Làm thế nào họ có thể được dự kiến để biết nó là cơ sở gì? Chúng tôi chỉ có thể yêu cầu tất cả các số đầu vào cho các chương trình của chúng tôi phải là cơ sở 10 và từ chối đầu vào thập lục phân hoặc bát phân là không hợp lệ. Nhưng đó không phải là rất linh hoạt hoặc mạnh mẽ. Không có giải pháp đơn giản cho vấn đề này. Bạn không thể đơn giản thử chuyển đổi một lần cho mỗi cơ sở, vì chuyển đổi thập phân sẽ luôn thành công đối với các số bát phân (có số 0 đứng đầu) và chuyển đổi bát phân có thể thành công đối với một số số thập phân. Vì vậy, bây giờ bạn phải kiểm tra một số không hàng đầu. Nhưng chờ đã! Các số thập lục phân cũng có thể bắt đầu bằng số 0 đứng đầu (0x ...). Thở dài.
Ngay cả khi bạn thành công trong việc xử lý các vấn đề trên, vẫn còn một vấn đề lớn hơn: nếu người gọi cần phân biệt giữa đầu vào xấu (ví dụ: "123foo") và một số nằm ngoài phạm vi int
(ví dụ: "4000000000" cho 32-bit int
)? Với stringstream
, không có cách nào để làm cho sự khác biệt này. Chúng tôi chỉ biết liệu chuyển đổi thành công hay thất bại. Nếu thất bại, chúng ta không có cách nào để biết tại sao nó thất bại. Như bạn có thể thấy, stringstream
để lại nhiều điều mong muốn nếu bạn muốn sự mạnh mẽ và xử lý lỗi rõ ràng.
Điều này dẫn tôi đến lời khuyên thứ hai: đừng sử dụng Boost lexical_cast
cho việc này . Xem xét những gì lexical_cast
tài liệu đã nói:
Khi mức độ kiểm soát cao hơn được yêu cầu đối với các chuyển đổi, std :: stringstream và std :: wopesstream cung cấp một đường dẫn phù hợp hơn. Trong trường hợp cần phải chuyển đổi không dựa trên luồng, lexical_cast là công cụ sai cho công việc và không được áp dụng đặc biệt cho các tình huống như vậy.
Gì?? Chúng ta đã thấy rằng stringstream
có mức độ kiểm soát kém, và nó nói rằng stringstream
nên được sử dụng thay vì lexical_cast
nếu bạn cần "mức độ kiểm soát cao hơn". Ngoài ra, vì lexical_cast
chỉ là một trình bao bọc xung quanh stringstream
, nó gặp phải cùng một vấn đề stringstream
: hỗ trợ kém cho nhiều cơ sở số và xử lý lỗi kém.
May mắn thay, ai đó đã giải quyết tất cả các vấn đề trên. Thư viện chuẩn C chứa strtol
và gia đình không có vấn đề nào trong số này.
enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };
STR2INT_ERROR str2int (int &i, char const *s, int base = 0)
{
char *end;
long l;
errno = 0;
l = strtol(s, &end, base);
if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) {
return OVERFLOW;
}
if ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) {
return UNDERFLOW;
}
if (*s == '\0' || *end != '\0') {
return INCONVERTIBLE;
}
i = l;
return SUCCESS;
}
Khá đơn giản cho một cái gì đó xử lý tất cả các trường hợp lỗi và cũng hỗ trợ bất kỳ cơ sở số nào từ 2 đến 36. Nếu base
bằng 0 (mặc định), nó sẽ cố gắng chuyển đổi từ bất kỳ cơ sở nào. Hoặc người gọi có thể cung cấp đối số thứ ba và chỉ định rằng chuyển đổi chỉ nên được thử cho một cơ sở cụ thể. Nó mạnh mẽ và xử lý tất cả các lỗi với một nỗ lực tối thiểu.
Các lý do khác để thích strtol
(và gia đình):
Hoàn toàn không có lý do chính đáng để sử dụng bất kỳ phương pháp nào khác.
strtol
an toàn cho chuỗi. POSIX cũng yêu cầu errno
sử dụng lưu trữ luồng cục bộ. Ngay cả trên các hệ thống không phải POSIX, gần như tất cả các triển khai errno
trên các hệ thống đa luồng đều sử dụng lưu trữ luồng cục bộ. Tiêu chuẩn C ++ mới nhất đòi hỏi errno
phải tuân thủ POSIX. Tiêu chuẩn C mới nhất cũng yêu cầu errno
phải có bộ lưu trữ cục bộ. Ngay cả trên Windows, chắc chắn không tuân thủ POSIX, errno
cũng an toàn theo luồng và, bằng cách mở rộng, cũng vậy strtol
.
std::stol
điều này, sẽ đưa ra các ngoại lệ một cách thích hợp thay vì trả về các hằng số.
std::stol
thậm chí được thêm vào ngôn ngữ C ++. Điều đó nói rằng, tôi không nghĩ rằng thật công bằng khi nói rằng đây là "Mã hóa C trong C ++". Thật ngớ ngẩn khi nói rằng đó std::strtol
là mã hóa C khi nó rõ ràng là một phần của ngôn ngữ C ++. Câu trả lời của tôi áp dụng hoàn hảo cho C ++ khi nó được viết và nó vẫn áp dụng ngay cả với cái mới std::stol
. Các hàm gọi có thể đưa ra ngoại lệ không phải lúc nào cũng tốt nhất cho mọi tình huống lập trình.
Đây là cách C an toàn hơn atoi ()
const char* str = "123";
int i;
if(sscanf(str, "%d", &i) == EOF )
{
/* error */
}
C ++ với chuỗi thư viện chuẩn : (cảm ơn CMS )
int str2int (const string &str) {
stringstream ss(str);
int num;
if((ss >> num).fail())
{
//ERROR
}
return num;
}
Với thư viện boost : (cảm ơn jk )
#include <boost/lexical_cast.hpp>
#include <string>
try
{
std::string str = "123";
int number = boost::lexical_cast< int >( str );
}
catch( const boost::bad_lexical_cast & )
{
// Error
}
Chỉnh sửa: Đã sửa lỗi phiên bản chuỗi để nó xử lý lỗi. (cảm ơn bình luận của CMS và jk về bài viết gốc)
Cách C cũ tốt vẫn hoạt động. Tôi khuyên bạn nên strtol hoặc strtoul. Giữa trạng thái trả về và 'endPtr', bạn có thể đưa ra kết quả chẩn đoán tốt. Nó cũng xử lý nhiều cơ sở độc đáo.
Bạn có thể sử dụng Boost'slexical_cast
, bao bọc điều này trong một giao diện chung hơn.
lexical_cast<Target>(Source)
ném bad_lexical_cast
vào thất bại.
Bạn có thể sử dụng một chuỗi chuỗi từ thư viện chuẩn C ++:
stringstream ss(str);
int x;
ss >> x;
if(ss) { // <-- error handling
// use x
} else {
// not a number
}
Trạng thái luồng sẽ được đặt thành thất bại nếu gặp phải một chữ số không khi đọc số nguyên.
Xem Cạm bẫy luồng cho các cạm bẫy của lỗi và luồng trong C ++.
Bạn có thể sử dụng chuỗi
int str2int (const string &str) {
stringstream ss(str);
int num;
ss >> num;
return num;
}
Tôi nghĩ rằng ba liên kết này tổng hợp nó:
các giải pháp chuỗi và lexical_cast gần giống như các diễn viên từ vựng đang sử dụng chuỗi.
Một số chuyên môn của diễn viên từ vựng sử dụng cách tiếp cận khác nhau, xem http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp để biết chi tiết. Số nguyên và số float hiện được chuyên dùng để chuyển đổi số nguyên thành chuỗi.
Người ta có thể chuyên lexical_cast cho nhu cầu của riêng mình và làm cho nó nhanh chóng. Đây sẽ là giải pháp tối ưu làm hài lòng tất cả các bên, sạch sẽ và đơn giản.
Các bài viết đã được đề cập cho thấy so sánh giữa các phương pháp chuyển đổi số nguyên <-> khác nhau. Các cách tiếp cận có ý nghĩa: c-way cũ, Spirit.karma, fastformat, vòng lặp ngây thơ đơn giản.
Trong một số trường hợp, Lexical_cast vẫn ổn, ví dụ như chuyển đổi int thành chuỗi.
Chuyển đổi chuỗi thành int bằng cách sử dụng từ vựng cast không phải là một ý tưởng hay vì nó chậm hơn 10 - 40 lần so với atoi tùy thuộc vào nền tảng / trình biên dịch được sử dụng.
Boost.Sprite.Karma dường như là thư viện nhanh nhất để chuyển đổi số nguyên thành chuỗi.
ex.: generate(ptr_char, int_, integer_number);
và vòng lặp đơn giản cơ bản từ bài viết được đề cập ở trên là cách nhanh nhất để chuyển đổi chuỗi thành int, rõ ràng không phải là cách an toàn nhất, strtol () có vẻ như là một giải pháp an toàn hơn
int naive_char_2_int(const char *p) {
int x = 0;
bool neg = false;
if (*p == '-') {
neg = true;
++p;
}
while (*p >= '0' && *p <= '9') {
x = (x*10) + (*p - '0');
++p;
}
if (neg) {
x = -x;
}
return x;
}
Các C ++ Library Toolkit String (StrTk) có các giải pháp sau:
static const std::size_t digit_table_symbol_count = 256;
static const unsigned char digit_table[digit_table_symbol_count] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xFF - 0x07
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x08 - 0x0F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x10 - 0x17
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x18 - 0x1F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x20 - 0x27
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x28 - 0x2F
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 0x30 - 0x37
0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x38 - 0x3F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x40 - 0x47
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x48 - 0x4F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x50 - 0x57
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x58 - 0x5F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x60 - 0x67
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x68 - 0x6F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x70 - 0x77
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x78 - 0x7F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x80 - 0x87
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x88 - 0x8F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x90 - 0x97
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x98 - 0x9F
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA0 - 0xA7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA8 - 0xAF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB0 - 0xB7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB8 - 0xBF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC0 - 0xC7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC8 - 0xCF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD0 - 0xD7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD8 - 0xDF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE0 - 0xE7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE8 - 0xEF
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xF0 - 0xF7
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 0xF8 - 0xFF
};
template<typename InputIterator, typename T>
inline bool string_to_signed_type_converter_impl_itr(InputIterator begin, InputIterator end, T& v)
{
if (0 == std::distance(begin,end))
return false;
v = 0;
InputIterator it = begin;
bool negative = false;
if ('+' == *it)
++it;
else if ('-' == *it)
{
++it;
negative = true;
}
if (end == it)
return false;
while(end != it)
{
const T digit = static_cast<T>(digit_table[static_cast<unsigned int>(*it++)]);
if (0xFF == digit)
return false;
v = (10 * v) + digit;
}
if (negative)
v *= -1;
return true;
}
InputIterator có thể là các ký tự không dấu char *, char * hoặc std :: string và T được dự kiến là một int đã ký, chẳng hạn như int, int hoặc long
v = (10 * v) + digit;
tràn không cần thiết với đầu vào chuỗi với giá trị văn bản của INT_MIN
. Bảng có giá trị đáng ngờ so với đơn giảndigit >= '0' && digit <= '9'
Nếu bạn có C ++ 11, các giải pháp phù hợp hiện nay là C ++ nguyên chức năng chuyển đổi trong <string>
: stoi
, stol
, stoul
, stoll
, stoull
. Họ đưa ra các ngoại lệ thích hợp khi đưa ra đầu vào không chính xác và sử dụng các strto*
chức năng nhanh và nhỏ dưới mui xe.
Nếu bạn bị mắc kẹt với bản sửa đổi C ++ trước đó, bạn sẽ có thể chuyển tiếp để bắt chước các chức năng này khi triển khai.
Từ C ++ 17 trở đi, bạn có thể sử dụng std::from_chars
từ <charconv>
tiêu đề như được ghi lại ở đây .
Ví dụ:
#include <iostream>
#include <charconv>
#include <array>
int main()
{
char const * str = "42";
int value = 0;
std::from_chars_result result = std::from_chars(std::begin(str), std::end(str), value);
if(result.error == std::errc::invalid_argument)
{
std::cout << "Error, invalid format";
}
else if(result.error == std::errc::result_out_of_range)
{
std::cout << "Error, value too big for int range";
}
else
{
std::cout << "Success: " << result;
}
}
Là một phần thưởng, nó cũng có thể xử lý các cơ sở khác, như thập lục phân.
Tôi thích câu trả lời của Dan Moulding , tôi sẽ chỉ thêm một chút phong cách C ++ vào đó:
#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>
int to_int(const std::string &s, int base = 0)
{
char *end;
errno = 0;
long result = std::strtol(s.c_str(), &end, base);
if (errno == ERANGE || result > INT_MAX || result < INT_MIN)
throw std::out_of_range("toint: string is out of range");
if (s.length() == 0 || *end != '\0')
throw std::invalid_argument("toint: invalid string");
return result;
}
Nó hoạt động cho cả std :: string và const char * thông qua chuyển đổi ngầm định. Nó cũng hữu ích cho việc chuyển đổi cơ sở, ví dụ như tất cả to_int("0x7b")
và to_int("0173")
và to_int("01111011", 2)
và to_int("0000007B", 16)
và to_int("11120", 3)
và to_int("3L", 34);
sẽ quay trở lại 123.
Không giống như std::stoi
nó hoạt động trong tiền C ++ 11. Cũng không giống như std::stoi
, boost::lexical_cast
vàstringstream
nó đưa ra các ngoại lệ cho các chuỗi kỳ lạ như "123hohoho".
NB: Hàm này cho phép các không gian hàng đầu nhưng không theo dõi các khoảng trắng, tức là to_int(" 123")
trả về 123 trong khi to_int("123 ")
ném ngoại lệ. Hãy chắc chắn rằng điều này được chấp nhận cho trường hợp sử dụng của bạn hoặc điều chỉnh mã.
Chức năng như vậy có thể là một phần của STL ...
Tôi biết ba cách chuyển đổi String thành int:
Hoặc sử dụng hàm stoi (String to int) hoặc chỉ đi với Stringstream, cách thứ ba để chuyển đổi riêng lẻ, Mã bên dưới:
Phương pháp 1
std::string s1 = "4533";
std::string s2 = "3.010101";
std::string s3 = "31337 with some string";
int myint1 = std::stoi(s1);
int myint2 = std::stoi(s2);
int myint3 = std::stoi(s3);
std::cout << s1 <<"=" << myint1 << '\n';
std::cout << s2 <<"=" << myint2 << '\n';
std::cout << s3 <<"=" << myint3 << '\n';
Phương pháp 2
#include <string.h>
#include <sstream>
#include <iostream>
#include <cstring>
using namespace std;
int StringToInteger(string NumberAsString)
{
int NumberAsInteger;
stringstream ss;
ss << NumberAsString;
ss >> NumberAsInteger;
return NumberAsInteger;
}
int main()
{
string NumberAsString;
cin >> NumberAsString;
cout << StringToInteger(NumberAsString) << endl;
return 0;
}
Phương pháp thứ 3 - nhưng không dành cho chuyển đổi cá nhân
std::string str4 = "453";
int i = 0, in=0; // 453 as on
for ( i = 0; i < str4.length(); i++)
{
in = str4[i];
cout <<in-48 ;
}
Tôi thích câu trả lời của Dan , đặc biệt là vì tránh các ngoại lệ. Đối với phát triển hệ thống nhúng và phát triển hệ thống cấp thấp khác, có thể không có khung Ngoại lệ phù hợp.
Đã thêm kiểm tra khoảng trắng sau chuỗi hợp lệ ... ba dòng này
while (isspace(*end)) {
end++;
}
Đã thêm một kiểm tra cho các lỗi phân tích cú pháp quá.
if ((errno != 0) || (s == end)) {
return INCONVERTIBLE;
}
Đây là chức năng hoàn chỉnh ..
#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>
enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };
STR2INT_ERROR str2long (long &l, char const *s, int base = 0)
{
char *end = (char *)s;
errno = 0;
l = strtol(s, &end, base);
if ((errno == ERANGE) && (l == LONG_MAX)) {
return OVERFLOW;
}
if ((errno == ERANGE) && (l == LONG_MIN)) {
return UNDERFLOW;
}
if ((errno != 0) || (s == end)) {
return INCONVERTIBLE;
}
while (isspace((unsigned char)*end)) {
end++;
}
if (*s == '\0' || *end != '\0') {
return INCONVERTIBLE;
}
return SUCCESS;
}
" "
. strtol()
không được chỉ định để đặt errno
khi không có chuyển đổi xảy ra. Tốt hơn để sử dụng if (s == end) return INCONVERTIBLE;
để phát hiện không có chuyển đổi. Và sau đó if (*s == '\0' || *end != '\0')
có thể đơn giản hóa thành if (*end)
2) || l > LONG_MAX
và || l < LONG_MIN
không phục vụ mục đích - chúng không bao giờ đúng.
Bạn có thể sử dụng phương pháp xác định này.
#define toInt(x) {atoi(x.c_str())};
Và nếu bạn đã chuyển đổi từ Chuỗi thành Số nguyên, bạn sẽ chỉ cần làm như sau.
int main()
{
string test = "46", test2 = "56";
int a = toInt(test);
int b = toInt(test2);
cout<<a+b<<endl;
}
Đầu ra sẽ là 102.
atoi
dường như không giống như "cách C ++", theo các câu trả lời khác như được chấp nhận std::stoi()
.
Tôi biết đây là một câu hỏi cũ hơn, nhưng tôi đã bắt gặp nó rất nhiều lần và cho đến nay, vẫn chưa tìm thấy một giải pháp khuôn mẫu độc đáo nào có các đặc điểm sau:
Vì vậy, đây là của tôi, với một dây đeo thử nghiệm. Bởi vì nó sử dụng các hàm C strtoull / strtoll dưới mui xe, nó luôn chuyển đổi đầu tiên thành loại lớn nhất có sẵn. Sau đó, nếu bạn không sử dụng loại lớn nhất, nó sẽ thực hiện kiểm tra phạm vi bổ sung để xác minh loại của bạn chưa kết thúc (dưới). Đối với điều này, nó là một ít hiệu suất hơn so với nếu một người chọn đúng strtol / strtoul. Tuy nhiên, nó cũng hoạt động cho quần short / ký tự và, theo hiểu biết tốt nhất của tôi, không tồn tại chức năng thư viện tiêu chuẩn nào làm điều đó.
Thưởng thức; hy vọng ai đó thấy nó hữu ích
#include <cstdlib>
#include <cerrno>
#include <limits>
#include <stdexcept>
#include <sstream>
static const int DefaultBase = 10;
template<typename T>
static inline T CstrtoxllWrapper(const char *str, int base = DefaultBase)
{
while (isspace(*str)) str++; // remove leading spaces; verify there's data
if (*str == '\0') { throw std::invalid_argument("str; no data"); } // nothing to convert
// NOTE: for some reason strtoull allows a negative sign, we don't; if
// converting to an unsigned then it must always be positive!
if (!std::numeric_limits<T>::is_signed && *str == '-')
{ throw std::invalid_argument("str; negative"); }
// reset errno and call fn (either strtoll or strtoull)
errno = 0;
char *ePtr;
T tmp = std::numeric_limits<T>::is_signed ? strtoll(str, &ePtr, base)
: strtoull(str, &ePtr, base);
// check for any C errors -- note these are range errors on T, which may
// still be out of the range of the actual type we're using; the caller
// may need to perform additional range checks.
if (errno != 0)
{
if (errno == ERANGE) { throw std::range_error("str; out of range"); }
else if (errno == EINVAL) { throw std::invalid_argument("str; EINVAL"); }
else { throw std::invalid_argument("str; unknown errno"); }
}
// verify everything converted -- extraneous spaces are allowed
if (ePtr != NULL)
{
while (isspace(*ePtr)) ePtr++;
if (*ePtr != '\0') { throw std::invalid_argument("str; bad data"); }
}
return tmp;
}
template<typename T>
T StringToSigned(const char *str, int base = DefaultBase)
{
static const long long max = std::numeric_limits<T>::max();
static const long long min = std::numeric_limits<T>::min();
long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type
// final range check -- only needed if not long long type; a smart compiler
// should optimize this whole thing out
if (sizeof(T) == sizeof(tmp)) { return tmp; }
if (tmp < min || tmp > max)
{
std::ostringstream err;
err << "str; value " << tmp << " out of " << sizeof(T) * 8
<< "-bit signed range (";
if (sizeof(T) != 1) err << min << ".." << max;
else err << (int) min << ".." << (int) max; // don't print garbage chars
err << ")";
throw std::range_error(err.str());
}
return tmp;
}
template<typename T>
T StringToUnsigned(const char *str, int base = DefaultBase)
{
static const unsigned long long max = std::numeric_limits<T>::max();
unsigned long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type
// final range check -- only needed if not long long type; a smart compiler
// should optimize this whole thing out
if (sizeof(T) == sizeof(tmp)) { return tmp; }
if (tmp > max)
{
std::ostringstream err;
err << "str; value " << tmp << " out of " << sizeof(T) * 8
<< "-bit unsigned range (0..";
if (sizeof(T) != 1) err << max;
else err << (int) max; // don't print garbage chars
err << ")";
throw std::range_error(err.str());
}
return tmp;
}
template<typename T>
inline T
StringToDecimal(const char *str, int base = DefaultBase)
{
return std::numeric_limits<T>::is_signed ? StringToSigned<T>(str, base)
: StringToUnsigned<T>(str, base);
}
template<typename T>
inline T
StringToDecimal(T &out_convertedVal, const char *str, int base = DefaultBase)
{
return out_convertedVal = StringToDecimal<T>(str, base);
}
/*============================== [ Test Strap ] ==============================*/
#include <inttypes.h>
#include <iostream>
static bool _g_anyFailed = false;
template<typename T>
void TestIt(const char *tName,
const char *s, int base,
bool successExpected = false, T expectedValue = 0)
{
#define FAIL(s) { _g_anyFailed = true; std::cout << s; }
T x;
std::cout << "converting<" << tName << ">b:" << base << " [" << s << "]";
try
{
StringToDecimal<T>(x, s, base);
// get here on success only
if (!successExpected)
{
FAIL(" -- TEST FAILED; SUCCESS NOT EXPECTED!" << std::endl);
}
else
{
std::cout << " -> ";
if (sizeof(T) != 1) std::cout << x;
else std::cout << (int) x; // don't print garbage chars
if (x != expectedValue)
{
FAIL("; FAILED (expected value:" << expectedValue << ")!");
}
std::cout << std::endl;
}
}
catch (std::exception &e)
{
if (successExpected)
{
FAIL( " -- TEST FAILED; EXPECTED SUCCESS!"
<< " (got:" << e.what() << ")" << std::endl);
}
else
{
std::cout << "; expected exception encounterd: [" << e.what() << "]" << std::endl;
}
}
}
#define TEST(t, s, ...) \
TestIt<t>(#t, s, __VA_ARGS__);
int main()
{
std::cout << "============ variable base tests ============" << std::endl;
TEST(int, "-0xF", 0, true, -0xF);
TEST(int, "+0xF", 0, true, 0xF);
TEST(int, "0xF", 0, true, 0xF);
TEST(int, "-010", 0, true, -010);
TEST(int, "+010", 0, true, 010);
TEST(int, "010", 0, true, 010);
TEST(int, "-10", 0, true, -10);
TEST(int, "+10", 0, true, 10);
TEST(int, "10", 0, true, 10);
std::cout << "============ base-10 tests ============" << std::endl;
TEST(int, "-010", 10, true, -10);
TEST(int, "+010", 10, true, 10);
TEST(int, "010", 10, true, 10);
TEST(int, "-10", 10, true, -10);
TEST(int, "+10", 10, true, 10);
TEST(int, "10", 10, true, 10);
TEST(int, "00010", 10, true, 10);
std::cout << "============ base-8 tests ============" << std::endl;
TEST(int, "777", 8, true, 0777);
TEST(int, "-0111 ", 8, true, -0111);
TEST(int, "+0010 ", 8, true, 010);
std::cout << "============ base-16 tests ============" << std::endl;
TEST(int, "DEAD", 16, true, 0xDEAD);
TEST(int, "-BEEF", 16, true, -0xBEEF);
TEST(int, "+C30", 16, true, 0xC30);
std::cout << "============ base-2 tests ============" << std::endl;
TEST(int, "-10011001", 2, true, -153);
TEST(int, "10011001", 2, true, 153);
std::cout << "============ irregular base tests ============" << std::endl;
TEST(int, "Z", 36, true, 35);
TEST(int, "ZZTOP", 36, true, 60457993);
TEST(int, "G", 17, true, 16);
TEST(int, "H", 17);
std::cout << "============ space deliminated tests ============" << std::endl;
TEST(int, "1337 ", 10, true, 1337);
TEST(int, " FEAD", 16, true, 0xFEAD);
TEST(int, " 0711 ", 0, true, 0711);
std::cout << "============ bad data tests ============" << std::endl;
TEST(int, "FEAD", 10);
TEST(int, "1234 asdfklj", 10);
TEST(int, "-0xF", 10);
TEST(int, "+0xF", 10);
TEST(int, "0xF", 10);
TEST(int, "-F", 10);
TEST(int, "+F", 10);
TEST(int, "12.4", 10);
TEST(int, "ABG", 16);
TEST(int, "10011002", 2);
std::cout << "============ int8_t range tests ============" << std::endl;
TEST(int8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
TEST(int8_t, "80", 16);
TEST(int8_t, "-80", 16, true, std::numeric_limits<int8_t>::min());
TEST(int8_t, "-81", 16);
TEST(int8_t, "FF", 16);
TEST(int8_t, "100", 16);
std::cout << "============ uint8_t range tests ============" << std::endl;
TEST(uint8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
TEST(uint8_t, "80", 16, true, std::numeric_limits<int8_t>::max()+1);
TEST(uint8_t, "-80", 16);
TEST(uint8_t, "-81", 16);
TEST(uint8_t, "FF", 16, true, std::numeric_limits<uint8_t>::max());
TEST(uint8_t, "100", 16);
std::cout << "============ int16_t range tests ============" << std::endl;
TEST(int16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
TEST(int16_t, "8000", 16);
TEST(int16_t, "-8000", 16, true, std::numeric_limits<int16_t>::min());
TEST(int16_t, "-8001", 16);
TEST(int16_t, "FFFF", 16);
TEST(int16_t, "10000", 16);
std::cout << "============ uint16_t range tests ============" << std::endl;
TEST(uint16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
TEST(uint16_t, "8000", 16, true, std::numeric_limits<int16_t>::max()+1);
TEST(uint16_t, "-8000", 16);
TEST(uint16_t, "-8001", 16);
TEST(uint16_t, "FFFF", 16, true, std::numeric_limits<uint16_t>::max());
TEST(uint16_t, "10000", 16);
std::cout << "============ int32_t range tests ============" << std::endl;
TEST(int32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
TEST(int32_t, "80000000", 16);
TEST(int32_t, "-80000000", 16, true, std::numeric_limits<int32_t>::min());
TEST(int32_t, "-80000001", 16);
TEST(int32_t, "FFFFFFFF", 16);
TEST(int32_t, "100000000", 16);
std::cout << "============ uint32_t range tests ============" << std::endl;
TEST(uint32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
TEST(uint32_t, "80000000", 16, true, std::numeric_limits<int32_t>::max()+1);
TEST(uint32_t, "-80000000", 16);
TEST(uint32_t, "-80000001", 16);
TEST(uint32_t, "FFFFFFFF", 16, true, std::numeric_limits<uint32_t>::max());
TEST(uint32_t, "100000000", 16);
std::cout << "============ int64_t range tests ============" << std::endl;
TEST(int64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
TEST(int64_t, "8000000000000000", 16);
TEST(int64_t, "-8000000000000000", 16, true, std::numeric_limits<int64_t>::min());
TEST(int64_t, "-8000000000000001", 16);
TEST(int64_t, "FFFFFFFFFFFFFFFF", 16);
TEST(int64_t, "10000000000000000", 16);
std::cout << "============ uint64_t range tests ============" << std::endl;
TEST(uint64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
TEST(uint64_t, "8000000000000000", 16, true, std::numeric_limits<int64_t>::max()+1);
TEST(uint64_t, "-8000000000000000", 16);
TEST(uint64_t, "-8000000000000001", 16);
TEST(uint64_t, "FFFFFFFFFFFFFFFF", 16, true, std::numeric_limits<uint64_t>::max());
TEST(uint64_t, "10000000000000000", 16);
std::cout << std::endl << std::endl
<< (_g_anyFailed ? "!! SOME TESTS FAILED !!" : "ALL TESTS PASSED")
<< std::endl;
return _g_anyFailed;
}
StringToDecimal
là phương pháp sử dụng đất; nó bị quá tải nên có thể được gọi như thế này:
int a; a = StringToDecimal<int>("100");
hoặc này:
int a; StringToDecimal(a, "100");
Tôi ghét lặp lại kiểu int, vì vậy thích cái sau. Điều này đảm bảo rằng nếu loại 'a' thay đổi, người ta sẽ không nhận được kết quả xấu. Tôi muốn trình biên dịch có thể tìm ra nó như sau:
int a; a = StringToDecimal("100");
... nhưng, C ++ không suy ra các kiểu trả về mẫu, vì vậy đó là cách tốt nhất tôi có thể nhận được.
Việc thực hiện khá đơn giản:
CstrtoxllWrapper
kết thúc cả hai strtoull
và strtoll
, gọi bất cứ điều gì là cần thiết dựa trên tính chất đã ký của loại mẫu và cung cấp một số đảm bảo bổ sung (ví dụ: đầu vào phủ định không được phép nếu không được ký và nó đảm bảo toàn bộ chuỗi được chuyển đổi).
CstrtoxllWrapper
được sử dụng bởi StringToSigned
và StringToUnsigned
với loại lớn nhất (dài dài / dài không dấu) có sẵn cho trình biên dịch; điều này cho phép chuyển đổi tối đa được thực hiện. Sau đó, nếu cần thiết, StringToSigned
/ StringToUnsigned
thực hiện kiểm tra phạm vi cuối cùng trên loại cơ bản. Cuối cùng, phương pháp điểm cuối,StringToDecimal
, quyết định phương thức mẫu StringTo * nào sẽ gọi dựa trên kiểu đã ký của kiểu cơ bản.
Tôi nghĩ rằng hầu hết các rác có thể được tối ưu hóa bởi trình biên dịch; tất cả mọi thứ nên được biên dịch - thời gian xác định. Bất kỳ bình luận về khía cạnh này sẽ là thú vị đối với tôi!
long long
thay vì intmax_t
?
if (ePtr != str)
. Hơn nữa, sử dụng isspace((unsigned char) *ePtr)
để xử lý đúng các giá trị âm của *ePtr
.
Trong C, bạn có thể sử dụng int atoi (const char * str)
,
Phân tích chuỗi chuỗi C diễn giải nội dung của nó dưới dạng một số nguyên, được trả về dưới dạng giá trị của kiểu int.
atoi
trong câu hỏi, tôi biết về nó. Câu hỏi rõ ràng không phải về C, mà là về C ++. -1