一、一些最基础的概念
1、指针与数组的核心——地址
地址是理解指针与数组的基石,后续所有概念都建立在这一基础上。每个字节存储单元都对应着唯一且不重复的内存地址;变量、常量或数组元素在内存中也必然占据特定地址。内存由连续字节组成,每个字节按序编号,地址即其唯一标识符。
当我们声明一个变量时,编译器为其分配内存空间,并返回该空间的起始地址。这个地址可由指针变量保存和操作。
回顾几个关键基础概念:
- 计算机存储数据的基本单位:字节(Byte)
- 计算机表示数据的基本单位:位(bit)
- 表示数据的方式:二进制
- 整型数据占4个字节
- 1 Byte = 8 bit
那么,为什么需要地址?为什么每个字节都要有地址?
可类比一栋楼:每个房间是一个字节,门牌号就是地址。计算机存数据时,不是说“把数据放进去”,而是明确指定“放到 0x00000001 这个地址空间里”。

如何获取变量地址?使用取址运算符 &,例如:
int x;
printf("%p", &x);
会打印出变量 x 在内存中的地址。而通过解引用操作 *,可用指针访问其所指向地址的值:
printf("%d", *p); // 输出指针 p 所指向的整数值
理解地址对高级编程至关重要——动态内存分配、函数参数传递、数据结构实现等均依赖对地址的精准控制。但同时也需警惕空指针引用、野指针、内存泄漏等风险。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int x = 123;
/* int *y = 456;
* 初始化指针 y 时将一个整数(456)强制转换为了指针类型。
* 这是因为你试图直接将一个整数值赋给指针变量 y,而没有为它分配内存地址
*/
int *y; // 初始化一个指针变量
y = &x; // 将变量 x 的地址赋给变量 y
printf("&x = %p\n", (void *)&x);
printf("&x = %p\n", &x); // %p: 打印变量 x 在内存中的地址
printf("&y = %p\n", y);
/*
printf("*y = %p\n", *y);
这里会警告,是因为在 printf() 函数中,格式说明符 %p 需要一个指向内存地址的指针作为参数,
而这里传递了一个整型变量。
*/
printf("y = %p\n", (void *)y); // 打印出指针变量 y 的值(也就是变量 x 的地址)
return 0;
}

以上展示了获取并输出变量地址的几种标准方式。
2、什么是十六进制数字
我们熟悉十进制(逢十进一),计算机底层基于二进制(逢二进一)。十六进制(逢十六进一,0~9、A~F)则成为人机交互的理想桥梁——它能无损映射二进制,又比纯二进制简洁得多。
十六进制表示法的好处:
- 简化二进制转换:每4位二进制精确对应1位十六进制(如
0000=0,1111=F,1101=D),远优于十进制需8位才能近似表达。
- 编程便捷性:广泛用于内存地址、颜色代码(如
#FF0000)、文件格式等。C语言中 0x 前缀即表示十六进制字面量。
- 数据存储与传输:MAC地址、调试信息常用十六进制,更易读、易校验。
- 硬件与底层开发:汇编指令、寄存器值、固件镜像普遍采用十六进制表示。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 0x6a;
printf("a(10) = %d\n", a);
printf("a(16) = %x\n", a); // 注意:%x 表示输出小写十六进制
printf("a(16) = %X\n", a); // 注意:%X 是大写,输出也为大写
int int_max = 0x7fffffff; // 最大值:符号位0 + 后31位全1 → 0x7 + 31个 f
int int_min = 0x80000000; // 最小值:符号位1 + 后31位全0 → 0x8 + 31个 0
printf("int_max = %d\n", int_max); // 输出整型最大值
printf("int_min = %d\n", int_min);
printf("input hex:");
scanf("%x", &a); // 输入十六进制,终端输入无需加 0x
printf("a(10) = %d\n", a);
printf("a(16) = %x\n", a);
printf("a(16) = %X\n", a);
return 0;
}

