Ключевое слово static и его различные применения в C ++


196

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

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

Стандарт C ++ говорит это для членов класса данных с ключевым словом static:

3.7.1 Статическая продолжительность хранения [basic.stc.static]

3 Ключевое слово static может использоваться для объявления локальной переменной со статической продолжительностью хранения.

4 Ключевое слово static, примененное к члену данных класса в определении класса, дает длительность хранения статического члена данных.

Что это значит с локальной переменной ? Это локальная переменная функции? Потому что есть также то, что когда вы объявляете функцию локальной так, staticчто она инициализируется только один раз, при первом входе в эту функцию.

Это также говорит только о продолжительности хранения в отношении учеников, а как насчет того, чтобы это было не специфично для экземпляра, это тоже свойство staticno? Или это продолжительность хранения?

А как насчет случая с staticобластью действия файла? Все ли глобальные переменные по умолчанию имеют статическую продолжительность хранения? Следующее (из раздела 3.7.1), кажется, указывает на это:

1 Все переменные, которые не имеют динамической длительности хранения, не имеют длительности хранения потоков и не являются локальными, имеют статическую длительность хранения. Хранение для этих объектов должно продолжаться в течение всей программы (3.6.2, 3.6.3)

Какое staticотношение имеет связь с переменной?

Это целое staticключевое слово совершенно сбивает с толку, может кто-то прояснить различные варианты использования английского языка и сказать мне, когда нужно инициализировать staticученика?


Ответы:


147

Переменные:

staticпеременные существуют для «времени жизни» единицы перевода, в которой она определена , и:

  • Если он находится в области имен (то есть вне функций и классов), то к нему нельзя получить доступ из любого другого модуля перевода. Это известно как «внутренняя связь» или «статическая продолжительность хранения». (Не делайте этого в заголовках, за исключением constexpr. Что-нибудь еще, и в результате вы получите отдельную переменную в каждой единице перевода, что сумасшедшая путаница)
  • Если это переменная в функции , к ней нельзя получить доступ извне функции, как и к любой другой локальной переменной. (это местные они упоминали)
  • члены класса не имеют ограниченной области видимости static, но могут быть адресованы как из класса, так и из экземпляра (например std::string::npos). [Примечание: вы можете объявить статические члены в классе, но обычно они все равно должны быть определены в модуле перевода (файл cpp), и поэтому в каждом классе есть только один]

местоположения как код:

static std::string namespaceScope = "Hello";
void foo() {
    static std::string functionScope= "World";
}
struct A {
   static std::string classScope = "!";
};

Перед выполнением какой-либо функции в модуле перевода (возможно, после mainначала выполнения) переменные со статической продолжительностью хранения (область пространства имен) в этом модуле перевода будут «инициализированы константой» ( constexprгде это возможно, или нулем в противном случае), а затем Местные жители должным образом «динамически инициализируются» в том порядке, в котором они определены в модуле перевода (для подобных вещей std::string="HI";это не так constexpr). Наконец, локальные статические функции будут инициализированы, когда в первый раз выполнение «достигнет» строки, где они объявлены. Все staticпеременные уничтожаются в обратном порядке инициализации.

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

T& get_global() {
    static T global = initial_value();
    return global;
}

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

функции

Значительно более простой, staticчасто используется как функция-член класса, и очень редко используется для отдельно стоящей функции.

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

struct A {
    A() {++A_count;}
    A(const A&) {++A_count;}
    A(A&&) {++A_count;}
    ~A() {--A_count;}

    static int get_count() {return A_count;}
private:
    static int A_count;
}

int main() {
    A var;

    int c0 = var.get_count(); //some compilers give a warning, but it's ok.
    int c1 = A::get_count(); //normal way
}

staticСвободная функция означает , что функция не будет называться любым другим блоком перевода, и , таким образом линкер может игнорировать его полностью. Это имеет небольшое количество целей:

  • Может использоваться в файле cpp, чтобы гарантировать, что функция никогда не используется из любого другого файла.
  • Может быть помещен в заголовок, и каждый файл будет иметь свою собственную копию функции. Не полезно, так как inline делает почти то же самое.
  • Ускоряет время соединения за счет сокращения работы
  • Можно поместить функцию с одинаковым именем в каждую единицу перевода, и все они могут делать разные вещи. Например, вы можете поместить static void log(const char*) {}в каждый файл cpp, и каждый из них может войти по-своему.

1
Как насчет учеников? Разве это не третий отдельный случай?
Этьен

4
@Etienne - члены данных статического класса совпадают со статическими глобальными переменными, за исключением того, что вы можете обращаться к ним из других модулей перевода, и любой доступ (кроме функций-членов) должен указывать classname::область действия. Статические функции-члены класса похожи на глобальные функции, но ограничены классом, или похожи на обычные члены, но без this(это не выбор - эти два должны быть эквивалентны).
Steve314

1
@LuchianGrigore: хотя я понимаю вашу точку зрения, я не уверен, какую формулировку использовать.
Mooing Duck

1
@ Steve314: Я понимаю, что вы имеете в виду, но, имея дело с таким ужасно перегруженным термином, как статический , я бы хотел, чтобы мы все были немного осторожнее. В частности, все глобальные (на самом деле уровень пространства имен) переменные имеют статическую длительность, поэтому добавление статических в статические глобальные переменные можно понимать как namespace A { static int x; }, что означает внутреннюю связь и сильно отличается от поведения членов данных статического класса .
Дэвид Родригес - dribeas

1
«Если он находится в области пространства имен, то к нему нельзя получить доступ из любого другого модуля перевода ...» Что вы имеете в виду, если он находится в области пространства имен? Разве это не всегда так, не могли бы вы привести пример и контрпример?
AturSams

66

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

Связь является ортогональной к этому.

Я думаю, что это самое важное различие, которое вы можете сделать. Понимать это и все остальное, а также помнить это, должно быть легко (не обращаясь непосредственно к @Tony, но к тому, кто бы мог прочитать это в будущем).

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

Что это значит с локальной переменной? Это локальная переменная функции?

Да. Независимо от того, когда переменная инициализируется (при первом вызове функции и когда путь выполнения достигает точки объявления), она будет находиться в одном и том же месте в памяти на протяжении всей жизни программы. В этом случае staticдает статическое хранилище.

А как насчет случая со статической и файловой областью? Все ли глобальные переменные по умолчанию имеют статическую продолжительность хранения?

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

Как статические отношения связаны со связью переменной?

Это дает внутреннюю связь переменных пространства имен. Это дает членам и локальным переменным статическую продолжительность хранения.

Давайте расширим все это:

//

static int x; //internal linkage
              //non-static storage - each translation unit will have its own copy of x
              //NOT A TRUE GLOBAL!

int y;        //static storage duration (can be used with extern)
              //actual global
              //external linkage
struct X
{
   static int x;     //static storage duration - shared between class instances 
};

void foo()
{
   static int x;     //static storage duration - shared between calls
}

Это целое статическое ключевое слово совершенно сбивает с толку

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


1
Позвольте мне сказать прямо: вы говорите, что когда я говорю static int xв области имен, это дает нестатическое хранилище?
Майкл Агар

31

Чтобы прояснить вопрос, я бы предпочел классифицировать использование ключевого слова static в трех разных формах:

(А). переменные

(В). функции

(С). переменные-члены / функции классов

объяснение следует ниже для каждого из подзаголовков:

(A) «статическое» ключевое слово для переменных

Это может быть немного сложно, однако, если объяснить и правильно понять, это довольно просто.

Чтобы объяснить это, во-первых, действительно полезно узнать о области действия, продолжительности и связях переменных, без которых вещи всегда трудно увидеть сквозь мутную концепцию staic ключевого слова.

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

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

