Mã hóa RSA C#

18:26 1 Comment

Chương trình mô phỏng mã hóa và giải mã theo RSA - Phiên bản khác các phiên bản trước. Viết bằng C# và có kèm giao diện DevExpress bóng bẩy.

Sơ đồ tạo khóa, mã hóa và giải mã theo RSA:


Giao diện:


Cách Sử Dụng:
Để có thể run được code thì yêu cầu phải cài thêm DevExpress
1. Link: Tải Xuống
Hướng dẫn crack Devexpress trong file nén "DevExpress.v12.2.8.dlls PassedRegistry.rar"
2. Convert project hiện tại sang phiên bản Dev mới cài (12.2.8):
Sau khi cài DevExpress và crack xong, mở Visual Studio lên sẽ có thêm menu-item DEVEXPRESS mới trên thanh menu
Mở project cần convert trước, sau đó chọn DEVEXPRESS -> Project Converter -> chọn phiên bản muốn convert
Upgrade:

Sau khi convert xong, thấy 1 số dòng báo Skip thì kệ nó, đó là những dòng nó phát hiện không cần convert.
Giờ có thể chạy project như thường.
DEVEXPRESS Tải Xuống
Nguồn: Tùng Huỳnh
Hệ mã hóa DES

Hệ mã hóa DES

09:16 2 Comments
Hệ mã hóa Des mở rộng. Gồm có Mã hóa và Giải mã theo Des mở rộng. Chương trình được tạo khóa tự động (hướng người sử dụng).
Hỗ trợ mã hóa và giải mã File dữ liệu.
Có Demo + Source code Java


Source code: Tải Xuống

BTL Cờ Caro Java

21:13 Add Comment
¢Chương trình gồm 3 thành phần chính: Client, Server và MySQL
qKhởi động chương trình mysql sau đó chạy Server tiếp theo là đến Client.
qNếu có đã có tài khoản thành viên user có thể đăng nhập vào chương trình và chơi cờ bình thường
¢ Chương trình giao diện thân thiện dễ giao tiếp, dễ sử dụng.
¢ Server có thể quản lý số lượng máy lớn.
¢ Do thời gian gấp rút nên vẫn còn nhiều chỗ chưa hoàn thiện , rất mong thầy và các bạn đóng góp ý kiến.

Link tải: Tải xuống

BTL VẼ TRANH PHONG CẢNH CÓ SỬ DỤNG PHÉP TỊNH TIẾN C#

18:41 4 Comments
Từ đồ họa trên máy tính chúng ta có nhiều lĩnh vực có ứng dụng rất quan trọng trong thực tế như: tạo mô hình, hoạt cảnh (game, giải trí,…), hỗ trợ thiết kế đồ họa, mô phỏng hình ảnh, chuẩn đoán hình ảnh (trong Y tế), huấn luyện đào tạo ảnh (quân sự, hàng không,…) và còn nhiều ngành khác.
1.Vẽ :Vẽ mặt trời,vẽ ngôi nhà,vẽ mái nhà,vẽ ranh giới,vẽ phong cảnh.,vẽ thân nhà,vẽ cửa sổ trái,của sổ phải,vẽ kẻ ngang,vẽ cửa ra vào
2.Tô màu:Tô bầu trời,tô đất,tô mặt trời,tô mái nhà
3.Chuyển động:Sử dụng phép quay.

Demo:

Sau khi tô màu:

Link tải: Tải xuống (Có kèm cả báo cáo + thuyết trình + code)

Cận cảnh case máy tính ‘Búa của Thor’ tại triển lãm Computex 2015

00:21 Add Comment
Nếu như bạn là một fan cuồng của biệt đội siêu anh hùng Avenger thì chắc hẳn bạn cũng biết đến hoàng tử của Odin - Thor với cây búa của mình.



Mới đây một modder người Thái đã mang đến hội chợ triển lãm một chiếc case máy tính do anh tự mod với nền là chiếc búa của Thor cùng với nhiều phụ kiện của hãng Thermaltake nên anh đã cải biên lại thành Thermal..Thor


Cận cảnh case máy tính 'Búa của Thor' tại triển lãm Computex 2015

Cận cảnh case máy tính 'Búa của Thor' tại triển lãm Computex 2015

Cận cảnh case máy tính 'Búa của Thor' tại triển lãm Computex 2015

Cận cảnh case máy tính 'Búa của Thor' tại triển lãm Computex 2015

Cận cảnh case máy tính 'Búa của Thor' tại triển lãm Computex 2015

Cận cảnh case máy tính 'Búa của Thor' tại triển lãm Computex 2015

Cận cảnh case máy tính 'Búa của Thor' tại triển lãm Computex 2015

Cận cảnh case máy tính 'Búa của Thor' tại triển lãm Computex 2015

Cận cảnh case máy tính 'Búa của Thor' tại triển lãm Computex 2015

Nguồn: www.xemgame.com

Phòng tránh click vào web hoặc gmail lừa đảo

06:00 Add Comment
Hiện nay trên mạng xã hội thường chia sẻ nhiều link website ko rõ nguồn gốc..Thường có nội dung dật tít thu hút người xem click. Thường thường những trang như vậy đều có viruts hoặc mã độc.. vậy hôm nay mình xin có thủ thuật nhỏ về vẫn đề này cho các bạn :

Trước đây , kẻ lừa đảo thường hay dụ người dùng cài trojan về máy tính để lấy thông tin, mật khẩu nhưng do hiện tại chương trình Anti virus ngày càng phát triển nên việc gửi trojan khá là khó khăn .Sau này kẻ lừa đảo phát triển nhiều cách  để thực hiện trò lừa , nhưng phổ biến và dễ thực hiện vẫn là hình thức Phishing .Tìm cách dụ nạn nhân mở địa chỉ  trang web đăng nhập giả hoăc tạo một web giả mạo giống y như thật để lấy thông tin  . Cách chúng ta dễ nhận dạng nhất là chiêu lừa đảo trên facebook  mời các bạn xem tại 2 chiêu lừa đánh cắp tài khoản Facebook  ngoài ra thông qua đường liên kết của email và làm giả  thông tin từ PayPal   dụ chúng ta click vào nhập thông tin như hình bên dưới.


Không chỉ có vậy ,hacker còn kết hợp nhiều xảo thuật khác như tạo địa chỉ lẫn nội dung sao cho có sức thu hút ,mã hóa đường link trên các address bar, tạo ip server giả ….vvv..v

Vậy cách phòng tránh email và web lừa đảo như thế nào ?

  • Chúng ta cẩn thận với những email lạ, đặc biệt là những email yêu cầu cung cấp thông tin  dù là bạn bè, người yêu, người thân cũng nên confirm trước thi nhập thông tin .
  • Xem kỹ nội dung có chính xác , có giống với những biểu mẫu thướng gặp không.Nếu sai chính tả một cách khác thường ,hay một vấn đề nào lạ thì bạn phải nghĩ ngay nó là lừa đảo đấy.
  • Nếu bạn có yêu cầu xác nhận từ email nào thì hay xem kỹ liên kết , nếu có ký tự lạ như @ hay %1 thì có khả năng là giả mạo .
  • Nếu mở một liên kết thì nên tô khối và copy rồi dán vào trình duyệt, và đồng thời phải xem kỹ trên thanh địa chỉ xem liên kết có biến thổi thêm các ký tự lạ không hoặc có chuyển đổi qua một trang khác . Để an toàn phiên làm việc của trình duyệt và  cookie bạn nên mở web bằng trình duyệt ẩn danh .
  • Khi được yêu cầu cung cấp thông tin quan trọng , tốt hơn bạn nên trược tiếp truy cập vào trang chủ website đó để cung cấp thông tin chứ không nên truy cập vào đường liên kết , hoăc phone  trực tiếp xác nhận từ đối tác cung cấp  liên kết.
  • Các bạn lưu ý : với các trang xác nhận thông tin quạn trọng như dịch vụ ngân hàng  , họ luôn dùng giao thức HTTP secure ( có s sau http) nên địa chỉ thường có chử màu xanh https://.… chứ không phải là http:// thường 
  •  Để tránh bị mất hết tài khoản các bạn nên quản lý user và mật khẩu khác nhau đối với các dịch vụ bạn đang sử dụng vì nếu tài khoản này bị mất hacker không thể tìm ra tài khoản kia  và thường xuyên đổi thông tin password.
  • Các bạn nên thường xuyên cập nhật các phiên bản trình duyệt web để vá lỗ hổng bảo mật  web. Tốt nhất cài thêm vài chương trình  phồng chống virus,diệt worm, trojan thậm chí thêm tường lửa
  • Cuối cùng thường xuyên kiểm trả thông tin tài khoản ATM, thẻ tính dụng , tài khoản ngân hàng xem có bị hao hụt không nhé
Nguồn: khaccuong.info

Ngồi máy tính nhiều trong thời gian dài ảnh hưởng tới bạn như thế nào?

05:57 Add Comment
Với dân công nghệ thông tin phải tiếp xúc với máy tính ít nhất thường 15/24h.. Nó sẽ gây ảnh hưởng đến bạn như thế nào? . Các bạn cùng xem tác hại nhé 

