Từ khóa tĩnh và các ứng dụng khác nhau của nó trong C ++


195

Từ khóa staticlà một từ có nhiều ý nghĩa trong C ++ mà tôi thấy rất khó hiểu và tôi không bao giờ có thể suy nghĩ về cách nó thực sự hoạt động.

Theo những gì tôi hiểu, có staticthời lượng lưu trữ, có nghĩa là nó tồn tại trong suốt thời gian của chương trình trong trường hợp toàn cầu, nhưng khi bạn nói về một địa phương, điều đó có nghĩa là nó không được khởi tạo theo mặc định.

Tiêu chuẩn C ++ cho biết điều này đối với các thành viên dữ liệu lớp với từ khóa static:

3.7.1 Thời lượng lưu trữ tĩnh [basic.stc.static]

3 Từ khóa tĩnh có thể được sử dụng để khai báo một biến cục bộ với thời lượng lưu trữ tĩnh.

4 Từ khóa tĩnh được áp dụng cho thành viên dữ liệu lớp trong định nghĩa lớp cung cấp thời lượng lưu trữ tĩnh của thành viên dữ liệu.

Nó có nghĩa gì với biến cục bộ ? Đó có phải là một biến cục bộ? Bởi vì cũng có khi bạn khai báo một hàm cục bộ vì staticnó chỉ được khởi tạo một lần, lần đầu tiên nó vào hàm này.

Nó cũng chỉ nói về thời lượng lưu trữ liên quan đến các thành viên trong lớp, những gì về nó không cụ thể, đó cũng là một tài sản của static không? Hay là thời gian lưu trữ?

Bây giờ những gì về trường hợp với staticvà phạm vi tập tin? Có phải tất cả các biến toàn cục được coi là có thời lượng lưu trữ tĩnh theo mặc định? Sau đây (từ mục 3.7.1) dường như chỉ ra như vậy:

1 Tất cả các biến không có thời lượng lưu trữ động, không có thời lượng lưu trữ luồng và không cục bộ có thời lượng lưu trữ tĩnh. Việc lưu trữ cho các thực thể này sẽ kéo dài trong suốt thời gian của chương trình (3.6.2, 3.6.3)

Làm thế nào không staticliên quan đến các liên kết của một biến?

Toàn bộ statictừ khóa này hoàn toàn khó hiểu, ai đó có thể làm rõ các cách sử dụng khác nhau cho tiếng Anh và cũng cho tôi biết khi nào nên khởi tạo một staticthành viên trong lớp?


Câu trả lời:


147

Biến:

staticcác biến tồn tại trong "thời gian tồn tại" của đơn vị dịch thuật được xác định trong và:

  • Nếu nó nằm trong phạm vi không gian tên (nghĩa là bên ngoài các hàm và lớp), thì nó không thể được truy cập từ bất kỳ đơn vị dịch thuật nào khác. Điều này được gọi là "liên kết nội bộ" hoặc "thời gian lưu trữ tĩnh". (Đừng làm điều này trong các tiêu đề ngoại trừconstexpr . Bất cứ điều gì khác, và bạn kết thúc với một biến riêng biệt trong mỗi đơn vị dịch thuật, điều này thật khó hiểu)
  • Nếu đó là một biến trong một hàm , nó không thể được truy cập từ bên ngoài hàm, giống như bất kỳ biến cục bộ nào khác. (đây là địa phương họ đã đề cập)
  • các thành viên lớp không có phạm vi hạn chế do static, nhưng có thể được giải quyết từ lớp cũng như một thể hiện (như std::string::npos). [Lưu ý: bạn có thể khai báo các thành viên tĩnh trong một lớp, nhưng chúng thường vẫn phải được xác định trong một đơn vị dịch (tệp cpp) và như vậy, chỉ có một cho mỗi lớp]

vị trí dưới dạng mã:

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

Trước khi bất kỳ chức năng nào trong một đơn vị dịch thuật được thực thi (có thể sau khi mainbắt đầu thực hiện), các biến có thời lượng lưu trữ tĩnh (phạm vi không gian tên) trong đơn vị dịch đó sẽ là "khởi tạo không đổi" ( constexprnếu có thể, hoặc khác 0), sau đó không người dân địa phương được "khởi tạo động" đúng cách theo thứ tự họ được xác định trong đơn vị dịch thuật (đối với những thứ như thế std::string="HI";không constexpr). Cuối cùng, các thống kê chức năng cục bộ sẽ được khởi tạo khi thực hiện lần đầu tiên "đạt" dòng nơi chúng được khai báo. Tất cả các staticbiến đều bị hủy theo thứ tự ngược của khởi tạo.

Cách dễ nhất để có được tất cả quyền này là biến tất cả các biến tĩnh không được constexprkhởi tạo thành các hàm cục bộ tĩnh, điều này đảm bảo tất cả các số liệu thống kê / toàn cục của bạn được khởi tạo đúng cách khi bạn cố gắng sử dụng chúng bất kể điều gì, do đó ngăn chặn việc khởi tạo tĩnh đặt hàng fiasco .

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

Hãy cẩn thận, bởi vì khi đặc tả nói các biến phạm vi không gian tên có "thời lượng lưu trữ tĩnh" theo mặc định, chúng có nghĩa là bit "thời gian tồn tại của đơn vị dịch", nhưng điều đó không có nghĩa là nó không thể được truy cập bên ngoài tệp.

Chức năng

Đáng kể hơn, đơn giản hơn, staticthường được sử dụng như một hàm thành viên lớp và chỉ rất hiếm khi được sử dụng cho một hàm đứng.

Hàm thành viên tĩnh khác với hàm thành viên thông thường ở chỗ nó có thể được gọi mà không có phiên bản của lớp và vì nó không có thể hiện nên nó không thể truy cập các thành viên không tĩnh của lớp. Các biến tĩnh rất hữu ích khi bạn muốn có một hàm cho một lớp hoàn toàn không tham chiếu đến bất kỳ thành viên thể hiện nào hoặc để quản lý staticcác biến thành viên.

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
}

Hàm statictự do có nghĩa là hàm này sẽ không được bất kỳ đơn vị dịch thuật nào khác nhắc đến, và do đó trình liên kết có thể bỏ qua hoàn toàn. Điều này có một số mục đích nhỏ:

  • Có thể được sử dụng trong tệp cpp để đảm bảo rằng chức năng này không bao giờ được sử dụng từ bất kỳ tệp nào khác.
  • Có thể được đặt trong một tiêu đề và mỗi tệp sẽ có bản sao riêng của hàm. Không hữu ích, vì nội tuyến thực hiện khá nhiều điều tương tự.
  • Tăng thời gian liên kết bằng cách giảm công việc
  • Có thể đặt một hàm có cùng tên trong mỗi đơn vị dịch và tất cả chúng có thể làm những việc khác nhau. Chẳng hạn, bạn có thể đặt một static void log(const char*) {}tệp trong mỗi tệp cpp và mỗi tệp có thể đăng nhập theo một cách khác nhau.

1
Còn các thành viên trong lớp thì sao? Nó không phải là một trường hợp riêng thứ ba?
Étienne

4
@Etienne - Các thành viên dữ liệu lớp tĩnh giống như các biến toàn cục tĩnh ngoại trừ việc bạn có thể truy cập chúng từ các đơn vị dịch thuật khác và mọi truy cập (ngoại trừ từ các hàm thành viên) phải chỉ định classname::phạm vi. Các hàm thành viên của lớp tĩnh giống như các hàm toàn cục nhưng nằm trong lớp hoặc giống như các thành viên bình thường nhưng không có this(đó không phải là một lựa chọn - hai hàm này phải tương đương nhau).
Steve314

1
@LuchianGrigore: trong khi tôi thấy quan điểm của bạn, tôi không chắc nên sử dụng từ ngữ nào.
Vịt Mooing

1
@ Steve314: Tôi hiểu ý của bạn là gì, nhưng khi xử lý một thuật ngữ quá tải khủng khiếp như tĩnh , tôi ước tất cả chúng ta cẩn thận hơn một chút. Cụ thể, tất cả các biến toàn cục (mức không gian tên thực) đều có thời lượng tĩnh, do đó, việc thêm tĩnh vào các biến toàn cục tĩnh có thể được hiểu là namespace A { static int x; }, có nghĩa là liên kết nội bộ và rất khác với hành vi của các thành viên dữ liệu lớp tĩnh .
David Rodríguez - dribeas

1
"Nếu nó nằm trong phạm vi không gian tên, thì nó không thể được truy cập từ bất kỳ đơn vị dịch thuật nào khác ..." Ý bạn là gì nếu nó nằm trong phạm vi không gian tên? Không phải luôn luôn như vậy, bạn có thể đưa ra một ví dụ và một ví dụ phản biện không?
AturSams 17/9/2015

66

Thời lượng lưu trữ tĩnh có nghĩa là biến nằm trong cùng một vị trí trong bộ nhớ trong suốt thời gian tồn tại của chương trình.

Liên kết là trực giao với điều này.

Tôi nghĩ rằng đây là sự khác biệt quan trọng nhất bạn có thể làm. Hiểu điều này và phần còn lại, cũng như ghi nhớ nó, sẽ trở nên dễ dàng (không giải quyết trực tiếp @Tony, nhưng bất cứ ai có thể đọc được điều này trong tương lai).

