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òng và chỉ số cột
0 ≤ chỉ số dòng ≤ số dòng - 10 ≤ 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] = 50A[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 0x11C6.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 127.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
charchỉ 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 58.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 9Cá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 9Cá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 68.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à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ụ:
#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"