Kiểu chuỗi trong C. Làm việc với chuỗi. Lớp chuỗi. Các nhà xây dựng lớp. Các hàm gán(), nối thêm(), chèn(), thay thế(), xóa(), find(), rfind(), so sánh(), c_str(). Ví dụ

Làm việc với chuỗi. Lớp chuỗi. Các nhà xây dựng lớp. Các hàm gán() , chắp thêm() , chèn() , thay thế() , eras() , find() , rfind() , so sánh() , c_str() . Ví dụ

1. Mục đích của lớp chuỗi trong chương trình C++ là gì?

Lớp chuỗi được thiết kế để hoạt động với các chuỗi char*, là các chuỗi kết thúc bằng null. Lớp chuỗi được giới thiệu như một giải pháp thay thế cho việc làm việc với chuỗi char*. Các dòng kết thúc bằng một ký tự ‘\0’ còn được gọi là dây C. Vì chuỗi là một lớp nên bạn có thể khai báo các đối tượng của lớp này.

2. Những mô-đun (thư viện) nào cần được kết nối để sử dụng các khả năng của lớp chuỗi trong MS Visual Studio C++?

Để sử dụng các khả năng của lớp chuỗi trong MS Visual Studio (C++), bạn cần đưa vào thư viện và không gian tên std.

#bao gồm sử dụng không gian tên std;
3. Một biến kiểu chuỗi được khai báo như thế nào? Ví dụ

Việc khai báo một biến kiểu chuỗi được thực hiện tương tự như khai báo một biến thông thường. Có thể có biến thể khai báo với việc khởi tạo đồng thời.

// gõ chuỗi chuỗi s1; // biến có tên s1 kiểu string string s2 = "Đây là biến chuỗi" ; // khai báo có khởi tạo // sử dụng biến kiểu chuỗi với toán tử gán s1 = s2; // s1 = "Đây là biến chuỗi" s2 = "Văn bản mới" ;
4. Ưu điểm và nhược điểm của việc sử dụng lớp string so với kiểu char* là gì?

Việc tạo ra kiểu chuỗi mới là do những thiếu sót khi làm việc với các chuỗi ký tự mà kiểu char* đã bộc lộ. So với kiểu char* thì kiểu string có những ưu điểm chính sau:

  • khả năng xử lý chuỗi bằng toán tử C++ tiêu chuẩn ( = , + , = = , <> và như thế.). Như bạn đã biết, khi sử dụng kiểu char*, ngay cả những thao tác đơn giản nhất với chuỗi cũng trông phức tạp và yêu cầu phải viết mã chương trình quá mức;
  • đảm bảo độ tin cậy (bảo mật) tốt hơn của mã chương trình. Ví dụ: khi sao chép chuỗi, loại chuỗi cung cấp các hành động thích hợp có thể xảy ra nếu chuỗi nguồn lớn hơn chuỗi đích;
  • cung cấp một chuỗi dưới dạng kiểu dữ liệu độc lập. Việc khai báo kiểu chuỗi dưới dạng chuỗi giống nhau đối với tất cả các biến trong chương trình, điều này đảm bảo tính nhất quán của dữ liệu.

Nhược điểm chính của kiểu string so với kiểu char* là tốc độ xử lý dữ liệu chậm hơn. Điều này là do trên thực tế, kiểu chuỗi là một lớp chứa. Và làm việc với một lớp đòi hỏi phải triển khai thêm mã chương trình, do đó, sẽ mất thêm thời gian.

5. Những toán tử nào có thể được sử dụng với các đối tượng của lớp chuỗi?

Lớp chuỗi thuận tiện vì nó cho phép bạn thao tác các chuỗi một cách thuận tiện bằng cách sử dụng các toán tử tiêu chuẩn (quá tải).

Các toán tử sau có thể được sử dụng với các đối tượng của lớp chuỗi

  • = - phân công
  • + – nối (nối chuỗi)
  • += – phép gán có phép nối
  • == – bình đẳng
  • != – bất bình đẳng
  • < - ít hơn
  • <= - ít hơn hoặc bằng
  • > - hơn
  • >= - nhiều hơn hoặc bằng
  • – lập chỉ mục

Ví dụ,điều này thể hiện việc sử dụng các tuyên bố trên

// kiểu chuỗi, các thao tác trên chuỗi chuỗi s1 = "s-1"; chuỗi s2 = "s-2"; chuỗi s3; bool b; // thao tác "=" (gán chuỗi) s3 = s1; // s3 = "s-1" // thao tác "+" - nối chuỗi s3 = s3 + s2; // s3 = "s-1s-2" // thao tác "+=" - gán với phép nối s3 = "s-3" ; s3 += "abc" ; // s3 = "s-3abc" // thao tác "==" - so sánh chuỗi b = s2==s1; // b = false b = s2=="s-2" ; // b = đúng // thao tác "!=" - so sánh chuỗi (không bằng) s1 = "s1"; s2="s2" ; b = s1 != s2; // b = đúng // thao tác "<" и ">" - so sánh chuỗi s1 = "abcd" ; s2 = "de"; b = s1 > s2; // b = sai b = s1< s2; // b = true // thao tác "<=" и ">=" - so sánh chuỗi (nhỏ hơn hoặc bằng, lớn hơn hoặc bằng) s1 = "abcd" ; s2 = "ab" ; b = s1 >= s2; // b = đúng b = s1<= s2; // b = false b = s2 >= "ab" ; // b = đúng // thao tác - lập chỉ mục ký tự c; s1 = "abcd" ; c = s1; // c = "c" c = s1; // c = "a"
6. Lớp chuỗi có chứa hàm tạo không?

Giống như bất kỳ lớp nào, lớp chuỗi có một số hàm tạo. Những cái chính là như sau:

Sợi dây(); chuỗi(const char * str); chuỗi(const chuỗi & str);

7. Ví dụ về khởi tạo bằng hàm tạo

Dưới đây là ví dụ về khởi tạo các biến kiểu chuỗi

Chuỗi s1("Xin chào!" ); chuỗi s2 = "Xin chào!" ; // khởi tạo - chuỗi hàm tạo(const char * str) char * ps = "Xin chào"; chuỗi s3(ps); // chuỗi khởi tạo s4(s3); // khởi tạo - chuỗi khởi tạo(const string & str) chuỗi s5; // khởi tạo - hàm tạo string()

8. Gán chuỗi. hàm gán(). Ví dụ

Để gán một chuỗi cho chuỗi khác, bạn có thể sử dụng một trong hai phương pháp:

  • sử dụng toán tử gán ‘=’ ;
  • sử dụng hàm gán() từ lớp chuỗi.

Hàm gán() có một số cách triển khai bị quá tải.

Tùy chọn đầu tiên là gọi hàm không có tham số

Chuỗi &gán(void);

Trong trường hợp này, có một phép gán đơn giản từ chuỗi này sang chuỗi khác.

Tùy chọn thứ hai cho phép bạn sao chép một số ký tự được chỉ định từ một chuỗi:

Chuỗi &gán(const string & s, size_type st, size_type num);

  • s – đối tượng mà chuỗi nguồn được lấy;
  • st - chỉ mục (vị trí) trong dòng bắt đầu sao chép số ký tự;
  • num – số ký tự được sao chép từ vị trí st;
  • size_type – kiểu dữ liệu thứ tự.

Biến thể thứ ba của hàm gán() sao chép số ký tự đầu tiên của chuỗi s sang hàm gọi:

Chuỗi & gán(const char * s, size_type num);

  • s – một chuỗi kết thúc bằng ký tự ‘\0’ ;
  • num là số ký tự được sao chép vào đối tượng gọi. Các ký tự số đầu tiên từ chuỗi s được sao chép.

Dưới đây là một ví dụ với các cách triển khai khác nhau của hàm gán().

Ví dụ.

// gán chuỗi, hàm gán() chuỗi s1 = "trang web"; chuỗi s2; chuỗi s3; char * ps = "trang web"; s3 = s1; // s3 = "trang web" s2.sign(s1); // s2 = "trang web" s2.sign(s1, 0, 4); // s2 = "tốt nhất" s2.sign(ps, 8); // s2 = "bestprog"
9. Kết hợp các chuỗi. Hàm chắp thêm(). Ví dụ

Hàmappend() được sử dụng để nối chuỗi. Bạn cũng có thể sử dụng thao tác để thêm hàng ‘+’ , Ví dụ:

Chuỗi s1; chuỗi s2; s1 = "abc" ; s2 = "def" ; s1 = s1 + s2; // s1 = "abcdef"

Tuy nhiên, hàmappend() vẫn phù hợp nếu bạn cần nối thêm một phần của chuỗi.

Hàm này có các tùy chọn triển khai sau:

Chuỗi &append(const string & s, size_type start); chuỗi &append(const char * s, size_type num);

Trong lần triển khai đầu tiên, hàm nhận được tham chiếu đến một đối tượng chuỗi S, được thêm vào đối tượng gọi. Trong lần triển khai thứ hai, hàm nhận một con trỏ tới một chuỗi kiểu const char *, kết thúc bằng ký tự '\0'.

Ví dụ. Minh họa hàmappend().

Chuỗi s1 = "abcdef" ; s2 = "1234567890" ; nối thêm(s2, 3, 4); // s1 = "abcdef4567" char * ps = "1234567890" ; s1 = "abcdef" ; s1.append(ps, 3); // s1 = "abcdef123"

10. Chèn ký tự vào chuỗi. chèn chức năng. Ví dụ

Để chèn một dòng vào vị trí nhất định của một dòng khác, bạn cần sử dụng hàm Insert(), hàm này có một số tùy chọn triển khai.

Phiên bản đầu tiên của hàm cho phép bạn chèn toàn bộ chuỗi s vào vị trí bắt đầu được chỉ định của dòng gọi (đối tượng gọi):

Chuỗi & chèn (bắt đầu size_type, chuỗi const &s);

Phiên bản thứ hai của hàm cho phép bạn chèn một phần (tham số insStart , num ) của chuỗi s tại vị trí bắt đầu được chỉ định của dòng gọi:

Chuỗi & chèn (bắt đầu size_type, chuỗi const &s, size_type insStart, size_type num);

Trong các chức năng trên:

  • s – một chuỗi được chèn vào dòng gọi;
  • bắt đầu – vị trí trong dòng gọi mà chuỗi s được chèn vào;
  • insStart – vị trí trong chuỗi s nơi việc chèn xảy ra;
  • num – số ký tự trong chuỗi s được chèn từ vị trí insStart.
chuỗi s1 = "abcdef"; chuỗi s2 = "1234567890"; s1.insert(3, s2); // s1 = "abc"+"1234567890"+"def"="abc1234567890def" s2.insert(2, s1, 1, 3); // s2 = "12bcd34567890"
11. Thay thế các ký tự trong chuỗi. hàm thay thế(). Ví dụ

Hàm thay thế () thay thế các ký tự trong chuỗi gọi. Hàm này có các tùy chọn triển khai sau:

Chuỗi &thay thế(bắt đầu size_type, size_type num, chuỗi const &s); chuỗi & thay thế(size_type start, size_type num, const string &s, size_type replStart, size_type replNum);

Trong lần triển khai đầu tiên, chuỗi gọi được thay thế bằng chuỗi s. Có thể chỉ định vị trí (bắt đầu) và số ký tự (num) trong dòng gọi cần thay thế bằng chuỗi s.

Phiên bản thứ hai của hàm thay thế() khác với phiên bản đầu tiên ở chỗ nó chỉ cho phép một phần của chuỗi s được thay thế bằng chuỗi gọi. Trong trường hợp này, hai tham số bổ sung được đưa ra: vị trí của replStart và số ký tự trong chuỗi s, tạo thành chuỗi con thay thế chuỗi gọi.

Ví dụ. Trình diễn hàm thay thế().

Chuỗi s1 = "abcdef" ; chuỗi s2 = "1234567890"; s2.replace(2, 4, s1); // s2 = "12abcdef7890" s2 = "1234567890" ; s2.replace(3, 2, s1); // s2 = "123abcdef67890" s2 = "1234567890" ; s2.replace(5, 1, s1); // s2 = "12345abcdef7890" // thay thế ký tự, hàm thay thế() chuỗi s1 = "abcdef"; chuỗi s2 = "1234567890"; s2.replace(2, 4, s1); // s2 = "12abcdef7890" s2 = "1234567890" ; s2.replace(3, 2, s1); // s2 = "123abcdef67890" s2 = "1234567890" ; s2.replace(5, 1, s1); // s2 = "12345abcdef7890" s2 = "1234567890" ; s2.replace(5, 1, s1, 2, 3); // s2 = "12345cde7890" s2 = "1234567890" ; s2.replace(4, 2, s1, 0, 4); // s2 = "1234abcd7890"

12. Xóa một số ký tự nhất định khỏi chuỗi. hàm xóa(). Ví dụ

Để xóa các ký tự khỏi chuỗi gọi, hãy sử dụng hàm erase():

Chuỗi & xóa(size_type index=0, size_type num = npos);

  • chỉ mục – chỉ mục (vị trí) bắt đầu từ đó bạn muốn xóa ký tự trong dòng gọi;
  • num – số ký tự bị loại bỏ.