Từ khóa staticcó thể được sử dụng để biểu thị liên kết nội bộ lưu trữ tĩnh, nhưng về bản chất chúng là khác nhau.

Nó có nghĩa gì với biến cục bộ? Đó có phải là một biến cục bộ?

Đúng. Bất kể khi nào biến được khởi tạo (trong lần gọi đầu tiên đến hàm và khi đường dẫn thực thi đến điểm khai báo), nó sẽ nằm ở cùng một vị trí trong bộ nhớ trong vòng đời của chương trình. Trong trường hợp này, staticcung cấp cho nó lưu trữ tĩnh.

Bây giờ những gì về trường hợp với phạm vi tĩnh và tập tin? Có phải tất cả các biến toàn cục được coi là có thời lượng lưu trữ tĩnh theo mặc định?

Có, tất cả các quả cầu đều có định nghĩa thời lượng lưu trữ tĩnh (bây giờ chúng tôi đã làm rõ điều đó có nghĩa là gì). Nhưng các biến trong phạm vi không gian tên không được khai báo static, bởi vì điều đó sẽ cung cấp cho chúng liên kết nội bộ, do đó, một biến trên mỗi đơn vị dịch.

Làm thế nào để tĩnh liên quan đến liên kết của một biến?

Nó cung cấp liên kết nội bộ biến không gian tên. Nó cung cấp cho các thành viên và các biến cục bộ thời gian lưu trữ tĩnh.

Hãy mở rộng trên tất cả điều này:

//

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
}

Toàn bộ từ khóa tĩnh này là hết sức khó hiểu

Chắc chắn, trừ khi bạn quen thuộc với nó. :) Cố gắng tránh thêm từ khóa mới vào ngôn ngữ, ủy ban đã sử dụng lại từ khóa này, IMO, để tạo hiệu ứng này - nhầm lẫn. Nó được sử dụng để biểu thị những điều khác nhau (tôi có thể nói, có thể là những điều đối lập).


1
Hãy để tôi nói thẳng điều này - bạn đang nói rằng khi tôi nói static int xở phạm vi không gian tên, điều đó có cho nó lưu trữ không tĩnh không?
Michael Hagar

30

Để làm rõ câu hỏi, tôi muốn phân loại việc sử dụng từ khóa 'tĩnh' theo ba hình thức khác nhau:

(A). biến

(B). chức năng

(C). biến thành viên / chức năng của lớp

giải thích sau đây cho mỗi tiêu đề phụ:

(A) từ khóa 'tĩnh' cho các biến

Điều này có thể hơi khó khăn tuy nhiên nếu được giải thích và hiểu đúng, nó khá đơn giản.

Để giải thích điều này, đầu tiên thực sự hữu ích khi biết về phạm vi, thời lượng và mối liên kết của các biến, mà không có điều gì luôn khó thấy thông qua khái niệm mờ ám của từ khóa cổ

1. Phạm vi : Xác định vị trí trong tệp, biến có thể truy cập được. Nó có thể có hai loại: (i) Phạm vi cục bộ hoặc Khối . (ii) Phạm vi toàn cầu

2. Thời lượng : Xác định khi một biến được tạo và hủy. Một lần nữa, nó có hai loại: (i) Thời lượng lưu trữ tự động (đối với các biến có phạm vi cục bộ hoặc Khối). (ii) Thời lượng lưu trữ tĩnh (đối với các biến có Phạm vi toàn cầu hoặc biến cục bộ (trong hàm hoặc khối mã) với chỉ định tĩnh ).

3. Liên kết : Xác định xem một biến có thể được truy cập (hoặc liên kết) trong một tệp khác. Một lần nữa (và may mắn thay) có hai loại: (i) Liên kết nội bộ (đối với các biến có Phạm vi khối và Phạm vi toàn cầu / Phạm vi tệp / Phạm vi không gian toàn cầu) (ii) Liên kết ngoài (đối với các biến chỉ dành cho Phạm vi toàn cầu / Phạm vi tệp / Phạm vi không gian tên toàn cầu)

Hãy tham khảo một ví dụ dưới đây để hiểu rõ hơn về các biến toàn cục và biến cục bộ (không có biến cục bộ nào có thời lượng lưu trữ tĩnh):

//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

Bây giờ đến khái niệm Liên kết. Khi một biến toàn cục được xác định trong một tệp được dự định sẽ được sử dụng trong một tệp khác, liên kết của biến đó đóng một vai trò quan trọng.

Liên kết của các biến toàn cục được chỉ định bởi các từ khóa: (i) tĩnh và, (ii) extern

(Bây giờ bạn có được lời giải thích)

từ khóa tĩnh có thể được áp dụng cho các biến có phạm vi cục bộ và toàn cầu, và trong cả hai trường hợp, chúng có nghĩa là những thứ khác nhau. Trước tiên tôi sẽ giải thích việc sử dụng từ khóa 'tĩnh' trong các biến có phạm vi toàn cầu (trong đó tôi cũng làm rõ cách sử dụng từ khóa 'extern') và sau đó cho những người có phạm vi địa phương.

1. Từ khóa tĩnh cho các biến có phạm vi toàn cầu

Các biến toàn cục có thời lượng tĩnh, nghĩa là chúng không đi ra khỏi phạm vi khi một khối mã cụ thể (ví dụ: main ()) trong đó nó được sử dụng kết thúc. Tùy thuộc vào liên kết, chúng chỉ có thể được truy cập trong cùng một tệp nơi chúng được khai báo (đối với biến toàn cục tĩnh) hoặc bên ngoài tệp ngay cả bên ngoài tệp mà chúng được khai báo (biến toàn cục kiểu bên ngoài)

Trong trường hợp biến toàn cục có bộ xác định bên ngoài và nếu biến này được truy cập bên ngoài tệp đã được khởi tạo, thì nó phải được chuyển tiếp khai báo trong tệp nơi nó được sử dụng, giống như một hàm phải được chuyển tiếp tuyên bố nếu định nghĩa của nó là trong một tệp khác với nơi nó được sử dụng.

Ngược lại, nếu biến toàn cục có từ khóa tĩnh, nó không thể được sử dụng trong một tệp bên ngoài nó đã được khai báo.

(xem ví dụ dưới đây để làm rõ)

ví dụ:

//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;
}

bây giờ bất kỳ biến nào trong c ++ đều có thể là const hoặc không phải const và với mỗi 'const-ness', chúng ta có hai trường hợp liên kết c ++ mặc định, trong trường hợp không có chỉ định nào:

(i) Nếu một biến toàn cục không phải là const, mặc định liên kết của nó là extern , nghĩa là biến toàn cục không const có thể được truy cập trong một tệp .cpp khác bằng cách khai báo chuyển tiếp bằng cách sử dụng từ khóa extern (nói cách khác, không phải là toàn cầu các biến có liên kết bên ngoài (với thời lượng tĩnh của khóa học)). Ngoài ra việc sử dụng từ khóa extern trong tệp gốc nơi nó đã được xác định là không cần thiết. Trong trường hợp này để làm cho một biến toàn cục không const không thể truy cập được vào tệp bên ngoài, hãy sử dụng bộ xác định 'tĩnh' trước loại biến .

(ii) Nếu một biến toàn cục là const, liên kết của nó là tĩnh theo mặc định , nghĩa là một biến toàn cục const không thể được truy cập trong một tệp khác với nơi được định nghĩa, (nói cách khác, các biến toàn cục có liên kết bên trong (với thời lượng tĩnh tất nhiên)). Ngoài ra việc sử dụng từ khóa tĩnh để ngăn chặn một biến toàn cầu const được truy cập trong một tệp khác là không cần thiết. Ở đây, để tạo một biến toàn cục const có một liên kết ngoài, hãy sử dụng bộ xác định 'extern' trước kiểu của biến

Dưới đây là tóm tắt về các biến phạm vi toàn cầu với các liên kết khác nhau

//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

Tiếp theo, chúng tôi điều tra cách các biến toàn cục ở trên hoạt động khi được truy cập trong một tệp khác.

//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. Từ khóa tĩnh cho các biến có Phạm vi cục bộ

Cập nhật (tháng 8 năm 2019) về từ khóa tĩnh cho các biến trong phạm vi cục bộ

Điều này hơn nữa có thể được chia thành hai loại:

(i) từ khóa tĩnh cho các biến trong một khối chức năng(ii) từ khóa tĩnh cho các biến trong một khối cục bộ không tên.

(i) từ khóa tĩnh cho các biến trong một khối chức năng.

Trước đó, tôi đã đề cập rằng các biến có phạm vi cục bộ có thời lượng tự động, nghĩa là chúng tồn tại khi khối được nhập (có thể là khối bình thường, có thể là khối chức năng) và ngừng tồn tại khi khối kết thúc, câu chuyện dài, biến ngắn với phạm vi cục bộ có thời lượng tự động và các biến thời lượng tự động (và các đối tượng) không có liên kết có nghĩa là chúng không hiển thị bên ngoài khối mã.

Nếu bộ xác định tĩnh được áp dụng cho một biến cục bộ trong một khối chức năng, nó sẽ thay đổi thời lượng của biến từ tự động sang tĩnh và thời gian tồn tại của nó là toàn bộ thời lượng của chương trình, có nghĩa là nó có một vị trí bộ nhớ cố định và giá trị của nó chỉ được khởi tạo một lần trước khi khởi động chương trình như đã đề cập trong tài liệu tham khảo cpp (không nên nhầm lẫn với việc khởi tạo

Hãy xem một ví dụ.

//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;
}

Nhìn vào tiêu chí trên cho các biến cục bộ tĩnh và biến toàn cục tĩnh, người ta có thể muốn hỏi, sự khác biệt giữa chúng có thể là gì. Trong khi các biến toàn cục có thể truy cập bất cứ lúc nào trong vòng mã (trong cùng một cũng như đơn vị dịch thuật khác nhau tùy thuộc vào const -ness và extern -ness), một biến tĩnh được định nghĩa trong một khối chức năng là không trực tiếp tiếp cận. Biến phải được trả về bởi giá trị hàm hoặc tham chiếu. Hãy chứng minh điều này bằng một ví dụ:

//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;
}

Giải thích thêm về sự lựa chọn của biến cục bộ tĩnh và biến cục bộ tĩnh có thể được tìm thấy trên luồng stackoverflow này

(ii) từ khóa tĩnh cho các biến trong một khối cục bộ không tên.

các biến tĩnh trong một khối cục bộ (không phải là một khối chức năng) không thể được truy cập bên ngoài khối một khi khối cục bộ nằm ngoài phạm vi. Không cẩn thận với quy tắc này.

    //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 đã giới thiệu từ khóa constexprđảm bảo đánh giá biểu thức tại thời điểm biên dịch và cho phép trình biên dịch tối ưu hóa mã. Bây giờ nếu giá trị của biến const tĩnh trong phạm vi được biết tại thời điểm biên dịch, mã được tối ưu hóa theo cách tương tự với biến với constexpr. Đây là một ví dụ nhỏ

Tôi cũng khuyên độc giả nên tìm kiếm sự khác biệt giữa constexprstatic constcho các biến trong luồng stackoverflow này . điều này kết luận lời giải thích của tôi cho từ khóa tĩnh được áp dụng cho các biến.

Từ khóa B. 'tĩnh' được sử dụng cho các chức năng

về mặt chức năng, từ khóa tĩnh có nghĩa đơn giản. Ở đây, nó đề cập đến liên kết của hàm Thông thường tất cả các hàm được khai báo trong tệp cpp đều có liên kết ngoài theo mặc định, nghĩa là một hàm được xác định trong một tệp có thể được sử dụng trong tệp cpp khác bằng cách khai báo chuyển tiếp.

sử dụng một từ khóa tĩnh trước khi khai báo hàm giới hạn liên kết của nó thành nội bộ , tức là một hàm tĩnh có thể được sử dụng trong một tệp nằm ngoài định nghĩa của nó.

C. Staitc Keyword được sử dụng cho các biến thành viên và chức năng của các lớp

1. từ khóa 'tĩnh' cho các biến thành viên của các lớp

Tôi bắt đầu trực tiếp với một ví dụ ở đây

#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;
}

Trong ví dụ này, biến tĩnh m_designNum giữ nguyên giá trị của nó và biến thành viên riêng lẻ này (vì nó tĩnh) được chia sẻ b / w tất cả các biến của kiểu đối tượng DesignNumber

Cũng giống như các biến thành viên khác, các biến thành viên tĩnh của một lớp không được liên kết với bất kỳ đối tượng lớp nào, điều này được thể hiện bằng cách in anyNumber trong hàm chính

const vs non-const biến thành viên tĩnh trong lớp

(i) các biến thành viên tĩnh không phải là lớp const Trong ví dụ trước, các thành viên tĩnh (cả công khai và riêng tư) là các hằng số. Tiêu chuẩn ISO cấm các thành viên tĩnh không const được khởi tạo trong lớp. Do đó, như trong ví dụ trước, chúng phải được kích hoạt sau định nghĩa lớp, với lời cảnh báo rằng từ khóa tĩnh cần phải được bỏ qua

(ii) các biến thành viên const-static của lớp này rất đơn giản và đi theo quy ước khởi tạo biến thành viên const khác, tức là các biến thành viên tĩnh của một lớp có thể được khởi tạo tại điểm khai báo và chúng có thể được khởi tạo ở cuối của khai báo lớp với một cảnh báo rằng từ khóa const cần được thêm vào thành viên tĩnh khi được khởi tạo sau định nghĩa lớp.

Tuy nhiên, tôi khuyên bạn nên khởi tạo các biến thành viên tĩnh tại điểm khai báo. Điều này đi với quy ước C ++ tiêu chuẩn và làm cho mã trông gọn gàng hơn

để biết thêm ví dụ về các biến thành viên tĩnh trong một lớp, hãy tìm liên kết sau từ learncpp.com http://www.learncpp.com/cpp-tutorial/811-static-member-variabled/

2. Từ khóa 'tĩnh' cho chức năng thành viên của các lớp

Giống như các biến thành viên của các lớp có thể, là tĩnh, các hàm thành viên của các lớp cũng có thể. Các hàm thành viên bình thường của các lớp luôn được liên kết với một đối tượng của kiểu lớp. Ngược lại, các hàm thành viên tĩnh của một lớp không được liên kết với bất kỳ đối tượng nào của lớp, tức là chúng không có * con trỏ này.

Thứ hai, vì các hàm thành viên tĩnh của lớp không có * con trỏ này, nên chúng có thể được gọi bằng cách sử dụng tên lớp và toán tử phân giải phạm vi trong hàm chính (ClassName :: functionName ();)

Các hàm thành viên tĩnh thứ ba của một lớp chỉ có thể truy cập các biến thành viên tĩnh của một lớp, vì các biến thành viên không tĩnh của một lớp phải thuộc về một đối tượng lớp.

để biết thêm ví dụ về các hàm thành viên tĩnh trong một lớp, hãy tìm liên kết sau từ learncpp.com

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


1
1) Trước c ++ 17 chỉ có các biến thành viên const const tĩnh có thể được khởi tạo trong lớp, ví dụ struct Foo{static const std::string name = "cpp";};là lỗi, namephải được xác định bên ngoài lớp; với các biến nội tuyến được xâm nhập trong c ++ 17 người ta có thể mã: struct Foo{static inline const std::string name = "cpp";};2) Các hàm thành viên / thành viên tĩnh công khai có thể được truy cập bằng tên lớp với toán tử phân giải phạm vi và cũng là một thể hiện với toán tử dấu chấm (ví dụ: instance.some_static_method ())
oz1

Không nên "m_anyVariable" trở thành "m_anyNumber"? trong ví dụ mã cuối cùng của bạn?
gebbissimo

Tôi không thể đánh giá tính đầy đủ và chính xác của câu trả lời, nhưng nó có vẻ thực sự toàn diện và dễ làm theo. Cảm ơn rất nhiều! Nếu bạn muốn cải thiện nó, một bản tóm tắt ngắn ngay từ đầu có thể có ích vì nó là một văn bản khá dài và các điểm chính có thể dễ dàng được hình dung như một danh sách lồng nhau hoặc sơ đồ cây cho những người biết các thuật ngữ như "nội bộ / bên ngoài liên kết "
gebbissimo

18

Nó thực sự khá đơn giản. Nếu bạn khai báo một biến là tĩnh trong phạm vi của hàm, giá trị của nó được giữ nguyên giữa các lệnh gọi liên tiếp đến hàm đó. Vì thế:

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

sẽ hiển thị 678thay 666vì bởi vì nó ghi nhớ giá trị tăng dần.

Đối với các thành viên tĩnh, họ bảo tồn giá trị của chúng trên các thể hiện của lớp. Vậy đoạn mã sau:

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

sẽ in 4, vì First.a và second.a về cơ bản là cùng một biến. Đối với việc khởi tạo, xem câu hỏi này.


Điều này không giải quyết các biến phạm vi không gian tên.
Michael Hagar

10

Khi quý khách khai báo một staticbiến ở phạm vi tập tin, sau đó biến là chỉ có sẵn trong đó tập tin cụ thể (về mặt kỹ thuật, các đơn vị dịch *, nhưng chúng ta không làm phức tạp này quá nhiều). Ví dụ:

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;
}

chính.cpp:

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

    return 0;
}

Đối với một địa phương biến, staticphương tiện mà biến số sẽ là zero-khởi tạo duy trì giá trị của nó giữa các cuộc gọi:

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;
}

Đối với các biến lớp , điều đó có nghĩa là chỉ có một phiên bản duy nhất của biến đó được chia sẻ giữa tất cả các thành viên của lớp đó. Tùy thuộc vào quyền, biến có thể được truy cập từ bên ngoài lớp bằng tên đầy đủ.

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;
};

Đánh dấu một hàm không phải là lớp staticlàm cho hàm chỉ có thể truy cập được từ tệp đó và không thể truy cập được từ các tệp khác.

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;
}

Đối với các hàm thành viên lớp, đánh dấu chúng là staticcó nghĩa là hàm không cần phải được gọi trong một thể hiện cụ thể của một đối tượng (tức là nó không có thiscon trỏ).

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

Các biến tĩnh được chia sẻ giữa mọi phiên bản của một lớp, thay vì mỗi lớp có biến riêng.

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;

Mỗi phiên bản của 'MyClass' có 'myVar' của riêng họ, nhưng có chung 'myStaticVar'. Trên thực tế, bạn thậm chí không cần một phiên bản MyClass để truy cập 'myStaticVar' và bạn có thể truy cập nó bên ngoài lớp như thế này:

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

Khi được sử dụng bên trong một hàm như một biến cục bộ (chứ không phải là một biến thành viên lớp), từ khóa tĩnh sẽ làm một cái gì đó khác nhau. Nó cho phép bạn tạo một biến liên tục, mà không đưa ra phạm vi toàn cầu.

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;
}

Đó là một biến toàn cầu về sự bền bỉ ... nhưng không phải là toàn cầu về phạm vi / khả năng tiếp cận.

Bạn cũng có thể có các chức năng thành viên tĩnh. Các hàm tĩnh về cơ bản là các hàm không phải là thành viên, nhưng bên trong không gian tên của tên lớp và có quyền truy cập riêng đến các thành viên của lớp.

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;
}

Khi bạn gọi một hàm thành viên, có một tham số ẩn được gọi là 'this', đó là một con trỏ tới thể hiện của lớp gọi hàm. Các hàm thành viên tĩnh không có tham số ẩn đó ... chúng có thể gọi được mà không có thể hiện của lớp, nhưng cũng không thể truy cập các biến thành viên không tĩnh của một lớp, vì chúng không có con trỏ 'this' để làm việc. Họ không được gọi trong bất kỳ trường hợp cụ thể nào.


1
"Giả sử nó có thể truy cập công khai." - nó không thể.
Luchian Grigore

2
myStaticVarcũng cần được xác định Một điều quan trọng cần đề cập là khi trả lời một câu hỏi về ngữ nghĩa của statictừ khóa, bạn có nghĩ vậy không?
Praetorian

@Praetorian: Cảm ơn, đã sửa.
Jamin Gray

1
@JaminGrey Bởi "độc lập tĩnh" Tôi có nghĩa là các hàm không phải thành viên tĩnh và tôi viết như vậy bất cứ khi nào tôi cần một số chức năng mới chỉ trong tệp CPP hiện tại và không muốn trình liên kết phải xử lý một ký hiệu bổ sung.
VR

1
@VR lẻ! Tôi không bao giờ biết rằng chức năng tồn tại. Cảm ơn đã mở rộng kiến ​​thức của tôi!
Jamin Gray

1

Tôi không phải là lập trình viên C nên tôi không thể cung cấp cho bạn thông tin về việc sử dụng tĩnh trong chương trình C một cách chính xác, nhưng khi nói đến lập trình hướng đối tượng tĩnh về cơ bản khai báo một biến, hoặc một hàm hoặc một lớp giống nhau trong suốt cuộc đời của chương trình. Lấy ví dụ.

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

Khi bạn khởi tạo lớp này trong Main của bạn, bạn sẽ làm một cái gì đó như thế này.

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

Hai thể hiện lớp này hoàn toàn khác nhau và hoạt động độc lập với nhau. Nhưng nếu bạn định tạo lại lớp A như thế này.

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

Hãy quay trở lại chính một lần nữa.

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

Sau đó a1 và a2 sẽ chia sẻ cùng một bản sao của int x, theo đó mọi hoạt động trên x trong a1 sẽ ảnh hưởng trực tiếp đến các hoạt động của x trong a2. Vì vậy, nếu tôi đã làm điều này

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
}

Cả hai trường hợp của lớp A đều chia sẻ các biến và hàm tĩnh. Hy vọng điều này trả lời câu hỏi của bạn. Kiến thức hạn chế của tôi về C cho phép tôi nói rằng việc xác định hàm hoặc biến là tĩnh có nghĩa là nó chỉ hiển thị với tệp mà hàm hoặc biến được định nghĩa là tĩnh. Nhưng điều này sẽ được trả lời tốt hơn bởi một người C chứ không phải tôi. C ++ cho phép cả hai cách C và C ++ khai báo các biến của bạn là tĩnh vì nó hoàn toàn tương thích ngược với C.


1

Nó có nghĩa gì với biến cục bộ? Đó có phải là một biến cục bộ?

Có - Không toàn cầu, chẳng hạn như một biến cục bộ chức năng.

Bởi vì cũng có khi bạn khai báo một hàm cục bộ là tĩnh mà nó chỉ được khởi tạo một lần, lần đầu tiên nó vào hàm này.

Đúng.

Nó cũng chỉ nói về thời lượng lưu trữ liên quan đến các thành viên trong lớp, còn về trường hợp không cụ thể, đó cũng là một tính chất của tĩnh không? Hay là thời gian lưu trữ?

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

điều đó có nghĩa là, tất cả các trường hợp Rchia sẻ int R::a- int R::akhông bao giờ được sao chép.

Bây giờ những gì về trường hợp với phạm vi tĩnh và tập tin?

Hiệu quả là toàn cầu có hàm tạo / hàm hủy khi thích hợp - khởi tạo không được hoãn lại cho đến khi truy cập.

Làm thế nào để tĩnh liên quan đến liên kết của một biến?

Đối với một chức năng địa phương, nó là bên ngoài. Truy cập: Nó có thể truy cập vào chức năng (trừ khi tất nhiên, bạn trả lại nó).

Đối với một lớp học, nó là bên ngoài. Truy cập: Áp dụng tiêu chuẩn truy cập tiêu chuẩn (công khai, được bảo vệ, riêng tư).

static cũng có thể chỉ định liên kết nội bộ, tùy thuộc vào nơi nó được khai báo (tệp / không gian tên).

Toàn bộ từ khóa tĩnh này là hết sức khó hiểu

Nó có quá nhiều mục đích trong C ++.

ai đó có thể làm rõ các cách sử dụng khác nhau cho tiếng Anh và cũng cho tôi biết khi nào nên khởi tạo một thành viên lớp tĩnh?

Nó tự động được khởi tạo trước mainnếu nó được tải và có một hàm tạo. Điều đó có vẻ như là một điều tốt, nhưng thứ tự khởi tạo phần lớn nằm ngoài tầm kiểm soát của bạn, vì vậy việc khởi tạo phức tạp trở nên rất khó duy trì và bạn muốn giảm thiểu điều này - nếu bạn phải có tĩnh, thì chức năng quy mô cục bộ tốt hơn nhiều trên các thư viện và dự án. Theo như dữ liệu với thời lượng lưu trữ tĩnh, bạn nên cố gắng giảm thiểu thiết kế này, đặc biệt nếu có thể thay đổi (biến toàn cục). Khởi tạo 'thời gian' cũng thay đổi vì một số lý do - trình tải và kernel có một số thủ thuật để giảm thiểu dấu chân bộ nhớ và khởi tạo trì hoãn, tùy thuộc vào dữ liệu được đề cập.


1

Đối tượng tĩnh: Chúng ta có thể định nghĩa các thành viên lớp tĩnh bằng từ khóa tĩnh. Khi chúng ta khai báo một thành viên của một lớp là tĩnh, điều đó có nghĩa là cho dù có bao nhiêu đối tượng của lớp được tạo ra, chỉ có một bản sao của thành viên tĩnh.

Một thành viên tĩnh được chia sẻ bởi tất cả các đối tượng của lớp. Tất cả dữ liệu tĩnh được khởi tạo về 0 khi đối tượng đầu tiên được tạo, nếu không có khởi tạo nào khác. Chúng ta không thể đặt nó trong định nghĩa lớp nhưng nó có thể được khởi tạo bên ngoài lớp như được thực hiện trong ví dụ sau bằng cách khai báo biến tĩnh, sử dụng toán tử phân giải phạm vi :: để xác định nó thuộc lớp nào.

Chúng ta hãy thử ví dụ sau để hiểu khái niệm về các thành viên dữ liệu tĩnh:

#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;
}

Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:

Constructor called.
Constructor called.
Total objects: 2

Thành viên hàm tĩnh: Bằng cách khai báo một thành viên hàm là tĩnh, bạn làm cho nó độc lập với bất kỳ đối tượng cụ thể nào của lớp. Một hàm thành viên tĩnh có thể được gọi ngay cả khi không có đối tượng của lớp tồn tại và các hàm tĩnh được truy cập chỉ sử dụng tên lớp và toán tử phân giải phạm vi ::.

Hàm thành viên tĩnh chỉ có thể truy cập thành viên dữ liệu tĩnh, các hàm thành viên tĩnh khác và bất kỳ chức năng nào khác từ bên ngoài lớp.

Các hàm thành viên tĩnh có phạm vi lớp và chúng không có quyền truy cập vào con trỏ của lớp này. Bạn có thể sử dụng hàm thành viên tĩnh để xác định xem một số đối tượng của lớp đã được tạo hay chưa.

Chúng ta hãy thử ví dụ sau để hiểu khái niệm về các thành viên hàm tĩnh:

#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;
}

Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:

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

1
Nó sẽ là quyền đề cập rằng mô hình này được lấy từ tutorialspoint.com/cplusplus/cpp_static_members.htm
BugShotGG
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.