Hướng dẫn truyền tham số bằng giá trị (ByVal)

Liên hệ QC
Nếu để hiểu rõ cách truyền tham biến cho các biến đối tượng anh nên xem các sách lập trình hướng đối tượng. Và sau khi xem xong thì anh sẽ biết tại sao biến đối tượng lại set.
Tôi không cần biết tại sao biến đối tượng lại set nhưng tôi biết khi nào phải dùng từ khóa set (dựa vào kinh nghiệm chứ cũng chả phải đọc sách, sách lập trình tôi chưa đọc qua cuốn nào). Với tôi (và tôi nghĩ đa số thành viên của GPE) như thế là đủ.
Cá nhân tôi là người viết code thực dụng nên nếu có đọc tài liệu tôi cũng tìm nhưng tài liệu kiểu "Làm như thế nào" chứ tôi sẽ không đọc những tài liệu giải thích "Vì sao phải làm như vậy".
 
Đây là nội dung tôi dịch lại từ sách, nhiều nội dung còn hạn chế. Khi các bạn áp dụng sẽ thấy rõ, do đó cần mỗi lần xuất bản sách Lập trình VBA, tôi đều update. Có những nội dung viết lại hoặc sử dụng ví dụ khác hoàn toàn.
 
Đây là nội dung tôi dịch lại từ sách, nhiều nội dung còn hạn chế. Khi các bạn áp dụng sẽ thấy rõ, do đó cần mỗi lần xuất bản sách Lập trình VBA, tôi đều update. Có những nội dung viết lại hoặc sử dụng ví dụ khác hoàn toàn.
Nội dung mình có thấy đâu ... hay Bạn quên Úp
 
Bạn đọc lại bài đầu nhé.
Như em đã nói ở bài 17, nếu biến là object hoặc kiểu tự tạo thì VB quản lý những biến này thông qua địa chỉ vùng nhớ của biến. Nếu gọi hàm và truyền tham số object theo kiểu ByVal thì các thuộc tính object đó vẫn thay đổi được, chỉ có địa chỉ của biến không đổi (ví dụ ở bài 17).
Còn để ép 1 hàm đã khai báo ByRef gọi kiểu ByVal thì đưa tham số vào trong ngoặc đơn. Ví dụ:
Mã:
Sub ABC(ByRef x As Long)
      x=x+1
End Sub
Sub Main()
      Dim x As Long
      x=1
      ABC x
      Msgbox x
      x=1
      ABC(x)
      Msgbox x
End Sub
Code trên, lần đầu gọi Sub, biến x là ByRef nên thay đổi thành 2; lần sau x gọi theo kiểu ByVal nên vẫn =1.
Còn hàm khai báo kiểu ByVal, gọi kiểu ByRef thì em chưa biết, hay không thể?
 
Như em đã nói ở bài 17, nếu biến là object hoặc kiểu tự tạo thì VB quản lý những biến này thông qua địa chỉ vùng nhớ của biến. Nếu gọi hàm và truyền tham số object theo kiểu ByVal thì các thuộc tính object đó vẫn thay đổi được, chỉ có địa chỉ của biến không đổi (ví dụ ở bài 17).
Còn để ép 1 hàm đã khai báo ByRef gọi kiểu ByVal thì đưa tham số vào trong ngoặc đơn. Ví dụ:
Mã:
Sub ABC(ByRef x As Long)
      x=x+1
End Sub
Sub Main()
      Dim x As Long
      x=1
      ABC x
      Msgbox x
      x=1
      ABC(x)
      Msgbox x
End Sub
Code trên, lần đầu gọi Sub, biến x là ByRef nên thay đổi thành 2; lần sau x gọi theo kiểu ByVal nên vẫn =1.
Còn hàm khai báo kiểu ByVal, gọi kiểu ByRef thì em chưa biết, hay không thể?
Tôi nghĩ ví dụ của bạn không phải là khai báo kiểu ByRef gọi kiểu ByVal

Ở lần gọi thủ tục ABC thứ 2, tham số đưa vào thủ tục là giá trị của biểu thức (x) chứ không phải là biến x, nên không có gì ảnh hưởng đến biến x cả.
Tôi thí nghiệm như sau:
PHP:
Private Sub Main(ByRef Bien As Variant)
      MsgBox TypeName(Bien)
End Sub
PHP:
Sub Test1()
      Dim Rng As Range
      ActiveCell.Value = "GPE"
      Set Rng = ActiveCell
      Main Rng
End Sub
PHP:
Sub Test2()
      Dim Rng As Range
      ActiveCell.Value = "GPE"
      Set Rng = ActiveCell
      Main (Rng)
End Sub
 
Tôi nghĩ ví dụ của bạn không phải là khai báo kiểu ByRef gọi kiểu ByVal

Ở lần gọi thủ tục ABC thứ 2, tham số đưa vào thủ tục là giá trị của biểu thức (x) chứ không phải là biến x, nên không có gì ảnh hưởng đến biến x cả.
Tôi thí nghiệm như sau:
PHP:
Private Sub Main(ByRef Bien As Variant)
      MsgBox TypeName(Bien)
End Sub
PHP:
Sub Test1()
      Dim Rng As Range
      ActiveCell.Value = "GPE"
      Set Rng = ActiveCell
      Main Rng
End Sub
PHP:
Sub Test2()
      Dim Rng As Range
      ActiveCell.Value = "GPE"
      Set Rng = ActiveCell
      Main (Rng)
End Sub
Mình nói ở bài trên là theo MS https://msdn.microsoft.com/en-us/library/chy4288y.aspx chứ mình không bịa ra.
Trường hợp này đúng là như vậy, đây chắc lại liên quan đến call ByVal hay ByRef với object.
 
Vậy là bắt buộc phải có ByVal vào .....vậy thì bài #15 MÌNH viết cho thêm Byval vào cũng vậy mà bỏ đi cũng vậy ...Mình chưa tìm thấy sự khác biệt ...Quả thiệt là +-+-+-++-+-+-+ đây
cảm ơn Bạn
Dưới đây mà một ví dụ mà bạn phải khai báo Byval, nếu bỏ trống hoặc khai báo Byref thì sẽ không ra kết quả.

Dữ liệu:
Tôi có một bảng tổng hợp, và nhiều bảng chi tiết.
Trên bảng tổng hợp có 2 cột Mã và Số lượng, cột Mã là danh sách duy nhất các mã, cột Số lượng là số lượng tương ứng của mã
Trên mỗi bảng chi tiết cũng có 2 cột Mã và Số lượng nhưng cột Mã không phải là danh sách duy nhất mà có lặp lại.

Yêu cầu:
Xác định dữ liệu trên bảng tổng hợp là được tổng hợp từ bảng chi tiết nào

Thuật toán:
Tôi làm như sau:
- Gán dữ liệu bảng tổng hợp vào một mảng (mảng tổng hợp)
- Duyệt qua các bảng dữ liệu chi tiết
- Dùng một hàm phụ
+ Duyệt qua các dòng trong mảng chi tiết và trừ số lượng của mã tương ứng trên bảng tổng hợp
+ Sau khi duyệt qua hết các dòng của mảng chi tiết thì duyệt qua các dòng của mảng tổng hợp, nếu tất cả các dòng của mảng tổng hợp đều có số lượng bằng 0 thì mảng tổng hợp được tổng hợp từ mảng chi tiết đang xét.

Code:
PHP:
Sub Sub_Chinh()
Dim Dic As Object, TongHop As Variant, i As Long, Rng As Range
Set Dic = CreateObject("Scripting.Dictionary")
TongHop = Range(ThisWorkbook.Sheets("TongHop").[B2], ThisWorkbook.Sheets("TongHop").[A65536].End(xlUp)).Value
For i = 1 To UBound(TongHop, 1)
    Dic.Item(TongHop(i, 1)) = i
Next
ThisWorkbook.Sheets("TongHop").[E2:F100].ClearContents
For i = 1 To ThisWorkbook.Sheets.Count
    If ThisWorkbook.Sheets(i).Name <> "TongHop" Then
        Set Rng = Range(ThisWorkbook.Sheets(i).[B2], ThisWorkbook.Sheets(i).[A65536].End(xlUp))
        If Sub_Con(TongHop, Rng.Value, Dic) Then
            ThisWorkbook.Sheets("TongHop").[E2].Resize(Rng.Rows.Count, 2).Value = Rng.Value
            Exit Sub
        End If
    End If
Next
End Sub
PHP:
Private Function Sub_Con(ByVal TongHop As Variant, ChiTiet As Variant, Dic As Object) As Boolean
Dim i As Long, Index As Long
For i = 1 To UBound(ChiTiet, 1)
    If Dic.Exists(ChiTiet(i, 1)) Then
        Index = CLng(Dic.Item(ChiTiet(i, 1)))
        TongHop(Index, 2) = TongHop(Index, 2) - ChiTiet(i, 2)
    Else
        Sub_Con = False
        Exit Function
    End If
Next
Sub_Con = True
For i = 1 To UBound(TongHop, 1)
    If TongHop(i, 2) <> 0 Then
        Sub_Con = False
        Exit Function
    End If
Next
End Function
Lưu ý:
Nếu bạn không khai báo Byval cho tham số TongHop của Sub_Con thì sẽ không có kết quả.
Đương nhiên bạn có thể dùng cách khác hoặc vẫn là cách này nhưng trước khi gọi Sub_Con bạn gán TongHop vào một biết khác và dùng biến này để truyền cho Sub_Con thì không có gì để nói nữa vì nó không thuộc phạm vi của vấn đề đang đề cập nữa.
 

File đính kèm

  • VD.xls
    58.5 KB · Đọc: 14
Dưới đây mà một ví dụ mà bạn phải khai báo Byval, nếu bỏ trống hoặc khai báo Byref thì sẽ không ra kết quả.

Dữ liệu:
Tôi có một bảng tổng hợp, và nhiều bảng chi tiết.
Trên bảng tổng hợp có 2 cột Mã và Số lượng, cột Mã là danh sách duy nhất các mã, cột Số lượng là số lượng tương ứng của mã
Trên mỗi bảng chi tiết cũng có 2 cột Mã và Số lượng nhưng cột Mã không phải là danh sách duy nhất mà có lặp lại.

Yêu cầu:
Xác định dữ liệu trên bảng tổng hợp là được tổng hợp từ bảng chi tiết nào

Thuật toán:
Tôi làm như sau:
- Gán dữ liệu bảng tổng hợp vào một mảng (mảng tổng hợp)
- Duyệt qua các bảng dữ liệu chi tiết
- Dùng một hàm phụ
+ Duyệt qua các dòng trong mảng chi tiết và trừ số lượng của mã tương ứng trên bảng tổng hợp
+ Sau khi duyệt qua hết các dòng của mảng chi tiết thì duyệt qua các dòng của mảng tổng hợp, nếu tất cả các dòng của mảng tổng hợp đều có số lượng bằng 0 thì mảng tổng hợp được tổng hợp từ mảng chi tiết đang xét.

Code:
PHP:
Sub Sub_Chinh()
Dim Dic As Object, TongHop As Variant, i As Long, Rng As Range
Set Dic = CreateObject("Scripting.Dictionary")
TongHop = Range(ThisWorkbook.Sheets("TongHop").[B2], ThisWorkbook.Sheets("TongHop").[A65536].End(xlUp)).Value
For i = 1 To UBound(TongHop, 1)
    Dic.Item(TongHop(i, 1)) = i
Next
ThisWorkbook.Sheets("TongHop").[E2:F100].ClearContents
For i = 1 To ThisWorkbook.Sheets.Count
    If ThisWorkbook.Sheets(i).Name <> "TongHop" Then
        Set Rng = Range(ThisWorkbook.Sheets(i).[B2], ThisWorkbook.Sheets(i).[A65536].End(xlUp))
        If Sub_Con(TongHop, Rng.Value, Dic) Then
            ThisWorkbook.Sheets("TongHop").[E2].Resize(Rng.Rows.Count, 2).Value = Rng.Value
            Exit Sub
        End If
    End If
Next
End Sub
PHP:
Private Function Sub_Con(ByVal TongHop As Variant, ChiTiet As Variant, Dic As Object) As Boolean
Dim i As Long, Index As Long
For i = 1 To UBound(ChiTiet, 1)
    If Dic.Exists(ChiTiet(i, 1)) Then
        Index = CLng(Dic.Item(ChiTiet(i, 1)))
        TongHop(Index, 2) = TongHop(Index, 2) - ChiTiet(i, 2)
    Else
        Sub_Con = False
        Exit Function
    End If
Next
Sub_Con = True
For i = 1 To UBound(TongHop, 1)
    If TongHop(i, 2) <> 0 Then
        Sub_Con = False
        Exit Function
    End If
Next
End Function
Lưu ý:
Nếu bạn không khai báo Byval cho tham số TongHop của Sub_Con thì sẽ không có kết quả.
Đương nhiên bạn có thể dùng cách khác hoặc vẫn là cách này nhưng trước khi gọi Sub_Con bạn gán TongHop vào một biết khác và dùng biến này để truyền cho Sub_Con thì không có gì để nói nữa vì nó không thuộc phạm vi của vấn đề đang đề cập nữa.
đúng là vậy. cảm ơn Bạn giải thích rất chi tiết
 
Dưới đây mà một ví dụ mà bạn phải khai báo Byval, nếu bỏ trống hoặc khai báo Byref thì sẽ không ra kết quả.

….
Lưu ý:
Nếu bạn không khai báo Byval cho tham số TongHop của Sub_Con thì sẽ không có kết quả.
Đương nhiên bạn có thể dùng cách khác hoặc vẫn là cách này nhưng trước khi gọi Sub_Con bạn gán TongHop vào một biết khác và dùng biến này để truyền cho Sub_Con thì không có gì để nói nữa vì nó không thuộc phạm vi của vấn đề đang đề cập nữa.


Code của bạn dùng Variant để tổng quát hóa kiểu dữ liệu của tham. (tôi đã có đề cập đến kỹ thuật này rồi)

Variant là một kiểu tổng quát. Nó là một loại mà tiếng nhà nghề gọi là supertype, có thể biến theo đúng dạng nào cũng được, tùy theo đối tượng mà nó được gán. Khi trình dịch (compiler) gặp loại biến này trên tham số khai báo, nó tự biết rằng tham truyền vào có thể rất phức tạp cho nên nó để hở (vì kiểu phức tạp nên mặc định là ByRef). Để tránh sự mặc định này, bạn bắt buộc cho trình dịch biết thẳng là ByVal.

Nếu để hiểu rõ cách truyền tham biến cho các biến đối tượng anh nên xem các sách lập trình hướng đối tượng. Và sau khi xem xong thì anh sẽ biết tại sao biến đối tượng lại set.


Nếu được đề nghị bạn cho một VD điển hình coi....chứ lý thuyết suông chán lắm và lại tư duy triều tượng nữa chứ chắc cái đầu ngắn học của mình nổ tung mất ...


Trừ phi bạn muốn học lập trình chuyên nghiệp, chứ bỏ công học lập trình hướng đối tượng không hẳn là hiệu quả đâu. Tôi không chắc là một người bỏ ra học một năm HĐT có thể trả lời được câu này.

Nguyên tắc mà bạn cần biết là trình dịch (compiler) duyệt một câu lệnh, nó bắt buộc phải dịch một cách thống nhất, không chấp nhận chuyện hiểu lầm.

Lý do là vì ký tự = được dùng theo nhiều nghĩa:
Lệnh gán căn bản của BASIC là lệnh LET. Về sau này, các phiên bản BASIC đều bỏ từ LET, compiler (trình dịch) phải tự động duyệt cả các trường hợp dấu = để hiểu đó là lệnh gán hay là phép so sánh. Điều này complier có thể làm được dễ dàng đối với các biến dạng căn bản (integer, string,…)
Tuy nhiên khi áp dụng với đối tượng, các đối tượng trong VBA có hàm/thuộc tính ngầm để đáp ứng với nhiều loại gán cho nên lệnh gán thông thường có thể gây hiểu lầm. Ví dụ bạn có:
Dim a As Variant
a = Range("a1:a2")
Giả sử VBA không có từ khóa SET thì Compiler sẽ hiểu như thế nào đây? a bây giờ là range hay mảng?
Từ khóa Set giúp cho trình dịch giải quyết sự thắc mắc này.
Chạy thử code này sẽ rõ:
Sub t()
Dim a As Variant
Set a = Range("a1:a2")
Debug.Print TypeName(a)
a = Range("a1:a2")
Debug.Print TypeName(a)
End Sub
Kết quả cho thấy là

  1. Range
  2. Variant()
 
Web KT

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

Back
Top Bottom