1.指针的基础定义和使用

指针是C语言中存储变量内存地址(0x0000 0000-0xffff ffff---x86)的变量.通过指针可以直接访问和操作内存中的数据,提高程序的灵活性和效率

1.1核心定义语法

一级指针:数据类型* 指针名 = &目标变量

char val = 'a';
char* p = &val;
//p存储val的地址,通过*p = 'b'可修改val的值

二级指针:数据类型** 指针名 = &一级指针

char** q = &p;
//通过**q = 'c'可间接修改val的值

1.2基本操作

#include <stdio.h>

int main() {
    int num = 10;
    int *p = &num;      // &取地址
    
    printf("num的值: %d\n", num);        // 10
    printf("num的地址: %p\n", &num);     // 地址
    printf("p的值: %p\n", p);            // 地址
    printf("p指向的值: %d\n", *p);       // *解引用
    
    *p = 20;            // 通过指针修改变量值
    printf("修改后num: %d\n", num);      // 20
    
    return 0;
}

1.3基础概念

&(取地址符):获取变量的内存地址

*(解引用符):获取指针所指向地址中存储的值

指针的大小只取决于系统架构x86(win32)--4B  x64(win64)--8B

1.4注意事项

1)单个声明中,* 仅绑定左侧变量:char* pa, pb; 中 pa 是指针,pb 是普通字符变量;

若需多个指针,需显式加 *char* pa, *pb;)或用 typedef 简化:typedef char* PChar; PChar pa, pb;(两者均为指针)

2)避免用 #define 定义指针类型:#define PCHAR char*; PCHAR pa, pb; 会被替换为 char* pa, pb;,仅 pa 是指针,易出错

2.const和指针结合

const 与指针结合使用,可以定义不同的“常量”概念,增强程序的健壮性

2.1指向常量的指针

可以改变指向,不能修改值

const int* ptr;  //指向常量的指针
int const* str;  //等价写法
 

2.2常量指针

可以修改值,不能改变指向

int* const ptr = &var;
 

2.3指向常量的常量指针

既不能改变指向,也不能修改值

const int* const ptr = &var;
 

2.4记忆技巧

从右向左读

2.5记忆口诀

const 在 * 左边,修饰的是数据;const 在 * 右边,修饰的是指针本身

const int *p1;         // p1是指向const int的指针
int *const p2;         // p2是const指针指向int

3.野指针和空指针

野指针和空指针均会导致程序崩溃,核心是避免访问无权限或不确定的内存地址

3.1野指针(悬挂指针)

指向一个无效内存地址的指针。对野指针进行解引用会导致未定义行为(程序崩溃、数据损坏)

常见的场景有:

1)未初始化的指针:int* p;p 存储随机地址),直接解引用 *p = 1; 会触发内存访问错误

2)指向已销毁的局部变量:函数返回局部数组地址(int* func(){ int arr[2]; return arr; }),函数结束后 arr 内存释放,返回的指针变为野指针

int* ptr; // 未初始化,野指针
free(ptr); // 释放后未置空,野指针
 

3.2空指针

用 NULL(宏定义,值为 0)初始化的指针,int* p = NULL;。规则:0 号地址(内存前 128MB)是系统保留区域,禁止访问,*p = 1; 会崩溃;NULL 的作用是明确指针 “暂时无指向”,避免野指针的随机性

int* ptr = NULL;
 

3.3注意事项

1)指针初始化:要么指向有效变量(int* p = &val;),要么设为 NULL

2)避免强行转换固定数值为指针:int* p = (int*)200000;

4.指针运算

指针运算本质是地址的偏移,偏移量由指针指向的类型决定,仅在连续内存(如数组)中有实际意义

4.1指针 ± 整型:按类型偏移

1)核心规则:指针 + n 等价于 指针地址 + n * sizeof(指针指向的类型)。例:int arr[] = {1,2,3,4,5}; int* p = arr;arr 是数组首元素地址),p+1 偏移 1*4=4 字节(int 占 4 字节),指向 arr[1]char* pn = &num;(char 占 1 字节),pn+1 偏移 1 字节。

2)数组访问的本质:arr[i] 等价于 *(arr + i)arr 是首元素地址,arr + i 指向第 i 个元素,解引用后得到值

4.2指针 - 指针:计算元素个数

