Шаблонные классы в c: Шаблоны классов в С++ | Уроки С++
16. Шаблоны классов. C++ для начинающих
16. Шаблоны классов
В этой главе описывается, как определять и использовать шаблоны классов. Шаблон — это предписание для создания класса, в котором один или несколько типов либо значений параметризованы. Начинающий программист может использовать шаблоны, не понимая механизма, стоящего за их определениями и конкретизациями. Фактически на протяжении всей этой книги мы пользовались шаблонами классов, которые определены в стандартной библиотеке C++ (например, vector, list и т.д.), и при этом не нуждались в детальном объяснении механизма их работы. Только профессиональные программисты определяют собственные шаблоны классов и пользуются описанными в данной главе средствами. Поэтому этот материал следует рассматривать как введение в более сложные аспекты C++.
Глава 16 содержит вводные и продвинутые разделы. Во вводных разделах показано, как определяются шаблоны классов, иллюстрируются простые способы применения и обсуждается механизм их конкретизации. Мы расскажем, как можно задавать в шаблонах разные виды членов: функции-члены, статические данные-члены и вложенные типы. В продвинутых разделах представлен материал, необходимый для написания приложений промышленного уровня. Сначала мы рассмотрим, как компилятор конкретизирует шаблоны и какие требования в связи с этим предъявляются к организации нашей программы. Затем покажем, как определять специализации и частичные специализации для шаблона класса и для его члена. Далее мы остановимся на двух вопросах, представляющих интерес для проектировщиков: как разрешаются имена в определениях шаблона класса и как можно определять шаблоны в пространствах имен. Завершается эта глава примером определения и использования шаблона класса.
Поделитесь на страничке
Следующая глава >
16. 6. Вложенные типы шаблонов классов. C++ для начинающих
16.6. Вложенные типы шаблонов классов
Шаблон класса QueueItem применяется только как вспомогательное средство для реализации Queue. Чтобы запретить любое другое использование, в шаблоне QueueItem имеется закрытый конструктор, позволяющий создавать объекты этого класса исключительно функциям-членам класса Queue, объявленным друзьями QueueItem. Хотя шаблон QueueItem виден во всей программе, создать объекты этого класса или обратиться к его членам можно только при посредстве функций-членов Queue.
Альтернативный подход к реализации состоит в том, чтобы вложить определение шаблона класса QueueItem в закрытую секцию шаблона Queue. Поскольку QueueItem является вложенным закрытым типом, он становится недоступным вызывающей программе, и обратиться к нему можно лишь из шаблона класса Queue и его друзей (например, оператора вывода). Если же сделать члены QueueItem открытыми, то объявлять Queue другом QueueItem не понадобится.
Семантика исходной реализации при этом сохраняется, но отношение между шаблонами QueueItem и Queue моделируется более элегантно.
Поскольку при любой конкретизации шаблона Queue требуется конкретизировать тем же типом и QueueItem, то вложенный класс должен быть шаблоном. Вложенные классы шаблонов сами являются шаблонами классов, а параметры объемлющего шаблона можно использовать во вложенном:
template class Type
class Queue:
// ...
private:
class QueueItem {
public:
QueueItem( Type val )
: item( val ), next( 0 ) { ... }
Type item;
QueueItem *next;
};
// поскольку QueueItem - вложенный тип,
// а не шаблон, определенный вне Queue,
// то аргумент шаблона Type после QueueItem можно опустить
QueueItem *front, *back;
// . ..
};
При каждой конкретизации Queue создается также класс QueueItem с подходящим аргументом для Type. Между конкретизациями шаблонов QueueItem и Queue имеется взаимно однозначное соответствие.
Вложенный в шаблон класс конкретизируется только в том случае, если он используется в контексте, где требуется полный тип класса. В разделе 16.2 мы упоминали, что конкретизация шаблона класса Queue типом int не означает автоматической конкретизации и класса QueueItemint. Члены front и back — это указатели на QueueItemint, а если объявлены только указатели на некоторый тип, то конкретизировать соответствующий класс не обязательно, хотя QueueItem вложен в шаблон класса Queue. QueueItemint конкретизируется только тогда, когда указатели front или back разыменовываются в функциях-членах класса Queueint.
Внутри шаблона класса можно также объявлять перечисления и определять типы (с помощью typedef):
template class Type, int size
class Buffer:
public:
enum Buf_vals { last = size-1, Buf_size };
typedef Type BufType;
BufType array[ size ];
// . ..
}
Вместо того чтобы явно включать член Buf_size, в шаблоне класса Buffer объявляется перечисление с двумя элементами, которые инициализируются значением параметра шаблона. Например, объявление
Bufferint, 512 small_buf;
устанавливает Buf_size в 512, а last — в 511. Аналогично
Bufferint, 1024 medium_buf;
устанавливает Buf_size в 1024, а last — в 1023.
Открытый вложенный тип разрешается использовать и вне определения объемлющего класса. Однако вызывающая программа может ссылаться лишь на конкретизированные экземпляры подобного типа (или элементов вложенного перечисления). В таком случае имени вложенного типа должно предшествовать имя конкретизированного шаблона класса:
// ошибка: какая конкретизация Buffer?
Buffer::Buf_vals bfv0;
Bufferint,512::Buf_vals bfv1; // правильно
Это правило применимо и тогда, когда во вложенном типе не используются параметры включающего шаблона:
template class T class Q {
public:
enum QA { empty, full }; // не зависит от параметров
QA status;
// . ..
};
#include iostream
int main() {
Qdouble qd;
Qint qi;
qd.status = Q::empty; // ошибка: какая конкретизация Q?
qd.status = Qdouble ::empty; // правильно
int val1 = Qdouble ::empty;
int val2 = Qint ::empty;
if ( val1 != val2 )
cerr "ошибка реализации!" endl;
return 0;
}
Во всех конкретизациях Q значения empty одинаковы, но при ссылке на empty необходимо указывать, какому именно экземпляру Q принадлежит перечисление.
Упражнение 16.8
Определите класс List и вложенный в него ListItem из раздела 13.10 как шаблоны. Реализуйте аналогичные определения для ассоциированных членов класса.
Поделитесь на страничке
Следующая глава >
Как создать статический класс В C++?
считают решение Мэтта Прайса.
- в C++ «статический класс» не имеет смысла. Ближайшая вещь-это класс с только статическими методами и членами.
- использование статических методов ограничит вас.
то, что вы хотите, выражается в семантике C++, чтобы поместить вашу функцию (для нее is функция) в пространстве имен.
изменить 2011-11-11
в C++нет» статического класса». Ближайшей концепцией будет класс с только статическими методами. Например:
// header
class MyClass
{
public :
static void myMethod() ;
} ;
// source
void MyClass::myMethod()
{
// etc.
}
но вы должны помнить, что» статические классы » -это хаки в Java-подобных языках (например, C#), которые не могут иметь функции, не являющиеся членами, поэтому они должны вместо этого перемещать их внутри классов как статические методы.
в C++ то, что вы действительно хотите, это функция, не являющаяся членом, которую вы объявите в пространстве имен:
// header
namespace MyNamespace
{
void myMethod() ;
}
// source
namespace MyNamespace
{
void myMethod()
{
// etc.
}
}
почему это?
В C++, пространство имен является более мощным, чем классы для шаблона «Java static method», потому что:
- статические методы имеют доступ к классам частная символами
- частные статические методы по-прежнему видны (если недоступны) для всех, что несколько нарушает инкапсуляцию
- статические методы не могут быть объявлены вперед
- статические методы не могут быть перегружены пользователем без изменения библиотеки заголовок
- нет ничего, что можно сделать статическим методом, который не может быть сделан лучше, чем (возможно, друг) функция-нечлен в том же пространстве имен
- пространства имен имеют свою собственную семантику (их можно комбинировать, они могут быть анонимными и т. д.)
- etc.
заключение: не копируйте/не вставляйте шаблон Java / C#в C++. В Java/C# шаблон является обязательным. Но в C++, это плохой стиль.
изменить 2010-06-10
был аргумент в пользу статического метода, потому что иногда нужно использовать статическую частную переменную-член.
Я немного не согласен, как показано ниже:
решение «статический частный член»
// HPP
class Foo
{
public :
void barA() ;
private :
void barB() ;
static std::string myGlobal ;
} ;
во-первых, myGlobal называется myGlobal, потому что он по-прежнему является глобальной частной переменной. Взгляд на источник CPP прояснит это:
// CPP
std::string Foo::myGlobal ; // You MUST declare it in a CPP
void Foo::barA()
{
// I can access Foo::myGlobal
}
void Foo::barB()
{
// I can access Foo::myGlobal, too
}
void barC()
{
// I CAN'T access Foo::myGlobal !!!
}
на первый взгляд, тот факт, что свободная функция barC не удается получить доступ к Foo:: myGlobal кажется хорошей вещью с точки зрения инкапсуляции… Это круто, потому что кто-то, глядя на ГЭС, не сможет (если не прибегать к саботажу) получить доступ к Foo::myGlobal.
но если вы посмотрите на это внимательно, вы обнаружите, что это колоссальная ошибка: не только ваша частная переменная должна быть объявлена в HPP (и поэтому видна всему миру, несмотря на то, что она является частной), но вы должны объявить в той же HPP все (как и во всех) функции, которые будут разрешен доступ к нему !!!
так использование частного статического члена похоже на прогулку в обнаженном виде со списком ваших любовников, татуированных на вашей коже : никто не имеет права прикасаться, но каждый может заглянуть. И бонус: каждый может иметь имена тех, кто уполномочен играть с вашими уборными.
private
действительно…
:- D
решение «анонимные пространства имен»
анонимные пространства имен будет преимущество в том, чтобы сделать вещи действительно личными.
во-первых, заголовок HPP
// HPP
namespace Foo
{
void barA() ;
}
Урок 30. Использование шаблонов классов в C++
Урок 30. Использование шаблонов классов в C++
СОЗДАНИЕ ШАБЛОНА КЛАССА
Предположим, к примеру, вы создаете класс массива, в котором есть методы для вычисления суммы и среднего значения хранимых в массиве чисел. Предположим, что вы работаете с массивом типа int, и ваш класс мог бы выглядеть так:
class array
{
public:
— — -array(int size)-
— — -long sum(void)-
— — -int average_value(void)-
— — -void show_array(void)-
— — -int add_value(int)-
private:
— — -int *data-
— — -int size-
— — -int index-
}-
Следующая программа I_ARRAY. CPP использует класс array ддя работы со значениями типа int.
#include <-iostream.h>-
#include <-stdlib.h>-
class array
{
public:
— — -array(int size)-
— — -long sum(void)-
— — -int average_value(void)-
— — -void show_array(void)-
— — -int add_value(int) —
private:
— — -int *data-
— — -int size-
— — -int index-
}-array::array(int size)
{
— — -data = new int [size]-
— — -if (data == NULL)— — -{
— — — — — -cerr <-<- «Недостаточно памяти — программа завершается » <-<- endl-
— — — — — -exit(l)-
— — -}— — -array:: size = size-
— — -array::index = 0-
}long array::sum(void)
{
— — -long sum = 0-
— — -for (int i = 0- i <- index- i++) sum += data[i]-
— — -return(sum)-
}int array::average_value(void)
{
— — -long sum = 0-
— — -for (int i = 0- i <- index- i++) sum += data[i]-
— — -return (sum / index)-
}void array::show_array(void)
{
— — -for (int i = 0- i <- index- i++) cout <-<- data[i] <-<- » «-
font> — — -cout <-<- endl-
}int array::add_value(int value)
{
— — -if (index == size) —return(-1)- // массив полон —
— — -else— — -{
— — — — — -data[index] = value-
— — — — — -index++-
— — — — — -return(0)- // успешно —
— — -} —
}
void main(void)
{
— — -array numbers (100)- // массив из 100 эл-тов —
— — -int i-
— — -for (i = 0- i <- 50- i++) numbers. add_value(i)-
— — -numbers.show_array()-
— — -cout <-<- «Сумма чисел равна » <-<- numbers.sum () <-<- endl-
— — -cout <-<- «Среднее значение равно » <-<- numbers.average_value() <-<- endl-
}
Как видите, программа распределяет 100 элементов массива, а затем заносит в массив 50 значений с помощью метода add_value. В классе array переменная index отслеживает количество элементов, хранимых в данный момент в массиве. Если пользователь пытается добавить больше элементов, чем может вместить массив, функция add_value возвращает ошибку. Как видите, функция average_value использует переменную index для определения среднего значения массива. Программа запрашивает память для массива, используя оператор new, который подробно рассматривается в уроке 31.
Шаблоны классов
По мере того как количество создаваемых вами классов растет, вы обнаруживаете, что некоторый класс, созданный для одной программы (или, возможно, для этой), очень похож на требующийся вам сейчас. Во многих случаях классы могут отличаться только типами. Другими словами, один класс работает с целочисленными значениями, в —то время как требующийся вам сейчас должен работать со значениями типа. float. Чтобы увеличить вероятность повторного использования существующего кода, C++ позволяет вашим программам определять шаблоны классов. Если сформулировать кратко, то шаблон класса —определяет типонезависимый класс, который в дальнейшем служит для создания объектов требуемых типов. Если компилятор C++ встречает объявление объекта, основанное на шаблоне класса, то для построения класса требуемого типа он будет использовать типы, указанные при объявлении. Позволяя быстро создавать классы, отличающиеся только типом, шаблоны классов сокращают объем программирования, что, в свою очередь, экономит ваше время.
Пойдем дальше. Теперь предположим, что вашей программе необходимо работать с массивом значений с плавающей точкой, кроме того, что она работает с целочисленным массивом. Один из способов обеспечить поддержку массивов различных типов состоит в создании разных классов. С другой стороны, используя шаблоны классов, вы можете избавиться от необходимости дублировать классы. Ниже представлен шаблон класса, который создает общий класс array:
template<-class T, class T1>- class array
{
public:
— — -array(int size)-
— — -T1 sum (void)-
— — -T average_value(void)-
— — -void show_array(void)-
— — -int add_value(T)-
private:
— — -T *data-
— — -int size-
— — -int index-
}-
Этот шаблон определяет символы типов T и T1. В случае массива целочисленных значений Т будет соответствовать int, а T1 — long. Аналогичным образом для массива значений с плавающей точкой значения Т и Т1 равны float. Теперь потратьте время, чтобы убедиться, что вы поняли, как компилятор С++ будет подставлять указанные вами типы вместо символов Т и Т1.
Далее, перед каждой функцией класса вы должны указать такую же запись со словом template. Кроме того, сразу же после имени класса вы должны указать типы класса, например array <-T, T1>-::average_value. Следующий оператор иллюстрирует определение функции average_value для этого класса:
template<-class Т, class T1>- Т array<-T, T1>-::average_value(void)
{
— — -T1 sum = 0-
— — -int i- —
— — -for (i = 0- i <- index- i++) sum += data[i] —
— — -return (sum / index)-
}
После создания шаблона вы можете создавать класс требуемого типа, указывая имя класса, а за ним в угловых скобках необходимые типы, как показано ниже:
Имя шаблона //—->- —array —<-int, long>- -numbers (100)- —<——-//Типы шаблона
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —array —<-float, float>- -values(200)-
Программа GENARRAY. CPP использует шаблон класса array для создания двух классов, один из которых работает со значениями типа int, а второй — со значениями типа float.
#include <-iostream.h>-
#include <-stdlib.h>-
template<-class T, class T1>- class array
{
public:
— — -array(int size)-
— — -T1 sum(void)-
— — -T average_value(void)-
— — -void show_array(void)-
— — -int add_value(T)-
private:
— — -T *data-
— — -int size-
— — -int index-
}-template<-class T, class T1>- —array<-T, t1>-::array(int size)
{
— — -data = new T[size]-
— — -if (data == NULL)— — -{
— — — — — -cerr <-<- «Недостаточно памяти — программа завершается» <-<- endl-
— — — — —
-exit(l)-
— — -}— — -array::size = size-
— — -array::index = 0-
}template<-class T, class T1>- Tl array<-T, Tl>-::sum(void)
{
— — -T1 sum = 0-
— — -for (int i = 0- i <- index- i++) sum += data[i]-
— — -return(sum)-
}template<-class T, class T1>- T array<-T, T1>-::average_value(void)
{
— — -Tl sum =0-
— — -for (int i = 0- i <- index- i++) sum += data[i]-
— — -return (sum / index)-
}template<-class T, class T1>- void array<-T, T1>-::show_array(void)
{
— — -for (int i = 0- i <- index- i++) cout <-<- data[i] <-<- » «-
— — -cout <-<- endl-
}template<-class T, class T1>- int array<-T, T1>-::add_value(T value)
{
— — -if (index == size)
— — -return(-1)- // Массив полон —
— — -else— — -{
— — — — — -data[index] = value-
— — — — — -index++-
— — — — — -return(0)- // Успешно —
— — -} —
}void main(void)
{
— — -// Массив из 100 элементов —
— — -array<-int, long>- numbers(100)7 —
— — -// Массив из 200 элементов —
— — -array<-float, float>- values(200)-
— — -int i-
— — -for (i = 0- i <- 50- i++) numbers. add_value(i)-
— — -numbers.show_array()-
— — -cout <-<- «Сумма чисел равна » <-<- numbers.sum () <-<- endl-
— — -cout <-<- «Среднее значение равно » <-<- numbers.average_value() <-<- endl-
— — -for (i = 0- i <- 100- i++) values.add_value(i * 100)-
— — -values.show_array()-
— — -cout <-<- «Сумма чисел равна.» <-<- values.sum() <-<- endl-
— — -cout <-<- «Среднее значение равно —» <-<- values.average_value() <-<- endl-
}
Лучшим способом понять шаблоны классов будет напечатать две копии этой программы. В первой копии замените все символы T и Т1 на int и long. A во второй замените Т и Т1 на float.
Объявление объектов, основанных на шаблоне класса
Для создания объектов с использованием шаблона класса вы просто должны указать имя шаблона класса, за которым между левой и правой угловыми скобками укажите типы, которыми компилятор заменит символы Т, T1, T2 и т. д. Затем ваша программа должна указать имя объекта (переменной) со значениями парам
етров, которые вы хотите передать конструктору класса, как показано ниже:template_class_name<-typel, type2>- object_name( parameter1, parameter2)-
Когда компилятор C++ встречает такое объявление, он создает класс, основанный на указанных типах. Например, следующий оператор использует шаблон класса array для создания массива типа char, в котором хранится 100 элементов:
array<-char, int>- small_numbers(100) —
ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ
Из этого урока вы узнали, что шаблоны классов помогут вам избавиться от дублирования кода программы, если вам необходимы объекты похожих классов, которые отличаются только типом. Поскольку шаблоны классов могут быть сложными, они могут вас смутить. Когда вы определяете ваш класс, начните с определения, как будто бы вы создаете класс для конкретного типа. После того как вы полностью опишете класс, определите какие элементы необходимо изменить, чтобы работать с объектами различных типов. Теперь замените типы этих элементов такими символами, как, например, Т, Т1, Т2 и т.д.
- Программы, представленные в данном уроке, использовали оператор C++ new для динамического (во время выполнения программы) распределения памяти для массива. В уроке 31 вы подробно ознакомитесь с оператором new. Прежде чем перейти к уроку 31, убедитесь, что вы изучили следующее:
- Шаблоны классов позволяют избавиться от дублирования кода для таких классов, чьи объекты отличаются только типом их элементов.
- Для создания шаблона класса предварите определение класса ключевым словом template и символами типов, например Т и T1.
- Далее вы должны предварить определение каждой функции класса таким же оператором с ключевым словом template. Кроме того, укажите типы шаблона между левой и правой угловыми скобками, а выражение в угловых скобках поместите между именем класса и оператором разрешения области видимости, например class_name<-T,T1>-::function_name.
- Для создания класса с использованием шаблона укажите имя класса и замещающие значения для типов между левой и правой угловыми скобками, например class_name<-int, long>- object.
Как создать статический класс В C++?
считают решение Мэтта Прайса.
- в C++ «статический класс» не имеет смысла. Ближайшая вещь-это класс только со статическими методами и членами.
- использование статических методов только ограничит вас.
то, что вы хотите, выражается в семантике C++, чтобы поместить свою функцию (для нее и функция) в пространстве имен.
изменить 2011-11-11
в C++нет» статического класса». Ближайшей концепцией будет класс только со статическими методами. Например:
// header
class MyClass
{
public :
static void myMethod() ;
} ;
// source
void MyClass::myMethod()
{
// etc.
}
но вы должны помнить, что» статические классы » -это хаки в Java-подобных языках (например, C#), которые не могут иметь функции, не являющиеся членами, поэтому они должны вместо этого перемещать их внутри классов как статические методы.
в C++ то, что вы действительно хотите,-это функция, не являющаяся членом, которую вы объявите в пространстве имен:
// header
namespace MyNamespace
{
void myMethod() ;
}
// source
namespace MyNamespace
{
void myMethod()
{
// etc.
}
}
почему это?
В C++, пространство имен является более мощным, чем классы для шаблона «Java static method», потому что:
- статические методы имеют доступ к классам private symbols
- частные статические методы по-прежнему видны (если недоступны) для всех, что несколько нарушает инкапсуляцию
- статические методы не могут быть объявлены вперед
- статические методы не могут быть перегружены пользователем без изменения библиотеки заголовок
- нет ничего, что можно сделать с помощью статического метода, который не может быть сделан лучше, чем (возможно, друг) функция-не член в том же пространстве имен
- имена имеют свою собственную семантику (они могут быть объединены, они могут быть анонимными и т. д.)
- etc.
Вывод: Не копируйте/вставляйте этот шаблон Java/C#в C++. В Java/C# шаблон является обязательным. Но в C++, это плохой стиль.
изменить 2010-06-10
был аргумент в пользу статического метода, потому что иногда нужно использовать статическую закрытую переменную-член.
Я немного не согласен, как показано ниже:
решение «статический частный член»
// HPP
class Foo
{
public :
void barA() ;
private :
void barB() ;
static std::string myGlobal ;
} ;
во-первых, myGlobal называется myGlobal, потому что это все еще глобальная частная переменная. Взгляд на источник CPP прояснит, что:
// CPP
std::string Foo::myGlobal ; // You MUST declare it in a CPP
void Foo::barA()
{
// I can access Foo::myGlobal
}
void Foo::barB()
{
// I can access Foo::myGlobal, too
}
void barC()
{
// I CAN'T access Foo::myGlobal !!!
}
на первый взгляд, факт свободной функции barC не удается получить доступ к Foo:: myGlobal кажется хорошей вещью с точки зрения инкапсуляции. .. Это круто, потому что кто-то, кто смотрит на ГЭС, не сможет (если не прибегнет к саботажу) получить доступ к Foo::myGlobal.
но если вы посмотрите на него внимательно, вы обнаружите, что это колоссальная ошибка: не только ваша частная переменная должна быть объявлена в HPP (и поэтому видна всему миру, несмотря на то, что она является частной), но вы должны объявить в той же HPP все (как и во всех) функции, которые будут авторизован для доступа к нему !!!
так использование частного статического члена похоже на прогулку на улице в обнаженном виде со списком ваших любовников, татуированных на вашей коже : никто не имеет права прикасаться, но каждый может заглянуть. И бонус: каждый может иметь имена тех, кто уполномочен играть с вашими уборными.
private
действительно…
:- D
решение «анонимные пространства имен»
анонимные пространства имен будет преимущество в том, чтобы сделать вещи действительно частными.
во-первых, заголовок ГЭС
// HPP
namespace Foo
{
void barA() ;
}
просто чтобы убедиться, что вы заметили: нет бесполезного объявления Барба или myGlobal. Это означает, что никто, читающий заголовок, не знает, что скрывается за барой.
затем CPP:
// CPP
namespace Foo
{
namespace
{
std::string myGlobal ;
void Foo::barB()
{
// I can access Foo::myGlobal
}
}
void barA()
{
// I can access myGlobal, too
}
}
void barC()
{
// I STILL CAN'T access myGlobal !!!
}
как вы можете видеть, как и объявление так называемого» статического класса», fooA и fooB все еще могут получить доступ к myGlobal. Но больше никто не может. И никто другой вне этого CPP знает, что fooB и myGlobal даже существуют!
в отличие от «статического класса», идущего по обнаженной с ее адресной книгой, татуированной на ее коже,» анонимное » пространство имен полностью одето, которые, казалось бы, лучше инкапсулировал ЕМНИП.
это действительно важно?
если пользователи вашего кода не являются диверсантами (я позволю вам, в качестве упражнения, найти, как можно получить доступ к частной части публичного класса, используя грязный поведение-неопределенный Хак. ..), что такое private
и private
, даже если он виден в private
раздел класса, объявленного в заголовке.
тем не менее, если вам нужно добавить еще одну «частную функцию» с доступом к частному члену, вы все равно должны объявить ее всему миру, изменив заголовок, что является парадоксом, насколько я обеспокоен: если я изменю реализацию моего кода (часть CPP), то интерфейс (часть HPP) не должен меняться. цитировать Леонид :»это инкапсуляция!«
изменить 2014-09-20
когда статические методы классов на самом деле лучше, чем пространства имен с функциями, не являющимися членами?
когда вам нужно сгруппировать функции и передать эту группу в шаблон:
namespace alpha
{
void foo() ;
void bar() ;
}
struct Beta
{
static void foo() ;
static void bar() ;
};
template <typename T>
struct Gamma
{
void foobar()
{
T::foo() ;
T::bar() ;
}
};
Gamma<alpha> ga ; // compilation error
Gamma<Beta> gb ; // ok
gb. foobar() ; // ok !!!
потому что, если класс может быть параметром шаблона, пространства имен не может.
Специализация шаблонов
и частичная специализация на C ++
Специализация шаблона
Во многих случаях при работе с шаблонами вы напишете одну общую версию
для всех возможных типов данных и оставим все как есть — каждый вектор может быть
реализовано точно так же. Идея специализации шаблона
состоит в том, чтобы переопределить реализацию шаблона по умолчанию для обработки определенного типа в
по-другому.
Например, в то время как большинство векторов могут быть реализованы как массивы заданного
типа, вы можете решить сэкономить немного памяти и реализовать векторы bools как
вектор целых чисел, каждый бит которого соответствует одной записи в векторе.Так
у вас может быть два отдельных класса векторов. Первый класс выглядел бы так
этот.
шаблонвектор класса { // функции доступа и т. д. частный: Т * vec_data; // мы будем хранить данные как блок динамически выделяемых // объем памяти int length; // количество используемых элементов int vec_size; // фактический размер vec_data };
Но когда дело доходит до bools, вы, возможно, не захотите этого делать, потому что большинство
системы будут использовать 16 или 32 бита для каждого логического типа, даже если все
что требуется, это один бит.Так что мы можем сделать наш логический вектор похожим на
немного отличается, представляя данные в виде массива целых чисел,
биты, которыми мы манипулируем вручную. (Подробнее о прямом управлении битами см. В разделах Побитовые операторы и
битовые манипуляции в C и C ++.)
Для этого нам все равно нужно указать, что мы работаем с чем-то вроде
шаблон, но на этот раз список параметров шаблона будет пустым:
шаблон <>
а за именем класса следует специализированный тип: class
className.В этом случае шаблон будет выглядеть так:
шаблон <> вектор класса{ // интерфейс частный: беззнаковый int * vector_data; int length; int size; };
Обратите внимание, что было бы вполне разумно, если бы специализированная версия
векторный класс имел другой интерфейс (набор общедоступных методов), чем класс
общий векторный класс — хотя они оба являются векторными шаблонами, они не разделяют
любой интерфейс или любой код.
Стоит отметить, что основная причина специализации в этом
случай должен был обеспечить более компактную реализацию, но вы могли
подумайте о других причинах, по которым это может пригодиться — например, если вы
хотел добавить дополнительные методы в один шаблонный класс в зависимости от его типа, но не
к другим шаблонам.Например, у вас может быть вектор двойников с
метод, который возвращает нецелочисленный компонент каждого элемента, хотя вы
в этом случае можно подумать о наследовании. Нет особой причины
чтобы предотвратить существование вектора двойников без этих дополнительных функций.
Если, однако, вы серьезно относились к проблеме и хотели ее предотвратить, вы
можно сделать это, используя специализацию шаблона.
Другой раз, когда вы, возможно, захотите специализировать определенные шаблоны, может быть, если
у вас есть тип шаблона, который зависит от некоторого поведения, которое не было реализовано
в коллекции классов, которые вы хотите сохранить в этом шаблоне.Например,
если у вас был шаблонный тип sortedVector, который требовал, чтобы оператор> был
определены, и набор классов, написанных кем-то еще, которые не включали никаких
перегруженные операторы, но включили функцию для сравнения, вы можете
специализируйте свой шаблон, чтобы обрабатывать эти классы отдельно.
Шаблон частичной специализации
Частичная специализация шаблона проистекает из тех же мотивов, что и полная
специализация, как описано выше. Однако на этот раз вместо
реализуя класс для одного конкретного типа, вы в конечном итоге реализуете шаблон
это все еще допускает некоторую параметризацию.То есть вы пишете шаблон, который
специализируется на одной функции, но все же позволяет пользователю класса выбирать другие функции
как часть шаблона. Давайте конкретизируем это на примере.
Возвращаясь к идее расширить понятие векторов, чтобы мы могли
sortedVector, давайте подумаем, как это может выглядеть: нам понадобится способ
сравнивая. Хорошо; мы можем просто использовать>, если он реализован, или
специализируйтесь, если это не так. Но теперь предположим, что мы хотели иметь указатели на
объекты в нашем отсортированном векторе.Мы можем отсортировать их по значению
указатели, просто выполняя стандартное> сравнение (у нас будет вектор, отсортированный
от низкого к высокому):
шаблонкласс sortedVector { общественность: пустая вставка (T val) { if (length == vec_size) // длина - это количество элементов { vec_size * = 2; // мы просто проигнорируем возможность переполнения! vec_data = новый T [vec_size]; } ++ длина; // мы собираемся добавить элемент // мы начнем с конца, сдвигая элементы назад, пока не найдем // место для вставки нового элемента int pos; for (pos = length; pos> 0 && val> vec_data [pos - 1]; --pos) { vec_data [pos] = vec_data [pos - 1]; } vec_data [pos] = val; } // другие функции. .. частный: Т * vec_data; int length; int size; };
Теперь обратите внимание, что в приведенном выше цикле for мы проводим прямое сравнение
между элементами типа T. Это нормально для большинства вещей, но, вероятно,
имеет смысл сортировать по фактическому типу объекта, а не по
адрес указателя. Для этого нам нужно написать код, в котором есть эта строка:
for (pos = length; pos> 0 && * val> * vec_data [pos - 1]; --pos)
Конечно, это сломалось бы для любого типа, не являющегося указателем.Что мы хотим здесь делать
используется частичная специализация в зависимости от того, является ли тип указателем или
без указателя (вы могли бы получить несколько уровней указателей, но
мы останемся простыми).
Чтобы объявить частично специализированный шаблон, который обрабатывает любые типы указателей,
мы бы добавили это объявление класса:
шаблонкласс sortedVector { общественность: // те же функции, что и раньше. Теперь функция вставки выглядит так: вставить (T * val) { if (length == vec_size) // длина - это количество элементов { vec_size * = 2; // мы просто проигнорируем возможность переполнения! vec_data = новый T [vec_size]; } ++ длина; // мы собираемся добавить элемент // мы начнем с конца, сдвигая элементы назад, пока не найдем // место для вставки нового элемента int pos; for (pos = length; pos> 0 && * val> * vec_data [pos - 1]; --pos) { vec_data [pos] = vec_data [pos - 1]; } vec_data [pos] = val; } частный: T ** vec_data; int length; int size; };
Здесь следует обратить внимание на несколько синтаксических моментов. Во-первых, наш шаблон
список параметров по-прежнему называет T как параметр, но
объявление теперь имеет T * после имени класса; это сообщает компилятору
чтобы сопоставить указатель любого типа с этим шаблоном вместо более общего
шаблон. Во-вторых, следует отметить, что теперь типом T является тип; Это
это , а не сам указатель. Например, когда вы объявляете
sortedVector, T будет относиться к типу int! Это заставляет некоторых
смысл, если вы думаете об этом как о форме сопоставления с образцом, где T соответствует
введите, если за этим типом стоит звездочка.Это означает, что вам нужно
будьте немного осторожнее в своей реализации: обратите внимание, что vec_data — это T **
потому что нам нужен массив динамического размера, состоящий из указателей.
Вы можете задаться вопросом, действительно ли вы хотите, чтобы ваш тип sortedVector работал
вот так — в конце концов, если вы поместите их в массив указателей, вы бы
ожидайте, что они будут отсортированы по типу указателя. Но есть практическая причина для
делая это: когда вы выделяете память для массива объектов, по умолчанию
конструктор должен быть вызван для создания каждого объекта. Если нет по умолчанию
конструктор существует (например, если каждый объект требует, чтобы некоторые данные были
created), вам нужен список указателей на объекты, но вы, вероятно,
хотите, чтобы они были отсортированы так же, как и сами объекты!
Обратите внимание, кстати, что вы также можете частично специализироваться на шаблоне
аргументы — например, если у вас был тип fixedVector, который позволял пользователю
класса, чтобы указать как тип для хранения, так и длину вектора
(возможно, чтобы избежать затрат на распределение динамической памяти), это может выглядеть
что-то вроде этого:
шаблон <имя типа T, длина без знака> class fixedVector {...};
Затем вы можете частично специализироваться на логических значениях со следующим синтаксисом
шаблон <длина без знака> class fixedVector{...}
Обратите внимание, что, поскольку T больше не является параметром шаблона, он исключен из
список параметров шаблона, оставив только длину. Также обратите внимание, что длина теперь показывает
вверх как часть имени fixedVector (в отличие от общего шаблона
объявление, где после имени ничего не указывается). (Кстати, не будь
удивлен, увидев параметр шаблона, который не является типом: это совершенно верно,
а иногда полезно иметь аргументы шаблона, которые являются целочисленными типами, такими как
как беззнаковый.)
Последняя деталь реализации касается частичных специализаций: как
компилятор выбирает, какую специализацию использовать, если есть комбинация
полностью общие типы, некоторые частичные специализации и, возможно, даже некоторые
полные специализации? Общее практическое правило состоит в том, что компилятор
выберите наиболее конкретную специализацию шаблона — наиболее конкретный шаблон
специализация — это та, аргументы шаблона которой будут приняты
другие объявления шаблонов, но не принимающие все возможные аргументы
что другие шаблоны с таким же именем будут принимать.
Например, если вы решили, что вам нужен sortedVector
отсортированные по ячейкам памяти, вы можете создать полную специализацию
sortedVector, и если вы объявили sortedVector
компилятор выберет эту реализацию вместо менее специфичной частичной
специализация по указателям. Это самый специализированный, поскольку только int *
соответствует полной специализации, а не любому другому типу указателя, например двойному
*, тогда как int * определенно может быть параметром для любого другого
шаблоны.
Статьи по теме
Шаблонные классы
в C ++ Справочная информация о шаблонных классах
Шаблонный
функции Узнать больше о шаблонах функций
шаблонов — cppreference.com
Шаблон — это объект C ++, который определяет одно из следующего:
Шаблоны параметризуются одним или несколькими параметрами шаблона трех видов: параметры шаблона типа, параметры шаблона без типа и параметры шаблона шаблона.
Когда аргументы шаблона предоставляются или выводятся только для шаблонов функций и классов (начиная с C ++ 17), они заменяются параметрами шаблона для получения специализации шаблона, то есть определенного типа или специфическая функция lvalue. Специализации также могут быть указаны явно: полные специализации разрешены для классов, переменных (начиная с C ++ 14) и шаблонов функций, частичные специализации разрешены только для шаблонов классов и шаблонов переменных (начиная с C ++ 14).
Когда на специализацию шаблона класса ссылаются в контексте, который требует полного типа объекта, или когда на специализацию шаблона функции ссылаются в контексте, который требует существования определения функции, шаблон создается экземпляр (код для этого фактически компилируется ), за исключением случаев, когда шаблон уже был явно специализирован или явно создан. Создание экземпляра шаблона класса не создает экземпляров ни одной из его функций-членов, если они также не используются.Во время связывания идентичные экземпляры, созданные разными единицами перевода, объединяются.
Определение шаблона должно быть видимым в точке неявного создания экземпляра, поэтому библиотеки шаблонов обычно предоставляют все определения шаблонов в заголовках (например, большинство расширенных библиотек являются только заголовочными)
[править] Синтаксис
шаблон < список параметров > декларация | (1) | ||||||||
шаблон < список параметров > декларация требуемых условий | (2) | (начиная с C ++ 20) | |||||||
экспорт шаблон < список параметров > декларация | (3) | (до C ++ 11) | |||||||
шаблон < список параметров > концепция имя концепции = выражение-ограничение ; | (4) | (начиная с C ++ 20) | |||||||
| (до C ++ 11) |
За списком параметров шаблона может следовать необязательное предложение requires, которое определяет ограничения для аргументов шаблона. | (начиная с C ++ 20) |
[редактировать] идентификатор шаблона
имя-шаблона < список параметров > | |||||||||
имя-шаблона | — | либо идентификатор, который называет шаблон (в этом случае он называется «простой-шаблон-идентификатор»), либо имя перегруженного шаблона оператора или пользовательского литерального шаблона. |
Простой идентификатор шаблона, который именует специализацию шаблона класса, называет класс.
Идентификатор шаблона, который именует специализацию псевдонима шаблона, называет тип.
Идентификатор шаблона, который называет специализацию шаблона функции, называет функцию.
Идентификатор шаблона действителен, только если
- количество аргументов равно количеству параметров или параметр является пакетом параметров шаблона,
- есть аргумент для каждого невыводимого параметра без упаковки, который не имеет аргумента шаблона по умолчанию,
- каждый аргумент шаблона соответствует соответствующему параметру шаблона,
- подстановка каждого аргумента шаблона в следующие параметры шаблона (если есть) успешна, и
| (начиная с C ++ 20) |
Недопустимый идентификатор простого шаблона является ошибкой времени компиляции, если он не называет специализацию шаблона функции (в этом случае может применяться SFINAE).
templateclass X; struct S { используя type = int; }; используя T1 = X ; // ошибка: слишком много аргументов используя T2 = X <>; // ошибка: нет аргумента по умолчанию для первого параметра шаблона используя T3 = X <1>; // ошибка: значение 1 не соответствует параметру типа используя T4 = X; // ошибка: ошибка замены для второго параметра шаблона используя T5 = X ; // ОК
Когда имя-шаблона идентификатора простого-шаблона именует ограниченный нефункциональный шаблон или ограниченный шаблон-параметр шаблона, но не шаблон элемента, который является членом неизвестной специализации, и все аргументы шаблона в простом -template-id независимы, должны быть удовлетворены связанные ограничения ограниченного шаблона: шаблон | (начиная с C ++ 20) |
Два идентификатора шаблона совпадают, если
- их имена шаблонов относятся к одному и тому же шаблону, и
- соответствующие им аргументы шаблона типа имеют один и тот же тип, и
- соответствующие им аргументы шаблона, не являющиеся типом, после преобразования в тип параметра шаблона эквивалентны аргументу шаблона, и
- соответствующие им аргументы шаблона шаблона относятся к одному и тому же шаблону.
Два одинаковых идентификатора шаблона относятся к одной переменной, (начиная с C ++ 14) классу или функции.
[править] Шаблонный объект
Шаблонная сущность (или, в некоторых источниках, «темплоид») — это любая сущность, которая определена (или, для лямбда-выражения, создана) в определении шаблона. Все нижеперечисленные сущности являются шаблонными:
- шаблон класса / функции / переменной (начиная с C ++ 14)
- член шаблонной сущности (например, не являющаяся шаблоном функция-член шаблона класса)
- перечислитель перечисления, который является шаблонным объектом
- любая сущность, определенная или созданная внутри шаблонной сущности: локальный класс, локальная переменная, функция друга и т. Д.
- тип закрытия лямбда-выражения, которое появляется в объявлении шаблонной сущности
Например, в
templatestruct A {void f () {}};
функция A :: f
не является шаблоном функции, но по-прежнему считается шаблонной.
классов герметичности в C ++ — CodeProject
Введение
Некоторые недавние языки, такие как C # и Java, позволяют легко запечатать классы, используя ключевое слово, например, sealed или final соответственно. В C ++ нет такого ключевого слова для этой цели. Однако это все же можно сделать, используя уловку. При использовании виртуального наследования список инициализации конструктора самого производного класса напрямую вызывает конструктор виртуального базового класса. Это означает, что если мы можем скрыть доступ к конструктору виртуального базового класса, то мы сможем предотвратить создание от него любого класса.Это имитирует эффект запечатывания.
Попытка решения № 1
Чтобы упростить печать классов, мы можем написать файл заголовка Sealed.h следующим образом:
класс SealedBase
{
защищено:
SealedBase ()
{
}
};
#define Запечатанная частная виртуальная база SealedBase
Теперь, чтобы запечатать класс, скажем Penguin
, нам просто нужно наследовать его от Sealed
, например:
#include "Sealed. h"
класс Пингвин: Запечатанный
{
};
Вот и все. Penguin
теперь закрытый класс. Давайте попробуем получить другой класс, BigZ
(Surf’s Up (2007), кто-нибудь?) От Penguin
:
класс BigZ: Penguin
{
};
BigZ bigZ;
Создание экземпляра объекта BigZ
должно привести к ошибке компилятора. Компилятор MSVC ++ 2005 выдает следующее сообщение об ошибке:
Ошибка
C2248: «SealedBase :: SealedBase»: невозможно получить доступ к недоступному члену
объявлен в классе SealedBase
Серьезный недостаток
Все вроде работает нормально.Однако один из моих коллег-программистов, Анджело Рохит, указал мне, что в этом методе есть серьезный недостаток. Анджело говорит, что если BigZ
происходит от Penguin
и Sealed
, то можно будет создавать объекты BigZ
:
.
класс BigZ: Penguin, Sealed
{
};
BigZ bigZ;
Почему это происходит? BigZ
происходит от Sealed
точно так же, как Penguin
, что означает, что теперь у него есть доступ к конструктору Sealed
. А поскольку Sealed
фактически унаследован как Penguin
, так и BigZ
, существует только одна его копия, которая теперь также доступна для BigZ
. Облом. Нам нужен механизм, с помощью которого BigZ
вынужден вызывать конструктор класса, к которому у него нет доступа.
Попытка решения № 2
Поразмыслив над этим некоторое время, я понял, что если мы сможем каким-то образом генерировать разные базовые классы каждый раз, когда будет производиться Sealed
, тогда это будет работать.
Давайте перепишем заголовок Sealed.h , чтобы он выглядел так:
шаблон
класс SealedBase
{
защищено:
SealedBase ()
{
}
};
#define Запечатанная частная виртуальная база SealedBase <__COUNTER__>
Что это делает? SealedBase
теперь является шаблонным классом, который принимает целое число в качестве аргумента. __COUNTER__
— это предопределенный макрос, который расширяется до целого числа, начинающегося с 0 и увеличивающегося на 1 каждый раз, когда он используется в компиляции.Таким образом, каждый раз, когда Sealed
является производным от, он генерирует новый класс SealedBase
, используя инкрементное число, до которого расширяется __COUNTER__
.
Теперь вернемся к нашему классу BigZ
, который происходит от Penguin
и Sealed
:
.
класс BigZ: Penguin, Sealed
{
};
BigZ bigZ;
Однако на этот раз BigZ
не может уйти от компилятора. Penguin
происходит от SealedBase
и BigZ
происходит от SealedBase
, где number1
и number2
— два неидентичных целых числа.Итак, теперь BigZ
должен вызвать конструктор SealedBase
, к которому у него нет доступа.
Компилятор MSVC ++ 2005 выдает следующее сообщение об ошибке:
Ошибка
C2248: «SealedBase :: SealedBase»: невозможно получить доступ к недоступному элементу
объявлен в классе SealedBase
1> с
1> [
1> Т = 0
1>]
Проблемы переносимости
Однако вы можете подумать, что, поскольку в нашей реализации мы используем специальный предопределенный макрос __COUNTER__
, этот код не переносится.Что ж, он поддерживается MSVC ++ (который я использовал для тестирования приведенного выше кода), а также GCC (http://www.gnu.org/software/gcc/gcc-4.3/changes.html).
А как же компиляторы, которые этого не делают?
Портативное решение
Немного подумав, я пришел к следующему:
In Sealed.h :
шаблон <класс T>
класс SealedBase
{
защищено:
SealedBase ()
{
}
};
#define Sealed (_CLASS_NAME_) частная виртуальная база SealedBase <_CLASS_NAME_>
А для пломбы класс:
#include "Запечатанный. час"
class Penguin: Sealed (Пингвин)
{
};
При запечатывании класса нам нужно упомянуть имя этого класса в макросе Sealed
. Это позволяет макросу Sealed
создать новую версию SealedBase
. Это менее элегантно, чем просто быть производным от Sealed
, но более переносимо, что делает его хорошей альтернативой для компиляторов, которые не поддерживают предопределенный макрос __COUNTER__
.
Заключительные слова
Люди, использующие MSVC ++ или GCC, могут просто использовать попытку решения №2, так как она более чистая.Люди, использующие другие компиляторы, могут использовать Portable Solution. Если у вас есть какие-либо вопросы, предложения, улучшения или просто хотите поздороваться, напишите мне.
Спасибо за чтение!
Фрэнсис Ксавье
Список литературы
- C ++ Q&A: режим просмотра списка, SetForegroundWindow и защита классов
- Владислав Лазаренко: «[boost] Sealed C ++ class»
История
- 2 nd Сентябрь 2009 г . : Начальная публикация
Что такое класс шаблонов в C ++?
Что такое шаблонный класс в C ++?
Класс шаблона, как следует из названия, является шаблоном для классов.C ++ дает нам возможность создать класс, который будет служить планом / шаблоном для будущих классов. Класс шаблона будет иметь общие переменные и методы типа «T», которые позже можно будет настроить для использования с другими типами данных в соответствии с требованиями.
Определение
Согласно стандартному определению, шаблонный класс в C ++ — это класс, который позволяет программисту работать с универсальными типами данных. Это позволяет использовать класс для множества различных типов данных в соответствии с требованиями без необходимости переписывать для каждого типа.
Понимание класса шаблонов в C ++
Если для лучшего понимания мы рассмотрим реальный пример класса шаблона, то мы можем рассматривать его как образец. Если застройщик недвижимости проектирует поселок, он подготавливает план квартир, который включает общие характеристики, такие как план этажа, расположение дверей, окон и т. Д. Этот план можно рассматривать как шаблонный класс, который даст нам общий представление о том, как квартира будет выглядеть с большой картины.Это можно использовать для проектирования отдельных квартир, которые можно настроить в соответствии с предпочтениями владельца, которые будут специфичными для этой квартиры, но общий шаблон останется общим для всего городка.
Класс шаблона работает по тому же принципу. Если мы разрабатываем корпоративное приложение, у него будет несколько сущностей, которые будут представлять классы. У каждого класса будут свои специфические свойства и методы. Однако можно разработать шаблон, который сможет вставить эти сущности в базу данных.Мы будем использовать этот пример в следующих разделах этой статьи. Но если мы не будем использовать шаблонный класс, нам придется писать отдельные классы для операций Create, Retrieve, Update, Delete. Однако, используя шаблонный класс, мы можем выполнить эту работу, написав только один класс, тем самым сократив много времени и исключив возможность большого количества избыточного повторяющегося кода.
Как шаблонный класс в C ++ упрощает работу?
При работе с корпоративным приложением в большинстве случаев программисты сталкиваются со сценарием, в котором структура программы усложняется по мере увеличения количества классов модели.Сложность становится еще больше, когда мы реализуем концепции OOPS, такие как наследование и полиморфизм. В таких сценариях очень удобны классы шаблонов, в которых вы можете сократить количество строк кода, которые хотите написать для
классов и объектов C ++
Классы / объекты C ++
C ++ — объектно-ориентированный язык программирования.
Все в C ++ связано с классами и объектами вместе с их атрибутами и
методы. Например: в реальной жизни автомобиль — это объект .Автомобиль имеет атрибутов , таких как вес и цвет, и
методы , такие как привод и тормоз.
Атрибуты и методы — это в основном переменных и
функции , принадлежащие классу. Их часто называют
«ученики класса».
Класс — это определяемый пользователем тип данных, который мы можем использовать в нашей программе, и он
работает как конструктор объектов или «план» для создания объектов.
Создать класс
Чтобы создать класс, используйте ключевое слово class
:
Пример
Создайте класс под названием « MyClass
»:
class MyClass {
// Класс
public:
// Спецификатор доступа
int myNum; //
Атрибут (переменная типа int)
string myString; //
Атрибут (строковая переменная)
};
Объяснение примера
- Ключевое слово
class
используется для создания классаMyClass
. - Ключевое слово
public
— это спецификатор доступа , который указывает, что члены (атрибуты и методы) класса доступны извне. Вы узнаете больше о спецификаторах доступа позже. - Внутри класса есть целочисленная переменная
и строковая переменная
myNummyString
. Когда объявлены переменные
внутри класса они называются атрибутами . - Наконец, завершите определение класса точкой с запятой
;
.
Создать объект
В C ++ объект создается из класса. Мы уже создали класс с именем MyClass
,
так что теперь мы можем использовать это для создания объектов.
Чтобы создать объект MyClass
, укажите
имя класса, за которым следует имя объекта.
Для доступа к атрибутам класса ( myNum
и myString
) используйте точечный синтаксис (.
)
по объекту:
Пример
Создайте объект с именем « myObj
» и откройте
атрибуты:
class MyClass {// Класс
public:
// Спецификатор доступа
int myNum; //
Атрибут (переменная типа int)
string myString; //
Атрибут (строковая переменная)
};
int main () {
MyClass myObj ;
// Создание объекта MyClass
// Доступ к атрибутам и установка значений
myObj.myNum
= 15;
myObj.myString = «Немного текста»;
// Распечатать значения атрибутов
cout << myObj.myNum << "\ n";
cout << myObj.myString;
возврат 0;
}
Пример запуска »
Несколько объектов
Можно создать несколько объектов одного класса:
Пример
// Создаем класс Car с некоторыми атрибутами
class Car {
public:
string brand; Модель струны
;
внутр.
год;
};
int main () {
// Создаем объект Car
Car carObj1;
carObj1.марка = «BMW»;
carObj1.model = «X5»;
carObj1.year = 1999;
// Создаем еще один объект Car
Car
carObj2;
carObj2.brand = «Форд»;
carObj2.model =
«Мустанг»;
carObj2.year = 1969;
// Печать
значения атрибутов
cout << carObj1.brand
<< "" << carObj1.model << "" << carObj1.year << "\ n";
cout <<
carObj2.brand << "" << carObj2.модель << "" << carObj2.year << "\ n";
возврат 0;
}
Пример запуска »
Статические переменные в классах и методах шаблонов — Блог по программированию на C и C ++ | Фэй Уильямс
Это последний пост в серии статей о статических переменных, и в нем я расскажу о том, в какое последнее место мы можем их поместить — в классы или функции шаблонов.
Если вы следовали инструкциям и хорошо понимаете, как работают классы шаблонов, вы почти наверняка сможете сделать вывод, как статические переменные будут работать в этой ситуации.
По сути, поскольку класс шаблона является именно этим шаблоном, любые объявленные в нем статические переменные создаются только в том случае, если класс шаблона фактически используется для создания типа.
Давайте быстро рассмотрим основы того, что такое шаблонный класс, тогда способ поведения статических переменных станет более ясным.
Класс шаблона определяется как общий класс , который может использоваться для любого конкретного типа. Они обычно являются контейнерами или коллекциями (например, вектор или класс карты).Они создаются и кодируются таким образом, чтобы вы могли создать экземпляр класса, используя любой из множества различных типов: целые числа, строки или даже другие классы, которые вы создали; и вам не нужно переписывать класс шаблона, чтобы учесть разницу.
Видеть шаблонный класс в коде — лучший способ увидеть, что это на самом деле означает на практике.
В приведенном ниже примере программы показаны класс шаблона и принадлежащая ему статическая переменная.
#includeшаблон <класс T> Коллекция классов { общественность: статическое целое число; // статическое объявление Коллекция() { count ++; } }; // Мы инициализируем статику следующим образом: шаблон <класс T> int Collection :: count = 0; int main () { Коллекция v; Коллекция w; Коллекция x; Коллекция y; Коллекция z; std :: cout << Коллекция :: count << std :: endl; std :: cout << Коллекция :: count << std :: endl; std :: cout << Коллекция :: count << std :: endl; возврат 0; }
Результат вышеупомянутой программы:
3 1 1
Почему?
Сначала мы создаем пять объектов из нашего класса шаблона - три целых, один символ и один с плавающей точкой.
Целые числа одного типа имеют общую статическую переменную count . Каждый раз, когда создается класс шаблона int, счетчик увеличивается.
Объекты float и char имеют собственную статическую переменную count , потому что они разных типов. Если бы вы создали больше объектов типа char и float, счетчик увеличился бы и для этих конкретных типов.
Вот и все. Очень простой класс шаблона с одной статикой, которая создается для каждого другого типа создаваемого класса шаблона.
Хорошо, а как насчет статики в методах класса шаблона?
Они работают точно так же, как статические переменные в методах обычного класса, но, как и выше, статика создается для каждого другого типа класса шаблона, который создается.
А статика в шаблонных функциях?
Опять же, они работают точно так же, как статические переменные в обычных функциях, но статические переменные создаются для каждого отдельного типа шаблонной функции, экземпляр которой создается.