C和C++计算的顺序点和副作用

本文深入探讨了编程语言中副作用和透明引用的概念,通过具体的代码示例解释了这两种概念的区别,以及它们如何影响表达式的计算顺序和结果。文章特别强调了在C/C++中由于未明确规定表达式的计算顺序和副作用实现所带来的不确定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

预备知识

副作用和透明引用:

什么是副作用?

专业一点的解释就是一个表达式修改了环境(环境就是其它可修改的数据,如变量),不仅计算值还做了额外的事情。比如表达式:i++,这个表达式就有副作用,因为它修改了环境(i)。还有:

i++ + ++i, j + (i = 3) + k, --i, i-- ...等。

什么是透明引用呢?

即不对环境有影响,比如表达式:(a+b)-(c+d),a、b、c、d在这个表达式中都被透明引用了,因为不管先计算哪一边,这个表达式的结果都不变。

问题:

i = 1;

printf("%d %d", i++, ++i);

这里有一个参数列表的计算顺序,到底是++i,i++还是i++,++i? 有人说参数都是从右住左入栈的,所以是++i,i++,假定是这样的顺序,那么应该输出:2 2

如果是从左住右呢?那么输出:1 3 在gcc 4.4.0下输出: 2 3。是不是感到结果很惊讶,难道它有其它的求值顺序?

这里有一个顺序点的概念,什么是顺序点?比如:

i++ + ... + *p;

假定p指向i。表达式i++是一个有副作用的表达式,我们假定这个表达式的计算顺序是从左往右,那么i++肯定先算,当算到*p的时候,之前i++的副作用是否体现到了内存里面?也就是说是否把新值写到了i对应的内存里面?因为i的值是存放在CPU的寄存器里面的,所以如果编译器没有把新值从CPU写到内存里面,那么*p引用到的将是原来的值。如果已经体现了副作用,那么i++和*p之间就有一个顺序点,也就是体现副作用的地方,在下一个顺序点之前所有的副作用都必须实现,即写入内存。

回到上面i++,++i的问题,如果i++和++i之间有一个顺序点,并且计算顺序也知道,是不是就可以推算出结果呢?确实是这样,比如在Java里就可以推算出结果,因为Java规定表达式的计算顺序从左往右,并且所有副作用都会在下一个计算之前实现。

那么在C\C++里答案是什么呢?答案就是不知道!虽然这样回答有点水,不过确实不知道,如果有人说知道,那么它肯定是专家。

每个编译器都有自己的求值顺序,以及顺序点的规定,但是请放心,一条语句执行完毕之后,这条语句中的所有副作用都会实现,也就是说:
i = 1;
printf("%d %d", i++, ++i);

当printf这行执行之后i的值肯定是3。

在C\C++里,如果一个表达式中要引用一个变量多次,就不应该对该变量做有副作用的运算,只能是透明引用,否则无法推算出结果来,不同的编译器有自己的求值顺序及顺序点的规定,这都是C\C++允许的。C\C++没有规定这些是为了效率考虑,如果像Java那样规定顺序及副作用的体现,必定会有大量的内存访问,所以Java是牺牲了效率而带来了程序的清晰,稳定。但C\C++依然是高效的语言。当然这些问题只需要注意就行了。
### 考研复试 C C++ 笔试常见知识总结 #### 一、基础知识 CC++的基础知识是笔试中的核心部分,主要涉及变量声明、数据类型、运算符、控制语句等内容。例如,在C语言中,逗号表达式的计算顺序是从左到右,最终返回最右侧表达式的值[^4]。 #### 二、指针与内存管理 指针操作是C/C++的重要特性之一,常考的内容包括动态内存分配(`malloc`, `new`)、释放(`free`, `delete`),以及指针算术的操作。理解指针的概念对于解决复杂的数据结构问题是至关重要的[^1]。 #### 三、函数与作用域 掌握函数定义、调用方式、参数传递机制(按值传递 vs 按地址传递)是非常必要的。此外,还需要熟悉局部变量与全局变量的作用范围及生命周期[^2]。 #### 四、面向对象编程 (OOP) 这是C++特有的考,重在于类的设计原则、继承关系、多态性的实现方法等方面的知识。抽象基类、虚函数的应用场景也是高频考察领域。 #### 五、模板与泛型编程 模板允许程序员编写独立于类型的代码片段,从而提高程序的灵活性重用率。STL标准库广泛采用了这一技术来构建容器类其他工具集;因此熟练运用vector、list等常用容器成为必备技能[^3]。 #### 六、异常处理 学习如何通过try-catch块捕获并妥善处置运行期错误有助于提升软件健壮性。同时也要注意资源泄漏问题,在抛出异常前确保所有已获取资源均被正确释放。 #### 七、文件I/O操作 能够读写文本或二进制形式存储在外存上的信息是一项基本需求。熟悉fstream流类家族成员的功能及其使用技巧可以更好地完成此类任务。 ```cpp #include <iostream> #include <fstream> int main() { std::ofstream outFile; outFile.open("example.txt"); if(outFile.is_open()){ outFile << "This is an example."; outFile.close(); } } ``` #### 八、算法与数据结构 无论是链表还是树形结构亦或是图论模型,都是考查学生综合分析能力解决问题思路的有效手段。针对不同规模输入设计高效解决方案的能力尤为重要。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值