Cấp phát bộ nhớ động

     
Chào các bạn học viên sẽ theo dõi khóa huấn luyện lập trình trực tuyến ngôn từ C++.

Bạn đang xem: Cấp phát bộ nhớ động

Trong bài học này, mình đang tiếp tục giới thiệu đến các bạn một số sự việc về bé trỏ và thực hiện con trỏ nhằm quản lý bộ lưu trữ ảo trong ngôn ngữ C++.

Như mình đã đề cập trong bài học phạm vi của biến, thời hạn tồn trên của biến nhờ vào vào vị trí bạn khai báo biến.

Biến cục bộ (global variable) được khai báo bên ngoài khối lệnh, rất có thể được truy xuất tại bất cứ dòng lệnh làm sao đặt dưới biến đó. Biến tổng thể tồn tại đến lúc chương trình bị kết thúc.Biến toàn bộ (local variable) được khai báo bên trong khối lệnh, hoàn toàn có thể được tróc nã xuất tại bất cứ dòng lệnh làm sao đặt bên dưới biến đó cùng trong thuộc khối lệnh. Biến toàn cục bị diệt khi lịch trình chạy ra phía bên ngoài khối lệnh chứa trở nên đó.

Tương ứng cùng với 2 kiểu khai báo biến chuyển này là 2 phương thức cấp phát bộ nhớ cho chương trình trên bộ lưu trữ ảo:

Static memory allocation (cấp phát bộ nhớ lưu trữ tĩnh)

Static memory allocation còn được gọi là Compile-time allocation, được áp dụng cho biến chuyển static và đổi thay toàn cục.

Vùng nhớ của những biến này được cấp phát ngay trong khi chạy chương trình.Kích thước của vùng ghi nhớ được cấp phát phải được cung cấp tại thời điểm biên dịch chương trình.Đối với việc khai báo mảng một chiều, đây là lý do lý do số lượng phần tử là hằng số.Automatic memory allocation (cấp phát bộ nhớ tự động)

Automatic memory allocation được sử dụng để cấp phép vùng nhớ cho các biến cục bộ, tham số của hàm.

Bộ ghi nhớ được cấp phát tại thời khắc chương trình sẽ chạy, khi lịch trình đi vào một khối lệnh.Các vùng ghi nhớ được cấp phát sẽ được tịch thu khi chương trình đi ra khỏi một khối lệnh.Kích thước vùng cần cấp phép cũng đề nghị được cung ứng rõ ràng.Nhược điểm của những phương thức cấp cho phát bộ nhớ đã học

Kích thước vùng nhớ cấp phát phải được cung ứng tại thời điểm biên dịch chương trình

Lấy ví dụ, họ cần tàng trữ tên của tất cả sinh viên trong một tấm học. Họ sẽ sử dụng một mảng các string để lưu trữ như sau:

string name_of_students<50>;Mình bây giờ không biết bao gồm bao nhiêu sv trong một tờ học, nên mình chỉ ước tính con số tối nhiều lượng sinh viên của lớp này là 50 người. Vậy điều gì xảy ra khi lớp học có nhiều hơn 50 sinh viên? Mảng name_of_students sẽ không thể lưu hết tên của toàn bộ sinh viên được. Cạnh bên đó, nếu số lượng sinh viên của lớp học tập chỉ tất cả 30 người, mảng name_of_students sẽ thừa ra 20 bộ phận không cần sử dụng đến.

Cấp vạc và tịch thu vùng nhớ vị chương trình quyết định

Trong một số trong những trường hợp, họ cần thực hiện biến cục bộ để hoàn toàn có thể truy cập vùng lưu giữ của biến hóa tại các khối lệnh khác biệt trong chương trình, nhưng thời gian tồn tại của biến cục bộ khá lâu, nên lúc sử dụng biến toàn bộ sẽ gây ảnh hưởng đáng kể lượng tài nguyên bộ nhớ của máy tính xách tay nếu chúng ta cấp phát đến biến toàn cục một vùng nhớ lớn.

Hoặc trong một trong những trường thích hợp khác, bọn họ vẫn muốn sử dụng tiếp vùng nhớ cấp phát cho biến bên trong hàm, dẫu vậy biến tổng thể đặt trong khối lệnh (cùng với vùng lưu giữ nó quản lí lý) có khả năng sẽ bị hủy khi hàm kết thúc.