Bạn có biết?
  • 30 phút vận động mỗi ngày là chưa đủ.
  • Số lượng người làm văn phòng ngày càng tăng.
  • Ngồi nhiều dẫn đế tê, giãn tĩnh mạch chân.
  • Ngồi nhiều gây mệt mỏi, giảm thể lực, suy giảm chức năng tim, phổi và mắc nhiều bệnh tật.
Nguồn: khaccuong.info
Vấn đề Mở và Lưu file

Vấn đề Mở và Lưu file

05:51 Add Comment
Mở file:

Khi mở file mà thấy hiển thị vào khung soạn văn bản bị lỗi ký tự,
trong khi file đó mở bằng notepad thấy hoàn toàn bình thường thì Open lại với encoding khác thử xem sao
 
Lưu file:

Khi lưu file thì nên để mặc định encoding là Unicode để đảm bảo bản mã khi save không bị lỗi,
(nếu save lỗi thì sau đó sẽ không giải mã được)
Mã hóa và Giải mã Elgamal(p2)

Mã hóa và Giải mã Elgamal(p2)

18:10 3 Comments
Giao diện làm việc của chương trình Mã hóa và Giải mã theo Elgamal:


Cách tính Beta
Vì a là khóa bí mật của hệ mã, không thể public a. Cho nên khi mã hóa thì không thể nhập a vào để mã hóa mà cần phải tính beta trước.
Công thức:

Cách 1:
Có thể áp dụng phương pháp Bình phương và nhân để tính trên giấy trước để lấy được Beta
Cách 2:
Sử dụng Công cụ tích hợp trong chương trình: Vào menu Công cụ chọn Tính mũ MOD hoặc bấm tổ hợp phím Ctrl + M
Nhập các chỉ số tương ứng rồi bấm Tính mũ
Giả sử Alpha=2; a=765; P=2579 thì nhập như hình:
Cách 3:
Truy cập vào trang web http://tung_huynh.freevnn.com/atbmtt/nangcao.php để tính online,
Có cả tính phần tử nghịch đảo theo Euclid và tính mũ theo Bình phương và nhân.
Cách 4:
Cách tính nhanh bằng ngay bên Bản mã. Phần văn bản giải mã không nhập gì cả, chỉ nhập Alpha, a, P vào bên dưới phần Bản mã rồi bấm nút Giải mã
Beta sẽ được tự sinh, đọc và ghi lại vào bên Bản rõ là được

Hướng dẫn mã hóa:
Ở bên Bản rõ, nhập văn bản cần mã hóa, hoặc có thể bấm Mở để chọn 1 file văn bản text từ đĩa cứng vào để mã hóa
P: Nhập 1 số nguyên tố
K: 1 số ngẫu nhiên phải nhỏ hơn P và lớn hơn 0 (Hoặc có thể bấm Random K để chọn K ngẫu nhiên)
alpha: Nên để mặc định =2
B: Chính là Beta vừa tính được ở phần trên
Sau khi đã nhập đầy đủ thông số thì bấm Mã hóa. Kết quả ta thu được văn bản đã mã hóa ở bênBản mã
Có thể bấm Lưu để lưu văn bản đó vào file trên đĩa cứng
Hướng dẫn giải mã:
Nhập thông số tương tự như Mã hóa
Palpha phải giống như lúc Mã hóaa thì phải lấy giống như lúc tính Beta cho Mã hóa
Beta (B) không cần điền
Phần văn bản cần giải mã thì có thể đọc từ file hoặc là paste trực tiếp vào vùng soạn văn bản.
Nếu vừa Mã hóa xong mà muốn Giải mã luôn để kiểm tra chương trình thì có thể giữ nguyên bênBản mã, xóa hết bên Bản rõ, nhập P và a vào rồi bấm Giải mã


Nguồn by: Tùng Huỳnh
Chữ ký số Elgamal(p1)

Chữ ký số Elgamal(p1)

18:09 Add Comment
Hướng dẫn ký:
Xem ảnh: 
 
Các vùng nhập dữ liệu (màu đỏ):
1: Vùng nhập văn bản cần ký
2: Vùng hiển thị chữ ký sau khi ký (không cần nhập gì vào vùng này)
3: Nhập giá trị số nguyên tố P
4: Nhập giá trị alpha nên để mặc định là 2
5: Nhập giá trị k nguyên, ngẫu nhiên sao cho UCLN(k,p)=1
6: Nhập giá trị a nguyên sao cho nằm trong khoảng (0 - P)
Các nút điều khiển:
1: Mở 1 file văn bản dạng text từ đĩa cứng vào vùng nhập văn bản (1)
2: Copy phần văn bản hiện tại trong vùng 1 vào Clipboard
3: Paste nội dung trong Clipboard ra vùng văn bản 1
4: Copy phần văn bản hiện tại trong vùng 2 vào Clipboard
5: Chuyển toàn bộ (Forward) dữ liệu ở vùng văn bản và chữ ký ở bên panel Ký văn bản sang vùng tương ứng ở bên panel Kiểm tra chữ ký
6: Save đồng thời văn bản ở cả 2 vùng 1 và 2 ra file trên đĩa cứng. Chương trình sẽ tạo ra 2 file tương ứng
ten_file.txt: lưu nội dung vùng 1 (văn bản ký)
ten_file.txt.sig: lưu nội dung vùng 2 (chữ ký)
2 file này bắt buộc phải giữ nguyên trong cùng 1 thư mục, không được xóa hoặc đổi tên để phục vụ cho quá trình kiểm tra chữ ký 7: Nhấn nút này sau khi đã điền đầy đủ thông tin phía trên
Giả sử ký văn bản, sau khi nhấn sẽ ra kết quả (văn bản giữ nguyên nhưng có kèm theo chữ ký)

Hướng dẫn kiểm tra chữ ký:
Phần văn bản cần kiểm tra và các nút Copy, Paste, Save tương tự như bên Ký văn bản
Khi kiểm tra chữ ký thì cần phải có chữ ký để kiểm tra cho nên ở bên này bạn phải nhập cả chữ ký vào vùng 2 (Chữ ký)
Giá trị Alpha và P phải nhập giống như bên Ký văn bản Nút Open: Mở file văn bản dạng text từ đĩa cứng để nạp vào chương trình, ở phần này bạn chỉ cần chọn file văn bản text để nhập vào là được, file chữ ký .sig sẽ tự động được nạp vào vùng tương ứng.
Nếu chỉ có file văn bản text mà không có file chữ ký .sig, hoặc có nhưng ở thư mục khác với thư mục chứa file văn bản text, hoặc 1 trong 2 file đó đã bị đổi tên thì chương trình cũng không thể tìm được file chữ ký .sig dẫn đến việc không thể kiểm tra chữ ký.
Chương trình sẽ hiển thị thông báo:

Khi dữ liệu trong các vùng đã đầu đủ (Nếu test chương trình thì sau khi Ký, bạn có thể nhấn Fw (nút số 7 ở bên Ký văn bản) để chuyển toàn bộ văn bản và chữ ký đã ký sang bên Kiểm tra chữ ký để kiểm tra).
thì nhấn nút Kiểm tra chữ ký để tiến hành kiểm tra Nếu văn bản và chữ ký là nguyên bản gốc thì chương trình sẽ hiển thị thông báo:
Nếu văn bản hoặc chữ ký bị sai lệch (ví dụ ta sửa thử văn bản cho khác với văn bản gốc) thì chương trình báo:

By: Tùng Huỳnh
Demo + Code Hệ mã hóa Elgamal Java

Demo + Code Hệ mã hóa Elgamal Java

18:06 Add Comment
Thông tin:
Sau khi tải chương trình về, bạn mở file nén và chạy file .exe bên trong
(Nếu máy tính của bạn không có winrar để giải nén thì tải tại đây)
Sau khi chạy file .exe thì chương trình sẽ được cài đặt tự động vào máy tính của bạn và hiện biểu tượng shortcut trên Desktop

Nhấp chuột vào biểu tượng shortcut trên Desktop để chạy chương trình 
Nếu khi chạy chương trình mà báo lỗi Cannot find java 1.5.0

Có nghĩa là máy tính của bạn không có máy ảo Java để chạy chương trình
Xem Hướng dẫn cài máy ảo java JRE để có thể sử dụng được chương trình
Nếu không có lỗi gì xảy ra thì sẽ hiển thị giao diện chương trình như hình dưới

Thông Tin Cá Nhân

09:59 Add Comment


 TÙNG CHÈM  | CHIA SẺ NIỀM ĐAM MÊ

Xin Chào ! Tôi mong được đóng góp 1 chút it kiến thức về  lĩnh vực công nghệ thông tin. Chia sẻ cho các bạn một chút về các thủ thuật cũng như một số kinh nghiệm nho nhỏ mà trong quá trình Tôi đã bắt đầu vào ngành công nghệ thông tin .
Blog Tùng Chèm là nơi mà tôi chia sẻ những kinh nghiệm cho các bạn trong lĩnh vực công nghệ thông tin , hiện tai Blog chỉ mới thành lập trong tháng 6/2015 , vì thế có rất nhiều thứ mà tôi cần phải làm . hi vọng 1 năm sau blog sẻ phát triển và nơi giúp cho mọi người điều có thể tự mình làm những công việc của IT.
Hiện tại bạn IT nào có chí hướng muốn hợp tác với tôi chia sẻ kiến thức cho mọi người xin liên hệ qua mail  hiepsiaotrang1607@gmail.com.

TỰ BẠCH :

Tên : Nguyễn Thanh Tùng
Quê Quán: Từ Liêm - Hà Nội
Sinh Ngày: 16/07/1993
Đang Là :   Sinh viên trường ĐH MĐC – Ngành công nghệ thông tin

Mô phỏng hệ mã hóa RSA C#

09:21 4 Comments
Mô phỏng thuật toán RSA bằng ngôn ngữ C#


Có cả báo cáo wood + slideshow + demo : dowload

Bài 5: Cấu trúc, kiểu dữ liệu trừu tượng. Các công cụ của trình biên dịch.

09:09 Add Comment
Trong bài này, bạn sẽ học cách khai báo cấu trúc (structure) và khái niệm kiểu dữ liệu trừu tượng (abstract data type, ADT). Ngoài ra, bạn sẽ học thêm một số chức năng khác của trình biên dịch C, bao gồm: các chỉ thị tiền xử lý (preprocessor directives), cách biên dịch nhiều file.

I. Cấu trúc và kiểu dữ liệu trừu tượng:


1. Khái niệm kiểu dữ liệu trừu tượng và cấu trúc:

Bạn đã học một số kiểu dữ liệu trong ngôn ngữ lập trình C, ví dụ: kiểu nguyên (int), kiểu thực (double), kiểu ký tự (char), kiểu con trỏ (pointer như void*, char*, int*, ...). Khi có kiểu dữ liệu, bạn có thể khai báo biến số thuộc kiểu dữ liệu đó (ví dụ có thể khai báo "int x;" để có một biến x kiểu số nguyên và ánh xạ vào trong bộ nhớ bởi 4 bytes).
Tuy nhiên, cho đến nay bạn chỉ có thể sử dụng các kiểu dữ liệu đã được định nghĩa sẵn. Trong các vấn đề thực tế, nhiều khi ta cần các kiểu dữ liệu do ta tự định nghĩa, ví dụ: khi bạn muốn viết một chương trình game trong không gian 2 chiều, bạn rất hay xử lý các dữ liệu liên quan đến các điểm trong không gian đó vì thế cần có 1 kiểu dữ liệu mới là kiểu điểm (point) bao gồm 2 phần tử (x, y) là 2 tọa độ. Hai phần tử cấu tạo nên kiểu điểm (tạm gọi là kiểu Point2D) có thể là 2 số nguyên, 2 số thực ... Như vậy, kiểu Point2D là một kiểu dữ liệu mới, được cấu thành từ 2 phần tử của kiểu nguyên đã được định nghĩa sẵn trong C.
Các kiểu dữ liệu không được định nghĩa sẵn trong ngôn ngữ lập trình mà do người lập trình tự định nghĩa từ những kiểu cơ bản có sẵn hoặc các kiểu tự định nghĩa khác gọi là kiểu dữ liệu trừu tượng (abstract data type, ADT).
Trong ví dụ trên, kiểu Point2D là một kiểu dữ liệu trừu tượng cấu thành từ 2 phần tử kiểu int. Trong ngôn ngữ lập trình C, bạn có thể sử dụng kiểu cấu trúc (construct) để định nghĩa những kiểu dữ liệu trừu tượng này, cách định nghĩa kiểu Point2D như sau:
struct Point2D {
    int x;
    int y;
};   // Đừng quên dấu chấm phảy ở đây
Sau khi định nghĩa cấu trúc Point2D, ta có thể khai báo các biến thuộc kiểu cấu trúc này như sau:
struct Point2D     p, q;
Các biến p, q là các biến kiểu cấu trúc Point2D. Các biến này là các điểm trong không gian 2 chiều, có 2 thành phần là x và y. Hai thành phần này được truy cập (đọc / ghi) thông qua toán tử dot (dấu chấm) như sau:
     p.x = 1;
     p.y = 2;
     printf( "Point p = (%d, %d)\n", p.x, p.y );
Toán tử dot gọi là "toán tử truy cập nội dung cấu trúc".
Ngoài ra, bạn có thể khai báo con trỏ kiểu cấu trúc Point2D (giống như khi có kiểu int có thể khai báo kiểu int*) như sau:
struct Point2D *p;
p = (struct Point2D *)malloc(sizeofstruct Point2D ) );
p->x = 1; // Access struct pointer content with operator "->"
(*p).y = 2; // Note that *p is a struct, not a struct pointer!
printf( "Point (%d, %d)\n", p->x, p->y );
Với con trỏ trỏ đến một kiểu cấu trúc, bạn có thể truy cập thành phần của cấu trúc đó thông qua con trỏ nhờ một toán tử đặc biệt, gọi là toán tử mũi tên ("->") (xem ví dụ ở trên).
Như vậy, ta thấy "struct Point2D" hoàn toàn tương đương với một kiểu dữ liệu được định nghĩa sẵn (như int, double, ...) vì ta có thể khai báo các biến thuộc kiểu struct Point2D, hơn nữa ta cũng có thể khai báo các con trỏ thuộc kiểu đó bằng cách thêm dấu sao (*).
Tuy nhiên, "struct Point2D" chỉ là một kiểu cấu trúc, chưa phải là một kiểu dữ liệu mới (vì nó vẫn là kiểu con của kiểu cấu trúc). Trong phần tới ta sẽ học cách định nghĩa kiểu dữ liệu mới.

2. Cách định nghĩa kiểu dữ liệu mới:
Ngôn ngữ lập trình C cho phép định nghĩa kiểu dữ liệu mới dựa trên kiểu dữ liệu đã được định nghĩa trước. Sau khi định nghĩa kiểu mới, ta có thể dùng kiểu dữ liệu này giống như mọi kiểu thông thường.
Từ khóa typedef dùng để định nghĩa kiểu dữ liệu mới, cú pháp định nghĩa như sau:
    type_definition ::= "typedef" old_datatype new_datatype
Ví dụ, ta có thể định nghĩa kiểu SoNguyen bằng cách sau:
typedef   int   SoNguyen;
SoNguyen x, y; // Khai báo biến kiểu SoNguyen
SoNguyen *pX; // Khai báo biến kiểu con trỏ SoNguyen
x = 1; // Gán giá trị cho biến SoNguyen x
pX = (SoNguyen*)malloc( sizeof( SoNguyen ) );
*pX = 2;
printf( "x = %d, *pX = %d\n", x, *pX );
Khi đó kiểu SoNguyen hoàn toàn giống với kiểu int.
Vì thế, ta có thể dùng từ khóa typedef để khai báo kiểu dữ liệu mới Point2D dựa trên kiểu cấu trúc struct _Point2D (thêm dấu gạch vào identifier Point2D để cho đỡ nhầm giữa kiểu dữ liệu Point2D và cấu trúc _Point2D):
/*
  File: struct_demo.c
  Declare a new data type, Point2D using struct _Point2D.
*/
#include < stdio.h >
int main() {
   struct _Point2D {
     int x;
     int y;
   };   // Do not forget semicolon here!

   // Declare data type "Point2D"
   typedef struct     _Point2D    Point2D;
   Point2D p, q;
   Point2D *r;
   r = (Point2D*)malloc( sizeof(Point2D) );
   p.x = 1;     p.y = 2;
   r->x = 1;     r->y = 2;
   printf( "p = (%d, %d)\n", p.x, p.y );
   printf( "r = (%d, %d)\n", r->x, (*r).y );
   return 0;
}

Ngoài ra, ta có thể vừa định nghĩa cấu trúc, vừa khai báo kiểu dữ liệu mới như sau:
/*
  File: struct_demo1.c
  Declare a new data type, Point2D using struct _Point2D.
*/
#include < stdio.h >
int main() {
   typedef struct _Point2D {
     int x;
     int y;
   }     Point2D;   // Declare data type "Point2D"

   Point2D p, q;
   Point2D *r;
   r = (Point2D*)malloc( sizeof(Point2D) );
   p.x = 1;     p.y = 2;
   r->x = 1;     r->y = 2;
   printf( "p = (%d, %d)\n", p.x, p.y );
   printf( "r = (%d, %d)\n", r->x, (*r).y );
   return 0;
}

Hơn nữa, ta có thể bỏ qua không cần khai báo tên của cấu trúc (_Point2D) mà chỉ cần khai báo tên của kiểu dữ liệu mới:
/*
  File: struct_demo2.c
  Declare a new data type, Point2D using an anonymous struct.
*/
#include < stdio.h >
int main() {
   typedef struct {
     int x;
     int y;
   }     Point2D;   // Declare data type "Point2D"

   Point2D p, q;
   Point2D *r;
   r = (Point2D*)malloc( sizeof(Point2D) );
   p.x = 1;     p.y = 2;
   r->x = 1;     r->y = 2;
   printf( "p = (%d, %d)\n", p.x, p.y );
   printf( "r = (%d, %d)\n", r->x, (*r).y );
   return 0;
}
Bạn hãy thử nghĩ xem trong các ví dụ trên, ở câu lệnh typedef thì old_datatype là gì và new_datatype là gì?.

