C++中static、const、voilate

一 static

  在C语言中:
  1、加了 static 的全局变量和函数,对其他源文件隐藏(不能跨文件了)。
  2、static修饰的函数内的局部变量,生存期为整个源程序运行期间,但作用域仍为函数内。
  3、static变量和全部变量一样,存在静态存储区,会默认初始化为0。
  在C++语言中,仍然有上面的作业,但多了下面两个:
  1、声明静态成员变量,需要在类体外使用作用域运算符进行初始化。
  2、声明静态成员函数,在函数中不能访问非静态成员变量和函数。

二 const

1 const初始化

  const对象一旦创建后其值就不能再改变, 所以 const 对象必须初始化。 一如既往,初始值可以是任意复杂的表达式:

const int i = get_size(); // 正确: 运行时初始化
const int j = 42;   //正确: 编译时初始化

  当以编译时初始化的方式定义一个 const 对象时, 编译器将在编译过程中把用到该变量的地方都替换成对应的值。

2 const 对象作用域

  在全局作用域定义的非const对象在整个程序中都可以访问,默认为extern code。
  const对象默认为文件的局部变量,要使const变量能够在其他的文件中访问,必须地指定它为extern code。

3 const引用

  可以把引用绑定到 const 对象上, 就像绑定到其他对象上一样, 我们称之为对常量的引用( reference to const)。 与普通引用不同的是, 对常M的引用不能被用作修改它所绑定的对象:

const int ci = 1024;
const int &rl = ci; // 正确: 引用及其对应的对象都是常量
r1 = 42;  // 错误: rl 是对常量的引用
int &r2 = ci; //错误: 试图让一个非常量引用指向一个常量对象

  引用的类型必须与其所引用对象的类型一致, 但是有两个例外。第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值, 只要该表达式的结果能转换成引用的类型即可。尤其, 允许为一个常量引用绑定非常量的对象、 字面值, 甚至是个一般表达式;第二,对 const 的引用可能引用一个并非 const 的对象。

int i = 42;
const int &rl = i;//允 许 将 const int&绑定到一个普通 int 对象上
const int &r2 = 42;// 正确 : rl 是一个常量引用
const int &r3 = rl * 2;// 正确:i 3 是一个常量引用
int &r4 = rl 2;// 错误: r4 是一个普通的非常量引用

4 const和指针

  指针本身是一个对象, 它又可以指向另外一个对象。 因此, 指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。 用名词顶层const(top-level const) 表示指针本身是个常量, 而用名词底层 const ( low-level const) 表示指针所指的对象是一个常量。
  int const *p1 = &b;常量指针是指指向常量的指针,顾名思义,就是指针指向的是常量,它不能指向变量,它指向的内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变,从而指向另一个常量;
  int *const ptr指针常量是指指针本身是常量。它指向的地址是不可改变的,但地址里的内容可以通过指针改变。它指向的地址将伴其一生,直到生命周期结束。有一点需要注意的是,指针常量在定义时必须同时赋初值。从右向左把上述定义语句读作“ptr是指向 int 型对象的 const 指针”;
  指向 const 对象的 const 指针:const double pi = 3.14159; const double *const pi_ptr = π,既不能修改 pi_ptr 所指向对象的值,也不允许修改该指针的指向,从右向左阅读上述声明语句:“pi_ptr 首先是一个 const 指针,指向 double 类型的 const 对象”。

5 常量表达式

  常量表达式 ( const expression ) 是指值不会改变并且在编译过程就能得到计算结果的表达式。 显然, 字面值属于常量表达式, 用常量表达式初始化的 const 对象也是常量表达式。 C++语言中有几种情况下是要用到常量表达式的。
  C++11 新标准规定, 允许将变量声明为 constexpr 类型以便由编译器来验证变量的值是否是一个常量表达式。 声明为 constexpr 的变量一定是一个常量, 而且必须用常量表达式初始化:

constexpr int mf = 20; // 20 是常量表达式
constexpr int limit = mf + 1; // mf + 1 是常量表达式
constexpr int sz = size();  // 只有当 size 是一个 constexpr 函数时才是一条正确的声明语句

  尽管不能使用普通函数作为 constexpr 变量的初始值,新标准允许定义一种特殊的 constexpr 函数。这种函数应该足够简单以使得编译时就可以计算其结果,这样就能用 constexpr 函数去初始化 constexpr 变量了。

6 指针和constexpr

  在 constexpr 声明中如果定义了一个指针, 限定符 constexpr 仅对指针有效, 与指针所指的对象无关:

const int *p = nullptr; // p 是一个指向整型常量的指针
constexpr int *q = nullptr; // q 是一个指向整数的常量指针

  p 和 q 的类型相差甚远, p 是一个指向常量的指针, 而 q 是一个常量指针, 其中的关键在于 constexpr 把它所定义的对象置为了顶层 const。

7 typedef和指针

typedef string *pstring;
const pstring cstr;
string *const cstr;

  声明const pstring时,const 修饰的是 pstring 的类型,这是一个指针。因此,该声明语句应该是把cstr 定义为指向 string 类型对象的。

8 const和成员函数

  将成员函数声明为常量double avg_price() const;
  const成员不能改变其所操作的对象的数据成员,const 必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译时错误。
  从const 成员函数返回 *this

在普通的非 const 成员函数中,this 的类型是一个指向类类型的 const 指针,可以改变 this 所指向的值,但不能改变 this 所保存的地址。

在 const 成员函数中,this 的类型是一个指向 const 类类型对象的 const 指针。既不能改变 this 所指向的对象,也不能改变 this 所保存的地址。

不能从 const 成员函数返回指向类对象的普通引用。const 成员函数只能返回 *this 作为一个 const 引用。

9 const与成员函数重载

Screen& display(std::ostream &os)
{
	do_display(os); return *this;
}
const Screen& display(std::ostream &os) const
{
	do_display(os); return *this;
}

10 const与迭代器

  const_iterator:只能用于读取容器内的元素,但不能改变其值。

11 示例

  在全局作用域定义的const对象:

// file_1.cc
// defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_2.cc
extern const int bufSize; // uses bufSize from file_1,extern 标志着bufSize 是一个声明,所以没有初始化式
// uses bufSize defined in file_1
for (int index = 0; index != bufSize; ++index)
// ...

  const引用则可以绑定到不同但相关的类型的对象或绑定到右值:

/*const 引用可以初始化为不同类型的对象或者初始化为右值,如字面值常量:*/
int i = 42;
// legal for const references only
const int &r = 42;
const int &r2 = r + i;
 
/*同样的初始化对于非 const 引用却是不合法的,而且会导致编译时错误。其原因非常微妙,值得解释一下。
观察将引用绑定到不同的类型时所发生的事情,最容易理解上述行为。假如我们编写*/
double dval = 3.14;
const int &ri = dval;
 
/*编译器会把这些代码转换成如以下形式的编码:*/
int temp = dval;
const int &ri = temp;
// create temporary int from the double
// bind ri to that temporary
 
/*如果 ri 不是 const,那么可以给 ri 赋一新值。这样做不会修改 dval,而是修改了 temp。期望对 ri 的赋值会修改 dval 的程序员会发现 dval 并没89有被修改。仅允许 const 引用绑定到需要临时使用的值完全避免了这个问题,因为 const 引用是只读的。*/

  一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定:

const int max_files = 20; // max_files是常量表达式 
const int limit = max_files + 1; // limit是常量表达式 
int staff_size = 27; // staff_size不是常量表达式 
const int sz = get_size(); // sz不是常量表达式 
/*尽管staff_size的初始值是个字面值常量,但由于它的数据类型只是一个普通int而非const int,所以它不属于常量表达式。另一方面,尽管sz本身是一个常量,但它的具体值直到运行时才能获取到,所以也不是常量表达式。*/

11 const和static区别

C++:
1、const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。
2、Static表示静态的。类的静态成员变量、静态成员函数是和类相关的,而不是和类的对象相关。即使没有没有具体的对象,也能调用类的静态成员函数、变量。Static静态成员变量不能在类的内部初始化,只能在类内部声明、类外部初始化(且初始化不加static);const成员变量也不能在类内初始化,只能在构造函数的初始化列表进行初始化(即必须有构造函数)。const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。
想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static cosnt。
3、Cosnt成员函数主要目的是防止成员函数修改对象内容;static成员函数主要目的是作为类作用域的全局函数,且不能访问非静态成员变量,没有this指针。

七、const

const使用分类:
常变量: const 类型说明符 变量名
常引用: const 类型说明符 &引用名
常对象: 类名 const 对象名
常成员函数: 类名::fun(形参) const
常数组: 类型说明符 const 数组名[大小]
常指针: const 类型说明符* 指针名 ,类型说明符* const 指针名
首先提示的是:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小]), const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。如:
const int a=5; 与 int const a=5; 等同
类名 const 对象名 与 const 类名 对象名 等同
C语言:
与预编译指令相比,const修饰符有以下的优点:
1、预编译指令只是对值进行简单的替换,不能进行类型检查
2、可以保护被修饰的东西,防止意外修改,增强程序的健壮性
3、编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
一、常变量
const int n=5; int const n=5;
表示变量n的值不能被改变了,用const修饰变量时,一定要给变量初始化(c++类中则不然),否则之后就不能再进行赋值了。 C标准中,const定义的常量是全局的,C++中视声明位置而定。
二、常量指针与指针常量
const int * n; int const * n;
常量指针说的是不能通过这个指针改变变量的值,也并不是意味着指针本身不能改变,常量指针可以指向其他的地址。
int const n;
指针常量是指指针本身是个常量,不能在指向其他的地址,需要注意的是,指针常量指向的地址不能改变,但是地址中保存的数值是可以改变的,可以通过其他指向改地址的指针来修改。
const int
const p;
指向常量的常指针指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过其他的普通指针改变变量的值。
C++语言:
三、const修饰成员函数(c++特性)
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;
const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的;
const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。
四、常量引用
常量与引用的关系稍微简单一点。因为引用就是另一个变量的别名,它本身就是一个常量。也就是说不能再让一个引用成为另外一个变量的别名, 那么他们只剩下代表的内存区域是否可变。
五、常量函数
常量函数是C++对常量的一个扩展,它很好的确保了C++中类的封装性。在C++中,为了防止类的数据成员被非法访问,将类的成员函数分成了两类,一类是常量成员函数(也被称为观察着);另一类是非常量成员函数(也被成为变异者)。在一个函数的签名后面加上关键字const后该函数就成了常量函数。对于常量函数,最关键的不同是编译器不允许其修改类的数据成员。
但是C++也允许我们在数据成员的定义前面加上mutable,以允许该成员可以在常量函数中被修改。
六、const 修饰类的数据成员
const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么。
const数据成员的初始化只能在类的构造函数的初始化表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现。
枚举常量不会占用对象的存储空间,他们在编译时被全部求值。但是枚举常量的隐含数据类型是整数,其最大值有限,且不能表示浮点数。
对于非内部数据类型的输入参数,因该将“值传递”的方式改为“const引用传递”,目的是为了提高效率。例如,将void Func(A a)改为void Func(const A &a)
对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x)不应该改为void Func(const int &x)

三 voliate

  1、volatile表示“易变的”,即在运行期对象可能在当前程序上下文的控制流以外被修改(例如多线程中被其它线程修改;对象所在的存储器可能被多个硬件设备随机修改等情况):被volatile修饰的对象,编译器不会对这个对象的操作进行优化。
  2、一个对象可以同时被const和volatile修饰,表明这个对象体现常量语义,但同时可能被当前对象所在程序上下文意外的情况修改。
  3、被volatile这个关键字修饰的变量在每次访问的时候都要去相应内存地址去找,因为随时可能被修改。被const修饰只能说明这个不能被程序员修改,但可能被系统所修改。
const和volatile是类型修饰符,语法类似,用于变量或函数参数声明,也可以限制非静态成员函数。用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问,阻止编译器调整操作volatile变量的指令顺序。当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
一般说来,volatile用在如下的几个地方:

  1. 中断服务程序中修改的供其它程序检测的变量需要加volatile;
  2. 多任务环境下各任务间共享的标志应该加volatile;
  3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
    volatile 指针:
    1)修饰由指针指向的对象、数据是 const 或 volatile 的:
    const char* cpch;
    volatile char * vpch;
    2)指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:
    char* const pchc;
    char* volatile vchc;
    注意:(1) 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
       (2) 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
    (3) C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。
    多线程下的volatile:有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。
发布了68 篇原创文章 · 获赞 27 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/essity/article/details/104649585
今日推荐