Nhờ giải thích chi tiết về biến toàn cục (1 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

BuiQuangThuan

❆❆❆❆❆❆❆❆❆❆❆❆
Tham gia
17/12/10
Bài viết
2,768
Được thích
3,358
Giới tính
Nam
Xin chào mọi người.
Hiện tại có 1 vài điểm khiến cháu/em vẫn còn lấn cấn và chưa hiểu thực sự về biến toàn cục. Hỏi lên đây nhờ mọi người có thể chỉ dạy thêm.
Để lợi dụng việc không phải duyệt đi duyệt lại dữ liệu nhiều lần với data để tìm ra kết quả dựa vào điều kiện nào đó.
Ví dụ chẳng hạn:
Với data ban đầu (*) Cháu có thể duyệt qua và nạp toàn bộ data cần vào Dictionary với Key và Item mong muốn
Phần Dictionary này sẽ được sử dụng nhiều lần cho nhiều module khác nhau.
Ở 1 data khác hay 1 module khác. Muốn sử dụng Dic ở phần đầu đã nạp mà không cần phải nạp lại data.
Việc nạp lại data chỉ thực hiện khi mà dữ liệu data ban đầu (*) có thêm dữ liệu
Do cháu vẫn đang mơ hồ về việc biến toàn cục. Cảm phiền các chú bác, anh chị nào đang đọc và hiểu ý cháu đang muốn hỏi có thể chỉ rõ cho cháu hiểu hơn về vấn đề này được không ạ
Nếu có thể ví dụ minh họa sơ sơ cho cháu thì càng tốt ạ.
Xin cảm ơn nhiều ạ
 
Xin chào mọi người.
Hiện tại có 1 vài điểm khiến cháu/em vẫn còn lấn cấn và chưa hiểu thực sự về biến toàn cục. Hỏi lên đây nhờ mọi người có thể chỉ dạy thêm.
Để lợi dụng việc không phải duyệt đi duyệt lại dữ liệu nhiều lần với data để tìm ra kết quả dựa vào điều kiện nào đó.
Ví dụ chẳng hạn:
Với data ban đầu (*) Cháu có thể duyệt qua và nạp toàn bộ data cần vào Dictionary với Key và Item mong muốn
Phần Dictionary này sẽ được sử dụng nhiều lần cho nhiều module khác nhau.
Ở 1 data khác hay 1 module khác. Muốn sử dụng Dic ở phần đầu đã nạp mà không cần phải nạp lại data.
Việc nạp lại data chỉ thực hiện khi mà dữ liệu data ban đầu (*) có thêm dữ liệu
Do cháu vẫn đang mơ hồ về việc biến toàn cục. Cảm phiền các chú bác, anh chị nào đang đọc và hiểu ý cháu đang muốn hỏi có thể chỉ rõ cho cháu hiểu hơn về vấn đề này được không ạ
Nếu có thể ví dụ minh họa sơ sơ cho cháu thì càng tốt ạ.
Xin cảm ơn nhiều ạ
Biến Dict mà để toàn cục nhưng dữ liệu nguồn lại có thể thay đổi thì cũng khá bất tiện. Phải kiểm soát được khi nào dữ liệu nguồn thay đổi (ví dụ như worksheet_change) thì lại phải nạp lại dict. Làm kiểu này có khi phải tính toán nhiều hơn kiểu nạp dict trong mỗi lần chạy code.
Còn biến toàn cục thì khai báo public để đầu module là được
 
Xin chào mọi người.
Hiện tại có 1 vài điểm khiến cháu/em vẫn còn lấn cấn và chưa hiểu thực sự về biến toàn cục. Hỏi lên đây nhờ mọi người có thể chỉ dạy thêm.
Để lợi dụng việc không phải duyệt đi duyệt lại dữ liệu nhiều lần với data để tìm ra kết quả dựa vào điều kiện nào đó.
Ví dụ chẳng hạn:
Với data ban đầu (*) Cháu có thể duyệt qua và nạp toàn bộ data cần vào Dictionary với Key và Item mong muốn
Phần Dictionary này sẽ được sử dụng nhiều lần cho nhiều module khác nhau.
Ở 1 data khác hay 1 module khác. Muốn sử dụng Dic ở phần đầu đã nạp mà không cần phải nạp lại data.
Việc nạp lại data chỉ thực hiện khi mà dữ liệu data ban đầu (*) có thêm dữ liệu
Do cháu vẫn đang mơ hồ về việc biến toàn cục. Cảm phiền các chú bác, anh chị nào đang đọc và hiểu ý cháu đang muốn hỏi có thể chỉ rõ cho cháu hiểu hơn về vấn đề này được không ạ
Nếu có thể ví dụ minh họa sơ sơ cho cháu thì càng tốt ạ.
Xin cảm ơn nhiều ạ
Bác Vẹt có viết truyện ngắn về cái này.

Giá mà minh họa như 7 viên ngọc rồng thì tốt biết mấy. --=0 --=0 --=0
 
Để hiểu biến "toàn cục", cần phải có 2 khái niệm. Thứ nhất là về bộ nhớ, và thứ hai là sự "nhìn thấy".
Nói cách khác, biến được chứa trong góc nào của bộ nhớ, và làm cách nào để truy cập biến (điển hình, bị che khuất tức là không nhìn thấy, mà không thấy thì không truy cập được)

Cách xác định toàn cục/biến nội:
Biến toàn cục có thể được khai báo ở bất cứ module nào (trừ class module). Lúc khai báo, cần nhớ rõ rằng biến toàn cục phải được khai báo trước bất cứ lệnh Sub hay Function nào trong module.
Biến nội được khai báo sau lệnh Sub hay Function. Tất cả các biến nội khai báo giữa 2 dòng Sub/Function và EndSub/Function hoàn toàn là của riêng Sub/Func ấy.

Bộ nhớ (life):
VBA chia bộ nhớ ra làm hai phần, phần bộ nhớ ụ (heap memory) và phần bộ nhớ ngăn xếp (stack memory). Tạm thời ở bài này không giải thích tại sao có những tên quái lạ này.
Phần bộ nhớ ụ là phần lâu dài (permanent), thuộc về chung cả Project. Biến chứa trong phần này sẽ giữ trị của chúng cho đến khi bị dọn rác, hoặc cả project kết thúc (hết chạy). Biến toàn cục được VBA chứa trong phần này, vì vậy chúng được khởi trị chỉ một lần (lúc khai báo). Sau đó, chúng tùy thuộc vào chỗ nào hoặc lúc nào gán trị khác.
Phần bộ nhớ ngăn xếp thuộc về function/sub. Biến chứa trong phần này sẽ bị hủy khi function/sub kết thúc (Exit/End Sub/Function). Biến nội được chứa trong phần này, vì vậy chúng được khởi trị với mỗi lượt gọi của sub/func, và xóa trị khi thoát sub/func.
Nói cách khác, biến toàn cục được giữ suôt khoảng thời gian chạy prooject. Biến nội chỉ được giữ trong thời gian chạy sub/func

Khả năng "được nhìn thấy" (visibility):
Biến toàn cục hiển hiện khắp Project. Muốn giấu nó không cho module khác thấy thì khai nó là Private. Từ khóa Private không hề ảnh hưởng tính chất "toàn cục" của biến. Private chỉ là một lệnh dẫn trình dịch, bảo trình dịch rằng "các món này chỉ thấy được trong module này, các modules khác sẽ không nhìn thấy". Nói cách khác, nó vẫn nằm chình ình đó, nhưng module khác truy cập vào sẽ bị ẩy ra (lỗi).
Biến nội thì không cần nói thêm. Của ai nấy xài, sub/func khác không được rớ vào (thực ra thì có thấy được đâu mà "rớ"). Lưu ý rằng dối với sub/func đệ quy thì mỗi lượt gọi tính là một sub/func khác.
Nếu một biến được truy cập đến trong một sub/func mà không được khai báo thì VBA sẽ lần lượt xét:
- Xét tất cả modules để tìm xem biến ấy có được khai báo là toàn cục hay không. Lưu ý rằng nếu không phải module hiện tại thì nó phải được khai Public, VBA mới cho nhìn thấy.
- Nếu không "nhìn thấy" biến toàn cục nào cả thì VBA mặc định đây là biến nội.
Đó là lý do chính mà người ta khuyên dùng Option Explicit. (Không hẳn là việc "tường minh", tránh gõ sai chính tả...)
Nếu tôi đang đọc code mà thấy một biến a được truy cập, không thấy khai báo thì tôi nhớ lại đầu module có thấy câu "Option Excplcit"? Nếu có thì tôi biết chắc a có được khai báo đâu đó, và là toàn cục. Nếu không có thì tôi phải đi tìm xem a có được khai báo Public ở đâu chăng trước khi kết luận nó là toàn cục hay biến nội.

Khi một biến được khai báo nội thì bên trong sub/func ấy, nó sẽ che biến toàn cục trùng tên. Nói cách khá, nếu code của bạn có mọt biến toàn cục a, nhưng bên trong sub xx bạn lại khai báo a (biến nội) thì mội truy cập đến a trong sub xx sẽ là a biến nội, khong phải a toàn cục.

Xong phần lý thuyết, thực hành thì rất dễ:

Vấn đề của bạn thì hiển nhiên là nên khai cái đít sần kia là toàn cục, đỡ phải dựng đối tượng và nạp dữ liệu mỗi lần truy cập.
Cấu trúc thường dùng là

Public DIC As Object
-------------------------- (VBA tự động vẽ một lằn gạch phân biệt giữa khai báo biến toàn cục và sub/func)

Sub KhoiDIC()
Set DIC = CreateObject.....
' đọc data và nạp vào DIC ở đây
End Sub

Sub LamViecA() ' cùng module
If DIC Is Nothing Then KhoiDIC
' làm việc ở đây
End Sub

Sub LamViecB() ' module khác
If DIC Is Nothing Then KhoiDIC
' làm việc ở đây
End Sub

Với cấu trúc code này, DIC là một biến toàn cục, được sử dụng triệt để, mọi sub/func trong module hiện tại hay modules khác đều có thể truy cập và update nó. Vì bạn muốn nhiều modules thấy nó cho nên dùng từ khóa Public. Bình thường tôi sẽ dùng từ khóa Private để tránh rắc rối bằng cách che không cbo các modules khác truy cập nó.
Trong bất cứ code nào cần đến cái Dic này, bạn thêm dòng code xét xem nó đã được nạp dữ liệu chưa, nếu chưa thì dựng đối tượng và tùy tiện nạp dữ liệu. Lưu ý là nếu dữ liêu có thể thay đổi thì code LamViec cũng phải cập nhật nó.
Đại khái là vậy. Bạn làm quen rồi sẽ biết cách sửa đổi cho hạp ý mình, uyển chuyển theo từng hoàn cảnh.
 
Để hiểu biến "toàn cục", cần phải có 2 khái niệm. Thứ nhất là về bộ nhớ, và thứ hai là sự "nhìn thấy".
Nói cách khác, biến được chứa trong góc nào của bộ nhớ, và làm cách nào để truy cập biến (điển hình, bị che khuất tức là không nhìn thấy, mà không thấy thì không truy cập được)

Cách xác định toàn cục/biến nội:
Biến toàn cục có thể được khai báo ở bất cứ module nào (trừ class module). Lúc khai báo, cần nhớ rõ rằng biến toàn cục phải được khai báo trước bất cứ lệnh Sub hay Function nào trong module.
Biến nội được khai báo sau lệnh Sub hay Function. Tất cả các biến nội khai báo giữa 2 dòng Sub/Function và EndSub/Function hoàn toàn là của riêng Sub/Func ấy.

Bộ nhớ (life):
VBA chia bộ nhớ ra làm hai phần, phần bộ nhớ ụ (heap memory) và phần bộ nhớ ngăn xếp (stack memory). Tạm thời ở bài này không giải thích tại sao có những tên quái lạ này.
Phần bộ nhớ ụ là phần lâu dài (permanent), thuộc về chung cả Project. Biến chứa trong phần này sẽ giữ trị của chúng cho đến khi bị dọn rác, hoặc cả project kết thúc (hết chạy). Biến toàn cục được VBA chứa trong phần này, vì vậy chúng được khởi trị chỉ một lần (lúc khai báo). Sau đó, chúng tùy thuộc vào chỗ nào hoặc lúc nào gán trị khác.
Phần bộ nhớ ngăn xếp thuộc về function/sub. Biến chứa trong phần này sẽ bị hủy khi function/sub kết thúc (Exit/End Sub/Function). Biến nội được chứa trong phần này, vì vậy chúng được khởi trị với mỗi lượt gọi của sub/func, và xóa trị khi thoát sub/func.
Nói cách khác, biến toàn cục được giữ suôt khoảng thời gian chạy prooject. Biến nội chỉ được giữ trong thời gian chạy sub/func

Khả năng "được nhìn thấy" (visibility):
Biến toàn cục hiển hiện khắp Project. Muốn giấu nó không cho module khác thấy thì khai nó là Private. Từ khóa Private không hề ảnh hưởng tính chất "toàn cục" của biến. Private chỉ là một lệnh dẫn trình dịch, bảo trình dịch rằng "các món này chỉ thấy được trong module này, các modules khác sẽ không nhìn thấy". Nói cách khác, nó vẫn nằm chình ình đó, nhưng module khác truy cập vào sẽ bị ẩy ra (lỗi).
Biến nội thì không cần nói thêm. Của ai nấy xài, sub/func khác không được rớ vào (thực ra thì có thấy được đâu mà "rớ"). Lưu ý rằng dối với sub/func đệ quy thì mỗi lượt gọi tính là một sub/func khác.
Nếu một biến được truy cập đến trong một sub/func mà không được khai báo thì VBA sẽ lần lượt xét:
- Xét tất cả modules để tìm xem biến ấy có được khai báo là toàn cục hay không. Lưu ý rằng nếu không phải module hiện tại thì nó phải được khai Public, VBA mới cho nhìn thấy.
- Nếu không "nhìn thấy" biến toàn cục nào cả thì VBA mặc định đây là biến nội.
Đó là lý do chính mà người ta khuyên dùng Option Explicit. (Không hẳn là việc "tường minh", tránh gõ sai chính tả...)
Nếu tôi đang đọc code mà thấy một biến a được truy cập, không thấy khai báo thì tôi nhớ lại đầu module có thấy câu "Option Excplcit"? Nếu có thì tôi biết chắc a có được khai báo đâu đó, và là toàn cục. Nếu không có thì tôi phải đi tìm xem a có được khai báo Public ở đâu chăng trước khi kết luận nó là toàn cục hay biến nội.

Khi một biến được khai báo nội thì bên trong sub/func ấy, nó sẽ che biến toàn cục trùng tên. Nói cách khá, nếu code của bạn có mọt biến toàn cục a, nhưng bên trong sub xx bạn lại khai báo a (biến nội) thì mội truy cập đến a trong sub xx sẽ là a biến nội, khong phải a toàn cục.

Xong phần lý thuyết, thực hành thì rất dễ:

Vấn đề của bạn thì hiển nhiên là nên khai cái đít sần kia là toàn cục, đỡ phải dựng đối tượng và nạp dữ liệu mỗi lần truy cập.
Cấu trúc thường dùng là

Public DIC As Object
-------------------------- (VBA tự động vẽ một lằn gạch phân biệt giữa khai báo biến toàn cục và sub/func)

Sub KhoiDIC()
Set DIC = CreateObject.....
' đọc data và nạp vào DIC ở đây
End Sub

Sub LamViecA() ' cùng module
If DIC Is Nothing Then KhoiDIC
' làm việc ở đây
End Sub

Sub LamViecB() ' module khác
If DIC Is Nothing Then KhoiDIC
' làm việc ở đây
End Sub

Với cấu trúc code này, DIC là một biến toàn cục, được sử dụng triệt để, mọi sub/func trong module hiện tại hay modules khác đều có thể truy cập và update nó. Vì bạn muốn nhiều modules thấy nó cho nên dùng từ khóa Public. Bình thường tôi sẽ dùng từ khóa Private để tránh rắc rối bằng cách che không cbo các modules khác truy cập nó.
Trong bất cứ code nào cần đến cái Dic này, bạn thêm dòng code xét xem nó đã được nạp dữ liệu chưa, nếu chưa thì dựng đối tượng và tùy tiện nạp dữ liệu. Lưu ý là nếu dữ liêu có thể thay đổi thì code LamViec cũng phải cập nhật nó.
Đại khái là vậy. Bạn làm quen rồi sẽ biết cách sửa đổi cho hạp ý mình, uyển chuyển theo từng hoàn cảnh.
Xin cảm ơn anh, bài viết mạch lạc và rất dễ hiểu.
 
@VetMini cảm ơn chú nhiều. Cháu sẽ đọc kỹ bài viết để có thể ứng dụng cho công việc cá nhân của bản thân. Và hiểu đúng những gì còn đang mơ hồ
Bài đã được tự động gộp:

Biến Dict mà để toàn cục nhưng dữ liệu nguồn lại có thể thay đổi thì cũng khá bất tiện. Phải kiểm soát được khi nào dữ liệu nguồn thay đổi (ví dụ như worksheet_change) thì lại phải nạp lại dict. Làm kiểu này có khi phải tính toán nhiều hơn kiểu nạp dict trong mỗi lần chạy code.
Còn biến toàn cục thì khai báo public để đầu module là được
Giả sử dữ liệu nguồn thay đổi. Dựa vào target để nạp thêm vào chứ ko phải chạy lại từ đầu. Liệu có bị sót không nhỉ anh gì ơi
 
Lần chỉnh sửa cuối:
Lúc viết bài trên, tôi cố tình bỏ qua biến Static cho giản dị.

Nếu bạn nào thắc mắc, cứ tạm hiểu rằng biến Static là đứa con lai giữa toàn cục và biến nội.

Về cách khai báo, biến Static được khai báo bên trong Sub/Func như biến nội.

Về "nhìn thấy" thì nó in hệt như biến nội. Tức là khai báo bên trong Sub/Func nào thì chỉ Sub/Func ấy biết nó thôi.

Về bộ nhớ thì nó in hệt như biến toàn cục, tức là nó được chứa trong bộ nhớ ụ (heap). Chỉ khởi trị một lần duy nhất, và từ đó giữ lấy bất cứ trị nào được gán. Khi Sub/Func thoát thì nó cũng không bị hủy. Khi Sub/Func được gọi lần kế tiếp thì nó mang trị được gán lần trước. (khác với biến nội khởi trị lại mỗi lần Sub/Func được gọi)
 
Giả sử dữ liệu nguồn thay đổi. Dựa vào target để nạp thêm vào chứ ko phải chạy lại từ đầu. Liệu có bị sót không nhỉ anh gì ơi
Cũng có thể là nạp thêm, mà cũng có thể là bớt đi (xóa bớt dữ liệu chẳng hạn). Trên bảng tính họ xóa dòng trùng lặp. Trong Dic không tính kỹ lại xóa luôn Key thì mệt, mà muốn chính xác thì phải tính toán nhiều. Chi bằng nạp lại Dic, mình hay chọn phương án như vậy, nhanh hay chậm 1,2 giây không quá quan trọng bằng việc có thể bị sai sót, nhầm lẫn
 
Theo cá nhân, thì biến nào cần phải dùng toàn cục cho lợi thế để không phải khai báo nhiều lần nạp lại nhiều lần trong các Sub hay Function | hoặc các hằng số thì em mới dùng. Còn lại thì em toàn nạp và giải phóng.
Em chưa đến trình độ phải tiết kiệm tài nguyên nên em cứ theo nguyên tắc trên thôi.
 
@VetMini cảm ơn chú nhiều. Cháu sẽ đọc kỹ bài viết để có thể ứng dụng cho công việc cá nhân của bản thân. Và hiểu đúng những gì còn đang mơ hồ
Bài đã được tự động gộp:


Giả sử dữ liệu nguồn thay đổi. Dựa vào target để nạp thêm vào chứ ko phải chạy lại từ đầu. Liệu có bị sót không nhỉ anh gì ơi
Dictionary nếu lưu trữ dữ liệu nhiều như tự điển tiếng Việt sẽ khá nặng, mỗi lần tra 1 từ phải chạy ra nhà sách rinh tự điển về sẽ tốn nhiều thời gian và công sức, chỉ nên rinh quyển từ điển về 1 lần và dùng nhiều lần, khi có dịp lên nhà sách thì chịu khó cập nhật tự điển mới
Dữ liệu nạp vào Dic nên lưu ở 1 sheet riêng, và tạo sự kiện nạp Dic
Mã:
Private Sub Worksheet_Deactivate()
  'Nap du lieu vào dictionary
End Sub
Tùy tình huống có thể xét điều kiện để nạp dic lúc đó sẽ giảm số lần nạp dic rất nhiều
 
...
Em chưa đến trình độ phải tiết kiệm tài nguyên nên em cứ theo nguyên tắc trên thôi.
Ở GPE này thowngf là viết code chạy tuồn tuột. Rất ít truonwgf hợp sub này gọi sub kia sub kia gọi sub nọ lằng nhằng dây dưa rễ má. Vì vậy chuyện "tiết kiệm tài nguyên" theo ý bạn gần như không cần thết.

Chú:
Tài nguyên bộ nhớ: khi hàm A gọi hàm B thì có một đống biến được đẩy vào ngăn xếp. Khi B gọi C thì lại thêm một đống nữa. Vài lần như thế, nếu mỗi hàm có một vài mảng lớn thì năng xếp có thể hết chỗ.
Tài nguyên khác: hàm A giữ tài nguyên X, B cũng cần X. Nếu có một cái gì chung để quản lý X thì A và B không phải giành nhau.
 
Đây là tình trạng lười vận động, đa số GPEr gặp phải...
Sai. Khai báo biến toàn cục (cụ thể là Dict) thì trên GPE rất ít người.
Cụ thể trường hợp của tôi:
- 1 Dict là danh sách cửa hàng, 1 Dict là danh sách mặt hàng, 1 Dict là danh sách khu vực, 1 Dict là danh sách nhân viên, ... tổng cộng 8 Dict (ở 2 file master data).
- 6, 7 module viết riêng, mỗi module sử dụng 1 sheet dữ liệu chứa điều kiện, mỗi module cho kết quả riêng. Và tất cả các module đều dùng đến cả 2 Dict trên.
- Có tùy chọn chạy riêng từng module và chạy 1 lần tuần tự các modules cho người dùng có máy mạnh yếu khác nhau, hoặc chỉ cần chạy riêng lấy 1 kết quả.
- Nạp Dict viết trong 1 module riêng để gọi.
- Không chỉ Dict, biến mảng cũng phải nạp 1 lần và cũng phải khai báo public

Nếu không khai báo Public thì cứ phải mở file, nạp 4 Dict, đóng file 6, 7 lần. Tất nhiên về mặt tốc độ thì không nói (năm mười giây hay nửa phút không là gì cả), nhưng code viết nhẹ nhàng hơn.
GPEr mà bé bo nói không làm tới mức đó, không gọi là lười, mà gọi là không biết các thủ thuật.

Hình dưới đây cho thấy public 8 Dict, 11 mảng dùng trong cả 8, 9 modules


1695114719894.png

1695114602431.png
 
<quote orig=bebo...>
Đây là tình trạng lười vận động, đa số GPEr gặp phải...
<end quote>
Sai. Khai báo biến toàn cục (cụ thể là Dict) thì trên GPE rất ít người.
....
Không hẳn sai. Y nói chuyện về nghĩa đen (đen thui luôn) của đít toàn cục...
Chỉ là y chủ quan. Việc "cục" này tùy thuộc vào bẩm sinh và ăn uống nhiều hơn do vận động.
Việc chủ quan thứ hai là tự dưng cá mè một lứa, gọi mọi người là GPEr thì hơi kém lịch sự.
 
Lần chỉnh sửa cuối:
Nếu không khai báo Public thì cứ phải mở file, nạp 4 Dict, đóng file 6, 7 lần. Tất nhiên về mặt tốc độ thì không nói (năm mười giây hay nửa phút không là gì cả), nhưng code viết nhẹ nhàng hơn.
Cái này là đúng này. Tại trong code cháu cứ phải viết đi viết lại Dic trong khi có khi nó là data chết. Họa chăng mới có sự thay đổi. Cám ơn mọi người, đặc biệt là @VetMini và các chú, anh chị trên diễn dàn đã giải thích cặn kẽ cho cháu hiểu
 
@BuiQuangThuan
Tham khảo thêm ADO Recordset, ứng dụng bạn nhé.

Nạp vào Dictionary mà để đó là một cái "dại dột", Dictionary ngốn hết RAM, thì "out of memory" mà không biết lỗi chi chi.

Trang tính Excel nó là dạng XML, là một dạng cơ sở dữ liệu ADODB bổ trợ, nên rất nhanh. Tiết kiệm.
 
Phán tầm bậy, *.xls cướp đâu ra xml.

----
Em đố bài #13, tại sao không dựng 1 cái dic_0 thôi, rồi toàn bộ các dic_khác set theo dic_0 vừa dựng.
Cái này mới hay đó. Bữa lâu mình có nhắc tới cái này rồi.
 
Em đố bài #13, tại sao không dựng 1 cái dic_0 thôi, rồi toàn bộ các dic_khác set theo dic_0 vừa dựng.
Cái này mới hay đó. Bữa lâu mình có nhắc tới cái này rồi.
Chịu thua. Mình chỉ làm theo thói quen. Thực ra nguyên bộ code đó làm từng phần (từng module), làm 1 phần rồi khách hàng kêu thêm, thêm, thêm. Một lần là thêm 1 vài Dict, nên gộp lại vào 1 thủ tục như vậy.
Vụ hay hay là không biết thật.
 
...
Em đố bài #13, tại sao không dựng 1 cái dic_0 thôi, rồi toàn bộ các dic_khác set theo dic_0 vừa dựng.
Cái này mới hay đó. Bữa lâu mình có nhắc tới cái này rồi.
Không hiểu rõ câu hỏi lắm.

Nếu bạn nói là chỉ dựng Dic_0 (CreateObject...), các cái khác Set theo đấy?
Từ khóa: reference
Khi Set một biến vào một đối tượng, VBA sẽ trỏ biến vào đối tượng ấy. Như vậy, ta chỉ có một đít là thực sự, mấy biến khác chỉ là các tên khác nhau của nó.
Từ khóa thứ hai: kết nối sớm.
Nếu dùng kiểu kết nối sớm (không qua CreateObject...) thì khi biến được sử dụng lần đầu tiên, VBA sẽ dựng đối tượng. Viecj Set qua lại có thể không hiệu quả lắm (tôi phải xem code mới khẳng định được)

Nếu bạn muốn nói tại sao không dùng 1 đít và tọng tất cả mọi thứ vào?
Từ khóa: partition.
Đít sần là công cụ của Script Engine. Tác giả nó chỉ chú ý về tốc độ chứ không về thanh lý dữ liệu hỗn tạp. Vì vậy, đít sần không thể làm công việc "phân khoa".
Ta có thể gộp đủ kiểu key/item vào một đít nhưng hàm truy cập hàng loạt của nó (key collection, item collection) không thể phân biệt các loại khác nhau.

Chú thích: về ADO. Phiên bản cũ của ADO cũng bị vấn đề về resource locking. Đôi khi làm việc trên Excel, nó lock recordset khiến sinh lỗi "out of memory". Mãi về sau này MS mới khắc phục được.
Vả lại, ADO dùng cổ máy Access để đọc file Excel. Chưa hẳn đã là hiệu quả nhất.
Kể từ phiên bản 2016 (download bổ sung vào phiên bản 2010.2013) MS đặt trọng tâm vào Data Model. Chỉ khi sử dụng Data Model thì Excel mới dùng cổ máy gần giống như SQL Server Express, rất hiệu quả.
 
Lần chỉnh sửa cuối:
Em đố bài #13, tại sao không dựng 1 cái dic_0 thôi, rồi toàn bộ các dic_khác set theo dic_0 vừa dựng.
Cái này mới hay đó. Bữa lâu mình có nhắc tới cái này rồi.
Nếu em hiểu đúng ý anh thì cái này nó chỉ trỏ tới cái Dic_0 kia thôi chứ thực chất là 1 dic thôi anh
 
Ta có thể gộp đủ kiểu key/item vào một đít nhưng hàm truy cập hàng loạt của nó (key collection, item collection) không thể phân biệt các loại khác nhau.
Chính vì vậy mà tôi không dùng. Trên GPE tôi thấy anh Hieu_CD có dùng, sau khi tự phân tích thì tôi không học theo:
- Khi cần đếm riêng 1 loại key thì không dùng Dict.Count được
- Khi cần duyệt theo 1 loại key bằng 1 vòng lặp trên Dict thì rõ ràng là dư thừa, phải if iếc để lọc
- Giả sử trong 1 Dict có 10 cửa hàng, 30 nhân viên, 500 mặt hàng, thì khi xét 1 key "cửa hàng A" nào đó có tồn tại hay không thì VBA phải tìm trong 540 keys thay vì tìm trong 10 keys, tốn xăng
 
Chính vì vậy mà tôi không dùng. Trên GPE tôi thấy anh Hieu_CD có dùng, sau khi tự phân tích thì tôi không học theo:
- Khi cần đếm riêng 1 loại key thì không dùng Dict.Count được
- Khi cần duyệt theo 1 loại key bằng 1 vòng lặp trên Dict thì rõ ràng là dư thừa, phải if iếc để lọc
- Giả sử trong 1 Dict có 10 cửa hàng, 30 nhân viên, 500 mặt hàng, thì khi xét 1 key "cửa hàng A" nào đó có tồn tại hay không thì VBA phải tìm trong 540 keys thay vì tìm trong 10 keys, tốn xăng
Code của mình bắt chước các code trên diễn đàn và sui rủi chỉ đọc được các code dùng 1 dic, Trước đây do nhu cầu dùng 2 dic và dùng lệnh
set dic2=dic1
để tạo dic2 nhưng dic2 lại y như dic1 nên nghỉ chỉ được dùng 1 dic :cool: vậy là phải chế dạng phân biệt các loại key để nhét chung vào 1 dic, dùng nhiều thành quen và khi cần mới dùng nhiều dic
 
Cái này là vụ 'clone object'.

Ban đầu có Set object_0 = CreateObject()
* Giả sử có có nhu cầu dùng thêm object_1, object_2 mà ta dùng cách:
Set object_1 = object_0
Set object_2 = object_0

thì mọi THAY ĐỔI của object_0 tiếp sau đó cũng đều áp dụng cho cả object_1, object_2, và mọi thay đổi của object_1 và/hoặc object_2 cũng đều áp dụng cho object_0.
Tức là 3 object_0, object_1, object_2 đều trỏ tới một object như nhau, hay nôm na thực chất có 1 object mà có 3 tên khác nhau để gọi nó.

Nếu muốn 'clone' thực sự thì phải dùng phương thức clone của object đó (nếu nó có). Ví dụ Hashtable, SortedList, ArrayList

* Nếu muốn có 2 object_1, object_2 riêng biệt thì phải dùng cách CreateObject().

-----
Với array thì 'clone' sẽ dễ dàng.
Ví dụ
PHP:
Dim array_1 as Variant, array_2 as variant
Redim array_1(1 to 5)
'Thay vì dùng Redim array_2(1 to 5) thì hoàn toàn có thể dùng  '
array_2 = array_1
'là tạo được bản sao của array_1, mà array_2  ĐỘC LẬP với array_1 '
 
Code của mình bắt chước các code trên diễn đàn và sui rủi chỉ đọc được các code dùng 1 dic, Trước đây do nhu cầu dùng 2 dic và dùng lệnh
set dic2=dic1
để tạo dic2 nhưng dic2 lại y như dic1 nên nghỉ chỉ được dùng 1 dic :cool: vậy là phải chế dạng phân biệt các loại key để nhét chung vào 1 dic, dùng nhiều thành quen và khi cần mới dùng nhiều dic
Trước đây thì như thế đó.

Đến hôm nay, sao chép nhân bản dic sang dic2 Bác @HieuCD đã làm được chưa? Thấy google có đoạn api clone object cũng hay.
 
Cái này là vụ 'clone object'.

Ban đầu có Set object_0 = CreateObject()
* Giả sử có có nhu cầu dùng thêm object_1, object_2 mà ta dùng cách:
Set object_1 = object_0
Set object_2 = object_0
...
Bạn giải thích như vậy chưa rõ.

Lệnh gán (dấu bằng) chỉ giành cho loại biến có thể mang giá trị. Khi ta gán a = b thì VBA sẽ lấy trị của biểu thức b gán vào biểu thức a. Sau khi gán xong, a và b chả còn gì với nhau.

Lệnh gán Set có thể dịch là "đặt". Lệnh này không copy trị mà chỉ copy địa chỉ.
Nói cách khác, trong hầu hết các trường hợp, Set tức là "đặt" tên, hay thêm tên mới cho đối tượng.
Set sh = Sheet1 ' đặt thêm cho Sheet1 tên sh
Set sh1a = sh ' sh1a là tên mới của đối tượng mà sh đang trỏ vào
Set sh1b = Sheet1
' Sheet1 hiện giờ có thêm 3 tên mới: sh, sh1a, sh1b
Set sh1a = Nothing ' bứt tên sh1a ra khỏi đối tượng mà nó đang trỏ vào
' Sheet1 hiện giờ chỉ còn 3 tên: sh, sh1b, Sheet1

Lệnh Set này chỉ giành cho các kiểu cơ cấu dữ liệu phức tạp

Chú thích 1: khi đối tượng không còn tên nào, tức là các tên của nó đều được Set Nothing hoặc Set sang đối tượng khác,, nó sẽ được coi là mồ côi và sẽ được bộ phận dọn rác của VBA hủy.
Trường hợp đối tượng tạm, không tên như
With CreateObject...
...
End With
Thì nó được coi là mồ côi sau khi VBA chạy lệnh End With.

Chú thích 2: từ "clone" trong ngữ cảnh đối tượng có nghĩa là "bản sao", tức là một đối tượng khác với phương thức và thuộc tính in hệt như đối tượng ban đầu. Sau khi "clone" xong thì hai đối tượng này hoàn toàn độc lập nhau. Khi thay đổi thằng này không ảnh hưởng thằng kia. (nói đơn giản thôi, chứ loại đối tượng chứa đối tương là chuyện khác. Lúc ấy phải phân biệt clone sâu hay cạn)
 
Lần chỉnh sửa cuối:
Với array thì 'clone' sẽ dễ dàng.
'Thay vì dùng Redim array_2(1 to 5) thì hoàn toàn có thể dùng
array_2 = array_1
là tạo được bản sao của array_1, mà array_2 ĐỘC LẬP với array_1
Cuộc đời không dễ dàng đến thế, ở đâu mà trong 1 bộ code có 2 mảng kết quả cùng số lượng cột (mảng kết quả mới cần Redim, chứ mảng đầu vào đâu cần redim) huống hồ nhiều mảng.
Còn mảng đầu vào thì số lượng cột lại càng mênh mông ...

Lại có trường hợp bảng dữ liệu có 16 cột, tôi cần cột 2, 3, 10, 14, 15,16. Tôi sẽ tạo 3 mảng đầu vào ( mảng 2 cột, mảng 1 cột và mảng thứ ba 3 cột), chứ không lấy 1 mảng 16 cột to đùng
Lại có kiểu lưu trữ dữ liệu không chuẩn vừa ngang vừa dọc, 1 bảng trên sheet phải chia làm 2 khu vực: khu vực 1 có 10 cột lấy 2, khu vực 2 có 500 cột lấy hết. Làm sao mà áp dụng Arr2 = Arr1
 
Cái này là vụ 'clone object'.

Ban đầu có Set object_0 = CreateObject()
* Giả sử có có nhu cầu dùng thêm object_1, object_2 mà ta dùng cách:
Set object_1 = object_0
Set object_2 = object_0

thì mọi THAY ĐỔI của object_0 tiếp sau đó cũng đều áp dụng cho cả object_1, object_2, và mọi thay đổi của object_1 và/hoặc object_2 cũng đều áp dụng cho object_0.
Tức là 3 object_0, object_1, object_2 đều trỏ tới một object như nhau, hay nôm na thực chất có 1 object mà có 3 tên khác nhau để gọi nó.

Nếu muốn 'clone' thực sự thì phải dùng phương thức clone của object đó (nếu nó có). Ví dụ Hashtable, SortedList, ArrayList
Đối với Dict, mỗi Dict mong muốn là 1 list các key của những đối tượng khác nhau như Dict phòng ban, Dict nhân viên, Dict chức vụ.
- Tạo bằng cách 'Set object_1 = object_0' thì cả 2 cùng chứa 1 đối tượng là phòng ban à? Dict thứ 2 nạp nhân viên thì cả 2 Dict chứa cùng lúc phòng ban và nhân viên?
- Tạo bằng cách 'clone' (giả sử tìm ra cách clone), thì Dict bản sao có còn chứa keys và items từ Dict gốc không? Nếu có thì phải clear trước rồi mới nạp key mới. Vậy khốn khổ clone làm gì.
 
Cuộc đời không dễ dàng đến thế, ở đâu mà trong 1 bộ code có 2 mảng kết quả cùng số lượng cột (mảng kết quả mới cần Redim, chứ mảng đầu vào đâu cần redim) huống hồ nhiều mảng.
Còn mảng đầu vào thì số lượng cột lại càng mênh mông ...

Lại có trường hợp bảng dữ liệu có 16 cột, tôi cần cột 2, 3, 10, 14, 15,16. Tôi sẽ tạo 3 mảng đầu vào ( mảng 2 cột, mảng 1 cột và mảng thứ ba 3 cột), chứ không lấy 1 mảng 16 cột to đùng
Lại có kiểu lưu trữ dữ liệu không chuẩn vừa ngang vừa dọc, 1 bảng trên sheet phải chia làm 2 khu vực: khu vực 1 có 10 cột lấy 2, khu vực 2 có 500 cột lấy hết. Làm sao mà áp dụng Arr2 = Arr1
Những bài toán phức tạp cần nhiều mảng trung gian giống nhau lưu tạm kết quả xử lý lúc đó không cần redim nhiều lần chỉ cần gán mảng đã khai báo. Ví dụ
redim a(1 to 3)' mảng a lưu 3 giá trị tính toán trung gian
redim arr(1 to sRow)
for i=1 to sRow
arr(i)=a
next i
 
ở đâu mà trong 1 bộ code có 2 mảng kết quả cùng số lượng cột

Có thể anh ít/ không gặp trường hợp đó thật.



Đối với Dict, mỗi Dict mong muốn là 1 list các key của những đối tượng khác nhau như Dict phòng ban, Dict nhân viên, Dict chức vụ.
- Tạo bằng cách 'Set object_1 = object_0' thì cả 2 cùng chứa 1 đối tượng là phòng ban à? Dict thứ 2 nạp nhân viên thì cả 2 Dict chứa cùng lúc phòng ban và nhân viên?
- Tạo bằng cách 'clone' (giả sử tìm ra cách clone), thì Dict bản sao có còn chứa keys và items từ Dict gốc không? Nếu có thì phải clear trước rồi mới nạp key mới. Vậy khốn khổ clone làm gì.

Anh chưa hiểu được vấn đề chỗ này rồi.
 
Cháu xin cảm ơn @VetMini , @ptm0412 ,@befaint ,@HieuCD và mọi người đã thảo luận.
Thực ra cháu cứ lấn cấn việc phải duyệt đi duyệt lại nhiều lần.
Lý do rằng cháu có sử dụng sự kiện Exit của combobox để đưa data vào listbox
Do data khoảng 10000 dòng. Thực sự chưa hiểu rằng việc duyệt qua 10000 dòng để nhặt dữ liệu không biết nó có phát sinh làm ì ạch không?
Cũng thử bằng việc duyệt qua data, hoặc autofilter rồi đưa qua sheet tạm nào đó xong gán kết quả vào listbox hoặc nạp key vào dic theo item để truy xuất (chính vì vậy nên cháu mới hỏi tới việc sử dụng biến toàn cục)
Mà không cảm nhận được sự khác biệt lắm.
Cho cháu hỏi mọi người. Với những cách làm trên. Thì cái nào tối ưu hơn và nhược điểm của nó là gì được không ạ.
 
Cháu xin cảm ơn @VetMini , @ptm0412 ,@befaint ,@HieuCD và mọi người đã thảo luận.
Thực ra cháu cứ lấn cấn việc phải duyệt đi duyệt lại nhiều lần.
Lý do rằng cháu có sử dụng sự kiện Exit của combobox để đưa data vào listbox
Do data khoảng 10000 dòng. Thực sự chưa hiểu rằng việc duyệt qua 10000 dòng để nhặt dữ liệu không biết nó có phát sinh làm ì ạch không?
Cũng thử bằng việc duyệt qua data, hoặc autofilter rồi đưa qua sheet tạm nào đó xong gán kết quả vào listbox hoặc nạp key vào dic theo item để truy xuất (chính vì vậy nên cháu mới hỏi tới việc sử dụng biến toàn cục)
Mà không cảm nhận được sự khác biệt lắm.
Cho cháu hỏi mọi người. Với những cách làm trên. Thì cái nào tối ưu hơn và nhược điểm của nó là gì được không ạ.
Theo mình thì cứ nạp lại cho khỏe. Duyệt theo kiểu nạp lấy Key để gán vào listbox thì cũng khá nhanh. Code kiểu không cần kiểm tra sự tồn tại của key thì cũng gọn

Dic(Key)=Empty
 
Theo mình thì cứ nạp lại cho khỏe. Duyệt theo kiểu nạp lấy Key để gán vào listbox thì cũng khá nhanh. Code kiểu không cần kiểm tra sự tồn tại của key thì cũng gọn

Dic(Key)=Empty
Trong bài trả lời đầu tiên, đại khái tôi có dặn "cái này phải tập làm nhiều để lấy kinh nghiệm áp dụng những giải pháp thích ứng nhất cho những trường hợp khác nhau."
 
Anh chưa hiểu được vấn đề chỗ này rồi.
Giờ bỗng nhiên lại hiểu. CreateObject 1 Dict mới toanh, nó sẽ rỗng và để dành, clone Dict rỗng này thành những Dict sau đó. May quá, DIct không clone được nên cứ thế mà CreateObject,
Cũng không mất bao nhiêu thời gian: viết 1 dòng CreateObject, copy thêm 7 lần thành 8 dòng, mỗi dòng chỉ sửa tên
 
Giờ bỗng nhiên lại hiểu. CreateObject 1 Dict mới toanh, nó sẽ rỗng và để dành, clone Dict rỗng này thành những Dict sau đó. May quá, DIct không clone được nên cứ thế mà CreateObject,
Cũng không mất bao nhiêu thời gian: viết 1 dòng CreateObject, copy thêm 7 lần thành 8 dòng, mỗi dòng chỉ sửa tên

Chỗ này đang nói Object chung, không riêng gì Dictionary đâu anh.

Có nhiều trường hợp cần clone object đó anh. Không dưng tự nhiên người ta tạo phương thức object.clone

Ở đây cần chú ý:
(1) Chỗ dùng CreateObject và dấu =, nắm chắc để vận dụng.
(2) Tại sao lại xảy ra (1). Cái mới dừng ở mức độ công nhận, còn chưa rõ tại sao.
 
...Có nhiều trường hợp cần clone object đó anh. Không dưng tự nhiên người ta tạo phương thức object.clone
...
Clone Oject luôn luôn là một vấn đề phức tạp. Phuonwg thức Clone 99% truonwgf hợp là phải tự code lấy, có khi còn phải viết vài phương thức CloneA, CloneB,... cho các cách clone khác nhau.
Như tôi đã trình bày trước đây. Clone có hai dạng, sâu (deep copy) và nông (shallow copy).
Dạng sâu là đối tượng này copy đối tượng kia hoàn toàn từng chi tiết. Nếu đối tượng bên vế phải chứa đối tượng con thì cũng clone luôn đối tượng con. Dạng này rất khó viết phương thức Clone.
Dạng nông là đối tượng này copy đối tượng kia, nhưng các thuộc tính kiểu phức tạp thì chỉ copy con trỏ (lệnh Set). Nếu đối tượng bên vế phải chứa đối tượng con thì cũng chỉ Set thuộc tính, hai đối tượng sẽ cùng dùng chung một đối tượng con. Dạng dễ viết phương thức Clone hơn.
Lưu ý là khong phải do dễ hay khó mà người ta chọn cách viết. Cách Clone được thiết kế hoàn toàn theo nhu cầu.
Đối với một số ngôn ngữ như C++, người ta dùng hàm chồng toán tử = để xác định cách clone.
Đối với VBA, không phải là loại ngôn ngữ Hướng Đối Tượng cho nên phải lập phương thức Clone (và có thể phải CloneA. CloneB,...).
Vì clone khá phức tạp nên code này rất cần chú thích (comments) đầy đủ

Chú thích:
Đối với C++ là ngôn ngữ chú trọng tốc độ, clone nông đương nhiên nhanh hơn sâu nhiều.
Đối với ngôn ngữ như VBA, clone sâu thì phải sâu đến cùng.
 

Bài viết mới nhất

Back
Top Bottom