Các thao tác cơ bản với mảng kí tự

####Chào các bạn học viên đang theo dõi khóa học lập trình trực tuyến ngôn ngữ C++.

Trong bài học này, chúng ta sẽ cùng tìm hiểu một số cách để thao tác với C-style string thông qua các hàm đã được định nghĩa sẵn trong thư viện cstring. Vì có rất nhiều công việc cần giải quyết khi thao tác với mảng kí tự nên người ta đã tạo ra thư viện cstring để định nghĩa một tập hợp các hàm xử lý mảng kí tự, giúp chúng ta tiết kiệm thời gian và công sức viết code hơn trong khi chúng ta chỉ cần sử dụng lại những gì đã có sẵn.

Các bạn cần include thư viện cstring để có thể sử dụng các hàm được định nghĩa bên trong nó.

###Thiết lập giá trị cho một vùng nhớ thuộc mảng kí tự

Hàm memset sẽ giúp chúng ta lấp vào một số ô nhớ liên tiếp nhau trong mảng kí tự bằng một giá trị xác định. Dưới đây là khai báo của hàm memset:

void* memset( void *ptr, int value, size_t num);

Các bạn có thể hiểu hàm memset thiết lập num bytes ô nhớ đầu tiên bắt đầu từ địa chỉ ptr bằng giá trị value định sẵn. Ví dụ như sau:

char foo[] = "Almost every programmer should know memset!";
cout << foo << endl;

memset(foo, '-', 7);
cout << foo << endl;

Sau khi chạy đoạn chương trình này, kết quả mình nhận được trên màn hình console là 2 dòng text như sau:

Almost every programmer should know memset!

-------every programmer should know memset!

Như vậy, 7 bytes đầu tiên (tương đương với 7 kí tự trong mảng foo) đã bị hàm memset thiết lập bằng kí tự dấu trừ.

Chúng ta cũng thường sử dụng hàm memset để khởi tạo toàn bộ mảng kí tự bằng một giá trị xác định nào đó.

char foo[20];
memset(foo, 'a', sizeof(foo));
foo[sizeof(foo) - 1] = '\0'; //Don't forget to set the ending character at the end of C-style string.

Với đoạn code này, mảng kí tự foo sẽ được lấp toàn bộ mảng bằng kí tự a, và kí tự cuối cùng sẽ được thiết lập là kí tự kết thúc chuỗi như mình đã trình bày trong bài học về mảng kí tự.

###Xem độ dài của chuỗi kí tự

Các bạn cần phân biệt rõ số lượng ô nhớ được cấp phát cho mảng kí tự sizeof(c_string) và độ dài của chuỗi kí tự (length of c_string).

Độ dài của chuỗi kí tự được tính từ kí tự đầu tiên cho đến kí tự kết thúc chuỗi (’\0’), trong khi đó, số lượng ô nhớ được cấp phát cho mảng kí tự được trả về thông qua toán tử sizeof.

Để lấy được giá trị là độ dài của chuỗi kí tự được lưu bên trong mảng kí tự, chúng ta có thể sử dụng hàm strlen được khai báo như sau:

size_t strlen ( const char * str );

Hàm strlen nhận vào tên mảng kí tự mà các bạn muốn lấy độ dài và trả về một số nguyên là độ dài chuỗi kí tự lưu trong mảng. Ví dụ:

char foo[50] = "C++ Programming language";
cout << "Length of foo string: " << strlen(foo) << endl;

Đoạn code trên sẽ cho ra kết quả độ dài của chuỗi kí tự "C++ Programming language" là 24. Trong khi đó, nếu các bạn dùng toán tử sizeof(foo) thì kết quả sẽ là 50, vì mình yêu cầu cấp phát 50 ô nhớ cho mảng kí tự foo.

###Sao chép mảng kí tự

Khi sử dụng thư viện cstring, chúng ta có 2 cách để sao chép chuỗi kí tự từ một mảng kí tự sang mảng kí tự khác.

#####Sử dụng hàm strcpy

