文章目录
i++和++i的区别
- 对于普通独立的语句,i++和++i是一样的,如:
- i++; 等效于i=i+1;
- ++i; 等效于i=i+1;
- 对于赋值语句,i++先赋值后加1,++i先加1后赋值,如:
- i=1; y=i++; 等效于:i=1; y=i; i=i+1; 则执行完后 y=1; i=2;
- i=1; y=++i; 等效于:i=1; i=i+1; y=i; 则执行完后 y=2; i=2;
++i 是先加后赋值;i++ 是先赋值后加;++i和i++都是分两步完成的。
因为++i 是后面一步才赋值的,所以它能够当作一个变量进行级联赋值,++i = a = b,即 ++i 是一个左值;i++ 的后面一步是自增,不是左值。
形象的理解可以是i++先做别的事,再自己加1,++i 先自己加1,再做别的事情。
int main()
{
int a = 5;
int i = 2;
++i = a;
cout << i << endl; // 5
return 0;
}
- i++ 不能作为左值,而++i 可以。(一般来说,左值是可以放到赋值符号左边的变量。)
- 左值是对应内存中有确定存储地址的对象的表达式的值。
- 右值是所有不是左值的表达式的值。
- 能否被赋值不是区分左值与右值的依据。 比如,C++的const左值是不可赋值的;而作为临时对象的右值可能允许被赋值。
左值与右值的根本区别在于:是否允许取地址&运算符获得对应的内存地址。
提问:
- 为什么(i++)不能做左值,而(++i)可以。
看底层源码:
// 前缀形式:
int& int::operator++() // 这里返回的是一个引用形式,就是说函数返回值也可以作为一个左值使用
{
*this += 1; // 函数本身无参,意味着是在自身空间内增加1的
return *this; // 反回值
}
// 后缀形式:
const int int::operator++(int) // 函数返回值是一个非左值型的,与前缀形式的差别所在。
{
int oldValue = *this; //函数带参,且有另外的空间开辟
++(*this); // 增加
return oldValue; // 返回临时对象的值
}
i++和++i哪个效率高
这个问题是分两种情况的:
- 对于内置数据类型,以现在的编译器的优化水平,前置++和后置++没区别的,这个可以通过看汇编代码证明。
- 对于自定义数据类型,像STL,前置++的效率要高于后置++,所以STL中关于iterator都是前置++的。
只有在必要时才使用后置操作符。
因为前置操作需要做的工作更少,只需要加1后返回加1后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。对于int对象和指针,编译器可优化掉这项额外工作。但是对于更多的复杂迭代器类型,这种额外工作可能会花费更大代价。
因此,养成使用前置操作这个好习惯,就不必担心性能差异的问题。
知识点习题
在 printf() 函数中,后置++,存储的是一份拷贝出来的值,前置++,存储的是变量本身(VS编辑器)
本题相信大多数人都是对D选项有疑惑:
D选项,由++或者–运算的顺序是从右向左,故先计算++i,++i的返回值为 i本身,本应该是4,但是后面的运算却影响i的值,另外printf输出流的缓存栈(下面例题会有涉及)是在所有表达式计算完后再入栈的,只需要知道首先入栈的是i的地址里面存的值,此时i的值为5。
输出时从栈顶开始,相当于: int i = 3; ++i; ++i; printf("%d,%d",i,i);所以是 5,5;
再举一个例子,int i = 1; printf("%d,%d", i += 2, i *= 3); 在输出i之前先进行了i *= 3和 i += 2;最终i = 5;所以结果是5,5;
下面是所有选项的详解:
- A选项考察C语言的内存对齐
内存对齐是一个比较常考察的一个部分。下面的分析都是基于32位机器考虑的。这里要注意两点:
- 所有的空间均需要为最大类型大小的整数倍
- 内存需要保持对齐
对于题目中的:
struct X {
short s;
int i;
char c;
};
其中需要考虑变量s和c的对齐,很容易发现,应该是12。
但是有一些编译器存在对该形式的优化,在实际分配内存的时候,会将该结构体按照如下的结构进行分配:
struct X {
short s;
char c;
int i;
};
这样分配的内存会减少到8。
- B选项考察double类型的比较
这一点应该是课本上的基本常识,是由于双精度丢失,所以只能够采用比较两个差绝对值是否小于一个很小的数字来确定。
判断一个双精度浮点数是否为0:if( abs(f) <= 1e-15 )
- C选项中,在标准库中,对于char数组是无法采用这种赋值方式进行赋值的。
C数组初始化两种方式:
- 一种逐个赋值 char a[14] ={‘H’,‘e’,‘l’,‘l’,‘o’}
- 另一种 char a[14] = “Hello, world!”
- D选项中,主要考察下面的内容
int i=1;
printf("%d,%d\n",i--,i++);
步骤如下:
- 把i的值1存入缓存器a
- i值加1,i=i+1=2;
- 把i的值2存入缓存器
- i值减1,i=i-1=1
- 把缓存器[ebp-0E8h]=1,入栈
- 把缓存器[ebp-0ECh]=2,入栈
故,打印输出为 2,1
总结:
由 ++或者 --运算的顺序是从右向左,故先计算i++,i++在计算过程中会产生缓存区,返回的值就是缓存区的值
例:
1.
int x=2, y=3;
printf("%d,%d\n",(x++)+y,++y); // 等价x++ + y
第一个入栈的是y值本身,而其他表达式对其无影响,故入栈y==4
第二个入栈的是表达式的缓存区的值,为x+y=2+4=6,故入栈 6
输出值为6 4
2.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i = 1;
printf("%d,%d\n", ++i, ++i); //3,3
printf("%d,%d\n", ++i, i++); //5,3
printf("%d,%d\n", i++, i++); //6,5
printf("%d,%d\n", i++, ++i); //8,9
system("pause");
return 0;
}
首先是函数的入栈顺序从右向左入栈的,计算顺序也是从右往左计算的,不过都是计算完以后在进行的压栈操作:
- 对于第5行代码,首先执行++i,返回值是i,这时i的值是2,再次执行++i,返回值是i,得到i=3,将i压入栈中,此时i为3,也就是压入3,3;
- 对于第6行代码,首先执行i++,返回值是原来的i,也就是3,再执行++i,返回值是i,依次将3,5压入栈中得到输出结果
- 对于第7行代码,首先执行i++,返回值是5,再执行i++返回值是6,依次将5,6压入栈中得到输出结果
- 对于第8行代码,首先执行++i,返回i,此时i为8,再执行i++,返回值是8,此时i为9,依次将i,8压入栈中,得到输出结果。
- 在C/C++ 中++(a++)为什么错误
正确答案:a++是a的自增,结果是a+1,这不是一个左值,不能再进行自增操作。
答案解析:
比如:
a+3=10;
这个代码是错误的,因为这个不能确定一个储存单元。
你不能说出a+3的位置,所以这个表达式只能放在右侧,不能进行赋值赋值。
可以这样解释:
- (a++)本身是一个表达式没有问题
- ++x 是单目前缀运算符,需要作用到一个变量上
- 但它不能作用到一个表达式上,所以++(a++)不行
- 通过两句a++; ++a;应该也可以达到目的。
- 以下代码在C与C++的编译器下编译会出现什么问题。
int main()
{
int a = 5;
int i = 2;
++i = a;
return 0;
}
正确解答:
- 在C++中可以正常编译,且
i = 5
- 在C中
++i = a;
会报错 error: expression is not assignable