Kích thước bộ nhớ lưu trữ dùng mang đến Static memory allocation và Automatic memory allocation bị giới hạn

Bộ lưu giữ ảo được chia thành nhiều phân vùng khác nhau sử dụng mang lại những một số loại tài nguyên khác nhau. Vào đó, những phương thức cấp cho phát bộ nhớ Static memory allocation hay Automatic memory allocation sẽ áp dụng phân vùng Stack nhằm lưu trữ. Chúng ta sẽ gồm một bài học kinh nghiệm để nói cụ thể về các phân vùng trên bộ lưu trữ ảo. Bây chừ các bạn trong thời điểm tạm thời hình dung bộ nhớ lưu trữ ảo bọn họ sẽ chia thành các phần như sau:


*

Phân vùng Stack được để ở vùng có add cao tuyệt nhất trong dãy bộ lưu trữ ảo. Dung lượng của phân vùng này tương đối hạn chế. Tùy vào từng hệ quản lý và điều hành mà dung lượng bộ lưu trữ của phân vùng Stack không giống nhau. Đối với Visual studio năm ngoái chạy bên trên hệ điều hành và quản lý Windows, dung lượng bộ lưu trữ của phân vùng Stack là khoảng 1MB (tương đương khoảng 1024 Kilobytes hay 1024*1024 bytes).

Với sự tinh giảm về dung lượng bộ nhớ lưu trữ của phân vùng Stack, công tác của họ sẽ gây ra lỗi stack overflow nếu các bạn yêu cầu cấp phát vùng ghi nhớ vượt quá dung lượng của Stack. Các chúng ta có thể chạy demo 2 đoạn công tác sau nhằm kiểm chứng:

int main() char ch_array<1024 * 1000>; system("pause"); return 0;Trong đoạn chương trình trên, bản thân khai báo một mảng kí tự có tên ch_array, như các bạn biết đẳng cấp char có kích cỡ 1 byte cho từng biến 1-1 (tương ứng cùng với mỗi thành phần trong mảng kí tự), 1024 bytes sẽ khớp ứng với 1Kb (Kilobyte). Bởi vì ch_array là trở nên cục bộ, nó đã được cấp phép vùng nhớ trên phân vùng Stack của bộ nhớ lưu trữ ảo. Như vậy, mảng ch_array sẽ được cấp phép 1000 kilobytes trên phân vùng Stack, nhưng con số này vẫn không vượt quá số lượng giới hạn 1Mb (1 Megabyte = 1024 Kilobytes) đề nghị chương trình vẫn chạy bình thường. Bây giờ các bạn thử lại với đoạn lịch trình sau:

int main() char ch_array<1024 * 1024>; system("pause"); return 0;Kích thước vùng lưu giữ được yêu thương cầu cấp cho phát bây chừ là đúng bởi 1 Mb. Thử chạy lịch trình ở cơ chế Debug, Visual Studio năm ngoái trên máy tính xách tay mình chỉ dẫn thông báo:

*

Việc cấp phát vùng ghi nhớ có kích cỡ 1 Mb đã gây tràn bộ lưu trữ phân vùng Stack.

Đây là một vài hạn chế của các phương thức cung cấp phát bộ nhớ Static memory allocation và Automatic memory allocation. Để tự khắc phục giảm bớt này, mình trình làng đến các bạn một cách làm cấp phát bộ nhớ lưu trữ mới được ngôn ngữ C++ hổ trợ.

Dynamic memory allocation

Dynamic memory allocation là một chiến thuật cấp phát bộ nhớ cho chương trình tại thời điểm chương trình đang làm việc (run-time). Dynamic memory allocation thực hiện phân vùng Heap trên bộ lưu trữ ảo để cấp phép cho chương trình.


*

Như các bạn thấy vào hình trên, phân vùng Heap của bộ nhớ lưu trữ ảo gồm dung lượng bộ nhớ lưu trữ lớn nhất. Bởi đó, bộ nhớ dùng để cấp phát cho công tác trên phân vùng Heap chỉ bị giới hạn bởi sản phẩm phần cứng (ví dụ là RAM) chứ không phụ thuộc vào hệ điều hành. Trong các máy tính văn minh ngày nay, dung lượng bộ lưu trữ của phân vùng Heap rất có thể lên đến đơn vị chức năng GB (1 Gigabyte = 1024 Megabytes = 1024 * 1024 Kilobytes).

Đọc kỹ hướng dẫn sử dụng trước lúc dùng

Kỹ thuật Dynamic memory allocation dùng làm cấp phát bộ nhớ tại thời điểm run-time. Tại thời gian này, bọn họ không thể tạo thành tên thay đổi mới, mà chỉ rất có thể tạo ra vùng nhớ mới. Vày đó, phương pháp duy độc nhất để kiểm soát điều hành được phần đa vùng lưu giữ được cấp phép bằng chuyên môn Dynamic memory allocation là áp dụng con trỏ lưu giữ trữ địa chỉ cửa hàng đầu tiên của vùng lưu giữ được cấp phát, trải qua con trỏ để quản lý vùng ghi nhớ trên Heap.

Vậy, việc tiến hành cấp phát bộ nhớ cần tiến hành qua 2 bước:

Yêu cầu cấp phát vùng ghi nhớ trên Heap.Lưu trữ add của vùng nhớ vừa được cấp phát bằng nhỏ trỏ.

Để yêu thương cầu cấp cho phát bộ lưu trữ trên Heap, chúng ta sử dụng new operator.

Vùng nhớ được cấp phép trên Heap vẫn không auto hủy vì chương trình khi ngừng khối lệnh, việc tịch thu vùng nhớ đã cấp phép trên Heap được giao cho lập trình viên tự quản ngại lý. Nếu như trong chương trình gồm yêu cầu cung cấp phát bộ lưu trữ trên Heap nhưng mà không được thu hồi hợp lí sẽ gây lãng phí tài nguyên hệ thống. Cũng như xin nhà nước cấp phát cho một vùng khu đất để xuất bản nhà máy, đã xây thân chừng thì mặt thầu dự án công trình ăn hết vốn nên dự án xây dựng xí nghiệp sản xuất bị hoãn lại, cơ mà đất được đơn vị nước cấp phát không được trả lại mang đến nhà nước để làm việc khác, thế là tiêu tốn lãng phí một vùng đất mà lại không có tác dụng được gì, tài nguyên trên máy tính cũng giống như như vậy.

Để thu hồi vùng nhớ vẫn được cấp phát thông qua toán tử new, bọn họ sử dụng toán tử delete.

Dynamically allocate single variables

new operator

Toán tử new được dùng làm xin cấp phát vùng lưu giữ trên phân vùng Heap của bộ lưu trữ ảo.

Toán tử new trong chuẩn chỉnh C++11 được định nghĩa với 3 prototype như sau:

void* operator new (std::size_t size);void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;void* operator new (std::size_t size, void* ptr) noexcept;Các bạn chưa cần được hiểu rất nhiều tham số khai báo đến toán tử new, mà hiện tại chỉ cần chú ý kiểu trả về của chính nó (void *). Toán tử new sau khoản thời gian xin cấp phép vùng nhớ trên Heap sẽ trả về một nhỏ trỏ chứa add của vùng ghi nhớ được cấp phát (nếu cấp phát thành công).

Kiểu trả về của toán tử new là bé trỏ kiểu dáng void, đấy là một bé trỏ sệt biệt, bọn họ sẽ tìm hiểu nó trong bài học sau. Tuy thế dù nó là bé trỏ vẻ bên ngoài gì thì mục đích của nó vẫn là chứa địa chỉ, vì đó, bạn có thể gán giá trị trả về của toán tử new mang đến một bé trỏ khác để thống trị vùng nhớ đang được cấp phát.

Xem thêm: 5+ Cách Kiểm Tra Nợ Ngân Hàng Agribank Nhanh Nhất Trong Năm 2022

usage of new operator

Cú pháp áp dụng toán tử new như sau:

new ;Ví dụ:

new int; //allocate 4 bytes on Heap partition khổng lồ an int variablenew double; //allocate 8 bytes on Heap partition to a double variableKhi công tác đang chạy, nếu quá trình cấp phát bộ nhớ trên thành công, họ sẽ có add của 2 vùng nhớ được trả về. Tuy vậy như mình đã nói, họ không thể chế tạo thêm thương hiệu biến new khi lịch trình đang chạy, do đó họ cần gán nó cho những con trỏ cùng kiểu để quản lý:

