Làm thế nào để phân tích một chuỗi thành một int trong C ++?


261

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 ).


21
Làm thế nào về một số ví dụ từ sau: codeproject.com/KB/recipes/Tokenizer.aspx Chúng rất hiệu quả và hơi thanh lịch

@Beh Tou Cheh, nếu bạn nghĩ rằng đó là một cách hay để phân tích int, xin vui lòng gửi nó như một câu trả lời.
Eugene Yokota

Câu trả lời:


165

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


4
Nhưng họ chấp nhận các đối số hơn thế, một trong số đó là một điểm để size_t, nếu không phải là null, được đặt thành ký tự chưa được chuyển đổi đầu tiên
Zharf

Có, sử dụng tham số thứ hai của std :: stoi, bạn có thể phát hiện đầu vào không hợp lệ. Bạn vẫn phải cuộn chức năng chuyển đổi của riêng mình mặc dù ...
CC.

Giống như câu trả lời được chấp nhận đã làm, nhưng với các chức năng tiêu chuẩn này sẽ sạch hơn rất nhiều, imo
Zharf

Hãy nhớ rằng đối số thứ hai trong các hàm này có thể được sử dụng để cho biết toàn bộ chuỗi có được chuyển đổi hay không. Nếu kết quả size_tkhô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 possẽ là 2 thay vì độ dài chuỗi 3. coliru.stacked-crooking.com/a/cabe25d64d2ffa29
Zoe

204

Những gì không làm

Đâ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 trueisẽ 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 stringstreamsau 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 đó 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_castcho việc này . Xem xét những gì lexical_casttà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 stringstreamcó mức độ kiểm soát kém, và nó nói rằng stringstreamnên được sử dụng thay vì lexical_castnếu bạn cần "mức độ kiểm soát cao hơn". Ngoài ra, vì lexical_castchỉ 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.

Giải pháp tốt nhất

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 strtolvà 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 basebằ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):

  • Nó thể hiện hiệu năng thời gian chạy tốt hơn nhiều
  • Nó giới thiệu chi phí thời gian biên dịch ít hơn (những cái khác kéo SLOC gần gấp 20 lần từ các tiêu đề)
  • Nó dẫn đến kích thước mã nhỏ nhất

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.


22
@JamesDunne: POSIX yêu cầu phải strtolan toàn cho chuỗi. POSIX cũng yêu cầu errnosử 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 errnotrê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 errnophải tuân thủ POSIX. Tiêu chuẩn C mới nhất cũng yêu cầu errnophải có bộ lưu trữ cục bộ. Ngay cả trên Windows, chắc chắn không tuân thủ POSIX, errnocũng an toàn theo luồng và, bằng cách mở rộng, cũng vậy strtol.
Dan Mould

7
Tôi thực sự không thể làm theo lý luận của bạn chống lại việc sử dụng boost :: lexical_cast. Như họ nói, std :: stringstream thực sự cung cấp rất nhiều quyền kiểm soát - bạn làm mọi thứ từ kiểm tra lỗi đến xác định chính bạn. Tài liệu hiện tại đưa ra như sau: "Đối với các chuyển đổi có liên quan nhiều hơn, chẳng hạn như độ chính xác hoặc định dạng cần kiểm soát chặt chẽ hơn được cung cấp bởi hành vi mặc định của lexical_cast, nên sử dụng phương pháp std :: chuỗi dòng thông thường."
fhd

8
Đây là mã C không phù hợp trong C ++. Thư viện tiêu chuẩn chứa đựng 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ố.
fuzzyTew

22
@fuzzyTew Tôi đã viết câu trả lời này trước khi std::stolthậ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::strtollà 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.
Dan Mould

9
@fuzzyTew: Hết dung lượng đĩa là một điều kiện đặc biệt. Các tệp dữ liệu không đúng định dạng do máy tính tạo ra là một điều kiện đặc biệt. Nhưng lỗi chính tả trong đầu vào của người dùng không phải là ngoại lệ. Thật tốt khi có một cách tiếp cận phân tích cú pháp có thể xử lý các lỗi phân tích cú pháp thông thường, không đặc biệt.
Ben Voigt

67

Đâ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)


1
vui lòng cập nhật phiên bản chuỗi của bạn để bao gồm kiểm tra chuỗi dòng :: fail () (theo yêu cầu của người hỏi "Xử lý lỗi rõ ràng và mạnh mẽ")
jk.

2
Phiên bản chuỗi của bạn sẽ chấp nhận những thứ như "10haha" mà không phàn nàn
Johannes Schaub - litb

3
thay đổi nó thành (! (ss >> num) .fail () && (ss >> ws) .eof ()) từ ((ss >> num) .fail ()) nếu bạn muốn xử lý tương tự như lexical_cast
Johannes Schaub - litb

3
C ++ với phương thức chuỗi chuỗi thư viện chuẩn không hoạt động đối với các chuỗi như "12-someString" ngay cả với kiểm tra .fail ().
captonssj

C ++ 11 bao gồm các chức năng nhanh tiêu chuẩn cho việc này ngay bây giờ
fuzzyTew

21

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.


4
Oh xin vui lòng không sử dụng công cụ C cũ này khi lập trình C ++. Có nhiều cách tốt hơn / dễ dàng hơn / sạch hơn / hiện đại hơn / an toàn hơn để làm điều này trong C ++!
jk.

27
Thật buồn cười khi mọi người quan tâm đến những cách "hiện đại hơn" để giải quyết vấn đề.
J Miller

@Jason, IMO loại an toàn và xử lý lỗi mạnh hơn là ý tưởng hiện đại hơn so với C.
Eugene Yokota

6
Tôi đã xem xét các câu trả lời khác, và cho đến nay không có gì rõ ràng là tốt hơn / dễ dàng hơn / sạch hơn hoặc an toàn hơn. Các poster cho biết anh ta có một char *. Điều đó giới hạn mức độ an toàn mà bạn sẽ nhận được :)
Chris Arguin


16

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 ++.


2
Phương thức chuỗi chuỗi C ++ không hoạt động đối với các chuỗi như "12-someString" ngay cả với kiểm tra 'trạng thái luồng'.
captonssj

10

Bạn có thể sử dụng chuỗi

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  ss >> num;
  return num;
}

4
Nhưng điều này không xử lý bất kỳ lỗi nào. Bạn phải kiểm tra luồng cho thất bại.
jk.

1
Phải, bạn phải kiểm tra luồng nếu ((ss >> num) .fail ()) {// ERROR}
CMS

2
Phương thức chuỗi chuỗi C ++ không hoạt động đối với các chuỗi như "12-someString" ngay cả với kiểm tra 'trạng thái luồng'
captonssj

8

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;
}

7

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


1
CẢNH BÁO Việc triển khai này có vẻ tốt, nhưng không xử lý tràn ra xa như tôi có thể nói.
Vinnie Falco

2
Mã không xử lý tràn. 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'
chux - Tái lập Monica

6

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.


6

Từ C ++ 17 trở đi, bạn có thể sử dụng std::from_charstừ <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.


3

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")to_int("0173")to_int("01111011", 2)to_int("0000007B", 16)to_int("11120", 3)to_int("3L", 34);sẽ quay trở lại 123.

Không giống như std::stoinó hoạt động trong tiền C ++ 11. Cũng không giống như std::stoi, boost::lexical_caststringstream 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 ...


2

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 ;

}

1

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;
}

@chux thêm mã để chăm sóc những mối quan tâm bạn đã đề cập.
nang

1) Vẫn không phát hiện lỗi với đầu vào như thế nào " ". strtol()không được chỉ định để đặt errnokhi 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|| l < LONG_MINkhông phục vụ mục đích - chúng không bao giờ đúng.
chux - Phục hồi Monica

@chux Trên máy mac, errno được đặt để phân tích lỗi, nhưng trên linux thì errno không được đặt. Thay đổi mã để phụ thuộc vào con trỏ "kết thúc" để phát hiện điều đó.
nang

0

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.


4
idk. Viết một macro xác định xung quanh atoidườ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().
Eugene Yokota

Tôi thấy thú vị hơn khi sử dụng các phương pháp được xác định trước: P
Boris

0

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:

  • Có thể chuyển đổi bất kỳ cơ sở (và phát hiện loại cơ sở)
  • Sẽ phát hiện dữ liệu sai (nghĩa là đảm bảo toàn bộ chuỗi, khoảng trắng dẫn đầu / theo dõi ít ​​hơn, được sử dụng bởi chuyển đổi)
  • Sẽ đảm bảo rằng, bất kể loại được chuyển đổi thành, phạm vi giá trị của chuỗi đều được chấp nhận.

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;
}

StringToDecimallà 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:

CstrtoxllWrapperkết thúc cả hai strtoullstrtoll, 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 StringToSignedStringToUnsignedvớ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/ StringToUnsignedthự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!


"sử dụng loại lớn nhất" -> tại sao long longthay vì intmax_t?
chux - Phục hồi Monica

Tự tin bạn muốn 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.
chux - Phục hồi Monica

-3

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.


2
Khi tôi liên kết đến atoitrong 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
Eugene Yokota
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.