Chào các bạn học viên đã theo dõi khóa đào tạo lập trình trực tuyến ngôn ngữ C++.

Bạn đang xem: Cấp phát bộ nhớ động cho mảng struct trong c

Trong bài học kinh nghiệm này, mình vẫn tiếp tục trình làng đến các bạn một số vấn đề về nhỏ trỏ và áp dụng con trỏ nhằm quản lý bộ nhớ lưu trữ ảo trong ngôn ngữ C++.

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

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

Tương ứng với 2 đẳng cấp khai báo phát triển thành này là 2 phương pháp cấp phát bộ nhớ lưu trữ cho công tác trên bộ nhớ lưu trữ ảo:

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

Static memory allocation có cách gọi khác là Compile-time allocation, được áp dụng cho phát triển thành static và đổi mới toàn cục.

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

Automatic memory allocation được thực hiện để cấp phát vùng nhớ cho các biến viên bộ, thông số của hàm.

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

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

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

string name_of_students<50>;Mình bây giờ không biết bao gồm bao nhiêu sinh viên trong một tấm học, nên mình chỉ ước tính con số tối đa 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ó không ít hơn 50 sinh viên? Mảng name_of_students sẽ không thể lưu lại hết tên của toàn bộ sinh viên được. Kề bên đó, nếu con số sinh viên của lớp học tập chỉ bao gồm 30 người, mảng name_of_students sẽ thừa ra 20 bộ phận không cần thực hiện đến.

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

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

Hoặc trong một số trường thích hợp khác, bọn họ vẫn mong sử dụng tiếp vùng nhớ cấp phép cho biến bên trong hàm, tuy thế biến tổng thể đặt vào khối lệnh (cùng với vùng ghi nhớ nó quản ngại lý) 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 phân thành nhiều phân vùng khác nhau sử dụng mang đến những loại tài nguyên không giống nhau. Trong đó, những phương thức cấp phát bộ nhớ lưu trữ Static memory allocation hay Automatic memory allocation sẽ thực hiện phân vùng Stack nhằm lưu trữ. Bọn họ sẽ bao 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. Hiện giờ các bạn trong thời điểm tạm thời hình dung bộ nhớ lưu trữ ảo họ sẽ tạo thành các phần như sau:


*

Phân vùng Stack được để tại vùng có showroom 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ệ điều hành quản lý mà dung lượng bộ nhớ lưu trữ của phân vùng Stack không giống nhau. Đối cùng với Visual studio 2015 chạy trên hệ quản lý và điều hành Windows, dung lượng bộ nhớ của phân vùng Stack là khoảng tầm 1MB (tương đương khoảng 1024 Kilobytes hay 1024*1024 bytes).

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

int main() char ch_array<1024 * 1000>; system("pause"); return 0;Trong đoạn lịch 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 hình trạng char có form size 1 byte cho mỗi biến solo (tương ứng cùng với mỗi thành phần trong mảng kí tự), 1024 bytes sẽ tương xứng với 1Kb (Kilobyte). Vì ch_array là biến cục bộ, nó đã được cấp phát vùng lưu giữ trên phân vùng Stack của bộ lưu trữ ảo. Như vậy, mảng ch_array vẫn được cấp phép 1000 kilobytes bên 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) yêu cầu chương trình vẫn chạy bình thường. Hiện thời các các bạn thử lại với đoạn công tác sau:

int main() char ch_array<1024 * 1024>; system("pause"); return 0;Kích thước vùng ghi nhớ được yêu thương cầu cung cấp phát bây giờ là đúng bằng 1 Mb. Test chạy lịch trình ở chính sách Debug, Visual Studio 2015 trên laptop mình chỉ dẫn thông báo:

*

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

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

Dynamic memory allocation

Dynamic memory allocation là một phương án cấp phát bộ lưu trữ cho chương trình tại thời điểm chương trình đang chạy (run-time). Dynamic memory allocation áp dụng phân vùng Heap trên bộ nhớ ảo để cấp phát 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ớ ảo tất cả dung lượng bộ lưu trữ lớn nhất. Vày đó, bộ lưu trữ dùng để cấp phát cho chương trình trên phân vùng Heap chỉ bị giới hạn bởi thiết bị phần cứng (ví dụ là RAM) chứ không phụ thuộc vào vào hệ điều hành. Trong những máy tính tiến bộ 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ị GB (1 Gigabyte = 1024 Megabytes = 1024 * 1024 Kilobytes).

Đọc kỹ chỉ dẫn sử dụng trước khi dùng

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

Vậy, việc triển khai cấp phát bộ nhớ lưu trữ cần triển khai qua 2 bước:

Yêu cầu cấp phát vùng lưu giữ trên Heap.Lưu trữ địa chỉ của vùng lưu giữ vừa được cấp phát bằng nhỏ trỏ.

Để yêu cầu cấp cho phát bộ nhớ lưu trữ trên Heap, họ sử dụng new operator.

Vùng lưu giữ được cấp phép trên Heap sẽ không auto hủy bởi vì chương trình khi hoàn thành 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ự cai quản lý. Nếu như trong chương trình tất cả yêu cầu cung cấp phát bộ lưu trữ trên Heap mà không được thu hồi phù hợp sẽ gây tiêu tốn lãng phí tài nguyên hệ thống. Cũng tương tự xin bên nước cấp phát cho một vùng đất để xây đắp nhà máy, sẽ xây giữa chừng thì mặt thầu dự án công trình ăn không còn vốn nên dự án công trình xây dựng xí nghiệp sản xuất bị hoãn lại, mà lại đất được công ty nước cấp phép không được trả lại cho nhà nước để triển khai việc khác, cầm cố là tiêu tốn lãng phí một vùng đất cơ mà không làm được gì, tài nguyên trên thiết bị tính tương tự như như vậy.

Để tịch thu vùng nhớ vẫn được cung cấp phát trải 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 để xin cấp phép vùng nhớ trên phân vùng Heap của bộ nhớ lưu trữ ảo.

Toán tử new trong chuẩn C++11 được quan niệm 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 chúng ta chưa cần phải hiểu phần đông tham số khai báo mang đến toán tử new, mà lúc này 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 lưu giữ trên Heap sẽ trả về một con trỏ chứa địa chỉ cửa hàng của vùng lưu giữ đượ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à con trỏ kiểu dáng void, đây là một bé trỏ sệt biệt, họ sẽ tìm hiểu nó trong bài học sau. Tuy nhiên dù nó là nhỏ trỏ loại gì thì mục đích của nó vẫn là chứa địa chỉ, vì chưng đó, bạn có thể gán quý giá trả về của toán tử new mang đến một nhỏ trỏ không giống để làm chủ vùng nhớ sẽ được cung cấp phát.

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 to lớn an int variablenew double; //allocate 8 bytes on Heap partition to a double variableKhi lịch trình đang chạy, nếu quá trình cấp phát bộ lưu trữ trên thành công, họ sẽ có add của 2 vùng nhớ được trả về. Tuy nhiên như mình đã nói, bọn họ không thể chế tác thêm tên biến new khi lịch trình đang chạy, vì đó họ cần gán nó cho những bé trỏ thuộc kiểu nhằm quản lý:

int *p_int = new int;double *p_double = new double;Bây giờ, vùng ghi nhớ được cấp phát sẽ được làm chủ bởi 2 nhỏ trỏ p_int cùng p_double, hai vùng nhớ này được hệ quản lý điều hành trao quyền sử dụng trong thời điểm tạm thời cho công tác của bọn chúng ta, trải qua con trỏ, chúng ta cũng có thể thay thay đổi giá trị bên trong vùng ghi 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 cấp phát bộ nhớ vừa khởi chế tạo ra giá trị trên vùng lưu giữ đó mang lại một biến đơn:

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

Để xóa một vùng nhớ, bọn họ cần bao gồm một showroom cụ thể, add đó được duy trì bởi bé trỏ sau khoản thời gian gán địa chỉ 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, nhỏ trỏ p vẫn tồn tại giữ showroom của vùng nhớ đã được cấp phép trên Heap. Giả dụ may mắn, vùng lưu giữ đó không được hệ điều hành cấp phát cho công tác khác, chúng ta vẫn hoàn toàn có thể dùng bé trỏ p. để chuyển đổi giá trị bên phía trong nó.

int *p = new intdelete p;//keep using that memory area*p = 10;cout << p. << endl;Nếu rủi ro mắn, nhỏ trỏ p sẽ sở hữu tội danh xâm nhập phi pháp vào vùng lưu giữ của lịch trình khác, và chương trình của chúng ta sẽ bị crash.

mean of delete operator

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

Và toán tử new dùng để làm hợp đồng sử dụng vùng lưu giữ trên Heap, chúng ta lấy vùng lưu giữ được cấp cho phát trải 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à bạn chỉ xé phiên bản hợp đồng đó đi (hoặc gửi lại mang lại hệ điều hành). Lúc này, cực hiếm trên vùng nhớ kia có thể vẫn còn không thay đổi do chưa có chương trình nào can thiệp vào.

Toán tử delete không tác động ảnh hưởng gì đến con trỏ.

Dangling pointer

"Con trỏ bị treo" thường xuyên xảy ra sau thời điểm giải phóng vùng nhớ bởi toán tử delete. Sau thời điểm sử dụng toán tử delete, vùng lưu giữ được cấp phát được trả lại đến hệ quản lý quản lý, nhưng bé trỏ vẫn còn đấy trỏ vào địa chỉ đó. áp dụng toán tử dereference cho bé trỏ trên thời đặc điểm này sẽ gây ra 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 lớn the operating system. Ptr is now a dangling pointer. Std::cout << *ptr; // Dereferencing a dangling pointer will cause undefined behavior delete ptr; // trying to deallocate the memory again will also lead to undefined behavior. Return 0;Còn những trường hợp khác nhau có thể khiến nhỏ trỏ bị treo, mình sẽ để dành ra một bài học kinh nghiệm để nói về cách cai quản vùng ghi nhớ và nhỏ trỏ khi áp dụng kỹ thuật Dynamic memory allocation.

Điều gì xẩy ra khi xin cấp phép vùng nhớ trên Heap thất bại?

Quá trình cấp phát vùng lưu giữ trên Heap thất bại rất có thể do gồm chương trình nào kia đang sử dụng lượng bộ lưu trữ quá bự (ví dụ chương trình chế tạo máy ảo), với chương trình của chúng ta yêu cầu hỗ trợ vùng lưu giữ có form size nên hệ điều hành và quản lý không cố gắng tìm thấy đoạn vùng nhớ nào đủ mang lại yêu ước của lịch trình của bạn.

Chúng ta cùng xem lại những 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, bọn họ sử dụng toán tử new ở phương pháp khai báo (1), vào trường phù hợp này, nếu cấp phép vùng lưu giữ thất bại, toán tử new vẫn ném ra ngoại lệ std::bad_alloc. Ví như ngoại lệ này không được xử lý, chương trình họ sẽ bị xong xuôi với lỗi unhandled exception error.

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

int *p = new (std::nothrow) int;Sử dụng giải pháp này, nếu quy trình cấp phạt thất bại, toán tử new vẫn trả về quý giá NULL. Dịp này, bạn cũng có thể kiểm tra xem chương trình của họ có xin được vùng nhớ tuyệt 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 bí quyết này để giúp chương trình bọn họ sử dụng nhỏ 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à hóa giải vùng nhớ mang đến mảng một chiều bên trên Heap, bọn họ cũng áp dụng toán tử new và delete nhằm xử lý.

Dynamically allocate arrays

