Bạn đã biết gì về danh sách liên kết đơn (Linked List) trong C++? Nó có đặc điểm gì? Cài đặt linked list C ++ cũng như những bài tập liên quan như thế nào. Hãy cùng Techacademy tìm hiểu trong bài viết này nhé!
I. Linked List C++ Là Gì
Một Danh sách liên kết (Linked List) là 1 dãy các cấu trúc dữ liệu được kết nối với nhau thông qua các liên kết (link). Hiểu một cách đơn thuần thì Danh sách liên kết là một cấu trúc dữ liệu bao gồm 1 nhóm những nút (node) tạo thành 1 chuỗi. Mỗi nút gồm dữ liệu ở nút ấy và tham chiếu đến nút kế tiếp trong chuỗi.
Danh sách liên kết là cấu trúc dữ liệu được sử dụng rộng rãi thứ hai sau mảng. Dưới đây là những định nghĩa cơ bản liên quan tới Danh sách liên kết:
Link (liên kết): mỗi link của một Danh sách liên kết có thể lưu giữ một dữ liệu được gọi là một phần tử.
Next: Mỗi liên kết của một Danh sách liên kết chứa một link tới next link được gọi là Next.
First: một Danh sách liên kết bao gồm các link kết nối tới first link được gọi là First.
Linked List C++ Là Gì
II. Đặc Điểm Của Danh Sách Liên Kết Đơn
Do danh sách liên kết đơn là một cấu trúc dữ liệu động, được tạo nên nhờ việc cấp phát động nên nó mang một số đặc điểm sau đây:
Được cấp phát bộ nhớ khi chạy chương trình
Có thể đổi thay kích thước qua việc thêm, xóa phần tử
Kích thước tối đa phụ thuộc vào bộ nhớ khả dụng của RAM
Các phần tử được lưu trữ tự nhiên (không liên tiếp) trong RAM
Và do tính liên kết của phần tử đầu và phần tử đứng sau nó trong danh sách liên kết đơn, nó có những đặc điểm sau:
Chỉ cần nắm được phần tử đầu và cuối là có thể quản lý được danh sách
Truy cập tới phần tử ngẫu nhiên phải duyệt từ đầu tới vị trí đó
Chỉ có thể tìm kiếm tuyến tính một phần tử
Đặc Điểm Của Danh Sách Liên Kết Đơn
III. Cài Đặt Linked List C++
+ Khai báo linked list
Để đơn giản hóa, data của chúng ta sẽ là số nguyên(int). Bạn cũng có thể sử dụng các kiểu nguyên thủy khác(float, char,…) hay kiểu dữ liệu struct(SinhVien, CanBo,…) tự tạo.
struct LinkedList{
int data;
struct LinkedList *next;
};
Khai báo trên sẽ được sử dụng cho đa số Node trong linked list. Trường data sẽ lưu giữa giá trị và next sẽ là con trỏ để trỏ tới thằng kế tiếp của nó.
Tại sao next lại là kiểu LinkedList của chính nó? Bởi vì nó là con trỏ trỏ của chính bản thân nó, và nó trỏ tới một thằng Node kế tiếp cũng có kiểu LinkedList.
+ Tạo mới 1 Node
Hãy tạo 1 kiểu dữ liệu của struct LinkedList để code clear hơn:
typedef struct LinkedList *node; //Từ giờ dùng kiểu dữ liệu LinkedList có thể thay bằng node cho ngắn gọn
node CreateNode(int value){
node temp; // declare a node
temp = (node)malloc(sizeof(struct LinkedList)); // Cấp phát vùng nhớ dùng malloc()
temp->next = NULL;// Cho next trỏ tới NULL
temp->data = value; // Gán giá trị cho Node
return temp;//Trả về node mới đã có giá trị
}
Mỗi một Node lúc được khởi tạo, chúng ta cần cấp phát bộ nhớ cho nó, và mặc định cho con trỏ next trỏ tới NULL. Giá trị của Node sẽ được cung cấp khi thêm Node vào linked list.
typedef được dùng để định nghĩa một kiểu dữ liệu trong C. VD: typeder long long LL;
malloc là hàm cấp phát bộ nhớ của C. Với C++ chúng ta dùng new
sizeof là hàm trả về kích thước của kiểu dữ liệu, dùng làm tham số cho hàm malloc
Lưu ý: Không giống với mảng, cần khai báo arr[size]. Trong linked list, vì mỗi Node sẽ có con trỏ liên kết đến Node tiếp theo. Do đó, với danh sách liên kết đơn, bạn chỉ cần lưu giữ Node đầu tiên(HEAD). Có head rồi bạn có thể đi tới bất cứ Node nào.
+ Thêm Node vào danh sách liên kết
Thêm vào đầu
Việc thêm vào đầu chính là việc cập nhật lại thằng head. Ta gọi Node mới(temp), ta có:
– Nếu head đang trỏ tới NULL, nghĩa là linked list đang trống, Node mới thêm vào sẽ làm head luôn
– Ngược lại, ta bắt buộc thay thế thằng head cũ bằng head mới. Việc này phải làm theo thứ tự như sau:
Cho next của temp trỏ tới head hiện hành
Đặt temp làm head mới
node AddHead(node head, int value){
node temp = CreateNode(value); // Khởi tạo node temp với data = value
if(head == NULL){
head = temp; // //Nếu linked list đang trống thì Node temp là head luôn
}else{
temp->next = head; // Trỏ next của temp = head hiện tại
head = temp; // Đổi head hiện tại = temp(Vì temp bây giờ là head mới mà)
}
return head;
}
Thêm vào cuối
Chúng ta sẽ cần Node đầu tiên, và giá trị muốn thêm. Khi đó, ta sẽ:
Tạo một Node mới với giá trị value
Nếu head = NULL, tức là danh sách liên kết đang trống. Khi đó Node mới(temp) sẽ là head luôn.
Ngược lại, ta sẽ duyệt tới Node cuối cùng(Node có next = NULL), và trỏ next của thằng cuối tới Node mới(temp).
node AddTail(node head, int value){
node temp,p;// Khai báo 2 node tạm temp và p
temp = CreateNode(value);//Gọi hàm createNode để khởi tạo node temp có next trỏ tới NULL và giá trị là value
if(head == NULL){
head = temp; //Nếu linked list đang trống thì Node temp là head luôn
}
else{
p = head;// Khởi tạo p trỏ tới head
while(p->next != NULL){
p = p->next;//Duyệt danh sách liên kết đến cuối. Node cuối là node có next = NULL
}
p->next = temp;//Gán next của thằng cuối = temp. Khi đó temp sẽ là thằng cuối(temp->next = NULL mà)
}
return head;
}
Tổng quan hơn, chúng ta sẽ sẽ viết hàm thêm một Node vào vị trí bất kỳ nhé.
Thêm vào vị trí bất kỳ
Để làm được việc này, ta cần duyệt từ đầu để tìm tới vị trí của Node cần chèn, giả sử là Node Q, lúc đó ta cần làm theo thứ tự sau:
Cho next của Node mới trỏ tới Node mà Q đang trỏ tới
Cho Node Q trỏ tới Node mới
Lưu ý: Chỉ số chèn bắt đầu từ chỉ số 0 nhé các bạn
node AddAt(node head, int value, int position){
if(position == 0 || head == NULL){
head = AddHead(head, value); // Nếu vị trí chèn là 0, tức là thêm vào đầu
}else{
// Bắt đầu tìm vị trí cần chèn. Ta sẽ dùng k để đếm cho vị trí
int k = 1;
node p = head;
while(p != NULL && k != position){
p = p->next;
++k;
}
if(k != position){
// Nếu duyệt hết danh sách lk rồi mà vẫn chưa đến vị trí cần chèn, ta sẽ mặc định chèn cuối
// Nếu bạn không muốn chèn, hãy thông báo vị trí chèn không hợp lệ
head = AddTail(head, value);
// printf("Vi tri chen vuot qua vi tri cuoi cung!\n");
}else{
node temp = CreateNode(value);
temp->next = p->next;
p->next = temp;
}
}
return head;
}
Lưu ý: Bạn phải làm theo thứ tự trên, nếu bạn cho p->next = temp trước. Khi đó, bạn sẽ không thể lấy lại phần sau của danh sách liên kết nữa(Vì next chỉ được được lưu trong p->next mà thay đổi p->next rồi thì còn đâu giá trị cũ).
+ Xóa Node khỏi danh sách liên kết
Xóa đầu
Xóa đầu đơn giản lắm, bây giờ chỉ cần cho thằng kế tiếp của head làm head là được thôi. Mà thằng kế tiếp của head chính là head->next.
node DelHead(node head){
if(head == NULL){
printf("\nCha co gi de xoa het!");
}else{
head = head->next;
}
return head;
}
Xóa cuối
Xóa cuối mới nhọc nè, nhọc ở chỗ phải duyệt đến thằng cuối – 1, cho next của cuối – 1 đó bằng NULL.
node DelTail(node head){
if (head == NULL || head->next == NULL){
return DelHead(head);
}
node p = head;
while(p->next->next != NULL){
p = p->next;
}
p->next = p->next->next; // Cho next bằng NULL
// Hoặc viết p->next = NULL cũng được
return head;
}
Thằng Node cuối – 1 là thằng có p->next->next = NULL. Bạn cho next của nó bằng NULL là xong.
Xóa ở vị trí bất kỳ
Việc xóa ở vị trí bất kỳ cũng khá giống xóa ở cuối kia. Đơn giản là chúng ta bỏ qua một phần tử, như ảnh sau:
Lưu ý: Chỉ số xóa bắt đầu từ 0 nhé các bạn. Việc tìm vị trí càn xóa chỉ duyệt tới Node gần cuối thôi(cuối – 1). Sau đây là code xóa Node ở vị trí bất kỳ
node DelAt(node head, int position){
if(position == 0 || head == NULL || head->next == NULL){
head = DelHead(head); // Nếu vị trí chèn là 0, tức là thêm vào đầu
}else{
// Bắt đầu tìm vị trí cần chèn. Ta sẽ dùng k để đếm cho vị trí
int k = 1;
node p = head;
while(p->next->next != NULL && k != position){
p = p->next;
++k;
}
if(k != position){
// Nếu duyệt hết danh sách lk rồi mà vẫn chưa đến vị trí cần chèn, ta sẽ mặc định xóa cuối
// Nếu bạn không muốn xóa, hãy thông báo vị trí xóa không hợp lệ
head = DelTail(head);
// printf("Vi tri xoa vuot qua vi tri cuoi cung!\n");
}else{
p->next = p->next->next;
}
}
return head;
}
+ Lấy giá trị ở vị trí bất kỳ
Chúng ta sẽ viết một hàm để truy xuất giá trị ở chỉ số bất kỳ nhé. Trong trường hợp chỉ số vượt quá chiều dài của linked list – 1, hàm này trả về vị trí cuối cùng. Do hạn chế là chúng ta không thể raise error khi chỉ số không hợp lệ. Tôi mặc định chỉ số bạn truyền vào phải là số nguyên không âm. Nếu bạn muốn kiểm tra chỉ số hợp lệ thì nên kiểm tra trước khi gọi hàm này.
int Get(node head, int index){
int k = 0;
node p = head;
while(p->next != NULL && k != index){
++k;
p = p->next;
}
return p->data;
}
Lý do dùng p->next != NULL là vì chúng ta chỉ muốn đi qua các phần tử có value.
+ Tìm kiếm trong danh sách liên kết
Hàm tìm kiếm này sẽ trả về chỉ số của Node đầu tiên có giá trị bằng với giá trị cần tìm. Nếu không tìm thấy, chúng ta trả về -1.
int Search(node head, int value){
int position = 0;
for(node p = head; p != NULL; p = p->next){
if(p->data == value){
return position;
}
++position;
}
return -1;
}
Chúng ta có thể sử dụng hàm này để xóa tất cả các Node trong danh sách liên kết có giá trị chỉ định như sau:
node DelByVal(node head, int value){
int position = Search(head, value);
while(position != -1){
DelAt(head, position);
position = Search(head, value);
}
return head;
}
+ Duyệt danh sách liên kết
Việc duyệt danh sách liên kết cực đơn giản. Khởi tạo từ Node head, bạn cứ thế đi theo con trỏ next cho tới trước khi Node đó NULL.
void Traverser(node head){
printf("\n");
for(node p = head; p != NULL; p = p->next){
printf("%5d", p->data);
}
}
+ Một số hàm bổ trợ khác
Hàm khởi tạo Node head
Đơn giản là cho con trỏ head = NULL thôi. Nếu bạn để ý, chúng ta vẫn check head = NULL để biết rằng danh sách liên kết chưa có phần tử nào ở các hàm phía trên.
node InitHead(){
node head;
head = NULL;
return head;
}
Hàm lấy số phần tử của DSLK
Duyệt và đếm chừng nào các Node chưa NULL. Sau cùng, trả về giá trị đếm được.
int Length(node head){
int length = 0;
for(node p = head; p != NULL; p = p->next){
++length;
}
return length;
}
Hàm nhập danh sách liên kết
node Input(){
node head = InitHead();
int n, value;
do{
printf("\nNhap so luong phan tu n = ");
scanf("%d", &n);
}while(n <= 0);
for(int i = 0; i < n; ++i){
printf("\nNhap gia tri can them: ");
scanf("%d", &value);
head = AddTail(head, value);
}
return head;
}
Cài Đặt Linked List C++
IV. Thư Viện Linkedlist Trong C
Một Danh sách liên kết (Linked List) là 1 dãy các cấu trúc dữ liệu được kết nối với nhau thông qua các liên kết (link). Hiểu một cách đơn thuần thì Danh sách liên kết là một cấu trúc dữ liệu bao gồm 1 nhóm những nút (node) tạo thành một chuỗi. Mỗi nút gồm dữ liệu ở nút ấy và tham chiếu tới nút kế tiếp trong chuỗi.
Chương trình minh họa Danh sách liên kết (Linked List) trong C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
struct node
{
int data;
int key;
struct node *next;
};
struct node *head = NULL;
struct node *current = NULL;
//hien thi danh sach
void printList()
{
struct node *ptr = head;
printf("\n[ ");
//bat dau tu phan dau danh sach
while(ptr != NULL)
{
printf("(%d,%d) ",ptr->key,ptr->data);
ptr = ptr->next;
}
printf(" ]");
}
//chen link tai vi tri dau tien
void insertFirst(int key, int data)
{
//tao mot link
struct node *link = (struct node*) malloc(sizeof(struct node));
link->key = key;
link->data = data;
//tro link nay toi first node cu
link->next = head;
//tro first toi first node moi
head = link;
}
//xoa phan tu dau tien
struct node* deleteFirst()
{
//luu tham chieu toi first link
struct node *tempLink = head;
//danh dau next toi first link la first
head = head->next;
//tra ve link bi xoa
return tempLink;
}
//kiem tra list co trong hay khong
bool isEmpty()
{
return head == NULL;
}
int length()
{
int length = 0;
struct node *current;
for(current = head; current != NULL; current = current->next)
{
length++;
}
return length;
}
//tim mot link voi key da cho
struct node* find(int key){
//bat dau tim tu first link
struct node* current = head;
//neu list la trong
if(head == NULL)
{
return NULL;
}
//duyet qua list
while(current->key != key){
//neu day la last node
if(current->next == NULL){
return NULL;
}else {
//di chuyen toi next link
current = current->next;
}
}
//neu tim thay du lieu, tra ve link hien tai
return current;
}
//xoa mot link voi key da cho
struct node* deleteKey(int key){
//bat dau tu first link
struct node* current = head;
struct node* previous = NULL;
//neu list la trong
if(head == NULL){
return NULL;
}
//duyet qua list
while(current->key != key){
//neu day la last node
if(current->next == NULL){
return NULL;
}else {
//luu tham chieu toi link hien tai
previous = current;
//di chuyen toi next link
current = current->next;
}
}
//cap nhat link
if(current == head) {
//thay doi first de tro toi next link
head = head->next;
}else {
//bo qua link hien tai
previous->next = current->next;
}
return current;
}
// ham sap xep
void sort(){
int i, j, k, tempKey, tempData ;
struct node *current;
struct node *next;
int size = length();
k = size ;
for ( i = 0 ; i < size - 1 ; i++, k-- ) {
current = head ;
next = head->next ;
for ( j = 1 ; j < k ; j++ ) {
if ( current->data > next->data ) {
tempData = current->data ;
current->data = next->data;
next->data = tempData ;
tempKey = current->key;
current->key = next->key;
next->key = tempKey;
}
current = current->next;
next = next->next;
}
}
}
// ham dao nguoc list
void reverse(struct node** head_ref) {
struct node* prev = NULL;
struct node* current = *head_ref;
struct node* next;
while (current != NULL) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
*head_ref = prev;
}
main() {
insertFirst(1,10);
insertFirst(2,20);
insertFirst(3,30);
insertFirst(4,1);
insertFirst(5,40);
insertFirst(6,56);
printf("Danh sach ban dau: ");
//in danh sach
printList();
while(!isEmpty()){
struct node *temp = deleteFirst();
printf("\nGia tri bi xoa:");
printf("(%d,%d) ",temp->key,temp->data);
}
printf("\nDanh sach sau khi da xoa gia tri: ");
printList();
insertFirst(1,10);
insertFirst(2,20);
insertFirst(3,30);
insertFirst(4,1);
insertFirst(5,40);
insertFirst(6,56);
printf("\nPhuc hoi danh sach: ");
printList();
printf("\n");
struct node *foundLink = find(4);
if(foundLink != NULL){
printf("Tim thay phan tu: ");
printf("(%d,%d) ",foundLink->key,foundLink->data);
printf("\n");
}else {
printf("Khong tim thay phan tu.");
}
deleteKey(4);
printf("Danh sach, sau khi xoa mot phan tu: ");
printList();
printf("\n");
foundLink = find(4);
if(foundLink != NULL){
printf("Tim thay phan tu: ");
printf("(%d,%d) ",foundLink->key,foundLink->data);
printf("\n");
}else {
printf("Khong tim thay phan tu.");
}
printf("\n");
sort();
printf("Danh sach sau khi duoc sap xep: ");
printList();
reverse(&head);
printf("\nDanh sach sau khi bi dao nguoc: ");
printList();
}
Kết quả
Biên dịch và chạy chương trình C trên sẽ cho kết quả:
Thư Viện Linkedlist Trong C
V. Bài Tập Linked List C++
Nhằm giúp ace nâng cao kỹ năng, kiến thức lập trình, dễ dàng ghi nhớ và hiểu sâu hơn lúc đã học lý thuyết về linked list tại series tự học cấu trúc dữ liệu và giải thuật của techacademy. Sau đây là những bài tập có full lời giải và hướng dẫn giải cực chi tiết cho ace trên nhiều ngôn ngữ lập trình khác nhau.
Bài 1: Remove những phần tử trùng lặp trong linked list đã được sắp xếp
Cho 1 linked list được sắp xếp theo thứ tự tăng dần, hãy viết 1 hàm loại bỏ bất kỳ nút trùng lặp nào khỏi danh sách bằng phương pháp duyệt qua danh sách chỉ 1 lần
Cho thí dụ là: {1, 2, 2, 2, 3, 4, 4, 5}
sau lúc thực hiện sẽ được kết quả là: {1, 2, 3, 4, 5}
Gợi ý: Vì danh sách được sắp xếp, chúng ta có thể tiến hành rút gọn danh sách và so sánh các nút liền kề. Khi các nút liền kề giống nhau, hãy loại bỏ nút thứ hai. Có một trường hợp phức tạp trong đó nút sau nút tiếp theo cần được lưu ý trước khi xóa.
Bài 2: Đảo ngược mọi nhóm k nút trong danh sách liên kết đã cho
Cho một danh sách liên kết, đảo ngược mọi nhóm k nút liền kề trong đó với k là số nguyên dương.
Ví dụ:
–>
Input: 1>2>3>4>5>6>7>8>null
k = 3
Output: 3>2>1>6>5>4>8>7>null
k=2
Output: 2>1>4>3>6>5>8>7>null
k = 8
Output: 8>7>6>5>4>3>2>1>null
Gợi ý:
Ý tưởng là xem xét mọi nhóm k nút và đảo ngược đệ quy từng nút một. Cần phải đặc biệt chú ý khi liên kết các nhóm đảo ngược với nhau.
Bài 3: Di chuyển nút cuối cùng lên phía trước trong Danh sách được liên kết nhất định
Cho một danh sách được liên kết, hãy di chuyển nút cuối cùng của nó lên phía trước.
Ví dụ: Input: 1,2,3,4
Output: 4,1,2,3
Gợi ý: Ý tưởng là làm cho danh sách được liên kết có hình tròn và sau đó ngắt chuỗi trước nút cuối cùng sau khi làm cho danh sách này hướng đến nút cuối cùng.
Bài 4: Xóa mọi N nút trong danh sách được liên kết sau khi bỏ qua M nút
Cho một danh sách được liên kết và hai số nguyên dương M và N, xóa mọi N nút trong đó sau khi bỏ qua M nút.
Ví dụ:
1>2>3>4>5>6>7>8>9>10>null
if M = 1, N = 3
Output: 1>5>9>null
if M=2, N=2
Output: 1>2>5>6>9>10>null
Gợ ý: Ý tưởng rất đơn giản. Chúng tôi duyệt qua danh sách đã cho và bỏ qua m nút đầu tiên và xóa n nút tiếp theo trong đó và lặp lại cho các nút còn lại. Giải pháp rất đơn giản nhưng chúng ta cần đảm bảo rằng tất cả các điều kiện biên được xử lý đúng cách trong code.
Bài 5: Hợp nhất hai danh sách liên kết đã sắp xếp thành một
Viết một hàm nhận hai danh sách, mỗi danh sách được sắp xếp theo thứ tự tăng dần và hợp nhất hai danh sách với nhau thành một danh sách theo thứ tự tăng dần và trả về.
Ví dụ:
Input: 1>7>5>4 và 2>6>3>9
Output: 1>2>3>4>5>6>7>9
Gợi ý: Vấn đề có thể được giải quyết bằng vòng lặp hoặc đệ quy. Có nhiều trường hợp cần giải quyết: hoặc ‘a’ hoặc ‘b’ có thể để trống, trong quá trình xử lý, ‘a’ hoặc ‘b’ có thể hết đầu tiên và cuối cùng là vấn đề khởi động danh sách kết quả trống và xây dựng nó lên trong khi đi qua ‘a’ và ‘b’.
Có khá nhiều các giải như sau:
Sử dụng nút giả
Chiến lược ở đây sử dụng một nút giả tạm thời làm điểm bắt đầu của danh sách kết quả. Đuôi con trỏ luôn trỏ đến nút cuối cùng trong danh sách kết quả, vì vậy việc thêm các Nút mới rất dễ dàng. Nút giả cung cấp cho đuôi một cái gì đó để trỏ đến ban đầu khi danh sách kết quả trống. Nút giả này hiệu quả, vì nó chỉ là tạm thời và nó được cấp phát trong ngăn xếp. Vòng lặp tiếp tục, xóa một nút khỏi ‘a’ hoặc ‘b’ và thêm nó vào đuôi. Khi chúng ta hoàn tất, kết quả là dummy.next.
Sử dụng Tham chiếu cục bộ
Giải pháp này có cấu trúc rất giống với giải pháp trên, nhưng nó tránh sử dụng một nút giả. Thay vào đó, nó duy trì một con trỏ struct node ** lastPtrRef , luôn trỏ đến con trỏ cuối cùng của danh sách kết quả. Điều này giải quyết trường hợp tương tự mà nút giả đã làm – xử lý danh sách kết quả khi nó trống. Nếu bạn đang cố gắng tạo một danh sách ở đuôi của nó, bạn có thể sử dụng chiến lược nút giả hoặc nút cấu trúc ** “tham chiếu”.
Danh sách liên kết đơn(Single linked list) là ví dụ tốt nhất và đơn giản nhất về cấu trúc dữ liệu động sử dụng con trỏ để cài đặt. Do đó, kiến thức con trỏ là cực kỳ quan trọng để hiểu cách danh sách liên kết hoạt động, vì vậy nếu bạn chưa có kiến thức về con trỏ thì bạn nên học về con trỏ trước. Bạn cũng cần hiểu một chút về cấp phát bộ nhớ động. Để đơn giản và dễ hiểu, phần nội dung cài đặt danh sách liên kết của bài viết này sẽ chỉ trình bày về danh sách liên kết đơn.
I. Danh Sách Liên Kết Đơn C++
Danh sách liên kết đơn (Single Linked List) là một cấu trúc dữ liệu động, nó là một danh sách mà mỗi phần tử đều liên kết với phần tử đúng sau nó trong danh sách. Mỗi phần tử (được gọi là một node hay nút) trong danh sách liên kết đơn là một cấu trúc có hai thành phần:
Thành phần dữ liệu: lưu thông tin về bản thân phần tử đó.
Thành phần liên kết: lưu địa chỉ phần tử đứng sau trong danh sách, giả dụ phần tử đó là phần tử cuối cùng thì thành phần này bằng NULL.
Đặc điểm của danh sách liên kết đơn
Do danh sách liên kết đơn là 1 cấu trúc dữ liệu động, được tạo nên nhờ việc cấp phát động nên nó có một số đặc điểm sau đây:
Được cấp phát bộ nhớ khi chạy chương trình
Có thể đổi thay kích thước qua việc thêm, xóa phần tử
Kích thước tối đa phụ thuộc vào bộ nhớ khả dụng của RAM
Các phần tử được lưu trữ ngẫu nhiên (không liên tiếp) trong RAM
Và do tính liên kết của phần tử đầu và phần tử đứng sau nó trong danh sách liên kết đơn, nó mang những đặc điểm sau:
Chỉ cần nắm được phần tử đầu và cuối là có thể quản lý được danh sách
Truy cập tới phần tử ngẫu nhiên phải duyệt từ đầu đến vị trí đó
Chỉ có thể tìm kiếm tuyến tính một phần tử
Danh Sách Liên Kết Đơn C++
II. Nối 2 Danh Sách Liên Kết Đơn C++
Bài tập C: Nối hai danh sách liên kết đơn
Bài tập C này giúp bạn làm quen dần với cách tạo danh sách liên kết đơn và cách nối hai danh sách liên kết đơn trong C. Để giải bài tập này, mình sử dụng cấu trúc struct trong C.
Chương trình C
Dưới đây là chương trình C để giải bài tập nối hai danh sách liên kết đơn trong C:
Với hoạt động này, bạn cần phải cẩn thận. Chúng ta cần làm cho nút đầu (head) trỏ tới nút cuối cùng và đảo ngược toàn bộ danh sách liên kết.
Đảo Ngược Danh Sách Liên Kết Đơn C++
Đầu tiên, chúng ta duyệt tới phần cuối của danh sách. Nút này sẽ trỏ tới NULL. Bây giờ điều cần làm là làm cho nút cuối này trỏ tới nút phía trước của nó.
Đảo Ngược Danh Sách Liên Kết Đơn C++
Chúng ta phải đảm bảo rằng nút cuối cùng này sẽ không bị thất lạc, do đó chúng ta sẽ sử dụng một số nút tạm (temp node – giống như các biến tạm trung gian để lưu giữ giá trị). Tiếp theo, chúng ta sẽ làm cho từng nút bên trái sẽ trỏ tới nút trái của chúng.
Đảo Ngược Danh Sách Liên Kết Đơn C++
Sau đó, nút đầu tiên sau nút head sẽ trỏ tới NULL.
Đảo Ngược Danh Sách Liên Kết Đơn C++
Chúng ta sẽ làm cho nút head trỏ tới nút đầu tiên mới bởi sử dụng các nút tạm.
Đảo Ngược Danh Sách Liên Kết Đơn C++
Bây giờ Danh sách liên kết đã bị đảo ngược.
Chương trình minh họa Danh sách liên kết (Linked List) trong C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
struct node
{
int data;
int key;
struct node *next;
};
struct node *head = NULL;
struct node *current = NULL;
//hien thi danh sach
void printList()
{
struct node *ptr = head;
printf("\n[ ");
//bat dau tu phan dau danh sach
while(ptr != NULL)
{
printf("(%d,%d) ",ptr->key,ptr->data);
ptr = ptr->next;
}
printf(" ]");
}
//chen link tai vi tri dau tien
void insertFirst(int key, int data)
{
//tao mot link
struct node *link = (struct node*) malloc(sizeof(struct node));
link->key = key;
link->data = data;
//tro link nay toi first node cu
link->next = head;
//tro first toi first node moi
head = link;
}
//xoa phan tu dau tien
struct node* deleteFirst()
{
//luu tham chieu toi first link
struct node *tempLink = head;
//danh dau next toi first link la first
head = head->next;
//tra ve link bi xoa
return tempLink;
}
//kiem tra list co trong hay khong
bool isEmpty()
{
return head == NULL;
}
int length()
{
int length = 0;
struct node *current;
for(current = head; current != NULL; current = current->next)
{
length++;
}
return length;
}
//tim mot link voi key da cho
struct node* find(int key){
//bat dau tim tu first link
struct node* current = head;
//neu list la trong
if(head == NULL)
{
return NULL;
}
//duyet qua list
while(current->key != key){
//neu day la last node
if(current->next == NULL){
return NULL;
}else {
//di chuyen toi next link
current = current->next;
}
}
//neu tim thay du lieu, tra ve link hien tai
return current;
}
//xoa mot link voi key da cho
struct node* deleteKey(int key){
//bat dau tu first link
struct node* current = head;
struct node* previous = NULL;
//neu list la trong
if(head == NULL){
return NULL;
}
//duyet qua list
while(current->key != key){
//neu day la last node
if(current->next == NULL){
return NULL;
}else {
//luu tham chieu toi link hien tai
previous = current;
//di chuyen toi next link
current = current->next;
}
}
//cap nhat link
if(current == head) {
//thay doi first de tro toi next link
head = head->next;
}else {
//bo qua link hien tai
previous->next = current->next;
}
return current;
}
// ham sap xep
void sort(){
int i, j, k, tempKey, tempData ;
struct node *current;
struct node *next;
int size = length();
k = size ;
for ( i = 0 ; i < size - 1 ; i++, k-- ) {
current = head ;
next = head->next ;
for ( j = 1 ; j < k ; j++ ) {
if ( current->data > next->data ) {
tempData = current->data ;
current->data = next->data;
next->data = tempData ;
tempKey = current->key;
current->key = next->key;
next->key = tempKey;
}
current = current->next;
next = next->next;
}
}
}
// ham dao nguoc list
void reverse(struct node** head_ref) {
struct node* prev = NULL;
struct node* current = *head_ref;
struct node* next;
while (current != NULL) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
*head_ref = prev;
}
main() {
insertFirst(1,10);
insertFirst(2,20);
insertFirst(3,30);
insertFirst(4,1);
insertFirst(5,40);
insertFirst(6,56);
printf("Danh sach ban dau: ");
//in danh sach
printList();
while(!isEmpty()){
struct node *temp = deleteFirst();
printf("\nGia tri bi xoa:");
printf("(%d,%d) ",temp->key,temp->data);
}
printf("\nDanh sach sau khi da xoa gia tri: ");
printList();
insertFirst(1,10);
insertFirst(2,20);
insertFirst(3,30);
insertFirst(4,1);
insertFirst(5,40);
insertFirst(6,56);
printf("\nPhuc hoi danh sach: ");
printList();
printf("\n");
struct node *foundLink = find(4);
if(foundLink != NULL){
printf("Tim thay phan tu: ");
printf("(%d,%d) ",foundLink->key,foundLink->data);
printf("\n");
}else {
printf("Khong tim thay phan tu.");
}
deleteKey(4);
printf("Danh sach, sau khi xoa mot phan tu: ");
printList();
printf("\n");
foundLink = find(4);
if(foundLink != NULL){
printf("Tim thay phan tu: ");
printf("(%d,%d) ",foundLink->key,foundLink->data);
printf("\n");
}else {
printf("Khong tim thay phan tu.");
}
printf("\n");
sort();
printf("Danh sach sau khi duoc sap xep: ");
printList();
reverse(&head);
printf("\nDanh sach sau khi bi dao nguoc: ");
printList();
}
Kết quả
Biên dịch và chạy chương trình C trên sẽ cho kết quả:
Đảo Ngược Danh Sách Liên Kết Đơn C++
IV. Nhập Xuất Danh Sách Liên Kết Đơn C++
Tùy theo kiểu danh sách bạn là gì mà cách nhập xuất khác nhau. Bài viết này mình nhập xuất số nguyên nên có phần đơn giản hơn
Mình sẽ nhập vào phần tử cho danh sách liên kết đơn bằng con trỏ. Nếu nhập vào bằng 0 thì sẽ dừng nhập. Thêm phần tử vào danh sách mình sử dụng hàm chèn cuối Insert_Last
// Hàm nhập danh sách số nguyên từ bàn phím
void Input(List &L){
Init(L);
item x;
int i=1;
do{
cout<<"\nNhap phan tu thu "<<i<<": ";
cin>>x;
if (x!=0){
Insert_Last(L,x);
i++;
}
}
while (x!=0); // Nếu nhập x = 0 thì dừng nhập
//Ban co the dung cach khac
V. Sắp Xếp Danh Sách Liên Kết Đơn C++
Ở phần sắp xếp phần tử trong danh sách liên kết đơn, mình sẽ thực hiện sắp xếp bằng cách so sánh và đổi thay giá trị data chứ không thay đổi Node. Tức là chỉ so sánh các giá trị data rồi sắp xếp, các Node vẫn giữ nguyên không dịch chuyển.
Sắp Xếp Danh Sách Liên Kết Đơn C++
Thao tác sắp xếp trong danh sách về căn bản tương tự như những thuật toán sắp xếp khác, đơn giản chỉ là duyệt từng phần tử rồi so sánh với nhau, sau ấy hoán đổi vị trí của chúng.
Đầu tiên ta có một vòng lặp For sử dụng biến pTmp để lặp từng phần tử trong danh sách, vòng lặp For thứ hai sử dụng biến pTmp2 để lặp từng phần tử trong danh sách.
Nếu pTmp > pTmp2 thì hoán đổi vị trí giữa chúng, nếu pTmp < pTmp2 thì tiếp tục so sách những phần tử tiếp theo, cứ như vậy cho tới hết danh sách.
/* sắp xếp trong danh sách liên kết đơn theo thứ tự tăng dần */
void SortList(SingleList &list)
{
// for loop thứ nhất
for(Node *pTmp=list.pHead;pTmp!=NULL;pTmp=pTmp->pNext)
{
//for loop thứ hai
for(Node *pTmp2=pTmp->pNext;pTmp2!=NULL;pTmp2=pTmp2->pNext)
{
if(pTmp->data>pTmp2->data) // nếu giá trị trước > giá trị sau thì hoán đổi hai vị trí
{
int tmp=pTmp->data;
pTmp->data=pTmp2->data;
pTmp2->data=tmp;
}
}
}
}
VI. Cài Đặt Danh Sách Liên Kết Đơn C++
Trước lúc đi vào cài đặt danh sách liên kết đơn, hãy chắc chắn rằng bạn đã nắm vững phần con trỏ và cấp phát động trong C++. Do danh sách liên kết đơn là một cấu trúc dữ liệu động, nếu bạn không nắm vững con trỏ và cấp phát động sẽ rất khó để bạn hiểu được bài viết này. Nếu bạn cảm thấy chưa tự tin, hãy dành ít thời gian để xem bài viết này của mình. Còn bây giờ thì bắt đầu thôi!
Tạo node
Danh sách liên kết đơn được tạo thành từ nhiều node, do đó, chúng ta sẽ cùng đi từ node trước. Một node gồm hai thành phần là thành phần dữ liệu và thành phần liên kết. Thành phần dữ liệu có thể là kiểu dữ liệu có sẵn hoặc bạn tự định nghĩa (struct hay class…), trong bài viết này để đơn giản mình sẽ dùng kiểu int cho phần dữ liệu. Thành phần liên kết là địa chỉ đương nhiên sẽ là con trỏ, con trỏ này trỏ đến node tiếp theo, do đó, con trỏ này là con trỏ trỏ vào 1 node.
struct Node
{
int data;
Node* next;
};
Để tạo một node mới, ta thực hiện cấp phát động cho node mới, khởi tạo giá trị ban đầu và trả về địa chỉ của node mới được cấp phát.
Tạo danh sách liên kết đơn
Ta đã có được thành phần tạo nên danh sách liên kết đơn là node, tiếp theo chúng ta cần quản lý chúng bằng phương pháp biết được phần tử đầu và cuối. Vì mỗi phần tử đều liên kết với phần tử kế vậy nên tả chỉ cần biết phần tử đầu và cuối là có thể quản lý được danh sách này. Vậy đơn giản ta cần tạo 1 cấu trúc lưu trữ địa chỉ phần tử đầu (head) và phần tử cuối (hay phần tử đuôi tail).
struct LinkedList
{
Node* head;
Node* tail;
};
Khi mới tạo danh sách, danh sách sẽ không có phần tử nào, do đó head và tail không trỏ vào đâu cả, ta sẽ gán chúng bằng NULL. Ta xây dựng hàm tạo danh sách như sau:
LinkedList list;
CreateList(list); // Gán head và tail bằng NULL
Thêm phần tử vào danh sách
Thêm vào đầu
Để thêm node vào đầu danh sách, trước tiên ta cần kiếm tra xem danh sách đó có rỗng hay không, nếu danh sách rỗng, ta chỉ cần gán head và tail của danh sách bằng node đó. Ngược lại nếu danh sách không rỗng, ta thực hiện trỏ thành phần liên kết vào head, sau đó gán lại head bằng node mới.
Thêm phần tử vào đầu danh sách liên kết đơn
Như trong hình trên, chúng ta thêm node có data bằng 0 vào danh sách. Ta thực hiện trỏ next của node đó vào head của danh sách (chính là node đầu tiên của danh sách có data bằng 1), sau đó ta trỏ head vào node có data 0 vừa được thêm. Vậy là phần tử đó đã nằm ở đầu danh sách rồi.
Thêm vào cuối
Tương tự, để thêm node vào cuối danh sách, đầu tiên ta đánh giá xem danh sách rỗng hay không, rỗng thì gán head và tail đều bằng node mới. Nếu không rỗng, ta thực hiện trỏ tail->next vào node mới, sau đó gán lại tail bằng node mới (vì bây giờ node mới thêm chính là tail).
Thêm phần tử vào cuối danh sách liên kết đơn
Trong hình trên, chúng ta thực hiện thêm node có data bằng 6 vào danh sách. Tail hiện tại là node có data 5, thực hiện gán tail->next bằng node mới để nối thêm nó vào đuôi danh sách, lúc này node mới trở thành phần tử cuối danh sách nên ta gán tail lại bằng node mới.
Thêm vào sau node bất kỳ
Để thêm một node p vào sau node q bất kỳ, đầu tiên ta cần kiếm tra xem node q có NULL hay không, nếu node q là NULL tức là danh sách rỗng, vậy thì ta sẽ thêm vào đầu danh sách. Nếu node q không NULL, tức là tồn tại trong danh sách, ta thực hiện trỏ p->next = q->next, sau đó q->next = p. Tiếp theo chúng ta kiểm tra xem node q trước đó có phải là node cuối hay không, nếu node q là node cuối thì thêm p vào, p sẽ thành node cuối nên ta gán lại tail = p.
Thêm phần tử vào sau nút Q trong danh sách liên kết đơn
Trong hình trên, ta thêm node có data bằng 4 (node p) vào sau node có data bằng 3 (node q). Ta trỏ next của node p vào next của node q tức là node có data bằng 5, sau đó trỏ next của node q vào node p vậy là node p đã được thêm vào danh sách.
Xóa phần tử khỏi danh sách
Xóa ở đầu
Để xóa phần tử ở đầu danh sách, ta kiểm tra xem danh sách đó có rỗng hay không, nếu rỗng, ta không cần xóa, trả về kết quả là 0. Nếu danh sách không rỗng, ta thực hiện lưu node head lại, sau đó gán head bằng next của node head, sau đó xóa node head đi. Tiếp theo ta cần kiểm tra xem danh sách vừa bị xóa đi node head có rỗng hay không, nếu rỗng ta gán lại tail bằng NULL luôn sau đó trả về kết quả 1.
Lưu ý trước khi xóa node head đi, ta dùng biến tham chiếu x để lưu trữ lại giá trị của node bị hủy để sử dụng.
Xóa phần tử đầu danh sách liên kết đơn
Trong hình trên, mình thực hiện xóa node đầu tiên có data bằng 0. Mình trỏ head đến next của node 0 (hiện đang là head), thì head lúc này sẽ là node 1, sau đó mình hủy đi node 0 là được.
Xóa ở sau node bất kỳ
Để xóa một node p sau node q bất kỳ, ta kiểm tra xem node q có NULL hay không, nếu node q NULL thì không tồn tại trong danh sách, do đó trả về 0, không xóa. Nếu node q khác NULL nhưng next của q là NULL, tức là p bằng NULL thì không xóa, trả về 0 (do sau q không có node nào cả, q là tail). Nếu node p tồn tại, ta thực hiện kiểm tra xem node p có phải là tail hay không, nếu node p là tail thì gán lại tail là q, tức là node trước đó để xóa node p đi.
Trong hình trên, ta thực hiện xóa node có data 3 (node p) sau node có data 2 (node q). Ta trỏ next của node q vào next của node p tức là node có data 4, sau đó xóa node p đi là xong.
Duyệt danh sách và in
Sau khi có các thao tác thêm, xóa, chúng ta có thể in ra danh sách để kiểm tra xem có hoạt động đúng hay không. Để in danh sách, ta duyệt từ đầu đến cuối danh sách và in ra trong lúc duyệt. Ta gán một node bằng head, sau đó kiểm tra xem node đó có NULL hay không, không thì in ra data của node đó, sau đó gán tiếp node đó bằng next của chính nó tức node đó bây giờ là node tiếp theo, cứ như vậy cho đến hết.
Lấy giá trị node bất kỳ
Để lấy giá trị phần tử trong danh sách, ta thực hiện duyệt tương tự như khi in phần tử. Ta sẽ tạo một biến đếm để biết vị trí hiện tại, duyệt qua các node cho đến khi node bằng NULL hoặc biến đếm bằng với vị trí node cần lấy. Kiểm tra xem nếu node khác NULL và biến đếm bằng vị trí cần lấy, ta sẽ trả về địa chỉ của node đó, ngược lại trả về NULL (danh sách rỗng hoặc là vị trí cần lấy nằm ngoài phạm vi của danh sách).
Tìm kiếm phần tử trong danh sách
Ý tưởng tìm kiếm phần tử cũng là duyệt danh sách, nếu như chưa tìm thấy thì tiếp tục duyệt. Sau khi kết thúc duyệt, ta chỉ cần kiểm tra xem node duyệt có bằng NULL hay không, nếu không tức là đã tìm thấy, ta sẽ trả về địa chỉ của node đó.
Đếm số phần tử của danh sách
Đếm số phần tử thì cũng tương tự, ta áp dụng duyệt từ đầu đếm cuối và đếm số node.
Xóa danh sách
Để xóa danh sách, ta cần hủy tất cả các node tức là duyệt và hủy từng node. Ở đây mình sẽ dùng lại hàm RemoveHead. Đầu tiên, ta gán một node bằng head, kiểm tra nếu node đó khác NULL thì gọi RemoveHead và gán lại node bằng head tiếp, cứ lặp như vậy cho đến khi node đó NULL thì thôi. Sau khi xóa hết tất cả phần tử thì gán lại tail bằng NULL.
VII. Code Danh Sách Liên Kết Đơn Sinh Viên C++
Đề bài: Xây dựng chương trình quản lý sinh viên bằng DSLK đơn
Cho 1 sinh viên có cấu trúc: mã (int), tên (char *). Dùng danh sách liên kết đơn với con trỏ phead để thao tác:
Khởi tạo list dạng con trỏ
Thêm node vào cuối danh sách
Sắp xếp theo mã
Xóa node
Chương trình quản lý sinh viên sử dụng DSLK đơn
Chúng ta sẽ lần lượt tạo cấu trúc sinh viên, cấu trúc danh sách liên kết đơn và các thao tác liên quan.
Đầu tiên chúng ta buộc phải tạo một cấu trúc sinh viên với mã số sinh viên ma và tên sinh viên ten.
//tao cau truc sinh vien
struct SinhVien
{
int ma;
char ten[150];
};
Tiếp đến tạo cấu trúc dữ liệu của danh sách liên kết đơn với giá trị data và con trỏ pNext. Khởi tạo giá trị cho pHead và pTail bằng NULL.
//tao cau truc danh sach lien ket don
struct Node
{
SinhVien *data;
Node *pNext;
};
struct SingleList
{
Node *pHead;
};
//khoi tao danh sach lien ket don
void Initialize(SingleList *&list)
{
list=new SingleList;
list->pHead=NULL;
}
Tạo 1 hàm NhapSinhVien() dùng cấu trúc SinhVien để nhập những thông tin của sinh viên như: MSSV và tên sinh viên
SinhVien *NhapSinhVien()
{
SinhVien *sv=new SinhVien;
cout<<"Nhap MSSV:";
cin>>sv->ma;
cin.ignore();
cout<<"Nhap ho va ten:";
gets(sv->ten);
return sv;
}
Bây giờ chúng ta bắt đầu tạo Node với các thông tin của cấu trúc SinhVien, sau đó thêm Node vào cuối danh sách.
Sau lúc thêm Node vào danh sách ta thực hiện những thao tác theo đề nghị của đề bài. Đầu tiên là việc sắp xếp các sinh viên theo MSSV.
Ở bài tìm kiếm và sắp xếp trong danh sách liên kết đơn mình đã giới thiệu các bạn thao tác sắp xếp. Dựa vào đó ta chỉ cần biến đổi một chút sẽ có ngay hàm sắp xếp SortList() theo MSSV.
Tương tự như hàm sắp xếp, để xóa một sinh viên dựa vào tên ta thực hiện vòng lặp while lặp từng phần tử trong danh sách. Nếu phần tử đó trùng với phần tử được nhập vào từ bàn phím ta thực hiện delete phần tử đó ra khỏi danh sách.
Sau khi thực hiện tạo các thao tác, ta chỉ cần tạo hàm main() và gọi các thao tác đó ra để sử dụng.
int main(int argc, char** argv) {
SingleList *list;
Initialize(list);
SinhVien *teo=NhapSinhVien();
InsertLast(list,teo);
SinhVien *ty=NhapSinhVien();
InsertLast(list,ty);
SinhVien *bin=NhapSinhVien();
InsertLast(list,bin);
PrintList(list);
SortList(list);
cout<<"\nSau khi sap xep:\n";
PrintList(list);
cout<<"\Ban muon xoa sinh vien co MSSV: ";
int ma;
cin>>ma;
RemoveNode(list,ma);
cout<<"\nSau khi xoa:\n";
PrintList(list);
}
Full code:
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
//tao cau truc sinh vien
struct SinhVien
{
int ma;
char ten[150];
};
//tao cau truc danh sach lien ket don
struct Node
{
SinhVien *data;
Node *pNext;
};
struct SingleList
{
Node *pHead;
};
//khoi tao danh sach lien ket don
void Initialize(SingleList *&list)
{
list=new SingleList;
list->pHead=NULL;
}
//nhap thong tin sinh vien
SinhVien *NhapSinhVien()
{
SinhVien *sv=new SinhVien;
cout<<"Nhap MSSV:";
cin>>sv->ma;
cin.ignore();
cout<<"Nhap ho va ten:";
gets(sv->ten);
return sv;
}
//tao node sinh vien
Node *CreateNode(SinhVien *sv)
{
Node *pNode=new Node;
if(pNode!=NULL)
{
pNode->data=sv;
pNode->pNext=NULL;
}
else
{
cout<<"cap phat bo nho that bai!!!";
}
return pNode;
}
//them node vao cuoi danh sach
void InsertLast(SingleList *&list,SinhVien *sv)
{
Node *pNode=CreateNode(sv);
if(list->pHead==NULL)
{
list->pHead=pNode;
}
else
{
Node *pTmp=list->pHead;
while(pTmp->pNext!=NULL)
{
pTmp=pTmp->pNext;
}
pTmp->pNext=pNode;
}
}
//hien thi danh sach
void PrintList(SingleList *list)
{
Node *pTmp=list->pHead;
if(pTmp==NULL)
{
cout<<"Danh sach rong";
return;
}
while(pTmp!=NULL)
{
SinhVien *sv=pTmp->data;
cout<<sv->ma<<"\t"<<sv->ten<<"\n";
pTmp=pTmp->pNext;
}
}
//sap xep
void SortList(SingleList *&list)
{
for(Node *pTmp=list->pHead;pTmp!=NULL;pTmp=pTmp->pNext)
{
for(Node *pTmp2=pTmp->pNext;pTmp2!=NULL;pTmp2=pTmp2->pNext)
{
SinhVien *svTmp=pTmp->data;
SinhVien *svTmp2=pTmp2->data;
if(svTmp2->ma<svTmp->ma)
{
int ma=svTmp->ma;
char ten[150];
strcpy(ten,svTmp->ten);
svTmp->ma=svTmp2->ma;
strcpy(svTmp->ten,svTmp2->ten);
svTmp2->ma=ma;
strcpy(svTmp2->ten,ten);
}
}
}
}
//xoa
void RemoveNode(SingleList *&list,int ma)
{
Node *pDel=list->pHead;
if(pDel==NULL)
{
cout<<"Danh sach rong!";
}
else
{
Node *pPre=NULL;
while(pDel!=NULL)
{
SinhVien *sv=pDel->data;
if(sv->ma==ma)
break;
pPre=pDel;
pDel=pDel->pNext;
}
if(pDel==NULL)
{
cout<<"khong tim thay MSSV: "<<ma;
}
else
{
if(pDel==list->pHead)
{
list->pHead=list->pHead->pNext;
pDel->pNext=NULL;
delete pDel;
pDel=NULL;
}
else
{
pPre->pNext=pDel->pNext;
pDel->pNext=NULL;
delete pDel;
pDel=NULL;
}
}
}
}
int main(int argc, char** argv) {
SingleList *list;
Initialize(list);
SinhVien *teo=NhapSinhVien();
InsertLast(list,teo);
SinhVien *ty=NhapSinhVien();
InsertLast(list,ty);
SinhVien *bin=NhapSinhVien();
InsertLast(list,bin);
PrintList(list);
SortList(list);
cout<<"\nSau khi sap xep:\n";
PrintList(list);
cout<<"\Ban muon xoa sinh vien co MSSV: ";
int ma;
cin>>ma;
RemoveNode(list,ma);
cout<<"\nSau khi xoa:\n";
PrintList(list);
cout<<"\n---------------------------\n";
cout<<"Chuong trinh nay duoc dang tai Freetuts.net";
}
Kết quả:
Code Danh Sách Liên Kết Đơn Sinh Viên C++
VIII. Xóa Phần Tử Trong Danh Sách Liên Kết Đơn C++
Trong chỉ dẫn này mình sẽ giới thiệu tới các bạn cách xóa Node trong danh sách liên kết đơn.
Chúng ta sẽ cùng nhau tìm hiểu 3 ví dụ lúc xóa 1 Node khỏi danh sách liên kết đơn:
Xóa Node ở đầu danh sách liên kết đơn.
Xóa Node ở cuối danh sách liên kết đơn.
Xóa Node ở giữa danh sách liên kết đơn.
+ Xóa Node ở đầu danh sách liên kết đơn
Trong trường hợp chúng ta muốn xóa một Node, mà Node đó lại nằm ở đầu danh sách. Đây là một trường hợp đặc biệt, các bạn hãy xem các bước thực hiện sau đây:
Giả sử chúng ta có một Node pDel là Node cần xóa và một danh sách liên kết đơn.
Xóa Node ở đầu danh sách liên kết đơn
Bước 1: Vì Node cần xóa ở đầu danh sách, tức là ngay node pHead. Vì vậy chúng ta cần di chuyển pHead từ pDel sang Node kế tiếp: list.pHead = list.pHead -> pNext
Xóa Node ở đầu danh sách liên kết đơn
Bước 2: Sau khi di chuyển pHead sang Node kế tiếp, chúng ta sẽ ngắt mối liên kết giữa pDel với Node phía sau nó: pDel -> pNext = Null.
Xóa Node ở đầu danh sách liên kết đơn
Bước 3: Bây giờ pDel không còn liên kết với bất kì Node nào trong danh sách nữa, chúng ta đã có thể xóa Node này. delete pDel
Xóa Phần Tử Trong Danh Sách Liên Kết Đơn C++
// Nếu pDel == list.pHead, tức là số cần xóa ở đầu danh sách
if(pDel == list.pHead){
list.pHead = list.pHead -> pNext;
pDel -> pNext = NULL;
delete pDel;
pDel = NULL;
}
+ Xóa Node ở cuối danh sách liên kết đơn.
Trong trường hợp Node muốn xóa lại nằm ở cuối danh sách, tương tự như việc xóa ở đầu danh sách. Ta chỉ cần di chuyển pTail về Node trước đó (pPre) và thay đổi pNext = NULL.
Xóa Node ở cuối danh sách liên kết đơn
Sau khi di chuyển pTail về Node trước đó và ngắt mối liên kết giữa pPre với pDel, ta thực hiện xóa Node pDel: delete pDel
//Nếu pDel == list.pTail, tức là số cần xóa ở cuối danh sách
if(pDel -> pNext == NULL){
list.pTail = pPre;
pPre -> pNext = NULL;
delete pDel;
pDel = NULL;
}
+ Xóa Node ở giữa danh sách liên kết đơn.
Và trường hợp cuối cùng, khi xóa Node mà Node đó không nằm đầu cũng không nằm cuối danh sách, ta thực hiện các bước như sau:
Khi ta muốn xóa một Node ở giữa danh sách, đầu tiên ta cần xác định Node cần xóa pDel và Node đứng trước nó pPre.
Xóa Node ở giữa danh sách liên kết đơn.
Sau khi xác định được pDel và pPre, ta thay đổi mối liên kết giữa pPre đến pTail (pPre -> pNext = pDel -> pNext) và cho pDel -> pNext == NULL. Các bạn có thể xem hướng mũi tên để biết được các bước thực hiện của nó.
Xóa Node ở giữa danh sách liên kết đơn.
Ta có thể xóa Node pDel khi đã ngắt mối liên kết giữa nó với các Node khác: delete pDel
// và trường hợp cuối cùng số muốn xóa nằm ở giữa danh sách
else{
pPre -> pNext = pDel -> pNext;
pDel -> pNext = NULL;
delete pDel;
pDel = NULL;
}
+ Ví dụ xóa Node trong danh sách liên kết đơn
Chúng ta sẽ sử dụng dữ liệu ở ví dụ trước để thực hiện xóa cho ví dụ này, vừa có thể ôn lại kiến thức cũ vừa áp dụng kiến thức mới.
#include <iostream>
using namespace std;
/* Khai báo giá trị data và con trỏ pNext trỏ tới phần tử kế tiếp */
struct Node
{
int data;// giá trị data của node
Node *pNext;// con trỏ pNext
};
/* Khai báo Node đầu pHead và Node cuối pTail*/
struct SingleList
{
Node *pHead; //Node đầu pHead
Node *pTail; // Node cuối pTail
};
/* khởi tạo giá trị cho Node đầu và Node cuối */
void Initialize(SingleList &list)
{
list.pHead=list.pTail=NULL;// khởi tạo giá trị cho Node đầu và Node cuối là Null
}
/* Đếm số phần tử trong danh sách */
int SizeOfList(SingleList list)
{
Node *pTmp=list.pHead;
int nSize=0;
while(pTmp!=NULL)
{
pTmp=pTmp->pNext;
nSize++;
}
return nSize;
}
/* tạo Node trong danh sách liên kết đơn */
Node *CreateNode(int d)
{
Node *pNode=new Node; //sử dụng pNode để tạo một Node mới
if(pNode!=NULL) // Nếu pNode != Null, tức là pNode có giá trị thì
{
pNode->data=d; // gán giá trị data cho d
pNode->pNext=NULL;// và cho con trỏ pNext trỏ tới giá trị Null
}
else // Nếu pNode == Null, tức là pNode không có giá trị thì xuất thông tin
{
cout<<"Error allocated memory";
}
return pNode;//trả về pNode
}
/* chèn Node đầu danh sách */
void InsertFirst(SingleList &list,int d)
{
Node *pNode=CreateNode(d);
if(list.pHead==NULL)
{
list.pHead=list.pTail=pNode;
}
else
{
pNode->pNext=list.pHead;
list.pHead=pNode;
}
}
/* chèn node vào cuối danh sách */
void InsertLast(SingleList &list,int d)
{
Node *pNode=CreateNode(d);
if(list.pTail==NULL)
{
list.pHead=list.pTail=pNode;
}
else
{
list.pTail->pNext=pNode;
list.pTail=pNode;
}
}
/* chèn node vào giữa danh sách */
void InsertMid(SingleList &list, int pos, int d){
// Nếu pos < 0 hoặc pos lớn hơn kích thước của danh sách thì reuturn
if(pos < 0 || pos >= SizeOfList(list)){
cout<<"Không thể chèn Node!!!";
return;
}
// Nếu pos == 0 thì gọi hàm InsertFirst
if(pos == 0){
InsertFirst(list, d);
}
//Nếu pos == SizeOfList - 1 thì gọi hàm InsertLast
else if(pos == SizeOfList(list)-1){
InsertLast(list, d);
}
//Ngược lại thì thay đổi mối liên kết giữa các phần tử, cụ thể:
else{
Node *pNode = CreateNode(d);
Node *pIns = list.pHead;
Node *pPre = NULL;
int i = 0;
//thực hiện vòng lặp tìm pPre và pIns
while(pIns != NULL){
if(i == pos)
break;
pPre = pIns;
pIns = pIns ->pNext;
i++;
}
//sau khi tìm được thì thay đổi con trỏ pNext
pPre ->pNext=pNode;
pNode->pNext=pIns;
}
}
/* xóa node khỏi danh sách liên kết */
void RemoveNode(SingleList &list, int d){
Node *pDel = list.pHead; // tạo một node pDel để xóa
//Nếu pDel == Null thì danh sách rỗng
if(pDel == NULL){
cout<<"Danh sách rỗng!!";
}
//ngược lại thì xét điều kiện
else{
Node *pPre = NULL;
//dùng vòng lặp while để tìm ra pDel và pPre (vị trí đứng trước pDel)
while(pDel != NULL){
if(pDel -> data == d){
break;
}
pPre = pDel;
pDel = pDel -> pNext;
}
//Nếu pDel == null tức là không tìm thấy số cần xóa
if(pDel == NULL){
cout<<"Không tìm thấy số cần xóa";
}
// Ngược lại tiếp tục xét điều kiện
else{
// Nếu pDel == list.pHead, tức là số cần xóa ở đầu danh sách
if(pDel == list.pHead){
list.pHead = list.pHead -> pNext;
pDel -> pNext = NULL;
delete pDel;
pDel = NULL;
}
//Nếu pDel == list.pTail, tức là số cần xóa ở cuối danh sách
else if(pDel -> pNext == NULL){
list.pTail = pPre;
pPre -> pNext = NULL;
delete pDel;
pDel = NULL;
}
// và trường hợp cuối cùng số muốn xóa nằm ở giữa danh sách
else{
pPre -> pNext = pDel -> pNext;
pDel -> pNext = NULL;
delete pDel;
pDel = NULL;
}
}
}
}
/* hàm xuất dữ liệu */
void PrintList(SingleList list)
{
Node *pTmp=list.pHead;
if(pTmp==NULL)
{
cout<<"The list is empty!";
return;
}
while(pTmp!=NULL)
{
cout<<pTmp->data<<" ";
pTmp=pTmp->pNext;
}
}
int main() {
SingleList list;
Initialize(list);
//Thêm node đầu danh sách
InsertFirst(list, 5);
InsertFirst(list, 7);
InsertFirst(list, 3);
cout<<"Các Node trong danh sách sau khi InsertFirst là: ";
PrintList(list);
//Thêm node cuối danh sách
InsertLast(list, 4);
InsertLast(list, 2);
InsertLast(list, 6);
cout<<"\nCác Node trong danh sách sau khi InsertLast là: ";
PrintList(list);
//Thêm node giữa danh sách
InsertMid(list, 4, 11);
InsertMid(list, 2, 12);
InsertMid(list, 3, 13);
cout<<"\nCác Node trong danh sách sau khi InsertMid là: ";
PrintList(list);
//Xóa node khỏi danh sách
RemoveNode(list, 3);
RemoveNode(list, 11);
RemoveNode(list, 6);
cout<<"\nCác Node trong danh sách sau khi xóa là: ";
PrintList(list);
cout<<"\n-------------------------------------\n";
cout<<"Chương trình này được đăng tại Freetuts.net";
}
Kết quả:
Ví dụ xóa Node trong danh sách liên kết đơn
IX. Bài Tập Danh Sách Liên Kết Đơn C++
Bài tập danh sách liên kết đơn dưới đây là 1 dạng bài tập tổng hợp giúp những bạn ôn luyện lại kiến thức về danh sách liên kết đơn cũng như các kiến thức khác về lập trình C. Sau bài học này, bên cạnh kiến thức về danh sách liên kết đơn, bạn cũng sẽ nắm được:
Đọc ghi tệp trong ngôn ngữ C
Cách xử lý dữ liệu văn bản trong C: tách chuỗi, chuyển chuỗi về số, …
Làm việc với kiểu dữ liệu tự định nghĩa (structure)
Và các kiến thức căn bản khác của lập trình C
Đề bài tập danh sách liên kết đơn
Viết chương trình trong ngôn ngữ C thực hiện các bắt buộc sau:
Khai báo cấu trúc dữ liệu để tổ chức danh sách liên kết đơn quản lý các tỉnh/thành phố của Việt Nam. Thông tin của mỗi tỉnh/thành phố bao gồm: Mã tỉnh, tên tỉnh, diện tích, dân số.
Cài đặt các thao tác cơ bản (thêm ở vị trí bất kỳ; sửa, xóa theo mã (code), duyệt danh sách).
Tính tổng diện tích của tất cả các tỉnh thành.
Tìm vị trí của node của tỉnh có diện tích lớn nhất.
Tìm tỉnh/thành phố có dân số lớn nhất.
Sắp xếp danh sách theo mã tỉnh/thành phố.
Sắp xếp danh sách tăng dần theo diện tích.
Yêu cầu:
Viết chương trình cụ thể hóa những chức năng trên, người sử dụng có thể tương tác qua menu cho phép lựa chọn chức năng mà họ muốn.
Ban đầu, danh sách tỉnh/thành phố được nhập tự động từ 1 tập tin (Text file .txt) cho trước có nội dung
Lời giải bài tập danh sách liên kết đơn
+ Xây dựng các kiểu dữ liệu cần thiết
Chúng ta cần định nghĩa kiểu dữ liệu City theo yêu cầu của đề bài, gồm có các trường mã (code), tên (name), diện tích (area) và dân số (population).
Chúng ta cũng cần định nghĩa kiểu dữ liệu cho 1 Node của danh sách liên kết, mỗi Node sẽ gồm dữ liệu và con trỏ next.
Trong bài này, mình giả sử code (mã tỉnh,thành phố) là không trùng lặp nên sẽ bỏ qua bước kiểm tra.
// Khai báo kiểu cấu trúc City
struct City {
int code;
char name[100];
float area;
int population;
};
// Định nghĩa cho kiểu "struct City" 1 tên mới ngắn gọn hơn, thay vì khai báo kiểu "struct City" thì ta chỉ cần dùng "City"
typedef struct City City;
// Khai báo kiểu cấu trúc LinkedList
struct LinkedList{
City city;
struct LinkedList *next;
};
// Định nghĩa cho kiểu "struct LinkedList" 1 tên mới ngắn gọn hơn, thay vì khai báo kiểu "struct LinkedList" thì ta chỉ cần dùng "Node"
typedef struct LinkedList *Node;
+ Xây dựng các hàm khởi tạo
Với danh sách liên kết, chúng ta cũng cần khởi tạo Node đầu tiên cho nó, việc khởi tạo rất đơn giản chỉ bằng cách gán Node đó bằng NULL, tức là chưa có dữ liệu (chưa có Node nào cả)
// Hàm khởi tạo Node đầu tiên của LinkedList
Node initHead(){
Node head;
head = NULL;
return head;
}
Chúng ta cũng sẽ cần hàm khởi tạo 1 Node khi đã có dữ liệu của Node đó. Sau khi khởi tạo thì chúng ta có thể thêm nó vào danh sách.
// Hàm tạo mới 1 Node trong LinkedList
Node createNode(City city){
Node temp; // Khai báo 1 Node
temp = (Node)malloc(sizeof(struct LinkedList)); // Cấp phát vùng nhớ cho Node
temp->next = NULL;// Cho next trỏ tới NULL
temp->city = city; // Gán giá trị cho Node
return temp;
}
Lưu ý: Ta cần cho con trỏ next của Node được khởi tạo bằng NULL, tức là chưa trỏ tới đâu. Tránh trường hợp nó trỏ lung tung trong bộ nhớ.
Chúng ta cần có 1 hàm khởi tạo giá trị cho kiểu City đã định nghĩa ở trên qua stdin (nhập từ console). Lý do là bởi chương trình của chúng ta có chức năng thêm, sửa dữ liệu của 1 Node. Khi đó, ta sẽ gọi tới hàm này để tạo dữ liệu thông qua stdin.
City createCity(){
City newCity;
printf("Nhap code: ");
scanf("%d", &newCity.code);
printf("Nhap ten: ");
getchar(); // Bỏ qua '\n' trong bộ đệm
fgets(newCity.name, 100, stdin);
// Xóa \n ở cuối chuỗi vừa nhập nếu có
if ((p=strchr(newCity.name, '\n')) != NULL){
*p = '\0';
}
printf("Nhap dien tich: ");
scanf("%f", &newCity.area);
printf("Nhap dan so: ");
scanf("%d", &newCity.population);
return newCity;
}
Lưu ý:
Chúng ta cần hàm getchar() để xóa bộ đệm, cụ thể là xóa bỏ ký tự ‘\n’ còn sót ở lần nhập mã tỉnh/thành phố trước đó. Nếu không xóa, hàm nhập chuỗi sẽ nhận biết ‘\n’ trong bộ đệm là hành động kết thúc nhập chuỗi.
Hàm fgets() đọc cả newline, nên ta cần xóa đi nếu không muốn trường name (tên) có ký tự này.
+ Các hàm thao tác với danh sách liên kết
Trong bài toán này, chúng ta có các hành động thêm, sửa, xóa Node. Do đó, chúng ta cần xây dựng các hàm sau:
Hàm addHead: Thêm Node vào đầu DSLK
Hàm addTail: Thêm Node vào cuối DSLK
Hàm addAt: Thêm Node vào chỉ số bất kỳ, kế thừa sử dụng hàm addHead và addTail
Hàm traverser: Duyệt danh sách
Hàm delHead: Xóa Node đầu tiên của DSLK
Hàm delTail: Xóa Node cuối của DSLK
Hàm delAt: Xóa Node tại chỉ số bất kỳ, cũng sẽ kế thừa hàm delHead và delTail ở trên
Hàm findIndexByCode: Tìm chỉ số của Node trong danh sách theo mã code (mã tỉnh/thành)
Các hàm này đều là hàm cơ bản của DSLK đã được mình trình bày chi tiết tại bài danh sách liên kết đơn. Do vậy, bạn nào chưa hiểu thì có thể quay lại đọc bài đó trước nha.
Các thao tác với danh sách, mình thích để trong vòng lặp để người dùng có thể lặp lại thao tác đó nếu cần. Người dùng sẽ có quyền chọn có thực hiện thao tác đó tiếp hay không ngay sau khi hoàn thành thao tác.
char option;
while (TRUE) {
// Thao tác ở đây
scanf("%c", &option);
if (option == 'N' || option == 'n'){
break;
}
}
# Hàm duyệt danh sách
void traverser(Node head){
printf("Danh sach hien tai:\n");
printf("------------------------------------------------------------------------------------------------------------\n");
printf("%10s%50s%20s%20s\n", "Ma Tinh/TP", "Tinh thanh", "Dien tich", "Dan so");
for(Node p = head; p != NULL; p = p->next){
printf("%10d%50s%20f%20d\n", p->city.code, p->city.name, p->city.area, p->city.population);
}
printf("------------------------------------------------------------------------------------------------------------\n");
}
Ở đây, ta đơn giản là bắt đầu từ Node đầu tiên (head) cho tới khi không thể nhảy sang Node tiếp theo.
Chúng ta in ra dạng bảng bằng cách sử dụng format trong hàm printf().
# Các hàm phục vụ thêm Node
// Thêm vào cuối
Node addTail(Node head, City value){
Node temp,p;// Khai báo 2 Node tạm temp và p
temp = createNode(value);//Gọi hàm createNode để khởi tạo Node temp có next trỏ tới NULL và giá trị là value
if(head == NULL){
head = temp; //Nếu linked list đang trống thì Node temp là head luôn
}
else{
p = head;// Khởi tạo p trỏ tới head
while(p->next != NULL){
p = p->next;//Duyệt danh sách liên kết đến cuối. Node cuối là Node có next = NULL
}
p->next = temp;//Gán next của thằng cuối = temp. Khi đó temp sẽ là thằng cuối(temp->next = NULL mà)
}
return head;
}
// Thêm vào đầu
Node addHead(Node head, City value){
Node temp = createNode(value); // Khởi tạo Node temp với data = value
if(head == NULL){
head = temp; // //Nếu linked list đang trống thì Node temp là head luôn
}else{
temp->next = head; // Trỏ next của temp = head hiện tại
head = temp; // Đổi head hiện tại = temp(Vì temp bây giờ là head mới mà)
}
return head;
}
// Thêm vào ở "chỉ số" (bắt đầu từ 0) bất kỳ, nếu muốn thêm theo "vị trí" (bắt đầu từ 1) thì giảm position đi 1 đơn vị
Node addAt(Node head, City value, int position){
position = position - 1; // Thêm theo vị trí
if(position == 0 || head == NULL){
head = addHead(head, value); // Nếu vị trí chèn là 0, tức là thêm vào đầu
}else{
// Bắt đầu tìm vị trí cần chèn. Ta sẽ dùng k để đếm cho vị trí
int k = 1;
Node p = head;
while(p != NULL && k != position){
p = p->next;
++k;
}
if(k != position){
// Nếu duyệt hết danh sách lk rồi mà vẫn chưa đến vị trí cần chèn, ta sẽ mặc định chèn cuối
// Nếu bạn không muốn chèn, hãy thông báo vị trí chèn không hợp lệ
head = addTail(head, value);
// printf("Vi tri chen vuot qua vi tri cuoi cung!\n");
}else{
Node temp = createNode(value);
temp->next = p->next;
p->next = temp;
}
}
return head;
}
Kết hợp với hàm khởi tạo City (createCity) phía trên, chúng ta có thể hoàn chỉnh thao tác thêm Node vào danh sách với hàm dưới đây:
Node delHead(Node head){
if(head == NULL){
printf("\nCha co gi de xoa het!");
}else{
head = head->next;
}
return head;
}
Node delTail(Node head){
if (head == NULL || head->next == NULL){
return delHead(head);
}
Node p = head;
while(p->next->next != NULL){
p = p->next;
}
p->next = p->next->next; // Cho next bằng NULL
return head;
}
// Xóa Node ở "chỉ số" (bắt đầu từ 0) bất kỳ
Node delAt(Node head, int position){
if(position == 0 || head == NULL || head->next == NULL){
head = delHead(head); // Nếu vị trí xóa là 0, tức là thêm vào đầu
}else{
// Bắt đầu tìm vị trí cần xóa. Ta sẽ dùng k để đếm cho vị trí
int k = 1;
Node p = head;
while(p->next->next != NULL && k != position){
p = p->next;
++k;
}
if(k != position){
// Nếu duyệt hết danh sách lk rồi mà vẫn chưa đến vị trí cần chèn, ta sẽ mặc định xóa cuối
// Nếu bạn không muốn xóa, hãy thông báo vị trí xóa không hợp lệ
head = delTail(head);
// printf("Vi tri xoa vuot qua vi tri cuoi cung!\n");
}else{
p->next = p->next->next;
}
}
return head;
}
Ở trên, chúng ta đã có hàm xóa ở chỉ số bất kỳ, vậy để xóa Node theo mã (code) cung cấp. Ta cần viết thêm 1 hàm tìm chỉ số của Node có dữ liệu thành phố mà mã code trùng với giá trị được cung cấp:
// Hàm tìm chỉ số của Node có dữ liệu thành phố mà mã code của nó trùng với giá trị cần tìm
int findIndexByCode(Node head, int code){
int index = -1;
for(Node p = head; p != NULL; p = p->next){
index++;
if (p->city.code == code){
return index;
}
}
return -1; // Không tìm thấy
}
Như vậy, để hoàn chỉnh thao tác xóa Node theo mã tỉnh/thành phố. Ta sẽ thêm 1 hàm sau:
Node removeNode(Node head){
int code;
char option;
while (TRUE) {
printf("========== Chon Node muon xoa ===============\n");
printf("Nhap ma tinh/thanh pho can xoa: ");
scanf("%d", &code);
int position = findIndexByCode(head, code);
if (position < 0){
printf("Khong tim thay du lieu can xoa! Xoa tiep (Y/n)? ");
}else {
head = delAt(head, position);
printf("Xoa thanh cong? Xoa tiep (Y/n)? ");
}
getchar(); // Bỏ qua '\n' trong bộ đệm
scanf("%c", &option);
if (option == 'N' || option == 'n'){
break;
}
}
return head;
}
Các chức năng thêm, xóa Node của danh sách đều có thể thay đổi Node head (Ex: xóa Node head). Do đó, các hàm này đều cần trả về giá trị là Node head mới sau khi thay đổi (có thể vẫn giữ nguyên).
# Hàm sửa giá trị Node trong DSLK
Hàm này chắc chắn không thể thay đổi Node head, do đó chúng ta sẽ dùng kiểu void
Đơn giản là ta duyệt qua danh sách, nếu tìm thấy mã code tương ứng, sẽ cho người dùng nhập dữ liệu mới cho Node đó.
void editNode(Node head){
int code;
char option;
City newCity;
while (TRUE) {
printf("========== Chon Node muon sua ===============\n");
printf("Nhap ma tinh/thanh pho can sua: ");
scanf("%d", &code);
int found = 0;
for(Node p = head; p != NULL; p = p->next){
if (p->city.code == code){
found = 1;
newCity = createCity();
p->city = newCity;
break;
}
}
if (found) {
printf("Sua thanh cong! Sua tiep (Y/n)? ");
}else {
printf("Khong tim thay du lieu! Sua tiep (Y/n)? ");
}
getchar(); // Bỏ qua '\n' trong bộ đệm
scanf("%c", &option);
if (option == 'N' || option == 'n'){
break;
}
}
}
# Hàm sắp xếp danh sách
void swapCityData(City *a, City *b){
City tmp = *a;
*a = *b;
*b = tmp;
}
// Hàm sắp xếp
// Nếu sort theo code, thì byCode = 1, byArea = 0
// Nếu sort theo area, thì byCode = 0, byArea = 1
// Nếu sắp xếp tăng dần thì desc = 0, giảm dần thì desc = 1
void sortCities(Node head, int byCode, int byArea, int desc){
for(Node p = head; p != NULL; p = p->next){
for(Node q = p->next; q != NULL; q = q->next){
if (desc){
if (byCode && p->city.code < q->city.code){
swapCityData(&p->city, &q->city);
}else if (byArea && p->city.area < q->city.area){
swapCityData(&p->city, &q->city);
}
}else {
if (byCode && p->city.code > q->city.code){
swapCityData(&p->city, &q->city);
}else if (byArea && p->city.area > q->city.area){
swapCityData(&p->city, &q->city);
}
}
}
}
}
Hàm swap chúng ta cần dùng con trỏ để hàm sử dụng trực tiếp giá trị được truyền vào. Ta chỉ cần đổi giá trị của chúng cho nhau, chứ không cần đổi 2 Node (rắc rối lắm).
Mình cố ý rút gọn code bằng cách cho các option sắp xếp vào trong hàm sortCities. Mặc dù không tường minh lắm nhưng tách ra thì dài quá.
Hàm sắp xếp không thay đổi Node head, nên hàm cũng không cần trả về giá trị như các hàm thêm hay xóa Node.
# Các hàm chức năng khác
Ngoài các hàm thêm, sửa, xóa trên, đề bài còn yêu cầu một số hàm tính tổng diện tích, tìm tỉnh/thành phố có diện tích/dân số lớn nhất, và cả sắp xếp danh sách.
Về cơ bản, các hàm này chỉ cần dựa trên thao tác duyệt danh sách (traveser) là có thể hoàn thành rồi.
// Hàm tính tổng diện tích các thành phố trong DSLK
float sumArea(Node head){
float sum = 0;
for(Node p = head; p != NULL; p = p->next){
sum += p->city.area;
}
return sum;
}
// Hàm tìm chỉ số của Node có diện tích lớn nhất (giả sử chỉ có 1)
// Nếu dữ liệu có nhiều hơn 1, chúng ta tìm max rồi duyệt lại 1 lần nữa để tìm ra các Node có giá trị = max đó
int indexOfMaxArea(Node head){
int maxIndex = 0, index = 0;
int maxArea = head->city.area;
for(Node p = head; p != NULL; p = p->next){
if (p->city.area > maxArea){
maxArea = p->city.area;
maxIndex = index;
}
index++;
}
return maxIndex;
}
// Hàm tìm Node có dân số lớn nhất
City maxByPopulation(Node head){
City city = head->city;
for(Node p = head; p != NULL; p = p->next){
if (p->city.population > city.population){
city = p->city;
}
}
return city;
}
Thao tác đọc dữ liệu từ tệp
Đề bài yêu cầu chúng ta cần khởi tạo danh sách ban đầu bằng cách đọc dữ liệu từ tệp. Do đó, chúng ta cần thêm 1 số hàm con nữa.
– Do dữ liệu tên tỉnh/thành phố có dấu cách nên mình chỉ biết cách đọc từng dòng vào xử lý. Do vậy, mình cần:
Hàm handleLineData: Tách dòng ra các thành phần con, cụ thể là cho 1 dòng dữ liệu, phải trả về cho mình 1 City. Mình dùng hàm strtok để làm việc tách chuỗi.
Hàm readData: Đọc dữ liệu từ file, mỗi dòng đọc được sẽ gọi tới hàm handleLineData phía trên. Sau khi có City, ta thêm nó vào danh sách bằng cách gọi tới addTail hoặc addHead hoặc addAt
// Hàm tách các thành phần của 1 dòng trong file
City handleLineData(char *line){
City city;
city.code = INVALID_CITY_CODE; // Khởi tạo giá trị không hợp lệ. Về sau ta có thể kiểm tra.
const char delimiter[] = "\t";
char *tmp;
tmp = strtok(line, delimiter);
if (tmp == NULL) {
printf("Du lieu khong dung dinh dang: %s", line);
exit(EXIT_FAILURE);
}
city.code = atoi(tmp);
int index = 0;
for (;;index++) {
tmp = strtok(NULL, delimiter);
if (tmp == NULL)
break;
if (index == 0){
strcpy(city.name, tmp);
}else if (index == 1){
city.area = (float)atof(tmp);
}else if (index == 2){
city.population = atoi(tmp);
}else {
printf("Du lieu khong dung dinh dang: %s", line);
exit(EXIT_FAILURE);
}
}
return city;
}
// Hàm đọc dữ liệu từ tập tin
Node readData(Node head, const char* fileName){
FILE* file = fopen(fileName, "r");
if(!file){
printf("Co loi khi mo file : %s\n", fileName);
exit(EXIT_FAILURE);
}
char line[500];
while (fgets(line, sizeof(line), file)) {
City city = handleLineData(line);
if (city.code != INVALID_CITY_CODE) {
head = addTail(head, city);
}
}
fclose(file);
return head;
}
Như vậy là hoàn thiện, việc còn lại chỉ là đưa chúng vào hàm main theo 1 trật tự do chúng ta quy định.
Bài Tập Danh Sách Liên Kết Đơn C++
X. Danh Sách Liên Kết Đơn Quản Lý Sinh Viên C++
Hôm nay mình định viết về phần tiếp theo của opencv xử lý ảnh nhưng còn bài tập môn cấu trúc dữ liệu chưa hoàn thành , sẵn tiện mình vừa làm vừa ra bài này luôn .
Đề bài
Code
#include"iostream"
#include"string"
#include"stdlib.h"
using namespace std;
struct SinhVien {
int mssv;
string name;
string diachi;
string ngaysinh;
string lop;
};
typedef struct SinhVien sinhvien;
struct node {
sinhvien *data;
struct node* link;
};
typedef struct node Node;
struct list {
Node* pHead;
Node* pTail;
};
typedef struct list List;
void KhoiTaoList(List &l) {
l.pHead = l.pTail = NULL;
}
void Input_ThongTin(sinhvien *sv) {
cin.ignore();
cout << "Nhap Ten sinh vien: \n";
fflush(stdin);
getline(cin,sv->name);
cout << "Nhap Ma so sinh vien : ";
cin >> sv->mssv;
cin.ignore();
cout << "Nhap dia chi sinh vien :\n";
getline(cin,sv->diachi);
fflush(stdin);
cout << "Nhap ngay sinh cua sinh vien:\n";
getline(cin, sv->ngaysinh);
fflush(stdin);
cout << "Nhap lop cua sinh vien : ";
getline(cin, sv->lop);
}
Node *KhoiTaoNode() {
sinhvien* sv = new sinhvien;
Input_ThongTin(sv);
Node* p = new Node;
if (p == NULL) {
cout << "full ram ko thể tao thêm\n";
return 0;
}
p->data = sv;
p->link = NULL;
return p;
}
void ThemVaoDauMotSinhVien(List &l, Node *p) {
if (l.pHead == NULL) {
l.pHead = l.pTail= p;
}
else {
p->link = l.pHead;
l.pHead = p;
}
}
void Show(List l) {
for (Node* k = l.pHead; k != NULL; k = k->link) {
cout << "MSSV : " << k->data->mssv<<endl;
cout << "Ten : " << k->data->name << endl;
cout << "Dia Chi : " << k->data->diachi << endl;
cout << "Ngay Sinh : " << k->data->ngaysinh << endl;
cout << "Lop : " << k->data->lop << endl;
cout << "==============================SV================\n";
}
}
void showNode(Node *k) {
cout << "==============================SV================\n";
cout << "MSSV : " << k->data->mssv << endl;
cout << "Ten : " << k->data->name << endl;
cout << "Dia Chi : " << k->data->diachi << endl;
cout << "Ngay Sinh : " << k->data->ngaysinh << endl;
cout << "Lop : " << k->data->lop << endl;
}
void DelSinhVien(List& l) {
string del;
cin.ignore();
cout << "Nhap Ma So SV hoac Ten SV Can Xoa : \n";
fflush(stdin);
getline(cin, del);
Node* g = new Node;
if (del.compare(l.pHead->data->name) == 0 && l.pHead->link ==NULL) {
l.pHead = NULL;
}
else{
for (Node* k = l.pHead; k != NULL; k = k->link) {
if (del.compare(k->data->name) == 0) {
g->link = k->link;
k = g;
}
g = k;
}
}
}
void search(List l ) {
int mssv;
cout << "nhap Ma So Sinh Vien Can Tim Kiem : ";
cin >> mssv;
for (Node* k = l.pHead; k != NULL; k = k->link) {
if (k->data->mssv == mssv) {
showNode(k);
}
}
}
void upgrade(List& l) {
int mssv;
cout << "Nhap Ma So sinh vien can chinh sua thong tin : ";
cin >> mssv;
for (Node* k = l.pHead; k != NULL; k = k->link) {
if (k->data->mssv == mssv) {
cout << "Moi ban sua thong tin sinh vien co ma so : " << mssv << endl;
Input_ThongTin(k->data);
}
}
}
void DanhSachSinhVienChuaXepLop(List l) {
for (Node* k = l.pHead; k != NULL; k = k->link) {
if (k->data->lop == "") {
cout << "Danh Sach Sinh Vien Chua Xep Lop " << endl;
showNode(k);
}
}
}
void ChucNang(List &l) {
int n;
cout << "======Danh Sach Chuc Nang=========\n";
cout << "1 => Nhap 1 sinh vien moi .\n";
cout << "2 => In danh sach sinh vien .\n ";
cout << "3 => Tim kiem sinh vien theo ma so .\n";
cout << "4 => Sua thong tin sinh vien\n";
cout << "5 => Xoa sinh vien khoi danh sach\n";
cout << "6 =>Lay danh sach sinh vien chua xep lop\n";
cout << "0 = >Thoat chuong trinh\n";
while (1){
cout << "Nhap chuc nang ban chon : ";
cin >> n;
if (n == 1) {
cout << "Moi Ban nhap thong tin 1 sinh vien : \n";
Node* p = KhoiTaoNode();
ThemVaoDauMotSinhVien(l, p);
}
if (n == 3) {
search(l);
}
if (n == 2) {
cout << "Danh Sach Sinh Vien : \n";
Show(l);
}
if (n == 4) {
upgrade(l);
}
if (n == 5) {
DelSinhVien(l);
}
if (n == 6) {
DanhSachSinhVienChuaXepLop(l);
}
if (n == 0) {
break;
}
}
}
//hàm in danh sách sinh vien
int main() {
List l;
KhoiTaoList(l);
ChucNang(l);
system("pause");
return 0;
}
Trong bài học hôm nay chúng ta sẽ cùng tìm hiểu về hàm xây dựng (constructor) trong C++ nhé. Vậy hàm xây dựng (constructor) trong C++ là gì? Nó được sử dụng như thế nào? Nó có khác gì so với các hàm bình thường trong C++.
I. Constructor Trong C++ Là Gì
+ Constructor Mặc Định Trong C++
Một constructor không có tham số truyền vào (hoặc có tham số mà tất cả chúng đều có giá trị mặc định) được gọi là constructor mặc định. Khi sử dụng một constructor, nếu không có giá trị khởi tạo nào do người dùng cung cấp được truyền cho constructor này, thì constructor mặc định sẽ được gọi.
Dưới đây là ví dụ về một class có một constructor mặc định:
/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/
#include <iostream>
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction() // default constructor
{
m_numerator = 0;
m_denominator = 1;
}
int getNumerator() { return m_numerator; }
int getDenominator() { return m_denominator; }
double getValue() { return static_cast<double>(m_numerator) / m_denominator; }
};
int main()
{
Fraction frac; // Since no arguments, calls Fraction() default constructor
std::cout << frac.getNumerator() << "/" << frac.getDenominator() << '\n';
return 0;
}
Class này được thiết kế để giữ một giá trị phân số dưới dạng tử số và mẫu số kiểu integer. Một constructor mặc định có tên là Fraction (cùng tên với class) đã được định nghĩa bên trong class này.
Bởi vì chúng ta đang khởi tạo một đối tượng thuộc kiểu Fraction mà không có đối số nào được truyền vào, nên constructor mặc định sẽ được gọi ngày sau khi bộ nhớ được cấp phát cho đối tượng này, và đối tượng của chúng ta sẽ được khởi tạo.
–>
Đoạn chương trình ví dụ ở trên sẽ in ra kết quả:
0/1
Lưu ý rằng tử số (numerator) và mẫu số (denominator) của chúng ta đã được khởi tạo bằng các giá trị mà ta đã thiết lập bên trong constructor mặc định! Nếu không có constructor mặc định, tử số và mẫu số sẽ có các giá trị rác cho đến khi chúng ta gán rõ ràng cho chúng các giá trị hợp lý, hoặc là khởi tạo chúng bằng các phương tiện khác (hãy nhớ rằng: Các biến thuộc các kiểu dữ liệu cơ bản đều không được khởi tạo theo mặc định).
+ Constructor Có Tham Số Trong C++
Một constructor mặc định trong C++ không có bất kỳ tham số nào, nhưng nếu bạn cần, một constructor có thể có các tham số. Điều này giúp bạn gán giá trị khởi tạo tới một đối tượng tại thời điểm tạo nó, như trong ví dụ sau:
#include <iostream>
using namespace std;
class Line
{
public:
void setChieuDai( double dai );
double layChieuDai( void );
Line(double dai); // Day la phan khai bao constructor
private:
double chieudai;
};
// phan dinh nghia cac ham thanh vien, bao gom constructor
Line::Line( double dai)
{
cout << "Doi tuong dang duoc tao, chieudai = " << dai << endl;
chieudai = dai;
}
void Line::setChieuDai( double dai )
{
chieudai = dai;
}
double Line::layChieuDai( void )
{
return chieudai;
}
// ham main cua chuong trinh
int main( )
{
Line line(10.0);
// lay chieu dai da duoc khoi tao ban dau.
cout << "Chieu dai cua line la: " << line.layChieuDai() <<endl;
// thiet lap chieu dai mot lan nua
line.setChieuDai(6.0);
cout << "Chieu dai cua line la: " << line.layChieuDai() <<endl;
return 0;
}
Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:
Constructor Có Tham Số Trong C++
IV. Ý Nghĩa Của Constructor Trong C++
Trong C++, hàm xây dựng (constructor) là một phương thức đặc biệt được gọi tự động tại thời điểm đối tượng được tạo. Mục đích của hàm xây dựng là để khởi tạo các thành viên dữ liệu của đối tượng.
Hàm xây đựng phải cùng tên với tên lớp và không có bất cứ kiểu gì trả về kể cả kiểu void.
Ví dụ ta có lớp học sinh có 2 thuộc tính là mã số học sinh và tên, thì hàm xây dựng có thể định nghĩa cho lớp học sinh như sau:
Ví dụ
class HocSinh {
int mshs;
string ten;
public:
HocSinh();
HocSinh(int m) {
mshs = m;
}
HocSinh(string t) {
ten = t;
}
HocSinh(int m, string t) {
mshs = m;
ten = t;
}
};
Từ chương trình trên ta thấy, chúng ta có thể tạo hàm xây dựng có tham số hoặc không có tham số đều được.
Trong C++ hàm xây dựng không có tham được gọi là hàm xây dựng mặc nhiên (Default constructor), hàm xây dựng có tham số được gọi là hàm xây dựng tham số (Parameterized constructor). Chúng ta sẽ cùng tìm hiểu từng loại hàm xây dựng một nhé
V. Copy Constructor Trong C++
Copy constructor là một constructor mà tạo một đối tượng bằng việc khởi tạo nó với một đối tượng của cùng lớp đó, mà đã được tạo trước đó. Copy constructor được sử dụng để:
Khởi tạo một đối tượng từ đối tượng khác với cùng kiểu.
Sao chép một đối tượng để truyền nó như là một tham số tới một hàm.
Sao chép một đối tượng để trả về nó từ một hàm.
Nếu một copy constructor không được định nghĩa trong một lớp, compiler sẽ tự nó định nghĩa một copy constructor. Nếu lớp có các biến con trỏ và có một số cấp phát bộ nhớ động, thì nó là một sự cần thiết để có một copy constructor. Form phổ biến nhất của copy constructor trong C++ là:
Ở đây, obj là một tham chiếu tới một đối tượng mà đang được sử dụng để khởi tạo đối tượng khác.
#include <iostream>
using namespace std;
class Line
{
public:
int layChieuDai( void );
Line( int dai ); // Day la mot constructor don gian
Line( const Line &obj); // Day la copy constructor
~Line(); // Day la destructor
private:
int *contro;
};
// Phan dinh nghia cac ham thanh vien, bao gom constructor, copy constructor, destructor
Line::Line(int dai)
{
cout << "Constructor: cap phat bo nho cho con tro contro" << endl;
// cap phat bo nho cho con tro
contro = new int;
*contro = dai;
}
Line::Line(const Line &obj)
{
cout << "Copy constructor: cap phat bo nho cho con tro contro" << endl;
contro = new int;
*contro = *obj.contro; // sao chep gia tri
}
Line::~Line(void)
{
cout << "Giai phong bo nho!" << endl;
delete contro;
}
int Line::layChieuDai( void )
{
return *contro;
}
void hienThi(Line obj)
{
cout << "Chieu dai cua line la: " << obj.layChieuDai() <<endl;
}
// ham main cua chuong trinh
int main( )
{
Line line(50);
hienThi(line);
return 0;
}
Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:
Bạn theo dõi cùng ví dụ trên những với một sự thay đổi nhỏ để tạo đối tượng khác bởi sử dụng đối tượng đã tồn tại với cùng kiểu.
#include <iostream>
using namespace std;
class Line
{
public:
int layChieuDai( void );
Line( int dai ); // Day la mot constructor don gian
Line( const Line &obj); // Day la copy constructor
~Line(); // Day la destructor
private:
int *contro;
};
// Phan dinh nghia cac ham thanh vien, bao gom constructor, copy constructor, destructor
Line::Line(int dai)
{
cout << "Constructor: cap phat bo nho cho con tro contro" << endl;
// cap phat bo nho cho con tro
contro = new int;
*contro = dai;
}
Line::Line(const Line &obj)
{
cout << "Copy constructor: cap phat bo nho cho con tro contro" << endl;
contro = new int;
*contro = *obj.contro; // sao chep gia tri
}
Line::~Line(void)
{
cout << "Giai phong bo nho!" << endl;
delete contro;
}
int Line::layChieuDai( void )
{
return *contro;
}
void hienThi(Line obj)
{
cout << "Chieu dai cua line la: " << obj.layChieuDai() <<endl;
}
// Ham main cua chuong trinh
int main( )
{
Line line1(45);
Line line2 = line1; // Lenh nay cung goi copy constructor
hienThi(line1);
hienThi(line2);
return 0;
}
Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:
Copy Constructor Trong C++
VI. Move Constructor Trong C++
Hàm getContainer() ở đây là một rvalue, vì vậy nó có thể được tham chiếu bởi một tham chiếu rvalue. Ngoài sử dụng tham chiếu rvalue, chúng ta cũng có thể nạp chồng các hàm. Lần này, chúng ta sẽ nạp chồng cho Hàm tạo của lớp Container và Hàm tạo mới này sẽ được gọi là hàm tạo di chuyển (move constructor).
Hàm tạo di chuyển lấy tham chiếu rvalue làm đối số và nạp chồng vì hàm tạo sao chép lấy tham chiếu const lvalue làm đối số. Trong hàm tạo di chuyển, chúng ta chỉ di chuyển các biến thành viên của đối tượng được truyền vào các biến thành viên của đối tượng mới, thay vì cấp phát bộ nhớ mới cho chúng.
Hãy xem hàm khởi tạo di chuyển cho lớp Container như sau,
Container(Container && obj)
{
// Just copy the pointer
m_Data = obj.m_Data;
// Set the passed object's member to NULL
obj.m_Data = NULL;
std::cout<<"Move Constructor"<<std::endl;
}
Trong hàm tạo di chuyển, chúng ta chỉ sao chép con trỏ. Bây giờ biến thành viên m_Data trỏ đến cùng một bộ nhớ trên heap. Sau đó, ta đặt m_Data của đối tượng được truyền thành NULL. Vì vậy, chúng ta đã không phân bổ bất kỳ bộ nhớ nào trên heap trong hàm tạo di chuyển mà chỉ chuyển quyền kiểm soát bộ nhớ.
Bây giờ nếu chúng ta tạo vector lớp Container và đẩy một đối tượng trả về từ getContainer() vào đó. Sau đó, một đối tượng mới sẽ được tạo từ đối tượng tạm thời này nhưng vì getContainer() là một rvalue, vì vậy hàm tạo di chuyển của đối tượng của lớp Container mới này sẽ được gọi và trong bộ nhớ đó sẽ chỉ được dịch chuyển. Vì vậy, thực tế trên heap, chúng ta sẽ chỉ tạo một mảng các số nguyên.
Tương tự như hàm tạo di chuyển (Move Constructor), chúng ta có thể có toán tử gán di chuyển (Move Assignment). Hãy xem ví dụ đầy đủ như sau,
#include <iostream>
#include <vector>
class Container {
int * m_Data;
public:
Container() {
//Allocate an array of 20 int on heap
m_Data = new int[20];
std::cout << "Constructor: Allocation 20 int" << std::endl;
}
~Container() {
if (m_Data) {
delete[] m_Data;
m_Data = NULL;
}
}
//Copy Constructor
Container(const Container & obj) {
//Allocate an array of 20 int on heap
m_Data = new int[20];
//Copy the data from passed object
for (int i = 0; i < 20; i++)
m_Data[i] = obj.m_Data[i];
std::cout << "Copy Constructor: Allocation 20 int" << std::endl;
}
//Assignment Operator
Container & operator=(const Container & obj) {
if(this != &obj)
{
//Allocate an array of 20 int on heap
m_Data = new int[20];
//Copy the data from passed object
for (int i = 0; i < 20; i++)
m_Data[i] = obj.m_Data[i];
std::cout << "Assigment Operator: Allocation 20 int" << std::endl;
}
}
// Move Constructor
Container(Container && obj)
{
// Just copy the pointer
m_Data = obj.m_Data;
// Set the passed object's member to NULL
obj.m_Data = NULL;
std::cout<<"Move Constructor"<<std::endl;
}
// Move Assignment Operator
Container& operator=(Container && obj)
{
if(this != &obj)
{
// Just copy the pointer
m_Data = obj.m_Data;
// Set the passed object's member to NULL
obj.m_Data = NULL;
std::cout<<"Move Assignment Operator"<<std::endl;
}
}
};
// Create am object of Container and return
Container getContainer()
{
Container obj;
return obj;
}
int main() {
// Create a vector of Container Type
std::vector<Container> vecOfContainers;
//Add object returned by function into the vector
vecOfContainers.push_back(getContainer());
Container obj;
obj = getContainer();
return 0;
}
Kết quả:
Constructor: Allocation 20 int
Move Constructor
Constructor: Allocation 20 int
Constructor: Allocation 20 int
Move Assignment Operator
Trong ví dụ trên, hàm tạo di chuyển của lớp Container sẽ được gọi vì getContainer() trả về rvalue và lớp Container có hàm nạp chồng của hàm tạo chấp nhận rvalue trong tham số. Trong bộ nhớ hàm tạo di chuyển này chỉ được dịch chuyển con trỏ đến đối tượng được truyền. Tương tự, khi ta gán một đối tượng, toán tử gán di chuyển được sử dụng thay vì phép gán thông thường (dòng 71 và 72).
VII. Kế Thừa Constructor Trong C++
Khi kế thừa một hoặc nhiều lớp cha, chúng ta thường sử dụng lại phương thức khởi tạo (constructor method) của lớp cha vì cos nhiều lợi ích khác nhau như không phải viết lại cùng một đoạn code, khi sửa chỉ cần sửa 1 nơi,…
+ Sử dụng lại constructor trong C++
Để sử dụng constructor của lớp cha, chúng ta khai báo constructor của lớp con có dạng như sau:
LopCon::LopCon(type var1, type var2) : LopCha(var1, var2) {
// code của bạn
}
Ví dụ minh họa
Giả sử chúng ta có lớp A như sau:
class A {
protected:
int x;
public:
A() {
x = 0;
}
A(int x) {
this->x = x;
}
void xuatA() {
std::cout << "x = " << x << "n";
}
};
Và lớp B kế thừa lớp A:
class B: public A {
private:
int y;
public:
B(int x, int y) : A(x) {// su dung constructor cua lop A
this->y = y;
}
void xuatB() {
A::xuatA(); // su dung method cua lop A
std::cout << "y = " << y << "n";
}
};
Như các bạn thấy, trong ví dụ trên tôi đã sử dụng lại constructor của lớp A khi cài đặt constructor của lớp B, kết quả là tôi không phải viết lại đoạn code khởi tạo cho biến x mà chỉ phải viết đoạn code khởi tạo cho biến y
Nhìn xuống phía dưới, các bạn sẽ thấy dòng A::xuatA();, đó là cách sử dụng member của một lớp mà ở đây là lớp cha. Chúng ta cũng có thể sử dụng cách này để gọi static member của bất kì lớp nào với cú pháp như sau:
TenLop::TenMember
Toàn bộ code của chương trình minh họa:
/* Su dung member cua lop cha */
#include <iostream>
class A {
protected:
int x;
public:
A() {
x = 0;
}
A(int x) {
this->x = x;
}
void xuatA() {
std::cout << "x = " << x << "n";
}
};
class B: public A {
private:
int y;
public:
B(int x, int y) : A(x) {// su dung constructor cua lop A
this->y = y;
}
void xuatB() {
A::xuatA(); // su dung method cua lop A
std::cout << "y = " << y << "n";
}
};
int main() {
A a;
B b(5, 10);
a.xuatA();
b.xuatB();
return 0;
}
Kết quả khi chạy:
$ ./a.out
x = 0
x = 5
y = 10
VIII. Các Kiểu Constructor Trong C++
+ Hàm xây dựng trong C++
Trong C++, hàm xây dựng (constructor) là một phương thức đặc biệt được gọi tự động tại thời điểm đối tượng được tạo. Mục đích của hàm xây dựng là để khởi tạo các thành viên dữ liệu của đối tượng.
Hàm xây đựng phải cùng tên với tên lớp và không có bất cứ kiểu gì trả về kể cả kiểu void.
Ví dụ ta có lớp học sinh có 2 thuộc tính là mã số học sinh và tên, thì hàm xây dựng có thể định nghĩa cho lớp học sinh như sau:
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class HocSinh {
int mshs;
string ten;
public:
HocSinh();
HocSinh(int m) {
mshs = m;
}
HocSinh(string t) {
ten = t;
}
HocSinh(int m, string t) {
mshs = m;
ten = t;
}
};
Từ chương trình trên ta thấy, chúng ta có thể tạo hàm xây dựng có tham số hoặc không có tham số đều được.
Trong C++ hàm xây dựng không có tham được gọi là hàm xây dựng mặc nhiên (Default constructor), hàm xây dựng có tham số được gọi là hàm xây dựng tham số (Parameterized constructor). Chúng ta sẽ cùng tìm hiểu từng loại hàm xây dựng một nhé
+ Hàm xây dựng mặc nhiên
Như đã nói ở trên hàm xây dựng mặc nhiên là hàm xây dựng không có tham số. Nó sẽ tự động được gọi tại thời điểm đối tượng được tạo.
Nếu lớp chúng ta không có hàm xây dựng nào thì mặc nhiên chương trình chúng ta sẽ tạo cho lớp đó một hàm xây dựng mặc nhiên.
Chúng ta cùng xem xét một ví dụ đơn giản về hàm xây dựng mặc nhiên trong C++ như sau:
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;
class NhanVien {
public:
NhanVien() {
cout << "Ham xay dung mac nhien tu dong duoc goi." << endl;
}
};
int main() {
NhanVien e1; //khoi tao doi tuong nhan vien 1
NhanVien e2; //khoi tao doi tuong nhan vien 2
return 0;
}
Và kết quả sau khi thực thi chương trình trên như sau:
Hàm xây dựng mặc nhiên
+ Hàm xây dựng tham số
Như đã đề cập ở trên thì một hàm xây dựng có tham số được gọi là xây dựng tham số. Mục đích của hàm xây dựng tham số là để cung cấp các giá trị khác nhau cho các đối tượng riêng biệt.
Chúng ta cùng xem xét một ví dụ đơn giản về hàm xây dựng tham số trong C++ như sau:
Và kết quả sau khi thực thi chương trình trên như sau:
Ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
using namespace std;
class NhanVien {
int msnv;
string ten;
int tuoi;
public:
NhanVien(int m) {
msnv = m;
}
NhanVien(int m, string tn) {
msnv = m;
ten = tn;
tuoi = 20;
}
NhanVien(int m, string tn, int t) {
msnv = m;
ten = tn;
tuoi = t;
}
void HienThi() {
cout << ten << endl;
cout << " Ma so nhan vien: " << msnv << endl;
cout << " Tuoi: " << tuoi << endl;
}
};
int main() {
NhanVien n1 = NhanVien(111231, "Nguyen Van A", 25);
NhanVien n2 = NhanVien(213214, "Nguyen Van B");
NhanVien n3 = NhanVien(213215, "Nguyen Van C");
n1.HienThi();
n2.HienThi();
n3.HienThi();
return 0;
}
Và kết quả sau khi thực thi chương trình trên như sau:
Hàm xây dựng tham số
Lưu ý:
Khi chúng ta không định nghĩa hàm xây dựng thì mặc nhiên chương trình sẽ tạo cho lớp đó một hàm xây dựng mặc nhiên. Chúng ta chỉ có 1 cách để khởi tạo đối tượng duy nhất đó là TenHamXayDung p1, TenHamXayDung p2, …
Khi chúng ta định nghĩa hàm xây dưng kể cả mặc nhiên hay tham số thì chúng ta số cách để tạo đối tương đúng bằng số hàm xây dựng mà chúng ta đã định nghĩa
Khi chúng ta khai báo đối tượng không khớp với bất kỳ hàm xây dựng nào mà chúng ta đã định nghĩa thì chương trình chúng ta sẽ báo lỗi.
Tránh khai báo nhiều hàm xây dựng vô nghĩa mà chúng ta không sử dụng để khỏi tạo đối tượng.
Mục đích chính của hàm xây dựng là dùng để khởi tạo giá trị cho đối tượng, tuy nhiên chúng ta có thể sử dụng hàm xây dựng theo mục đích của riêng mình.
IX. Bài Tập Constructor Trong C++
Một class contructor là một hàm thành viên đặc biệt của một lớp mà được thực thi bất cứ khi nào chúng ta tạo các đối tượng mới của lớp đó.
Một constructor sẽ có tên giống như lớp và nó không có bất kỳ kiểu trả về, kể cả kiểu void. Constructor có thể rất hữu ích để thiết lập các giá trị khởi tạo cho các biến thành viên cụ thể.
Ví dụ sau giải thích khái niệm contructor trong C++:
#include <iostream>
using namespace std;
class Line
{
public:
void setChieuDai( double dai );
double layChieuDai( void );
Line(); // Day la constructor
private:
double chieudai;
};
// phan dinh nghia cac ham thanh vien, bao gom ca constructor
Line::Line(void)
{
cout << "Doi tuong da duoc tao!" << endl;
}
void Line::setChieuDai( double dai )
{
chieudai = dai;
}
double Line::layChieuDai( void )
{
return chieudai;
}
// Ham main cua chuong trinh
int main( )
{
Line line;
// thiet lap chieu dai cua line
line.setChieuDai(6.0);
cout << "Chieu dai cua duong la: " << line.layChieuDai() <<endl;
return 0;
}
Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:
Bài Tập Constructor Trong C++
Constructor được tham số hóa trong C++
Một constructor mặc định trong C++ không có bất kỳ tham số nào, nhưng nếu bạn cần, một constructor có thể có các tham số. Điều này giúp bạn gán giá trị khởi tạo tới một đối tượng tại thời điểm tạo nó, như trong ví dụ sau:
#include <iostream>
using namespace std;
class Line
{
public:
void setChieuDai( double dai );
double layChieuDai( void );
Line(double dai); // Day la phan khai bao constructor
private:
double chieudai;
};
// phan dinh nghia cac ham thanh vien, bao gom constructor
Line::Line( double dai)
{
cout << "Doi tuong dang duoc tao, chieudai = " << dai << endl;
chieudai = dai;
}
void Line::setChieuDai( double dai )
{
chieudai = dai;
}
double Line::layChieuDai( void )
{
return chieudai;
}
// ham main cua chuong trinh
int main( )
{
Line line(10.0);
// lay chieu dai da duoc khoi tao ban dau.
cout << "Chieu dai cua line la: " << line.layChieuDai() <<endl;
// thiet lap chieu dai mot lan nua
line.setChieuDai(6.0);
cout << "Chieu dai cua line la: " << line.layChieuDai() <<endl;
return 0;
}
Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:
Bài Tập Constructor Trong C++
Sử dụng danh sách khởi tạo cho các trường khởi tạo
Trong trường hợp constructor được tham số hóa, bạn có thể sử dụng cú pháp sau để khởi tạo các trường.
Nếu với một lớp trong C, bạn có nhiều trường X, Y, Z, … để được khởi tạo, thì bạn có thể sử dụng cú pháp tương tự và phân biệt các trường bởi dấu phảy, như sau:
Một destructor là một hàm thành viên đặc biệt của một lớp mà được thực thi bất cứ khi nào một đối tượng của lớp đó ra khỏi phạm vi hoặc bất cứ khi nào biểu thức delete được áp dụng tới một con trỏ tới đối tượng của lớp đó.
Một destructor sẽ có cùng tên với lớp và được đặt trước bỏi ký hiệu ~ và nó có thể: không trả về một giá trị và không nhận bất kỳ tham số nào. Destructor có thể rất hữu ích để giải phóng resource trước khi thoát khỏi chương trình, ví dụ: đóng file, giải phóng bộ nhớ, …
Ví dụ sau giải thích khái niệm về destructor trong C++:
#include <iostream>
using namespace std;
class Line
{
public:
void setChieuDai( double dai );
double layChieuDai( void );
Line(); // Day la phan khai bao constructor
~Line(); // Day la phan khai bao destructor
private:
double chieudai;
};
// phan dinh nghia ham thanh vien, bao gom constructor va destructor
Line::Line(void)
{
cout << "Doi tuong dang duoc tao" << endl;
}
Line::~Line(void)
{
cout << "Doi tuong dang bi xoa!" << endl;
}
void Line::setChieuDai( double dai )
{
chieudai = dai;
}
double Line::layChieuDai( void )
{
return chieudai;
}
// ham main cua chuong trinh
int main( )
{
Line line;
// Thiet lap chieu dai cua line
line.setChieuDai(6.0);
cout << "Chieu dai cua line la: " << line.layChieuDai() <<endl;
return 0;
}
Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:
Hàm char *strtok(char *str, const char *delim) chia chuỗi str thành 1 dãy những token được phân biệt riêng rẽ bởi dấu tách delim (ví dụ: dấu phảy, …).
II. Khai Báo Hàm Strtok() Trong C++
Dưới đây là phần khai báo cho strtok() trong C:
char *strtok(char *str, const char *delim)
Tham số
str — Nội dung của chuỗi này được sửa đổi và được chia thành những chuỗi nhỏ hơn (các token).
delim — Đây là chuỗi chứa Delimiter (chỉ những dấu tách). Chúng có thể vô cùng đa dạng tùy vào từng lời gọi.
Trả về giá trị
Hàm này trả về con trỏ đến token cuối cùng được tìm thấy trong chuỗi. Một con trỏ null được trả về nếu không thu được token nào.
III. Ví Minh Họa Khai Báo Hàm Strtok() Trong C++
#include string.h
#include stdio.h
int main()
{
char str[80] = "Hoc C - co ban va nang cao - tai QTM";
const char s[2] = "-";
char *token;
/* lay token dau tien */
token = strtok(str, s);
/* duyet qua cac token con lai */
while( token != NULL )
{
printf( " %sn", token );
token = strtok(NULL, s);
}
return(0);
}
IV. Tách Toàn Bộ Chuỗi Con Từ Chuỗi Trong C
Hướng dẫn cách tách chuỗi chuỗi trong C. Bạn sẽ học được cách tách chuỗi trong C bằng các hàm tách chuỗi strtok(), cũng như các lưu ý khi sử dụng hàm strtok() trong C sau bài học này.
Tách chuỗi trong c | hàm strtok
Hàm strtok() trong C là một hàm có sẵn trong header file string.h, giúp chúng ta tách chuỗi trong chuỗi C bằng ký tự chỉ định.
Để có thể sử dụng được hàm strtok(), chúng ta cần phải viết thêm dòng #include <string.h> để load header file string.h vào trong chuơng trình.
Chúng ta sử dụng hàm strtok() để tách chuỗi trong chuỗi C với cú pháp sau đây:
strtok(str, sep)
Trong đó:
str là chuỗi cần tách
sep là ký tự phân tách. (sep viết tắt separator)
Hàm strtok() sẽ trả về vị trí đầu chuỗi của chuỗi con đầu tiên được phân tách bằng dấu phân cách từ chuỗi ban đầu dưới dạng con trỏ chuỗi trong C. Trong trường hợp không tìm thấy ký tự phân tách , giá trị NULL sẽ được trả về.
Cơ chế của hàm strtok() là điền ký tự kết thúc chuỗi \0 vào chuỗi ban đầu mỗi khi tìm thấy ký tự phân tách, do đó cần lưu ý là hàm strtok() sẽ làm biến đổi chuỗi ban đầu.
Lại nữa, hàm strtok() chỉ có thể tách một chuỗi con bằng dấu phân tách từ chuỗi ban đầu trong mỗi lần thực thi mà thôi.
Ví dụ cụ thể:
#include <stdio.h>
#include <string.h>
int main(void){
char str[50] = "Ha Noi,Ninh Binh,Nam Dinh,Thanh Hoa";
//Khai báo con trỏ chuỗi để chứa kết quả
char * p;
p = strtok(str, ",");
printf("%s\n", p);
}
//Ha Noi
Ở ví dụ này, hàm strtok() sẽ hoạt động với các bước như sau:
Hàm strtok() bắt đầu tìm vị trí ký tự phân tách là dấu , ở bên trong chuỗi str.
Sau khi tìm thấy vị trí dấu phẩy đầu tiên, hàm strtok() sẽ điền ký tự kết thúc chuỗi \0 vào vị trí tìm thấy. Khi đó, chuỗi ban đầu sẽ chuyển thành dạng Ha Noi\0Ninh Binh,Nam Dinh,Thanh Hoa.
Hàm strtok() quay trở về đầu chuỗi ban đầu và trả về vị trí đầu chuỗi này dưới dạng con trỏ chuỗi trong C. Cuối cùng khi in con trỏ chuỗi này, chuỗi sẽ được in ra từ vị trí đầu chuỗi là ký tự H cho đến ký tự thúc chuỗi \0 (Ha Noi\0), do đó chuỗi kết quả Ha Noi sẽ được in ra màn hình.
Tách toàn bộ chuỗi con từ chuỗi trong C | hàm strtok
Ở phần trên chúng ta đã biết hàm strtok() trong C chỉ có thể giúp chúng ta tách một chuỗi con bằng dấu phân tách từ chuỗi ban đầu trong mỗi lần sử dụng mà thôi.
Do đó, để có thể tách toàn bộ chuỗi con từ chuỗi ban đầu trong C thì sau lần tách đầu tiên, chúng ta cần phải tạo ra một vòng lặp để lần lượt tách các chuỗi con con lại từ chuỗi ban đầu bằng hàm strtok().
Và lựa chọn sáng giá ở đây chính là vòng lặp while, khi mà chúng ta vốn không biết rõ số vòng lặp cần dùng là bao nhiêu trong chương trình.
Chúng ta sẽ viết chương trình tách toàn bộ chuỗi con từ chuỗi trong c như sau:
#include <stdio.h>
#include <string.h>
int main(void){
char str[50] = "Ha Noi,Ninh Binh,Nam Dinh,Thanh Hoa";
char * p;
//Tách chuỗi con lần đầu tiên
p = strtok(str, ",");
printf("%s\n", p);
//Tách chuỗi con từ lần thứ 2 trở đi
//Bằng cách sử dụng hàm strok cho tới khi kết quả NULL được trả về.
while(p != NULL) {
//Chỉ dịnh đối số NULL trong hàm strtok để tiếp tục tách chuỗi ban đầu
p = strtok(NULL, ",");
if(p != NULL) {
printf("%s\n", p);
}
}
return 0;
}
//Ha Noi
//Ninh Binh
//Nam Dinh
//Thanh Hoa
Ở đây chúng ta cần phải lưu ý rằng:
Lần gọi hàm strtok() đầu tiên và các lần tiếp theo là khác nhau.
Trong lần gọi strtok đầu tiên, chúng ta phải chỉ định đối số của hàm là chuỗi ban đầu để bắt đầu tách chuỗi. Trong các lần gọi tiếp theo, chúng ta phải chỉ định NULL làm đối số của hàm. Đối số này sẽ yêu cầu hàm tiếp tục tách từ chuỗi ban đầu đã truyền vào từ trước đó. Nếu chúng ta chỉ định lại chuỗi ban đầu hoặc một chuỗi khác thì kết quả hàm sẽ không tiếp tục tách nữa mà sẽ bắt đầu xử lý lại từ đầu.
Khi không còn tìm thấy ký tự phân tách từ trong chuỗi ban đầu nữa, kết quả NULL sẽ được trả về, và khi đó thì vòng lặp While sẽ kết thúc.
V. Lưu Ý Khi Tách Chuỗi Trong C Bằng Hàm Strtok
Lưu ý thứ nhất
Giống như Kiyoshi đã phân tích ở trên, thì do cơ chế hàm strtok() sẽ điền thêm ký tự kết thúc chuỗi \0 vào các vị trí tìm thấy ký tự phân tách, nên chuỗi ban đầu sẽ bị thay đổi sau khi chúng ta sử dụng hàm strtok().
Bởi vậy, lưu ý đầu tiên khi sử dụng hàm này đó chính là, chuỗi ban đầu sẽ bị thay đổi. Do đó, bạn cần phải backup chuỗi này bằng cách copy nó chẳng hạn trước khi dùng hàm, và đối với các chuỗi không được thay đổi trong chương trình, thì bạn đừng sử dụng chuỗi với hàm strtok nhé.
Cách copy chuỗi có thể tham khảo tại bài dưới đây:
Lưu ý thứ hai
Chúng ta không nhất thiết chỉ chỉ định một ký tự làm ký tự phân tách, mà có thể chỉ định ký tự phân tách bằng chuỗi ký tự. Ví dụ như chúng ta có thể kết hợp nhiều dấu dấu cách với cụm từ để làm ký tự phân tách ” and ” như sau:
#include <stdio.h>
#include <string.h>
int main(void){
char str[50] = "Tom and Jerry and me";
char * p;
p = strtok(str, " and ");
printf("%s\n", p);
while(p != NULL) {
p = strtok(NULL, " and ");
if(p != NULL) {
printf("%s\n", p);
}
}
return 0;
}
//Tom
//Jerry
//me
Bất cứ một ngôn ngữ lập trình nào cũng có một tập các kiểu dữ liệu, kiểu dữ liệu là cơ bản, và nó khá giống nhau với tất cả các ngôn ngữ.
Các ứng dụng luôn xử lý dữ liệu ở đầu vào và xuất dữ liệu kết quả ở đầu ra. Đầu vào, đầu ra và kết quả của các quá trình tính toán đều liên quan đến dữ liệu. Trong môi trường tính toán, dữ liệu được phân lớp theo các tiêu chí khác nhau phụ thuộc vào bản chất của nó.
Ở mỗi tiêu chí, dữ liệu có một tính chất xác định và có một kiểu thể hiện riêng biệt. Java cung cấp một vài kiểu dữ liệu, chúng được hỗ trợ trên tất cả các nền. Ví dụ, dữ liệu loại int (integer) của Java được thể hiện bằng 4 bytes trong bộ nhớ của tất cả các loại máy bất luận ở đâu chạy chương trình Java. Bởi vậy các chương trình Java không cần phải thay đổi khi chạy trên các nền khác nhau
Java có 2 loại kiểu dữ liệu:
Các kiểu dữ liệu nguyên thủy (Primitive Data Types)
Các kiểu dữ liệu tham chiếu (Reference Types)
I. Kiểu Dữ Liệu Date Trong Java
Java cung cấp lớp Date có sẵn trong java.util package, lớp này tóm lược ngày tháng và thời gian hiện tại.
Lớp Date hỗ trợ 2 constructor. Constructor đầu tiên khởi tạo đối tượng với ngày và thời gian hiện tại.
Date( )
Constructor sau chấp nhận 1 tham số bằng số mili giây đã trôi qua từ nửa đêm ngày 1/1/1970.
Date(long millisec)
1 khi các bạn có 1 đối tượng Date có sẵn, các bạn có thể gọi bất kỳ phương thức hỗ trợ nào để thao tác với ngày tháng này:
STT
Phương thức và Miêu tả
1
boolean after(Date date)Trả về true nếu gọi đối tượng Date chứa 1 ngày mà chậm hơn ngày đã xác định, nếu không là false.
2
boolean before(Date date)Trả về true nếu gọi đối tượng Date chứa 1 ngày mà sớm hơn ngày đã xác định, nếu không là false.
3
Object clone( )Sao chép đối tượng Date đang gọi
4
int compareTo(Date date)So sánh giá trị đối tượng đang gọi với giá trị đó của date. Trả về 0 nếu các giá trị này là cân bằng. Trả về 1 giá trị âm nếu đối tượng đang gọi là sớm hơn date. Trả về 1 giá trị dương nếu đối tượng đang gọi chậm hơn date.
5
int compareTo(Object obj)Tiến hành tương tự như compareTo(Date) nếu đối tượng là của lớp Date. Nếu không thì, nó cho một ClassCastException.
6
boolean equals(Object date)Trả về true nếu đối tượng Date đang gọi chứa thời gian và ngày tháng giống như date đã cho, nếu không là false.
7
long getTime( )Trả về số mili giây đã trôi qua từ 1/1/1970
8
int hashCode( )Trả về 1 mã hóa băm (hash code) cho đối tượng đang gọi
9
void setTime(long time)Thiết lập ngày tháng và thời gian như time đã cho, mà biểu diễn một time đã trôi qua (giá trị mili giây) từ nửa đêm 1/1/1970
10
String toString( )Biến đổi đối tượng Date đang gọi thành 1 chuỗi và trả về kết quả
II. Kiểu Dữ Liệu Double Trong Java
Kiểu dữ liệu Double là 1 kiểu thập phân 64-bit có độ chính xác kép.
Phạm vi giá trị của nó là không giới hạn. Kiểu dữ liệu Double thường được dùng cho các giá trị thập phân giống như float.
Kiểu dữ liệu Double cũng không bao giờ nên được dùng cho các giá trị chính xác, ví dụ như tiền tệ. Giá trị mặc định của nó là 0,0d.
Ví dụ:
double d1 = 12.3
III. Kiểu Dữ Liệu String Trong Java
Java hỗ trợ các thao tác với chuỗi thông qua lớp String. Lớp này nằm trong gói java.lang.
Trong quá trình sử dụng, import gói java.lang theo cú pháp sau:
import java.lang.String;
Hoặc:
import java.lang.*;
Lớp String hỗ trợ hơn 50 hàm để thao tác với kiểu chuỗi. Chức năng của một số hàm thông thường:
STT
Nguyên mẫu hàm
Chức năng
1
public char charAt(int s)
Trả về ký tự có chỉ số là i trong chuỗi cần tìm.
2
public String concat(String s2)
Trả về chuỗi đã được nối thêm vào chuỗi s2, s2 sẽ được nối vào sau chuỗi gọi hàm.
3
public boolean equals(String s2)
Trả về true nếu giá trị của chuỗi s2 bằng giá trị chuỗi đang xét, trả về false nếu ngược lại. Phân biệt chữ hoa và chữ thường trong khi so sánh.
4
public boolean equalsIgnoreCase(String s2)
Trả về true nếu giá trị của chuỗi s2 bằng giá trị chuỗi đang xét, trả về false nếu ngược lại. Không phân biệt chữ hoa và chữ thường trong khi so sánh.
5
public int length()
Trả về độ dài của chuỗi đang xét.
6
public String replace(char oldChar, char newChar)
Thay thế ký tự oldChar bằng ký tự newChar.
7
public String substring(int begin)
public String substring(int begin)
Lấy ra chuỗi con từ vị trí begin đến hết chuỗi, hoặc từ vị trí begin đến vị trí end.
8
public int indexOf(String str)
Kiểm tra sự tồn tại của chuỗi str trong chuỗi đang xét. Nếu có, trả về vị trí đầu tiên của sự xuất hiện, nếu không trả về -1.
9
public String toUpperCase()
Chuyển tất cả ký tự trong chuỗi đang xét thành ký tự hoa.
10
public String toLowerCase()
Chuyển tất cả ký tự trong chuỗi đang xét thành ký tự thường.
11
public String toString()
Trả về giá trị của chuỗi đang xét. Trong 1 vài trường hợp, bạn có thể override lại để sử dụng đúng mục đích.
12
public String trim()
Trả về chuỗi đã được loại bỏ khoảng trắng.
Dưới đây là một ví dụ về việc sử dụng một số hàm thông thường trong Java. Khi sử dụng, chú ý đến kiểu trả về và đối số truyền vào hàm để có kết quả chính xác.
package TestString;
import java.lang.*;
public class main {
public static void main(String[] args) {
String s = "STDIO";
// Access a single character at specific index
System.out.println("1. s[3] = " + s.charAt(3));
// Concatenate 2 strings
System.out.println("2. Concatenate 2 strings: ");
s = s.concat("-LAPTRINHJAVA");
System.out.println(s);
// Compare 2 strings
if(s.equals("Im Michael"))
System.out.println("3. Equal strings!");
else
System.out.println("3. Not equal strings!");
// Get length of string
System.out.println("4. Length of s: " + s.length());
// Replace characters from string
System.out.println("5. Replacing - by _ from s: " + s.replace('-', '_'));
}
}
IV. Kiểu Dữ Liệu Object Trong Java
Mặc định lớp Object là lớp cha của tất cả các lớp trong java. Nói cách khác nó là 1 lớp cáo nhất trong java.
Sử dụng lớp Object là hữu ích nếu bạn muốn tham chiếu bất kỳ đối tượng nào mà bạn chưa biết kiểu dữ liệu của đối tượng đó. Lưu ý rằng biến tham chiếu của lớp cha có thể tham chiếu đến đối tượng của lớp con được gọi là upcasting.
Ví dụ: giả sử phương thức getObject() trả về một đối tượng nhưng nó có thể là bất kỳ kiểu nào như Employee, Student… các bạn có thể sử dụng biến tham chiếu của lớp Object để tham chiếu tới đối tượng đó.
Object obj = getObject();//Chung ta khong biet doi tuong nao se duoc tra ve tu phuong thuc nay
Lớp Object cung cấp 1 vài cách xử lý chung cho tất cả các đối tượng như đối tượng có thể được so sánh, đối tượng có thể được cloned, đối tượng có thể được notified…
Các phương thức của lớp Object
Lớp Object cung cấp các phương thức như trong bảng sau:
Phương thức
Mô tả
public final Class getClass()
trả về đối tượng lớp Class của đối tượng hiện tại. Từ lớp Class đó có thể lấy được các thông tin metadata của class hiện tại.
tạo và trả về bản sao chép (clone) của đối tượng hiện tại.
public String toString()
trả về chuỗi ký tự đại diện của đối tượng hiện tại.
public final void notify()
đánh thức một luồng, đợi trình giám sát của đối tượng hiện tại.
public final void notifyAll()
đánh thức tất cả các luồng. đợi trình giám sát của đối tượng hiện tại.
public final void wait(long timeout)throws InterruptedException
làm cho Thread hiện tại đợi trong khoảng thời gian là số mili giây cụ thể, tới khi Thread khác thông báo (gọi phương thức notify() hoặc notifyAll()).
public final void wait(long timeout,int nanos)throws InterruptedException
làm cho Thread hiện tại đợi trong khoảng thời gian là số mili giây và nano giây cụ thể, tới khi Thread khác thông báo (gọi phương thức notify() hoặc notifyAll()).
public final void wait()throws InterruptedException
làm Thread hiện tại đợi, tới khi Thread khác thông báo (gọi phương thức notify() hoặc notifyAll()).
protected void finalize()throws Throwable
Được gọi bởi Garbage Collector trước khi đối tượng bị dọn rác.
Khai báo Object
1 Object (đối tượng) nó chứa trong đó bao gồm các method (phương thức) và properties (thuộc tính) để tạo ra 1 kiểu dữ liệu hữu ích.
Object xác định hành vi của class. Khi bạn gửi 1 thông điệp vào 1 object, có nghĩa là bạn đang yêu cầu gọi các object hoặc thực hiện 1 trong các phương thức của nó.
Từ 1 quan điểm của lập trình hướng đối tượng, một đối tượng có thể là 1 cấu trúc dữ liệu (data structure), 1 biến (variable) hoặc 1 chức năng (function).
Object được phân bổ vị trí bộ nhớ. Các Object được thiết kế như class phân cấp.
ClassName ReferenceVariable = new ClassName();
V. Kiểu Dữ Liệu Boolean Trong Java
Boolean là 1 trong những kiểu dữ liệu nguyên thủy trong Java, nó chỉ cho phép biến mang 2 giá trị true (ĐÚNG) hoặc false (SAI).
Cùng với kiểu dữ liệu nguyên thủy thì Java cũng đã được phát triển 1 lớp Boolean, nhằm hỗ trợ lập trình viên nhiều hơn.
Boolean khác biệt với hầu hết các kiểu dữ liệu khác vì nó chỉ cho phép 2 giá trị.
Vậy nên nó thường được sử dụng trong những trường hợp chỉ có 2 kết quả ĐÚNG hoặc SAI và đặc biệt thường sẽ được sử dụng làm điều kiện rẽ nhánh.
Để cho dễ hiểu thì mình sẽ liên hệ thực tế và cung cấp 1 ví dụ về kiểu dữ liệu boolean này.
Ví dụ:
Bạn muốn lưu giá trị thời tiết vào một thời điểm nhất định
Tại thời điểm đó chỉ có 2 trường hợp có thể xảy ra đó là có mưa hoặc không có mưa.
Như vậy ta chỉ cần lưu 1 biến troiMua với kiểu boolean, với giá trị true là có mưa còn false là không có mưa.
Định nghĩa về kiểu dữ liệu boolean trong Java thì chỉ có bấy nhiêu, khá là dễ hiểu đúng không nào? Song song với kiểu dữ liệu nguyên thủy thì như hầu hết kiểu dữ liệu nguyên thủy khác, Java đã được phát triển một lớp Boolean với mục đích hỗ trợ nhiều hơn cho lập trình viên.
Bắt đầu với 1 ví dụ đơn giản xem tình trạng thời tiết hiện tại là mưa hay là không mưa.
public class Main
{
public static void main(String[] args) {
boolean troiMua = true;
if (troiMua) {
System.out.println("Trời đang mưa");
}
else
{
System.out.println("Trời không mưa");
}
}
}
Như bạn thấy, ở thời điểm ban đầu, vừa khai báo vài khởi tạo giá trị ban đầu của biến troiMua.
boolean troiMua = true;
Trong đó, kiểu dữ liệu của biến troiMua là boolean, giá trị khởi tạo là true.
Tiếp đó, trong cấu trúc điều kiện if … else, mình kiểm tra nếu biến troiMua mà đúng thì thông báo “Trời đang mưa”.
Ngược lại thì thông báo “Trời không mưa”.
Bạn cũng có thể làm thế này.
if (troiMua == true)
VI. Kiểu Dữ Liệu Byte Trong Java
Kiểu dữ liệu byte là 1 ví dụ về kiểu dữ liệu nguyên thủy.
Kiểu byte gồm 8-bit. Phạm vi giá trị của nó nằm trong khoảng từ -128 đến 127. Giá trị tối thiểu của nó là -128 và giá trị tối đa là 127. Giá trị mặc định của nó là 0.
Kiểu dữ liệu byte được dùng để lưu bộ nhớ trong các mảng lớn, trong đấy việc tiết kiệm bộ nhớ là bắt buộc nhất. Nó tiết kiệm không gian vì một byte nhỏ hơn 4 lần so với số integer. Nó cũng có thể được dùng thay cho kiểu dữ liệu ‘int’.
Ví dụ:
byte a = 10, byte b = -20
VII. Ép Kiểu Dữ Liệu Trong Java
Ép kiểu là cách chuyển biến thuộc kiểu dữ liệu này thành biến thuộc kiểu dữ liệu khác.
Ý nghĩa:
Việc chuyển kiểu dữ liệu sẽ đến lúc phải cần trong quá trình xử lý chương trình
Có thể định dạng đúng kiểu dữ liệu mình mong muốn (Như cách hiển thị kiểu ngày tháng năm trên thế giới khác với Việt Nam nên ta sẽ chuyển kiểu ngày theo phong cách địa phương).
Cách sử dụng ép kiểu
Trong bài này chỉ nói đến ép kiểu dữ liệu đối với dữ liệu nguyên thủy (Primitive Data Types), còn đối với ép kiểu dữ liệu tham chiếu (Reference Types) thì cách ép kiểu là những hàm (phương thức) ép kiểu do người ta viết riêng cho mỗi kiểu dữ tham chiếu đó.
Thì trong ép kiểu trong kiểu dữ liệu nguyên thủy được chia ra làm 2 loại:
Chuyển đổi kiểu ngầm định (implicit)
Chuyển đổi kiểu tường minh (explicit)
Kiểu chuyển đổi ngầm định (implicit)
Việc chuyển đổi sẽ tự thực hiện bởi compiler và các bạn không cần làm gì. Việc chuyển đổi này gì dành cho kiểu dữ liệu nhỏ sang kiểu dữ liệu lớn hơn. Ta có thể xem chiều từ nhỏ sang lớn như sau:
Ví dụ: Ta lấy 1 biến kiểu int gán giá trị cho biến kiểu long
public class HelloWorld{
public static void main(String []args){
int a = 5;
long b = a;
System.out.print(b);
}
}
Kiểu chuyển đổi tường minh (explicit)
Ngược lại với cách chuyển đổi ngầm định, việc chuyển đổi tường minh là chiều ngược lại từ kiểu dữ liệu lớn hơn sang kiểu dữ liệu nhỏ hơn (với điều kiện giá trị đó kiểu dữ liệu sẽ thay đổi có thể lưu trữ được trong kiểu dữ liệu mới).
Với ép kiểu theo cú pháp:
(<Kiểu dữ liệu>) <Tên biến>;
Ví dụ: Ta lấy một biến kiểu long gán giá trị cho biến kiểu int
public class HelloWorld{
public static void main(String []args){
long a = 6;
int b = (int) a;
System.out.print(a);
}
}
Chú ý:
Nếu ép kiểu dữ liệu kí tự char sang kiểu dữ liệu số hoặc ngược lại.
Khi ép kiểu char sang số thì sẽ ép kiểu ngầm định chuyển kí tự sang hệ thập phân ASCII tương ứng kí tự đó.
Nếu ngược lại thì phải dùng ép kiểu tường minh để chuyển sang kiểu kí tự.
VIII. Kiểm Tra Kiểu Dữ Liệu Trong Java
Bài viết này sẽ định nghĩa 1 lớp dùng để kiểm tra dữ liệu và có thể sử dụng lại được, nó sẽ cho mọi người 1 cái nhìn tổng quan về cấu trúc của biểu thức chính quy. Ứng dụng chạy lặp đi lặp lại việc yêu cầu người dùng nhập vào một chuỗi cho đến khi nhập đúng theo mẫu mà biểu thức chính quy đã đề ra thì thôi.
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexTestHarness {
public static void main(String[] args) {
Console console = System.console();
if (console == null) {
System.err.println("No console.");
System.exit(1);
}
while (true) {
Pattern pattern =
Pattern.compile(console.readLine("%nNhập vào một biểu thức chính quy: "));
Matcher matcher =
pattern.matcher(console.readLine("Nhập vào một chuỗi để tìm kiếm: "));
boolean found = false;
while (matcher.find()) {
console.format("Tìm thấy " +
" \"%s\" bắt đầu tại " +
"index %d và kết thúc tại %d.%n",
matcher.group(),
matcher.start(),
matcher.end());
found = true;
}
if (!found) {
console.format("Không tìm thấy, không tương thích.%n");
}
}
}
}
Trước khi tiếp tục chuyển sang bài viết tiếp theo, các bạn hãy lưu và chạy đoạn mã trên để đảm bảo rằng môi trường phát triển của bạn hỗ trợ các gói yêu cầu. Trong trường hợp môi trường phát triển của bạn không hỗ trợ Console, bạn có thể thực thi đoạn code sau để thay thế:
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexTestHarness {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
while (true) {
System.out.print("Nhập vào một biểu thức chính quy: ");
Pattern pattern = Pattern.compile(scanner.nextLine());
System.out.print("Nhập vào một chuỗi để tìm kiếm: ");
Matcher matcher = pattern.matcher(scanner.nextLine());
boolean found = false;
while (matcher.find()) {
System.out.printf("Tìm thấy " +
" \"%s\" bắt đầu tại " +
"index %d và kết thúc tại %d.%n",
matcher.group(),
matcher.start(),
matcher.end());
found = true;
}
if (!found) {
System.out.printf("Không tìm thấy, không tương thích.%n");
}
}
}
}
IX. Kiểu Số Thực Trong Java
Cùng chúng tôi xem qua 1 ví dụ về kiểu số thực trong Java nhé
Bạn hãy viết chương trình tạo ra 2 biến a, b kiểu số thực. Sau đó gán giá trị cho a = 10.5, b = 7 và thực hiện hiển thị ra màn hình:
a / b = {P}
Với {P} là thương của a và b ({P} là kết quả của phép chia a / b).
Lý thuyết
Để khai báo và gán giá trị cho biến kiểu số thực bạn dùng từ khóa double:
// Khai báo biến a kiểu số thực và gán giá trị cho a = 10.5
double a = 10.5;
Ví dụ chương trình tạo và hiển thị biến kiểu số thực ra màn hình:
public class Variable {
public static void main(String[] args) {
// Khai báo biến a kiểu số thực và gán giá trị cho a = 1.5
double a = 1.5;
System.out.println("a = " + a);
}
}
Kết quả khi chạy chương trình:
a = 1.5
Ngoài double ra thì trong Java còn một kiểu dữ liệu nữa cũng được dùng để lưu trữ số thực nữa là float nhưng trong hầu hết mọi trường hợp bạn nên sử dụng kiểu double.
Lưu ý: Bạn không thể dùng biến kiểu int để lưu trữ số thực và kết quả của phép toán giữa 2 số nguyên là một số nguyên. Ví dụ:
public class Variable {
public static void main(String[] args) {
int a = 4;
int b = 3;
System.out.println(a / b);
System.out.println(4 / 3);
}
}
Kết quả sau khi chạy chương trình:
1
1
Kết quả sẽ là 1 mà không phải 1.(3) do cả a và b đều là biến kiểu số nguyên nên kết quả sẽ là 1 số nguyên. Do đó khi thực hiện các phép toán trong Java bạn cần lưu ý tới kiểu dữ liệu. Để chương trình trên hiển thị đúng kết quả bạn có thể làm như sau:
public class Variable {
public static void main(String[] args) {
double a = 4;
double b = 3;
System.out.println(a / b);
System.out.println(4.0 / 3);
}
}
Kết quả sau khi chạy chương trình:
1.3333333333333333
1.3333333333333333
Đọc tới đây các bạn đã hiểu về cách khai báo và sử dụng biến kiểu số thực. Hãy thực hành để hiểu rõ hơn về cách làm nhé.
X. Kiểu Dữ Liệu Bigdecimal Trong Java
BigDecimal đại diện cho 1 số thập phân có độ chính xác cao. Một BigDecimal object là immutable và được chia làm 2 phần:
Precision- Biểu diễn tất các ký số có trong BigDecimal dưới dạng số nguyên không phân biệt phần thập phân.
Scale (32 bit) – Số nguyên biểu diễn số chữ số thập phân.
Ví dụ BigDecimal 3.14 có phần Precision là 314 và scale là 2.
các bạn sử dụng BigDecimal cho các phép tính số học đòi hỏi độ chính xác cao như các bài toán liên quan đến tiền tệ etc.
Khởi tạo BigDecimal
các bạn có thể khởi tạo BigDecimal object từ String, mảng character int, long, double, BigInteger.
BigDecimal bdFromString = new BigDecimal("0.1");
BigDecimal bdFromCharArray = new BigDecimal(new char[ {'3','.','1','6','1','5'});
BigDecimal bdlFromInt = new BigDecimal(42);
BigDecimal bdFromLong = new BigDecimal(123412345678901L);
BigInteger bigInteger = BigInteger.probablePrime(100, new Random());
BigDecimal bdFromBigInteger = new BigDecimal(bigInteger);
các bạn cũng có thể tạo BigDecimal với double, nhưng hãy cẩn trọng, xem ví dụ sau:
BigDecimal bdFromDouble = new BigDecimal(0.1d);
System.out.println(bdFromDouble.toString()); // 0.1000000000000000055511151231257827021181583404541015625
Trên mình đã khởi tạo BigDecimal với giá trị là 0.1d, mong muốn BigDecimal của mình mang giá trị 0.1 thế nhưng kết quả lại khác hoàn toàn. Đó là vì 0.1 không có giá trị chính xác trong double, BigDecimal chỉ đơn giản là lấy giá trị sai của 0.1 trong double sang và tất nhiên là giá trị cũng sẽ bị sai.
Vì vậy các bạn nên sử dụng String để khởi tạo 1 giá trị double hoặc sử dụng BigDecimal.valueOf() để có giá trị chính xác như mong muốn.
BigDecimal cũng giống như các kiểu dữ liệu Number khác(Integer, Long, Double, etc) nó cung cấp đầy đủ các phép tính cộng, trừ, nhân, chia, so sánh, etc.
Kiểm tra các thành phần trong BigDecimal như unscaled, scale, sign.
int precision = bd.precision(); // 9
int scale = bd.scale(); // 4
int signum = bd.signum(); // -1
So sánh BigDecimal
compareTo()
Sử dụng compareTo() method để so sánh giữa BigDecimal với một BigDecimal khác. Method compareTo() trả về -1 nếu nhỏ hơn BigDecimal được so sánh, 0 nếu bằng và 1 nếu lớn hơn.
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
BigDecimal bd3 = new BigDecimal("2.0");
System.out.println(bd1.compareTo(bd2)); // 0
System.out.println(bd1.compareTo(bd3)); // -1
System.out.println(bd3.compareTo(bd1)); 1
Note: các bạn nhận thấy rằng compareTo() bỏ qua phần scale khi so sánh. 1.0 = 1.00
XI. Kiểu Dữ Liệu Enum Trong Java
Enum trong java là 1 kiểu dữ liệu đặc biệt của Java được sử dụng để định nghĩa các tập hợp các hằng số. Cụ thể hơn, Java enum là một kiểu đặc biệt của lớp trong java. Một enum có thể chứa các trường, phương thức và Constructor.
Nó có thể được dùng để định nghĩa các ngày trong tuần (SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY), các mùa trong năm (SPRING, SUMMER, FALL, WINTER), …
Enum trong java có thể được định nghĩa bên trong hoặc bên ngoài 1 lớp, vì nó tương tự như lớp trong java.
Ví dụ enum trong java: định nghĩa bên trong một lớp
package vn.viettuts.javaenum;
public class EnumExample1 {
// define enum
enum Season {
SPRING, SUMMER, FALL, WINTER;
}
public static void main(String[] args) {
Season season = Season.WINTER;
System.out.println(season);
}
}
Kết quả:
WINTER
Ví dụ enum trong java: định nghĩa bên ngoài một lớp
package vn.viettuts.javaenum;
// define enum
enum Season {
SPRING, SUMMER, FALL, WINTER;
}
public class EnumExample2 {
public static void main(String[] args) {
Season season = Season.WINTER;
System.out.println(season);
}
}
Kết quả:
WINTER
Ví dụ enum trong java: định nghĩa trong một file riêng biệt
File Season.java
package vn.viettuts.javaenum;
public enum Season {
SPRING, SUMMER, FALL, WINTER;
}
File EnumExample3.java
package vn.viettuts.javaenum;
public class EnumExample3 {
public static void main(String[] args) {
Season season = Season.WINTER;
System.out.println(season);
}
}
Phương thức này sẽ trả về đối tượng giữ giá trị của tham số đã truyền, ví dụ nhập giá trị “9” là 1 string, thì giá trị trả về sẽ là 1 đối tượng Integer nếu dùng Integer.valueOf(“9”).
II. Phương Thức ValueOf Trong Java String
Phương thức valueOf() được dùng để chuyển đối kiểu dữ liệu khác thành chuỗi. Bằng việc dùng phương thức valueOf(), các các bạn có thể chuyển int thành chuỗi, long thành chuỗi, boolean thành chuỗi, float thành chuỗi, double thành chuỗi, char thành chuỗi, mảng char thành chuỗi, đối tượng thành chuỗi.
Phương thức String valueOf() trong Java có những form sau, mà phụ thuộc vào những tham số đã truyền. Phương thức này trả về biểu diễn chuỗi của tham số đã truyền.
valueOf(boolean b): Trả về biểu diễn chuỗi của tham số boolean.
valueOf(char c): Trả về biểu diễn chuỗi của tham số char.
valueOf(char[] data): Trả về biểu diễn chuỗi của tham số char array.
valueOf(char[] data, int offset, int count): Trả về biểu diễn chuỗi của mảng phụ cụ thể của tham số char array.
valueOf(double d): Trả về biểu diễn chuỗi của tham số double.
valueOf(float f): Trả về biểu diễn chuỗi của tham số float.
valueOf(int i): Trả về biểu diễn chuỗi của tham số int.
valueOf(long l): Trả về biểu diễn chuỗi của tham số long.
valueOf(Object obj): Trả về biểu diễn chuỗi của tham số Object.
III. Cú pháp
Trên đây là cú pháp đơn giản của ValueOf() trong Java String:
public String trim()
IV. Ví Dụ Phương Thức ValueOf Trong Java String:
public class StringValueOfExample {
public static void main(String args[]) {
int value = 30;
String s1 = String.valueOf(value);
System.out.println(s1 + 10);
}
}
Kết quả trả về:
3010
V. Khác Biệt Giữa ParseInt Và ValueOf Trong Java
Cả 2 phương thức valueOf và parseInt đều được dùng để chuyển đổi String thành Integer trong Java, nhưng giữa chúng vẫn có sự khác biệt. Nếu các bạn nhìn vào đoạn mã của phương thức valueOf() , các bạn sẽ thấy rằng bên trong nó gọi phương thức parseInt() để chuyển đổi String thành Integer.
Một sự khác biệt khác giữa phương thức parseInt() và valueOf() là kiểu trả về. ValueOf() của java.lang.Integer trả về một đối tượng Integer, trong khi phương thức parseInt() trả về một kiểu dữ liệu nguyên thủy int.
ParseInt và valueOf trong Java
– Nếu các bạn xem mã nguồn của phương thức parseInt() và valueOf() từ class java.lang.Integer, các bạn sẽ thấy rằng công việc thực tế của việc chuyển đổi Chuỗi thành số nguyên được thực hiện bằng phương thức parseInt(), valueOf() chỉ cung cấp bộ nhớ đệm của Integer được dùng thường xuyên Các đối tượng, Đây là đoạn mã từ phương thức valueOf() giúp mọi thứ rõ ràng:
Phương thức này trước tiên gọi phương thức parseInt(), để chuyển đổi String thành int nguyên thủy, sau đó tạo đối tượng Integer từ giá trị đó. các bạn có thể thấy bên trong nó duy trì bộ đệm Integer. Nếu int nguyên thủy nằm trong phạm vi của bộ đệm, nó trả về đối tượng Integer từ pool, nếu không nó sẽ tạo một đối tượng mới.
public static Integer valueOf(int i) {
if (i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
Luôn luôn có sự nhầm lẫn, nên dùng parseInt() hay valueOf() để chuyển đổi String thành int nguyên thủy trong java và đối tượng java.lang.Integer. Tôi khuyên các bạn nên dùng parseInt() nếu các bạn cần int nguyên thủy và dùng valueOf() nếu các bạn cần đối tượng java.lang.Integer.
Trong bài viết này chúng ta sẽ tìm hiểu về phương thức split() trong Java. Đây là một phương thức được sử dụng để tách chuỗi dựa trên một regex được chỉ định.
1. Split Trong Java Là Gì
Phương thức split() thường được dùng chung với ArrayList để tách những phần tử trong mảng và trả về một mảng các chuỗi con. Vì vậy bạn hãy khai báo thư viện đã nhé: import java.util.Arrays
2. Cú Pháp Phương Thức Split() Trong Java
Như đã nói ở trên thì phương thức split () chia chuỗi tại regex được chỉ định và trả về 1 mảng các chuỗi con.
Cú pháp:
string.split(String regex, int limit)
Trong đó:
regex – chuỗi được chia tại regex này (có thể là chuỗi).
limit (option): kiểm soát số lượng các chuỗi con kết quả. Nếu tham số limit không được truyền, hàm split () trả về tất cả những chuỗi con có thể có.
Hàm trả về 1 mảng các chuỗi con.
Ví dụ: ta dùng phương thức split() để tách chuỗi str dựa trên khoảng trắng.
1
2
//tách chuỗi str dựa trên khoảng trắng
str.split(" ");
3. Cách Dùng Phương Thức Split() Trong Java
Phương thức split() tách chuỗi này theo biểu thức chính quy(regular expression) sau đó trả về mảng chuỗi.
Phương thức:
public String split(String regex)
public String split(String regex, int limit)
4. Ví Dụ 1
Ở ví dụ này chúng tôi sẽ tách chuỗi vowels dựa trên “::”.
import java.util.Arrays;
class Main {
public static void main(String[] args) {
//khai báo một chuỗi vowels với nội dung là "a::b::c::d:e"
String vowels = "a::b::c::d:e";
//sử dụng phương thức split() để tách chuỗi vowels dựa trên "::"
String[] result = vowels.split("::");
//hiển thị kết quả sau khi tách ra màn hình
System.out.println("Kết quả: " + Arrays.toString(result));
System.out.println("--------------------------------");
System.out.println("Chương trình này được đăng tại Frertuts.net");
}
Kết quả là:
5. Ví Dụ 2
Tùy vào hoàn cảnh mà chúng ta dùng những phương thức cho hợp lý,tuy nhiên thông thường chúng ta sử dụng cấu trúc thứ 2 public String[] split(String regex),với regex là dấu mà chúng ta cần tách. Để hiểu rõ hơn mời bạn đọc xem ví dụ dưới đây về việc tách chuỗi thành mảng :
package action;
public class Demo {
public static void main(String[] args) {
String str1 = "itphutran.com";
String[] arStr = str1.split("\\.");
for (String item : arStr) {
System.out.println(item);
}
}
}
Với ví dụ trên,chúng ta tách chuỗi dựa vào dấu chấm (.).Như vậy sau khi tách trả về mảng gồm 2 phần tử.
Kết quả đầu ra :
itphutran
com
6. Split Nhiều Ký Tự Trong Java
Để cắt chuỗi trong java ta có thể xử dụng 2 cách phổ biến là sử dụng phương thức split có sẵn trong class String hoặc dùng đối tượng StringTokenizer để xử lý.
Sử dụng phương thức string.spilt(regex) để cắt chuỗi với với ký tự định dạng vị trí cắt theo cú pháp regex
Chú ý: khi sử dụng regex để tìm vị trí cần cắt trong chuổi nếu ký tự regex này là các ký tự keyword đặc biệt thì chúng ta cần xử dụng ký tự đánh dấu “\\” trước ký tự đặc biệt, Trong ví dụ sau “.” là ký tự giữ vị trí cần cắt
import java.util.regex.Pattern;
public class TestSplit {
public static void main(String[] args) {
String test = "abc.def.123";
String[] output = test.split("\\.");
//alternative
//String[] output = test.split(Pattern.quote("."));
System.out.println(output[0]);
System.out.println(output[1]);
System.out.println(output[2]);
}
}
Kết quả trả về
abc
def
123
Để kiểm tra logic xem chuỗi cần cắt có thể cắt hay không chúng ta sử dụng phương thức lấy số lượng phần tử trong mảng kết quả trả về, nếu length lớn hơn 0 thì chuổi có thể cắt và ngược lại. Bạn có thể xem bài viết “thao tác xử lý mảng” này để rõ hơn cách dùng.
import java.util.regex.Pattern;
public class TestSplit {
public static void main(String[] args) {
String test = "abc.def.123";
if(test.contains(".")){
String[] output = test.split("\\.");
if(output.length!=3){
throw new IllegalArgumentException(test + " - invalid format!");
}else{
System.out.println(output[0]);
System.out.println(output[1]);
System.out.println(output[2]);
}
}else{
throw new IllegalArgumentException(test + " - invalid format!");
}
}
}
7. Phương Thức Split () Trong Java Không Hoạt Động Trên Dấu Chấm (.) [Trùng lặp]
Chúng tôi đã chuẩn bị 1 đoạn mã đơn giản để tách phần lỗi khỏi ứng dụng web của mình.
public class Main {
public static void main(String[] args) throws IOException {
System.out.print("\nEnter a string:->");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String temp = br.readLine();
String words[] = temp.split(".");
for (int i = 0; i < words.length; i++) {
System.out.println(words[i] + "\n");
}
}
}
Chúng tôi đã thử nghiệm nó trong khi xây dựng 1 ứng dụng web JSF. Tôi chỉ muốn biết tại sao trong đoạn mã trên temp.split(“.”) không hoạt động. Tuyên bố,
System.out.println(words[i]+"\n");
không hiển thị gì trên bảng điều khiển có nghĩa là nó không đi qua vòng lặp. Khi chúng tôi thay đổi đối số của phương thức temp.split() thành các ký tự khác, nó hoạt động tốt như bình thường. Rắc rối có thể là cái gì?
Dấu chấm là 1 ký tự đặc biệt trong cú pháp biểu thức chính quy. Sử dụng Pattern.quote() trên tham số cần chia () nếu các bạn muốn phân tách nằm trên mẫu chuỗi ký tự:
Date trong Java là 1 trong các class mô tả ngày tháng đầu tiên trong Java. Thật đáng tiếc là hầu hết các cách thức của nó đã lỗi thời, và thay vào đó là dùng các cách thức của java.util.Calendar. Nhưng bạn vẫn có thể dùng java.util.Date để mô tả ngày tháng.
1. Các Lớp Date, Time, Calendar Trong Java
Java cung cấp 1 số class liên quan tới thời gian và lịch (Calendar), sau đây là danh sách các class này:
Class
Mô tả
java.util.Date
1 lớp đại diện cho ngày tháng năm và thời gian. Tiếc là hầu hết các cách thức của nó đã bị lỗi thời, khuyến cáo là không nên dùng các cách thức đó, tuy nhiên lớp Date vẫn được dùng rỗng rãi.
java.util.concurrent.TimeUnit
TimeUnit là 1 Enum mô tả các đơn vị ngày tháng năm và thời gian.
java.sql.Date
1 lớp mô tả ngày tháng năm. Thông tin về thời gian bị cắt bỏ. Lớp này thường dùng trong JDBC.
java.sql.Time
1 lớp mô tả thời gian (Giờ phút giây, milli giây), và không chứa thông tin ngày tháng năm. Lớp này thường dùng trong JDBC.
java.sql.Timestamp
1 lớp mô tả ngày tháng năm và thời gian. Lớp này thường dùng trong JDBC.
java.util.Calendar
Lớp mô tả bộ Lịch. Nó có các cách thức toán học về thời gian, chẳng hạn thêm ngày, bớt ngày,…
java.util.GregorianCalendar
Là 1 lớp con trực tiếp của java.util.Calendar, mô tả ngày Dương Lịch, bộ lịch được dùng rộng rãi trên thế giới ngày nay. Nó có tất cả các method từ java.util.Calendar để thao tác toán học trên ngày tháng năm và thời gian.
java.util.TimeZone
TimeZone là lớp mô tả múi giờ, nó có ích khi bạn làm việc với Lịch trên múi giờ.
java.text.SimpleDateFormat
Lớp này giúp bạn chuyển 1 String có định dạng ngày tháng sang kiểu Date và ngược lại
2. System.currentTimeMillis()
CurrentTimeMillis() là 1 method tĩnh của class System. Kết quả trả về khoảng thời gian bằng mili giây tính từ ngày 1-1-1970 cho tới thời điểm hiện tại.
System.currentTimeMillis() thường được dùng để đo khoảng thời gian làm 1 việc gì đó bằng cách gọi method này trước khi bắt đầu công việc, và sau khi hoàn thành công việc.
JobTimeDemo.java
package org.o7planning.tutorial.dt;
public class JobTimeDemo {
// Đây là cách thức tính tổng các số từ 1 tới 100.
private static int sum() {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
private static void doJob(int count) {
// Gọi cách thức 'sum' với số lần cho bởi tham số.
for (int i = 0; i < count; i++) {
sum();
}
}
public static void main(String[] args) {
long millis1 = System.currentTimeMillis();
doJob(10000);
long millis2 = System.currentTimeMillis();
long distance = millis2 - millis1;
System.out.println("Distance time in milli second: " + distance);
}
}
Kết quả chạy ví dụ:
Distance time in milli second: 3
3. TimeUnit
TimeUnit là 1 Enum mô tả các đơn vị ngày tháng năm và thời gian. Nó cung cấp các phương pháp có sẵn để chuyển đổi thời gian qua các đơn vị thời gian. TimeUnit khá hữu ích để biết cách diễn giải 1 thời gian nhất định là đơn vị thời gian cần được xem xét. Sự khác biệt nhỏ giữa các khoảng thời gian như micro giây và nano giây có thể được tìm ra bằng cách sử dụng TimeUnit.
Nó hỗ trợ các đơn vị nano giây, micro giây, mili giây, giây, phút, giờ và ngày. Đối với các đơn vị này, TimeUnit chỉ định các hằng số enum tương ứng:
Nanoseconds: 1 phần nghìn của micro giây
Microseconds: 1 phần nghìn của mili giây
Mili giây: 1 phần nghìn giây
Giây: 1 giây
Phút: 60 giây
Giờ: 60 phút
Ngày: Hai mươi bốn giờ
Ví dụ 1:
// Java program to demonstrate TimeUnit Class
import java.util.concurrent.TimeUnit;
public class GFG {
public static void main(String args[])
{
long hours = 96;
// Convert given time (hours)in days
long days = TimeUnit.DAYS.convert(hours, TimeUnit.HOURS);
// Convert days in minutes
long minutes = TimeUnit.MINUTES.convert(days, TimeUnit.DAYS);
System.out.println(hours + " Hours = " + days
+ " Days = " + +minutes
+ " Minutes");
// Convert given time (minutes) to microseconds
long micros = TimeUnit.MINUTES.toMicros(minutes);
System.out.println(minutes + " Minutes = " + micros
+ " Microseconds");
// Convert given time (microseconds) to seconds
long seconds = TimeUnit.MICROSECONDS.toSeconds(micros);
System.out.println(micros + " Microseconds = "
+ seconds + " Seconds");
// Create TimeUnit object of type Minutes
TimeUnit time = TimeUnit.valueOf("MINUTES");
System.out.println("TimeUnit object type: " + time);
}
}
Kết quả:
96 Giờ = 4 Ngày = 5760 Phút
5760 Phút = 345600000000 Micro giây
345600000000 Microseconds = 345600 giây
Loại đối tượng TimeUnit: MINUTES
Ví dụ 1:
// Java program to demonstrate TimeUnit Class
import java.util.concurrent.TimeUnit;
public class GFG implements Runnable {
public void run()
{
// Get array of TimeUnit enum constants using
// values()
for (TimeUnit unit : TimeUnit.values()) {
try {
// pause for 1 second
TimeUnit.SECONDS.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
unit + " : "
+ unit.convert(24, TimeUnit.HOURS));
}
}
public static void main(String args[])
{
GFG obj1 = new GFG();
System.out.println("TimeUnit Example");
// Create and start Thread
Thread t1 = new Thread(obj1);
t1.start();
System.out.println("Now, thread will run for 5 seconds");
try {
// Specify Thread join time, here, 5 seconds
TimeUnit.SECONDS.timedJoin(t1, 5);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread Execution Paused");
System.out.println("Resuming Thread Execution...");
}
}
Kết quả:
Ví dụ về TimeUnit
Bây giờ, chuỗi sẽ chạy trong 5 giây
NANOSECONDS: 86400000000000
MICROSECONDS: 86400000000
TRIỆU GIÂY: 86400000
GIÂY: 86400
Thực thi chuỗi bị tạm dừng
Tiếp tục thực thi chuỗi ...
PHÚT: 14g40
GIỜ: 24
NGÀY: 1
4. Java.Util.Date
Lớp java.util.Date trong java biểu diễn ngày và giờ (date và time). Nó cung cấp các Constructor và cách thức để xử lý date và time trong java.
Lớp java.util.Date implements các giao diện Serializable, Cloneable and Comparable<Date>. Nó được kế thừa bởi các lớp java.sql.Date, java.sql.Time và java.sql.Timestamp.
Sau khi lớp Calendar ra đời, hầu hết các Constructor và cách thức của lớp java.util.Date đã bị khuyến cáo không nên sử dụng nữa (@deprecated).
Các Constructor của lớp java.util.Date trong java
No.
Constructor
Mô tả
1)
Date()
Tạo 1 đối tượng Date đại diện cho ngày tháng và thời gian hiện tại.
2)
Date(long milliseconds)
CTạo 1 đối tượng Date trong 1 mili giây cho trước kể từ ngày 1 tháng 1 năm 1970, 00:00:00 GMT.
Các cách thức của lớp java.util.Date trong java
No.
cách thức
Mô tả
1)
boolean after(Date date)
kiểm tra nếu ngày hiện tại là sau ngày đã cho.
2)
boolean before(Date date)
kiểm tra nếu ngày hiện tại là trước ngày đã cho.
3)
Object clone()
Trả về đối tượng nhân bản của ngày hiện tại.
4)
int compareTo(Date date)
So sánh ngày hiện tại với ngày đã cho.
5)
boolean equals(Date date)
So sánh ngày hiện tại với ngày đã cho.
6)
static Date from(Instant instant)
Trả về 1 thể hiện của đối tượng Date từ ngày Instant.
7)
long getTime()
Trả về thời gian được đại diện bởi đối tượng date.
8)
int hashCode()
Trả về giá trị mã băm cho đối tượng ngày này.
9)
void setTime(long time)
Thay đổi ngày hiện tại và thời gian cho thời gian đã cho.
10)
Instant toInstant()
Chuyển đổi date hiện tại thành đối tượng Instant.
11)
String toString()
Chuyển đổi date hiện tại thành chuỗi String.
Ví dụ về java.util.Date trong java
Dưới đây là ví dụ lấy date trong java và in ra màn hình:
package vn.viettuts.date;
import java.util.Date;
public class DateExample1 {
public static void main(String[] args) {
long miliSeconds = System.currentTimeMillis();
// cach 1
Date date1 = new Date();
System.out.println(date1);
// cach 2
Date date2 = new Date(miliSeconds);
System.out.println(date2);
}
}
java.sql có 3 class liên quan đến ngày tháng và thời gian:
java.sql.Date
java.sql.Time
java.sql.Timestamp
Cụ thể:
– java.sql.Date đại diện cho SQL DATE, lưu trữ năm, tháng và ngày mà không có thành phần thời gian. java.sql. Date đang bỏ qua múi giờ.
– java.sql.Time đại diện cho SQL TIME và chỉ chứa thông tin về giờ, phút, giây và mili giây mà không có thành phần ngày tháng.
– java.sql.Timestamp đại diện cho SQL TIMESTAMP chứa cả thông tin Ngày và Giờ với độ chính xác nano giây.
Những class trên tham gia vào trong PreparedStatement trong JDBC API, chẳng hạn các method setDate, setTime, setTimestamp. Hoặc có thể lấy ra từ ResultSet.
Hãy kiểm tra bảng sau đây đề cập đến 5 cơ sở dữ liệu hàng đầu (không theo thứ tự) các kiểu dữ liệu ngày giờ:
Cơ sở dữ liệu
SQL DATE
SQL TIME
SQL TIMESTAMP
Nguồn
MySQL / MariaDB
NGÀY
DATETIME
THỜI GIAN
TIMESTAMP
Liên
kết Liên kết
PostgreSQL
NGÀY
GIỜ THỜI
GIAN VỚI KHU VỰC THỜI GIAN
TIMESTAMP
TIMESTAMP VỚI KHU VỰC THỜI GIAN
Liên kết
Oracle
NGÀY
TIMESTAMP
TIMESTAMP VỚI THỜI GIAN KHU VỰC THỜI GIAN
VỚI KHU VỰC GIỜ ĐỊA PHƯƠNG
Liên kết
Microsoft SQL Server
DATE
SMALLDATETIME
DATETIME
DATETIME2
DATETIMEOFFSET
THỜI GIAN
Liên kết
IBM Db2
NGÀY
THỜI GIAN
TIMESTAMP
Liên kết
6. Java.Util.Calendar
Sơ lược về các bộ lịch:
Gregorian Calendar: Đây chính là Dương Lịch, còn gọi lịch Thiên chúa giáo, là lịch quốc tế. Nó được dùng rộng rãi nhất được đặt tên theo Đức Giáo Hoàng Gregory XIII, người đã giới thiệu nó vào năm 1582.
Buddhist Calendar: Đây là 1 bộ lịch phật giáo, thường được sử dụng tại một số nước Đông Nam Á trước kia như Thái Lan, Lào, Campuchia, cũng như Sri Lanka. Hiện nay lịch này được dùng trong các lễ hội phật giáo. Và không còn quốc gia nào sử dụng lịch này 1 cách chính thức, các quốc gia này đã đổi sang dùng Gregorian Calendar. Bạn có thể tham khảo thêm thông tin về lịch này tại:
Japanese Imperial Calendar: Đây là bộ lịch truyền thống của Nhật bản, hiện nay Nhật bản đã chuyển sang sử dụng dương lịch (Gregorian Calendar), tuy nhiên bộ lịch truyền thống vẫn được dùng 1 cách không chính thức.
Calendar là class mô phỏng một hệ thống Lịch. Lớp Calendar trong java là một lớp trừu tượng (abstract) cung cấp phương thức chuyển đổi ngày giữa 1 thời điểm cụ thể theo thời gian và 1 tập hợp các trường của calendar như MONTH, YEAR, HOUR, … Nó kế thừa lớp Object và implements giao diện Comparable.
Calendar có một vài class con:
GregorianCalendar
JapaneseImperialCalendar
BuddhistCalendar
Calendar là 1 class trừu tượng. Nghĩa là bạn không thể khởi tạo nó từ cấu tử (Constructor). Tuy nhiên có 2 method tĩnh để tạo ra đối tượng Calendar.
public static Calendar getInstance();
public static Calendar getInstance(TimeZone zone);
public static Calendar getInstance(Locale aLocale);
public static Calendar getInstance(TimeZone zone,Locale aLocale);
Ví dụ:
// Tạo đối tượng Calendar mô tả thời điểm hiện tại.
// Với Locale mặc định, và TimeZone (múi giờ) mặc định
// (Của máy tính đang chạy).
Calendar c = Calendar.getInstance();
Khi bạn dùng Calendar.getInstance(TimeZone,Locale) sẽ nhận được trả về là 1 trong các class con nói trên. Mà hầu hết là trả về GregorianCalendar.
Gọi Calendar.getInstance() trả về đối tượng Calendar với tham số TimeZone theo máy tính của bạn và Locale mặc định.
Hãy xem code của class Calendar (JDK7):
Calendar (JDK7)
/**
* Gets a calendar using the default time zone and locale. The
* <code>Calendar</code> returned is based on the current time
* in the default time zone with the default locale.
*
* @return a Calendar.
*/
public static Calendar getInstance()
{
Calendar cal = createCalendar(TimeZone.getDefaultRef(),
Locale.getDefault(Locale.Category.FORMAT));
cal.sharedZone = true;
return cal;
}
/**
* Gets a calendar using the specified time zone and default locale.
* The <code>Calendar</code> returned is based on the current time
* in the given time zone with the default locale.
*
* @param zone the time zone to use
* @return a Calendar.
*/
public static Calendar getInstance(TimeZone zone)
{
return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
}
/**
* Gets a calendar using the default time zone and specified locale.
* The <code>Calendar</code> returned is based on the current time
* in the default time zone with the given locale.
*
* @param aLocale the locale for the week data
* @return a Calendar.
*/
public static Calendar getInstance(Locale aLocale)
{
Calendar cal = createCalendar(TimeZone.getDefaultRef(), aLocale);
cal.sharedZone = true;
return cal;
}
/**
* Gets a calendar with the specified time zone and locale.
* The <code>Calendar</code> returned is based on the current time
* in the given time zone with the given locale.
*
* @param zone the time zone to use
* @param aLocale the locale for the week data
* @return a Calendar.
*/
public static Calendar getInstance(TimeZone zone,
Locale aLocale)
{
return createCalendar(zone, aLocale);
}
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
Calendar cal = null;
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype == null) {
// Calendar type is not specified.
// If the specified locale is a Thai locale,
// returns a BuddhistCalendar instance.
if ("th".equals(aLocale.getLanguage())
&& ("TH".equals(aLocale.getCountry()))) {
cal = new BuddhistCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
} else if (caltype.equals("japanese")) {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else if (caltype.equals("buddhist")) {
cal = new BuddhistCalendar(zone, aLocale);
} else {
// Unsupported calendar type.
// Use Gregorian calendar as a fallback.
cal = new GregorianCalendar(zone, aLocale);
}
return cal;
}
Một số method quan trọng:
Phương thức get(int)
Giá trị trả về
get(Calendar.DAY_OF_WEEK)
1 (Calendar.SUNDAY) tới 7 (Calendar.SATURDAY).
get(Calendar.YEAR)
Năm (year)
get(Calendar.MONTH)
0 (Calendar.JANUARY) tới 11 (Calendar.DECEMBER).
get(Calendar.DAY_OF_MONTH)
1 tới 31
get(Calendar.DATE)
1 tới 31
get(Calendar.HOUR_OF_DAY)
0 tới 23
get(Calendar.MINUTE)
0 tới 59
get(Calendar.SECOND)
0 tới 59
get(Calendar.MILLISECOND)
0 tới 999
get(Calendar.HOUR)
0 tới 11, được sử dụng cùng với Calendar.AM_PM.
get(Calendar.AM_PM)
0 (Calendar.AM) hoặc 1 (Calendar.PM).
get(Calendar.DAY_OF_WEEK_IN_MONTH)
DAY_OF_MONTH 1 tới 7 luôn luôn tương ứng với DAY_OF_WEEK_IN_MONTH 1;
8 tới 14 tương ứng với DAY_OF_WEEK_IN_MONTH 2, …
get(Calendar.DAY_OF_YEAR)
1 tới 366
get(Calendar.ZONE_OFFSET)
Giá trị GMT của múi giờ.
get(Calendar.ERA)
Biểu thị AD (GregorianCalendar.AD), BC (GregorianCalendar.BC).
CalendarFieldsDemo.java
package org.o7planning.tutorial.calendar;
import java.util.Calendar;
public class CalendarFieldsDemo {
public static void main(String[] args) {
// Tạo một đối tượng Calendar (Lịch) mặc định.
// Với time zone (múi giờ) và locale mặc định.
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
// Trả về giá trị từ 0 - 11
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);
int second = c.get(Calendar.SECOND);
int millis = c.get(Calendar.MILLISECOND);
System.out.println("Year: " + year);
System.out.println("Month: " + (month + 1));
System.out.println("Day: " + day);
System.out.println("Hour: " + hour);
System.out.println("Minute: " + minute);
System.out.println("Second: " + second);
System.out.println("Minute: " + minute);
System.out.println("Milli Second: " + millis);
}
}
void set(int calendarField, int value)
void set(int year, int month, int date)
void set(int year, int month, int date, int hour, int minute, int second)
// Thêm hoặc trừ một khoảng thời gian trên một trường (field) của Calendar.
// Dựa trên quy tắc của bộ Lịch.
void add(int field, int amount)
// Cuộn (lên/xuống) một trường (field) của Calendar.
// roll(): Không làm ảnh hưởng tới các trường khác của Calendar.
void roll(int calendarField, boolean up)
// Cuộn lên (roll up) một trường của Calendar.
// roll(): Không làm ảnh hưởng tới các trường khác của Calendar.
void roll(int calendarField, int amount):
// Trả về đối tượng Date dựa trên giá trị của Calendar.
Date getTime()
void setTime(Date date)
// Trả về số mili giây của đối tượng Calendar này.
long getTimeInMills():
void setTimeInMillis(long millis)
void setTimeZone(TimeZone value)
CalendarDemo.java
package org.o7planning.tutorial.calendar;
import java.util.Calendar;
public class CalendarDemo {
public static void showCalendar(Calendar c) {
int year = c.get(Calendar.YEAR);
// Trả về giá trị từ 0 - 11
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);
int second = c.get(Calendar.SECOND);
int millis = c.get(Calendar.MILLISECOND);
System.out.println(" " + year + "-" + (month + 1) + "-" + day //
+ " " + hour + ":" + minute + ":" + second + " " + millis);
}
public static void main(String[] args) {
// Tạo đối tượng Calendar, mô tả thời điểm hiện tại.
// Với time zone (múi giờ) và locale mặc định.
Calendar c = Calendar.getInstance();
System.out.println("First calendar info");
showCalendar(c);
// roll(..): Cuộn một trường (field) của Calendar.
// roll(..): Không làm thay đổi các trường khác.
// Ví dụ: Cuộn lên thêm một giờ (boolean up = true)
c.roll(Calendar.HOUR_OF_DAY, true);
System.out.println("After roll 1 hour");
showCalendar(c);
// roll(..): Không làm thay đổi các trường khác của Calendar.
// Cuộn xuống một giờ (boolean up = false)
c.roll(Calendar.HOUR_OF_DAY, false);
System.out.println("After roll -1 hour");
showCalendar(c);
// add(..): Có thể làm thay đổi các trường khác của Calendar.
// Tăng thêm một giờ (boolean up = true)
c.add(Calendar.HOUR_OF_DAY, 1);
System.out.println("After add 1 hour");
showCalendar(c);
// roll(..): Không làm thay đổi các trường khác của Calendar.
// Cuộn xuống 30 ngày.
c.roll(Calendar.DAY_OF_MONTH, -30);
System.out.println("After roll -30 day");
showCalendar(c);
// add(..): Có thể làm thay đổi các trường khác của Calendar.
// Thêm 30 ngày.
c.add(Calendar.DAY_OF_MONTH, 30);
System.out.println("After add 30 day");
showCalendar(c);
}
}
Kết quả chạy ví dụ:
First calendar info
2021-5-15 20:35:27 395
After roll 1 hour
2021-5-15 21:35:27 395
After roll -1 hour
2021-5-15 20:35:27 395
After add 1 hour
2021-5-15 21:35:27 395
After roll -30 day
2021-5-16 21:35:27 395
After add 30 day
2021-6-15 21:35:27 395
7. Chuyển Đổi Kiểu Dữ Liệu Giữa Date Và Timestamp Trong Java
Date ở đây là class thuộc gói java.util
Timestamp là class thuộc gói java.sql
Các bạn cùng Techacademy tìm hiểu ví dụ sau đây để hiểu cách chuyển đổi cụ thể ra sao nhé.
Ví dụ Chuyển Date thành Timestamp và Timestamp thành Date
package com.ngockhuong;
import java.sql.Timestamp;
import java.util.Date;
public class DateToTimestamp {
public static void main(String[] args) {
// lấy ngày giờ hiện tại
Date now = new Date();
System.out.println(now);
// chuyển Date sang Timestamp
Timestamp timestamp = new Timestamp(now.getTime());
System.out.println(timestamp);
// chuyển Timestamp sang Date
Date last = new Date(timestamp.getTime());
System.out.println(last);
}
}
Kết quả:
Tue Feb 14 01:53:24 ICT 2017
2017-02-14 01:53:24.981
Tue Feb 14 01:53:24 ICT 2017
8. Chuyển Đổi Kiểu Dữ Liệu Giữa String Và Date Trong Java
Các bạn có thể chuyển đổi String thành Date trong java bằng cách sử dụng phương thức parse() của các lớp DateFormat và SimpleDateFormat. Để tìm hiểu sâu hơn về 2 khái niệm này, bạn có thể tham khảo lớp DateFormat và lớp SimpleDateFormat.
Ví dụ về chuyển đổi String thành Date trong java
Ví dụ 1:
import java.text.SimpleDateFormat;
import java.util.Date;
public class StringToDateExample1 {
public static void main(String[] args) throws Exception {
String sDate = "17/07/2017";
Date date = new SimpleDateFormat("dd/MM/yyyy").parse(sDate);
System.out.println(sDate + "\t" + date);
}
}
Kết quả:
17/07/2017 Mon Jul 17 00:00:00 ICT 2017
Ví dụ 2: Các kiểu khác của chuyển đổi String thành Date trong java
import java.text.SimpleDateFormat;
import java.util.Date;
public class StringToDateExample2 {
public static void main(String[] args) throws Exception {
String sDate1 = "12/7/2017";
String sDate2 = "12-07-2017";
String sDate3 = "12/7/2017 13:30:50";
String sDate4 = "12-07-2017 13:30:50";
SimpleDateFormat formatter1 = new SimpleDateFormat("dd/MM/yyyy");
SimpleDateFormat formatter2 = new SimpleDateFormat("dd-MM-yyyy");
SimpleDateFormat formatter3 = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
SimpleDateFormat formatter4 = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
Date date1 = formatter1.parse(sDate1);
Date date2 = formatter2.parse(sDate2);
Date date3 = formatter3.parse(sDate3);
Date date4 = formatter4.parse(sDate4);
System.out.println(sDate1 + "\t" + date1);
System.out.println(sDate2 + "\t" + date2);
System.out.println(sDate3 + "\t" + date3);
System.out.println(sDate4 + "\t" + date4);
}
}
Tùy biến định dạng Date. Hãy xem 1 số ví dụ định dạng và kết quả trả về.
Khuôn mẫu
Kết quả
dd.MM.yy
30.06.09
yyyy.MM.dd G ‘at’ hh:mm:ss z
2009.06.30 AD at 08:29:36 PDT
EEE, MMM d, ”yy
Tue, Jun 30, ’09
h:mm a
8:29 PM
H:mm
8:29
H:mm:ss:SSS
8:28:36:249
K:mm a,z
8:29 AM,PDT
yyyy.MMMMM.dd GGG hh:mm aaa
2009.June.30 AD 08:29 AM
Tham khảo bảng sau đây cho một số ngày và thời gian phổ biến được dùng trong java.text.SimpleDateFormat
Ký hiệu
Ý nghĩa
Trả về
Example
G
Kỷ nguyên
Text
AD
y
Năm
Number
2009
M
Tháng trong năm
Text & Number
July & 07
d
Ngày trong tháng
Number
10
h
Giờ am/pm (1-12)
Number
12
H
Giờ trong ngày (0-23)
Number
0
m
Phút trong giờ
Number
30
s
Giây trong phút
Number
55
S
millisecond
Number
978
E
Ngày trong tuần
Text
Tuesday
D
Ngày trong năm
Number
189
F
Ngày của tuần trong tháng
Number
2 (2nd Wed in July)
w
Tuần trong năm
Number
27
W
Tuần trong tháng
Number
2
a
am/pm
Text
PM
k
Giờ trong ngày (1-24)
Number
24
K
Giờ am/pm (0-11)
Number
0
z
Múi giờ (time zone)
Text
Pacific Standard Time
‘
escape for text
Delimiter
(none)
‘
single quote
Literal
‘
10. Kiểu Ngày Tháng Trong Java
Dưới đây là 1 số lớp hỗ trợ kiểu dữ liệu ngày giờ:
java.util.Date: Một lớp đại diện cho ngày tháng năm và thời gian. Tiếc là hầu hết các phương thức của nó đã bị lỗi thời, khuyến cáo là không nên sử dụng các phương thức đó, tuy nhiên lớp Date vẫn được sử dụng rỗng rãi.
java.sql.Date: Một lớp mô tả ngày tháng năm. Thông tin về thời gian bị cắt bỏ. Lớp này thường sử dụng trong JDBC.
java.util.Calendar: Lớp mô tả bộ Lịch. Nó có các phương thức toán học về thời gian, chẳng hạn thêm ngày, bớt ngày,…
java.time.*: Lớp này cung cấp đầy đủ các API xử lý ngày giờ trong Java. Đây là lớp mới được hỗ trợ từ Java 8.
11. Ép Kiểu Date Sang String Trong Java
Các bạn cũng có thể dùng hàm String() hoặc phương thức toString()
String(Date()) // Trả về "Thu Jul 17 2021 15:38:19 GMT+0200 (W. Europe Daylight Time)"
Dùng toString() cũng cho kết quả tương tự.
Date().toString() // Trả về "Thu Jul 17 2014 15:38:19 GMT+0200 (W. Europe Daylight Time)"
Trước khi thảo luận về mã căn bậc hai trong java, trước tiên chúng ta nên hiểu thuật ngữ căn bậc hai.
Căn bậc hai của một số n là một số x sao cho x2 = n hoặc, một số x có bình phương là n.
Ngoài ra còn có một phương thức dựng sẵn trong java để tính căn bậc hai trong gói java.math có phương thức sqrt ().
Căn bậc hai của x là hợp lý khi và chỉ khi x là số hữu tỷ có thể được biểu diễn dưới dạng tỷ lệ của hai hình vuông hoàn hảo. (căn bậc hai của 2 là một số vô tỷ và bất phương trình bậc hai cho tất cả các số tự nhiên không vuông.) Hàm căn bậc hai ánh xạ các số hữu tỷ thành các số đại số (một siêu số của các số hữu tỷ).
I. Cú Pháp Hàm Sqrt() Trong Thư Viện Math Java
Trước tiên các bạn sẽ tìm hiểu về cú pháp của nó đã nhé.
double sqrt(double d)
Tham số
Dưới đây là chi tiết về tham số của sqrt() trong Java:
d — Một kiểu dữ liệu gốc
Trả về giá trị
Trả về căn bậc 2 của tham số.
II. Cách Dùng Hàm Sqrt() Trong Thư Viện Math Java
Trong phần này chúng tôi sẽ thực hiện 2 ví dụ sử dụng phương thức sqrt() để minh họa cho cách dùng của nó.
Ví dụ 1: Ở ví dụ dưới đây mình sẽ khai báo và khởi tạo sẵn giá trị cho biến, sau đó tính căn bậc hai của các số đó.
import java.lang.Math;
class Main {
public static void main(String[] args) {
//khai báo 4 biến và khởi tạo cho nó
double value1 = Double.POSITIVE_INFINITY;
double value2 = 25.0;
double value3 = -16;
double value4 = 0.0;
//sử dụng phương thức sqrt() để tính căn bậc hai của 4 số trên
System.out.println(Math.sqrt(value1));
System.out.println(Math.sqrt(value2));
System.out.println(Math.sqrt(value3));
System.out.println(Math.sqrt(value4));
System.out.println("--------------------------------");
System.out.println("Chương trình này được đăng tại Freetuts.net");
}
}
Kết quả:
Ví dụ 2: Ở ví dụ dưới đây chúng tôi sẽ yêu cầu người dùng nhập vào một số, sau đó tính căn bậc 2 rồi hiển thị ra màn hình.
import java.lang.Math;
import java.util.Scanner;
class Main {
public static void main(String[] args) {
//sử dụng class Scanner để yêu cầu người dùng nhập vào số cần tính căn bậc hai
Scanner sc = new Scanner(System.in);
double a, squareRoot;
System.out.println("Nhập vào số cần tính căn bậc hai: ");
a = sc.nextDouble();
//tính căn bậc hai của số nhập vào rồi gán cho biến squareRoot
squareRoot = Math.sqrt(a);
System.out.println("Căn bậc hai của "+a+" là: " + squareRoot);
System.out.println("--------------------------------");
System.out.println("Chương trình này được đăng tại Freetuts.net");
}
}
Trong ví dụ dưới đây chúng tôi sẽ sử dụng class Scanner, để nhận dữ liệu từ người dùng rồi mới thực hiện tính căn bậc 2 của giá trị đó.
Kết quả:
III. Tính Căn Bậc 2 Từ 1 Đến 100 Trong Java
Đề bài: Viết chương trình dùng ngôn ngữ lập trình Java tính S = √1 + √2 + √3 + … + √100′
Yêu cầu kiến thức:
Xác định chính xác kiểu dữ liệu cho các biến
Sử dụng hàm căn bậc 2 trong thư viện Math của Java
Code tham khảo dưới đây được viết trên JDK 8.x:
package timoday;
public class Main {
public static void main(String[] args) {
double S = 0;
for (int i = 1; i <= 100; i++) {
S += Math.sqrt(i);
}
System.out.println("Tong can bac 2 tu 1 den 100 la: " + S);
}
}
IV. Giải Phương Trình Bậc 2 Trong Java
Viết chương trình giải phương trình bậc 2 trong java. Phương trình bậc 2 có dạng:
ax2 + bx + c = 0
Kiến thức dùng trong bài này, java.util.Scanner được dùng để đọc dữ liệu nhập vào từ bàn phím và từ khóa static trong java. Các bạn cũng cần tìm hiểu về package trong java.
Bài này được viết trên eclipse, bạn có thể tham khảo bài tạo chương trình java đầu tiên trên eclipse.
package vn.viettuts.baitap;
import java.util.Scanner;
/**
* Giải phương trình bậc 2
*
* @author viettuts.vn
*/
public class BaiTap1 {
private static Scanner scanner = new Scanner(System.in);
/**
* main
*
* @param args
*/
public static void main(String[] args) {
System.out.print("Nhập hệ số bậc 2, a = ");
float a = BaiTap1.scanner.nextFloat();
System.out.print("Nhập hệ số bậc 1, b = ");
float b = BaiTap1.scanner.nextFloat();
System.out.print("Nhập hằng số tự do, c = ");
float c = scanner.nextFloat();
BaiTap1.giaiPTBac2(a, b, c);
}
/**
* Giải phương trình bậc 2: ax2 + bx + c = 0
*
* @param a: hệ số bậc 2
* @param b: hệ số bậc 1
* @param c: số hạng tự do
*/
public static void giaiPTBac2(float a, float b, float c) {
// kiểm tra các hệ số
if (a == 0) {
if (b == 0) {
System.out.println("Phương trình vô nghiệm!");
} else {
System.out.println("Phương trình có một nghiệm: "
+ "x = " + (-c / b));
}
return;
}
// tính delta
float delta = b*b - 4*a*c;
float x1;
float x2;
// tính nghiệm
if (delta > 0) {
x1 = (float) ((-b + Math.sqrt(delta)) / (2*a));
x2 = (float) ((-b - Math.sqrt(delta)) / (2*a));
System.out.println("Phương trình có 2 nghiệm là: "
+ "x1 = " + x1 + " và x2 = " + x2);
} else if (delta == 0) {
x1 = (-b / (2 * a));
System.out.println("Phương trình có nghiệm kép: "
+ "x1 = x2 = " + x1);
} else {
System.out.println("Phương trình vô nghiệm!");
}
}
}
Kết quả:
Nhập hệ số bậc 2, a = 2
Nhập hệ số bậc 1, b = 1
Nhập hằng số tự do, c = -1
Phương trình có 2 nghiệm là: x1 = 0.5 và x2 = -1.0
Trong ví dụ trên, phương thức Math.sqrt(double a) được dùng để tính căn bậc 2 của a.
Kế thừa trong Java là 1 trong những tính năng quan trọng nhất của hệ thống lập trình hướng đối tượng (OOP). Chúng ta đã thấy tổng quan về kế thừa trong hướng dẫn trước của Các khái niệm OOP trong Java. Trong hướng dẫn này, chúng ta sẽ hiểu chi tiết về kế thừa trong Java và kiểu của nó với nhiều ví dụ khác nhau.
Kế thừa trong Java là 1 tính năng giúp dùng lại các phương thức và biến của 1 lớp này trong 1 lớp khác. Nói cách khác, nó cho phép 1 lớp mới kế thừa các thuộc tính và chức năng của 1 lớp hiện có mà không cần viết lại mã. Nó thực hiện Cha mẹ và con mối quan hệ. Điều này có nghĩa là lớp con có thể dùng lại trực tiếp các biến và hàm của lớp cha.
I. Tại Sao Dùng Tính Kế Thừa Trong Java?
Tính kế thừa trong Java là môt kỹ thuật mà trong đấy 1 đối tượng thu được hầu hết thuộc tính và hành vi của đối tượng cha. Ý tưởng đằng sau tính kế thừa trong Java là các các bạn có thể tạo các lớp mới mà được xây dựng dựa trên các lớp đang tồn tại.
Khi các các bạn kế thừa từ 1 lớp đang tồn tại, các các bạn có thể tái dùng các phương thức và các trường của lớp cha, và các các bạn cũng có thể bổ sung thêm các phương thức và các trường khác. Tính kế thừa biểu diễn mối quan hệ IS-A, còn được gọi là mối quan hệ cha-con.
Có 1 số lý do vì sao nên dùng kế thừa trong lập trình Java:
– Khả năng thể hiện các mối quan hệ kế thừa đảm bảo sự gần gũi với các mô hình trong thế giới thực.
– 1 lý do khác là khả năng tái dùng. Khi dùng tính kế thừa ta có thể lấy 1 lớp mới từ 1 lớp hiện có và thêm tính năng mới vào nó mà không cần sửa đổi lớp cha và viết lại lớp cha để kế thừa nó.
– 1 lý do khác là tính chất bắc cầu. Nếu lớp 1 kế thừa các thuộc tính từ 1 lớp B, sau đấy hầu hết các lớp con của A sẽ tự động kế thừa từ B. Tính chất này được gọi là tính chất bắc cầu của kế thừa.
II. Cú Pháp Của Tính Kế Thừa Trong Java
Mọi người đã biết rằng để kế thừa 1 lớp, chúng ta dùng từ khóa expand. Cú pháp của việc dùng kế thừa trong Java chính là:
class ten_lop_con extends ten_lop_cha {
//cac phuong thuc va cac truong
}
Từ khóa extends chỉ rằng bạn đang tạo 1 lớp mới mà kế thừa từ 1 lớp đang tồn tại. Trong Java, 1 lớp mà được kế thừa được gọi là 1 lớp cha. Lớp mới được gọi là lớp con.
Trong ví dụ sau, Programmer là lớp con và Employee là lớp cha. Mối quan hệ giữa hai lớp là Programmer IS-A Employee. Nghĩa là Programmer là 1 kiểu của Employee.
class Employee {
float salary = 40000;
}
class Programmer extends Employee {
int bonus = 10000;
public static void main(String args[]) {
Programmer p = new Programmer();
System.out.println("Luong Lap trinh vien la:" + p.salary);
System.out.println("Bonus cua Lap trinh vien la:" + p.bonus);
}
}
Ở trên, đối tượng Programmer có thể truy cập trường của riêng lớp nó cũng như của lớp Employee, đấy là ví dụ cho tính tái dùng.
III. Các Loại Kế Thừa Trong Java
Có 5 kiểu kế thừa trong Java đấy là đơn kế thừa, Đa kế thừa, Kế thừa đa cấp, Kế thừa phân cấp, Kế thừa lai
Khi 1 class được kế thừa từ nhiều class đươc gọi là đa kế thừa. Trong Java, đa kế thừa chỉ được support thông qua interface, như đã được nói đến trong bài interface trong Java
1. Đơn kế thừa (Single Inheritance):
Đơn kế thừa: nghĩa là 1 lớp chỉ được kế thừa từ đúng 1 lớp khác. Hay nói cách khác, lớp con chỉ có duy nhất 1 lớp cha.
Cú pháp khai báo đơn kế thừa:
class lopcon : phamvidulieu lopcha
{
// nội dung lớp con
};
dưới đây là 1 ví dụ:
#include <iostream>
using namespace std;
// Lớp cha
class Mayvitinh
{
public:
Mayvitinh()
{
cout << "This is a computer" << endl;
}
};
// Lớp con kế thừa từ lớp cha
class mayAcer : public Mayvitinh
{
};
// main function
int main()
{
mayAcer may1;
return 0;
Chương trình sẽ cho kết quả:
2. Đa kế thừa (Multiple Inheritance):
Đa kế thừa là 1 tính năng của ngôn ngữ C++. Trong đấy 1 lớp có thể kế thừa từ nhiều hơn 1 lớp khác. Nghĩa là 1 lớp con được kế thừa từ nhiều hơn 1 lớp cơ sở.
Cú pháp khai báo đa kế thừa:
class lopcon : phamvitruycap lopcha1, phamvitruycap lopcha2, ....
{
// nội dung của lớp con
};
Ở đây, các lớp cơ sở sẽ được phân tách bằng dấu phẩy , và phạm vi truy cập cho mọi lớp cơ sở phải được chỉ định.
Chúng ta cùng xem ví dụ sau:
#include <iostream>
using namespace std;
// Lớp cơ sở thứ nhất
class Mayvitinh
{
public:
Mayvitinh()
{
cout << "This is a computer's brand" << endl;
}
};
// Lớp cơ sở thứ hai
class Maylaptop
{
public:
Maylaptop()
{
cout << "This is a laptop's brand" << endl;
}
};
// Lớp con kế thừa từ 2 lớp cha
class mayAcer : public Mayvitinh, public Maylaptop
{
};
// main function
int main()
{
mayAcer may1;
return 0;
}
Sau khi chạy ta sẽ có kết quả sau:
3. Kế thừa đa cấp (Multilevel Inheritance):
Kế thừa đa cấp: Trong kiểu thừa kế này, 1 lớp dẫn xuất được tạo từ 1 lớp dẫn xuất khác.
Ví dụ về kế thừa đa cấp:
#include <iostream>
using namespace std;
// Lớp cha
class Mayvitinh
{
public:
Mayvitinh()
{
cout << "This is a computer's brand" << endl;
}
};
// Lớp con kế thừa từ lớp cha
class Maylaptop : public Mayvitinh
{
public:
Maylaptop()
{
cout << "This is a laptop's brand" << endl;
}
};
// Lớp con kế thừa từ lớp cha thứ 2
class mayAcer : public Maylaptop
{
public:
mayAcer(){
cout << "This brand is Acer" << endl;
}
};
// main function
int main()
{
mayAcer may1;
return 0;
}
Sau khi chạy ta có kết quả:
4. Kế thừa phân cấp (Hierarchical Inheritance):
Kế thừa phân cấp: Trong kiểu thừa kế này, sẽ có nhiều hơn 1 lớp con được kế thừa từ 1 lớp cha duy nhất.
Chúng ta có ví dụ:
#include <iostream>
using namespace std;
// Lớp cha
class Mayvitinh
{
public:
Mayvitinh()
{
cout << "This is a computer's brand" << endl;
}
};
// Lớp con thứ nhất
class mayAsus : public Mayvitinh
{
};
// Lớp con thứ hai
class mayAcer : public Mayvitinh
{
};
// main function
int main()
{
mayAcer may1;
mayAsus may2;
return 0;
}
Sau khi chạy ta có kết quả:
5. Kế thừa lai (Kế thừa ảo) – Hybrid (Virtual) Inheritance:
Kế thừa lai (Kế thừa ảo): được thực hiện bằng cách kết hợp nhiều hơn 1 loại thừa kế.
Chúng ta lấy ví dụ về sự kết hợp của phân cấp và đa kế thừa dưới đây:
#include <iostream>
using namespace std;
// Lớp cha
class Mayvitinh
{
public:
Mayvitinh()
{
cout << "This is a computer's brand" << endl;
}
};
// Lớp cha
class Maylaptop
{
public:
Maylaptop()
{
cout << "This is a laptop's brand" << endl;
}
};
// Lớp con thứ nhất
class mayAcer : public Mayvitinh
{
};
// Lớp con thứ hai
class mayAsus : public Mayvitinh, public Maylaptop
{
};
// main function
int main()
{
mayAsus may1;
mayAcer may2;
return 0;
}
Sau khi chạy ta có kết quả:
IV. Từ Khóa Instanceof Trong Java
Trong bài viết này, các bạn sẽ tìm hiểu về toán tử Instanceof Java 1 cách chi tiết với sự trợ giúp của các ví dụ.
Trong Java, từ khóa Instanceof là 1 toán tử nhị phân. Nó được dùng để kiểm tra xem 1 đối tượng có phải là 1 Instance của 1 class cụ thể hay không.
Toán tử cũng kiểm tra xem 1 đối tượng có phải là 1 Instance của 1 class thực hiện 1 giao diện(interface) hay không (sẽ được thảo luận sau trong bài hướng dẫn này).
Cú pháp của toán tử Instanceof là:
result = objectName Instanceof className;
Toán hạng bên trái của toán tử Instanceof là tên đối tượng và toán hạng bên phải là tên class. Kết quả sẽ true nếu 1 đối tượng là 1 Instance của 1 class và sẽ là false nếu nó không phải.
Ví dụ 1: Toán tử Instanceof
/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/
class Main {
public static void main (String[] args) {
String name = "Programiz";
Integer age = 22;
System.out.println("Is name an Instance of String: "+ (name Instanceof String));
System.out.println("Is age an Instance of Integer: "+ (age Instanceof Integer));
}
}
Khi chúng ta chạy chương trình, kết quả sẽ là:
Is name an Instance of String: true
Is age an Instance of Integer: true
Trong ví dụ trên, chúng ta đã tạo đối tượng name của kiểu String và 1 đối tượng khác là age của kiểu Integer. Sau đấy, chúng ta đã dùng toán tử Instanceof để kiểm tra xem liệu rằng đối tượng name có thuộc kiểu String và đối tượng age có thuộc kiểu Integer hay không.
dùng toán tử Instanceof trong tính kế thừa
Vơi tính thừa kế, toán tử Instanceof được dùng để kiểm tra xem liệu rằng 1 đối tượng của subclass có phải cũng là 1 toán tử Instanceof của siêu class hay là không.
Ví dụ 2: Instanceof trong kế thừa
/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/
class Animal {
}
// Dog class is a subclass of Animal
class Dog extends Animal {
}
class Main {
public static void main(String[] args){
Dog d1 = new Dog();
// checks if d1 is an object of Dog
System.out.println("Is d1 an Instance of Dog: "+ (d1 Instanceof Dog));
// checks if d1 is an object of Animal
System.out.println("Is d1 an Instance of Animal: "+ (d1 Instanceof Animal));
}
}
Khi chúng ta chạy chương trình, kết quả sẽ là:
Is d1 is an Instance of Dog: true
Is d1 an Instance of Animal: true
Trong ví dụ trên, d1 là 1 Instance của cả class Dog và Animal. Do đấy, cả d1 Instanceof Dog và d1 Instanceof Animal trả về kết quả là true .
V. Quan hệ HAS-A Trong Java
Nếu 1 class có tham chiếu đến 1 thực thể khác, được gọi là quan hệ HAS – A.
Ví dụ Chúng ta có class Student, 1 Student cần có các thông tin như id, name, email, etc. Và thực thể Address chứa các thông tin city, country, zipcode etc.
// File Student.java
public class Student {
String id;
String name;
Address address;
}
// File Address.java
public class Address {
String city;
String country;
String zipcode;
}
Class Student tham chiếu đến Address là 1 thực thể, chúng ta có thể phát biểu như sau: Student có quan hệ HAS – A Address.
Mục đích dùng HAS – A
Ở ví dụ trên, các bạn hoàn toàn có thể gộp các thuộc tính địa chỉ vào class Student như sau.
// File Student.java
public class Student {
String id;
String name;
String city;
String country;
String zipcode;
}
Với cách làm trên của chúng ta là hoàn toàn ổn. Sau đấy ứng dụng mở rộng chúng ta có thêm class Employee cũng cần dùng đến địa chỉ. Nếu như làm theo cách trên thì chúng ta phải duplicate các thuộc tính address cho Employee. Làm như vậy thì code sẽ trở nên dài dòng, giảm tính reused code của project.
Vậy nên chúng ta cần dùng HAS – A để tăng tính tái dùng code. Định nghĩa Address ra 1 class riêng, và Student, Employee đều tham chiếu đến Address.
Khi nào nên dùng HAS -A
Như đã bàn luận ở trên, dùng HAS – A làm tăng tính tái dùng code. Khi các bạn thấy giữa các class có các thông tin chung giống hệt nhau thì ta nên tách nhỏ nó ra thành 1 class khác bao gồm những thuộc tính chung.
Các bạn đừng nhầm lẫn HAS – A với IS – A(thừa kế) nha. Thừa kế là 1 khái niệm mà các class con dùng lại các thuộc tính và method của class cha có sẵn. HAS – A là đặt những điểm chung ra riêng 1 class và import vào các class khác để dùng.
Ví dụ
Address.java
// File Address.java
public class Address {
String city;
String country;
String zipcode;
public Address(String city, String country, String zipcode) {
this.city = city;
this.country = country;
this.zipcode = zipcode;
}
}
// File Main.java
public class Main {
public static void main(String[] args) {
Address address = new Address("HCM", "VN", 11000);
Student student = new Student(1, "Hai", address);
student.display();
}
}
Output:
ID: 1
Name: Hai
Address: HCM VN 11000
VI. Bài Tập Kế Thừa Trong Java Có Lời Giải
Tạo 1 lớp Person lưu trữ các thông tin dưới đây ( Tên, giới tính, địa chỉ,ngay sinh)
Tạo 1 lớp Student kế thừa từ lớp person lưu trữ các thông tin như dưới đây:
Mã sinh viên,điểm trung bình, email
Viết 1 phương thức nhập thông tin của student
Viết 1 phương thức hiện thị thông tin của student
Viết phương trình xem xét có sinh viên nào được học bổng không? điểm trung bình hơn 8.0 sẽ được hổng bổng
Khai báo class parent Person
class Person{
protected String name;
protected String sex;
protected String adrress;
protected String ngaysinh;
public void intputPerson(){
Scanner scanner = new Scanner(System.in);
System.out.println("Nhap Ten :");
this.name = scanner.nextLine();
System.out.println("Nhap gioi tinh");
this.sex = scanner.nextLine();
System.out.println("Nhap dia chi");
this.adrress = scanner.nextLine();
System.out.println("Nhap ngay sinh");
this.ngaysinh = scanner.nextLine();
}
public void showPerson(){
System.out.println("Ho Ten : " +this.name + " Gioi Tinh : " +this.sex +" Dia chi : " +this.adrress +" Ngay sinh : " +this.ngaysinh);
}
}
Trong đấy:
Có 4 biến bao gồm ( ten,gioitinh,diachi,ngaysing) và có 2 phương thức:
intputPerson : Là phương thức nhập thông tin
showPerson : Xuất hiện thông tin
Tiếp thep khai báo 1 lớp student được kế thừa từ lớp Person
class Student extends Person{
protected String masv;
protected float diemtrungbinh;
protected String email;
public void intPutStudent(){
Scanner input = new Scanner(System.in);
System.out.println("Nhap ma sinh vien");
this.masv = input.nextLine();
System.out.println("Nhap diem trung binh");
this.diemtrungbinh = input.nextFloat();
System.out.println("Nhap email");
this.email = input.nextLine();
}
public void showStudent(){
System.out.println("Ma sinh vien : " +this.masv +" Diem trung binh " + this.diemtrungbinh);
}
public void kiemtrahocbong(){
if(this.diemtrungbinh >=8){
System.out.println("Duoc hoc bong");
}else{
System.out.println("Khong duoc hoc bong");
}
}
}
Lớp Person có 3 biến cần lưu trữ,và 3 phương thức java
intPutStudent : Nhập thông tin của sinh viên đấy.
showStudent : Xuất thông tin của sinh viên
kiemtrahocbong : Phương thức kiểm tra học bổng
Hàm khởi tạo để thực hiện chương trình như sau:
ublic class BT1 {
public static void main(String []args){
Student person = new Student();
person.intputPerson();
person.intPutStudent();
person.showPerson();
person.showStudent();
person.kiemtrahocbong();
}
}
Kết quả:
VII. Đa Kế Thừa Trong Java
Nếu 1 lớp triển khai đa kế thừa, hoặc 1 Interface kế thừa từ nhiều Interface thì đó là đa kế thừa.
Trong Java, 1 lớp chỉ được thừa kế (extends) từ 1 lớp, có thể cài đặt (implements) nhiều interface. Tuy nhiên, 1 interface có thể thừa kế (extends) nhiều interface.
1 interface không thể cài đặt (implements) interface khác, do interface không phần cài đặt, chỉ chứa các khai báo.
Ví dụ 1 lớp cài đặt (implements) nhiều interface:
public interface Shape {
void draw();
}
public interface Color {
String getColor();
}
public class Rectangle implements Shape, Color {
@Override
public void draw() {
System.out.println("Draw " + this.getColor() + " rectangle");
}
@Override
public String getColor() {
return "red";
}
}
Ví dụ interface kế thừa (extend) nhiều interface:
public interface Shape {
void draw();
}
public interface Color {
String getColor();
}
public interface ShapeColor extends Shape, Color {
}
public class Circle implements ShapeColor {
@Override
public void draw() {
System.out.println("Draw " + this.getColor() + " circle");
}
@Override
public String getColor() {
return "red";
}
}
Câu hỏi: Đa kế thừa không được hỗ trợ thông qua lớp trong Java nhưng là có thể bởi Interface, vì sao?
Như đã giới thiệu, kế thừa không được hỗ trợ thông qua lớp. Nhưng nó được hỗ trợ bởi Interface bởi vì không có tính lưỡng nghĩa khi trình triển khai được cung cấp bởi lớp Implementation.
Ví dụ đa thừa kế với Interface
public interface Printable {
void print();
}
public interface Showable {
void print();
}
public class InterfaceDemo implements Printable, Showable {
public void print() {
System.out.println("Welcome to gpcoder.com");
}
public static void main(String args[]) {
InterfaceDemo obj = new InterfaceDemo();
obj.print();
}
}
Trong ví dụ trên, interface Printable và Showable có cùng các phương thức print() nhưng trình triển khai của nó được cung cấp bởi lớp InterfaceDemo, vì thế không có tính lưỡng nghĩa ở đây.
Ví dụ đa thừa kế với class
public class Printable {
void print() {
System.out.println("Printable");
}
}
public class Showable {
void print() {
System.out.println("Showable");
}
}
// Không thể thực hiện đa thừa kế với class
public class InterfaceDemo extends Printable, Showable {
public static void main(String args[]) {
InterfaceDemo obj = new InterfaceDemo();
obj.print(); // Không thể xác định được gọi phương thức print() của class nào
}
}
Trong ví dụ trên, lớp Printable và Showable có cùng các phương thức print() và InterfaceDemo kế thừa 2 class đó không override lại phương thức print() nên trình biên dịch không biết thực thi phương thức print() của lớp Printable hay là của lớp Showable. Để bảo đảm an toàn và giảm tính phức tạp của hệ thống nên Java không hỗ trợ đa thừa kế đối với class.