Chuyển đổi một chuỗi trong C ++ sang chữ hoa


268

Làm thế nào người ta có thể chuyển đổi một chuỗi thành chữ hoa Các ví dụ tôi đã tìm thấy từ googling chỉ phải đối phó với ký tự.

Câu trả lời:


205

Tăng cường thuật toán chuỗi:

#include <boost/algorithm/string.hpp>
#include <string>

std::string str = "Hello World";

boost::to_upper(str);

std::string newstr = boost::to_upper_copy<std::string>("Hello World");

5
Điều này cũng có lợi ích của i18n, nơi ::toupperrất có thể giả định ASCII.
Ben Straub

4
Dòng cuối cùng của bạn không biên dịch - bạn phải đổi thành thứ gì đó như:std::string newstr(boost::to_upper_copy<std::string>("Hello World"));
maxschlepzig 18/03/2016

58
đây không phải là câu trả lời được chấp nhận vì nó yêu cầu tăng hoặc tiêu đề nên được thay đổi.
Andrea

44
vâng, tôi sẽ cài đặt boost chỉ cho to_upper ... ý tưởng tuyệt vời! </ mỉa mai> :)
thang

12
Cá nhân tôi không thích sử dụng boost như một câu trả lời cho "làm thế nào để tôi làm x trong C ++?" bởi vì boost không phải là một giải pháp nhẹ. Có vẻ như bạn mua tăng cường dưới dạng khung (hoặc ACE hoặc Qt hoặc Recusion ToolKit ++ hoặc ...) hoặc bạn không. Tôi muốn xem các giải pháp ngôn ngữ.
JWM

486
#include <algorithm>
#include <string>

std::string str = "Hello World";
std::transform(str.begin(), str.end(),str.begin(), ::toupper);

8
Trên thực tế, toupper()có thể được thực hiện như một vĩ mô. Điều này có thể gây ra một vấn đề.
dirkgently

3
một liên kết (:: toupper, xây dựng <unsign char> (_ 1)) với boost.lambda sẽ phục vụ hoàn toàn tốt tôi nghĩ.
Julian Schaub - litb

11
Cách tiếp cận này hoạt động tốt đối với ASCII, nhưng thất bại đối với mã hóa ký tự nhiều byte hoặc cho các quy tắc vỏ đặc biệt như 'ß' của Đức.
dan04

9
Tôi đã thay đổi câu trả lời được chấp nhận thành câu trả lời bằng thư viện boost, vì nó nhanh hơn (trong thử nghiệm không chính thức của tôi), dễ sử dụng hơn và không có vấn đề liên quan đến giải pháp này. Vẫn là một giải pháp tốt cho những trường hợp không thể sử dụng boost.
OrangeAlmondSoap

2
Tôi không thể hiểu tại sao trình biên dịch từ chối mã này mà không có :: vòng loại trước đó toupper. Có ý kiến ​​gì không?
sasha.sochka

89

Giải pháp ngắn sử dụng C ++ 11 và toupper ().

for (auto & c: str) c = toupper(c);

Sẽ không cthuộc const charloại (từ auto)? Nếu vậy, bạn không thể gán nó (vì constmột phần) cho những gì được trả về toupper(c).
PolGpson

5
@PolGpson: Phạm vi - dựa trên việc sử dụng các phương thức start () / end () của container để lặp lại nội dung của nó. std :: basic_opes có cả const và iterator có thể thay đổi (được trả về bởi cbegin () và started (), xem std :: basic_opes :: started), vì vậy for (:) sử dụng một (cbegin () thích hợp nếu str là khai báo const, với auto =: = const char, bắt đầu () nếu không, với auto =: = char).
Thanocation Papoutsidakis

5
Xem anser của dirkgently dưới đây, ccần phải được sử dụng để sửa lỗi unsigned charnày.
Cris Luengo

to'upper () của boost có vẻ phù hợp hơn nhiều với các hàm ST ++ của c ++ so với toupper.
tartaruga_casco_mole

29
struct convert {
   void operator()(char& c) { c = toupper((unsigned char)c); }
};

// ... 
string uc_str;
for_each(uc_str.begin(), uc_str.end(), convert());

Lưu ý: Một vài vấn đề với giải pháp hàng đầu:

21.5 Tiện ích trình tự kết thúc Null

Nội dung của các tiêu đề này sẽ giống như các tiêu đề Thư viện C tiêu chuẩn <ctype.h>, <wctype.h>, <string.h>, <wchar.h> và <stdlib.h> [...]

  • Điều đó có nghĩa là các cctypethành viên cũng có thể là các macro không phù hợp để tiêu thụ trực tiếp trong các thuật toán tiêu chuẩn.

  • Một vấn đề khác với cùng ví dụ là nó không đưa ra đối số hoặc xác minh rằng điều này không âm; Điều này đặc biệt nguy hiểm đối với các hệ thống nơi đồng bằng charđược ký kết. (Lý do là: nếu điều này được triển khai dưới dạng macro, nó có thể sẽ sử dụng bảng tra cứu và lập chỉ mục đối số của bạn vào bảng đó. Một chỉ mục phủ định sẽ cung cấp cho bạn UB.)


Các thành viên cctype bình thường là macro. Tôi nhớ đã đọc rằng chúng cũng phải có chức năng, mặc dù tôi không có bản sao của tiêu chuẩn C90 và không biết liệu nó có được tuyên bố rõ ràng hay không.
David Thornley

1
chúng phải là các hàm trong C ++ - ngay cả khi C cho phép chúng là macro. Tôi đồng ý với quan điểm thứ hai của bạn về việc casting. giải pháp hàng đầu có thể vượt qua các giá trị âm và gây ra UB với điều đó. đó là lý do tôi đã không bỏ phiếu cho nó (nhưng tôi cũng không bỏ phiếu cho nó) :)
Johannes Schaub - litb

