作用域就是它能在哪去使用。生命周期就是从哪开始从哪结束,例如:夏虫朝菌、浮游朝生暮死、昙花一现······
C语言变量的作用域分为:
使用auto修饰,局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块 {} 内部定义的变量都是自动变量,它有如下特点:
局部变量存在栈区。
复合语句(compound statement)简称为语句块,它使用大括号 { } 把许多语句和声明组合到一起,形成单条语句。
{ [声明和语句的列表] }
加不加auto都指的是局部变量,一般都不加auto进行修饰,跟声明extern一样不加。
#include
void Add(int a,int b)//形参也是局部变量
{
int c = 0;//局部变量
}
int main(void)
{
/*
在函数内部定义的变量 局部变量
作用域:在函数内部,从变量定义到函数结束
生命周期:从变量定义到函数结束
*/
auto int a = 6;//定义变量 局部变量
for (int i = 0; i < 10; i++) {} //局部变量 i 作用域只限于for函数中
//printf("%d\n", i);//err
printf("%d\n", a);//6
return 0;
}
不声明找不到
存在数据区。
test1.c
#include
//全局变量 在函数外部定义的变量
//作用域:整个项目中所有文件 如果在其他文件中使用 需要声明
//生命周期:从程序创建到程序销毁
int j = 1;
void Func02()
{
j = 666;
printf("%d\n", j);
}
int main()
{
printf("%d\n", j);//1
Func02();//666
Func03();//666
return 0;
}
test2.c
#include
extern int j;//声明
void Func03()
{
printf("%d\n", j);
}
#include
int j = 1;
int main()
{
printf("%d\n", j);//1
int j = 60;
printf("%d\n", j);//60
//匿名内部函数
{ //这里叫代码体(程序体)
//int j = 52;//第一种 ------------------------------
j = 52;//第二种 ------------------------------
printf("%d\n", j);//52
} //程序结束之后这个函数就销毁了
printf("%d\n", j);//第一种:60 ; 第二种:52
return 0;
}
还可以打印地址看看printf("%p\n", &j);
extern int a; 声明一个变量,这个全局变量在别的文件中已经定义了,这里只是声明,而不是定义。
存储的位置:数据区
只初始化一次,但可以赋值多次。
数据区
进行存储,程序在执行起来的时候,static这句话就已经走完一遍了#include
void Func01()
{
//int y = 1;//局部变量
static int y = 1;//静态局部变量
y++;
printf("%d\n", y);
}
int main()
{
//静态局部变量
//static int y = 1;
//printf("%d\n", y);
for (int i = 0; i < 10; i++)
{
Func01();
}
return 0;
}
作用域:可以在本文件中使用,但是不可以在其他文件中使用
。
生命周期:从程序创建到程序销毁。
#include
//静态全局变量
static int c = 1;
void Func()
{
c = 666;
printf("%d\n", c);
}
int main()
{
for (int i = 0; i < 10; i++)
{
c++;
printf("%d\n", c);
}
Func();
}
局部变量未初始化在Visual Studio中是不允许使用的,因为虽然内存开辟空间了,但是打印的值都是乱码(任意值)
。在其他的编译环境中是允许使用的,Visual Studio做一个安全限制,你没有初始化它一定是错的,它会给你进行报错。
未初始化的全局变量:0
未初始化的静态全局变量:0
未初始化的静态局部变量:0
在C语言中函数默认都是全局的(全局函数),extern可省略。项目中的所有文件都可以去调用它。
声明函数
extern 类型标识符 函数名( 形参列表 );
或者
类型标识符 函数名( 形参列表 );
其实不声明程序能找到
,但是如果你声明了你就可以右键跳转,找起来比较方便,声明是有一定意义的,声明一般是放在头文件中( .h文件),建议声明
。
C++有多态可以方法重载;C语言不支持一个函数名对应多个参数的样式,所以全局函数名称是作用域中唯一的。
作用域: 在整个项目中所有文件中使用。
生命周期:从程序创建到程序销毁
在C语言中函数默认都是全局的,使用关键字static
可以将函数声明为静
态,函数定义为static就意味着这个函数只能在定义这个函数的文件中使
用,在其他文件中不能调用,即使在其他文件中声明这个函数都没用。
对于不同文件中的staitc函数名字可以相同。
static 类型标识符 函数名( 形参列表 );
作用域:当前文件中
静态函数可以和全局函数重名,当前文件中优先调用静态函数,就近原则。当静态函数和全局函数重名并且在同一文件中时,报错。
生命周期:从程序创建到程序销毁
#include
static void FuncStatic()
{
printf("静态函数\n");
}
int main()
{
FuncStatic();
return 0;
}
内存不止四区,内存四区只是对于我们应用程序来说的,内存四区模型图:
全局常量,安全的常量 ,存储区域为数据区常量区。局部常量是不安全的。
data段 或者 data segment
)bss区
)(stack)
:系统为每一个程序分配一个临时的空间
从高地址开始存
,向下增长(只有栈区是这样的)
,数组是例外首元素地址是从低地址开始。先进后出、后进先出(沙桶原理)
(heap)
#include
//全局常量 安全的常量 存储区域为数据区常量区
const int c1 = 123;
//未初始化的全局变量
int x1;
//初始化全局变量
int x2 = 52;
//未初始化的静态全局变量
static int s1;
//初始化静态全局变量
static int s2 = 16;
int main()
{
const int c2 = 123;//局部常量 不安全的
int y = 10;
//未初始化的局部变量
static int j1;
//初始化的局部变量
static int j2 = 10;
char* p = "hello xy";//字符串常量
int arr[] = { 1,2,3,4 };//数组
int** pp = arr;//指针
printf("未初始化的全局变量: % p\n", &x1);
printf("未初始化的静态全局变量: % p\n", &x2);
printf("未初始化的静态全局变量: % p\n", &s1);
printf("初始化静态全局变量: % p\n", &s2);
printf("局部变量: % p\n", &y);
printf("字符串常量: % p\n", p);
printf("数组: % p\n", &arr);
printf("指针变量: % p\n", pp);
printf("指针地址: % p\n", &pp);
return 0;
}
可以看到红色的存储区域都是数据区,里面又有一些区别,初始化的、未初始化、字符串常量。
绿色的是栈区,栈区一般存局部变量、数组信息、指针、结构体。
栈区超出1M会报错,我们定义一个数组看看效果
#include
int main()
{
int arr[1000000] = { 0 };//超出1M(默认),报错
/*
字节B 1000000*4=4000000
KB 4,000,000/1024=3,906.25
MB 3,906.25/1024=3.81
*/
//int arr[1000000/4] = { 0 };//没有超出
return 0;
}
默认是1M,如果想要扩充,通过程序编译之前,右键项目选择属性,windows中最大可改为10M,默认单位为byte,修改为10M,1010241024=10485760
#include
int main()
{
//栈区大小
//int arr[1000000] = { 0 };//超出1M,报错
/*
字节B 1000000*4=4000000
KB 4,000,000/1024=3,906.25
MB 3,906.25/1024=3.81
*/
int arr[1000000/4] = { 0 };//没有超出
return 0;
}
#include
void* malloc(size_t size);
功能:在内存的动态存储区(堆区)中分配一块长度为size字节的
连续区域
,用来存放类型说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。
参数:
- size:需要分配内存大小(单位: 字节)
返回值:
- 成功:分配空间的起始地址,
- 失败:NULL
#include
void free(void* ptr);
功能: 释放ptr指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址,对同一内存空间多次释放会出错。
参数:
ptr:需要择放空间的首地址,被释放区应是由malloc函数所分的区域。返回值: 无
注意:开辟和释放必须是同一个指针
就算没有free(),main()结束后也是会自动释放malloc()的内存的,free()的用处在于实时回收内存。如果你的程序很简单,那么你不写free()也没关系。
#include
int main()
{
//开辟堆空间存储数据
int* p = (int*)malloc(sizeof(int));
printf("%p\n",p);//01685FE0
printf("%d\n",*p);//-842150451
//使用堆空间
*p = 123;
printf("%d\n", *p);//123
//释放堆空间
free(p);
printf("释放完之后:%p\n", p);//01685FE0
printf("释放完之后:%d\n", *p);//-572662307
*p = 456;
printf("%d\n", *p);//456
return 0;
}
释放完还能操作这个堆空间地址吗?答案是可以,释放完p之后这个p就变成野指针了,p指向未知空间了,操作野指针对应的空间是可能报错的,可能也不报错的,我们尽量避免野指针的出现,可以在每次使用完p之后p=NULL;
把p置为空。
注意:开辟堆空间的时候,要有连续的空间,如果没有就失败了
#include
#include
int main(void)
{
//int* p = (int*)malloc(sizeof(int) * 1000000);//3M 打印的地址:00EE4040
//int* p = (int*)malloc(sizeof(int) * 1000000 * 100);//300M 打印的地址:00EE4040
//int* p = (int*)malloc(sizeof(int) * 1000000 * 1000);//3000M==2.92GB 打印的地址:00000000
int* p = (int*)malloc(sizeof(int) * 1000000 * 1000 / 3);//1000M==1GB 打印的地址:01288040
printf("%p\n",p);//
free(p);
return 0;
}
一般情况下, 加载到内存中的数据最多控制在1个G左右。
#include
#include
int main(void)
{
//开辟10个内存大小
int* p = malloc(sizeof(int) * 10);//开辟了40字节
//一般不写
//if (!p) {//p == NULL
// printf("程序异常\n");
// return -1;
//}
for (int i = 0; i < 10; i++)
{
p[i] = i;
}
for (int i = 0; i < 10; i++)
{
//printf("%d\n", p[i]);
printf("%d\n", *(p+i));
}
free(p);
return 0;
}
还可以放随机数
#include
#include
#include
//随机数导入stdlib、time.h文件
#define MAX 10
int main()
{
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]);
}
free(p);
return 0;
}
#include
void * memset(void *s, int c, size_t n);
功能: 将的内存区域的前n个
字节
以参数填入,栈区堆区都可以
参数:
- s:需要剩作内存s的首地址
- c: 填的疗符.c虽然参数为int,但必须是unsigned char,范围为0~255
- n:指定需要设置的大小
返回值: s的首地址
#include
#include
#include
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);//40
//memset()重置内存空间的值
//memset(p,0,40);//打印全是0
memset(p,1,40);//打印全是16843009
for (int i = 0; i < 10; i++)
{
printf("%d\n",p[i]);
}
free(p);
return 0;
}
一般都是用 memset(p,0,40); 重置成0。
#include
#include
#include
int main()
{
char ch[10];//栈区
memset(ch,'y',10);
for (int i = 0; i < 10; i++)
{
printf("%c\n",ch[i]);
}
return 0;
}
#include
void * memcpy(void *dest, const void * src , size_t n);
功能: 拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。
参数:
- dest:目的内存首地址
- src: 源内存首地址,注意:dest和src所指的内存空间不可重叠,可能会导致程序报错,内存里面一边往里读一边往里写,可能会导致内存被占用掉
- n:需要拷贝的字节数
返回值: dest的首地址
#include
#include
#include
int main()
{
int arr[10] = { 0,9,1,2,3,4,5,6,7,8 };
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]);
}
free(p);
return 0;
}
字符串拷贝strcpy 和 内存拷贝memcpy 的区别
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
int main()
{
char ch[] = "hello\0 xy";
char s[100];
//strcpy(s,ch);//字符串拷贝遇到\0就停止
memcpy(s,ch,9);
for (int i = 0; i < 9; i++)
{
printf("%c",s[i]);
}
return 0;
}
当拷贝源和拷贝目标发生重叠
#include
#include
int main()
{
int arr[] = {0,1,2,3,4,5,6,7,8,9};
memcpy(&arr[5], &arr[3], 12);//0 1 2 3 4 3 4 5 8 9
for (int i = 0; i < 9; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
0,1,2,3,4,5,6,7,8,9 ==> 0 1 2 3 4 3 4 5 8 9
memmove() 功能用法和 memcpy() 一样,区别在于:dest和src所指的内存空间重叠时,memmove() 仍然能处理,不过执行效率比memcpy() 低些。
如果源和目标重叠,它会自己在内存中开辟一块空间,先把你 源 挪到这去,然后再挪到目标文件,这样就不会报错了。
不重叠的时候跟memcpy一样
#include
#include
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
//memcpy(&arr[5], &arr[3], 12);
memmove(&arr[5], &arr[3], 12);//0 1 2 3 4 3 4 5 8
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
#include
void * memcmp(const void *s1, const void *s2, size_t n);
功能: 比较s1和s2所指向内存区域的前n个字节,比较的是内存中的值,不限于字符串、整型······
参数:
- s1:内存首地址s1
- s2:内存首地址s2
- n:需比较的前n个字节
返回值:
- 相等:=0
- 大于:>0
- 小于:<0
#include
#include
int main()
{
int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
int arr2[] = { 0,1,2,3,4,5 };
int val= memcmp(arr1,arr2,20);
printf("%d\n", val);//0
return 0;
}
比较字符串:strcmp(s1,s2)只能比较 \0 之前的内容;memcmp可以比较\0之后,它比较的跟字节有关。
#include
#include
int main()
{
char s1[] = "hello\0 xy";
char s2[] = "hello\0 xy";
int val= memcmp(s1,s2,10);
printf("%d\n", val);//0
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
int main()
{
//数组下标越界
//char ch[8] = "hello xy";
//堆空间数组下标越界
char* p = (char*)malloc(sizeof(char) * 8);
strcpy(p,"hello xy");
printf("%s\n",p);
/* 我开辟了8空间,但是我用了9个空间,
释放是按照8释放还是9释放?按8释放没释放掉,按9释放释放多了
释放多余的空间会出现问题 */
//free(p);//一用就报错
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
int main()
{
int* p = (int*)malloc(0);//野指针
printf("%p\n", p);
*p = 666;
printf("%d\n", *p);
//free(p);//一用就报错
return 0;
}
一般情况建议用int* p = (int*)malloc(sizeof(int)*10);
#include
#include
int main()
{
int* p = (int*)malloc(10);
p[0] = 0;
p[1] = 11;
//p[2] = 22;//一用就报错
printf("%p\n", p);
printf("%d\n", *p);
printf("%d\n", *(p+1));
free(p);
return 0;
}
第一次释放是正确指针,第二次释放是野指针,释放野指针是不对的,操作野指针空间可能报错,释放野指针一定报错。堆空间不允许多次释放
我们用完给它置为空指针,就不会报错啦,堆空间不允许多次释放,空指针允许多次释放
#include
#include
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
free(p);
p = NULL;//空指针
free(p);
free(p);
free(p);
free(p);
free(p);
return 0;
}
通过指针操作对应的堆空间的时候,尽量不要修改,如果需要修改最好做一个备份。
#include
#include
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
printf("%p\n",p);
for (int i = 0; i < 10; i++)
{
//指针叠加 不断改变指针方向,释放会出错
*p++ = i;
}
printf("%p\n", p);
free(p);//这时候的p地址不是一开始的地址了
return 0;
}
正确:备份
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
//printf("%p\n",p);
int* temp = p;
for (int i = 0; i < 10; i++)
{
//指针叠加 不断改变指针方向,释放会出错
//*p++ = i;
*temp++ = i;
}
//printf("%p\n", p);
free(p);//这时候的p地址不是一开始的地址了
return 0;
}
因为这两个p都是一级指针(同一级别的),虽然两个都是指针,但是指针传递的情况下,这种方式也是叫值传递。这种方式并没有堆空间地址传递过来。
#include
#include
void Func01(int * p)
{
p = (int*)malloc(sizeof(int) * 10);
}
int main()
{
int* p = NULL;
Func01(p);
for (int i = 0; i < 10; i++)
p[i] = i;
free(p);
return 0;
}
我们想要地址传递应该这样写:
用更高级的指针接收它
#include
#include
void Func01(int** p) //
{
*p = (int*)malloc(sizeof(int) * 10);//
}
int main()
{
int* p = NULL;
Func01(&p);//
for (int i = 0; i < 10; i++)
p[i] = i;
free(p);
return 0;
}
返回值去接收
#include
#include
int* Func03()
{
int* p = malloc(sizeof(int) * 10);
return p;
}
int main()
{
int* p = NULL;
p = Func03(&p);
for (int i = 0; i < 10; i++)
p[i] = i;
for (int i = 0; i < 10; i++)
printf("%d ",p[i]);
free(p);
return 0;
}
打印0 1 2 3 4 5 6 7 8 9。因为堆空间在函数Func03结束之后不会被释放,所以p操作堆空间不会报错。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
int main()
{
//int p[5][3];
//开辟二级指针对应的堆空间
int** p = (int**)malloc(sizeof(int*) * 5);
//开辟5行3列的二维数组
for (int i = 0; i < 5; i++)
{
//开辟一级指针对应的堆空间
p[i] = (int*)malloc(sizeof(int) * 3);
}
//存值
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]);
}
puts("");
}
//先释放里面的再释放外面的
for (int i = 0; i < 5; i++)
{
free(p[i]);
}
free(p);
return 0;
}
注意:不是连续的堆空间