Skip to content

Instantly share code, notes, and snippets.

@justmangoou
Last active March 13, 2025 03:31
Show Gist options
  • Save justmangoou/60c50e534e37f9f454f2b8226570c11b to your computer and use it in GitHub Desktop.
Save justmangoou/60c50e534e37f9f454f2b8226570c11b to your computer and use it in GitHub Desktop.
Cơ bản về lập trình

Cơ bản về lập trình

Đây là những phần lí thuyết mà phải nắm chắc vì là nền tảng cho những phần nâng cao sau này.

Kiểu dữ liệu (Data types)

Trong đa số các ngôn ngữ lập trình đặc biệt là những ngôn ngữ bắt nguồn từ C như C++, Java hay Go đều có nét tương đầu về kiểu dữ liệu.

Trong đó ta chia ra làm 3 phần (Ngôn ngữ C/C++):

Kiểu dữ liệu số

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

Nếu các kiểu chứa unsigned ở đằng trước thì sẽ chỉ có số dương.

short / short int: là kiểu dữ liệu số nguyên 2 bytes (16 bits)

  • Số lớn nhất: 2^15 - 1 = 32,767
  • Số bé nhất: -2^15 = -32,768
  • unsigned: 2^16 = 65536

int / long / long int (Integer): là kiểu dữ liệu số nguyên 4 bytes (32 bits)

  • Số lớn nhất: 2^31 - 1 = 2,147,483,647
  • Số bé nhất: -2^31 = -2,147,483,648
  • unsigned: 2^32 = 4,294,967,295

long long / long long int: là kiểu dữ liệu số nguyên 8 bytes (64 bits)

  • Số lớn nhất: 2^63 - 1
  • Số bé nhất: -2^63
  • unsigned: 2^64

Lưu ý: Ở các ngôn ngữ mới, short (2 bytes), int (4 bytes) và long (8 bytes)

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

float là kiểu dữ liệu số thực 4 bytes (32 bits)

  • Số lớn nhất: 3.4×10^38
  • Số bé nhất: -3.4×10^38

double là kiểu dữ liệu số thực 8 bytes (64 bits)

  • Số lớn nhất: 1.7×10^308
  • Số bé nhất: -1.7×10^308

long double là kiểu dữ liệu số thực 12 bytes (96 bits) - Không thông dụng và cũng ko tồn tại ở nhiều ngôn ngữ mới

  • Số lớn nhất: 1.1×10^4932
  • Số bé nhất: -1.1×10^4932

Kiểu dữ liệu kí tự (char) và chuỗi kí tự (string)

char là kiểu dữ liệu kí tự (1 byte - 8 bits): đây là kiểu dữ liệu thường được dùng để biểu diễn 1 kí tự duy nhất

  • Thường sử dụng dấu ' để khai báo
  • Ví dụ: 'A', 'B', 'C'

string / char[] là kiểu dữ liệu xâu kí tự

  • bytes của kiểu này phụ thuộc vào độ dài của xâu, và theo quy tắc như trên 1 kí tự = 1 bytes
  • Thường sử dụng dấu " để khai báo
  • Ví dụ: "Hello World!"
  • char[] được sử dụng ở C và string được sử dụng ở nhiều ngôn ngữ mới hơn là C++, Java

Kiểu dữ liệu đặc biệt

void là kiểu dữ liệu mà không trả lại gì bất kì giá trị nào cả

bool (Boolean): là kiểu dữ liệu true (1)/false (0) (1 bit)

Cách khai báo biến (Variable) [Ngôn ngữ C/C++]

int n = 0;
double d = 0.0;

Cơ số (Base)

Cơ số là cách biểu diễn số trong hệ thống máy tính. Có 4 cơ số phổ biến nhất là: 2, 8, 10, 16. Cơ số 10 là cơ số mà chúng ta thường sử dụng trong cuộc sống hàng ngày, còn cơ số 2, 8, 16 thì thường được sử dụng trong lập trình. Đặc biệt máy tính chỉ hiểu được cơ số 2.

Toán tử bits (Bitwise operators)

