Giáo trình C++ và Lập trình Hướng đối tượng (Phần 1) - GS. Phạm Văn Ất

2023

209
0
0

Phí lưu trữ

55 Point

Tóm tắt

I. Tổng quan giáo trình C và lập trình hướng đối tượng OOP

Giáo trình C và lập trình hướng đối tượng là nền tảng cốt lõi cho mọi lập trình viên. Ngôn ngữ C, ra đời vào năm 1973 bởi Dennis Ritchie, là một ngôn ngữ lập trình cấu trúc mạnh mẽ và được ưa chuộng trên toàn thế giới. Tuy nhiên, để đáp ứng nhu cầu phát triển phần mềm phức tạp hơn, Bjarne Stroustrup đã phát triển C++ vào năm 1980, ban đầu với tên gọi "C có lớp". C++ không chỉ là một sự mở rộng của C mà còn tích hợp đầy đủ các khái niệm của lập trình hướng đối tượng (OOP). Điều này biến C++ thành một ngôn ngữ lai mạnh mẽ, cho phép tổ chức chương trình theo cả hàm và lớp. Việc nắm vững nhập môn lập trình C và sau đó chuyển tiếp lên C++ OOP là một lộ trình học tập hiệu quả. Tài liệu này sẽ hệ thống hóa các khái niệm từ cơ bản đến nâng cao, bắt đầu từ những khác biệt giữa C và C++, giới thiệu các phương pháp lập trình, và đi sâu vào các trụ cột của OOP như lớp, đối tượng, thừa kế và đa hình. Các chương trình minh họa thực tế, được thử nghiệm trên cả Turbo C++ và Code::Blocks, sẽ là công cụ hỗ trợ đắc lực cho người học. Việc hiểu rõ sự chuyển dịch từ lập trình cấu trúc sang tư duy hướng đối tượng là chìa khóa để khai thác toàn bộ sức mạnh của C++. Các khái niệm như biến và kiểu dữ liệu, toán tử trong C, và cấu trúc điều khiển vẫn là nền tảng, nhưng được bổ sung và mở rộng trong C++ để hỗ trợ mô hình OOP.

1.1. Lịch sử phát triển và sự kế thừa từ ngôn ngữ C

C++ là một sự phát triển mạnh mẽ và trực tiếp từ ngôn ngữ C. Theo tài liệu gốc, C++ "là một sự mở rộng (đáng kể) của C. Điều đó có nghĩa là mọi khả năng, mọi khái niệm trong C đều dùng được trong C++". Do đó, một yêu cầu bắt buộc đối với người học C++ là phải có kiến thức tương đối thành thạo về C. Sự ra đời của C++ bởi Bjarne Stroustrup tại Bell Labs đã đưa C vào thế giới hướng đối tượng, bổ sung các công cụ mạnh mẽ như lớp (class), thừa kế (inheritance), và tính tương ứng bội (polymorphism). Về cơ bản, một chương trình C đã là một chương trình C++, chỉ cần thay đổi đuôi tệp từ .c thành .cpp và đảm bảo khai báo đầy đủ nguyên mẫu hàm bằng các lệnh #include, một yêu cầu nghiêm ngặt hơn của trình biên dịch C++ so với C. Đây là bước đầu tiên trong quá trình tự học lập trình C++.

1.2. So sánh hai phương pháp lập trình cấu trúc và OOP

Lập trình cấu trúc, với các đại diện như C hay Pascal, tập trung vào việc phân chia chương trình thành các đơn vị nhỏ hơn gọi là hàm (hoặc thủ tục). Các hàm này dùng để xử lý dữ liệu, nhưng lại tách rời khỏi cấu trúc dữ liệu mà chúng thao tác. Ngược lại, lập trình hướng đối tượng (OOP) có khái niệm trung tâm là lớp (class). Lớp là một đơn vị bao gồm cả dữ liệu (thành phần dữ liệu) và các phương thức (hàm thành viên) xử lý dữ liệu đó. Sự kết hợp này cho phép mô tả các thực thể trong thế giới thực một cách đầy đủ và chân thực hơn. C++ là một ngôn ngữ lai, hỗ trợ cả hai phương pháp, cho phép người dùng linh hoạt lựa chọn giữa việc tổ chức chương trình theo hàm hoặc theo lớp.

