Kiểu ký tự (char)

####Chào mừng các bạn đến với bài học tiếp theo trong khóa học lập trình ngôn ngữ C++ hướng thực hành.

Trong hầu hết tất cả các bài học trước đây, chúng ta chỉ làm việc cùng nhau trên kiểu dữ liệu số. Chúng ta sử dụng các biến lưu trữ giá trị số (số nguyên int, số thực như float hoặc double, …) để phục vụ cho việc tính toán toán học, giải quyết các bài toán đơn giản là chủ yếu.

Trong bài học ngày hôm nay, chúng ta sẽ tìm hiểu một kiểu dữ liệu cũng là một trong những kiểu dữ liệu cơ bản trong ngôn ngữ C và C++, đó là kiểu kí tự.

###Kiểu kí tự là gì?

Cũng tương tự như các kiểu dữ liệu số (int32_t, float, uint64_t, …), kiểu kí tự là một kiểu dữ liệu có độ lớn 1 byte (8 bits) dùng để lưu trữ 1 kí tự trong vùng nhớ máy tính. Kí tự có thể là các chữ cái đơn trong bảng chữ cái (a, b, c, … x, y z), có thể là các kí hiệu toán học (+, -, *, /, …), hay có thể là những con số (0, 1, 2, …, 9)…

Một đặc điểm của kiểu kí tự là KHÔNG PHẢI MỌI KÝ TỰ đều có thể hiển thị được lên màn hình.

Trong C/C++, kiểu kí tự có thể lưu trữ 1 kí tự trong bảng mã ASCII.

Đây là bảng mã kí tự ASCII đầy đủ:

Bảng mã ASCII được chia làm 2 cột:

  • Cột Code là số thứ tự của kí tự trong bảng mã ASCII.
  • Cột Symbol là kí tự được chuyển đổi từ mã Code sang dạng có thể đọc được.

###Khai báo biến kiểu kí tự như thế nào?

Để khai báo biến kiểu kí tự trong C/C++, ta dùng từ khóa char như sau:

char character; //declare a variable type char
char ch(65); //declare a variable type char and initialze with ASCII code
char a = 'a'; //declare a variable type char and initialize with a symbol of ASCII table

Cú pháp hoàn toàn giống việc thực hiện khai báo biến thông thường.

Biến kiểu kí tự (char) thực tế cũng là một kiểu số nguyên kích thước 1 byte (tương đương với int8_t), nó lưu trữ giá trị là mã Code của kí tự đó, nhưng khi hiển thị lên màn hình, nó cho ra kết quả là kí tự (Symbol) chứ không in ra mã ASCII của kí tự đó.

Vì thế, chúng ta có thể khởi tạo cho biến kiểu char bằng cách gán một kí tự đặt giữa cặp dấu nháy đơn. Ví dụ:

char ch = 'a';
ch = 'b';

hoặc cũng có thể gán trực tiếp mã Code của kí tự đó trong bảng mã ASCII. Ví dụ:

char ch = 97; //kí tự 'a' trong bảng mã ASCII có mã là 97
ch = 98; //kí tự 'b' trong bảng mã ASCII có mã là 98

###In biến kiểu kí tự ra màn hình

Để in một kí tự ra màn hình, chúng ta có thể thực hiện bằng nhiều cách khác nhau:

  • In trực tiếp một kí tự đặt trong cặp dấu nháy đơn:

      cout << 'h' << 'e' << 'l' << 'l' << 'o' << endl;
    

Ở câu lệnh trên, mình sử dụng đối tượng cout để in ra một dãy nhiều kí tự đơn nối tiếp nhau. Sau khi chạy chương trình, dòng lệnh trên sẽ in ra màn hình dãy kí tự hello.

  • Sử dụng biến kiểu char để lưu trữ một kí tự:

      char h = 'h', e = 'e', l = 'l', o = 'o';
      cout << h << e << l << l << o << endl;
    

Chúng ta có thể sử dụng lại nhiều lần 1 biến, nên mình chỉ cần khai báo 1 lần biến l để lưu trữ kí tự ‘l’. Câu lệnh trên cho kết quả hoàn toàn tương tự, dòng hello sẽ được in ra màn hình.

  • In trực tiếp mã Code của kí tự trong bảng mã ASCII (nhưng ép về kiểu char):

      cout << static_cast<char>(67) << static_cast<char>(43) << static_cast<char>(43) << endl;
    

