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

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


const,volatile,static,typdef,几个关键字辨析和理解的更多相关文章

  1. let 、const 、var、function声明关键字的新理解

    今天在群里看到大佬们讨论let .const 的提升问题,有个大佬问  三种声明都在什么阶段提升?  什么阶段?这个真不清楚,以前是只知道let.const存在死区,没有变量提升,一下子就懵了 后经手 ...

  2. const extern static 终极指南

    const extern static 终极指南 不管是从事哪种语言的开发工作,const extern static 这三个关键字的用法和原理都是我们必须明白的.本文将对此做出非常详细的讲解. co ...

  3. php中$this、static、final、const、self 等几个关键字的用法

    <?phpclass A { public static function get_self(){ return new self(); } public static function get ...

  4. php类中的$this,static,const,self这几个关键字使用方法

    本篇文章主要分享一下关于php类中的$this,static,final,const,self这几个关键字使用方法 $this $this表示当前实例,在类的内部方法访问未声明为const及stati ...

  5. C语言关键字—-sizeof 、typedef、const、static、register、extern、#define

    关键字:sizeof .#define.typedef.const.static.register.extern sizeof 1. 作用:求数据所占得内存空间大小 2. 本质:求数据得类型所占的内存 ...

  6. C++ static、const和static const类型成员变量声明以及初始化

    C++ static.const和static const 以及它们的初始化 const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间. sta ...

  7. 今天思考一个问题,PHP const和static的区别

    static关键字在类中是,描述一个成员是静态的,static能够限制外部的访问,因为static后的成员是属于类的,是不属于任何对象实例,其他类是无法访问的,只对类的实例共享,能一定程序对该成员尽心 ...

  8. const、static和extern的正确使用方式

    我们在看一些大牛的第三方时,里面会出现很多const.static和extern,尤其是const和static,const和extern的结合使用,直接令很多小伙伴懵逼了,今天就详细讲解一下这三个关 ...

  9. 《OOC》笔记(1)——C语言const、static和extern的用法

    <OOC>笔记(1)——C语言const.static和extern的用法 C语言中const关键字用法不少,我只喜欢两种用法.一是用于修饰函数形参,二是用于修饰全局变量和局部变量. 用c ...

随机推荐

  1. 动态sql语句、逆向工程(generator)、分页助手(pagehelper)

    1.动态sql语句 if if where 配合使用 <select id="selectByWhere" resultType="com.alibaba.wlq. ...

  2. websocket的应用---Django

    websocket的应用---Django 1.长轮询 轮询:在前端通过写js实现.缺点:有延迟.服务器压力大. 就是客户端通过一定的时间间隔以频繁请求的方式向服务器发送请求,来保持客户端和服务器端的 ...

  3. Azure Terraform(七)利用Azure DevOps 实现自动化部署基础资源(补充)

    一,引言 之前一篇文章有讲解到利用 利用Azure DevOps 实现自动化部署基础资源,当时 TF 代码没有针对 Azure 各个资源的封装,所有的资源代码全部写在一个 main.tf 文件中.然后 ...

  4. Failed to start ssh.service: Unit not found.

    Failed to start ssh.service: Unit not found. 报错内容: [Centos7@localhost ~]$ service ssh start Redirect ...

  5. RestTemplate post请求

    以前一开始用原生的http请求,那叫一个累,后来找到一个第三方的工具包,用起来是真的舒服,不过有一说一,第三方工具包依赖性真的强,除非和组长商量过,不然能少用,还是少用点.话说搞微服务的肯定少不了和H ...

  6. ModuleNotFoundError 模块寻找路径

    t = os.path.dirname(os.path.dirname((os.path.dirname(os.path.abspath(__file__)))))os.path.sys.path.a ...

  7. https://learnku.com/docs/go-blog/qihoo/6532 。 heap size went up to 69G, with maximum garbage collection (GC)

    https://learnku.com/docs/go-blog/qihoo/6532 Use a Task Pool, a mechanism with a group of long-lived ...

  8. 唯一ID生成算法剖析

    https://mp.weixin.qq.com/s/E3PGP6FDBFUcghYfpe6vsg 唯一ID生成算法剖析 原创 cloudoxou 腾讯技术工程 2019-10-08    

  9. 初始TypeScript

    什么是TypeScript? TypeScript是拥有类型系统的JavaScript的超集,可以编译成纯JavaScript: 1.类型检查:TS会在编译代码时进行严格的静态类型检查,这意味着可以在 ...

  10. loj1011愤怒的牛

    题目描述 原题来自:USACO 2005 Feb. Gold 农夫约翰建造了一座有 n 间牛舍的小屋,牛舍排在一条直线上,第 i 间牛舍在 x_i 的位置,但是约翰的 m 头牛对小屋很不满意,因此经常 ...