1.3. Giới thiệu khái niệm OOP Lớp và đối tượng trong C

Lớp và đối tượng trong C++ là nền tảng của OOP. Lớp (class) có thể được xem là một sự mở rộng của cấu trúc (struct) trong C, bằng cách bổ sung các phương thức (hàm thành viên). Lớp hoạt động như một bản thiết kế, định nghĩa các thuộc tính (dữ liệu) và hành vi (phương thức) của một loại thực thể. Từ một lớp, có thể tạo ra các biến kiểu lớp, được gọi là đối tượng (object). Mỗi đối tượng là một thể hiện cụ thể của lớp, có các thành phần dữ liệu và phương thức riêng. Một chương trình hướng đối tượng bao gồm các lớp có quan hệ với nhau, tập trung vào việc thiết kế và xây dựng các lớp này để mô tả bài toán. Đây là một trong những khái niệm OOP quan trọng nhất mà người mới bắt đầu cần nắm vững.

II. Thách thức khi tự học lập trình C cho người mới bắt đầu

Quá trình chuyển đổi từ nhập môn lập trình C sang C++ và OOP đặt ra không ít thách thức. Đối với C++ cho người mới bắt đầu, rào cản đầu tiên là sự khác biệt trong yêu cầu của trình biên dịch. Tài liệu gốc chỉ ra rằng, trình biên dịch C++ "yêu cầu mọi hàm chuẩn dùng trong chương trình đều phải khai báo nguyên mẫu bằng một câu lệnh #include", điều mà trình biên dịch C có thể bỏ qua. Lỗi "Function should have a prototype" là một ví dụ điển hình khi biên dịch mã C cũ trong môi trường C++. Một thách thức lớn khác là việc tiếp cận các khái niệm OOP hoàn toàn mới. Tư duy lập trình phải thay đổi từ việc viết các hàm tuần tự sang thiết kế các lớp và đối tượng. Các khái niệm trừu tượng như đóng gói, thừa kế, đa hình đòi hỏi thời gian để hiểu và áp dụng đúng đắn. Hơn nữa, cú pháp C++ cũng được mở rộng đáng kể. Sự xuất hiện của các toán tử mới như new, delete, cout <<, cin >> hay các khái niệm như biến tham chiếu, hàm trùng tên, đối mặc định có thể gây bối rối cho người mới học. Việc quản lý bộ nhớ động cũng trở nên phức tạp hơn, đòi hỏi sự cẩn thận để tránh rò rỉ bộ nhớ. Nắm vững những thách thức này và tìm kiếm tài liệu lập trình hướng đối tượng chất lượng là bước quan trọng để thành công.

2.1. Lỗi thường gặp khi chuyển mã nguồn từ C sang C

Một trong những lỗi phổ biến nhất khi chuyển đổi mã nguồn là thiếu khai báo nguyên mẫu hàm. Ví dụ, một chương trình C sử dụng hàm sqrt() từ thư viện math.h có thể chạy mà không có lệnh #include <math.h>. Tuy nhiên, khi biên dịch bằng C++, trình biên dịch sẽ báo lỗi Error: Funtion ‘sqrt’ should have a prototype. Điều này nhấn mạnh tính chặt chẽ của C++. Một điểm khác biệt nữa là cách xử lý kiểu dữ liệu. Trong C, sizeof('A') thường bằng sizeof(int), trong khi ở C++ sizeof('A') bằng sizeof(char). Những khác biệt nhỏ nhưng quan trọng này đòi hỏi lập trình viên phải chú ý khi chuyển đổi dự án, đặc biệt là các dự án lớn.

2.2. Khó khăn trong việc tiếp cận các khái niệm OOP mới

Việc chuyển từ tư duy lập trình cấu trúc sang hướng đối tượng là một bước nhảy vọt về nhận thức. Các khái niệm OOP như lớp, đối tượng, đóng gói (encapsulation), thừa kế (inheritance) và đa hình (polymorphism) không có trong C. Người học cần thời gian để hiểu rằng dữ liệu và hành vi nên được gói gọn trong một thực thể duy nhất (lớp), thay vì bị tách rời. Việc thiết kế mối quan hệ giữa các lớp, chẳng hạn như quan hệ "is-a" (thừa kế) hay "has-a" (composition), là một kỹ năng mới cần được rèn luyện. Các bài giảng C++ OOPslide bài giảng OOP thường tập trung vào việc xây dựng nền tảng tư duy này trước khi đi vào code.