3. Một số ví dụ về kiểu dữ liệu trừu tượng: 
Kiểu dữ liệu trừu tượng rất có ích khi phải mô hình hóa những vấn đề phức tạp, nó giúp việc xử lý những dữ liệu phức tạp trở nên đơn giản hơn. Trong phần này, ta sẽ xem xét một số ví dụ để thấy rõ lợi ích của kiểu dữ liệu trừu tượng.

3.1. Mô hình các điểm trong hệ tọa độ 2 chiều:
Giả sử ta phải viết chương trình nhập vào tọa độ của 2 điểm và tính khoảng cách giữa 2 điểm đó. Nếu không có kiểu dữ liệu trừu tượng Point2D ta có thể làm như sau:
    /*
File point_demo1.c
Compile with -lm: $ gcc -o point_demo1 point_demo1.c -lm
*/
#include < stdio.h >
#include < math.h >
double distance( int x1, int y1, int x2, int y2 )
{
double r, retVal;
r = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
retVal = sqrt( r );
return retVal;
}
int main() {
int x1, y1;
int x2, y2;
printf( "Point1: (x, y) = " );
scanf( "%d %d", &x1, &y1 );
printf( "Point2: (x, y) = " );
scanf( "%d %d", &x2, &y2 );
printf( "Distance = %lf\n", distance( x1, y1, x2, y2 ) );
return 0;
}

Ta dùng 4 biến nguyên x1, y1 và x2, y2 là tọa độ của 2 điểm nhập vào. Tuy nhiên, không có gì đảm bảo là x1 luôn luôn đi với y1 để tạo thành tọa độ điểm p1, x2 luôn luôn đi với y2 để tạo thành tọa độ điểm p2. Ví dụ, ta có thể nhầm lẫn (x1, x2) là tọa độ của điểm p1 và (y1, y2) là tọa độ của điểm p2. Hoặc ta cũng có thể nhầm (x1, y2) là tọa độ của điểm p1, ...
Khi dùng kiểu dữ liệu trừu tượng Point2D ta có thể tránh được những nhầm lẫn này, vì các tọa độ luôn được ghép đúng cặp với nhau:
    /*
File point_demo2.c
Compile with -lm: $ gcc -o point_demo2 point_demo2.c -lm
*/
#include < stdio.h >
#include < math.h >
typedef struct _Point2D {
int x;
int y;
} Point2D;

double distance( Point2D p, Point2D q )
{
double r, retVal;
r = (p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y);
retVal = sqrt( r );
return retVal;
}

int main()
{
Point2D p1, p2;
printf( "Point1 (x, y) = " );
scanf( "%d %d", &p1.x, &p1.y );
printf( "Point2 (x, y) = " );
scanf( "%d %d", &p2.x, &p2.y );
printf( "Distance = %lf\n", distance( p1, p2 ) );
return 0;
}

Vì các biến x, y ở trong cấu trúc Point2D nên không thể nhầm lẫn ghép tọa độ x của p1 với y của p2. Ngoài ra, hàm distance cũng chỉ cần 2 tham số là 2 điểm (không cần tới 4 tham số int).
Khi mô hình hóa những bài toán phức tạp như mạch điện, quản lý sinh viên, mạng lưới thành phố, ... thì kiểu dữ liệu trừu tượng không thể thiếu vì nó làm chương trình dễ đọc và dễ hiểu.

3.2. Kiểu dữ liệu trừu tượng cho ngăn xếp (stack):
Trong bài trước bạn đã biết ngăn xếp dùng khi gọi hàm, nó có tác dụng kỳ diệu là thứ tự push vào và thứ tự pop ra ngược với nhau nên có thể dùng để đảo thứ tự một dãy. Ở phần này ta sẽ implement một ngăn xếp đơn giản bằng cách khai báo kiểu dữ liệu trừu tượng Stack.
Kiểu Stack gồm 2 thành phần: thành phần thứ nhất là một biến nguyên (int) để mô tả vị trí hiện thời của đỉnh ngăn xếp, thành phần thứ 2 là một mảng tĩnh các số nguyên để chứa các phần tử của ngăn xếp.
Ta dùng một preprocessor directive mới có tên là "#define" để định nghĩa một hằng số MAX_SIZE là số phần tử lớn nhất có thể cho vào ngăn xếp. Trình biên dịch sẽ thay thế MAX_SIZE bằng số được định nghĩa ở phần sau của #define mỗi khi gặp identifier "MAX_SIZE".
    #include < stdio.h >
#define MAX_SIZE 10
typedef struct {
int m_nCurPos; // member m_nCurPos is current position of stack's top
int m_arrElems[MAX_SIZE]; // array of elements
} Stack;

void push( Stack* stk, int elem )
{
if( stk->m_nCurPos >= MAX_SIZE ) {
printf( "Error: Stack is full!\n" );
} else {
stk->m_nCurPos ++;
stk->m_arrElems[stk->m_nCurPos] = elem;
}
}

int pop( Stack* stk )
{
int retVal;
if( stk->m_nCurPos < 0 ) {
printf( "Error: stack is empty\n" );
return 0;
} else {
retVal = stk->m_arrElems[stk->m_nCurPos];
stk->m_nCurPos --;
return retVal;
}
}

int isEmpty( Stack* stk )
{
if( stk->m_nCurPos < 0 ) return 1;
return 0;
}
int main()
{
int i, x;
Stack st;
st.m_nCurPos = -1;
for( i = 1; i < MAX_SIZE + 5; i++ ) {
printf( "Push %d into stack\n", i );
push( &st, i );
}
printf( "\n\n" );
while( ! isEmpty( &st ) ) {
printf( "pop( &st ) = %d\n", pop( &st ) );
}
printf( "Testing: pop( &st ) while st is empty\n" );
pop( &st );
return 0;
}

II. Các công cụ của trình biên dịch:

Đây là phần quan trọng cuối cùng bạn cần biết để lập trình với C. Phần này là phần lý thuyết cuối cùng của toàn bộ giáo trình này. Sau khi đọc xong phần này bạn coi như đã nắm được cơ bản ngôn ngữ C!.

1. Toán tử chọn ternary:
Toán tử chọn ternary là toán tử gồm 3 toán hạng, nếu toán hạng đầu có giá trị true, toán tử này trả về toán hạng thứ 2, ngược lại nó trả về toán hạng thứ 3. Toán tử này có cú pháp như sau:
    ternary_selector ::= operand1 "?" operand2 ":" operand3

Như vậy toán tử này bao gồm toán hạng 1, đi theo sau bởi dấu chấm hỏi (?) rồi đến toán hạng 2, sau đó là dấu hai chấm (:) và cuối cùng là toán hạng 3. Vì là toán tử nên nó cũng có giá trị trả về, giá trị trả về như đã đề cập ở trên (trả về operand2 hoặc operand3 tùy vào điều kiện operand1 đúng hay sai).
Ví dụ:
    int a, x;
a = 6;
x = ((a > 5) ? (a + 1) : (a - 1));
Sau khi thực hiện lệnh gán, x sẽ có giá trị bằng 7 vì a > 5 là đúng.

2. Một số chỉ thị tiền xử lý hữu dụng:
2.1. Chỉ thị #define:
Như bạn đã gặp ở phần trên, dạng đơn giản của chỉ thị #define có cú pháp như sau:
    define_directive ::= "#define" identifier something
Chỉ thị này báo cho trình biên dịch C biết là khi gặp identifier sẽ phải thay thế bằng something. Vì nó là chỉ thị tiền xử lý nên trình biên dịch sẽ thực hiện việc thay thế trước khi biên dịch chương trình. Something có thể là bất kỳ cái gì bạn viết được trong editor. Ví dụ:
    #include < stdio.h >

#define MAX_SIZE 100
#define HELLO "Xin chao!\n"
#define SoNguyen int

SoNguyen main()
{
SoNguyen x;
printf( HELLO );
printf( "max size is %d\n", MAX_SIZE );
x = 1;
printf( "x = %d\n", x );
return 0;
}
Chú ý: cần phân biệt giữa "typedef int SoNguyen;" và "#define SoNguyen int". Câu lệnh typedef được thực hiện khi biên dịch, còn chỉ thị define SoNguyen int được thực hiện trước khi biên dịch, nó chỉ đơn giản là thay thế chỗ nào có "SoNguyen" bằng từ "int". 
Ở dạng phức tạp hơn, chỉ thị #define có cú pháp như sau:
    define_directive ::= "#define" identifier ["(" formal_parameter_list ")] something
formal_parameter_list ::= empty | identifier ( ", " identifier)*
Khi có formal parameter (như khi khai báo hàm số), chỉ thị này sẽ thay thế những chỗ nào có formal parameter name ở trong something bằng giá trị thực của formal parameter đó:
    #define max( a, b ) (( a > b ) ? a : b)
int x, y;
x = 5;
y = 6;
printf( "max(%d, %d) = %d\n", x, y, max(x, y) );
Quá trình thay thế đó gọi là quá trình "triển khai macro", định nghĩa của max được gọi là định nghĩa của 1 macro (lệnh gộp) và max được gọi là một macro (không phải là 1 hàm).
Trông max(x, y) gần giống như lời gọi hàm nhưng thực chất khác hẳn: trước khi thực hiện biên dịch, trình biên dịch sẽ thay thế max(x, y) bởi định nghĩa của nó, vì thế chương trình thực chất được biên dịch là:
    int x, y;
x = 5;
y = 6;
printf( "max(%d, %d) = %d\n", x, y, (( x > y ) ? x : y ) );
Bạn phải hết sức cẩn thận khi sử dụng chỉ thị #define với tham số vì khi bộ tiền xử lý thay thế tham số có thể gây ra nhầm lẫn nếu không có đủ dấu ngoặc.
Ví dụ chương trình sau định tính bình phương của max nhưng sẽ sai vì phép nhân được hiểu sai:
    #define max( a, b ) ( a > b ) ? a : b
int x, y;
x = 6;
y = 5;
printf( "max^2(%d, %d) = %d\n", x, y, max(x, y) * max(x, y) );
Sau khi triển khai macro max, ta có kết quả của biểu thức max(x, y) * max(x, y) là:
    (x > y) ? x : y * (x > y) ? x : y
Như vậy operand thứ 3 của toán tử chọn ternary đầu tiên sẽ là y * (x > y) ? x : y, chứ không phải là y!. 

2.2. Chỉ thị #if, #ifdef:
Chỉ thị #if và #ifdef dùng để hướng dẫn trình biên dịch lựa chọn hoặc bỏ qua một đoạn mã lệnh nào đó. Cú pháp của chỉ thị #if như sau:
    if_directive ::= "#if" expression something ["#else" some_other_thing] "#endif"
ifdef_directive ::= "#ifdef" identifier something ["#else" some_other_thing] "#endif"
Bộ tiền xử lý sẽ bỏ qua 1 some_other_thing nếu expression đúng, ngược lại nó bỏ qua something. Với chỉ thị #ifdef, bộ tiền xử lý sẽ bỏ qua some_other_thing nếu định danh identifier đã được định nghĩa bởi chỉ thị #define hoặc được định nghĩa bởi tham số dòng lệnh khi biên dịch, ngược lại nó bỏ qua something.
Ví dụ, đoạn mã sau có thể dùng để nhận biết trình biên dịch đang chạy trên hệ điều hành nào. Khi bạn dùng Cygwin, định danh __CYGWIN__ sẽ được define và định danh __linux__ cũng được define vì Cygwin làm giả Linux. Ngược lại, nếu bạn dịch chương trình bằng MSVC thì cả 2 định danh này đều không được định nghĩa. Nếu bạn dịch chương trình với 1 máy Linux thật thì chỉ __linux__ được định nghĩa.
    #include < stdio.h >

int main()
{
#ifdef __CYGWIN__
#define MOCK_LINUX 1
#define LINUX 1
#else
#ifdef __linux__
#define MOCK_LINUX 0
#define LINUX 1
#else
#define LINUX 0
#endif
#endif

#if LINUX == 0
printf( "NG, your OS is not Linux (may be Windows?)!" );
#else
printf( "OK, you seem to be using something like Linux\n" );
printf( "Let me see ...\n" );
#if MOCK_LINUX
printf( "Blah, you are simulating Linux on Cygwin\n" );
#else
printf( "Congratulation, you passed our test: You are using Linux!\n" );
#endif
#endif
return 0;
}
Lưu ý là chương trình được dịch thật chỉ bao gồm nhiều nhất là 3 lệnh printf (mặc dù có tới 4 lần viết printf trong chương trình) vì khi LINUX != 0 thì trình biên dịch sẽ bỏ qua đoạn printf( "NG, ..." );.

2.3. Chỉ thị #undef:
Chỉ thị #undef ngược lại với chỉ thị #define: nó xóa bỏ định nghĩa của 1 định danh nào đó, ví dụ:
    #ifdef __CYGWIN__
#undef __linux__
#endif



3. Dịch từng phần và Makefile:
3.1. Dịch từng phần:
Như bạn đã biết chỉ thị #include dùng để chèn 1 file vào vị trí có chỉ thị đó. Bạn có thể dùng chỉ thị #include để đính các file có các định nghĩa hàm thường dùng vào 1 chương trình nào đó.
Ví dụ, giả sử bạn có 3 file: file matrix_funcs.c có các hàm để malloc 1 ma trận, free ma trận và hiển thị ma trận. Khi bạn muốn viết chương trình nhập vào 2 ma trận và tính tổng bạn có thể làm như sau:
 // matrix_sum.c
#include <stdio.h>
// matrix_funcs.c must be in the same folder with this file (matrix_sum.c)
#include "matrix_funcs.c"
int main()
{
// use malloc_matrix here to malloc matrices
// calculate matrix sum
// use print_matrix here!
// use free_matrix here!
}

Khi bạn muốn viết chương trình nhập vào 2 ma trận và tính tích của 2 ma trận, bạn chỉ việc include file "matrix_funcs.c" giống như là với chương trình matrix_sum.c mà không cần viết lại các hàm malloc_matrix, free_matrix, ...
Hãy thử làm với chương trình matrix mà bạn đã viết ở bài học trước.
Tuy nhiên, giả sử bạn phải viết 1 chương trình vừa tính tổng, vừa tính tích của ma trận, và bạn phân ra 4 files: matrix_funcs.c, matrix_sum.c, matrix_prot.c, matrix.c (file matrix_sum.c chỉ chứa hàm để cộng ma trận, matrix_prot.c chỉ chứa hàm để nhân ma trận và matrix.c phải dùng các hàm của cả 3 file trước) thì có vấn đề xảy ra: trong file matrix_sum.c bạn phải dùng hàm malloc_matrix (để malloc ma trận kết quả) nên bạn phải include file này, trong file matrix_prot.c bạn cũng phải dùng hàm này và cũng phải include file matrix_funcs.c. Trong file matrix.c bạn phải include cả 2 file matrix_sum.c và matrix_prot.c, điều này dẫn đến việc các hàm trong matrix_funcs.c được định nghĩa đi định nghĩa lại 2 lần và gây ra lỗi biên dịch.
Để tránh điều này, C cho phép khai báo prototype của hàm mà không cần khai báo thân hàm. Chỉ cần có prototype là bạn có thể dùng hàm, mặc dù khai báo thân hàm chưa xuất hiện (tất nhiên cuối cùng thân hàm cũng phải xuất hiện):
    // file: prototype_demo.c
#include < stdio.h >

// prototype
int add( int x, int y );

// main
int main()
{
int x, y, z;
x = 1;
y = 2;
z = add( x, y );
printf( "z = %d\n", z );
return 0;
}

// implementation of add function:
int add( int x, int y )
{
return x + y;
}
Trong ví dụ trên, bạn dùng hàm add trong hàm main khi chưa định nghĩa body của hàm add. Vì có prototype nên trình biên dịch không báo lỗi gì.
Thông thường, prototype của hàm được khai báo ở trong các file header (.h) còn phần implementation được khai báo trong các file .c. Ví dụ, chương trình prototype_demo.c ở trên có thể chia làm 3 file: add.c, add.h và add_main.c như sau:
    // file: add.h
int add( int x, int y );
    // file: add.c
#include "add.h" // include add.h to get the prototype of add.
int add( int x, int y )
{
return x + y;
}
    // file: add_main.c
#include < stdio.h >
#include "add.h"

int main()
{
int x, y, z;
x = 1;
y = 2;
z = add( x, y );
printf( "z = %d\n", z );
return 0;
}
Khi biên dịch, bạn cần biên dịch file add.c và file add_main.c trước. Sau đó bạn link kết quả biên dịch của 2 file này lại để có chương trình chạy:
   $ gcc -c add.c       # chỉ biên dịch, không link
$ gcc -c add_main.c # chỉ biên dịch, không link (không tạo ra file chạy)
$ ls # xác nhận đã có 2 file: add.o và add_main.o
$ gcc -o add add.o add_main.o # link 2 file add.o và add_main.o để tạo ra file chạy add.
Khi bạn dùng option "-c" với gcc, bạn chỉ cho trình biên dịch biết là chỉ dịch ra file object (.o) chứ không dịch và link chương trình để ra 1 chương trình toàn vẹn chạy được. Sau khi có 2 file object, bạn phải link 2 file này để có file add (hoặc add.exe trong Windows).
Tương tự, với chương trình matrix, bạn cần phải viết 3 file .h, bao gồm: matrix_funcs.h, matrix_sum.h, matrix_prot.h. Sau đó phải viết 4 file .c bao gồm: matrix_funcs.c, matrix_sum.c, matrix_prot.c và matrix.c. Trong file matrix_sum.h, matrix_sum.c có thể include file "matrix_funcs.h" (tương tự cho matrix_prot). Trong file matrix.c bạn cần include cả 3 file .h, quá trình biên dịch sẽ như sau:
    $ gcc -c matrix_funcs.c
$ gcc -c matrix_sum.c
$ gcc -c matrix_prot.c
$ gcc -c matrix.c
$ gcc -o matrix matrix_funcs.o matrix_sum.o matrix_prot.o matrix.o
Quá trình biên dịch như trên gọi là "biên dịch từng phần". Để tránh phải viết đi viết lại nhiều lệnh ở mỗi lần biên dịch ta dùng 1 công cụ gọi là GNU Make. 

3.2. Makefile:
Trong các shell của Unix (hoặc các môi trường giống Unix như Linux, Cygwin) có thể dùng một file đặc biệt tên là "Makefile" (chú ý chữ hoa chữ thường) để ghi các lệnh muốn thực hiện khi biên dịch. Sau khi có Makefile ta chỉ việc gõ lệnh
    $ make
và chương trình GNU Make sẽ tự mò xem đã có những thay đổi gì từ lần biên dịch trước và thực hiện các lệnh trong Makefile cho ta. Ví dụ, với chương trình matrix ở trên, ta có thể tạo 1 file tên là "Makefile" và đặt vào cùng folder với các file .h và .c, với nội dung như sau:
    # Makefile for matrix, everything after sharp is comment
gcc -c matrix_funcs.c
gcc -c matrix_sum.c
gcc -c matrix_prot.c
gcc -c matrix.c
gcc -o matrix matrix_funcs.o matrix_sum.o matrix_prot.o matrix.o
khi muốn biên dịch chương trình chỉ việc gõ lệnh
    $ make
Cú pháp của Makefile còn rất phức tạp để mô tả dependencies (sự phụ thuộc) giữa các file, bạn có thể tìm hiểu thêm về cách dùng Makefile ở các tutorial trên Internet (search với từ khóa "Makefile tutorial"). 

III. Giới thiệu một số hàm thư viện:

1. Thư viện stdio:
Thư viện stdio (standard input/output) chứa các hàm để phục vụ cho việc xuất nhập dữ liệu. Bạn đã quen thuộc với các hàm printf và scanf của thư viện này.
Có 4 hàm hoàn toàn tương tự như printf và scanf (để xuất/nhập dữ liệu), đó là các hàm:
  • fprintf: thay vì xuất ra standard output (màn hình) như printf thì xuất dữ liệu ra file.
  • fscanf: thay vì đọc dữ liệu từ standard input (bàn phím) như scanf thì đọc dữ liệu từ file.
  • sprintf: thay vì xuất dữ liệu ra màn hình như printf, xuất dữ liệu ra chuỗi ký tự (string).
  • sscanf: thay vì đọc dữ liệu từ bàn phím thì đọc dữ liệu từ chuỗi ký tự.
Dưới đây là cách dùng cụ thể của các hàm này:
1.1. Xuất nhập dữ liệu với file:
Đối hàm fprintf gần giống như hàm printf, chỉ thêm duy nhất 1 đối số ở đầu, đó là một con trỏ kiểu FILE*:
int fprintf( FILE* fp, char* strFormat, ... );
(tham khảo: khai báo của printf là: int printf( char* strFormat, ...);)
Đối số FILE* này gọi là "file handle" đến file mà bạn muốn ghi dữ liệu ra. Con trỏ FILE* này cần được khởi tạo trước khi có thể truyền cho hàm fprintf để thực hiện việc output ra file.
Muốn khởi tạo con trỏ FILE*, bạn cần cho biết tên file (path đến file) và kiểu truy cập, như trong ví dụ sau đây:
FILE* fp; // declare a file pointer
int age;
age = 26;
fp = fopen( "example.txt", "at" );
fprintf( fp, "Hello, My name is %s and I am %d years old", "CBD", age );
Đối số đầu tiên của hàm fopen (file open) là path đến file (trong trường hợp chỉ chỉ định tên file, hàm này hiểu là file đó ở cùng current directory). Đối số thứ 2 là một chuỗi ký tự biểu thị cách truy cập file:
  • a: mở file để append (thêm vào cuối), nếu file chưa tồn tại thì tạo file.
  • r: mở file để đọc (read), nếu chưa tồn tại sẽ báo lỗi.
  • w: mở file để ghi (write), nếu chưa tồn tại, tạo file.
  • t: mở file kiểu văn bản (text).
  • b: mở file kiểu nhị phân (ví dụ các file ảnh, không phải file text) (binary).
Có thể kết hợp a, r, w với b, t để thành "at", "wt", "rb", "wb", ....
Hàm fscanf cũng giống hệt như hàm scanf, chỉ khác là tham số đầu là FILE*. File mở ra phải ở chế độ "r" thì mới có thể đọc file:
 #include<stdio.h>
int main()
{
int x;
double d;
FILE* fp;
x = 0;
d = 0;
fp = fopen( "data.txt", "rt" );
if( fp == NULL ) {
printf( "Could not open data.txt, check file\n" );
return 1; // error
}
fscanf( fp, "%d", &x ); // read an integer from the file
fscanf( fp, "%lf", &d ); // read a double
fclose( fp ); // do not forget to close the opened file
printf( "Input data: x = %d, d = %lf\n", x, d );
return 0;
}
(để chạy chương trình trên, bạn cần tạo 1 file text có tên là "data.txt" (tạo bằng Notepad, ...) và ghi vào đó 2 số: 1 số nguyên và 1 số thực, cách nhau ít nhất 1 dấu cách: ví dụ 100 50.3).

1.2. Đọc/ghi từng dòng vào text file:
Muốn đọc/ghi một dòng của một file văn bản, ta dùng hàm fputs và fgets: hàm fputs sẽ ghi một chuỗi ký tự vào file, hàm fgets đọc một file cho đến khi gặp dấu xuống dòng hoặc số ký tự đọc được vượt quá một số max nào đó (chỉ định ở tham số):
fputs( char* strToWrite, FILE* fp );
fgets( char* strBuffer, int nMaxChars, FILE* fp);
(chú ý: strBuffer là một con trỏ kiểu char* để chứa dữ liệu đọc được, nó phải được malloc cẩn thận với cỡ bằng nMaxChars, hàm fgets sẽ đọc cho đến khi gặp ký tự xuống dòng hoặc nếu dòng đó dài quá nMaxChars thì nó sẽ dừng lại ở nMaxChars ký tự).
Nếu đã đọc hết file thì hàm fgets sẽ trả về NULL pointer.
Ví dụ, chương trình sau để copy file văn bản:
 // file: mycopy.c
#include<stdio.h>
int main( int argc, char** argv )
{
/* the main function can be declared like this:
argc: argument count (number of arguments provided from the command line)
argv: argument vector (the actual place where arguments are stored)
Example: $ mycopy file1.txt file2.txt
--> argc = 3
--> argv[0] = "mycopy" (name of the program), argv[1] = "file1.txt", argv[2] = "file2.txt"
*/
char* strInputFile;
char* strOutputFile;
char* buffer;
FILE *fpIn, *fpOut;
if( argc < 3 ) {
printf( "Error: missing input/ouput files\n" );
return 1;
}
strInputFile = argv[1];
strOutputFile = argv[2];
fpIn = fopen( strInputFile, "rt" );
if( fpIn == NULL ) {
printf( "Error: could not read file %s\n", strInputFile );
return 1;
}
fpOut = fopen( strOutputFile, "at" );
buffer = (char*)malloc( 256 * sizeof(char) ); // maximum 255 chars / line
while( fgets( buffer, 255, fpIn ) != NULL ) { // read file line by line until reaching end of file
fputs( buffer, fpOut );
}
fclose( fpIn );
fclose( fpOut );
printf( "Okie, 1 file copied!\n" );
return 0;
}
(chương trình trên dùng cách khai báo thứ 2 của hàm main (int main(int argc, char** argv)) để lấy tham số nhập vào từ dòng lệnh khi chương trình được gọi. Để chạy chương trình trên bạn cần có 1 file văn bản nào đó, ví dụ "file1.txt", sau đó bạn gọi chương trình như sau:
$ mycopy file1.txt file2.txt
nếu gọi như trên, chương trình sẽ copy file1.txt ra file2.txt.

1.3. Đọc dữ liệu / ghi dữ liệu với chuỗi ký tự:
Nhiều khi bạn cần đổi một số nguyên/thực thành một chuỗi và ngược lại, hàm sprintf và sscanf sẽ giúp bạn làm việc đó. Hàm này cũng giống hệt như hàm fprintf, fscanf, chỉ khác là thay vì FILE*, bạn truyền cho tham số đầu tiên là một chuỗi ký tự đã được khai báo và khởi tạo:
 #include<stdio.h>
int main()
{
double x = 100.34;
int y;
char* strX;
char* strInt = "345";
strX = (char*)malloc( 20 * sizeof(char) );
sprintf( strX, "%lf", x );
sscanf( strInt, "%d", &y );
printf( "x = %d, strInt = %s\n", x, strInt );
printf( "strX = %s, y = %d\n", strX, y );
return 0;
}
2. Các hàm trong string.h:
String.h là file header khai báo các hàm liên quan đến chuỗi ký tự. 

* Hàm strcpy để copy nội dung một chuỗi vào một chuỗi khác:
char* strcpy( char* strDestination, char* strSource );
Ví dụ, muốn khởi tạo một chuỗi ký tự mà không dùng phép gán ngay sau khi khai báo (kiểu char* str = "Con Bo Dien"), bạn có thể malloc chuỗi đó và dùng strcpy để gán:
   char* strCustomerName;
strCustomerName = (char*)malloc( 256 * sizeof( char ) );
strcpy( strCustomerName, "Nguyen Van Abc" );
printf( "Customer Name = %s\n", strCustomerName );

* Hàm strcat để nối (concatenate) một chuỗi vào chuỗi khác: char* strcat( char* strDestination, char* strToAttach );
Ví dụ, str1 = "Nguyen Van ", str2 = "Abc" thì strcat( str1, str2 ) sẽ cho str1 = "Nguyen Van Abc".

* Hàm strlen để tính chiều dài của chuỗi ký tự, ví dụ: int nLen = strlen( strInput );

* Hàm strcmp để compare 2 chuỗi xem chuỗi nào có thứ tự ABC lớn hơn (xem manual).

* Ngoài ra còn các hàm như strncpy, strncmp, strdup, strstr, strtok, ... hãy tự tham khảo manual.

3. Các hàm trong math.h:
Math.h gồm các hàm toán học: sin, cos, tan, exp, log (cơ số e), sqrt, sqr, ...

4. Các hàm trong stdlib.h:
Stdlib.h gồm các hàm thư viện thông dụng (general purpose standard library). Ở đây chỉ giới thiệu 3 hàm rất hay dùng:
4.1. Thoát khỏi chương trình bằng hàm exit:
Hàm này để thoát ngay khỏi chương trình, nó sẽ đóng các file đang mở, và thông báo cho hệ điều hành biết trạng thái kết thúc mà ta truyền cho nó (thường trạng thái 0 là không có lỗi, 1 là có lỗi):
void exit( int nStatus );
Ví dụ: exit( 1 ); hay exit( 0 );
4.2. Sinh số ngẫu nhiên theo phân bố đều:
Hàm rand và srand để điều khiển việc sinh số ngẫu nhiên. Hàm rand sẽ sinh ra 1 số nguyên ngẫu nhiên trong khoảng từ 0 đến RAND_MAX (một hằng số được define sẵn). Hàm srand sẽ đặt nhân (seed) cho thuật toán sinh số ngẫu nhiên. Để mỗi lần chạy chương trình thì dãy số ngẫu nhiên sinh ra khác nhau bạn cần đặt nhân khác nhau, vì thế cách tốt nhất là dùng hàm time để lấy thời gian hiện hành làm nhân.
Ví dụ:
 #include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
int i, v;
srand( time( NULL ) ); // set random seed
for( i = 0; i < 1000; i++ ) {
v = rand();
printf( "%d\n", v );
}
}
Muốn sinh số thực ngẫu nhiên trong khoảng (0, 1) bạn chỉ việc lấy rand()/(double)RAND_MAX.

5. Đo thời gian chạy chương trình bằng hàm gettimeofday:
Hàm gettimeofday sẽ trả về số giây (chính xác đến micro giây) đã trôi qua kể từ 00:00:00 giờ ngày 1/1/1970.
Hàm này có thể dùng để đo thời gian chạy của chương trình chính xác đến micro giây (1 phần triệu giây). Hàm getttimeofday có đối số là struct timeval, gồm 2 field là tv_sec và tv_usec thể hiện số giây và số micro giây đã trôi qua kể từ 00:00:00 ngày 1/1/1970.
Chương trình sau sẽ đo xem mất bao nhiêu thời gian để tính 33 số đầu trong dãy Fibonacci:
 #include<stdio.h>
#include<time.h>
#include<sys/time.h>

int fib( int n )
{
if( n <= 1 ) return 1;
return fib( n - 1 ) + fib( n - 2 );
}

int main()
{
struct timeval tv1, tv2;
int i, v;
double ellapsed_time;
printf( "Testing your processor speed, please wait ...\n" );
gettimeofday( &tv1, NULL );
for( i = 0; i < 33; i++ ) {
v = fib( i );
}
gettimeofday( &tv2, NULL );
ellapsed_time = 1000000.0 * (tv2.tv_sec - tv1.tv_sec) + 1.0 * (tv2.tv_usec - tv1.tv_usec);
printf( "Ellapsed time = %lf microsec\n", ellapsed_time );
printf( "That means your processor can compute Fibonacci series from 0..32 in the above time\n" );
return 0;
}
Chú ý: hàm gettimeofday chỉ dùng được với GCC (không dùng được với MS VC ...). Trong MSVC có thể đo thời gian bằng hàm QueryPerformanceCounter và QueryPerformanceFrequency.



Tổng kết: Bạn đã học qua toàn bộ các phần thiết yếu của ngôn ngữ lập trình C: biến số, hàm số, toán tử, cách gọi hàm, các kiểu dữ liệu cơ bản, con trỏ, quản lý bộ nhớ, kiểu dữ liệu trừu tượng và các chỉ thị tiền xử lý. Nắm vững những kiến thức này sẽ giúp bạn tự tin trong quá trình lập trình với ngôn ngữ C. Muốn hiểu rõ hơn nữa và sử dụng thành thạo ngôn ngữ C bạn cần luyện tập thật nhiều thông qua những bài tập ở cuối mỗi bài học, cũng như những bài tập ở phần bài tập đi theo sau phần lý thuyết này và các chương trình trong thực tế. Chúc bạn thành công!.

Câu hỏi ôn tập:
1. Thế nào là kiểu dữ liệu trừu tượng (abstract data type, ADT). Nêu ý nghĩa của ADT.
2. Kiểu dữ liệu trừu tượng được biểu diễn trong C bằng công cụ gì?
3. Dùng từ khóa nào để định nghĩa một kiểu dữ liệu mới?.
4. Nêu sự khác nhau giữa 2 identifier: _Complex và Complex trong khai báo sau:
   typedef struct _Complex {
double real;
double img;
} Complex;

5. Hãy viết chương trình cho phép nhập vào 2 số phức, sau đó in ra màn hình Tổng, Hiệu, Tích, Thương của chúng. Chương trình của bạn phải sử dụng kiểu dữ liệu Complex được định nghĩa ở trên. 
6. Hãy viết chương trình giải phương trình bậc 2 trong trường số phức (hiển thị cả nghiệm khi delta âm).
7. Nêu cú pháp của toán tử chọn ternary?
8. Giữa #define SoNguyen int và typedef int SoNguyen; có gì khác nhau?
9. Macro (lệnh gộp) là gì?
10. Hãy dùng toán tử ternary để định nghĩa một macro tên là "myabs" để tính giá trị tuyệt đối của một số.
11. Chỉ thị (directive) #if khác gì với lệnh (statement) if?
12. Hãy viết lại chương trình ở bài 6 sao cho thỏa mãn yêu cầu sau: 
Khi biên dịch bình thường thì chương trình vẫn giống như chương trình ban đầu ở bài 6.
Khi biên dịch với định nghĩa -D__DEBUG__ thì chương trình in ra cả giá trị delta ra màn hình trước khi in kết quả.
Cho biết, với gcc, có thể định nghĩa một địa danh bằng cách dùng option -D như sau:
   $ gcc -D__DEBUG__ file.c
Khi có -D__DEBUG__ thì chỉ thị #ifdef( __DEBUG__ ) sẽ thực hiện nhánh đúng.
13. Hãy viết các file matrix_funcs.c, matrix_sum.c, matrix_prot.c và matrix.c như ở phần 3.1 của bài học và thực hiện dịch từng phần.
14. Hãy viết Makefile cho bài 13.
15. Hãy viết chương trình đếm số dòng có trong một file văn bản. Hãy hỏi tên file và cho phép người dùng nhập tên file từ bàn phím.
16. Hãy sửa chương trình 15 để người dùng có thể nhập ngay tên file từ dòng lệnh:
$ line_count file.txt
17. Hãy viết chương trình đọc 2 ma trận từ 1 file và hiển thị ra màn hình kết quả ma trận tổng. Định dạng của file dữ liệu như sau:
   m n
a11 a12 a13 ...
a21 a22 a23 ...
....
a_m1 a_m2 ... a_mn
b11 b12 b13 ...
...
b_m1 b_m2 ... b_mn
Hai số đầu là cỡ của ma trận (row, col). m dòng tiếp theo là m hàng của ma trận số 1 (mỗi hàng chứa n số nguyên). m dòng tiếp theo nữa là m hàng của ma trận số 2.
18. Hãy sửa lại bài 17 để kết quả cũng được xuất ra 1 file, tên file input và output được nhập từ command line.
19. Cho biết một file handle được khai báo sẵn có tên là stdin, nó biểu diễn standard input (bàn phím). Hãy dùng hàm fgets để đọc một chuỗi từ bàn phím như sau: fgets( strBuf, 255, stdin ); (lưu ý: biến stdin đã được khai báo sẵn trong stdio.h, bạn không cần khai báo và khởi tạo, chỉ việc dùng).
20. Cho biết stdard output (màn hình) được biểu diễn bằng 1 file handle (FILE*) có tên là stdout. Hãy dùng hàm fprintf để xuất một chuỗi dữ liệu ra màn hình.
21. Hãy sử dụng cấu trúc Complex ở trên để nhập vào một dãy các số phức và tính tổng của chúng. Mảng của số phức có thể được khai báo như sau:
   Complex *arrComplex;
arrComplex = (Complex*)malloc( nSize * sizeof(Complex) );
arrComplex[0].real = 1;
arrComplex[0].img = 2;
22. Hãy định nghĩa một cấu trúc dữ liệu mới có tên là Student. Cấu trúc này bao gồm tên của sinh viên (dạng char*, cần malloc và free cẩn thận, dài tối đa 256 ký tự), và điểm Toán, Lý, Hóa của sinh viên đó. Sau đó, hãy viết chương trình cho phép người dùng nhập vào từ bàn phím tên và điểm của 10 sinh viên ( dùng hàm fgets để đọc tên sinh viên như bài 19).
Cuối cùng, hãy hiển thị danh sách sinh viên bao gồm 5 cột: Tên, Điểm Toán, Điểm Lý, Điểm Hóa và một cột tên là Đỗ/Trượt. Trong cột Đỗ/Trượt hãy hiển thị "Đỗ" nếu tổng điểm lớn hơn hay bằng 15, ngược lại hiển thị "Trượt".
Chú ý: malloc và free cẩn thận.
23. Hãy viết 2 hàm cùng để tính giai thừa của một số, một hàm dùng đệ quy, một hàm không dùng đệ quy.
Hãy đo thời gian thực hiện việc tính giai thừa từ 1..12 dùng 2 hàm này.
So sánh thời gian và thử giải thích kết quả?

24. Sau đây là ý nghĩa (bằng lời) của lệnh gán các struct trong ngôn ngữ C:
Giả sử có định nghĩa struct và 2 biến số kiểu struct như sau:
 typedef struct _StructName {
type1 field1;
type2 field2;
...
type_n field_n;
} ADTName;
ADTName data1, data2; // 2 biến kiểu struct
Khi đó, lệnh gán "data1 = data2;" có ý nghĩa tương đương với khối lệnh sau:
 data1.field1 = data2.field2;
data1.field2 = data2.field2;
...
data1.field_n = data2.field_n;
Có nghĩa là, lệnh gán struct sẽ làm một việc đơn giản là copy từng bit của data2 vào data1, do đó nó tương đương với việc gán từng trường của data2 cho các trường tương ứng của data1. 
a)(*) Viết phát biểu trên dưới dạng operational semantics? (chỉ cần viết từ đoạn "Khi đó, ...", không cần quan tâm đến đoạn "Giả sử ..." ở phía trước).
b) Hãy đoán kết quả của chương trình sau:
 // file: swap_struct.c
#include<stdio.h>
typedef struct _Point {
int x;
int y;
} Point;

void swap( Point p, Point q )
{
Point r;
r = p;
p = q;
q = r;
}

void xchg( Point* pP, Point* pQ )
{
Point r;
r = *pP;
*pP = *pQ;
*pQ = r;
}

int main()
{
Point p1, p2;
p1.x = 1; p1.y = 2;
p2.x = -3; p2.y = -4;
printf( "Before swap: p1 = (%d, %d), p2 = (%d, %d)\n", p1.x, p1.y, p2.x, p2.y );
swap( p1, p2 );
printf( "After swap: p1 = (%d, %d), p2 = (%d, %d)\n", p1.x, p1.y, p2.x, p2.y );
printf( "Before xchg: p1 = (%d, %d), p2 = (%d, %d)\n", p1.x, p1.y, p2.x, p2.y );
xchg( &p1, &p2 );
printf( "After xchg: p1 = (%d, %d), p2 = (%d, %d)\n", p1.x, p1.y, p2.x, p2.y );
return 0;
}
Sau đó, hãy chạy chương trình và giải thích kết quả. 


25. Hãy đoán kết quả chương trình sau:
 // file: modify_struct.c
#include<stdio.h>

typedef struct _Point {
int x;
int y;
} Point;

