Chuyển tới nội dung chính

Chương 2: Cú pháp lập trình

Mở đầu về lập trình với cú pháp, thao tác cơ bản với dữ liệu trong C++


1. Cú pháp cơ bản

1.1. Cấu trúc chương trình C++

Cấu trúc của một đoạn mã tương ứng với một chương trình máy tính thường gồm các thành phần sau:

Thành phầnÝ nghĩa
#Tiền xử lýKhai báo thư viện và định nghĩa các marco.
Khai báo biến, hàm (prototype function)Khai báo các biến toàn cục và các hàm được sử dụng trong chương trình chính.
int main() { }Chương trình chính. Nội dung của hàm, thực hiện các chức năng chính của chương trình.
Định nghĩa hàm (define function)Định nghĩa thân hàm của các hàm đã được khai báo trong phần trước đó. Lưu ý: Nếu một hàm f đã được khai báo và có lời gọi hàm đến f thì bắt buộc phải có định nghĩa hàm của f.

Ví dụ: Xét chương trình tính diện tích hình tròn

// Tinh dien tich hinh tron
#include <iostream>
using namespace
#define PI 3.14
float TinhDienTich (int);
int main(){
float r;
cin >> r;
cout<< TinhDienTich (int r);
return r*r*PI;
}

Dòng lệnh 1: Dòng ghi chú.
Dòng lệnh 2: Khai báo cho biết trong chương trình có sử dụng các hàm hoặc biến (đối tượng) được khai báo và cài đặt trước trong thư viện iostream.
Dòng lệnh 3: Khai báo namespace std (để cùng cout, cin).
Dòng lệnh 4: Định nghĩa marco PI với giá trị 3.14
Dòng lệnh 5: Nguyên mẫu hàm TinhDienTich.
Dòng lệnh 6: Khai báo hàm chính của chương trình, đây là hàm bắt buộc phải có để chương trình có thể thực thi được.
Dòng lệnh 7: Khai báo biến r có kiểu dữ liệu float.
Dòng lệnh 8: Nhập giá trị r.
Dòng lệnh 9: Gọi hàm TinhDienTich và in ra kết quả.
Dòng lệnh 10: Lệnh return có tác dụng kết thúc hàm main.
Dòng lệnh 11: Là ký hiệu cho biết kết thúc nội dung của hàm main.
Dòng lệnh 12, 13, 14: Định nghĩa hàm TinhDienTich.


1.2. Bộ keywords trong C++

1.2.1. Ký tự (Character)

C++ sử dụng nhiều loại ký tự khác nhau để viết chương trình:

  • Chữ cái ký tự Latin: A, B, C, ..., Z, a, b, c, ..., z
  • Chữ số thập phân: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
  • Chú thích
    • Chú thích một dòng: //
    • Chú thích nhiều dòng: /* ... */
  • Dấu nháy đơn: Ký tự được đặt trong dấu nháy đơn ' ... '
  • Dấu nháy kép: Chuỗi được đặt trong dấu nháy kép " ... "
  • Ký tự điều khiển: sử dụng ký tự thoát (escape characters) là dấu gạch chéo ngược \ đặt ở đầu: \n (dòng mới), \t (tab ngang), \\ (ký tự gạch chéo ngược), \' (dấu nháy đơn), \" (dấu nháy kép), \0 (null)
  • Toán tử: +, -, *, /, %, ++, --, =, +=, -=, *=, /=, %=, ==, !=, >, <, >=, <=, &&, ||, !, ? ,= (toán tử điều kiện)
  • Ký tự phân tách: ; (dấu chấm phẩy), , (dấu phẩy)
  • Ký tự ngoặc: (, ) , {, } , [, ]
  • Ký tự toán tử bit: &, |, ^, ~, <<, >>
  • Ký tự điều khiển: # (cho các chỉ thị tiền xử lý)
  • Ký tự chấm: . (truy cập thành viên)
  • Ký tự mũi tên: -> (truy cập thành viên qua con trỏ)
  • Ký tự hai chấm: : (sử dụng trong toán tử ba ngôi và khai báo phạm vi), :: (toán tử phạm vi)
  • Ký tự dấu gạch dưới: _ (thường được sử dụng trong tên biến hoặc hàm)

1.2.2. Từ khóa (keyword)

Không thể sử dụng từ khóa để đặt tên cho biến, hàm, tên chương trình con.

Keyword chỉ chứa ký tự chữ cái thường.
Không có ký hiệu hoặc dấu câu đặc biệt nào được sử dụng trong từ khóa.

Một số từ khóa thông dụng:

  • Nhóm 1: Điều khiển luồng
    if, else, switch, case, default, for, while, do, break, continue, goto, return, try, catch, throw
  • Nhóm 2: Khai báo và định nghĩa
    • int, char, float, double, void, bool, short, long, signed, unsigned, enum
    • class, struct, union, typedef, constexpr, consteval, concept, decltype, inline, static, extern, mutable, thread_local, register
  • Nhóm 3: Truy cập và quản lý bộ nhớ
    new, delete, sizeof, alignas, alignof, reinterpret_cast, static_cast, dynamic_cast, const_cast
  • Nhóm 4: Lập trình hướng đối tượng
    class, public, private, protected, virtual, explicit, friend, this, operator
  • Nhóm 5: Không gian tên và mô-đun
    namespace, using, export, module, import
  • Nhóm 6: Từ khóa không đồng bộ và đồng thời
    co_await, co_return, co_yield, synchronized, atomic_cancel, atomic_commit, atomic_noexcept
  • Nhóm 7: Logic và toán tử
    and, or, not, bitand, bitor, compl, and_eq, or_eq, not_eq
  • Nhóm 8: Các từ khóa khác
    asm, auto, bool, false, true, nullptr, static_assert, noexcept, requires, reflexpr
  • Nhóm 9: Các kiểu dữ liệu và các từ khóa liên quan đến kiểu
    int, char, char8_t, char16_t, char32_t, float, double, void, bool

1.2.3. Dấu chấm phẩy

Dùng để phân cách các câu lệnh.

