C 常量、变量与存储方式

# 常量 常量是固定值,在程序执行的过程中不会发生改变。

****# 常量 常量是固定值,在程序执行的过程中不会发生改变。


整数常量

整数常量可以是十进制、八进制或十六进制的常量。

前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。

整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意

下面列举几个整数常量的实例:

212         /* 合法的 */
215u        /* 合法的 */
0xFeeL      /* 合法的 */
078         /* 非法的:8 不是八进制的数字 */
032UU       /* 非法的:不能重复后缀 */

以下是各种类型的整数常量的实例:

85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* 整数 */
30u        /* 无符号整数 */
30l        /* 长整数 */
30ul       /* 无符号长整数 */

整数常量可以带有一个后缀表示数据类型,例如:

int myInt = 10;  
long myLong = 100000L;  
unsigned int myUnsignedInt = 10U;

浮点常量

浮点常量由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。

当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。 当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。

  • 带符号的指数是用 e 或 E 引入的。

INFO: 浮点常量的写法 包含小数点的一个带符号的数字序列,接着是字母e或E,然后是一个代表10的指数的有符号值,且必须为整数
其中 e 或 E 被称为阶码标志,e 或 E 后面的有符号整数被称为阶码。 阶码代表 10 的阶码次方。

EXAMPLE

15.75 1.575E1 /* = 15.75 */ 
1575e-2       /* = 15.75 */ 
-2.5e-3       /* = -0.0025 */ 
25E-4         /* = 0.0025 */

ATTENTION

  1. 浮点型常量可以省略正号,可以没有小数点或指数部分
  2. 但是不能同时没有二者
  3. 浮点型常量可以省略小数或者整数部分,但不能同时省略
  4. e前后的实数和整数不能省略
  5. 浮点型常量中不能有空格!
  6. 浮点型常量有阶码的小数点前面只能有一位非0的数字

下面列举几个浮点常量的实例:

3.14159       /* 合法的 */
314159E-5L    /* 合法的 */
510E          /* 非法的:不完整的指数 */
210f          /* 非法的:没有小数或指数 */
.e55          /* 非法的:缺少整数或分数 */

浮点数常量可以带有一个后缀表示数据类型,例如:

float myFloat = 3.14f;  
double myDouble = 3.14159;

字符常量

符常量是括在单引号中,例如,x 可以存储在 char 类型的简单变量中。
字符常量可以是一个普通的字符(例如 x)、一个转义序列(例如 \t),或一个通用的字符(例如 \u02C0)。

转义字符

在 C 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。下表列出了一些这样的转义序列码:

转义序列 含义
\ \ 字符
' ’ 字符
" “ 字符
? ? 字符
\a 警报铃声
\b 退格键
\f 换页符
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\ooo 一到三位的八进制数
\xhh 一个或多个数字的十六进制数
\\ 输出反斜杠字符“\”
\' 输出单引号字符‘’
\'' 输出双引号字符”“
o和h表示数
例如\101, \x60

字符串常量

字符串字面值或常量是括在双引号 “ “ 中的。

字符串常量在内存中以 null 终止符 \0 结尾。例如: char myString[] = "Hello, world!"; //系统对字符串常量自动加一个 '\0'


定义常量

在 C 中,有两种简单的定义常量的方式:

  1. 使用 #define 预处理器: #define 可以在程序中定义一个常量,它在编译时会被替换为其对应的值。
  2. 使用 const 关键字:const关键字用于声明一个只读变量,即该变量的值不能在程序运行时修改。

#define预处理器

#define预处理器定义常量的形式:

#define 常量名 常量值

EXAMPLE #define PI 3.14159

const 关键字

使用 const 前缀声明指定类型的常量

const 数据类型 常量名 = 常量值;

EXAMPLE const int MAX_VALUE = 100;

常量定义 const 声明常量要在一个语句内完成: 如何声明常量

实例:

#include <stdio.h>
 
int main()
{
   const int  LENGTH = 10;
   const int  WIDTH  = 5;
   const char NEWLINE = '\n';
   int area;  
   
   area = LENGTH * WIDTH;
   printf("value of area : %d", area);
   printf("%c", NEWLINE);
 
   return 0;
}

ATTENTION 把常量定义为大写字母形式,是一个很好的编程习惯

#defineconst区别

  • 替换机制:#define 是进行简单的文本替换,而 const 是声明一个具有类型的常量。#define 定义的常量在编译时会被直接替换为其对应的值,而 const 定义的常量在程序运行时会分配内存,并且具有类型信息。

  • 类型检查:#define 不进行类型检查,因为它只是进行简单的文本替换。而 const 定义的常量具有类型信息,编译器可以对其进行类型检查。这可以帮助捕获一些潜在的类型错误。

  • 作用域:#define 定义的常量没有作用域限制,它在定义之后的整个代码中都有效。而 const 定义的常量具有块级作用域,只在其定义所在的作用域内有效。

  • 调试和符号表:使用 #define 定义的常量在符号表中不会有相应的条目,因为它只是进行文本替换。而使用 const 定义的常量会在符号表中有相应的条目,有助于调试和可读性。


变量

类型

变量是程序可操作存储区的名称。C中每个变量都有特定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。 变量的名称可以由字母数字下划线字符组成。它必须以字母或下划线开头。大写字母和小写字母是不同的,因为 C 是大小写敏感的。基于前一章讲解的基本类型,有以下几种基本的变量类型:

类型 描述
char 通常是一个字节(八位), 这是一个整数类型。
int 整型,4 个字节,取值范围 -2147483648 到 2147483647。
float 单精度浮点值。单精度是这样的格式,1位符号,8位指数,23位小数。

Float图解
double 双精度浮点值。双精度是1位符号,11位指数,52位小数。

Double图示
void 表示类型的缺失。

C 中的变量定义

定义整型变量:int age; 定义指针变量:int *ptr; (以上代码中,ptr 被定义为一个整型指针变量。) 定义多个变量:int i, j, k;

变量初始化

在 C 语言中,变量的初始化是在定义变量的同时为其赋予一个初始值。变量的初始化可以在定义时进行,也可以在后续的代码中进行。 初始化器由一个等号,后跟一个常量表达式组成 type variable_name = value; 其中,type 表示变量的数据类型,variable_name 是变量的名称,value 是变量的初始值。

变量不初始化

在 C 语言中,如果变量没有显式初始化,那么它的默认值将取决于该变量的类型和其所在的作用域。

对于全局变量和静态变量(在函数内部定义的静态变量和在函数外部定义的全局变量),它们的默认初始值为零。

以下是不同类型的变量在没有显式初始化时的默认值:

  • 整型变量(int、short、long等):默认值为0。
  • 浮点型变量(float、double等):默认值为0.0。
  • 字符型变量(char):默认值为’\0’,即空字符。
  • 指针变量:默认值为NULL,表示指针不指向任何有效的内存地址。
  • 数组、结构体、联合等复合类型的变量:它们的元素或成员将按照相应的规则进行默认初始化,这可能包括对元素递归应用默认规则。

需要注意的是,局部变量(在函数内部定义的非静态变量)不会自动初始化为默认值,它们的初始值是未定义的(包含垃圾值)。因此,在使用局部变量之前,应该显式地为其赋予一个初始值。


C 中的变量声明

变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。

变量的声明有两种情况:

  • 1、一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间。
  • 2、另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a其中变量a可以在别的文件中定义的。

除非有extern关键字,否则都是变量的定义。

extern int i; //声明,不是定义
int i; //声明,也是定义

extern 关键字

extern 用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。

extern 声明不是定义,即不分配内存空间。也就是说,在一个文件中定义了变量和函数,在其他文件中要使用它们,可以有两种方式:

  • 使用头文件,然后声明它们,在其他文件中使用时包含头文件;
  • 在其他文件中直接使用 extern 声明。

例如,在文件 a.h 中的代码如下:

#include<stdio.h>
int a = 10;

如果在另一个文件 a.c 中要用到文件 a.h 中的变量 a,代码如下:

#include <stdio.h>
#include "a.h" //包含该头文件
 
int main(int argc,char *argv[])
{
    printf("a = %d \n " , a);
 
    return 0;
}

另外一种方式是在文件 b.c 中定义变量 a,代码如下:

#include<stdio.h>
int a = 10;

在另一个文件的 main() 主函数中通过 extern 声明外部文件变量,代码如下:

#include <stdio.h>
extern int a; //使用外部文件b.c中的变量a
 
int main(int argc , char *argv[])
{
 
    printf("a = %d \n " , a);
 
    return 0;
}

/*
编译运行,结果均如下:
a = 10
*/

static 静态变量 的 用法

static 修饰符, 加在 一个变量之前,就说明 这个变量是一个静态变量 存储在 静态数据区 的变量会在程序刚开始运行时就完成初始化,⚠️也是唯一的一次初始化⚠️,声明它的函数共享这个变量

函数中的静态变量是静态局部变量,退出函数后不被释放,在程序运行结束时才释放。该变量只在函数中可使用,退出函数后就不能再使用,其生存期为整个源程序,但只能在定义该变量的函数内使用

静态局部变量在编译时赋初值,且只赋一次,赋值语句在程序运行时不再运行。如果在定义局部变量时不赋初值,则对静态局部变量来说,编译时自动赋初值 0(对数值型变量)或空字符(对字符型变量)。

静态变量中所谓的静态,是指在程序运行的过程中,变量的内存地址始终不变,而不是其值不变。静态全局变量的作用域只在定义它的文件里,不能被其他文件使用。

static 修饰的静态变量有以下特征:

  • 变量会被放在程序的数据区,在下一次被调用的时候还保持原来赋的值;
  • 变量用 static 告知编译器,仅在变量的作用域内可用,这是其与全局变量的区别。

分别定义局部和全局静态变量的代码如下:

int fun(void){
    static int count = 3; //事实上此赋值语句从来没有执行过
    return count--;
}
 
static int count = 1; //全局静态变量
 
int main(void)
{
    printf("global\t\tlocal static\n");
    for(; count <= 3; ++count)
        printf("%d\t\t%d\n", count, fun());
 
    return 0;
}

/*
编译运行,结果如下:
global local static
1         3
2         2
3         1
*/

变量的存储类别和作用域限制

  • static(静态全局变量):当在全局变量前加上 static 关键字时,该变量的链接属性变为“内部链接(internal linkage)”。这意味着它的作用域被严格限制在定义它的那个源文件内部,其他源文件无法通过 extern 引用它。这正是题目所描述的需求。

  • extern:用于声明一个在其他文件中定义的全局变量,表明该变量具有“外部链接”属性,可以跨文件使用。

  • register:暗示编译器尽量将该局部变量存储在CPU寄存器中以提高访问速度,不能用于全局变量。

  • auto:是局部变量的默认存储类别,表示变量在栈上自动分配和释放,不能用于全局变量。

相关笔记