2.3. Rào cản về cú pháp C và các toán tử mở rộng mới

Cú pháp C++ có nhiều mở rộng so với C. Ví dụ, cách viết chú thích bằng // bên cạnh /* */, hay khả năng khai báo biến ở bất kỳ đâu trong khối lệnh thay vì phải ở đầu khối. Đặc biệt, C++ giới thiệu các toán tử vào/ra mới là coutcin, thay thế cho printfscanf. Mặc dù vẫn có thể sử dụng các hàm của C, việc làm quen với luồng (stream) I/O của C++ là cần thiết. Ngoài ra, toán tử cấp phát bộ nhớ new và giải phóng delete thay thế cho mallocfree, cung cấp một cơ chế an toàn và hướng đối tượng hơn. Việc học và sử dụng thành thạo những công cụ mới này là một phần không thể thiếu trong quá trình tự học lập trình C++.

III. Bí quyết nắm vững lập trình C cơ bản và cú pháp cốt lõi

Để nắm vững lập trình C++ cơ bản, cần bắt đầu từ những mở rộng đơn giản nhưng hiệu quả so với C. C++ mang lại sự linh hoạt cao hơn trong việc khai báo biến. Không giống như C yêu cầu tất cả khai báo phải đặt ở đầu khối lệnh, C++ cho phép "khai báo linh hoạt", tức là có thể khai báo biến ngay tại vị trí cần sử dụng. Điều này giúp mã nguồn rõ ràng và dễ kiểm soát hơn. Một cải tiến quan trọng khác là toán tử ép kiểu. Ngoài cách viết (kiểu)biểu_thức của C, C++ còn hỗ trợ cách viết kiểu(biểu_thức), tiện lợi và gần gũi hơn với cú pháp gọi hàm. Việc quản lý bộ nhớ động cũng được cách mạng hóa với sự ra đời của hai toán tử trong C++newdelete. Toán tử new không chỉ cấp phát bộ nhớ mà còn có thể tự động gọi hàm tạo (constructor) của đối tượng, một tính năng mà malloc không có. Tương tự, delete sẽ gọi hàm hủy (destructor) trước khi giải phóng bộ nhớ. Hệ thống vào/ra dữ liệu cũng được cải tiến mạnh mẽ. Thay vì dùng printfscanf, C++ giới thiệu khái niệm dòng tin (stream) với các toán tử cout << để xuất và cin >> để nhập. Phương pháp này có tính độc lập thiết bị cao và an toàn hơn về kiểu dữ liệu. Hiểu rõ và thực hành thường xuyên với những code mẫu C++ sử dụng các tính năng này là cách nhanh nhất để làm chủ cú pháp C++.

3.1. Tìm hiểu biến và kiểu dữ liệu khai báo linh hoạt

Một trong những tiện lợi đầu tiên mà C++ mang lại là khả năng khai báo biến linh hoạt. Trong C, các biến cục bộ phải được khai báo ở ngay đầu một khối lệnh (scope). C++ loại bỏ ràng buộc này, cho phép khai báo biến ở bất kỳ vị trí nào trước khi nó được sử dụng. Ví dụ, biến lặp trong vòng lặp for while có thể được khai báo ngay trong câu lệnh for: for (int i=1; i<=n; ++i). Điều này giúp giới hạn phạm vi của biến, làm cho mã nguồn trở nên sáng sủa, dễ đọc và giảm thiểu lỗi do sử dụng biến ngoài phạm vi dự kiến. Đây là một cải tiến nhỏ nhưng có tác động lớn đến phong cách lập trình và bảo trì mã nguồn.

3.2. Các toán tử trong C nhập xuất và ép kiểu mở rộng

C++ giới thiệu các toán tử nhập xuất >> (extraction) và << (insertion) hoạt động với các đối tượng luồng cin (console input) và cout (console output). Để sử dụng, cần khai báo tệp tiêu đề #include <iostream.h>. Phương thức này tiện lợi hơn scanfprintf vì nó không yêu cầu chuỗi định dạng, giúp tránh các lỗi liên quan đến việc không khớp định dạng và đối số. Ngoài ra, toán tử ép kiểu cũng được mở rộng. Bên cạnh (float)i, C++ cho phép viết float(i), một cú pháp nhất quán hơn với việc gọi hàm. Những cải tiến này giúp việc viết code mẫu C++ trở nên trực quan và an toàn hơn.