3. Связывание : определяет, может ли переменная быть доступна (или связана) в другом файле. Опять же (и, к счастью) это бывает двух типов: (i) Внутренняя связь (для переменных, имеющих блочную область и глобальную область действия / область действия файла / глобальную область имен) (ii) Внешняя связь (для переменных, имеющих значение только для глобальной области действия / области действия файла /) Область глобального пространства имен)

Давайте рассмотрим пример ниже для лучшего понимания простых глобальных и локальных переменных (без локальных переменных со статической длительностью хранения):

//main file
#include <iostream>

int global_var1; //has global scope
const global_var2(1.618); //has global scope

int main()
{
//these variables are local to the block main.
//they have automatic duration, i.e, they are created when the main() is 
//  executed and destroyed, when main goes out of scope
 int local_var1(23);
 const double local_var2(3.14);

 {
/* this is yet another block, all variables declared within this block are 
 have local scope limited within this block. */
// all variables declared within this block too have automatic duration, i.e, 
/*they are created at the point of definition within this block,
 and destroyed as soon as this block ends */
   char block_char1;
   int local_var1(32) //NOTE: this has been re-declared within the block, 
//it shadows the local_var1 declared outside

 std::cout << local_var1 <<"\n"; //prints 32

  }//end of block
  //local_var1 declared inside goes out of scope

 std::cout << local_var1 << "\n"; //prints 23

 global_var1 = 29; //global_var1 has been declared outside main (global scope)
 std::cout << global_var1 << "\n"; //prints 29
 std::cout << global_var2 << "\n"; //prints 1.618

 return 0;
}  //local_var1, local_var2 go out of scope as main ends
//global_var1, global_var2 go out of scope as the program terminates 
//(in this case program ends with end of main, so both local and global
//variable go out of scope together

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

Связывание глобальных переменных задается ключевыми словами: (i) статическая и (ii) внешняя

(Теперь вы получите объяснение)

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

1. Статическое ключевое слово для переменных с глобальной областью видимости

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

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

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

(см. пример ниже для пояснения)

например:

//main2.cpp
 static int global_var3 = 23;  /*static global variable, cannot be                            
                                accessed in anyother file */
 extern double global_var4 = 71; /*can be accessed outside this file                  linked to main2.cpp */
 int main() { return 0; }

main3.cpp

//main3.cpp
#include <iostream>

int main()
{
   extern int gloabl_var4; /*this variable refers to the gloabal_var4
                            defined in the main2.cpp file */
  std::cout << global_var4 << "\n"; //prints 71;

  return 0;
}

теперь любая переменная в c ++ может быть как константной, так и неконстантной, и для каждой «константности» мы получаем два случая связи по умолчанию с ++, в случае, если ни одна не указана:

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

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

Вот сводка для глобальных переменных области с различными связями

//globalVariables1.cpp 

// defining uninitialized vairbles
int globalVar1; //  uninitialized global variable with external linkage 
static int globalVar2; // uninitialized global variable with internal linkage
const int globalVar3; // error, since const variables must be initialized upon declaration
const int globalVar4 = 23; //correct, but with static linkage (cannot be accessed outside the file where it has been declared*/
extern const double globalVar5 = 1.57; //this const variable ca be accessed outside the file where it has been declared

Далее мы исследуем, как ведут себя вышеупомянутые глобальные переменные при доступе в другом файле.

//using_globalVariables1.cpp (eg for the usage of global variables above)

// Forward declaration via extern keyword:
 extern int globalVar1; // correct since globalVar1 is not a const or static
 extern int globalVar2; //incorrect since globalVar2 has internal linkage
 extern const int globalVar4; /* incorrect since globalVar4 has no extern 
                         specifier, limited to internal linkage by
                         default (static specifier for const variables) */
 extern const double globalVar5; /*correct since in the previous file, it 
                           has extern specifier, no need to initialize the
                       const variable here, since it has already been
                       legitimately defined perviously */

2. Статическое ключевое слово для переменных с локальной областью действия

Обновления (август 2019 г.) статического ключевого слова для переменных в локальной области видимости

