您的位置:首页 > 编程语言 > C语言/C++

C/C++学习笔记:基础知识6

2014-08-21 20:54 756 查看
1 复杂表达式与序列点

序列点是一个时间点(在整个表达式全部计算完毕之后或在 ||、 &&、 ? : 或逗号运算符处, 或在函数调用之前), 此刻尘埃落定, 所有的副作用都已确保结束.

ANSI/ISO C 标准这样描述:

在上一个和下一个序列点之间, 一个对象所保存的值至多只能被表达式的计算修改一次。而且前一个值只能用于决定将要保存的值。

i++ 和 ++i 都不同于 i+1。如果你要使 i 自增 1, 使用 i=i+1, i+=1, i++ 或 ++i, 而不是任何组合。

例如 i = i+1 合法, 而 a[i] = i++ 则非法。

2 *p++ 自增 p 还是 p 所指向的变量?

后缀 ++ 和 -- 操作符本质上比前缀一目操作的优先级高, 因此 *p++ 和*(p++) 等价, 它自增 p 并返回 p 自增之前所指向的值

要自增 p 指向的值, 使用 (*p)++, 如果副作用的顺序无关紧要也可以使用 ++*p。

3 有一个 char * 型指针正巧指向一些 int 型变量, 想跳过它们。为什么如下的代码((int *)p)++; 不行?

在 C 语言中, 类型转换意味着 “把这些二进制位看作另一种类型, 并作相应的对待”; 这是一个转换操作符, 根据定义它只能生成一个右值 (rvalue)。而右值既不

能赋值, 也不能用 ++ 自增。

要达到目的可以用:

p = (char *)((int *)p + 1);

或者,因为 p 是 char * 型, 直接用

p += sizeof(int);

4 能否用 void** 指针作为参数, 使函数按引用接受一般指针?

不可移植。C 中没有一般的指针的指针类型。void* 可以用作一般指针只是因为当它和其它类型相互赋值的时候, 如果需要, 它可以自动转换成其它类型; 但

是, 如果试图这样转换所指类型为 void* 之外的类型的 void** 指针时, 这个转换不能完成。

5 空指针

语言定义中说明, 每一种指针类型都有一个特殊值 —— “空指针” —— 它与同类型的其它所有指针值都不相同, 它 “与任何对象或函数的指针值都不相等”。

也就是说, 取地址操作符 & 永远也不能得到空指针, 同样对 malloc() 的成功调用也不会返回空指针, 如果失败, malloc() 的确返回空指针, 这是空指针的典型用法:

表示 “未分配” 或者 “尚未指向任何地方” 的指针。

空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数; 而未初始化指针则可能指向任何地方。

如上所述, 每种指针类型都有一个空指针, 而不同类型的空指针的内部表示可能不尽相同。尽管程序员不必知道内部值, 但编译器必须时刻明确需要那种空指针, 以便在需要的时候加以区分。

6 NULL 是什么?

作为一种风格, 很多人不愿意在程序中到处出现未加修饰的 0。

因此定义了预处理宏 NULL (在 <stdio.h> 和其它几个头文件中) 为空指针常数, 通常是 0 或者((void *)0) 。

希望区别整数 0 和空指针 0 的人可以在需要空指针的地方使用 NULL。

使用 NULL 只是一种风格习惯; 预处理器把所有的 NULL 都还原回 0, 而编译还是依照上文的描述处理指针上下文的 0。

特别是, 在函数调用的参数里, NULL之前 (正如在 0 之前) 的类型转换还是需要。

带修饰的 NULL 和带修饰的 0 完全等价 ,NULL 只能用作指针常数;。

#define NULL ((void *)0)

除了潜在地帮助错误程序运行 (仅限于使用同样类型指针的机器, 因此帮助有限) 以外, 这样的定义还可以发现错误使用 NULL 的程序。

有两条简单规则你必须遵循:

(1)当你在源码中需要空指针常数时, 用 “0” 或 “NULL”。

(2)如果在函数调用中 “0” 或 “NULL” 用作参数, 把它转换成被调函数需要的指针类型

7 char a[ ] 和 char *a 是一样的?

并非如此。 数组不是指针。数组定义 char a[6] 请求预留 6 个字符的位置, 并用名称 “a” 表示。也就是说, 有一个称为 “a” 的位置, 可以放入 6 个字符。而指针申明 char *p, 请求一个位置放置一个指针, 用名称 “p” 表示。这个指针几乎可以指向任何位置: 任何字符和任何连续的字符, 或者哪里也不指。

根据 x 是数组还是指针, 类似 x[3] 这样的引用会生成不同的代码。认识到这一点大有裨益。以上面的声明为例, 当编译器看到表达式 a[3] 的时候, 它生成代码从 a 的位置开始跳过 3 个, 然后取出那个字符. 如果它看到 p[3], 它生成代码找到“p” 的位置, 取出其中的指针值, 在指针上加 3 然后取出指向的字符。换言之, a[3]是名为 a 的对象 (的起始位置) 之后 3 个位置的值, 而 p[3] 是 p 指向的对象的 3 个位置之后的值.
在上例中, a[3] 和 p[3] 碰巧都是 ’l’ , 但是编译器到达那里的途径不尽相同。本质的区别在于类似 a 的数组和类似 p 的指针一旦在表达式中出现就会按照不同的方法计算, 不论它们是否有下标。

数组和指针 “等价”不表示它们相同, 甚至也不能互换。它的意思是说数组和指针的算法定义可以用指针方便的访问数组或者模拟数组。

特别地, 等价的基础来自这个关键定义:

一个 T 的数组类型的左值如果出现在表达式中会蜕变为一个指向数组第一个成员的指针(除了三种例外情况); 结果指针的类型是 T 的指针。

这就是说, 一旦数组出现在表达式中, 编译器会隐式地生成一个指向数组第一个成员地指针, 就像程序员写出了 &a[0] 一样。

例外的情况是, 数组为 sizeof 或 &操作符的操作数, 或者为字符数组的字符串初始值。

作为这个这个定义的后果, 编译器并那么不严格区分数组下标操作符和指针。在形如 a[i] 的表达式中, 根据上边的规则, 数组蜕化为指针然后按照指针变量的方式如 p[i] 那样寻址, 尽管最终的内存访问并不一样。

如果你把数组地址赋给指针:

p = a;

那么 p[3] 和 a[3] 将会访问同样的成员。

8 为什么作为函数形参的数组和指针申明可以互换呢?

由于数组会马上蜕变为指针, 数组事实上从来没有传入过函数。允许指针参数声明为数组只不过是为让它看起来好像传入了数组, 因为该参数可能在函数内当作数组使用。

特别地, 任何声明 “看起来象” 数组的参数, 例如

void f(char a[])

{ ... }

在编译器里都被当作指针来处理, 因为在传入数组的时候,那正是函数接收到的.

void f(char *a)

{ ... }

这种转换仅限于函数形参的声明, 别的地方并不适用。如果这种转换令你困惑, 请避免它

9 数组和指针地区别是什么?

数组自动分配空间, 但是不能重分配或改变大小。指针必须明确赋值以指向分配的空间 (可能使用 malloc), 但是可以随意重新赋值 (即, 指向不同的对象), 同时除了表示一个内存块的基址之外, 还有许多其它的用途。

由于数组和指针所谓的等价性(参见问题 6.3), 数组和指针经常看起来可以互换, 而事实上指向 malloc 分配的内存块的指针通常被看作一个真正的数组(也可以用 [ ] 引用)。

但是, 要小心 sizeof。

可以用指向 malloc 分配的内存的指针来模拟数组。执行

#include <stdlib.h>

int *dynarray;

dynarray = malloc(10 * sizeof(int));

以后 (如果 malloc 调用成功), 你可以象传统的静态分配的数组那样引用dynarry[i] (i 从 0 到 9)。唯一的区别是 sizeof 不能给出 “数组” 的大小。

数组和下标在 C 语言中可以互换。

这个奇怪的事实来自数组下标的指针定义, 即对于任何两个表达式 a 和 e, 只要其中一个是指针表达式而另一个为整数, 则 a[e] 和 *((a)+(e)) 完全一样。

数组蜕化为指针的规则 (参见问题 6.3) 不能递归应用。数组的数组 (即 C 语言中的二维数组) 蜕化为数组的指针, 而不是指针的指针。

数组指针常常令人困惑, 需要小心对待;

如果你向函数传递二位数组:

int array[NROWS][NCOLUMNS];

f(array);

那么函数的声明必须匹配:

void f(int a[][NCOLUMNS])

{ ... }

或者

void f(int (*ap)[NCOLUMNS]) /* ap 是个数组指针 */

{ ... }

在第一个声明中, 编译器进行了通常的从 “数组的数组” 到 “数组的指针” 的隐式转换; 第二种形式中的指针定义显而易见。因为被调函数并不为数组分配地址, 所以它并不需要知道总的大小, 所以行数 NROWS 可以省略。但数组的宽度依然重要, 所以列维度 NCOLUMNS (对于三维或多维数组, 相关的维度) 必须保留。

如果一个函数已经定义为接受指针的指针, 那么几乎可以肯定直接向它传入二维数组毫无意义。

10 当数组是函数的参数时, 为什么 sizeof 不能正确报告数组的大小?

编译器把数组参数当作指针对待 , 因而报告的时指针的大小。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: