/*
简单开辟内存
1.申请
---有两个函数能够实现申请内存的功能:
A. malloc(参数:需要字节的总数);
B. calloc(参数:每个需要的字节数,个数);
返回值都是 void* 类型的指针
2.使用
3.释放
free(参数:首地址)
如果不释放的话,会导致“内存泄露”
4.置空
如果不置空的话,会出现“野指针”
*/
# include <stdio.h>
int main()
{
/* malloc */
double* p = (double*)malloc(sizeof(double)); // 申请一个double类型大小的内存(8字节)
*p = 3.14; // 使用
printf("%lf \n", *p);
free(p); // 通过 p 里面存储的首地址,找到相对应的内存,从这里开始释放,一直释放到,申请内存的时候,做了标记的地方
p = NULL; // 通过置空,让指针不再指向已经被释放掉的内存
/* calloc */
float* p1 = (float*)calloc(sizeof(float),1);
*p1 = 3.14f;
printf("%f \n", *p1);
free(p1);
p1 = NULL;
printf("进阶运用 \n");
// 进阶运用
p = (double*)malloc(sizeof(double)*10); // 申请10个 double 类型大小的连续的内存(补充:因为上面将p定为 double* 而且置空过了,所以可再度利用)
for (int i = 0; i < 10; i++ )
{
*(p + i) = 10 + i; // 给值
printf("%lf \n", *(p + i)); // 展示值
}
free(p);
p = NULL;
/*
对于上面 for 循环部分的补充:
p:里面存的是:申请的内存的首地址
在一次申请中,申请的内存是连续的
*(p + i) <===> p[i] // 注意!它不是数组!
*/
return 0;
}
# include <stdio.h>
int main()
{
// 指针布局
int row = 3;
int** pp = (int**)calloc(sizeof(int*), row);
int len = 4;
for (size_t i = 0; i < row; i++) // size_t是什么?点我跳转学习
{
pp[i] = (int*)calloc(sizeof(int), len);
}
// 内容展示
for (size_t i = 0; i < row; i++)
{
for (size_t j = 0; j < len; j++)
{
pp[i][j] = i * 10 + j; // 给值
printf("%-5d", pp[i][j]); // 展示值,注意!这里不是二维数组!(看不懂请回顾上页内容)
}
printf("\n");
}
// 释放内存
for (size_t i = 0; i < row; i++)
{
free(pp[i]);
pp[i] = NULL;
}
free(pp);
pp = NULL;
return 0;
}
自动扩容
# include <stdio.h>
int main()
{
int len = 5; // 默认长度
int* p = (int*)calloc(sizeof(int), len);
int num = 1;
for (size_t i = 0; num != 0; i++) // 用户不输入0结束,就一直获取数据并复制到开辟的内存中
{
scanf("%d", &num);
p[i] = num; // 数据复制到开辟的内存中
}
for (size_t i = 0; p[i] != 0; i++)
{
printf("%-5d", p[i]); // 展示数据
}
free(p);
p = NULL;
return 0;
}
/*
扩容的本质是:
将小内存中的所有内容拷贝到大内存中,然后,再继续对大内存进行别的操作
*/
# include <stdio.h>
int main()
{
// 长度
int len = 5;
// 首次申请内存
int* p = (int*)calloc(sizeof(int), len);
int* temp = p; // 成为p的分身,以防万一
// 重复输入数据(并复制到内存中)
int num = 1;
int i = 0;
while (scanf("%d", &num), num != 0)
{
if (i < len) // 没满的情况下
{
temp[i++] = num; // 存完一次,记录一下
}
else // 满了的情况下
{
len += 5;
p = (int*)calloc(sizeof(int), len); // 重新申请更大的内存
for (int j = 0; j < i; j++)
{
p[j] = temp[j];
}
free(temp);
temp = NULL;
temp = p; // 继续成为当前p的分身
temp[i++] = num;
}
}
// 输出数据
printf("--------------------\n");
for (int j = 0; j != i; j++)
{
printf("%d \n", temp[j]);
}
free(p);
p = NULL;
temp = NULL;
return 0;
}
// 1.指针函数
// 下面的代码是一个错误的例子,你能发现它的错误吗?
# include <stdio.h>
int* test(); // 声明
int main()
{
int* temp = test();
printf("%d \n", *temp);
return 0;
}
int* test()
{
int num = 10;
int* p = #
return p; // 返回了栈区变量的首地址(非常严重的问题!详情见下)
}
/*
下面的内容是在栈区,当运行完毕,系统会回收其内存资源:
int* test()
{
int num = 10;
int* p = #
return p;
}
当函数返回栈区变量 num 的内存地址之后,
函数运行完毕,系统回收 num 内存,供以后"某某东西"使用
所以,返回的地址不但没有作用,还会导致以后非法访问内存的问题出现
*/
// 2.函数指针
/*
函数指针的定义
返回类型说明符 (*函数指针变量名)(参数列表);
*/
# include <stdio.h>
int func(); // 声明
int main()
{
// 定义函数指针,并进行初始化
int(*p)() = func; // 即:定义了指针p,而且 p 等于 func(func里面存的是函数的首地址)
func();
p();
return 0;
}
int func()
{
printf("成功执行了 func 函数!\n");
return 6;
}
// 函数指针,知识扩展1
# include <stdio.h>
int func(); // 声明
typedef int funcType(); // 将 int...() 取别名为 funcType
int main()
{
funcType* p = func;
p();
return 0;
}
int func()
{
printf("成功执行了 func 函数!\n");
return 6;
}
// 函数指针,知识扩展2
# include <stdio.h>
int func(int a, int b); // 声明
typedef int(*pfunc)(int a, int b);
int main()
{
pfunc p = func; // 这样也能定义函数指针
int res = p(1,2);
printf("%d \n", res);
return 0;
}
int func(int a, int b)
{
printf("成功执行了 func 函数!\n");
return a + b;
}
// 2.数组指针
/*
定义"数组"指针:
所指向的数组里面存的数据类型 (*数组指针名称)[所指向的数组的长度];
*/
# include <stdio.h>
int main()
{
int arr[3] = { 1, 2, 3 }; // 建立一个数组。
// arr 里面存的是数组内存的首地址,而 [] 表示内存里面存的那一堆东西是数组,3 表示数组长度,int 表示数组里面存的数据是 int 类型
int(*p)[3]; // 长度为 3 的数组指针
p = arr;
printf("%d \n", p[1]);
return 0;
}
// 数组指针,知识扩展
# include <stdio.h>
typedef int(*pType)[3]; // 定义类型:int(*pType)[3],取别名为:pType
int main()
{
int arr[3] = { 1, 2, 3 };
pType p; // 变量 p 的类型为 pType,而属于这种类型的变量 p 必然满足 int(*pType)[3] 模板格式
p = arr; // arr里面储存的“数组的内存首地址”复制给变量 p
printf("%d \n", (*p)[0]); // 注意!不要写成 p[0],虽然 p 获得了 arr 里面存的首地址,但是 *p 才是代表数组整体
return 0;
}
/*
同理:
# include <stdio.h>
typeof int pArr[3];
int main()
{
int arr[3] = { 1, 2, 3 };
pArr p;
p = arr;
}
*/
使用const修饰指针
/*
const:常量,被它修饰的变量会具有常量的属性,使用 const 修饰指针包含三种情况
1.常量指针(指向常量的指针)
指向常量的指针 type const *p; 或者 const type *p;
可以改变指向,但是不能用 *p 修改指向变量的值
2.指针常量
它是常量,本身不能改变,也就是不能改变指向
因为指向不能改,所以必须初始化
但是可以通过取内容修改指向的内存中的内容
3.常量指针常量("常量指针"常量即:指针常量)
指针本身是一个常量,指向的也是常量
const int * const p = &a;
不能改变指向,也不能改变指向的内存的内容
*/
# include <stdio.h>
int main()
{
const int num = 0; // 变量 num 使用 const 修饰了就不能被修改了
// 1.常量指针
int a = 0, b = 9;
const int * p = &a; // const -> int
p = &b; // 可以改变指向
// 2.指针常量
int c = 6;
int* const p1 = &c; // const -> p1
*p1 = 10; // 可以修改内容
// 3.常量指针常量
int d = 8;
const int* const p = &d; // const -> int 和 p
return 0;
}