《征服C指针》——读书笔记(5)

本文详细探讨了C语言中数组与指针的性质,包括如何声明指向数组的指针,C语言中不存在多维数组的概念,以及如何在表达式中将数组解读为指针。同时,文章还介绍了数组和指针相关的运算符,以及如何在表达式中访问多维数组。
摘要由CSDN通过智能技术生成

一、C的数据类型的模型

1. 什么是指向数组的指针

  “数组”和“指针”都是派生类型,它们都是由基本类型开始重复派生生成的。也就是说,派生出“数组”后,再派生出“指针”,就可以生成“指向数组的指针”。在表达式中,数组可以被解读成指针。但是,这不是“指向数组的指针”,而是“指向数组初始元素的指针”。

//  实际地声明一个“指向数组的指针”:

int (*array_p)[3];    // array_p 是指向 int 数组(元素个数3)的指针。

  根据 ANSI C 的定义,在数组前加上&,可以取得“指向数组的指针”(这里是“数组可以解读成指向它初始元素的指针”这个规则的一个例外)。因此,

int array[3]; 
int (*array_p)[3]; 
array_p = &array;   // 数组添加&,取得“指向数组的指针”

  这样的赋值是没有问题的,因为类型相同。 可是,如果进行 array_p = array; 这样的赋值,编译器就会报出警告。“指向 int 的指针”和“指向 int 的数组(元素个数 3)的指针”是完全不同的数据类型。 但是,从地址的角度来看,array 和&array`也许就是指向同一地址。但要说起它们的不同之处,那就是它们在做指针运算时结果不同。
   在我的机器上,因为 int 类型的长度是 4 个字节,所以给“指向 int 的指针”加 1,指针前进 4 个字节。但对于“指向 int 的数组(元素个数 3)的指针”,这个指针指向的类型为“int 的数组(元素个数 3)”,当前数组的尺寸为 12 个字节(如果 int 的长度为 4 个字节),因此给这个指针加 1,指针就前进 12 个字节。
  

2. C语言中不存在多维数组

  在 C 中,可以通过下面的方式声明一个多维数组: int hoge[3][2]
  按照C 的声明的解读方法,上面的声明应该怎样解读呢?是“int 类型的多维数组”吗? 这是不对的。应该是“int 的数组(元素个数 2)的数组(元素个数 3)”。也就是说,即使 C 中存在“数组的数组”,也不存在多维数组。“数组”就是将一定个数的类型进行排列而得到的类型。“数组的数组”也只不过是派生源的类型恰好为数组。
  对于下面的这个声明: int hoge[3][2];可以通过 hoge[i][j]的方式去访问,此时,hoge[i]是指“int 的数组(元素个数 2)的数组(元素个数 3)”中的第 i 个元素,其类型为“int 数组(元素个数 2)”。当然,因为是在表达式中,所以在此时此刻,hoge[i]也可以被解读成“指向 int 的指针”。
  那么,如果将这个“伪多维数组”作为函数的参数进行传递,会发生什么呢?
  试图将“int 的数组”作为参数传递给函数,其实可以直接传递“指向 int 的指针”。这是因为在表达式中,数组可以解释成指针。因此,在将“int 的数组”作为参数传递的时候,对应的函数的原型如下:void func(int *hoge);
  在“int 的数组(元素个数 2)的数组(元素个数 3)”的情况下,假设使用同样的方式来考虑, int 的数组(元素个数 2)的数组(元素个数 3) ,其中下划线部分,在表达式中可以解释成指针,所以可以向函数传递指向 int 的数组(元素个数 2)的指针。这样的参数,说白了它就是“指向数组的指针”。
  也就是说,接收这个参数的函数的原型为: void func(int (*hoge)[2]);
  直到现在,有很多人将这个函数原型写成下面这样:
  void func(int hoge[3][2]);
  或者这样:
  void func(int hoge[][2]);
  其实,void func(int (*hoge)[2]);就是以上两种写法的语法糖,它和上面两种写法完全相同。

3. 函数类型派生

  函数类型也是一种派生类型,“参数(类型)”是它的属性。可是,函数类型和其他派生类型有不太相同的一面。 无论是 int 还是 double,亦或数组、指针、结构体,只要是函数以外的类型,大体都可以作为变量被定义。而且,这些变量在内存占用一定的空间。因此,通过 sizeof 运算符可以取得它们的大小。 像这样,有特定长度的类型,在标准中称为对象类型
  可是,函数类型不是对象类型。因为函数没有特定长度。 所以 C 中不存在“函数类型的变量”(其实也没有必要存在)。 数组类型就是将几个派生类型排列而成的类型。因此,数组类型的全体长度为: 派生源的类型的大小×数组的元素个数。
  可是,函数类型是无法得到特定长度的,所以从函数类型派生出数组类型是不可能的。也就是说,不可能出现“函数的数组”这样的类型。 可以有“指向函数的指针”类型,但不幸的是,对指向函数类型的指针不能做指针运算,因为我们无法得到当前指针类型的大小。 此外,函数类型也不能成为结构体和共用体的成员。另外,函数类型也不可以从数组类型派生。 可以通过“返回~的函数”的方式派生出函数类型,不过在 C 中,数组是不能作为函数返回值返回的。

4. 不完全类型

  不完全类型指“函数之外、类型的大小不能被确定的类型”。
  总结一下,C 的类型分为:
  对象类型(char、int、数组、指针、结构体等);
  函数类型;
  不完全类型(结构体标记的声明就是一个不完全类型的典型例子)。
  在 C 标准中,void 类型也被归类为不完全类型。

二、表达式

1. “左值”是什么——变量的两张面孔

  假设有下面这样一个声明: int hoge;
  因为此时 hoge 是 int 类型,所以,只要是可以写 int 类型的值的地方,hoge 就可以像常量一样使用。 比如,
  将 5 赋予 hoge 之后,下面的语句
  piyo = hoge * 10;
  理所当然地可以写成
  piyo = 5 * 10;
  但是,在 hoge = 10; 的情况下,即使此时 hoge 的值为 5,
  5 = 10;
  这样的置换也是非法的。
  也就是说,作为变量,它有作为“自身的值”使用和作为“自身的内存区域”使用两种情况。
  表达式代表某处的内存区域的时候,我们称当前的表示式为左值(lvalue);相对的是,表达式只是代表值的时候,我们称当前的表达式为右值(rvalue)。

2. 将数组解读成指针

  正如在前面翻来覆去提到的那样,在表达式中,数组可以解读成指针。
  int hoge[10];
  以上的声明中,hoge 等同于&hoge[0]。
  hoge 原本的类型为“int 的数组(元素个数 10)”,但并不妨碍将其类型分类“数组”变换为“指针”。
  此外,数组被解读成指针的时候,该指针不能作为左值。
  这个规则有以下的例外情况:
  数组为 sizeof 运算符的操作数
  在通过“sizeof 表达式”的方式使用 sizeof 运算符的情况下,如果操作数是“表达式”,此时即使对数组使用sizeof,数组也会被当成指针,得到的结果也只是指针自身的长度。照理来分析,应该是这样的吧?可是,当数组成为 sizeof 的操作数时,“数组解读为指针”这个规则会被抑制,此时返回的是数组全体的大小。
  数组为&运算符的操作数
  通过对数组使用&,可以返回指向整体数组的指针。
  初始化数组时的字符串常量
   我们都知道字符串常量是“char 的数组”,在表达式中它通常被解读成“指向 char 的指针”。其实,初始化char 的数组时的字符串常量,作为在花括号中将字符用逗号分开的初始化表达式的省略形式,会被编译器特别解释。

3. 数组和指针相关的运算符

  在标准中,似乎并没有定义->运算符的名称,现实中有时它被称为“箭头运算符”。 通过指针访问结构体的成员的时候,会使用->运算符。
  p->hoge;
  是 (*p).hoge;的语法糖。
  利用p 的,从指针 p 获得结构体的实体,然后引用成员 hoge。

4. 多维数组

  int hoge[3][5];
  对于上面这个“数组的数组”,使用 hoge[i][j]这样的方式进行访问,如下图
  这里写图片描述
  
  hoge 的类型为“int 的数组(元素个数 5)的数组(元素个数 3)”。 尽管如此,在表达式中数组可以被解读成指针。因此,hoge 的类型为“指向 int 的数组(元素个数 5)的指针”。
  hoge[i]是*(hoge + i)的语法糖。 给指针加上 i,就意味着指针前移它指向的类型×i 的距离。
  hoge 指向的类型为“int 的数组(元素个数 5)”,因此,hoge + i 让指针前移了 sizeof(int[5])×i 的距离。
  通过*(hoge + i)中的**,去掉一个指针,*(hoge + i)的类型就是“指向 int 的数组(元素个数 5)”。 尽管如此,由于在表达式中,数组可以解读成指针,所以*(hoge + i)的最终类型为“指向 int 的指针”。
  (*(hoge + i))[j]*((*(hoge + i)) + j)其实是相等的,因此,(*(hoge + i))[j]就是“对指向 int 的指针加上 j 后得到的地址上的内容”,其类型为 int。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值