Это далее может быть разделено на две категории:

(i) статическое ключевое слово для переменных в функциональном блоке и (ii) статическое ключевое слово для переменных в неназванном локальном блоке.

(i) статическое ключевое слово для переменных в функциональном блоке.

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

Если статический спецификатор применяется к локальной переменной в функциональном блоке, он меняет продолжительность переменной с автоматического на статический, а его срок службы равен всей продолжительности программы, что означает, что она имеет фиксированное расположение в памяти, и ее значение инициализируется только один раз перед запуском программы, как указано в справочнике cpp (инициализация не должна быть перепутана с присваиванием)

Давайте посмотрим на пример.

//localVarDemo1.cpp    
 int localNextID()
{
  int tempID = 1;  //tempID created here
  return tempID++; //copy of tempID returned and tempID incremented to 2
} //tempID destroyed here, hence value of tempID lost

int newNextID()
{
  static int newID = 0;//newID has static duration, with internal linkage
  return newID++; //copy of newID returned and newID incremented by 1
}  //newID doesn't get destroyed here :-)


int main()
{
  int employeeID1 = localNextID();  //employeeID1 = 1
  int employeeID2 = localNextID();  // employeeID2 = 1 again (not desired)
  int employeeID3 = newNextID(); //employeeID3 = 0;
  int employeeID4 = newNextID(); //employeeID4 = 1;
  int employeeID5 = newNextID(); //employeeID5 = 2;
  return 0;
}

Глядя на приведенный выше критерий для статических локальных переменных и статических глобальных переменных, можно задаться вопросом, в чем может быть разница между ними. Хотя глобальные переменные доступны в любой точке кода (как в разных, так и в разных единицах перевода в зависимости от const -ness и extern -ness), статическая переменная, определенная в функциональном блоке, недоступна напрямую. Переменная должна быть возвращена значением функции или ссылкой. Давайте продемонстрируем это на примере:

//localVarDemo2.cpp 

//static storage duration with global scope 
//note this variable can be accessed from outside the file
//in a different compilation unit by using `extern` specifier
//which might not be desirable for certain use case.
static int globalId = 0;

int newNextID()
{
  static int newID = 0;//newID has static duration, with internal linkage
  return newID++; //copy of newID returned and newID incremented by 1
}  //newID doesn't get destroyed here


int main()
{
    //since globalId is accessible we use it directly
  const int globalEmployee1Id = globalId++; //globalEmployeeId1 = 0;
  const int globalEmployee2Id = globalId++; //globalEmployeeId1 = 1;

  //const int employeeID1 = newID++; //this will lead to compilation error since newID++ is not accessible direcly. 
  int employeeID2 = newNextID(); //employeeID3 = 0;
  int employeeID2 = newNextID(); //employeeID3 = 1;

  return 0;
}

Более подробное объяснение о выборе статической глобальной и статической локальной переменной можно найти в этом потоке stackoverflow

(ii) статическое ключевое слово для переменных в неназванном локальном блоке.

Статические переменные в локальном блоке (не функциональном блоке) не могут быть доступны вне блока, как только локальный блок выходит из области видимости. Никаких предостережений к этому правилу.

    //localVarDemo3.cpp 
    int main()
    {

      {
          const static int static_local_scoped_variable {99};
      }//static_local_scoped_variable goes out of scope

      //the line below causes compilation error
      //do_something is an arbitrary function
      do_something(static_local_scoped_variable);
      return 0;
    }

В C ++ 11 введено ключевое слово, constexprкоторое гарантирует оценку выражения во время компиляции и позволяет компилятору оптимизировать код. Теперь, если значение статической константной переменной в области видимости известно во время компиляции, код оптимизируется аналогично тому, с которым constexpr. Вот небольшой пример

Я рекомендую читателям также посмотреть разницу между переменными в потоке stackoverflowconstexpr и static constдля них . На этом мое объяснение ключевого слова static применяется к переменным.

B. ключевое слово static для функций