1
không được thiếu trích dẫn tiêu chuẩn: 7.4.2.2/1 (kém litb, chỉ tham khảo dự thảo C99 TC2) và C ++ 17.4.1.2/6 trong tiêu chuẩn vinh quang c ++ 98.
Julian Schaub - litb

1
(chú ý chân-lưu ý với nó: "Không cho nay, thực tế phổ biến cung cấp một mặt nạ vĩ mô .... blah blupp .. cách duy nhất để làm điều đó trong C ++ là cung cấp một hàm inline extern.") :)
Johannes Schaub - litb

1
... điều đó đạt được nhờ thủ thuật này: stackoverflow.com/questions/650461/ Kẻ
Johannes Schaub - litb

27

Vấn đề này có thể thay đổi được với SIMD cho bộ ký tự ASCII.


So sánh tăng tốc:

Thử nghiệm sơ bộ với x86-64 gcc 5.2 -O3 -march=nativetrên Core2Duo (Merom). Cùng một chuỗi gồm 120 ký tự (hỗn hợp chữ thường và chữ thường ASCII), được chuyển đổi trong một vòng lặp 40M lần (không có nội dung tệp chéo, do đó trình biên dịch không thể tối ưu hóa hoặc loại bỏ bất kỳ ký tự nào ra khỏi vòng lặp). Cùng một bộ đệm nguồn và hủy, do đó, không có hiệu ứng trên bộ nhớ đệm hoặc bộ nhớ cache / bộ nhớ cache: dữ liệu luôn nóng trong bộ đệm L1 và chúng tôi hoàn toàn bị ràng buộc bởi CPU.

  • boost::to_upper_copy<char*, std::string>(): 198.0s . Vâng, Boost 1.58 trên Ubuntu 15.10 thực sự rất chậm. Tôi đã lược tả và thực hiện một bước trong trình gỡ lỗi và điều đó thực sự rất tệ: có một biến động của một biến cục bộ xảy ra trên mỗi ký tự !!! (Dynamic_cast nhận nhiều cuộc gọi đến strcmp). Điều này xảy ra với LANG=Cvà với LANG=en_CA.UTF-8.

    Tôi đã không kiểm tra bằng RangeT khác với std :: string. Có thể hình thức khácto_upper_copy tối ưu hóa tốt hơn, nhưng tôi nghĩ nó sẽ luôn luôn new/ mallockhông gian cho bản sao, vì vậy khó kiểm tra hơn. Có lẽ một cái gì đó tôi đã làm khác với trường hợp sử dụng thông thường và có lẽ g ++ thường dừng lại có thể nâng công cụ thiết lập ngôn ngữ ra khỏi vòng lặp cho mỗi ký tự. Vòng lặp của tôi đọc từ một std::stringvà viết để char dstbuf[4096]có ý nghĩa để thử nghiệm.

  • gọi vòng lặp glibc toupper: 6,67s (không kiểm tra intkết quả cho UTF-8 nhiều byte tiềm năng. Tuy nhiên, điều này quan trọng đối với tiếng Thổ Nhĩ Kỳ.)

  • Vòng lặp chỉ ASCII: 8.79s (phiên bản cơ sở của tôi cho các kết quả bên dưới.) Rõ ràng việc tra cứu bảng nhanh hơn a cmov, dù sao thì bảng cũng nóng trong L1.
  • Chỉ tự động vector hóa ASCII : 2.51s . (120 ký tự là một nửa giữa trường hợp xấu nhất và trường hợp tốt nhất, xem bên dưới)
  • ASCII chỉ được vector hóa thủ công: 1.35s

Xem thêm câu hỏi này về toupper()việc chậm trên Windows khi cài đặt ngôn ngữ .


Tôi đã bị sốc khi Boost là một thứ tự cường độ chậm hơn so với các tùy chọn khác. Tôi đã kiểm tra lại xem tôi đã -O3kích hoạt chưa, và thậm chí chỉ bước một bước để xem nó đang làm gì. Nó gần như chính xác cùng tốc độ với clang ++ 3.8. Nó có chi phí rất lớn bên trong vòng lặp trên mỗi ký tự. Các perf record/ reportquả (đối với cyclestrường hợp Perf) là:

  32.87%  flipcase-clang-  libstdc++.so.6.0.21   [.] _ZNK10__cxxabiv121__vmi_class_type_info12__do_dyncastElNS_17__class_type_info10__sub_kindEPKS1_PKvS4_S6_RNS1_16
  21.90%  flipcase-clang-  libstdc++.so.6.0.21   [.] __dynamic_cast                                                                                                 
  16.06%  flipcase-clang-  libc-2.21.so          [.] __GI___strcmp_ssse3                                                                                            
   8.16%  flipcase-clang-  libstdc++.so.6.0.21   [.] _ZSt9use_facetISt5ctypeIcEERKT_RKSt6locale                                                                     
   7.84%  flipcase-clang-  flipcase-clang-boost  [.] _Z16strtoupper_boostPcRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE                                   
   2.20%  flipcase-clang-  libstdc++.so.6.0.21   [.] strcmp@plt                                                                                                     
   2.15%  flipcase-clang-  libstdc++.so.6.0.21   [.] __dynamic_cast@plt                                                                                             
   2.14%  flipcase-clang-  libstdc++.so.6.0.21   [.] _ZNKSt6locale2id5_M_idEv                                                                                       
   2.11%  flipcase-clang-  libstdc++.so.6.0.21   [.] _ZNKSt6locale2id5_M_idEv@plt                                                                                   
   2.08%  flipcase-clang-  libstdc++.so.6.0.21   [.] _ZNKSt5ctypeIcE10do_toupperEc                                                                                  
   2.03%  flipcase-clang-  flipcase-clang-boost  [.] _ZSt9use_facetISt5ctypeIcEERKT_RKSt6locale@plt                                                                 
   0.08% ...

