C语言三大基本类型:整型,浮点型,字符型
1、常量定义方法一:使用关键字define
定义
#include <stdio.h>
#define PI 2 + 3
int main()
{
int pi = PI * 2;
printf("%d\n",pi);
float a = 3.14f;
float b = 1.4e-2f;
float c = 0.14e-1f;
printf("%f %f %f",a,b,c);
return 0;
}
上面定义的PI
是符号常量。
#define
发挥作用的时机是编译阶段之前的预处理阶段:
预处理——编译——链接——运行
用#define
定义的字符常量的唯一作用是:在预处理过程中,编译器会用符号常量定义时对应的字面量,对程序中出现的所有符号常量进行文本替换。
上面的代码在预处理时,程序中所有出现的文本PI
都被替换为字面量:
pi = 2 + 3 * 2
。
#define
是一种宏定义,它在预处理阶段将标识符替换为相应的文本。
当使用#define
定义一个常量时,预处理器会在编译之前(预处理阶段)将该常量的所有出现都替换为其定义的文本。
宏定义的常量没有实际的内存分配,它们只是在代码中的文本替换。故而,使用#define
定义的常量不会具有内存空间。
注意:
(1)这里使用#define
关键字定义常量时,语法格式中不需要加 =
和 ;
。
(2)符号常量可以是表达式,但这个表达式不会发生计算。
2、常量定义方法二:使用关键字const
定义
const
发挥作用的时机是编译阶段:
预处理——编译——链接——运行
使用关键字const
定义常量时,其语法格式为:const 变量名
,即const
作用于一个变量。
const
的实际作用:将const
修饰的变量的值锁定,即让这个变量的值不能被更改,本质依然是变量。
从内存视图中也可看出,在用const
去修饰变量前后,变量的存储位置依然在栈区,而常量的存储位置一般都是在静态区,故而也可论证用const
修饰的变量的本质依然是变量。
3、字面量与常量的区别
字面量和常量是两个不同的概念。
字面量是代码中直接使用的固定值,例如整数、浮点数、字符串等。
它们直接出现在代码中,并且表示自身的值。例:42
、3.14
和 "Hello"
都是字面量。
简而言之,直接在程序中写出来的整型,浮点型,字符型,字符串类型都是字面量。也可以将字面量理解为在被定义为常量和变量之前的数据的值。
而常量是通过 const
或 #define
定义出的,带有常量名的数据。
3.1 字面量的存储区别
字符串字面量相比于其他类型的字面量比较特殊
对于字符串字面量,通常会在编译时先将其存储在程序的静态区,并且处于静态区的字面量是唯一的,也就是说静态区中不会出现重复的内容。
这些内存区域在程序启动时就被分配,并且在整个程序运行期间保持不变。
这样做的好处是可以在需要时直接引用字面量的地址,避免了重复创建相同字面量的开销。
对于其他类型的字面量,如整数、浮点数等,它们可能会被直接存储在程序的指令中,也可能会在运行时被加载到内存中的栈或堆区域。
3.2 字面量的存储过程
字面量会在程序编译时被分配内存空间
主要的依据是被赋值为常量还是变量:
3.2.1 若字面量赋值给一个变量
(1)字面量为字符串
字符串变量格式为字符数组:
char arr[20] = "Caesar";
以上代码编译时,会先在栈区中开辟一块属于字符数组的内存空间(以上为20bit),且会将字符串字面量存放于静态区中;当发生赋值操作时,编译器将这个存放于静态区的字符串字面量的一份拷贝放入到栈区中属于字符数组的内存空间中。
字符串变量格式为字符指针:
char *arr = "Caesar";
以上代码编译时,会先在栈区中开辟一块属于字符指针的内存空间(64位操作系统下以上为8byte=64bit),且会将字符串字面量存放于静态区中;当发生赋值操作时,编译器将这个存放于静态区的字符串字面在静态区中所对应的内存地址的值放入到栈区中属于字符指针的内存空间中。
(2)字面量为三大基本类型
int num = 7;
以上代码编译时,会先在栈区中开辟一块属于整型变量的内存空间,但此时不会对整型字面量有操作,当编译进行到赋值时,整型字面量会直接被放入栈区中属于整型变量的内存空间中。
3.2.2 若字面量赋值给一个常量
(1)常量的定义方式为const
:
由前文标题2
我们可知:用const
定义的常量的本质依然是变量,即它的存储规则依然遵循变量的存储规则,故而字面量赋值给一个用const
定义的常量,其存储规则依然相同于变量,即标题3.2.1
。
(2)常量的定义方式为#define
:
#define
发挥作用的时机是编译阶段之前的预处理阶段,const
发挥作用的时机是编译阶段:
预处理——编译——链接——运行
也就是说很可能出现以下情况,用#define定义的常量在发生文本替换后,再次被const定义成常量:
#include <stdio.h>
#define PI (2+3)
int main()
{
const int pi = PI * 2;
printf("%d\n",pi);
return 0;
}
预处理结束以后,程序中的符号常量被文本替换为相应的字面量。
由于之前论述了即使是被const
修饰成为常量,该常量的存储方式依然相同于变量。
故在常量的定义方式为#define
的情况下,程序进入编译阶段后,无论文本替换后,字面量是否还会被const
修饰为常量,其存储方式依然相同于标题3.2.1
。
根据以上探讨得出字面量存储的结论:
无论字面量后续被赋值为常量还是变量,字面量的存储原则都是:
1、字符串字面量先分配于静态区,再将拷贝内容分配于堆区或栈区,拷贝内容的分配方式类似于三大基本数据类型的分配。
2、三大基本类型直接分配于堆区或栈区。
3、对数据在内存中的存储只需要关注变量即可,因为常量的存储实际上就是变量的存储。
4、字符串字面量原件,静态变量,全局变量存储于静态区,其他变量和常量存储于堆区或栈区。
5、存放于静态区的数据才会在编译阶段分配内存空间,其他数据只有在发生函数调用(包括主函数的运行)时,才会分配内存空间。
4、静态变量与全局变量
这两个类型的变量是比较特殊的,都存储在静态区,
全局变量只有在整个程序执行结束后才会消亡;
静态变量则是在所处的代码块彻底执行结束后(即不会再像循环那样重复执行),静态变量才会消亡。
局部变量和静态变量在消亡前都是长期有效,且可被更改的。