Blog Feed

Static Trong C++

Chúng ta có thể định nghĩa các thành viên lớp là static bởi dùng từ khóa static trong C++. Khi chúng ta khai báo 1 thành viên của 1 lớp là static, lúc đó dù cho có bao nhiêu đối tượng của lớp được tạo, thì sẽ chỉ mang 1 bản sao của thành viên static.

Một thành viên static chỉ buộc phải khởi tạo 1 lần và được chia sẻ cho tất cả đối tượng của lớp. Tất cả dữ liệu static được khởi tạo về 0 khi đối tượng đầu tiên được tạo.

Chúng ta không thể khởi tạo thành viên static trong định nghĩa lớp, nhưng nó có thể được định nghĩa bên ngoài lớp đó bởi việc khai báo lại biến static như trong ví dụ sau, sử dụng toán tử phân giải phạm vi :: để nhận diện lớp nào sở hữu nó. Hãy cùng Techacademy tìm hiểu chi tiết qua bài viết dưới đây nhé.

I. Định Nghĩa Static C++

Static trong c++ là dữ liệu của lớp không phải là dữ liệu của đối tượng. Static trong c++ tồn tại như 1 biến toàn cục. Hay nói cách khác dữ liệu static xuất hiện trước lúc bạn khởi tạo đối tượng của lớp, và nó chỉ tồn tại duy nhất.

Các thành viên static có thể là public, private hoặc protected.

Static ngoài khai báo biến còn có thể khai báo hàm.

Đối với class, static sử dụng để khai báo thành viên dữ liệu dùng chung cho mọi thể hiện của lớp:

  • Một bản duy nhất tồn tại trong suốt quá trình chạy của chương trình.
  • Dùng chung cho tất cả các thể hiện của lớp.
  • Bất kể lớp đó có bao nhiêu thể hiện.
Định Nghĩa Static C++
Định Nghĩa Static C++

II. Biến Static Trong Hướng Đối Tượng C++

Ví dụ mình sẽ khai báo một biến static, int count, biến này dùng để đếm số lượng các hình chữ nhật đã được tạo ra.

class Rectangle {
  private: int width;
  int length;
  static int count;
  public: void set(int w, int l);
  int area();
  Rectangle() {
    count++;
  }
  Rectangle(int x, int y) {
    count++;
    set(x, y);
  }
}

class Rectangle {
  private:
    int width;
  int length;
  static int count;
  public:
    void set(int w, int l);
  int area();
  Rectangle() {
    count++;
  }
  Rectangle(int x, int y) {
    count++;
    set(x, y);
  }
}

 

Như vậy khi một đối tượng được tạo chúng ta sẽ tăng count lên để thực hiện đúng ý đồ đếm hình chữ nhật.

Tuy nhiên chúng ta không thể gán trước giá trị ban đầu của static trong class mà chúng ta phải gán giá trị cho nó ở ngoài class.

ví dụ

int main() {
    int Rectangle::count = 0; // Sau khi khởi tạo giá trị đầu mình tạo minh họa 3 hình chữ nhật Rectangle r1(2,4); Rectangle r2(1,2); Rectangle r3;}

    int main() {
      int Rectangle::count = 0;
      // Sau khi khởi tạo giá trị đầu mình tạo minh họa 3 hình chữ nhật
      Rectangle r1(2, 4);
      Rectangle r2(1, 2);
      Rectangle r3;
    }

 

Biến Static Trong Hướng Đối Tượng C++
Biến Static Trong Hướng Đối Tượng C++

Ví dụ về static trong c++

Ví dụ bên trên chỉ rõ biến static count không phải là dữ liệu của đối tượng như width và length mà nó là dữ liệu của class và chỉ tồn tại duy nhất.

Để sử dụng, gán hoặc lấy giá trị của static bạn sử dụng phạm vi truy xuất để gọi nó

//Ví dụ khởi tạo ban đầu
int Rectangle::count = 0;
 
//Gọi biến static:
Rectangle::count

Lưu ý thêm ở phần khai báo mình để static count ở private, nên bên ngoài class không thể dùng phạm vi truy xuất đến truy cập (Như cách gọi Rectangle::count ) Nhưng việc khai báo và gán giá trị ban đầu như int Rectangle::count = 0; là được phép.

Để dùng Rectangle::count bạn có thể khai báo biến static này ở public hoặc viết thêm hàm static get giá trị count này.

III. Hàm Static Trong Hướng Đối Tượng C++

Hàm static có vai trò cũng như biến static. Nghĩa là khi bạn đã khai báo class nhưng chưa tạo ra đối tượng như Rectangle r1(2,4) thì chúng ta vẫn truy cập được biến count như ví dụ trên. Vậy ở phần hàm static cũng có vai trò như vậy, hàm này sẽ là tồn tại duy nhất của class.

Mình sẽ khai báo thêm static int getCount() ở thuộc tính public

a. Code cụ thể về static

class Rectangle
{
    private:
        int width;
        int length;
        static int count;
    public:
        void set(int w, int l);
 
        static int getCount()
        {
            return count;
        }
 
        int area();
        Rectangle()
        {
            count++;
        }
        Rectangle(int x, int y) 
        { 
            count++; 
            set(x,y);
        }
}

b. Gọi hàm static

Bạn có thể gọi hàm static bằng cách sau:

  • Gọi Rectangle::getCount() ở ngoài class hoặc bên trong class
  • Gọi getCount() Bên trong class.
Hàm Static Trong Hướng Đối Tượng C++
Hàm Static Trong Hướng Đối Tượng C++

IV. Thành Viên Static Của Class

Hàm thành viên static trong C++

Bằng việc khai báo 1 hàm thành viên là static trong C++, bạn làm nó độc lập với bất kỳ đối tượng cụ thể nào của lớp. Một hàm thành viên static có thể được gọi ngay cả lúc không có đối tượng nào của lớp tồn tại và những hàm static được truy cập chỉ bởi dùng tên lớp và toán tử phân giải phạm vi :: trong C++.

Một hàm thành viên chỉ có thể truy cập thành viên dữ liệu static, những hàm thành viên static khác và bất kỳ hàm khác từ bên ngoài lớp đó.

Các hàm thành viên static có một phạm vi lớp và chúng không có sự truy cập đến con trỏ this của lớp trong C++. Bạn có thể sử dụng 1 hàm thành viên để xác định có hay không một số đối tượng của lớp đã được tạo.

Bạn thử ví dụ sau để hiểu khái niệm của hàm thành viên static trong C++:

#include <iostream>
 
using namespace std;

class Box
{
   public:
      static int biendemDT;
      // phan dinh nghia Constructor
      Box(double dai=1.0, double rong=1.0, double cao=1.0)
      {
         cout <<"Constructor duoc goi." << endl;
         chieudai = dai;
         chieurong = rong;
         chieucao = cao;
         // gia tri cua biendemDT tang len moi khi doi tuong duoc tao
         biendemDT++;
      }
      double theTich()
      {
         return chieudai * chieurong * chieucao;
      }
      static int layBienDem()
      {
         return biendemDT;
      }
   private:
      double chieudai;     // Chieu dai cua mot box
      double chieurong;    // Chieu rong cua mot box
      double chieucao;     // Chieu cao cua mot box
};

// khoi tao thanh vien static cua lop Box
int Box::biendemDT = 0;

int main(void)
{
  
   // in tong so doi tuong duoc tao truoc khi tao cac doi tuong.
   cout << "So doi tuong ban dau la: " << Box::layBienDem() << endl;

   Box Box1(2.4, 4.2, 2.2);    // khai bao box1
   Box Box2(4.5, 2.0, 3.2);    // khai bao box2

   // in tong so doi tuong duoc tao sau khi tao cac doi tuong.
   cout << "Tong so doi tuong sau khi tao la: " << Box::layBienDem() << endl;

   return 0;
}

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

Hàm Static Trong Hướng Đối Tượng C++
Hàm Static Trong Hướng Đối Tượng C++

V. Khi Nào Nên Sử Dụng Biến Static

Biến static được dùng lúc chỉ cần một bản sao của biến. vì vậy trường hợp bạn khai báo biến bên trong phương thức thì không dùng biến như vậy, nó sẽ trở thành cục bộ chỉ hoạt động ..

Các biến được khai báo static thường được chia sẻ trên tất cả các trường hợp của một lớp.

Các biến được khai báo static thường được chia sẻ trên tất cả các trường hợp của một lớp. Khi bạn tạo đa dạng phiên bản của lớp VariableTest Biến này vĩnh viễn được chia sẻ trên tất cả chúng. Do đó, tại bất kỳ thời điểm nhất định nào, sẽ chỉ có 1 giá trị chuỗi được chứa trong biến vĩnh viễn.

Vì chỉ có 1 bản sao của biến có sẵn cho tất cả các trường hợp, mã this.permament sẽ dẫn đến lỗi biên dịch vì có thể nhớ lại rằng this.variablename tham chiếu đến tên biến cá thể. Do đó, những biến tĩnh phải được truy cập trực tiếp, như được chỉ ra trong mã.

Khi Nào Nên Sử Dụng Biến Static
Khi Nào Nên Sử Dụng Biến Static

VI. Const Static’ Có Nghĩa Là Gì Trong C Và C ++?

Const static’ có nghĩa là gì trong C và C ++

Tôi đã thấy điều này trong 1 số mã ở đây trên StackOverflow và tôi không thể tìm ra nó làm gì. Sau đó, tôi thấy 1 số câu trả lời nhầm lẫn trên các diễn đàn khác. Tôi đoán tốt nhất là nó được dùng trong C để ẩn hằng số fookhỏi những mô-đun khác. Điều này có đúng không? Nếu vậy, tại sao mọi người sẽ dùng nó trong bối cảnh C ++ nơi bạn có thể tạo ra nó private?

Câu trả lời:

+ Nó có dùng trong cả C và C ++.

Như bạn đã đoán, staticphần giới hạn phạm vi của nó cho đơn vị biên dịch đó. Nó cũng cung cấp cho khởi tạo tĩnh. constchỉ nói với trình biên dịch để không cho phép bất cứ ai sửa đổi nó. Biến này được đặt trong phân đoạn dữ liệu hoặc bss tùy thuộc vào kiến ​​trúc và có thể nằm trong bộ nhớ được đánh dấu chỉ đọc.

Tất cả đó là cách C xử lý các biến này (hoặc phương pháp C ++ xử lý các biến không gian tên). Trong C ++, một thành viên được đánh dấu staticđược chia sẻ bởi tất cả các phiên bản của một lớp nhất định. Cho dù đó là riêng tư hay không không ảnh hưởng đến thực tế là một biến được chia sẻ bởi nhiều trường hợp. Có consttrên đó sẽ cảnh báo bạn nếu có bất kỳ mã nào sẽ tìm mọi cách sửa đổi điều đó.

Nếu nó hoàn toàn riêng tư, thì mỗi phiên bản của lớp sẽ có phiên bản riêng (mặc dù trình tối ưu hóa).

+ Rất nhiều người đã đưa ra câu trả lời cơ bản nhưng không ai chỉ ra rằng trong C ++ constmặc định staticở namespace cấp độ (và một số đã cung cấp thông tin sai). Xem phần tiêu chuẩn C ++ 98 3.5.3.

Đầu tiên một số nền tảng:

Đơn vị dịch: Một tệp nguồn sau bộ xử lý trước (đệ quy) bao gồm tất cả các tệp bao gồm.

Liên kết tĩnh: Một biểu tượng chỉ có sẵn trong đơn vị dịch thuật của nó.

Liên kết ngoài: Một biểu tượng có sẵn từ các đơn vị dịch thuật khác.

– Ở cấp độ chức năng

staticcó nghĩa là giá trị được duy trì giữa các lệnh gọi hàm.
Các ngữ nghĩa của các staticbiến chức năng tương tự như các biến toàn cục ở chỗ chúng nằm trong phân đoạn dữ liệu của chương trình (chứ không phải ngăn xếp hoặc heap), xem câu hỏi này để biết thêm chi tiết về statictuổi thọ của biến.

– Ở classcấp độ

staticcó nghĩa là giá trị được chia sẻ giữa tất cả các thể hiện của lớp và constcó nghĩa là nó không thay đổi.

Const Static' Có Nghĩa Là Gì Trong C Và C ++
Const Static’ Có Nghĩa Là Gì Trong C Và C ++

VII. Bài Tập C++ OOP Giá Trị Của Biến Static

Biến static (static variables)

Biến static được tạo ra bên trong một khối lệnh có khả năng lưu giữ giá trị của nó cho dù chương trình đã chạy ra bên ngoài khối lệnh chứa nó.

Biến static chỉ cần được khai báo một lần duy nhất, và tiếp tục được duy trì sự tồn tại xuyên suốt cho đến khi chương trình kết thúc.

Dưới đây là một ví dụ đơn giản cho thấy sự khác biệt giữa việc sử dụng biến cục bộ và biến static:

#include <iostream>
 
void incrementAndPrint()
{
    int value = 1; // automatic duration by default
    ++value;
    std::cout << value << '\n';
} // value is destroyed here
 
int main()
{
    incrementAndPrint();
    incrementAndPrint();
    incrementAndPrint();
    
    return 0;
}

Mỗi khi hàm incrementAndPrint được gọi, một biến có tên gọi “value” được tạo ra và được gán giá trị 1. Hàm incrementAndPrint sẽ tăng giá trị của biến “value” lên 1 đơn vị, giá trị in ra màn hình là 2. Khi hàm incrementAndPrint thực hiện xong tác vụ, biến “value” bị hủy. Cứ thực hiện liên tiếp như vậy, kết quả in ra màn hình là:

2
2
2

Bây giờ, chúng ta thêm từ khóa static trước nơi biến “value” được khai báo:

#include <iostream>
 
void incrementAndPrint()
{
    static int s_value = 1; // static duration variable
    ++s_value;
    std::cout << s_value << '\n';
} // s_value is not destroyed here, but became inaccessible
 
int main()
{
    incrementAndPrint();
    incrementAndPrint();
    incrementAndPrint();
    
    return 0;
}

Lần này, vì biến “s_value” được khai báo với từ khóa static, nó sẽ chỉ được khởi tạo một lần duy nhất, dù hàm incrementAndPrint được gọi nhiều lần. Mỗi lần hàm incrementAndPrint được gọi, giá trị của biến “s_value” được tăng thêm 1, và còn được lưu giữ lại đó cho đến khi chương trình kết thúc. Kết quả in ra như sau:

2
3
4

Hàm static (static functions)

Từ khóa static cũng có thể được sử dụng cho các hàm, thường là những hàm được đặt bên trong các struct hoặc class (gọi là phương thức). Trong bài học này, mình lấy một ví dụ về việc sử dụng hàm static bên trong một struct mẫu như sau:

#include <iostream>

struct Test
{
   static void foo()
   {
      std::cout << "Called static function: foo" << std::endl;
   }
};

int main()
{
   Test::foo();
   return 0;
}

Các bạn có thể thấy, hàm foo được gọi mà không cần tạo ra thực thể kiểu Test.

Điểm đáng lưu ý trong việc sử dụng hàm static trong struct/class là chúng không thể truy xuất đến non-static members trong struct/class đó:

struct Test
{
   int value;

   void nonstatic_foo()
   {
   }

   static void foo()
   {
      value = 1; // error
      nonstatic_foo(); // error
   }
};
Bài Tập C++ OOP Giá Trị Của Biến Static
Bài Tập C++ OOP Giá Trị Của Biến Static

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

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

Memset Trong C++

Trong bài viết này chúng ta sẽ tìm hiểu về hàm memset() trong C++. Đây là một hàm được sử dụng sao chép ký tự trong một chuỗi.

I. Memset Trong C++ Là Gì

Hàm void *memset(void *str, int c, size_t n) sao chép ký tự c (một unsigned char) tới n ký tự đầu tiên của chuỗi được trỏ tới bởi tham số str.

Memset Trong C++ Là Gì
Memset Trong C++ Là Gì

II. Cú Pháp Memset Trong C++

Hàm memset() trong C++ được sử dụng để sao chép một ký tự đơn lẻ trong một khoảng nhất định vào một đối tượng.

Cú pháp:

void* memset( void* dest, int ch, size_t count );

Trong đó:

  • dest: Con trỏ tới đối tượng để sao chép ký tự.
  • ch: Ký tự cần sao chép.
  • count: Số lần sao chép.

Hàm trả về một chuỗi đã được sao chép và lưu vào con trỏ dest.

Cú Pháp Memset Trong C++
Cú Pháp Memset Trong C++

III. Ví Dụ Về Hàm Memset Trong C++

Hàm memset() trong C++ được sử dụng để sao chép một ký tự đơn lẻ trong một khoảng nhất định vào một đối tượng.

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

Cụ thể mình sẽ tạo một biến dest với độ dài 50 ký tự để lưu các ký tự đã được sao chép. Sau đó sử dụng hàm memset() để sao chép ký tự ‘a’ với số lần là 5.

#include <iostream>
#include <cstring>
using namespace std;
 
int main() {
  //khai báo biến dest có độ dài 50 ký tự, được dùng để lưu các ký tự lặp lại
  char dest[50];
  //biến ch là biến được sử dụng để lặp lại
  char ch = 'a';
  //gọi hàm memset để lặp lại ký tự a 5 lần rồi gán vào biến dest
  memset(dest, ch, 5);
 
  cout << "Sau khi gọi hàm memset để lặp lại" << endl;
  cout << "\tBiến dest = " << dest;
 
  cout<<"\n-------------------------------\n";
  cout<<"Chương trình này được đăng tại Techcademy.edu.vn";
}

Kết quả:

Ví Dụ Về Hàm Memset Trong C++
Ví Dụ Về Hàm Memset Trong C++

IV. Memcpy() Trong C++

Hàm memcpy() trong C

Hàm void *memcpy(void *str1, const void *str2, size_t n) sao chép n ký tự từ str2 tới str1.

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

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

void *memcpy(void *str1, const void *str2, size_t n)

Tham số

str1 — Đây là con trỏ tới mảng đích, nơi mà nội dung để được sao chép, ép kiểu thành một con trỏ của kiểu void*.

str2 — Đây là con trỏ tới nguồn dữ liệu để sao chép, ép kiểu thành một con trỏ của kiểu void*.

n — Đây là số byte để được sao chép.

Trả về giá trị

Hàm này trả về một con trỏ tới chuỗi đích, đó là str1.

Ví dụ

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

#include <stdio.h>
#include <string.h>


struct {
  char name[40];
  int age;
} person, person_copy;

int main ()
{
  char myname[] = "Nguyen Hoang Nam";

  /* su dung ham memcpy de sao chep chuoi: */
  memcpy ( person.name, myname, strlen(myname)+1 );
  person.age = 46;

  /* su dung ham memcpy de sao chep struct: */
  memcpy ( &person_copy, &person, sizeof(person) );

  printf ("person_copy = %s, %d \n", person_copy.name, person_copy.age );

  return 0;
}

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

Memcpy() Trong C++
Memcpy() Trong C++

V. Memcmp() Trong C++

Hàm int memcmp(const void *str1, const void *str2, size_t n)) so sánh n byte đầu của hai chuỗi str1 và str2.

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

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