в терминах функций ключевое слово static имеет прямое значение. Здесь это относится к связыванию функции. Обычно все функции, объявленные в файле cpp, по умолчанию имеют внешнюю связь, то есть функция, определенная в одном файле, может использоваться в другом файле cpp путем предварительного объявления.

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

C. Staitc Ключевое слово, используемое для переменных-членов и функций классов

1. «статическое» ключевое слово для переменных-членов классов

Я начну прямо с примера здесь

#include <iostream>

class DesignNumber
{
  private:

      static int m_designNum;  //design number
      int m_iteration;     // number of iterations performed for the design

  public:
    DesignNumber() {     }  //default constructor

   int  getItrNum() //get the iteration number of design
   {
      m_iteration = m_designNum++;
      return m_iteration;
   }
     static int m_anyNumber;  //public static variable
};
int DesignNumber::m_designNum = 0; // starting with design id = 0
                     // note : no need of static keyword here
                     //causes compiler error if static keyword used
int DesignNumber::m_anyNumber = 99; /* initialization of inclass public 
                                    static member  */
enter code here

int main()
{
   DesignNumber firstDesign, secondDesign, thirdDesign;
   std::cout << firstDesign.getItrNum() << "\n";  //prints 0
   std::cout << secondDesign.getItrNum() << "\n"; //prints 1
   std::cout << thirdDesign.getItrNum() << "\n";  //prints 2

   std::cout << DesignNumber::m_anyNumber++ << "\n";  /* no object
                                        associated with m_anyNumber */
   std::cout << DesignNumber::m_anyNumber++ << "\n"; //prints 100
   std::cout << DesignNumber::m_anyNumber++ << "\n"; //prints 101

   return 0;
}

В этом примере статическая переменная m_designNum сохраняет свое значение, и эта единственная закрытая переменная-член (потому что она статическая) является общей для всех переменных типа объекта DesignNumber.

Также как и другие переменные-члены, статические переменные-члены класса не связаны ни с одним объектом класса, что демонстрируется печатью anyNumber в основной функции.

const против неконстантных статических переменных-членов в классе

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

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

Однако я бы порекомендовал инициализировать статические переменные-члены const в точке объявления. Это соответствует стандартному соглашению C ++ и делает код более чистым

Дополнительные примеры статических переменных-членов в классе можно найти по следующей ссылке с learncpp.com http://www.learncpp.com/cpp-tutorial/811-static-member-variables/

2. «статическое» ключевое слово для функции-члена классов

Как переменные-члены классов могут быть статическими, так и функции-члены классов. Обычные функции-члены классов всегда связаны с объектом типа класса. Напротив, статические функции-члены класса не связаны ни с одним объектом класса, т. Е. У них нет * этого указателя.

Во-вторых, поскольку статические функции-члены класса не имеют * этого указателя, их можно вызывать с помощью оператора разрешения имени и области видимости в главной функции (ClassName :: functionName ();)

В-третьих, статические функции-члены класса могут обращаться только к статическим переменным-членам класса, поскольку нестатические переменные-члены класса должны принадлежать объекту класса.

Дополнительные примеры статических функций-членов в классе можно найти по следующей ссылке с learncpp.com.

http://www.learncpp.com/cpp-tutorial/812-static-member-functions/


1
1) До c ++ 17 только встроенные статические переменные-члены const могут быть инициализированы в классе, например struct Foo{static const std::string name = "cpp";};, ошибка, nameдолжна быть определена вне класса; с встроенными переменными, внедренными в c ++ 17, можно кодировать: struct Foo{static inline const std::string name = "cpp";};2) Доступ к открытым статическим членам / функциям-членам можно получить по имени класса с помощью оператора разрешения области, а также по экземпляру с оператором точки (например: instance.some_static_method ())
oz1

Не должен ли m_anyVariable стать m_anyNumber? в вашем последнем примере кода?
Геббиссимо

