Regular Expressions (biểu thức chính quy, viết tắt là regexp, regex hay regxp) là một chuỗi mẫu để mô tả một bộ các chuỗi khác, theo những quy tắc cú pháp nhất định. Trong lập trình Regex thường được dùng để kiểm tra sự trùng khớp cũng như tính hợp lệ của một chuỗi, chẳng hạn như kiểm tra tính hợp lệ của email, hay số điện thoại. Nhiều ngôn ngữ lập trình hiện nay như C#, Java, PHP,... đã hỗ trợ biểu thức chính quy trong việc xử lý chuỗi. Vì vậy kiến thức về Regex là không thể thiếu bởi nó sẽ giúp đơn giản hóa công việc lập trình.
Bài viết này mình sẽ giới thiệu sơ lược về Regex và cú pháp của nó để các bạn có thể sử dụng và nghiên cứu thêm.
Nơi chia sẻ bài tập, tài liệu, kiến thức về lập trình, thủ thuật máy tính, tiếng Nhật công nghệ thông tin,...
Hiển thị các bài đăng có nhãn kiến thức. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn kiến thức. Hiển thị tất cả bài đăng
21/10/16
10/10/16
Các khái niệm cơ bản trong Git
Nếu bạn là người mới sử dụng Git để quản lý source code thì chắc chắn có rất nhiều thứ để tìm hiểu, đặc biệt là các thuật ngữ trong Git. Vì vậy trong bài viết này mình sẽ cố gắng giải thích các thuật ngữ cơ bản trong Git một các dễ hiểu nhất để có thể nhanh chóng nắm bắt được hệ thống quản lý source code này.
9/11/15
Hướng dẫn sử dụng tính năng Debug trong Visual Studio 2015
Debug là một công việc mà hầu hết người lập trình đều phải thực hiện để tìm lỗi trong phần mềm của mình. Visual Studio đã hỗ trợ khá mạnh tính năng này để giúp đơn giản hơn trong việc tìm lỗi sản phẩm. Trong bài viết này mình sẽ hướng dẫn các bạn sử dụng tính năng Debug trên phiên bản mới nhất của Visual Studio, hiện tại là bản 2015. Các thao tác sẽ được thực hiện trên Visual Studio Enterprise 2015.
Một số thành phần cơ bản
Breakpoints: Là điểm mà chương trình sẽ dừng lại để cho phép bạn chạy từng bước các dòng code. Có thể đặt nhiều breakpoint trong chương trình.
Các cửa sổ theo dõi biến: Giúp bạn theo dõi sự thay đổi của biến hoặc hàm cho mỗi bước chạy. Nếu một biến có sự thay đổi giá trị thì sẽ có màu đỏ để phân biệt. Có 3 loại:
Sơ lược về Debug của Visual Studio
Debug trong Visual Studio cho phép bạn chạy chương trình từng bước để xem sự thay đổi giá trị của biến, trả về của hàm,... qua đó phát hiện những lỗi logic trong chương trình.Một số thành phần cơ bản
Breakpoints: Là điểm mà chương trình sẽ dừng lại để cho phép bạn chạy từng bước các dòng code. Có thể đặt nhiều breakpoint trong chương trình.
Các cửa sổ theo dõi biến: Giúp bạn theo dõi sự thay đổi của biến hoặc hàm cho mỗi bước chạy. Nếu một biến có sự thay đổi giá trị thì sẽ có màu đỏ để phân biệt. Có 3 loại:
- Autos: Hiển thị tự động các biến, hoặc hàm trả về trong các dòng code trước.
- Locals: Tương tự Autos nhưng chỉ hiển thị các biến liên quan trong nội bộ hàm hoặc khối lệnh.
- Watch: cửa sổ tùy chỉnh cho phép bạn xem chỉ các biến mà bạn yêu cầu hoặc giá trị tùy chỉnh bất kỳ. Visual Studio cho phép bạn mở tối đa 4 cửa sổ Watch.
Thanh công cụ Debug: cung cấp các nút lệnh để bạn thực hiện Debug chương trình.
Cửa sổ Call Stack: Chứa lời gọi hàm trong ngăn xếp. Nếu chỉ debug ở mức độ cơ bản thì cũng không cần quan tâm cửa sổ này lắm.
Cửa sổ Diagnostic Tool: Chứa các công cụ chẩn đoán nâng cao. Cung cấp biểu đồ thời gian thực bộ nhớ, CPU,... mà chương trình sử dụng. Ngoài ra nó còn hiển thị các sự kiện được bắt và thời gian bắt.
19/8/15
Danh sách liên kết vòng và một số thao tác
Chúng ta cùng tìm hiểu một cấu trúc dữ liệu cũng khá hữu ích là Danh sách liên kết vòng (Circular Linked List). Nó biểu diễn một cách tự nhiên các cấu trúc dạng tròn như các góc của đa giác, v.v... DSLK vòng có hai dạng thường thấy là dạng vòng đơn và vòng kép.
Dạng vòng đơn thực chất là một danh sách liên kết đơn có phần tử cuối trỏ về phần tử đầu tiên. Nó cũng có nhược điểm là chỉ duyệt từ một chiều. Dạng vòng kép cũng là một danh sách liên kết kép có phần tử cuối trỏ về đầu và đầu trỏ ngược về cuối.
Với DSLK vòng ta cần biết một vài thao tác cơ bản đủ dùng và các thao tác này sẽ được minh họa bằng C++. Bài này chỉ nói về danh sách liên kết vòng kép và bạn cũng nên sử dụng vòng kép để việc code lại đơn giản hơn.
Tổ chức dữ liệu
Một danh sách gồm có các phần tử gọi là node, mỗi node gồm 1 biến chứa dữ liệu và một hoặc nhiều biến con trỏ để liên kết với các node khác. Dưới đây là khai báo cấu trúc node:
Do cấu trúc này ở dạng vòng nên một danh sách ta chỉ cần chọn một phần tử đầu thôi.
Dạng vòng đơn thực chất là một danh sách liên kết đơn có phần tử cuối trỏ về phần tử đầu tiên. Nó cũng có nhược điểm là chỉ duyệt từ một chiều. Dạng vòng kép cũng là một danh sách liên kết kép có phần tử cuối trỏ về đầu và đầu trỏ ngược về cuối.
Với DSLK vòng ta cần biết một vài thao tác cơ bản đủ dùng và các thao tác này sẽ được minh họa bằng C++. Bài này chỉ nói về danh sách liên kết vòng kép và bạn cũng nên sử dụng vòng kép để việc code lại đơn giản hơn.
Tổ chức dữ liệu
Một danh sách gồm có các phần tử gọi là node, mỗi node gồm 1 biến chứa dữ liệu và một hoặc nhiều biến con trỏ để liên kết với các node khác. Dưới đây là khai báo cấu trúc node:
struct DoublyNode
{
<datatype> info;
DoublyNode* prev, *next;
};
Do cấu trúc này ở dạng vòng nên một danh sách ta chỉ cần chọn một phần tử đầu thôi.
struct DoublyList
{
DoublyNode* head;
};
17/8/15
Danh sách liên kết kép và các thao tác cơ bản
Nếu bạn đã đọc bài viết về Danh sách liên kết đơn thì có thể thấy việc tổ chức dạng danh sách tiện lợi hơn rất nhiều so với dùng mảng. Tuy nhiên, danh sách liên kết đơn vẫn có nhược điểm là chỉ có thể duyệt từ đầu đến cuối. Vì vậy, một số thao tác sẽ rất khó cài đặt trên nó. Danh sách liên kết kép có thể khắc phục nhược điểm này. Hầu hết các thao tác điều giống với danh sách liên kết đơn nhưng mình cũng khuyên các bạn nên đọc bài: Danh sách liên kết đơn và các thao tác cơ bản trước khi đọc bài viết này. Các thao tác được minh họa bằng C++.
Tổ chức dữ liệu
Với danh sách liên kết kép, mỗi phần tử sẽ liên kết với phần tử đứng trước và sau nó trong danh sách.
Mỗi phần tử trong danh sách (node) gồm biến lưu dữ liệu và 2 con trỏ liên kết tới phần tử trước và sau nó.
Khai báo phần tử của danh sách:
Một danh sách thì gồm nhiều phần tử, các phần tử đã liên kết nhau, mà đây là cấu trúc dữ liệu động nên ta cần biết phần tử đầu và phần tử cuối của danh sách.
Khai báo cấu trúc danh sách:
Các thao tác cơ bản
Tạo danh sách rỗng
Khi bạn tạo một biến kiểu List như trên thì 2 con trỏ thành viên được tạo ra, tuy nhiên chúng chưa được khởi tạo giá trị. Thao tác với con trỏ rỗng thì nguy hiểm nên cần thiết phải gán NULL cho 2 con trỏ này.
Tổ chức dữ liệu
Với danh sách liên kết kép, mỗi phần tử sẽ liên kết với phần tử đứng trước và sau nó trong danh sách.
Mỗi phần tử trong danh sách (node) gồm biến lưu dữ liệu và 2 con trỏ liên kết tới phần tử trước và sau nó.
Khai báo phần tử của danh sách:
struct Node
{
<datatype> info;
Node *prev, *next;
};
Một danh sách thì gồm nhiều phần tử, các phần tử đã liên kết nhau, mà đây là cấu trúc dữ liệu động nên ta cần biết phần tử đầu và phần tử cuối của danh sách.
Khai báo cấu trúc danh sách:
struct List
{
Node *head, *tail;
}
Có thể minh họa danh sách liên kết kép như sau:Các thao tác cơ bản
Tạo danh sách rỗng
Khi bạn tạo một biến kiểu List như trên thì 2 con trỏ thành viên được tạo ra, tuy nhiên chúng chưa được khởi tạo giá trị. Thao tác với con trỏ rỗng thì nguy hiểm nên cần thiết phải gán NULL cho 2 con trỏ này.
void CreateList(List &list)
{
list.head = list.tail = NULL;
}
23/7/15
Kiểu dữ liệu trong C
Trong C có 5 kiểu dữ liệu cơ bản:
Kiểu số nguyên
Kiểu số nguyên trong C là
Ví dụ:
Kiểu số thực
Trong C có 2 kiểu số thực là
Kiểu
Tùy nhu cầu mà người ta sử dụng
Lưu ý: tính toán với các kiểu dữ liệu thực luôn có sai số.
Ví dụ:
int, float, double, char, void. Ngoài ra khi kết hợp với các từ khóa signed, unsigned, long, short ta còn có thêm các kiểu dẫn xuất. Kiểu cấu trúc struct trong C cho phép ta định nghĩa các kiểu dữ liệu mới dựa trên các kiểu dữ liệu cơ bản. Kiểu con trỏ là kiểu dữ liệu đặc biệt dùng lưu địa chỉ của biến khác.Kiểu số nguyên
Kiểu số nguyên trong C là
int, được dùng để lưu trữ số nguyên. Đây là kiểu dữ liệu cơ bản nhất trong các ngôn ngữ lập trình. Giá trị của kiểu int không cố định mà phụ thuộc vào trình biên dịch, thông thường là 4 bytes.Ví dụ:
int a; int a = 5; int a, b;
Kiểu số thực
Trong C có 2 kiểu số thực là
float và double, cho phép lưu trữ cả phần thập phân của một số.Kiểu
float có giá trị 4 bytes và có độ chính xác tới 6 chữ số thập phân. Kiểu double có giá trị 8 bytes và chính xác tới 10 chữ số. Tuy nhiên độ chính xác này là tương đối và còn phụ thuộc vào hệ điều hành.Tùy nhu cầu mà người ta sử dụng
float hay double, nếu tính toán không yêu cầu độ chính xác cao nên dùng kiểu float để tiết kiệm bộ nhớ.Lưu ý: tính toán với các kiểu dữ liệu thực luôn có sai số.
Ví dụ:
float a = 5.7; double b = 6; //=> 6.0
13/7/15
Các phạm vi truy xuất trong một lớp đối tượng C++
Khi xây dựng một class, chắc chắn bạn sẽ phải xác định phạm vi truy cập cho các thuộc tính và phương thức trong class đó. Mục đích của việc này nhằm quy định các thành phần nào có thể được truy cập, thay đổi từ bên ngoài, thành phần nào là riêng tư.
Có thể hiểu phạm vi truy xuất này cũng giống như biến toàn cục và biến cục bộ. Biến toàn cục có thể được truy cập từ tất cả các hàm sau khai báo nó, còn biến cục bộ chỉ có thể được truy cập nội bộ trong hàm.
Trong C++, phạm vi truy cập được xác định qua 3 từ khóa: public, private và protected.
Có thể hiểu phạm vi truy xuất này cũng giống như biến toàn cục và biến cục bộ. Biến toàn cục có thể được truy cập từ tất cả các hàm sau khai báo nó, còn biến cục bộ chỉ có thể được truy cập nội bộ trong hàm.
Trong C++, phạm vi truy cập được xác định qua 3 từ khóa: public, private và protected.
- public: Các thành phần mang thuộc tinh này đều có thể được truy cập từ bất kỳ hàm nào, dù ở trong hay ngoài lớp.
- private: Các thành phần mang thuộc tinh này chỉ có thể được truy cập bên trong phạm vi lớp. Vì trong C++ cho phép định nghĩa phương thức ngoài khai báo lớp nên phạm vi lớp được hiểu là bên trong khai báo lớp và bên trong các định nghĩa thuộc lớp.
- protected: Các thành phần mang thuộc tinh này chỉ có thể được truy cập bên trong phạm vi lớp và các lớp con kế thừa nó. Như vậy, nếu một lớp không có lớp con kế thừa nó thì phạm vi protected cũng giống như private.
Một ngoại lệ chỉ có trong C++ đó là định nghĩa friend. Một hàm hoặc lớp friend có thể truy cập vào các thành phần private và protected của lớp với hàm đó (hàm friend) hoặc với các đối tượng khác (lớp friend) với điều kiện phải được khai báo trước trong lớp.
Một số lưu ý về phạm vi truy xuất trong C++:
Một số lưu ý về phạm vi truy xuất trong C++:
- Phạm vi truy xuất trong C++ được xác định trong qua các nhãn trong khai báo lớp. Nhãn bao gồm từ khóa và dấu hai chấm.
- Mỗi nhãn có phạm vi ảnh hưởng từ lúc khai báo đến khi gặp nhãn khác hoặc hết khai báo lớp.
- Nếu không chỉ rõ nhãn đầu tiên thì ngầm định nó có phạm vi truy cập là private.
9/7/15
Tổng quan về đối tượng fstream trong C++
Đối tượng fstream cung cấp nhiều phương thức để đọc ghi file nhưng tiện lợi và an toàn hơn con trỏ
1. Tạo đối tượng fstream để đọc/ghi file. Việc đọc/ghi file sẽ được thực hiện thông qua đối tượng này.
Khai báo: fstream <tênbiến>;
2. Dùng phương thức
Cú pháp:
Đường dẫn có thể là tương đối (tính từ file exe khi được build ra) hoặc tuyệt đối (tính từ ổ đĩa gốc).
Chế độ mở có nhiều loại:
Có thể kết hợp nhiều chế độ bằng toán tử
Ví dụ:
FILE nhiều. Để sử dụng được fstream bạn cần khai báo #include<fstream>. Ngoài ra, C++ còn cung cấp thêm 2 đối tượng khác là ifstream và ofstream chỉ chuyên biệt cho việc đọc file và ghi file trong khi fstream có thể thực hiện được cả 2 việc này. Chính vì vậy, bài viết này sẽ hướng dẫn các bạn một cách vắn tắt các thao tác cơ bản với fstream trong C++.1. Tạo đối tượng fstream để đọc/ghi file. Việc đọc/ghi file sẽ được thực hiện thông qua đối tượng này.
Khai báo: fstream <tênbiến>;
2. Dùng phương thức
open() để mở một file.Cú pháp:
<tênbiến>.open(<đường dẫn>,<chế độ mở>);
Hoặc có thể vừa khai báo biến vừa mở file luôn.
fstream <tênbiến>(<đường dẫn>,<chế độ mở>);
Đường dẫn có thể là tương đối (tính từ file exe khi được build ra) hoặc tuyệt đối (tính từ ổ đĩa gốc).
Chế độ mở có nhiều loại:
| Chế độ mở | Ý nghĩa |
|---|---|
| ios::in | Mở file ở chế độ đọc |
| ios::out | Mở file ở chế độ ghi |
| ios::app | Mở file chế độ ghi thêm vào |
| ios::ate | Mở file để đọc/ghi từ cuối file |
| ios::trunc | Tạo đè file mới hoàn toàn |
| ios::nocreate | Mở một file phải tồn tại |
| ios::noreplace | Tạo file mới chưa tồn tại |
| ios::binary | Mở file chế độ nhị phân |
| ios::text | Mở file chế độ văn bản |
Có thể kết hợp nhiều chế độ bằng toán tử
|.Ví dụ:
fstream file("data/text.txt", ios::in | ios::out);
8/7/15
Danh sách liên kết đơn và các thao tác cơ bản
Danh sách liên kết là một cấu trúc dữ liệu mà mỗi phần tử cần phải lưu thông tin của nó và địa chỉ của phần tử kế tiếp hoặc trước nó. Danh sách liên kết linh động hơn mảng rất nhiều do có thể thêm, xóa phần tử. Có nhiều dạng danh sách liên kết khác nhau và ở phần này mình sẽ nói về danh sách liên kết đơn cùng các thao tác với nó (minh họa bằng C++).
Tổ chức dữ liệu
Mỗi phần tử trong DSLK đơn (gọi là node hay nút) sẽ gồm một biến lưu dữ liệu của bạn và một biến con trỏ lưu địa chỉ của phần tử kế tiếp. Các phần tử được liên kết với nhau dựa vào con trỏ này.
Các thao tác cơ bản
Tạo danh sách rỗng
Tại sao đây lại là một thao tác quan trọng. Một danh sách được tạo ra chứa 2 con trỏ rỗng chưa trỏ đến đâu cả. Vì vậy sẽ rất nguy hiểm nếu thao tác với 2 con trỏ này. Cần thiết phải gán cho nó giá trị NULL trước khi thao tác trên nó. Nếu bạn cài đặt danh sách theo phương pháp hướng đối tượng thì có thể khai báo phần này trong constructor nên không cần gọi hàm này bên ngoài.
Tổ chức dữ liệu
Mỗi phần tử trong DSLK đơn (gọi là node hay nút) sẽ gồm một biến lưu dữ liệu của bạn và một biến con trỏ lưu địa chỉ của phần tử kế tiếp. Các phần tử được liên kết với nhau dựa vào con trỏ này.
struct Node
{
<kiểu dữ liệu> info;
Node *next;
};
Như vậy, khi biết được phần tử đầu danh sách thì dựa vào con trỏ next, ta có thể truy cập được tất cả các phần tử trong danh sách. Vậy một danh sách chỉ gồm 1 con trỏ trỏ đến phần tử đầu danh sách. Tuy nhiên, để một vài thao tác trở nên dễ dàng, ta có thể thêm một con trỏ đến cuối danh sách.struct List
{
Node *head, *tail;
};
Nếu chưa hiểu cấu trúc của nó, bạn có thể xem hình minh họa sau.Các thao tác cơ bản
Tạo danh sách rỗng
Tại sao đây lại là một thao tác quan trọng. Một danh sách được tạo ra chứa 2 con trỏ rỗng chưa trỏ đến đâu cả. Vì vậy sẽ rất nguy hiểm nếu thao tác với 2 con trỏ này. Cần thiết phải gán cho nó giá trị NULL trước khi thao tác trên nó. Nếu bạn cài đặt danh sách theo phương pháp hướng đối tượng thì có thể khai báo phần này trong constructor nên không cần gọi hàm này bên ngoài.
void CreateList(List &list)
{
list.head = list.tail = NULL;
}
9/5/15
Tại sao cần xóa bộ đệm bàn phím trước khi nhập chuỗi?
Thông thường, khi nhập một chuỗi trong màn hình console, ta phải có thao tác xóa bộ nhớ đệm bàn phím. Nếu không có thể thấy rằng kết quả nhập chuỗi bị sai hoặc trôi đi mất.
Trong quá trình chạy chương trình ta sẽ phải nhập bằng bàn phím, mọi ký tự bạn gõ vào bàn phím (kể cả ký tự Enter
Bạn có thể sử dụng các hàm sau để thực hiện xóa bộ nhớ đệm.
flushall()
Hàm này được định nghĩa trong
fflush(stdin)
Hàm
cin.ignore()
Đây là một phương thức của đối tượng
Vậy nên dùng hàm nào? Hàm nào cũng được,
Trong quá trình chạy chương trình ta sẽ phải nhập bằng bàn phím, mọi ký tự bạn gõ vào bàn phím (kể cả ký tự Enter
\n) đều được đẩy vào bộ nhớ đệm trước khi được gán vào biến. Nếu trước đó bạn có nhập số bằng scanf hoặc cin, chúng chỉ nhận số chứ không nhận được ký tự Enter, và ký tự Enter vẫn còn trong bộ nhớ đệm. Đến khi nhập chuỗi, các hàm nhập chuỗi nhận được ký tự Enter thì dừng nhập luôn và chương trình vẫn chạy tiếp. Điều này khiến kết quả bị sai.Bạn có thể sử dụng các hàm sau để thực hiện xóa bộ nhớ đệm.
flushall()
Hàm này được định nghĩa trong
stdio.h. Nó có tác dụng xóa bộ nhớ đệm tất cả các dòng nhập, xuất chuẩn và nhập xuất file.fflush(stdin)
Hàm
fflush() trong thư viện stdio.h cũng có tác dụng tương tự flushall(). Tuy nhiên nó cho phép lựa chọn xóa bộ nhớ đệm cho stream nào. Ở đây ta truyền vào stdin để xóa bộ đệm cho dòng nhập chuẩn, tức là bàn phím.cin.ignore()
Đây là một phương thức của đối tượng
cin trong C++. Phương thức này còn có những tham số khác nghĩa là bỏ qua hay loại bỏ một số lượng ký tự trong bộ đệm hoặc bỏ qua đến khi gặp ký tự nào đó. Nếu không có tham số thì mặc định là bỏ 1 ký tự trong bộ đệm. Dùng cách này hữu ích khi nhập dữ liệu chuyển từ số sang chữ.Vậy nên dùng hàm nào? Hàm nào cũng được,
flushall và fflush(stdin) có vẻ đơn giản hơn trong khi dùng cin.ignore() bạn phải cẩn thận, không lạm dụng để tránh sai ý muốn.
20/4/15
Những nguyên tắc cơ bản khi thiết kế class
Khi xây dựng một class chúng ta cũng cần có những nguyên tắc để class hoàn chỉnh hơn, đảm bảo các tính chất của lập trình hướng đối tượng.
Khi nào thì xây dựng class để biểu diễn đối tượng? Khi chúng ta nghĩ đến vấn đề như một khái niệm riêng lẻ thì lúc này nên xây dựng lớp để biểu diễn khái niệm đó. Lớp là biểu diễn của một khái niệm nào đó, nên tên lớp luôn là danh từ.
Các thuộc tính của lớp là các thành phần dữ liệu mô tả lớp nên chúng cũng là danh từ. Các hàm thành phần đặc trưng cho các hành vi của lớp, vì thế tên hàm là động từ.
Ví dụ:
Đa số các trường hợp nên đặt phạm vi của các biến thành viên là
Các thuộc tính có thể suy diễn từ những thuộc tính khác thì nên dùng hàm thành phần để thực hiện tính toán. Tuy nhiên, nếu các thuộc tính suy diễn dòi hỏi nhiều tài nguyên hoặc thời gian để thực hiện tính toán, ta có thể khai báo là dữ liệu thành phần.
Nếu các dữ liệu thành phần có thể được trừu tượng hóa thành lớp khác thì nên định nghĩa lớp đó để kết hợp chúng lại.
Ví dụ: Với lớp TamGiac nên dùng 3 biến thuộc lớp Diem để biểu diễn 3 điểm A, B, C thay vì dùng 6 biến biểu diễn xA, yA, xB, yB, xC, yC.
Trong mọi trường hợp, nên có phương thức thiết lập để khởi động đối tượng. Nên có phương thức thiết lập có khả năng tự khởi động mà không cần tham số. Nếu đối tượng có nhu cầu cấp phát tài nguyên thì phải có phương thức thiết lập, copy constructor để khởi động đối tượng bằng đối tượng cùng kiểu và có destructor để dọn dẹp. Ngoài ra còn phải định nghĩa lại phép gán (tương tự như copy constructor). Nếu đối tượng đơn giản không cần tài nguyên riêng thì không cần copy constructor và destructor.
Có thể xem thêm bài viết này: Khi nào cần định nghĩa constructor, copy constructor và destructor?
Khi nào thì xây dựng class để biểu diễn đối tượng? Khi chúng ta nghĩ đến vấn đề như một khái niệm riêng lẻ thì lúc này nên xây dựng lớp để biểu diễn khái niệm đó. Lớp là biểu diễn của một khái niệm nào đó, nên tên lớp luôn là danh từ.
Các thuộc tính của lớp là các thành phần dữ liệu mô tả lớp nên chúng cũng là danh từ. Các hàm thành phần đặc trưng cho các hành vi của lớp, vì thế tên hàm là động từ.
Ví dụ:
class Point
{
private:
float x, y;
public:
void SetX(int);
void SetY(int);
///...
};
Đa số các trường hợp nên đặt phạm vi của các biến thành viên là
private để đảm bảo tính đóng gói.Các thuộc tính có thể suy diễn từ những thuộc tính khác thì nên dùng hàm thành phần để thực hiện tính toán. Tuy nhiên, nếu các thuộc tính suy diễn dòi hỏi nhiều tài nguyên hoặc thời gian để thực hiện tính toán, ta có thể khai báo là dữ liệu thành phần.
Nếu các dữ liệu thành phần có thể được trừu tượng hóa thành lớp khác thì nên định nghĩa lớp đó để kết hợp chúng lại.
Ví dụ: Với lớp TamGiac nên dùng 3 biến thuộc lớp Diem để biểu diễn 3 điểm A, B, C thay vì dùng 6 biến biểu diễn xA, yA, xB, yB, xC, yC.
Trong mọi trường hợp, nên có phương thức thiết lập để khởi động đối tượng. Nên có phương thức thiết lập có khả năng tự khởi động mà không cần tham số. Nếu đối tượng có nhu cầu cấp phát tài nguyên thì phải có phương thức thiết lập, copy constructor để khởi động đối tượng bằng đối tượng cùng kiểu và có destructor để dọn dẹp. Ngoài ra còn phải định nghĩa lại phép gán (tương tự như copy constructor). Nếu đối tượng đơn giản không cần tài nguyên riêng thì không cần copy constructor và destructor.
Có thể xem thêm bài viết này: Khi nào cần định nghĩa constructor, copy constructor và destructor?
15/4/15
Khi nào cần định nghĩa constructor, copy constructor và destructor?
Hàm dựng (constructor) và hàm hủy (destructor) là 2 yếu tố quan trọng luôn có trong một lớp (class) trong lập trình hướng đối tượng. Nếu người dùng không định nghĩa thì trình biên dịch sẽ tự động thêm vào hàm dựng và hàm hủy mặc định. Tuy nhiên, đôi khi chúng ta cần phải định nghĩa lại hàm dựng và hàm hủy để đảm bảo an toàn và hợp logic hơn.
Hàm dựng (constructor)
Hàm dựng là hàm được tự động gọi khi đối tượng được tạo lập. Có 3 loại: mặc định (không tham số), có tham số và hàm dựng sao chép (copy constructor).
Trong hầu hết các trường hợp, bạn nên định nghĩa hàm dựng để khởi tạo giá trị ban đầu cho các biến thành viên.
Nếu khai báo đối tượng mà không cung cấp đối số thì hàm dựng mặc định sẽ được gọi, nếu có đối số thì hàm dựng có tham số sẽ được gọi. Vì vậy, hàm dựng có tham số thường được dùng để khởi động đối tượng với giá trị tùy người dùng thay vì giá trị được định nghĩa trong hàm dựng mặc định. Ngoài ra, còn được dùng để chuyển kiểu ngầm định khi gọi với toán tử.
Hàm dựng sao chép dùng để tạo nên đối tượng mới giống hệt đối tượng cũ và thường được dùng trong phép gán. Tất nhiên trình biên dịch sẽ tự thêm copy constructor mặc định khi không khai báo. Nguyên tắc của copy constructor là copy giá trị theo từng cặp biến thành viên.
Vấn đề chỉ xuất hiện khi lớp có thành viên là con trỏ và có cấp phát tài nguyên động như mảng. Trong trường hợp này, nếu không định nghĩa lại copy constructor thì mặc định chỉ sao chép giá trị của các biến thành viên mà không sao chép mảng động. Cụ thể hơn, với biến thành viên là con trỏ, giá trị (tức địa chỉ của mảng động) sẽ được bê nguyên xi qua con trỏ của đối tượng kia và hiển nhiên 2 con trỏ đang trỏ đến cùng một vùng nhớ. Khi hủy 2 đối tượng thì vùng nhớ sẽ bị thu hồi 2 lần và điều này gây ra lỗi. Hơn nữa, việc 2 đối tượng khác nhau cùng dùng chung vùng nhớ cũng sẽ không hợp logic. Vậy, bạn nên định nghĩa lại copy constructor khi lớp có thành viên là con trỏ và có cấp phát tài nguyên động.
Nguyên tắc định nghĩa lại:
1. Gán các biến thành viên tĩnh cho nhau.
2. Cấp phát lại vùng nhớ động cho con trỏ.
3. Sao chép lại mảng động.
Hàm dựng (constructor)
Hàm dựng là hàm được tự động gọi khi đối tượng được tạo lập. Có 3 loại: mặc định (không tham số), có tham số và hàm dựng sao chép (copy constructor).
Trong hầu hết các trường hợp, bạn nên định nghĩa hàm dựng để khởi tạo giá trị ban đầu cho các biến thành viên.
Nếu khai báo đối tượng mà không cung cấp đối số thì hàm dựng mặc định sẽ được gọi, nếu có đối số thì hàm dựng có tham số sẽ được gọi. Vì vậy, hàm dựng có tham số thường được dùng để khởi động đối tượng với giá trị tùy người dùng thay vì giá trị được định nghĩa trong hàm dựng mặc định. Ngoài ra, còn được dùng để chuyển kiểu ngầm định khi gọi với toán tử.
Hàm dựng sao chép dùng để tạo nên đối tượng mới giống hệt đối tượng cũ và thường được dùng trong phép gán. Tất nhiên trình biên dịch sẽ tự thêm copy constructor mặc định khi không khai báo. Nguyên tắc của copy constructor là copy giá trị theo từng cặp biến thành viên.
Vấn đề chỉ xuất hiện khi lớp có thành viên là con trỏ và có cấp phát tài nguyên động như mảng. Trong trường hợp này, nếu không định nghĩa lại copy constructor thì mặc định chỉ sao chép giá trị của các biến thành viên mà không sao chép mảng động. Cụ thể hơn, với biến thành viên là con trỏ, giá trị (tức địa chỉ của mảng động) sẽ được bê nguyên xi qua con trỏ của đối tượng kia và hiển nhiên 2 con trỏ đang trỏ đến cùng một vùng nhớ. Khi hủy 2 đối tượng thì vùng nhớ sẽ bị thu hồi 2 lần và điều này gây ra lỗi. Hơn nữa, việc 2 đối tượng khác nhau cùng dùng chung vùng nhớ cũng sẽ không hợp logic. Vậy, bạn nên định nghĩa lại copy constructor khi lớp có thành viên là con trỏ và có cấp phát tài nguyên động.
Nguyên tắc định nghĩa lại:
1. Gán các biến thành viên tĩnh cho nhau.
2. Cấp phát lại vùng nhớ động cho con trỏ.
3. Sao chép lại mảng động.
20/3/15
Biến tham chiếu khác biến con trỏ như thế nào?
Cả tham chiếu (reference) và con trỏ (pointer) đều thuộc kiểu địa chỉ trong C++ và thường được dùng để truy cập gián tiếp đến các đối tượng khác. Tuy nhiên chúng cũng có sự khác nhau cơ bản các bạn có thể tham khảo dưới đây:
1. Khai báo biến con trỏ:
Khai báo biến tham chiếu:
2. Dấu
3. Con trỏ có thể khởi tạo mà không cần gán địa chỉ, ta có một con trỏ không xác định. Tham chiếu bắt buộc phải gán bằng một đối tượng khác, có thể là một biến. Vì vậy dùng con trỏ có thể dễ gây nhầm lẫn và nguy hiểm hơn tham chiếu.
5. Kích thước một biến con trỏ là cố định. Kích thước biến tham chiếu phụ thuộc kiểu dữ liệu của đối tượng.
6. Con trỏ có thể được chỉ định để truy cập đến các đối tượng khác nhau bằng cách thay đổi giá trị địa chỉ mà con trỏ lưu giữ. Tuy nhiên, tham chiếu chỉ truy cập được duy nhất một đối tượng đã được khởi tạo cho nó. Vì vậy con trỏ linh hoạt hơn tham chiếu.
7. Có thể khai báo mảng các con trỏ nhưng không được khai báo mảng các tham chiếu.
8. Tham chiếu thường được dùng làm các tham số truyền vào hàm khi muốn giá trị của tham số thay đổi sau khi ra khỏi hàm.
Tham chiếu cũng được dùng để tiết kiệm bộ nhớ khi truyền đối tượng vào hàm. Hệ thống sẽ tạo biến tham chiếu thay vì tạo bản copy đối tượng. Tuy nhiên, đối tượng có thể bị thay đổi. Có thể dùng hằng tham chiếu để ngăn điều này.
9. Con trỏ dùng để cấp phát động nhưng không thể thực hiện việc này với tham chiếu.
10. Biến tham chiếu không thể tham chiếu đến con trỏ nhưng con trỏ có thể trỏ đến biến tham chiếu.
1. Khai báo biến con trỏ:
<kiểu dữ liệu> *<tên biến>;Khai báo biến tham chiếu:
<kiểu dữ liệu> &<tên biến> = <tên đối tượng>;2. Dấu
& trong khai báo tham chiếu khác với toán tử lấy địa chỉ &. Cũng như dấu * khai báo con trỏ khác với toán tử lấy giá trị *. Chúng chỉ báo hiệu đây là biến tham chiếu hay là biến con trỏ thôi.3. Con trỏ có thể khởi tạo mà không cần gán địa chỉ, ta có một con trỏ không xác định. Tham chiếu bắt buộc phải gán bằng một đối tượng khác, có thể là một biến. Vì vậy dùng con trỏ có thể dễ gây nhầm lẫn và nguy hiểm hơn tham chiếu.
int *p; //hợp lệ int a = 5; //hợp lệ int &b; //không hợp lệ int &c = a; //OK, lúc này c và a là một.4. Con trỏ là một biến riêng biệt lưu giữ địa chỉ của đối tượng khác nên truy cập gián tiếp thông qua địa chỉ. Tham chiếu được xem như một cái tên khác của đối tượng vì nó dùng chung địa chỉ với đối tượng được tham chiếu.
5. Kích thước một biến con trỏ là cố định. Kích thước biến tham chiếu phụ thuộc kiểu dữ liệu của đối tượng.
sizeof(double) // 8 bytes sizeof(double*) // 4 bytes sizeof(double&) // 8 bytes
6. Con trỏ có thể được chỉ định để truy cập đến các đối tượng khác nhau bằng cách thay đổi giá trị địa chỉ mà con trỏ lưu giữ. Tuy nhiên, tham chiếu chỉ truy cập được duy nhất một đối tượng đã được khởi tạo cho nó. Vì vậy con trỏ linh hoạt hơn tham chiếu.
7. Có thể khai báo mảng các con trỏ nhưng không được khai báo mảng các tham chiếu.
8. Tham chiếu thường được dùng làm các tham số truyền vào hàm khi muốn giá trị của tham số thay đổi sau khi ra khỏi hàm.
Tham chiếu cũng được dùng để tiết kiệm bộ nhớ khi truyền đối tượng vào hàm. Hệ thống sẽ tạo biến tham chiếu thay vì tạo bản copy đối tượng. Tuy nhiên, đối tượng có thể bị thay đổi. Có thể dùng hằng tham chiếu để ngăn điều này.
int sum(int a); // a sẽ không thay đổi khi ra khỏi hàm int swap(int &a, int &b); // thay đổi trong hàm vẫn lưu lại với a, b int addition(const int& a); //a sẽ không thay đổiTất nhiên cũng có thể dùng con trỏ cho những trường hợp này.
9. Con trỏ dùng để cấp phát động nhưng không thể thực hiện việc này với tham chiếu.
10. Biến tham chiếu không thể tham chiếu đến con trỏ nhưng con trỏ có thể trỏ đến biến tham chiếu.
6/3/15
Tổng quan về kiểu string trong C++
1. string là kiểu dữ liệu mới được định nghĩa sẵn trong C++, nó có nhiều ưu điểm vượt trội và giúp tránh được những phiền phức so với chuỗi kiểu char* của C.
2. Trong C++, bạn vẫn có thể dùng kiểu char* nếu muốn. Có thể chuyển từ kiểu string sang chuỗi char* bằng phương thức
3. Cần khai báo
4. string có các phép +, += để nối chuỗi thay vì dùng hàm trong thư viện
5. Các hàm trong thư viện
6. Có thể so sánh trực tiếp 2 chuỗi string: ==, !=, >, >=, <, <=. Nguyên tắc so sánh giống hệt như khi dùng hàm
7. Dùng phương thức
Ví dụ:
2. Trong C++, bạn vẫn có thể dùng kiểu char* nếu muốn. Có thể chuyển từ kiểu string sang chuỗi char* bằng phương thức
c_str()3. Cần khai báo
#include<string> để có thể sử dụng đầy đủ tiện ích của string.4. string có các phép +, += để nối chuỗi thay vì dùng hàm trong thư viện
string.h như kiểu char*5. Các hàm trong thư viện
string.h (strlen, strcmp, strlwr,... ) sẽ không sử dụng được với string.6. Có thể so sánh trực tiếp 2 chuỗi string: ==, !=, >, >=, <, <=. Nguyên tắc so sánh giống hệt như khi dùng hàm
strcmp().7. Dùng phương thức
length() để lấy độ dài chuỗi, dùng phép lấy chỉ số [] để lấy từng phần tử của chuỗi.Ví dụ:
string a = "ABCDE"; cout<< a.length(); cout<< a[2];
5/3/15
Những kiến thức căn bản về con trỏ
1. Con trỏ khác với biến bình thường ở chỗ nó lưu giữ địa chỉ của một biến khác thay vì lưu trữ giá trị (hay còn gọi là trỏ đến biến khác), để dễ hình dung bạn có thể coi con trỏ là một mặt nạ tượng trưng cho biến mà nó trỏ đến.
2. Vì nó chỉ lưu giữ địa chỉ thay vì nội dung nên kích thước mọi biến con trỏ trong Windows là 4 bytes, trong Linux là 2 bytes.
3. Cách khai báo:
Ví dụ:
4. Con trỏ cũng có 1 địa chỉ riêng. Toán tử
Với khai báo:
Con trỏ trỏ đến con trỏ khác:
6. Mã đặc tả của con trỏ và địa chỉ là
Ví dụ:
2. Vì nó chỉ lưu giữ địa chỉ thay vì nội dung nên kích thước mọi biến con trỏ trong Windows là 4 bytes, trong Linux là 2 bytes.
3. Cách khai báo:
<kiểu dữ liệu> *<tên biến>Ví dụ:
int *p; //p là con trỏ int* p; //p là con trỏ int* p, q; //p là con trỏ, q không phải là con trỏ int *p, *q; //cả p và q là con trỏ
4. Con trỏ cũng có 1 địa chỉ riêng. Toán tử
* lấy nội dung tại vùng nhớ mà con trỏ trỏ đến. Toán tử & lấy địa chỉ của một biến (kể cả con trỏ).Với khai báo:
int *p; thìplà con trỏ*plà giá trị tại vùng nhớ mà p trỏ đến&plà địa chỉ của con trỏ p
<tên con trỏ> = &<tên biến>Con trỏ trỏ đến con trỏ khác:
<con trỏ 1> = <con trỏ 2>6. Mã đặc tả của con trỏ và địa chỉ là
%p, dùng để in địa chỉ lên màn hình.Ví dụ:
int x = 5;
int *p = &x;
printf("%p", &x); //xuất địa chỉ của x
printf("%p", p); //xuất giá trị của con trỏ p, tức là địa chỉ của x
printf("%p", &p); //xuất địa chỉ của con trỏ p
11/2/15
Bảng mã ASCII là gì?
ASCII (American Standard Code for Information Interchange - Chuẩn mã trao đổi thông tin Hoa Kì) là bộ kí tự và bộ mã kí tự dựa trên bảng chữ cái La Tinh được dùng trong tiếng Anh hiện đại và các ngôn ngữ Tây Âu khác. Nó thường được dùng để hiển thị văn bản trong máy tính và các thiết bị thông tin khác. Nó cũng được dùng bởi các thiết bị điều khiển làm việc với văn bản.
Bảng mã ASCII chuẩn có 128 kí tự, gồm các kí tự điều khiển (0 - 31 và 127), các ký tự in được như bảng chữ cái, các dấu (32 - 126). Bảng mã ASCII mở rộng có 255 kí tự gồm 128 ký tự của bảng mã ASCII chuẩn và các chữ có dấu, ký tự trang trí, v.v...
Ở đây giới thiệu bảng mã ASCII mở rộng. Các bảng ASCII mở rộng có thể khác nhau ở các chuẩn khác nhau. Bảng dưới đây tuân theo chuẩn ISO 8859-1, còn gọi là ISO Latin-1, các mã từ 129 - 159 các ký tự mở rộng Microsoft Windows Latin-1 (nguồn: www.ascii-code.com)
Bảng mã ASCII chuẩn có 128 kí tự, gồm các kí tự điều khiển (0 - 31 và 127), các ký tự in được như bảng chữ cái, các dấu (32 - 126). Bảng mã ASCII mở rộng có 255 kí tự gồm 128 ký tự của bảng mã ASCII chuẩn và các chữ có dấu, ký tự trang trí, v.v...
Ở đây giới thiệu bảng mã ASCII mở rộng. Các bảng ASCII mở rộng có thể khác nhau ở các chuẩn khác nhau. Bảng dưới đây tuân theo chuẩn ISO 8859-1, còn gọi là ISO Latin-1, các mã từ 129 - 159 các ký tự mở rộng Microsoft Windows Latin-1 (nguồn: www.ascii-code.com)
12/1/15
Các vấn đề cơ bản về số nguyên tố trong lập trình
Số nguyên tố là số chỉ có 2 ước, đó là 1 và chính nó, tức là nó chỉ chia hết cho số 1 và chính nó. Số 1 và 0 không được coi là số nguyên tố. Các bài toán cơ bản về số nguyên tố gồm kiểm tra một số nguyên n có phải là số nguyên tố và tìm các số nguyên tố nhỏ hơn hoặc bằng một số nguyên cho trước.
Kiểm tra một số nguyên có là số nguyên tố
Ý tưởng: Kiểm tra xem số n có chia hết cho từng số nhỏ hơn nó hay không. Nếu có thì không là số nguyên tố, nếu tất cả đều không có thì là số nguyên tố.
Cài đặt bằng C: Nếu số n là 1 hoặc 0 thì không là số nguyên tố. Dùng một vòng for chạy từ 2 đến n-1 để kiểm tra xem n có chia hết cho bất kỳ số nào trong đó không, nếu có thỉ không là số nguyên tố, nếu tất cả đều không thì là số nguyên tố. Trong một hàm nếu gặp lệnh
Tuy nhiên có thể nhận thấy việc kiểm tra đến n-1 là không cần thiết. Vì nếu n có các ước thì các ước của nó chắc chắn không vượt qua căn bậc 2 của n. Như vậy điều kiện trong vòng for sẽ được đổi thành
Kiểm tra một số nguyên có là số nguyên tố
Ý tưởng: Kiểm tra xem số n có chia hết cho từng số nhỏ hơn nó hay không. Nếu có thì không là số nguyên tố, nếu tất cả đều không có thì là số nguyên tố.
Cài đặt bằng C: Nếu số n là 1 hoặc 0 thì không là số nguyên tố. Dùng một vòng for chạy từ 2 đến n-1 để kiểm tra xem n có chia hết cho bất kỳ số nào trong đó không, nếu có thỉ không là số nguyên tố, nếu tất cả đều không thì là số nguyên tố. Trong một hàm nếu gặp lệnh
return, hàm sẽ trả về giá trị và kết thúc hàm nên có thể viết gọn như sau:int IsPrime(int n)
{
if (n < 2) return 0;
for(int i = 2; i < n; i++)
if(n%i==0) return 0;
return 1;
}
Tuy nhiên có thể nhận thấy việc kiểm tra đến n-1 là không cần thiết. Vì nếu n có các ước thì các ước của nó chắc chắn không vượt qua căn bậc 2 của n. Như vậy điều kiện trong vòng for sẽ được đổi thành
i <= sqrt(n), nhưng để gọi hàm sqrt() cần phải khai báo thư viện math.h, ép kiểu n sang kiểu thực,... khá rườm rà nên ta có thể đổi thành i*i <= n để tiện hơn.
Đăng ký:
Bài đăng (Atom)