int memcmp(const void *str1, const void *str2, size_t n)

Tham số

str1 — Đây là con trỏ tới một khối bộ nhớ.

str2 — Đây là con trỏ tới một khối bộ nhớ.

n — Đây là số byte để được so sánh.

Trả về giá trị

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

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

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

Ví dụ

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

#include <stdio.h>
#include <string.h>

int main ()
{
   char str1[15];
   char str2[15];
   int ret;

   memcpy(str1, "abcdef", 6);
   memcpy(str2, "ABCDEF", 6);

   ret = memcmp(str1, str2, 5);

   if(ret > 0)
   {
      printf("Chuoi str2 la ngan hon chuoi str1");
   }
   else if(ret < 0) 
   {
      printf("Chuoi str1 la ngan hon chuoi str2");
   }
   else 
   {
      printf("Chuoi str1 la bang chuoi str2");
   }
   
   return(0);
}

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

Memcmp() Trong C++
Memcmp() Trong C++

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

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

Hàm Gets() Trong C++

Trong bài viết này chúng ta sẽ tìm hiểu về hàm gets() trong C / C++. Đây là một hàm được sử dụng để đọc các ký tự từ stdin.

I. Hàm Gets() Trong C++ Là Gì

Hàm gets() là hàm có sẵn trong thư viện cstdio, vì vậy trước khi sử dụng nó các bạn nhớ khai báo thư viện đã nhé: #include<cstdio>.

Hàm char *gets(char *str) trong Thư viện C chuẩn đọc một dòng từ stdin và lưu trữ nó bên trong chuỗi được trỏ bởi str. Nó dừng khi bắt gặp end-of-file hoặc ký tự newline (dòng mới) được đọc.

Hàm gets() khác hàm scanf() ở chỗ là hàm này chấp nhận các chuỗi có khoảng trống.

Hàm Gets() Trong C++ Là Gì
Hàm Gets() Trong C++ Là Gì

II. Cú Pháp Hàm Gets() Trong C++

Hàm gets() được sử dụng để đọc các ký tự từ stdin và lưu trữ chúng cho đến khi tìm thấy ký tự dòng mới. Hàm get () không cung cấp hỗ trợ để ngăn chặn tràn bộ đệm nếu chuỗi đầu vào lớn.

Cú pháp:

char* gets(char* str);

Trong đó:

  • str: Con trỏ đến một mảng ký tự lưu trữ các ký tự từ stdin.

Hàm trả về:

  • Khi thành công, hàm gets() trả về str.
  • Khi thất bại, nó trả về null.

Ví dụ: khi chúng ta muốn yêu cầu người dùng nhập vào họ và tên thì có thể sử dụng hàm gets() kết hợp với cout.

char str[100];
cout << "Nhập vào họ tên của bạn: ";
gets(str);