Các câu lệnh có thể viết trên 1 dòng, tuy nhiên luôn được phân cách bằng dấu chấm phẩy ;

cout << "Xin chao"; return 0;

1.2.4. Tên/ Định danh (Name/Indetifier)

Một dãy ký tự dùng chỉ tên hằng, biến, hàm.

Được tạo thành từ các chữ cái, chữ số, dấu gạch nối _

Quy ước đặt tên:

  • Không trùng với các từ khóa
  • Ký tự đầu tiên là chữ cái hoặc _
  • Tối đa 255 ký tự
  • Không được sử dụng khoảng trắng ở giữa các ký tự
  • Tên có phân biệt chữ hoa chữ thường (case sensitive).

1.2.5. Câu chú thích

Dùng để mô tả hoặc ghi chú trong source code, giúp dễ dàng đọc code sau này

Có 2 cách để viết chú thích trong C++

  • Cách 1: Viết chú thích trong /chú thích/
    /* Day la chu thich 1
    Day la chu thich 2 */
  • Cách 2: Viết chú thích sau // chú thích
    // Day la chu thich 1
    // Day la chu thich 2

Nên sử dụng nhất quán 1 cách. Cách 2 được sử dụng phổ biến hơn

Khi biên dịch source code thì các phần comments sẽ không được biên dịch


2. Dữ liệu trong lập trình

Về bản chất, lập trình là tập hợp các thao tác điều khiển và xử lý dữ liệu để giải quyết một bài toán cụ thể. Để một chương trình hoạt động hiệu quả, chúng ta không chỉ cần các dòng lệnh logic mà còn phải quản lý tốt "nguyên liệu" đầu vào và đầu ra.
Trong phần này, ta sẽ tìm hiểu các kiểu dữ liệu để phân loại dữ liệu, cách lưu trữ dữ liệu (biến) và các phép toán biến đối dữ liệu.

2.1. Kiểu dữ liệu

2.1.1. Khái niệm kiểu dữ liệu

Kiểu dữ liệu (data type) là kiểu hay loại của dữ liệu.
Ví dụ: giá trị dữ liệu 3.2 có kiểu là số thực và giá trị 5 có kiểu là số nguyên.

Để thuận tiện cho việc đọc và ghi các giá trị dữ liệu, các trình biên dịch thường quy ước mỗi kiểu dữ liệu sẽ chiếm một không gian lưu trữ nhất định trong bộ nhớ, nghĩa là cần một số bits cố định cho mỗi kiểu dữ liệu.

Lưu ý:

  • Để lưu trữ số nguyên có giá trị 0 và số nguyên có giá trị 1000 thì đều cần một số lượng bit như nhau vì chúng đều có kiểu là số nguyên.
  • Cùng một dữ liệu nhưng trên các trình biên dịch khác nhau có thể khác nhau.

Đặc điểm của kiểu dữ liệu: Các kiểu dữ liệu thường có 2 đặc điểm chính:

  • Bao gồm một tập hợp các giá trị mà biến thuộc kiểu đó có thể nhận
  • Các phép toán có thể thực hiện

Các kiểu dữ liệu trong ngôn ngữ lập trình:

  • Các kiểu được cung cấp bởi thư viện có sẵn
  • Các kiểu do lập trình viên định nghĩa (user defined types)

2.1.2 Kiểu dữ liệu số nguyên

Kiểu dữ liệu số nguyên được dùng để biểu diễn các giá trị nguyên (integer). Ký hiệu, kích thước và miền giá trị một số kiểu dữ liệu số nguyên được mô tả trong bảng sau:

Kiểu dữ liệuKích thước lưu trữ (bytes)Miền giá trị (Value range)
signed char1-128 → 127
signed short int2-32,768 → 32,767
signed int4-2,147,483,648 → 2,147,483,647
signed long4-2,147,483,648 → 2,147,483,647
signed long long int8-9,223,372,036,854,775,808 → 9,223,372,036,854,775,807
unsigned char10 → 255
unsigned short int20 → 65,535
unsigned int40 → 4,294,967,295
unsigned long int40 → 4,294,967,295
unsigned long long int80 → 18,446,744,073,709,551,615
  • Phạm vi các kiểu số nguyên nn bit có dấu: 2n12n11-2^{n-1} \to 2^{n-1} - 1.
  • Phạm vi các kiểu số nguyên nn bit không dấu: 02n10 \to 2^n - 1

2.1.3 Kiểu dữ liệu số thực

Kiểu dữ liệu số thực được dùng để biểu diễn các giá trị thực (real). Ký hiệu, kích thước và miền giá trị một số kiểu số thực được mô tả trong bảng sau:

Kiểu dữ liệuKích thước lưu trữ (bytes)Miền giá trị (Value range)Độ chính xác (precision)
float4[1.17549E−38, 3.50282E+38]~6–7 chữ số
double8[2.22507E−308, 1.79769E+308]~15–16 chữ số
long double12[3.4621E−4932, 1.1893E+4932]~18–19 chữ số

2.1.4 Kiểu luận lý/logic

Kiểu dữ liệu bool (Boolean) trong C++ được sử dụng để biểu diễn các giá trị logic, thường được gọi là "đúng" (true) hoặc "sai" (false).

Kiểu dữ liệuKích thướcGiá trị
bool1 byte- false: giá trị 0
- true: giá trị khác 0

2.1.5 Kiểu dữ liệu void

Kiểu dữ liệu void là một kiểu dữ liệu đặc biệt vì nó được dùng để biểu diễn cho dữ liệu không có giá trị cụ thể.

Kiểu dữ liệuKích thước lưu trữ (bytes)Miền giá trị (Value range)
void1

Kiểu dữ liệu void thường được sử dụng trong trường hợp sau:

  • Một hàm số mà nó không trả về giá trị kết quả nào cả.
  • Một hàm số mà tham số của nó không chấp nhận giá trị nào.
  • Con trỏ dùng để lưu trữ địa chỉ của một vùng nhớ mà kiểu dữ liệu của vùng nhớ này chưa xác định.

