Skip to content

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:

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

Ví dụ:

C++
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:

C++
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
Text Only
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ụ:

C++
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
Text Only
        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:

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

Ví dụ:

C++
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
Text Only
        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:

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

Ví dụ:

C++
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]
Text Only
        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:

Text Only
┌───┬───┬───┐
│ 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]

Text Only
┌───┬───┬───┐
│ 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]

Text Only
┌───┬───┬───┐
│ 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}

Text Only
┌───┬───┬───┐
│ 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}

Text Only
┌───┬───┬───┐
│ 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ố:

C++
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

Đặc điểm

  • Mảng có thể thay đổi nội dung sau khi thực hiện hàm
  • Có thể bỏ số dòng, nhưng phải giữ số cột:
    C++
    void NhapMang(int A[][50], int n, int m);
    void NhapMang(int (*A)[50], int n, int m);
    

Ví dụ đầy đủ:

C++
#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¤

C++
#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¤

C++
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:

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

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

C++
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?

C++
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?

C++
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)

C++
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ử¤

C++
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;
}

Lưu ý

Số lượng phần tử = m × n, không cần duyệt!

C++
int Dem(int m, int n) {
    return m * n;
}

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

C++
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¤

C++
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ể:

C++
// 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¤

Bài 1: Nhập/Xuất ma trận

Viết hàm nhập và xuất ma trận m×n.

Bài 2: Tìm kiếm

Tìm kiếm một phần tử trong ma trận.

Bài 3: Kiểm tra đối xứng

Kiểm tra ma trận có đối xứng qua đường chéo chính hay không?

Gợi ý: So sánh A[i][j] với A[j][i]

Bài 4: Tính tổng

Tính tổng các phần tử:

  • Trên dòng k
  • Trên cột k
  • Toàn ma trận
  • Đường chéo chính
  • Nửa trên/nửa dưới đường chéo chính

Bài 5: Tìm min/max

Tìm giá trị nhỏ nhất/lớn nhất của ma trận.

Bài 6: Tính tổng 2 ma trận

Cho 2 ma trận A và B cùng kích thước m×n. Tính C = A + B.

Bài 7: Tính tích 2 ma trận

Cho ma trận A (m×p) và B (p×n). Tính C = A × B (m×n).

Công thức: C[i][j] = Σ(A[i][k] × B[k][j]) với k từ 0 đến p-1

Bài 8: Ma trận đơn vị

Kiểm tra ma trận có phải là ma trận đơn vị không?

Ma trận đơn vị: Đường chéo chính = 1, còn lại = 0


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

C++
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:

Text Only
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:

C++
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ể)

C++
char s[10] = {'T', 'H', 'C', 'S', ' ', 'A', '\0'};
Text Only
┌───┬───┬───┬───┬───┬───┬────┬───┬───┬───┐
│ 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')

C++
char s[10] = "THCS A";  // Tự động thêm '\0'
Text Only
┌───┬───┬───┬───┬───┬───┬────┬───┬───┬───┐
│ 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

C++
char s[] = {'T', 'H', 'C', 'S', ' ', 'A', '\0'};
char s[] = "THCS A";  // Khuyến nghị
Text Only
┌───┬───┬───┬───┬───┬───┬────┐
│ 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()

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

Lưu ý về gets()

  • gets() không an toàn, dễ bị tràn bộ nhớ
  • Nên dùng fgets() hoặc cin.getline() thay thế

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

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

Sử dụng cin và cout:

C++
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àm Chứ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ụ:

C++
#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¤

C++
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¤

C++
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¤

C++
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¤

C++
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:

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

9.5. Chuyển Thường Sang Hoa¤

C++
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:

C++
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¤

C++
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:

Text Only
Hello
World
C++

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

Xóa đầu chuỗi:

C++
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:

C++
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ự¤

Bài 1: Nhập/Xuất chuỗi

Viết hàm nhập và xuất chuỗi ký tự.

Bài 2: Xuất ký tự in hoa

Xuất các ký tự in hoa trong chuỗi.

Gợi ý: Kiểm tra c >= 'A' && c <= 'Z'

Bài 3: Đảo ngược chuỗi

Đảo ngược các ký tự trong chuỗi.

Gợi ý: Hoán vị s[i]s[n-1-i]

Bài 4: Xen kẽ hoa thường

Đổi chữ xen kẽ: 1 chữ hoa, 1 chữ thường.

Input: "hello world"
Output: "HeLlO wOrLd"

Bài 5: Đếm ký tự xuất hiện

Đếm một ký tự xuất hiện bao nhiêu lần trong chuỗi.

Bài 6: Ký tự xuất hiện nhiều nhất

Tìm ký tự nào xuất hiện nhiều nhất trong chuỗi.

Gợi ý: Dùng mảng đếm tần suất 256 phần tử

Bài 7: Kiểm tra đối xứng

Kiểm tra xem chuỗi có đối xứng hay không?

Ví dụ: "abccba" → đối xứng

Bài 8: Xóa từ trong chuỗi

Nhập vào một từ và xóa từ đó trong chuỗi đã cho.

Gợi ý: Sử dụng strstr() để tìm, sau đó dịch chuỗi


Tổng kết Mảng 2 Chiều và Chuỗi

Mảng 2 chiều:

  • Khai báo: int a[m][n];
  • Truy xuất: a[i][j]
  • Truyền hàm: Phải giữ số cột
  • Các khái niệm: đường chéo chính, phụ, nửa trên/dưới

Chuỗi ký tự:

  • Là mảng ký tự kết thúc bằng '\0'
  • Độ dài = kích thước - 1
  • Nhập: gets(), cin.getline()
  • Xuất: puts(), cout
  • Thư viện <string.h>: nhiều hàm hữu ích

Các thao tác cơ bản:

  • Đếm, tìm kiếm, chuyển đổi
  • Xóa, thêm, sửa
  • So sánh, nối chuỗi

Chương tiếp theo: Đệ quy (Recursion)