Như vậy là khi chạy thì chương trình sẽ yêu cầu bạn nhập vào từ bàn phím.

Cú Pháp Hàm Gets() Trong C++
Cú Pháp Hàm Gets() Trong C++

III. Ví Dụ Hàm Gets() Trong C++

Chương trình C sau minh họa cách sử dụng của hàm gets() trong C. Bạn sẽ thấy rằng hàm gets() chấp nhận chuỗi có chứa khoảng trống, không giống như hàm scanf():

#include <stdio.h>

int main()
{
   char str[50];

   printf("Nhap mot chuoi: ");
   gets(str);

   printf("Ban vua nhap chuoi: %s", str);

   return(0);
}

Biên dịch và chạy chương trình C trên để xem kết quả.

Ví Dụ Hàm Gets() Trong C++
Ví Dụ Hàm Gets() Trong C++

IV. Hướng Dẫn Xử Lý Lỗi Bỏ Qua Khi Dùng Hàm Gets Và Cin.Getline Trong C & C++

Ví dụ với thư viện stdio.h

PHP Code:

#include <stdio.h>
 
struct sinhvien
{
    char hoten[50];
    char ngaysinh[20];
    float cannang;
};
 
void Xuat1SV(sinhvien x)
{
    printf("Ten Sinh Vien: %s\n",x.hoten);
    printf("Ngay sinh: %s\n",x.ngaysinh);
}
void Nhap1SV(sinhvien &x)
{   
    float cn;
    printf("Ten Sinh Vien: ");
    gets(x.hoten);
 
    printf("Ngay sinh: ");
    gets(x.ngaysinh);
     
    printf("Can nang: ");
    scanf("%f",&cn);
    x.cannang=cn;
      
}
 
void main()
{
    sinhvien dssv[3];
    int i=0;
    //-------Nhap SV
    for (i=0;i<3;i++)
    {      
        printf("Nhap sinh vien thu %d\n----------------------------------------\n",i+1);
        Nhap1SV(dssv[i]);
        printf("\n");
    }
}

Ví dụ với iostream.h

PHP Code:

#include <iostream.h>
 
struct sinhvien
{
    char hoten[50];
    char ngaysinh[20];
    float cannang;
};
 
void Xuat1SV(sinhvien x)
{
    cout<<"Ten Sinh Vien: "<<x.hoten<<endl;
    cout<<"Ngay sinh: "<<x.ngaysinh<<endl;
}
void Nhap1SV(sinhvien &x)
{
     
    float cn;
    cout<<"Ten Sinh Vien: ";
    cin.getline(x.hoten,50);
 
    cout<<"Ngay sinh: ";
    cin.getline(x.ngaysinh,20);
     
    cout<<"Can nang: ";
    cin>>cn;
    x.cannang=cn;
     
     
 
}
 
 
void main()
{
    sinhvien dssv[3];
    int i=0;
    //-------Nhap SV
    for (i=0;i<3;i++)
    {
         
        cout<<"Nhap sinh vien thu "<<i+1<<endl<<"---------------------------"<<endl;
        Nhap1SV(dssv[i]);
        cout<<endl;
    }
}

Các bạn chạy thử ví dụ trên nha

Chắc các bạn sẽ thấy rằng sau lúc nhập sinh viên đầu tiên nó bỏ qua bước nhập tên sinh viên 2 mà nhảy tới ngày sinh luôn

Hướng Dẫn Xử Lý Lỗi Bỏ Qua Khi Dùng Hàm Gets Và Cin.Getline Trong C & C++
Hướng Dẫn Xử Lý Lỗi Bỏ Qua Khi Dùng Hàm Gets Và Cin.Getline Trong C & C++

Điều này đc giải thích như sau:

Khi bạn nhập một ký tự từ bàn phím thì ký tự ấy sẽ được chuyển vào 1 trong 2 nơi : bộ đệm bàn phím hoặc tệp stdin .
Làm sao biết lúc ta nhập 1 ký tự bất kỳ thì nó sẽ gửi vào đâu. Câu trả lời là trường hợp ta nhập ký tự trong lúc máy dừng chờ trước những hàm scanf , gets , getchar thì máy sẽ gửi ký tự vào tệp stdin. Nếu ko phải 3 hàm ấy thì là vào bộ đệm bàn phím . Tệp stdin có tính chất như sau :

+ Nếu trên stdin có dư dữ liệu ( tức là bạn bấm nhiều phím bất kỳ ) thì các hàm trên sẽ nhận được 1 phần dữ liệu mà chúng yêu cầu . Các ký tự còn lại vẫn nằm trên stdin .

+ lúc stdin chưa đủ dữ liệu thì máy sẽ chờ cho đến khi người dùng nhập đủ dữ liệu vào .

khi bạn nhập như sau : 45 và bấm ENTER cho hàm cin thì ký tự 45 sẽ được gán cho thuộc tính cân nặng . Nhưng ký tự ENTER vẫn còn lưu lại trên stdin hoặc bộ đệm và hàm cin>> (scanf)sẽ nhận nó bởi vậy sẽ làm trôi hàm cin.getline (gets) đi . Dù bạn rất muốn nhập cái gì đó cho chuỗi hoten nhưng cũng bó tay .

Thế thì cách giải quyết chính là làm sạch tệp stdin và bộ đệm buffer đi

Đối với thư viện stdio.h bị lỗi với gets thì ta chèn

PHP Code:
fflush(stdin);
Sau hàm nhập số liền kề với gets sau nó