Tự động hóa

Gcc và clang sẽ chỉ tự động vector hóa các vòng lặp khi số lần lặp được biết trước vòng lặp. (tức là các vòng tìm kiếm như triển khai đồng bằng C strlensẽ không tự động kiểm tra.)

Do đó, đối với các chuỗi đủ nhỏ để phù hợp với bộ đệm, chúng ta sẽ có được sự tăng tốc đáng kể cho các chuỗi ~ 128 ký tự dài từ strlenlần đầu tiên thực hiện . Điều này sẽ không cần thiết cho các chuỗi có độ dài rõ ràng (như C ++ std::string).

// char, not int, is essential: otherwise gcc unpacks to vectors of int!  Huge slowdown.
char ascii_toupper_char(char c) {
    return ('a' <= c && c <= 'z') ? c^0x20 : c;    // ^ autovectorizes to PXOR: runs on more ports than paddb
}

// gcc can only auto-vectorize loops when the number of iterations is known before the first iteration.  strlen gives us that
size_t strtoupper_autovec(char *dst, const char *src) {
    size_t len = strlen(src);
    for (size_t i=0 ; i<len ; ++i) {
        dst[i] = ascii_toupper_char(src[i]);  // gcc does the vector range check with psubusb / pcmpeqb instead of pcmpgtb
    }
    return len;
}

Bất kỳ libc phong nha nào cũng sẽ có hiệu quả strlennhanh hơn nhiều so với việc lặp một byte tại một thời điểm, do đó, các vòng lặp strlen và toupper được vector hóa nhanh hơn.

Đường cơ sở: một vòng lặp kiểm tra kết thúc 0 khi đang bay.

Lần cho 40 lần lặp, trên Core2 (Merom) 2.4GHz. gcc 5.2 -O3 -march=native. (Ubuntu 15.10). dst != src(vì vậy chúng tôi tạo một bản sao), nhưng chúng không trùng nhau (và không ở gần đó). Cả hai đều được căn chỉnh.

  • Chuỗi 15 char: đường cơ sở: 1.08s. autovec: 1.34s
  • Chuỗi 16 char: đường cơ sở: 1.16s. autovec: 1,52 giây
  • Chuỗi 127 char: đường cơ sở: 8,91s. autovec: 2,98s // dọn dẹp không vector có 15 ký tự để xử lý
  • Chuỗi 128 char: đường cơ sở: 9.00s. autovec: 2.06s
  • Chuỗi char 129: đường cơ sở: 9.04s. autovec: 2.07s // dọn dẹp không vector có 1 char để xử lý

Một số kết quả là một chút khác nhau với tiếng kêu.

Vòng lặp microbenchmark gọi hàm nằm trong một tệp riêng. Nếu không, nó sẽ nội tuyến và strlen()được kéo ra khỏi vòng lặp, và nó chạy nhanh hơn đáng kể, đặc biệt. cho 16 chuỗi char (0.187s).

Điều này có lợi thế lớn là gcc có thể tự động vector hóa nó cho bất kỳ kiến ​​trúc nào, nhưng nhược điểm lớn là nó chậm hơn đối với trường hợp thường gặp của các chuỗi nhỏ.


Vì vậy, có những sự tăng tốc lớn, nhưng tự động hóa trình biên dịch không tạo ra mã tuyệt vời, đặc biệt. để dọn dẹp tối đa 15 ký tự cuối cùng.

Vector hóa thủ công với nội tại SSE:

Dựa trên chức năng lật trường hợp của tôi mà đảo ngược trường hợp của mọi ký tự chữ cái. Nó lợi dụng "thủ thuật so sánh không dấu", trong đó bạn có thể thực hiện low < a && a <= highvới một so sánh không dấu bằng cách dịch chuyển phạm vi, sao cho bất kỳ giá trị nào nhỏ hơn lowkết thúc thành một giá trị lớn hơn high. (Điều này hoạt động nếu lowhighkhông quá xa nhau.)

SSE chỉ có một so sánh được ký lớn hơn, nhưng chúng ta vẫn có thể sử dụng thủ thuật "so sánh không dấu" bằng cách dịch chuyển phạm vi xuống dưới cùng của phạm vi đã ký: Trừ 'a' + 128, vì vậy các ký tự chữ cái nằm trong khoảng từ -128 đến -128 +25 (-128 + 'z' - 'a')

Lưu ý rằng việc thêm 128 và trừ 128 là điều tương tự đối với số nguyên 8 bit. Không có nơi nào để mang đi, vì vậy nó chỉ là xor (addless add), lật bit cao.

#include <immintrin.h>

__m128i upcase_si128(__m128i src) {
    // The above 2 paragraphs were comments here
    __m128i rangeshift = _mm_sub_epi8(src, _mm_set1_epi8('a'+128));
    __m128i nomodify   = _mm_cmpgt_epi8(rangeshift, _mm_set1_epi8(-128 + 25));  // 0:lower case   -1:anything else (upper case or non-alphabetic).  25 = 'z' - 'a'

    __m128i flip  = _mm_andnot_si128(nomodify, _mm_set1_epi8(0x20));            // 0x20:lcase    0:non-lcase

    // just mask the XOR-mask so elements are XORed with 0 instead of 0x20
    return          _mm_xor_si128(src, flip);
    // it's easier to xor with 0x20 or 0 than to AND with ~0x20 or 0xFF
}

Với hàm này hoạt động cho một vectơ, chúng ta có thể gọi nó trong một vòng lặp để xử lý toàn bộ chuỗi. Vì chúng tôi đã nhắm mục tiêu SSE2, chúng tôi có thể thực hiện kiểm tra cuối chuỗi được vector hóa cùng một lúc.