3.3. Quản lý bộ nhớ với con trỏ pointer new và delete

Quản lý bộ nhớ động là một phần quan trọng của C và C++. Trong C, các hàm mallocfree được sử dụng. C++ đưa vào hai toán tử mới: new để cấp phát và delete để giải phóng bộ nhớ. Con trỏ (pointer) vẫn giữ vai trò then chốt để lưu trữ địa chỉ của vùng nhớ được cấp phát. Ví dụ: float *px = new float; hoặc int *pn = new int[100];. Ưu điểm của new so với malloc là nó nhận biết được kiểu dữ liệu, không cần ép kiểu và tự động gọi hàm tạo của đối tượng. Tương tự, delete sẽ tự động gọi hàm hủy. Cần lưu ý sử dụng delete [] để giải phóng bộ nhớ được cấp phát cho mảng và chuỗi bằng new Kiểu[n].

IV. Phương pháp xây dựng và sử dụng hàm trong C hiệu quả

C++ mang đến nhiều cải tiến và mở rộng quan trọng cho việc xây dựng và sử dụng hàm trong C++, giúp chương trình trở nên linh hoạt và mạnh mẽ hơn. Một trong những khái niệm đột phá là đối kiểu tham chiếu (reference). Thay vì phải dùng con trỏ (pointer) để thay đổi giá trị của tham số truyền vào, C++ cho phép sử dụng biến tham chiếu (khai báo với &). Điều này làm cho cú pháp gọi hàm và định nghĩa hàm trở nên đơn giản, tự nhiên hơn, tương tự như Pascal. Bên cạnh đó, C++ còn giới thiệu khái niệm đối có giá trị mặc định. Tính năng này cho phép gán trước giá trị cho các đối số của hàm. Khi gọi hàm, nếu không cung cấp giá trị cho các đối số này, chúng sẽ tự động nhận giá trị mặc định, giúp giảm số lượng các biến thể hàm cần viết. Một khái niệm mạnh mẽ khác là hàm trùng tên (function overloading), cho phép định nghĩa nhiều hàm cùng tên nhưng khác nhau về danh sách tham số (số lượng hoặc kiểu). Trình biên dịch sẽ tự động chọn hàm phù hợp dựa trên các đối số được truyền vào lúc gọi hàm. Điều này giúp loại bỏ sự phiền toái khi phải đặt các tên khác nhau cho các hàm thực hiện cùng một logic trên các kiểu dữ liệu khác nhau, ví dụ như abs(), labs(), fabs() trong C. Cuối cùng, hàm inline (hàm trực tuyến) là một công cụ tối ưu hóa hiệu năng, đặc biệt cho các hàm nhỏ, giúp giảm chi phí gọi hàm bằng cách thay thế lời gọi hàm bằng chính mã nguồn của hàm đó tại thời điểm biên dịch.

4.1. Sử dụng biến tham chiếu và đối mặc định hiệu quả

Biến tham chiếu, được khai báo bằng toán tử &, hoạt động như một bí danh cho một biến khác. Khi được sử dụng làm đối số của hàm, nó cho phép hàm thao tác trực tiếp trên biến gốc mà không cần tạo bản sao, giúp tiết kiệm bộ nhớ và thời gian, đặc biệt với các đối tượng lớn. Ví dụ, hàm hoán vị void hv(double &x, double &y) sẽ trực tiếp thay đổi giá trị của hai biến truyền vào. Đối mặc định cho phép hàm trở nên linh hoạt hơn. Ví dụ, void delay(int n = 1000) có thể được gọi là delay() để dùng giá trị mặc định 1000, hoặc delay(5000) để truyền giá trị mới.

4.2. Khám phá khái niệm hàm trùng tên overloading trong C

Hàm trùng tên (overloading) là một tính năng quan trọng của C++ cho phép nhiều hàm trong C++ có cùng tên miễn là chúng có danh sách tham số khác nhau (khác về số lượng hoặc kiểu). Ví dụ, có thể định nghĩa int max(int a, int b)double max(double a, double b). Khi gọi max(3, 5), trình biên dịch sẽ gọi phiên bản int. Khi gọi max(3.5, 2.1), nó sẽ gọi phiên bản double. Tính năng này làm cho mã nguồn trở nên dễ đọc và gần gũi hơn với logic tự nhiên, vì người lập trình chỉ cần nhớ một tên hàm cho một tác vụ chung.