Ví dụ.

Chuỗi s = "01234567890"; s.erase(3, 5); // s = "012890" s = "01234567890" ; s.erase(); // s = ""

13. Tìm kiếm một ký tự trong chuỗi. Hàm Find() và rfind(). Ví dụ

Trong lớp chuỗi, việc tìm kiếm một chuỗi trong chuỗi con có thể được thực hiện theo hai cách, khác nhau về hướng tìm kiếm:

  • bằng cách quét chuỗi từ đầu đến cuối bằng hàm find();
  • bằng cách quét chuỗi từ đầu đến cuối bằng hàm rfind().

Nguyên mẫu hàm find() trông như thế này:

Size_type find(const string &s, size_type start = 0) const ;

  • s là chuỗi con được tìm kiếm trong chuỗi gọi hàm này. Hàm tìm kiếm sự xuất hiện đầu tiên của chuỗi s. Nếu tìm thấy chuỗi con s trong chuỗi gọi hàm này thì vị trí của lần xuất hiện đầu tiên sẽ được trả về. Ngược lại -1 được trả về;

Nguyên mẫu hàm rfind() trông như thế này:

Size_type rfind(const string &s, size_type start = npos) const ;

  • s là chuỗi con được tìm kiếm trong chuỗi gọi. Việc tìm kiếm chuỗi con trong chuỗi được thực hiện từ đầu đến cuối. Nếu tìm thấy chuỗi con s trong chuỗi gọi thì hàm sẽ trả về vị trí xuất hiện đầu tiên. Ngược lại, hàm trả về -1;
  • npos – vị trí ký tự cuối cùng của dòng gọi;
  • bắt đầu – vị trí mà việc tìm kiếm được thực hiện.

Ví dụ 1. Một đoạn mã thể hiện kết quả của hàm find()

// gõ chuỗi, hàm find() chuỗi s1 = "01234567890"; chuỗi s2 = "345"; chuỗi s3 = "abcd"; int vị trí; pos = s1.find(s2); // pos = 3 pos = s1.find(s2, 1); // pos = 3 pos = s1.find("jklmn" , 0); // pos = -1 pos = s1.find(s3); // pos = -1 pos = s2.find(s1); // vị trí = -1

Ví dụ 2. Trình diễn hàm rfind().

// kiểu chuỗi, hàm find() và rfind() chuỗi s1 = "01234567890"; chuỗi s2 = "345"; chuỗi s3 = "abcd"; chuỗi s4 = "abcd---abcd" ; int vị trí; pos = s1.rfind(s2); // pos = 3 pos = s1.rfind(s2, 12); // pos = 3 pos = s1.rfind(s2, 3); // pos = 3 pos = s1.rfind(s2, 2); // pos = -1 pos = s2.rfind(s1); // pos = -1 pos = s1.rfind(s3, 0); // vị trí = -1 // sự khác biệt giữa hàm find() và rfind() pos = s4.rfind(s3); // pos = 7 pos = s4.find(s3); // vị trí = 0
14. So sánh các phần của dây. hàm so sánh(). Ví dụ

Vì kiểu chuỗi là một lớp nên bạn có thể sử dụng thao tác để so sánh hai chuỗi với nhau ‘= =’ . Nếu hai chuỗi giống nhau thì kết quả so sánh sẽ là true. Ngược lại, kết quả so sánh sẽ sai.

Nhưng nếu bạn cần so sánh một phần của chuỗi này với chuỗi khác, thì hàm so sánh() sẽ được cung cấp cho việc này.

nguyên mẫu hàm so sánh():

int so sánh(size_type start, size_type num, const string &s) const ;
  • s – chuỗi được so sánh với chuỗi gọi;
  • bắt đầu – vị trí (chỉ mục) trong chuỗi s từ đó việc xem các ký tự chuỗi để so sánh bắt đầu;
  • num là số ký tự trong chuỗi s được so sánh với chuỗi gọi.

Chức năng này hoạt động như sau. Nếu chuỗi gọi nhỏ hơn chuỗi s thì hàm trả về -1 (giá trị âm). Nếu chuỗi gọi lớn hơn chuỗi s, hàm trả về 1 (giá trị dương). Nếu hai chuỗi bằng nhau thì hàm trả về 0.

Ví dụ. Minh họa hàm so sánh():

// kiểu chuỗi, hàm so sánh() chuỗi s1 = "012345"; chuỗi s2 = "0123456789"; int độ phân giải; res = s1.compare(s2); // res = -1 res = s1.compare("33333" ); // res = -1 res = s1.compare("012345" ); // res = 0 res = s1.compare("345" ); // res = -1 res = s1.compare(0, 5, s2); // res = -1 res = s2.compare(0, 5, s1); // res = -1 res = s1.compare(0, 5, "012345" ); // res = -1 res = s2.compare(s1); // res = 1 res = s2.compare("456" ); // res = -1 res = s2.compare("000000" ); // độ phân giải = 1
15. Nhận chuỗi có ký tự cuối dòng ‘\0’ (char * ). Hàm c_str() . Ví dụ

Để có được một chuỗi kết thúc bằng ký tự ‘\0’ Hàm c_str() được sử dụng.

Nguyên mẫu chức năng:

const char * c_str() const ;

Hàm được khai báo bằng công cụ sửa đổi const. Điều này có nghĩa là hàm không thể sửa đổi đối tượng gọi (chuỗi).

ví dụ 1. Chuyển đổi loại chuỗi thành const char * .

// gõ chuỗi, hàm c_str() chuỗi s = "abcdef"; const char * ps; ps = sc_str(); // ps = "abcdef"

Ví dụ 2.

Dưới đây là minh họa cách chuyển đổi một chuỗi từ chuỗi sang loại System::String để hiển thị nó trong điều khiển Nhãn

Trong bài học này chúng ta sẽ thảo luận về các chuỗi kiểu C; bạn có thể đã thấy những chuỗi này trên trang web của chúng tôi hoặc trong bất kỳ sách giáo khoa nào khác. Trên thực tế, chuỗi C chỉ là các mảng ký tự nhưng có đặc điểm riêng nên chúng ta luôn biết đâu là cuối dòng. Trong bài viết này, chúng ta sẽ xem xét một số hàm để làm việc với chuỗi, ví dụ: bạn - sao chép, nối, lấy độ dài của chuỗi.

Dây là gì?

Lưu ý rằng cùng với các chuỗi kiểu C, về cơ bản là các mảng đơn giản, cũng có các chuỗi ký tự, chẳng hạn như "chữ" này. Trong thực tế, cả chuỗi và chữ chỉ đơn giản là các tập hợp ký tự nằm cạnh nhau trong bộ nhớ máy tính. Nhưng vẫn có sự khác biệt giữa mảng và chữ: chữ không thể thay đổi được và chuỗi thì có thể.

Bất kỳ hàm nào lấy chuỗi kiểu C cũng có thể lấy chuỗi ký tự làm tham số. Ngoài ra còn có một số thực thể trong C có thể trông giống như các chuỗi trong khi thực tế thì không phải vậy. Bây giờ tôi đang nói về các ký tự, chúng được đặt trong dấu ngoặc đơn, đây là một ví dụ - "a", như bạn có thể thấy, đây không phải là một chuỗi. Tại một vị trí cụ thể, một ký tự có thể được gán cho một chuỗi nhưng các ký tự không thể được xử lý dưới dạng chuỗi. Nếu bạn còn nhớ, mảng hoạt động giống như con trỏ, vì vậy nếu bạn truyền một ký tự đơn vào một chuỗi thì sẽ bị coi là lỗi.

Từ những điều trên, bạn hẳn đã nhận ra rằng chuỗi là mảng ký tự và chuỗi ký tự là các từ được bao quanh bởi dấu ngoặc kép. Đây là một ví dụ khác về nghĩa đen:

"Đây là một chuỗi tĩnh"

Bạn đã quên tính đặc hiệu của chuỗi, được đề cập cao hơn một chút? Vì vậy, chuỗi C phải luôn kết thúc bằng ký tự null, nghĩa đen là “\0”. Vì vậy, để khai báo một chuỗi gồm 49 chữ cái, bạn cần dành thêm một ô cho ký tự null:

Char myString;

Như bạn có thể thấy từ ví dụ, độ dài của mảng là 50 ký tự, 49 trong số đó sẽ là một dòng và một, ký tự cuối cùng sẽ là ký tự null. Điều quan trọng cần nhớ là phải luôn có ký tự rỗng ở cuối dòng C, giống như có dấu chấm ở cuối mỗi câu. Mặc dù ký tự null không được hiển thị khi chuỗi được xuất ra nhưng nó vẫn chiếm dung lượng trong bộ nhớ. Vì vậy, về mặt kỹ thuật, trong một mảng gồm 50 phần tử, bạn chỉ có thể lưu trữ 49 chữ cái vì cần có ký tự cuối cùng để kết thúc chuỗi. Ngoài ra, con trỏ cũng có thể được sử dụng như một chuỗi. Nếu bạn đọc bài viết về , bạn có thể làm điều gì đó như thế này:

Char *myString; // con trỏ kiểu char myString = malloc(sizeof(*myString) * 64); // cấp phát bộ nhớ

Trong ví dụ này, chúng tôi đã phân bổ 64 vị trí bộ nhớ cho mảng myString. Để giải phóng bộ nhớ, hãy sử dụng hàm free().

Miễn phí (myString);

Sử dụng chuỗi

Chuỗi rất hữu ích khi bạn cần thực hiện nhiều thao tác khác nhau trên thông tin văn bản. Ví dụ: nếu bạn muốn người dùng nhập tên vào chương trình, bạn sẽ sử dụng một chuỗi. Việc sử dụng hàm scanf() để nhập chuỗi có tác dụng nhưng có thể dẫn đến tràn bộ đệm. Rốt cuộc, chuỗi đầu vào có thể lớn hơn kích thước của chuỗi đệm. Có một số cách để giải quyết vấn đề này, nhưng cách đơn giản nhất là sử dụng , được khai báo trong tệp tiêu đề .

Khi đọc đầu vào từ người dùng, nó sẽ đọc tất cả các ký tự ngoại trừ ký tự cuối cùng. Sau đó, dấu kết thúc số 0 sẽ được đặt ở cuối dòng đọc. Hàm fgets() sẽ đọc các ký tự cho đến khi người dùng nhấn Enter. Hãy xem một ví dụ về việc sử dụng fgets():

#bao gồm int main() ( char myString; // chuỗi dài printf("Nhập một chuỗi dài: "); fgets(myString, 100, stdin); // đọc chuỗi từ luồng đầu vào printf("Bạn đã nhập chuỗi sau: %s", myString); getchar(); )

Tham số đầu tiên của fgets() là một chuỗi, tham số thứ hai là kích thước của chuỗi và tham số thứ ba là một con trỏ tới luồng dữ liệu đầu vào.

Kết quả của chương trình:

<ВВОД>...

Như bạn có thể thấy, từ đầu ra của chương trình, một ký tự dòng mới - "\n" - được nhập vào dòng đầu vào. Điều này xảy ra vì fgets() đếm nút Enter được nhấn vào chuỗi myString và hoàn thành công việc của nó. Điều này có nghĩa là bạn có thể cần phải xóa ký tự dòng mới theo cách thủ công. Một cách để làm điều này là liệt kê từng ký tự. Hãy sửa đổi chương trình và xóa ký tự dòng mới:

#bao gồm int main() ( char myString; // chuỗi dài printf("Nhập một chuỗi dài: "); fgets(myString, 100, stdin); // đọc một chuỗi từ luồng đầu vào int i; for (i = 0; Tôi< 100; i++) { if (myString[i] == "\n") { myString[i] = "\0"; break; } } printf("Вы ввели следующую строку: %s", myString); getchar(); }

Xin lưu ý rằng nếu chuỗi đầu vào chứa ít hơn 100 ký tự thì ký tự dòng mới cũng sẽ được đưa vào chuỗi. Do đó, chúng ta có thể loại bỏ ký tự này bằng cách sử dụng vũ lực đơn giản. Chúng tôi đã thêm một vòng lặp vào chương trình để lặp qua các ký tự của chuỗi, dòng 12-19. Và khi chúng ta gặp một ký tự dòng mới, chúng ta thay thế nó bằng một ký tự null, dòng 16. Kết quả của chương trình:

Nhập một dòng dài: Định mệnh để lại dấu ấn Bạn đã nhập dòng sau: Định mệnh để lại dấu ấn Để đóng cửa sổ này, bấm vào<ВВОД>...

Đó là tất cả cho bây giờ. Trong bài viết tiếp theo tôi sẽ cho bạn biết về các hàm đặc biệt để làm việc với chuỗi.

P.S.: Tất cả chúng ta đều thích xem các bản ghi video khác nhau, nhưng đôi khi không phải lúc nào cũng có thể phát một số định dạng tệp video. Vì vậy, bạn có thể giải quyết vấn đề này bằng chương trình - Xilisoft Converter Ultimate. Bạn có thể dễ dàng nhanh chóng chuyển đổi video từ định dạng này sang định dạng khác. Ngoài ra, chương trình này còn có thể chuyển đổi các tập tin âm thanh và hình ảnh động.