Chúng ta cũng có thể làm tốt hơn nhiều cho việc "dọn dẹp" tối đa 15 byte cuối cùng còn sót lại sau khi thực hiện các vectơ 16B: lớp trên là idempotent, vì vậy xử lý lại một số byte đầu vào là tốt. Chúng tôi thực hiện tải không được sắp xếp của 16B cuối cùng của nguồn và lưu trữ nó vào bộ đệm định mệnh chồng lên cửa hàng 16B cuối cùng từ vòng lặp.

Lần duy nhất điều này không hoạt động là khi toàn bộ chuỗi dưới 16B: Ngay cả khi dst=src, đọc-sửa đổi-ghi không phải nguyên tử không giống như không chạm vào một số byte và có thể phá vỡ mã đa luồng.

Chúng tôi có một vòng vô hướng cho điều đó, và cũng để được srccăn chỉnh. Vì chúng tôi không biết kết thúc 0 sẽ ở đâu, tải không được phân bổ từ srccó thể đi qua trang tiếp theo và segfault. Nếu chúng ta cần bất kỳ byte nào trong một đoạn 16B được căn chỉnh, thì luôn luôn an toàn để tải toàn bộ đoạn 16B được căn chỉnh.

Nguồn đầy đủ: trong một github gist .

// FIXME: doesn't always copy the terminating 0.
// microbenchmarks are for this version of the code (with _mm_store in the loop, instead of storeu, for Merom).
size_t strtoupper_sse2(char *dst, const char *src_begin) {
    const char *src = src_begin;
    // scalar until the src pointer is aligned
    while ( (0xf & (uintptr_t)src) && *src ) {
        *(dst++) = ascii_toupper(*(src++));
    }

    if (!*src)
        return src - src_begin;

    // current position (p) is now 16B-aligned, and we're not at the end
    int zero_positions;
    do {
        __m128i sv = _mm_load_si128( (const __m128i*)src );
        // TODO: SSE4.2 PCMPISTRI or PCMPISTRM version to combine the lower-case and '\0' detection?

        __m128i nullcheck = _mm_cmpeq_epi8(_mm_setzero_si128(), sv);
        zero_positions = _mm_movemask_epi8(nullcheck);
        // TODO: unroll so the null-byte check takes less overhead
        if (zero_positions)
            break;

        __m128i upcased = upcase_si128(sv);   // doing this before the loop break lets gcc realize that the constants are still in registers for the unaligned cleanup version.  But it leads to more wasted insns in the early-out case

        _mm_storeu_si128((__m128i*)dst, upcased);
        //_mm_store_si128((__m128i*)dst, upcased);  // for testing on CPUs where storeu is slow
        src += 16;
        dst += 16;
    } while(1);

    // handle the last few bytes.  Options: scalar loop, masked store, or unaligned 16B.
    // rewriting some bytes beyond the end of the string would be easy,
    // but doing a non-atomic read-modify-write outside of the string is not safe.
    // Upcasing is idempotent, so unaligned potentially-overlapping is a good option.

    unsigned int cleanup_bytes = ffs(zero_positions) - 1;  // excluding the trailing null
    const char* last_byte = src + cleanup_bytes;  // points at the terminating '\0'

    // FIXME: copy the terminating 0 when we end at an aligned vector boundary
    // optionally special-case cleanup_bytes == 15: final aligned vector can be used.
    if (cleanup_bytes > 0) {
        if (last_byte - src_begin >= 16) {
            // if src==dest, this load overlaps with the last store:  store-forwarding stall.  Hopefully OOO execution hides it
            __m128i sv = _mm_loadu_si128( (const __m128i*)(last_byte-15) ); // includes the \0
            _mm_storeu_si128((__m128i*)(dst + cleanup_bytes - 15), upcase_si128(sv));
        } else {
            // whole string less than 16B
            // if this is common, try 64b or even 32b cleanup with movq / movd and upcase_si128
#if 1
            for (unsigned int i = 0 ; i <= cleanup_bytes ; ++i) {
                dst[i] = ascii_toupper(src[i]);
            }
#else
            // gcc stupidly auto-vectorizes this, resulting in huge code bloat, but no measurable slowdown because it never runs
            for (int i = cleanup_bytes - 1 ;  i >= 0 ; --i) {
                dst[i] = ascii_toupper(src[i]);
            }
#endif
        }
    }

    return last_byte - src_begin;
}

Lần cho 40 lần lặp, trên Core2 (Merom) 2.4GHz. gcc 5.2 -O3 -march=native. (Ubuntu 15.10). dst != src(vì vậy chúng tôi tạo một bản sao), nhưng chúng không trùng nhau (và không ở gần đó). Cả hai đều được căn chỉnh.

  • Chuỗi 15 char: đường cơ sở: 1.08s. autovec: 1,34s. hướng dẫn sử dụng: 1.29s
  • Chuỗi 16 char: đường cơ sở: 1.16s. autovec: 1,52s. hướng dẫn sử dụng: 0.335s
  • Chuỗi 31 char: hướng dẫn sử dụng: 0.479s
  • Chuỗi 127 char: đường cơ sở: 8,91s. autovec: 2,98s. hướng dẫn sử dụng: 0.925s
  • Chuỗi 128 char: đường cơ sở: 9.00s. autovec: 2.06s. hướng dẫn sử dụng: 0.931s
  • Chuỗi char 129: đường cơ sở: 9.04s. autovec: 2.07s. hướng dẫn sử dụng: 1,02 giây

(Trên thực tế, đã hết thời gian _mm_storetrong vòng lặp, _mm_storeubởi vì storeu chậm hơn Merom ngay cả khi địa chỉ được căn chỉnh. Nó vẫn ổn trên Nehalem và sau đó. Tôi cũng đã để lại mã như hiện tại, thay vì sửa lỗi không sao chép chấm dứt 0 trong một số trường hợp, vì tôi không muốn tính lại thời gian cho mọi thứ.)

Vì vậy, đối với các chuỗi ngắn dài hơn 16B, tốc độ này nhanh hơn đáng kể so với tự động véc tơ. Độ dài một chiều dài nhỏ hơn một vectơ không gây ra vấn đề. Chúng có thể là một vấn đề khi hoạt động tại chỗ, vì một gian hàng chuyển tiếp cửa hàng. (Nhưng lưu ý rằng vẫn ổn khi xử lý đầu ra của chúng ta, thay vì đầu vào ban đầu, bởi vì toupper là idempotent).

Có rất nhiều phạm vi để điều chỉnh điều này cho các trường hợp sử dụng khác nhau, tùy thuộc vào những gì mã xung quanh muốn và kiến ​​trúc vi mô đích. Bắt trình biên dịch để phát ra mã đẹp cho phần dọn dẹp là khó khăn. Sử dụng ffs(3)(biên dịch thành bsf hoặc tzcnt trên x86) có vẻ tốt, nhưng rõ ràng bit đó cần phải suy nghĩ lại vì tôi nhận thấy một lỗi sau khi viết hầu hết câu trả lời này (xem các bình luận FIXME).

Tăng tốc vector cho các chuỗi thậm chí nhỏ hơn có thể được lấy với movqhoặc movdtải / cửa hàng. Tùy chỉnh khi cần thiết cho trường hợp sử dụng của bạn.


UTF-8:

Chúng ta có thể phát hiện khi vectơ của chúng ta có bất kỳ byte nào với tập bit cao và trong trường hợp đó rơi trở lại vòng lặp nhận biết vô hướng utf-8 cho vectơ đó. Các dstđiểm có thể thúc đẩy bởi một số tiền khác nhau hơn so với srccon trỏ, nhưng một khi chúng ta lấy lại cho một liên kết srctrỏ, chúng tôi sẽ vẫn chỉ làm cửa hàng vector unaligned tới dst.

Đối với văn bản là UTF-8, nhưng chủ yếu bao gồm tập hợp con ASCII của UTF-8, điều này có thể tốt: hiệu suất cao trong trường hợp phổ biến với hành vi đúng trong mọi trường hợp. Tuy nhiên, khi có rất nhiều phi ASCII, điều đó có thể sẽ tồi tệ hơn là luôn ở trong vòng nhận thức vô hướng UTF-8 mọi lúc.

Làm cho tiếng Anh nhanh hơn với chi phí của các ngôn ngữ khác không phải là một quyết định trong tương lai nếu nhược điểm là đáng kể.


Nhận thức địa phương:

Trong miền địa phương Thổ Nhĩ Kỳ ( tr_TR), kết quả chính xác toupper('i')'İ'(U0130), không phải 'I'(ASCII đơn giản). Xem ý kiến ​​của Martin Bonner về một câu hỏi về tolower()việc chậm trên Windows.

Chúng ta cũng có thể kiểm tra danh sách ngoại lệ và dự phòng cho vô hướng ở đó, như đối với các ký tự đầu vào UTF8 nhiều byte.

Với sự phức tạp này, SSE4.2 PCMPISTRMhoặc một cái gì đó có thể có thể thực hiện nhiều kiểm tra của chúng tôi trong một lần.


20

Bạn có ký tự ASCII hoặc International trong chuỗi không?

Nếu đó là trường hợp thứ hai, "cấp trên" không đơn giản và nó phụ thuộc vào bảng chữ cái được sử dụng. Có bảng chữ cái lưỡng tính và đơn hướng. Chỉ bảng chữ cái lưỡng tính có các ký tự khác nhau cho chữ hoa và chữ thường. Ngoài ra, có các ký tự hỗn hợp, như chữ in hoa Latin 'DZ' (\ u01F1 'DZ') sử dụng trường hợp được gọi là trường hợp tiêu đề . Điều này có nghĩa là chỉ có ký tự đầu tiên (D) được thay đổi.

Tôi đề nghị bạn xem xét ICU và sự khác biệt giữa Ánh xạ trường hợp đơn giản và toàn bộ. Điều này có thể giúp:

http://userguide.icu-project.org/transforms/casemappings


7
Hoặc eszet của Đức (sp?), Thứ trông giống như chữ cái Hy Lạp beta, và có nghĩa là "ss". Không có ký tự tiếng Đức nào có nghĩa là "SS", tương đương chữ hoa. Từ tiếng Đức có nghĩa là "đường phố", khi được đặt ở trên, sẽ có một ký tự dài hơn.
David Thornley

6
Một trường hợp đặc biệt khác là sigma chữ Hy Lạp (), có hai phiên bản chữ thường, tùy thuộc vào việc nó có ở cuối từ (ς) hay không (σ). Và sau đó là các quy tắc cụ thể về ngôn ngữ, như tiếng Thổ Nhĩ Kỳ có bản đồ trường hợp II và İi.
dan04

1
"Uppercasing" được gọi là trường hợp gấp.
Columbo

20
string StringToUpper(string strToConvert)
{
   for (std::string::iterator p = strToConvert.begin(); strToConvert.end() != p; ++p)
       *p = toupper(*p);

   return p;
}

Hoặc là,

string StringToUpper(string strToConvert)
{
    std::transform(strToConvert.begin(), strToConvert.end(), strToConvert.begin(), ::toupper);

    return strToConvert;
}

4
nếu bạn không có quyền truy cập để tăng giải pháp thứ hai có lẽ là cách tốt nhất bạn có thể nhận được. các ngôi sao **sau các tham số trên giải pháp đầu tiên làm gì?
Sam Brinck

1
Tôi khá chắc chắn rằng đó **là một lỗi đánh máy còn sót lại khi cố gắng sử dụng phông chữ đậm trong cú pháp mã.
MasterHD

1
Mã này gọi hành vi không xác định khi toupperđược gọi với số âm.
Roland Illig

17

Các công việc sau đây cho tôi.

#include <algorithm>
void  toUpperCase(std::string& str)
{
    std::transform(str.begin(), str.end(), str.begin(), ::toupper);
}

int main()
{
   std::string str = "hello";
   toUpperCase(&str);
}

Lưu ý rằng std :: Transform được định nghĩa trong <Thuật toán>
edj

Đúng. # này bao gồm bắt buộc, #include <thuật toán>
Pabitra Dash

1
Mã này gọi hành vi không xác định khi toupperđược gọi với số âm.
Roland Illig

trùng lặp câu trả lời được đưa ra bởi người dùng648545 - -1
Piotr Dobrogost

@PiotrDobrogost Tôi không biết câu trả lời của người dùng648545. Tôi chưa sao chép điều đó. Khi tôi so sánh hai phương thức, chữ ký phương thức hoàn toàn khác nhau mặc dù cả hai hàm đều sử dụng biến đổi hàm thư viện.
Pabitra Dash

13

Sử dụng lambda.

std::string s("change my case");

auto to_upper = [] (char_t ch) { return std::use_facet<std::ctype<char_t>>(std::locale()).toupper(ch); };

std::transform(s.begin(), s.end(), s.begin(), to_upper);

2
Byron, đừng lo lắng về những bình luận khác. Bạn có thể trả lời các câu hỏi cũ bằng giải pháp mới (hiện đại) như bạn đã làm.
Kyberias

13

Nhanh hơn nếu bạn chỉ sử dụng các ký tự ASCII :

for(i=0;str[i]!=0;i++)
  if(str[i]<='z' && str[i]>='a')
    str[i]-=32;

Xin lưu ý rằng mã này chạy nhanh hơn nhưng chỉ hoạt động trên ASCII và không phải là giải pháp "trừu tượng".

Nếu bạn cần các giải pháp UNICODE hoặc các giải pháp thông thường và trừu tượng hơn, hãy tìm các câu trả lời khác và làm việc với các phương thức của chuỗi C ++.


1
Câu hỏi được gắn thẻ là C++, nhưng bạn đã viết một Ccâu trả lời ở đây. (Tôi không phải là một trong những kẻ hạ bệ.)
hkBattousai

6
Tôi đã viết một câu trả lời C VÀ một câu trả lời C ++ ở đây, beacuse C ++ được viết để tương thích hoàn toàn với các nguồn C, vì vậy mọi giải pháp C cũng là một giải pháp đúng C ++
Luca C.

Nhưng tốt hơn hết là đưa ra một câu trả lời tôn trọng cách C ++.
Dmitriy Yurchenko

Cách c ++ tiêu chuẩn sẽ là sử dụng std :: Transform với toupper. Đó là ít mã hơn và chắc chắn di động. Mã này dựa trên "thực tế" rằng hệ thống sẽ sử dụng ascii làm cơ chế mã hóa ký tự. Không chắc chắn rằng tất cả các hệ thống được dựa trên mã hóa này và do đó không chắc chắn rằng đây là di động.
AlexTheo

1
Tại sao bạn quyết định sử dụng mã ASCII thay vì các ký tự kèm theo '?
HolyBlackCat

11

Miễn là bạn ổn với chỉ ASCII và bạn có thể cung cấp một con trỏ hợp lệ cho bộ nhớ RW, có một lớp lót đơn giản và rất hiệu quả trong C:

void strtoupper(char* str)
{ 
    while (*str) *(str++) = toupper((unsigned char)*str);
}

Điều này đặc biệt tốt cho các chuỗi đơn giản như mã định danh ASCII mà bạn muốn chuẩn hóa thành cùng một trường hợp ký tự. Sau đó, bạn có thể sử dụng bộ đệm để xây dựng một thể hiện std: string.


Một lưu ý rằng câu trả lời này là dành cho chuỗi ac chứ không phải chuỗi std ::
EvilTeach

Điều này có một lỗ hổng bảo mật cố hữu rõ ràng. Tôi sẽ không làm điều này.
Byron

9
//works for ASCII -- no clear advantage over what is already posted...

std::string toupper(const std::string & s)
{
    std::string ret(s.size(), char());
    for(unsigned int i = 0; i < s.size(); ++i)
        ret[i] = (s[i] <= 'z' && s[i] >= 'a') ? s[i]-('a'-'A') : s[i];
    return ret;
}

s.size () thuộc loại std :: size_t, AFAIK rất có thể được bỏ dấu int tùy thuộc vào việc triển khai
odinthenerd

Tôi không nghĩ có bất kỳ triển khai hiện đại nào trong đó kết quả của std :: string :: size được ký kết. Cho rằng, cả về mặt ngữ nghĩa và thực tế, không có thứ gọi là kích thước âm, tôi sẽ sử dụng size_t ít nhất là một số nguyên không dấu 32 bit.
dùng1329482

Không có lý do để không viết for (size_t i = 0 .... Cũng không có lý do chính đáng để làm cho nó khó đọc như vậy. Điều này cũng sao chép chuỗi đầu tiên và sau đó lặp qua nó. Câu trả lời của @ Luke tốt hơn trong một số cách, ngoại trừ việc không tận dụng các 'a'hằng số ký tự.
Peter Cordes

9
#include <string>
#include <locale>

std::string str = "Hello World!";
auto & f = std::use_facet<std::ctype<char>>(std::locale());
f.toupper(str.data(), str.data() + str.size());

Điều này sẽ thực hiện tốt hơn tất cả các câu trả lời sử dụng chức năng toupper toàn cầu và có lẽ là những gì boost :: to_upper đang làm bên dưới.

Điều này là do :: toupper phải tra cứu miền địa phương - bởi vì nó có thể đã bị thay đổi bởi một luồng khác nhau - cho mỗi lần gọi, trong khi ở đây chỉ có lệnh gọi miền địa phương () có hình phạt này. Và tìm kiếm địa phương thường liên quan đến việc lấy một khóa.

Điều này cũng hoạt động với C ++ 98 sau khi bạn thay thế tự động, sử dụng str.data () không phải const mới () và thêm một khoảng trắng để phá vỡ việc đóng mẫu (">>" thành ">>" như thế này:

std::use_facet<std::ctype<char> > & f = 
    std::use_facet<std::ctype<char> >(std::locale());
f.toupper(const_cast<char *>(str.data()), str.data() + str.size());

7
typedef std::string::value_type char_t;

char_t up_char( char_t ch )
{
    return std::use_facet< std::ctype< char_t > >( std::locale() ).toupper( ch );
}

std::string toupper( const std::string &src )
{
    std::string result;
    std::transform( src.begin(), src.end(), std::back_inserter( result ), up_char );
    return result;
}

const std::string src  = "test test TEST";

std::cout << toupper( src );

sẽ không đề xuất back_inserter như bạn đã biết về độ dài; sử dụng kết quả chuỗi std :: (src.size ()); std :: Transform (src.begin (), src.end (), result.begin (), up_char);
Viktor Sehr

Tôi chắc chắn bạn biết điều này.
Viktor Sehr

@Viktor Sehr, @bayda: Tôi biết đây là 2 tuổi, nhưng tại sao không có được điều tốt nhất của cả hai thế giới. Sử dụng reserveback_inserter(làm cho chuỗi chỉ được sao chép một lần). inline std::string to_lower(const std::string &s) { std::string result; result.reserve(s.size()); std::transform(s.begin(), s.end(), std::back_inserter( result ), static_cast<int(*)(int)>(std::tolower)); return result; }
Evan Teran

4
std::string value;
for (std::string::iterator p = value.begin(); value.end() != p; ++p)
    *p = toupper(*p);

Mã này gọi hành vi không xác định khi toupperđược gọi với số âm.
Roland Illig

2

thử toupper()hàm ( #include <ctype.h>). nó chấp nhận các ký tự làm đối số, các chuỗi được tạo thành từ các ký tự, vì vậy bạn sẽ phải lặp lại qua từng ký tự riêng lẻ mà khi kết hợp lại bao gồm chuỗi


Gợi ý này gọi hành vi không xác định khi toupperđược gọi với số âm. Bạn nên đề cập đến các diễn viên cần thiết để unsigned char.
Roland Illig

2

Đây là mã mới nhất với C ++ 11

std::string cmd = "Hello World";
for_each(cmd.begin(), cmd.end(), [](char& in){ in = ::toupper(in); });

Mã này gọi hành vi không xác định khi toupperđược gọi với số âm.
Roland Illig

1

Sử dụng Boost.Text, sẽ hoạt động cho văn bản Unicode

boost::text::text t = "Hello World";
boost::text::text uppered;
boost::text::to_title(t, std::inserter(uppered, uppered.end()));
std::string newstr = uppered.extract();

1

Câu trả lời của @dirkgently rất truyền cảm, nhưng tôi muốn nhấn mạnh rằng do mối quan tâm như được hiển thị bên dưới,

Giống như tất cả các hàm khác từ, hành vi của std :: toupper không được xác định nếu giá trị của đối số không thể biểu thị dưới dạng char không dấu cũng như bằng EOF. Để sử dụng các chức năng này một cách an toàn với các ký tự đơn giản (hoặc ký tự đã ký), trước tiên, đối số nên được chuyển đổi thành char không dấu
Tham chiếu : std :: toupper

cách sử dụng đúng std::touppernên là:

#include <algorithm>
#include <cctype>
#include <iostream>
#include <iterator>
#include <string>

void ToUpper(std::string& input)
{
    std::for_each(std::begin(input), std::end(input), [](char& c) {
        c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
    });
}

int main()
{
    std::string s{ "Hello world!" };
    std::cout << s << std::endl;
    ::ToUpper(s);
    std::cout << s << std::endl;

    return 0;
}

Đầu ra:

Hello world!
HELLO WORLD!

0

không chắc chắn có một chức năng tích hợp. Thử cái này:

Bao gồm các thư viện ctype.h HOẶC cctype, cũng như stdlib.h như một phần của các chỉ thị tiền xử lý.

string StringToUpper(string strToConvert)
{//change each element of the string to upper case
   for(unsigned int i=0;i<strToConvert.length();i++)
   {
      strToConvert[i] = toupper(strToConvert[i]);
   }
   return strToConvert;//return the converted string
}

string StringToLower(string strToConvert)
{//change each element of the string to lower case
   for(unsigned int i=0;i<strToConvert.length();i++)
   {
      strToConvert[i] = tolower(strToConvert[i]);
   }
   return strToConvert;//return the converted string
}

.length () không thuộc loại 'unsign int'
malat

Mã này gọi hành vi không xác định khi toupperđược gọi với số âm.
Roland Illig

0

Giải pháp của tôi (xóa bit thứ 6 cho alpha):

#include <ctype.h>

inline void toupper(char* str)
{
    while (str[i]) {
        if (islower(str[i]))
            str[i] &= ~32; // Clear bit 6 as it is what differs (32) between Upper and Lowercases
        i++;
    }
}

Mã này gọi hành vi không xác định khi toupperđược gọi với số âm.
Roland Illig

Không ... Vui lòng kiểm tra xem bạn có đúng không trước khi bỏ phiếu. Islower sẽ chỉ hoạt động trên các giá trị không âm ...
Antonin GAVREL

-1

TẤT CẢ các giải pháp trên trang này khó hơn mức cần thiết.

Làm cái này

RegName = "SomE StRing That you wAnt ConvErTed";
NameLength = RegName.Size();
for (int forLoop = 0; forLoop < NameLength; ++forLoop)
{
     RegName[forLoop] = tolower(RegName[forLoop]);
}

RegNamelà bạn string. Nhận kích thước chuỗi của bạn không sử dụng string.size()làm người thử nghiệm thực tế của bạn, rất lộn xộn và có thể gây ra sự cố. sau đó. forvòng lặp cơ bản nhất .

nhớ kích thước chuỗi cũng trả về dấu phân cách, vì vậy hãy sử dụng <và không <= trong kiểm tra vòng lặp của bạn.

đầu ra sẽ là: một số chuỗi mà bạn muốn chuyển đổi


4
Tôi không thấy cách này đơn giản hơn giải pháp boost :: toupper. Bạn có thể xây dựng?
tr9sh

2
Đã có rất nhiều tolowervòng lặp đơn giản và hầu hết chúng sử dụng các tên biến vòng lặp tiêu chuẩn như thế i, không phải là lạ forLoop.
Peter Cordes

-1

Không sử dụng bất kỳ thư viện:

std::string YourClass::Uppercase(const std::string & Text)
{
    std::string UppperCaseString;
    UppperCaseString.reserve(Text.size());
    for (std::string::const_iterator it=Text.begin(); it<Text.end(); ++it)
    {
        UppperCaseString.push_back(((0x60 < *it) && (*it < 0x7B)) ? (*it - static_cast<char>(0x20)) : *it);
    }
    return UppperCaseString;
}

Đoạn mã trên chỉ hoạt động đối với các bảng mã tương thích ASCII. Không phải câu hỏi không phải câu trả lời của bạn đề cập đến hạn chế này. Một trong số họ nên.
Roland Illig

-1

Nếu bạn chỉ quan tâm đến các ký tự 8 bit (mà tất cả các câu trả lời khác ngoại trừ Milan Babuškov cũng giả sử), bạn có thể có tốc độ nhanh nhất bằng cách tạo bảng tra cứu tại thời điểm biên dịch bằng cách sử dụng siêu lập trình. Trên ideone.com, nó chạy nhanh hơn 7 lần so với chức năng thư viện và nhanh hơn 3 lần so với phiên bản viết tay ( http://ideone.com/sb1Rup ). Nó cũng có thể tùy chỉnh thông qua các đặc điểm không làm chậm.

template<int ...Is>
struct IntVector{
using Type = IntVector<Is...>;
};

template<typename T_Vector, int I_New>
struct PushFront;
template<int ...Is, int I_New>
struct PushFront<IntVector<Is...>,I_New> : IntVector<I_New,Is...>{};

template<int I_Size, typename T_Vector = IntVector<>>
struct Iota : Iota< I_Size-1, typename PushFront<T_Vector,I_Size-1>::Type> {};
template<typename T_Vector>
struct Iota<0,T_Vector> : T_Vector{};

template<char C_In>
struct ToUpperTraits {
    enum { value = (C_In >= 'a' && C_In <='z') ? C_In - ('a'-'A'):C_In };
};

template<typename T>
struct TableToUpper;
template<int ...Is>
struct TableToUpper<IntVector<Is...>>{
    static char at(const char in){
        static const char table[] = {ToUpperTraits<Is>::value...};
        return table[in];
    }
};

int tableToUpper(const char c){
    using Table = TableToUpper<typename Iota<256>::Type>;
    return Table::at(c);
}

với trường hợp sử dụng:

std::transform(in.begin(),in.end(),out.begin(),tableToUpper);

Để biết cách phân tích sâu (nhiều trang) về cách thức hoạt động cho phép tôi không biết xấu hổ cắm blog của mình: http://metaporky.blogspot.de/2014/07/part-4-generating-look-up-tables-at.html


-1
template<size_t size>
char* toupper(char (&dst)[size], const char* src) {
    // generate mapping table once
    static char maptable[256];
    static bool mapped;
    if (!mapped) {
        for (char c = 0; c < 256; c++) {
            if (c >= 'a' && c <= 'z')
                maptable[c] = c & 0xdf;
            else
                maptable[c] = c;
        }
        mapped = true;
    }

    // use mapping table to quickly transform text
    for (int i = 0; *src && i < size; i++) {
        dst[i] = maptable[*(src++)];
    }
    return dst;
}

-1

Hàm c ++ này luôn trả về chuỗi chữ hoa ...

#include <locale> 
#include <string>
using namespace std; 
string toUpper (string str){
    locale loc; 
    string n; 
    for (string::size_type i=0; i<str.length(); ++i)
        n += toupper(str[i], loc);
    return n;
}

-3

Tôi sử dụng giải pháp này. Tôi biết bạn không cần phải sửa đổi vùng dữ liệu đó .... nhưng tôi nghĩ rằng phần lớn là do lỗi tràn bộ đệm và ký tự null .... những thứ có vỏ trên không giống nhau.

void to_upper(const std::string str) {
    std::string::iterator it;
    int i;
    for ( i=0;i<str.size();++i ) {
        ((char *)(void *)str.data())[i]=toupper(((char *)str.data())[i]);
    }
}

I know you're not supposed to modify that data area- khu vực dữ liệu nào bạn không cần phải sửa đổi?
dùng93353

3
Đây là muộn, nhưng những gì trên trái đất? Dòng điên đó có thể được thay thế bằng str[i] = toupper(str[i]);hoàn toàn tốt (tốt, không hoàn toàn tốt, nhưng nó sửa hầu hết những điều sai).
chris
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.