1.指针的基本概念
·指针的内容是C语言语法的主要内容,也是C语言的难点。
·计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的
数据也会放回内存中。
那我们买电脑的时候,电脑上内存是 8GB/16GB/32GB 等,那这些内存空间如
何高效的管理呢?
·其实也是把内存划分为⼀个个的内存单元,每个内存单元的大小取1个字节。
·计算机中常用的单位:
1 bit (比特位)
2 byte (字节)==8bit
3 KB==1024byte
3 MB==1024KB
4 GB==1024MB
5 TB==1024GB
·同时每个内存单元也都有⼀个编号,有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。在C语言中这样的编号称为地址,也称为指针。
·因此我们可以理解:
内存单元的编号==地址==指针
2.指针变量
·理解了内存和地址的关系,我们再回到C语言,在C语言中创建变量和函数调用其实就是向内存申请空间。因此我们可以通过取地址操作符(&),将地址存放在指针变量中,之后还可以通过解引用操作符(*)将指针变量中存放的内容进行修改。
#include<stdio.h>
int main()
{
int num = 100;
int* p = #
*p = 0;
printf("%d", num);
return 0;
}
·输出为0。
·比较特殊的是指针变量的大小和类型无关,只要是指针变量,在同⼀个平台下,大小都是⼀样的。但指针变量的指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。比如: char* 的指针解引用就只能访问⼀个字节,而 int* 的指针的解引用就能访问四个字节。
3.指针的运算
·指针+-整数==地址+-整数==地址
·指针+-指针==地址+-地址==整数
·这部分的内容比较好理解,不必进行过多阐述。
4.const修饰指针
·这里以int*类型指针为例,
const int* p //表示指针所指向的内容不被修改,指向对象可以被修改(最常用,保护数据)
int* const p //表示指针所指向的内容可以被修改,指向对象不可以被修改(固定指针指向)
const int* const p//表示指针所指向的内容不可以被修改,指向对象不可以被修改(双重保护)
5.传值调用和传址调用
·学习指针的目的是使用指针解决问题,那什么问题,非指针不可呢?
·例如:写⼀个函数,交换两个整型变量的值。
void swap(int x,int y)
{
int temp=x;
x=y;
y=temp;
}
·但当我们调用函数时却发现并没有发生交换。
·swap函数在使用的时候,是把变量本声直接传递给了函数,这种调用叫传值调用。实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。因此无法实现交换。
·将实参的地址传给函数,函数通过对实参地址的间接操作就可以实现交换。
void swap(int* x,int* y)
{
int temp=(*x);
(*x)=(*y);
(*y)=temp;
}
6.assert断言
·assert 断言是 C 语言里一个用于调试阶段检查程序假设条件是否成立的宏,核心作用是在开发时快速发现代码中的逻辑错误,避免问题带到生产环境。
assert(表达式);
·如果表达式为真,程序继续执行;如果为假,会立刻终止程序,并在控制台输出错误位置(文件名、行号、表达式内容),方便定位问题。
7.数组名的理解和指针访问数组
·数组名的本质是数组首元素的地址常量,这是理解数组名的核心,其所有特性和使用规则均源于此。
·数组名是指向数组第一个元素的地址常量—— 它存储的是数组首元素的内存地址,且值不可修改(不能做自增++、自减–、赋值=等操作,这是和普通指针的关键区别)。
·多数情况下数组名等价于首元素地址,但在sizeof和&取地址操作符2种特殊情况下,数组名会表示整个数组,而非单个元素的地址。
·除上述 2 个例外,所有使用数组名的场景,编译器都会自动将数组名隐式转换为 “首元素的地址指针”,这也是数组能通过指针方式访问的原因。
·指针访问数组是 C 语言的核心特性,底层本质是利用数组名的隐式转换(首元素地址)+ 指针偏移 + 解引用,和数组下标法arr[i]完全等价,编译器最终会将两种写法编译为相同的机器码。
·指针访问数组的核心公式:
arr[i]==*(arr+i)
8.一维数组传参的本质和二级指针
·有了前面对数组名的理解,不难发现,C 语言中一维数组传参的本质是传递数组首元素的地址(即传递一个指向数组元素类型的指针),数组本身并不会被整体拷贝传递。
·二级指针的本质是存储「一级指针变量内存地址」的指针,简单说就是 “指针的指针”,其核心作用是间接操作一级指针本身(如修改一级指针的指向、为一级指针动态分配内存),是解决函数内修改外部指针、处理指针数组的关键语法。
#include<stdio.h>
int main()
{
int num = 0;
int* p = #
int** pp = #
return 0;
}
9.指针数组
·指针数组是存储一级指针的数组,数组的每个元素都是同类型的指针变量,核心作用是批量管理多个指针(如多个字符串、多个结构体地址),是处理多指针场景的高效方式。
·结合一维数组传参的本质,指针数组的数组名在非sizeof/&场景下,会被编译器隐式转换为二级指针。
·用指针数组模拟二维数组是 C 语言的经典用法,核心原理是让指针数组的每个一级指针元素,指向一个独立的一维数组,通过 “指针数组 + 多个一维数组” 的组合实现二维数组的访问效果。这种方式相比原生二维数组更灵活。
#include<stdio.h>
int main()
{
int arr1[3] = { 1,2,3 };
int arr2[3] = { 4,5,6 };
int arr3[3] = { 7,8,9 };
int* arr[3] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", arr[i][j]);//arr[i][j]==*(*(arr+i)+j)
}
printf("\\n");
}
return 0;
}
·注意这并非真正的二维数组,因为内存并不连续。
10.数组指针变量
·数组指针变量(简称数组指针)是 C 语言中专门指向整个一维数组的指针变量,其核心作用是存储整个数组的内存地址,而非单个数组元素的地址,是批量操作一维数组、处理二维数组的关键语法。
// 元素类型 (*数组指针变量名)[整个数组的长度];
类型 (*pArr)[N];
//这边的()涉及到操作符的优先级
·
·数组指针的本质是指针,但并非普通指针(如一级指针)—— 它是 **“数组类型的指针”,其指向的对象是整个一维数组 **,而非单个元素。
·数组指针只能指向 “长度、元素类型完全匹配”的一维数组,初始化时通过&数组名获取整个数组的地址 **(而非首元素地址)赋值给它:
int arr[]={1,2,3,4,5};
int (*pArr)[5]=&arr;
数组指针指向整个数组,需先通过一次解引用*pArr拿到整个数组的数组名(等价于原数组名arr),再通过下标法[] 访问具体元素:
(*pArr)[i]==arr[i] //*pArr==arr
11.二维数组传参的本质
·有了数组指针的理解,我们就能够探讨⼀下⼆维数组传参的本质了。
·但我们需要将一个二维数组传给函数的时候,我们是这样实现的:
类型 函数名(类型 arr[][N],…,….)
{
……….
}
·这里实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
·我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个⼀维数组。那么⼆维数组的首元素就是第⼀行,是个⼀维数组。

