Week 1 C
Chào mừng bạn!
Trong buổi học trước, chúng ta đã tìm hiểu về Scratch, một ngôn ngữ lập trình trực quan.
Thực tế, tất cả các khái niệm lập trình thiết yếu được trình bày trong Scratch sẽ được tận dụng khi bạn học cách lập trình bằng bất kỳ ngôn ngữ lập trình nào. Các hàm (functions), điều kiện (conditionals), vòng lặp (loops) và biến (variables) tìm thấy trong Scratch là những khối xây dựng cơ bản mà bạn sẽ thấy trong mọi ngôn ngữ lập trình.
Hãy nhớ lại rằng máy tính chỉ hiểu hệ nhị phân. Trong khi con người viết mã nguồn (source code) — một danh sách các hướng dẫn cho máy tính mà con người có thể đọc được — thì máy tính chỉ hiểu những gì chúng ta gọi là mã máy (machine code). Mã máy này là một mẫu các số một và số không tạo ra hiệu ứng mong muốn.
Hóa ra chúng ta có thể chuyển đổi mã nguồn thành mã máy bằng một phần mềm rất đặc biệt gọi là trình biên dịch (compiler). Hôm nay, chúng tôi sẽ giới thiệu cho bạn một trình biên dịch cho phép bạn chuyển đổi mã nguồn bằng ngôn ngữ lập trình C thành mã máy.
Hôm nay, ngoài việc học cách lập trình, bạn sẽ được học cách viết mã tốt.
Visual Studio Code dành cho CS50
Trình soạn thảo văn bản được sử dụng cho khóa học này là Visual Studio Code, hay còn gọi là VS Code, được gọi một cách thân mật là cs50.dev, có thể truy cập qua URL đó.
Một trong những lý do quan trọng nhất khiến chúng ta sử dụng VS Code là nó đã được tích hợp sẵn tất cả các phần mềm cần thiết cho khóa học. Khóa học này và các hướng dẫn trong đây được thiết kế dành riêng cho VS Code.
Việc tự cài đặt các phần mềm cần thiết cho khóa học trên máy tính cá nhân là một công việc rất phức tạp và phiền toái. Tốt nhất là luôn sử dụng VS Code cho các bài tập trong khóa học này.
Bạn có thể mở VS Code tại cs50.dev.
Trình biên dịch có thể được chia thành một số khu vực:
Lưu ý rằng có một trình duyệt tệp (file explorer) ở phía bên trái, nơi bạn có thể tìm thấy các tệp của mình. Tiếp theo, hãy chú ý khu vực ở giữa được gọi là trình soạn thảo văn bản (text editor), nơi bạn có thể chỉnh sửa chương trình của mình. Cuối cùng, có một giao diện dòng lệnh (command line interface), được gọi là CLI, dòng lệnh (command line), hoặc cửa sổ terminal (terminal window), nơi chúng ta có thể gửi lệnh đến máy tính trên đám mây.
- Trong cửa sổ terminal, một số lệnh phổ biến mà chúng ta có thể sử dụng bao gồm:
cd, dùng để thay đổi thư mục hiện hành
cp, dùng để sao chép tệp và thư mục
ls, dùng để liệt kê các tệp trong một thư mục
mkdir, dùng để tạo một thư mục mới
mv, dùng để di chuyển (đổi tên) tệp và thư mục
rm, dùng để xóa tệp
rmdir, dùng để xóa thư mục
Lệnh được sử dụng phổ biến nhất là
ls, lệnh này sẽ liệt kê tất cả các tệp trong thư mục hiện tại. Hãy thử gõlsvào cửa sổ terminal và nhấnenter. Bạn sẽ thấy tất cả các tệp trong thư mục hiện hành.Vì môi trường IDE này đã được cấu hình sẵn với tất cả các phần mềm cần thiết, bạn nên sử dụng nó để hoàn thành tất cả các bài tập của khóa học.
Hello World
Chúng ta sẽ sử dụng ba lệnh để viết, biên dịch và chạy chương trình đầu tiên:
code hello.c
make hello
./hello
Lệnh đầu tiên, code hello.c, tạo một tệp và cho phép chúng ta nhập các hướng dẫn cho chương trình này. Lệnh thứ hai, make hello, biên dịch tệp từ các hướng dẫn bằng ngôn ngữ C của chúng ta và tạo ra một tệp thực thi gọi là hello. Lệnh cuối cùng, ./hello, chạy chương trình có tên là hello.
Chúng ta có thể xây dựng chương trình đầu tiên bằng C bằng cách gõ code hello.c vào cửa sổ terminal. Lưu ý rằng chúng ta cố tình viết thường toàn bộ tên tệp và bao gồm phần mở rộng .c. Sau đó, trong trình soạn thảo văn bản hiện ra, hãy viết mã như sau:
// A program that says hello to the world
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
}
Lưu ý rằng mỗi ký tự ở trên đều có mục đích riêng. Nếu bạn gõ sai, chương trình sẽ không chạy. printf là một hàm có thể xuất ra một dòng văn bản. Hãy chú ý vị trí của các dấu ngoặc kép và dấu chấm phẩy. Ngoài ra, hãy lưu ý rằng \n tạo ra một dòng mới sau cụm từ hello, world.
Nhấp chuột trở lại cửa sổ terminal, bạn có thể biên dịch mã của mình bằng cách thực hiện lệnh
make hello. Lưu ý rằng chúng ta bỏ qua phần.c.makelà một trình biên dịch sẽ tìm tệphello.ccủa chúng ta và biến nó thành một chương trình có tên làhello. Nếu việc thực hiện lệnh này không gây ra lỗi, bạn có thể tiếp tục. Nếu không, hãy kiểm tra lại mã của bạn để đảm bảo mã khớp với đoạn mã trên.Bây giờ, hãy gõ
./hellovà chương trình của bạn sẽ thực thi và nóihello, world.Bây giờ, hãy mở trình duyệt tệp ở bên trái. Bạn sẽ nhận thấy rằng hiện có cả một tệp tên là
hello.cvà một tệp khác tên làhello.hello.clà tệp mà trình biên dịch có thể đọc: Đó là nơi lưu trữ mã của bạn.hellolà một tệp thực thi mà bạn có thể chạy nhưng trình biên dịch không thể đọc.
Từ Scratch sang C
- Trong Scratch, chúng ta đã sử dụng khối
sayđể hiển thị bất kỳ văn bản nào trên màn hình. Trong C, chúng ta có một hàm gọi làprintfthực hiện chính xác điều này.
Lưu ý mã của chúng ta đã gọi hàm này:
printf("hello, world\n");
Lưu ý rằng hàm printf được gọi. Đối số được truyền cho printf là hello, world\n. Câu lệnh mã được kết thúc bằng dấu ;.
Lỗi trong mã là điều phổ biến. Hãy sửa đổi mã của bạn như sau:
// \n is missing
#include <stdio.h>
int main(void)
{
printf("hello, world");
}
Lưu ý rằng \n hiện đã biến mất.
- Trong cửa sổ terminal, hãy chạy
make hello. Gõ./hellotrong cửa sổ terminal, chương trình của bạn đã thay đổi như thế nào? Ký tự\này được gọi là một ký tự thoát (escape character) thông báo cho trình biên dịch rằng\nlà một hướng dẫn đặc biệt để tạo ngắt dòng.
Có những ký tự thoát khác mà bạn có thể sử dụng:
\n tạo dòng mới
\r quay lại đầu dòng
\" in dấu ngoặc kép
\' in dấu ngoặc đơn
\\ in dấu gạch chéo ngược
Khôi phục chương trình của bạn về như sau:
// A program that says hello to the world
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
}
Lưu ý dấu chấm phẩy và \n đã được khôi phục.
Tệp tiêu đề và Trang hướng dẫn CS50 (CS50 Manual Pages)
Câu lệnh ở đầu mã
#include <stdio.h>là một lệnh rất đặc biệt thông báo cho trình biên dịch rằng bạn muốn sử dụng các khả năng của một thư viện gọi làstdio.h, một tệp tiêu đề (header file). Điều này cho phép bạn, cùng với nhiều thứ khác, sử dụng hàmprintf.Một thư viện là một tập hợp mã do ai đó tạo ra. Thư viện là tập hợp các mã và hàm viết sẵn mà những người khác đã viết trong quá khứ mà chúng ta có thể sử dụng trong mã của mình.
Bạn có thể đọc về tất cả các khả năng của thư viện này trên Manual Pages. Các Trang hướng dẫn cung cấp một phương tiện để hiểu rõ hơn các lệnh khác nhau thực hiện những gì và cách chúng hoạt động.
Hóa ra CS50 có thư viện riêng gọi là cs50.h. Có rất nhiều hàm được bao gồm cung cấp bánh xe tập đi trong khi bạn mới bắt đầu học C:
get_char
get_double
get_float
get_int
get_long
get_string
- Hãy sử dụng thư viện này trong chương trình của bạn.
Xin chào bạn (Hello, You)
- Hãy nhớ lại rằng trong Scratch chúng ta có khả năng hỏi người dùng, “Tên bạn là gì?” và nói “hello” với tên đó được đính kèm phía sau.
Trong C, chúng ta cũng có thể làm tương tự. Hãy sửa mã của bạn như sau:
// get_string and printf with incorrect placeholder
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string answer = get_string("What's your name? ");
printf("hello, answer\n");
}
Hàm get_string được sử dụng để nhận một chuỗi (string) từ người dùng. Sau đó, biến answer được truyền cho hàm printf.
- Chạy lại
make hellotrong cửa sổ terminal, lưu ý rằng có rất nhiều lỗi xuất hiện.
Nhìn vào các lỗi, string và get_string không được trình biên dịch nhận dạng. Chúng ta phải dạy cho trình biên dịch những tính năng này bằng cách thêm một thư viện gọi là cs50.h. Ngoài ra, chúng ta nhận thấy rằng answer không được cung cấp như chúng ta mong muốn. Hãy sửa mã của bạn như sau:
// get_string and printf with %s
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string answer = get_string("What's your name? ");
printf("hello, %s\n", answer);
}
Hàm get_string được sử dụng để nhận một chuỗi từ người dùng. Sau đó, biến answer được truyền cho hàm printf. %s báo cho hàm printf chuẩn bị nhận một string.
- Bây giờ, hãy chạy lại
make hellotrong cửa sổ terminal, bạn có thể chạy chương trình bằng cách gõ./hello. Chương trình bây giờ sẽ hỏi tên của bạn và sau đó nói xin chào kèm theo tên của bạn, đúng như mong đợi.
answer là một nơi lưu giữ đặc biệt mà chúng ta gọi là một biến (variable). answer có kiểu dữ liệu là string và có thể chứa bất kỳ chuỗi nào bên trong nó. Có nhiều kiểu dữ liệu khác như int, bool, char và nhiều kiểu khác nữa.
%s là một trình giữ chỗ được gọi là mã định dạng (format code) thông báo cho hàm printf chuẩn bị nhận một string. answer là string đang được truyền vào %s.
Kiểu dữ liệu (Types)
printf cho phép sử dụng nhiều mã định dạng. Dưới đây là danh sách không đầy đủ các mã định dạng mà bạn có thể sử dụng trong khóa học này:
%c
%f
%i
%li
%s
%s được sử dụng cho các biến string. %i được sử dụng cho các biến int hoặc số nguyên. Bạn có thể tìm hiểu thêm về điều này trên Manual Pages.
Các mã định dạng này tương ứng với nhiều kiểu dữ liệu có sẵn trong C:
bool
char
float
int
long
string
...
Chúng ta sẽ sử dụng nhiều kiểu dữ liệu có sẵn của C trong suốt khóa học này.
Cấu trúc điều kiện (Conditionals)
Một khối xây dựng khác mà bạn đã sử dụng trong Scratch là điều kiện. Ví dụ, bạn có thể muốn thực hiện một việc nếu x lớn hơn y. Ngoài ra, bạn có thể muốn thực hiện một việc khác nếu điều kiện đó không được đáp ứng.
Chúng ta cùng xem một vài ví dụ từ Scratch.
Trong C, bạn có thể so sánh hai giá trị như sau:
// Conditionals that are mutually exclusive
if (x < y)
{
printf("x is less than y\n");
}
else
{
printf("x is not less than y\n");
}
Lưu ý nếu x < y, một kết quả sẽ xảy ra. Nếu x không nhỏ hơn y, thì một kết quả khác sẽ xảy ra.
Tương tự, chúng ta có thể lập kế hoạch cho ba kết quả có thể xảy ra:
// Conditional that isn't necessary
if (x < y)
{
printf("x is less than y\n");
}
else if (x > y)
{
printf("x is greater than y\n");
}
else if (x == y)
{
printf("x is equal to y\n");
}
Lưu ý rằng không phải tất cả các dòng mã này đều bắt buộc. Làm thế nào chúng ta có thể loại bỏ phép tính không cần thiết ở trên?
Bạn có lẽ đã đoán được rằng chúng ta có thể cải thiện mã này như sau:
// Compare integers
if (x < y)
{
printf("x is less than y\n");
}
else if (x > y)
{
printf("x is greater than y\n");
}
else
{
printf("x is equal to y\n");
}
Lưu ý câu lệnh cuối cùng được thay thế bằng else.
Các toán tử (Operators)
Toán tử đề cập đến các phép toán toán học được hỗ trợ bởi trình biên dịch của bạn. Trong C, các toán tử toán học này bao gồm:
+ cho phép cộng
- cho phép trừ
* cho phép nhân
/ cho phép chia
% cho phép chia lấy số dư
Chúng ta sẽ sử dụng tất cả các toán tử này trong khóa học này.
Biến (Variables)
Trong C, bạn có thể gán một giá trị cho một biến int hoặc số nguyên như sau:
int counter = 0;
Lưu ý một biến có tên counter thuộc kiểu int được gán giá trị 0.
C cũng có thể được lập trình để cộng thêm một vào counter như sau:
counter = counter + 1;
Lưu ý cách 1 được cộng vào giá trị của counter.
Điều này cũng có thể được biểu diễn dưới dạng:
counter += 1;
Và có thể được đơn giản hóa hơn nữa thành:
counter++;
Lưu ý cách sử dụng ++ để cộng thêm 1.
Bạn cũng có thể trừ đi một từ counter như sau:
counter--;
Lưu ý cách 1 được bớt đi từ giá trị của counter.
compare.c
- Sử dụng kiến thức mới này về cách gán giá trị cho các biến, bạn có thể lập trình câu lệnh điều kiện đầu tiên của mình.
Trong cửa sổ terminal, hãy gõ code compare.c và viết mã như sau:
// Conditional, Boolean expression, relational operator
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Prompt user for integers
int x = get_int("What's x? ");
int y = get_int("What's y? ");
// Compare integers
if (x < y)
{
printf("x is less than y\n");
}
}
Lưu ý rằng chúng ta tạo ra hai biến, một số nguyên int gọi là x và một biến khác gọi là y. Giá trị của chúng được điền bằng hàm get_int.
- Bạn có thể chạy mã của mình bằng cách thực hiện lệnh
make comparetrong cửa sổ terminal, tiếp theo là./compare. Nếu bạn nhận được bất kỳ thông báo lỗi nào, hãy kiểm tra lại mã của bạn.
Lưu đồ (Flow charts) là một cách để bạn có thể xem xét cách một chương trình máy tính hoạt động. Những lưu đồ như vậy có thể được sử dụng để kiểm tra hiệu quả của mã nguồn.
- Nhìn vào lưu đồ của đoạn mã trên, chúng ta có thể nhận thấy nhiều thiếu sót.
Chúng ta có thể cải thiện chương trình của mình bằng cách viết mã như sau:
// Conditionals
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Prompt user for integers
int x = get_int("What's x? ");
int y = get_int("What's y? ");
// Compare integers
if (x < y)
{
printf("x is less than y\n");
}
else if (x > y)
{
printf("x is greater than y\n");
}
else
{
printf("x is equal to y\n");
}
}
Lưu ý rằng tất cả các kết quả tiềm năng hiện đã được tính đến.
Bạn có thể biên dịch lại và chạy lại chương trình của mình để kiểm tra.
Xem xét chương trình này trên lưu đồ, bạn có thể thấy tính hiệu quả trong các quyết định thiết kế mã của chúng ta.
agree.c
Xem xét một kiểu dữ liệu khác gọi là
char, chúng ta có thể bắt đầu một chương trình mới bằng cách gõcode agree.cvào cửa sổ terminal.Trong khi
stringlà một chuỗi các ký tự,charlà một ký tự đơn duy nhất.
Trong trình soạn thảo văn bản, hãy viết mã như sau:
// Comparing against lowercase char
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Prompt user to agree
char c = get_char("Do you agree? ");
// Check whether agreed
if (c == 'y')
{
printf("Agreed.\n");
}
else if (c == 'n')
{
printf("Not agreed.\n");
}
}
Lưu ý rằng dấu ngoặc đơn được sử dụng cho các ký tự đơn. Ngoài ra, hãy lưu ý rằng == đảm bảo rằng một thứ gì đó bằng với một thứ khác, trong khi một dấu bằng đơn sẽ có chức năng rất khác trong C.
- Bạn có thể kiểm tra mã của mình bằng cách gõ
make agreevào cửa sổ terminal, sau đó là./agree.
Chúng ta cũng có thể cho phép nhập cả ký tự viết hoa và viết thường:
// Comparing against lowercase and uppercase char
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Prompt user to agree
char c = get_char("Do you agree? ");
// Check whether agreed
if (c == 'y')
{
printf("Agreed.\n");
}
else if (c == 'Y')
{
printf("Agreed.\n");
}
else if (c == 'n')
{
printf("Not agreed.\n");
}
else if (c == 'N')
{
printf("Not agreed.\n");
}
}
Lưu ý rằng các tùy chọn bổ sung đã được cung cấp. Tuy nhiên, đây không phải là mã hiệu quả.
Chúng ta có thể cải thiện mã này như sau:
// Logical operators
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Prompt user to agree
char c = get_char("Do you agree? ");
// Check whether agreed
if (c == 'Y' || c == 'y')
{
printf("Agreed.\n");
}
else if (c == 'N' || c == 'n')
{
printf("Not agreed.\n");
}
}
Lưu ý rằng || có nghĩa là hoặc (or).
Vòng lặp và meow.c
- Chúng ta cũng có thể sử dụng khối xây dựng vòng lặp từ Scratch trong các chương trình C của mình.
Trong cửa sổ terminal, hãy gõ code meow.c và viết mã như sau:
// Opportunity for better design
#include <stdio.h>
int main(void)
{
printf("meow\n");
printf("meow\n");
printf("meow\n");
}
Lưu ý việc này thực hiện đúng ý định nhưng có cơ hội để thiết kế tốt hơn. Mã bị lặp lại nhiều lần.
Chúng ta có thể cải thiện chương trình của mình bằng cách sửa mã như sau:
// Better design
#include <stdio.h>
int main(void)
{
int i = 3;
while (i > 0)
{
printf("meow\n");
i--;
}
}
Lưu ý rằng chúng ta tạo một số nguyên int gọi là i và gán cho nó giá trị 3. Sau đó, chúng ta tạo một vòng lặp while sẽ chạy miễn là i > 0. Sau đó, vòng lặp chạy. Mỗi lần chạy, 1 được trừ đi khỏi i bằng câu lệnh i--.
Tương tự, chúng ta có thể thực hiện một kiểu đếm tiến bằng cách sửa đổi mã như sau:
// Print values of i
#include <stdio.h>
int main(void)
{
int i = 1;
while (i <= 3)
{
printf("meow\n");
i++;
}
}
Lưu ý cách bộ đếm i của chúng ta bắt đầu từ 1. Mỗi khi vòng lặp chạy, nó sẽ tăng bộ đếm thêm 1. Khi bộ đếm lớn hơn 3, nó sẽ dừng vòng lặp.
Thông thường, trong khoa học máy tính, chúng ta đếm từ số không. Tốt nhất là sửa lại mã của bạn như sau:
// Better design
#include <stdio.h>
int main(void)
{
int i = 0;
while (i < 3)
{
printf("meow\n");
i++;
}
}
Lưu ý bây giờ chúng ta đếm từ số không.
- Một công cụ khác trong bộ công cụ lặp của chúng ta là vòng lặp
for.
Bạn có thể cải thiện hơn nữa thiết kế của chương trình meow.c bằng cách sử dụng vòng lặp for. Sửa mã của bạn như sau:
// Better design
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 3; i++)
{
printf("meow\n");
}
}
Lưu ý rằng vòng lặp for bao gồm ba đối số. Đối số đầu tiên int i = 0 khởi tạo bộ đếm của chúng ta tại số không. Đối số thứ hai i < 3 là điều kiện đang được kiểm tra. Cuối cùng, đối số i++ thông báo cho vòng lặp tăng thêm một đơn vị mỗi khi vòng lặp chạy.
Chúng ta thậm chí có thể lặp mãi mãi bằng cách sử dụng đoạn mã sau:
// Infinite loop
#include <cs50.h>
#include <stdio.h>
int main(void)
{
while (true)
{
printf("meow\n");
}
}
Lưu ý rằng true sẽ luôn đúng. Do đó, mã sẽ luôn chạy. Bạn sẽ mất quyền kiểm soát cửa sổ terminal khi chạy mã này. Bạn có thể thoát khỏi vòng lặp vô tận bằng cách nhấn control-C trên bàn phím.
Hàm (Functions)
Mặc dù chúng tôi sẽ cung cấp nhiều hướng dẫn hơn sau này, bạn có thể tạo hàm của riêng mình trong C như sau:
void meow(void)
{
printf("meow\n");
}
Từ void ban đầu có nghĩa là hàm này không trả về bất kỳ giá trị nào. (void) có nghĩa là không có giá trị nào được cung cấp cho hàm.
Hàm này có thể được sử dụng trong hàm main như sau:
// Abstraction
#include <stdio.h>
void meow(void);
int main(void)
{
for (int i = 0; i < 3; i++)
{
meow();
}
}
// Meow once
void meow(void)
{
printf("meow\n");
}
Lưu ý cách hàm meow được gọi bằng lệnh meow(). Điều này khả thi vì hàm meow được định nghĩa ở dưới cùng của mã, và nguyên mẫu (prototype) của hàm được cung cấp ở trên cùng của mã dưới dạng void meow(void).
Hàm meow của bạn có thể được sửa đổi thêm để chấp nhận đầu vào:
// Abstraction with parameterization
#include <stdio.h>
void meow(int n);
int main(void)
{
meow(3);
}
// Meow some number of times
void meow(int n)
{
for (int i = 0; i < n; i++)
{
printf("meow\n");
}
}
Lưu ý rằng nguyên mẫu đã thay đổi thành void meow(int n) để cho thấy rằng meow chấp nhận một số nguyên int làm đầu vào.
Ngoài ra, chúng ta có thể nhận đầu vào từ người dùng:
// User input
#include <cs50.h>
#include <stdio.h>
void meow(int n);
int main(void)
{
int n;
do
{
n = get_int("Number: ");
}
while (n < 1);
meow(n);
}
// Meow some number of times
void meow(int n)
{
for (int i = 0; i < n; i++)
{
printf("meow\n");
}
}
Lưu ý rằng get_int được sử dụng để lấy một số từ người dùng. n được truyền vào meow.
Chúng ta thậm chí có thể kiểm tra để đảm bảo rằng đầu vào chúng ta nhận được từ người dùng là chính xác:
// Return value
#include <cs50.h>
#include <stdio.h>
int get_positive_int(void);
void meow(int n);
int main(void)
{
int n = get_positive_int();
meow(n);
}
// Get number of meows
int get_positive_int(void)
{
int n;
do
{
n = get_int("Number: ");
}
while (n < 1);
return n;
}
// Meow some number of times
void meow(int n)
{
for (int i = 0; i < n; i++)
{
printf("meow\n");
}
}
Lưu ý rằng một hàm mới tên là get_positive_int yêu cầu người dùng nhập một số nguyên trong khi n < 1. Sau khi nhận được một số nguyên dương, hàm này sẽ trả về giá trị return n cho hàm main.
Độ chính xác, Thiết kế, Phong cách (Correctness, Design, Style)
Mã nguồn có thể được đánh giá dựa trên ba trục.
Đầu tiên, độ chính xác (correctness) đề cập đến việc “Mã có chạy đúng như dự định không?” Bạn có thể kiểm tra độ chính xác của mã bằng
check50.Thứ hai, thiết kế (design) đề cập đến việc “Mã được thiết kế tốt như thế nào?” Bạn có thể đánh giá thiết kế mã của mình bằng
design50.Cuối cùng, phong cách (style) đề cập đến việc “Mã có đẹp mắt và nhất quán về mặt thẩm mỹ không?” Bạn có thể đánh giá phong cách mã của mình bằng
style50.
Mario
Tất cả những gì chúng ta đã thảo luận hôm nay đều tập trung vào các khối xây dựng khác nhau trong công việc của bạn với tư cách là một nhà khoa học máy tính mới vào nghề.
Phần sau đây sẽ giúp bạn định hướng cách thực hiện một bộ bài tập cho lớp học này nói chung: Làm thế nào để tiếp cận một vấn đề liên quan đến khoa học máy tính?
Hãy tưởng tượng chúng ta muốn mô phỏng hình ảnh của trò chơi Super Mario Bros. Hãy xem xét bốn khối câu hỏi trong hình, làm thế nào chúng ta có thể tạo mã đại diện cho bốn khối nằm ngang này?
Trong cửa sổ terminal, hãy gõ code mario.c và viết mã như sau:
// Prints a row of 4 question marks with a loop
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 4; i++)
{
printf("?");
}
printf("\n");
}
Lưu ý cách bốn dấu hỏi được in ra ở đây bằng một vòng lặp.
Tương tự, chúng ta có thể áp dụng logic này để tạo ra ba khối nằm dọc.
Để thực hiện việc này, hãy sửa mã của bạn như sau:
// Prints a column of 3 bricks with a loop
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 3; i++)
{
printf("#\n");
}
}
Lưu ý cách ba viên gạch dọc được in ra bằng một vòng lặp.
Nếu chúng ta muốn kết hợp những ý tưởng này để tạo ra một nhóm khối ba nhân ba thì sao?
Chúng ta có thể làm theo logic ở trên, kết hợp các ý tưởng tương tự. Sửa mã của bạn như sau:
// Prints a 3-by-3 grid of bricks with nested loops
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("#");
}
printf("\n");
}
}
Lưu ý rằng một vòng lặp nằm trong một vòng lặp khác. Vòng lặp đầu tiên xác định hàng dọc nào đang được in. Đối với mỗi hàng, ba cột được in ra. Sau mỗi hàng, một dòng mới được in.
Nếu chúng ta muốn đảm bảo rằng số lượng khối là một hằng số (constant), tức là không thể thay đổi thì sao? Hãy sửa mã của bạn như sau:
// Prints a 3-by-3 grid of bricks with nested loops using a constant
#include <stdio.h>
int main(void)
{
const int n = 3;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
printf("#");
}
printf("\n");
}
}
Lưu ý cách n bây giờ là một hằng số. Nó không bao giờ có thể bị thay đổi.
Như đã minh họa ở phần trước của bài giảng này, chúng ta có thể trừu tượng hóa (abstract away) các chức năng thành các hàm. Hãy xem xét đoạn mã sau:
// Helper function
#include <stdio.h>
void print_row(int width);
int main(void)
{
const int n = 3;
for (int i = 0; i < n; i++)
{
print_row(n);
}
}
void print_row(int width)
{
for (int i = 0; i < width; i++)
{
printf("#");
}
printf("\n");
}
Lưu ý việc in một hàng được thực hiện thông qua một hàm mới.
Chú thích (Comments)
Chú thích là những phần cơ bản của một chương trình máy tính, nơi bạn để lại những nhận xét giải thích cho chính mình và những người khác có thể cộng tác với bạn về mã của bạn.
Tất cả mã bạn tạo cho khóa học này phải bao gồm các chú thích rõ ràng.
Thông thường, mỗi chú thích dài vài từ hoặc hơn, giúp người đọc có cơ hội hiểu chuyện gì đang xảy ra trong một khối mã cụ thể. Hơn nữa, những chú thích này đóng vai trò như một lời nhắc nhở cho bạn sau này khi bạn cần chỉnh sửa mã của mình.
Chú thích bao gồm việc đặt // vào mã của bạn, theo sau là lời chú thích. Sửa mã của bạn như sau để tích hợp các chú thích:
// Helper function
#include <stdio.h>
void print_row(int width);
int main(void)
{
const int n = 3;
// Print n rows
for (int i = 0; i < n; i++)
{
print_row(n);
}
}
void print_row(int width)
{
for (int i = 0; i < width; i++)
{
printf("#");
}
printf("\n");
}
Lưu ý mỗi chú thích bắt đầu bằng //.
Tìm hiểu thêm về các toán tử
Bạn có thể xây dựng một máy tính trong C. Trong cửa sổ terminal, gõ code calculator.c và viết mã như sau:
// Addition with int
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Prompt user for x
int x = get_int("x: ");
// Prompt user for y
int y = get_int("y: ");
// Add numbers
int z = x + y;
// Perform addition
printf("%i\n", z);
}
Lưu ý cách hàm get_int được sử dụng để lấy một số nguyên từ người dùng hai lần. Một số nguyên được lưu trữ trong biến int gọi là x. Một số nguyên khác được lưu trữ trong biến int gọi là y. Tổng được lưu trữ trong z. Sau đó, hàm printf in giá trị của z, được biểu thị bằng ký hiệu %i.
Chúng ta cũng có thể nhân đôi một số:
// int
#include <cs50.h>
#include <stdio.h>
int main(void)
{
int dollars = 1;
while (true)
{
char c = get_char("Here's $%i. Double it and give to next person? ", dollars);
if (c == 'y')
{
dollars *= 2;
}
else
{
break;
}
}
printf("Here's $%i.\n", dollars);
}
Khi chạy chương trình này, một số lỗi có vẻ xuất hiện ở dollars. Tại sao vậy?
Một trong những thiếu sót của C là cách nó quản lý bộ nhớ một cách đơn giản. Mặc dù C cung cấp cho bạn khả năng kiểm soát to lớn đối với cách sử dụng bộ nhớ, nhưng các lập trình viên phải rất lưu tâm đến những cạm bẫy tiềm ẩn của việc quản lý bộ nhớ.
Kiểu dữ liệu (Types) đề cập đến các dữ liệu có thể được lưu trữ trong một biến. Ví dụ, một
charđược thiết kế để chứa một ký tự duy nhất nhưahoặc2.Các kiểu dữ liệu rất quan trọng vì mỗi kiểu đều có các giới hạn cụ thể. Ví dụ, do các giới hạn trong bộ nhớ, giá trị cao nhất của một
intcó thể là4294967295. Nếu bạn cố gắng đếm một số nguyênintcao hơn, kết quả là tràn số nguyên (integer overflow) và một giá trị không chính xác sẽ được lưu trữ trong biến này.Số lượng bit giới hạn mức độ chúng ta có thể đếm cao hay thấp.
Điều này có thể gây ra những tác động thảm khốc trong thế giới thực.
Chúng ta có thể khắc phục điều này bằng cách sử dụng một kiểu dữ liệu gọi là long.
// long
#include <cs50.h>
#include <stdio.h>
int main(void)
{
long dollars = 1;
while (true)
{
char c = get_char("Here's $%li. Double it and give to next person? ", dollars);
if (c == 'y')
{
dollars *= 2;
}
else
{
break;
}
}
printf("Here's $%li.\n", dollars);
}
Lưu ý việc chạy mã này sẽ cho phép các số tiền đô la rất lớn.
Các kiểu dữ liệu mà bạn có thể tương tác trong khóa học này bao gồm:
bool, một biểu thức Boolean có giá trị đúng (true) hoặc sai (false)
char, một ký tự đơn như a hoặc 2
double, một giá trị dấu phẩy động với nhiều chữ số hơn float
float, một giá trị dấu phẩy động, hoặc một số thực có giá trị thập phân
int, các số nguyên lên đến một kích thước nhất định hoặc số lượng bit nhất định
long, các số nguyên có nhiều bit hơn, vì vậy chúng có thể đếm cao hơn int
string, một chuỗi các ký tự
Sự cắt bỏ (Truncation)
Một vấn đề khác có thể phát sinh khi sử dụng các kiểu dữ liệu là sự cắt bỏ.
// Division with ints, demonstrating truncation
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Prompt user for x
int x = get_int("x: ");
// Prompt user for y
int y = get_int("y: ");
// Divide x by y
printf("%i\n", x / y);
}
Một số nguyên chia cho một số nguyên sẽ luôn cho kết quả là một số nguyên trong C. Theo đó, mã trên thường sẽ dẫn đến việc bất kỳ chữ số nào sau dấu thập phân sẽ bị vứt bỏ.
Điều này có thể được giải quyết bằng cách sử dụng kiểu float:
// Floats
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Prompt user for x
float x = get_float("x: ");
// Prompt user for y
float y = get_float("y: ");
// Divide x by y
printf("%.50f\n", x / y);
}
Lưu ý rằng điều này giải quyết được một số vấn đề của chúng ta. Tuy nhiên, chúng ta có thể nhận thấy sự không chính xác trong câu trả lời do chương trình cung cấp.
Sự không chính xác của số thực dấu phẩy động (Floating point imprecision) minh họa rằng có những giới hạn về mức độ chính xác mà máy tính có thể tính toán các con số.
Khi bạn đang lập trình, hãy đặc biệt chú ý đến các kiểu biến mà bạn đang sử dụng để tránh các vấn đề trong mã của mình.
Chúng ta đã xem xét một số ví dụ về những thảm họa có thể xảy ra do các lỗi liên quan đến kiểu dữ liệu.
Tổng kết
Trong bài học này, bạn đã học cách áp dụng các khối xây dựng đã học trong Scratch vào ngôn ngữ lập trình C. Bạn đã học được…
Cách tạo chương trình đầu tiên bằng C.
Cách sử dụng dòng lệnh.
Về các hàm được định nghĩa sẵn đi kèm với C.
Cách sử dụng biến, điều kiện và vòng lặp.
Cách tạo các hàm của riêng bạn để đơn giản hóa và cải thiện mã của bạn.
Cách đánh giá mã của bạn dựa trên ba trục: độ chính xác, thiết kế và phong cách.
Cách tích hợp các chú thích vào mã của bạn.
Cách sử dụng các kiểu dữ liệu và toán tử cũng như ý nghĩa của các lựa chọn của bạn.
Hẹn gặp lại bạn lần sau!