Bài 3.2: Bài tập thực hành kiểu dữ liệu cơ bản trong C++

Chào mừng các bạn quay trở lại với chuỗi bài học lập trình C++ cùng FullhouseDev! Sau khi đã tìm hiểu về cách nhập xuất dữ liệu cơ bản, hôm nay chúng ta sẽ đi sâu vào một khái niệm cực kỳ quan trọng và là nền tảng của mọi ngôn ngữ lập trình: kiểu dữ liệu.

Trong C++, mỗi biến (variable) mà chúng ta sử dụng để lưu trữ thông tin đều cần được khai báo với một kiểu dữ liệu cụ thể. Kiểu dữ liệu này sẽ cho trình biên dịch biết:

  1. Kích thước của vùng nhớ cần cấp phát cho biến đó.
  2. Loại dữ liệu mà biến đó có thể chứa (số nguyên, số thực, ký tự, logic...).
  3. Những thao tác nào có thể thực hiện trên biến đó.

Việc hiểu rõ và sử dụng đúng kiểu dữ liệu là chìa khóa để viết code hiệu quả, tránh lỗi và quản lý bộ nhớ tốt. Bài hôm nay sẽ tập trung vào các kiểu dữ liệu cơ bản nhất và cách thực hành chúng qua các ví dụ cụ thể.

Các Kiểu Dữ Liệu Cơ Bản Nhất trong C++

C++ cung cấp một bộ sưu tập các kiểu dữ liệu nguyên thủy (primitive data types) mà từ đó chúng ta có thể xây dựng nên các cấu trúc phức tạp hơn. Dưới đây là những kiểu dữ liệu cơ bản mà chúng ta sẽ làm việc hôm nay:

  • Số nguyên (Integer Types): Dùng để lưu trữ các số không có phần thập phân.
    • **int**: Kiểu số nguyên thông dụng nhất, thường có kích thước 4 byte (trên hầu hết các hệ thống hiện đại), có thể chứa cả số dương, số âm và số 0.
    • **short**: Số nguyên kích thước nhỏ hơn int, thường 2 byte.
    • **long**: Số nguyên kích thước lớn hơn hoặc bằng int, thường 4 hoặc 8 byte.
    • **long long**: Số nguyên kích thước lớn nhất, thường 8 byte, dùng cho các số cực lớn.
    • (Có thể thêm unsigned vào trước các kiểu trên để chỉ lưu trữ số không âm).
  • Số thực (Floating-Point Types): Dùng để lưu trữ các số có phần thập phân.
    • **float**: Số thực độ chính xác đơn (single-precision), thường 4 byte.
    • **double**: Số thực độ chính xác kép (double-precision), thường 8 byte, có độ chính xác cao hơn float.
    • **long double**: Số thực độ chính xác mở rộng, kích thước có thể khác nhau (8, 12, 16 byte), độ chính xác cao nhất.
  • Ký tự (Character Type): Dùng để lưu trữ một ký tự đơn lẻ.
    • **char**: Thường 1 byte, lưu trữ một ký tự theo bảng mã ASCII (hoặc Unicode).
  • Logic (Boolean Type): Dùng để lưu trữ giá trị logic đúng/sai.
    • **bool**: Chỉ có hai giá trị là true (đúng) hoặc false (sai).

Thực Hành với Từng Kiểu Dữ Liệu

Để hiểu rõ hơn, chúng ta hãy đi qua các ví dụ thực hành cho từng loại.

1. Thực Hành với Số Nguyên (Integer Types)

Số nguyên là kiểu dữ liệu phổ biến nhất cho việc đếm, tính toán các giá trị rời rạc.

Ví dụ 1: Khai báo và gán giá trị

#include <iostream>

int main() {
    int so_luong_tao = 10;
    short diem_kiem_tra = 95;
    long dan_so_the_gioi = 8000000000L; // Dùng hậu tố 'L' cho long
    long long so_hat_cat_trong_sa_mac = 1000000000000000LL; // Dùng hậu tố 'LL' cho long long

    cout << "So luong tao: " << so_luong_tao << endl;
    cout << "Diem kiem tra: " << diem_kiem_tra << endl;
    cout << "Dan so the gioi (uoc luong): " << dan_so_the_gioi << endl;
    cout << "So hat cat (vi du): " << so_hat_cat_trong_sa_mac << endl;

    return 0;
}
  • Giải thích: Chúng ta khai báo các biến với các kiểu int, short, long, long long và gán cho chúng các giá trị phù hợp với phạm vi của từng kiểu. Chú ý hậu tố LLL giúp trình biên dịch hiểu rõ chúng ta đang muốn sử dụng kiểu longlong long cho các giá trị số lớn. endl dùng để xuống dòng sau khi in.

Ví dụ 2: Các phép toán số học cơ bản

#include <iostream>

int main() {
    int a = 25;
    int b = 7;

    int tong = a + b;      // Cộng
    int hieu = a - b;      // Trừ
    int tich = a * b;      // Nhân
    int thuong_nguyen = a / b; // Chia lấy phần nguyên
    int so_du = a % b;     // Chia lấy phần dư (modulo)

    cout << "a = " << a << ", b = " << b << endl;
    cout << "Tong: " << tong << endl;         // Output: 32
    cout << "Hieu: " << hieu << endl;         // Output: 18
    cout << "Tich: " << tich << endl;         // Output: 175
    cout << "Thuong nguyen: " << thuong_nguyen << endl; // Output: 3 (25 / 7 = 3 dư 4)
    cout << "So du: " << so_du << endl;           // Output: 4

    return 0;
}
  • Giải thích: Ví dụ này minh họa các phép toán cộng (+), trừ (-), nhân (*), chia lấy phần nguyên (/) và chia lấy phần dư (%) trên hai biến kiểu int. Lưu ý quan trọng: Phép chia (/) giữa hai số nguyên sẽ luôn cho kết quả là một số nguyên bằng cách loại bỏ phần thập phân (không làm tròn).
2. Thực Hành với Số Thực (Floating-Point Types)

Số thực được sử dụng khi bạn cần làm việc với các giá trị có phần thập phân, như tính toán tiền tệ, đo lường, khoa học, v.v.

Ví dụ 3: Khai báo và tính toán

#include <iostream>
#include <iomanip> // Để sử dụng fixed và setprecision

int main() {
    float gia_san_pham = 199.99f; // Dùng hậu tố 'f' cho float
    double ban_kinh_hinh_tron = 5.5;

    // Tính diện tích hình tròn: Pi * R^2
    const double PI = 3.14159; // const double cho giá trị hằng số Pi
    double dien_tich = PI * ban_kinh_hinh_tron * ban_kinh_hinh_tron;

    cout << "Gia san pham: " << gia_san_pham << endl;
    cout << "Ban kinh hinh tron: " << ban_kinh_hinh_tron << endl;

    // In dien tich voi do chinh xac 2 chu so thap phan
    cout << fixed << setprecision(2);
    cout << "Dien tich hinh tron: " << dien_tich << endl; // Output: 95.03

    return 0;
}
  • Giải thích: Chúng ta khai báo biến gia_san_pham kiểu float (sử dụng hậu tố f) và ban_kinh_hinh_tron kiểu double. double thường được ưa chuộng hơn float vì độ chính xác cao hơn, trừ khi bộ nhớ là vấn đề cực kỳ quan trọng. Phép toán nhân (*) hoạt động như mong đợi. fixedsetprecision(2) từ thư viện <iomanip> được dùng để định dạng đầu ra số thực với 2 chữ số sau dấu thập phân.
3. Thực Hành với Ký Tự (Character Type)

Kiểu char lưu trữ một ký tự duy nhất. Ký tự được bao quanh bởi dấu nháy đơn (' ').

Ví dụ 4: Lưu trữ và hiển thị ký tự

#include <iostream>

int main() {
    char chu_cai_dau = 'A';
    char ki_tu_dac_biet = '$';
    char chu_so_ky_tu = '7';

    cout << "Chu cai dau: " << chu_cai_dau << endl;
    cout << "Ki tu dac biet: " << ki_tu_dac_biet << endl;
    cout << "Chu so ky tu: " << chu_so_ky_tu << endl;

    // In ma ASCII cua ky tu
    cout << "Ma ASCII cua '" << chu_cai_dau << "': " << static_cast<int>(chu_cai_dau) << endl; // Output: 65

    return 0;
}
  • Giải thích: Biến kiểu char có thể lưu trữ bất kỳ ký tự nào được đặt trong dấu nháy đơn. Điều thú vị là ký tự cũng có giá trị số tương ứng (theo bảng mã ASCII). Bằng cách ép kiểu (type casting) static_cast<int>(chu_cai_dau), chúng ta có thể hiển thị giá trị số (ASCII) của ký tự đó.
4. Thực Hành với Logic (Boolean Type)

Kiểu bool chỉ có hai trạng thái: true (đúng) hoặc false (sai). Chúng rất quan trọng trong các cấu trúc điều khiển luồng (như if, while).

Ví dụ 5: Sử dụng giá trị boolean

#include <iostream>

int main() {
    bool la_sinh_vien = true;
    bool da_tot_nghiep = false;

    cout << "La sinh vien: " << la_sinh_vien << endl; // Output: 1 (true thường được hiển thị là 1)
    cout << "Da tot nghiep: " << da_tot_nghiep << endl; // Output: 0 (false thường được hiển thị là 0)

    // Sử dụng boolalpha để hiển thị true/false thay vì 1/0
    cout << boolalpha;
    cout << "La sinh vien (boolalpha): " << la_sinh_vien << endl; // Output: true
    cout << "Da tot nghiep (boolalpha): " << da_tot_nghiep << endl; // Output: false

    return 0;
}
  • Giải thích: Biến kiểu bool được gán trực tiếp giá trị true hoặc false. Mặc định, khi in ra, true thường được hiển thị là 1false0. Tuy nhiên, bạn có thể sử dụng boolalpha để thay đổi cài đặt luồng xuất, khiến chúng hiển thị trực tiếp chuỗi "true""false".

Thực Hành Tổng Hợp: Ép Kiểu Dữ Liệu (Type Casting)

Đôi khi, bạn cần chuyển đổi giá trị từ kiểu dữ liệu này sang kiểu dữ liệu khác. Đây gọi là ép kiểu (type casting). C++ có thể tự động ép kiểu (implicit casting) trong một số trường hợp an toàn, nhưng thường bạn cần ép kiểu rõ ràng (explicit casting) để đảm bảo kết quả mong muốn.

Ví dụ 6: Ép kiểu giữa số nguyên và số thực

Hãy quay lại bài toán chia. Nếu chúng ta muốn lấy kết quả chia có phần thập phân từ hai số nguyên thì sao?

#include <iostream>
#include <iomanip>

int main() {
    int so_chia = 25;
    int so_bi_chia = 7;

    // Chia lay phan nguyen (nhu vi du 2)
    int ket_qua_nguyen = so_chia / so_bi_chia;
    cout << "Ket qua nguyen (int / int): " << ket_qua_nguyen << endl; // Output: 3

    // Chia lay phan thuc - Cach 1: Ep kieu mot trong hai so truoc khi chia
    double ket_qua_thuc_c1 = static_cast<double>(so_chia) / so_bi_chia;
    cout << fixed << setprecision(2);
    cout << "Ket qua thuc (double / int): " << ket_qua_thuc_c1 << endl; // Output: 3.57

    // Chia lay phan thuc - Cach 2: Ep kieu ca bieu thuc sau khi chia (khong nen lam the nay cho phep chia)
    // double ket_qua_thuc_c2 = static_cast<double>(so_chia / so_bi_chia);
    // cout << "Ket qua thuc (ep kieu sau): " << ket_qua_thuc_c2 << endl; // Output: 3.00 (Sai!)

    // Chia lay phan thuc - Cach 3: Khai bao bien la so thuc ngay tu dau
    double so_chia_d = 25.0;
    double so_bi_chia_d = 7.0;
    double ket_qua_thuc_c3 = so_chia_d / so_bi_chia_d;
    cout << "Ket qua thuc (double / double): " << ket_qua_thuc_c3 << endl; // Output: 3.57

    return 0;
}
  • Giải thích:
    • Khi bạn chia int cho int, kết quả là int (lấy phần nguyên).
    • Để có kết quả double, ít nhất một trong hai toán hạng (số bị chia hoặc số chia) phải là kiểu số thực trước khi phép chia diễn ra.
    • static_cast<double>(so_chia) ép kiểu so_chia từ int sang double. Khi đó, phép chia trở thành double / int. C++ sẽ tự động nâng cấp (promote) so_bi_chia thành double để thực hiện phép chia double / double, cho kết quả là double.
    • Quan trọng: Ép kiểu kết quả sau khi chia nguyên (static_cast<double>(so_chia / so_bi_chia)) là sai vì phép chia nguyên đã được thực hiện trước, làm mất đi phần thập phân rồi.
    • Cách tốt nhất nếu bạn biết trước sẽ cần kết quả thực là khai báo biến ngay từ đầu với kiểu số thực (double).

Ví dụ 7: Một bài toán thực tế nhỏ - Tính chỉ số BMI

Chỉ số BMI (Body Mass Index) được tính bằng công thức: cân nặng (kg) / (chiều cao (m) * chiều cao (m)). Đây là một ví dụ điển hình cần sử dụng cả số nguyên và số thực, và có thể cần ép kiểu.

#include <iostream>
#include <iomanip>

int main() {
    int can_nang_kg = 70; // Can nang bang so nguyen
    int chieu_cao_cm = 175; // Chieu cao bang so nguyen (cm)

    // Chuyen chieu cao tu cm sang met (so thuc)
    double chieu_cao_m = static_cast<double>(chieu_cao_cm) / 100;

    // Tinh BMI. Can nang la int, chieu cao la double.
    // Phep chia int / double se tu dong chuyen int sang double.
    double bmi = static_cast<double>(can_nang_kg) / (chieu_cao_m * chieu_cao_m);

    cout << "Can nang: " << can_nang_kg << " kg" << endl;
    cout << "Chieu cao: " << chieu_cao_cm << " cm (" << chieu_cao_m << " m)" << endl;

    cout << fixed << setprecision(2);
    cout << "Chi so BMI cua ban: " << bmi << endl; // Output: Khoang 22.86 (tùy hệ thống)

    return 0;
}
  • Giải thích:
    • Chúng ta có cân nặng và chiều cao dưới dạng số nguyên.
    • Công thức BMI yêu cầu chiều cao tính bằng mét và là số thực. Chúng ta ép kiểu chieu_cao_cm sang double rồi chia cho 100 để được chiều cao dạng double tính bằng mét.
    • Khi tính bmi, chúng ta ép kiểu can_nang_kg sang double để đảm bảo toàn bộ phép tính diễn ra với số thực, cho kết quả chính xác. Nếu không ép kiểu can_nang_kg, phép chia đầu tiên (can_nang_kg / chieu_cao_m) sẽ vẫn được thực hiện dưới dạng số thực (vì một toán hạng là double), nhưng việc ép kiểu rõ ràng giúp code dễ đọcminh bạch hơn ý định của lập trình viên.

Lời Khuyên Khi Sử Dụng Kiểu Dữ Liệu

  • Chọn kiểu phù hợp: Đừng lúc nào cũng dùng int hoặc double. Hãy nghĩ xem dữ liệu của bạn là gì và cần phạm vi/độ chính xác như thế nào. Dùng short hoặc int cho số đếm nhỏ, long long cho số rất lớn, float cho số thực cần ít bộ nhớ, double cho hầu hết các tính toán số thực cần độ chính xác cao.
  • Cẩn thận với phép chia số nguyên: Luôn nhớ int / int cho kết quả int. Nếu muốn kết quả số thực, hãy đảm bảo ít nhất một toán hạng là số thực trước khi phép chia diễn ra.
  • Hiểu về phạm vi: Mỗi kiểu dữ liệu có một phạm vi giá trị nhất định. Nếu lưu trữ một số vượt quá phạm vi, sẽ xảy ra hiện tượng tràn số (overflow), dẫn đến kết quả không mong muốn.
  • Ép kiểu khi cần: Sử dụng static_cast<Kieu_Moi>(bien_cu) để ép kiểu một cách rõ ràng, giúp code dễ hiểu và kiểm soát được quá trình chuyển đổi.

Comments

There are no comments at the moment.