L6. Mảng Hai Chiều và Chuỗi Ký Tự

Phần 1: Mảng Hai Chiều

6. Mảng 2 Chiều

6.1. Khai Báo Mảng 2 Chiều

Cú pháp:

<Kiểu_dữ_liệu> <Tên_biến_mảng>[<Số_Dòng>][<Số_Cột>];

Ví dụ:

char A[10][20];
// Kiểu dữ liệu: char
// Tên biến mảng: A
// Mảng có 10 dòng và 20 cột

int Mang2Chieu[3][5];
// Kiểu dữ liệu: int
// Tên biến mảng: Mang2Chieu
// Mảng có 3 dòng và 5 cột

Minh họa các mảng 2 chiều:

int A[2][4];        // Ma trận 2×4
int B[2][2];        // Ma trận vuông 2×2
int C[2][1];        // Ma trận 2×1
A[2][4]:                B[2][2]:        C[2][1]:
┌────┬─────┬─────┬────┐  ┌────┬─────┐  ┌────┐
│ 29 │ 137 │  50 │  4 │  │ 29 │ 137 │  │ 29 │
├────┼─────┼─────┼────┤  ├────┼─────┤  ├────┤
│  5 │  32 │ 657 │ 97 │  │  5 │  32 │  │  5 │
└────┴─────┴─────┴────┘  └────┴─────┘  └────┘

6.2. Chỉ Số Mảng 2 Chiều

Đặc điểm:

  • Chỉ số là giá trị số nguyên int
  • Gồm chỉ số dòngchỉ số cột
  • 0 ≤ chỉ số dòng ≤ số dòng - 1
  • 0 ≤ chỉ số cột ≤ số cột - 1

Ví dụ:

int A[2][3];
// Tên mảng: A
// Kiểu: int
// Số phần tử: 2 × 3 = 6
// Chỉ số dòng: 0, 1
// Chỉ số cột: 0, 1, 2
        Cột 0   Cột 1   Cột 2
      ┌───────┬───────┬───────┐
Dòng 0│   2   │  45   │   7   │
      ├───────┼───────┼───────┤
Dòng 1│  73   │  11   │  187  │
      └───────┴───────┴───────┘

6.3. Truy Xuất Phần Tử Mảng 2 Chiều

Cú pháp:

<Tên_biến_mảng>[<Chỉ_số_dòng>][<Chỉ_số_cột>]

Ví dụ:

int A[2][3] = {
    {29, 137, 50},
    {3, 78, 943}
};

// Truy xuất hợp lệ
A[0][0]  // = 29
A[0][1]  // = 137
A[0][2]  // = 50
A[1][0]  // = 3
A[1][1]  // = 78
A[1][2]  // = 943

// Truy xuất KHÔNG hợp lệ
A[-1][0]  // SAI: chỉ số âm
A[1][4]   // SAI: vượt quá số cột
A[2][0]   // SAI: vượt quá số dòng
        Cột 0   Cột 1   Cột 2
      ┌───────┬───────┬───────┐
Dòng 0│  29   │  137  │  50   │
      ├───────┼───────┼───────┤
Dòng 1│   3   │  78   │  943  │
      └───────┴───────┴───────┘

Giá trị các phần tử:

  • A[0][0] = 29, A[0][1] = 137, A[0][2] = 50
  • A[1][0] = 3, A[1][1] = 78, A[1][2] = 943

6.4. Địa Chỉ Các Phần Tử Mảng 2 Chiều

Cú pháp:

&<Tên_biến_mảng>[<Chỉ_số_dòng>][<Chỉ_số_cột>];

Ví dụ:

int A[2][4];

// Địa chỉ các phần tử dòng 0:
&A[0][0], &A[0][1], &A[0][2], &A[0][3]

// Địa chỉ các phần tử dòng 1:
&A[1][0], &A[1][1], &A[1][2], &A[1][3]
        Cột 0   Cột 1   Cột 2   Cột 3
      ┌───────┬───────┬───────┬───────┐
Dòng 0│  76   │  87   │  40   │  331  │
      ├───────┼───────┼───────┼───────┤
Dòng 1│  456  │  23   │  174  │  56   │
      └───────┴───────┴───────┴───────┘
Địa chỉ:
      0x100   0x104   0x108   0x10C
      0x110   0x114   0x118   0x11C

6.5. Khái Niệm Liên Quan Đến Ma Trận

Cho ma trận vuông 3×3:

┌───┬───┬───┐
│ 3 │ 7 │ 8 │  Dòng 0
├───┼───┼───┤
│ 6 │ 1 │ 4 │  Dòng 1
├───┼───┼───┤
│ 0 │ 9 │ 5 │  Dòng 2
└───┴───┴───┘
  0   1   2  ← Cột

Đường chéo chính: {3, 1, 5} - Các phần tử A[i][i]

┌───┬───┬───┐
│ 3 │ 7 │ 8 │  ← A[0][0] = 3
├───┼───┼───┤
│ 6 │ 1 │ 4 │  ← A[1][1] = 1
├───┼───┼───┤
│ 0 │ 9 │ 5 │  ← A[2][2] = 5
└───┴───┴───┘

Đường chéo phụ: {8, 1, 0} - Các phần tử A[i][n-1-i]

┌───┬───┬───┐
│ 3 │ 7 │ 8 │  ← A[0][2] = 8
├───┼───┼───┤
│ 6 │ 1 │ 4 │  ← A[1][1] = 1
├───┼───┼───┤
│ 0 │ 9 │ 5 │  ← A[2][0] = 0
└───┴───┴───┘

Nửa trên đường chéo chính: {3, 7, 8, 1, 4, 5}

┌───┬───┬───┐
│ 3 │ 7 │ 8 │  ← Tất cả
├───┼───┼───┤
│ 6 │ 1 │ 4 │  ← Từ A[1][1] trở đi
├───┼───┼───┤
│ 0 │ 9 │ 5 │  ← Chỉ A[2][2]
└───┴───┴───┘

Nửa dưới đường chéo chính: {3, 6, 1, 0, 9, 5}

┌───┬───┬───┐
│ 3 │ 7 │ 8 │  ← Chỉ A[0][0]
├───┼───┼───┤
│ 6 │ 1 │ 4 │  ← Từ A[1][0] đến A[1][1]
├───┼───┼───┤
│ 0 │ 9 │ 5 │  ← Tất cả
└───┴───┴───┘

6.6. Truyền Mảng 2 Chiều Cho Hàm

Khai báo tham số:

int TinhDCheo(int A[50][50], int n, int m);
// Tên hàm: TinhDCheo
// Tham số: mảng A, số dòng n, số cột m
// Giá trị trả về: int

void XuatMang(int A[50][50], int n, int m);
// Tên hàm: XuatMang
// Tham số: mảng A, số dòng n, số cột m
// Giá trị trả về: void

Ví dụ đầy đủ:

#include <stdio.h>
#include <conio.h>

void nhap(int A[][100], int &N, int &M);
void xuat(int A[][100], int N, int M);
void SapXep(int A[][100], int N, int M);

void main() { 
    int a[100][100], n, m; 
    nhap(a, n, m); 
    xuat(a, n, m);  
    SapXep(a, n, m);  
}

7. Các Tác Vụ Trên Mảng 2 Chiều

7.1. Nhập Ma Trận

#define MAXC 100

void NhapMaTran(int A[][MAXC], int &m, int &n) {
    cout << "Nhap so dong, so cot cua ma tran: ";
    cin >> m >> n;
    
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            cout << "Nhap A[" << i << "][" << j << "] = ";
            cin >> A[i][j];
        }
    }
}

7.2. Xuất Ma Trận

void XuatMaTran(int A[][MAXC], int m, int n) {
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            printf("%d ", A[i][j]);
        }
        printf("\n");  // Xuống dòng sau mỗi hàng
    }
}

Ví dụ output:

1  2  3  4
5  6  7  8
9  10 11 12

7.3. Tìm Kiếm Phần Tử

int TimKiem(int a[][MAXC], int m, int n, int x) {
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (a[i][j] == x) 
                return 1;  // Tìm thấy
        }
    }
    return 0;  // Không tìm thấy
}

7.4. Kiểm Tra Tính Chất Ma Trận

Yêu cầu: Ma trận có toàn số chẵn không?

Ý tưởng 1: Đếm số chẵn = m×n?

int KiemTra_YT1(int a[][MAXC], int m, int n) {
    int dem = 0;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (a[i][j] % 2 == 0)
                dem++;
        }
    }
    if (dem == m * n) 
        return 1;
    return 0;
}

Ý tưởng 2: Đếm số lẻ = 0?

int KiemTra_YT2(int a[][MAXC], int m, int n) {
    int dem = 0;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (a[i][j] % 2 != 0)
                dem++;
        }
    }
    if (dem == 0) 
        return 1;
    return 0;
}

Ý tưởng 3: Tìm số lẻ (Tối ưu nhất)

int KiemTra_YT3(int a[][MAXC], int m, int n) {
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (a[i][j] % 2 != 0)
                return 0;  // Tìm thấy số lẻ
        }
    }
    return 1;  // Không có số lẻ
}

7.5. Đếm Số Lượng Phần Tử

int Dem(int A[][MAXC], int m, int n) {
    int Dem = 0;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            Dem++;
        }
    }
    return Dem;
}

7.6. Tính Tổng Các Phần Tử Chẵn

int TongChan(int A[][MAXC], int m, int n) {
    int TC = 0;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (A[i][j] % 2 == 0)
                TC = TC + A[i][j];
        }
    }
    return TC;
}

7.7. Tính Tổng Đường Chéo Chính

int TongDCChinh(int a[][MAXC], int m, int n) {
    int tong = 0;
    // Chỉ áp dụng cho ma trận vuông
    if (m != n) {
        cout << "Khong phai ma tran vuong!";
        return 0;
    }
    
    for (int i = 0; i < n; i++) {
        tong = tong + a[i][i];  // Đường chéo chính
    }
    return tong;
}

Các biến thể:

// Tổng đường chéo phụ
int TongDCPhu(int a[][MAXC], int n) {
    int tong = 0;
    for (int i = 0; i < n; i++) {
        tong += a[i][n-1-i];
    }
    return tong;
}

// Tổng nửa trên đường chéo chính
int TongNuaTren(int a[][MAXC], int n) {
    int tong = 0;
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            tong += a[i][j];
        }
    }
    return tong;
}

// Tổng nửa dưới đường chéo chính
int TongNuaDuoi(int a[][MAXC], int n) {
    int tong = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= i; j++) {
            tong += a[i][j];
        }
    }
    return tong;
}

8. Bài Tập - Mảng 2 Chiều


Phần 2: Chuỗi Ký Tự

8. Chuỗi Ký Tự

8.1. Khái Niệm

Vấn đề:

  • Kiểu char chỉ chứa được một ký tự
  • Để lưu trữ nhiều ký tự (chuỗi) → Sử dụng mảng ký tự

Đặc điểm:

  • Chuỗi ký tự kết thúc bằng ký tự '\0' (null)
  • Độ dài chuỗi = kích thước mảng - 1
char Hoten[30];     // Dài tối đa 29 ký tự
char NgaySinh[9];   // Dài tối đa 8 ký tự

Minh họa:

Chuỗi "HELLO"
┌───┬───┬───┬───┬───┬────┐
│ H │ E │ L │ L │ O │ \0 │
└───┴───┴───┴───┴───┴────┘
  0   1   2   3   4   5

8.2. Khai Báo Chuỗi Ký Tự

Các cách khai báo:

char sName[100];  // Cách 1: Chỉ định kích thước
char sName[];     // Cách 2: Tự động xác định
char *sName;      // Cách 3: Con trỏ ký tự

8.3. Khởi Tạo Chuỗi Ký Tự

Cách 1: Khởi tạo từng ký tự (độ dài cụ thể)

char s[10] = {'T', 'H', 'C', 'S', ' ', 'A', '\0'};
┌───┬───┬───┬───┬───┬───┬────┬───┬───┬───┐
│ T │ H │ C │ S │   │ A │ \0 │   │   │   │
└───┴───┴───┴───┴───┴───┴────┴───┴───┴───┘
  0   1   2   3   4   5   6    7   8   9

Cách 2: Khởi tạo bằng chuỗi (tự động thêm ‘\0’)

char s[10] = "THCS A";  // Tự động thêm '\0'
┌───┬───┬───┬───┬───┬───┬────┬───┬───┬───┐
│ T │ H │ C │ S │   │ A │ \0 │   │   │   │
└───┴───┴───┴───┴───┴───┴────┴───┴───┴───┘
  0   1   2   3   4   5   6    7   8   9

Cách 3: Tự động xác định độ dài

char s[] = {'T', 'H', 'C', 'S', ' ', 'A', '\0'};
char s[] = "THCS A";  // Khuyến nghị
┌───┬───┬───┬───┬───┬───┬────┐
│ T │ H │ C │ S │   │ A │ \0 │
└───┴───┴───┴───┴───┴───┴────┘
  0   1   2   3   4   5   6

8.4. Nhập Xuất Chuỗi

Hàm nhập chuỗi: gets()

void nhapchuoi(char s[100]) {
    printf("Nhap chuoi: ");
    gets(s);  // Tự động thêm '\0' vào cuối
}

Hàm xuất chuỗi: puts()

void xuatchuoi(char s[100]) {
    printf("Xuat chuoi: ");
    puts(s);  // Tự động xuống dòng
}

Sử dụng cin và cout:

char s[100];

// Nhập (chỉ nhận đến khoảng trắng đầu tiên)
cin >> s;

// Nhập cả dòng (bao gồm khoảng trắng)
cin.getline(s, 100);

// Xuất
cout << s;

8.5. Các Hàm Thư Viện <string.h>

HàmChức năng
strlen(s)Tính độ dài chuỗi
strcpy(dest, src)Sao chép chuỗi
strdup(s)Tạo bản sao
strlwr(s)Chuyển thành chữ thường
strupr(s)Chuyển thành chữ hoa
strrev(s)Đảo ngược chuỗi
strcmp(s1, s2)So sánh (phân biệt hoa/thường)
stricmp(s1, s2)So sánh (không phân biệt)
strcat(dest, src)Nối 2 chuỗi
strstr(s1, s2)Tìm chuỗi con

Ví dụ:

#include <string.h>

char s1[] = "Hello";
char s2[20];

strlen(s1);          // = 5
strcpy(s2, s1);      // s2 = "Hello"
strcat(s2, " World"); // s2 = "Hello World"
strcmp(s1, "Hello"); // = 0 (bằng nhau)
strupr(s1);          // s1 = "HELLO"

9. Các Thao Tác Trên Chuỗi Ký Tự

9.1. Đếm Ký Tự Khoảng Trắng

int DemKhoangTrang(char chuoi[100]) {
    int dem = 0;
    for (int i = 0; i < strlen(chuoi); i++) {
        if (chuoi[i] == ' ') 
            dem++;
    }
    return dem;
}

9.2. Đếm Ký Tự Hoa/Thường

void DemKyTu(char chuoi[]) {
    int dt = 0, dh = 0;
    
    for (int i = 0; i < strlen(chuoi); i++) {
        if (chuoi[i] >= 'a' && chuoi[i] <= 'z') 
            dt++;
        else if (chuoi[i] >= 'A' && chuoi[i] <= 'Z') 
            dh++;
    }
    
    printf("So ky tu thuong: %d\n", dt);
    printf("So ky tu hoa: %d\n", dh);
}

9.3. Viết Hoa Chữ Cái Đầu Câu

void DoiHoaThuong(char chuoi[100]) {
    // Chữ đầu tiên viết hoa
    chuoi[0] = toupper(chuoi[0]);
    
    // Các chữ còn lại viết thường
    for (int i = 1; i < strlen(chuoi); i++) {
        chuoi[i] = tolower(chuoi[i]);
    }
    
    printf("Xuat chuoi: ");
    puts(chuoi);
}

Input: hELLo WoRLD
Output: Hello world

9.4. Chuyển Hoa Sang Thường

void ChuyenHoaSangThuong(char chuoi[100]) {
    char kq[100];
    strcpy(kq, chuoi);
    
    for (int i = 0; kq[i] != '\0'; i++) {
        if (kq[i] >= 'A' && kq[i] <= 'Z') 
            kq[i] = tolower(kq[i]);
    }
    
    printf("Xuat chuoi: ");
    puts(kq);
}

Cách đơn giản hơn:

void ChuyenHoaSangThuong(char chuoi[100]) {
    strlwr(chuoi);  // Hàm có sẵn
    puts(chuoi);
}

9.5. Chuyển Thường Sang Hoa

void ChuyenThuongSangHoa(char chuoi[100]) {
    char kq[100];
    strcpy(kq, chuoi);
    
    for (int i = 0; kq[i] != '\0'; i++) {
        if (kq[i] >= 'a' && kq[i] <= 'z') 
            kq[i] = toupper(kq[i]);
    }
    
    printf("Xuat chuoi: ");
    puts(kq);
}

Cách đơn giản hơn:

void ChuyenThuongSangHoa(char chuoi[100]) {
    strupr(chuoi);  // Hàm có sẵn
    puts(chuoi);
}

9.6. Liệt Kê Các Từ Trong Chuỗi

void LietKe(char chuoi[100]) {
    int d = 0;  // Vị trí bắt đầu từ
    
    for (int i = 0; i < strlen(chuoi); i++) {
        if (chuoi[i] == ' ') {
            // In từ từ vị trí d đến i-1
            for (int j = d; j < i; j++) {
                printf("%c", chuoi[j]);
            }
            d = i + 1;  // Vị trí bắt đầu từ mới
            printf("\n");
        }
    }
    
    // In từ cuối cùng
    for (int j = d; j < strlen(chuoi); j++) {
        printf("%c", chuoi[j]);
    }
}

Input: "Hello World C++"
Output:

Hello
World
C++

9.7. Xóa Khoảng Trắng Đầu/Cuối

Xóa đầu chuỗi:

void xoadau(char chuoi[100]) {
    while (chuoi[0] == ' ') {
        // Dịch tất cả ký tự sang trái
        for (int i = 0; i < strlen(chuoi); i++) {
            chuoi[i] = chuoi[i + 1];
        }
    }
}

Xóa cuối chuỗi:

void xoacuoi(char chuoi[100]) {
    int len = strlen(chuoi);
    while (chuoi[len - 1] == ' ') {
        chuoi[len - 1] = '\0';  // Thay khoảng trắng bằng '\0'
        len--;
    }
}

Input: " Hello World "
Output sau xoadau: "Hello World "
Output sau xoacuoi: "Hello World"

10. Bài Tập - Chuỗi Ký Tự