Lưu ý:

  • Kích thước các kiểu dữ liệu có thể khác nhau tùy theo trình biên dịch.
  • Để biết kích thước của một kiểu dữ liệu (hoặc một biến), ta có thể sử dụng toán tử sizeof(tên_kiểu_dữ_liệu).

2.2. Biến

2.2.1. Khái niệm biến

Biến là một khái niệm dùng để chỉ một vùng bộ nhớ được cấp phát nhằm mục đích lưu trữ giá trị (giá trị này có thể do người dùng nhập vào hoặc giá trị của một biểu thức; giá trị này có thể thay đổi trong quá trình thực hiện chương trình hoặc trong các lần chương trình được thực thi khác nhau) và được truy xuất thông qua một tên đã được khai báo cho biến đó.
Mỗi biến sẽ có một kiểu dữ liệu tương ứng với kiểu của các giá trị dữ liệu sẽ được lưu trữ.

2.2.2. Khai báo biến

Cú pháp khai báo biến có dạng chung:

<Kiểu_dữ_liệu> tên_biến;

Trong đó:

  • <Kiểu_dữ_liệu> xác định kiểu của dữ liệu được lưu trữ, còn được gọi là kiểu dữ liệu của biến.
  • Tên_biến là một định danh được gán cho một vùng nhớ tương ứng. Ta có thể truy xuất dữ liệu được lưu trữ tại vùng nhớ đó qua tên biến.
  • Dấu ; dùng để xác định sự kết thúc của câu lệnh khai báo biến.

Ta có thể thực hiện khai báo biến và khởi tạo giá trị cho biến theo cú pháp:

<Kiểu_dữ_liệu> tên_biến = giá_trị_khởi_tạo;

Lưu ý: Giá trị của biến chính là giá trị được lưu trữ trong vùng nhớ dành cho biến đó.

Trong một dòng lệnh có thể khai báo nhiều biến có cùng kiểu bằng cách phân tách các biến đó bởi dấu phẩy , theo cú pháp như sau:

<Kiểu_dữ_liệu> tên_biến_1, tên_biến_2, tên_biến_3;

Lưu ý:

  • Luôn luôn khai báo biến trước khi sử dụng biến đó.
  • Nếu một biến X được sử dụng nhưng chưa được khai báo trong các dòng lệnh trước đó thì trình biên dịch thường báo lỗi: "error: 'X' was not declared in this scope"

2.2.3. Quy tắc/ cách thức đặt tên biến

Khi đặt tên biến cần lưu ý những vấn đề sau:

  • Không được sử dụng khoảng trắng (space) ở giữa các ký tự.
  • Không bắt đầu bằng chữ số.
  • Không trùng tên với các từ khóa hoặc tên hàm.
  • Ký tự đầu tiên là các chữ cái hoặc _.
  • Nên sử dụng tất cả chữ thường với dấu _ giữa các từ.

Ví dụ: Giả sử cần khai báo một biến để chứa giá trị diện tích của hình tròn. Khi đó:

  • Khai báo double Dien tich; là khai báo sai vì có khoảng trắng bên trong tên biến.
  • Khai báo double Dientich; hoặc double Dien_tich; là khai báo đúng.

2.2.4. Phạm vi của biến

Mỗi biến sẽ có một phạm vi tồn tại và hoạt động trong chương trình. Dựa trên phạm vi hoạt động, người ta thường chia các biến thành biến toàn cục (global) và biến cục bộ (local).

  • Biến toàn cục: nếu biến được khai báo ở bên ngoài khối lệnh (hoặc ở bên ngoài các hàm) thì biến đó được gọi là biến toàn cục so với khối lệnh (hoặc các hàm) đó. Thời gian tồn tại và phạm vi hoạt động của nó sẽ tính từ vị trí khai báo đến hết khối lệnh chứa biến (hoặc chương trình).
  • Biến cục bộ: là biến được khai báo bên trong khối lệnh (hoặc bên trong phạm vi của hàm). Phạm vi sử dụng của biến là bên trong khối lệnh (hoặc trong phạm vi của hàm) mà nó được khai báo. Thời gian tôn tại của biến là thời gian bắt đầu chương trình sử dụng khối lệnh (hoặc hàm) đó đến khi kết thúc khối lệnh (hoặc hàm).

Lưu ý:
Tính toàn cục và cục bộ của các biến là tương đối, nghĩa là nếu biến z là biến cục bộ so với biến y thì biến y sẽ là biến toàn cục đối với z, tuy nhiên, biến y có thể là biến cục bộ so với biến x và biến x là biến toàn cục so với biến y.

Ví dụ:

...
int x;
{
int y;
{
int z;
}
}
...

Biến cục bộ có thể trùng tên với biến toàn cục. Khi đó, để truy xuất đến biến toàn cục ta sử dụng dấu 4 chấm :: để truy xuất.

Ví dụ:

//CODE                // Giải thích
#include <iostream>
using namespace std;

int x = 9; // Biến x toàn cục

int main() {
cout << x; // In ra giá trị x là 9
int x = 1; // Biến x cục bộ trong hàm main
{
cout << x; // In ra giá trị x là 1
int x = 7; // Biến x cục bộ trong khối lệnh con
cout << x; // In ra giá trị x là 7
cout << ::x; // Sử dụng toán tử :: để in ra x toàn cục (9)
}
cout << x; // In ra giá trị x là 1 (x của hàm main)
cout << ::x; // In ra giá trị x là 9 (x toàn cục)
return 0;
}

2.2.5. Một số loại biến thường gặp

  • Biến thông thường (normal): là biến đơn giản, được dùng để lưu giá trị thông thường.
  • Biến con trỏ (pointer): là biến mà giá trị của nó là địa chỉ. Thông thường ta dùng biến này khi cần lưu trữ địa chỉ của một vùng nhớ.
  • Biến tham chiếu (reference): là biến nhưng nó không được trình biên dịch cấp phát ô nhớ riêng và phải sử dụng chung ô nhớ với biến được tham chiếu.

2.3. Hằng

2.3.1. Khái niệm hằng

Hằng là các đại lượng mà giá trị của nó không thay đổi trong quá trình thực hiện chương trình hoặc giữa các lần chương trình được thực thi khác nhau.

2.3.2. Khai báo hằng

Cách 1: dùng chỉ thị tiền xử lý "preprocessor definition" theo cú pháp sau:

#define tên_hằng giá_trị_hằng 

Lưu ý: Tên hằng thông thường được khai báo bằng chữ in hoa.

Cách 2: dùng biến hằng theo cú pháp sau:

const kiểu_dữ_liệu tên_biến = giá_trị_khởi_tạo ; 

Ví dụ:

// --- Cách 1: Sử dụng tiền xử lý #define ---
#define PI 3.14

int main() {
double R = 0, S;
// Tính diện tích hình tròn
S = PI * R * R;
return 0;
}
// --- Cách 2: Sử dụng biến hằng const (Khuyên dùng trong C++) ---
const double PI = 3.14;

int main() {
double R = 0, S;
// Tính diện tích hình tròn
S = PI * R * R;
return 0;
}

2.4. Các phép toán

Lập trình là quá trình điều khiển dữ liệu thông qua các biểu thức. Một biểu thức biến đổi dữ liệu hoàn chỉnh thường bao gồm:

  • Biến: Nơi lưu trữ dữ liệu cần xử lý.
  • Giá trị: Các dữ liệu trực tiếp dùng trong tính toán (ví dụ: 1, 9, 3.14).
  • Toán tử (Operators): Công cụ thực hiện việc biến đổi dữ liệu. Kết quả của các toán tử là một dữ liệu mới và được gán vào biến.

Như vậy, quy trình xử lý dữ liệu thực chất là việc lấy giá trị từ biến, dùng toán tử để tính toán và gán lại kết quả mới vào biến để điều khiển dữ liệu. Trong phần này, ta sẽ tìm hiểu các loại toán tử phổ biến trong C++ và các ngôn ngữ lập trình khác.

2.4.1. Toán tử gán

Toán tử gán = cho phép gán trực tiếp một giá trị vào một biến, hoặc gán giá trị từ biến này sang biến.

tên_biến = giá_trị;

Ví dụ:

int a = 5 // Gán biến cho biến a giá trị 5
int b = a // Gán cho biến b giá trị của biến a (5)

2.4.2. Toán tử số học

Ký hiệu phép toánPhép toán
+Cộng
-Trừ
*Nhân
/Chia
%Lấy phần dư

Các phép toán tử thực hiện phép toán theo khác quy luật số học trên kiểu số nguyên, số thực

Kiểu trả về của các toán tử trên là kiểu lớn nhất trong các giá trị tham gia nếu không thực hiện ép kiểu.

Ví dụ:

int a = 1, b = 2;
double c = 1.5;

int n = a / b; // n sẽ bằng 0, do kết quả trả về là một số nguyên nên 0.5 được chuyển về số nguyên là 0
int i = b / c; // i sẽ bằng 0, kết quả phép chia trả về là double mang giá trị 1.33333, tuy nhiên ta thực hiện ép kiểu double xuống int nên i còn giá trị 1
double d = b / c // d sẽ bằng 1.33333, do kiểu của d cùng với kiểu trả về của phép chia.

Toán tử chia lấy phần dư (%) chỉ được áp dụng trên các toán hạng có kiểu số nguyên. Nếu áp dụng trên kiểu dữ liệu khác thì trình biên dịch sẽ báo lỗi.

2.4.3. Phép toán tăng/ giảm một đơn vị

Toán tử tăng (++) tăng 1 đơn vị cho toán hạng của nó, toán tử giảm (--) trừ 1 đơn vị cho toán hạng của nó.

Các toán tử tăng, giảm chỉ áp dụng cho toán hạng là các biến dạng số hoặc con trỏ.

Ví dụ:

  • ++x; tương đương x += 1; tương đương x = x+1;
  • Biểu thức (i+j)++ \rightarrow không hợp lệ

Giả sử x là biến (toán hạng) sử dụng toán tử ++, --, ta có các dạng sau:

  • ++x : Đây được gọi là Prefix increment. Trong biểu thức chứa ++x, x sẽ tăng lên 1 đơn vị trước tiên, sau đó giá trị cập nhật sẽ được sử dụng.
  • x++ : Đây được gọi là Postfix increment. Trong biểu thức chứa x++, biến x sử dụng giá trị hiện tại của x (chưa tăng lên 1 đơn vị), tính xong biểu thức rồi cuối cùng mới tăng x lên 1 đơn vị.
  • --x : Đây được gọi là Prefix decrement. Trong biểu thức chứa --x, biến x sẽ giảm xuống 1 đơn vị trước, sau đó giá trị cập nhật sẽ được sử dụng.
  • x-- : Đây được gọi là Postfix decrement. Trong biểu thức chứa x--, biến x sử dụng giá trị hiện tại của x (chưa giảm 1 đơn vị), tính xong biểu thức rồi cuối cùng mới giảm x 1 đơn vị.

Ví dụ:

#include <iostream>
using namespace std;

int main() {
int a = 5;
int b = 5;

// 1. Prefix increment (++a): Tăng trước, dùng sau
cout << "Gia tri cua ++a: " << ++a << endl;
// Giải thích: a tăng lên 6 trước, sau đó lệnh cout mới in giá trị 6 ra.

// 2. Postfix increment (b++): Dùng trước, tăng sau
cout << "Gia tri cua b++: " << b++ << endl;
// Giải thích: Lệnh cout in giá trị hiện tại của b là 5 ra trước,
// sau khi in xong thì b mới âm thầm tăng lên 6.

// 3. Kiểm tra lại giá trị của b sau khi thực hiện b++
cout << "Gia tri cua b sau khi in dong tren: " << b << endl;

return 0;
}

2.4.4. Toán tử kết hợp

