C,C++电容器C的表达式式求值顺序 裘老的解释

posts - 331,&
comments - 16,&
trackbacks - 0
随笔分类 - CC++
摘要: C++浅拷贝和深拷贝的区别
21:22 11454人阅读 评论(6) 收藏 举报c++deleteclass编译器cc++默认的拷贝构造函数是浅拷贝浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝,如:class A { public: A(int _data) : data(_data){} A(){}private: };int main() { A a(5), b = // 仅仅是数据成员之间的赋值 }这一句b =就是浅拷贝,执行完这.
jeans chen 阅读(53) |
摘要: 可以先参考一个帖子:http://bbs.csdn.net/topics/?page=1
jeans chen 阅读(21) |
jeans chen 阅读(29) |
jeans chen 阅读(19) |
摘要: 1)2)3)4)
jeans chen 阅读(18) |
jeans chen 阅读(20) |
jeans chen 阅读(24) |
jeans chen 阅读(21) |
jeans chen 阅读(20) |
摘要: http://bbs.csdn.net/topics/adlayadlay等级: #9 得分:0 回复于:
11:19:35 引用6楼的回复:引用4楼的回复:通过显示实例化你可以把模版的实现放在cpp里,要不然的话模版实现必须放在.h里面.好像不显示实例化,我把模板放在main.cpp里面也没报错啊,比如你把刚才我那句注释掉,也可以运行的.你是只有一个cpp的情况.如果有多个cpp文件再使用这个模版,你必须把它放在头文件里,然后每个cpp都要#include这个头文件.显示实例化之后头文件里只需要声明,然后在其中一个cpp里面实现并显示实例化,其它的..
jeans chen 阅读(170) |
jeans chen 阅读(16) |
jeans chen 阅读(17) |
摘要: CHAPTER 13:面向对象程序设计方法概述13.5组合composition用于表示类的整体与部分关系。例如主机和显示器、鼠标等组合成计算机继承则表示类的“一般与特殊”的关系类的组合特性表现为两种:聚合(has-a) 和 关联(holds-a)聚合:比如head类有成员Eye,Nose,Mouth,Ear关联:就是类之间的引用,详细看第九章的关于结构的论述struct A{ char *pN//A holds-a string B *//A holds-a B};struct B{ A *//B holds-a A B *pNex
jeans chen 阅读(42) |
摘要: #include #include #includeint main(){ double (*fp[2])(double) = {sqrt,fabs};//[]优先级大于* for(int i = 0;i & 2;i++) { cout && fp[i](9) && } for(int i = 0;i & 2;i++) { cout && (*fp[i])(9) &&//[]优先级大于* } for(int i = 0;i & 2;i++) { ...
jeans chen 阅读(41) |
摘要: http://blog.csdn.net/waken_ma/article/details/4004972先转两篇文章:拨开自定义operator new与operator delete的迷雾 C++允许用户通过自定义operator new和operator delete的方式来更改new与delete表达式的某些行为,这给了程序员定制内存管理方案的自由。但是享受这种自由的时候必须遵守一定的规范,具体可以参见《Effective C++ 2nd》的相关条款。本文补充解释一些特别容易引起误解的问题。 operator new和operator delete都有其正规形式(normal sign
jeans chen 阅读(29) |
摘要: http://blog.csdn.net/waken_ma/article/details/4007914C++关键字new学习很多新手对C++关键字new可能不是很了解吧,今天我一起来学习一下。 “new”是C++的一个关键字,同时也是操作符。关于new的话题非常多,因为它确实比较复杂,也非常神秘,下面我将把我了解到的与new有关的内容做一个总结。new的过程当我们使用关键字new在堆上动态创建一个对象时,它实际上做了三件事:获得一块内存空间、调用构造函数、返回正确的指针。当然,如果我们创建的是简单类型的变量,那么第二步会被省略。假如我们定义了如下一个类A:class A{pu
jeans chen 阅读(24) |
摘要: 关于类的前向声明( 11:08:09)转载▼标签:杂谈分类: 技术记录 前向声明的定义:有些时候我们可以声明一些类但是并不去定义它,当然这个类的作用也很有限了。比如声明一个foo类,这个声明,有时候也叫做前向声明(forward declaration),在声明完这个foo类之后,定义完这个foo类之前的时期,foo类是一个不完全的类型(incomplete type),也就是说foo类是一个类型,但是这个类型的一些性质(比如包含哪些成员,具有哪些操作)都不知道。因此这个类的作用也很有限.(1)不能定义foo类的对象。(2)可以用于定义指向这个类型的指针
jeans chen 阅读(60) |
摘要: http://stackoverflow.com/questions/5849457/using-namespace-in-c-headershttp://stackoverflow.com/questions/5849457/using-namespace-in-c-headers 這篇有解釋 避免在 header用 using namespace, 以免header被其他人include後 造成Conflictn all our c++ courses, all the teachers always put right after the ...
jeans chen 阅读(100) |
摘要: 当出现访问类的函数或者需要确定类大小的时候,才需要用头文件(使用其类定义) http://blog.csdn.net/clever101/article/details/4751717看到这个警告,我想你一定悟到了什么。下面我说说我的结论:类的前置声明和包含头文件的区别在于类的前置声明是告诉编译器有这种类型,但是它没有告诉编译器这种类型的大小、成员函数和数据成员,而包含头文件则是完全告诉了编译器这种类型到底是怎样的(包括大小和成员)。这下我们也明白了为何前置声明只能使用指针来进行,因为指针大小在编译器是确定的。上面正因为前置声明不能提供析构函数信息,所以编译器提醒我们:“CSymbol”类型的
jeans chen 阅读(91) |
摘要: P143:“声明的依赖性&替换“定义的依存性”
jeans chen 阅读(29) |
jeans chen 阅读(30) |
摘要: 函数名: lseek功 能: 移动文件读/写指针头文件:#include #include 用 法: off_t lseek(int handle, off_t offset, int fromwhere);所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。使用 lseek 函数可以改变文件的 cfo 。lseek 的以下用法返回当前.
jeans chen 阅读(86) |
jeans chen 阅读(19) |
摘要: strtoulstrtoul (将字符串转换成无符号长整型数)相关函数atof,atoi,atol,strtod,strtol表头文件#include定义函数unsigned long strtoul(const char *nptr,char **endptr,int base);函数说明strtoul()会将参数nptr字符串根据参数base来转换成无符号的长整型数。 参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制数等。当 base值为0时会根据情况选择用哪种进制:如果第一个字符是'0',
jeans chen 阅读(196) |
摘要: 187页private继承意味implementedin-terms-of(根据某物实现出)。如果你让class D以private形式继承class B,你的用意为了采用class B内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。Private继承纯粹是一种实现技术,(这就是why继承自一个private base class的每样东西在你的class内都是private:因为他们都只是实现枝节而已。)Private继承在软件“设计”层面上没有意义,其意义只及于软件实现层面。懒了 不想继续看了。。。。。。哈哈哈哈和黑黑
jeans chen 阅读(25) |
摘要: Model &has-a&or “is-implemented-in-terms-of” through composition
jeans chen 阅读(25) |
摘要: C++ list 类学习笔记 分类: C++
00:12 7819人阅读 评论(0) 收藏 举报listc++iteratorvectorcconstructor双向循环链表list list是双向循环链表,,每一个元素都知道前面一个元素和后面一个元素。在STL中,list和vector一样,是两个常被使用的容器。和 vector不一样的是,list不支持对元素的任意存取。list中提供的成员函数与vector类似,不过list提供对表首元素的操作 push_front、pop_front,这是vector不具备的。和vector另一点不同的是,list的迭代器不会...
jeans chen 阅读(28) |
摘要: C++标准库set类型 分类: C++编程语言
10:53 909人阅读 评论(0) 收藏 举报目录(?)[-]在set中添加元素从set中获取元素set容器只是单纯的键的集合,键必须为一。set容器不支持下标操作,而且没有定义maped_type类型。在set容器中,value_type不是pair类型,而是与key_type类型相同的类型。1.在set中添加元素set set1;set1.insert(&the&);set1.insert(&end&);也可以添加一对迭代器,如下:set1.insert(set33.begin(
jeans chen 阅读(51) |
摘要: 绝不重新定义继承而来的缺省参数值静态类型动态类型
jeans chen 阅读(25) |
jeans chen 阅读(26) |
摘要: http://bbs.csdn.net/topics/[置顶] [推荐] C,C++表达式求值顺序 裘老的解释。 [问题点数:300分] 最近这问题有从日经变时经的趋势,这里贴出裘老的解释。求加精。---------------------------------------------------------------------------------------------裘宗燕:C/C++语言中的表达式求值经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?”m=1;n=m+++m++;最近有位不相识的朋友发email给我,问为什么在某个C...
jeans chen 阅读(23) |
摘要: 本质上是说了:Template Pattern & Strategy Pattern详细见《C++设计模式 23种设计模式.pdf 55页》宁可要组合 不要继承。——————————————————————————————————————————要先看懂策略模式
jeans chen 阅读(24) |
摘要: [EffectiveC++]item34:区分接口继承和实现继承
jeans chen 阅读(22) |
摘要: 先看看:ZT C++ 重载、覆盖和隐藏的区别http://www.cnblogs.com/jeanschen/p/3405987.html隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下: 1) 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。 2) 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。#includeclass Base{private:public: vo..
jeans chen|
摘要: 81页最后一段 到最后没看懂。
jeans chen|
摘要: C++内部类
14:09:07分类: C/C++ #includeclass A{public:A() : _a(10) {}private:class B{public:void show(const A &aa) { cout && &_a: & && aa._a &&}};private:int _public:B};int main(int argc, char *argv[]){cout&&&Hello, w
jeans chen 阅读(805) |
摘要: c++ primer658页嵌套类最常用于定义执行类,
jeans chen 阅读(34) |
摘要: extern &C&用法详解
19:14:12分类: C/C++1.前言:时常在cpp的代码之中看到这样的代码:#ifdef __cplusplusextern &C& {#endif//一段代码#ifdef __cplusplus}#endif 这样的代码到底是什么意思呢?首先,__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern &C&{和}处理其中的代码。 要明白为何使用extern &quot
jeans chen 阅读(34) |
摘要: 不知道哪里转的。呵呵 抱歉C++是最难的语言。这个世界上最难的编程语言可能非C++莫属了。你千万不要以为几天就可以学好C++,C++的学习曲线是相当BT的,你可以看看这篇文章。C++是一门很自由的语言,自由到了有点BT和恐怖的地步。我甚至认为C++并不是一门成熟的编程语言,因为太容易犯错了。所以,你一定要在一开始就要有很小心谨慎的态度,并把C++当成一种难以训服的猛兽来看待。 多问“为什么要这样”的问题。学习C++一定要多问几个“为什么是这样”,“凭什么要这样”的问题。比如:很多人知道C++有拷贝构造函数和初始化列表,但你真的知道为什么要有拷贝构造函数?为什么要有初始化列表吗?为什么要有tem
jeans chen 阅读(39) |
摘要: Declare non-member functions when type conversions should apply to all parameters.104页只有当参数被列于参数列(parameter list)内,这个参数才是隐式类型转换的合格参与者。这就是为什么上述第一次调用可通过编译,第二次调用则否。因为第一次调用伴随一个放在参数列内的参数,第二次调用则否。
jeans chen 阅读(32) |
摘要: 99页导致较大封装性的是non-member non-friend函数,因为它并不增加“能否访问class内之private成分”的函数数量。
jeans chen 阅读(44) |
摘要: 将成员变量隐藏在函数接口的背后,可以为“所有可能的实现”提供弹性,假设我们有一个public成员变量,而我们最终取消了它,多少代码可能会被破坏呢?那是一个不可知的大量。protected成员变量就像public成员一样缺乏封装性(所有使用它的derived都会被破坏)
jeans chen 阅读(27) |
jeans chen 阅读(38) |
jeans chen 阅读(29) |
摘要: Store newed objects in smart pointers in standalone statements
jeans chen 阅读(20) |
jeans chen 阅读(20) |
摘要: 在资源管理类中提供对原始资源的访问
jeans chen 阅读(28) |
摘要: 【转】C++ operator两种用法 C++,有时它的确是个耐玩的东东,就比如operator,它有两种用法,一种是operator overloading(操作符重载),一种是operator casting(操作隐式转换)。1、操作符重载C++可以通过operator实现重载操作符,格式如下:类型T operator 操作符 (),比如重载+,比如下面这个例子template class A{public: const T operator+(const T& rhs) { return this-&m_ + }private: T m_;};又比如STL中的函数
jeans chen 阅读(3898) |
摘要: baidu百科RAII百科名片RAII,也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象。目录RAII简介RAII的分类RAII实际应用RAII与STL容器编辑本段RAII简介RAII[1](Resource Acquisition Is Initialization),也成为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源
jeans chen 阅读(46) |
摘要: 为什么拷贝构造函数的参数必须是引用?( 17:31:21)转载▼例子:inline Account::Account( const Accout &rhs ): _balance( rhs._balance ){ _name = new char[strlen(rhs._name)+1 ]; strcpy(_name, rhs._name ); // 不能拷贝 rhs._acct_nmbr _acct_nmbr = get_unique_acct_nmbr();}int main(){Account acct2( acct1 );}问题:为什么拷贝构造函数Accou
jeans chen 阅读(114) |
摘要: 在小书C++中,4.2.2 派生类的构造函数和析构函数的构造规则(103页)在定义派生类对象时,构造函数执行顺序如下:基类的构造函数对象成员的构造函数派生类的构造函数。
jeans chen 阅读(30) |
摘要: 令operator=返回一个reference to *this
jeans chen 阅读(27) |
jeans chen 阅读(31) |
jeans chen 阅读(24) |
摘要: 速度 #includeclass TextBlock{private:public: TextBlock(string s) { text = } const char& operator[](size_t position) const {return text[position]; } char& operator[](size_t position) {return text[position]; }};int main(){ TextBlock...
jeans chen 阅读(33) |
jeans chen 阅读(31) |
摘要: class B{public: explicit B(int x = 0,bool b = true); //default构造函数 };explicit可以阻止用来执行隐式类型转换,但是可以用来进行显式类型转换。void doSomething (B bObject);doSometing(B(28));//使用B的构造函数将int显式转换(cast)为一个B以促成此一调用copy构造函数被用来“以同型对象初始化自我对象”,copy assignment操作符被用来“从另一个同型对象中拷贝其值到自我对象。”声明式(declaration)是告诉编译器某个东西的名称和类型(type...
jeans chen 阅读(86) |
摘要: 接受指针的构造函数为explicit构造函数,所以必须使用初始化的直接形式来创建auto_ptr对象:auto_ptr pi = new int(1024);//errorauto_ptr pi(new int(1024));//ok:uses direct initialization
jeans chen 阅读(30) |
摘要: classes和templates都支持接口和多态,interfaces and polymorphism对classes而言接口是显示的explicit,以函数签名为中心。多态则是通过virtual函数发生于运行期对templates参数而言,接口是隐式的implicit,奠基于有效表达式。多态则是通过templates具现化和函数重载解析function overloading resolution发生于编译期
jeans chen 阅读(43) |
摘要: 28 页C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。构造函数的一个较佳的写法是,使用所谓的member initialization list替换赋值动作。29页但请立下一个规则,规定总是在初值列中列出所有成员变量,以免还得记住哪些成员变量可以无需初值。31页幸运的是一个小小的设计便可以完全消除这个问题。将每个non-local static对象搬到自己的专属函数内(改对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说,non-local static对象被local static
jeans chen 阅读(33) |
摘要: 将某些东西声明为const可以帮助编译器检测出错误用法,const编译器强制实施bitwise constness,但是你code的时候应该使用“概念上的常量性”当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以避免代码重复。0)迭代器类似T*指针,声明迭代器为const就像声明指针为const一样。(即声明一个T* const指针)如果你希望迭代器所指的不能被改动,需要的是const_iteratorvector::iterator iter = vec.begin();//iter作用像T* const*iter = 10;//没问
jeans chen|C,C++表达式求值顺序 裘老的解释。
我的图书馆
C,C++表达式求值顺序 裘老的解释。
& & & & & & & & & & & & & & & 最近这问题有从日经变时经的趋势,这里贴出裘老的解释。求加精。
---------------------------------------------------------------------------------------------
裘宗燕:C/C++&语言中的表达式求值
经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?”
m&=&1;&n&=&m+++m++;
最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5:
a&=&4;&cout&&&&a++&&&&a;
C++&不是规定&&&&操作左结合吗?是C++&书上写错了,还是这个系统的实现有问题?
要弄清这些,需要理解的一个问题是:如果程序里某处修改了一个变量(通过赋值、增量/减量操作等),什么时候从该变量能够取到新值?有人可能说,“这算什么问题!我修改了变量,再从这个变量取值,取到的当然是修改后的值!”其实事情并不这么简单。
C/C++&语言是“基于表达式的语言”,所有计算(包括赋值)都在表达式里完成。“x&=&1;”就是表达式“x&=&1”后加表示语句结束的分号。要弄清程序的意义,首先要理解表达式的意义,也就是:1)表达式所确定的计算过程;2)它对环境(可以把环境看作当时可用的所有变量)的影响。如果一个表达式(或子表达式)只计算出值而不改变环境,我们就说它是引用透明的,这种表达式早算晚算对其他计算没有影响(不改变计算的环境。当然,它的值可能受到其他计算的影响)。如果一个表达式不仅算出一个值,还修改了环境,就说这个表达式有副作用(因为它多做了额外的事)。a++&就是有副作用的表达式。这些说法也适用于其他语言里的类似问题。
现在问题变成:如果C/C++&程序里的某个表达式(部分)有副作用,这种副作用何时才能实际体现到使用中?为使问题更清楚,我们假定程序里有代码片段“...a[i]++&...&a[j]&...”,假定当时i与j的值恰好相等(a[i]&和a[j]&正好引用同一数组元素);假定a[i]++&确实在a[j]&之前计算;再假定其间没有其他修改a[i]&的动作。在这些假定下,a[i]++&对&a[i]&的修改能反映到&a[j]&的求值中吗?注意:由于&i&与&j&相等的问题无法静态判定,在目标代码里,这两个数组元素访问(对内存的访问)必然通过两段独立代码完成。现代计算机的计算都在寄存器里做,问题现在变成:在取&a[j]&值的代码执行之前,a[i]&更新的值是否已经被(从寄存器)保存到内存?如果了解语言在这方面的规定,这个问题的答案就清楚了。
程序语言通常都规定了执行中变量修改的最晚实现时刻(称为顺序点、序点或执行点)。程序执行中存在一系列顺序点(时刻),语言保证一旦执行到达一个顺序点,在此之前发生的所有修改(副作用)都必须实现(必须反应到随后对同一存储位置的访问中),在此之后的所有修改都还没有发生。在顺序点之间则没有任何保证。对C/C++&语言这类允许表达式有副作用的语言,顺序点的概念特别重要。
现在上面问题的回答已经很清楚了:如果在a[i]++&和a[j]&之间存在一个顺序点,那么就能保证a[j]&将取得修改之后的值;否则就不能保证。
C/C++语言定义(语言的参考手册)明确定义了顺序点的概念。顺序点位于:
1.&每个完整表达式结束时。完整表达式包括变量初始化表达式,表达式语句,return语句的表达式,以及条件、循环和switch语句的控制表达式(for头部有三个控制表达式);
2.&运算符&&&、||、?:&和逗号运算符的第一个运算对象计算之后;
3.&函数调用中对所有实际参数和函数名表达式(需要调用的函数也可能通过表达式描述)的求值完成之后(进入函数体之前)。
假设时刻ti和ti+1是前后相继的两个顺序点,到了ti+1,任何C/C++&系统(VC、BC等都是C/C++系统)都必须实现ti之后发生的所有副作用。当然它们也可以不等到时刻ti+1,完全可以选择在时段&[t,&ti+1]&之间的任何时刻实现在此期间出现的副作用,因为C/C++&语言允许这些选择。
前面讨论中假定了a[i]++&在a[i]&之前做。在一个程序片段里a[i]++&究竟是否先做,还与它所在的表达式确定的计算过程有关。我们都熟悉C/C++&语言有关优先级、结合性和括号的规定,而出现多个运算对象时的计算顺序却常常被人们忽略。看下面例子:
(a&+&b)&*&(c&+&d)&fun(a++,&b,&a+5)
这里“*”的两个运算对象中哪个先算?fun及其三个参数按什么顺序计算?对第一个表达式,采用任何计算顺序都没关系,因为其中的子表达式都是引用透明的。第二个例子里的实参表达式出现了副作用,计算顺序就非常重要了。少数语言明确规定了运算对象的计算顺序(Java规定从左到右),C/C++&则有意不予规定,既没有规定大多数二元运算的两个对象的计算顺序(除了&&、||&和&,),也没有规定函数参数和被调函数的计算顺序。在计算第二个表达式时,首先按照某种顺序算fun、a++、b和a+5,之后是顺序点,而后进入函数执行。
不少书籍在这些问题上有错(包括一些很流行的书)。例如说C/C++&先算左边(或右边),或者说某个C/C++&系统先计算某一边。这些说法都是错误的!一个C/C++&系统可以永远先算左边或永远先算右边,也可以有时先算左边有时先算右边,或在同一表达式里有时先算左边有时先算右边。不同系统可能采用不同的顺序(因为都符合语言标准);同一系统的不同版本完全可以采用不同方式;同一版本在不同优化方式下,在不同位置都可能采用不同顺序。因为这些做法都符合语言规范。在这里还要注意顺序点的问题:即使某一边的表达式先算了,其副作用也可能没有反映到内存,因此对另一边的计算没有影响。
回到前面的例子:“谁知道下面C语句给n赋什么值?”
m&=&1;&n&=&m++&+m++;
正确回答是:不知道!语言没有规定它应该算出什么,结果完全依赖具体系统在具体上下文中的具体处理。其中牵涉到运算对象的求值顺序和变量修改的实现时刻问题。对于:
cout&&&&a++&&&&a;
我们知道它是
(cout.operator&&&(a++)).operator&&&&(a);
的简写。先看外层函数调用,这里需要算出所用函数(由加下划线的一段得到),还需要计算a的值。语言没有规定哪个先算。如果真的先算函数,这一计算中出现了另一次函数调用,在被调函数体执行前有一个顺序点,那时a++的副作用就会实现。如果是先算参数,求出a的值4,而后计算函数时的副作用当然不会改变它(这种情况下输出两个4)。当然,这些只是假设,实际应该说的是:这种东西根本不该写,讨论其效果没有意义。
有人可能说,为什么人们设计&C/C++时不把顺序规定清楚,免去这些麻烦?C/C++&语言的做法完全是有意而为,其目的就是允许编译器采用任何求值顺序,使编译器在优化中可以根据需要调整实现表达式求值的指令序列,以得到效率更高的代码。像Java那样严格规定表达式的求值顺序和效果,不仅限制了语言的实现方式,还要求更频繁的内存访问(以实现副作用),这些可能带来可观的效率损失。应该说,在这个问题上,C/C++和Java的选择都贯彻了它们各自的设计原则,各有所获(C/C++&潜在的效率,Java更清晰的程序行为),当然也都有所失。还应该指出,大部分程序设计语言实际上都采用了类似C/C++的规定。
讨论了这么多,应该得到什么结论呢?C/C++&语言的规定告诉我们,任何依赖于特定计算顺序、依赖于在顺序点之间实现修改效果的表达式,其结果都没有保证。程序设计中应该贯彻的规则是:如果在任何“完整表达式”(形成一段由顺序点结束的计算)里存在对同一“变量”的多个引用,那么表达式里就不应该出现对这一“变量”的副作用。否则就不能保证得到预期结果。注意:这里的问题不是在某个系统里试一试的问题,因为我们不可能试验所有可能的表达式组合形式以及所有可能的上下文。这里讨论的是语言,而不是某个实现。总而言之,绝不要写这种表达式,否则我们或早或晚会某种环境中遇到麻烦。
后记:去年参加一个学术会议,看到有同行写文章讨论某个C系统里表达式究竟按什么顺序求值,并总结出一些“规律”。从讨论中了解到某“程序员水平考试”出了这类题目。这使我感到很不安。今年给一个教师学习班讲课,发现许多专业课教师也对这一基本问题也不甚明了,更觉得问题确实严重。因此整理出这篇短文供大家参考。
后后记:4年多过去了,许多新的和老的教科书仍然在不厌其烦地讨论在C语言里原本并无意义的问题(如本文所指出的)。希望学习和使用C语言的人不要陷入其中。
相关主题推荐:
相关帖子推荐:
喜欢该文的人也喜欢

我要回帖

更多关于 电容器C的表达式 的文章

 

随机推荐