Khai báo của hàm strcpy trong thư viện cstring:

char * strcpy ( char * destination, const char * source );

Hàm strcpy cho phép bạn sao chép toàn bộ chuỗi kí tự được lưu bên trong mảng kí tự source sang mảng kí tự destination.

Ví dụ:

char str_source[] = "This is source string";
char str_destination[30];

strcpy(str_destination, str_source);
cout << "str_destination: " << str_destination << endl;

Cách hoạt động của hàm strcpy:

Lưu ý: Mảng kí tự str_destination phải được cấp phát đủ bộ nhớ để lưu trữ chuỗi kí tự được copy từ mảng kí tự str_source.

#####Sử dụng hàm strncpy

Hàm strncpy trong thư viện cstring được khai báo như sau:

char * strncpy ( char * destination, const char * source, size_t num );

Hàm strncpy cho phép bạn sao chép num kí tự từ mảng kí tự source sang mảng kí tự destination.

Ví dụ:

char str_source[] = "This is source string";
char str_destination[30];

strncpy(str_destination, str_source, strlen(str_source) / 2);

Với đoạn code trên, chỉ có một nữa số lượng kí tự của mảng str_source được copy sang mảng str_destination.

#####Sử dụng hàm strncpy_s khi sử dụng Visual studio 2015

Khi sử dụng hàm strcpystrncpy, có một số trường hợp dẫn đến việc copy dữ liệu không an toàn. Ví dụ:

char str_source[] = "aaaaaaaaaaaaaaaaaaaaaa";
char str_destination[10];

strcpy(str_destination, str_source);

Đoạn code trên có thể gây xung đột vùng nhớ vì số lượng kí tự được copy vượt ra ngoài giới hạn vùng nhớ của str_destination. Dó đó, Visual studio 2015 sẽ ngăn chặn hành vi build chương trình.

Vì Visual studio 2015 áp dụng chuẩn C++11 trở lên, nên chúng ta cần sử dụng hàm strncpy_s thay thế cho 2 hàm copy mảng kí tự trong thư viện cstring.

char str_source[] = "This is source string";
char str_destination[30];

strncpy_s(str_destination, str_source, strlen(str_source));

Trong ví dụ trên, mình thực hiện copy toàn bộ mảng str_source vào mảng str_destination bằng cách truyền vào đối số thứ ba của hàm là độ dài toàn bộ chuỗi kí tự bên trong mảng str_source.

Nếu gặp trường hợp số lượng ô nhớ của mảng str_destination không đủ để chứa số lượng phần tử được copy từ mảng str_source, Visual studio sẽ đưa ra thông báo vi phạm Assertion của hàm strncpy_s.

###So sánh hai chuỗi kí tự

Hàm strcmp sẽ giúp chúng ta so sánh hai chuỗi kí tự.

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

Khi sử dụng hàm so sánh 2 chuỗi strcmp, có 3 trường hợp có thể xảy ra:

  • Giá trị trả về là 0:

    Điều này có nghĩa nội dung của hai chuỗi kí tự này hoàn toàn giống nhau. Ví dụ:

      char str1[] = "This is a string";
      char str2[] = "This is a string";
    
      if(strcmp(str1, str2) == 0)	{
      	cout << "str1 and str2 are equal" << endl;		
      }
      else	{
      	cout << "str1 ans str2 are not equal" << endl;
      }
    
  • Giá trị trả về nhỏ hơn 0:

    Điều này có nghĩa tại vị trí phát hiện cặp kí tự không tương xứng giữa str1 và str2 tạm gọi là vị trí index_not_match, ta có:

    str1[index_not_match] < str[index_not_match]

    Ví dụ:

      char str1[] = "abcDEF";
      char str2[] = "abcdef";
    

    Khi so sánh chuỗi str1 và chuỗi str2 như trên bằng dòng lệnh strcmp(str1, str2), ta nhận được giá trị trả về nhỏ hơn 0, vì tại vị trí có chỉ số là 3, kí tự ‘D’ của str1 có mã ASCII nhỏ hơn kí tự ‘d’ của str2.

  • Giá trị trả về lớn hơn 0:

    Ngược lại với việc giá trị trả về nhỏ hơn 0. Mình lấy lại ví dụ trên:

      char str1[] = "abcDEF";
      char str2[] = "abcdef";
    

    Nếu các bạn thực hiện so sánh như sau:

      strcmp(str2, str1);
    

    Giá trị trả về sẽ lớn hơn 0.

###Nối chuỗi kí tự bằng một chuỗi kí tự khác

Để thực hiện thao tác nối một chuỗi bằng chuỗi kí tự khác, chúng ta sử dụng hàm strcat trong thư viện cstring:

char * strcat ( char * destination, const char * source );

Ý nghĩa của hàm này là nối vào sau chuỗi destination một bản copy của chuỗi kí tự source.

Lưu ý: Mảng kí tự destination phải có đủ bộ nhớ để chứa được thêm chuỗi mới được nối vào.

char str_destination[50] = "Hello";
strcat( str_destination, " " );
strcat( str_destination, "every" );
strcat( str_destination, "one");

cout << str_destination << endl;

Tuy nhiên, Visual studio 2015 sử dụng chuẩn C++11 trở lên, vì thế đối với môi trường làm việc là Visual studio 2015, chúng ta nên sử dụng hàm strcat_s.

Hàm strcat_s có cách sử dụng hoàn toàn giống với hàm strcat, các bạn chỉ cần đổi lại tên hàm:

char str_destination[50] = "Hello";
strcat_s( str_destination, " " );
strcat_s( str_destination, "every" );
strcat_s( str_destination, "one");

###Tìm kiếm chuỗi kí tự trong một chuỗi kí tự khác

Phần này có liên quan đến khái niệm con trỏ mà các bạn sẽ được học trong các bài học sắp tới, vì thế, mình sẽ chưa giải thích nhiều về thao tác của hàm tìm kiếm chuỗi mà thư viện cstring cung cấp.

Để thực hiện tìm kiếm chuỗi kí tự pattern bên trong chuỗi kí tự text nào đó, chúng ta sử dụng hàm strstr:

const char * strstr ( const char * text, const char * pattern );

Có thể tạm hiểu, hàm này sẽ trả về địa chỉ của ô nhớ của mảng kí tự text mà hàm này tìm thấy sự trùng khớp giữa chuỗi kí tự pattern với chuỗi kí tự text. Nếu không tìm thấy, hàm này trả về giá trị NULL.

Để biết được vị trí mà hàm strstr tìm thấy chuỗi pattern trong chuỗi text, các bạn có thể lấy địa chỉ của hàm strstr trả về trừ đi địa chỉ của ô nhớ đầu tiên trong mảng kí tự text.

char text[] = "This is a simple string";
char pattern[] = "simple";

char *p = strstr(text, pattern);

if (p == NULL) {
	cout << "Could not find the pattern string in the text string" << endl;
}
else    {

	int32_t match_index = (p - text) / sizeof(char);
	cout << "The pattern string match the text string at: " << match_index << endl;
}

###Tổng kết

Trong bài học này, chúng ta vừa làm quen với một số thao tác cơ bản với C-style string.

Một số hàm trong thư viện cstring khi sử dụng trong môi trường làm việc Visual studio 2015 sẽ bị cảnh báo không an toàn, và chương trình không thể biên dịch. Đối với những hàm này, chúng ta cần sử dụng theo chuẩn của Visual studio 2015, nhưng đó không phải là những hàm chuẩn, nó chỉ dùng trong môi trường của Visual studio 2015.

Thư viện cstring còn hổ trợ cho chúng ta rất nhiều những hàm khác, nhưng thường ít được sử dụng. Các bạn có thể truy cập liên kết bên dưới để tham khảo thêm về thư viện cstring:

http://www.cplusplus.com/reference/cstring/

###Bài tập cơ bản