4.3. Tối ưu hiệu năng chương trình với hàm inline trực tuyến

Hàm inline là một gợi ý cho trình biên dịch để thay thế lời gọi hàm bằng mã nguồn của chính hàm đó. Điều này giúp loại bỏ chi phí của một lời gọi hàm (như cấp phát bộ nhớ cho các đối số, lưu trạng thái thanh ghi). Nó đặc biệt hữu ích cho các hàm nhỏ và được gọi thường xuyên. Để định nghĩa một hàm inline, chỉ cần thêm từ khóa inline phía trước khai báo hàm. Tuy nhiên, đây chỉ là một yêu cầu, trình biên dịch có thể bỏ qua nếu hàm quá phức tạp (ví dụ chứa vòng lặp hoặc đệ quy). Sử dụng hàm inline đúng cách giúp cân bằng giữa việc tổ chức mã nguồn rõ ràng và tối ưu hóa tốc độ thực thi.

V. Ứng dụng thực tiễn qua các code mẫu C lớp và đối tượng

Lý thuyết về lập trình hướng đối tượng sẽ trở nên rõ ràng hơn khi được áp dụng vào các bài toán thực tế. Tài liệu lập trình hướng đối tượng này cung cấp nhiều code mẫu C++ minh họa chi tiết. Một ví dụ điển hình là việc xây dựng lớp daydiem để quản lý một dãy các điểm trong không gian hai chiều. Lớp này bao gồm các thành phần dữ liệu như số điểm n và hai con trỏ (pointer) x, y để lưu trữ tọa độ. Các phương thức đi kèm như nhapsl() (nhập số liệu) và do_dai() (tính độ dài) được tích hợp ngay trong lớp, thể hiện rõ nguyên tắc đóng gói của OOP. Dữ liệu và các thao tác trên dữ liệu được kết hợp thành một khối thống nhất. Một ví dụ khác là chương trình quản lý danh sách thí sinh. Ban đầu, có thể sử dụng struct để định nghĩa cấu trúc dữ liệu cho một thí sinh. Sau đó, nâng cấp nó thành một lớp TS (Thí sinh) bằng cách thêm vào các phương thức xử lý. Việc phân tích các ví dụ này giúp người học hiểu được cách chuyển đổi từ tư duy lập trình cấu trúc sang thiết kế hướng đối tượng. Các slide bài giảng OOP thường sử dụng những ví dụ kinh điển này để minh họa quá trình xây dựng một chương trình C++ OOP hoàn chỉnh, từ việc định nghĩa lớp, khai báo đối tượng, đến việc gọi các phương thức để giải quyết bài toán.

5.1. Phân tích code mẫu C Xây dựng lớp daydiem từ A Z

Trong ví dụ về lớp daydiem, ta thấy rõ sự khác biệt so với lập trình cấu trúc. Thay vì dùng các mảng toàn cục và các hàm độc lập, tất cả được gói gọn trong một class. class daydiem { public: int n; float *x, *y; float do_dai(int i, int j); void nhapsl(void); };. Các thành phần dữ liệu n, x, y và các phương thức do_dai, nhapsl thuộc về cùng một thực thể. Khi sử dụng, ta tạo một đối tượng daydiem p; và gọi phương thức thông qua đối tượng đó, ví dụ p.nhapsl();. Điều này làm cho mã nguồn có tổ chức, dễ quản lý và tái sử dụng hơn.

5.2. Hướng dẫn xây dựng chương trình quản lý thí sinh OOP

Chương trình quản lý thí sinh là một bài toán thực tế phổ biến. Bắt đầu với một cấu trúc struct TS, chứa các thông tin như họ tên, điểm các môn. Trong C++, cấu trúc này có thể được nâng cấp thành lớp TS bằng cách thêm vào các phương thức như nhap_thong_tin(), tinh_tong_diem(), in_thong_tin(). Cách tiếp cận này giúp quản lý từng đối tượng thí sinh một cách độc lập và sạch sẽ. Dữ liệu của một thí sinh chỉ có thể được truy cập và thay đổi thông qua các phương thức của chính lớp đó, đảm bảo tính toàn vẹn dữ liệu. Đây là một ví dụ tuyệt vời cho C++ cho người mới bắt đầu để thực hành các khái niệm OOP.

5.3. Slide bài giảng OOP Từ struct đến lớp và đối tượng

Các slide bài giảng OOP thường trình bày một lộ trình học tập logic, bắt đầu từ những gì người học đã biết. Quá trình chuyển đổi từ struct trong C sang class trong C++ là một bước đệm hoàn hảo. Về bản chất, class trong C++ là một struct mà các thành viên mặc định là private, trong khi struct có các thành viên mặc định là public. Việc giải thích sự tương đồng và khác biệt này giúp người học dễ dàng tiếp cận khái niệm lớp. Các bài giảng sau đó sẽ mở rộng với các từ khóa kiểm soát truy cập như public, private, protected và giới thiệu các khái niệm quan trọng khác như hàm tạo, hàm hủy, tạo thành một nền tảng vững chắc về lớp và đối tượng trong C++.

VI. Kết luận Lộ trình tự học lập trình C và OOP hiệu quả

Phần 1 của giáo trình đã cung cấp một cái nhìn tổng quan nhưng đầy đủ về sự chuyển dịch từ C sang C++ và những bước đầu tiên vào thế giới lập trình hướng đối tượng. Nội dung bao trùm từ lịch sử phát triển, so sánh các phương pháp lập trình, đến việc phân tích chi tiết các mở rộng cú pháp và cải tiến về hàm trong C++. Lộ trình tự học lập trình C++ hiệu quả bắt đầu bằng việc nắm chắc các khái niệm nền tảng của C, sau đó làm quen với các mở rộng của C++ như khai báo biến linh hoạt, toán tử new/delete, và hệ thống vào/ra cin/cout. Bước tiếp theo là làm chủ các tính năng mạnh mẽ của hàm trong C++, bao gồm biến tham chiếu, đối mặc định và hàm trùng tên. Cuối cùng, việc áp dụng các kiến thức này để xây dựng các lớp và đối tượng đơn giản thông qua các code mẫu C++ là cách tốt nhất để củng cố lý thuyết. Các khái niệm OOP cốt lõi như lớp, đối tượng và đóng gói đã được giới thiệu, tạo tiền đề vững chắc cho các chủ đề nâng cao hơn như thừa kế và đa hình sẽ được đề cập trong các phần tiếp theo. Việc tìm kiếm thêm các tài liệu lập trình hướng đối tượngEbook C++ PDF uy tín sẽ giúp quá trình học tập trở nên toàn diện và sâu sắc hơn.

6.1. Tóm tắt các khái niệm C và OOP cốt lõi đã học

Phần này đã hệ thống hóa các kiến thức trọng tâm, bao gồm: sự khác biệt và mối quan hệ giữa C và C++; sự đối lập giữa lập trình cấu trúclập trình hướng đối tượng; các cải tiến về cú pháp C++ (chú thích //, khai báo linh hoạt, cin/cout, new/delete); các tính năng nâng cao của hàm (tham chiếu, đối mặc định, overloading, inline); và quan trọng nhất là khái niệm OOP nền tảng: lớp và đối tượng trong C++. Việc nắm vững những điểm này là điều kiện tiên quyết để tiếp tục khám phá các khía cạnh phức tạp hơn của C++.

6.2. Tài liệu và Ebook C PDF bổ sung cho người mới bắt đầu

Để hỗ trợ quá trình tự học lập trình C++, việc tham khảo thêm các nguồn tài liệu chất lượng là rất quan trọng. Bên cạnh giáo trình chính, người học nên tìm kiếm các Ebook C++ PDF kinh điển như "The C++ Programming Language" của Bjarne Stroustrup hay các khóa học trực tuyến. Các trang web như GeeksforGeeks, CPlusPlus.com cung cấp vô số code mẫu C++ và bài giải thích chi tiết. Việc kết hợp giữa giáo trình, sách tham khảo và thực hành liên tục sẽ tạo ra một lộ trình học tập vững chắc, giúp bạn nhanh chóng trở thành một lập trình viên C++ thành thạo.

17/07/2025
Giáo trình c và lập trình hướng đối tượng phần 1