一、函数的局部变量和全局变量
1.局部变量
(1)局部变量定义
在函数内部定义的变量称为局部变量
代码示例:
#include <stdio.h>
void test() {
int a = 10; // 局部变量
printf("a = %d\\n", a);
}
int main() {
test();
return 0;
}
(2)局部变量的特点
作用域:只能在定义它的函数内部使用
生命周期:函数调用时创建,函数结束时销毁
示例:
void test() {
int a = 10;
}
每次调用test()
创建a
使用a
销毁a
默认值:局部变量没有默认值,如果没有初始化,里面是随机值(垃圾值)
代码示例
#include <stdio.h>
int main() {
int a;
printf("%d\\n", a);
return 0;
}
输出结果为:
12548
-3567
0
…
2.全局变量
(1)全局变量的定义
在所有函数外部定义的变量称为全局变量
代码示例
#include <stdio.h>
int num = 100; // 全局变量
void test() {
printf("num = %d\\n", num);
}
int main() {
printf("num = %d\\n", num);
test();
return 0;
}
(2)全局变量的特点
作用域:从定义位置开始,到整个文件结束都可以使用
生命周期:程序开始运行时创建,程序结束时销毁
示例:
程序启动
↓
创建全局变量
↓
main执行
↓
程序结束
↓
销毁全局变量
默认值:全局变量如果没有初始化,系统自动初始化为0;
示例:
#include <stdio.h>
int num;
int main() {
printf("%d\\n", num);
return 0;
}
输出:
0
3.局部变量和全局变量同名
(1)当局部变量和全局变量同名时,采取局部变量优先原则(就近原则)
代码示例:
#include <stdio.h>
int num = 100; // 全局变量
int main() {
int num = 50; // 局部变量
printf("%d\\n", num);
return 0;
}
输出:
50
(2)口诀:先找局部变量,找不到再找全局变量
4.局部变量和全局变量总结对比表
| 定义位置 | 函数内部 | 函数外部 |
| 作用域 | 当前函数内部 | 整个文件 |
| 生命周期 | 函数调用期间 | 程序运行期间 |
| 默认值 | 随机值(垃圾值) | 0 |
| 存储位置 | 栈区(Stack) | 全局区/静态区 |
| 是否推荐大量使用 | 是 | 否 |
二、指针变量的定义和使用
1.指针的定义
指针本身也是一种变量,它是用来存放变量地址的变量(用大白话说就是指针变量指向谁,就把谁的地址赋值给指针变量)
2.指针的语法格式
类型 变量;
类型 * 指针变量 = &变量;
(1)在定义指针变量时:* 号表示所声明的变量为指针类型
指针变量要保存某个变量的地址
(2)在使用指针变量时:* 叫解引用,可以操作(读取、修改)指针所指向的变量的值
(3)&:取地址,返回操作数的内存地址
· 例如:&a,那就是取a的地址
(4)指针的类型
| char * | char | 1字节 | 4字节 | 8字节 |
| short * | short | 2字节 | 4字节 | 8字节 |
| int * | int | 4字节 | 4字节 | 8字节 |
| long * | long | 4或8字节 | 4字节 | 8字节 |
| float * | float | 4字节 | 4字节 | 8字节 |
| double * | double | 8字节 | 4字节 | 8字节 |
| long long * | long long | 8字节 | 4字节 | 8字节 |
| void * | 任意类型 | 无固定大小 | 4字节 | 8字节 |
(5)指针示例代码
#include <stdio.h>
#include <stdint.h>
int main() {
//定义一个整型变量
int a = 10;
printf("&a = %p\\n", &a);
//定义一个指针,指向a的地址
int* p = &a;
printf("p = %p,*p = %d\\n", p, *p);
//定义char* ,int* ,double*
char *p1;
int *p2;
double *p3;
//输出三个指针类型的长度
printf("%zu\\n", sizeof(p1));
printf("%zu\\n", sizeof(p2));
printf("%zu\\n", sizeof(p3));
return 0;
}
3.通过指针间接修改变量的值
(1)间接修改的本质:通过指针访问改地址,然后改变它指向的值
(2)示例代码:
#include <stdio.h>
int main() {
int a = 10; // 原变量
int* p = &a; // 指针指向a
printf("修改前 a = %d\\n", a);
*p = 99; // 通过指针修改a的值
printf("修改后 a = %d\\n", a);
return 0;
}
4.指针步长
(1)定义:指针步长指的是指针每移动一次,实际跳过的字节数(步长大小由指针指向的数据类型决定)
(2)指针加n等于指针地址加上 n 个 sizeof(type) 的长度
指针减n等于指针地址减去 n 个 sizeof(type) 的长度
(3)指针步长的数据类型
| char * | 1 字节 | 1 字节 | 最小单位,单字节 |
| short * | 2 字节 | 2 字节 | 2字节类型指针步长为2 |
| int * | 4 字节(PC)/2字节(STC8) | 4 或 2 字节 | 与平台相关 |
| long * | 4 或 8 字节 | 4 或 8 字节 | 根据系统位数 |
| float * | 4 字节 | 4 字节 | 单精度浮点 |
| double * | 8 字节 | 8 字节 | 双精度浮点 |
| long long * | 8 字节 | 8 字节 | 64位整数 |
| void * | 不确定 | 通常等于平台指针大小 | 不能直接解引用,需要类型转换 |
(4)代码示例
#include <stdio.h>
int main() {
char ch;
char *p1 = &ch;
printf("p1:%p, p1+1: %p\\n", p1, p1 + 1); // 步长为1字节
int a;
int *p2 = &a;
printf("p2:%p, p2+1: %p\\n", p2, p2 + 1); // 步长为4字节
double d;
double *p3 = &d;
printf("p3:%p, p3+1: %p\\n", p3, p3 + 1); // 步长为8字节
return 0;
}
5.空指针
(1)定义:空指针指向“没有任何有效内存地址”的指针,它是安全的,不能用来访问数据
(2)特点:指针有明确的值NULL(一般是0);
不能解引用,否则会出现运行错误
用途:初始化指针、判断指针是否指向有效地址
(3)代码示例;
#include <stdio.h>
int main() {
int *p = NULL;
if (p == NULL) {
printf("p 没有指向有效内存\\n");
}
return 0;
}
输出:
p 没有指向有效内存
6.野指针
(1)定义:野指针指向未知或非法内存地址的指针,它可能随机指向某块内存,访问会导致不可预知的错误
(2)野指针的产生原因:未初始化指针(最常见)
指针指向的内存被释放
指针指向局部变量的地址,但局部变量已超出作用域
(3)代码示例:(代码可以运行,但输出随机值)
#include <stdio.h>
int main() {
int *p; // 指针未初始化
printf("%d\\n", *p); // 解引用未初始化指针,输出垃圾值(随机值)
return 0;
}
7.多级指针
(1)多级指针的定义:普通指针保存的是变量的地址,而多级指针保存的是指针变量的地址
(2)代码示例:
#include <stdio.h>
int main() {
int a = 100;
// 一级指针
int* p1 = &a;
printf("&a=%p\\n", &a);
printf("p1=%p\\n", p1);
printf("&p1=%p\\n", &p1);
// 二级指针,可以存储一级指针变量的地址
int** p2 = &p1;
printf("p2=%p\\n", p2);
printf("&p2=%p\\n", &p2);
// 三级指针,可以存储二级指针变量的地址
int*** p3 = &p2;
printf("p3=%p\\n", p3);
printf("&p3=%p\\n", &p3);
printf("———————\\n");
// 通过一级指针访问100,打印出来
printf("*p1=%d\\n", *p1);
// 通过二级指针访问100,打印出来
printf("**p2=%d\\n", **p2);
// 通过三级指针访问100,打印出来
printf("***p3=%d\\n", ***p3);
return 0;
}
8.指针与常量
(1)常量指针(指向常量的指针)
1.语法格式:两种方法相同
const int *p;
int const *p;
2.含义:p可以改变指向,但是*p不能修改(即指针可变,指向的数据不可变)
3.代码示例:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
const int *p = &a;
p = &b; // √ 可以
//*p = 100; // × 不允许
printf("%d\\n", *p);
return 0;
}
(2)指针常量
1.语法格式;
int * const p = &a;
2.含义:p不能改变指向,*p可以修改(即指针不可变,指向的数据可变)
3.示例代码:
#include <stdio.h>
int main()
{
int a = 10;
int * const p = &a;
*p = 100;
printf("%d\\n", a);
return 0;
}
(3)常量指针常量
1.语法格式:
const int * const p = &a;
2.含义:p不能改,*p不能改(即指针不可变,数据不可变)
3.示例代码:
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
// 定义常量指针常量
const int * const p = &a;
printf("p 指向的数据: %d\\n", *p);
// *p = 100; // ❌ 错误:数据不可改
// p = &b; // ❌ 错误:指针不可改
return 0;
}
(4)总结表格
| int *p | √ | √ |
| const int *p | √ | × |
| int * const p | × | √ |
| const int * const p | × | × |
三、指针和函数
1.函数参数传值
(1)在C语言中,函数调用时采用的是值传递(意思是:调用函数时,会把实参的值复制一份给形参,函数内部修改的是副本,不会影响原来的变量)
(2)代码示例:
// 函数参数传值,函数内部交换2个变量的值,验证函数外部的变量有没有改变
#include <stdio.h>
// 函数定义
void func(int m, int n) {
// 函数内部交换2个变量的值
int temp = m;
m = n;
n = temp;
printf("函数内部 m = %d, n = %d\\n", m, n);
}
int main() {
int a = 10;
int b = 20;
// 调用函数,值传递
func(a, b);
printf("函数外部 a = %d, b = %d\\n", a, b);
return 0;
}
(3)总结版:定义函数时的是形参,调用函数时的是实参。实参把值复制给形参,形参改变一般 不影响实参。
2.函数参数传址(指针传参)
(1)参数传址实际上就是指针传参
(2)代码示例:
#include <stdio.h>
//定义了一个指针类型的函数
void change(int *p)
{
//指针的值为100
*p = 100;
}
int main()
{
//定义了一个整型a
int a = 10;
//调用函数change,把a的地址赋值给p,p的地址和a的地址现在是相同的
change(&a);
//所以a = *p ,a = 100
printf("%d\\n", a);
return 0;
}
(3)总结口诀:传值传的是数据,形参实参两块地;
传址传的是地址,指针找到原数据;
*p就是访问目标,修改之后立即生效。
3.函数指针
(1)定义:函数指针就是指向函数的指针变量
(2)语法格式:
返回值 (*函数指针变量)(形参列表);
(3)函数指针变量的定义,其中返回值、形参列表需要和指向的函数匹配(类型必须相同)
(4)代码示例:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
// 定义函数指针
int (*fp)(int, int);
// 指向add函数
fp = add;
// 通过函数指针调用函数
int result = fp(10, 20);
printf("result = %d\\n", result);
return 0;
}
4.函数指针用作函数参数
(1)函数指针可以作为另一个函数的参数,这样就可以动态决定调用那个函数
(2)优点:可以实现回调机制
可以动态切换代码
避免写重复代码
(3)代码示例:
#include <stdio.h>
// 普通函数
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a – b;
}
// 函数指针作为参数
int calculate(int x, int y, int (*fp)(int, int))
{
return fp(x, y); // 通过函数指针调用函数
}
int main()
{
int a = 10, b = 5;
// 调用calculate,并传入add
printf("a+b = %d\\n", calculate(a, b, add));
// 调用calculate,并传入sub
printf("a-b = %d\\n", calculate(a, b, sub));
return 0;
}
5.回调函数
(1)回调函数 = 通过函数指针传递给另一个函数,然后在合适的时候被调用的函数
(2)回调函数本质上还是普通的函数,它的特殊之处在于它不是自己调用,而是作为参数传给别的函数,由别的函数调用
(3)经典的回调结构:
void execute(void (*fp)())
{
fp();
}
int main()
{
execute(test);
}
(4)更容易理解的图片


