Vẽ Tam Giác Trong C++

Vẽ tam giác trong C++ là một trong những bài tập lập trình về C++ sử dụng vòng lặp khá hay giúp các bạn luyện tư duy code cũng như cách sử dụng vòng lặp. Dưới đây là một số lời giải các bài tập vẽ tam giác trong C++

I. Vẽ Tam Giác Cân Trong C++

Viết chương trình C++ sử dụng ký tự * để vẽ tam giác vuông cân trong C++.Chúng ta sử dụng hai vòng lặp lồng nhau để giải bài toán này.

Lời Giải:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int n; int q = 0;
    printf("Chuong trinh nay se in ra tam giac can\n");
    printf("Nhap chieu cao tam giac cua ban: \n");
    scanf("%d",&n);

    while (n > 0)
    {
        for (int i = 1; i<n; i++)
            printf("%c", ' ');
        for (int k = 0; k <= q; k ++)
            printf("%c", '*');
        n -- ;
        q += 2 ;
        printf("\n");
    }

    return 0;

}

II. Vẽ Hình Tam Giác Trong C++

Viết một chương trình in ra hình tam giác như sau:

Vẽ hình tam giác trong C++

Số dòng được nhập từ bàn phím

Lời Giải:

#include <iostream>
using namespace std;
 
int main()
{
    int soDong;
    cout << "Nhap so dong: ";
    cin >> soDong;
    for(int i = 1; i <= soDong; i++) {
        //in so ky tu sao
        for(int j = 1; j <= i; j++) {
            cout << "*";
        }
 
        //xuong dong ke tiep
        cout << endl;
    }
    return 0;
}

III. Vẽ Tam Giác Vuông Trong C

Bài tập 1: vẽ tam giác vuông cân trong C

Đề bài: Viết chương trình C sử dụng ký tự * để vẽ tam giác vuông cân trong C.

Vẽ một tam giác sao vuông cân trong C thỏa mãn điều kiện:

  • Cạnh góc vuông bên cạnh trái màn hình và cạnh góc vuông còn lại nằm ở phần dưới màn hình.
  • Đỉnh nằm phía trên màn hình.

Lời giải: bài tập vẽ tam giác vuông cân trong C

Chúng ta sử dụng hai vòng lặp lồng nhau. Vòng lặp bên ngoài điều khiển số hàng, vòng lặp bên trong chịu trách nhiệm in dấu sao và khoảng trống.

Dưới đây là chương trình C để giải bài tập vẽ tam giác sao vuông cân trong C:

#include <stdio.h>
 
int main() {
   int n,i,j;
 
   n = 6;
 
   printf("Ve tam giac sao vuong can:\n");
   for(i = 1; i <= n; i++) {
      for(j = 1; j <= i; j++)
         printf("* ");
 
      printf("\n");
   }
   return 0;
}

Kết quả:

Vẽ Tam Giác Vuông Trong C
Vẽ Tam Giác Vuông Trong C

Bài tập 2: vẽ tam giác vuông cân trong C

Đề bài: Viết chương trình C sử dụng ký tự * để vẽ tam giác vuông cân trong C.

Vẽ một tam giác sao vuông cân trong C thỏa mãn điều kiện:

  • Cạnh huyền nằm về phía trái màn hình.

Lời giải: bài tập vẽ tam giác vuông cân trong C (2)

Chúng ta sử dụng hai vòng lặp lồng nhau. Vòng lặp bên ngoài điều khiển số hàng, vòng lặp bên trong chịu trách nhiệm in dấu sao và khoảng trống.

Dưới đây là chương trình C để giải bài tập vẽ tam giác sao vuông cân trong C:

#include <stdio.h>
 
int main() {
   int n,i,j;
 
   n = 6;
 
   printf("Ve tam giac sao vuong can:\n");
   for(i = 0; i < n; i++) {
      for(j=0; j<i; j++)
         printf("  "); 
 
      for(j=i; j < n; j++)
         printf(" *");
 
      printf("\n");
       
   }
    
   return 0;
}

Kết quả:

Vẽ Tam Giác Vuông Trong C
Vẽ Tam Giác Vuông Trong C

Bài tập 3: vẽ tam giác vuông cân trong C

Đề bài: Viết chương trình C sử dụng ký tự * để vẽ tam giác vuông cân trong C.

Vẽ một tam giác sao vuông cân trong C thỏa mãn điều kiện:

  • Một cạnh góc vuông nằm cạnh cạnh trái màn hình.
  • Cạnh góc vuông còn lại nằm phía trên màn hình.

Lời giải: bài tập vẽ tam giác vuông cân trong C