整型最大值原理:符号位为0(正数),后31位全1 → 0x7fffffff;最小值:符号位为1(负数),后31位全0 → 0x80000000。
3、地址到底是一个几位的二进制数据
地址的位宽取决于系统架构,而非数据类型本身。以下代码验证了这一点:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a;
double b;
char c;
float d;
printf("sizeof(int &) = %u\n", sizeof(&a));
printf("sizeof(double &) = %u\n", sizeof(&b));
printf("sizeof(char &) = %u\n", sizeof(&c));
printf("sizeof(float &) = %u\n", sizeof(&d));
return 0;
}

无论 int、double、char 或 float,其地址大小(sizeof(&x))在当前系统下均为 4 字节 —— 这正是32位地址总线的体现。32位地址可寻址 $2^{32} = 4\text{GB}$ 内存;64位系统则可达 $2^{64}$ 字节,远超当前物理内存上限。
二、数组的定义与使用
1、初识数组
数组是一组相同类型元素的有序集合,通过下标访问。C语言中,数组名本质上是首元素地址的别名。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int i;
int a[i]; // C99+ 支持变长数组(VLA),但此处 i 未初始化,属未定义行为(UB)
for(i = 0; i < 10; i++)
{
a[i] = 2 * i;
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
![Linux终端执行 array.c:输出 a[0]=0 到 a[9]=18](https://static1.yunpan.plus/attachment/1adc02d0e32801cd.jpeg)
什么是可变长数组(VLA)?
- 先读入一个数字
n
- 动态声明长度为
2*n 的数组(如 int a[2*n];)
- 用
for 循环遍历
#include <stdio.h>
int arr1()
{
int i;
int a[i];
for(i = 0; i < 10; i++)
{
a[i] = 2 * i;
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
int arr2()
{
int i;
int n;
printf("input:");
scanf("%d", &n);
int a[2*n];
for(i = 0; i < 2*n; i++)
{
a[i] = 3 * i;
printf("a[%d] = %d\n", i, a[i]);
}
}
int main(int argc, const char *argv[])
{
arr2();
return 0;
}
更安全、更常用的静态数组定义方式:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int i;
int x;
int a[10] = {1, 2, 3, 4, 5}; // 后5个自动补0
int b[10] = {0}; // 全部初始化为0
int c[10] = {}; // 同上,C99+ 支持
int d[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 编译器推导长度为10
printf("sizeof(a)/sizeof(int) = %u\n", sizeof(a)/sizeof(int));
printf("sizeof(b)/sizeof(int) = %u\n", sizeof(b)/sizeof(int));
printf("sizeof(c)/sizeof(int) = %u\n", sizeof(c)/sizeof(int));
printf("sizeof(d)/sizeof(int) = %u\n", sizeof(d)/sizeof(int));
printf("\n");
size_t size = sizeof(a)/sizeof(int);
printf("sizeof(a)/sizeof(int) = %u\n", size);
printf("size = %zu\n", size);
printf("a = %p\n", a); // 数组名即首地址
for(i = 0; i < size; i++)
{
printf("a[%d] = %p\n", i, &a[i]); // 打印每个元素地址
}
return 0;
}
size_t 是C语言中专用于表示大小、长度或容量的无符号整数类型,定义于 <stddef.h>。它确保足够容纳任何对象大小,避免溢出,是 sizeof 运算符的返回类型及 malloc、strlen 等函数的标准参数/返回类型。
#include <stddef.h>
#include <stdio.h>
#include <string.h>
size_t length = strlen("Hello, World!");
printf("The length of the string is: %zu\n", length);
数组的掌握是指针学习的必经之路,务必多练、多敲、多调试。
2、关于数组的两个算法
2.1 素数筛算法
素数筛(埃氏筛)是一种高效生成指定范围内所有素数的算法,核心思想是“用已知素数筛去其倍数”。
- 时间复杂度:$O(N \log \log N)$
- 空间复杂度:$O(N)$
- 关键逻辑:若
i 是素数,则 2*i, 3*i, 4*i... 均为合数,标记即可。
#include <stdio.h>
int prime[1000] = {0};
// 定义一个数组,求1000以内的所有素数,并初始化为0
void init_prime(int n)
{
int i,j;
prime[0] = prime[1] = 1; // 0和1非素数
// 外层循环只需到 sqrt(n),因合数必有 ≤√n 的因子
for(i = 2; i*i <= n; i++)
{
if(prime[i]) continue; // i 已被标记为合数,跳过
printf("%d is prime:", i);
for(j = i*i; j <= n; j += i) // 从 i*i 开始,避免重复标记
{
prime[j] = 1;
printf(" %d", j);
}
printf("\n");
}
}
int main(int argc, const char *argv[])
{
init_prime(100);
int x;
while(~scanf("%d", &x))
{
printf("prime[%d] = %d\n", x, prime[x]);
}
return 0;
}
2.2 二分查找算法
二分查找适用于有序数组,通过每次比较中间元素,将搜索范围缩小一半,效率远高于线性查找。
随机数补充知识
C语言中 rand() 生成伪随机数,但需用 srand(time(0)) 设置种子,否则每次运行结果相同。

二分查找思想(难点)
假设在 [min, max] 区间查找 x:
- 计算中点
mid = (left + right) / 2
- 若
arr[mid] == x → 找到
- 若
arr[mid] < x → x 在右半段 → left = mid + 1
- 若
arr[mid] > x → x 在左半段 → right = mid - 1
- 重复直至
left > right
![二分查找示意图:有序数组 [1,2,3,4,5,6,7,8,9],绿色箭头标出 min/mid/max 指针](https://static1.yunpan.plus/attachment/f55b7cfb68e34c35.webp)
#include <stdlib.h>
#include <time.h>
int binary_search(int arr[], int l, int r, int target)
{
while(l <= r)
{
int mid = (l + r) / 2;
if(arr[mid] == target)
{
return mid;
}
else if(arr[mid] < target)
{
l = mid + 1;
}
else
{
r = mid - 1;
}
}
return -1;
}
int main(int argc, const char *argv[])
{
int arr[] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
int n = sizeof(arr)/sizeof(arr[0]);
int target = 16;
int result = binary_search(arr, 0, n-1, target);
if(result != -1)
{
printf("目标元素%d在数组中的索引为%d\n", target, result);
}
else
{
printf("目标元素%d不在数组中\n", target);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int i;
int main() {
srand(time(0));
int arr[10] = {0};
for(i = 1; i < 10; i++) {
arr[i] = arr[i - 1] + (rand() % 10);
}
int len = 0;
for(i = 0; i < 10; i++) {
len += printf("%4d", i);
}
printf("\n");
for(i = 0; i < len; i++) printf("-");
printf("\n");
for(i = 0; i < 10; i++) {
printf("%4d", arr[i]);
}
printf("\n");
int x, i;
while(scanf("%d", &x) != EOF) {
int cnt1 = 0, cnt2 = 0, flag1 = 0, flag2 = 0;
for(i = 0; i < 10; i++) {
cnt1 += 1;
if(arr[i] != x) continue;
flag1 = 1;
break;
}
int l = 0, r = 9, mid;
while(l <= r) {
cnt2 += 1;
mid = (l + r) >> 1;
if(arr[mid] == x) {
printf("(%d) arr[%d] = %d, find %d\n", cnt2, mid, arr[mid], x);
flag2 = 1;
break;
}
if(arr[mid] > x) {
printf("(%d) arr[%d] = %d > %d, change [%d, %d] to [%d, %d]\n",
cnt2, mid, arr[mid], x, l, r, l, mid - 1);
r = mid - 1;
} else {
printf("(%d) arr[%d] = %d < %d, change [%d, %d] to [%d, %d]\n",
cnt2, mid, arr[mid], x, l, r, mid + 1, r);
l = mid + 1;
}
}
printf("flag1 = %d, cnt1 = %d\n", flag1, cnt1);
printf("flag2 = %d, cnt2 = %d\n", flag2, cnt2);
}
return 0;
}

三、多维数组的定义与使用
多维数组通过嵌套方括号 [][] 定义,二维数组最常见。其本质是“数组的数组”。
![二维数组内存布局示意图:3行4列,标注 b[0][0]、b[1][1]、b[2][0],强调 b = &b[0][0]](https://static1.yunpan.plus/attachment/80ae571bc6f7b853.jpeg)
#include <stdio.h>
int i, j;
int main(int argc, const char *argv[])
{
int b[3][4], cnt = 1;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 4; j++)
{
b[i][j] = cnt;
cnt += 1;
printf("%4d", b[i][j]);
}
printf("\n");
}
return 0;
}

四、字符数组、字符串数组及操作
C语言中字符串本质是空终止字节串(Null-terminated byte string),即以 \0 结尾的 char 数组。
内存布局示例 "hello":
h e l l o \0
标准库提供丰富字符串处理函数(定义于 <string.h>):
| 函数 |
说明 |
strlen(str) |
计算字符串长度(遇 \0 停止) |
strcmp(str1, str2) |
字符串比较 |
strcpy(dest, src) |
字符串拷贝 |
strncmp(str1, str2, n) |
安全比较(最多 n 字符) |
strncpy(str1, str2, n) |
安全拷贝 |
memcpy(str1, str2, n) |
内存块拷贝(无 \0 限制) |
memcmp(str1, str2, n) |
内存块比较 |
memset(str1, c, n) |
内存块填充 |
// 定义字符数组
char str[size];
// 初始化字符数组
char str[] = "hello world"; // 长度为12(11字符+1个\0)
char str[size] = {'h','e','l','l','o'};
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, const char *argv[])
{
char str1[10] = "abc";
printf("str1 = %s\n", str1);
strcpy(str1, "def"); // 正确:用 strcpy 拷贝
printf("str1 = %s\n", str1);
char str2[] = "hello\0 world";
printf("strlen(str2) = %u\n", strlen(str2)); // 输出5(遇\0停止)
printf("sizeof(str2) = %u\n", sizeof(str2)); // 输出13(整个数组大小)
printf("str2 = %s\n", str2); // 输出"hello"
str2[5] = 'A'; // 修改\0为'A'
printf("str2 = %s\n", str2); // 输出"helloA world"
char str3[] = "abcdef", str4[] = "abc";
printf("strcmp(str3, str4) = %d\n", strcmp(str3, str4)); // 输出正数(str3 > str4)
printf("str3[3] = %d\n", str3[3]); // 输出'd'的ASCII码100
int i;
int arr[10];
srand(time(0));
for(i = 0; i < 10; i++) {
arr[i] = rand() % 100;
}
for(i = 0; i < 10; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
// 将arr全部置0
memset(arr, 0, sizeof(arr));
for(i = 0; i < 10; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
五、数组的存储方式
1)行序优先(Row-major order)
C语言默认按行优先存储多维数组:先存第0行所有列,再存第1行...
![行序优先示意图:标注“行序优先”,计算 b[1][2] 地址 = 63 + (1*4+2)*4 = 87](https://static1.yunpan.plus/attachment/9ff2be0367e7ca85.jpeg)
2)列序优先(Column-major order)
Fortran等语言采用列优先:先存第0列所有行,再存第1列...
![列序优先示意图:计算 b[1][2] 地址 = 63 + (2*3+1)*4 = 91](https://static1.yunpan.plus/attachment/772c005759bbeb78.jpeg)
验证代码:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a[10][10];
printf("&a[0][0] = %p\n", &a[0][0]);
printf("&a[0][1] = %p\n", &a[0][1]);
printf("&a[1][0] = %p\n", &a[1][0]);
return 0;
}
![Linux终端执行 row_col_array.c:输出 &a[0][0]、&a[0][1]、&a[1][0] 地址](https://static1.yunpan.plus/attachment/e801d886ddb5e6a3.jpeg)
输出证实:&a[0][1] - &a[0][0] = 4(1个int),&a[1][0] - &a[0][0] = 40(10个int),符合行序优先。
六、初识指针
1、指针变量也是变量
指针变量与其他变量一样,有名字、占内存、可赋值、可传参。区别在于:它存储的是地址,而非具体数值。
// 定义一个存储整型地址的指针变量(不赋值)
int *ptr;
// 定义一个存储双精度浮点型的指针变量(赋值)
double *ptr1;
double a = 88.88;
ptr1 = &a;
// 输出指针指向的所存储的字符型的地址
char *ptr2;
char b = 'a';
ptr2 = &b;
printf("ptr2 = %p\n", ptr2);
printf("&b = %p\n", &b);
// 输出指针指向的所存储的字符型的地址的值
printf("ptr2 = %c\n", *ptr2);
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 10;
int *p = &a;
printf("a = %p\na = %d\n", p, a);
printf("sizeof(*p) = %d\n", sizeof(*p));
printf("sizeof(a) = %d\n", sizeof(a));
return 0;
}

2、函数传递指针变量的场景和用途
2.1 用指针去定义传入参数
当需在函数内修改实参值时,传入其地址。

#include <stdio.h>
void add_once(int *p)
{
*p += 1; // 对整型指针取值后再加1
return ;
}
int main(int argc, const char *argv[])
{
int a = 123;
printf("a = %d\n", a);
add_once(&a); // 传入地址
printf("a = %d\n", a);
return 0;
}

2.2 用指针去定义传出参数
当函数需返回多个值,或主调函数需接收处理结果时,用指针作“传出参数”。
void f(int n, int *sum_addr)
{
*sum_addr = (1 + n) * n / 2;
return ;
}
int main(int argc, const char *argv[])
{
int a = 123;
printf("a = %d\n", a);
add_once(&a);
printf("a = %d\n", a);
int n = 10, sum;
f(n, &sum); // sum 是传出参数
printf("sum = %d\n", sum);
return 0;
}
2.3 用指针去接收数组参数
数组名退化为指针,函数参数可直接声明为对应类型的指针。
void output(int *p, int n)
{
int i;
for(i = 0; i < n; i++)
{
printf("p[%d] = %d\n", i, p[i]);
// p[i] 等价于 *(p+i),[] 是解引用运算符的语法糖
}
return ;
}
int main(int argc, const char *argv[])
{
int arr[10] = {1,3,5,7,9,2,4,6,8,0};
output(arr, 10); // arr 自动转为 &arr[0]
return 0;
}
2.4 交换指针变量(练习题)
注意:swap(&a, &a) 会导致未定义行为(异或交换失效),应使用临时变量。

#include <stdio.h>
int swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int main(int argc, const char *argv[])
{
int a, b;
scanf("%d%d", &a, &b);
printf("a = %d\nb = %d\n", a, b);
swap(&a, &b);
printf("a = %d\nb = %d\n", a, b);
return 0;
}

七、深入指针
1、地址的操作与取值规则
1.1 深入理解什么是“p+1”
p+1 不是地址数值加1,而是按指针类型偏移一个单位:int *p 加1跳4字节,double *p 加1跳8字节。

#include <stdio.h>
int main(int argc, const char *argv[])
{
int a, *p1 = &a;
double b, *p2 = &b;
char c, *p3 = &c;
printf("p1 = %p\n", p1);
printf("p1+1 = %p\n", p1+1);
printf("p1+2 = %p\n", p1+2);
printf("p1+3 = %p\n", p1+3);
printf("p2 = %p\n", p2);
printf("p2+1 = %p\n", p2+1);
printf("p2+2 = %p\n", p2+2);
printf("p2+3 = %p\n", p2+3);
printf("p3 = %p\n", p3);
printf("p3+1 = %p\n", p3+1);
printf("p3+2 = %p\n", p3+2);
printf("p3+3 = %p\n", p3+3);
return 0;
}
1.2 指针和数组的关系
数组名可作指针使用,arr[i] 与 *(arr+i) 完全等价。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int arr[5] = {1, 2, 3, 4, 5};
int *p = &arr[0]; // p 指向首元素
int i;
for(i = 0; i < 4; i++)
{
printf("p + %d = %p\n", i, p+i);
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
![Linux终端执行 point_arr.c:p+0=&arr[0], p+1=&arr[1] 等,完全等价](https://static1.yunpan.plus/attachment/82a40c35e7588d72.jpeg)
1.3 特殊语法:int (*p)[10] vs int *p[10]
int (*p)[10]:数组指针,p 是指针,指向一个含10个 int 的数组。
int *p[10]:指针数组,p 是数组,含10个 int* 元素。
![图解:int(*p)[10] 指向10个整型的数组,p+1 跳过整个数组(40字节)](https://static1.yunpan.plus/attachment/fd8b4f8d765545e5.jpeg)
#include <stdio.h>
int main(int argc, const char *argv[])
{
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[0];
int (*p2)[10] = 0x0; // 指向10元素int数组的指针
for(int i = 0; i < 4; i++)
{
printf("p + %d = %p\n", i, p1+i);
printf("&arr[%d] = %p\n", i, &arr[i]);
}
printf("p2 = %p\n", p2);
printf("p2 + 1 = %p\n", p2 + 1);
printf("p2 + 2 = %p\n", p2 + 2);
printf("p2 + 3 = %p\n", p2 + 3);
return 0;
}

1.4 int *(*p3[10])[20] 解析
从变量名 p3 开始读:
p3[10] → p3 是含10个元素的数组
*p3[10] → 数组每个元素是 *(指针)
(*p3[10])[20] → 每个指针指向一个含20个元素的数组
int *(*p3[10])[20] → 每个指针指向含20个 int 的数组
即:指针数组,每个指针指向一个 int[20]。
![指针数组示意图:p3[10],每个p指向 int*[20]](https://static1.yunpan.plus/attachment/ff07e236348f2019.jpeg)
2、深入理解 *p 操作
*p 取值的字节数仅由指针类型决定,与内存中实际存储的数据无关。
int *p → 取4字节
char *p → 取1字节
double *p → 取8字节
即使内存中存的是 int n = 0x61626364(4字节整数),用 char *p = (char*)&n 取值,*(p+0) 到 *(p+3) 分别得到 'a'、'b'、'c'、'd' —— 这正是小端序(Little-endian) 的体现:低位字节存低地址。

#include <stdio.h>
int main(int argc, const char *argv[])
{
int n = 0x61626364; // 十六进制初始化,4字节
char *p = (char *)&n; // 强制转换:用char指针读取
printf("*(p + 0) = %c\n", *(p + 0));
printf("*(p + 1) = %c\n", *(p + 1));
printf("*(p + 2) = %c\n", *(p + 2));
printf("*(p + 3) = %c\n", *(p + 3));
return 0;
}

3、指针的几种等价形式
| 形式 |
等价关系 |
说明 |
p |
&arr[0] |
指针变量即首地址 |
p + 1 |
&arr[1] |
指针算术:加1跳过1个元素 |
*p |
p[0] |
解引用即取首元素 |
p[i] |
*(p+i) |
方括号是 *(p+i) 的语法糖 |
&p[i] |
p+i |
取第i个元素地址 |
#include <stdio.h>
int main(int argc, const char *argv[])
{
int arr[] = {0,1,2,3,4,5,6};
int *p = arr;
int i;
for(i = 0; i < 3; i++)
{
printf("%d\n", (i + 5)[&p[1] - 2]);
// 推导:&p[1] → p+1;p+1-2 → p-1;(i+5)[p-1] → *(i+5+p-1) → *(p+i+4) → p[i+4]
// i=0→p[4]=4;i=1→p[5]=5;i=2→p[6]=6
}
return 0;
}
八、特殊的指针
1、数组指针与函数指针
- 数组指针:指向数组的指针,如
int (*p)[10]
- 函数指针:指向函数入口地址的指针,声明需匹配返回值与参数列表
int arr1[10];
int arr2[10][10];
int *p1 = arr1; // 指向int的指针
int (*p2)[10] = arr2; // 指向int[10]的指针(必须加括号!)
int (*add)(int, int); // 指向 int func(int,int) 的函数指针
#include <stdio.h>
void test1() { printf("function_test1\n"); }
void test2() { printf("function_test2\n"); }
void test3() { printf("function_test3\n"); }
void (*p)();
int main(int argc, const char *argv[])
{
p = test1; p();
p = test2; p();
p = test3; p();
return 0;
}

函数指针数组:随机调用
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void test1() { printf("function_test1\n"); }
void test2() { printf("function_test2\n"); }
void test3() { printf("function_test3\n"); }
int main()
{
srand(time(0));
void (*arr[3])() = {test1, test2, test3}; // 函数指针数组
for(int i = 0; i < 10; i++)
{
arr[rand() % 3](); // 随机调用
}
return 0;
}
![语法记忆图:1. 变量名前加*→指针;2. 变量名后加[]→数组](https://static1.yunpan.plus/attachment/07db6a2656fd487e.jpeg)
指针数组:本质是数组,元素为指针;
数组指针:本质是指针,指向数组;
函数指针数组:本质是数组,元素为函数指针。
九、内存管理方法
指针是内存操作的抽象接口。C语言提供标准库函数进行动态内存管理:
| 函数 |
作用 |
malloc(size) |
申请 size 字节未初始化内存 |
calloc(n, size) |
申请 n*size 字节,自动初始化为0 |
realloc(ptr, new_size) |
调整已分配内存块大小 |
free(ptr) |
释放内存(必须配对 malloc/calloc/realloc) |
memset(ptr, c, n) |
将 n 字节内存设为字符 c |
memcpy(dst, src, n) |
内存拷贝(要求 dst/src 不重叠) |
memmove(dst, src, n) |
安全拷贝(支持重叠区域) |
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int *arr = (int *)malloc(sizeof(int) * 20); // 申请20个int空间(80字节)
// malloc 返回 void*,需强制转换为所需指针类型
int i;
for(i = 0; i < 10; i++)
{
arr[i] = rand() % 100;
}
for(i = 0; i < 10; i++)
{
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
![Linux终端执行 1.memory.c:输出 arr[0]=83 到 arr[9]=21](https://static1.yunpan.plus/attachment/8bd1aaac876be0cf.jpeg)
动态分配的内存位于堆区(heap),与函数内局部变量所在的栈区(stack) 独立。使用完毕必须 free(),否则造成内存泄漏。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, const char *argv[])
{
int *arr1 = (int *)malloc(sizeof(int) * 10);
int *arr2 = (int *)calloc(10, sizeof(int)); // calloc 初始化为0
// ... 使用 arr1, arr2 ...
free(arr1);
free(arr2);
char s1[100] = "hello world";
char s2[100], s3[100];
memcpy(s2, s1, 12); // 无重叠:安全
memmove(s3, s1, 12); // 通用:支持重叠
printf("s2 = %s\n", s2);
printf("s3 = %s\n", s3);
memcpy(s2 + 4, s2, 12); // 重叠!行为未定义(可能崩溃)
memmove(s3 + 4, s3, 12); // 安全:正确处理重叠
printf("s2 + 4 = %s\n", s2);
printf("s3 + 4 = %s\n", s3);
return 0;
}

十、结尾:typedef关键字
typedef 用于为现有类型创建别名,提升代码可读性与可维护性,尤其在处理复杂指针、结构体、函数指针时极为关键。
// 内建类型重命名
typedef long long LL;
typedef char * pchar;
// 结构体重命名
typedef struct __node {
int x, y;
} Node, *PNode;
// 函数指针重命名
typedef int (*func)(int);
#include <stdio.h>
typedef long long LL;
typedef int (*Arr2Dim10)[10];
typedef void (*Func)();
void test() { printf("hello function pointer!\n"); }
int main()
{
LL a;
printf("sizeof(a) = %lu\n", sizeof(a));
int arr[5][10];
Arr2Dim10 p = arr;
Func p2 = test;
p2();
return 0;
}
typedef 是 C/C++ 中实现类型抽象的重要工具,推荐在项目中系统性使用,以降低认知负荷、减少错误。更多高级用法可参考 C/C++ 技术专题。