Nhập Môn Lập Trình C - Phần 2

BinhHT
NHẬP MÔN LẬP TRÌNH C
7926

CHƯƠNG 2: CÁC CẤU TRÚC ĐIỀU KHIỂN TRONG LẬP TRÌNH
I. GIỚI THIỆU


Những chương trình trong Chương I mình đã giới thiệu bao gồm các chỉ thị được thực hiện một cách tuần tự ( từ trên xuống ). Trong thực tế thì để viết một chương trình hoàn chỉnh, chúng ta cần phải sử dụng nhiều cấu trúc bổ sung thêm cho các chỉ thị tuần tự. Chẳng hạn như lặp lại một chỉ thị nào đó nhiều lần hay bỏ qua không thực hiện một vài chỉ thị không cần thiết nào đó.
Ở chương này mình sẽ trình bày chi tiết và cách sử dụng các cấu trúc điều khiển trong quá trình viết chương trình. Đây là những kiến thức rất cơ bản để làm nền tảng cho các bạn trong lĩnh vực lập trình. Tất cả các ngôn ngữ lập trình đều hỗ trợ các cấu trúc điều khiển tương tự nhau. Để khởi đầu cho việc làm quen với các cấu trúc điều khiển lập trình trong ngôn ngữ lập trình C, trước hết chúng ta cần tiếp cận với các khái niệm: khối lệnh, phạm vi sử dụng biến.

I.1 Khối lệnh
Một khối lệnh (block) là một tập hợp các lệnh nằm trong một cặp dấu ngoặc nhọn: bắt đầu bởi dấu { và kết thúc bởi dấu }. Các khối lệnh có thể lồng nhau: một khối lệnh có thể nằm bên trong một khối lệnh khác. Sau đây là code minh họa.

C:
// Demo.c
#include <stdio.h>

int main()
{
    int firstNumber = 0, secondNumber = 2;
    firstNumber = secondNumber++;
    printf("firstNumber = %d\n", firstNumber);
    {
        int thirdNumber;
        thirdNumber = firstNumber + secondNumber;
        printf("thirdNumber = %d\n",thirdNumber);
    }
    return 0;
}

