首页 > 其他分享 >C 语言变量说明符

C 语言变量说明符

时间:2024-11-20 19:44:11浏览次数:3  
标签:const 变量 int 编译器 说明符 extern 语言

目录

1.const

2.static

3.auto

4.extern

5.register

6.volatile

7.restrict


C 语言允许声明变量的时候,加上一些特定的说明符(specifier),为编译器提供变量行为的额外信息。它的主要作用是帮助编译器优化代码,有时会对程序行为产生影响。

1.const

const说明符表示变量是只读的,不得被修改。

const double PI = 3.14159;
PI = 3; // 报错

上面示例里面的const,表示变量PI的值不应改变。如果改变的话,编译器会报错。

对于数组,const表示数组成员不能修改。

const int arr[] = {1, 2, 3, 4};
arr[0] = 5; // 报错

上面示例中,const使得数组arr的成员无法修改。

对于指针变量,const有两种写法,含义是不一样的。如果const*前面,表示指针指向的值不可修改。

// const 表示指向的值 *x 不能修改
int const * x
// 或者
const int * x

下面示例中,对x指向的值进行修改导致报错。

int p = 1
const int* x = &p;

(*x)++; // 报错

如果const*后面,表示指针包含的地址不可修改。

// const 表示地址 x 不能修改
int* const x

下面示例中,对x进行修改导致报错。

int p = 1
int* const x = &p;

x++; // 报错

这两者可以结合起来。

const char* const x;

上面示例中,指针变量x指向一个字符串。两个const意味着,x包含的内存地址以及x指向的字符串,都不能修改。

const的一个用途,就是防止函数体内修改函数参数。如果某个参数在函数体内不会被修改,可以在函数声明时,对该参数添加const说明符。这样的话,使用这个函数的人看到原型里面的const,就知道调用函数前后,参数数组保持不变。

void find(const int* arr, int n);

上面示例中,函数find的参数数组arrconst说明符,就说明该数组在函数内部将保持不变。

有一种情况需要注意,如果一个指针变量指向const变量,那么该指针变量也不应该被修改。

const int i = 1;
int* j = &i;
*j = 2; // 报错

上面示例中,j是一个指针变量,指向变量i,即ji指向同一个地址。j本身没有const说明符,但是i有。这种情况下,j指向的值也不能被修改。

2.static

static说明符对于全局变量和局部变量有不同的含义。

(1)用于局部变量(位于块作用域内部)。

static用于函数内部声明的局部变量时,表示该变量的值会在函数每次执行后得到保留,下次执行时不会进行初始化,就类似于一个只用于函数内部的全局变量。由于不必每次执行函数时,都对该变量进行初始化,这样可以提高函数的执行速度,详见《函数》一章。

(2)用于全局变量(位于块作用域外部)。

static用于函数外部声明的全局变量时,表示该变量只用于当前文件,其他源码文件不可以引用该变量,即该变量不会被链接(link)。

static修饰的变量,初始化时,值不能等于变量,必须是常量。

int n = 10;
static m = n; // 报错

上面示例中,变量mstatic修饰,它的值如果等于变量n,就会报错,必须等于常量。

只在当前文件里面使用的函数,也可以声明为static,表明该函数只在当前文件使用,其他文件可以定义同名函数。

static int g(int i);

3.auto

auto说明符表示该变量的存储,由编译器自主分配内存空间,且只存在于定义时所在的作用域,退出作用域时会自动释放。

由于只要不是extern的变量(外部变量),都是由编译器自主分配内存空间的,这属于默认行为,所以该说明符没有实际作用,一般都省略不写。

auto int a;
// 等同于
int a;

4.extern

extern说明符表示,该变量在其他文件里面声明,没有必要在当前文件里面为它分配空间。通常用来表示,该变量是多个文件共享的。

extern int a;

上面代码中,aextern变量,表示该变量在其他文件里面定义和初始化,当前文件不必为它分配存储空间。