Đối với thư viện iostrem.h bị lỗi cin.getline thì ta chèn

PHP Code:
cin.ignore();
Sau hàm nhập số trước cin.getline() liền kề sau nó để xóa bộ đệm đi là ok;

Kết quả sau khi chèn cin.ignore();

PHP Code:

#include <iostream.h>
 
struct sinhvien
{
    char hoten[50];
    char ngaysinh[20];
    float cannang;
};
 
void Xuat1SV(sinhvien x)
{
    cout<<"Ten Sinh Vien: "<<x.hoten<<endl;
    cout<<"Ngay sinh: "<<x.ngaysinh<<endl;
}
void Nhap1SV(sinhvien &x)
{
     
    float cn;
    cout<<"Ten Sinh Vien: ";
    cin.getline(x.hoten,50);
 
    cout<<"Ngay sinh: ";
    cin.getline(x.ngaysinh,20);
     
    cout<<"Can nang: ";
    cin>>cn;
    x.cannang=cn;
     
    cin.ignore();
 
}
 
 
void main()
{
    sinhvien dssv[3];
    int i=0;
    //-------Nhap SV
    for (i=0;i<3;i++)
    {
         
        cout<<"Nhap sinh vien thu "<<i+1<<endl<<"---------------------------"<<endl;
        Nhap1SV(dssv[i]);
        cout<<endl;
    }
}

Kết quả sau khi chèn fflush(stdin);

PHP Code:

#include <stdio.h>
 
struct sinhvien
{
    char hoten[50];
    char ngaysinh[20];
    float cannang;
};
 
void Xuat1SV(sinhvien x)
{
    printf("Ten Sinh Vien: %s\n",x.hoten);
    printf("Ngay sinh: %s\n",x.ngaysinh);
}
void Nhap1SV(sinhvien &x)
{
     
    float cn;
    printf("Ten Sinh Vien: ");
    gets(x.hoten);
 
    printf("Ngay sinh: ");
    gets(x.ngaysinh);
     
    printf("Can nang: ");
    scanf("%f",&cn);
    x.cannang=cn;
    fflush(stdin);
     
 
}
 
 
void main()
{
    sinhvien dssv[3];
    int i=0;
    //-------Nhap SV
    for (i=0;i<3;i++)
    {
         
        printf("Nhap sinh vien thu %d\n----------------------------------------\n",i+1);
        Nhap1SV(dssv[i]);
        printf("\n");
    }
}

V. Bài Tập Về Hàm Gets() Trong C++

Ví dụ

Chương trình C sau minh họa cách sử dụng của hàm gets() trong C. Bạn sẽ thấy rằng hàm gets() chấp nhận chuỗi có chứa khoảng trống, không giống như hàm scanf():

#include <stdio.h>

int main()
{
   char str[50];

   printf("Nhap mot chuoi: ");
   gets(str);

   printf("Ban vua nhap chuoi: %s", str);

   return(0);
}
Bài Tập Về Hàm Gets() Trong C++
Bài Tập Về Hàm Gets() Trong C++

 

The post Hàm Gets() Trong C++ first appeared on Techacademy.

source https://techacademy.edu.vn/ham-gets-trong-c/

Destructor Trong C++

Trong bài học hôm nay chúng ta tiếp tục tìm hiểu về hàm hủy (Destructor) trong C++. Mục đích của hàm hủy trong C++ là gì? Cách sử dụng hàm hủy như thế nào? Chúng ta sẽ cùng tìm hiểu trong nội dung sau đây.

I. Hàm Destructor Trong C+Là Gì

Hàm hủy (Destructor) trong C++ ngược lại với hàm xây dựng, trong khi hàm xây dựng dùng để khởi tạo giá trị cho đối tượng thì hàm hủy dùng để hủy đối tượng.

Chỉ có duy nhất một hàm hủy trong 1 lớp. Hàm hủy tự động được gọi. Nếu như chúng ta không định nghĩa hàm hủy thì mặc định trình biên dịch sẽ tự tạo ra 1 hàm hủy mặc nhiên

Cũng giống như hàm xây dựng, hàm hủy được định nghĩa có cùng tên với tên lớp, khôn có bất cứ kiểu gì trả về kể cẳ kiểu void, tuy nhiên phải có dấu ~ trước tên của hàm hủy.

Lưu ý: Hàm hủy (Destructor) không có bất cứ tham số nào

Cú pháp

Cú pháp của hàm hủy (Destructor) trong C++ như sau:

Cú pháp
~TenLop() { };

Ví dụ cụ thể là lớp nhân viên, thì chúng ta sẽ tạo hàm hủy cho lớp nhân viên như sau:

Ví dụ
class NhanVien {  
   public:  
        ~NhanVien(){};
};

Ví dụ

Chúng ta cùng xem xét một ví dụ đơn giản nhất về hàm hủy trong C++ như sau:

Ví dụ

#include <iostream>  
using namespace std;  
class NhanVien  {  
   public:  
        NhanVien() {    
            cout << "Ham xay dung duoc goi" << endl;    
        }    
        ~NhanVien() {    
            cout << "Ham huy duoc goi" << endl;    
        }  
};  
int main(void) {  
    NhanVien n1;   
    NhanVien n2; 
    return 0;  
}
Hàm Destructor Trong C+Là Gì
Hàm Destructor Trong C+Là Gì

II. Khi Nào Hàm Destructor Được Gọi

Hàm hủy (Destructor) trong C++ được gọi tự động lúc đối tượng đi ra khỏi phạm vi:

  • Kết thúc hàm
  • Kết thúc chương trình
  • Kết thúc 1 block
  • Toán tử delete được gọi

