目录
函数指针:
指向函数的指针,称为函数指针
void f (int); // 函数 f 的类型是: void (int)
void (*p)(int); // 指针 p 专门用于指向类型为 void (int) 的函数
p = &f; // p 指向 f(取址符&可以省略)
p = f; // p 指向 f
// 以下三个式子是等价的:
f (666); // 直接调用函数 f
(*p)(666); // 通过索引指针 p 的目标,间接调用函数 f
p (666); // 函数指针在索引其目标时,星号可以省略
指针函数:
指针函数就是返回指针值的函数,本质是一个函数。
//一般定义方法有以下三种格式
int *fun(int x,int y);
int * fun(int x,int y);
int* fun(int x,int y);
一般在申请堆空间的时候函数需要返回一个指针,例如
int *get_memory(int size)
{
int *p = malloc(sizeof(int) * size);
printf("分配的堆空间地址 %p\n", p);
return p;
}
参数为指针的函数:
函数有形参和实参之分,如何通过形参改变实参呢?
函数的传递分为两种,一种是值传递,一种是地址传递
参数的值传递,形参与实参相互独立互不影响。
参数的地址传递,形参可以通过地址修改实参的数据。(通过函数的地址传递的解引用才能修改实参的数据)
有以下三种情况
#include <stdio.h>
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
printf("swap中a=%d, b=%d\n", a, b);
}
int main()
{
int a = 100, b = 200;
swap(a, b);
printf("main函数中a=%d, b=%d\n", a, b);
return 0;
}
这是一个简单的交换函数,输出如图
可以看到在main函数中,函数的值实际上是没有交换的,这就是函数的值传递
第二种情况
#include <stdio.h>
void swap_addr(int *a, int *b)
{
printf("swap函数中交换之前 a=%p,b=%p\n", a, b);
printf("swap函数中交换之前 *a=%d,*b=%d\n\n", *a, *b);
int *tmp = a; // 只是交换了,两个指针所指向的地址而已,并不是交换地址上的内容(数据)!
a = b;
b = tmp;
printf("swap函数中交换之后 *a=%d,*b=%d\n", *a, *b);
printf("swap函数中交换之后 a=%p,b=%p\n\n", a, b);
}
int main()
{
int a = 100, b = 200;
printf("main函数中交换之前 &a=%p,&b=%p\n", &a, &b);
printf("main函数中交换之前 a=%d, b=%d\n\n", a, b);
swap_addr(&a, &b);
printf("main函数中交换之后 &a=%p,&b=%p\n", &a, &b);
printf("main函数中交换之后 a=%d, b=%d\n", a, b);
return 0;
}
输出如图
这种情况虽然是函数的地址传递,但是main函数中的值仍然没有变化,这就是因为我们只对地址进行了交换,没有对地址中的值解引用交换
所以第三种情况时对swap_addr()函数进行修改
void swap_addr_new(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
可以看到,main函数中的值被成功修改
参数为数组的函数:
在函数形参为数组时,编译器为了节省内存空间会自动将数组转换为指向数组首地址的指针
如:
int arry_sum(int arry[5]) 等价于 int arry_sum_x(int *arry)
正常由于arry是含有五个元素的int型数组,所以sizeof(arry)等于20,但由于函数中arry[5]是一个指针,所以在函数中sizeof(arry)等于8
二维数组同理
int two_arry_sum(int arr[3][3]) 等价于 int two_arry_sum_x(int (*arr)[3]) 等价于 int two_arry_sum_x(int (*)[3])
C语言内存管理
可以参考这篇文章,写的非常详细,这里只做总结和补充
stdlib.h头文件常用函数介绍
#include <stdlib.h>
---------分配----------
void *malloc(size_t size); //分配大小为size 的堆空间
void *calloc(size_t nmemb, size_t size); //分配大写为 size * nmemb 的堆空间
---------修改----------
void *realloc(void *ptr, size_t size); //修改ptr的堆空间大小为 size
void *reallocarray(void *ptr, size_t nmemb, size_t size);//修改ptr的堆空间大小为 size * nmemb
---------释放----------
void free(void *ptr); //释放ptr 所指向的堆空间
1、局部变量
局部变量:自动分配空间,自动释放空间
存储区:栈空间
定义:在函数内部的 花括号定义 { }
作用域:只在当前 花括号{ } 内有效
生命周期: 只在当前 花括号{ } 内有效
2、全局变量
全局变量: 在整个程序中,都一直存在
存储区:数据段
定义:在所有的函数外面定义
作用域:在整个程序中都有效
生命周期:在整个程序中都有效
#include <stdio.h>
int a = 10, b = 20;
void swap_all()
{
printf("swap函数中全局变量地址 &a=%p,&b=%p\n", &a, &b);
printf("swap函数中全局变量交换前 a=%d,b=%d\n", a, b);
int tmp = a;
a = b;
b = tmp;
printf("swap函数中全局变量交换后 a=%d,b=%d\n\n", a, b);
}
int main()
{
swap_all(&a, &b);
printf("main函数中全局变量 a=%d, b=%d\n", a, b);
return 0;
}
输出结果
但是如果在main函数中重新定义 int a,b;则main函数会输出其中的局部变量,因为局部变量在本函数中优先级大于全局变量。
3、 堆空间变量
堆空间变量: 由用户自己去管理空间 (最灵活的内存空间)
存储区:堆空间
定义:调用 malloc,calloc 进行分配
作用域:在空间未被释放之前都有效
生命周期:利用 malloc,calloc 分配, 利用 free 释放
int *Heap()
{
// int value = 100; //会释放,不能返回
int *value = malloc(sizeof(int));
// 8个局部变量的指针空间 4个堆空间变量 = 12 个空间
*value = 100; // 往堆空间存储数据
printf("value=%p,*value=%d\n", value, *value);
return value; // 返回堆空间,没问题 , 指针会释放! 堆不会
// return &value;
}
//在堆内存中申请一个空间,依次存入元素
#include <stdio.h>
#include <stdlib.h>
void *size1(int size)
{
int *p = (int *)malloc(sizeof(int) * size);
return p;
}
int main()
{
int size=5;
int *q = size1(size);
for(int i = 0; i<5; i++)
{
q[i] = i;
printf("%d\n", q[i]);
}
}
内存释放和内存泄露
int *Heap_tmp()
{
int *tmp = malloc(sizeof(int));
*tmp = 100;
printf("tmp=%p,*tmp=%d\n", tmp, *tmp);
// 内存泄漏!!堆空间的地址已经找不到了!
// 注意:如果在一个函数中使用了堆空间, 记得释放 或者 把空间地址返回出去,否则会出现内存泄漏
}
需要添加free()函数
4、静态变量
静态变量:在整个程序中,都一直存在
存储区:数据段
定义:利用 static 关键字定义
作用域:在整个程序中都有效
生命周期:在整个程序中都有效
静态局部变量
1.静态局部变量,只能被初始化1次 ,存储在数据段,不会释放。
#include <stdio.h>
void increment() {
static int count = 0; // 静态局部变量
count++;
printf("Count: %d\n", count);
}
int main() {
increment(); // 第一次调用,count 初始化为 0,然后加 1,输出 1
increment(); // 第二次调用,count 不再初始化,直接加 1,输出 2
increment(); // 第三次调用,count 继续加 1,输出 3
return 0;
}
2.静态全局变量,只能在当前文件使用,无法跨文件使用,防止全局变量名冲突。
5、常量
常量:在整个程序中,都一直存在
存储区:数据段
定义:利用 const 关键字定义
作用域:在整个程序中都有效
生命周期:在整个程序中都有效