void modify_struct1( Point* pP )
{
pP->x += 1;
pP->y += 1;
}

void modify_struct2( Point p )
{
p.x += 1;
p.y += 1;
}

void main()
{
Point q;
q.x = 10;
q.y = 20;
printf( "Step0: q = (%d, %d)\n", q.x, q.y );
modify_struct1( &q );
printf( "Step1: q = (%d, %d)\n", q.x, q.y );
modify_struct2( q );
printf( "Step2: q = (%d, %d)\n", q.x, q.y );
return 0;
}
Hãy chạy chương trình xem bạn đoán có đúng không và giải thích kết quả?

26. Hãy đoán kết quả chương trình sau. Sau đó, chạy và giải thích kết quả?.
 // file: sm_struct.c
// swap and modify structs
#include<stdio.h>

typedef struct _Point {
int x;
int y;
} Point;

void swap_and_mod1( Point* pP, Point* pQ )
{
Point* pR;
// swap
pR = pP;
pP = pQ;
pQ = pR;
// modify
(*pP).x += 1;
(*pP).y += 1;
}

void swap_and_mod2( Point* pP, Point *pQ )
{
Point* pR;
pR = (Point*)malloc( sizeof(Point) );
// swap
*pR = *pP;
*pP = *pQ;
*pQ = *pR;

free( pR );
pR = NULL;

// modify
pP->x += 1;
pP->y += 1;
}

void swap_and_mod3( Point p, Point q )
{
Point *pR;
// swap
pR = &p;
p = q;
q = *pR;
// modify
p.x += 1;
p.y += 1;
}

void swap_and_mod4( Point* pP, Point* pQ )
{
Point r;
// swap
r = *pP;
*pP = *pQ;
*pQ = r;
// modify
r.x += 1;
(&r)->y += 1;
}

void swap_and_mod5( Point p, Point q )
{
Point r;
/* swap */
// r = p
r.x = p.x;
r.y = p.y;
// p = q
p.x = q.x;
p.y = q.y;
// q = r
q.x = r.x;
q.y = r.y;

/* modify */
p.x += 1;
p.y += 1;
}

void print_result( Point* pP, Point* pQ, int nStep )
{
printf( "Step%d: p = (%d, %d), q = (%d, %d)\n", nStep, pP->x, pP->y, pQ->x, pQ->y );
}

int main()
{
Point p, q;
p.x = 100; p.y = 200;
q.x = -300; q.y = -400;

print_result( &p, &q, 0 );
swap_and_mod1( &p, &q );
print_result( &p, &q, 1 );
swap_and_mod2( &p, &q );
print_result( &p, &q, 2 );
swap_and_mod3( p, q );
print_result( &p, &q, 3 );
swap_and_mod4( &p, &q );
print_result( &p, &q, 4 );
swap_and_mod5( p, q );
print_result( &p, &q, 5 );

return 0;
}
Hãy cho biết hàm swap_and_mod thứ mấy đáp ứng được cả 2 yêu cầu: vừa swap vừa modify?.

27. Hãy đoán kết quả chương trình sau, sau đó chạy để kiểm chứng. Giải thích kết quả?.
 // file: arr_struct.c
// array of struct
#include<stdio.h>

typedef struct _Point {
int x;
int y;
} Point;

void modify( Point* arrInput, int nSize )
{
int i;
for( i = 0; i < nSize; i++ ) {
arrInput[i].x += 1;
arrInput[i].y += 1;
}
}

void modify_by_pointer( Point* arrInput, int nSize )
{
int i;
Point *pQ;
pQ = arrInput;
for( i = 0; i < nSize; i++ ) {
pQ->x += 1;
pQ->y += 1;
pQ++;
}
}

/* begin modify by value */
void modify_value( Point p )
{
p.x += 1;
p.y += 1;
}
void modify_by_value( Point* arrInput, int nSize ) {
int i;
for( i = 0; i < nSize; i++ ) {
modify_value( arrInput[i] );
}
}
/* end modify by value */

Point* allocate_points( int nSize )
{
Point* arrRet;
int i;
arrRet = (Point*)malloc( nSize * sizeof(Point) );
for( i = 0; i < nSize; i++ ) {
arrRet[i].x = 3;
arrRet[i].y = 3;
}
return arrRet;
}

void print_points( Point* arrInput, int nSize, int nStep )
{
int i;
printf( "Step%d: ", nStep );
for( i = 0; i < nSize; i++ ) {
printf( "p%d = (%d, %d), ", i, arrInput[i].x, arrInput[i].y );
}
printf( "\n" );
}

int main()
{
Point* arrPoints;
int nSize;
nSize = 2; // you can change this value!
arrPoints = allocate_points( nSize );
print_points( arrPoints, nSize, 0 );

modify( arrPoints, nSize );
print_points( arrPoints, nSize, 1 );
modify_by_pointer( arrPoints, nSize );
print_points( arrPoints, nSize, 2 );
modify_by_value( arrPoints, nSize );
print_points( arrPoints, nSize, 3 );

return 0;
}


28.(**) Hãy chạy chương trình sau và giải thích điều gì xảy ra?
Gợi ý: hãy dùng hàm printf để in ra mọi thứ cần in!.
 #include<stdio.h>
// a point in space-time coordinate (4D)
typedef struct _Point {
int x;
int y;
int z;
int t;
} Point;

int main()
{
int i;
Point arrPoints[16];
int j;

j = 0;
for( i = 0; i <= 16; i++ ) {
arrPoints[i].x = 1;
arrPoints[i].y = 2;
arrPoints[i].z = 3;
arrPoints[i].t = 4;
printf( "j = %d\n", j );
j++;
if( j > 10000 ) j = 0;
}
return 0;
}