Ở đoạn code trên, ta thấy có 2 khối lệnh, bên trong mỗi khối lệnh có thể khai báo các biến: những biến này được gọi là biến cục bộ. Chẳng hạn như firstNumber, secondNumber được khai báo, khởi tạo giá trị và sử dụng trong khối lệnh chính ( bắt đầu từ int firstNumber.... cho đến return 0, tạm gọi là A), còn thirdNumber được khai báo và sử dụng trong khối lệnh phụ ( bắt đầu từ int thirdNumber... đến printf(.....thirdNumber, tạm gọi là B). Trong đoạn code trên thì cả 3 biến firstNumber, secondNumber, thirdNumber được gọi là biến cục bộ. Những biến không nằm trong bất kỳ một khối lệnh nào được gọi là biến toàn cục.

I.2 Phạm vi sử dụng của biến

Các biến có thể được khai báo một cách cục bộ bên trong mỗi khối lệnh. Biến trong một khối lệnh không thể sử dụng được ở phía bên ngoài của khối lệnh đó. Cần lưu ý thêm:

  • Hai biến trong cùng một khối không thể trùng tên.
  • Biến trong một khối A có thể được hiểu trong các khối lệnh nằm lồng bên trong A, nghĩa là các biến firstNumber secondNumber có thể sử dụng được trong khối lệnh B.
  • Trong trường hợp trùng tên biến giữa biến bên trong một khối lệnh và biến nằm ở khối lệnh lồng bên ngoài: mặc định sẽ hiểu biến nằm trong phạm vi của khối lệnh gần nhất bao bọc vị trí sử dụng biến.
C:
#include <stdio.h>

int main()
{
    /* main block */
    int firstNumber = 1995, secondNumber = 2000;
    printf("first Number (main block) = %d\n", firstNumber);
    printf("second Number (main block) = %d\n", secondNumber);
    /* sub block */
    {
        int secondNumber = 1900;
        firstNumber = 1800;
        printf("first Number (of main block is changed) = %d\n", firstNumber);
        printf("second Number (sub block) = %d\n", secondNumber);
    }
    printf("Now in main block:\n");
    printf("first Number (changed) = %d\n", firstNumber);
    printf("second Number (unchanged) = %d\n", secondNumber);
    return 0;
}

Ở đoạn code trên, có sự trùng tên giữa các biến, biến firstNumber chỉ được khai báo một lần (int firstNumber = 1995) trong khối lệnh của hàm main(). Khối lệnh con ( sub block ) không có bất kỳ biến nào tên là firstNumber; vì vậy lệnh gán firstNumber = 1800 sẽ tác động lên biến firstNumber của khối lệnh chính, giá trị lưu trong firstNumber sẽ đổi thành 1800 ( thay cho giá trị cũ là 1995). Đối với trường hợp biến secondNumber, có 2 lần khai báo là ở khối lệnh chính và khối lệnh con (sub block). Bên trong khối lệnh con thì biến secondNumber đã được khai báo (int secondNumber = 1900). Nhưng khi xuất ra biến secondNumber ở câu lệnh (printf(secondNumber unchanged.... thì biến secondNumber sẽ có giá trị là 2000 do chương trình sẽ lấy biến ở khối lệnh lớn hơn.

Kết quả chạy chương trình.

first Number (main block) = 1995
second Number (main block) = 2000
first Number (of main block is changed) = 1800
second Number (sub block) = 1900
Now in main block:
first Number (changed) = 1800
second Number (unchanged) = 2000

II.2 CẤU TRÚC RẼ NHÁNH

Các cấu trúc lập trình rẽ nhánh được sử dụng trong trường hợp việc tính toán trong chương trình phụ thuộc vào một điều kiện luận lý nào đó, được viết dưới dạng một biểu thức luận lý. Khi biểu thức luận lý đúng thì một số lệnh tương ứng được thực hiện, nếu ngược lại (nghĩa là biểu thức luận lý sai) thì một số câu lệnh khác được thi hành. Ngôn ngữ lập trình C có sẵn hai cấu trúc rẽ nhánh là if elseswitch.

II.1 Cấu trúc rẽ nhánh if else

C:
if (/* condition */)
{
    /* code */
}

Đây là cấu trúc rẽ nhánh if. Phần bên trong if là phần điều kiện để khối lệnh bên dưới được thực thi ( là phần code ). Nếu điều kiện bên trong if là sai thì chương trình sẽ không thực hiện khối lệnh này.

C:
if (/* condition */)
{
    /* code */
}
else
{
    /* code */
}

Đây là cấu trúc rẽ nhánh if else. nếu điều kiện bên trong if là đúng thì nó sẽ thực hiện khối lệnh bên dưới câu lệnh if, còn nếu điều kiện bên trong if là sai thì nó sẽ thực hiện khối lệnh bên trong else. Sau đây là sơ đồ cấu trúc rẽ nhánh if else dạng đơn giản.

7927

Để làm rõ hơn cấu trúc if else thì mình sẽ lấy một ví dụ về chương trình so sánh hai số nguyên là firstNumber secondNumber.
C:
// Comparison.c
#include <stdio.h>

int main()
{
    int firstNumber = 10, secondNumber = 20;
    if(firstNumber > secondNumber)
    {
        printf("firstNumber > secondNumber");
    }
    else
    {
        printf("secondNumber > firstNumber");
    }
    return 0;
}

Trong đoạn code trên, mình đã khai báo và khởi tạo hai biến firstNumber secondNumber, sau đó mình dùng cấu trúc rẽ nhánh if else để so sánh hai biến này, điều kiện của câu lệnh if lúc này là firstNumber > secondNumber, nếu điều kiện if đúng thì câu lệnh printf("firstNumber > secondNumber"); sẽ được thực thi, ngược lại thì câu lệnh printf("secondNumber > firstNumber"); sẽ được thực thi. Và do firstNumber lớn hơn secondNumber (10 > 20) câu lệnh dưới if printf("firstNumber > secondNumber"); sẽ được thực thi.

Một số toán tử so sánh để ta sử dụng là == (so sánh bằng), > (lớn hơn), < (bé hơn), >= (lớn hơn bằng), <= (bé hơn bằng). Ngoài ra còn một số cách so sánh nữa, phần này mình sẽ trình bày trong các chương sau. Để kết hợp nhiều điều kiện so sánh thì ta có thể sử dụng hai từ khóa sau đây, && (tương đương với phép và) và phép || (tương đương với phép hoặc).

C:
//ComparisonOperator.c
#include <stdio.h>

int main()
{
    int firstNumber = 16, secondNumber = 8;
    if(firstNumber > secondNumber && firstNumber % 2 == 0)
    {
        printf("firstNumber > secondNumber and firstNumber là số chẵn.");
    }
    return 0;
}

Đoạn code trên mình đã sử dụng toán tử so sánh là &&, ý nghĩa ở đây là nếu firstNumber lớn hơn secondNumber firstNumber chia lấy dư cho 2 == 0 ( dùng để kiểm tra một số là chẵn hay lẻ) thì sẽ in ra là firstNumber > secondNumber firstNumber là số chẵn. Toán tử so sánh || cũng được sử dụng tương tự như thế. Sau đây là sơ đồ cho cấu trúc if else dạng đầy đủ.
7928

II.2 Cấu trúc rẽ nhánh switch

Nếu trong chương trình có nhiều câu lệnh if và phải thực hiện các câu lệnh khác nhau vì việc này sẽ làm mất thời gian của các lập trình viên vì khi đó chúng ta phải viết code dài và có thể nó không tường minh. Do đó có một loại cấu trúc rẽ nhánh thứ hai đó chính là switch.

C:
switch (expression)
{
case /* constant-expression */:
    /* code */
    break;

default:
    break;
}

Cấu trúc rẽ nhánh switch có dạng như trên. Phần bên trong dấu ngoặc đơn của câu lệnh switch ( expression) chính là giá trị mà bạn đưa vào để cho các case trong switch thực hiện. Mỗi case đều phải có câu lệnh break để kết thúc và thoát ra khỏi case đó, nếu ta không có câu lệnh break thì chương trình sẽ chạy cho đến khi nào gặp câu lệnh break thì thoát, khi đó chương trình của chúng ta sẽ bị lỗi và không còn đúng nữa.

C:
//Switch.c
#include <stdio.h>

int main()
{
    int value;
    printf("Nhập số nguyên: ");
    scanf("%d",&value);
    switch (value) {
    case 0:
        printf("\nSố 0");
        break;
    case 1:
        printf("\nSố 1");
        break;
    case 2:
        printf("\nSố 2");
        break;
    case 3:
        printf("\nSố 0");
        break;
    default:
        printf("\nCác số khác 0, 1, 2, 3");
        break;
    }
    return 0;
}

Đoạn code trên mô tả cách sử dụng cấu trúc rẽ nhánh switch. Đầu tiên ta khai báo một biến value, sau đó nhập giá trị cho biến value, ta tạo ra 4 case case 0, case 1, case 2case 3. Câu lệnh default có nghĩa là nếu giá trị nhập vào không thỏa các case thì nó sẽ mặc định là thực hiện các câu lệnh bên trong default. Khi ta nhập số 0 thì case 0 sẽ nhận giá trị và thực hiện câu lệnh tương ứng ( ở đây sẽ thực hiện câu lệnh in ra số 0), sau đó gặp câu lệnh break thì sẽ thoát ra khỏi switch và thực hiện các câu lệnh bên ngoài switch. Khi ta nhập giá trị không phải là 0, 1, 2 hay 3 thì chương trình sẽ thực hiện câu lệnh default (in ra dòng chữ Các số khác 0 1 2 3 và thoát ra khỏi switch).

Switch được sử dụng khi ta phải xét nhiều trường hợp khác nhau.

III CẤU TRÚC LẶP

Các cấu trúc lặp đóng vai trò rất quan trọng trong việc lập trình. Những cấu trúc này được sử dụng để thực hiện lặp đi lặp lại nhiều lần một nhóm các chỉ thị nào đó, mọi ngôn ngữ lập trình đều xây dựng sẵn các cấu trúc lặp hay đưa ra những cách thức để hỗ trợ tính toán lặp.

Ngôn ngữ C và các ngôn ngữ khác có 3 cấu trúc lặp: while, do while for.

Ví dụ như mình muốn in 10 lần câu lệnh printf("Hello World"); thì mình sẽ phải làm như thế nào ?. Viết 10 lần câu lệnh printf("Hello World"); ?. Điều này là đúng nhưng nếu số lượng là 100 hay thậm chí là 1000 thì không lẽ các bạn cứ ngồi viết 1000 câu lệnh như thế , việc này rất mất thời gian. Ngoài ra việc tính toán các số như từ 1 đến 100 thì chúng ta sẽ làm như thế nào ?. Ngồi viết từng câu lệnh cộng các số lại từ 1 đến 100 phải không ?. Do đó các ngôn ngữ lập trình đã hỗ trợ 3 loại cấu trúc lặp như mình đã nói ở trên để giải quyết vấn đề này.

III.1 Cấu trúc while
C:
while (/* condition */)
{
    /* code */
}
Đây là cách khai báo và sử dụng cấu trúc while, trước hết khi gặp cấu trúc dạng này, chương trình sẽ kiểm tra câu lệnh bên trong while là đúng hay sai, nếu đúng thì thực hiện câu lệnh bên trong while còn không thì bỏ qua và đi đến lệnh kế tiếp bên dưới. Dưới đây là sơ đồ cấu trúc dạng while.

7929

Cấu trúc while mặc dù nhìn giống cấu trúc if nhưng về cơ bản thì chúng hoàn toàn khác nhau. Ở cấu trúc while thì khi điều kiện trong while đúng, nó sẽ thực thi câu lệnh bên dưới, sau khi thực hiện xong nó sẽ quay lại kiểm tra coi điều kiện trong while còn đúng không, nếu đúng thì nó sẽ thực hiện tiếp câu lệnh bên dưới while cho đến khi nào điều kiện trong while sai thì nó kết thúc vòng lặp while này và sang các câu lệnh kế tiếp.

C:
//While.c
#include <stdio.h>

int main()
{
    int i = 0;
    int S = 0;
    while(i < 10)
    {
        S += i;
        i++;
    }
    printf("S = %d\n",S);
}

Đoạn code trên cài đặt chương trình tính tổng S từ 0 đến 10. Ở đây ta khai báo một biến i, biến i lúc này còn được gọi là biến khởi tạo để chạy vòng lặp (mình sẽ nói rõ hơn ở vòng lặp for). Trước hết chương trình kiểm tra xem i có nhỏ hơn 10 không, nếu nhỏ hơn thì thực hiện câu lệnh S = S + i; rồi sau đó thực hiện câu lệnh i++; ( i = i + 1). Sau đó lại quay về kiểm tra điều kiện while, nếu i vẫn bé hơn 10 thì thực hiện hai câu lệnh bên dưới cho đến khi điều kiện không còn đúng ( i = 10) thì sẽ thoát ra và thực hiện các câu lệnh bên dưới.

III.2 Cấu trúc do while

Ngôn ngữ C cung cấp cấu trúc lặp do while, khác đôi chút so với cấu trúc while. Trong cấu trúc do while, việc thực hiện các lần lặp cũng được thực hiện trong khi biểu thức điều kiện lặp có giá trị true (đúng). Tuy nhiên điểm khác nhau là khối lệnh bên trong cấu trúc do while được thực hiện trước khi kiểm tra điều kiện lặp, điều này sẽ thuận lợi hơn khi cài đặt một số thuật toán cụ thể.

Về mặt tổng quát, cấu trúc do while hoạt động như sau:
  • Bước 1: Thực hiện các lệnh trong khối lệnh A;
  • Bước 2: Nếu điều kiện condition đúng thì trở lại Bước 1;
  • Bước 3: Thực hiện lệnh kế tiếp (sau cấu trúc do while).
C:
do
{
// Khối lệnh A ...
}while(condition);
// Lệnh kế tiếp
...

7931

C:
#include <stdio.h>

int main()
{
    int S = 0, i = 0;
    do
    {
        i = 11;
        S += i;
        i++;
    }while(i < 10);
    printf("%d",S);
    return 0;
}
Ở đây các bạn có thể thấy, mặc dù i trong vòng lặp do while được gán là bằng 11 nhưng nó vẫn thực hiện 1 lần. Chương trình sẽ xử lí như sau.
  • Bước 1: Khai báo và khởi tạo giá trị cho biến S = 0i = 0;​
  • Bước 2: Gán giá trị 11 cho biến i.​
  • Bước 3: Gán giá trị cho SS = S + i;​
  • Bước 4: Tăng i lên 1 đơn vị ( i++ tương đương với i = i + 1).​
  • Bước 5: Kiểm tra điều kiện i có bé hơn 10 hay không, nếu bé hơn thì quay trở lại vòng lặp do while, còn không thì thoát ra.​
Như vậy, sự khác nhau giữa while do while là như sau, với vòng lặp while thì chương trình sẽ kiểm tra điều kiện trước rồi mới thực hiện câu lệnh bên trong while, còn với vòng lặp do while thì chương trình sẽ thực hiện ít nhất 1 lần rồi mới kiểm tra điều kiện. Do đó trong quá trình lập trình, các bạn nên lựa chọn hợp lí giữa hai loại vòng lặp này.

III. 3 Cấu trúc for

Cấu trúc for trong ngôn ngữ C rất mạnh và có thể sử dụng rất linh động trong nhiều tình huống khi viết chương trình. Cấu trúc for được sử dụng rất nhiều trong quá trình viết code, nó là một cấu trúc quan trọng để ta thực hiện các công việc như tìm kiếm, sắp xếp, tính tổng, bla bla...

C:
for (size_t i = 0; i < count; i++)
{
    /* code */
}

Bên trên là một cấu trúc for điển hình, có thể tùy từng trường hợp mà nó có cách thể hiện khác nhau một chút, nhưng nhìn chung là giống như tiêu chuẩn bên trên. Cấu trúc for gồm có 3 phần như sau: for(<biểu thức 1>;<biểu thức 2>;<biểu thức 3>) Các câu lệnh....
  • Biểu thức 1: Thường được sử dụng để khởi gán một hay nhiều biến, Biểu thức 1 được thực hiện một lần duy nhất khi bắt đầu cấu trúc, ngược lại Biểu thức 2 và 3 sẽ được thực hiện vào mỗi lần lặp.
  • Biểu thức 2: Thường là điều kiện để vòng lặp có thể lặp, khi điều kiện này không còn đúng thì vòng lặp sẽ kết thúc. Nếu Biểu thức 2 không có thì điều kiện kiểm tra của vòng lặp sẽ được mặc định là luôn luôn đúng, điều này sẽ dẫn đến việc lặp vô tận.
  • Biểu thức 3: Dùng để tăng bước nhảy của vòng lặp. Ở đây thì i++ nghĩa là sau khi thực hiện vòng lặp thì sẽ tăng i lên 1 đơn vị và sau đó so sánh với điều kiện ở Biểu thức 2.
C:
...
for(A, condition, B)
{
// Khối lệnh D ...
}
// Lệnh kế tiếp
...


7934

C:
#include <stdio.h>

int main()
{
    int S = 0;
    for(int i = 0; i < 10; i++)
    {
        S += i;
    }
    printf("%d",S);
    return 0;
}
Đoạn code trên cũng có chức năng là tính tổng S từ 0 đến 10 nhưng ở đây là xài vòng lặp for. Vòng for lúc này sẽ thực hiện như sau.
  • Bước 1: Khởi tạo biến i và gán giá trị cho biến i = 0;
  • Bước 2: Kiểm tra điều kiện xem i có nhỏ hơn 10 hay không, nếu nhỏ hơn thì đến Bước 3.
  • Bước 3: Thực hiện câu lệnh S = S + i;
  • Bước 4: Tăng i lên một đơn vị thông qua câu lệnh i++;.
  • Bước 5: Quay trở lại Bước 2, nếu đúng thì tiếp tục. Còn sai thì qua Bước 6.
  • Bước 6: Kết thúc vòng lặp và sang các câu lệnh kế tiếp
Mình đã trình bày xong những kiến thức cơ bản về khối lệnh, phạm vi sử dụng của biến và hai dạng cấu trúc đó là cấu trúc rẽ nhánh và cấu trúc lặp. Hi vọng qua bài viết này các bạn sẽ hiểu rõ và nắm vững cơ bản các loại cấu trúc để có cách sử dụng theo từng trường hợp cho phù hợp. Cảm ơn các bạn đã đọc bài.
 
Sửa lần cuối:
Trả lời

alex1989

Búa Gỗ Đôi
Này phãi vô truong lớp hoc mới đúng bài, tự học thì không biết phải từ dau, cái gì cũng tiếp thu là loạn hết không có nền cớ bản thì sẽ mau quên không hiểu đoạn code đó có ý nghia gì và sẽ chỉ copy past không có tư duy vê thuat toán dua trên nền code cơ bản. C++ là ngon ngư cơ bản để học các ngôn ngư C#, asp.net. .. Không nắm vững nền thì sẽ không tiến bộ dc. Học o truong lop còn di thi kiễm tra có dộng luc cầm viết học thuoc code, chứ tư học sẽ rất là lazy, không có 1 cái " nền móng" chắc chắn!
 

LegendSamurai

Rìu Bạc Đôi
Bác chịu khó thật! {bye}trình bày trên forum mà chỉn chu vãi
 

dangthaihoc

Búa Gỗ Đôi
hay quá ạ. Tiện chủ thớt làm luôn phần hướng đối tượng vs giải thuật nhé!
làm cả C# nữa thì tốt quá. hehe