轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

  • Spring

  • 其他语言

    • C语言指针二三事
      • 一、起源
      • 二、何为指针
        • 1、C的特殊性
        • 2、初识指针
        • 3、函数中变更变量的值
        • 4、指针的基础知识
        • 5、利用指针解决问题
        • 6、指针安全问题
      • 三、数组与指针
        • 1、函数形参、指针与数组
        • 2、指针与多维数组
        • 3、多维数组与函数
      • 四、字符串与指针
      • 五、结构与指针
      • 六、函数与指针
      • 七、const指针
    • 从Java到Kotlin
    • Groovy语言探索
  • 工具

  • 后端
  • 其他语言
轩辕李
2018-05-10
目录

C语言指针二三事

# 一、起源

想学一下UNIX系统编程,所以把C重学一遍,C中指针关联甚多,是重点也是难点。

下面是我自己学习的总结,希望对你也有所帮助!

# 二、何为指针

# 1、C的特殊性

C是一门很特殊的语言,特殊的地方在于它对计算机是友好的,对程序员并不太友好。
常识上来说,我们是不需要知道一个变量在内存中的实际地址的,对于我们实现业务没有意义。但早期编写C的这帮计算机科学家都非常精通计算机原理,而且做的是底层研究,他们熟悉汇编,更熟悉计算机硬件原理,他们只是想做一个抽象层,编出一门语言来简化系统软件开发。
现在来看,对于学习JS、Java或是Python的人来说,C的编写过于复杂了,不够简洁。不过反过来看,我们也不能太苛刻,相比汇编语言来说,C已经好太多了。

# 2、初识指针

说到指针,它的定义就是:用于存储变量的内存地址。比如一个变量int num = 0;来说,代码执行的时候必然会给他分配内存,如果你是一个高端玩家,想要知道num储存在内存具体的哪块地址上,那么可以用&num来获得实际地址。
执行代码:

int num = 10;
printf("%d  %p", num, &num);

执行结果大致为:

10  0060FF0C

那么获得一个内存地址有什么用呢?
我们先来看它在应用方面来讲最大的作用。

# 3、函数中变更变量的值

如果要实现两个int值互换,那么非常简单:

int num1 = 10;
int num2 = 20;
int temp = num2;
num2 = num1;
num1 = temp;
printf("%d %d",num1,num2);

如果要实现一个interchange函数呢,你可能想到:

void interchange(int num1, int num2){
    int temp = num2;
    num2 = num1;
    num1 = temp;
}

在main函数中调用:

int main(void){
    int num1 = 10;
    int num2 = 20;
    interchange(num1,num2);
    printf("%d %d",num1,num2);
}

打印出来的结果很有可能不符合预期哦,实际上num1和num2并没有进行交换。
这是怎么回事儿呢?这是由C语言的特性所决定的,num1和num2实际上只传递了值,传递了副本,所以可以这么看:在interchange()函数中的num1和num2已经是独立的值,已经和main()中的两个数没啥关系了,他们怎么改变也不会影响到main()中的num1和num2了。
说到这里,我们遇到了第一个问题,怎么解决呢?需要指针登场了!

# 4、指针的基础知识

先看下指针的基础知识,然后我们可以用这些知识来解决上述问题。
先有一个表达式:int num = 10;,指针的知识:

  • 获得一个变量的指针:&num
  • 指针声明:int * pnum = &num
  • 解指针:*pnum = num

这里容易让人迷惑的地方是指针声明和解指针用的都是*号。
简单来说,&num也是一个类型,是一个什么类型呢?指针类型。那么如何声明指针类型:int * pnum;,pnum就是一个指针类型。 pnum是一个指针,那么我想获取它的实际表达值怎么办?也就是怎么解这个指针呢?也用到*号。所以*pnum其实和num是相等的。

重要提示:指针的大小取决于系统架构,在32位系统中是4字节,在64位系统中是8字节,与指向的数据类型无关。

# 5、利用指针解决问题

所以改良后的interchange()函数如下:

void interchange(int * num1, int * num2){
    int temp = *num2;
    *num2 = *num1;
    *num1 = temp;
}

调用:

interchange(&num1, &num2);

interchange()函数用了指针和解指针的方式巧妙的改变了num1和num2变量的值。

# 6、指针安全问题

在使用指针时,需要特别注意以下安全问题:

空指针(NULL Pointer)

int *p = NULL;  // 空指针,指向地址0
// 使用前必须检查
if (p != NULL) {
    *p = 10;  // 安全
}

野指针(Wild Pointer)

int *p;  // 未初始化的指针,指向随机地址
*p = 10;  // 危险!可能导致程序崩溃

悬垂指针(Dangling Pointer)

int *p = (int *)malloc(sizeof(int));
free(p);  // 释放内存
*p = 10;  // 危险!p现在是悬垂指针
p = NULL;  // 好习惯:释放后置为NULL

# 三、数组与指针

定义一个数组,非常简单:

int powers[5] = {3,4,5,6,7};

下面开始引入数组与指针的关系了。
主要的规则有以下几点:

  • 数组名在大多数表达式中会隐式转换为指向数组首元素的指针。也就是说*powers等于powers[0]
  • 指针+1,则指针的值递增它所指向类型的大小。也就是说powers+1等于&powers[1]
  • 注意:数组名是数组的标识符,不是变量,不能进行++操作。但是可以将数组名赋值给指针变量,然后对指针变量进行++操作
int powers[5] = {3,4,5,6,7};
// powers++;  // 错误!数组名是常量
int *p = powers;  // 正确
p++;  // 正确,p现在指向powers[1]

多举几个等式的例子,感受一下:

powers = &powers[0]
powers + 2 = &powers[2]
*(powers + 2) = powers[2]

# 1、函数形参、指针与数组

数组可以理解为是一种特殊的指针,所以下面的四种函数原型是等价的:

int sum(int *ar, int n);
int sum(int *,int);
int sum(int ar[], int n);
int sum(int [],int);

# 2、指针与多维数组

先看一个多维数组的定义:

int zippo[4][2];

对这个多维数组进行分析:

  • zippo[0]是一个占用一个int大小对象的地址,而zippo是一个占用两个int大小对象的地址。它们两个都开始于同一个地址,所以zippo和zippo[0]的值相同
  • []运算符本质上是指针运算的语法糖,a[i]等价于*(a+i)。对于多维数组,*zippo等于zippo[0],**zippo等于zippo[0][0]

请说明以下两个表达式的不同:

int (* pz)[2];  //1
int * pax[2];  //2

对于表达式1来说,int (* pz)[2]声明了一个数组指针,即pz是一个指针,指向包含2个int元素的数组。

对于表达式2来说,int * pax[2]声明了一个指针数组,即pax是一个数组,包含2个int类型的指针。

简单记忆:

  • int (* pz)[2] - 括号优先,pz先和*结合,是指针,指向int[2]数组
  • int * pax[2] - []优先级高于*,pax先和[2]结合,是数组,元素是int*

# 3、多维数组与函数

如果多维数组作为参数来传递,那么函数原型声明上也有要注意的一些地方,先来看正确的声明方式:

int sum(int (* ar)[4], int rows);
int sum(int ar[][4], int rows);

再来看错误的方式:

int sum(int ar[][], int rows);

为什么下面是错的呢,是因为编译器会把数组表示法转换为指针表示法,那么就必须知道ar所指向的对象大小。

# 四、字符串与指针

在C中,字符串是以空字符(\0)结尾的char类型数组。
那么我们很容易想到他的定义:

char mesg[12] = "hello world";  // 注意:需要12个字符(11个字符+1个'\0')
char * pmesg = "hello world";

根据前面数组的内容,这两种定义都是ok的。
那么他们有什么异同呢?还是说使用的时候任何情况下都可以看成是含义相同的?

关键区别:

  1. 存储位置不同:

    • char mesg[]:在栈上分配内存,内容可以修改
    • char *pmesg:指向存储在只读数据段的字符串字面量,内容不可修改
  2. 可修改性:

char mesg[12] = "hello world";
mesg[0] = 'H';  // 正确,可以修改

char *pmesg = "hello world";
pmesg[0] = 'H';  // 错误!运行时会崩溃,试图修改只读内存
  1. 使用建议:
    • 需要修改字符串内容时,使用字符数组
    • 只读字符串时,使用const char *更安全
    • scanf()必须使用字符数组,因为需要写入内容

# 五、结构与指针

C中的结构如:

struct node{
    int num;
};

跟Java中的Class相似。
这里要讲到结构指针,作用和前面的函数中变更变量的值差不多。
先思考一个问题,如果一个结构作为入参传入一个函数中,函数中修改结构的值会影响到main()函数中结构的值么? 可能答案你已经猜出来了,是不能的。这里还是要借助指针来实现:

void struct_demo(){
    struct node tn;
    tn.num = 1;
    struct_demo_swap(&tn);
    printf("%d",tn.num);
}
void struct_demo_swap(struct node * temp){
    temp->num = 10;
}

打印出来的结果是tn.num=10。
注意,这里对于指针访问结构成员有两种方式:(*temp).num或temp->num

# 六、函数与指针

C语言支持函数指针,可以将函数作为参数传递。注意:这与JavaScript的闭包或Java 8的Lambda表达式不同,C的函数指针不能捕获外部变量(没有闭包特性)。
在C中,函数也有地址,指向函数的指针中存储着函数代码的起始处的地址。
一个规则:函数名可以用于表示函数的地址。看代码:

void ToUpper(char *);
void (*pf)(char *);
pf = ToUpper;

可以看到第二行声明了一个函数指针叫pf,这个函数指针定义了返回值和形参列表,只要是和他结构一样的,都可以赋值给他。这里pf = ToUpper;没有什么问题。
这样的规则会存在一些小问题:*pf实际上表示ToUpper函数,而pf和函数名又可以互换,所以(*pf)("abc")和pf("abc")、ToUpper("abc")又都是等价的。
下面看一个函数指针最常用的用法,就是作为入参:

void show(void (* fp)(char *), char * str);

实际应用示例 - qsort函数:

#include <stdio.h>
#include <stdlib.h>

// 比较函数,用于qsort
int compare(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr)/sizeof(arr[0]);
    
    // qsort使用函数指针作为参数
    qsort(arr, n, sizeof(int), compare);
    
    printf("排序后的数组: ");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    return 0;
}

# 七、const指针

const与指针的结合使用是C语言中的重要概念:

1. 指向const的指针(底层const)

const int *p;  // p可以改变,但不能通过p修改所指向的值
int const *p;  // 同上,两种写法等价

2. const指针(顶层const)

int * const p = &num;  // p不能改变,但可以通过p修改所指向的值

3. 指向const的const指针

const int * const p = &num;  // p不能改变,也不能通过p修改所指向的值

记忆技巧:const修饰其左边的内容,如果左边没有内容,则修饰右边。


关于指针的知识就总结完了,希望你有所收获!

编辑 (opens new window)
#C指针
上次更新: 2025/08/14
Thymeleaf 与 Spring 框架的集成教程
从Java到Kotlin

← Thymeleaf 与 Spring 框架的集成教程 从Java到Kotlin→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式