Có hai hạn chế lúc dùng hàm hủy đó là:

  • Chúng ta không thể lấy địa chỉ của nó
  • Lớp con không có thừa kế hàm hủy từ lớp cha của nó
Khi Nào Hàm Destructor Được Gọi
Khi Nào Hàm Destructor Được Gọi

III. So Sánh Hàm Destructor Với Hàm Constructor

+ Giống Nhau:

Hàm tạo và hàm hủy là những hàm thành viên có cùng tên với lớp của chúng. Loại cũ constructor giúp khởi tạo một đối tượng. Ngược lại, a người phá hủy khác với hàm tạo sẽ xóa hàm tạo đã tạo khi nó không được sử dụng.

Đôi khi nó được yêu cầu khởi tạo 1 số phần của một đối tượng trước lúc nó có thể được sử dụng. Ví dụ, chúng ta đang thao tác trên ngăn xếp, trước khi chúng ta thực hiện bất kỳ hành động nào, đỉnh của ngăn xếp phải luôn được đặt bằng 0. Tính năng khởi tạo tự động này được thực hiện thông qua ‘Constructor’.

Giống như, trường hợp 1 đối tượng cần thực thi 1 số mã trước lúc nó bị phá hủy. Ví dụ: nếu một đối tượng cần đóng một tệp mà nó đã mở, trước khi nó bị phá hủy. Nó có thể được thực hiện với sự trợ giúp của ‘Destructor’. Bây giờ, hãy tổng quan về một số điểm khác biệt cơ bản giữa hàm tạo và hàm hủy với sự trợ giúp của biểu đồ so sánh

+ Khác Nhau:

– Biểu đồ so sánh:

Cơ sở để so sánh Constructor Kẻ hủy diệt
Mục đích Nó cấp phát bộ nhớ cho 1 đối tượng. Nó phân bổ bộ nhớ của một đối tượng.
Tờ khai class_name (các đối số nếu có) {}; ~ class_name (không có đối số) {};
Tranh luận Hàm tạo chấp nhận đối số Trình hủy không chấp nhận bất kỳ đối số nào.
Kêu gọi Hàm tạo được gọi tự động, trong lúc đối tượng được tạo. Bộ hủy được gọi tự động, lúc khối được thoát hoặc chương trình kết thúc.
Đang làm việc Constructor cho phép một đối tượng khởi tạo một số giá trị của nó trước đó, nó được sử dụng. Destructor cho phép một đối tượng thực thi một số mã tại thời điểm nó bị phá hủy.
Thứ tự thực hiện Hàm tạo được gọi theo thứ tự liên tiếp. Hàm hủy được gọi theo thứ tự ngược lại của hàm tạo.
Bằng số Có thể có nhiều hàm tạo trong một lớp. Luôn có một hàm hủy duy nhất trong lớp.
Copy Constructor Copy constructor cho phép một constructor khai báo và khởi tạo một đối tượng từ một đối tượng khác. Không có khái niệm như vậy.
Quá tải Các trình xây dựng có thể bị quá tải. Bộ hủy không thể bị quá tải.

– Định nghĩa của Constructor

A constructor về căn bản là 1 hàm thành viên của lớp, nó khởi tạo đối tượng và cấp phát bộ nhớ cho nó. Các hàm tạo có thể được xác định dễ dàng vì chúng được khai báo và định nghĩa cùng tên với tên của lớp. Một phương thức khởi tạo không có bất kỳ kiểu trả về nào; vì vậy, chúng không trả lại bất cứ thứ gì, thậm chí không phải là ‘void’. Một Constructor luôn được định nghĩa trong phần public của 1 lớp.

Có thể có nhiều hàm tạo trong 1 lớp; chúng có thể được phân biệt dựa trên số lượng và loại đối số được truyền vào. Nếu có nhiều hàm tạo trong một lớp; phương thức khởi tạo ngầm định (hàm tạo không làm gì) phải được định nghĩa cùng với chúng; nó không làm gì ngoài, đáp ứng trình biên dịch.

Các hàm tạo cũng có thể được định nghĩa với những đối số mặc định. Trong khi đó, họ cũng khởi tạo đối tượng “động”. Các hàm tạo không thể được kế thừa, cũng không thể là ảo, nhưng chúng có thể bị quá tải. Họ không thể được giới thiệu đến địa chỉ của họ.

– Các loại cấu tạo

Về cơ bản có ba loại cấu trúc – Cấu trúc mặc định, Tham số và Sao chép.

  • Nhà xây dựng mặc định: Nó là một hàm tạo mà không có đối số nào được đưa ra cho hàm tạo. Hàm tạo mặc định không có tham số, nhưng các giá trị cho hàm tạo mặc định có thể được truyền theo mặc định (động).
  • Trình tạo tham số: Kiểu hàm tạo này nhận các đối số; chúng ta có thể chuyển các giá trị khác nhau cho các thành viên dữ liệu làm đối số.
  • Copy Constructor: Copy constructor khác với các loại constructor khác vì nó chấp nhận địa chỉ của đối tượng khác làm đối số.

– Thực hiện hàm tạo

