C语言中的一些反智设计
反智,是指与我们日常学到的知识有冲突的处理方式,C语言这样设计的原因是节省代码容量。
i++和++i
-
对于普通的独立语句;i++和++i的效果是一样的,结果也一样
i++; 等效于i = i + 1;
++i;等效于i = i + 1;
int i = 0;
i++;
printf("%d\n", i); // i的值变为原来加1
i = 0;
++i;
printf("%d\n", i); // i的值变为原来加1 -
对于赋值语句,i++先执行赋值操作,然后i的值变为原来的加1,而++i是i的值先变为原来的值加1,然后执行赋值操作
i = 1;
y = i++;
// 上面的语句等效于
// i = 1;
// y = i;
// i++;
// 执行完后的结果应该是 i = 2; y = 1;
i = 1;
y = ++i;
// 上面的语句等效于
// i = 1;
// i++;
// y = i;
// 执行完后的结果应该是 i = 2; y = 2;
// example
int main(void)
{
int i = 1;
int y;
// y = i++;
y = ++i;
printf("i=%d, y=%d\n", i, y);
return 0;
}
-
用于判断表达式
-
i++; 先执行判断后加1
-
++i; 先执行加1操作后执行判断
-
int main()
{
int i=0;
while(++i && i < 3) // i++ ++i 的结果不一样
{
printf("i=%d\n",i);
}
printf("i=%d\n",i);
}
-
i-- 和 --i 的情况与 i++和 ++i的情况是类似的
副作用
副作用是对数据对象或文件的修改。如:states = 50, 它的副作用是将变量states的值设为50。从C语言的角度看,主要目的是表达式求值,给出表达式4 + 6
,C会对其求值得10,给出表达式states = 50
,C会对其求值为50。对该表达式的副作用是把变量states的值改为50。跟赋值运算符(=
)一样,递增运算符(++
)和递减运算(--
)也有运算符,使用它们的目的主要就是使用其副作用。
这里我们可以简单理解副作用就是对变量值的修改
复杂表达式求值顺序
问题:在求解一个复杂表达式的值时候,求解这个表达式的值的子表达式的值的时候存在一个先计算哪个子表达式的问题。
(1 + 1) + (3 + 3)
//(1 + 1) + (3 + 3) : (2) + (3 + 3) ,(2) + (6), (8)
//(1 + 1) + (3 + 3) : (1+1) + (6), (2)+(6), (8)
如果是如下表达式呢?
int x = 1;
y = (4 + x++) + (6 + x++);
规则
-
符合标准的C编译器可以在两个连续的序列点之间以任意顺序计算子表达式
-
C编译器在求解一个表达式的值的时候,核心规则在于序列点(sequence point),序列点是指程序执行过程中修改变量值的最晚时刻。(在该点上所有的副作用都在进入下一步之前完成)
-
序列点之前的表达式总是在序列点之后的表达式之前求值
序列点
序列点是程序运行中一个特殊的点。
-
C标准规定:在两个序列点之间,一个对象所保存的值最多只能被修改一次。
-
在两个序列点之间,连续两次修改,并且访问该变量,会产生不确定性,产生未定义行为。
int main()
{
int i = 10;
int j;
j = i;
j = i++ * i++; // j的值是不确定的,取决于编译器,所以在书写代码的时候不要书写这种表达式
return 0;
}
序列点有:
-
完整表达式的尾部
int x = 1;
int y = 2;
x++; // 分号后面是一个序列点
y = x; // 上面表达式对x产生的副作用已经生效,所以x的值为2
printf("%d\t%d", x, y);
int guest = 1;
// 表达式 guests++ < 10是一个完整表达式,该表达式的结束就是一个序列点,所以在printf()之前发生副作用,也就是会递增guests
// guests++ 表明 guests先与10进行比较,然后再自增
while(guests++ < 10)
{
printf("%d \n", guests);
}
int i = 0;
for(i = 2; i < 10; i++) // for语句中的三个表达式都是完整表达式
if(a && --a)
-
&&,||,三目运算符(?:),以及逗号操作符处
int main(int argc, char *argv[])
{
int a = 1;
// 序列点前的表达式一定在序列点后的表达式前计算
// if (1 + 2 || 3 * 4)
// 1 > 2 ? (1 + 2) : (2 * 3)
// 1, 2 + 3, 3 * 4
if(a-- && a)
{
printf("a = %d\n",a);
}
return 0;
}
-
分隔函数参数的逗号不是序列点。
int i = 2;
i = i++;
printf("%s %s\n", argv[i = 0], argv[++i]);
//先进行i = 0 还是 ++i是不确定的
-
函数调用中对所有实际参数的求值完成之后(进入函数体之前)
未定义行为
未定义行为、二义性问题
例如:南京市长江大桥 如果市长作为一个词,长江大桥作为一个词,得到两种意思。
未定义:包含多个不确定副作用的代码行为称为未定义。
int main()
{
int i = 2;
i = i++ + ++i - --i - i--;
printf("i=%d\n", i); // i = 0 (使用的是gcc编译器测试) 未定义行为
return 0;
}
int x = 1;
y = (4 + x++) + (6 + x++);
// 4 + x++ 不是一个完整表达式
// (4 + x++) + (6 + x++); 是一个完整表达式
// x的值为3 y的值不确定
int main(void)
{
int a, b;
int c;
c = 1;
a = c++ + ++c; // 两个连续的序列点之间对一个变量进行了两次修改,是未定义行为,结果有二义性,取决于编译器。
// a = c++;
// a += c++;
c = 1;
b = ++c + c++;
printf("%d\t%d\n", a, b);
c = 1;
printf("%d\t%d\t%d\n", c++ + ++c, c++ + ++c, ++c + c++);
return 0;
}
因为在“++ c++ c++”中,c的修改之间没有序列点,所以有可能以多个顺序执行求值步骤,从而导致语句不明确,不同的编译器有不同的执行顺序,所以不要使用上面的写法或者加入序列点
如何避免未定义行为
-
确保一个表达式最多只修改一个对象
-
如果一个变量在一个表达式中出现一次以上且在该表达式中被修改,确保该对象的所有的读访问都被用于计算它的最终值。
i = i + 1;
i = i++; // 在一个表达式中改变变量的值两次以上
i+=e
i += e; // 简洁写法
i = i + e
// 以上两个语句是等价的
// 类似的还有
i -= e; // 等价于 i = i - e;
i /= e;
i *= e;
i %= e;
=与==
赋值与相等
= 是赋值符号,例如a=1;就是把1赋值给a
== 是判断符号,例如0==1;结果为假,即是0
int main()
{
int a=0;
a=1;
printf("%d\n",a);
if(a==0){
printf("1\n");
}
else printf("0\n");
return 0;
}
赋值语句表达式
a=b;是将b赋值给a;
(a=b)==c是先将b的值赋值给a在判断与c相不相等
int main()
{
int a=0,b=1,c=1;
if((a=b)==c){
printf("1\n");
}
else printf("0\n");
return 0;
}
注意 a=b==c以及c==(a=b)也是可以的,但c==a=b是不可以的,因为赋值语句的左边不能是表达式
可以连续赋值,但不能连续初始化
int a=b=1;就是错误的
int a,b;
a=b=1;这样就是可以的
逗号表达式
逗号表达式是一种特殊的运算符,逗号运算符优先级最低,它将两式连接起来,如(1+2,4+5)称为逗号表达式,求解过程先表达式1,后表达式2,整个表达式值是表达式2的值,如上式的值是7;
int main()
{
int a;
a=(1+2,3+4);
printf("%d\n",a);
return 0;
}
int main()
{
int a,b,c,d;
a=1,b=1;
d=(c=a++,b++,++b);
printf("%d,%d,%d,%d\n",a,b,c,d);
return 0;
}
赋值表达式优先级高于逗号表达式
注意逗号表达式的要领:
1, 从左到右逐个计算;
2, 逗号表达式作为一个整体,它的值为最后一个表达式的值;
3, 逗号表达式的优先级别在所有运算符中最低;
条件表达式
条件表达式a?b:c;的意思是如果表达式a为真,则表达式值为b,如果表达式a为假,则表达式为c,条件语句
if(a>b) max=a;
else max=b;
用条件表达式写为
max=a>b?a:b;
注意条件运算符?和:是一对运算符,不能分开使用
int main()
{
int a=0,b=1,c=2,max;
max=a>b?a:b>c?b:c;
printf("%d\n",max);
return 0;
}
条件运算符的结合方向是自右至左,如上图例,上例是一个条件表达式的嵌套,从右向左算,先算b,c之间的大值,再将这个大值与a相比取大值赋值给max,总体就是求a,b,c中的最大值
秦九韶算法
计算一个一元n次多项式求值
上面的式子转换为求n个一次多项式的值,只需做n次乘法和n次加法
举个例子:
1365 = 1 * 10^3 + 3 * 10^2 + 6 *10^1 + 5
5631 = 5 * 10^3 + 6 * 10^2 + 3 * 10^1 + 1
int reverseNum(int num)
{
int reverse_num = 0;
int digit; // 记录该数的每一位
int digit_count = 0; // 记录该数是几位数
// 这里选用do while循环结构,因为一个数至少有一位,do while循环至少会执行一次
do{
digit = num % 10; // 将num最末尾分离出来
reverse_num = reverse_num * 10 + digit;
num /= 10; // 将num的末尾去除
digit_count++;
}while(num != 0);
return reverse_num;
}
int main(void)
{
int n;
int reverse_num;
scanf("%d", &n);
if (n % 10 == 0)
{
printf("The number cannot be changed.");
}
else
{
reverse_num = reverseNum(n);
printf("%d", reverse_num);
}
return 0;
}