1/ Viết chương trình nhập một chuỗi kí tự vào từ bàn phím, chuyển tất cả các kí tự trong chuỗi thành dạng in hoa. Ví dụ:

Chuỗi kí tự nhập vào: “This is a simple string”

Output: “THIS IS A SIMPLE STRING”

2/ Viết chương trình nhập vào một chuỗi kí tự str và một kí tự ch từ bàn phím, đếm trong chuỗi kí tự str có bao nhiêu lần xuất hiện kí tự ch mà bạn vừa nhập.


Hẹn gặp lại các bạn trong bài học tiếp theo trong khóa học lập trình C++ hướng thực hành.

Mọi ý kiến đóng góp hoặc thắc mắc có thể đặt câu hỏi trực tiếp tại diễn đàn.

www.daynhauhoc.com


Link Videos khóa học

https://www.udemy.com/c-co-ban-danh-cho-nguoi-moi-hoc-lap-trinh/learn/v4/overview

9 Likes

trường hợp này là trả về địa chỉ của chuỗi pattern đầu tiên tìm thấy trong chuỗi text phải không.
giống như string.indexof();

1 Like

Bài tập vui: ( áp dụng bài học so sánh chuỗi ký tự và ôn lại vòng lặp do … while, while)

Yêu cầu: Chạy chương trình cho người dùng nhập vào nick name của mình. Kiểm tra nếu người dùng nhập đúng nick name thì thoát vòng lặp, output ra 1 câu chửi nick name đó. Nếu không đúng thì tiếp tục kiểm tra 1 số điều kiện để nhắc nhở người dùng nhập cho đúng.

Giải bài của mình: (mình lấy ví dụ nick name bạn mình là “vietdexom”, các bạn áp dụng cho nick name của các bạn nhé, đem chương trình đi chọc bạn cho vui, vừa học vừa chơi :D)

#include <iostream>
#include <cstring>

using namespace std;

int main() 
{
    char ten[50];
    std::cout << "Ban nhap vao nick name cua ban: " ;
    cin.getline(ten,20);
    std::cout << ten << endl;
    while (strcmp(ten, "vietdexom") != 0)
    {
        if ((strcmp(ten, "viet") != 0) && (strcmp(ten, "vietde") != 0)) 
            {    
            std::cout << "To cha mi, cai nick name cua mi ma cung khong nho ha\n";
            std::cout << "Mi co phai ten Viet khong, nhap vao di:";
            
            }
        else if (strcmp(ten, "viet") == 0)
           {
            std::cout << "Dung ten mi roi day, nhung ta dang hoi nick name cua mi\n";
            std::cout << "Nhap nick name vao di:\n";
            std::cout << "mi co de khong, de thi nhap vao di: ";
            
           }
            else if (strcmp(ten, "vietde") == 0)
            {
                std::cout << "Gan dung roi day nhung chua du, nhap lai cho chinh xac di\n";        
                std::cout << "Ta goi y cho mi lan cuoi nhe, them chu \"xom\" vao cuoi\n";
                std::cout << "Roi day, nhap nick name vao lan nua di, ket hop 3 tu do nhe: ";
                
            }
           cin.getline(ten,20);
        
        }
        std::cout << "Fuck cai thang " << ten << std::endl;
        return 0;
}

P/s: Bạn nào có thể chuyển chương trình qua sử dụng do … while xem sao nhé.

1 Like

Hi mọi người !

Mình muốn làm 1 chương trình trò chơi là “Oảnh tù tì”

Mình muốn sử dụng hàm lấy chuỗi ngẫu nhiên trong mảng ký tự cho trước (“bao”,“bua”,“keo”) để đem so sánh với chuỗi ký tự người dùng nhập vào.

Bạn nào biết sử dụng hàm nào để làm được việc đó. Cám ơn mọi người.

1 Like

Hi mọi người, sau một vài ngày từ mình cũng tìm ra cách chọn chọn ngẫu nhiên với chuỗi cho trước với trò chơi “oản tù tì”.
Nhưng mình có một vài thắc mắc sau:

khi mình khai báo mảng ký tự char computer[10] ban đầu để lưu giá trị ngẫu nhiên của computer sinh ra, khi đến câu lệnh điều kiện để gán giá trị cho chuỗi computer theo số ngẫu nhiên sinh ra. Thì mình bắt buộc phải khai lại kiểu ký tự computer lần nữa, biến computer này mình đã khai báo từ đầu rồi.

ví dụ :slight_smile:

srand(time(NULL));
            com = 1 + rand()%(3);
            cout << com << endl;
            
            if (com == 1)
            {
                char computer[4] = "bao"; // mình bỏ khai báo kiểu char chỉ để computer = "bua" thì báo lỗi.
                cout << computer << endl;
            }

Nhờ các bạn xem giúp.
Đây là đoạn code Trò chơi “Oản Tù Tì” của mình.

#include <iostream>
#include <cstring>
#include <time.h>


using namespace std;

int main() 
{
    menu:
    cout << "*****   Tro choi Oan Tu Ti    *****" << endl;
    cout << "***** Go 1. Bat dau choi      *****" << endl;
    cout << "***** Go 2. Thoat tro choi    *****" << endl;
    cout << "***** Go 3. Huong dan choi    *****" << endl;
    
    int menu;
    cin >> menu;
    
    switch (menu)
    {
        case 1:
        {
            char user[10];
            int com; // luu so ngau nhien tu 1 den 3 tuong ung voi bua, dao, keo
            char computer[10];
            
            cout << "Bat dau choi \n";            
            
            // Kiem tra nguoi choi co go dung khong, khong dung thi bat nhap lai
            do
            {
                cout << "Nguoi choi chon: ";
                cin >> user;
                cout << user << endl;
            }
            while ((strcmp(user, "bua") != 0) && (strcmp(user, "keo") !=0) && (strcmp(user, "bao") != 0));
            srand(time(NULL));
            com = 1 + rand()%(3);
            cout << com << endl;
            
            if (com == 1)
            {
                char computer[4] = "bao";
                cout << computer << endl;
            }
            if (com == 2)
            {
                char computer[4] = "keo";
                cout << computer << endl;
            }
            if (com == 3)
            {
                char computer[4] = "bua";
                cout << computer << endl;
            }
            if ((strcmp(user, computer) == 0))
            {
                cout << "Hoa nhau nhe ! \n";
            }
            else 
                if ((strcmp(user, "bua") == 0) && (strcmp(computer, "keo") == 0)) 
                    {
                        cout << "Chuc mung - Ban da chien thang may tinh \n";
                    } 
                if ((strcmp(user, "bua") == 0) && (strcmp(computer, "bao") == 0))
                    {
                        cout << "Chia bun - Ban da thua roi \n";
                    }
                if ((strcmp(user, "bao") == 0) && (strcmp(computer, "keo") == 0))
                    {
                        cout << "Chia bun - Ban da thua roi \n";
                    }
                if ((strcmp(user, "bao") == 0) && (strcmp(computer, "bua") == 0))
                    {
                        cout << "Chuc mung - Ban da chien thang may tinh \n";
                    }
                if ((strcmp(user, "keo") == 0) && (strcmp(computer, "bao") == 0))
                    {
                        cout << "Chuc mung - Ban da chien thang may tinh \n";
                    }
                if ((strcmp(user, "keo") == 0) && (strcmp(computer, "bua") == 0))
                    {
                        cout << "Chia bun - Ban da thua roi \n";
                    }
                goto menu;
            }
        case 2:
        {
        exit(0);
        }
        case 3:
        {
            cout << "Nguoi dung se go vao \"Bua, Bao, Keo\", computer se ra ngau nhien " << endl; 
            cout << "Bua se thang Keo" << endl;
            cout << "Keo se thang Bao" << endl;
            cout << "Bao se thang Bua" << endl;
            goto menu;
        }
    }
    return 0;   
}