Các bạn thử tra trong bảng mã ASCII xem thử hai số 6743 đại diện cho 2 kí tự gì nhé, sau đó đoán xem kết quả in ra màn hình của dòng lệnh trên là gì.

  • Chúng ta có thể in ra mã Code của 1 biến kí tự:

      char ch = 'V';
      cout << static_cast<int16_t>(ch) << endl;
    

Bằng cách ép kiểu của biến ch về kiểu số nguyên, chương trình sẽ in ra 1 con số là số thứ tự của kí tự đó trong bảng mã ASCII.

Như mình đã nói, kiểu kí tự (char) hoàn toàn là kiểu số nguyên (int8_t). Để in ra kí tự đại diện cho số nguyên đó, chúng ta cần định dạng cho nó là kiểu kí tự (char) thì compiler mới hiểu là chúng ta đang cần hiển thị kí tự chứ không phải con số.

Bây giờ chúng ta thử in ra toàn bộ bảng mã ASCII trên màn hình dưới dạng

<Code>: <Symbol>

bằng cách sử dụng 1 vòng lặp for để in ra toàn bộ kí tự từ mã 0 đến mã 127.

cout << "Code" << '\t' << "Symbol" << endl;
for(int16_t ascii_code = 0; ascii_code <= 127; ascii_code++)	{
	cout << ascii_code << '\t' << static_cast<char>(ascii_code) << endl;
}

Và kết quả in ra màn hình:

Chắc các bạn còn nhớ kí tự đặc biệt ‘\t’ tương đương với 1 Tab trên màn hình console. Nếu không nhớ thì cũng không sao, mình sẽ nhắc lại một chút ở phần bên dưới.

Có một số mã Code cho ra kí tự khoảng trắng vì đó là những kí tự đặc biệt, ví dụ mã 7 đại diện cho 1 tiếng Beep, nên nó không có kí tự để in ra.

###Nhập giá trị cho kiểu kí tự (char) từ bàn phím

Nhập kí tự từ bàn phím cũng tương tự việc nhập giá trị số từ bàn phím để gán cho biến. Chúng ta có thể sử dụng đối tượng cin như cách chúng ta sử dụng với biến số nguyên, số thực …

char ch;
cout << "Enter a character from your keyboard: ";
cin >> ch;
cout << ch << " has ASCII code " << static_cast<int16_t>(ch) << endl;

Các bạn cùng nhìn vào phần kết quả chương trình mình đã thực thi bên dưới:

Mình không thực hiện nhập 1 kí tự từ bàn phím, thay vào đó, mình nhập nhiều kí tự liên tiếp nhau, và điều gì xảy ra? Biến ch (kiểu char) chỉ nhận vào 1 kí tự duy nhất là kí tự đầu tiên mà mình nhập vào.

Vậy thì những kí tự còn lại sẽ đi đâu? Nó vẫn còn được lưu trữ tạm thời bên trong đối tượng file stdin. Để kiếm chứng điều này, mình thêm một đoạn mã nhỏ sau khi in ra kí tự của biến ch trên màn hình:

char ch;
cout << "Enter a character from your keyboard: ";
cin >> ch;
cout << ch << " has ASCII code " << static_cast<int16_t>(ch) << endl;

//check if there is any character exist in stdin file object
if (!cin.eof())	{
	cout << "There are some character more in stdin file object" << endl;
}

Nếu cin.eof() trả về giá trị là đúng, điều này có nghĩa chúng ta đã lấy hết kí tự trong đối tượng file stdin ra và đọc được kí tự kết thúc file (EOF = End of file). Vì thế, nếu điều này không xảy ra, tức là !cin.eof() là đúng, nghĩa là vẫn còn kí tự bên trong đối tượng file stdin.

Mình sẽ chạy lại chương trình với đoạn mã mà mình vừa thêm vào để các bạn cùng xem kết quả:

Mình nhập vào “Le Tran Dat” và biến ch (kiểu char) nhận vào kí tự đầu tiên (kí tự ‘L’), chương trình thông báo tiếp vẫn còn kí tự tồn tại bên trong đối tượng file stdin.

Vì thế, khi chúng ta tiếp tục thêm vào dòng lệnh nhập kí tự khác phía sau đoạn chương trình trên, nó sẽ không dừng lại chờ người dùng nhập kí tự nữa mà nó lấy luôn kí tự tiếp theo trong đối tượng file stdin để đưa vào biến. Các bạn cùng chạy đoạn mã lệnh sau để kiểm chứng kết quả:

char ch;
cout << "Enter a character from your keyboard: ";
cin >> ch;
cout << ch << " has ASCII code " << static_cast<int16_t>(ch) << endl;

//check if there is any character exist in stdin file object
if (!cin.eof())	{
	cout << "There are some character more in stdin file object" << endl;
}

//Continue reading a character from stdin file object
char next_ch;
cin >> next_ch;
cout << "The next character is " << next_ch << endl;

Và đây là những gì chương trình cho ra kết quả:

Biến ch nhận vào kí tự đầu tiên là ‘L’, biến next_ch lấy ngay kí tự ‘e’ mà không đợi người dùng nhập thêm kí tự khác.

Sẽ có trường hợp chúng ta chỉ muốn nhận vào biến kí tự đầu tiên chúng ta nhập vào, những kí tự thừa phía sau có thể là do chúng ta nhấn nhầm phím nào đó, và chúng ta muốn loại bỏ những kí tự thừa đi để nhập lại kí tự khác cho biến char tiếp theo. Trong trường hợp này, có hai cách để thực hiện xóa toàn bộ dữ liệu đang tồn tại trong đối tượng file stdin:

  • Sử dụng hàm fflush(FILE *file):

Đây là một hàm được định nghĩa trong ngôn ngữ C, nhưng chúng ta hoàn toàn có thể sử dụng nó trong ngôn ngữ C++. Hàm fflush nhận vào một đối tượng file mà chúng ta muốn xóa dữ liệu bên trong nó (trong trường hợp này là đối tượng file stdin).

fflush(stdin); //Add this command line where you want to clear all data in stdin file object
  • Sử dụng phương thức ignore của đối tượng cin để bỏ qua toàn bộ kí tự bên trong đối tượng file stdin:

Phương thức ignore này nhận vào 2 đối số là số kí tự nó sẽ bỏ qua, và kí tự khiến lệnh này dừng lại khi gặp phải trong đối tượng file stdin, ở đây mình sử dụng kí tự ‘\n’ là kí tự được tạo ra khi nhấn phím Enter.

cin.ignore( INT64_MAX, '\n');

Sau khi sử dụng một trong hai cách trên, lần yêu cầu nhập dữ liệu từ bàn phím tiếp theo (thông qua đối tượng cin) sẽ phải thực hiện nhập lại từ đầu.

###Một cách nhập dữ liệu khác cho kiểu kí tự (char)

Cũng sử dụng đối tượng cin thuộc thư viện iostream, nhưng không dùng toán tử >> mà sử dụng phương thức get của đối tượng cin. Có hai cách sử dụng phương thức get của đối tượng cin để nhập dữ liệu cho biến kiểu char, các bạn có thể nhớ 1 trong 2 cách mà bạn cảm thấy dễ hiểu nhất:

char ch;
ch = cin.get(); //get method return the character which you just entered
cin.get(ch); //put a char variable into the brackets

Cả 2 cách trên đều cho ra kết quả tương đương nhau.

###Escape sequences

Trong C/C++ có một số kí tự có ý nghĩa đặc biệt, nó được gọi là escape sequences. Một escape sequences bắt đầu bằng một dấu ‘’ và theo sau là một kí tự hoặc con số.

Ví dụ:

cout << "First line\nSecond line" << endl;

Dòng lệnh trên sẽ cho ra output là:

First line
Second line

Hay dòng lệnh dưới đây:

cout << "First part\tSecond part" << endl;

Sẽ cho chúng ta kết quả:

First part    Second part

Bạn chỉ có thể in ra kí tự nháy kép bằng cách thêm dấu backslash ‘’ trước kí tự nháy kép trong dãy kí tự bạn muốn in ra.

cout << "This is \"quote\" text" << endl;

###Có một số bạn thắc mắc rằng, kí tự xuống dòng ‘\n’ và đối tượng endl trong thư viện iostream khác nhau như thế nào?

Khi sử dụng std::endl (sử dụng toán tử phạm vi để truy cập vào đối tượng endl bên trong namespace std), output sẽ được đẩy vào vùng bộ nhớ đệm, đối tượng cout có thể không chuyển text trực tiếp đến thiết bị đầu ra ngay lập tức.

Cả kí tự ‘\n’ và đối tượng endl đều chuyển con trỏ đến vị trí đầu dòng tiếp theo, thêm vào đó, đối tượng endl đảm bảo thứ tự trên thiết bị đầu ra đúng với lúc nhập dữ liệu từ đầu vào.

cout << endl;

tương đương với

cout << '\n' << std::fflush;

Sử dụng đối tượng std::endl sẽ làm sạch luôn stream, trong khi đó, sử dụng kí tự ‘\n’ chỉ đơn giản là đưa kí tự xuống dòng lên màn hình.

Câu trả lời ngắn gọn cho việc khi nào sử dụng std::endl‘\n’ là:

  • Sử dụng std::endl khi bạn cần đảm bảo cho ra kết quả ngay lập tức, cụ thể khi làm việc trên các thiết bị đầu ra chậm.
  • Sử dụng ‘\n’ cho các trường hợp còn lại.

###Sự khác nhau khi đặt kí tự bên trong cặp dấu nháy đơn và cặp dấu nháy kép là gì?

Như đã học trong bài này, một biến kí tự (char) chỉ được dùng để đặc tả 1 kí tự trong bảng mã ASCII, và chúng ta luôn đặt 1 kí tự đơn bên trong 1 cặp dấu nháy đơn.

char ch('65');
ch = 'a';

Những kí tự được đặt bên trong cặp dấu nháy kép được gọi là chuỗi kí tự (string). Một string là một tập hợp các kí tự nối tiếp nhau. Ví dụ:

cout << "Hello everyone!" << endl; //Hello everyone is a string

Tất nhiên làm việc với chuỗi kí tự (string) sẽ phức tạp hơn, nên các bạn sẽ được học nó trong các bài học sau.

###Do stupid thing with char type

Trước khi kết thúc bài học này, mình sẽ hướng dẫn các bạn làm một cái gì đó với kiểu kí tự (char) mà các bạn đã được học.

Mình muốn thực hiện nhập họ và tên của mình (Viết không dấu do bảng mã ASCII bị giới hạn) từ bàn phím. Xóa màn hình console đi và in ra lại họ tên mà mình vừa nhập từ bàn phím, nhưng in ra lần lượt từng kí tự, mỗi lần in kí tự sẽ tạm dừng trong một khoảng thời gian ngắn.

Để thực hiện được yêu cầu này, mình sẽ cung cấp cho các bạn một số chức năng cần thiết:

  • system("cls");

Hàm này gọi đến lệnh cls, thực hiện xóa dữ liệu đã in ra trên console.

  • Sleep(DWORD miliseconds);

Hàm này sẽ tạm dừng mọi công việc thực hiện trên console trong một khoảng thời gian miliseconds mà bạn truyền vào. Để sử dụng hàm này cần thêm thư viện windows.h tại phần khai báo thư viện.

Các bạn chưa được học cách để lưu trữ biến là một chuỗi các kí tự liên tiếp nhau, nên việc lưu trữ dãy kí tự tên của bạn bên trong biến là rất khó khăn. Chúng ta chỉ mới biết đến cách lưu trữ 1 kí tự bên trong 1 biến kiểu char.

Nhưng thử nhớ lại những điều mình đã nói, khi thực hiện nhập kí tự từ bàn phím mà bạn nhập thừa kí tự thì điều gì xảy ra? Những kí tự thừa vẫn còn lưu trữ bên trong đối tượng file stdin, vì thế, chúng ta chỉ cần lấy những kí tự đó ra 1 lần nữa thông qua đối tượng cin.

Ban đầu, chúng ta yêu cầu người dùng nhập tên đầy đủ của mình vào:

char ch;
cout << "Enter your full name: ";
cin >> ch;

Kí tự đầu tiên mà bạn nhập sẽ lưu vào biến ch, những kí tự còn lại vẫn lưu trong đối tượng file stdin.

Tiếp theo, chúng ta thực hiện xóa màn hình console:

system("cls");

Công việc còn lại, chúng ta lấy lần lượt từng kí tự vẫn được lưu trong đối tượng file stdin cho đến khi gặp kí tự xuống dòng ‘\n’ hoặc kí tự kết thúc file EOF. Các bạn nhớ phải in kí tự đã lưu trong biến ch ra trước rồi mới đọc tiếp vào nhé.

do
{
	Sleep(50); //Pause the program for 50 miliseconds
	cout << ch;
	ch = cin.get();
} while (ch != '\n' && ch != EOF);

Vòng lặp trên sẽ dừng khi biến ch nhận được kí tự xuống dòng ‘\n’ (lúc bạn nhấn Enter để kết thúc nhập) hoặc kí tự kết thúc file EOF.

Kết hợp những phần trên thành một chương trình hoàn chỉnh:

#include <iostream>
#include <Windows.h>
using namespace std;

int main()	
{
	char ch;
	cout << "Enter your full name: ";
	cin >> ch;

	system("cls");

	do
	{
		cout << ch;
		ch = cin.get();
		Sleep(50);
	} while (ch != '\n' && ch != EOF);
	cout << endl;

	system("pause");
	return 0;
}

Các bạn thử chạy chương trình, nhập full-name của các bạn vào xem điều gì xảy ra nhé!


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

7 Likes

Ủa, em nhớ là có các ký tự từ 0 đến 255 mà :sweat_smile:, hình như thiếu rồi anh. Anh hình như chưa giới thiệu unsigned char nữa. :slight_smile:

Trong bảng mã ASCII chuẩn thì có 126 kí tự, bảng mã ASCII mở rộng thì có 256 kí tự bao gồm cả bảng ASCII chuẩn.

Cảm ơn anh đã chỉ giáo :smile:

Nhưng mà em nhớ là bảng mã ASCII chuẩn có 128 ký tự và anh. :sweat_smile:

đúng rồi bạn từ 0-127 là 128 đó :smiley:

1 Like

Làm các nào để in các icon ra khi chay the của m mấy ký tự mặt cưởi toàn in ra dấu " ? "

Mình chạy nó báo lỗi "2:21: fatal error: windows.h: No such file or directory
compilation terminated.". Không biết phải IDE thiếu thư viện này không

Có lẽ bạn bị thiếu thư viện rồi.

vậy làm sao để thêm thư viện vào?

khi đọc tới đoạn này:

ở trên anh dùng từ “hoặc” mà xuống dưới anh lại dùng toán tử “và”.

khi em dùng toán tử hoặc thì chương trình không dừng lại.
khi chỉ dùng "ch != ‘\n’ " thì chương trình dừng.
khi dùng toán tử “và” thì chương trình dừng, mọi người giải thích dùm em được không.

bạn dùng toán tử hoặc ( || ) có nghĩa là nếu một trong 2 điều kiện đúng thì vòng lặp sẽ thực thi
khi ch= ‘\n’ —> \n != EOF ----> điều kiện đúng
khi ch= ‘EOF’ —> EOF!=\n ---->điều kiện vẫn đúng
vậy nên sẽ lặp vô hạn :slight_smile:

1 Like

Cho em hỏi với ạ:
Cách nhập vào kiểu ký tự: cin >> ch; và cách nhập cin.get(ch); khác nhau như thế nào ạ?

Em nhập theo cách cin >> ch; thì biến ch không lấy được ký tự dấu cách (màn hình console không in ra ký tự dấu cách), còn nhập theo cách cin.get(ch) hay ch = cin.get() thì biến ch đều lấy được ký tự dấu cách (làn này màn hình console lại in ra được ký tự dấu cách).

Ai biết chỉ giúp em với ạ, em cảm ơn!

sao trên Visual Studio 2017 em xài fflush(stdin) mà không thấy nó dọn bộ nhớ của cin nhỉ?


1 Like

xài cin.ignore(100, '\n') để ignore dòng cũ và mấy ký tự thừa của dòng cũ. fflush(stdin) ko có chức năng này. Ai bảo có thì họ chưa đọc kỹ tài liệu, trình biên dịch nào ignore thông qua fflush(stdin) thì xóa nó lẹ dùm, trường nào dạy thì viết đơn xin đuổi việc thầy dạy đi, sách nào ghi thì đem đi đốt ngay.

4 Likes

100 cũng chưa chắc ăn :smiley: mà fflush(stdin) ngay từ đầu đã là không chuẩn, newb tập thói quen xem docs thôi.

1 Like

Thử cái này coi !

Đó là code C, còn topic này là C++.

2 Likes

Lập Topic mới để hỏi, tránh đào mộ topic cũ lên nhé !

Bạn thử những dòng này xem:

#include<chrono>
#include<thread>
//
std::this_thread::sleep_for(std::chrono::milliseconds(150));

Anh Vũ ơi, hình như cái hàm !cin.eof() có vấn đề, dù em có nhập 1 kí tự thôi thì nó vẫn hiện “There are some character more in stdin file object”

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