Đối với việc yêu cầu cấp cho phát bộ nhớ lưu trữ cho biến đối kháng trên Heap, bọn họ chỉ cần hỗ trợ kiểu dữ liệu cho toán tử new, hệ quản lý sẽ trường đoản cú tính được size cần cấp phép (tương từ việc áp dụng toán tử sizeof). Cơ mà khi cần cấp phát một dãy vùng nhớ tiếp tục nhau (mảng một chiều), xung quanh kiểu dữ liệu chúng ta cần cung cấp thêm số lượng phần tử.

new ;Nếu quy trình cấp phát thành công, toán tử new đã trả về địa chỉ của thành phần đầu tiên của vùng ghi nhớ được cung cấp phát, và giống như như cấp phát cho biến đơn, chúng ta cho 1 bé trỏ bao gồm kiểu dữ liệu phù hợp lưu trữ địa trả về để quản lý 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 hoàn toàn có thể khởi làm cho vùng nhớ đã được cấp cho phát tương tự như như khởi tạo thành 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 ý cách này chỉ sử dụng được trong chuẩn C++11 trở lên.

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

char *c_str = new char <100> "Allocated on Heap partition" ;Nhưng trường đúng theo này hoàn toàn có thể chạy được trên Visual studio năm ngoái với chuẩn chỉnh C++14.

Điều khiến cho kỹ thuật Dynamic memory allocation khác với Static memory allocation là số lượng bộ phận 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 sử dụng giá trị của thay đổi num_of_elements làm số lượng bộ phận 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 từ bỏ bàn phím. Để tinh giảm trường hợp người dùng nhập số âm, họ cần kiểm tra trước khi xin cấp phát:

int num_of_elements;cout << "Enter number of elements you want khổng lồ create: ";cin >> num_of_elements;if(num_of_elements > 0) int *p_arr = new int;dynamically delete arraysĐối với dãy vùng nhớ tiếp tục được cấp phép trên Heap, bọn họ cần chế tạo toán tử < > nhằm báo với hệ điều hành và quản lý rằng vùng nhớ sẽ được cấp phép không cần sử dụng cho một đổi mới đơ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 ghi nhớ biến đối kháng cho dãy vùng nhớ liên tục rất có thể gây ra những vấn đề không giống nhau cho chương trình (memory leak, data corruption, ...).

resizing dynamic arrays

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

Cấp phát lại vùng ghi nhớ mới.(Copy tài liệu từ vùng lưu giữ cũ sáng sủa vùng nhớ mới nếu cần).Giải phóng vùng lưu giữ cũ.Cho bé trỏ trỏ mang lại 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ớ bắt đầu sẽ có showroom khác cùng với vùng lưu giữ đã cấp phép ban đầu, mình cần sử dụng con trỏ p_temp để lưu lại lại tài năng truy cập mang lại vùng lưu giữ ban đầu. Sau thời điểm copy toàn bộ dữ liệu tự vùng nhớ cũ sang trọng vùng ghi nhớ mới, bọn họ nên giải phóng vùng lưu giữ cũ ngay nhằm khỏi lãng phí tài nguyên hệ thông.

Tổng kết

Trong bài học này, bọn họ đã mày mò về nghệ thuật Dynamic memory allocation trong ngôn từ C++. Chuyên môn này giúp chương trình họ ít bị giới hạn dung lượng bộ lưu trữ hơn. Nhưng lân cận đó, bọn họ cần có kỹ năng về quản lý các vùng ghi nhớ trong chương trình. áp dụng kỹ thuật Dynamic memory allocation ko thành thạo là tại sao gây thịnh hành gây ra lỗi memory leak. Vì chưng đó, bọn họ sẽ gồm một bài xích học nói về các lỗi thường gặp gỡ khi thực hiện 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 mặt lại các bạn trong bài xích học tiếp sau 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: Câu Giả Định Tiếng Anh Là Gì ? Câu Giả Định Là Gì

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

www.hutgiammo.com.com

Link Videos khóa học

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