Статические переменные

Статические переменные-члены класса

Из урока №51 мы узнали, что статические переменные сохраняют свои значения и не уничтожаются даже после выхода из блока, в котором они объявлены, например:

#include <iostream>

int generateID()
{
static int s_id = 0;
return ++s_id;
}

int main()
{
std::cout << generateID() << ‘\n’;
std::cout << generateID() << ‘\n’;
std::cout << generateID() << ‘\n’;

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#include <iostream>

intgenerateID()

{

staticints_id=;

return++s_id;

}

intmain()

{

std::cout<<generateID()<<‘\n’;

std::cout<<generateID()<<‘\n’;

std::cout<<generateID()<<‘\n’;

return;

}

Результат выполнения программы:

Обратите внимание, сохраняет свое значение после каждого вызова функции generateID(). Ключевое слово static имеет другое значение, когда речь идет о глобальных переменных — оно предоставляет им внутреннюю связь (что ограничивает их видимость/использование за пределами файла, в котором они определены)

Поскольку использование глобальных переменных — это зло, то ключевое слово static в этом контексте используется не очень часто

Ключевое слово static имеет другое значение, когда речь идет о глобальных переменных — оно предоставляет им внутреннюю связь (что ограничивает их видимость/использование за пределами файла, в котором они определены). Поскольку использование глобальных переменных — это зло, то ключевое слово static в этом контексте используется не очень часто.

В языке C++ ключевое слово static можно использовать в классах: статические переменные-члены и статические методы. Мы поговорим о статических переменных-членах на этом уроке, а о статических методах на следующем.

Прежде чем мы перейдем к ключевому слову static с переменными-членами класса, давайте сначала рассмотрим следующий класс:

#include <iostream>

class Anything
{
public:
int m_value = 3;
};

int main()
{
Anything first;
Anything second;

first.m_value = 4;

std::cout << first.m_value << ‘\n’;
std::cout << second.m_value << ‘\n’;

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

#include <iostream>
 

classAnything

{

public

intm_value=3;

};

intmain()

{

Anything first;

Anything second;

first.m_value=4;

std::cout<<first.m_value<<‘\n’;

std::cout<<second.m_value<<‘\n’;

return;

}

При создании объекта класса, каждый объект получает свою собственную копию всех переменных-членов класса. В этом случае, поскольку мы объявили два объекта класса Anything, у нас будет две копии : и . Это разные значения, следовательно, результат выполнения программы:

Переменные-члены класса можно сделать статическими, используя ключевое слово static. В отличие от обычных переменных-членов, статические переменные-члены являются общими для всех объектов класса. Рассмотрим следующую программу:

#include <iostream>

class Anything
{
public:
static int s_value;
};

int Anything::s_value = 3;

int main()
{
Anything first;
Anything second;

first.s_value = 4;

std::cout << first.s_value << ‘\n’;
std::cout << second.s_value << ‘\n’;
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

#include <iostream>
 

classAnything

{

public

staticints_value;

};

intAnything::s_value=3;

intmain()

{

Anything first;

Anything second;

first.s_value=4;

std::cout<<first.s_value<<‘\n’;

std::cout<<second.s_value<<‘\n’;

return;

}

Результат выполнения программы:

Поскольку является статической переменной-членом, то она является общей для всех объектов класса Anything. Следовательно, — это та же переменная, что и . Вышеприведенная программа показывает, что к значению, которое мы установили через первый объект, можно получить доступ и через второй объект.

Последовательность шагов и уже готовая шаблонная магия

Итак, нам нужно иметь класс с несколькими наборами методов. Содержимое этих наборов должно откуда-то взяться. Откуда?

В языке D мы могли бы воспользоваться и определить разные части класса в зависимости от разных условий. В каком-нибудь Ruby мы могли бы подмешать методы в свой класс посредством метода include. Но мы в C++, в котором пока наши возможности сильно ограничены: мы можем либо определить метод/атрибут прямо внутри класса, либо можем унаследовать метод/атрибут из какого-то базового класса.

Определить разные методы/атрибуты внутри класса в зависимости от какого-то условия мы не можем, т.к. C++ный — это не D-шный . Следовательно, остается только наследование.

В C++ мы можем определить несколько базовых классов, от которых мы затем отнаследуем . А выбор того или иного базового класса уже будем делать в зависимости от значений параметров шаблона, посредством std::conditional.

Но фокус в том, что нам потребуется не просто набор базовых классов, а небольшая цепочка наследования. В ее начале будет класс, который будет определять общую функциональность, которая потребуется в любом случае. Далее будут базовые классы, которые будут определять логику поведения «умного указателя». А уже затем будет класс, который определит нужные getter-ы. В таком порядке мы и рассмотрим реализованные классы.

Нашу задачу упрощает то, что в SObjectizer-е уже есть готовая шаблонная магия, , а также . Поэтому в реализации мы эту готовую магию будем просто использовать и не станем погружаться в детали ее работы.

2 Статические классы

Статические вложенные классы можно использовать вне своего родительского класса. Если у такого класса стоит модификатор доступа public, его можно использовать в любом месте программы. Такие классы фактически ничем не отличаются от любого обычного класса. Хотя есть пара отличий.

Имя класса

Если вы хотите обратиться к статическому вложенному классу не из его класса-родителя, а из другого места в программе, вам нужно будет указать имя класса: оно состоит из имени класса родителя и имени вложенного класса. Общий вид этого имени такой:

Примеры:

Класс родитель Вложенный класс Полное имя вложенного класса

Если вложенный класс имеет свой вложенный класс, их имена просто склеиваются через точку.

Кстати, вы уже работали с вложенным классом — это . Если вы хотите получить множество пар элементов из объекта , то метод вернет вам множество пар типа .

Вот запись — это и есть внешний и внутренний классы.

Создание объекта

Создать объект вложенного статического класса очень легко. Выглядит это так:

Все, как и с обычными классами, только имя двойное.

Обращение к статическим методам

Если у статического класса есть статические методы, обращаться к ним можно точно так же, как к статическим методам обычных классов (только имя класса теперь двойное).

Обращение к статическим переменным

Обращаться к публичным статическим переменным вложенного класса тоже легко:

Статические члены не связаны с объектами класса

Хотя вы можете получить доступ к статическим членам через разные объекты класса (как в примере, приведенном выше), но, оказывается, статические члены существуют, даже если объекты класса не созданы! Подобно глобальным переменным, они создаются при запуске программы и уничтожаются, когда программа завершает свое выполнение.

Следовательно, статические члены принадлежат классу, а не объектам этого класса. Поскольку существует независимо от любых объектов класса, то доступ к нему осуществляется напрямую через имя класса и оператор разрешения области видимости (в данном случае, через ):

#include <iostream>

class Anything
{
public:
static int s_value; // объявляем статическую переменную-член
};

int Anything::s_value = 3; // определяем статическую переменную-член

int main()
{
// Примечание: Мы не создаем здесь никаких объектов класса Anything

Anything::s_value = 4;
std::cout << Anything::s_value << ‘\n’;
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

#include <iostream>
 

classAnything

{

public

staticints_value;// объявляем статическую переменную-член

};

intAnything::s_value=3;// определяем статическую переменную-член

intmain()

{

// Примечание: Мы не создаем здесь никаких объектов класса Anything

Anything::s_value=4;

std::cout<<Anything::s_value<<‘\n’;

return;

}

В вышеприведенном фрагменте, доступ к осуществляется через имя класса, а не через объект этого класса

Обратите внимание, мы даже не создавали объект класса Anything, но мы все равно имеем доступ к и можем использовать эту переменную-член

Статический блок

Если вам нужно выполнить вычисления для инициализации статических переменных, вы можете объявить статический блок, который выполняется ровно один раз, когда класс загружается впервые. Как использовать статический блок:

// Java program to demonstrate the use of static blocks
import java.util.*;
public class BlockExample{
// static variable
static int j = 10;
static int n;

// static block
static {
System.out.println("Static block initialized.");
n = j * 8;
}

public static void main(String[] args)
{
System.out.println("Inside main method");
System.out.println("Value of j : "+j);
System.out.println("Value of n : "+n);
}
}

Когда вы выполняете вышеуказанную программу, статический блок инициализируется и отображает значения инициализированных переменных.

Вывод:

Static block initialized
Inside main method
Value of j:10
Value of n : 80

Статические методы не имеют указателя *this

У статических методов есть две интересные особенности.

Во-первых, поскольку статические методы не привязаны к объекту, то они не имеют скрытого указателя *this! Здесь есть смысл, так как указатель *this всегда указывает на объект, с которым работает метод. Статические методы могут не работать через объект, поэтому и указатель *this не нужен.

Во-вторых, статические методы могут напрямую обращаться к другим статическим членам (переменным или функциям), но не могут напрямую обращаться к нестатическим членам. Это связано с тем, что нестатические члены принадлежат объекту класса, а статические методы — нет!

СинтаксисSyntax

ПараметрыParameters

константное выражениеconstant-expressionЦелочисленное константное выражение, которое можно преобразовать в логическое значение.An integral constant expression that can be converted to a Boolean. Если вычисленное выражение равно нулю (false), то отображается параметр строкового литерала и компиляция завершается ошибкой.If the evaluated expression is zero (false), the string-literal parameter is displayed and the compilation fails with an error. Если выражение не равно нулю (true), объявление не оказывает никакого влияния.If the expression is nonzero (true), the declaration has no effect.

строковый литералstring-literalСообщение, которое отображается, если параметр константного выражения равен нулю.An message that is displayed if the constant-expression parameter is zero. Сообщение представляет собой строку символов в базовой кодировке компилятора; то есть не многобайтовые или расширенные символы.The message is a string of characters in the base character set of the compiler; that is, not multibyte or wide characters.

Объявление и определение переменной.

В глобальном контексте переменная сначала требует объявления. Таким образом, компилятор будет знать её имя и тип. Определение переменной требует выделения под неё памяти и инициализации. Посмотрите следующий код. Он абсолютно легален и должен работать по стандарту

#include <conio.h> #include <stdio.h> int Global; //Объявили переменную int Global = 20; //Определили переменную void main() { printf(«%d», Global); getch(); }

Теперь, что будет, если одновременно объявить переменную и инициализировать её. Это определение переменной, которое требует её объявления

int Global = 20;

Следующая программа не скомпилируется

#include <conio.h> #include <stdio.h> extern int Global; void main() { Global = 30; printf(«%d», Global); getch(); }

Это связано с тем, что отсутствует определение переменной. Если определить переменную внутри main, то это будет уже другой экземпляр переменной, которая будет расположена на стеке. Вообще, при работе с одним файлом использование extern переменных не оправдано. Рассмотрим ситуацию, когда у нас имеются ещё два файла – заголовочный File1.h и File1.c. В заголовочном файле объявим extern переменную Global

#ifndef _FILE1_H_ #define _FILE1_H_ extern int Global; #endif

в файле исходного кода определим её

#include «File1.h» int Global = 100;

После подключения файла File1.h можно использовать эту переменную в файле main.c, при этом гарантировано, что существует только один экземпляр этой переменной для всех файлов проекта

#include <conio.h> #include <stdio.h> #include «File1.h» void main() { printf(«%d\n», Global); getch(); }

Если теперь определим функцию, которая изменяет эту переменную, то все функции из всех файлов будут видеть эти изменения.

#ifndef _FILE1_H_ #define _FILE1_H_ #include <stdio.h> extern int Global; void changeAndPrint(); #endif #include «File1.h» int Global = 100; void changeAndPrint() { printf(«from File1: Global = %d\n», Global); Global = 1234; printf(«changed to %d\n», Global); } #include <conio.h> #include <stdio.h> #include «File1.h» void main() { Global = 567; printf(«From main: Global = %d\n», Global); changeAndPrint(); printf(«From main: Global = %d\n», Global); getch(); }

Вывод

Свойства

В отличие от обычных свойств, изменение значения статического свойства во время выполнения программы повлияет на все экземпляры содержащего свойство класса. Даже на те, для которых еще не созданы экземпляры. Таким образом, статические свойства можно рассматривать в качестве «констант изменяемого класса». На статические свойства можно ссылаться только с помощью оператора разрешения области видимости.

Благодаря природе статических свойств их можно использовать для реализации шаблона одиночка (Singleton). Одиночка содержит один и тот же экземпляр класса на протяжении всего выполнения программы.

В данном примере первый вызов  создает и назначает экземпляр для  и возвращает его. Каждый последующий вызов будет возвращать один и тот же экземпляр , ранее присвоенный :

Статический членыStatic Members

Нестатический класс может содержать статические методы, поля, свойства или события.A non-static class can contain static methods, fields, properties, or events. Статический член вызывается для класса даже в том случае, если не создан экземпляр класса.The static member is callable on a class even when no instance of the class has been created. Доступ к статическому члены всегда выполняется по имени класса, а не экземпляра.The static member is always accessed by the class name, not the instance name. Существует только одна копия статического члена, независимо от того, сколько создано экземпляров класса.Only one copy of a static member exists, regardless of how many instances of the class are created. Статические методы и свойства не могут обращаться к нестатическим полям и событиям в их содержащем типе, и они не могут обращаться к переменной экземпляра объекта, если он не передается явно в параметре метода.Static methods and properties cannot access non-static fields and events in their containing type, and they cannot access an instance variable of any object unless it’s explicitly passed in a method parameter.

Более привычно объявление нестатического класса с несколькими статическими членами, чем объявление всего класса как статического.It is more typical to declare a non-static class with some static members, than to declare an entire class as static. Статические поля обычно используются для следующих двух целей: хранение счетчика числа созданных объектов или хранение значения, которое должно совместно использоваться всеми экземплярами.Two common uses of static fields are to keep a count of the number of objects that have been instantiated, or to store a value that must be shared among all instances.

Статические методы могут быть перегружены, но не переопределены, поскольку они относятся к классу, а не к экземпляру класса.Static methods can be overloaded but not overridden, because they belong to the class, and not to any instance of the class.

Несмотря на то, что поле не может быть объявлено как , поле const по своему поведению является статическим.Although a field cannot be declared as , a const field is essentially static in its behavior. Он относится к типу, а не к экземплярам типа.It belongs to the type, not to instances of the type. Поэтому к полям можно обращаться с использованием той же нотации , что и для статических полей.Therefore, fields can be accessed by using the same notation that’s used for static fields. Экземпляр объекта не требуется.No object instance is required.

C# не поддерживает статические локальные переменные (то есть переменные, объявленные в области действия метода).C# does not support static local variables (that is, variables that are declared in method scope).

Для объявления статических методов класса используется ключевое слово перед возвращаемым типом члена, как показано в следующем примере:You declare static class members by using the keyword before the return type of the member, as shown in the following example:

Статические члены инициализируются перед первым доступом к статическому члену и перед вызовом статического конструктора, если таковой имеется.Static members are initialized before the static member is accessed for the first time and before the static constructor, if there is one, is called. Для доступа к члену статического класса следует использовать имя класса, а не имя переменной, указывая расположение члена, как показано в следующем примере:To access a static class member, use the name of the class instead of a variable name to specify the location of the member, as shown in the following example:

Если класс содержит статические поля, должен быть указан статический конструктор, который инициализирует эти поля при загрузке класса.If your class contains static fields, provide a static constructor that initializes them when the class is loaded.

Вызов статического метода генерирует инструкцию вызова в промежуточном языке Microsoft (MSIL), в то время как вызов метода экземпляра генерирует инструкцию , которая также проверяет наличие ссылок на пустые объекты.A call to a static method generates a call instruction in Microsoft intermediate language (MSIL), whereas a call to an instance method generates a instruction, which also checks for null object references. Однако в большинстве случаев разница в производительности двух видов вызовов несущественна.However, most of the time the performance difference between the two is not significant.

2 Отличие статических и нестатических переменных

Нестатические (обычные) переменные класса объявляются точно так же, как статические, только без ключевого слова .

Чем же отличаются обычные и статические переменные?

Обычные переменные класса привязаны к объектам своего класса (экземплярам класса), статические переменные — к статическому объекту класса.

Если экземпляров класса несколько, в каждом из них существует своя копия нестатических (обычных) переменных класса. Статические переменные класса всегда находятся внутри статического объекта класса и существуют только в одном экземпляре.

Обращаться к обычным переменным класса (полям класса) можно только имея ссылку на объект класса. Ну или в методах внутри этого же класса.

Пример:

Обращение к полю класса с использованием ссылки на объект класса

Обращаться к статическим переменным можно откуда угодно (с учетом модификаторов видимости): из обычных методов, из статических методов того же класса, из методов других классов и т.п.

Пример:

Обращение к статическому полю класса не используя ссылку на объект класса

Устройство в памяти:

Допустим, у нас есть класс с 4 полями: два статических, а два — нет.

Сразу после загрузки класса

Когда Java-машина завершит загрузку класса , в памяти у нас будет наблюдаться такая картина:

После создания первого объекта

Если мы создадим объект класса , картинка станет такой

Обратите внимание, что хоть у объектов по две переменные, это разные переменные: у обычного объекта — обычные, у статического — статические. Нужно больше объектов

Нужно больше объектов

Давайте создадим еще два объекта, типа . Новая картина будет выглядеть так:

Обратите внимание: у каждого объекта есть собственная переменная age и name

1 Статические переменные

Когда класс загружается в память, для него сразу создается статический объект класса. Этот объект хранит статические переменные класса (статические поля класса). Статический объект класса существует, даже если не был создан ни один обычный объект класса.

Когда мы описываем переменные в классе, мы указываем, будут ли эти переменные созданы всего один раз или же нужно создавать их копии для каждого объекта. По умолчанию создаётся новая копия переменной для каждого объекта.

Статическая (static) же переменная привязана к статическому объекту класса и всегда существует в единственном экземпляре.

Чтобы создать статическую переменную класса, нужно перед ее именем написать ключевое слово . Общий вид объявления статической переменной такой:

Если статической переменной не присвоить стартовое значение, она инициализируется значением по умолчанию:

Тип Значение по умолчанию
(то же самое, что и )
и любые классы

Примеры:

Код Примечание

Обращаться к статической переменной в ее классе можно просто по имени. Если обращение идет из другого класса, то перед именем статической переменной нужно писать имя класса.

Пример:

Переменная Класс Обращение к переменной вне класса
Переменная , вне класса не видна
Переменная , вне класса не видна
Переменная , вне класса не видна

ПримерExample

В этом примере класс имеет статический конструктор.In this example, class has a static constructor. При создании первого экземпляра класса () для инициализации класса вызывается статический конструктор.When the first instance of is created (), the static constructor is invoked to initialize the class. В выходных данных этого примера можно увидеть, что статический конструктор выполняется только один раз, несмотря на то, что создается два экземпляра класса . Кроме того, этот конструктор вызывается до выполнения конструктора экземпляра.The sample output verifies that the static constructor runs only one time, even though two instances of are created, and that it runs before the instance constructor runs.

Предупреждение о классах со всеми статическими членами

Будьте осторожны при написании классов со всеми статическими членами. Хотя такие «чисто статические классы» могут быть полезны, но они также имеют свои недостатки.

Во-первых, поскольку все статические члены создаются только один раз, то несколько копий «чисто статического класса» быть не может (без клонирования класса и его дальнейшего переименования). Например, если нам нужны два независимых объекта класса IDGenerator, то это будет невозможно через «чисто статический» класс.

Во-вторых, из урока о глобальных переменных мы знаем, что глобальные переменные опасны, поскольку любая часть кода может изменить их значения и, в конечном итоге, изменит другие фрагменты, казалось бы, не связанного с этими переменными кода (детально см. здесь). То же самое справедливо и для «чисто статических» классов. Поскольку все члены принадлежат классу (а не его объектам), а классы имеют глобальную область видимости, то в «чисто статическом классе» мы объявляем глобальные функции и переменные со всеми минусами, которые они имеют.

Статические свойства

Недавно в JavaScript была добавлена
возможность объявления статических свойств внутри класса, например, так:

class Users {
         static countUsers = ;
…
}

Это свойство
будет принадлежать только классу Users и не копироваться в его объекты.
Например, так можно сделать подсчет числа созданных пользователей в программе.
Добавим в конструктор строчку:

Users.countUsers++;

И, затем,
выведем это свойство в консоль:

console.log( Users.countUsers );

Увидим число
созданных пользователей.

Видео по теме

JavaScript ООП #1: Прототипное наследование, свойство __proto__

JavaScript ООП #2: Свойство prototype

JavaScript ООП #3: Базовые свойства Object, методы create, getPrototypeOf и setPrototypeOf

JavaScript ООП #4: Классы — class, методы и свойства, Class Expression

JavaScript ООП #5: Наследование классов, переопределение методов, функция super

JavaScript ООП #6: Статические методы и свойства классов

JavaScript ООП #7: Приватные методы и свойства, оператор instanceof

JavaScript ООП #8: Примеси (Mixins). Что это, где и для чего используются

JavaScript ООП #9: Блоки try/catch/finally, оператор throw, проброс исключений

shared_ptr vs unique_ptr

Третий фактор — это логика поведения как умного указателя. Когда-то он должен вести себя как , т.е. можно иметь несколько message_holder-ов, ссылающихся на один и тот же экземпляр сообщения. А когда-то он должен вести себя как , т.е. только один экземпляр message_holder-а может ссылаться на экземпляр сообщения.

По умолчанию, поведение должно зависеть от изменяемости/неизменяемости сообщения. Т.е. с неизменяемыми сообщениями должен вести себя как , а с изменяемыми, как :

Но жизнь штука сложная, поэтому нужно иметь еще и возможность вручную задать поведение . Чтобы можно было сделать message_holder-а для иммутабельного сообщения, который ведет себя как unique_ptr. И чтобы можно было сделать message_holder-а для изменяемого сообщения, который ведет себя как shared_ptr:

Соответственно, когда работает как shared_ptr, у него должен быть обычный набор конструкторов и операторов присваивания: и копирования, и перемещения. Кроме того, должен быть константный метод , который возвращает копию хранящегося внутри указателя.

А вот когда работает как unique_ptr, то конструктор и оператор копирования у него должны быть запрещены. А метод должен изымать указатель у объекта : после вызова исходный должен остаться пустым.

Операторы явного преобразования типов данных

В языке C++ есть 5 видов операций явного преобразования типов:

   конвертация C-style;

   применение оператора static_cast;

   применение оператора const_cast;

   применение оператора dynamic_cast;

   применение оператора reinterpret_cast.

На этом уроке мы рассмотрим конвертацию C-style и оператор static_cast. Оператор dynamic_cast мы будем рассматривать, когда дойдем до указателей и наследования. Применения операторов const_cast и reinterpret_cast следует избегать, так как они полезны только в редких случаях и могут создать немало проблем, если их использовать неправильно.

Правило: Избегайте использования const_cast и reinterpret_cast, если у вас нет на это веских причин.

Статические и окончательные модификаторы

  • Статический модификатор в основном используется для управления памятью.
  • Это также позволяет переменной быть доступной без загрузки какого-либо экземпляра класса, в котором она определена.
  • Последний модификатор означает, что значение переменной не может измениться. Как только значение назначено переменной, другое значение не может быть переназначено той же переменной.

С помощью модификатора final типы данных Primitive, такие как int, float, char, byte, long, short, double, Boolean, можно сделать неизменяемыми / неизменяемыми. Вместе, как мы поняли ранее, эти модификаторы создают постоянную переменную.

Классы памяти переменных

По умолчанию, локальные переменные имеют класс auto. Такие переменные располагаются на стеке а их область видимости ограничена своим блоком. Запись

#include <conio.h> #include <stdio.h> void main() { int x = 10; { int x = 20; { int x = 30; printf(«%d\n», x); } printf(«%d\n», x); } printf(«%d\n», x); getch(); }

идентична

#include <conio.h> #include <stdio.h> void main() { int auto x = 10; { int auto x = 20; { int auto x = 30; printf(«%d\n», x); } printf(«%d\n», x); } printf(«%d\n», x); getch(); }

Очевидно, что глобальные переменные не могут быть объявлены как auto, потому что располагаются в data-сегменте.

Следующий класс памяти – register. Когда мы определяем регистровую переменную, то мы просим компилятор, чтобы переменная располагалась в регистре, а не в оперативной памяти. Компилятор может сделать переменную регистровой, если позволяют условия (регистры не заняты, и по мнению компилятора это не приведёт к увеличению издержек). Регистровые переменные определяются с помощью служебного слово register перед типом

register int x = 20; register int y = 30;

Так как регистровая переменная не имеет адреса, то к ней не применима операция взятия адреса, это вызовет ошибку во время компиляции. Аргументы функции также могут быть заданы как register. Внутри функции они будут вести себя также, как и регистровые переменные.

Следующий класс памяти – статический. Переменные, объявленные как static, хранятся в data или в bss сегменте. Отличительной чертой является то, что время их жизни совпадает с временем жизни приложения, как и у глобальных переменных. Но в отличие от глобальных переменных, область видимости ограничена только блоком, в котором они определены.

#include <conio.h> #include <stdio.h> unsigned long long factorial(unsigned char n) { static unsigned char prevArg = 0; static long long prevAns = 1; if (n == prevArg) { printf(«return previous answer\n»); return prevAns; } else { unsigned i = 0; printf(«count new answer\n»); prevAns = 1; for (i = 1; i <= n; i++) { prevAns *= i; } prevArg = n; return prevAns; } } void main() { printf(«!%d == %llu\n», 10, factorial(10)); printf(«!%d == %llu\n», 10, factorial(10)); printf(«!%d == %llu\n», 11, factorial(11)); printf(«!%d == %llu\n», 11, factorial(11)); getch(); }

В этом примере переменные prevArg и prevAns инициализируются единожды, и не уничтожаются после выхода из функции. Переменная prevArg используется для хранения предыдущего аргумента функции, а prevAns для хранения предыдущего результата. Если аргумента функции совпадает с предыдущим, то возвращается ранее вычисленное значение, иначе оно вычисляется по-новому.

Другой показательный пример – функция-генератор, которая при каждом вызове возвращает новое значение.

#include <conio.h> #include <stdio.h> int next() { static int counter = 0; counter++; return counter; } void main() { printf(«%d\n», next()); printf(«%d\n», next()); printf(«%d\n», next()); printf(«%d\n», next()); printf(«%d\n», next()); _getch(); }

Если бы служебное слово static отсутствовало, то каждый раз при вызове функции локальная переменная counter снова создавалась, инициализировалась и уничтожалась после выхода из функции.

Статическая переменная может иметь только константную инициализацию. Например, она не может быть инициализирована вызовом функции.

… static double x = foo(3); //Ошибка …

Переменная, объявленная как static, должна иметь только один экземпляр в данной области видимости и вне этой области видимости не видна. Глобальная переменная, объявленная как static, видна только в своём файле.

Напротив, переменная, объявленная как extern может быть использована в других файлах при условии, что она была определена.

Преобразование типов данных в C#

Преобразование типов данных, это приведение одного типа к другому. Например, приведение целочисленной переменной к числу с плавающей точной, или преобразование числа в строку. В C# выделяют два варианта преобразования типов:

  • Неявное преобразование типов. Это, так называемое безопасное преобразование типов в C#. Например, преобразование из типа float (более «маленький» тип) в тип double (более «большой» тип). При таком преобразовании никакая информация не «потеряется», что при обратном преобразовании вполне возможно.
  • Явное преобразование типов. Такое преобразование выполняется программистом с прямым указанием типа, к которому нужно привести переменную. Для такого преобразования требуется наличие оператора преобразования.

А теперь, я покажу как можно использовать явное преобразование типов в C#:

using System;

namespace TypesConvertion
{
    class Program
    {
        static void Main(string[] args)
        {
            //Число с плавающей точкой
            double doubleData = 245.45;

            //Вывод этого числа 
            Console.WriteLine(doubleData);

            //А теперь, мы сделаем на основе этого числа, цело число
            int intData = (int)doubleData; 

            //Вывод целого числа 
            Console.WriteLine(intData);

            //Чтобы окно не закрылось раньше времени
            Console.ReadKey();
        }
    }
}

Преобразование типа double в тип int выполнялось в выделенной строке (строке с номером 16). Если вы соберете данный пример и запустите его, то увидите, что преобразование типов прошло, т.е. из числа с плавающей точкой мы получили целое число.

Так же, для преобразования данных из одного типа в другой, есть целый класс с именем Convert, у которого имеются специальные методы. Ниже, в таблице, представлены основные методы этого класса.

Название метода Целевой тип
ToBoolean bool
ToByte byte
ToChar char
ToDouble double
ToSingle float
ToInt32 int
ToInt64 long
ToString string

А теперь, пример использования класса Convert и его методов:

using System;

namespace TypesConvertion
{
    class Program
    {
        static void Main(string[] args)
        {
            //Число с плавающей точкой
            double doubleData = 245.45;

            //Вывод этого числа 
            Console.WriteLine(doubleData);

            //А теперь, мы сделаем на основе этого числа, цело число
            int intData = Convert.ToInt32(doubleData);

            //Вывод целого числа 
            Console.WriteLine(intData);

            //Чтобы окно не закрылось раньше времени
            Console.ReadKey();
        }
    }
}

Как видите, это практически тот же код, что был в первом примере, но с использованием другого подхода в преобразовании типов.

Пользоваться возможность преобразования типов вам придется довольно часто!

Итоговый наследник message_holder_t

Теперь можно посмотреть на то, что же из себя представляет , для реализации которого потребовались все эти базовые классы и метафункции (из реализации удалена часть методов для конструирования экземпляра хранящегося в message_holder-е сообщения):

По сути все то, что мы разбирали выше, потребовалось для того, чтобы записать вот этот «вызов» двух метафункций:

Т.к. это не первый вариант, а результат упрощения и сокращения кода, то могу сказать, что компактные формы метафункций ну очень сильно уменьшают объем кода и увеличивают его понятность (если о понятности здесь вообще уместно говорить).

А что было бы, если бы…

А вот если бы в C++ был настолько же мощен, как в D, то можно было бы написать что-то вроде:

Как по мне, так отличия слишком уж разительны. И они не в пользу текущего C++ 🙁
(разобранный выше C++ный код в виде одной сплошной «портянки» можно увидеть здесь).

Кстати говоря, я не очень сильно слежу за тем, что происходит в области предложений по метапрограммированию и рефлексии для будущих версий С++. Но из того, что помню, складывается ощущение, что предлагавшиеся Саттером метаклассы не очень упростят вот эту конкретную задачу. Как я понимаю, посредством метаклассов можно будет написать генератор классов . Может быть такой генератор получится и несложным в написании, но вряд ли такой подход в данном конкретном случае окажется выразительнее и понятнее, чем в случае действительно продвинутого .

Заключение

Как по мне, так этот пример показывает весь блеск и нищету C++. Да, можно сотворить все что угодно. В смысле, можно сделать шаблонный класс, содержимое которого будет кардинально меняться в зависимости от параметров шаблона.

Но вот чтобы сделать это придется несколько поломать мозги и написать на шаблонах столько вспомогательного кода, что копаться во всем этом не будет желания даже у автора.

Тем не менее, сам факт того, что на С++ можно такое сотворить, меня лично радует. Огорчает количество труда и объем кода, который для этого потребуется. Но, надеюсь, что со временем объем этого кода и его сложность будет только сокращаться. В принципе, это видно уже сейчас. Ибо для C++98/03 я даже не взялся бы такой трюк проделывать, тогда как начиная с C++11 делать подобное становится все проще и проще.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector