1. 指针相关
&a 表示整个数组首地址
&a[0] 表示数组首元素a[0]的首地址
a 表示数组首元素的首地址 同&a[0]
上述值都相等,含义不同
&arr 代表整个数组的首地址;
指向整个数组的指针
arr 代表数组首元素的首地址;
指向单个元素的指针
上述两者的值相等,但数据类型不相同。
1.1 指针数组 & 数组指针
1.1.1 指针数组——int *p1[4]
因为"[]“的优先级比*要高,所以p1先与”[ ]"结合,构成一个数组的定义,数组名为p1,int*则是修饰数组的内容,即数组的每个元素。该数组包含4个指向int类型数据的指针。
1.1.2 数组指针——int (*p2)[4]
因为()的优先级比[]的优先级要高,所以*和p2构成一个指针的定义,指针变量名为p2,p2是一个指针,int修饰的是数组的内容,也就是说,p2是一个指针,是指向一个包含4个int类型数据的数组。
int arr[5] = {1,2,3,4,5};
int (*p1)[5] = &arr; //&arr 数组的首地址 正确赋值 匹配
int (*p2)[5] = arr; //数组arr首元素的首地址 错误赋值 两边元素类型不匹配
int *p3[5];
p3[0] = arr; //第一个指针指向数组arr
printf("%d",p3[0][2]);//数组arr的第三个元素3
1.2 指向指针的指针——int **p
可以用来定义二维数组
int** p;
p = new int* [3]; //申请了3行 列数未知
for (int i = 0; i < 3; i++)
{
p[i] = new int[5];//每行5列
}
int x = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
p[i][j] = x++;
}
}
delete(p);//释放内存
1.3 指针做函数参数
为了节省空间和实践,提高程序的运行效率,当一维数组作为函数参数的时候,编译器总是将它解析成一个指向其首元素首地址的指针,因此可以将一位数组的函数参数写成指针形式。
//一位数组的情况
void f(int *p){}
void f(int a[]){//里面的元素个数可有可无,推荐不写}
//二维数组的情况 a[3][5]
//可以将其理解成一个一位数组a[3],其中该一维数组中的每个元素是含有5个int类型数据的数组。
void f(int (*p)[5]){//p指向含有5个int类型数据的数组,行数不确定,通过指针的++/--来确定第几行}
void f(int a[][5]){//行数不确定,可写可不写,推荐不写,自定义行数}
//大于二维的情况
void f(int (*p)[5][5]){}
void f(int [][5][5]){}
//......
2. 内存管理
声明变量就是告诉编译器存在这么一个变量,但是编译器不为他分配任何内存空间;
定义变量则是实现这个变量,真正在内存中(堆或栈)为此变量分配空间。定义只能出现一次,声明可以出现多次。
变量定义在一个文件中,在另外的文件中进行声明使用(extern),定义和声明的类型必须一致。
栈:就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。
堆:通过new和malloc分配,由delete或free手动释放或者程序结束自动释放。动态内存的生存期人为决定,使用灵活。缺点是容易分配/释放不当容易造成内存泄漏,频繁分配/释放会产生大量内存碎片。 若程序员不释放,程序结束时可能由OS(操作系统)回收。注意它与数据结构中的堆是两回事,分配方式类似于链表。
常量区: 存放局部变量或者全局变量的值;对于全局常量,编译器一般不分配存储空间,而是放在符号表中以提高访问效率。
静态存储区:存放全局变量和静态变量(全局和局部)
代码区:函数二进制代码
C/C++不提供垃圾回收机制,因此需要对堆中的数据进行及时销毁,防止内存泄漏,使用free和delete销毁new和malloc申请的堆内存,而栈内存是动态释放。
内存中的栈区主要用于分配局部变量空间,处于相对较高的地址,其栈地址是向下增长的(由高到低);而堆区则主要用于分配程序员申请的内存空间,堆地址是向上增长的(由低到高)。
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
int main() {
/*在栈上分配*/
int a1 = 0;
int a2 = 0;
int a3 = 0;
int a4 = 0;
printf("栈-向下生长(由高到低):\n");
printf("a1 = 0x%08X\n", &a1);
printf("a2 = 0x%08X\n", &a2);
printf("a3 = 0x%08X\n", &a3);
printf("a4 = 0x%08X\n", &a4);
/*在堆上分配*/
char* p1 = (char*)malloc(4);//申请4字节内存
char* p2 = (char*)malloc(4);
char* p3 = (char*)malloc(4);
char* p4 = (char*)malloc(4);
printf("堆-向上生长(由低到高):\n");
printf("p1 = 0x%08X \n", p1);
printf("p2 = 0x%08X \n", p2);
printf("p3 = 0x%08X \n", p3);
printf("p4 = 0x%08X \n", p4);
free(p1);
p1 = NULL;
free(p2);
p2 = NULL;
free(p3);
p3 = NULL;
free(p4);
p4 = NULL;
system("pause");
return 0;
}
运行结果
栈-向下生长(由高到低):
a1 = 0x0062FDFC
a2 = 0x0062FDF8
a3 = 0x0062FDF4
a4 = 0x0062FDF0
堆-向上生长(由低到高):
p1 = 0x009B6D70
p2 = 0x009B6D90
p3 = 0x009B6DB0
p4 = 0x009B6DD0
上述运行结果是在64位操作系统得到的,int型变量的长度为4字节,指针型变量的长度为8字节。(但是为什么4个指针变量之间增长间隔不是8呢?)
指针的释放
free(p)
之后,p
还要赋值为NULL
,否则虽然释放了指针变量p
,但此时的指针变量p
本身并没有被删除,其保存的地址没有改变。但是,此时p
虽然不是NULL
指针,但它却指向了不合法的内存,称为“野指针”或者称为“悬垂指针”。
free(p)
释放的是指针变量所指向的内存,而不是指针变量p
本身。指针变量p
并没有被释放,仍然指向原来的存储空间。其实,指针只是一个变量,只有程序结束时才会被销毁。释放内存空间后,原先指向这块空间的指针还存在,只不过现在这个指针指向的内存是不合法的。因此在释放指针之后一定要将该指针指向NULL
,防止在后面指针又被解引用了。