仅当两个指针指向同一连续内存(如同一数组)时,结果为 “两指针间的元素数量”,而非地址差值。例:int arr[] = {1,2,3}; int* p1 = &arr[0], *p2 = &arr[2];p2 - p1 = 2(间隔 2 个元素)

4.3面试高频:内存大小端判断

1)大端模式:高位字节存低地址(如 int val = 0x12345678,内存中存为 0x12 0x34 0x56 0x78

2)小端模式:低位字节存低地址(内存中存为 0x78 0x56 0x34 0x12,主流 CPU(x86)采用)

int val = 0x12345678;
char* p = (char*)&val;
if (*p == 0x78) printf("小端");
else printf("大端");
//利用 char 指针仅访问 1 字节的特性

4.4基本要点

递增/递减: p++p--

加上/减去整数: p + np - n

指针相减: 得到的是两个指针之间相隔的元素个数

int arr[] = {10, 20, 30, 40};
int *p = arr; // p指向数组第一个元素

printf("%d\n", *p);     // 输出:10
p++; // 移动到下一个int元素
printf("%d\n", *p);     // 输出:20

int *q = &arr[3];
printf("q - p = %td\n", q - p); // 输出:3 (相隔3个int)

5.指针数组和数组指针

5.1指针数组

一个数组,里面的每个元素都是指针

int a = 1, b = 2, c = 3;
int *arr[3] = {&a, &b, &c}; // arr是一个包含3个int*的数组

5.2数组指针

一个指针,它指向一个数组

int arr[2][3] = {{1,2,3}, {4,5,6}};
int (*p)[3] = arr; // p是一个指针,指向一个包含3个int元素的数组

// 通过p访问arr[1][2]
printf("%d\n", *(*(p + 1) + 2)); // 输出:6
// p + 1 跳过一行(3个int),解引用后得到第2行首地址,再+2得到元素地址,最后解引用。

5.3区分技巧

用 “优先级” 判断 

1)int *arr[3]arr先与[3]结合,是数组,其元素是int*

2)int (*p)[3]p先与*结合,是指针,指向int [3]的数组

6.指针与函数

6.1指针作为函数参数

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int x = 5, y = 10;
swap(&x, &y); // x和y的值被交换

6.2指针作为函数返回值

int* getMax(int *a, int *b) {
    return (*a > *b) ? a : b;
}

切勿返回指向局部变量的指针,因为函数结束后局部变量会被销毁。可以返回全局变量、静态变量或动态分配内存的指针

6.3函数指针

int add(int a, int b) { return a + b; }
int (*funcPtr)(int, int) = &add; // 定义函数指针并指向add函数

int result = funcPtr(3, 4); // 通过函数指针调用函数,result = 7
// 等价于:int result = (*funcPtr)(3, 4);

7.void*

void* 是一种通用指针类型,可以指向任何类型的数据,但不可以直接进行解引用和算术运算

7.1核心特性

1)可接收任意类型地址:int val = 10; void* p = &val; double num = 3.14; p = &num;(无需强制转换)

2)禁止直接操作:不能解引用(*p 报错,无法确定访问字节数),不能算术运算(p+1 报错,无法确定偏移量)

int n = 100;
float f = 3.14;
void *vp;

vp = &n; // 指向int
printf("%d\n", *(int*)vp); // 必须先进行强制类型转换

vp = &f; // 指向float
printf("%f\n", *(float*)vp);

7.2应用:实现通用函数

1)原理:将 void* 强转为 char*(1 字节),通过逐字节操作适配任意类型,需传入 “数据大小”(size_t

void myCopy(void* dest, const void* src, size_t size) {
    char* d = (char*)dest;
    const char* s = (char*)src;
    while (size--) { *d++ = *s++; } // 逐字节拷贝
}

2)使用场景:拷贝 int 数组、double 数组等任意类型数据,如 int arr1[3] = {1,2,3}; int arr2[3]; myCopy(arr2, arr1, 3*sizeof(int));

8.内存 - men 系列函数

mem 系列函数是标准库中操作内存的工具,核心是 “直接操作字节”,需区分 memcpy 和 memmove 的差异

8.1memset

将内存块的前 size 字节设为指定值(int c),如 memset(arr, 0, sizeof(arr));(数组初始化)

8.2memcpy

从 src 指向的内存,拷贝 size 字节到 dest 指向的内存,返回 dest.若 dest 和 src 内存重叠,拷贝结果会出错

8.3memcmp

比较两个内存块的前 size 字节,返回 0(相等)、正数(前者大)、负数(后者大)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