int *p_int = new int;double *p_double = new double;Bây giờ, vùng nhớ được cấp phép sẽ được làm chủ bởi 2 nhỏ trỏ p_int với p_double, 2 vùng nhớ này được hệ quản lý trao quyền sử dụng tạm thời cho lịch trình của bọn chúng ta, thông qua con trỏ, bạn có thể thay đổi giá trị bên trong vùng nhớ này. Ví dụ:

int *p_int = new int;cout << "Put value into memory area" << endl;cin >> *p_int;cout << "Value at " << p_int << " is " << *p_int << endl;Chúng ta còn hoàn toàn có thể vừa cung cấp phát bộ nhớ lưu trữ vừa khởi sinh sản giá trị trên vùng ghi nhớ đó mang đến một trở thành đơn:

int *p1 = new int(5);int *p2 = new int *p1 ;usage of delete operatorKhi không thích sử dụng tiếp vùng nhớ đã được cấp phép cho công tác trên Heap, họ nên trả lại vùng lưu giữ đó mang đến hệ điều hành. Thiệt ra khi lịch trình kết thúc, tất cả khu vực nhớ của chương trình đông đảo bị hệ điều hành và quản lý thu hồi, nhưng bọn họ nên giải phóng vùng ghi nhớ không quan trọng càng sớm càng tốt.

Để xóa một vùng nhớ, chúng ta cần bao gồm một showroom cụ thể, showroom đó được duy trì bởi bé trỏ sau khi gán showroom cấp phát đến nó:

int *p = new int;//using memory area at p//and then phối it freedelete p;Lúc này, con trỏ p vẫn còn đấy giữ showroom của vùng nhớ sẽ được cấp phát trên Heap. Trường hợp may mắn, vùng ghi nhớ đó không được hệ điều hành cấp phép cho công tác khác, chúng ta vẫn có thể dùng bé trỏ p để thay đổi giá trị phía bên trong nó.

int *p = new intdelete p;//keep using that memory area*p = 10;cout << p << endl;Nếu không may mắn, con trỏ p sẽ mang tội danh xâm nhập bất hợp pháp vào vùng ghi nhớ của lịch trình khác, và công tác của bọn họ sẽ bị crash.

mean of delete operator

Sử dụng toán tử delete không tức là delete toàn bộ mọi thứ phía bên trong vùng lưu giữ mà nhỏ trỏ trỏ đến. Toán tử new và delete chỉ mang chân thành và ý nghĩa về "quyền sử dụng" vùng nhớ. Toàn thể dãy showroom trên bộ lưu trữ ảo được cai quản bởi một lịch trình mang tên "Hệ điều hành", với hệ điều hành và quản lý có quyền trao lại quyền sử dụng một vùng lưu giữ nào đó (trên Stack hoặc trên Heap...) cho hầu hết chương trình an toàn trên thứ tính.

Và toán tử new dùng để làm hợp đồng áp dụng vùng lưu giữ trên Heap, các bạn lấy vùng nhớ được cung cấp phát thông qua hợp đồng (make by new operator) để chương trình chạy, vậy khi bạn sử dụng toán tử delete, đơn giản và dễ dàng là các bạn chỉ xé bạn dạng hợp đồng đó đi (hoặc chuyển lại đến hệ điều hành). Cơ hội này, quý giá trên vùng nhớ kia có thể vẫn còn không thay đổi do chưa có chương trình như thế nào can thiệp vào.

Toán tử delete không tác động gì đến bé trỏ.

Dangling pointer

"Con trỏ bị treo" thường xảy ra sau khi giải phóng vùng nhớ bằng toán tử delete. Sau khi sử dụng toán tử delete, vùng lưu giữ được cấp phát được trả lại mang đến hệ quản lý và điều hành quản lý, nhưng con trỏ vẫn còn đấy trỏ vào địa chỉ đó. áp dụng toán tử dereference cho nhỏ trỏ trên thời đặc điểm đó sẽ gây nên lỗi undefined behavior.