34

--- Hướng dẫn C# --- Chuỗi

Từ quan điểm lập trình thông thường, chuỗi kiểu dữ liệu chuỗi là một trong những điều quan trọng nhất trong C#. Kiểu này xác định và hỗ trợ các chuỗi ký tự. Trong một số ngôn ngữ lập trình khác, chuỗi là một mảng các ký tự. Và trong C#, chuỗi là đối tượng. Vì vậy, kiểu chuỗi là kiểu tham chiếu.

Xây dựng chuỗi

Cách đơn giản nhất để xây dựng một chuỗi ký tự là sử dụng một chuỗi ký tự. Ví dụ: dòng mã sau đây gán biến tham chiếu chuỗi str một tham chiếu đến một chuỗi ký tự:

String str = "Chuỗi mẫu";

Trong trường hợp này, biến str được khởi tạo với chuỗi ký tự "Chuỗi ví dụ". Một đối tượng kiểu chuỗi cũng có thể được tạo từ một mảng kiểu char. Ví dụ:

Char chararray = ("e", "x", "a", "m", "p", "l", "e"); chuỗi str = chuỗi mới (chararray);

Khi một đối tượng chuỗi được tạo, nó có thể được sử dụng ở bất cứ đâu bạn cần một chuỗi văn bản được đặt trong dấu ngoặc kép.

Sự kiên trì của chuỗi

Thật kỳ lạ, nội dung của một đối tượng thuộc loại chuỗi không thể thay đổi được. Điều này có nghĩa là một khi chuỗi ký tự đã được tạo thì không thể thay đổi được. Nhưng hạn chế này góp phần triển khai chuỗi ký tự hiệu quả hơn. Vì vậy, nhược điểm tưởng chừng như hiển nhiên này thực chất lại trở thành một lợi thế. Do đó, nếu một chuỗi được yêu cầu như một biến thể của một chuỗi hiện có thì với mục đích này, một chuỗi mới sẽ được tạo có chứa tất cả các thay đổi cần thiết. Và vì các đối tượng chuỗi không được sử dụng sẽ tự động được thu thập dưới dạng rác nên bạn thậm chí không phải lo lắng về số phận của các chuỗi không cần thiết.

Tuy nhiên, cần nhấn mạnh rằng các tham chiếu biến đến chuỗi (nghĩa là các đối tượng thuộc loại chuỗi) có thể thay đổi và do đó chúng có thể tham chiếu đến một đối tượng khác. Nhưng nội dung của đối tượng chuỗi không thay đổi sau khi nó được tạo.

Hãy xem một ví dụ:

Tĩnh void addNewString() ( string s = "Đây là nét vẽ của tôi"; s = "Đây là nét vẽ mới"; )

Hãy biên dịch ứng dụng và tải tập hợp kết quả vào tiện ích ildasm.exe. Hình minh họa mã CIL sẽ được tạo cho phương thức void addNewString():

Lưu ý rằng có rất nhiều lệnh gọi đến opcode ldstr (tải chuỗi). Mã hoạt động CIL ldstr này thực hiện tải một đối tượng chuỗi mới vào vùng được quản lý. Kết quả là đối tượng trước đó chứa giá trị "Đây là nét vẽ của tôi" cuối cùng sẽ bị thu gom rác.

Làm việc với chuỗi

Trong lớp Hệ thống.String cung cấp một tập hợp các phương thức để xác định độ dài của dữ liệu ký tự, tìm kiếm chuỗi con trong chuỗi hiện tại, chuyển đổi các ký tự từ chữ hoa sang chữ thường và ngược lại, v.v. Tiếp theo chúng ta sẽ xem xét lớp này chi tiết hơn.

Thuộc tính trường, bộ chỉ mục và lớp chuỗi

Lớp String định nghĩa một trường duy nhất:

Chuỗi chỉ đọc tĩnh công khai Trống;

Trường trống biểu thị một chuỗi trống, tức là một chuỗi không chứa bất kỳ ký tự nào. Điều này khác với tham chiếu Chuỗi trống, được tạo đơn giản cho một đối tượng không tồn tại.

Ngoài ra, lớp String định nghĩa một bộ chỉ mục chỉ đọc duy nhất:

Công khai char này ( get; )

Bộ chỉ mục này cho phép bạn lấy một ký tự ở một chỉ mục được chỉ định. Việc lập chỉ mục các chuỗi, giống như mảng, bắt đầu từ số 0. Các đối tượng chuỗi là cố định và không thay đổi, do đó, việc lớp String hỗ trợ trình chỉ mục chỉ đọc là điều hợp lý.

Cuối cùng, lớp String định nghĩa một thuộc tính chỉ đọc duy nhất:

Độ dài int công khai ( get; )

Thuộc tính Độ dài trả về số ký tự trong chuỗi. Ví dụ dưới đây cho thấy việc sử dụng bộ chỉ mục và thuộc tính Độ dài:

Sử dụng hệ thống; class Ví dụ ( static void Main() ( string str = "Simple string"; // Lấy độ dài của chuỗi và ký tự thứ 6 trong dòng bằng bộ chỉ mục Console.WriteLine("Độ dài của chuỗi là (0), thứ 6 ký tự là "(1)"" , str.Length, str); ) )

Toán tử lớp chuỗi

Lớp String nạp chồng hai toán tử sau: == và !=. Toán tử == được sử dụng để kiểm tra sự bằng nhau của hai chuỗi ký tự. Khi toán tử == được áp dụng cho các tham chiếu đối tượng, nó thường kiểm tra xem cả hai tham chiếu có được tạo cho cùng một đối tượng hay không. Và khi toán tử == được áp dụng cho các tham chiếu đến các đối tượng thuộc loại Chuỗi, bản thân nội dung của các chuỗi đó sẽ được so sánh về sự bằng nhau. Điều tương tự cũng áp dụng cho toán tử != . Khi nó được áp dụng cho các tham chiếu đến các đối tượng thuộc loại Chuỗi, bản thân nội dung của các chuỗi đó sẽ được so sánh về sự bất bình đẳng. Tuy nhiên, các toán tử quan hệ khác, bao gồm =, so sánh các tham chiếu với các đối tượng thuộc loại Chuỗi giống như cách chúng so sánh các tham chiếu với các đối tượng thuộc các loại khác. Và để kiểm tra xem một chuỗi có lớn hơn chuỗi khác hay không, bạn nên gọi phương thức Compare() được định nghĩa trong lớp String.

Như sẽ rõ, nhiều kiểu so sánh chuỗi ký tự dựa vào thông tin văn hóa. Nhưng điều này không áp dụng cho các toán tử == và !=. Suy cho cùng, họ chỉ đơn giản là so sánh giá trị thứ tự của các ký tự trong chuỗi. (Nói cách khác, chúng so sánh các giá trị nhị phân của các ký tự chưa được sửa đổi bởi các chuẩn mực văn hóa, tức là các tiêu chuẩn địa phương.) Do đó, các toán tử này thực hiện so sánh chuỗi theo cách không phân biệt chữ hoa chữ thường và không phân biệt văn hóa.

Các phương thức lớp chuỗi

Bảng sau liệt kê một số phương thức thú vị nhất trong lớp này, được nhóm theo mục đích:

Các phương thức làm việc với chuỗi
Phương pháp Cấu trúc và tình trạng quá tải Mục đích
So sánh chuỗi
so sánh() public static int So sánh(chuỗi strA, chuỗi strB)

So sánh int tĩnh công khai (chuỗi strA, chuỗi strB, bool ignCase)

So sánh int tĩnh công khai(chuỗi strA, chuỗi strB, StringComparison so sánhType)

So sánh int tĩnh công khai (chuỗi strA, chuỗi strB, bool ignCase, văn hóa CultureInfo)

Phương thức tĩnh so sánh chuỗi strA với chuỗi strB. Trả về giá trị dương nếu strA lớn hơn strB; âm nếu strA nhỏ hơn strB; và 0 nếu chuỗi strA và strB bằng nhau. So sánh được thực hiện dựa trên đăng ký và văn hóa.

Nếu ignCase được đánh giá là đúng thì việc so sánh không tính đến sự khác biệt giữa chữ hoa và chữ thường. Mặt khác, những khác biệt này sẽ được tính đến.

Tham số so sánh Loại chỉ định cách so sánh các chuỗi cụ thể. Lớp CultureInfo được định nghĩa trong không gian tên System.Globalization.

public static int So sánh(chuỗi strA, int indexA, chuỗi strB, int indexB, int length)

So sánh int tĩnh công khai (chuỗi strA, int indexA, chuỗi strB, int indexB, độ dài int, bool ignCase)

So sánh int tĩnh công khai (chuỗi strA, int indexA, chuỗi strB, int indexB, độ dài int, StringComparison so sánhType)

So sánh int tĩnh công khai (chuỗi strA, int indexA, chuỗi strB, int indexB, độ dài int, bool ignCase, văn hóa CultureInfo)

So sánh các phần của chuỗi strA và strB. Việc so sánh bắt đầu với các phần tử chuỗi strA và strB và bao gồm số ký tự được chỉ định bởi tham số độ dài. Phương thức trả về giá trị dương nếu một phần của chuỗi strA lớn hơn một phần của chuỗi strB; giá trị âm nếu một phần của chuỗi strA nhỏ hơn một phần của chuỗi strB; và bằng 0 nếu các phần của chuỗi strA và strB được so sánh bằng nhau. So sánh được thực hiện dựa trên đăng ký và văn hóa.

So sánhOrdinal() public static int CompareOrdinal(chuỗi strA, chuỗi strB)

Công khai tĩnh int CompareOrdinal(chuỗi strA, int indexA, chuỗi strB, int indexB, int count)

Thực hiện tương tự như phương thức Compare() nhưng không tính đến cài đặt cục bộ

So với() int công khai CompareTo(giá trị đối tượng)

So sánh chuỗi gọi với chuỗi biểu diễn của đối tượng giá trị. Trả về giá trị dương nếu chuỗi gọi lớn hơn giá trị; âm nếu chuỗi gọi nhỏ hơn giá trị; và 0 nếu các chuỗi so sánh bằng nhau

int công khai CompareTo(chuỗi strB)

So sánh chuỗi gọi với chuỗi strB

Bằng() ghi đè công khai bool Equals(object obj)

Trả về giá trị boolean true nếu chuỗi gọi chứa cùng chuỗi ký tự như chuỗi biểu diễn của obj. Thực hiện so sánh thứ tự phân biệt chữ hoa chữ thường nhưng không nhạy cảm về mặt văn hóa

public bool Bằng(giá trị chuỗi)

Bool công khai Bằng(giá trị chuỗi, Loại so sánh StringComparison)

Trả về giá trị boolean true nếu chuỗi gọi chứa cùng chuỗi ký tự với giá trị chuỗi. Một so sánh thứ tự được thực hiện phân biệt chữ hoa chữ thường nhưng không nhạy cảm về mặt văn hóa. Tham số so sánh chỉ định cách so sánh các chuỗi cụ thể

public static bool Bằng(chuỗi a, chuỗi b)

Bool tĩnh công khai Bằng(chuỗi a, chuỗi b, StringComparison so sánhType)

Trả về giá trị boolean true nếu chuỗi a chứa cùng chuỗi ký tự với chuỗi b. Một so sánh thứ tự được thực hiện phân biệt chữ hoa chữ thường nhưng không nhạy cảm về mặt văn hóa. Tham số so sánh chỉ định cách so sánh các chuỗi cụ thể

Nối (kết nối) các chuỗi
Concat() chuỗi tĩnh công khai Concat(chuỗi str0, chuỗi str1);

chuỗi tĩnh công khai Concat(giá trị chuỗi params);

Kết hợp các phiên bản chuỗi riêng lẻ thành một chuỗi duy nhất (nối)
Tìm kiếm trong một chuỗi
Chứa() bool công khai Chứa(giá trị chuỗi) Một phương thức cho phép bạn xác định xem một chuỗi có chứa một chuỗi con (giá trị) nhất định hay không
Bắt đầu với() bool công khai StartsWith(string value)

Bool công khai StartsWith(string value, StringComparison so sánhType)

Trả về giá trị boolean true nếu chuỗi gọi bắt đầu bằng giá trị chuỗi con. Nếu không, giá trị boolean sai sẽ được trả về. Tham số so sánh chỉ định cách cụ thể để thực hiện tìm kiếm

Kết thúcVới() bool công khai EndsWith(giá trị chuỗi)

Bool công khai EndsWith(giá trị chuỗi, Loại so sánh StringComparison)

Trả về giá trị boolean true nếu chuỗi gọi kết thúc bằng giá trị chuỗi con. Ngược lại, trả về giá trị boolean sai. Tham số so sánh chỉ định phương pháp tìm kiếm cụ thể

Chỉ số() public int IndexOf(giá trị char)

Public int IndexOf(giá trị chuỗi)

Tìm lần xuất hiện đầu tiên của một chuỗi con hoặc ký tự nhất định trong một chuỗi. Nếu không tìm thấy ký tự hoặc chuỗi con được tìm kiếm thì giá trị -1 sẽ được trả về.

public int IndexOf(char value, int startIndex)

Public int IndexOf(giá trị chuỗi, int startIndex)

Public int IndexOf(giá trị char, int startIndex, int count)

Public int IndexOf(giá trị chuỗi, int startIndex, int count)

Trả về chỉ mục lần xuất hiện đầu tiên của ký tự hoặc giá trị chuỗi con trong chuỗi gọi. Việc tìm kiếm bắt đầu tại phần tử được chỉ định bởi startIndex và mở rộng số phần tử được chỉ định theo số lượng (nếu được chỉ định). Phương thức trả về -1 nếu không tìm thấy ký tự hoặc chuỗi con được tìm kiếm

LastIndexOf() Các phiên bản được nạp chồng tương tự như phương thức IndexOf()

Tương tự như IndexOf, nhưng tìm sự xuất hiện cuối cùng của một ký tự hoặc chuỗi con, không phải lần đầu tiên

IndexOfAny() int công khai IndexOfAny(char AnyOf)

Public int IndexOfAny(char AnyOf, int startIndex)

Public int IndexOfAny(char AnyOf, int startIndex, int count)

Trả về chỉ mục xuất hiện đầu tiên của bất kỳ ký tự nào từ mảng AnyOf được tìm thấy trong chuỗi gọi. Việc tìm kiếm bắt đầu tại phần tử được chỉ định bởi startIndex và mở rộng số phần tử được chỉ định theo số lượng (nếu được chỉ định). Phương thức trả về -1 nếu không có ký tự nào trong mảng AnyOf được khớp. Việc tìm kiếm được thực hiện theo thứ tự

Chỉ mục cuối cùng của bất kỳ Các phiên bản bị quá tải tương tự như phương thức IndexOfAny()

Trả về chỉ mục xuất hiện cuối cùng của bất kỳ ký tự nào từ mảng AnyOf được tìm thấy trong chuỗi gọi

Tách và nối chuỗi
Tách ra Tách chuỗi công khai(dấu phân cách ký tự params)

Tách chuỗi công khai(dấu phân cách ký tự params, số int)

Một phương thức trả về một mảng chuỗi có các chuỗi con có trong phiên bản này bên trong, các chuỗi này được phân tách với nhau bằng các phần tử từ mảng char hoặc chuỗi đã chỉ định.

Dạng đầu tiên của phương thức Split() chia chuỗi gọi thành các phần thành phần của nó. Kết quả là một mảng chứa các chuỗi con thu được từ chuỗi gọi. Các ký tự phân cách các chuỗi con này được chuyển vào mảng phân cách. Nếu mảng phân cách trống hoặc tham chiếu đến chuỗi trống thì khoảng trắng sẽ được sử dụng làm dấu phân tách chuỗi con. Và ở dạng thứ hai của phương thức này, số lượng chuỗi con được xác định bởi tham số count sẽ được trả về.

Tách chuỗi công khai (dấu phân cách ký tự params, tùy chọn StringSplitOptions)

Tách chuỗi công khai (dấu tách chuỗi, tùy chọn StringSplitOptions)

Phân tách chuỗi công khai (dấu phân cách ký tự params, số int, tùy chọn StringSplitOptions)

Tách chuỗi công khai (dấu tách chuỗi, số int, tùy chọn StringSplitOptions)

Trong hai dạng đầu tiên của phương thức Split(), chuỗi gọi được chia thành nhiều phần và trả về một mảng chứa các chuỗi con thu được từ chuỗi gọi. Các ký tự phân tách các chuỗi con này được truyền vào mảng phân cách. Nếu mảng phân cách trống thì một khoảng trắng sẽ được sử dụng làm dấu phân cách. Và ở dạng thứ ba và thứ tư của phương thức này, số lượng hàng bị giới hạn bởi tham số count sẽ được trả về.

Nhưng ở mọi dạng, tham số tùy chọn chỉ định một cách cụ thể để xử lý các dòng trống được tạo khi hai dấu phân cách liền kề. Bảng liệt kê StringSplitOptions chỉ xác định hai giá trị: Không cóLoại bỏ các mục trống. Nếu tùy chọn là Không có thì các chuỗi trống sẽ được đưa vào kết quả phân tách cuối cùng của chuỗi gốc. Và nếu tham số tùy chọn được đặt thành RemoveEmptyEntries thì các dòng trống sẽ bị loại khỏi kết quả cuối cùng của việc tách chuỗi gốc.

Tham gia() chuỗi tĩnh công khai Tham gia (dấu phân tách chuỗi, giá trị chuỗi)

Chuỗi tĩnh công khai Tham gia(dấu tách chuỗi, giá trị chuỗi, int startIndex, int count)

Xây dựng một chuỗi mới bằng cách kết hợp nội dung của một mảng các chuỗi.

Dạng đầu tiên của phương thức Join() trả về một chuỗi bao gồm các chuỗi con được nối với nhau được truyền trong mảng giá trị. Dạng thứ hai cũng trả về một chuỗi bao gồm các chuỗi con được truyền trong mảng giá trị, nhưng chúng được nối với nhau theo một số lượng nhất định, bắt đầu bằng phần tử mảng giá trị. Trong cả hai biểu mẫu, mỗi dòng tiếp theo được phân tách khỏi dòng trước bằng một dòng phân cách được chỉ định bởi tham số phân tách.

Làm đầy và cắt đường
Cắt() chuỗi công khai Trim()

Chuỗi công khai Trim(params char TrimChars)

Một phương pháp cho phép bạn loại bỏ tất cả các lần xuất hiện của một bộ ký tự cụ thể ở đầu và cuối dòng hiện tại.

Dạng đầu tiên của phương thức Trim() loại bỏ các khoảng trắng ở đầu và cuối khỏi chuỗi gọi. Và dạng thứ hai của phương thức này loại bỏ các lần xuất hiện ở đầu và cuối của chuỗi ký tự gọi khỏi mảng TrimChars. Cả hai biểu mẫu đều trả về chuỗi kết quả.

PadLeft() chuỗi công khai PadLeft(int TotalWidth)

Chuỗi công khai PadLeft(int TotalWidth, char đệmChar)

Cho phép bạn đệm một chuỗi bằng các ký tự ở bên trái.

Dạng đầu tiên của phương thức PadLeft() giới thiệu các khoảng trắng ở phía bên trái của chuỗi gọi sao cho tổng chiều dài của nó bằng giá trị của tham số TotalWidth. Và ở dạng thứ hai của phương thức này, các ký tự được biểu thị bằng tham số đệmChar được nhập vào bên trái của chuỗi gọi sao cho tổng chiều dài của nó bằng giá trị của tham số TotalWidth. Cả hai biểu mẫu đều trả về chuỗi kết quả. Nếu giá trị của tham số TotalWidth nhỏ hơn độ dài của chuỗi gọi thì một bản sao của chuỗi gọi không thay đổi sẽ được trả về.

PadRight() Tương tự như PadLeft()

Cho phép bạn nối thêm một chuỗi có ký tự ở bên phải.

Chèn, xóa và thay thế hàng
Chèn() chuỗi công khai Chèn(int startIndex, giá trị chuỗi)

Được sử dụng để chèn một hàng vào một hàng khác, trong đó giá trị biểu thị hàng được chèn vào hàng gọi tại startIndex. Phương thức trả về chuỗi kết quả.

Di dời() chuỗi công khai Xóa(int startIndex)

Xóa chuỗi công khai (int startIndex, int count)

Được sử dụng để loại bỏ một phần của chuỗi. Ở dạng đầu tiên của phương thức Remove(), việc xóa bắt đầu tại vị trí được chỉ định bởi startIndex và tiếp tục cho đến cuối dòng. Và ở dạng thứ hai của phương thức này, số ký tự được xác định bởi tham số count sẽ bị xóa khỏi chuỗi, bắt đầu từ vị trí được chỉ định bởi chỉ số startIndex.

Thay thế() chuỗi công khai Thay thế(char oldChar, char newChar)

Chuỗi công khai Thay thế(chuỗi oldValue, chuỗi newValue)

Được sử dụng để thay thế một phần của chuỗi. Trong dạng đầu tiên của phương thức Reply(), tất cả các lần xuất hiện của ký tự oldChar trong chuỗi gọi đều được thay thế bằng ký tự newChar. Và ở dạng thứ hai của phương thức này, tất cả các lần xuất hiện của chuỗi oldValue trong dòng gọi đều được thay thế bằng chuỗi newValue.

Trường hợp thay đổi
ToUpper() chuỗi công khai ToUpper()

Viết hoa tất cả các chữ cái trong chuỗi gọi.

ToLower() chuỗi công khai ToLower()

Viết thường tất cả các chữ cái trong chuỗi gọi.

Lấy một chuỗi con từ một chuỗi
Chuỗi con() chuỗi công khai Chuỗi con(int startIndex)

Chuỗi con công khai(int startIndex, int length)

Ở dạng đầu tiên của phương thức Substring(), chuỗi con được truy xuất bắt đầu từ vị trí được chỉ định bởi tham số startIndex và kết thúc ở cuối chuỗi gọi. Và ở dạng thứ hai của phương thức này, một chuỗi con bao gồm số ký tự được xác định bởi tham số độ dài sẽ được trích xuất, bắt đầu từ vị trí được chỉ định bởi tham số startIndex.

Chương trình ví dụ sau đây sử dụng một số phương pháp trên:

Sử dụng hệ thống; sử dụng System.Collections.Generic; sử dụng System.Linq; sử dụng System.Text; namespace ConsoleApplication1 ( class Program ( static void Main(string args) ( // So sánh hai dòng đầu tiên string s1 = "đây là một chuỗi"; string s2 = "đây là văn bản và đây là một chuỗi"; if (String. CompareOrdinal(s1, s2) != 0) Console.WriteLine("Chuỗi s1 và s2 không bằng nhau"); if (String.Compare(s1, 0, s2, 13, 10, true) == 0) Console.WriteLine ("Tuy nhiên, chúng chứa cùng một văn bản"); // Nối các chuỗi Console.WriteLine(String.Concat("\n" + "One, two ","ba, bốn")); // Tìm kiếm trong một chuỗi / / Lần xuất hiện đầu tiên của chuỗi con if (s2. IndexOf("this") != -1) Console.WriteLine("Từ \"this\" tìm thấy trong dòng, nó "+ "nằm ở: (0) vị trí" , s2.IndexOf("this")); / / Lần xuất hiện cuối cùng của chuỗi con if (s2.LastIndexOf("this") != -1) Console.WriteLine("Lần xuất hiện cuối cùng của từ \"this\" là " + "at (0) location", s2.LastIndexOf("this" )); // Tìm kiếm từ một mảng ký tự char myCh = ("ы","x","t"); if (s2.IndexOfAny (myCh) != -1) Console.WriteLine("Một trong các ký tự của mảng ch "+ "được tìm thấy trong dòng hiện tại tại vị trí (0)", s2.IndexOfAny(myCh)); // Xác định xem dòng có bắt đầu bằng chuỗi con đã cho hay không if (s2.StartsWith("this is text") == true) Console.WriteLine("Đã tìm thấy chuỗi con!"); // Xác định xem chuỗi có chứa chuỗi con hay không // sử dụng ví dụ xác định chuỗi hệ điều hành của người dùng myOS = Environment.OSVersion.ToString(); if (myOS.Contains("NT 5.1")) Console.WriteLine("Hệ điều hành của bạn là Windows XP"); else if (myOS.Contains("NT 6.1")) Console.WriteLine("Hệ điều hành của bạn là Windows 7"); Console.ReadLine(); ) ) )

Một chút về so sánh chuỗi trong C#

Có lẽ thao tác phổ biến nhất trong tất cả các chuỗi ký tự là so sánh chuỗi này với chuỗi khác. Trước khi xem xét bất kỳ phương pháp so sánh chuỗi nào, cần nhấn mạnh điều sau: So sánh chuỗi có thể được thực hiện trong .NET Framework theo hai cách chính:

    Đầu tiên, sự so sánh có thể phản ánh những phong tục và chuẩn mực của một môi trường văn hóa cụ thể, thường là những bối cảnh văn hóa phát huy tác dụng khi chương trình được thực hiện. Đây là hành vi tiêu chuẩn đối với một số phương pháp, nhưng không phải tất cả các phương pháp so sánh.

    Và thứ hai, việc so sánh có thể được thực hiện bất kể cài đặt văn hóa chỉ bằng giá trị thứ tự của các ký tự tạo nên chuỗi. Nói chung, so sánh phi văn hóa của các chuỗi sử dụng thứ tự từ điển (và các đặc điểm ngôn ngữ) để xác định xem một chuỗi lớn hơn, nhỏ hơn hoặc bằng chuỗi khác. Trong so sánh thứ tự, các chuỗi được sắp xếp đơn giản dựa trên giá trị chưa sửa đổi của mỗi ký tự.

Do sự khác biệt trong cách so sánh chuỗi văn hóa và so sánh thứ tự cũng như hậu quả của mỗi so sánh đó, chúng tôi thực sự khuyên bạn nên làm theo các biện pháp thực hành tốt nhất hiện được Microsoft cung cấp. Rốt cuộc, việc chọn sai phương pháp so sánh chuỗi có thể dẫn đến hoạt động không chính xác của chương trình khi chương trình được vận hành trong môi trường khác với môi trường mà nó được phát triển.

Việc lựa chọn cách so sánh các chuỗi ký tự là một quyết định rất quan trọng. Theo nguyên tắc chung và không có ngoại lệ, bạn nên chọn so sánh các chuỗi theo cách nhạy cảm về mặt văn hóa nếu việc này được thực hiện nhằm mục đích hiển thị kết quả cho người dùng (ví dụ: để hiển thị một chuỗi các chuỗi được sắp xếp theo thứ tự từ điển). Nhưng nếu các chuỗi chứa thông tin cố định không nhằm mục đích sửa đổi vì sự khác biệt về văn hóa, chẳng hạn như tên tệp, từ khóa, địa chỉ trang web hoặc giá trị bảo mật thì bạn nên chọn so sánh chuỗi thứ tự. Tất nhiên, đặc điểm của ứng dụng cụ thể đang được phát triển sẽ quyết định việc lựa chọn phương pháp thích hợp để so sánh các chuỗi ký tự.

Lớp String cung cấp nhiều phương thức so sánh chuỗi khác nhau, được liệt kê trong bảng trên. Phổ biến nhất trong số đó là phương thức Compare(). Nó cho phép hai chuỗi được so sánh toàn bộ hoặc một phần, phân biệt chữ hoa chữ thường hoặc không phân biệt chữ hoa chữ thường, theo cách được chỉ định bởi tham số loại So sánh chuỗi, cũng như thông tin văn hóa được cung cấp bởi tham số loại Thông tin văn hóa.

Những phần quá tải của phương thức Compare() không bao gồm tham số kiểu StringComparison sẽ thực hiện so sánh các chuỗi ký tự phân biệt chữ hoa chữ thường và văn hóa. Và trong các biến thể quá tải không chứa tham số loại CultureInfo, thông tin về môi trường văn hóa được xác định bởi môi trường thời gian chạy hiện tại.

Kiểu StringComparison là một kiểu liệt kê xác định các giá trị được hiển thị trong bảng bên dưới. Bằng cách sử dụng các giá trị này, bạn có thể tạo các so sánh chuỗi phù hợp với nhu cầu của ứng dụng cụ thể của mình. Do đó, việc thêm tham số kiểu StringComparison sẽ mở rộng khả năng của phương thức Compare() và các phương thức so sánh khác như Equals(). Điều này cũng giúp có thể chỉ ra rõ ràng cách thức so sánh các chuỗi.

Do sự khác biệt giữa so sánh chuỗi nhạy cảm về mặt văn hóa và so sánh thứ tự, điều quan trọng là phải chính xác nhất có thể trong vấn đề này.

Các giá trị được xác định trong bảng liệt kê StringComparison
Nghĩa Sự miêu tả
Hiện TạiVăn Hóa So sánh chuỗi được thực hiện bằng cách sử dụng cài đặt môi trường văn hóa hiện tại
Văn hóa hiện tạiBỏ qua trường hợp So sánh chuỗi được thực hiện bằng cách sử dụng cài đặt văn hóa hiện tại, nhưng không phân biệt chữ hoa chữ thường
Bất biếnVăn hóa So sánh chuỗi được thực hiện bằng cách sử dụng các chuỗi bất biến, tức là dữ liệu phổ quát về môi trường văn hóa
Bất biếnVăn hóaBỏ quaTrường hợp So sánh chuỗi được thực hiện bằng cách sử dụng các chuỗi bất biến, tức là dữ liệu văn hóa phổ quát và không phân biệt chữ hoa chữ thường
thứ tự So sánh chuỗi được thực hiện bằng cách sử dụng giá trị thứ tự của các ký tự trong chuỗi. Trong trường hợp này, trật tự từ điển có thể bị phá vỡ và các quy ước được áp dụng trong một môi trường văn hóa cụ thể sẽ bị bỏ qua.
Thứ tựBỏ quaTrường hợp So sánh chuỗi được thực hiện bằng cách sử dụng giá trị thứ tự của các ký tự trong chuỗi nhưng không phân biệt chữ hoa chữ thường

Trong mọi trường hợp, phương thức Compare() trả về giá trị âm nếu chuỗi đầu tiên được so sánh nhỏ hơn chuỗi thứ hai; dương nếu chuỗi đầu tiên được so sánh lớn hơn chuỗi thứ hai; và cuối cùng là 0 nếu cả hai chuỗi được so sánh đều bằng nhau. Mặc dù phương thức Compare() trả về 0 nếu các chuỗi được so sánh bằng nhau, nhưng nhìn chung tốt hơn nên sử dụng phương thức Equals() hoặc toán tử == để xác định xem các chuỗi ký tự có bằng nhau hay không.

Thực tế là phương thức Compare() xác định sự bằng nhau của các chuỗi được so sánh dựa trên thứ tự sắp xếp của chúng. Do đó, nếu so sánh văn hóa được thực hiện giữa các chuỗi thì cả hai chuỗi có thể giống nhau về thứ tự sắp xếp nhưng không bằng nhau về bản chất. Theo mặc định, đẳng thức chuỗi được xác định trong phương thức Equals(), dựa trên giá trị thứ tự của các ký tự và không tính đến môi trường văn hóa. Do đó, theo mặc định, cả hai chuỗi được so sánh trong phương thức này để đạt được sự bằng nhau tuyệt đối, theo từng ký tự, tương tự như cách thực hiện trong toán tử ==.

Bất chấp tính linh hoạt tuyệt vời của phương thức Compare(), để so sánh thứ tự đơn giản của các chuỗi ký tự, việc sử dụng phương thức CompareOrdinal() sẽ dễ dàng hơn. Cuối cùng, hãy nhớ rằng phương thức CompareTo() chỉ thực hiện so sánh chuỗi nhạy cảm về mặt văn hóa.

Chương trình sau đây trình bày cách sử dụng các phương thức Compare(), Equals(), CompareOrdinal() và các toán tử == và != để so sánh các chuỗi ký tự. Lưu ý rằng hai ví dụ so sánh đầu tiên thể hiện rõ ràng sự khác biệt giữa so sánh chuỗi nhạy cảm về mặt văn hóa và so sánh thứ tự trong môi trường nói tiếng Anh:

Sử dụng hệ thống; Ví dụ về lớp ( static void Main() ( string str1 = "alpha"; string str2 = "Alpha"; string str3 = "Beta"; string str4 = "alpha"; string str5 = "alpha, beta"; int result; / / Đầu tiên, chứng minh sự khác biệt giữa so sánh chuỗi nhạy cảm về văn hóa // và so sánh thứ tự result = String.Compare(str1, str2, StringComparison.CurrentCulture); Console.Write("So sánh chuỗi nhạy cảm về văn hóa: "); if (kết quả 0 ) Console.WriteLine(str1 + " lớn hơn " + str2); khác Console.WriteLine(str1 + " bằng " + str2); result = String.Compare(str1, str2, StringComparison.Ordinal); Console.Write(" Các dòng so sánh thứ tự: "); if (kết quả 0) Console.WriteLine(str1 + " lớn hơn " + str2); else Console.WriteLine(str1 + " bằng " + str4); // Sử dụng kết quả của phương thức CompareOrdinal() = String.CompareOrdinal( str1, str2); Console.Write("So sánh các chuỗi bằng phương thức CompareOrdinal():\n"); if (kết quả 0) Console.WriteLine(str1 + " lớn hơn " + str2); else Console .WriteLine(str1 + " bằng " + str4); Console.WriteLine(); // Xác định sự bằng nhau của chuỗi bằng toán tử == // Đây là sự so sánh thứ tự của các chuỗi ký tự if (str1 == str4) Console.WriteLine(str1 + " == " + str4); // Xác định bất đẳng thức dòng bằng toán tử != if(str1 != str3) Console.WriteLine(str1 + " != " + str3); if(str1 != str2) Console.WriteLine(str1 + " != " + str2); Console.WriteLine(); // Thực hiện so sánh thứ tự các chuỗi không phân biệt chữ hoa chữ thường // bằng phương thức Equals() if(String.Equals(str1, str2, StringComparison.OrdinalIgnoreCase)) Console.WriteLine("So sánh các chuỗi bằng phương thức Equals() với " + "Tham số OrdinalIgnoreCase: \n" + str1 + " bằng " + str2); Console.WriteLine(); // So sánh các phần của chuỗi if(String.Compare(str2, 0, str5, 0, 3, StringComparison.CurrentCulture) > 0) ( Console.WriteLine("So sánh các chuỗi có tính đến môi trường văn hóa hiện tại:" + "\n3 ký tự đầu tiên của chuỗi " + str2 + " nhiều hơn 3 ký tự đầu tiên của dòng " + str5); ) ) )

Chạy chương trình này sẽ tạo ra kết quả sau:

Habra, xin chào!

Cách đây không lâu, một sự việc khá thú vị đã xảy ra với tôi, trong đó có liên quan đến một giáo viên của một trường đại học khoa học máy tính.

Cuộc trò chuyện về lập trình Linux dần dần tiến triển đến chỗ người này cho rằng sự phức tạp của việc lập trình hệ thống thực sự đã bị phóng đại quá mức. Rằng ngôn ngữ C đơn giản như một trận đấu, trên thực tế, giống như nhân Linux (theo cách nói của ông).

Tôi mang theo một chiếc máy tính xách tay chạy Linux, trong đó có một bộ tiện ích dành cho quý ông để phát triển bằng ngôn ngữ C (gcc, vim, make, valgrind, gdb). Tôi không nhớ lúc đó chúng tôi đã đặt ra mục tiêu gì cho mình, nhưng sau vài phút, đối thủ của tôi đã thấy mình ở chiếc máy tính xách tay này, hoàn toàn sẵn sàng giải quyết vấn đề.

Và theo đúng nghĩa đen, ngay từ những dòng đầu tiên, anh ấy đã mắc một sai lầm nghiêm trọng khi cấp phát bộ nhớ cho… một dòng.

Char *str = (char *)malloc(sizeof(char) * strlen(buffer));
bộ đệm - một biến ngăn xếp để ghi dữ liệu từ bàn phím vào.

Tôi nghĩ chắc chắn sẽ có người hỏi: “Sao chuyện này có thể có vấn đề gì cơ chứ?”
Hãy tin tôi, nó có thể.

Và chính xác là gì - đọc về con mèo.

Một lý thuyết nhỏ - một loại LikBez.

Nếu bạn biết, hãy cuộn đến tiêu đề tiếp theo.

Chuỗi trong C là một mảng các ký tự, luôn kết thúc bằng "\0" - ký tự cuối dòng. Các chuỗi trên ngăn xếp (tĩnh) được khai báo như sau:

Char str[n] = ( 0 );
n là kích thước của mảng ký tự, bằng độ dài của chuỗi.

Bài tập ( 0 ) - "zeroing" chuỗi (tùy chọn, bạn có thể khai báo nó mà không cần nó). Kết quả giống như khi chạy các hàm memset(str, 0, sizeof(str)) và bzero(str, sizeof(str)). Nó được sử dụng để ngăn rác bị bỏ lại trong các biến chưa được khởi tạo.

Bạn cũng có thể khởi tạo ngay một chuỗi trên ngăn xếp:

Char buf = "văn bản đệm mặc định\n";
Ngoài ra, một chuỗi có thể được khai báo như một con trỏ và bộ nhớ có thể được cấp phát cho chuỗi đó trên heap:

Char *str = malloc(size);
kích thước - số byte mà chúng tôi phân bổ cho chuỗi. Các chuỗi như vậy được gọi là động (do kích thước yêu cầu được tính toán linh hoạt + kích thước bộ nhớ được phân bổ có thể tăng lên bất kỳ lúc nào bằng cách sử dụng hàm realloc()).

Trong trường hợp biến ngăn xếp, tôi sử dụng ký hiệu n để xác định kích thước của mảng; trong trường hợp biến heap, tôi sử dụng ký hiệu kích thước. Và điều này phản ánh hoàn hảo bản chất thực sự của sự khác biệt giữa khai báo trên ngăn xếp và khai báo cấp phát bộ nhớ trên heap, bởi vì n thường được sử dụng khi nói về số lượng phần tử. Và kích thước lại là một câu chuyện hoàn toàn khác...

Valgrind sẽ giúp chúng tôi

Trong bài viết trước tôi cũng đã đề cập đến nó. Valgrind ( , two - một cách thực hiện nhỏ) là một chương trình rất hữu ích giúp lập trình viên theo dõi rò rỉ bộ nhớ và lỗi ngữ cảnh - chính xác là những thứ thường xuất hiện nhất khi làm việc với chuỗi.

Hãy xem một danh sách ngắn triển khai một cái gì đó tương tự như chương trình tôi đã đề cập và chạy nó thông qua valgrind:

#bao gồm #bao gồm #bao gồm #define HELLO_STRING "Xin chào, Habr!\n" void main() ( char *str = malloc(sizeof(char) * strlen(HELLO_STRING)); strcpy(str, HELLO_STRING); printf("->\t%s" , str); tự do(str); )
Và trên thực tế, kết quả của chương trình:

$ gcc main.c $ ./a.out -> Xin chào, Habr!
Chưa có gì bất thường cả. Bây giờ hãy chạy chương trình này với valgrind!

$ valgrind --tool=memcheck ./a.out ==3892== Memcheck, trình phát hiện lỗi bộ nhớ ==3892== Bản quyền (C) 2002-2015 và GNU GPL"d, bởi Julian Seward và cộng sự. == 3892== Sử dụng Valgrind-3.12.0 và LibVEX; chạy lại với -h để biết thông tin bản quyền ==3892== Lệnh: ./a.out ==3892== ==3892== Ghi kích thước 2 không hợp lệ ==3892= = tại 0x4005B4: main (in /home/indever/prg/C/public/a.out) ==3892== Địa chỉ 0x520004c là 12 byte bên trong một khối có kích thước 13 alloc"d ==3892== tại 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== ==3892== Kích thước đọc 1 không hợp lệ == 3892== tại 0x4C30BC4: strlen (vg_replace_strmem.c:454) ==3892== bởi 0x4E89AD0: vfprintf (trong /usr/lib64/libc-2.24.so) ==3892== bởi 0x4E90718: printf (trong /usr/ lib64/libc-2.24.so) ==3892== by 0x4005CF: main (in /home/indever/prg/C/public/a.out) ==3892== Địa chỉ 0x520004d là 0 byte sau một khối có kích thước 13 alloc"d ==3892== tại 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== -> Xin chào, Habr! ==3892== ==3892== TÓM TẮT HEAP: ==3892== đang sử dụng khi thoát: 0 byte trong 0 khối ==3892== tổng mức sử dụng heap: 2 cấp phát, 2 giải phóng, 1.037 byte được phân bổ ==3892= = ==3892== Tất cả các khối heap đã được giải phóng -- không có rò rỉ nào ==3892== ==3892== Để biết số lượng lỗi được phát hiện và ngăn chặn, hãy chạy lại với: -v ==3892== TÓM TẮT LỖI: 3 lỗi từ 2 bối cảnh (bị chặn: 0 từ 0)
==3892== Tất cả các khối heap đã được giải phóng - không thể xảy ra rò rỉ- không có rò rỉ nào cả, đó là tin tốt. Nhưng đáng để bạn hạ tầm mắt xuống một chút (mặc dù tôi muốn lưu ý rằng đây chỉ là phần tóm tắt, thông tin chính ở chỗ khác một chút):

==3892== TÓM TẮT LỖI: 3 lỗi từ 2 ngữ cảnh (bị chặn: 0 từ 0)
3 sai lầm. Trong 2 bối cảnh. Trong một chương trình đơn giản như vậy. Làm sao!?

Vâng, rất đơn giản. Toàn bộ “điều buồn cười” là hàm strlen không tính đến ký tự cuối dòng - “\0”. Ngay cả khi bạn chỉ định rõ ràng nó trong dòng đến (#define HELLO_STRING “Xin chào, Habr!\n\0”), nó sẽ bị bỏ qua.

Ngay phía trên kết quả thực hiện chương trình, dòng -> Xin chào, Habr! có một báo cáo chi tiết về những gì và ở đâu mà valgrind quý giá của chúng tôi không thích. Tôi khuyên bạn nên tự mình nhìn vào những dòng này và rút ra kết luận của riêng mình.

Trên thực tế, phiên bản chính xác của chương trình sẽ trông như thế này:

#bao gồm #bao gồm #bao gồm #define HELLO_STRING "Xin chào, Habr!\n" void main() ( char *str = malloc(sizeof(char) * (strlen(HELLO_STRING) + 1)); strcpy(str, HELLO_STRING); printf("->\ t%s", str); miễn phí(str); )
Hãy chạy nó qua valgrind:

$ valgrind --tool=memcheck ./a.out -> Xin chào, Habr! ==3435== ==3435== TÓM TẮT HEAP: ==3435== đang sử dụng khi thoát: 0 byte trong 0 khối ==3435== tổng mức sử dụng heap: 2 cấp phát, 2 giải phóng, 1.038 byte được phân bổ ==3435= = ==3435== Tất cả các khối heap đã được giải phóng -- không có rò rỉ nào ==3435== ==3435== Để biết số lượng lỗi được phát hiện và ngăn chặn, hãy chạy lại với: -v ==3435== TÓM TẮT LỖI: 0 lỗi từ 0 bối cảnh (bị chặn: 0 từ 0)
Tuyệt vời. Không có lỗi, +1 byte bộ nhớ được phân bổ đã giúp giải quyết vấn đề.

Điều thú vị là trong hầu hết các trường hợp, cả chương trình thứ nhất và chương trình thứ hai sẽ hoạt động giống nhau, nhưng nếu bộ nhớ được phân bổ cho dòng mà ký tự kết thúc không vừa sẽ không bằng 0, thì hàm printf() khi xuất ra dòng đó , cũng sẽ xuất ra tất cả rác sau dòng này - mọi thứ sẽ được in cho đến khi ký tự kết thúc dòng cản trở printf().

Tuy nhiên, bạn biết đấy, (strlen(str) + 1) là một giải pháp như vậy. Chúng tôi gặp phải 2 vấn đề:

  1. Điều gì sẽ xảy ra nếu chúng ta cần phân bổ bộ nhớ cho một chuỗi được tạo bằng cách sử dụng s(n)printf(..) chẳng hạn? Chúng tôi không ủng hộ các lập luận.
  2. Vẻ bề ngoài. Dòng khai báo biến trông thật khủng khiếp. Một số người cũng có thể đính kèm (char *) vào malloc, như thể họ viết dưới dấu cộng. Trong một chương trình mà bạn thường xuyên cần xử lý chuỗi, việc tìm một giải pháp tinh tế hơn là điều hợp lý.
Hãy đưa ra một giải pháp có thể làm hài lòng cả chúng ta và valgrind.

snprintf()

int snprintf(char *str, size_t size, const char *format, ...);- một hàm - một phần mở rộng của sprintf, định dạng một chuỗi và ghi nó vào con trỏ được truyền làm đối số đầu tiên. Nó khác với sprintf() ở chỗ str sẽ không ghi một byte lớn hơn kích thước được chỉ định.

Hàm này có một tính năng thú vị - trong mọi trường hợp, nó trả về kích thước của chuỗi được tạo ra (không tính đến ký tự cuối dòng). Nếu chuỗi trống thì trả về 0.

Một trong những vấn đề tôi đã mô tả khi sử dụng strlen có liên quan đến các hàm sprintf() và snprintf(). Giả sử chúng ta cần viết gì đó vào chuỗi str. Dòng cuối cùng chứa giá trị của các biến khác. Mục nhập của chúng tôi sẽ giống như thế này:

Char * str = /* cấp phát bộ nhớ ở đây */; sprintf(str, "Xin chào, %s\n", "Habr!");
Câu hỏi đặt ra: làm thế nào để xác định lượng bộ nhớ cần phân bổ cho chuỗi str?

Char * str = malloc(sizeof(char) * (strlen(str, "Xin chào, %s\n", "Habr!") + 1)); - nó sẽ không làm việc. Nguyên mẫu hàm strlen() trông như thế này:

#bao gồm size_t strlen(const char *s);
const char *s không ngụ ý rằng chuỗi được truyền cho s có thể là chuỗi có định dạng biến đổi.

Thuộc tính hữu ích của hàm snprintf() mà tôi đã đề cập ở trên sẽ giúp chúng ta ở đây. Chúng ta hãy xem mã cho chương trình sau:

#bao gồm #bao gồm #bao gồm void main() ( /* Vì snprintf() không tính đến ký tự cuối dòng, nên chúng tôi thêm kích thước của nó vào kết quả */ size_t Need_mem = snprintf(NULL, 0, "Xin chào, %s!\n", "Habr") + sizeof("\0"); char *str = malloc( Need_mem); snprintf(str, Need_mem, "Xin chào, %s!\n", "Habr"); printf("->\t %s", str); tự do(str); )
Chạy chương trình trong valgrind:

$ valgrind --tool=memcheck ./a.out -> Xin chào, Habr! ==4132== ==4132== TÓM TẮT HEAP: ==4132== đang sử dụng khi thoát: 0 byte trong 0 khối ==4132== tổng mức sử dụng heap: 2 cấp phát, 2 giải phóng, 1.041 byte được phân bổ ==4132= = ==4132== Tất cả các khối heap đã được giải phóng -- không có rò rỉ nào ==4132== ==4132== Để biết số lượng lỗi được phát hiện và ngăn chặn, hãy chạy lại với: -v ==4132== TÓM TẮT LỖI: 0 lỗi từ 0 bối cảnh (bị chặn: 0 từ 0) $
Tuyệt vời. Chúng tôi có hỗ trợ lập luận. Do thực tế là chúng ta chuyển null làm đối số thứ hai cho hàm snprintf() nên việc ghi vào con trỏ null sẽ không bao giờ gây ra Seagfault. Tuy nhiên, bất chấp điều này, hàm vẫn sẽ trả về kích thước cần thiết cho chuỗi.

Nhưng mặt khác, chúng tôi phải đưa vào một biến số bổ sung, và thiết kế

Size_t Need_mem = snprintf(NULL, 0, "Xin chào, %s!\n", "Habr") + sizeof("\0");
trông thậm chí còn tệ hơn trong trường hợp strlen().

Nói chung, + sizeof("\0") có thể bị xóa nếu bạn chỉ định rõ ràng "\0" ở cuối dòng định dạng (size_t Need_mem = snprintf(NULL, 0, "Xin chào, %s!\n \0 ", "Habr");), nhưng điều này không phải lúc nào cũng có thể thực hiện được (tùy thuộc vào cơ chế xử lý chuỗi, chúng ta có thể phân bổ thêm một byte).

Chúng ta cần phải làm một vài thứ. Tôi suy nghĩ một chút và quyết định rằng bây giờ là lúc phải viện đến trí tuệ của người xưa. Hãy mô tả một hàm macro sẽ gọi snprintf() với con trỏ null làm đối số đầu tiên và null làm đối số thứ hai. Và đừng quên phần cuối của dòng!

#define strsize(args...) snprintf(NULL, 0, args) + sizeof("\0")
Đúng, điều này có thể là mới đối với một số người, nhưng macro C hỗ trợ số lượng đối số thay đổi và dấu ba chấm cho bộ tiền xử lý biết rằng đối số của hàm macro đã chỉ định (trong trường hợp của chúng tôi là đối số) tương ứng với một số đối số thực.

Hãy kiểm tra giải pháp của chúng tôi trong thực tế:

#bao gồm #bao gồm #bao gồm #define strsize(args...) snprintf(NULL, 0, args) + sizeof("\0") void main() ( char *str = malloc(strsize("Xin chào, %s\n", "Habr! ")); sprintf(str, "Xin chào, %s\n", "Habr!"); printf("->\t%s", str); free(str); )
Hãy bắt đầu với valgrund:

$ valgrind --tool=memcheck ./a.out -> Xin chào, Habr! ==6432== ==6432== TÓM TẮT HEAP: ==6432== đang sử dụng khi thoát: 0 byte trong 0 khối ==6432== tổng mức sử dụng heap: 2 cấp phát, 2 giải phóng, 1.041 byte được phân bổ ==6432= = ==6432== Tất cả các khối heap đã được giải phóng -- không có rò rỉ nào ==6432== ==6432== Để biết số lượng lỗi được phát hiện và ngăn chặn, hãy chạy lại với: -v ==6432== TÓM TẮT LỖI: 0 lỗi từ 0 bối cảnh (bị chặn: 0 từ 0)
Vâng, không có lỗi. Mọi thứ đều chính xác. Và valgrind rất vui, và lập trình viên cuối cùng cũng có thể đi ngủ.

Nhưng cuối cùng, tôi sẽ nói thêm một điều nữa. Trong trường hợp chúng ta cần cấp phát bộ nhớ cho bất kỳ chuỗi nào (ngay cả khi có đối số) thì đã có sẵn giải pháp hoàn toàn sẵn sàng làm việc.

Chúng ta đang nói về hàm asprintf:

#define _GNU_SOURCE /* Xem feature_test_macros(7) */ #include int asprintf(char **strp, const char *fmt, ...);
Nó lấy một con trỏ tới một chuỗi (**strp) làm đối số đầu tiên của nó và phân bổ bộ nhớ cho con trỏ không được tham chiếu.

Chương trình của chúng tôi được viết bằng asprintf() sẽ trông như thế này:

#bao gồm #bao gồm #bao gồm void main() ( char *str; asprintf(&str, "Xin chào, %s!\n", "Habr"); printf("->\t%s", str); free(str); )
Và trên thực tế, trong valgrind:

$ valgrind --tool=memcheck ./a.out -> Xin chào, Habr! ==6674== ==6674== TÓM TẮT HEAP: ==6674== đang sử dụng khi thoát: 0 byte trong 0 khối ==6674== tổng mức sử dụng heap: 3 cấp phát, 3 giải phóng, 1.138 byte được phân bổ ==6674= = ==6674== Tất cả các khối heap đã được giải phóng -- không có rò rỉ nào ==6674== ==6674== Để biết số lượng lỗi được phát hiện và ngăn chặn, hãy chạy lại với: -v ==6674== TÓM TẮT LỖI: 0 lỗi từ 0 bối cảnh (bị chặn: 0 từ 0)
Mọi thứ đều ổn, nhưng như bạn có thể thấy, nhiều bộ nhớ hơn đã được phân bổ và hiện có 3 cấp phát chứ không phải 2. Trên các hệ thống nhúng yếu, việc sử dụng chức năng này là không mong muốn.
Ngoài ra, nếu viết man asprintf trong console, chúng ta sẽ thấy:

TUÂN THỦ Các chức năng này là phần mở rộng GNU, không phải trong C hoặc POSIX. Chúng cũng có sẵn dưới dạng *BSD. Việc triển khai FreeBSD đặt strp thành NULL do lỗi.

Điều này cho thấy rõ rằng chức năng này chỉ có sẵn trong các nguồn GNU.

Phần kết luận

Tóm lại, tôi muốn nói rằng làm việc với chuỗi trong C là một chủ đề rất phức tạp và có nhiều sắc thái. Ví dụ: để viết mã “an toàn” khi cấp phát bộ nhớ động, bạn nên sử dụng hàm calloc() thay vì malloc() - calloc lấp đầy bộ nhớ được cấp phát bằng các số 0. Hoặc sau khi cấp phát bộ nhớ, hãy sử dụng hàm memset(). Nếu không, rác ban đầu nằm trong vùng bộ nhớ được cấp phát có thể gây ra sự cố trong quá trình gỡ lỗi và đôi khi khi làm việc với chuỗi.

Hơn một nửa số lập trình viên C mà tôi biết (hầu hết là người mới bắt đầu) đã giải quyết vấn đề cấp phát bộ nhớ cho chuỗi theo yêu cầu của tôi, đã làm theo cách mà cuối cùng dẫn đến lỗi ngữ cảnh. Trong một trường hợp - thậm chí là rò rỉ bộ nhớ (à, một người đã quên làm free(str), điều đó không bao giờ xảy ra với bất kỳ ai). Trên thực tế, điều này đã thôi thúc tôi tạo ra tác phẩm mà bạn vừa đọc.

Tôi hy vọng bài viết này sẽ hữu ích cho ai đó. Tại sao tôi lại làm ầm ĩ lên thế này - không có ngôn ngữ nào là đơn giản cả. Mọi nơi đều có sự tinh tế riêng. Và bạn càng biết nhiều sự tinh tế trong ngôn ngữ thì mã của bạn càng tốt.

Tôi tin rằng sau khi đọc bài viết này, mã của bạn sẽ tốt hơn một chút :)
Chúc may mắn, Habr!

Dòng. Đầu vào/đầu ra chuỗi. I/O được định dạng. Xử lý chuỗi bằng các hàm ngôn ngữ C. Làm việc với bộ nhớ.

1.1. Khai báo và khởi tạo chuỗi.

Chuỗi là một mảng các ký tự kết thúc bằng ký tự trống '\0'. Chuỗi được khai báo là một mảng ký tự thông thường, ví dụ:

ký tự s1; // chuỗi dài chín ký tự

ký tự *s2; // con trỏ tới chuỗi

Sự khác biệt giữa con trỏ s1 và s2 là con trỏ s1 là hằng số được đặt tên và con trỏ s2 là biến.

Các hằng chuỗi được đặt trong dấu ngoặc kép, trái ngược với các ký tự được đặt trong dấu ngoặc đơn. Ví dụ,

“Đây là một sợi dây.”

Độ dài của hằng chuỗi không được vượt quá 509 ký tự theo tiêu chuẩn. Tuy nhiên, nhiều triển khai cho phép độ dài chuỗi dài hơn.

Khi khởi tạo chuỗi, tốt hơn là không chỉ định kích thước mảng; trình biên dịch sẽ thực hiện việc này bằng cách tính độ dài của chuỗi và thêm một chuỗi vào chuỗi đó. Ví dụ,

char s1 = “Đây là một chuỗi.”;

Trong ngôn ngữ lập trình C, có một số lượng lớn các hàm để làm việc với chuỗi, nguyên mẫu của chúng được mô tả trong các tệp tiêu đề stdlib.h và string.h. Làm việc với các chức năng này sẽ được thảo luận trong các đoạn văn sau.

1.2. Đầu vào/đầu ra chuỗi.

Để nhập một chuỗi từ bảng điều khiển, hãy sử dụng hàm

char* được(char *str);

ghi một chuỗi vào địa chỉ str và trả về địa chỉ của chuỗi đã nhập. Hàm dừng nhập nếu gặp ký tự '\n' hoặc EOF (cuối tệp). Ký tự dòng mới không được sao chép. Một byte 0 được đặt ở cuối dòng đọc. Nếu thành công, hàm trả về một con trỏ tới dòng đã đọc và nếu không thành công, trả về NULL.

Để xuất một chuỗi ra bàn điều khiển, hãy sử dụng hàm tiêu chuẩn

int đặt (const char *s);

nếu thành công sẽ trả về số không âm và nếu không thành công sẽ trả về EOF.

Nguyên mẫu hàm get và put được mô tả trong tệp tiêu đề stdio.h.

#bao gồm

printf("Chuỗi đầu vào: ");

1.3. I/O được định dạng.

Để nhập dữ liệu được định dạng từ bảng điều khiển, hãy sử dụng hàm

int scanf (const char *định dạng, ...);

nếu thành công sẽ trả về số đơn vị dữ liệu đã đọc và nếu không thành công sẽ trả về EOF. Tham số định dạng phải trỏ đến chuỗi cần định dạng, chứa các thông số định dạng đầu vào. Số lượng và loại đối số theo chuỗi định dạng phải khớp với số lượng và loại định dạng đầu vào được chỉ định trong chuỗi định dạng. Nếu điều kiện này không được đáp ứng thì kết quả của hàm sẽ không thể đoán trước được.

Ký tự khoảng trắng, "\t" hoặc "\n" trong chuỗi định dạng mô tả một hoặc nhiều ký tự trống trong luồng đầu vào, bao gồm các ký tự: dấu cách, '\t', '\n', '\v', '\f'. Hàm scanf bỏ qua các ký tự trống trong luồng đầu vào.

Các ký tự chữ trong chuỗi định dạng, ngoại trừ ký tự %, yêu cầu các ký tự giống hệt nhau xuất hiện trong luồng đầu vào. Nếu không có ký tự đó thì chức năng scanf sẽ ngừng nhập. Hàm scanf bỏ qua các ký tự bằng chữ.

Nói chung, đặc tả định dạng đầu vào trông như thế này:

%[*] [chiều rộng] [sửa đổi] loại

Ký hiệu '*' biểu thị sự thiếu sót khi nhập trường được xác định bởi thông số kỹ thuật này;

- ‘width’ xác định số lượng ký tự tối đa được nhập theo thông số kỹ thuật này;

Kiểu này có thể nhận các giá trị sau:

c – mảng ký tự,

s – một chuỗi ký tự, các dòng cách nhau bằng ký tự trống,

d – số nguyên có dấu của 10 s/s,

i – số nguyên có dấu, hệ thống số phụ thuộc vào hai chữ số đầu tiên,

u – số nguyên không dấu ở tốc độ 10 s/s,

o – số nguyên không dấu trong 8 s/s,

x, X – số nguyên không dấu ở tốc độ 16 s/s,

e, E, f, g, G – số thực,

p – con trỏ tới con trỏ,

n – con trỏ tới một số nguyên,

[…] – một mảng các ký tự được quét, ví dụ: .

Trong trường hợp sau, chỉ các ký tự nằm trong dấu ngoặc vuông mới được nhập từ luồng đầu vào. Nếu ký tự đầu tiên bên trong dấu ngoặc vuông là '^' thì chỉ những ký tự không có trong mảng mới được nhập. Phạm vi ký tự trong mảng được chỉ định bằng ký hiệu '-'. Khi bạn nhập ký tự, các ký tự trống ở đầu và byte rỗng cuối của chuỗi cũng được nhập.

Công cụ sửa đổi có thể nhận các giá trị sau:

h - số nguyên ngắn,

l, L – số nguyên dài hoặc số thực,

và chỉ được sử dụng cho số nguyên hoặc số thực.

Ví dụ sau đây cho thấy cách sử dụng hàm scanf. Lưu ý rằng bộ xác định định dạng, bắt đầu bằng đầu vào số động, đứng trước ký tự khoảng trắng.

#bao gồm

printf("Nhập số nguyên: ");

scanf("%d", &n);

printf("Nhập số double: ");

scanf(" %lf", &d);

printf("Nhập một ký tự: ");

scanf(" %c", &c);

printf("Nhập một chuỗi: ");

scanf(" %s", &s);

Lưu ý rằng trong chương trình này số dấu phẩy động được khởi tạo. Điều này được thực hiện để trình biên dịch bao gồm thư viện hỗ trợ làm việc với các số thực. Nếu điều này không được thực hiện, lỗi sẽ xảy ra trong thời gian chạy khi nhập số thực.

Để định dạng dữ liệu đầu ra cho bảng điều khiển, hãy sử dụng hàm

int printf (const char *định dạng, ...);

nếu thành công sẽ trả về số đơn vị dữ liệu đầu ra và nếu không thành công sẽ trả về EOF. Tham số định dạng là một chuỗi định dạng chứa thông số kỹ thuật cho các định dạng đầu ra. Số lượng và loại đối số theo sau chuỗi định dạng phải khớp với số lượng và loại thông số định dạng đầu ra được chỉ định trong chuỗi định dạng. Nói chung, đặc tả định dạng đầu ra trông như thế này:

%[flags] [width] [.precision] [sửa đổi] loại

- ‘cờ’ là các ký hiệu khác nhau chỉ định định dạng đầu ra;

- ‘width’ xác định số lượng ký tự đầu ra tối thiểu theo thông số kỹ thuật này;

- ‘.precision’ xác định số lượng ký tự tối đa được hiển thị;

- ‘công cụ sửa đổi’ chỉ định loại đối số;

- 'type' chỉ định loại đối số.

Để xuất số nguyên có dấu, định dạng đầu ra sau được sử dụng:

%[-] [+ | khoảng trống] [chiều rộng] [l] d

- – căn trái, mặc định – phải;

+ – hiển thị dấu ‘+’, lưu ý số âm luôn hiển thị dấu ‘-’;

‘space’ – một khoảng trắng được hiển thị ở vị trí ký tự;

d – kiểu dữ liệu int.

Để xuất số nguyên không dấu, hãy sử dụng định dạng đầu ra sau:

%[-] [#] [chiều rộng] [l]

# – 0 ban đầu là đầu ra cho các số trong 8 c/c hoặc 0x hoặc 0X ban đầu cho các số trong 16 c/c,

l – công cụ sửa đổi kiểu dữ liệu dài;

u – số nguyên trong 10c/c,

o – số nguyên trong 8 c/c,

x, X – số nguyên ở 16 c/c.

Định dạng đầu ra sau đây được sử dụng để hiển thị số dấu phẩy động:

%[-] [+ | dấu cách] [chiều rộng] [.precision]

"chính xác" - cho biết số chữ số sau dấu thập phân đối với định dạng f, e và E hoặc số chữ số có nghĩa đối với định dạng g và G. Các số được làm tròn. Độ chính xác mặc định là sáu chữ số thập phân;

f - số điểm cố định,

e – số ở dạng mũ, số mũ được ký hiệu bằng chữ “e”,

E – số ở dạng mũ, số mũ được ký hiệu bằng chữ “E”,

g - định dạng ngắn nhất trong số các định dạng f hoặc g,

G - định dạng ngắn nhất của f hoặc G.

printf ("n = %d\n f = %f\n e = %e\n E = %E\n f = %.2f", -123, 12.34, 12.34, 12.34, 12.34);

// in ra: n = 123 f = 12.340000 e = 1.234000e+001 E = 1.234000E+001 f = 12.34

1.4. Định dạng chuỗi.

Có các biến thể của hàm scanf và printf được thiết kế để định dạng chuỗi và được gọi tương ứng là sscanf và sprintf.

int sscanf (const char *str, const char *format, ...);

đọc dữ liệu từ chuỗi được chỉ định bởi str, theo chuỗi định dạng được chỉ định bởi format. Nếu thành công, trả về số lượng dữ liệu đã đọc và nếu không thành công, trả về EOF. Ví dụ,

#bao gồm

char str = "Chuỗi 10 1.2 Không có đầu vào";

sscanf(str, "%c %d %lf %s", &c, &n, &d, s);

printf("%c\n", c); // in: a

printf("%d\n", n); // in: 10

printf("%f\n", d); // in: 1.200000

printf("%s\n", s); // in: Chuỗi

int sprintf (char *buffer, const char *format, ...);

định dạng chuỗi theo định dạng được chỉ định bởi tham số định dạng và ghi kết quả thu được vào mảng ký tự bộ đệm. Hàm trả về số ký tự được ghi vào bộ đệm mảng ký tự, không bao gồm byte null kết thúc. Ví dụ,

#bao gồm

char str = "c = %c, n = %d, d = %f, s = %s";

char s = "Đây là một chuỗi.";

sprintf(buffer, str, c, n, d, s);

printf("%s\n", đệm); // in ra: c = c, n = 10, d = 1.200000, s = Đây là một chuỗi

1.5. Chuyển đổi chuỗi thành dữ liệu số.

Nguyên mẫu của các hàm chuyển đổi chuỗi thành dữ liệu số được cung cấp trong tệp tiêu đề stdlib.h, tệp này phải được đưa vào chương trình.

Để chuyển đổi một chuỗi thành số nguyên, hãy sử dụng hàm

int atoi (const char *str);

char *str = “-123”;

n = atoi(str); // n = -123

Để chuyển đổi một chuỗi thành số nguyên dài, hãy sử dụng hàm

atol int dài (const char *str);

nếu thành công sẽ trả về số nguyên mà chuỗi str được chuyển đổi và nếu không thành công, sẽ trả về 0. Ví dụ:

char *str = “-123”;

n = atol(str); // n = -123

Để chuyển đổi một chuỗi thành một số kép, hãy sử dụng hàm

atof kép (const char *str);

trong trường hợp thành công, trả về một số thực loại double, trong đó chuỗi str được chuyển đổi và trong trường hợp thất bại, là 0. Ví dụ:

char *str = “-123.321”;

n = atof(str); // n = -123.321

Các hàm sau thực hiện các chức năng tương tự như atoi, atol, atof, nhưng cung cấp chức năng nâng cao hơn.

int strtol dài (const char *str, char **endptr, int base);

chuyển đổi chuỗi str thành một số int dài và trả về số này. Các tham số của chức năng này có các mục đích sau.

Nếu cơ sở là 0 thì việc chuyển đổi phụ thuộc vào hai ký tự đầu tiên của str:

Nếu ký tự đầu tiên là một số từ 1 đến 9 thì số đó được coi là được biểu thị bằng 10 c/c;

Nếu ký tự đầu tiên là chữ số 0 và ký tự thứ hai là chữ số từ 1 đến 7 thì số đó được coi là được biểu thị bằng 8 c/c;

Nếu ký tự đầu tiên là 0 và ký tự thứ hai là 'X' hoặc 'x' thì số đó được coi là được biểu thị bằng 16 c/c.

Nếu cơ số là một số từ 2 đến 36 thì giá trị đó được lấy làm cơ số của hệ thống số và bất kỳ ký tự nào bên ngoài hệ thống số sẽ ngừng chuyển đổi. Trong hệ thống số từ cơ số 11 đến cơ số 36, các ký hiệu 'A' đến 'Z' hoặc 'a' đến 'z' được sử dụng để biểu thị các chữ số.

Giá trị của đối số endptr được thiết lập bởi hàm strtol. Giá trị này chứa một con trỏ tới ký tự ngăn chuỗi str được chuyển đổi. Hàm strtol trả về số đã chuyển đổi nếu thành công và 0 nếu không thành công. Ví dụ:

n = strtol (“12a”, &p, 0);

printf("n = %ld, %stop = %c, n, *p); // n = 12, dừng = a

n = strtol("012b", &p, 0);

printf("n = %ld, %stop = %c, n, *p); // n = 10, dừng = b

n = strtol (“0x12z”, &p, 0);

printf("n = %ld, %stop = %c, n, *p); // n = 18, dừng = z

n = strtol (“01117”, &p, 0);

printf("n = %ld, %stop = %c, n, *p); // n = 7, dừng = 7

unsigned long int strtol (const char *str, char **endptr, int base);

hoạt động tương tự như hàm strtol, nhưng chuyển đổi biểu diễn ký hiệu của một số thành một số kiểu int dài không dấu.

strtod kép (const char *str, char **endptr);

Chuyển đổi biểu diễn tượng trưng của một số thành gấp đôi.

Tất cả các hàm được liệt kê trong đoạn này đều ngừng hoạt động khi chúng gặp ký tự đầu tiên không phù hợp với định dạng của số được đề cập.

Ngoài ra, nếu giá trị ký tự của một số vượt quá phạm vi giá trị chấp nhận được cho kiểu dữ liệu tương ứng thì các hàm atof, strtol, strtoul, strtod sẽ đặt giá trị của biến errno thành ERANGE. Biến errno và hằng số ERANGE được xác định trong tệp tiêu đề math.h. Trong trường hợp này, hàm atof và strtod trả về giá trị HUGE_VAL, hàm strtol trả về giá trị LONG_MAX hoặc LONG_MIN và hàm strtoul trả về giá trị ULONG_MAX.

Các hàm không chuẩn itoa, ltoa, utoa, ecvt, fcvt và gcvt có thể được sử dụng để chuyển đổi dữ liệu số thành chuỗi ký tự. Nhưng tốt hơn hết bạn nên sử dụng hàm sprintf tiêu chuẩn cho những mục đích này.

1.6. Các hàm tiêu chuẩn để làm việc với chuỗi.

Phần này thảo luận về các hàm làm việc với chuỗi, nguyên mẫu của chúng được mô tả trong tệp tiêu đề string.h.

1. So sánh chuỗi. Hàm strcmp và strncmp được sử dụng để so sánh các chuỗi.

int strcmp (const char *str1, const char *str2);

so sánh theo từ điển các chuỗi str1, str2 và trả về –1, 0 hoặc 1 nếu str1 tương ứng nhỏ hơn, bằng hoặc lớn hơn str2.

int strncmp (const char *str1, const char *str2, size_t n);

về mặt từ điển so sánh tối đa n ký tự đầu tiên của chuỗi str1 và str2. Hàm trả về -1, 0 hoặc 1 nếu n ký tự đầu tiên của str1 lần lượt nhỏ hơn, bằng hoặc lớn hơn n ký tự đầu tiên của str2.

// ví dụ về so sánh chuỗi

#bao gồm

#bao gồm

char str1 = "aa bb";

char str2 = "aa aa";

char str3 = "aa bb cc";

printf("%d\n", strcmp(str1, str3)); // in: -1

printf("%d\n", strcmp(str1, str1)); // in: -0

printf("%d\n", strcmp(str1, str2)); // in: 1

printf("%d\n", strncmp(str1, str3, 5)); // in: 0

2. Sao chép dòng. Hàm strcpy và strncpy được sử dụng để sao chép chuỗi.

char *strcpy (char *str1, const char *str2);

sao chép chuỗi str2 vào chuỗi str1. Toàn bộ chuỗi str2 được sao chép, bao gồm cả byte null kết thúc. Hàm trả về một con trỏ tới str1. Nếu các đường chồng lên nhau, kết quả là không thể đoán trước.

char *strncpy (char *str1, const char *str2, size_t n);

sao chép n ký tự từ chuỗi str2 sang chuỗi str1. Nếu str2 chứa ít hơn n ký tự thì byte 0 cuối cùng sẽ được sao chép nhiều lần nếu cần để mở rộng str2 thành n ký tự. Hàm trả về một con trỏ tới chuỗi str1.

char str2 = "Sao chép chuỗi.";

strcpy(str1, str2);

printf(str1); // in: Sao chép chuỗi.

4. Nối dây. Các hàm strcat và strncat được sử dụng để nối các chuỗi thành một chuỗi.

char* strcat (char *str1, const char *str2);

nối chuỗi str2 vào chuỗi str1, với byte 0 ở cuối chuỗi str1 bị xóa. Hàm trả về một con trỏ tới chuỗi str1.

char* strncat (char *str1, const char *str2, size_t n);

nối n ký tự từ chuỗi str2 vào chuỗi str1, với byte 0 ở cuối chuỗi str1 bị xóa. Hàm trả về một con trỏ tới chuỗi str1. nếu độ dài của chuỗi str2 nhỏ hơn n thì chỉ các ký tự có trong chuỗi str2 mới được thêm vào. Sau khi nối chuỗi, một byte rỗng luôn được thêm vào str1. Hàm trả về một con trỏ tới chuỗi str1.

#bao gồm

#bao gồm

char str1 = "Chuỗi";

char str2 = "catenation";

char str3 = "Có Không";

strcat(str1, str2);

printf("%s\n", str1); // in: Nối chuỗi

strncat(str1, str3, 3);

printf("%s\n", str1); // in: Nối chuỗi Có

5. Tìm kiếm một ký tự trong chuỗi.Để tìm kiếm một ký tự trong một chuỗi, hãy sử dụng các hàm strchr, strrchr, strspn, strcspn và strpbrk.

char* strchr (const char *str, int c);

tìm kiếm sự xuất hiện đầu tiên của ký tự được chỉ định bởi c trong chuỗi str. Nếu thành công, hàm trả về một con trỏ tới ký tự đầu tiên được tìm thấy và nếu không thành công, trả về NULL.

char* strrchr (const char *str, int c);

tìm kiếm lần xuất hiện cuối cùng của ký tự được chỉ định bởi c trong chuỗi str. Nếu thành công, hàm trả về một con trỏ tới ký tự cuối cùng được tìm thấy và nếu không thành công, trả về NULL.

#bao gồm

#bao gồm

char str = "Tìm kiếm Char";

printf("%s\n", strchr(str, "r")); // in: r tìm kiếm

printf("%s\n", strrchr(str, "r")); // in: rch

size_t strspn (const char *str1, const char *str2);

trả về chỉ mục của ký tự đầu tiên từ str1 không có trong str2.

size_t strcspn (const char *str1, const char *str2);

trả về chỉ mục của ký tự đầu tiên từ str1 xuất hiện trong str2.

char str = "123 abc";

printf ("n = %d\n", strspn (str, "321"); // in: n = 3

printf ("n = %d\n", strcspn(str, "cba"); // in: n = 4

char* strpbrk (const char *str1, const char *str2);

tìm ký tự đầu tiên trong chuỗi str1 bằng một trong các ký tự trong chuỗi str2. Nếu thành công, hàm trả về một con trỏ tới ký tự này và nếu không thành công, trả về NULL.

char str = "123 abc";

printf("%s\n", strpbrk(str, "bca")); // in: abc

6. So sánh chuỗi. Hàm strstr được sử dụng để so sánh các chuỗi.

char* strstr (const char *str1, const char *str2);

tìm lần xuất hiện đầu tiên của str2 (không có byte null ở cuối) trong str1. Nếu thành công, hàm trả về một con trỏ tới chuỗi con tìm thấy và nếu không thành công, trả về NULL. Nếu con trỏ str1 trỏ đến một chuỗi có độ dài bằng 0 thì hàm sẽ trả về con trỏ str1.

char str = "123 abc 456;

printf("%s\n", strstr(str, "abc"); // print: abc 456

7. Phân tích chuỗi thành mã thông báo. Hàm strtok được sử dụng để phân tích chuỗi thành mã thông báo.

char* strtok (char *str1, const char *str2);

trả về một con trỏ tới mã thông báo (word) tiếp theo trong chuỗi str1, trong đó dấu phân cách mã thông báo là các ký tự từ chuỗi str2. Nếu không còn mã thông báo nào nữa, hàm trả về NULL. Trong lệnh gọi đầu tiên đến hàm strtok, tham số str1 phải trỏ đến một chuỗi được mã hóa và trong các lệnh gọi tiếp theo, tham số này phải được đặt thành NULL. Sau khi tìm thấy mã thông báo, hàm strtok sẽ ghi một byte rỗng sau mã thông báo này thay cho dấu phân cách.

#bao gồm

#bao gồm

char str = "12 34 ab cd";

p = strtok(str, " ");

printf("%s\n", p); // in các giá trị trong một cột: 12 34 ab cd

p = strtok(NULL, " ");

8. Xác định độ dài của một chuỗi. Hàm strlen được sử dụng để xác định độ dài của chuỗi.

size_t strlen (const char *str);

trả về độ dài của chuỗi, bỏ qua byte null cuối cùng. Ví dụ,

char str = "123";

printf("len = %d\n", strlen(str)); // in ra: len = 3

1.7. Các chức năng làm việc với bộ nhớ.

Tệp tiêu đề string.h cũng mô tả các hàm để làm việc với các khối bộ nhớ, tương tự như các hàm tương ứng để làm việc với các chuỗi.

void* memchr (const void *str, int c, size_t n);

tìm kiếm lần xuất hiện đầu tiên của ký tự được chỉ định bởi c trong n byte của chuỗi str.

int memcmp (const void *str1, const void *str2, size_t n);

so sánh n byte đầu tiên của chuỗi str1 và str2.

void* memcpy (const void *str1, const void *str2, size_t n);

sao chép n byte đầu tiên từ chuỗi str1 sang chuỗi str2.

void* memmove (const void *str1, const void *str2, size_t n);

sao chép n byte đầu tiên từ str1 sang str2, đảm bảo rằng các chuỗi chồng chéo được xử lý chính xác.

void* bộ nhớ (const void *str, int c, size_t n);

sao chép ký tự được chỉ định bởi c vào n byte đầu tiên của str.