但是,变量声明时,同时进行初始化,extern就会无效。

// extern 无效
extern int i = 0;

// 等同于
int i = 0;

上面代码中,extern对变量初始化的声明是无效的。这是为了防止多个extern对同一个变量进行多次初始化。

函数内部使用extern声明变量,就相当于该变量是静态存储,每次执行时都要从外部获取它的值。

函数本身默认是extern,即该函数可以被外部文件共享,通常省略extern不写。如果只希望函数在当前文件可用,那就需要在函数前面加上static

extern int f(int i);
// 等同于
int f(int i);

5.register

register说明符向编译器表示,该变量是经常使用的,应该提供最快的读取速度,所以应该放进寄存器。但是,编译器可以忽略这个说明符,不一定按照这个指示行事。

register int a;

上面示例中,register提示编译器,变量a会经常用到,要为它提供最快的读取速度。

register只对声明在代码块内部的变量有效。

设为register的变量,不能获取它的地址。

register int a;
int *p = &a; // 编译器报错

上面示例中,&a会报错,因为变量a可能放在寄存器里面,无法获取内存地址。

如果数组设为register,也不能获取整个数组或任一个数组成员的地址。

register int a[] = {11, 22, 33, 44, 55};

int p = a;  // 报错
int a = *(a + 2); // 报错

历史上,CPU 内部的缓存,称为寄存器(register)。与内存相比,寄存器的访问速度快得多,所以使用它们可以提高速度。但是它们不在内存之中,所以没有内存地址,这就是为什么不能获取指向它们的指针地址。现代编译器已经有巨大的进步,会尽可能优化代码,按照自己的规则决定怎么利用好寄存器,取得最佳的执行速度,所以可能会忽视代码里面的register说明符,不保证一定会把这些变量放到寄存器。

6.volatile

volatile说明符表示所声明的变量,可能会预想不到地发生变化(即其他程序可能会更改它的值),不受当前程序控制,因此编译器不要对这类变量进行优化,每次使用时都应该查询一下它的值。硬件设备的编程中,这个说明符很常用。

volatile int foo;
volatile int* bar;

volatile的目的是阻止编译器对变量行为进行优化,请看下面的例子。

int foo = x;
// 其他语句,假设没有改变 x 的值
int bar = x;

上面代码中,由于变量foobar都等于x,而且x的值也没有发生变化,所以编译器可能会把x放入缓存,直接从缓存读取值(而不是从 x 的原始内存位置读取),然后对foobar进行赋值。如果x被设定为volatile,编译器就不会把它放入缓存,每次都从原始位置去取x的值,因为在两次读取之间,其他程序可能会改变x

7.restrict

restrict说明符允许编译器优化某些代码。它只能用于指针,表明该指针是访问数据的唯一方式。

int* restrict pt = (int*) malloc(10 * sizeof(int));

上面示例中,restrict表示变量pt是访问 malloc 所分配内存的唯一方式。

下面例子的变量foo,就不能使用restrict修饰符。

int foo[10];
int* bar = foo;

上面示例中,变量foo指向的内存,可以用foo访问,也可以用bar访问,因此就不能将foo设为 restrict。

如果编译器知道某块内存只能用一个方式访问,可能可以更好地优化代码,因为不用担心其他地方会修改值。

restrict用于函数参数时,表示参数的内存地址之间没有重叠。

void swap(int* restrict a, int* restrict b) {
  int t;
  t = *a;
  *a = *b;
  *b = t;
}

上面示例中,函数参数声明里的restrict表示,参数a和参数b的内存地址没有重叠。

标签:const,变量,int,编译器,说明符,extern,语言
From: https://blog.csdn.net/xinfanyyds/article/details/143923741

