C语言指针

一切都是地址

CPU 只能通过地址来取得内存中的代码和数据,程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。如果程序不小心出错,或者开发者有意为之,在 CPU 要写入数据时给它一个代码区域的地址,就会发生内存访问错误。这种内存访问错误会被硬件和操作系统拦截,强制程序崩溃,程序员没有挽救的机会

CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址

需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址

指针变量

数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量

现在假设有一个 char 类型的变量 c,它存储了字符 'K'(ASCII码为十进制数 75),并占用了地址为 0X11A 的内存(地址通常用十六进制表示)。另外有一个指针变量 p,它的值为 0X11A,正好等于变量 c 的地址,这种情况我们就称 p 指向了 c,或者说 p 是指向变量 c 的指针

指针变量的运算

指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1,这是为什么呢?

以 a 和 pa 为例,a 的类型为 int,占用 4 个字节,pa 是指向 a 的指针,如下图所示:

如果pa++;使得地址加 1 的话,就会变成如下图所示的指向关系:

其中前 3 个是变量 a 的,后面 1 个是其它数据的,把它们“搅和”在一起显然没有实际的意义,取得的数据也会非常怪异;如果pa++;使得地址加 4 的话,正好能够完全跳过整数 a,指向它后面的内存,这样指针的加减运算就具有了现实的意义

字符串指针

char *str = "hello";

字符串中的所有字符在内存中是连续排列的,str 指向的是字符串的第 0 个字符;我们通常将第 0  个字符的地址称为字符串的首地址。字符串中每个字符的类型都是char,所以 str 的类型也必须是char *

字符数组和字符串常量

它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,字符串常量存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限

内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于字符串常量,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的

#include <stdio.h>
int main(){
    char *str = "Hello World!";
    str = "I love C!";  //正确
    str[3] = 'P';  //错误

    return 0;
}

二级指针

如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针

二维数组指针

二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”

C语言允许把一个二维数组分解成多个一维数组来处理

定义一个二维数组

int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

定义二维数组指针

int (*p)[3] = arr;

*p表示p是一个指针,它指向一个数组,数组类型为int[3],这正是arr包含的每个一维数组类型,即每行。这里括号是必须的,否则p会变成一个指针数组

二维数组指针遍历

#include <iostream>

using namespace std;

int main() {
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*p)[3] = arr;

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            cout << *(*(p + i) + j) << endl;
        }
    }

    return 0;
}

函数指针

一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针

定义一个函数指针

returnType (*pointerName)(param list);

 这里括号是必须的,如果写作returnType *pointerName(param list);就成了函数原型,它表示函数的返回值为returnType*

使用函数指针

#include <iostream>

using namespace std;

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

int main() {
    int (*p)(int, int) = max;
    cout << (*p)(3, 2) << endl;

    return 0;
}

p是一个函数指针,在前面加 * 就表示对它指向的函数进行调用,这里括号同样不能省略

参考

C语言指针详解,30分钟玩转C语言指针

  • 40
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值