Toán tử bits là những toán tử dùng để thực hiện các phép toán trên các bit của 2 số nguyên. Các toán tử này thường được sử dụng để tối ưu hóa code hoặc thực hiện các phép toán logic. Lưu ý toán tử này sẽ chỉ dùng cho hệ cơ số 2.

Lưu ý: Ví dụ sẽ sử dụng unsigned int để toàn bộ số là số dương để tránh nhầm lẫn

X Y X & Y X | Y X ^ Y
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0

Áp dụng bảng trên ta có ví dụ sau đây:

// Biểu diễn số 3 và 6 dưới dạng hệ cơ số 2 - 4 bits
unsigned int a = 3; // 0011
unsigned int b = 6; // 0110

unsigned int xANDy = a & b; // 0010 = 2
unsigned int xORy = a | b; // 0111 = 7
unsigned int xXORy = a ^ b; // 0101 = 5

Ngoài ra có một số phép toán tử

  • ~ (NOT): Đảo ngược bit
  • << (Left shift): Dịch trái bit
  • >> (Right shift): Dịch phải bit
unsigned int a = 3; // 0011

unsgined int notA = ~a; // 1100 = 12
unsigned int leftShiftA = a << 1; // 0110 = 6
unsigned int rightShiftA = a >> 1; // 0001 = 1

Mảng (Array)

Mảng là 1 khái niệm có lẽ là mới hoặc xa lạ nhưng thực chất thì không có gì khó hiểu cả. Ở đây mình sẽ chỉ giới thiệu về mảng 1 chiều và mảng 2 chiều.

Mảng 1 chiều

Bạn hãy tưởng tượng một ngăn chứa chỉ có 1 hàng và 10 ô (hoặc ma trận 1x10). Đó chính là mảng 1 chiều.

Array

Vị trị đầu tiên của mảng lúc nào cũng là số 0, nếu bạn thử truy cập vào -1 chắc chắn sẽ bị lỗi.

Số lượng bộ nhớ mà mảng 1 chiều sử dụng sẽ bằng số ô * bộ nhớ sử dụng của kiểu dữ liệu

Ví dụ: intArray[10] sẽ dùng 10 * 4 bytes = 40 bytes

// Cách để khai báo 1 mảng 1 chiều có 10 phần tử 
int intArrray[10];

intArrray[0] = 1;
printf("%d", intArray[0]); // Sẽ in ra màn hình số 1

Mảng 2 chiều

Bây giờ bạn hãy tưởng tượng thử ngăn chứa có 9 hàng và 10 ô (hoặc ma trận 9x10). Thì bây giờ mảng của bạn là mảng 2 chiều.

Vị trí đầu vào vẫn là 0 như mảng 1 chiều.

Số lượng bộ nhớ cũng như 1 mảng chiều nhưng nhân thêm với cả số hàng.

// Cách để khai báo 1 mảng 2 chiều có 9 hàng và 10 cột
int intArrray[9][10];

intArrray[0][0] = 1;
printf("%d", intArray[0][0]); // Sẽ in ra màn hình số 1

Toán tử (Operators) và Luồng điều khiển / rẽ nhánh (if - else / switch - case)

Toán tử

Như chúng ta đã đuọc học ở môn toán, lập trình cũng có nhiều điểm tương đồng.

Chúng ta có các phép như cộng (+), trừ (-), nhân (*), chia lấy nguyên (/) và chia lấy dư (%).

int i = 0;
int y = 0;

// Các cách viết tối (có thể thay y bằng 1 số)
i += y; // Tương đương với i = i + y;
i -= y; // Tương đương với i = i - y;
i *= y; // Tương đương với i = i * y;
i /= y; // Tương đương với i = i / y;
i %= y; // Tương đương với i = i % y;

// Ngoài ra chúng ta còn có
i++; // Tương đương với i = i + 1;
i--; // Tương đương với i = i - 1;
++i; // Giống như i nhưng mà trong trường hợp lấy dữ liệu thì sẽ lấy i trước xong mới +1 vào i
--i; // Tương tự như ++i nhưng -1;

Luồng điều khiển và rẽ nhánh