lớp Const {int a, b; public: Const () // hàm tạo không có tham số {a = 0; b = 0; } Const (int c, int d) {// hàm tạo với tham số a = c; c = d; }}; int main () {Const C1; C2 (10,20); // câu lệnh này gọi hàm tạo}

Khi C1 được tạo, một hàm tạo không có tham số nào được thực thi, vì C1 không truyền bất kỳ tham số nào. Trong khi, khi C2 được tạo, một hàm tạo có tham số sẽ được thực thi, vì nó đang truyền hai số nguyên cho hàm tạo.

– Định nghĩa của Destructor

A Kẻ hủy diệt cũng là một hàm thành viên của một lớp, nó sẽ phân bổ bộ nhớ được cấp phát cho một đối tượng. Nó được định nghĩa cùng tên với tên của một lớp, đứng trước dấu ngã (~) Biểu tượng. Các hàm hủy luôn được gọi theo thứ tự ngược lại của các hàm tạo.

Luôn có một hàm hủy duy nhất trong một lớp, vì nó không chấp nhận bất kỳ đối số nào. Các đối tượng địa phương bị phá hủy ngay sau khi quyền kiểm soát của việc thực hiện thúc đẩy khối; mặt khác, các đối tượng toàn cục bị phá hủy khi toàn bộ chương trình kết thúc.

Một trình hủy được gọi ngầm bởi một trình biên dịch. Nếu các lớp được kế thừa và một lớp được dẫn xuất từ ​​lớp cha và cả lớp con và lớp cha đều có hàm hủy; sau đó, hàm hủy của lớp dẫn xuất được gọi đầu tiên, tiếp theo là hàm hủy của lớp cha.

– Triển khai Trình hủy

lớp Const {int a, b; public: Const (int c, int d) // hàm tạo có tham số. {a = c; c = d; cout “giá trị của a và b là” ab ” n”; } ~ Const () // hàm hủy đang được gọi. {cout “đối tượng C1 bị phá hủy” ” n”; }}; int main () {Const C1 (10,20); }

Khi đối tượng C1 được tạo, một hàm tạo có hai tham số kiểu số nguyên được gọi và thành viên “a, b” được khởi tạo và giá trị của “a, b” được in ra. Sau khi trình hủy đó được gọi và in ra thông báo “đối tượng C1 bị phá hủy”.

Need of Destructor

Việc tạo hàm tạo sẽ tiêu tốn một lượng không gian bộ nhớ, vì nó cuối cùng sẽ cấp phát bộ nhớ cho các đối tượng. Bộ nhớ được cấp phát này phải được phân bổ trước khi hủy các đối tượng để giải phóng tài nguyên cho các tác vụ khác. Bộ hủy cực kỳ hữu ích cho mục đích đã định, nó phá hủy hiệu quả các đối tượng và thực hiện các nhiệm vụ dọn dẹp để giải phóng bộ nhớ.

So Sánh Hàm Destructor Với Hàm Constructor
So Sánh Hàm Destructor Với Hàm Constructor

IV. Bài Tập Hàm Destructor Trong C++

Chúng ta cùng xem một class đơn giản có sử dụng hàm destructor:

/**
* Techacademy.edu.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>
#include <cassert>
 
class IntArray
{
private:
   int *m_array;
   int m_length;
 
public:
   IntArray(int length) // constructor
   {
      assert(length > 0);
 
      m_array = new int[length]{};
      m_length = length;
   }
 
   ~IntArray() // destructor
   {
      // Dynamically delete the array we allocated earlier
      delete[] m_array;
   }
 
   void setValue(int index, int value) { m_array[index] = value; }
   int getValue(int index) { return m_array[index]; }
 
   int getLength() { return m_length; }
};
 
int main()
{
   IntArray ar(10); // allocate 10 integers
   for (int count{ 0 }; count < ar.getLength(); ++count)
      ar.setValue(count, count+1);
 
   std::cout << "The value of element 5 is: " << ar.getValue(5) << '\n';
 
   return 0;
} // ar is destroyed here, so the ~IntArray() destructor function is called here

Mẹo nhỏ

Nếu bạn thử biên dịch đoạn code trên và bị lỗi sau:

If you compile the above example and get the following error:

error: 'class IntArray' has pointer data members [-Werror=effc++]|
error:   but does not override 'IntArray(const IntArray&)' [-Werror=effc++]|
error:   or 'operator=(const IntArray&)' [-Werror=effc++]|

Để khắc phúc lỗi này, bạn có thể loại bỏ cờ “-Weffc++” khỏi compile settings (những thiết lập về biên dịch) cho ví dụ này, hoặc là bạn có thể thêm hai dòng code sau vào trong class IntArray:

IntArray(const IntArray&) = delete;
IntArray& operator=(const IntArray&) = delete;

Chúng ta sẽ thảo luận về chức năng của hai câu lệnh này trong chương sau.

Đoạn chương trình trên sẽ in ra:

The value of element 5 is: 6

Trên dòng đầu tiên, chúng ta đã khởi tạo 1 đối tượng mới của class IntArray, được gọi là ar, và truyền vào độ dài mảng là 10. Việc khởi tạo này sẽ gọi tới hàm constructor nhằm cấp phát bộ nhớ động cho những biến thành viên của class IntArray.

Chúng ta bắt buộc sử dụng cấp phát động tại đây bởi vì chúng ta không thể biết được tại thời điểm biên dịch (compile time) thì độ dài của mảng là bao nhiêu (caller – đối tượng gọi hàm sẽ quyết định điều đó).

Khi hàm main() kết thúc, lúc này đối tượng ar đã nằm ngoài phạm vi đoạn code mà chương trình đang chạy trên đấy (tức là ar đã goes out of scope). Điều này sẽ làm cho hàm destructor ~IntArray() được gọi, để xóa đi mảng mà chúng ta đã cấp phát bên trong phần thân hàm của constructor!

Bài Tập Hàm Destructor Trong C++
Bài Tập Hàm Destructor Trong C++

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

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

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/