函数得分类:
- 有参数有返回值、无参数有返回值、有参数无返回值、无参数无返回值
区分函数的特征是根据【返回值类型】【参数类型】来确定的
函数名是调用当前函数的重要名称,同时【函数名是当前指针的指针常量】,存储当前函数在内存【方法区/函数区】的空间首地址。
#include
/*
计算得到两个 int 类型数据之和
@param n1 用户提供的 int 类型数据
@param n2 用户提供的 int 类型数据
@return 两个 int 类型数据之和,返回值数据类型为 int 类型
*/
int add(int n1, int n2);
int main(int argc, char const *argv[])
{
/*
【重点1】
函数关注的点是函数的【返回值数据类型】【形式参数列表数据类型】
【重点2】
函数名指针变量,存储当前函数在内存【方法去/函数区】首地址
*/
printf("add = %p\n", add);
/*
根据当前函数的【类型】,自定函数指针,存储函数空间首地址
自定义函数指针的格式:
返回值类型:
int
形式参数列表数据以及顺序
(int, int) 参数名称不重要
函数指针的名称和固定格式
(* 函数指针变量名称)
小括号包含 * 指针变量标记是为了优化组合关系,
如果没有对应函数返回值类型从 int 转换 int *
可以存储 add 函数空间首地址函数指针变量
*/
int (*pf) (int, int) = add;
/*
pf 就是函数指针
要求:
1. 对应函数的返回值数据类型必须是 int 类型
2. 对应的函数形式参数列表必须是(int, int)
*/
// 函数指针,可以当作对应函数使用
int ret = pf(10, 20);
printf("ret = %d\n", ret);
return 0;
}
int add(int n1, int n2)
{
return n1 + n2;
}
接口思维
- 规范限制
- 接口可以做什么,取决于接口对应的连接目标是什么
- 对应 USB 接口概念
#include
int add(int n1, int n2);
int sub(int n1, int n2);
int mul(int n1, int n2);
int div(int n1, int n2);
/*
多功能函数,可以处理用户提供的两个 int 类型数据,
处理方式由函数指针 pf 对应的函数决定
@param n1 用户提供的 int 类型数据
@param n2 用户提供的 int 类型数据
@param pf 处理两个 int 类型数据对应的函数指针,决定当前函数最终功能
@return 处理数据之后的最终反馈结果
*/
int opreation(int n1, int n2, int (*pf)(int, int));
int main(int argc, char const *argv[])
{
int (*pf)(int, int) = NULL;
// pf 函数指针指向不同的函数,处理结果的结果不同,执行操作一致
pf = add;
printf("ret = %d\n", pf(10, 20));// 30
pf = sub;
printf("ret = %d\n", pf(30, 10));// 20
pf = mul;
printf("ret = %d\n", pf(30, 30));// 900
pf = div;
printf("ret = %d\n", pf(200, 40));// 5
printf("--------------------------------\n");
// 调用函数指针作为函数参数的 operation 函数
/*
函数指针作为函数的参数,可以直接提供符合函数指针要求的函数名称
作为实际参数,提供的数据本质是函数在内存【方法区/函数区】的空间
首地址,CPU 可以根据函数指针对应的地址,访问对应的二进制可执行
内存空间内容,执行目标函数
*/
int ret = opreation(10, 20, add);// 30
printf("ret = %d\n", ret);
ret = opreation(50, 30, sub);// 20
printf("ret = %d\n", ret);
ret = opreation(30, 40, mul);// 1200
printf("ret = %d\n", ret);
ret = opreation(40, 10, div);// 4
printf("ret = %d\n", ret);
return 0;
}
int opreation(int n1, int n2, int (*pf)(int, int))
{
// 函数指针可以直接当前函数使用
return pf(n1, n2);
}
int add(int n1, int n2)
{
return n1 + n2;
}
int sub(int n1, int n2)
{
return n1 - n2;
}
int mul(int n1, int n2)
{
return n1 * n2;
}
int div(int n1, int n2)
{
return n1 / n2;
}
内存申请是为了解决程序所需变量占用内存空间较大时,像数组、链表······,如果存储在栈区,对于整个内存空间不友好,再者是栈区得内存空间较小。针对于这个问题,可以利用申请【内存堆区】空间得方式,存储对应的目标数据内容,利用指针存储对应申请空间得首地址,方便之后的操作。
通过内存申请函数在内存【堆区】做开辟的空间,需要通过释放函数告知系统:当前内存已经归还!
在 C 语言中,申请内存的常用函数时 malloc、calloc 和 realloc。
在使用这些函数的时候,需要在代码前导入头文件 stdlib.h
1. malloc 函数用于在堆区上分配指定大小得内存空间
malloc 的声明如下:
void* malloc(size_t size); /* malloc 函数接受一个参数 size,表示需要分配的字节数。 如果分配成功,它会返回一个指针分配内存的指针; 如果分配失败,返回 NULL。 */
2. calloc 函数用于在堆区上分配指定数量的指定大小的内存空间,并将分配的内存全部设置为零
calloc 的声明如下:
void* calloc(size_t num, size_t size); /* calloc 函数接受两个参数 num 和 size,分别表示需要分配的元素个数和每个元素的大小。 如果分配成功,它会返回一个指针分配内存的指针; 如果分配失败,返回 NULL。 */
3. realloc 函数用于重新分配已经申请的内存空间大小
realloc 的声明如下:
void* realloc(void* ptr, size_t size); /* realloc 函数接受两个参数 ptr 和 size,其中 ptr 时先前使用 malloc calloc 或 realloc 返回的指针,size 表示需要重新分配的字节数。 如果分配成功,它会返回一个指针分配内存的指针; 如果分配失败,返回 NULL。 【注意】 realloc 函数可能会在原地或者重新分配内存。 */
4. free 函数释放内存
以上这些函数使用后应该通过调用 free 函数来释放内存,以免内存泄漏。
free 函数的声明如下:
void free(void* ptr); // free 函数接受一个参数 ptr,表示要释放的内存指针
#include
#include
int main(int argc, char const *argv[])
{
/*
void *malloc(size_t size);
size_t ==> unsigned long 无符号长整型,仅支持正数 long 类型
malloc 函数接受一个参数 size,表示需要分配的字节数。
如果分配成功,它会返回一个指向分配内存的指针;如果分配失败,返回 NULL。
void * 万能指针,可以指向任何一个空间首地址,到那时系统要求,不可以通过
void * 访问读取目标空间中数据内容,在使用时需要进行【强制类型转换】,
明确告知当前指针对应数据空间,按照哪一个类型进行处理。
*/
// 当前操作只是向 CPU 申请内存堆区空间,要求空间字节数为 40 个字节
void *p = malloc(40);
/*
【小缺失】使用 malloc 申请的内存空间,需要明确告知空间存储数据类型
或者说申请的内存空间,按照哪一个类型进行分配
(int *)p 强制转换类型,原本 p 是 void * ,强制转换为 int *
告知 CPU 当前 p 对应地址的内存,按照 int 类型数据方式处理
*/
int *p2 = (int *)p;
/*
可以通过 p2 操作申请的内存空间,CPU 通过 p2 操作对应的内存空间,
空间每一个冤死都是 int 类型
*/
for (int i = 0; i < 40 / sizeof(int); i++)
{
// 方式一:可以按照指针形式进行操作
*(p2 + i) = i * 2;
}
for (int i = 0; i < 40 / sizeof(int); i++)
{
// 可以按照指针形式进行操作
printf("*(p2 + %d) = %d\n", i, *(p2 + i));
}
printf("---------------------------------------\n");
for (int i = 0; i < 40 / sizeof(int); i++)
{
// 方式二:可以按照数组形式进行操作,当前指针形式,数组的存储数据类型为 int 类型
p2[i] = i * 2;
}
for (int i = 0; i < 40 / sizeof(int); i++)
{
// 方式二:可以按照数组形式进行操作,当前指针形式,数组的存储数据类型为 int 类型
printf("p2[%d] = %d\n", i, p2[i]);
}
/*
void free(void* ptr);
free 函数是用于释放 malloc reallc calloc 申请的内存空间,需要提供给
当前函数申请的空间的【首地址】,所需参数是 void * 类型,在实际使用中,
可以提供任意类型指针。
free 操作将 p2 指向内存空间进行释放操作,告知系统当前内存使用完毕,
可以其他程序申请
*/
free(p2);
/*
p 和 p2 原本都存储申请空间首地址,但是对应空间已经通过 free 释放
归还给系统,为了安全操作不可以通过 p 和 p2 访问原本的数据空间,
重新赋值为 NULL,防止后期使用。
*/
p = NULL;
p2 = NULL;
return 0;
}
#include
#include
int main(int argc, char const *argv[])
{
/*
calloc 案例
void* calloc(size_t num, size_t size);
通过 calloc 函数申请内存空间【堆区空间】,要求申请 10 个元素,
每一个元素占用内存 4 个字节,总计内存空间 40 个字节
同时直接将当前申请内存空间首地址,强制转为 int * ,CPU 通过 p 操作
对应内存空间,每一个元素都是 int 类型。
*/
int *p = (int *)calloc(10, sizeof(int));
for (int i = 0; i < 10; i++)
{
p[i] = 777;
}
for (int i = 0; i < 10; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
free(p);
p = NULL;
return 0;
}
#include
#include
int main(int argc, char const *argv[])
{
// malloc 申请 40 个直接数据空间
int *p = (int *)malloc(10 *sizeof(int));
printf("p = %p\n", p);
for (int i = 0; i < 10; i++)
{
p[i] = i * 2;
}
for (int i = 0; i < 10; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
/*
void* realloc(void* ptr, size_t size);
ptr 是需要给当前函数的通过 malloc calloc realloc
申请的内存空间首地址
size 是当前重新分配空间对应的空间字节数
返回值是新空间首地址
1. 地址不变
2. 地址改变
【必须使用原本存储空间首地址的指针变量,接受 realloc 返回值】
*/
p = (int *)realloc(p, 20);
printf("p = %p\n", p);
for (int i = 0; i < 5; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
free(p);
p = NULL;
return 0;
}
#include
#include
int *grow(int *arr, int capacity);
int main(int argc, char const *argv[])
{
int *p = (int*)malloc(10 *sizeof(int));
for (int i = 0; i < 10; i++)
{
p[i] = i * 2;
}
p = grow(p, 20);
for (int i = 0; i < 10; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
printf("----------------------------\n");
for (int i = 0; i < 20; i++)
{
p[i] = i * 2;
}
for (int i = 0; i < 20; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
free(p);
p = NULL;
return 0;
}
// 扩容到原本空间的 2 倍
int *grow(int *arr, int capacity)
{
return (int *)realloc(arr, capacity * 2 * sizeof(int));
}
#include
#include
int *sub_int_array(int *arr, int capacity, int begin, int end);
int main(int argc, char const *argv[])
{
// 1. 创建一个数组,数组内存空间在内存的堆区
int *arr = (int *)calloc(10, sizeof(int));
// 2. 数据赋值
for (int i = 0; i < 10; i++)
{
arr[i] = 2 * i;
}
// 3. 用户指定的起始和终止下标位置
int begin = 3;
int end = 6;
// 4. 调用函数
int *new_arr = sub_int_array(arr, 10, begin, end);
for (int i = 0; i < end - begin; i++)
{
printf("new_arr[%d] = %d\n", i, new_arr[i]);
}
// 5. 释放内存
free(new_arr);
free(arr);
new_arr = NULL;
arr = NULL;
return 0;
}
int *sub_int_array(int *arr, int capacity, int begin, int end)
{
// 1. 判断用户提供的参数的合法性
if (NULL == arr || capacity <= 0)
{
printf("用户提供数组相关数据不合法!\n");
return NULL;
}
if (begin > end || begin < 0 || end > capacity)
{
printf("用户提供的下标不合法!\n");
}
// 2. 计算新数组容量,同时根据容量直接创建新的数组,同样占用内存堆区
int *new_arr = (int *)calloc((end - begin), sizeof(int));
if (NULL == new_arr)
{
// malloc calloc realloc 申请内存堆区空间,都有可能因为种种原因申请失败
// 返回值 NULL
return NULL;
}
// 3. 拷贝数据到新数组中
int count = 0;
for (int i = begin; i < end; i++)
{
new_arr[count++] = arr[i];
}
// 4. 返回新数组空间首地址
return new_arr;
}