Создание пользовательских типов данных в VBA Excel. Оператор Type, его описание и параметры. Создание массива «одномерных массивов» с пользовательскими данными.
Определение пользовательских типов данных
Пользовательский тип данных в VBA Excel представляет из себя набор элементов, каждому из которых пользователем присваивается свой тип данных. Другими словами, пользовательский тип данных — это набор данных разного типа, который может быть присвоен одной переменной.
Если простую переменную (не массив) объявить с пользовательским типом данных, она будет представлять из себя «одномерный массив»* с элементами разных типов данных, определенных пользователем.
Если с пользовательским типом данных объявить переменную массива, она будет представлять из себя массив «одномерных массивов»* пользовательских данных.
* Выражение «одномерный массив» взято в кавычки, так как фактически это не массив, а набор пользовательских данных, но для практического применения это не имеет значения.
Синтаксис оператора Type
Type <strong>Name</strong> <strong>Element</strong>_1 as <strong>Tip</strong> <strong>Element</strong>_2 as <strong>Tip</strong> <strong>Element</strong>_3 as <strong>Tip</strong> ———————— <strong>Element</strong>_n as <strong>Tip</strong> End Type |
Пользовательский тип данных в VBA Excel может быть объявлен с ключевым словом Public или Private, которое явно укажет зону его видимости. Если ключевое слово опущено, конструкция с оператором Type по умолчанию объявляется с зоной видимости Public.
Параметры оператора Type
Параметр | Описание |
---|---|
Name | Имя пользовательского типа данных, по которому этот тип данных будет присваиваться переменным. |
Element | Наименование отдельного элемента пользовательского типа данных. |
Tip | Тип данных отдельного элемента (стандартный тип VBA). |
Применение пользовательских типов данных
Применение пользовательских типов данных в VBA Excel рассмотрим на примере домиков для животных.
Объявление пользовательского типа данных
Объявление пользовательского типа данных (конструкция с оператором Type) размещается в самом начале модуля в разделе Declarations.
Пример 1
Type Domik naimenovaniye As String obyem_m3 As Single material As String kolichestvo As Long End Type |
В этом примере:
- Domik — имя, по которому этот тип данных будет присваиваться переменным;
- naimenovaniye — наименование домика для животных;
- obyem_m3 — объем домика в куб. метрах;
- material — материал, из которого сделан домик;
- kolichestvo — количество домиков на складе.
Заполнение данными массива
Обычно в качестве контейнеров для пользовательских типов данных в VBA Excel используются массивы. В простую переменную можно уместить только один набор пользовательских данных, а в массив — сколько нужно. В следующем примере мы заполним трехэлементный массив тремя наборами пользовательских данных.
Если представить набор пользовательских данных как «одномерный массив», то таким образом мы создадим массив «одномерных массивов» с пользовательскими данными.
Пример 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Sub Primer2() ‘Объявляем трехэлементный массив ‘с пользовательским типом данных Dim a(1 To 3) As Domik ‘Заполняем первый элемент массива a(1).naimenovaniye = «Скворечник» a(1).obyem_m3 = 0.02 a(1).material = «сосна» a(1).kolichestvo = 15 ‘Заполняем второй элемент массива a(2).naimenovaniye = «Собачья будка» a(2).obyem_m3 = 0.8 a(2).material = «береза» a(2).kolichestvo = 5 ‘Заполняем третий элемент массива a(3).naimenovaniye = «Клетка кролика» a(3).obyem_m3 = 0.4 a(3).material = «металл» a(3).kolichestvo = 6 End Sub |
Обращение к пользовательским данным в массиве
Для обращения в коде VBA Excel к пользовательским данным в массиве используется та же конструкция, что и при записи: указывается элемент массива и через точку наименование элемента пользовательских данных.
Пример 3
‘Считываем информацию из массива Dim b As Variant b = a(2).naimenovaniye MsgBox b b = a(3).obyem_m3 MsgBox b b = «Мы продаем следующие товары: « _ & a(1).naimenovaniye & «, « _ & a(2).naimenovaniye & » и « _ & a(3).naimenovaniye MsgBox b |
Для наглядной демонстрации вставьте строки кода Примера 3 перед строкой End Sub Примера 2.
Картинка: Designed by vectorjuice / Freepik
Кому будет полезна статья, по мнению автора:
начинающим программистам на языке VBA и тем, кто не работал ранее с оператором Type. Если вы используете этот оператор постоянно, можно сравнить свой вариант применения и вариант автора.
Большинство пользователей VBA прекрасно знают такую штуку как Type
, он же User Defined Type (UDT). Кто-то, как я, использует его на повседневной основе. Кто-то, возможно, о нем слышал, но не мог понять как его применить.
Лично я помню, как не так давно смотрел на этот Type
и пытался понять зачем он мне нужен, ведь он просто хранит в себе переменные, которые можно с тем же успехом объявить в функции/процедуре или на уровне модуля?
В этой статье я хотел бы показать на примере как можно использовать Type
. Мы разберем некоторые его особенности, и возможно кто-нибудь из читателей найдет для себя один из примеров крайне интересным (а может быть даже будет использовать в своих проектах). Поехали!
Вычисляем ошибки, чтобы их не допускать
Что же, для начала давайте обратимся к официальной документации:
(вольный перевод автора)
Оператор Type – используется на уровне модуля для объявления пользовательского типа данных, содержащего один или несколько элементов.Type можно использовать только на уровне модуля. После объявления пользовательского типа вы можете объявить переменную этого типа в любом месте в пределах области видимости. Для объявления переменной пользовательского типа используйте Dim, Private, Public, ReDim или Static… Номера и метки строк не допускаются внутри блоков Type…End Type.
Итак, исходя из документации мы можем выделить два основных момента:
-
Оператор Type используется только на уровне модуля. Это значит, что его нельзя объявлять в процедурах/функциях/методах/свойствах.
-
Номера и метки строк не допускаются внутри блоков.
Давайте протестируем оба утверждения:
В первом случае получаем ошибку компиляции «Недопустимая внутренняя процедура»,
во втором так же ошибка компиляции «Оператор (заявление/утверждение) недопустим внутри блока Type».
Не описано в официальной документации то, что объявленный в Class
модуле Type
может быть только Private
, иначе мы снова получим ошибку компиляции, в этот раз «Нельзя объявлять публичный пользовательский тип в объектном модуле»:
Компилятор перестает ругаться только в случае Private Type
в Class
модуле, но здесь нужно помнить, что возвращать такой UDT можно только Private
функцией, иначе:
мы снова получим ошибку компиляции, теперь это «Private перечисления и пользовательские типы, не могут использоваться в качестве параметров или возвращаемых типов для Public процедур, членов данных или полей пользовательских типов».
Кстати, как и обозначено в описании ошибки, в модуле класса нельзя создавать публичные поля или использовать параметры для публичных методов с приватным типом UDT. Ну оно и логично.
Постановка задачи
Итак, если я не ошибаюсь, с ошибками мы разобрались. Перейдем к использованию.
Давайте представим, что наша задача – почтовая рассылка по определенному скрипту. Во время выполнения макроса мы получаем информацию об email-адресе получателя, адресате копии письма и его теме, после чего все эти данные нам нужно передать в отдельную функцию, которая занимается созданием письма и его отправкой или сохранением в черновики.
Решаем без UDT
Для начала разберемся с обычным модулем. Использование UDT в Class
модуле будет во второй части.
Как можно решить эту задачу стандартными средствами?
Что ж, первое что мы делаем – объявляем переменные, которые будут содержать адрес получателя и адрес адресата копии (простите за тавтологию), а так же тему письма, после чего присваиваем напрямую значения, чтобы не усложнять пример, и отправляем их как аргументы в функцию CreateLetter
:
Sub Mailing()
Dim AddressTo As String: AddressTo = "exampleTo@test.vba"
Dim AddressCC As String: AddressCC = "exampleCC@test.vba"
Dim Subject As String: Subject = "Тема письма"
CreateLetter AddressTo, AddressCC, Subject
End Sub
Далее, пропишем функцию, которая создаст и отправит или сохранит письмо (это значение сделаем необязательным, по умолчанию установим в False
):
Sub CreateLetter(ByVal AddressTo As String, _
ByVal AddressCC As String, _
ByVal Subject As String, _
Optional ByVal Submit As Boolean = False)
Dim Outlook As Object
Set Outlook = CreateObject("Outlook.Application")
With Outlook.CreateItem(olMailItem)
.To = AddressTo
.CC = AddressCC
.Subject = Subject
If Submit Then .Send
End With
End Sub
Итак, в целом все нормально. У нас есть данные, мы передаем их в функцию, функция их использует.
Но это всего лишь два адреса и тема.
А теперь представим, что нам нужно передавать еще текст тела письма и вложение.
А еще в параметрах можно указать нужно ли удалять письмо после отправки (свойство DeleteAfterSubmit
), или указать нужно ли отметить неотправленное письмо (черновик) как прочитанное (свойство UnRead
).
А еще, возможно нам потребуется создавать письмо из другой процедуры и тогда снова придется перечислять все переменные в объявлении и передавать их все в функцию.
И многое, многое другое…
Представьте на секунду насколько сильно разрастутся параметры функции.
Плюс, копия в письме может быть не всегда, как и вложение. Тогда придется делать все параметры Optional
? Или прописать ParamArray
? Это все не наглядно и может вызвать ошибки, в случае не верной передачи параметров.
Код становится менее читаемым и сумбурным, согласитесь. На таком небольшом примере все ок, ничего особо критичного. Но в реальном проекте это может стать большой проблемой.
Гораздо более лаконичное решение, как вы уже поняли, использовать UDT.
Решаем с UDT
Для решения нам потребуется объявить Type
на уровне модуля и поместить в него все наши переменные. Давайте назовем его TLetter
:
Type TLetter
AddressTo As String
AddressCC As String
Subject As String
End Type
Далее, в процедуре Mailing
создадим переменную Letter
типа TLetter
:
Sub Mailing()
Dim Letter As TLetter
Dim AddressTo As String: AddressTo = "exampleTo@test.vba"
Dim AddressCC As String: AddressCC = "exampleCC@test.vba"
Dim Subject As String: Subject = "Тема письма"
CreateLetter AddressTo, AddressCC, Subject
End Sub
Теперь, всем полям нашего типа присваиваем необходимые значения. Сделать это можно написав имя переменной Letters
и далее через точку выбрать нужное поле:
Sub Mailing()
Dim Letter As TLetter
Letter.AddressTo = "exampleTo@test.vba"
Letter.AddressCC = "exampleCC@test.vba"
Letter.Subject = "Тема письма"
CreateLetter Letter
End Sub
Ничего вам это не напоминает?😏
Если вы сказали «да это же как объект» – то вы совершенно правы. Взаимодействие с Type очень похоже на взаимодействие с объектами. Только мы объявляем его без ключевых слов New и Set, как в случае с объектами, а так же не сможем поместить в него функции/процедуры. Я бы даже назвал этот блок, скорее, своего рода, структурой.
Все что нам осталось сделать – заменить в процедуре CreateLetter
три старых параметра на один новый и переписать присваивание параметров:
Sub CreateLetter(ByRef Letter As TLetter, _
Optional ByVal Submit As Boolean = False)
Dim Outlook As Object
Set Outlook = CreateObject("Outlook.Application")
With Outlook.CreateItem(olMailItem)
.To = Letter.AddressTo
.CC = Letter.AddressCC
.Subject = Letter.Subject
If Submit Then .Send
End With
End Sub
Кстати, в блоке Ошибки я забыл упомянуть еще одну небольшую особенность – UDT в параметры можно передавать только
ByRef
.
Так лучше, верно?
Не совсем. Давайте уберем последний опциональный параметр Submit
из функции и пропишем его в нашей структуре как поле:
Option Explicit
Type TLetter
AddressTo As String
AddressCC As String
Subject As String
Submit As Boolean ' Переносим параметр в структуру.
End Type
Sub Mailing()
Dim Letter As TLetter
Letter.AddressTo = "exampleTo@test.vba"
Letter.AddressCC = "exampleCC@test.vba"
Letter.Subject = "Тема письма"
CreateLetter Letter
End Sub
Sub CreateLetter(ByRef Letter As TLetter)
Dim Outlook As Object
Set Outlook = CreateObject("Outlook.Application")
With Outlook.CreateItem(olMailItem)
.To = Letter.AddressTo
.CC = Letter.AddressCC
.Subject = Letter.Subject
If Letter.Submit Then .Send ' Передаем поле из структуры.
End With
End Sub
Вот теперь действительно лучше.
Обратите внимание, мы не присваиваем полю Submit
значение в процедуре Mailing
. Не присвоенное значение по умолчанию останется False
:
Думаю не нужно объяснять, что расширять этот тип можно сколько угодно, при этом использовать все его поля нет необходимости. Вы можете оставлять их пустыми и уже в функции прописывать валидацию для пустых полей, если в этом есть потребность.
Расширяем возможности
Итак, мы научились складывать несколько связанных переменных в одну и использовать их в качестве аргумента функции.
Но что если нам нужно очень много таких переменных и при этом они связаны между собой в своего рода блоки, а эти блоки можно связать в одну единую переменную?
Давайте добавим новые вводные в задачу и рассмотрим на примере.
Допустим в функцию CreateLetter
нам нужно дополнительно передавать параметр UnRead
, а так же тело письма.
Для начала разделим все наши вводные на несколько блоков:
-
Блок адресатов: получатель, копия.
-
Блок письма: тема и тело.
-
Блок параметров: отправлять или нет, помечать как прочитанное или нет.
Итого получаем три блока по две переменных в каждом.
Как это реализовать? Очень просто.
Для начала, под каждый блок создаем свой UDT:
Option Explicit
' Блок адресатов
Type TRecipient
To As String
CC As String
End Type
' Блок письма
Type TMain
Subject As String
Body As String
End Type
' Блок параметров
Type TParameter
Submit As Boolean
UnRead As Boolean
End Type
После чего снова создаем UDT TLetter
, а уже в нем объявляем три переменных с ранее созданными блоками:
Type TLetter
Recipient As TRecipient
Main As TMain
Parameter As TParameter
End Type
Да, так можно было. 😁
Дальше, что называется, следите за руками.
В процедуре Mailing
через уже знакомую переменную Letter
присваиваем значения переменнным блока адресатов и блока письма:
Sub Mailing()
Dim Letter As TLetter
Letter.Recipient.To = "exampleTo@test.vba"
Letter.Recipient.CC = "exampleCC@test.vba"
Letter.Main.Subject = "Тема письма"
Letter.Main.Body = "Тело письма"
CreateLetter Letter
End Sub
Немного корректируем функцию CreateLetter
и добавляем новые параметры для создаваемого элемента письма (не функции):
Sub CreateLetter(ByRef Letter As TLetter)
Dim Outlook As Object
Set Outlook = CreateObject("Outlook.Application")
With Outlook.CreateItem(olMailItem)
.To = Letter.Recipient.To
.CC = Letter.Recipient.CC
.Subject = Letter.Main.Subject
.Body = Letter.Main.Body
.UnRead = Letter.Parameter.UnRead
If Letter.Parameter.Submit Then .Send
End With
End Sub
И все! Да, так просто.
В реальных задачах меня такая гибкость очень сильно выручала, выручает и, уверен, еще будет выручать.
Что в итоге
В итоге, мы имеем очень удобный и гибкий инструмент для хранения некой связанной структуры данных.
Так же, этот инструмент помогает нам защитить код от ошибок на моменте его написания, потому что передать непонятно что в функцию будет сильно сложнее, чем если бы мы использовали обычные типы.
Код, благодаря такому подходу, становится, во-первых, более читаемым, и во-вторых, более гибким и расширяемым. С таким кодом гораздо приятнее работать.
А ведь это важные вещи, к которым мы все стремимся при написании кода.
Это не все, что я хотел рассказать про Type
. В следующей статье рассмотрим еще один пример использования UDT в модуле, а так же увидим как его применять в Class
модуле.
Спасибо, что прочитали до конца.
А как вы используете Type? Пишите в комментариях!
А также, подписывайтесь на мой телеграмм.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Умели раньше пользоваться оператором Type?
16.67%
Да, использую его так же как автор
2
16.67%
Да, но использую его иначе (опишите, пожалуйста, как именно в комментариях)
2
25%
Нет, но слышал о нем. Не вижу смысла применять
3
25%
Нет, но слышал о нем. Возможно теперь буду применять
3
0%
Нет, и не слышал. Не вижу смысла применять
0
16.67%
Нет, и не слышал. Возможно теперь буду применять
2
Проголосовали 12 пользователей.
Воздержались 3 пользователя.
Содержание
- Объявление переменных
- Оператор Public
- Оператор Private
- Оператор Static
- Оператор Option Explicit
- Объявление объектной переменной для автоматизации
- См. также
- Поддержка и обратная связь
- Сводка типов данных
- Набор встроенных типов данных
- Преобразование между типами данных
- Проверка типов данных
- Возвращаемые значения функции CStr
- См. также
- Поддержка и обратная связь
Объявление переменных
При объявлении переменных обычно используется оператор Dim. Оператор объявления может быть помещен внутрь процедуры для создания переменной на уровне процедуры. Или он может быть помещен в начале модуля в разделе объявлений, чтобы создать переменную на уровне модуля.
В примере ниже создается переменная и указывается тип данных «String».
Если данный оператор отображается в процедуре, переменная strName может использоваться только в этой процедуре. Если же оператор отображается в разделе объявлений модуля, переменная strName доступна для всех процедур данного модуля, но недоступна для процедур других модулей проекта.
Чтобы предоставить доступ к переменной всем процедурам проекта, перед ней нужно поставить оператор Public, как показано в примере ниже:
Дополнительные сведения об именовании переменных см. в статье Правила именования в Visual Basic.
Переменные могут быть объявлены одним из следующих типов данных: Boolean, Byte, Integer, Long, Currency, Single, Double, Date, String (для строк переменной длины), String * length (для строк фиксированной длины), Object или Variant. Если тип данных не указан, по умолчанию присваивается тип данных Variant. Вы также можете создать определяемый пользователем тип с помощью оператора Type.
Вы можете объявить несколько переменных в одном операторе. Чтобы указать тип данных, необходимо задать тип данных для каждой переменной.
В приведенном ниже операторе переменные intX , intY и intZ объявлены типом Integer.
В приведенном ниже операторе intX и intY объявлены как Variant и только intZ объявлен как тип Integer.
Нет необходимости указывать тип данных переменной в операторе объявления. Если вы не укажите тип данных, переменной будет присвоен тип Variant.
Сокращение для объявления переменных x и y типом Integer в приведенном выше операторе
Сокращение для типов: % -integer; & -long; @ -currency; # -double; ! – Single; $ – String
Оператор Public
Используйте оператор Public для объявления общих переменных на уровне модуля.
Общие переменные могут использоваться в любой процедуре проекта. Если общая переменная объявлена в стандартном модуле или модуле класса, она может использоваться в любых проектах, содержащих ссылку на проект, в котором объявлена данная общая переменная.
Оператор Private
Используйте оператор Private для объявления частных переменных на уровне модуля.
Частные переменные могут использоваться только процедурами одного модуля.
На уровне модуля оператор Dim является эквивалентным оператору Private. Вы можете использовать оператор Private, чтобы упростить чтение и интерпретацию кода.
Оператор Static
Если для объявления переменной в процедуре используется оператор Static вместо оператора Dim, объявленная переменная сохраняет свое значение между вызовами в этой процедуре.
Оператор Option Explicit
В Visual Basic можно неявно объявить переменную, просто используя оператор присвоения значения. Все неявно объявленные переменные относятся к типу Variant. Для переменных типа Variant требуется больший объем памяти, чем для большинства других переменных. Приложение будет работать эффективнее, если переменные будут явно объявленными и им будет присвоен определенный тип данных. Явное объявление переменных снижает вероятность возникновения ошибок, вызванных конфликтом имен или опечатками.
Если вы не хотите, чтобы в Visual Basic были неявные объявления, то оператор Option Explicit должен стоять в модуле перед всеми процедурами. Этот оператор требует явного объявления всех переменных модуля. Если модуль содержит оператор Option Explicit, то при обнаружении необъявленной ранее переменной или опечатки в ее имени Visual Basic выдаст ошибку времени компиляции.
В программной среде Visual Basic имеется возможность задавать параметр, который будет автоматически включать оператор Option Explicit во все новые модули. Справочная информация по изменению параметров среды Visual Basic предоставлена в документации приложения. Обратите внимание, что данный параметр не меняет уже написанный код.
Статические и динамические массивы нужно объявлять в явном виде.
Объявление объектной переменной для автоматизации
При использовании приложения для управления объектами другого приложения необходимо указать ссылку на библиотеку типов этого другого приложения. Когда ссылка указана, можно объявлять объектные переменные в соответствии с наиболее подходящими для них типами. Например, если вы указываете ссылку на библиотеку типов Microsoft Excel при работе в Microsoft Word, то можете объявить переменную типа Worksheet внутри Word, чтобы она представляла объект Worksheet приложения Excel.
При использовании другого приложения для управления объектами Microsoft Access, как правило, можно объявлять объектные переменные согласно наиболее подходящим для них типам. Вы можете также использовать ключевое слово New для автоматического создания нового экземпляра объекта. Однако может возникнуть необходимость указать, что объект принадлежит Microsoft Access. Например, при объявлении объектной переменной, представляющей форму Access внутри Visual Basic, необходимо сделать различимыми объект Form приложения Access и объект Form приложения Visual Basic. Для этого следует включать имя библиотеки типов в объявление переменной, как показано в примере ниже:
Некоторые приложения не распознают отдельные объектные типы Access. Даже если в этих приложениях указана ссылка на библиотеку типов Access, все объектные переменные Access необходимо объявлять с типом Object. Также невозможно использовать ключевое слово New для создания нового экземпляра объекта.
В примере ниже показано, как объявлять переменную, представляющую экземпляр объекта Application Access в приложении, которое не распознает объектные типы Access. Затем приложение создает экземпляр объекта Application.
В документации приложения предоставлена информация о поддерживаемом им синтаксисе.
См. также
Поддержка и обратная связь
Есть вопросы или отзывы, касающиеся Office VBA или этой статьи? Руководство по другим способам получения поддержки и отправки отзывов см. в статье Поддержка Office VBA и обратная связь.
Источник
Сводка типов данных
Тип данных — это характеристика переменной, определяющая тип содержащихся в ней данных. К типам данных относятся типы, указанные в таблице ниже, а также пользовательские типы и определенные типы объектов.
Набор встроенных типов данных
В следующей таблице показываются поддерживаемые типы данных, включая размеры хранилищ и диапазоны.
Тип данных | Размер хранилища | Диапазон |
---|---|---|
Boolean | 2 байта | True или False |
Byte | 1 байт | от 0 до 255 |
Collection | Неизвестно | Неизвестно |
Currency (масштабируемое целое число) | 8 байт | от –922 337 203 685 477,5808 до 922 337 203 685 477,5807 |
Date | 8 байт | от 1 января 100 г. до 31 декабря 9999 г. |
Decimal | 14 байт | +/–79 228 162 514 264 337 593 543 950 335 без десятичной запятой
+/–7,9228162514264337593543950335 с 28 разрядами справа от десятичной запятой Наименьшее ненулевое число равно +/–0,0000000000000000000000000001 |
Dictionary | Неизвестно | Неизвестно |
Double (число с плавающей запятой двойной точности) | 8 байт | от –1,79769313486231E308 до –4,94065645841247E-324 для отрицательных значений
от 4,94065645841247E-324 до 1,79769313486232E308 для положительных значений |
Integer | 2 байта | от –32 768 до 32 767 |
Long (целое число Long) | 4 байта | от –2 147 483 648 до 2 147 483 647 |
LongLong (целое число LongLong) | 8 байт | от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807
Действительно только для 64-разрядных платформ. |
LongPtr (целое число Long в 32-разрядных системах, целое число LongLong в 64-разрядных системах) | 4 байта в 32-разрядных системах
8 байт в 64-разрядных системах |
от –2 147 483 648 до 2 147 483 647 в 32-разрядных системах
от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 в 64-разрядных системах |
Object | 4 байта | Любая ссылка на Object |
Single (число с плавающей запятой одинарной точности) | 4 байта | от –3,402823E38 до –1,401298E-45 для отрицательных значений
от 1,401298E-45 до 3,402823E38 для положительных значений |
String (переменная длина) | 10 байтов + длина строки | от 0 до приблизительно 2 миллиардов |
String (фиксированная длина) | Длина строки | от 1 до приблизительно 65 400 |
Variant (с числами) | 16 байт | Любое числовое значение до диапазона типа Double |
Variant (с символами) | 22 байта + длина строки (24 байтов в 64-разрядных системах) | Тот же диапазон как для типа String переменной длины |
Определяется пользователем (используя Type) | Число, необходимое для элементов | Диапазон каждого элемента совпадает с диапазоном его типа данных. |
Тип Variant, содержащий массив, требует на 12 байт больше, чем сам массив.
Для массивов данных любого типа требуются 20 байтов памяти плюс 4 байта на каждую размерность массива, плюс количество байтов, занимаемых самими данными. Память, занимаемая данными, может быть вычислена путем умножения количества элементов данных на размер каждого элемента.
Например, данные в одномерном массиве, состоящем из 4 элементов данных Integer размером 2 байта каждый занимают 8 байтов. 8 байтов, необходимых для данных, плюс 24 байта служебных данных составляют 32 байта полной памяти, требуемой для массива. На 64-разрядных платформах массив SAFEARRAY занимает 24 бита (плюс 4 байта на оператор Dim). Элемент pvData является 8-байтным указателем, и он должен находиться в границах 8 байтов.
Тип LongPtr не является настоящим типом данных, так как он преобразуется в тип Long в 32-разрядных средах или в тип LongLong в 64-разрядных средах. Тип LongPtr должен использоваться для представления указателя и обработки значений в операторах Declare и позволяет писать переносимый код, который может выполняться как в 32-разрядных, так и в 64-разрядных средах.
Для преобразования одного типа строковых данных в другой используется функция StrConv.
Преобразование между типами данных
В статье Функции преобразования типов приведены примеры использования следующих функций для приведения выражения к определенному типу данных: CBool, CByte, CCur, CDate, CDbl, CDec, CInt, CLng, CLngLng, CLngPtr, CSng, CStr и CVar.
Ниже приведены страницы соответствующих функций: CVErr, Fix и Int.
Функция CLngLng действительна только для 64-разрядных платформ.
Проверка типов данных
Чтобы проверить типы данных, ознакомьтесь с приведенными ниже функциями.
Возвращаемые значения функции CStr
Если expression | CStr возвращает |
---|---|
Boolean | Строка, содержащая значение True или False. |
Date | Строка, содержащая полный или краткий формат даты, установленный в системе. |
Empty | Строка нулевой длины («»). |
Error | Строка, содержащая слово Error и номер ошибки. |
Null | Ошибка во время выполнения. |
Другое числовое значение | Строка, содержащая число |
См. также
Поддержка и обратная связь
Есть вопросы или отзывы, касающиеся Office VBA или этой статьи? Руководство по другим способам получения поддержки и отправки отзывов см. в статье Поддержка Office VBA и обратная связь.
Источник
When thinking about custom objects / types in VBA most users turn to the VBA Class and create additional modules that quickly start cluttering their VBA Projects. Often these turn out to be simply structures, for the sole purpose of grouping several variables within a single object, bereft of any procedures or functions. VBA does not allow you to create subfolders in your VBA Project, so next time consider creating a VBA Type instead!
Below an example VBA Type name Person as well as usage:
'Declaration of Person Type Type Person name as String surname as String age as Long End Type 'Example usage of created Type Sub TestType Dim p as Person p.name = "Tom" p.surname = "Hanks" p.age = 54 Debug.Print p.name & " " & p.surname & ", age " & p.age 'Result: Tom Hanks, age 54" End Sub
What is a VBA Type and why use it?
What is a VBA Type? The Type statement defines a new user-defined data type containing one or more variables (basic VBA data types, custom Types or objects including custom VBA Classes). VBA Types fill the gap between native basic VBA data types such as Strings, Integers, Booleans etc. and custom objects – VBA Classes. Type declarations can only consist of variables.
Why would you want to use a VBA Type? Why use a cannon to shoot a fly? i.e why create a new VBA Class when all you need to is to group several variables into a new data type. One other thing is that Types don’t need separate VBA Modules and are embedded into existing modules, saving you space and preventing VBA Module cluttering.
Syntax of the VBA Type
The VBA Typed can be defined within VBA Modules, at the beginning, similarly like global variables. The syntax of a VBA Type looks like:
Type TypeName VariableName [as VariableType] '... End Type
VBA Types can:
- Have object such as other VBA Types or VBA Classes as variables
- Contain arrays – both static (predefined size) and dynamic (dynamically sized/resized)
- Be declared within VBA Modules. Types can also be declared within Forms and Classes, however, have to be declared Private (scoped only to Form or Class)
VBA Types can’t:
- Contain procedures or functions
- Be declared as Public in Classes and Forms
VBA Types are treated like other types (not objects) hence does not require to be defined using the Set statement. A Type object can be immediately used after it has been declared.
Type SomeType num as Long '... End Type Sub TestType() Dim myType as SomeType Set myType = New SomeType 'WRONG! myType.num = 10 'OK! End Sub
VBA Type examples
Now that we now the basic let’s look at some VBA Type examples. The Type example below is an excellent example of how much you can “squeeze” into a custom VBA Types.
Type CustomType someString As String threeCharacterStrings As String * 3 someNumber As Long someEnum As CustomEnum someObject As Object predefinedSizeArray(10) As Long dynamicSizedArray() As Long End Type
Now let’s use this newly defined VBA Type:
Enum CustomEnum EnumValue1 EnumValue2 EnumValue3 End Enum Sub TestCustomType() Dim myType As CustomType myType.someString = "Hello there!" myType.threeCharacterStrings = "Hello!" 'Will be trimmed to "Hel" myType.someEnum = EnumValue1 myType.someNumber = 10 Set myType.someObject = CreateObject("Scripting.Dictionary") myType.predefinedSizeArray(0) = 20 ReDim myType.dynamicSizedArray(20) As Long myType.dynamicSizedArray(1) = 100 End Sub
The VBA Type can basically aggregate any VBA Type within – including objects, classes, enums etc.
Types are typically compared to VBA Classes. Similarly most VBA users start by creating a class instead of contemplating the use of a simple VBA Type. Let’s thus compare the 2.
Property | VBA Type | VBA Class |
---|---|---|
Contains variables |
|
|
Contains procedures / functions |
|
|
Need to be defined using the Set statement |
|
|
Can be declared in any module |
|
|
VBA Type vs Module
Consider the reasons for creating new objects. In general I encourage the creation of Types and Classes as this goes in line with Object-Oriented Programming, however sometimes this is an overkill.
Are you creating the new Type / Class to have an easily accessible list of variables? Why not use a VBA Module instead. Simply make sure you variables are Public.
VBA Modules are singletons – objects accessible within the whole project and which variables have a single instance. As you probably know Modules like Classes can have procedures and functions.
VBA Types on the other hand are a simple vehicle of grouping variables into a new data type. Hence, don’t use them as singletons.
Summary
Hopefully, now you will consider using VBA Types when pondering a reason for creating a new custom VBA structure. I encourage you to use VBA Types and VBA Classes more often as you will notice how your code becomes more readable, comprehensive and less redundant. Do read my post on writing shorter VBA code if you want to read more tips on VBA best practices.
In my last week’s Blog post I elaborated on data types and their use in VBA.
Beyond using the readily available native data types we’re all familiar with, such as Integer, Date, String and Boolean, we can define our own, user-defined data type.
What is a User-Defined Data Type?
Simply put, a user-defined data type is a group of variables of native data types.
Here’s an example of grouping together several variables to define a new data type representing (or holding) information about a vehicle:
Type Vehicle
VIN as Long
Make as String
Model as String
Year as Integer
End Type
That’s it! As simple as that.
We can now dimension variables of type Vehicle:
Dim vehLeasedCar as Vehicle
We now have a kind of a hierarchy: a variable of type Vehicle containing sub-variables of different (native) types: Long, String and Integer in this example.
We can think of the sub-variables as properties of the Vehicle-type variable and use the “dot” to address each of these sub-variables. Let’s assign some values to our vehLeasedCar variable:
vehLeasedCar.VIN = 4656418
vehLeasedCar.Make = “Ford”
vehLeasedCar.Model = “Taurus”
vehLeasedCar.Year = 2001
In the same way, we can read the values of our variable (or its sub-variables). Let’s print out our vehicle’s model:
Debug.Print vehLeasedCar.Model
Why use User-Defined Data Types?
I can think of two main reasons to make use of user-defined data types:
- They are suitable for records arrangement in a readable format
- They are very efficient to process, even more than collections
Think of a process that needs to store and manipulate 5,000 vehicles.
One way would be to have a two-dimensional array store a table-like structure of the vehicles. This is valid (and you know how much I love arrays), and even performant. However, it would not be that clear by viewing the code what we are doing. What do you find more telling in your code: arrCars(i,j) or arrCars(i).Model?
Let’s define another data type to store information about an employee:
Private Type TEmployeeRecord
Name As String
DOB As Date
Age As Integer
City As String
Score As Integer
End Type
Consider how readable and self-explanatory the following Sub is, tasked with printing out all data of an employee, passed over as a TEmployeeRecord type variable:
Sub PrintEmployeeReport(employee As TEmployeeRecord)
With employee
Debug.Print .Name, .City, .DOB, .Age, .Score
End With
End Sub
Storing Many Records in an Array
Of course, storing a single record, as in the above examples, is not very helpful. We typically need to store many records of data in our program.
For this, we can combine our own defined data type (record structure) with a one-dimensional array.
To illustrate this in an example, let’s pick up a list of employees with some data on them from an Excel table and arrange it in an array of employees’ records. Our table look like this:
We first use a 2-dimensional array as an interim structure to quickly read the table from the Worksheet:
Dim arrEmployees() As Variant
arrEmployees = ThisWorkbook.Worksheets("Scores").Range("ScoresTable[#Data]").Value
Next, we loop our array and transfer each employee (“row”) in the array into a new, 1-dimentional array, holding employees’ records:
Dim employees(5) As TEmployeeRecord
Dim i As Integer
For i = LBound(arrEmployees, 1) To UBound(arrEmployees, 1)
With employees(i - 1)
.Name = arrEmployees(i, 1)
.DOB = arrEmployees(i, 2)
.Age = arrEmployees(i, 3)
.City = arrEmployees(i, 4)
.Score = arrEmployees(i, 5)
End With
Next i
With that, we have an efficient array of employees to work with.
We can now write a function to return the score of an employee, given a pointer to the array of employees we prepared and the employee name to look for.
Function GetScoreOfEmployee(strName As String, employees() As TEmployeeRecord) As Integer
Dim i As Integer
GetScoreOfEmployee = 0
For i = LBound(employees) To UBound(employees)
With employees(i)
If (.Name = strName) Then
GetScoreOfEmployee = .Score
Exit For
End If
End With
Next i
End Function
See how elegant and clear this function is? We’re looping the employees array, checking each employee name to match strName we’re looking for, returning his score upon a successful match.
Here’s how we can make use of the above function to ask the user for a name of an employee and get his score (we’ll store his score in the intScore variable). We’re making use of the employees array we have populated earlier with our employees:
Dim strName As String
Dim intScore As Integer
strName = InputBox("Name of employee:", "Query Employee Form")
intScore = GetScoreOfEmployee(strName, employees)
If (intScore = 0) Then
MsgBox "Employee not found", vbOKOnly + vbInformation, "Employee Error"
Else
MsgBox strName & "'s score: " & intScore, vbOKOnly + vbInformation, "Employee Score Result"
End If
By the way, if you are not familiar with the InputBox function, I have a detailed Blog post about it for you here. Similarly, the MsgBox function is explained here.
Unfortunately, Excel VBA doesn’t allow us to add user-defined type variables to a collection, therefore we’re missing out on a potentially very useful and efficient data structure. One can argue that if a collection is our best structure to maintain our records, we can implement our records as objects defined in a Class Module instead of a user-defined data type. Yes, I have a series of Blog posts about objects and Class Modules starting right here.
The above examples are featured in my flagship on-line course: Computer Programming with Excel VBA, in case they seemed familiar to you 😉.
Hey, a small request from me to you: please share this Blog post so that we can help more colleagues with Excel VBA.