一.常量和变量的介绍
首先,常量和变量均是用来存储数据的。
1.常量:在程序执行的过程中它的值是不可以改变的
如:圆周率 性别 血型等。
常量分为**(1)字面常量****(2)const修饰的常变量**(3)#define定义的标识符常量 (4)枚举常量**
补充:对于#define定义的标识符常量,没有分号结束标记
字面常量:
如20 3.14
const修饰的常变量:(本质上还是一个变量,而不是常量)
当n没有被const修饰
例:
int num=10;
printf("%d\n",num);
num=20;//修改
printf("%d\n",num);
return 0;
结果是
10
20
但是,当const修饰num后
这里要注意,用const修饰变量的时候,一定要给变量初始化,否则之后就不能再进行赋值了。
const int num=10;//也可以写成 int const num=10;
printf("%d\n",num);
num=20;//修改
printf("%d\n",num);
return 0;
结果会报错
因为const修饰后,不可以再更改数值
那么,const修饰的变量最终变为了常量,还是任然是变量呢?
如下的一个例子:证明它还是一个变量
例:
int n=10;
int arr[n]={0};
return 0;
结果会报错“应输入常量表达式”
[]中需要的是一个常量,而n是一个变量
const int n=10;
int arr[n]={0};
return 0;
结果会报错
说明const修饰后的n本质上仍然是变量
补充一个用const修饰常量静态字符串:
const char* str="abcdef";
如果没有const修饰,当我们在之后的代码不小心写出str[2]='g';这样的语句时,这个语句会导致对只读内存区域的赋值,然后程序会立刻异常终止。当加了const之后,这个错误在程序编译的时候就被检查出来,这就是const的好处,让逻辑错误在程序编译期就被发现。
const修饰全局变量
全局变量的作用域是整个文件,应该尽量避免使用全局变量,因为一旦有一个函数改变了全局变量的值,会影响到其他引用这个全局变量的函数,导致bug很难发现。如果一定要使用全局变量,应尽量使用const修饰全局变量,防止不必要的人为修改。
常量指针和指针常量
常量指针:是指针指向的内容是常量
注意:
(1)常量指针指的是不能通过这个指针改变变量的值,但是可以通过其他的引用来改变变量的值
例:
int a=5;
const int * n=&a;
a=6;
//结果是6
(2)常量指针指向的值不能改变,并不意味着指针本身不能改变,这个常量指针仍可以指向其他地址
int a=5;
int b=6;
const int *n=&a;
n=&b;
//此时printf("%d\n",*n);
//结果是6
指针常量:是指指针本身是个常量,不能再指向其他地址
注意:
指针常量指向的地址不能改变,但是地址中保存的数值是可以改变的,可以通过其他指向该地址的指针来修改。
例:
int a=5;
int *p=&a;
int * const n=&a;
*p=6;
//printf("%d\n",*p);
//结果是6
怎么区分常量指针和指针常量呢?
关键在于星号和const的相对位置,如果const在 * 的左边,为常量指针,如果const在*的右边,为指针常量。int const*n是常量指针,int * const n是指针常量。
指向常量的常指针
指针指向的位置不能改变,并且不能通过这个指针改变变量的值,但可以通过其他普通指针改变变量的值。
const int* const p;
const修饰函数的参数
(1)防止修改指针指向的内容
void StringCopy(char* dest,const char* source);
用const修饰source后,如果函数体内的语句试图改变source的内容,编译器会报错。
(2)防止修改指针指向的地址
void swap (int * const p1,int * const p2);
指针p1和p2指向的地址都不能修改
(3)修饰函数的返回值
那么函数返回值的内容不能被修改,该返回值只能被赋值给const修饰的变量。
例:
const char* GetString(void);
如下的语句将会报错:
char* str=GetString();
正确的写法是:
const char* str=GetString();
#define(预编译指令)定义的标识符常量:
例:
#define M 100
int main()
{
int a=M;
int arr[M]={0};
printf("%d\n",a);
return 0;
}
例:
#define MAX 100
int main()
{
printf("%d\n",MAX);
MAX=200;//要修改MAX的值
printf("%d\n",MAX);
return 0;
}
结果会报错,证明MAX的值不能被修改,因为MAX被标识为了常量
所以,此时,MAX的值就可以做数组的大小
例:
#define M 10
int main()
{
int arr[M]={0};
return 0;
}
认识了const和#define之后,有人就会觉得用#define定义常量不就可以了吗?为什么还要用const呢?
这是因为与预编译指令相比,const修饰有以下的优点:
(1)预编译指令只是对值进行简单的替换,不能进行类型检查
(2)可以保护被修饰的东西,防止被修改,增强程序的健壮性
(3)编译器通常不为普通const常量分配内存空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与都内存的操作,提高了效率
枚举常量
enum是枚举关键字
枚举的语法:
enum 枚举名{枚举元素1,枚举元素2....};//注意这个分号的存在
例:
enum Sex
{
MALE;
FEMALE;
SECRET;
};//枚举类型的声明
int mian()
{
enum Sex s=MALE;//枚举类型的定义
return 0;
}
*补充:
printf("%d %d %d\n",MALE,FEMALE,SECRET);
结果是0 1 2
也就是说,第一个枚举成员的默认值为0,后续成员的值为前一个成员加1
我们也可以在定义或声明枚举类型时改变枚举元素的值
例:
enum DAY
{
MON=1,TUE,WED,THU,FRI,SAT,SUN
};//枚举类型的声明
在这个例子中我们把第一个枚举元素的值设置为1,那么第二个就为2,以此类推。
例:
enum DAY
{
MON,TUE=2,WED,THU.FRI,SAT,SUN
};//枚举类型的声明
也就是MON的值为0,WED的值为3,THU的值为4,以此类推。
枚举类型的定义
方法一:
先定义枚举类型,再定义枚举变量
enum DAY
{
MON,TUE,WED,THU,FRI,SAT,SUN
};
enum DAY day;//枚举变量day
方法二:
在定义枚举类型的同时定义枚举变量
enum DAY
{
MON,TUE,WED,THU,FRI,SAT,SUN
}day;//枚举变量day
方法三:
省略枚举名称,直接定义枚举变量
enum
{
MON,TUE,WED,THU,FRI,SAT,SUN
}day;//枚举变量day
例:
enum DAY
{
MON=1,TUE,WED,THU,FRI,SAT,SUN
};
int main()
{
enum DAY day;
day=THU;
printf("%d\n",day);
return 0;
}
结果为4
二.变量
首先,变量是指在程序执行的过程中可变的量,由变量名和变量值组成,变量名是一个标识,变量值是一个数据值,程序为每个变量开辟了内存空间,变量值就储存在这个空间中,可以通过变量名访问这个空间。
变量分为局部变量和全局变量
变量的定义和声明
在对变量声明和定义之前,我们先了解一下
标识符
标识符只可以用数字,字母,下划线来表示,且不能以数字开头。并且禁止用关键字和系统函数作为标识符名称。
- 变量的定义:
int a=4;变量的声明:int a;
也就是说没有变量值的变量定义,叫做变量声明。
如:extern int a;也是变量声明
- 变量定义会开辟内存空间,但变量声明****不会开辟内存空间,变量要想使用必须有定义。
- 定义变量完成的两个功能:
(1)声明变量 (2)为变量分配内存空间
功能1是为编译期服务的,功能2是在运行期间完成的。 - 当编译器编译程序时,在变量使用之前,必须要看到变量定义,如果没有看到变量定义,编译器会自动找寻一个变量声明提升为变量定义。但如果该变量的声明前有extern关键字,则无法提升。
变量的赋值和初始化的概念和异同
变量要声明和定义才可以使用,且在声明和定义之后可对它赋值,其中的第一次赋值就叫做初始化。
赋值是为变量设定数值的过程。
在声明变量的同时为其赋值的做法叫做初始化。
作用域和生命周期
1.局部变量的作用域是变量所在的局部范围
2.全局变量的作用域是整个工程
首先 我们来思考,在一个函数中定义的变量,在其它函数中能否被引用?在不同位置定义的变量,在什么范围内有效?
定义变量有三种情况:
(1)在函数的开头定义
在函数内部定义的变量,只有在本函数内才能引用它们,也只有在本函数范围内有效。
(2)在函数内的复合语句内定义
在复合语句内定义的变量,只有在本复合语句内才能引用它们,也只有在本复合语句范围内有效。
(3)在函数外部定义
举个例子:
void test()
{
char a='1';
printf("a:%c\n",a);
}
void test2()
{
int a=6;
printf("n:%d\n",a);
}
int main()
{
test();
test2();
return 0;
}
结果是:
a:1
n:6
说明两个函数的a只作用在自己的函数体内
全局变量:在函数之外定义的变量
要尽量减少使用全局变量,原因是:
(1)内存开销大,全局变量在程序整个执行过程中都占有内存单元
(2)降低函数的通用性,不利于函数作为一个功能模块拷贝到别的文件中使用
(3)代码可阅读性降低,难以清楚判断每个瞬间,各个外部变量的值
生命周期
变量的生命周期:指的是变量的创建到变量销毁之间的一个时间段
1.局部变量:进入作用域生命周期开始,出作用域生命周期结束
局部变量又分为:普通局部变量和static修饰的局部变量(静态局部变量)
普通局部变量:属于某个{ },在{ }外部不能使用此变量,在{ }内部可以使用,执行到普通局部变量定义语句,才会分配内存空间,离开{ },自动释放,普通局部变量不初始化,默认值为随机数。
static局部变量:属于某个{ },在{ }外部不能使用此变量,在{ }内部可以使用,在编译阶段就已经分配了内存空间,static局部变量不初始化,默认值为0.
**注意:静态局部变量的作用域属于某个{ },但它的生命周期却从编译阶段到整个程序结束。
2.全局变量:整个程序的生命周期
全局变量又分为:普通全局变量和static全局变量
普通全局变量:在编译阶段分配空间,只有整个程序结束才释放,只要定义了(只能定义一次),任何地方都可以使用,使用前要声明所有的 .c文件,但是可以声明多次(外部链接)。
static全局变量:在编译阶段分配内存空间,整个程序结束才释放,static全局变量只能在定义所在的文件中使用(内部链接),不同的 .c文件,可以定义一次static全局变量。
例:

结果会报错 未定义的标识符a
原因是;
{
int a=100;
printf("%d\n",a);
}
这个大括号里是a的作用域,出了大括号a就被销毁,不再存在
例:

这个a是全局变量,作用域是整个工程
总结:全局变量和静态变量是在程序编译期就分配到数据段或bss段中的,在整个程序的运行期,数据段和bss段中的内容是不会发生改变的,所以所有的全局变量和静态变量的生命周期必定是从程序编译到程序运行结束,而且如果全局变量和静态变量没有初始化,会统一初始化为0.
而局部变量是在程序运行时,在栈中分配内存,所以生命周期一定是从程序运行的某一个时刻开始。
接下来,我们补充一下内存管理的知识
内存管理
一.内存分区
C源代码经过预处理 编译 汇编 链接 生成一个可执行程序
程序在没有运行之前,也就是程序没有被加载到内存前,可执行程序已经分好3段信息,分别是 代码区(text)数据区(data)未初始化数据区(bss)
运行可执行程序,系统把程序加载到内存,除了分出代码区 数据区 未初始化数据区外,还额外增加了堆区 栈区
接下来,我们简单介绍一下栈和堆:
栈(Stack)
是一种先进后出的内存结构,由编译器自动分配释放,栈区用来存放 函数的参数值 函数的返回值 局部变量,在程序运行中实时加载和释放,因此,局部变量的生命周期为申请到释放该段栈空间,不同的操作系统分配给每个程序的栈区大小不同。
堆(Heap)
堆是一个大容器,它的容量远大于栈,用于动态内存分配,堆在空间中位于BSS区和栈区之间,一般由程序员分配和释放,若不主动释放,程序结束时,由操作系统(OS)回收。堆区通常加载音频文件 视频文件 图像文件 文本文件及大小超过栈大小由程序员主动申请分配的内存的大数组。
注意:
(1)所有未初始化的静态变量和全局变量,编译器会默认赋值0
(2)程序在加载到内存前,代码区和全局区(data和bss)大小是固定的,程序运行期间不能改变。
(3)data段和bss段中的数据的生命周期为整个程序运行过程
(4)data段 text区 bss区是由编译器在编译时分配的,堆和栈是由系统在运行时分配的
对变量的补充:
#include <stdio.h>
int num=10;
int mian()
{
int num=1;
printf("num=%d\n",num);
return 0;
}
结果是num=1
因为局部变量优先
本文详细介绍了C语言中的常量和变量,包括字面常量、const修饰的常变量、#define定义的标识符常量、枚举常量,以及变量的定义、声明、标识符、赋值与初始化、作用域和生命周期。讨论了const修饰全局变量、常量指针和指针常量,以及预编译指令#define在定义常量中的应用。同时,枚举类型的使用和枚举元素的值的自定义也有所阐述。最后,文章提到了内存管理中的栈和堆的区别和作用。
2445

被折叠的 条评论
为什么被折叠?



