Đố vui về VBA!

Liên hệ QC

anhtuan1066

Thành viên gạo cội
Tham gia
10/3/07
Bài viết
5,802
Được thích
6,905
Nhằm cũng cố kiến thức về VBA cho các bạn mới bắt đầu và cả những bạn đang ứng dụng mà chưa hiểu nhiều về nó, tôi mở topic này với mong mõi qua những câu hỏi vui, các bạn sẽ nhận định lại sự hiểu biết cũa mình... (Kễ cã chính tôi cũng đang tập tành nên có rất nhiều cái chưa biết)
Mong rằng topic sẽ mang đến cho các bạn những khám phá thú vị với những cái tưỡng chừng như đã biết
Mong nhận dc bài viết về câu đố cũa các cao thủ! Còn các bạn mới thì đừng ngại khi đưa ra ý kiến cũa mình.. Có sai có sữa sẽ hoàn thiện!
Tôi xin mỡ màn trước bằng 1 câu hỏi đơn giãn
ANH TUẤN

CÂU HỎI 1: Tại sao biến K ko hoạt động?
Tôi muốn khi nhấn vào 1 button thì cell A1 sẽ tăng lên 1 đơn vị... Tôi đã làm như sau:
-Tạo 1 Command Button (nút nhấn thuộc thanh Control Toolbox), click phải chuột lên nút nhấn, chọn View code, rồi gõ vào đoạn code sau:
PHP:
Private Sub CommandButton1_Click()
   K = K + 1
   Range("A1").Value = K
End Sub
Ban đầu K chưa có gì, xem như =0, nhấn nút lần thứ nhất thì K dc tăng thêm 1, vậy K hiện tại sẽ bằng 1, và gán K vào cell A1 thì đương nhiên A1 sẽ =1... Nhấn nút lần 2, K lại dc tăng thêm 1 nên hiện tại K sẽ =2 và cell A1 cũng sẽ =2... vân vân.. từ đó diễn tiến tiếp...
Hi.. hi.. Điều này nghe qua có vẽ rất hợp lý, ấy thế mà khi nhấn nút nó chỉ hoạt động dc duy nhất 1 lần (A1 = 1) rồi thôi ko nhút nhít nữa...
Các bạn có thể giãi thích tại sao lại như thế ko? Tại sao những lần nhấn nút sau đó K lại ko tăng thêm tí nào (vì thực tế A1 vẫn cứ = 1 hoài) ?
ANH TUẤN
 
Tại cửa số Immediate ta có
?GiaiThua(2.5)
3.75
 
Upvote 0
Tại cửa số Immediate ta có
?GiaiThua(2.5)
3.75
Giai thừa là một hàm toán.. Dân toán không ngu, họ thừa biết chỉ nạp số nguyên dương. Nếu không biết chắc kiểu của trị được nạp vào thì họ dùng Abs(Fix(x)).
Khác với dân "huyền thoại" của GPE, tôi không theo trường phái "một hàm lo lắng đủ mọi thứ".

Vả lại, hàm giải thừa viết theo đệ quy là chỉ dùng để dạy học thôi. Sách dạy lập trình nào cũng dùng giai thừa để dạy đệ quy kèm theo câu dặn "rất kém hiệu quả".
 
Upvote 0
Thôi thì trong lúc bà con ngóng mỏ, đố tiếp.
...
Nếu tôi muốn ngắn hơn thì dùng hàm IIF:
Cũng ngóng mỏ, giương cổ nhưng không phải để giải mà chờ đến lượt.
Cấu trúc If Then Else khác hàm IIf, nên hàm IIf chạy mãi đến khi quá tải. Khác thế nào thì có hiểu nhưng khó diễn giải thành lời :p
Em chưa thử nhưng mà sao không có vòng lặp gì hết vậy nhỉ? Theo suy đoán như hàm thì 5! = 5x4 ư?
Bôi đỏ như hình rồi chạy (chạy bằng Immediate hoặc gõ = giaithua(5) ở 1 ô trên sheet. Mỗi lần nó xanh ngó coi n bằng bao nhiêu, giaithua bằng bao nhiêu. Đếm xem xanh bao nhiêu lần, cuối cùng ra kết quả là bao nhiêu.

1674894979889.png

1674895049917.png
 
Upvote 0
Cũng ngóng mỏ, giương cổ nhưng không phải để giải mà chờ đến lượt.
Cấu trúc If Then Else khác hàm IIf, nên hàm IIf chạy mãi đến khi quá tải. Khác thế nào thì có hiểu nhưng khó diễn giải thành lời :p
...
Cái lời kêu gọi JavaScript/Google Script thực ra là một gợi ý.
 
Upvote 0
Theo kinh nghiệm đoán mò của em là hàm sẽ kiểm tra các đối số có nghĩa trước rồi mới tiến hành thực thi, và đối số sẽ không gọi được chính bản thân nó. Nó sẽ tự hỏi gà trứng cái nào có trước. Tất cả chỉ là sự đoán mò thiên bẩm.

Giống như hàm if của excel (đk, đk nếu true, đk nếu false) bắt buộc cả 3 cái này phải có nghĩa, chứ N/A là tịt, nên các bác hay bổ sung thêm Iferror cho nó hoành tráng.
 
Upvote 0
Theo kinh nghiệm đoán mò của em là hàm sẽ kiểm tra các đối số có nghĩa trước rồi mới tiến hành thực thi, và đối số sẽ không gọi được chính bản thân nó. Nó sẽ tự hỏi gà trứng cái nào có trước. Tất cả chỉ là sự đoán mò thiên bẩm.

Giống như hàm if của excel (đk, đk nếu true, đk nếu false) bắt buộc cả 3 cái này phải có nghĩa, chứ N/A là tịt, nên các bác hay bổ sung thêm Iferror cho nó hoành tráng.
Kinh nghiệm đoán mò của bạn lần này đã phản bạn rồi

1674902211596.png

1674902480346.png
So sánh hai hình trên bạn sẽ thấy hàm IF của Excel chỉ tính điều kiện rồi chọn tính một trong hai biểu thức trả về chứ không tính cả hai..
 
Upvote 0
Hic, đúng là nhầm thật. Thế này mới nhớ lâu được bác ạ, chứ học từ căn bản nó không nhớ được.
Tại không chịu tự mày mò, không tự suy nghĩ, và không tự test trước khi phát biểu hoặc trước khi hỏi.
Thí dụ bài bên trên (1464) tôi không nói ra để bạn tự hiểu, nhưng bạn không nhận thấy: trong biểu thức n * giaithua(n - 1) có chữ giaithua to tổ pố, mà khi gán n = 5 vào lại chỉ là 5 * 4
Người khác lanh lợi đọc thoáng qua đã thấy, nếu mình dở hơn người ta (gọi là có khiêm tốn) thì đọc chậm lại, nghiền ngẫm từng chữ lại, 5 phút chưa ra thì 10 phút, ... Khi nào quá 1 tiếng đồng hồ vẫn chưa thấy mới phải cầu thầy để hỏi, chứ tay nhanh hơn ... thì ...
Thêm số 2 vào thì chạy được ạ.
Mã:
Function giaithua2(n)
giaithua2 = IIf(n <= 1, 1, n * giaithua(n - 1))
End Function
Tôi không tin. Bạn chụp hình cái chạy được ra xem nào? Đừng nói là bạn copy cả 2 hàm trong câu hỏi vào 1 chỗ rồi sửa thêm 2 vào 1 trong 2 hàm.
 
Lần chỉnh sửa cuối:
Upvote 0
Đừng nói là bạn copy cả 2 hàm trong câu hỏi vào 1 chỗ rồi sửa thêm 2 vào 1 trong 2 hàm
Vâng, dùng cả 2 hàm mới được, phải gọi hàm trên. Vậy nên em mới đoán mò là IIF nó cần phải có nghĩa giống IF Excel, đối số không tự gọi chính nó được, thế là tèo.
Giờ em đang dựa cột mà nghe đây. Các bác cứ chỉ bảo nhẹ nhàng là em nghe hết, đấy là cái hay của dở bác ạ.
 
Upvote 0
Khác thế nào thì có hiểu nhưng khó diễn giải thành lời
Cũng ráng nói ra thành lời như sau (nhưng không phải nói kiểu lý thuyết, mà là nói kiểu thực hành):
1. Nếu ta viết thủ tục với hàm IIf như sau:
a. Bình thường:

Mã:
Sub tinhY()
y = IIf(5 > 3, 5 , 5 / 2)
Debug.Print y
End Sub
Ta sẽ có kết quả 5, vì (5 > 3) = True
Nếu sửa IIf( 5 = 3, 5, 5/ 2), điều kiện bị False nên kết quả = 5/ 2 = 2.5
Đến đây chưa có vấn đề gì

b. Viết lại:
Mã:
Sub tinhY()
y = IIf(5 > 3, 5 , 5 / 0)
Debug.Print y
End Sub
Ta thấy (5 > 3) = True, tưởng nó ra 5, nhưng không. Nó chưa tính gì hết, thấy ngay 5 / 0 là lỗi Divided by zero, nó cóc tính nữa. Nếu thay 5 / 0 bằng 5 / "a" cũng vậy, lỗi Type Mismatch và quăng thông báo, không tính tiếp
Thay (5 = 3) = False, cũng vậy
c. Viết lại lần 2:
Mã:
Sub tinhY()
y = IIf(5 = 3, 5 / 0, 5 / 2)
Debug.Print y
End Sub
Kết quả giống như viết lại lần 1: Một trong 2 cách tính True/ False mà lỗi thì cách gì cũng không tính tiếp

Hàm IF của worksheet không phải như vậy, nên đánh đồng 2 cái với nhau là sai (không thực hành mà dám kết luận)

2. Viết code với If Then Else
a. Lỗi Else

Mã:
Sub tinhX()
If 5 > 3 Then x = 5 / 2 Else x = 5 / "a"
Debug.Print x
End Sub
x = 5 / "a" rõ ràng là lỗi, nhưng cứ ra kết quả. Nghĩa là nếu điều kiện True thì code không xét Else có lỗi hay không.
Nhưng khi thay If 5 = 3 (False), code xét Else và quăng ra thông báo lỗi
b. Lỗi Then
Mã:
Sub tinhX()
If 5 > 3 Then x = 5 / "a" Else x = 5
Debug.Print x
End Sub
Ngược với trên, lỗi Then nên True mới báo lỗi, 5 = 3 bị False, không xét Then mà xét Else (không lỗi) nên ra kết quả.

3. Tạm kết luận:
Hàm IIf bị xét lỗi trước khi tính.

Cụ thể trong hàm giaithua(n):
giaithua = IIf(n <= 1, 1, giaithua(n - 1))
vế 1: giaithua = 1, không lỗi
vế 2: giaithua = giaithua(n - 1), code phải xét xem có lỗi với n - 1 hay không.
Sau rất nhiều lần xét với n giảm 1 dần dần, vẫn chưa biết có lỗi hay không thì đã bị tràn (quá hớp)
1674916105878.png
May là bị quá hớp, chứ không nó chạy đến sáng!
 
Upvote 0
Hàm IF của worksheet không phải như vậy, nên đánh đồng 2 cái với nhau là sai (không thực hành mà dám kết luận)
Vâng bác. Đấy là trong cái rủi có cái may, trong ẩu có lý. If trong excel mà điều kiện lỗi là trả kết quả lỗi luôn, chắc tay em nhanh quá nên bình loạn luôn chứ để lâu lại quên, chiều em mới thử lại if với điều kiện bị NA hoặc DIV0 thì thấy thế.
Nhanh nhẩu đoảng. He he.
 
Upvote 0
Chả phải do nhanh nhẩu. Tôi biết có nhiều bạn sẽ vướng vấn đề này cho nên mới ra câu đố.

Lý thuyết:
Hầu hết các ngôn ngữ lập trình đều phân biệt lệnh, biểu thức, và hàm.

IF Then Else là lệnh rẽ nhánh. Nếu đúng điều kiện thì chạy tiếp tục lệnh kế tiếp Then; và nếu khong thì nhảy qua phần ấy, chạy cái lệnh kế tiếp theo những gì sau Else (hoặc nếu có ElseIF thì xét tiếp điều kiện rồi chiếu theo đó mà rẽ nhánh..
If (dk) Then
x = biểu thức a
Else
x = biểu thức b ' giả sử biểu thức b có lỗi Div/0
End If
Bên trong máy thực ra có một lệnh JMP (nhảy) sau khi xét dk sang Else. Trong khi đó, sau lệnh gán x = a sẽ có một lênh nhảy vượt qua phần Else
Vì tất cả đều là lệnh với biểu thức cho nên VBA cứ chạy theo đúng lô gic. Những lệnh và biểu thức bị nhảy qua sé không được chạy. Dẫu các lệnh ấy có lỗi run time (DIV/0 là lỗi run time) cũng chả thây; vì chúng có chạy bao giờ đâu?

Nhưng IIF là chuyện khác. Đây là một hàm. Hàm này nhận 3 tham; nó sẽ xét lô gic tham nhứ nhât và dựa theo đó mà trả về trị của tham 2 hoặc 3.
Luật của hàm là:
- Tất cả các biểu thức (biến/hàm) nạp vào các tham đều được VBA tính ra trị rồi mới nạp cho hàm.

Luật của đệ quy là:
- Trong sub/function phải có ít nhất một chỗ gội là bẫy thoát.

Thuật toán đệ quy của bài này là "nhân n giảm dần, khi n = 1 thì thoát"

Trong code
giaithua =IIF( n <= 1, 1, n * giaithua(n - 1)
3 biểu thức n <= 1; 1; và n * giaithua(n - 1) đêu được VBA tính ra trước khi nạp cho hàm IIF.
Vì vậy hàm giaithua luôn luôn được gọi,. Biểu thức lô gic n <= 1 chỉ chọn 1 hay n * giaithua(n - 1 để trả về thôi.
Việc này làm cho bẫy thoát đệ quy (n <= 1 thì hàm trả về 1 thay vì tính tiếp) mất công hiệu.
Không có bẫy thoát , hàm giaithua cứ được gọi mãi. Một lúc sau thì stack/ngăn xếp hết chỗ, và bị lỗi tràn ngăn xếp. (xem lý thuyết về tầm vực của biến để biết tại sao có cái vụ ngăn xếp dính dáng vào đây)
Tôi tin là nếu đặt Function (n As Byte); giới hạn n trong vùng khá nhỏ thì code sẽ bị lỗi tràn số trước khi tràn ngăn xếp. Biến Byte nhỏ nhất là 0, dưới 0 là nó tràn số.

Trong khi đó, với code:
If n <= 1 Then giaithua = 1 Else giaithua = n * giaithua(n - 1)
Thì bẫy thoát hoạt động dàng hoàng. Khii n xuống tới 1 thì code nhảy vọt khỏi phần Else,, giaithua không được gọi nữa, chấm dứt.

Giải thích phần bố-nợt:
Các ngôn ngữ thuộc dòng họ C có một toán tử đặc biệt: toán tử 3 vế.
Các toán tử bình thường chỉ có 1 vế (toán tử đổi dấu: -a) hay 2 vế (a + b)

Toán tử đặc biệt này gồm hai ký nhiệu là ? : (dấu hỏi và dấu 2 chấm), và có dạng ngữ pháp sau
(điều kiện)? biểu thức a : biểu thức b
Vế thứ nhất là điều kiện, thứ hai là biểu thức a, và thứ ba là biểu thức b
Toán tử này xét vế thứ nhất, nếu true thì nó tính và trả về biêu thức ở vế thứ hai, nếu false thì nó tính và trả về biểu thức ở vế thứ ba.
Hình thức bề ngoài của nó gần giống hàm IF của bảng tính Excel. Và cách hoạt động thì có thể coi như rất giống.
Điểm cần lưu ý: tuy trông có vẻ là hàm nhưng nó là một toán tử chân chính.; và sau khi vế thứ nhất, nó chỉ tính một trong hai vế còn lại. Tức là tuy có 3 biểu thức, luôn luôn chỉ có 2 được tính.
 
Upvote 0
Chả phải do nhanh nhẩu. Tôi biết có nhiều bạn sẽ vướng vấn đề này cho nên mới ra câu đố.
Vẫn bị tính là nhanh nhẩu. Nếu chịu thực hành như tôi thì dù kém thông minh chưa luận ra nguyên nhân cũng không đến nỗi kết luận sai.
Ghi chú:
Tôi không được học lý thuyết nhiều, nhưng nhờ thực hành nên tránh được vô số lỗi như vậy. Nhiều không phải là nhiều bài tập, mà là mỗi bài thực hành đi thực hành lại nhiều lần.
 
Upvote 0
Điểm chính mà các bạn cần biết ở đây là:

- Khi gọi hàm trong VBA, mọi đối số của tham đều được VBA tính ra kết quả trước khi nạo vào cho hàm chạy. Nếu hàm có N tham thì VBA tính đủ N dối số.
Việc tính từ trái sang phải hay phải sang trái thì chưa biết. Trước mắt, theo tôi thử nghiệm thì nó tính từ trái qua phải. Nhưng tôi khong thấy tài liệu nào của MS nói vậy cho nên bvaats cứ code nào dựa vào tính chất này đều phải coi chừng có ngày MS đổi lại thì sai. Nguyên tắc: cái gì MS có nói thì họ sẽ giữ lời, cái gì họ không nói nhưng bạn mò ra là chuyện của bạn, họ đổi ráng chịu.

- Hàm bảng tính Excel hoạt động khác. Đối số nào cần thiết mới tính. Có những hàm tính tất cả đối số. Nhưng cũng có những hàm có cách đi tắt của chúng: đủ rồi thì dừng.
Điển hình hàm OR, OR(biểu thức 1, biểu thức 2, ..., biểu thức N). Theo lô gic thì OR chỉ cần 1 trong số N điều kiện là true thì kết quả chung là true. Vì vậy, hàm OR chỉ tính từ trái sang phải, đến biểu thức nào True thì nó dừng lại và bỏ rơi các biểu thức còn lại.
Ngày xưa, MS làm như vậy là vì:
1. máy cần tiết kiệm
2. tránh code chạy lòng vòng
3. Lotus123 và Excel có sau Unix. Ý tưởng "đi tắt" bắt nguồn từ Unix.
 
Upvote 0
Xuân năm nay các Thầy/Cô làm biếng bài tập cho học sinh.
Giờ này GPE đối bài dữ.

Ra thử một đề bài đơn giản cho người mới tập cũng không đến nổi lúng túng:

Viết code liệt kê các ước số của mọt số nguyên.
1. muốn số nguyên thuần (Long) cũng được; mà muốn dùng số thực dạng nguyên (cho nó lớn hơn chút) cũng được. Miễn là biết cách xử lý số nguyên số thực nghiêm chỉnh.
2.muốn xuất kết quả ra kiểu nào tùy ý: mảng, chuỗi, hay trên bảng tính cũng được.
 
Upvote 0
Web KT
Back
Top Bottom