·所以,根据数组名是数组首元素的地址这个规则,⼆维数组的数组名表示的就是第⼀行的地址,是⼀ 维数组的地址。因此我们可以将二维数组传参改写为:
类型 函数名(类型 (*pArr)[N],..,…)
{
……
}
·二维数组传参的核心本质是:传递指向二维数组首行的数组指针(即指向二维数组中 “每一行一维数组” 的数组指针),并非传递整个二维数组,也非二级指针。其底层源于二维数组的本质是 “一维数组的数组”,结合数组名的隐式转换规则,最终决定了传参的类型和语法要求。
12.函数指针变量
·根据前面学习整型指针,数组指针的时候,我们来类比关系,我们不难得出结论:函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。
·C 语言中每个函数在内存中都有唯一的入口地址(函数执行的起始内存地址),函数名在表达式中会直接表示这个入口地址;而函数指针变量就是用于存储该地址的变量,本质是指向函数的指针,通过它可以间接调用对应函数。
// 无参无返回值函数的指针 返回值类型 (*函数指针变量名)(参数类型列表);
// 示例:指向int add(int, int)的函数指针
int (*pf)(int, int);
·调用时通过(*pf)(参数)或简化的pf(参数)实现间接调用:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int (*pf)(int, int) = add; // 等价于 int (*pf)(int, int) = &add;(&可省略)
// 方式1:标准调用(解引用+传参,符合指针调用逻辑)
int res1 = (*pf)(2, 3);
// 方式2:简化调用(编译器自动解析,推荐,更简洁)
int res2 = pf(2, 3);
// 直接调用函数(对比参考)
int res3 = add(2, 3);
printf("(*pf)(2,3)=%d, pf(2,3)=%d, add(2,3)=%d\\n", res1, res2, res3);
return 0;
}
·输出结果如下:
(*pf)(2,3)=5,pf(2,3)=5,add(2,3)=5
13.typedef
·typedef 是 C 语言的类型重定义 / 类型别名关键字,核心作用是为已存在的数据类型创建一个自定义的新别名,并非定义新类型。其核心价值是简化复杂类型的书写、提升代码可读性与可维护性,是开发中处理指针、结构体、数组等复杂类型的常用技巧。
// 语法:typedef 原数据类型 自定义别名;
typedef 原类型 新别名;
//定义内置类型
//以int改为INT为例
typedef int INT;
·但是对于数组指针和函数指针稍微有点区别,比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef
int
(*
parr_t
)[5];
//
新的类型名必须在
*
的右边
·函数指针类型的重命名也是⼀样的,比如,将
void(*)(int)
类型重命名为
pf_t
,就可以这样写:
typedef
void
(*
pf_t
)(
int
);
//
新的类型名必须在
*
的右边
14.回调函数
·回调函数是 C 语言中基于函数指针实现的高级编程技巧,核心定义是:一个通过函数指针传递给另一个函数(称为「主调函数」)的函数,主调函数在执行过程中,会通过该函数指针间接调用这个传入的函数。简单来说,就是把函数作为参数传递给另一个函数并被其调用。
·回调函数的实现完全依赖函数指针,本质是「函数地址的传递与间接调用」,核心流程分 3 步,层层递进且逻辑清晰:
·步骤 1:定义回调函数(待传递、被间接调用的函数)
·根据业务需求定义函数,需提前约定返回值类型、参数类型及个数(即函数原型),这是后续函数指针匹配的基础。
·步骤 2:定义主调函数(接收函数指针、调用回调函数的函数)
·主调函数的参数中包含函数指针,该函数指针的原型必须与回调函数严格一致;主调函数内部通过这个函数指针,间接调用传入的回调函数。
·步骤 3:调用主调函数,将回调函数的地址作为参数传入
函数名本身就是函数的入口地址,直接将回调函数名传递给主调函数(无需加&,加了也合法),主调函数接收后即可触发回调。
#include <stdio.h>
// 步骤1:定义回调函数(待传递的函数)
void myCallback() {
printf("我是回调函数,被主调函数调用了!\\n");
}
// 步骤2:定义主调函数(参数为函数指针,匹配回调函数原型)
// void (*fp)() 是函数指针,指向“无参、无返回值”的函数
void callerFunc(void (*fp)()) {
printf("我是主调函数,准备调用回调函数…\\n");
fp(); // 核心:通过函数指针间接调用回调函数(回调触发)
}
// 步骤3:主程序调用,传递回调函数地址
int main() {
callerFunc(myCallback); // 传入回调函数名(即函数地址)
return 0;
}
我是主调函数,准备调用回调函数…
我是回调函数,被主调函数调用了!
15.qsort
·qsort是 C 标准库<stdlib.h>中的通用快速排序函数,支持对任意类型数据(整型、浮点型、结构体、字符串等)进行排序,核心优势是类型无关、通用性强,其底层基于快速排序算法实现,对外通过回调函数解耦排序逻辑与数据类型判断,是回调函数的经典工业级应用。
void qsort(
void* base, // 待排序数组的**首地址(任意类型数组首元素地址,void*兼容所有类型)
size_t nitems, // 数组中元素的个数(size_t是无符号整型,通常用sizeof计算)
size_t size, // 数组中单个元素的字节大小(如int占4字节)
int (*compare)(const void*, const void*) // 比较回调函数指针(核心:自定义数据比较规则) );
·qsort的通用性完全依赖比较回调函数,用户需根据待排序数据类型编写符合规则的回调函数,qsort内部会频繁调用该函数判断元素大小,实现排序。
·回调函数强制原型
int (*compar)(const void* a, const void* b);
·参数:const void* a、const void* b → 指向待比较的两个元素,const保证不修改原数据,void*兼容任意类型;
·返回值:int → 严格通过返回值表示两个元素的大小关系,qsort根据返回值决定元素位置,规则如下:
< 0 a小于b a排在b前面
= 0 a等于b a和b位置不变
> 0 a大于b a排在b后面
#include <stdio.h>
#include <stdlib.h>
// 比较回调:整型升序(a < b 返回<0,a > b 返回>0)
int cmp_int_asc(const void* a, const void* b) {
// 强转+解引用,返回差值
return *(const int*)a – *(const int*)b;
}
// 比较回调:整型降序(反转a和b的差值)
int cmp_int_desc(const void* a, const void* b) {
return *(const int*)b – *(const int*)a;
}
int main() {
int arr[] = {5, 2, 9, 1, 3, 7};
int n = sizeof(arr) / sizeof(arr[0]); // 计算元素个数
// 升序排序
qsort(arr, n, sizeof(int), cmp_int_asc);
printf("整型升序:");
for (int i = 0; i < n; i++) printf("%d ", arr[i]); // 1 2 3 5 7 9
printf("\\n");
// 降序排序
qsort(arr, n, sizeof(int), cmp_int_desc);
printf("整型降序:");
for (int i = 0; i < n; i++) printf("%d ", arr[i]); // 9 7 5 3 2 1
printf("\\n");
return 0;
}
1 2 3 5 7 9
9 7 5 3 2 1
网硕互联帮助中心





评论前必须登录!
注册