Một trong những yếu tố quan trọng để giúp lập trình chính là logic. Luồng điều khiển và lệnh rẽ nhánh giúp chúng ta thực hiện những lệnh mà khi đáp ứng được điều kiện.

Chúng ta cũng bổ sung 1 số toán tử cho phần này như sau:

A B A == B A != B A && B A || B
true true true false true true
true false false true false true
false true false true false true
false false true false false false
  • > (Lớn hơn - Greater): kiểm tra xem A lớn hơn B
  • >= (Lớn hơn hoặc bằng - Greater or equal): kiểm tra xem A bé hơn hoặc bằng B
  • < (Bé hơn - Less): kiểm tra xem A lớn hơn B
  • <= (Bé hơn hoặc bằng - Less or equal): kiểm tra xem A bé hơn hoặc bằng B

if - else

int x = 5;
int y = 7;

if (x == y) {
    // Không xảy ra vì x != y;
}

if ((x != y) && (y > 5)) {
    // Xảy ra vì cả điều kiện 1 và 2 đều đúng
}

if ((x != y) && (y > 5) && (x > 10)) {
    // Không xảy ra vì điều kiện 3 sai
}

// Ngoài ra bạn cũng có thể lồng if else như thế này
if (điều kiện 1) {
    // Thực hiện nếu điều kiện 1 đúng
} else if (điều kiện 2) {
    // Thực hiện nếu điều kiện 1 sai và điều kiện 2 đúng
} else if (điều kiện 3) {
    // Thực hiện nếu điều kiện 1, 2 sai và điều kiện 3 đúng
} else {
    // Thực hiện nếu không điều kiện nào thõa mãn
}

switch - case

int weekDay = 0;
scanf("%d", &weekDay);

switch(weekDay) {
    case 1: {
        // Thực hiện nếu ngày nhập vào là 1
        break;
    }
    case 2: {
        // Thực hiện nếu ngày nhập vào là 2
        break;
    }
    case 3: {
        // Thực hiện nếu ngày nhập vào là 3
        break;
    }
    case 4: {
        // Thực hiện nếu ngày nhập vào là 4
        break;
    }
    case 5: {
        // Thực hiện nếu ngày nhập vào là 5
        break;
    }
    case 6: {
        // Thực hiện nếu ngày nhập vào là 6
        break;
    }
    case 7: {
        // Thực hiện nếu ngày nhập vào là 7
        break;
    }
    default {
        // Thực hiện nếu ngày nhập vào không rơi vào bất kì trường hợp nào ở trên
        break;
    }
}

Vòng lặp - Loop (for/while/do - while)

Cũng tương tự như các câu lệnh rẽ nhánh, vòng lặp được tạo ra khi bài toàn muốn chúng ta phải lặp lại nhiều tác vụ.

Ví dụ khi bạn bị bắt chép phạt 5 trang, vì bạn khong phải máy tinh nên bạn chỉ có thể tự chép bằng tay. Trong lập trình vòng lặp sẽ giúp bạn có thể thực hiện lại 1 tác vụ giống nhau mà không cần phải viết đi viết lại.

for (int i = 0; i < 10; i++) {
    printf("Em xin hứa lần sau sẽ không tái phạm nữa.");
}
// Chương trình sẽ in ra 10 dòng 'Em xin hứa lần sau sẽ không tái phạm nữa.'

for

Vòng for được sử dụng khi bạn biết trước số lượng lần cần lặp. Vì vậy bên trong for sẽ có 3 phần

  • Phần 1: khai báo biến đếm int i = 0
  • Phần 2: điều kiện kết thúc lặp i < 10
  • Phần 3: bước nhảy i++

while

Vòng while được sử dụng khi bạn không biết phải lặp bao nhiêu lần. Vì vậy bên trong sẽ chỉ có 1 phần

  • Điều kiện kết thúc vòng lặp

Ví dụ:

int num = 12345;
int reversedNum = 0;

// Trường hợp này dùng lặp vì không thể nào biết được 1 số có bao nhiêu chữ số (hoặc bạn có thể dùng log10)
while (num > 0) {
    reversedNum = reversedNum * 10 + (num % 10);
    n /= 10;
}

do while

