c++ static、extern、const、volatile

 

 

=============

来自:https://blog.csdn.net/fymx203/article/details/89604263

static作用:
修饰局部变量,改变局部变量生命周期。(具有记忆功能)

下面我们看个例子:
(1)变量不加 static 修饰

运行结果如下:

(2)变量被 static 修饰

运行结果如下:

总结:

不加static修饰,函数或者代码块中的变量在函数或者代码块执行完毕后就直接回收销毁了,每次执行都会重新分配内存,每次都会销毁。
加 static 修饰,函数或者代码块中的变量在函数或者代码块执行第一次初始化分配内存后,就算函数或者代码块执行完毕,该变量也不会被回收销毁,直到程序结束 static 变量才会被回收。
当 static 作用于代码块{ }内部定义的变量(局部变量)声明时,static关键字用于修改变量的存储类型(生命期),使变量从自动变量成为静态的局部变量,即编译时就为变量分配内存,直到程序退出才释放存储单元。这样,使得该局部变量有记忆功能,可以记忆上次的数据。
当 static 作用于函数定义,或者用于代码块{ }之外的变量(外部变量)声明时,static关键字用于修改标识符的链接属性。外部链接属性变为内部链接属性,标识符的存储类型和作用域不受影响。也就是说变量或者函数只能在当前源文件中访问,不能在其他源文件中访问。
extern作用
仅仅是用来声明外部全局变量(注:extern不能用来定义变量)。

为了起到全局变量的作用,即一个变量在一个文件中定义了,可以在其他文件中修改。这样可以在.h头文件中,使用extern修饰,然后在其他文件中使用该变量。
int a; ——> 这是声明变量a
int a = 0; ——>这是定义一个变量a
extern int x; //变量是声明,并未实际分配地址。

const作用
const 修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。或者说const意味着只读。这样可以保护被修改的东西,防止意外的修改,增强程序的健壮性。

参看:C语言再学习 – 关键字const

volatile作用
volatile 关键字是一种类型修饰符。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。直接读值是指从内存重新装载内容,而不是直接从寄存器拷贝内容。
volatile应用比较多的场合,在中断服务程序和cpu相关寄存器的定义。
参看:C语言再学习 – 关键字volatile

参考:

https://blog.csdn.net/qq_29350001/article/details/54668893
https://www.jianshu.com/p/1598004e8215
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/fymx203/article/details/89604263

==================

首先得知道为什么要使用静态数据成员:


在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。

使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

静态数据成员的使用方法和注意事项如下:

1、静态数据成员在定义或说明时前面加关键字static。

2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:
<数据类型><类名>::<静态数据成员名>=<值>

这表明:
(1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。

(2) 初始化时不加该成员的访问权限控制符private,public等。

(3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。

3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。

4、引用静态数据成员时,采用如下格式:

<类名>::<静态成员名>

 

为什么不能在静态成员函数中使用非静态变量:

程序最终都将在内存中执行,变量只有在内存中占有一席之地时才能被访问。

因为静态是针对类的,而成员变量为对象所有。

静态成员函数不属于任何一个类对象,没有this指针,而非静态成员必须随类对象的产生而产生,所以静态成员函数”看不见”非静态成员,自然也就不能访问了

类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。

在一个类的静态成员中去访问其非静态成员之所以会出错是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。

C++会区分两种类型的成员函数:静态成员函数和非静态成员函数。这两者之间的一个重大区别是,静态成员函数不接受隐含的this自变量。所以,它就无法访问自己类的非静态成员。

 

静态成员函数

静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。

在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。如果静态成员函数中要引用非静态成员时,可通过对象来引用。下面通过例子来说明这一点。

#include <iostream>
using namespace std;
class M{
public:
M(int a){
this->A=a;
this->B=this->B+a;
}
static void f1(M m);
private:
int A;
static int B;
};
int M::B=0;//静态数据成员初始化的格式<数据类型><类名>::<静态数据成员名>=<值>,z初始化在外部实现
void M::f1(M m){
cout<<"A="<<m.A<<endl;静态成员函数中通过对象来引用非静态成员
cout<<"B="<<B<<endl;
}
int main()
{
M P(5);
M Q(10);
M::f1(P);静态成员函数调用时不用对象名
return 0;
}
从中可看出,调用静态成员函数使用如下格式:

<类名>::<静态成员函数名>(<参数表>);

运行结果:

A=5

B=15


————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_42145502/article/details/106963659

===================

const:

const关键字

【C++ const的各种用法详解】【const用法深入浅出】

C++作为一种面向对象的经典语言,它是非常的强大,它的每个部分都值得我们去深入了解。

const的基本概念:

     const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。

下面我就const的用法来谈谈:

const的用法大致可分为以下几个方面:

(1)const修饰基本数据类型

(2)const应用到函数中

(3)const在类中的用法

(4)const修饰类对象,定义常量对象 

一、const修饰基本数据类型 

     1.const修饰一般常量及数组  

          const int a=10;               等价的书写方式:     int const a=10;
          const int arr[3]={1,2,3};                        int const arr[3]={1,2,3};

     对于类似这些基本数据类型,修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。在使用这些常量的时候,只要不改变这些常量的值便好。  

     2.const修饰指针变量*及引用变量&         

          介绍本部分内容之前,先说说指针和引用的一些基本知识。

          指针(pointer)是用来指向实际内存地址的变量,一般来说,指针是整型,而且一般的大家会接受十六进制的输出格式。
          引用(reference)是其相应变量的别名,用于向函数提供直接访问参数(而不是参数的副本)的途径,与指针相比,引用是一种受限制的指针类型,或者说是指针的一个子集,而从其功能上来看,似乎可以说引用是指针功能的一种高层实现。

关于运算符&和*:
           在C++里,沿袭C中的语法,有两个一元运算符用于指针操作:&和*。按照本来的定义,&应当是取址符,*是指针符也就是说, &用于返回变量的实际地址,*用于返回地址所指向的变量,他们应当互为逆运算。实际的情况也是如此。

         在定义变量的引用的时候,&只是个定义引用的标志,不代表取地址。

举例:  

复制代码
 1 #include<iostream.h>
 2 void main()
 3 {
 4     int a;  //a is an integer
 5     int *aPtr;  //aPtr is a pointer to an integer
 6 
 7     a=7;
 8     aPtr = &a;
 9     cout<<"Showing that * and & are inverses of "<<"each other.\n";
10     cout<<"a="<<a<<"  *aPtr="<<*aPtr<<"\n";
11     cout<<"&*aPtr = "<<&*aPtr<<endl;
12     cout<<"*&aPtr = "<<*&aPtr <<endl;
13 }
复制代码

 运行结果: 

        

         了解完指针和应用的基本概念之后,下面继续我们的话题。

const修饰指针(*):

const int* a = & [1]          //非常量数据的常量指针    指针常量
int const *= & [2]          //非常量数据的常量指针     a is a pointer to the constant char variable
int* const a = & [3]          //常量数据的非常量指针指针常量 常量指针 a is a constant pointer to the (non-constant) char variable
const int* const a = & [4]    //常量数据的常量指针

可以参考《Effective c++》Item21上的做法,

如果const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;

如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。

因此,[1]和[2]的情况相同,都是指针所指向的内容为常量,这种情况下不允许对内容进行更改操作,如不能*a = 3 ;

[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;

[4]为指针本身和指向的内容均为常量。   

const修饰引用(&):

   int const &a=x;
   const int &a=x;
   int &const a=x;//这种方式定义是C、C++编译器未定义,虽然不会报错,但是该句效果和int &a一样。   
          这两种定义方式是等价的,此时的引用a不能被更新。如:a++ 这是错误的。   

 二、const应用到函数中  

      1.作为参数的const修饰符

      2.作为函数返回值的const修饰符  

      其实,不论是参数还是返回值,道理都是一样的,参数传入时候和函数返回的时候,初始化const变量
      1      修饰参数的const,如 void fun0(const A* a ); void fun1(const A& a);
      调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const A* a,

      则不能对传递进来的指针的内容     进行改变,保护了原指针所指向的内容;如形参为const A& a,则不能对传递进来的引用对象进行改变,

      保护了原对象的属性。
     [注意]:参数const通常用于参数为指针或引用的情况; 
     2      修饰返回值的const,如const A fun2( ); const A* fun3( );
         这样声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。

const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
     返回值用const修饰可以防止允许这样的操作发生:

Rational a,b;
Radional c;
(a*b) = c;

一般用const修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候。

类中的成员函数:A fun4()const; 其意义上是不能修改所在类的的任何变量。 


  三、类中定义常量(const的特殊用法) 

 

 在类中实现常量的定义大致有这么几种方式实现: 

1.使用枚举类型 

复制代码
class test
{
     enum { SIZE1 = 10, SIZE2 = 20}; // 枚举常量
     int array1[SIZE1];  
     int array2[SIZE2];
};
复制代码

2.使用const 

     不能在类声明中初始化const数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道SIZE的值是什么。 

  class test
  {
        const int SIZE = 100;     // 错误,企图在类声明中初始化const数据成员
        int array[SIZE];          // 错误,未知的SIZE
  };

     正确的使用const实现方法为:const数据成员的初始化只能在类构造函数的初始化表中进行。 

复制代码
 class A
 {…
        A(int size);      // 构造函数
        const int SIZE ; 
 };
 A::A(int size) : SIZE(size)    // 构造函数的初始化表
{
      …
}
//error 赋值的方式是不行的
A::A(int size)
{
     SIZE=size;
}
void main()
{
    A  a(100); // 对象 a 的SIZE值为100
    A  b(200); // 对象 b 的SIZE值为200
}
复制代码

       注意:对const成员变量的初始化,不能在变量声明的地方,必须在类的构造函数的初始化列表中完成,即使是在构造函数内部赋值也是不行的。

      具体原因请参见 【初始化列表和赋值的区别】 

3.使用static const 

通过结合静态变量来实现:

复制代码
#include<iostream.h> 
class Year

private:
 int y; 
public:
 static int const Inity;
public
 Year()
 {
  y=Inity;
 }
 };
int const Year::Inity=1997;//静态变量的赋值方法,注意必须放在类外定义
void main()
{
 cout<<Year.Inity<<endl;//注意调用方式,这里是用类名调用的。
}
复制代码

 到这里就把在类中定义常量的方法都陈列出来了。 

四、const定义常量对象,以及常量对象的用法 

 class test
{
public:
    test():x(1)
    {
        y=2;
    }
    ~test()
    {}
    void set(int yy)
    {
        y=yy;
    }
    int getx() const
    {
        return x;
    }
//protected:
    const int x;
    int y;
};
void main()
{
 const test t;
 t.set(33);//error
 t.getx();
}

常量对象只能调用常量函数,别的成员函数都不能调用。 

五、使用const的一些建议
   <1>要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
   <2> 要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
   <3> 在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
   <4> const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
   <5>不要轻易的将函数的返回值类型定为const;
   <6>除了重载操作符外一般不要将返回值类型定为对某个对象的const引用; 

参考资料:

(1)继续C++——指针,引用和常量限定符const           http://blog.csdn.net/jarodwen/archive/2006/12/19/1449568.aspx  

(2)C++中const用法总结                                         http://lanmh.iteye.com/blog/796410

(3)常量函数、常量引用参数、常量引用返回值[C++]    http://www.cnblogs.com/JCSU/articles/1045801.html

(4)const使用详解                                                   http://www.vckbase.com/document/viewdoc/?id=412

(5)c/c++中const用法总结                                       http://www.newsmth.net/pc/pccon.php?id=10002714&nid=359712

 

(6)高质量C++/C编程指南                                           http://fanqiang.chinaunix.net/a4/b2/20020717/060200270.html

(7) Effective C++笔记—构造函数赋值VS初始化列表  http://blog.csdn.net/lifeisbetter/archive/2009/12/29/5099085.aspx

(8)初始化列表与构造函数内赋值                                      http://yoyo.is-programmer.com/posts/10610.html 

 
https://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html
 

===============

const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。

      static表示的是静态的。类的静态成员函数、静态成员变量是和类相关的,而不是和类的具体对象相关的。即使没有具体对象,也能调用类的静态成员函数和成员变量。一般类的静态函数几乎就是一个全局函数,只不过它的作用域限于包含它的文件中。

      在C++中,static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化,如:double Account::Rate=2.25;static关键字只能用于类定义体内部的声明中,定义时不能标示为static

      在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。

      const数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。

      const数据成员的初始化只能在类的构造函数的初始化列表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static cosnt。

 

  1. class Test  
  2. {  
  3. public:  
  4.       Test():a(0){}  
  5.       enum {size1=100,size2=200};  
  6. private:  
  7.       const int a;//只能在构造函数初始化列表中初始化  
  8.        static int b;//在类的实现文件中定义并初始化  
  9.       const static int c;//与 static const int c;相同。  
  10. };  
  11.   
  12. int Test::b=0;//static成员变量不能在构造函数初始化列表中初始化,因为它不属于某个对象。  
  13. cosnt int Test::c=0;//注意:给静态成员变量赋值时,不需要加static修饰符。但要加cosnt  

 

      cosnt成员函数主要目的是防止成员函数修改对象的内容。即const成员函数不能修改成员变量的值,但可以访问成员变量。当方法成员函数时,该函数只能是const成员函数。

      static成员函数主要目的是作为类作用域的全局函数。不能访问类的非静态数据成员。类的静态成员函数没有this指针,这导致:1、不能直接存取类的非静态成员变量,调用非静态成员函数2、不能被声明为virtual

关于static、const、static cosnt、const static成员的初始化问题:

1、类里的const成员初始化:

在一个类里建立一个const时,不能给他初值

 

  1. class foo  
  2. {  
  3. public:  
  4.       foo():i(100){}  
  5. private:  
  6.       const int i=100;//error!!!  
  7. };  
  8. //或者通过这样的方式来进行初始化  
  9. foo::foo():i(100)  
  10. {}  

 

2、类里的static成员初始化:

      类中的static变量是属于类的,不属于某个对象,它在整个程序的运行过程中只有一个副本,因此不能在定义对象时 对变量进行初始化,就是不能用构造函数进行初始化,其正确的初始化方法是:

数据类型 类名::静态数据成员名=值;

 

  1. class foo  
  2. {  
  3. public:  
  4.       foo();  
  5. private:  
  6.       static int i;  
  7. };  
  8.   
  9. int foo::i=20;  
  10. 这表明:  
  11. 1、初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆  
  12. 2、初始化时不加该成员的访问权限控制符private、public等  
  13. 3、初始化时使用作用域运算符来表明它所属的类,因此,静态数据成员是类的成员而不是对象的成员。  

 

3、类里的static cosnt 和 const static成员初始化

      这两种写法的作用一样,为了便于记忆,在此值说明一种通用的初始化方法:

 

  1. class Test  
  2. {  
  3. public:  
  4.       static const int mask1;  
  5.       const static int mask2;  
  6. };  
  7. const Test::mask1=0xffff;  
  8. const Test::mask2=0xffff;  
  9. //它们的初始化没有区别,虽然一个是静态常量一个是常量静态。静态都将存储在全局变量区域,其实最后结果都一样。可能在不同编译器内,不同处理,但最后结果都一样。  

 

这是一个完整的例子:

 

  1. #ifdef A_H_  
  2. #define A_H_  
  3. #include <iostream>  
  4. using namespace std;  
  5. class A  
  6. {  
  7. public:  
  8.       A(int a);  
  9.       static void print();//静态成员函数  
  10. private:  
  11.       static int aa;//静态数据成员的声明  
  12.        static const int count;//常量静态数据成员(可以在构造函数中初始化)  
  13.        const int bb;//常量数据成员  
  14. };  
  15. int A::aa=0;//静态成员的定义+初始化  
  16. const int A::count=25;//静态常量成员定义+初始化  
  17. A::A(int a):bb(a)//常量成员的初始化  
  18. {  
  19.       aa+=1;  
  20. }  
  21. void A::print()  
  22. {  
  23.       cout<<"count="<<count<<endl;  
  24.       cout<<"aa="<<aa<<endl;  
  25. }  
  26. #endif  
  27. void main()  
  28. {  
  29.       A a(10);  
  30.       A::print();//通过类访问静态成员函数  
  31.       a.print();//通过对象访问静态成员函数  
  32. }  

 

 https://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html

=====

extern:

extern是什么及其作用

  extern是c++引入的一个关键字,它可以应用于一个全局变量,函数或模板声明,说明该符号具有外部链接(external linkage)属性。也就是说,这个符号在别处定义。一般而言,C++全局变量的作用范围仅限于当前的文件,但同时C++也支持分离式编译,允许将程序分割为若干个文件被独立编译。于是就需要在文件间共享数据,这里extern就发挥了作用。

 

先导知识

符号的定义和声明

  在介绍extern之前,我们需要了解一下变量的声明和定义。变量的声明指向程序表名变量的类型和名字,即使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而变量的定义指申请存储空间,并将其与变量名相关联,除此之外,还可以为变量指定初始值。在程序中变量可以声明多次,但只能定义一次。一般而言,定义就是声明。但C++中由于extern的缘故,变量的声明和定义是可以分开的。凡是没有带extern的声明同时也都是定义。而对函数而言,带有{}是定义,否则是声明。如果想声明一个变量而非定义它,就在变量名前添加关键字extern,且不要显式的初始化变量。看下面的例子:

复制代码
//fileA.cpp
int i;                //声明并定义i
int j = 1;            //声明并定义j
double max(double d1,double d2){} //定义

//fileB.cpp
extern int i;          //声明i而非定义
extern int j = 2;     //定义j而非声明,会报错,多重定义
int j;                //错误,重定义,注意这里的j是在全局范围内声明
extern double max(double d1,double d2); //声明
复制代码

C++中的链接属性

   链接属性一定程度范围决定着符号的作用域,C++中链接属性有三种:none(无)、external(外部)和 internal(内部)。

  • external,外部链接属性。非常量全局变量和自由函数(除成员函数以外的函数)均默认为外部链接的,它们具有全局可见性,在全局范围不允许重名,详情可见例子。
  • internal,内部链接属性。具有该属性的类型有,const对象,constexpr对象,命令空间内的静态对象(static objects in namespace scope)
  • none,在类中、函数体和代码块中声明的变量默认是具有none链接属性。它和internal一样只在当前作用域可见。

extern的用法

  extern有3种用法,分别如下:

非常量全局变量的外部链接

   最常见的用法,当链接器在一个全局变量声明前看到extern关键字,它会尝试在其他文件中寻找这个变量的定义。这里强调全局且非常量的原因是,全局非常量的变量默认是外部链接的。

复制代码
//fileA.cpp
int i = 1;         //声明并定义全局变量i

//fileB.cpp
extern int i;    //声明i,链接全局变量

//fileC.cpp
extern int i = 2;        //错误,多重定义
int i;                    //错误,这是一个定义,导致多重定义
main()
{
    extern int i;        //正确
    int i = 5;            //正确,新的局部变量i;
}
复制代码

常量全局变量的外部链接

  常量全局变量默认是内部链接的,所以想要在文件间传递常量全局变量需要在定义时指明extern,如下所示:

//fileA.cpp
extern const int i = 1;        //定义

//fileB.cpp                    //声明
extern const int i;

extern "C" 和extern "C++"函数声明

   在C++中,当与字符串连用时,extern指明当前声明使用了其他语言的链接规范,如extern "C",就指明使用C语言的链接规范。原因是,C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时无法找到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。C和C++对函数的处理方式是不同的.extern "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话,那么就要使用extern "C"来说明。

  例子如下:

复制代码
// 声明printf函数使用C链接
extern "C" int printf(const char *fmt, ...);


//声明指定的头文件内所有的东西都使用 C 链接
extern "C" {
#include <stdio.h>
}

//  声明函数ShowChar和GetChar使用 C 链接
extern "C" {
    char ShowChar(char ch);
    char GetChar(void);
}

//  定义函数 ShowChar 和 GetChar 使用 C 链接
extern "C" char ShowChar(char ch) {
    putchar(ch);
    return ch;
}

extern "C" char GetChar(void) {
    char ch;
    ch = getchar();
    return ch;
}

// 声明全局变量 errno 为C链接
extern "C" int errno;

//又比如,在程序中常见的代码段
#ifdef __cplusplus  
extern "C" {  
#endif  
  
/**** some declaration or so *****/  
  
#ifdef __cplusplus  
}  
#endif

//这里__cplusplus是cpp中的自定义宏,定义了这个宏就表明这是一段cpp的代码,也就是说,
//上面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{和}处理其中的代码。
复制代码

 

一些问题

  •  使用extern和包含头文件来引用函数有什么区别呢?

  与include相比,extern引用另一个文件的范围小,include可以引用另一个文件的全部内容。extern的引用方式比包含头文件要更简洁。extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。

注意事项

  • 不要把变量定义放入.h文件,这样容易导致重复定义错误
  • 尽量使用static关键字把变量定义限制于该源文件作用域,除非变量被设计成全局的。
  • 可以在头文件中声明一个变量,在用的时候包含这个头文件就声明了这个变量。

 

参考博客:

1.https://blog.csdn.net/duangyhn/article/details/81393810

1.https://docs.microsoft.com/en-us/cpp/cpp/extern-cpp?view=vs-2019

https://www.cnblogs.com/honernan/p/13431431.html

=========

C++ volatile 关键字

转载C/C++ 中 volatile 关键字详解

1. 为什么使用 volatile?

C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier。这是 BS 在 “The C++ Programming Language” 对 volatile 修饰词的说明:

A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。因此,遇到这个关键字声明的变量,编译器对访问该变量的代码就不能再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:

volatile int i = 10; 
int a = i;

...

// 其他代码,并未明确告诉编译器,对 i 进行过操作;
int b = i;

volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i 的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据(例如从 a 中读到的数据)放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。下面通过插入汇编代码,测试有无 volatile 关键字,对程序最终代码的影响,输入下面的代码:

关于寄存器变量:在程序运行时,根据需要到内存中相应的存储单元中调用,如果一个变量在程序中频繁使用,例如循环变量,那么,系统就必须多次访问内存中的该单元,影响程序的执行效率。因此,C语言\C++语言还定义了一种变量,不是保存在内存上,而是直接存储在CPU中的寄存器中,这种变量称为寄存器变量。register型变量常用于作为循环控制变量,这是使用它的高速特点的最佳场合。

#include <stdio.h> 

void main() {
  int i = 10;
  int a = i;

  printf("i = %d", b);

  // 下面的汇编的作用就是改变内存中 i 的值
  // 但是又不让编译器知道
  __asm {
    mov dword ptr [ebp-4], 20h
  } 
  
  int b = i;
  printf("i = %d", b);
}

然后,在 Debug 模式运行程序,输出结果如下:

i = 10
i = 32

然而,在 Release 模式运行程序,输出结果如下:

i = 10
i = 10

输出的结果明显表明,Release 模式下,编译器对代码进行了优化,第二次没有输出正确的 i 值。下面,我们把 i 的声明加上 volatile 关键字,看看有什么变化:

#include <stdio.h>
 
void main() {
  volatile int i = 10;
  int a = i;
 
  printf("i = %d", a);
  __asm {
    mov dword ptr [ebp-4], 20h
  }
 
  int b = i;
  printf("i = %d", b);
}

分别在 Debug 和 Release 模式运行程序,结果都是:

i = 10
i = 32

这说明这个 volatile 关键字发挥了它的作用。其实不只是内嵌汇编操纵栈这种方式属于编译无法识别的变量改变,另外更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。一般说来,volatile 用在如下的几个地方:

  • 中断服务程序中修改的供其它程序检测的变量需要加 volatile;
  • 多线程间被几个任务共享的变量;
  • 状态寄存器一类的并行设备硬件寄存器,因为每次对它的读写都可能有不同的意义。

2. volatile 指针

和 const 修饰词类似,const 有常量指针和指针常量的说法, volatile 也有相应的概念:

修饰由指针指向的对象,数据是 const 或者 volatile 的:

const char* cpch;
volatile char* vpch;

指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:

char* const pchc;
char* volatile pchv;

注意:

  • 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
  • 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。

此外,基本类型的对象用volatile修饰后,仍旧支持所有的操作(加、乘、赋值等)。但是,用户定义的非基本类型(class、struct、union)的对象被volatile修饰后,具有不同行为:

  • 只能调用 volatile 成员函数,即只能访问它的接口的子集;
  • 只能通过 const_cast 运算符转为没有 volatile 修饰的普通对象,即由此可以获得对类型接口的完全访问;
  • volatile 性质会传递给它的数据成员;

3. 多线程下的 volatile

有些变量是用 volatile 关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用 volatile 声明,该关键字的作用是防止优化编译器把变量从内存装入 CPU 寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile 的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值,如下:

volatile bool bStop = false;

(1) 在一个线程中:

while(!bStop) { ... }
bStop = false;
return;

(2) 在另一个线程中,要终止上面的线程循环:

bStop = true;
while(bStop);
// 等待上面的线程终止,如果 bStop 不使用 volatile 申明,那么这个循环将是一个死循环
// 因为 bStop 已经读取到了寄存器中,寄存器中 bStop 的值永远不会变成 FALSE
// 加上volatile,程序在执行时,每次均从内存中读出 bStop 的值,就不会死循环了

这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其拷贝放在寄存器中用以加快指令的执行速度,例如下段代码中:

int nMyCounter = 0;
for (; nMyCounter < 100; nMyCounter++) {

}

在此段代码中,nMyCounter 的拷贝可能存放到某个寄存器中(循环中,对 nMyCounter 的测试及操作总是对此寄存器中的值进行),但是另外又有段代码执行了这样的操作:nMyCounter -= 1; 这个操作中,对 nMyCounter 的改变是对内存中的 nMyCounter 进行操作,于是出现了这样一个现象:nMyCounter 的改变不同步。

https://v1otusc.github.io/2021/05/20/C++_Volatile_%E5%85%B3%E9%94%AE%E5%AD%97/

========

C++ 存储类

存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 C++ 程序中可用的存储类:

  • auto:这是默认的存储类说明符,通常可以省略不写。auto 指定的变量具有自动存储期,即它们的生命周期仅限于定义它们的块(block)。auto 变量通常在栈上分配。

  • register:用于建议编译器将变量存储在CPU寄存器中以提高访问速度。在 C++11 及以后的版本中,register 已经是一个废弃的特性,不再具有实际作用。

  • static:用于定义具有静态存储期的变量或函数,它们的生命周期贯穿整个程序的运行期。在函数内部,static变量的值在函数调用之间保持不变。在文件内部或全局作用域,static变量具有内部链接,只能在定义它们的文件中访问。

  • extern:用于声明具有外部链接的变量或函数,它们可以在多个文件之间共享。默认情况下,全局变量和函数具有 extern 存储类。在一个文件中使用extern声明另一个文件中定义的全局变量或函数,可以实现跨文件共享。

  • mutable (C++11):用于修饰类中的成员变量,允许在const成员函数中修改这些变量的值。通常用于缓存或计数器等需要在const上下文中修改的数据。

  • thread_local (C++11):用于定义具有线程局部存储期的变量,每个线程都有自己的独立副本。线程局部变量的生命周期与线程的生命周期相同。

从 C++ 17 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。

中的存储类说明符为程序员提供了控制变量和函数生命周期及可见性的手段。

合理使用存储类说明符可以提高程序的可维护性和性能。

从 C++11 开始,register 已经失去了原有的作用,而 mutable 和 thread_local 则是新引入的特性,用于解决特定的编程问题。

https://www.runoob.com/cplusplus/cpp-storage-classes.html

==

来自:https://www.cnblogs.com/honernan/p/14478366.html

C++中static关键字的作用

1.隐藏

​ 在同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。如果加了static,就会对其它源文件隐藏。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏.

//a.c
char a = 'A'; 			// 全局变量
void msg()				// 函数
{
     printf("Hello\n");
}
 
//main.c
#include<stdio.h>
void msg();				//使用全局函数前需要声明
int main()
{
     extern char a; 	//使用全局变量前需要声明
     printf("%c ", a);
     msg();
     return 0;
}

运行结果是:

在变量a和函数msg的声明前加上static后,再次编译:

2. 保持局部变量内容持久

​ 修饰局部变量时,使得该变量不会因为函数终止而丢失,使其生命周期为整个源程序。把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。

#include <stdio.h>
 
int fun(){
    static int count = 10; //在第一次进入这个函数的时候,变量a被初始化为10!并接着自减1,以后每次进入该函数,a
    return count--; //就不会被再次初始化了,仅进行自减1的操作;在static发明前,要达到同样的功能,则只能使用全局变量:    
 
}
int count = 1;
 
int main(void)
{
     printf("global\t\tlocal static\n");
     for(; count <= 10; ++count)
               printf("%d\t\t%d\n", count, fun());
     return 0;
}

3. 默认初始化为0

​ 全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。

#include <stdio.h>
 
int a;
 
int main()
{
     int i;
     static char str[10];
     printf("integer: %d; string: (%s)", a, str);
     return 0;
}

4. 类中的static

​ 在类中使用static的目的是为了实现共享。因为静态成员函数和静态成员变量属于类,不属于类的实体,这样可以被多个对象所共享,同时静态成员函数调用方便,不需要生成对象就能调用。

在类中声明static变量或者函数时,初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员,就出现以下注意事项:

(1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 它仅能访问类的静态数据和静态成员函数。

(2)不能将静态成员函数定义为虚函数。

(3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,变量地址是指向其数据类型的指针 ,函数地址类型是一个“非成员函数指针”。

(4)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问 时间,节省了子类的内存空间。

(5)静态数据成员在<定义或说明>时前面加关键字static。

(6)静态数据成员是静态存储的,所以必须对它进行初始化。 (程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)

(7)静态成员初始化与一般数据成员初始化不同:

  • 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
  • 初始化时不加该成员的访问权限控制符private,public等;
  • 初始化时使用作用域运算符来标明它所属类;
  • 静态数据成员初始化的格式:
    <数据类型><类名>::<静态数据成员名>=<值>

(8)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。

5. 问答

(1)在头文件把一个变量申明为static变量,那么引用该头文件的源文件能够访问到该变量吗。

答:可以。声明static变量一般是为了在本cpp文件中的static变量不能被其他的cpp文件引用,但是对于头文件,因为cpp文件中包含了头文件,故相当于该static变量在本cpp文件中也可见。当多个cpp文件包含该头文件中,这个static变量将在各个cpp文件中将是独立的,彼此修改不会对相互有影响。

(2)为什么静态成员函数不能申明为const

答:这是C++的规则,在类中,const修饰符用于表示函数不能修改成员变量的值,并且const实际修饰的就是指向类的this函数,而类中的static函数本质上是全局函数,不能用const来修饰它。一个静态成员函数可以访问的值是其参数、静态数据成员和全局变量,而这些数据都不是对象状态的一部分。而对成员函数中使用关键字const的作用是表明:函数不会修改该函数访问的目标对象的数据成员。既然一个静态成员函数根本不访问非静态数据成员,那么就没必要使用const了

(3)为什么不能在类的内部定义以及初始化static成员变量,而必须要放到类的外部定义

答:因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。

(4)static关键字为什么只能出现在类内部的声明语句中,而不能重复出现在类外的定义中。

答:如果类外定义函数时在函数名前加了static,因为作用域的限制,就只能在当前cpp里用,类本来就是为了给程序里各种地方用的,其他地方使用类是包含类的头文件,而无法包含类的源文件。

static还可以根据其修饰的对象分为以下五种:

  • 静态成员变量(面向对象)
  • 静态成员函数(面向对象)
  • 静态全局变量(面向过程)
  • 静态局部变量(面向过程)
  • 静态函数(面向过程)

具体的分析可见:https://zhuanlan.zhihu.com/p/37439983

==

 

参考:

  https://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html

https://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html

https://v1otusc.github.io/2021/05/20/C++_Volatile_%E5%85%B3%E9%94%AE%E5%AD%97/

posted @ 2024-05-29 18:06  redrobot  阅读(10)  评论(0编辑  收藏  举报