深入理解C语言中使用频率较高的指针与数组

 更新时间:2022年03月30日 09:21:20   作者:Firefly  
在C语言中要说到哪一部分最难搞,首当其冲就是指针,指针永远是个让人又爱又恨的东西,用好了可以事半功倍,用不好就会有改不完的bug和通不完的宵,下面这篇文章主要给大家介绍了关于C语言中使用频率较高的指针与数组的相关资料,需要的朋友可以参考下

定义

指针:C语言中某种数据类型的数据存储的内存地址,例如:指向各种整型的指针或者指向某个结构体的指针。

数组:若干个相同C语言数据类型的元素在连续内存中储存的一种形态。

数组在编译时就已经被确定下来,而指针直到运行时才能被真正的确定到底指向何方。所以数组的这些身份(内存)一旦确定下来就不能轻易的改变了,它们(内存)会伴随数组一生。

而指针则有很多的选择,在其一生他可以选择不同的生活方式,比如一个字符指针可以指向单个字符同时也可代表多个字符等。

指针和数组在C语言中使用频率是很高的,在极个别情况下,数组和指针是“通用的”,比如数组名表示这个数组第一个数据的指针。

如下代码:

#include <stdio.h>
char array[4] = {1, 2, 3, 4};
int main(void)
{
    char * p;
    int i = 0;
    p = array;
    for (; i < 4; i++)
    {
        printf("*array = %d\n", *p++);
    }
    return (0);
}

这里我们将数组名array作为数组第一个数据的指针赋值给p。但是不能写成*array++。准确来说数组名可以作为右值,不能作为左值(左值和右值的概念这里不再展开讲解)。

数组名的值其实是一个指针常量,这样我想你就明白了数组名为什么不能做为左值了。如果想用指针p访问array的下面2的数据,以下写法是合法的。

char data;
/*第一种写法*/
p = array;
data = p[2];
/*第二种写法*/
p = array;
data = *(p+2);
/*第三种写法*/
p = array + 

指针与二维数组

先说一下二维数组,二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。以下面的二维数组 a 为例:

int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };

从概念上理解,a 的分布像一个矩阵:

  • 0 1 2 3。
  • 4 5 6 7。
  • 8 9 10 11。

但是内存是连续的,没有这样的“矩阵内存”,所以二维数组a分布是连续的一块内存。

C语言允许把一个二维数组分解成多个一维数组来处理。对于数组 a,它可以分解成三个一维数组,即 a[0]、a[1]、a[2]。每一个一维数组又包含了 4 个元素,例如 a[0] 包含 a[0][0]、a[0][1]、a[0][2]、a[0][3]。那么定义如下指针如何理解呢?

int (*p)[4];

括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。那么和下面定义有什么区别呢?

int *p[4];

这里就要先说明*和[]的优先级了,[]的优先级是高于*的,所以int *p[4];等同于int *(p[4]);。所以它是一个指针数组。这里很绕,总接下:

int (*p)[4];是数组指针,它指向二维数组中每个一维数组的类型,它指向的是一个数组。

int *p[4];是指针数组,它是一个数组,数组中每个数是指向int型的指针。

指针数组与数组指针

对于指针数组,说的已经很明确了,不再详细讲解,下面说一下数组指针。举例看一下:

#include <stdio.h>
int main()
{
    int a[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
    int(*p)[4];
    p = a;
    printf("%d\n", sizeof(*(p + 1)));
    return (0);
}

对于数组指针p如下:

那么printf("%d\n", sizeof(*(p + 1)));的结果就是16。如果想打印a[1][0]的值,代码如下:

printf("%d\n", *(*(p + 1)));

如果想打印a[1][1]的值,代码如下:

printf("%d\n", *(*(p + 1)+1));

这个代价自行体会,p是数组指针,它指向的是一个数组,所以对获取它指向的值,也就是*p,是指向一个数组还是一个值,指向a[0]。获取获取a[0][0],就需要写成**p。

对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节,这正好是数组 a 所包含的每个一维数组的长度。也就是说,p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。

最后再次捋一下数组指针和指针数组。

int *p1[4];是指针数组。

int (*p2)[4];是数组指针。

“[]”的优先级比“*”要高。

对于指针数组,p1先和“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那么它本质是一个数组,这个数组里有4个指向int类型数据的指针。

对于数组指针,“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那么它本质是一个指针,它指向一个包含4个int 类型数据的数组。

既然深入谈了指针数组和数组指针,就多聊一下。

#include <stdio.h>
int main()
{
    char a[5] = {'A', 'B', 'C', 'D'};
    char(*p3)[5] = &a;
    char(*p4)[5] = a;
    return 0;
}

上面代码是编译编译是报了waring的,报警如下:

注意:不同的编译器编译结果可能不同,我的编译方法请参考《使用vscode编译C语言》。p3 这个定义的“=”号两边的数据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。所以才有了上面的警告。

但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值,所以运行并没有什么问题。不过编译器仍然警告你别这么用。

再举一个栗子:

int vector[10];
int matrix[3][10];
int *vp,*vm;
vp = vector;
vm = matrix;

上面的代码第5行是错误的,因为vm是指向整型的指针,但是matrix不是指向正向的指针,他是指向整型数组的指针。下面是正确的写法:

int matrix[3][10];
int (*vm)[10];
vm = matrix;

数组指针的应用

上面说了那么多,可能大部分开发者用不到,数组指针在很多时候都是可以代替二维数组的,有些程序员喜欢用指针数组来代替多维数组,一个常见的用法就是处理字符串。

#include <stdio.h>
char *Names[] =
    {
        "Bill",
        "Sam",
        "Jim",
        "Paul",
        "Charles",
        0};
void main()
{
    char **nm = Names;
    while (*nm != 0)
        printf("%s \n", *nm++);
}

具体运行我就不讲解了,运行结果如下:

注意数组中的最后一个元素被初始化为0,while循环以次来判断是否到了数组末尾。具有零值的指针常常被用做循环数组的终止符。

这种零值的指针称为为空指针(NULL)。采用空指针作为终止符,在增删元素时就不必改动遍历数组的代码,因为此时数组仍然以空指针作为结束。

操作

写到这里想到一个“操作”,先看下面代码是否正确。

p[-1]=0;

初看这句代码,觉得奇怪,甚至觉得它就是错误,日常C语言开发基本有见到小标是负数的,但是仔细想想没有哪一本书说过下标能为负数的。看下面代码:

void main()
{
    int data[4] = {0, 1, 2, 3};
    int *p;
    p = data +2;
    printf("p[-1] is %d\n",p[-1]);
    printf("*(p-1) is %d\n",*(p-1));
}

运行结果如下:

不仅可以编译通过,还能正确的输出结果为1。这表明,C的下标引用和间接访问表达式是一样的。当然不鼓励这种操作,代码需要很强的可读性,而不是这样的操作,这里只是演示下标引用和简介表达式的关系。

总结

到此这篇关于C语言中使用频率较高的指针与数组的文章就介绍到这了,更多相关C语言指针与数组内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言实现简单的扫雷游戏操作

    C语言实现简单的扫雷游戏操作

    这篇文章主要为大家详细介绍了C语言实现简单的扫雷游戏操作,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-03-03
  • c++验证哥德巴赫猜想

    c++验证哥德巴赫猜想

    这篇文章主要介绍了c++验证哥德巴赫猜想,哥德巴赫猜想就是任一大于2的偶数,都可表示成两个素数之和,需要的朋友可以参考下
    2014-04-04
  • C++常对象精讲_const关键字的用法

    C++常对象精讲_const关键字的用法

    用const修饰的声明数据成员称为常数据成员。变量或对象被 const修饰后其值不能被更新。因此被const修饰的变量或对象必须要进行初始化
    2013-10-10
  • 一起来看看C语言线性表的线性链表

    一起来看看C语言线性表的线性链表

    这篇文章主要为大家详细介绍了C语言线性表的线性链表,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • C++第三方日志库log4cplus的安装与使用配置教程

    C++第三方日志库log4cplus的安装与使用配置教程

    log4cplus是C++编写的开源的日志系统,log4cplus具有线程安全、灵活、以及多粒度控制的特点,本文给大家介绍C++第三方日志库log4cplus的安装与使用教程,感兴趣的朋友一起看看吧
    2022-02-02
  • 利用Matlab绘制优美的k线图

    利用Matlab绘制优美的k线图

    本期又是一个花里胡哨的数据可视化,前两天刷到了耐克的视觉设计师Gladys Orteza绘制的k线图作品,把沉闷的股票图变成了精彩的风景,但是那些大部分是真的完全看不清,我这里挑选了几个能看清的k线图风格将其用MATLAB进行了实现
    2022-10-10
  • VC中控制台程序创建窗口的实例方法

    VC中控制台程序创建窗口的实例方法

    在本篇文章里小编给大家分享的是关于VC中控制台程序创建窗口的实例方法及相关代码内容,有需要的朋友学习下吧。
    2021-12-12
  • c_str()的用法详细解析

    c_str()的用法详细解析

    c_str()就是把string类对象转换成和c兼容的char *类型。这是为了与c语言兼容,在c语言中没有string类型,故必须通过string类对象的成员函数c_str()把string 对象转换成c中的字符串样式
    2013-09-09
  • C++11新特性std::make_tuple的使用

    C++11新特性std::make_tuple的使用

    这篇文章主要介绍了C++11新特性std::make_tuple的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • C语言PlaySound函数使用方法

    C语言PlaySound函数使用方法

    这篇文章介绍了C语言PlaySound函数的使用方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12

最新评论