C 语言变量的作用域主要有三种:
局部变量也叫 auto 自动变量(auto 可以不写),一般情况下代码块 {} 内部定义的变量都是自动变量,具有如下特点:
int main()
{
//定义变量,局部变量,只在main函数内有效
//作用域:main函数之内
//生命周期:从创建到函数结束
auto int a=10; //auto可以省略不写
return 0;
}
main.c
#include
//全局变量:在函数外部定义的变量
//作用域,整个项目中所有文件,如果在其他文件中使用,需要声明
//生命周期,从程序创建到程序销毁
int a=10;
void func()
{
a=100;
printf("%d\n",a);
}
int main()
{
printf("%d\n",a); //10
int a=123; //数据在操作时,采用就近原则
//匿名内部函数
{
int a=456;
printf("%d\n",a); //456
}
//匿名内部函数
{
a=456;
printf("%d\n",a); //456
}
printf("%d\n",a); //456
func(); //100
func2(); //1000
return 0;
}
test.c
#include
//声明全局变量
extern int a;
//定义全局变量
int b=10; //全局变量不可以重名
int b=10; //err,全局变量不可以重名
void func2()
{
a=1000;
printf("%d\n",a);
}
//静态局部变量
void func()
{
printf("%d\n",b); //err,未定义变量b
}
int main()
{
//静态局部变量
static int b=10;
printf("%d\n",b); //10
return 0;
}
//静态局部变量
void func()
{
// printf("%d\n",b); //err,未定义变量b
static int b=10; //静态局部变量只会初始化一次,可以多次赋值,作用域只在函数内,只能在当前函数中使用,生命周期与程序生命周期相同
b++;
printf("%d\n",b);
}
int main()
{
//如果在 func() 中定义的是局部变量,会输出十次 11
for(int i=0;i<10;i++)
{
func(); //十次输出,分别输出:11,12,13,14,15,16,17,18,19,20
}
return 0;
}
main.c
//静态全局变量,作用域为本文件,只能在本文件中使用,不能在其他文件中使用
//生命周期:从程序创建到程序销毁
//存储在数据区
static int a=10;
void func()
{
a=20;
printf("%d\n",a); //20
}
int main()
{
printf("%d\n",a); //10
func();
func06();
return 0;
}
test.c
extern int a; //err,静态全局变量的作用域为本文件,因此这里无法使用
void func06()
{
printf("%d\n",a); //20
}
变量类型 | 作用域 | 生命周期 | 存储位置 |
---|---|---|---|
局部变量 | 函数内部 | 从局部变量创建到函数结束 | 栈区 |
全局变量 | 项目中所有文件 | 从程序创建到程序销毁 | 数据区 |
静态局部变量 | 函数内部 | 从程序创建到程序销毁 | 数据区 |
静态全局变量 | 定义所在的文件中 | 从程序创建到程序销毁 | 数据区 |
//未初始化的全局变量,值为0
int abc;
int main()
{
/*
//局部变量未初始化,值为任意值(乱码)
int abc;
printf("%d\n",abc); //在 vs 中 err,在其他环境中可以输出,但是为乱码
*/
printf("%d\n",abc);
}
main.c
//全局函数声明
void buble();
int main()
{
buble(); //err,静态函数,作用域仅限于定义这个函数的文件
return 0;
}
test.c
//静态函数,作用域仅限于定义这个函数的文件
static void buble()
{
printf("buble\n");
}
函数类型 | 作用域 | 生命周期 | 存储位置 |
---|---|---|---|
全局函数 | 项目中所有文件 | 从程序创建到程序销毁 | 代码区(未唤醒),栈区(唤醒之后) |
静态函数 | 定义所在的文件 | 从程序创建到程序销毁 | 代码区 |
注意:
//未初始化全局变量
int a1;
//初始化全局变量
int b1=10;
//未初始化静态全局变量
static int c1;
//初始化静态全局变量
static int d1=10;
int main()
{
//初始化局部变量
int e1=10;
//未初始化静态局部变量
static int f1;
//初始化静态局部变量
static int g1=10;
//常量字符串
char* p="hello world";
//数组
int arr[]={1,2,3,4};
//指针变量
int *pp=arr;
//指针地址 &pp
return 0;
}
应用程序的内存四区模型:
程序运行之前:
1)代码区
2)全局初始化数据区 / 静态数据区(data 段)
3)未初始化数据区(bss 区)
程序运行之后:
1)代码区(text segment)
2)未初始化数据区(bss)
3)全局初始化数据区 / 静态数据区(data segment)
4)栈区(stack)
5)堆区(heap)
1)普通变量存储:从高地址区往低地址区存储
2)数组:开辟一段连续的内存,数组从前往后是从低地址区往高地址区存储
3)栈区:从高地址区向低地址区生长。先进后出,后进先出
4)堆区:从低地址区向高地址区生长
int main()
{
//栈区的大小
int arr[820000]={0}; //err,数组太大了,栈区内存不够 820000*4/1024/1024>31MB,远大于栈区的大小,栈区大小为1MB
}
1)malloc() 函数
#include
void* malloc(size_t size);
2)free() 函数
#include
void free(void* ptr);
int main()
{
//开辟堆空间存储数据
int* p=(int*)malloc(sizeof(int));
printf("%p\n",p);
printf("%d\n",*p); //乱码
//使用堆空间
*p=123;
printf("%d\n",*p); //123
//释放堆空间
free(p);
printf("%p\n",p); //还是输出 p 的地址,但是此时 p 指向的是一个未知空间,p 为野指针
*p=456;
printf("%d\n",*p); //456
p=NULL;
return 0;
}
为了防止所使用的堆空间指针释放后成为野指针,应该让其指向空指针。
3)可以开辟多大的堆空间
#define MAX 10
int* p=(int*)malloc(sizeof(int)*10);
for(int i=0;i<10;i++)
{
p[i]=i;
}
for(int i=0;i<10;i++)
{
printf("%d\n",*(p+i));
}
free(p);
srand((size_t)time(NULL));
int* p=(int*)malloc(sizeof(int)*MAX);
for(int i=0;i<MAX;i++)
{
p[i]=rand()%100;
printf("%d\n",*(p+i));
}
for(int i=0;i<MAX;i++)
{
printf("%d\n",*p);
p++;
}
free(p); //err,这里会报错,因为指针在叠加过程中,正在不断改变,最后释放的指针和开辟的空间首地址不同,会导致前面开辟的堆空间成为无主空间,只有等程序结束才能释放
//在堆空间的开辟和释放的时候,应该保证释放的指针和开辟时的指针相同
1)memset()
#include
void* memset(void* s,int c,size_t,n);
int* p=(int*)malloc(sizeof(int)*10);
//未初始化时,原始数据,乱码
for(int i=0;i<10;i++)
{
printf("%d\n",*(p+i));
}
//memset() 重置内存空间的值
memset(p,0,40);
//memset(p,1,40),这样无法使内部每个整型都变成 1,实际上是将四十个字节分别设置为 00000001,最后再将 0000001000000100000010000001 以整型读出,就和预想的不一样了
//字符数组可以使用 memset() 重置,但是不能用于字符串形式输出
for(int i=0;i<10;i++)
{
printf("%d\n",*(p+i)); //输出十个 0
}
free(p);
2)memcpy()
#include
void* memcpy(void* dest,const void* src,size_t,n);
注:dest 和 src 所指的内存空间不可以重叠,否则可能会导致程序报错。当他们重叠的时候,使用 memmove
int arr[]={1,2,3,4,5,6,7,8,9,10};
int* p=(int*)malloc(sizeof(int)*10);
memcpy(p,arr,sizeof(int)*10);
for(int i=0;i<10;i++)
{
printf("%d\n",*(p+i)); //输出:1,2,3,4,5,6,7,8,9,10
}
free(p);
//dest 和 src 所指的内存空间重叠,可能会导致报错
memcpy(&arr[5],&arr[3],20);
strcpy() 和 memcpy() 的区别:
3)memmove()
memmove() 功能用法和 memcpy() 一样,区别在于:/dest 和 src 所指的内存空间重叠时,memmove() 仍然能够处理,不过执行效率比 memcpy() 低。
int arr[]={1,2,3,4,5,6,7,8,9,10};
//dest 和 src 所指的内存空间重叠,使用 memmove() 更安全,不会报错
memmove(&arr[5],&arr[3],20);
4)memcmp()
#include
int memcmp(const void* s1,const void* s2,size_t n);
int arr1[]={1,2,3,4,5,6,7,8,9,10};
int arr2[]={1,2,3,4,5};
memcmp(arr1,arr2,20); // 0,相同
char str1[]="hello\0 world";
char str1[]="hello\0 world";
memcmp(str1,str2,13); // 0,相同
strcmp() 和 memcmp() 的区别:
1)数组下标越界
//数组下标越界
char ch[11]="hello world";
//堆空间越界
char* p=(char*)malloc(sizeof(char)*11);
strcpy(p,"hello world");
printf("%s\n",p); //输出:hello world
free(p); //err,开辟了11字节,使用和释放了12个
2)开辟内存大小和释放内存大小不一致
int* p=(int*)malloc(0);
printf("%p\n",p); //会输出一个地址
*p=100;
printf("%d\n",*p); //输出:100
free(p); //err,由于分配了 0 字节空间,p 相当于是野指针,但是释放了 4 字节,开辟内存大小和释放内存大小不一致
3)开辟空间和类型不对应
int* p=(int*)malloc(10);
p[0]=123;
p[1]=456;
//p[2]=789;
printf("%p\n",p); //会输出一个地址
printf("%d\n",*p); //输出:123
printf("%d\n",*(p+1)); //输出:456
//printf("%d\n",*(p+2)); //err,堆空间越界了
4)堆空间不允许多次释放
int* p=(int*)malloc(sizeof(int)*10);
free(p); //释放 p,没问题
//free(p); //此时 p 是野指针,再次释放会报错
//在释放完之后,应该让 p 指向空指针
p=NULL;
//空指针允许多次释放
free(p);
free(p);
free(p);
free(p);
free(p);
5)释放的指针应该和开辟空间时得到的指针相同
int* p=(int*)malloc(sizeof(int)*10);
for(int i=0;i<10;i++)
{
*p=0;
//指针叠加,不断改变指针方向,释放会出错
p++;
}
free(p); //err,指针叠加,不断改变指针方向,释放会出错
p=NULL;
4)值传递和地址传递
void func(int* p) //本质上还是值传递,传递的是指针变量
{
p=(int*)malloc(sizeof(int)*10);
}
voidfunc1(int** p) //这个才是地址传递
{
*p=(int*)malloc(sizeof(int)*10);
}
int main()
{
int *p=NULL;
func(p);
//func1(&p);
for(int i=0;i<10;i++)
{
p[i]=i; //err,这里 p 依然是空指针,操作空指针会报错
}
free(p);
}
//解决这里的问题可以使用地址传递,或者是使用函数的返回值
//开辟二级指针对应的堆空间,实际上就是一个 5*3 的二维整型数组,int arr[5][3]
int** p=(int**)malloc(sizeof(int*)*5); //在堆空间中开辟
p[0]=(int*)malloc(sizeof(int)*3);
p[1]=(int*)malloc(sizeof(int)*3);
//开辟一级指针对应的堆空间
for(int i=0;i<5;i++)
{
p[i]=(int*)malloc(sizeof(int)*3); //在堆空间中开辟,但是 p 和 p[i]之间不一定是连续的
}
//内存使用
for(int i=0;i<5;i++)
{
for(int j=0;j<3;j++)
{
scanf("%d",&p[i][j]);
}
}
for(int i=0;i<5;i++)
{
for(int j=0;j<3;j++)
{
printf("%d ",p[i][j]);
}
printf("\n");
}
//空间释放,需要一级一级地释放,从内层向外层释放
for(int i=0;i<5;i++)
{
free(p[i]);
}
f