Tương tự như while nhưng sẽ lặp ít nhất 1 lần

int x = 0;

do {
    printf("Hello World!");
} while (x > 0);
// Sẽ in ra 1 lần "Hello World!" mặc dù x không lớn hơn 0;

Hàm (Function) và Đệ Quy (Recursion)

Hàm (Function)

Ở các chương trình lớn, không thể nào mà viết hết tất cả vào hàm main được mà phải chia nhỏ code ra làm nhiều phần khác nhau. Vì vậy hàm là 1 trong những tính năng vô cùng quan trọng của lập trình

  • Giúp bạn không viết lặp code
  • Giúp code rõ ràng và mạch lạc hơn
  • Giúp cô lập những phần không liên quan ra chỗ khác
int sum(int a, int b) {
    return a + b;
}

int main() {
    int a = 2;
    int b = 3;
    int sum = sum(a, b);

    printf("%d", sum); // In ra số 5
}

Đệ quy (Recursion)

Đệ quy xảy ra khi gọi 1 hàm trong chính hàm đó

int factorial(int a) {
    if (a == 1) return 1;
    return a * factorial(a - 1);
}

int main() {
    int result = factorial(5);
    printf("%d", result); // In ra 120
}

Về mặt toán học, đoạn code factorial(int a) có thể viết như sau; Cho f(x) = a.f(x - 1) với f(1) = 1x > 0 Ta có:

  • f(5) = 5.f(4)
  • f(4) = 4.f(3)
  • f(3) = 3.f(2)
  • f(2) = 2.f(1)

Và từ đó viết ngược lại:

  • f(2) = 2.1 = 2
  • f(3) = 3.2 = 6
  • f(4) = 4.6 = 24
  • f(5) = 5.24 = 120

Đây là những gì sẽ xảy ra trong đệ quy, máy tính sẽ lưu hàm vào cái gọi là stack, và chỉ dừng lưu đến khi code đến khi return không gọi chính hàm đấy nữa. Trong trường hợp này cihnhs là đoạn if (a == 1) return 1;

Lưu ý: Phần lớn các bài có thể giải bằng được cả vòng lặp lẫn đệ quy, nhưng khi chúng ta suy nghĩ 1 bài toán như dãy fibonacci thì cách tiếp cận đệ quy có thể sẽ dễ hơn. Bù lại sử dụng đệ quy để giải tốn nhiêu tài nguyên máy hơn so với vòng lặp

Con trỏ (Pointer) và tham chiếu (Reference)

Để hiểu về khái niệm này, chúng ta cần phải biết một chút về cách máy tính hoạt động, cụ thể là về phần bộ nhớ. Stack và Heap

Ở giai đoạn cơ bản, bạn chỉ cần nhớ 1 vai trò của con trỏ, đó chính là truyền địa chỉ biến. Chúng ta sẽ xét một trường hợp cụ thể.

int swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int a = 3;
    int b = 6;

    swap(a, b);

    printf("a = %d, b = %d", a, b);
}

c Lúc này nếu bạn đoán rằng chương trình sẽ in ra a = 6, b = 3, thì bạn đã sai. Chương trình vẫn sẽ chỉ in ra a = 3, b = 6.

Thực chất, việc truyền 2 biến ab vào hàm swap(int a, int b) là truyền giá trị của biến vào cho hàm. Tức là biến a ở trong hàm main()a ở trong swap(int a, int b) là 2 biến khác nhau (vì chúng không cùng địa chỉ). Do đó bạn việc truyền gia trị này giống với việc copy giá trị từ biến này qua biến khác (2 biến độc lập).

Thế nên chúng ta mới phải sử dụng đến con trỏ:

// Sử dụng * đằng trước biến trong đây giúp chúng ta đặt hàm này nhận 2 con trỏ vào
int swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int a = 3;
    int b = 6;

    // '&' đằng trước biến để chỉ đến địa chỉ cua biến
    swap(&a, &b);

    printf("a = %d, b = %d", a, b);
}

Lúc này chương trình sẽ thực hiện việc đổi chỗ giữa 2 biến ab.

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@justapieop
Copy link

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment