const,volatile,static,typdef,几个关键字辨析和理解

1、const类型修饰符

 const它限定一个变量初始化后就不允许被改变的修饰符。使用const在一定程度上可以提高程序的安全性和可靠性。它即有预编译命令的优点也有预编译没有的优点。const修饰的变量被编译器处理只读变量(不是常量,常量是放到内存的只读区域的)放在内存中,由编译器限定不允许改变。

(1)具有不可变性。 
  例如:const int Max=100; Max++会产生错误; 
(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
  例如: void f(const int i) { .........} 编译器就会知道i是一个不允许修改的变量; 
(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。 同宏定义一样,可以做到不变则已,一变都变!
  如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!
  (实际这样去改变const变量是错误的,会报重复定义的错误)
(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。 还是上面的例子,如果在函数体内修改了i,编译器就会报错; 
  例如: void f(const int i)
       {
        i=10;//error!
       } 
(5)如果你非有意的用一个指针去修改const变量的值, 编译器会在你取const变量地址时给出警告
(6) 可以节省空间,避免不必要的内存分配。 例如: 
 
 #define PI          3.14159 //常量宏放入只读内存中
 
  const double Pi=3.14159; //此时并未将Pi放入ROM中 ...... 
  double i=Pi; //此时为Pi分配内存,以后不再分配! 
  double I=PI; //编译期间进行宏替换,分配内存 
  double j=Pi; //没有内存分配 
  double J=PI; //再进行宏替换,又一次分配内存! 
  const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干份拷贝。 
(6) 提高了效率。 
  编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。(参考百度百科

2、volatile类型修饰符

用来修饰被不同线程访问和修改的变量(即随时会被意想不到的改变)。volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1)并行设备的硬件寄存器(如:状态寄存器)
2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3)多线程应用中被几个任务共享的变量
1)一个参数既可以是const还可以是volatile吗?解释为什么。
  是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2)一个指针可以是volatile 吗?解释为什么。
  是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。
3)下面的函数被用来计算某个整数的平方,它能实现预期设计目标吗?如果不能,试回答存在什么问题:
int square(volatile int *ptr)
{
    return ((*ptr) * (*ptr));
}
这段代码是个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int* &ptr)//这里参数应该申明为引用,不然函数体里只会使用副本,外部没法更改
{
    int a,b;
    a = *ptr;
    b = *ptr;
    return a*b;
}
 
由于*ptr的值可能在两次取值语句之间发生改变,因此a和b可能是不同的。结果,这段代码可能返回的不是你所期望的平方值!正确的代码如下:
long square(volatile int*ptr)
{
    int a;
    a = *ptr;
    return a*a;
}
 
在一次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后再取变量值时,就直接从寄存器中取值;
当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致,此时如果使用了volatile类型修饰符就不会出现这种问题,所以说volatile可以保证对特殊变量的稳定访问。
 

3、static类型修饰符

  当一个进程的全局变量被声明为static之后,它就是静态全局变量。静态全局变量和其他的全局变量的存储地点并没有区别,要说有区别就是static是在.data段因为编译器会在你未初始化时自动初始化为0,而普通变量已初始化的变量在data段或者未初始化在.bss段内;static变量它只在定义它的源文件内有效,其他源文件无法访问它。普通全局变量extern后,它就可以被其他源文件及其函数访问,而static变量是无法extern  因为编译器在其他源文件中看不到被static修饰的变量。

1.用在全局变量上时例如

 1 //test.c
 2 #include <stdio.h>
 3 #include "test.h"
 4 
 5 static  char str[] = "read ok !";
 6 int test()
 7 {    
 8     printf("static is %s\n",str);
 9     return 0;
10 }

主函数:

 1 //main.c
 2 
 3 #include <stdio.h>
 4 #include "test.h"
 5 
 6 char str[]="is not ok!";
 7 
 8 int main() 
 9 {
10     test();
11     printf("static is %s\n",str);
12     return 0;
13 }

上面程序输出的结果是明显的,但如果去掉test.c内的str的static修饰符后编译时链接会出错,因为是有两个地方存在相同的变量,导致编译器编译时无法识别应该使用哪一个。但当你用static 修饰test.c内的str变量后编译器就知道在那个源文件内该使用哪一个。

2,在局部变量上使用时

普通的局部变量在栈空间上分配,这个局部变量所在的函数被多次调用时,每次调用这个局部变量在栈上的位置都不一定相同。局部变量也可以在堆上动态分配,但是记得使用完这个堆空间后要释放。

       static局部变量中文名叫静态局部变量。它与普通的局部变量比起来有如下几个区别:

           1)位置:静态局部变量被编译器放在全局存储区.data(前面提到编译器会自动初始化),所以它虽然是局部的,但是在程序的整个生命周期中存在。

           2)访问权限:静态局部变量只能被其作用域内的变量或函数访问。也就是说虽然它会在程序的整个生命周期中存在,由于它是static的,它不能被其他的函数或源文件访问。

           3)值:静态局部变量如果没有被用户初始化,则会被编译器自动赋值为0,以后每次调用静态局部变量的时候都用上次调用后的值。这个比较好理解,每次函数调用静态局部变量的时候都修改它然后离开,下次读的时候从全局存储区读出的静态局部变量就是上次修改后的值。

例如:

1 #include "test.h"
2 #include <stdio.h>
3 int test()
4 {
5     int b = 0;
6     static int a;
7     printf("%d,%d\n",a++,b++);
8     return 0;
9 }

主函数:

 1 #include <stdio.h>
 2 #include "test.h"
 3 
 4 int main() 
 5 {
 6     test();
 7     test();
 8     test();
 9     test();
10     test();
11     return 0;
12 }

结果:

0,0   注意这里验证了static变量的自动初始化
1,0
2,0
3,0
4,0 

3.static函数

既然static变量在其他源程序中不可以访问,那用在函数前是否也有相同的功能呢?验证一下:

//test.c
#include "test.h"
#include <stdio.h>
static int test()
{
    int b = 0;
    printf("%d\n",b);
    return 0;
}
int mytry()
{
    test();
    return 0;
}

头文件:

//test.h
#ifndef __TEST_H__
#define __TEST_H__
#include"test.h"

static int test();
int mytry();
 
#endif
 

主函数:

//main.c
#include <stdio.h>
#include "test.h"

int main() 
{
    //test();//加上这一句程序链接时会出错 
    mytry();
    return 0;
}

结果你猜对了吗?所以static函数可以很好地解决不同原文件中函数同名的问题,因为一个源文件对于其他源文件中的static函数是不可见的。参考博客

4、typdef 或 #define 类型修饰符

(网上好多地方都有关于这两个关键字如何使用的讨论,本文只是简单罗列各自的特性。)

 1.#define是预处理指令,通常用来替代常量(大写)在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入。即使你在末尾习惯性的写上 ; define 还是会乖乖的替换,幽默点说如果你爱浪爱自由#define是不二之选(但是记住出来混是要还的)

知乎上看到这样的代码:

#include <iostream>
#define start using namespace std; 
int main(int argc, char *argv[]) {
#define end <<endl;}
#define print cout<<

start
print"hello world!"
end

作者:DreamPiggy
链接:https://www.zhihu.com/question/29798061/answer/78916243
来源:知乎。

另一个例子:

#define Pi 3.1415926;

(Pi*R*R ) 就会成为 (3.1415926;*R*R)

2.typdef 是由编译器处理的,由名字也能感觉到功能的一点差别,他是用于长类型 有一个剪短的名字而常常被使用,其实typdef创造的是一个类型(官方的解释是任何声明变量的语句前面加上typedef之后,原来是变量的都变成一种类型。不管这个声明中的标识符号出现在中间还是最后.)

如:

define   PINT int*

PINT a,b  就是  int *a;int b;

而

typedef PINT int*

PINT a,b 就是 int  *a,int  *b;

常见用法:

  • 用在C代码中,帮助struct。声明struct新对象时,必须要带上struct,即形式为: struct 结构名对象名,如:
 struct tagPOINT1

   {
      int x;

      int y; 
  };
struct tagPOINT1 p1;

而在C++中,则可以直接写:结构名对象名,即:tagPOINT1 p1;

typedef struct tagPOINT
  {
      int x;

      int y;
  }POINT;

  POINT p1; 简洁多了吧
  • 用typedef来定义与平台无关的类型。

  比如定义一个叫 FLOAT的浮点类型,在目标平台一上,让它表示最高精度的类型为:

  typedef long double FLOAT;

  在不支持 long double 的平台二上,改为:

  typedef double FLOAT;

  在连 double 都不支持的平台三上,改为:

  typedef float FLOAT;

  也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。

  标准库就广泛使用了这个技巧,比如size_t。另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健。

  • 还有就是类似#deine的作用

3.区别

  • typedef是有作用域的,而#define不管怎么写都是全局的
  • 细节上的差别

  typedef int * pint ; 

  #define PINT int * 
  那么: 
  const pint p ;//p不可更改,但p指向的内容可更改 
  const PINT p ;//p可更改,但是p指向的内容不可更改。

两者的区别还很多。。。。。。在同样的功能下看个人喜好。

5、extern 

  • 默认情况下所有文件的变量都可以访问,只需要在定义变量时添加一个extern(extern int age)(而且没有分配内存)引用一下就行,这个不管int age是定义在哪个文件中都可以得到,而且此变量还可以被修改。
  • extern引用的时候,优先找本文件夹,如果找不到再去其它文件夹。

 

2017.9.1

本博客起笔与于一个月前刚到实习地点,在实习最后一天再次继续完成。

 

 


 

posted @ 2017-09-01 16:49  Little_Village  阅读(950)  评论(0编辑  收藏  举报