int main() int *ptr = new int; // dynamically allocate an integer *ptr = 7; // put a value in that memory location delete ptr; // return the memory to the operating system. Ptr is now a dangling pointer. Std::cout << *ptr; // Dereferencing a dangling pointer will cause undefined behavior delete ptr; // trying khổng lồ deallocate the memory again will also lead to undefined behavior. Return 0;Còn nhiều trường vừa lòng khác nhau hoàn toàn có thể khiến con trỏ bị treo, bản thân sẽ dành ra một bài học kinh nghiệm để nói đến cách cai quản vùng lưu giữ và nhỏ trỏ khi thực hiện kỹ thuật Dynamic memory allocation.

Điều gì xảy ra khi xin cấp phát vùng lưu giữ trên Heap thất bại?

Quá trình cấp phát vùng ghi nhớ trên Heap thất bại hoàn toàn có thể do có chương trình nào đó đang thực hiện lượng bộ nhớ lưu trữ quá lớn (ví dụ chương trình chế tạo ra máy ảo), với chương trình của doanh nghiệp yêu cầu cung cấp vùng lưu giữ có kích cỡ nên hệ quản lý điều hành không nạm tìm thấy đoạn vùng nhớ nào đủ mang lại yêu mong của công tác của bạn.

Chúng ta cùng xem lại các protoyte của toán tử new:

void* operator new (std::size_t size); // (1)void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept; // (2)void* operator new (std::size_t size, void* ptr) noexcept; // (3)Mặc định, chúng ta sử dụng toán tử new ở phương pháp khai báo (1), vào trường vừa lòng này, nếu cấp phép vùng ghi nhớ thất bại, toán tử new đã ném ra ngoại lệ std::bad_alloc. Nếu như ngoại lệ này không được xử lý, chương trình họ sẽ bị hoàn thành với lỗi unhandled exception error.

Trong một số trong những trường hợp, họ không ước ao dính mang đến ngoại lệ (exception) trong C++, họ nên chọn áp dụng phiên bản toán tử new (2), ví dụ:

int *p = new (std::nothrow) int;Sử dụng bí quyết này, nếu quy trình cấp vạc thất bại, toán tử new đã trả về cực hiếm NULL. Dịp này, bạn cũng có thể kiểm tra xem chương trình của chúng ta có xin được vùng nhớ giỏi không:

if (p == NULL) cout << "Could not allocate memory on Heap partition" << endl; exit(1);else //use that memory area //and then delete it delete p;Sử dụng giải pháp này để giúp chương trình họ sử dụng bé trỏ bình an hơn khi sử dụng kỹ thuật Dynamic memory allocation.

Dynamically allocate arrays

Để xin cấp phát và giải phóng vùng nhớ đến mảng một chiều bên trên Heap, họ cũng sử dụng toán tử new với delete nhằm xử lý.

Dynamically allocate arrays

Đối với bài toán yêu cầu cấp phát bộ nhớ lưu trữ cho biến đối kháng trên Heap, bọn họ chỉ cần cung ứng kiểu tài liệu cho toán tử new, hệ điều hành quản lý sẽ từ tính được kích thước cần cấp phát (tương từ việc thực hiện toán tử sizeof). Dẫu vậy khi cần cấp phát một hàng vùng nhớ liên tiếp nhau (mảng một chiều), kế bên kiểu dữ liệu bọn họ cần cung ứng thêm số lượng phần tử.

new ;Nếu quy trình cấp vạc thành công, toán tử new sẽ trả về add của bộ phận đầu tiên của vùng lưu giữ được cấp phát, và tương tự như cấp phép cho trở thành đơn, họ cho 1 bé trỏ bao gồm kiểu dữ liệu phù hợp lưu trữ địa trả về để làm chủ vùng nhớ. Ví dụ:

int *p_arr = new int<10>;//using this memory areafor (int i = 0; i < 10; i++) //Set value for each element cin >> *p_arr;Chúng ta rất có thể khởi tạo nên vùng nhớ sẽ được cung cấp phát tựa như như khởi tạo nên mảng một chiều thông thường. Ví dụ:

int arr<5> = 1, 2, 3, 4, 5 ;int *p_arr = new int<5> 1, 2, 3, 4, 5 ; //no operator = between array-size and initializer listLưu ý phương pháp này chỉ thực hiện được trong chuẩn chỉnh C++11 trở lên.

Trường đúng theo mảng kí tự luôn luôn là ngôi trường hợp đặc biệt quan trọng của mảng một chiều. Chúng ta không thể thực hiện cách khởi tạo ra này trong chuẩn C++11:

char *c_str = new char <100> "Allocated on Heap partition" ;Nhưng trường hòa hợp này có thể chạy được bên trên Visual studio 2015 với chuẩn chỉnh C++14.

Điều làm cho kỹ thuật Dynamic memory allocation khác với Static memory allocation là số lượng phần tử có thể được cung cấp trong khi công tác đang chạy. Ví dụ:

int num_of_elements;cout << "Enter number of elements you want to create: ";cin >> num_of_elements;int *p_arr = new int;Chúng ta thực hiện giá trị của biến chuyển num_of_elements có tác dụng số lượng phần tử cung cấp cho cho toán tử new, và quý giá này chỉ được xác minh sau khi người dùng nhập vào trường đoản cú bàn phím. Để tinh giảm trường hợp người tiêu dùng nhập số âm, họ cần kiểm tra trước lúc xin cung cấp phát:

int num_of_elements;cout << "Enter number of elements you want to lớn create: ";cin >> num_of_elements;if(num_of_elements > 0) int *p_arr = new int;dynamically delete arraysĐối với hàng vùng nhớ liên tiếp được cấp phát trên Heap, họ cần sản xuất toán tử < > nhằm báo với hệ quản lý rằng vùng nhớ đang được cấp phép không sử dụng cho một trở thành đơn.

int *p_arr = new int<10>;//...........delete<> p_arr;Sử dụng toán tử delete theo phong cách giải phóng vùng lưu giữ biến solo cho dãy vùng nhớ liên tục hoàn toàn có thể gây ra nhiều vấn đề không giống nhau cho công tác (memory leak, data corruption, ...).

resizing dynamic arrays

Trong các trường hợp, chúng ta cần đổi khác kích thước vùng nhớ đã được cấp phép cho phù hợp với yêu mong của chương trình. Biện pháp duy tuyệt nhất là:

Cấp tái phát vùng nhớ mới.(Copy tài liệu từ vùng nhớ cũ sáng sủa vùng nhớ bắt đầu nếu cần).Giải phóng vùng lưu giữ cũ.Cho con trỏ trỏ cho vùng nhớ mới.

int main()int *p = new int<5>;for (int i = 0; i < 5; i++)cin >> *(p + i);//re-allocateint *p_temp = p;p = new int<10>;//copy datafor (int i = 0; i < 5; i++)*(p + i) = *(p_temp + i);//dealocate old memory areadelete<> p_temp;//keep using data//and then delete itdelete<> p;system("pause");return 0;Do vùng nhớ mới sẽ có địa chỉ khác cùng với vùng lưu giữ đã cấp phát ban đầu, mình cần thực hiện con trỏ p_temp để giữ lại lại kỹ năng truy cập mang lại vùng nhớ ban đầu. Sau khi copy cục bộ dữ liệu tự vùng lưu giữ cũ thanh lịch vùng nhớ mới, họ nên giải phóng vùng lưu giữ cũ ngay để khỏi lãng phí tài nguyên hệ thông.

Tổng kết

Trong bài học kinh nghiệm này, họ đã tìm hiểu về nghệ thuật Dynamic memory allocation trong ngữ điệu C++. Chuyên môn này góp chương trình bọn họ ít bị số lượng giới hạn dung lượng bộ nhớ hơn. Nhưng bên cạnh đó, bọn họ cần có khả năng về quản lý các vùng lưu giữ trong chương trình. Thực hiện kỹ thuật Dynamic memory allocation không thành thạo là vì sao gây phổ biến gây ra lỗi memory leak. Vày đó, chúng ta sẽ bao gồm một bài học nói tới các lỗi thường chạm chán khi áp dụng Dynamic memory allocation và cách kiểm soát và điều hành các lỗi này.

Hẹn gặp lại chúng ta trong bài bác học tiếp theo trong khóa huấn luyện và đào tạo lập trình C++ hướng thực hành.

Xem thêm: Hướng Dẫn Phát Wifi Từ Điện Thoại, Cách Phát Wifi Trên Điện Thoại Android

Mọi chủ ý đóng góp hoặc thắc mắc có thể đặt câu hỏi trực tiếp trên diễn đàn.

www.thietkewebshop.vn.com

Link Videos khóa học

https://www.udemy.com/c-co-ban-danh-cho-nguoi-moi-hoc-lap-trinh/learn/v4/overview