Toán tửVí dụTương đươngÝ nghĩa
+=x += 5x = x + 5Cộng và gán
-=x -= 5x = x - 5Trừ và gán
*=x *= 5x = x * 5Nhân và gán
/=x /= 5x = x / 5Chia và gán
%=x %= 5x = x % 5Chia lấy dư và gán
<<=x <<= 5x = x << 5Dịch trái và gán
>>=x >>= 5x = x >> 5Dịch phải và gán
&=x &= 5x = x & 5Bitwise AND và gán
^=x ^= 5x = x ^ 5Bitwise XOR và gán
|=x |= 5x = x | 5Bitwise OR và gán

2.4.5. Toán tử bit

  • Toán tử bit (bitwise operators) trong C++ là các toán tử thao tác trực tiếp trên các bit của toán hạng (nguyên).
  • Gồm các toán tử: &(and), | (or), ^ (xor), ~ (not hay lấy số bù 1), >> (shift bits right: dịch phải), << (shift bits left: dịch trái)

Bảng chân trị các phép toán logic cơ bản:

pqp & q (AND)p ^ q (XOR)p | q (OR)~p (NOT)
000001
010111
111010
100110

Ví dụ:

  • 5 & 3 Kết quả: 1 (0101 & 0011 = 0001)
  • 5 | 3 Kết quả: 7 (0101 | 0011 = 0111)
  • 5 ^ 3 Kết quả: 6 (0101 ^ 0011 = 0110)
  • ~5 Kết quả: -6 (bit đảo ngược của 0101 là 1010, tương đương với -6)
  • 12 >> 3 = 1100 >> 3 = 0001 = 1 (toán tử dịch phải)
  • 3 << 2 = 0011 << 2 = 1100 = 12 (toán tử dịch trái)

2.4.6. Toán tử điều kiện - Conditional Ternary Operator

Toán tử điều kiện là một toán tử được áp dụng để xác định biểu thức thứ nhất hay thứ hai được thực hiện tùy theo giá trị đúng hay sai của một điều kiện. Cú pháp:

<điều kiện> ? biểu_thức_1> : <biểu_thức_2> 

Ví dụ: Giữa hai số a,b hãy tìm số nào lớn hơn.

a > b ? max = a : min = b

2.4.7. Toán tử quan hệ - Relational operators

OperatorToán tửKý hiệuVí dụGiải thích
Greater thanLớn hơn>x > yNếu x lớn hơn ytrue (1)
Ngược lại → false (0)
Less thanNhỏ hơn<x < yNếu x nhỏ hơn ytrue (1)
Ngược lại → false (0)
Greater than or Equal toLớn hơn hoặc bằng>=x >= yNếu x lớn hơn hoặc bằng ytrue (1)
Ngược lại → false (0)
Less than or Equal toNhỏ hơn hoặc bằng<=x <= yNếu x nhỏ hơn hoặc bằng ytrue (1)
Ngược lại → false (0)
Equal toBằng==x == yNếu x bằng ytrue (1)
Ngược lại → false (0)
Not equal toKhác!=x != yNếu x khác ytrue (1)
Ngược lại → false (0)

Ví dụ:

(7 == 5) // evaluates to false
(5 > 4) // evaluates to true

2.5.8. Toán tử luận lý - Logical operators

Toán tửKý hiệuVí dụGiải thích
NOT!!XToán tử một ngôi, thể hiện điều kiện "phủ định (not) X".
AND&&X && YToán tử hai ngôi, thể hiện điều kiện "X và (and) Y".
OR||X || YToán tử hai ngôi, thể hiện điều kiện "X hoặc (or) Y".

Ví dụ:

XY!XX && YX || Y
True (nonzero)False (0)
False (0)True (1)
True (nonzero)True (nonzero)True (1)True (1)
True (nonzero)False (0)False (0)True (1)
False (0)True (nonzero)False (0)True (1)
False (0)False (0)False (0)False (0)

Phân biệt giữa toán tử luận lý và toán tử bit

Đặc điểmToán tử Luận lý (Logical)Toán tử Bit (Bitwise)
Ký hiệu&&, ||, !&, |, ^, ~, <<, >>
Đối tượngBiểu thức Đúng/Sai (Boolean).Các bit nhị phân của số nguyên.
Kết quảLuôn là 0 (False) hoặc 1 (True).Một giá trị số nguyên mới.
Cơ chếKiểm tra toàn bộ giá trị (0 hoặc khác 0).Thao tác trên từng cặp bit tương ứng.

Ví dụ:

#include <iostream>
using namespace std;

int main() {
int a = 2; // Nhị phân: 0010
int b = 1; // Nhị phân: 0001

// So sánh toán tử AND
cout << "Logical AND: " << (a && b) << endl; // Xuất ra 1
cout << "Bitwise AND: " << (a & b) << endl; // Xuất ra 0 (0000)

// So sánh toán tử OR
cout << "Logical OR: " << (a || b) << endl; // Xuất ra 1
cout << "Bitwise OR: " << (a | b) << endl; // Xuất ra 3 (0011)

return 0;
}

2.5. Biểu thức và độ ưu tiên toán tử

2.5.1. Biểu thức

Định nghĩa: Biểu thức được tạo thành được từ các toán hạng (Operand) (hằng số, biến số và hàm số) liên kết với nhau bằng các toán tử (Operator). Trong đó:

  • Toán tử tác động lên các giá trị của toán hạng và cho giá trị có kiểu nhất định.
  • Toán hạng: hằng, biến, lời gọi hàm...

Giá trị của biểu thức là giá trị kết quả từ các toán tử tác động lên các toán hạng tương ứng.

#include <iostream>
using namespace std;
int main() {
int a = 2 + 3; //Giá trị 2+3 là 5, nên a được khởi tạo là 5
int b = a / 5; //Giá trị a/5 là 1, nên b được khởi tạo là 1
int c = (a + b) * 5; //Biểu thức (a+b) được thực hiện trước, sau đó kết quả nhân với 5
return 0;
}

2.5.2. Độ ưu tiên toán tử

MứcToán tửMô tảThứ tự thực hiện
1::Phân giải phạm vi (Scope resolution)
2++ --
() []
. ->
Hậu tố (Postfix increment/decrement)
Gọi hàm, truy cập mảng
Truy cập thành viên (biến/con trỏ)
Trái sang Phải
3++ --
+ -
! ~
(type)
* &
sizeof
new delete
Tiền tố (Prefix increment/decrement)
Cộng/Trừ nhất phân (Unary plus/minus)
Logic NOT, Bitwise NOT
Ép kiểu (C-style cast)
Giải tham chiếu (Indirection), Lấy địa chỉ
Kích thước kiểu dữ liệu
Cấp phát/Giải phóng bộ nhớ
Phải sang Trái
4.* ->*Con trỏ tới thành viên (Pointer to member)Trái sang Phải
5* / %Nhân, Chia, Chia lấy dưTrái sang Phải
6+ -Cộng, TrừTrái sang Phải
7<< >>Dịch bit trái/phảiTrái sang Phải
8< <= > >=Các toán tử quan hệ (So sánh)Trái sang Phải
9== !=So sánh bằng và khácTrái sang Phải
10&Bitwise ANDTrái sang Phải
11^Bitwise XORTrái sang Phải
12|Bitwise ORTrái sang Phải
13&&Logic ANDTrái sang Phải
14||Logic ORTrái sang Phải
15?:Toán tử điều kiện (Ternary)Phải sang Trái
16=
+= -= *= /= %=
<<= >>=
&= ^= |=
Gán trực tiếp
Gán kết hợp số học
Gán dịch bit
Gán toán tử bit
Phải sang Trái
17throwNém ngoại lệ (Exceptions)Phải sang Trái
18,Toán tử phẩyTrái sang Phải

Một biểu thức có nhiều phép toán, thì quy tắc thực hiện như sau:

  • Thực hiện biểu thức trong ( ) sâu nhất trước.
  • Thực hiện theo thứ tự ưu tiên các toán tử.
  • Khi một biểu thức có hai toán tử có cùng mức độ ưu tiên: thực hiện phép toán từ trái sang phải hoặc từ phải sang trái sẽ theo quy ước chung của nhóm phép toán đó.
  • Việc đặt tất cả các câu lệnh phụ trong dấu ngoặc đơn (ngay cả những câu lệnh không cần thiết vì mức độ ưu tiên của chúng) sẽ cải thiện khả năng đọc mã.

Ví dụ: Tính biểu thức:

int a = 5, b = 10, c = 15;  
bool result = (a + b * c > b) && (c / a == b % c) || (a - b < c);

Ta có:

  • (a + b * c > b) = ((a + (b * c)) > b) = ((5+(10*15)) > 10) = true
  • (c / a == b % c) = ((c / a) == (b % c)) = (15 / 5 == 10 % 15) = false
  • (a - b < c) = ((a – b) < c) = (5 - 10 < 15) = true

\Rightarrow result = true && false || true (&& ưu tiên hơn ||)
\Rightarrow result = (true && false) || true
\Rightarrow result = true


3. Nhập xuất dữ liệu

Trong lập trình, dữ liệu có thể gán bằng code, tính toán và lưu vào chương trình. Bên cạnh đó, ta có thể cho người dùng nhập dữ liệu và chương trình, chương trình thực hiện tính toán, biến đổi và trả về kết quả có thể tương tác với người dùng.

Trong môn Nhập môn lập trình, quá trình tương tác dữ liệu sẽ diễn ra ở hình thức đơn giản nhất là màn hình console. Ngoài ra, phần này sẽ giới thiệu cách đọc và ghi dũ liệu vào file.

3.1. Nhập xuất qua màn hình console

3.1.1. Thư viện nhập xuất

Có 2 loại thư viện nhập xuất trong C++

  • Thư viện nhập xuất <stdio.h> (kế thừa từ ngôn ngữ C)
  • Thư viện nhập xuất tiêu chuẩn <iostream> của C++.
Ngôn ngữ C++Ngôn ngữ C
Khai báo thư viện và namespace#include <iostream>
using namespace std;
#include <stdio.h>
Lệnh nhậpcin >>
cin.get, cin.getline,
getline (<string>)
scanf
fgetc, getc, getchar, fgets
Lệnh xuấtcout <<printf
putchar, putc, puts, fputs

3.1.2. Câu lệnh xuất: cout

  • cout là một đối tượng giúp bạn có thể hiện thị nội dung như : số nguyên, số thực, giá trị của biến, đoạn text ra màn hình. Để sử dụng đối tượng này bạn cần khai báo thư viện iostream và sử dụng namespace std
  • cout được sử dụng đi kèm với toán tử chèn <<
  • Cú pháp:
    #include <iostream> 
    ...
    std:: cout << Tham_số_1 << Tham_số_2 << ... << Tham_số_k;
    hoặc
    #include <iostream> 
    using namespace std;
    ...
    cout << Tham_số_1 << Tham_số_2 << ... << Tham_số_k;
  • Tham số có thể:
    • Chuỗi hằng
    • Biến, hằng số, biểu thức, hàm
    • Ký tự điều khiển (escape sequence)

      Ký tự điều khiển (escape sequence): Gồm dấu \ và một ký tự
      Ví dụ \n dùng để xuống dòng, \t cùng để cách vào một khoảng tab

  • Ngoài \n, endl là một đối tượng đặc biệt giúp bạn có thể xuống dòng khi sử dụng cout, bạn sẽ thêm đối tượng này trong câu lệnh cout tại vị trí bạn muốn xuống dòng.
    #include <iostream>
    using namespace std;
    int main() {
    cout << "Hello UIT\n"; //Hiển thị dòng Hello UIT lên màn hình và xuống dòng
    cout << 100 << endl; //Hiển thị số 100 lên màn hình và xuống dòng
    cout << banKinh << endl; //Hiển thị giá trị của biến banKinh lên màn hình và xuống dòng
    }

    Lưu ý: nếu không khai báo namespace std thì phải dùng đầy đủ cú pháp

    std::cout << std::endl

Ví dụ:

#include <iostream>
using namespace std;
int main(){
cout << "UIT" << endl;
cout << "TRUONG DAI HOC CNTT" << " " << "UIT" <<endl;
cout << 100 << " " << 200 << " " << 300 << endl;
int n=282828;
cout << "Gia tri cua bien n: " << n <<endl;
}

Output:

UIT
TRUONG DAI HOC CNTT UIT
100 200 300
Gia tri cua bien n : 282828

Lưu ý khi in giá trị của số thực float, double

Đối với kiểu dữ liệu số thực, đề bài thường yêu cầu bạn in ra các số này với độ chính xác nhất định, cụ thể là bao nhiêu chữ số phần thập phân

Mặc định chương trình sẽ in ra 6 chữ số có nghĩa kể cả phần nguyên và phần thập phân.

#include<iostream>
using namespace std;
int main() {
cout << 1.123456789 << " " << 1234.123456789 << " " << 123456.12345;
}

Output:

1.12346 1234.12 123456

Để in ra giá trị số float, double với độ chính xác k chữ số sau dấu phẩy bạn thêm cụm << fixed << setprecision(k) trước tên biến.

Thư viện chứa đối tượng setprecision là thư viện <iomanip>, bạn cần thêm vào trước khi sử dụng.

#include <iostream>
#include <iomanip>
using namespace std;
int main() {
float a = 30.192387;
double b = 9.18293;
cout << fixed << setprecision(2) << a << endl;
cout << fixed << setprecision(10) << b << endl;
return 0;
}

Output

30.19 

9.1829300000

Xác định độ rộng khi in với setw và setfill

Khi muốn in ra giá trị của 1 số, 1 biến hay đoạn text với độ rộng tương đương bao nhiêu dấu cách bạn có thể sử dụng setw(k) với k là độ rộng trước tên biến hay nội dung bạn muốn in.

Trong trường hợp bạn muốn thay thế các dấu cách khi nội dung bạn in ra không đủ độ rộng chỉ định bạn có thể sử dụng setfill(c) với c là ký tự bạn muốn thay thế cho các dấu cách trống.

#include <iostream>
#include <iomanip>
using namespace std;
int main(){
int n = 12345;
cout << setw(10) << n << endl;
cout << setw(10) << setfill('0') << n << endl;
cout << setw(10) << setfill('#') << "UIT" << endl;
return 0;
}

Output

       12345 

000012345

#######UIT

3.1.3. Câu lệnh nhập: cin

  • cin là một đối tượng được sử dụng để nhập dữ liệu từ thiết bị nhập chuẩn (thông thường là bàn phím).
  • Tương tự như cout, để sử dụng cin bạn cần khai báo thư viện <iostream> và sử dụng namespace std
  • cin được sử dụng đi kèm với toán tử trích xuất (extraction operator) >>. Toán tử này sẽ điều hướng dữ liệu từ bàn phím vào biến mà bạn chỉ định.
  • Cú pháp:
    #include <iostream>
    // Cách 1: Sử dụng namespace std
    using namespace std;
    cin >> ten_bien;

    // Cách 2: Không sử dụng namespace std
    std::cin >> ten_bien;
  • Các biến cin có thể nhập thuộc các kiểu dữ liệu cơ bản int, long long, short, float, double, char, bool, string
    • Khi nhập số (int, double,...) cin sẽ tự động bỏ qua các khoảng trắng, dấu tab hoặc dấu xuống dòng ở đầu cho đến khi thấy một con số. Nếu không tìm thấy số trong dữ liệu nhập, chương trình sẽ báo lỗi.
    • Khi nhập ký tự (char) cin >> sẽ lấy ký tự không phải khoảng trắng đầu tiên mà nó thấy.
    • Khi nhập bool, theo mặc định phải nhập một giá trị số tương ứng với true (số khác 0) và false (số 0)
      • Nếu không tìm thấy giá trị số trong dữ liệu nhập, chương trình sẽ báo lỗi.
      • Trong nhiều trình biên dịch, chương trình sẽ gán cho biến bool giá trị mặc định là 0 và không báo lỗi. Đó là lý cho khi bạn nhập true (hay false) vào một biến bool, giá trị biến sẽ là 0. Do truefalse là một chuỗi không hợp lệ với bool.
      • Nếu muốn chương trình hiểu true và false không phải một dữ liệu chuỗi, có thể dùng lệnh cin >> boolalpha >> bien_bool để nhập dữ liệu cho bien_bool dưới dạng true hoặc false. Trường hợp này sẽ ngược lại với trên, kể cả khi bạn nhập giá trị số hợp lệ, giá trị bool vẫn sẽ là 0. boolalpha chỉ cho biến bool nhận đúng giá trị khi nhập true hoặc false.
    • Khi nhập string, giá trị nhận được sẽ là chuỗi các ký tự cho trước khi gặp khoảng trắng, tab hay xuống dòng. Ta sẽ xem kỹ hơn hướng xử lý khi nhập chuỗi ở bên dưới.
  • Có thể nhập liên tiếp cho nhiều biến trong cùng một câu lệnh, với kiểu dữ liệu không nhất thiết giống nhau
    cin >> bien_1 >> bien_2 >> ... >> bien_k;
    Khi đó, các biến sẽ được phân cách bằng một ký tự trắng (white space characters). White space character bao gồm:
    • Ký tự khoảng trắng (ASCII code = 32): Khi nhấn SPACE
    • Ký tự tab \t: Khi nhấn TAB
    • Ký tự xuống dòng \n: Khi nhấn ENTER

Nhập chuỗi (string)

Vì lý do trên, Khi sử dụng cin để nhập một chuỗi ký tự, chương trình sẽ ngừng đọc ngay khi gặp khoảng trắng đầu tiên.
Nếu muốn nhập một chuỗi có dấu cách (ví dụ: Họ và tên), ta cần dùng hàm getline()

#include <iostream>
#include <string> // Cần thêm thư viện này để dùng string và getline
using namespace std;

int main() {
string hoTen, hoTenFull;
cout << "Nhap ho va ten 1: ";
cin >> hoTen;
cout << "Nhap ho va ten 2: ";
getline(cin, hoTenFull); // Doc toan bo dong text
cout << endl << hoTen << endl << hoTenFull;
return 0;
}

Input:

Chao Ban hoc tap

Output:

Nhap ho va ten 1: Chao Ban hoc tap
Nhap ho va ten 2:
Chao
Ban hoc tap

Trong ví dụ trên, khi nhập xong Chao Ban hoc tap và nhấn Enter, toàn bộ chuỗi này (bao gồm cả các khoảng trắng và ký tự xuống dòng \n) sẽ được đưa vào bộ nhớ đệm.

  • Khi lệnh cin >> chỉ tìm và lấy các ký tự không phải khoảng trắng. Nó đọc chữ "Chao" và dừng lại ngay khi chạm mặt khoảng trắng đầu tiên. Quan trọng là, nó để lại khoảng trắng sau đó cùng phần nội dung phía sau trong bộ nhớ đệm.
  • Lệnh getline(cin, hoTenFull); bắt đầu đọc ngay tại vị trí hiện tại của bộ nhớ đệm (vị trí dấu cách mà cin vừa bỏ lại) cho đến khi gặp ký tự xuống dòng \n. Vì vậy, nó thu nhận cả dấu cách ở đầu, khiến biến hoTenFull có giá trị là " Ban hoc tap" (bao gồm khoảng trắng).

Hiện tượng xuất hiện kí tự thừa này cũng xảy ra tương tự với \n, \t khi thực hiện getline(cin, ...) sau cin >>. Lệnh cin >> không đọc những ký tự này mà đẩy nó sang phần dữ liệu tiếp theo của getline, nên dữ liệu in ra không như mong muốn.

Để loại bỏ ký tự rác này, C++ cung cấp hàm cin.ignore() trước khi getline để dọn dẹp bộ nhớ đệm trước khi thực hiện lệnh nhập chuỗi tiếp theo.

#include <iostream>
#include <string>
using namespace std;

int main() {
string hoTen, hoTenFull;
cout << "Nhap ho va ten 1: ";
cin >> hoTen;

cin.ignore();
cout << "Nhap ho va ten 2: ";
getline(cin, hoTenFull);
cout << endl << hoTen << endl << hoTenFull;
return 0;
}

Input:

Chao Ban hoc tap

Output:

Nhap ho va ten 1: Chao Ban hoc tap
Nhap ho va ten 2:
Chao
Ban hoc tap

Ban hoc tap in ra không chứa ký tự thừa, kể cả \n hay \t.


3.2. Nhập xuất vào file

Ngoài tương tác với dữ liệu qua màn hình console, C++ còn cung cấp các thư viện nhận và xuất dữ liệu vào các file trong hệ thống, giúp đơn giản quá trình nhập xuất dữ liệu thủ công.

Khi sử dụng file ta cần phải khai báo thư viện fstream với cú pháp: #include <fstream>

Khi xử lí file trong C++ ta có 3 class là:

  • ifstream là class để đọc dữ liệu đầu vào từ file
    Ví dụ khai báo một biến kiểu ifstream để đọc dữ liệu từ một file có tên là input.txt: ifstream ip("input.txt");
  • ofstream là class để ghi dữ liệu vào (o viết tắt của out, f viết tắt của file) Ví dụ khai báo một biến ofstream để mở file: ofstream op;
  • fstream là class để đọc hoặc ghi dữ liệu. Ta có thể thay thế 2 từ khóa trên bằng từ khóa fstream

Đọc ghi file thường có các chế độ (mode) định dạng đi kèm như sau:

  • ios::in dùng để Mở cho các hoạt động đầu vào. (mode mặc định của ifstream)
  • ios::out dùng để mở cho các hoạt động đầu ra. (mode mặc định của ofstream)
  • ios::binary dùng để mở file nhị phân.
  • ios::ate là đặt vị trí con trỏ ở cuối file khi chúng ta mở file.
  • ios::app là khi file đã có sẵn data thì dữ liệu sẽ được thêm vào cuối file.
  • ios::trunc là khi ta chèn dữ liệu vào file, thì csc dữ liệu cũ sẽ bị xóa hết.

Để ghi dữ liệu vào file ta dùng cú pháp: nameFile<<"Data";

Để đọc dữ liệu từ file ta dùng cú pháp: nameFile>>value;

Để mở file bất kì ta dùng hàm open("nameFile", mode);

#include <bits/stdc++.h>
#include <fstream>
using namespace std;
int main() {
ifstream input("C:\\Users\\minhh\\OneDrive\\Desktop\\input.txt");
fstream output;
output.open ("C:\\Users\\minhh\\OneDrive\\Desktop\\input.txt", ios::out);

string str;
input >> str; // lấy giá trị biến str từ file input
cout << str; // in ra console
output >> "Hello World"; // in Hello World vào file input

input.close(); // đóng file input
output.close(); // đóng file output
return 0;
}

Khi sử dụng file trong C++ ta cần lưu ý liên kết tới file ta cần sử dụng ở đây bình thường ta copy link sẽ là C:\Users\minhh\OneDrive\Desktop\input.txt sẽ bị lỗi do \ là một kí tự đặc biệt và nó sẽ hiểu \U \m \O \D \i là một lệnh giống như \n (trỏ xuống dòng) vì vậy để ta coi \ là một char bình thường ta cần phải thêm \ trước \ tức là \\ như code mẫu trên.

Bình thường khi chúng ta lấy dữ liệu từ bàn phím ta sẽ sử dụng câu lệnh cin >> và in dữ liệu ra màn hình console ta dùng cout <<, và tương tự với đọc dữ liệu từ file và ghi dữ liệu vào file ta chỉ cần thay cin thành tên file cần đọc dữ liệu, và thay cout thành tên file cần ghi dữ liệu vào.

Nếu chỉ dùng input >> str; thì ta chỉ đọc được chuỗi cho tới khi nó gặp dấu cách. Muốn lấy dữ liệu lấy được cả dấu cách ta dùng hàm getline(input,str);

Tài liệu tham khảo

  1. UIT - Các kiểu dữ liệu cơ bản
  2. UIT - Các phép toán trong C++
  3. UIT - Nhập xuất trong C++