IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C指针总结 -> 正文阅读

[C++知识库]C指针总结

本文深入剖析数组指针、函数指针、以及函数、数组和指针的深度结合,最后一针见血地指出多级指针的本质。

数组指针

基本概念

什么是数组指针?

int (*pa)[3];

pa就是一个数组指针。首先它是一个指针,它指向一个数组,这个数组里存放了3个int型数字

再通过以下代码来看一些基本概念:

int a[3];
int * p = a;  // 数组名 a 等同于该数组首元素的地址,即 &a[0], 因此是 int * 类型

p = &a;  // 编译会失败, 因为 &a 代表的是一个指向数组的指针,并不是一个指向int型变量的指针

int (*pa)[3] = &a;  // OK. pa是一个数组指针,而a是一个数组,&a正是一个数组的地址

int size = sizeof(pa);  // 12 = 4*3

a[i]   // 即 *(a+i) , 即数组a中下标为i的元素

然后看一个程序:

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

int main()
{
    int a1[3];
    int (*p)[3] = &a1;
    printf("sizeof(p) = %ld\n", sizeof(p));     // 指针大小,64位机器上是占 8 字节
    printf("sizeof(&a1) = %ld\n", sizeof(&a1)); // 地址即指针, 占 8 字节
    printf("sizeof(a1) = %ld\n", sizeof(a1));   // 数组名即数组, 该一维数组所占字节数为 3*4=12
    
    int a2[2][2];
    printf("sizeof(a2) = %ld\n", sizeof(a2));   // 数组名即数组,该二维数组所占字节数为 2*2*4=16
    
    return 0;
}

总结一下上面的几个重要知识点:

  1. 地址即指针
  2. 数组名即数组
  3. 数组名等同于该数组首元素的地址
  4. 对数组名取地址后所得到的就是一个数组指针
  5. 方括号运算符就是方括号左边的变量(可视为基本地址)加偏移后再解引用(deference)
  6. 数组与指针其实不同

数组指针与二维数组

假设有一个二维数组 int a[2][3] = {{1,2,3}, {4,5,6}};
以下来解释每一项的含义:

  • a: 这是二维数组名
  • &a: 这是一个数组指针,它指向一个二维数组
  • a[0]: 这是一维数组的数组名
  • &a[0]: 对一维数组的数组名取地址,因此这是一个数组指针,指向一个一维数组
  • &a[0][0]: 对int型数组中的一个元素取地址,因此这是一个int *类型的指针

那么以上这5项的在数值上是一样吗?
是一样的。但若进行 +1 操作,则所增加的字节数则可能完全不同。

下面给出一个较为全面的示例程序:

#include <cstdlib>
#include <cstdio>
#include <typeinfo>

int main()
{
    int a[2][3] = {{1,2,3}, {4,5,6}}; // 二维数组
    int (*p0)[3] = NULL; // p0是一个指针,指向一个数组,该数组有3个int型元素
    int (*p1)[3] = NULL; // p1是一个指针,指向一个数组,该数组有3个int型元素
    p0 = &a[0];
    p1 = &a[1];
    int(*pp)[2][3] = &a; // pp是一个指针,指向一个int [2][3]类型的二维数组
    
    printf("a        = %p\n", a);
    printf("&a       = %p\n", &a);
    printf("pp       = %p\n", pp);
    printf("a[0]     = %p\n", a[0]);
    printf("&a[0]    = %p\n", &a[0]);
    printf("&a[0][0] = %p\n", &a[0][0]);
    printf("p0       = %p\n", p0);
    
    printf("typeid(a): %s\n", typeid(a).name());        // A2_A3_i
    printf("typeid(&a): %s\n", typeid(&a).name());      // PA2_A3_i
    printf("typeid(pp): %s\n", typeid(pp).name());      // PA2_A3_i
    printf("typeid(a[0]): %s\n", typeid(a[0]).name());      // A3_i
    printf("typeid(&a[0]): %s\n", typeid(&a[0]).name());    // PA3_i
    printf("typeid(p0): %s\n", typeid(p0).name());          // PA3_i
    printf("typeid(&a[0][0]): %s\n", typeid(&a[0][0]).name());  // Pi
    
    // p0 为 &a[0], *p0 即 a[0], *p1 即 a[1]
    printf("0: %d %d %d \n", *(*p0 + 0), *(a[0] + 1), *(*p0 + 2));
    printf("1: %d %d %d \n", *(*p1 + 0), *(a[1] + 1), *(*p1 + 2));
    
    return 0;
}

其输出为:

a        = 0x7ffca5e4ca90
&a       = 0x7ffca5e4ca90
pp       = 0x7ffca5e4ca90
a[0]     = 0x7ffca5e4ca90
&a[0]    = 0x7ffca5e4ca90
&a[0][0] = 0x7ffca5e4ca90
p0       = 0x7ffca5e4ca90
typeid(a): A2_A3_i
typeid(&a): PA2_A3_i
typeid(pp): PA2_A3_i
typeid(a[0]): A3_i
typeid(&a[0]): PA3_i
typeid(p0): PA3_i
typeid(&a[0][0]): Pi
0: 1 2 3 
1: 4 5 6 

若 pp + 1, 则得到的新地址比 pp 增大了 2*3*4 = 24字节
若 p0 + 1, 则得到的新地址比 p0 增大了 3*4 = 12字节

另外,在C语言中,无法将一个数组作为一个函数的参数来直接传递。如果将数组名作为一个函数的参数,那么数组名将立刻被转换为指向该数组首元素的指针。
因此,若传递的是一个二维数组名,比如上述的a,则实际传递的是 &a[0], 而因为a[0]是一维数组,所以&a[0]是数组指针,即 int (*) [3]类型。

函数指针

函数指针的typedef写法,和函数指针的声明基本一致。即在函数声明前面加一个typedef即可。

typedef void (*fp)();

以后就可以用 fp fp1, fp2;这样的语句来定义函数指针了。

示例程序:

typedef void (*fp)();
// (* (fp) 0 )();  // 将0转成fp类型的函数指针,再对其进行调用。本语句会使程序崩溃

void func() { 
    cout << "My func" << endl; 
}

fp fp1 = func;  // 定义函数指针
fp1();     // 调用方式一
(*fp1)();  // 调用方式二

如果一个函数返回函数指针,会是怎么样的呢?经典的例子是signal函数。
详情请见这篇博文《返回函数指针的函数与signal函数原型分析》.

最后看这条语句是什么意思呢:

int (*p[4])(int, int);

先分析中间部分,标识符p先与中括号结合(优先级高),所以p是一个数组,该数组中有4个元素;然后剩下的内容(即 int (*)(int, int))表示一个函数指针,那么就说明数组中的元素类型是函数指针;
所以上面这条语句表示: p是一个数组,该数组中保存了4个函数指针。

深入理解函数、数组与指针的结合

以下是规则允许的

  • 函数的返回值允许是一个函数指针,如 int(*fun())();
    解释: 去掉fun以及后面的一对小括号,即 int(*)();这就是一个函数指针,而去掉的小括号代表函数,所以这是一个返回函数指针的函数。

  • 函数的返回值允许是一个指向数组的指针,如 int (*foo())[];
    解释: 去掉foo以及后面的一对小括号,即 int (*)[];这就是一个数组指针,而去掉的小括号代表函数,所以这是一个返回数组指针的函数。

  • 数组里面允许有函数指针,如 int (*foo[])();
    解释:去掉foo以及后面的一堆中括号,即 int (*)();这就是一个函数指针,而去掉的中括号代表数组,所以这是一个包含函数指针的数组。

现在回头看著名的signal函数的原型: void (*signal(int, void (*)(int)))(int);
使用以上的分析法,
首先,去掉signal以及后面的一对小括号,得到 void (*)(int);, 这就是一个函数指针;
其次,去掉的是 signal(int, void (*)(int)), 这是一个函数,它拥有2个参数,第一个参数是int型,第二个参数也是一个函数指针;
综上,signal函数是一个返回函数指针的函数,它拥有2个参数,第1个参数是int,第2个参数也是一个函数指针。

二级指针与多级指针

二级指针的存在只有一个意义,就是修改它所指向的一级指针。
比如一个函数的参数是一个二级指针,如 void func(int **pp);
那么这个 int ** pp存在的意义就是func的调用者希望在func中去修改一个一级指针。如:

void func(int **pp)
{
    ...
    *pp = some_int_ptr; // e.g. *pp = (int *)malloc(sizeof(int));
    ...
}

void caller() 
{
    int * pa = NULL;
    func(&pa); 
    
    // use pa later
}

多级指针同理。
(完)

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-07 13:32:31  更:2022-02-07 13:33:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:53:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码