P/s: Mình đang dùng cloud9 ide, bạn nào biết lệnh nào dừng 1 khoảng thời gian nhưng trong visual studio 2015 không ah?

1 Like

bạn chỉ gọi srand(time(NULL)) 1 lần duy nhất, ko phải gọi mỗi khi xài rand().

int main() 
{
    srand(time(NULL)); //gọi 1 lần duy nhất ở đây
    ...

còn về câu hỏi của bạn thì phải xài strcpy

if (com == 1)
{
    /*char computer[4] = "bao";*/
    strcpy(computer, "bao");
    cout << computer << endl;
}
3 Likes

Bạn nào làm được rồi cho mình xem code của bài 1 với

1 Like

Sao em nhập zô nó báo lỗi

6 47 C:\Documents and Settings\Administrator\Desktop\C++\thuchanhvaoday.cpp [Error] 'strlen' was not declared in this scope

là sao? Code:

#include <iostream>
using namespace std;
int main()
{
    char foo[50] = "C++ Programming language";
cout << "Length of foo string: " << strlen(foo) << endl;
return 0;
}
1 Like

Bạn thiếu:

#include <cstring>

PS: Bạn làm ơn post code vào markdown dùm. :expressionless:

2 Likes

Cảm ơn thánh. Thanks so much…Ngại chết đi được :yum:

1 Like

Đoạn này *p là sao hả anh ?? Có phải lưu giá trị của kết quả strstr(text, pattern); vào p không ạ ?

1 Like
char *p = strstr(text, pattern);

if (p == NULL) {
cout << "Could not find the pattern string in the text string" << endl;

Đoạn này *p là sao hả anh ?? Có phải lưu giá trị của kết quả strstr(text, pattern); vào p không ạ ?

1 Like

Hàm strstr có chức năng tìm vị trí xuất hiện đầu tiên của chuỗi s2 trong s1

Nếu thành công, nó trả về con trỏ đến vị trí xuất hiện đầu tiên của s2 trong s1

Còn thất bại thì trả về NULL

Vậy nên nếu tìm thấy s2 trong s1 thì có thể code:

if (strstr(s1, s2) != NULL)
         founded

Còn *p là 1 con trỏ, nó trỏ đến cái vị trí xuất hiện đầu tiên của s2 trong s1. Nếu nó không trỏ tới, tức là NULL thì coi như không tìm thấy s2 trong s1 và ngược lại

1 Like

Chưa học con trỏ mà -_- Thế còn toán tử & thì là sao hả anh ?

1 Like

Nếu bạn chưa học con trỏ thì có thể “cưỡi ngựa xem hoa” phần này :wink:
Nhưng chưa học cũng không sao, *p là 1 con trỏ, nhưng trong tình huống này thì nó “không phải” là con trỏ.

& là toán tử lấy địa chỉ của 1 biến được khởi tạo ở trong bộ nhớ. Nhưng mình có thấy & đâu nhỉ ?

1 Like

@@ Dù sao cũng cám ơn

1 Like

mn xem giúp mình bài 2 mình làm sai ở đâu với

1 Like

Đây là bài tập của 1 của mình. Xin mọi người đóng góp ý kiến :wink:

#include <iostream>
#include <cstring>
using namespace std;
int main()
{
	char arr[50];
	gets_s(arr,sizeof(arr));
	for (int x = 0; x <= strlen(arr); x++)
	{
		arr[x] = toupper(arr[x]);
	}
	cout << arr << endl;
	system("pause");
}
1 Like

thiế dấu = thành phép gán kìa bạn

1 Like

minh dùng VS 2015 khi viết code này:

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
	char str_source[] = "This is source string";
	char str_destination[30];

	strcpy(str_destination, str_source);
	cout << "str_destination: " << str_destination << endl;
	

	system("pause");
	return 0;
}

nó báo lỗi là :c:\program files (x86)\windows kits\10\include\10.0.10240.0\ucrt\string.h(119): note: see declaration of 'strcpy'

mình không biết nó bị sai chỗ nào? mong mọi người giúp đỡ.

1 Like
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?