Я не могу судить о полноте и правильности ответа, но он кажется действительно всеобъемлющим, и за ним легко было следовать. Большое спасибо! Если вы хотите улучшить его, короткое резюме в начале может быть полезным, так как это довольно длинный текст, а основные моменты можно легко представить в виде вложенного списка или древовидной схемы для людей, которые знают такие термины, как «внутренний / внешний». связь "
Gebbissimo

18

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

int myFun()
{
static int i=5;
i++;
return i;
}
int main()
{
printf("%d", myFun());
printf("%d", myFun());
printf("%d", myFun());
}

будет показывать 678вместо 666, потому что он запоминает увеличенное значение.

Что касается статических членов, они сохраняют свое значение в экземплярах класса. Итак, следующий код:

struct A
{
static int a;
};
int main()
{
A first;
A second;
first.a = 3;
second.a = 4;
printf("%d", first.a);
}

выведет 4, потому что first.a и second.a по сути являются одной и той же переменной. Что касается инициализации, см. Этот вопрос.


Это не относится к переменным области имен пространства имен.
Майкл Агар

10

Когда вы объявляете staticпеременную в области видимости файла, тогда эта переменная доступна только в этом конкретном файле (технически, модуль перевода *, но давайте не будем слишком усложнять это). Например:

a.cpp

static int x = 7;

void printax()
{
    cout << "from a.cpp: x=" << x << endl;
}

b.cpp

static int x = 9;

void printbx()
{
    cout << "from b.cpp: x=" << x << endl;
}

main.cpp:

int main(int, char **)
{
    printax(); // Will print 7
    printbx(); // Will print 9

    return 0;
}

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

unsigned int powersoftwo()
{
    static unsigned lastpow;

    if(lastpow == 0)
        lastpow = 1;
    else
        lastpow *= 2;

    return lastpow;
}

int main(int, char **)
{
    for(int i = 0; i != 10; i++)
        cout << "2^" << i << " = " << powersoftwo() << endl;
}

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

class Test
{
private:
    static char *xxx;

public:
    static int yyy;

public:
    Test()
    {        
        cout << this << "The static class variable xxx is at address "
             << static_cast<void *>(xxx) << endl;
        cout << this << "The static class variable yyy is at address "
             << static_cast<void *>(&y) << endl;
    }
};

// Necessary for static class variables.
char *Test::xxx = "I'm Triple X!";
int Test::yyy = 0;

int main(int, char **)
{
    Test t1;
    Test t2;

    Test::yyy = 666;

    Test t3;
};

Пометка функции, не относящейся к классу as, staticделает функцию доступной только из этого файла и недоступной из других файлов.

a.cpp

static void printfilename()
{ // this is the printfilename from a.cpp - 
  // it can't be accessed from any other file
    cout << "this is a.cpp" << endl;
}

b.cpp

static void printfilename()
{ // this is the printfilename from b.cpp - 
  // it can't be accessed from any other file
    cout << "this is b.cpp" << endl;
}

Для функций-членов класса их маркировка staticозначает, что функцию не нужно вызывать для конкретного экземпляра объекта (т. Е. У нее нет thisуказателя).

class Test
{
private:
    static int count;

public:
    static int GetTestCount()
    {
        return count;
    };

    Test()
    {
        cout << this << "Created an instance of Test" << endl;
        count++;
    }

    ~Test()
    {
        cout << this << "Destroyed an instance of Test" << endl;
        count--;
    }
};

int Test::count = 0;

int main(int, char **)
{
    Test *arr[10] = { NULL };

    for(int i = 0; i != 10; i++)
        arr[i] = new Test();

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    // now, delete them all except the first and last!
    for(int i = 1; i != 9; i++)
        delete arr[i];        

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    delete arr[0];

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    delete arr[9];

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    return 0;
}

8

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

class MyClass
{
    public:
    int myVar; 
    static int myStaticVar;
};

//Static member variables must be initialized. Unless you're using C++11, or it's an integer type,
//they have to be defined and initialized outside of the class like this:
MyClass::myStaticVar = 0;

MyClass classA;
MyClass classB;

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

MyClass::myStaticVar //Assuming it's publicly accessible.

При использовании внутри функции в качестве локальной переменной (а не в качестве переменной-члена класса) ключевое слово static делает что-то другое. Это позволяет вам создавать постоянные переменные, не давая глобальной области видимости.

int myFunc()
{
   int myVar = 0; //Each time the code reaches here, a new variable called 'myVar' is initialized.
   myVar++;

   //Given the above code, this will *always* print '1'.
   std::cout << myVar << std::endl;

   //The first time the code reaches here, 'myStaticVar' is initialized. But ONLY the first time.
   static int myStaticVar = 0;

   //Each time the code reaches here, myStaticVar is incremented.
   myStaticVar++;

   //This will print a continuously incrementing number,
   //each time the function is called. '1', '2', '3', etc...
   std::cout << myStaticVar << std::endl;
}

Это глобальная переменная с точки зрения постоянства ... но не глобальная по объему / доступности.

Вы также можете иметь статические функции-члены. Статические функции в основном не являются членами, но находятся внутри пространства имен имени класса и имеют частный доступ к членам класса.

class MyClass
{
    public:
    int Func()
    {
        //...do something...
    }

    static int StaticFunc()
    {
        //...do something...
    }
};

int main()
{
   MyClass myClassA;
   myClassA.Func(); //Calls 'Func'.
   myClassA.StaticFunc(); //Calls 'StaticFunc'.

   MyClass::StaticFunc(); //Calls 'StaticFunc'.
   MyClass::Func(); //Error: You can't call a non-static member-function without a class instance!

   return 0;
}

Когда вы вызываете функцию-член, появляется скрытый параметр с именем 'this', который является указателем на экземпляр класса, вызывающего функцию. Статические функции-члены не имеют этого скрытого параметра ... они могут вызываться без экземпляра класса, но также не могут получить доступ к нестатическим переменным-членам класса, поскольку у них нет указателя «this» для работы. Они не вызываются ни для какого конкретного экземпляра класса.


1
«Предполагая, что это общедоступно». - это не.
Лучиан Григоре

2
myStaticVarнужно также определить. Конечно, важно отметить, что, отвечая на вопрос о семантике staticключевого слова, вы не думаете?
преторианец

@Praetorian: Спасибо, исправлено.
Джамин Грей

1
@JaminGrey Под «статическим автономным» я имел в виду статические функции, не являющиеся членами, и я пишу такие всякий раз, когда мне нужны новые функции только в текущем файле CPP, и я не хочу, чтобы компоновщик обрабатывал дополнительный символ.
VR

1
@VR Странно! Я никогда не знал, что такая функциональность существует. Спасибо за расширение моих знаний!
Джамин Грей,

1

Я не программист на C, поэтому я не могу дать вам информацию об использовании static в программе на C должным образом, но когда дело доходит до объектно-ориентированного программирования, static в основном объявляет переменную или функцию или класс одинаковыми. на протяжении всей жизни программы. Взять к примеру.

class A
{
public:
    A();
    ~A();
    void somePublicMethod();
private:
    void somePrivateMethod();
};

Когда вы создаете этот класс в своем Main, вы делаете что-то вроде этого.

int main()
{
   A a1;
   //do something on a1
   A a2;
   //do something on a2
}

Эти два экземпляра класса полностью отличаются друг от друга и работают независимо друг от друга. Но если бы вы воссоздали класс А вот так.

class A
{
public:
    A();
    ~A();
    void somePublicMethod();
    static int x;
private:
    void somePrivateMethod();
};

Вернемся к основному снова.

int main()
{
   A a1;
   a1.x = 1;
   //do something on a1
   A a2;
   a2.x++;
   //do something on a2
}

Тогда a1 и a2 будут совместно использовать одну и ту же копию int x, в результате чего любые операции над x в a1 будут напрямую влиять на операции x в a2. Так что, если бы я должен был сделать это

int main()
{
   A a1;
   a1.x = 1;
   //do something on a1
   cout << a1.x << endl; //this would be 1
   A a2;
   a2.x++;
   cout << a2.x << endl; //this would be 2 
   //do something on a2
}

Оба экземпляра класса A имеют общие статические переменные и функции. Надеюсь, что это ответ на ваш вопрос. Мои ограниченные знания C позволяют мне сказать, что определение функции или переменной как статического означает, что для файла видно, что функция или переменная определена как статическая. Но на этот вопрос лучше бы ответил парень из C, а не я. C ++ позволяет как C, так и C ++ объявлять ваши переменные как статические, потому что они полностью обратно совместимы с C.


1

Что это значит с локальной переменной? Это локальная переменная функции?

Да - Неглобальный, такой как локальная переменная функции.

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

Правильно.

Кроме того, речь идет только о продолжительности хранения в отношении членов класса, а как насчет того, чтобы это не зависело от экземпляра, это также свойство static no? Или это продолжительность хранения?

class R { static int a; }; // << static lives for the duration of the program

то есть, все экземпляры Rдоли int R::a- int R::aникогда не копируются.

А как насчет случая со статической и файловой областью?

Эффективно глобальный, который имеет конструктор / деструктор, где это уместно - инициализация не откладывается до доступа.

Как статические отношения связаны со связью переменной?

Для функции локальной она является внешней. Доступ: он доступен для функции (если, конечно, вы ее не вернете).

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

static также может указывать внутреннюю связь, в зависимости от того, где она объявлена ​​(файл / пространство имен).

Это целое статическое ключевое слово совершенно сбивает с толку

У него слишком много целей в C ++.

Может кто-нибудь прояснить различные варианты использования английского языка, а также сказать мне, когда инициализировать статический член класса?

Он автоматически инициализируется раньше, mainесли он загружен и имеет конструктор. Это может звучать как хорошая вещь, но порядок инициализации в значительной степени находится вне вашего контроля, поэтому сложную инициализацию становится очень трудно поддерживать, и вы хотите минимизировать это - если у вас есть статический тип, то функция локального масштабирования гораздо лучше масштабируется по библиотекам и проекты. Что касается данных со статической продолжительностью хранения, вы должны попытаться минимизировать этот дизайн, особенно если он изменчив (глобальные переменные). «Время» инициализации также варьируется по ряду причин - у загрузчика и ядра есть несколько хитростей, позволяющих минимизировать использование памяти и отложить инициализацию в зависимости от данных.


1

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

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

Давайте попробуем следующий пример, чтобы понять концепцию статических членов данных:

#include <iostream>

using namespace std;

class Box
{
   public:
      static int objectCount;
      // Constructor definition
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // Increase every time object is created
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

// Initialize static member of class Box
int Box::objectCount = 0;

int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   // Print total number of objects.
   cout << "Total objects: " << Box::objectCount << endl;

   return 0;
}

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

Constructor called.
Constructor called.
Total objects: 2

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

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

Статические функции-члены имеют область видимости класса, и у них нет доступа к указателю this класса. Вы можете использовать статическую функцию-член, чтобы определить, были ли созданы некоторые объекты класса.

Давайте попробуем следующий пример, чтобы понять концепцию статических членов функции:

#include <iostream>

using namespace std;

class Box
{
   public:
      static int objectCount;
      // Constructor definition
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // Increase every time object is created
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      static int getCount()
      {
         return objectCount;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

// Initialize static member of class Box
int Box::objectCount = 0;

int main(void)
{

   // Print total number of objects before creating object.
   cout << "Inital Stage Count: " << Box::getCount() << endl;

   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   // Print total number of objects after creating object.
   cout << "Final Stage Count: " << Box::getCount() << endl;

   return 0;
}

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

Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2

1
Было бы правильно упомянуть, что эти парадигмы были взяты из tutorialspoint.com/cplusplus/cpp_static_members.htm
BugShotGG
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.