相关文章

  • C语言第14节:字符函数和字符串函数
    1.字符分类函数C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。这些函数的使用都需要包含一个头文件是<ctype.h><ctype.h>头文件中的字符分类函数提供了一组用于检查单个字符特性的函数。这些函数接收一个字符(通常为int类型的char值),返回......
  • 经典C语言代码——part 4(素数问题)
    【程序10】题目:打印楼梯,同时在楼梯上方打印两个笑脸。 1.程序分析:用i控制行,j来控制列,j根据i的变化来控制输出黑方格的个数。2.程序源代码:#include"stdio.h"main(){inti,j;printf("\1\1\n");/*输出两个笑脸*/for(i=1;i<11;i++){for(j=1;j<=i;j++)printf......
  • 不用打分、没有语言要求,申请后直接上岸!加拿大魁省IT人才试点项目值得一试!
    2025-2027年的加拿大移民计划让许多还在观望的朋友有点儿热情减退,尤其是海外雇员类别,高学历、高语言、高分筛选,几乎筛掉了八成以上。但是,加拿大欢迎移民的态度从来没有发生过动摇,这意味着,在你没有了解到的地方,还有可以“捡漏”的途径!想“捡漏”得知道加拿大需要什么想“捡漏......
  • 【论文阅读笔记】多模态大语言模型必读 —— LLaVA
    论文地址:https://arxiv.org/abs/2304.08485代码地址:https://github.com/haotian-liu/LLaVA目录简介VisualInstruction数据生成视觉指令微调模型架构训练简介人类对于世界的认知是通过视觉、语言多个途径的,因此设计出能够遵循多模态的视觉和语言指令的通用大模型成为了人......
  • C语言指针学习
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、指针变量是什么?二、p的类型及使用三、函数里定义的数组名是符号量四、指针函数五、函数指针六、指针数组七、数组指针前言提示:这里可以添加本文要记录的大概内容:C语言指针学习提......
  • 用Python语言,从键盘上输入一个1到365的数字,判断该数字是第几个月的第几天。(不需要考虑
     从键盘上输入一个1到365的数字,判断该数字是第几个月的第几天。(不需要考虑闰年的情况)例:从键盘输入60输出:第3个月的第1天{31,28,31,30,31,30,31,31,30,31,30,31}importsysday=int(input("请输入天数:"))ifday<1orday>365:print("输入的数据不合法")......
  • C语言:链表
    链表是一种常见的线性数据结构,其中每个元素(称为节点)包含两部分:数据和指向下一个节点的指针。链表的主要优点是插入和删除操作的时间复杂度较低,但随机访问的效率不如数组。1.链表的基本概念节点(Node):链表的基本单元,包含数据和指向下一个节点的指针。头节点(Head):链表的第一个......
  • 0基础勇闯C语言(2) 数组
    数组可分为数值数组,字符数组,指针数组,结构体数组。一,一维数组1,一维数组的命名inta[5]={1,2,9,23,8};(数组下标范围是0-n-1)2,一维数组的应用冒泡排序和选择排序二,二维数组1,二维数组的命名(2种)inta[2][3]={{1,2,3},{4,5,6}};inta[2][3]={1,2,3,4,5,6};2,二维数组的理解......
  • 2024-11-20:交替子数组计数。用go语言,给定一个二进制数组 nums, 如果一个子数组中的相邻
    2024-11-20:交替子数组计数。用go语言,给定一个二进制数组nums,如果一个子数组中的相邻元素的值都不相同,我们称这个子数组为交替子数组。请返回数组nums中交替子数组的总数。输入:nums=[0,1,1,1]。输出:5。解释:以下子数组是交替子数组:[0]、[1]、[1]、[1]以及[0,1]。......
  • 动态内存管理(c语言)
    我们通常开辟空间的方式intval=20;//大小为4个字节chararr[10]={0}//开辟出一块连续的空间且大小为10但是上面开辟空间方式的特点1.空间开辟大小是固定的2.数组在声明得时候,必须指定数组得长度,它所需要得内存在编译时分配但是以上的方式不能满足所有情况,有时候......