Chúng ta sử dụng hai vòng lặp lồng nhau. Vòng lặp bên ngoài điều khiển số hàng, vòng lặp bên trong chịu trách nhiệm in dấu sao và khoảng trống.

Dưới đây là chương trình C để giải bài tập vẽ tam giác sao vuông cân trong C:

#include <stdio.h>
 
int main() {
   int n, i, j;
 
   n = 6;
 
   printf("Ve tam giac sao vuong can:\n");
   for(i = n; i >= 1; i--) {
      for(j = 1; j <= i; j++)
         printf("* ");
 
      printf("\n");
   }
    
   return 0;
}

Kết quả:

Vẽ Tam Giác Vuông Trong C
Vẽ Tam Giác Vuông Trong C

IV. Vẽ Tam Giác Pascal Trong C

Bài tập vẽ tam giác Pascal là bài tập điển hình của sinh viên trong khi học về các ngôn ngữ lập trình. Bạn theo dõi hình minh họa tam giác Pascal sau:

Vẽ Tam Giác Pascal Trong C
Vẽ Tam Giác Pascal Trong C

Tam giác Pascal có qui tắc sau:

Tất cả các giá trị bên ngoài tam giác được xem như là 0.

Hàng đầu tiên sẽ là 0 1 0, trong đó chỉ có giá trị 1 có được một khoảng trống trong tam giác Pascal, còn 0 là không nhìn thấy.

Hàng thứ hai được tạo bằng cách cộng hai số liên tiếp nhau từ hàng thứ nhất: (0 + 1) và (1 + 0).

Các hàng còn lại cũng được tạo bằng cách cộng như trên. Ví dụ với hàng thứ ba là cộng các số liên tiếp nhau từ hàng thứ hai: (0 + 1), (1 + 1) và (1 + 0).

Từ các qui tắc trên, trước hết chúng ta viết một hàm để tính các giá trị của tam giác Pascal bởi sử dụng đệ qui (tất nhiên là bạn có thể sử dụng cách khác). Trong hàm main(), chúng ta sẽ sử dụng ba vòng lặp. Một vòng lặp bên ngoài để điều khiển số hàng. Hai vòng lặp bên trong: một vòng lặp để in khoảng trống và một vòng lặp để in giá trị.

V. Vẽ Tam Giác Đều Trong C

Bài tập 1: vẽ tam giác đều trong C

Đề bài: Viết chương trình C sử dụng ký tự * để vẽ tam giác đều trong C.

Vẽ một tam giác sao đều trong C thỏa mãn điều kiện:

  • Các cạnh bằng nhau.
  • Một đỉnh nằm phía trên màn hình và cạnh đối diện với đỉnh này nằm dưới.

Lời giải: bài tập vẽ tam giác đều trong C

Chúng ta sử dụng ba vòng lặp lồng nhau. Một vòng lặp bên ngoài để điều khiển số hàng. Hai vòng lặp bên trong: một vòng lặp để in các khoảng trống, một vòng lặp để in các dấu sao.

Dưới đây là chương trình C để giải bài tập vẽ tam giác sao đều trong C:

#include <stdio.h>
 
int main() {
   int n,i,j;
 
   n = 6;   // khai bao so hang.
 
   printf("Ve tam giac sao deu:\n");
   for(i = 1; i <= n; i++) {
      for(j = 1; j <= n-i; j++)
         printf(" ");
 
      for(j = 1; j <= i; j++)
         printf("* ");
 
      printf("\n");
   }
   return 1;
}

Kết quả:

 Vẽ Tam Giác Đều Trong C
Vẽ Tam Giác Đều Trong C

Bài tập 1: vẽ tam giác đều trong C

Đề bài: Viết chương trình C sử dụng ký tự * để vẽ tam giác đều trong C.

Vẽ một tam giác sao đều trong C thỏa mãn điều kiện:

  • Các cạnh bằng nhau.
  • Một đỉnh nằm phía dưới màn hình và cạnh đối diện với đỉnh này nằm trên.

Lời giải: bài tập vẽ tam giác đều trong C

Chúng ta sử dụng ba vòng lặp lồng nhau. Một vòng lặp bên ngoài để điều khiển số hàng. Hai vòng lặp bên trong: một vòng lặp để in các khoảng trống, một vòng lặp để in các dấu sao.

Dưới đây là chương trình C để giải bài tập vẽ tam giác sao đều trong C:

#include <stdio.h>
 
int main() {
   int n,i,j;
 
   n = 6;
 
   printf("Ve tam giac sao deu:\n");
   for(i = 1; i <= n; i++) {
      for(j = 1; j < i; j++)
         printf(" "); 
 
      for(j = i; j <= n; j++)
         printf("* ");
 
      printf("\n");
   }
 
   return 1;
}

Kết quả:

 Vẽ Tam Giác Đều Trong C
Vẽ Tam Giác Đều Trong C

VII. Vẽ Tam Giác Vuông Ngược Trong C

Bài : Vẽ tam giác vuông cân rỗng có chiều cao h. Ví dụ: h = 5
*
* *
* *
* *
* * * * *
Hướng làm: Cách làm tương tự bài 3 nhưng ta phải xác định đoạn in dấu * và vị trí in dấu cách. Nhìn ví dụ ta nhận thấy các đoạn in dấu * là i = h, j = 1 và j = i.

void TamGiacVuongCanRong(int h)
{
   for (int i = 1; i <= h; i++)
   {
      for (int j = 1; j <= i; j++)
      {
         if (j == 1 || j == i || i == h)
            printf("* ");
         else
            printf("  ");
      }
      printf("\n");
   }
}

VIII. Vẽ Tam Giác Cân Rỗng Trong C

Vẽ tam giác cân rỗng có chiều cao h. Ví dụ: h = 5
*
* *
* *
* *
* * * * * * * * *
Hướng làm: Tương tự cách làm của bài 5 nhưng ta chỉ cần xác định đoạn in dấu *. Các đoạn in dấu * là i = h, j = m và j = n.

void TamGiacCanRong(int h)
{
   int m = h, n = h;
   for (int i = 1; i <= h; i++)
   {
      for (int j = 1; j <= 2 * h - 1; j++)
      {
         if (j == m || j == n || i== h)
            printf("*");
         else
            printf(" ");
      }
      m--;
      n++;
      printf("\n");
   }
}

IX. Vẽ Tam Giác Vuông Cân Trong C

Bài tập: Vẽ tam giác sao vuông cân

Vẽ tam giác sao vuông cân trong C thỏa mãn điều kiện:

Cạnh huyền nằm về phía trái màn hình.

Với bài tập C này, chúng ta chỉ cần hai vòng lặp: vòng lặp bên ngoài điều khiển số hàng, vòng lặp bên trong chịu trách nhiệm in dấu sao và khoảng trống.

Lời Giải:

Dưới đây là chương trình C để giải bài tập vẽ tam giác sao vuông cân trong C:

#include <stdio.h>

int main() {
   int n,i,j;

   n = 5;

   printf("Ve tam giac sao vuong can:\n\n");
   for(i = 0; i < n; i++) {
      for(j=0; j<i; j++)
         printf("  "); 

      for(j=i; j < n; j++)
         printf(" *");

      printf("\n");
      
   }
   
   return 0;
}

Biên dịch chương trình C trên sẽ cho kết quả:

Vẽ Tam Giác Vuông Cân Trong C
Vẽ Tam Giác Vuông Cân Trong C

The post Vẽ Tam Giác Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/ve-tam-giac-trong-c/

Strcmp Trong C++

String là 1 một mảng các ký tự được viết liền nhau, trong lập trình thường sử dụng rất nhiều để lưu chuỗi kí tự. Để thao tác với chuỗi có rất nhiều hàm và thư viện string.h hỗ trợ nhiều hàm hữu ích để xử lý chuỗi và hàm hỗ trợ xử lý mảng nhị phân. Bài viết này đề cập tới strcmp trong C++ và sử dụng hàm strcmp trong thư viện string.h hỗ trợ xử lý chuỗi.

I. Strcmp Trong C++ Là Gì

Hàm strcmp() trong C

Hàm int strcmp(const char *str1, const char *str2) so sánh chuỗi được trỏ đến bởi sr1 với chuỗi được trỏ đến bởi srt2.

Khai báo hàm strcmp() trong C

Dưới đây là phần khai báo cho strcmp() trong C:

int strcmp(const char *str1, const char *str2)

Tham số

str1 — Đây là chuỗi thứ nhất để được so sánh.

str2 — Đây là chuỗi thứ hai để được so sánh.

Trả về giá trị

Hàm này trả về các giá trị như sau:

Nếu giá trị trả về < 0 thì hàm này chỉ rằng str1 là ngắn hơn str2.

Nếu giá trị trả về > 0 thì hàm này chỉ rằng str2 là ngắn hơn str1.

Nếu giá trị trả về = 0 thì hàm này chỉ rằng str1 là bằng str2.

Strcmp Trong C++ Là Gì
Strcmp Trong C++ Là Gì

II. Cú Pháp Hàm Strcmp Trong C+

Hàm strcmp() được dùng để so sánh hai chuỗi, việc so sánh được thực hiện về mặt từ vựng.

Cú pháp:

int strcmp( const char* lhs, const char* rhs );

Trong đó:

  • lhs và rhs là hai chuỗi cần so sánh.

Hàm sẽ trả về 1 trong những trường hợp sau:

  • Giá trị dương giả dụ ký tự khác biệt đầu tiên trong lhs lớn hơn ký tự tương ứng trong rhs.
  • Giá trị âm trường hợp ký tự khác biệt trước tiên trong lhs nhỏ hơn ký tự tương ứng trong rhs.
  • Giá trị 0 giả dụ hai chuỗi bằng nhau về mặt từ vựng.
Cú Pháp Hàm Strcmp Trong C+
Cú Pháp Hàm Strcmp Trong C+

III. Cách Dùng Strcmp Trong C++

Trong phần này mình sẽ thực hiện một ví dụ để mình họa cho hàm memchr() trong C++.

Cụ thể mình sẽ khai báo hai chuỗi với hai nội dung khác nhau. Sau đó gọi hàm strcmp() để so sánh và thông báo ra màn hình.

#include <iostream>

#include <cstring>

using namespace std;

int main() {
  //khai báo hai biến lhs và rhs với hai nội dung cần so sánh
  char lhs[] = "Armstrong";
  char rhs[] = "Army";
  int result;
  //sử dụng hàm strcmp để so sánh hai chuỗi rồi gán kết quả cho biến result
  //*lưu ý: hàm strcmp sẽ trả về một số
  result = strcmp(lhs, rhs);
  //nếu hàm trả về số khác không tức là hai chuỗi khác nhau
  if (result != 0)
    cout << lhs << " và " << rhs << " khác nhau";
  //nếu hàm trả về = 0 thì hai chuỗi giống nhau
  else
    cout << lhs << " và " << rhs << " giống nhau";
  cout << endl << endl;
  //tương tự như vậy thử so sánh hai chuỗi giống nhau nhé
  result = strcmp(lhs, lhs);
  if (result != 0)
    cout << lhs << " và " << lhs << " khác nhau";
  else
    cout << lhs << " và " << lhs << " giống nhau";

  cout << "\n-------------------------------\n";
  cout << "Chương trình này được đăng tại Freetuts.net";
}

 

Kết quả:

Cách Dùng Strcmp Trong C++
Cách Dùng Strcmp Trong C++

Như vậy là chúng ta đã tìm hiểu xong hàm strcmp() trong C / C++. Đây là 1 hàm được dùng khá đa dạng trong những bài tâp về string, vì thế hãy luyện tập thật nhiều để sử dụng nó thành thạo nhé. Chúc các bạn thành công !!!

IV. Bài Tập So Sánh 2 Chuỗi Trong C++

+ Hàm strcmp trong string.h

Khi so sánh 2 số trong C thì ta có 1 số phép toán làm vô cùng đơn giản <, >, >=, <=, ==, !=, ngoài ra để so sánh chuỗi thì chúng ta không thể sử dụng những phép toán đó mà bắt buộc phải dùng hàm strcmp nằm trong thư viện string.h.

Hàm strcmp so sánh chuỗi s1 và chuỗi s2 và cho ta kết quả:

  • 1 Nếu s1 lớn hơn s2
  • 0 Nếu s1 giống s2
  • -1 Nếu s1 nhỏ hơn s2

Lưu ý: Trong Linux, hàm này trả về giá trị âm, dương, 0 (là khoảng cách giữa 2 ký tự khác nhau tương ứng trong s1, s2).

Ví dụ chương trình sau:

#include <stdio.h>
#include <string.h>
 
int main()
{
    char s1[20];
    char s2[20];                
 
    do
    {
        printf("Enter s1: ");
        gets(s1);
        printf("Enter s2: ");
        gets(s2);
 
        int x = strcmp(s1, s2);
        printf("x = %d", x);
 
        if(x < 0) printf(" => %s < %s", s1, s2);
        if(x > 0) printf(" => %s > %s", s1, s2);
        if(x == 0)printf(" => %s = %s", s1, s2);
 
        printf("\n\n");
 
    } while ( strcmp(s1, s2) != 0);
 
    return 0;
}

Kết quả:

Enter s1: Hang
Enter s2: Ho
x = -1 => Hang < Ho

Enter s1: Hong
Enter s2: Hon
x = 1 => Hong > Hon

Enter s1: Hung
Enter s2: hung
x = -1 => Hung < hung

Enter s1: Quan
Enter s2: Quan
x = 0 => Quan = Quan

Nguyên tắc so sánh

Nguyên tắc so sánh 2 chuỗi đó là duyệt lần lượt 2 từng ký tự của 2 chuỗi. So sánh mã ACSII của 2 ký tự đó, mã ký tự nào lớn hơn tức là chuỗi lớn hơn và ngừng so sánh. Nếu một chuỗi nào hết ký tự để so sanh trước thì chuỗi đó bé hơn.

s1	s2	Kết quả	Nguyên nhân
Hang	Ho	Hang < Ho	do a < o (97 < 111)
Hong	Hon	Hong > Hon	do s2 hết ký tự để so sánh
Hung	hung	Hung < hung	do H < h (72 < 104)
Quan	Quan	Quan = Quan	2 chuỗi giống nhau hoàn toàn
Bài Tập So Sánh 2 Chuỗi Trong C++
Bài Tập So Sánh 2 Chuỗi Trong C++

The post Strcmp Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/strcmp-trong-c/

Srand Trong C++

Làm sao để sinh số ngẫu nhiên trong C/C++? Hãy cùng Techacademy đi tìm cách để khởi tạo các số ngẫu nhiên sử dụng C/C++ nhé. Mình sẽ hướng dẫn các bạn khởi tạo các số ngẫu nhiên. Không chỉ sinh số nguyên ngẫu nhiên, mình sẽ hướng dẫn tạo 1 số ngẫu nhiên trong C ++ và cả cách phân biệt hàm rand và hàm srand () dùng để sinh số ngẫu nhiên nữa nhé. Các bạn cùng tìm hiểu với mình nhé.

I. Srand C++ Là Gì ?

Hàm srand() trong C

Hàm void srand(unsigned int seed) cung cấp seed cho bộ sinh số ngẫu nhiên được sử dụng bởi hàm rand.

Khai báo hàm srand() trong C

Dưới đây là phần khai báo cho srand() trong C:

void srand(unsigned int seed)

Tham số

seed: là một giá trị nguyên, được sử dụng như là seed bởi giải thuật sinh số ngẫu nhiên.

Trả về giá trị

Hàm này không trả về bất cứ giá trị nào.

Ví dụ

Chương trình C sau minh họa cách sử dụng của srand() trong C:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
   int i, n;
   time_t t;
   
   n = 5;
   
   /* Khoi tao bo sinh so ngau nhien */
   srand((unsigned) time(&t));

   /* in 5 so ngau nhien trong day tu 0 toi 50 */
   for( i = 0 ; i < n ; i++ ) 
   {
      printf("%d\n", rand() % 50);
   }
   
   return(0);
}

Biên dịch và chạy chương trình C trên sẽ cho kết quả:

Srand C++ Là Gì ?
Srand C++ Là Gì ?

II. Tạo 1 Số Ngẫu Nhiên Trong C++

Để tạo ra các số ngẫu nhiên khác nhau tại tất cả thời điểm chạy code, chúng ta sẽ thêm hàm srand() và truyền vào 1 tham số seed kiểu int. Tham số này đổi thay thì hàm srand() sẽ sinh ra các số khác nhau.

Ví dụ:

srand(123456);

Trong trường hợp này, giá trị a vẫn sẽ không đổi ở các lần chạy do 123456 là một hằng số. Vậy, chúng ta sẽ cần truyền vào một giá trị động chứ không phải giá trị tĩnh 😀

Có một giải pháp tốt nhất là chúng ta sẽ truyền cho seed thời gian hiện tại. Bằng phương pháp sử dụng hàm time() trong thư viện time.h. Hàm time() trả về kiểu time_t nhưng chúng ta có thể convert về int.

srand((int) time(0));

Bằng cách thêm hàm này trước lúc gọi hàm rand(), chúng ta đã có thể sinh số ngẫu nhiên khác nhau.

Một thí dụ cụ thể:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main(){
    int r;
    srand((int)time(0));
    for(int i = 0; i < 5; ++i){
        r = rand();
        printf("Rand %d is %d\n",i, r);
    }    
}

Kết quả chạy thử:

// Lần 1
Rand 0 is 5113
Rand 1 is 26832
Rand 2 is 11368
Rand 3 is 11635
Rand 4 is 20552
 
// Lần 2
Rand 0 is 5168
Rand 1 is 12947
Rand 2 is 20147
Rand 3 is 27496
Rand 4 is 32060

Ok, đã giải quyết được bài toán sinh số ngẫu nhiên cơ bản. Nhưng nếu tôi muốn sinh số ngẫu nhiên trong đoạn [min, max] thì phải làm thế nào?

Tạo 1 Số Ngẫu Nhiên Trong C++
Tạo 1 Số Ngẫu Nhiên Trong C++

III. Tạo Số Ngẫu Nhiên Thay Đổi Theo Thời Gian Trong C++

– Để tạo một số ngẫu nhiêu sau thay đổi khác nhau trong các lần chạy, bạn dùng hàm time (có trong #include<ctime>). Theo như thư viện MSDN (Microsoft Developers Network) đề cập rằng: time trả về số của giây được trôi qua được tính từ nữa đêm (00:00:00), ngày 1, tháng 1, năm 1970 theo UTC . (“The time function returns the number of seconds elapsed since midnight (00:00:00), January 1, 1970, coordinated universal time (UTC), according to the system clock”).

– Các số ngẫu nhiên giả được tạo ra bắt đầu từ thời khắc bạn thiết lập sử dụng hàm srand. Dòng code dưới đây thiết lập điểm bắt đầu của thời gian hiện hành.

srand(time(0));

– Giá trị được trả về từ time là qua srand. Lưu ý rằng số ngẫu nhiên được tạo ra trước lời gọi rand.

Ví dụ với chương trình đầu tiên: chúng ta thêm srand(time(0)) trước lời gọi rand().

// Second example 
#include <iostream> 
#include <cstdlib> 
#include <ctime> 
using namespace std;

int main()
{
    srand(time(0));
    int r0 = rand();
    int r1 = rand();
    int r2 = rand();
    int r3 = rand();
    int r4 = rand();

    cout<< "r0 = "<< r0 << endl;
    cout<< "r1 = "<< r1 << endl;
    cout<< "r2 = "<< r2 << endl;
    cout<< "r3 = "<< r3 << endl;
    cout<< "r4 = "<< r4 << endl;

    system("pause");
    return 0;
}

– Output lần đầu:

Tạo Số Ngẫu Nhiên Thay Đổi Theo Thời Gian Trong C++
Tạo Số Ngẫu Nhiên Thay Đổi Theo Thời Gian Trong C++

– Output lần 2:

Tạo Số Ngẫu Nhiên Thay Đổi Theo Thời Gian Trong C++
Tạo Số Ngẫu Nhiên Thay Đổi Theo Thời Gian Trong C++

– Output lần 3:

Tạo Số Ngẫu Nhiên Thay Đổi Theo Thời Gian Trong C++
Tạo Số Ngẫu Nhiên Thay Đổi Theo Thời Gian Trong C++

IV. Tạo Số Ngẫu Nhiên Với Một Khoảng Xác Định Trong C++

Hai cách dùng trên đều cho kết quả giá trị ngẫu nhiên là những giá trị trong khoảng từ 0 đến RAND_MAX. Để tạo ra một giá trị ngẫu nhiên trong khoảng xác định, sử dụng công thức rand() % (max – min + 1) + min để nhận kết quả trong khoảng từ min đến max.

Ví dụ: random giá trị trong khoảng [3, 50]

#include <cstdlib>
#include <ctime>

int main()
{
   srand(time(NULL)); 
   int res = rand() % (50 - 3 + 1) + 3;
   return 0;
}

 

Tạo Số Ngẫu Nhiên Với Một Khoảng Xác Định Trong C++
Tạo Số Ngẫu Nhiên Với Một Khoảng Xác Định Trong C++

V. Phân Biệt Hàm Rand Và Hàm Srand() Dùng Để Sinh Số Ngẫu Nhiên

Để sinh số nguyên trong lập trình C/C++. Chúng ta có thể sử dụng hàm rand(). Hàm này trả về một số nguyên có kiểu dữ liệu là int

Ví dụ:

#include
#include
 
int main(){
    int r;
    for(int i = 0; i < 5; ++i){
        r = rand();
        printf("Rand %d is %d\n",i, r);
    }    
}

Kết quả chạy thử:

0
1
2
3
4
5
6

 
Rand 0 is 41
Rand 1 is 18467
Rand 2 is 6334
Rand 3 is 26500
Rand 4 is 19169

Tuy nhiên, hàm rand() này sẽ không hề random ra những số mới lúc bạn chạy code ở các lần sau. Nghĩa là, kết quả của code trên ở tất cả lần chạy sẽ đều random ra 5 số giống nhau. Bạn có thể thử chạy đoạn code trên nhiều lần để kiếm chứng.
Vậy làm sao để random các số ngẫu nhiên tại mọi thời điểm? Hãy đọc phần tiếp theo nào.

Sinh số ngẫu nhiên trong C/C++ với srand()

Để tạo ra các số ngẫu nhiên khác nhau tại mọi thời điểm chạy code, chúng ta sẽ thêm hàm srand() và truyền vào 1 tham số seed kiểu int. Tham số này đổi thay thì hàm srand() sẽ sinh ra các số khác nhau.

Ví dụ:

0
1
2

 
srand(123456);

Trong trường hợp này, giá trị a vẫn sẽ không đổi ở các lần chạy do 123456 là một hằng số. Vậy, chúng ta sẽ cần truyền vào một giá trị động chứ không phải giá trị tĩnh

Có một giải pháp tốt nhất là chúng ta sẽ truyền cho seed thời gian hiện tại. Bằng cách sử dụng hàm time() trong thư viện time.h. Hàm time() trả về kiểu time_t nhưng chúng ta có thể convert về int.

0
1
2

 
srand((int) time(0));

Bằng cách thêm hàm này trước khi gọi hàm rand(), chúng ta đã có thể sinh số ngẫu nhiên khác nhau.

Một ví dụ cụ thể:

#include
#include
#include
 
int main(){
    int r;
    srand((int)time(0));
    for(int i = 0; i < 5; ++i){
        r = rand();
        printf("Rand %d is %d\n",i, r);
    }    
}

Kết quả chạy thử:

// Lần 1
Rand 0 is 5113
Rand 1 is 26832
Rand 2 is 11368
Rand 3 is 11635
Rand 4 is 20552
 
// Lần 2
Rand 0 is 5168
Rand 1 is 12947
Rand 2 is 20147
Rand 3 is 27496
Rand 4 is 32060
 

Phân Biệt Hàm Rand Và Hàm Srand() Dùng Để Sinh Số Ngẫu Nhiên
Phân Biệt Hàm Rand Và Hàm Srand() Dùng Để Sinh Số Ngẫu Nhiên

The post Srand Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/srand-trong-c/

Linked List C++

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ì
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
Đặ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ẽ:

  1. Tạo một Node mới với giá trị value
  2. 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.
  3. 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++
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
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”.

Bài Tập Linked List C++
Bài Tập Linked List C++

The post Linked List C++ first appeared on Techacademy.

source https://techacademy.edu.vn/linked-list-c/

Danh Sách Liên Kết Đơn C++

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++
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:

#include <stdio.h>
#include <stdlib.h>

struct node {
   int data;
   struct node *next;
};

struct node *even = NULL;
struct node *odd = NULL;
struct node *list = NULL;

//tao danh sach lien ket
void insert(int data) {
   // cap phat bo nho cho node moi;
   struct node *link = (struct node*) malloc(sizeof(struct node));
   struct node *current;

   link->data = data;
   link->next = NULL;

   if(data%2 == 0) {
      if(even == NULL) {
         even = link;
         return;
      }else {
         current = even;

         while(current->next != NULL)
         current = current->next;

         // chen link vao phan cuoi cua list
         current->next = link; 
      }
   }else {
      if(odd == NULL) {
         odd = link;
         return;
      }else {
         current = odd;

         while(current->next!=NULL)
            current = current->next;

         // chen link vao phan cuoi cua list
         current->next = link; 
      }
   }
}

void display(struct node *head) {
   struct node *ptr = head;

   printf("[head] =>");
   
   while(ptr != NULL) {        
      printf(" %d =>",ptr->data);
      ptr = ptr->next;
   }

   printf(" [null]\n");
}

void combine() {
   struct node *link;

   list = even;
   link = list;
    
   while(link->next!= NULL) {
      link = link->next;
   }
        
   link->next = odd;
}

int main() {
   int i;

   for(i=1; i<=10; i++)
      insert(i);

   printf("Danh sach chan: ");
   display(even);

   printf("Danh sach le: ");
   display(odd);

   combine();
   
   printf("Sau khi noi: \n");
   display(list);
   
   return 0;
}

Biên dịch chương trình C trên sẽ cho kết quả:

Nối 2 Danh Sách Liên Kết Đơn C++
Nối 2 Danh Sách Liên Kết Đơn C++

III. Đảo Ngược Danh Sách Liên Kết Đơn C++

Đảo ngược Danh sách liên kết

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++
Đả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++
Đả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++
Đả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++
Đả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++
Đả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++
Đả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++
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:

void CreateList(LinkedList& l)
{
   l.head = NULL;
   l.tail = NULL;
}

Bây giờ để tạo một danh sách, ta làm 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
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
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
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
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.

//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;
    }
}

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.

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);             
            }
        }   
    }
}

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.

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;
            }
        }
    }
}

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++
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
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
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
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++
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
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.
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.
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
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++
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;
}
Danh Sách Liên Kết Đơn Quản Lý Sinh Viên C++
Danh Sách Liên Kết Đơn Quản Lý Sinh Viên C++

The post Danh Sách Liên Kết Đơn C++ first appeared on Techacademy.

source https://techacademy.edu.vn/danh-sach-lien-ket-don-c/

Constructor Trong C++

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++
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à:

ten_lop (const ten_lop &obj) {
   // phan than cua constructor
}

Ở đâ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++
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 &amp;&amp; obj)
{
    // Just copy the pointer
    m_Data = obj.m_Data;
    // Set the passed object's member to NULL
    obj.m_Data = NULL;
    std::cout&lt;&lt;"Move Constructor"&lt;&lt;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 &lt;iostream>
#include &lt;vector>
class Container {
    int * m_Data;
public:
    Container() {
        //Allocate an array of 20 int on heap
        m_Data = new int[20];
        std::cout &lt;&lt; "Constructor: Allocation 20 int" &lt;&lt; std::endl;
    }
    ~Container() {
        if (m_Data) {
            delete[] m_Data;
            m_Data = NULL;
        }
    }
    //Copy Constructor
    Container(const Container &amp; 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 &lt; 20; i++)
            m_Data[i] = obj.m_Data[i];
        std::cout &lt;&lt; "Copy Constructor: Allocation 20 int" &lt;&lt; std::endl;
    }
    //Assignment Operator
    Container &amp; operator=(const Container &amp; obj) {
        if(this != &amp;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 &lt; 20; i++)
                m_Data[i] = obj.m_Data[i];
            std::cout &lt;&lt; "Assigment Operator: Allocation 20 int" &lt;&lt; std::endl;
        }
    }
    // Move Constructor
    Container(Container &amp;&amp; obj)
    {
        // Just copy the pointer
        m_Data = obj.m_Data;
        // Set the passed object's member to NULL
        obj.m_Data = NULL;
        std::cout&lt;&lt;"Move Constructor"&lt;&lt;std::endl;
    }
    // Move Assignment Operator
    Container&amp; operator=(Container &amp;&amp; obj)
    {
        if(this != &amp;obj)
        {
            // Just copy the pointer
            m_Data = obj.m_Data;
            // Set the passed object's member to NULL
            obj.m_Data = NULL;
            std::cout&lt;&lt;"Move Assignment Operator"&lt;&lt;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&lt;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 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ố
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++
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++
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.

Line::Line( double dai): chieudai(dai)
{
    cout << "Doi tuong dang duoc tao, chieudai = " << dai << endl;
}

Cú pháp trên là tương đương với cú pháp sau:

Line::Line( double dai)
{
    cout << "Doi tuong dang duoc tao, chieudai = " << dai << endl;
    chieudai = dai;
}

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:

C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
  ....
}

Class Destructor trong C++

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:

Bài Tập Constructor Trong C++
Bài Tập Constructor Trong C++

The post Constructor Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/constructor-trong-c/

Strtok Trong C++

I. Strtok Trong C++ Là Gì

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:

  1. 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.
  2. 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.
  3. 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:

  1. Lần gọi hàm strtok() đầu tiên và các lần tiếp theo là khác nhau.
  2. 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.
  3. 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

 

The post Strtok Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/strtok-trong-c/

Các Kiểu Dữ Liệu Trong Java

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.
public int hashCode() trả về số hashcode cho đối tượng hiện tại.
public boolean equals(Object obj) so sánh đối tượng đã cho với đối tượng hiện tại.
protected Object clone() throws CloneNotSupportedException 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 bdFromLong1 = BigDecimal.valueOf(123412345678901L); // 123412345678901
        
BigDecimal bdFromLong2 = BigDecimal.valueOf(123412345678901L, 2); // 1234123456789.01
        
BigDecimal bdFromDouble = BigDecimal.valueOf(0.1d); // 0.1

Phép tính trong BigDecimal

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);
    }
}

Kết quả:

WINTER

The post Các Kiểu Dữ Liệu Trong Java first appeared on Techacademy.

source https://techacademy.edu.vn/cac-kieu-du-lieu-trong-java/

Valueof Trong Java

I. Valueof Trong Java Là Gì

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:

public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}

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.

The post Valueof Trong Java first appeared on Techacademy.

source https://techacademy.edu.vn/valueof-trong-java/

Split Trong Java

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

String phone = "012-3456789";
String[] output = phone.split("-");
System.out.println(output[0]);
System.out.println(output[1]);

Kết quả trả về

012
3456789

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ự:

String[] words = temp.split(Pattern.quote("."));

 

The post Split Trong Java first appeared on Techacademy.

source https://techacademy.edu.vn/split-trong-java/