Làm sao để không phải khai báo biến toàn cục trong code này (1 người xem)

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

Quang_Hải

Thành viên gạo cội
Tham gia
21/2/09
Bài viết
6,077
Được thích
8,011
Nghề nghiệp
Làm đủ thứ
Mình có viết đoạn code để lấy tất cả file trong thư mục mẹ và thư mục con.
Nhưng loay hoay mãi không biết làm sao để không phải khai báo biến toàn cục.
Các anh chị xem qua code và vui lòng xin cho ý kiến.
PHP:
Public k As Long, Res()
Sub Main()
Dim strPath As String, fso As Object, chk As Boolean
Set fso = CreateObject("Scripting.FileSystemObject")
   With Application.FileDialog(msoFileDialogFolderPicker)
      chk = .Show
      If Not chk Then Exit Sub
      strPath = .SelectedItems(1)
   End With
   If Len(strPath) Then Call GetAllFiles(strPath, fso)
   If k Then [A5].Resize(k) = Application.Transpose(Res)
   k = 0
End Sub
Function GetAllFiles(ByVal StrFolder As String, ByRef fso As Object)
Dim objFolder As Object, objSubFolder As Object, File
Set objFolder = fso.GetFolder(StrFolder)
For Each objSubFolder In objFolder.SubFolders
   Call GetAllFiles(objSubFolder.path, fso)
   For Each File In objSubFolder.Files
      k = k + 1
      ReDim Preserve Res(1 To k)
      Res(k) = fso.GetBaseName(File)
   Next
Next objSubFolder
End Function
 
Em cũng viết thủ tục lấy list toàn bộ file trong thư mục như thế này (đã post diễn đàn rồi) nhưng cũng phải khai báo 2 biến toàn cục như anh, hóng các bài trả lời tại topic của anh.|||||
 
Upvote 0
Em cũng viết thủ tục lấy list toàn bộ file trong thư mục như thế này (đã post diễn đàn rồi) nhưng cũng phải khai báo 2 biến toàn cục như anh, hóng các bài trả lời tại topic của anh.|||||
Nếu dùng cách khác thì mình có thể, nhưng muốn thử xem dùng fso có xử được hay không.
 
Upvote 0
Chưa kiểm tra code, nhưng Thử dùng cái này xem sao:

Mã:
Sub Main()
    Static k As Long, Res()
    ''...
End Sub
 
Upvote 0
Upvote 0
Thử thêm 2 cái ByRef cho nó nhé:

Mã:
Function HoangTrongNghia([B][COLOR=#ff0000]ByRef [/COLOR]k As Long, [COLOR=#ff0000]ByRef [/COLOR]Res()[/B])
    k = k + 1
    ReDim Preserve Res(1 To k)
    Res(k) = k
End Function

''---------------------------------------------------


Sub Test()
[B]    [COLOR=#ff0000]Static [/COLOR]k As Long, Res()[/B]
    HoangTrongNghia k, Res
    MsgBox k & "/" & UBound(Res)
End Sub

Bấm code Test vài lần xem sao!
 
Upvote 0
[h=2]Làm sao để không phải khai báo biến toàn cục trong code này[/h]

Tôi cũng dùng biến toàn cục nhưng chỉ 1 biến thôi. Tôi dùng Dictionary làm biến toàn cục, mọi thứ nạp vào nó
Tuy nhiên, đã test nhiều lần rồi, FileSystemObject dùng để lấy list file, folder không kham nỗi khi phải lấy vài chục ngàn đến vài trăm ngàn file ---> Nó chậm lết bánh luôn. Chỉ có DOS mới là vô địch trong vụ này
 
Upvote 0
Các bạn lưu ý, với Mảng được khai báo là đối số trong Function hay trong Sub thì có ByRef hay không cũng ngầm hiểu là ByRef nha các bạn, các bạn không thể khai ByVal với mảng được!

Mã:
[COLOR=#000000]Function HoangTrongNghia([/COLOR][B][COLOR=#ff0000]ByRef [/COLOR]k As Long, [COLOR=#ff0000]ByRef [/COLOR]Res()[/B][COLOR=#000000])[/COLOR]

Hoặc:

Mã:
[COLOR=#000000]Function HoangTrongNghia([/COLOR][B][COLOR=#ff0000]ByRef [/COLOR]k As Long, Res()[/B][COLOR=#000000])[/COLOR]

Đều OK.
 
Upvote 0
Mình có viết đoạn code để lấy tất cả file trong thư mục mẹ và thư mục con.
Nhưng loay hoay mãi không biết làm sao để không phải khai báo biến toàn cục.
Các anh chị xem qua code và vui lòng xin cho ý kiến.
PHP:
Public k As Long, Res()
Sub Main()
Dim strPath As String, fso As Object, chk As Boolean
Set fso = CreateObject("Scripting.FileSystemObject")
   With Application.FileDialog(msoFileDialogFolderPicker)
      chk = .Show
      If Not chk Then Exit Sub
      strPath = .SelectedItems(1)
   End With
   If Len(strPath) Then Call GetAllFiles(strPath, fso)
   If k Then [A5].Resize(k) = Application.Transpose(Res)
   k = 0
End Sub
Function GetAllFiles(ByVal StrFolder As String, ByRef fso As Object)
Dim objFolder As Object, objSubFolder As Object, File
Set objFolder = fso.GetFolder(StrFolder)
For Each objSubFolder In objFolder.SubFolders
   Call GetAllFiles(objSubFolder.path, fso)
   For Each File In objSubFolder.Files
      k = k + 1
      ReDim Preserve Res(1 To k)
      Res(k) = fso.GetBaseName(File)
   Next
Next objSubFolder
End Function

For Each File In objSubFolder.Files


Nhìn vào chỗ đỏ đỏ thì thấy bạn chỉ liệt kê những tập tin có trong những thư mục con. Như vậy bạn mất những tập tin có trong thư mục được lựa chọn. Vd. trong thư mục hichic có 100 tập tin từ f1.txt tới f100.txt, và k thư mục con (k >= 0) thì sau khi chạy code của bạn và chọn thư mục hichic thì bạn không có các kết quả từ f1.txt tới f100.txt

Code của bạn, tôi chỉ "sắp xếp" lại

Mã:
Sub Main()
Dim fso As Object, res() As String
    Set fso = CreateObject("Scripting.FileSystemObject")
    With Application.FileDialog(msoFileDialogFolderPicker)
        If .Show Then
            ReDim res(1 To 1)
            GetAllFiles .SelectedItems(1), fso, res
            If UBound(res) > 1 Then [A5].Resize(UBound(res) - 1) = Application.Transpose(res)
        End If
    End With
End Sub

Function GetAllFiles(ByVal StrFolder As String, fso As Object, res() As String)
Dim objFolder As Object, objSubFolder As Object, File
    Set objFolder = fso.GetFolder(StrFolder)
    For Each objSubFolder In objFolder.SubFolders
        Call GetAllFiles(objSubFolder.Path, fso, res)
        For Each File In objSubFolder.Files
            res(UBound(res)) = fso.GetBaseName(File)
            ReDim Preserve res(1 To UBound(res) + 1)
        Next
    Next objSubFolder
End Function

Để liệt kê hết tôi đề nghị - mới test 1 lần, không chịu trách nhiệm nếu sảy ra thiệt hại ;-)

Mã:
Function GetAllFiles(ByVal StrFolder As String, fso As Object, res() As String)
Dim objFolder As Object, objSubFolder As Object, File
    Set objFolder = fso.GetFolder(StrFolder)
    For Each File In objFolder.Files
        res(UBound(res)) = fso.GetBaseName(File)
        ReDim Preserve res(1 To UBound(res) + 1)
    Next
    For Each objSubFolder In objFolder.SubFolders
        GetAllFiles objSubFolder.Path, fso, res
    Next objSubFolder
End Function
 
Lần chỉnh sửa cuối:
Upvote 0
[/COLOR][/COLOR]
Nhìn vào chỗ đỏ đỏ thì thấy bạn chỉ liệt kê những tập tin có trong những thư mục con. Như vậy bạn mất những tập tin có trong thư mục được lựa chọn. Vd. trong thư mục hichic có 100 tập tin từ f1.txt tới f100.txt, và k thư mục con (k >= 0) thì sau khi chạy code của bạn và chọn thư mục hichic thì bạn không có các kết quả từ f1.txt tới f100.txt

Code của bạn, tôi chỉ "sắp xếp" lại

Mã:
Sub Main()
Dim fso As Object, res() As String
    Set fso = CreateObject("Scripting.FileSystemObject")
    With Application.FileDialog(msoFileDialogFolderPicker)
        If .Show Then
            ReDim res(1 To 1)
            GetAllFiles .SelectedItems(1), fso, res
            If UBound(res) > 1 Then [A5].Resize(UBound(res) - 1) = Application.Transpose(res)
        End If
    End With
End Sub

Function GetAllFiles(ByVal StrFolder As String, fso As Object, res() As String)
Dim objFolder As Object, objSubFolder As Object, File
    Set objFolder = fso.GetFolder(StrFolder)
    For Each objSubFolder In objFolder.SubFolders
        Call GetAllFiles(objSubFolder.Path, fso, res)
        For Each File In objSubFolder.Files
            res(UBound(res)) = fso.GetBaseName(File)
            ReDim Preserve res(1 To UBound(res) + 1)
        Next
    Next objSubFolder
End Function

Để liệt kê hết tôi đề nghị - mới test 1 lần, không chịu trách nhiệm nếu sảy ra thiệt hại ;-)

Mã:
Function GetAllFiles(ByVal StrFolder As String, fso As Object, res() As String)
Dim objFolder As Object, objSubFolder As Object, File
    Set objFolder = fso.GetFolder(StrFolder)
    For Each File In objFolder.Files
        res(UBound(res)) = fso.GetBaseName(File)
        ReDim Preserve res(1 To UBound(res) + 1)
    Next
    For Each objSubFolder In objFolder.SubFolders
        GetAllFiles objSubFolder.Path, fso, res
    Next objSubFolder
End Function
Chạy tốt rồi anh. Lại học thêm được 1 cái hay từ anh. Vậy mà em nghĩ nát óc không ra.
 
Upvote 0
Tôi cũng dùng biến toàn cục nhưng chỉ 1 biến thôi. Tôi dùng Dictionary làm biến toàn cục, mọi thứ nạp vào nó
Tuy nhiên, đã test nhiều lần rồi, FileSystemObject dùng để lấy list file, folder không kham nỗi khi phải lấy vài chục ngàn đến vài trăm ngàn file ---> Nó chậm lết bánh luôn. Chỉ có DOS mới là vô địch trong vụ này

Em cũng có học được cách lấy file bằng Dos từ code của anh rồi, đúng là nhanh lắm. Nhưng tại muốn thử sức mình với FileSystemObject xem thế nào. Thật ra nghiên cứu cho thỏa mản thôi chứ có bao giờ xài tới đâu.
 
Upvote 0
Em cũng có học được cách lấy file bằng Dos từ code của anh rồi, đúng là nhanh lắm. Nhưng tại muốn thử sức mình với FileSystemObject xem thế nào. Thật ra nghiên cứu cho thỏa mản thôi chứ có bao giờ xài tới đâu.

Với trình độ của bạn đâu có cần phải "thử sức" với ba cái công cụ của Script.

Nếu tôi là bạn, tôi chú trọng "thử sức" kỹ thuật hàm đệ quy.
(thông thường thì hàm đệ quy tránh dùng biến toàn cục, và biến tĩnh (static) lại càng tránh hơn - tôi chỉ nói tránh thôi, nếu cực chẳng đã thì vẫn phải dùng)
 
Upvote 0
Với trình độ của bạn đâu có cần phải "thử sức" với ba cái công cụ của Script.

Nếu tôi là bạn, tôi chú trọng "thử sức" kỹ thuật hàm đệ quy.
(thông thường thì hàm đệ quy tránh dùng biến toàn cục, và biến tĩnh (static) lại càng tránh hơn - tôi chỉ nói tránh thôi, nếu cực chẳng đã thì vẫn phải dùng)
Mình có qua trường lớp gì đâu, toàn là lấy code của anh em trên diễn đàn về nghiên cứu thuật toán, rồi thêm bớt sửa qua sửa lại. Sau 2 năm vọc phá thì chỉ có được chút ít kiến thức căn bản. Giờ anh nói hàm đệ quy gì đó, thật tình mình chẳng biết nó là cái gì, và phải bắt đầu từ đâu.
 
Upvote 0
Đệ quy (recursive) có hai loại:

1. trực tiếp tức là hàm gọi chính nó (code của bạn sử dụng loại này)

2. gián tiếp tức là hàm A gọi B và B gọi A, hoặc A gọi B, B gọi C và C gọi A; chéo cẳng ngỗng tùm lum. loại này khó giàn trời.

Vì loại 2 rất khó nên thường người ta chỉ nói đến loại 1.
Loại này còn chia ra nhiều kiểu nhỏ là trực tuyên, phi tuyến, (còn mấy loại nữa tôi quên mất tên)

Hầu hết các code vòng lặp đều có thể đổi thành hàm đệ quy. Nếu bạn muốn luyện thì cứ đè chỗ đó ra mà luyện.

Mẹo: khi viết code đệ quy thì chú trọng đến điểm thoát. Tức là có một thông số nào đó để hàm quay về không gọi tiếp nữa. Nếu điểm thoát không được chú trọng kỹ, code có thể chạy hoài và crash (khác với vòng lặp, code đệ quy tốn bộ nhớ cho nên chạy một chốc sẽ bị hết bộ nhớ và crash)

Ví dụ hàm tính giai thừa:
Function GT(byVal so As Long) As Long
If so <= 0 then
GT = 1 ' đây là điểm thoát, không gọi tiếp nữa
Else
GT = so * GT(so-1)
End If
End Function
 
Lần chỉnh sửa cuối:
Upvote 0
[/COLOR][/COLOR]
Nhìn vào chỗ đỏ đỏ thì thấy bạn chỉ liệt kê những tập tin có trong những thư mục con. Như vậy bạn mất những tập tin có trong thư mục được lựa chọn. Vd. trong thư mục hichic có 100 tập tin từ f1.txt tới f100.txt, và k thư mục con (k >= 0) thì sau khi chạy code của bạn và chọn thư mục hichic thì bạn không có các kết quả từ f1.txt tới f100.txt

Code của bạn, tôi chỉ "sắp xếp" lại

Mã:
Sub Main()
Dim fso As Object, res() As String
    Set fso = CreateObject("Scripting.FileSystemObject")
    With Application.FileDialog(msoFileDialogFolderPicker)
        If .Show Then
            ReDim res(1 To 1)
            GetAllFiles .SelectedItems(1), fso, res
            If UBound(res) > 1 Then [A5].Resize(UBound(res) - 1) = Application.Transpose(res)
        End If
    End With
End Sub

Function GetAllFiles(ByVal StrFolder As String, fso As Object, res() As String)
Dim objFolder As Object, objSubFolder As Object, File
    Set objFolder = fso.GetFolder(StrFolder)
    For Each objSubFolder In objFolder.SubFolders
        Call GetAllFiles(objSubFolder.Path, fso, res)
        For Each File In objSubFolder.Files
            res(UBound(res)) = fso.GetBaseName(File)
            ReDim Preserve res(1 To UBound(res) + 1)
        Next
    Next objSubFolder
End Function

Để liệt kê hết tôi đề nghị - mới test 1 lần, không chịu trách nhiệm nếu sảy ra thiệt hại ;-)

Mã:
Function GetAllFiles(ByVal StrFolder As String, fso As Object, res() As String)
Dim objFolder As Object, objSubFolder As Object, File
    Set objFolder = fso.GetFolder(StrFolder)
    For Each File In objFolder.Files
        res(UBound(res)) = fso.GetBaseName(File)
        ReDim Preserve res(1 To UBound(res) + 1)
    Next
    For Each objSubFolder In objFolder.SubFolders
        GetAllFiles objSubFolder.Path, fso, res
    Next objSubFolder
End Function
Em xin mượn gió bẻ măng. Vụ chế tác em cũng thuộc hạng ghê thiệt. Nhiều lúc tự phục mình sát đất.
PHP:
Sub Main()
Dim fso As Object, Dic As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    Set Dic = CreateObject("scripting.dictionary")
    With Application.FileDialog(msoFileDialogFolderPicker)
        If .Show Then
            GetAllFiles .SelectedItems(1), fso, Dic
            [A5].Resize(Dic.Count) = Application.Transpose(Dic.Keys)
        End If
    End With
End Sub
Function GetAllFiles(ByVal StrFolder As String, fso As Object, Dic As Object)
Dim objFolder As Object, objSubFolder As Object, File As Object
    Set objFolder = fso.GetFolder(StrFolder)
    For Each File In objFolder.Files
        Dic.Item(File.Name) = ""
    Next
    For Each objSubFolder In objFolder.SubFolders
        GetAllFiles objSubFolder.path, fso, Dic
    Next objSubFolder
End Function
 
Upvote 0
Em xin mượn gió bẻ măng. Vụ chế tác em cũng thuộc hạng ghê thiệt. Nhiều lúc tự phục mình sát đất.
PHP:
Sub Main()
Dim fso As Object, Dic As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    Set Dic = CreateObject("scripting.dictionary")
    With Application.FileDialog(msoFileDialogFolderPicker)
        If .Show Then
            GetAllFiles .SelectedItems(1), fso, Dic
            [A5].Resize(Dic.Count) = Application.Transpose(Dic.Keys)
        End If
    End With
End Sub
Function GetAllFiles(ByVal StrFolder As String, fso As Object, Dic As Object)
Dim objFolder As Object, objSubFolder As Object, File As Object
    Set objFolder = fso.GetFolder(StrFolder)
    For Each File In objFolder.Files
        Dic.Item(File.Name) = ""
    Next
    For Each objSubFolder In objFolder.SubFolders
        GetAllFiles objSubFolder.path, fso, Dic
    Next objSubFolder
End Function
code này Nếu mình thích lấy thêm đường dẫn của file nữa thì sữa thế nào anh
thanks Anh
 
Upvote 0
code này Nếu mình thích lấy thêm đường dẫn của file nữa thì sữa thế nào anh
thanks Anh
Sửa lại chỗ này
PHP:
    For Each File In objFolder.Files
        Dic.Item(fso.GetAbsolutePathName(File)) = ""
    Next
Thật ra thì phải nên có đường dẫn mới đúng, nếu không sẽ có thể bị thiếu file nếu có file trùng tên
 
Lần chỉnh sửa cuối:
Upvote 0
Dùng Dictionary ở đây có vẻ lạm phát. Theo tôi thì nên dùng ArrayList. Nếu kết quả muốn theo thứ tự từng lớp (tầng folder) thì cũng dễ sort.
 
Upvote 0
Dùng Dictionary ở đây có vẻ lạm phát. Theo tôi thì nên dùng ArrayList. Nếu kết quả muốn theo thứ tự từng lớp (tầng folder) thì cũng dễ sort.
Dùng ArrayList sẽ có máy không xài được vì thiếu NET hay gì đó. Thật ra chỉ nghiên cứu chơi thôi, chứ nếu phải lấy toàn bộ file thì mình sẽ kết hợp FSO và DOS cho nhanh và cũng đơn giản.
 
Upvote 0
Sửa lại chỗ này
PHP:
    For Each File In objFolder.Files
        Dic.Item(fso.GetAbsolutePathName(File)) = ""
    Next
Thật ra thì phải nên có đường dẫn mới đúng, nếu không sẽ có thể bị thiếu file nếu có file trùng tên

File.Name là để lấy tên file
File.Path là lấy đường dẫn đầy đủ đến file
-----------------------------
Thật ra chỉ nghiên cứu chơi thôi, chứ nếu phải lấy toàn bộ file thì mình sẽ kết hợp FSO và DOS cho nhanh và cũng đơn giản.
Khi ấy FSO chỉ làm công việc đọc file TEXT mà thôi
 
Upvote 0
Bổ sung thêm cho bài viết của bạn VetMini.

Tất nhiên thậm chí một code bình thường nếu không khéo (vd. dùng Do ... Loop) thì sẽ được thực hiện "đến ngày tận thế", và không một RAM nào chịu nổi dù là RAM "khủng".

Còn về đệ qui thì cái mà bạn VetMini nói
code đệ quy tốn bộ nhớ cho nên chạy một chốc sẽ bị hết bộ nhớ và crash

thì bộ nhớ ở đây là bạn VetMini nói về "local heap". Nói rõ thế để khỏi có ai thắc mắc: tôi có RAM khủng thì sao hết được.

"local heap" hay "global heap" chẳng qua là một vùng trong RAM mà system "trích" ra để dùng vào "một số việc". RAM có thể vẫn còn khủng nhưng nếu "độ sâu" đệ qui lớn thì sẽ tràn bộ nhớ "local" heap".

"Độ sâu" đệ qui là gì? Nói nôm na thì là số lần hàm tự gọi, số lần gọi đệ qui. Ta lấy vd. hàm GT của bạn VetMini. Tất nhiên với hàm này thì với thông số so = 13 thì "độ sâu" = 13, chưa thể tràn "local heap" được nhưng đã tràn biến/kiểu biến vì 13! = 6227020800, vượt quá giá trị có thể có của biến Long.

Tại sao "độ sâu" = 12? (hoặc 11 tùy theo cách tính, không quan trọng hơn kém 1, cái quan trọng là bản chất)

Function GT(ByVal n As Long) As Long
If n = 1 Then
GT = 1
Else
GT = n * GT(n - 1)
End If
End Function

Bản chất gọi đệ qui ở đây là: Để tính GT(n) thì trước đó ta phải tính được, có được GT(n-1), vậy ta gọi GT(n-1). Để tính GT(n-1) thì trước đó ta phải tính được, có được GT(n-2), vậy ta gọi GT(n-2). ... Để tính được GT(1) thì ... thì ta không gọi gì nữa vì ta biết GT(1) = 1.
Khi có được GT(1) rồi thì sảy ra một loạt return. GT(1) return và trả về 1. Lúc này GT(2) = 2.GT(1) = 2*1 = 2. Vậy GT(2) return và trả về 2. Lúc này GT(3) = 3*GT(2) = 3*2 = 6.
.... Vậy GT(n-1) return và trả về 1*2*...*(n-1). Lúc này GT(n) = n*GT(n-1) = 1*2*...*n

Nói cách khác để tính GT(n) thì ta "hoãn" tính nó và phải tính GT(n-1). Nhưng để tính GT(n-1) thì ta cũng phải "hoãn" tính nó và phải tính GT(n-2). Có (n-1) lần hoãn như thế vì GT(1) = 1 (không gọi đệ qui). Từ lúc này sẽ sảy ra một loạt return của những lần gọi hàm GT(1), GT(2), ..., GT(n-1)

"Độ sâu" đệ qui là số lần mà ta phải "hoãn" và gọi đệ qui.

Tại sao "độ sâu" lớn có thể dẫn tới tràn "local heap"?

Bộ nhớ "local heap" thường rất nhỏ. Nếu tôi nhớ không lầm thì cỡ vài (vài chục?) KB mà thôi. Mỗi khi gọi đệ qui thì Windows (ta viết 1 lệnh đơn giản nhưng system sau "cánh gà" làm rất nhiều việc): đặt (ghi nhớ) tất cả các thông số lên "local heap" (rõ ràng khi thực hiện vd. GT(12) thì chỗ đỏ đỏ n = 12, khi thực hiện GT(11), ..., GT(2) thì n = 11, ..., 2). Tất cả các biến local cũng được đặt lên "local heap". Ở đây không có biến local nhưng trong trường hợp tổng quát thì hàm có biến local, và vd. có những tính toán trên các biến đó sau dòng gọi đệ qui. Giá trị của những biến đó có thể khác nhau trong mỗi lần gọi đệ qui. Vậy để sau khi gọi đệ qui trở về (return) ta có được những giá trị "đúng" của biến local thì Windows phải đặt chúng lên "local heap".

Nói lại: khi sẩy ra gọi đệ qui thì: system đặt các thông số, các biến local lên "local heap" --> gọi đệ qui --> khi gọi đệ qui trở về (return) thì system "tạo dựng lại" các giá trị của thông số, biến local bằng cách lấy lại chúng từ "local heap". Các giá trị được đặt và lấy từ "local heap" theo nguyên tắc LIFO (đặt cuối cùng lấy trước tiên). Khi "độ sâu" đệ qui rất lớn thì "local heap" không đủ để nhớ hết "dữ liệu" và tràn bộ nhớ. Tràn "local heap" cho dù RAM có khủng tới bao nhiêu.
----------------
Thực ra những việc mà system làm sau "cánh gà" không biết cũng được. Chỉ cần nhớ bản chất của gọi đệ qui.
 
Lần chỉnh sửa cuối:
Upvote 0

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

Back
Top Bottom