目录
算术操作符
+ | - | * | / | % |
---|---|---|---|---|
加 | 减 | 乘 | 除 | 取余 |
-
除了%以外,其他的几个操作符都可以用于整数和浮点数
-
对于/操作符,如果两个操作数都为整数,执行整数除法,省略小数点。而只要两端有一个浮点数,则进行浮点数除法,小数点不会被省略。
-
%操作符的两个操作数必须为整数。返回的是余数
#include <stdio.h> int main() { //如果是整数之间的除法,则计算出的也是整数,不会出现小数 int a = 2/4; printf("%d\n",a); //输出0,为什么不是0.5?因为这是整数的除法,省略小数点 //即使使用float类型变量接收,因为是整数除法,所以结果存到变量之后,以%f形式打印,输出的也是整数除法的结果+6个小数点位 float b = 6/5; printf("%f\n",b);//1.000000 //正确做法:除数或者被除数只要一个是浮点数,实行的就是浮点数计算。此时的计算结果就是带小数点的。 //注意:如果这里直接写6.0、5.0、4.0,会被编译器认为是double类型的,而我们是使用的float类型接收,编译器认为double转float会出现精度丢失问题。 //我们可以在6.0、5.0后面加一个f:6.0f、5.0f就是float类型的了。 float e = 10.0f/3.0f; float c = 6.0f/0; float d = 2/4.0f; //float c = 6.0/5.0 这里也可以两端都写成浮点数。 printf("6.0/5=%f 、 2/4.0=%f\n",c,d);//6.0/5=1.200000 、 2/4.0=0.500000 //进行取模预算时,%两端都必须是一个整数 //int m = 7 % 3.0; 编译会报错。所以不能与浮点数取余。 int m = 7 % 3;//1 printf("%d",m); return 0; }
移位操作符
<< | >> |
---|---|
左移操作符 | 右移操作符 |
-
注意:移位操作符的操作符只能是整数。并且移位运算符移动的位数只能是整数,不能移动负数位。
//移位操作符。移动的是二进制位。 // 例如int类型的数据2,int类型占四个字节,也就是32位二进制。左移一下,2(二进制:10)就变成了4(100) #include <stdio.h> int main() { //左移运算符:<< 。移动二进制位,左边多余的一位二进制位丢弃,右边最后补0。 //我们发现,左移一位,数字是原来的2倍。左移三位,数字就是原来的8倍,也就是2的三次方。所以左移一位,数字就*2。 int a = 3; int b = a << 1; printf("%d %d\n",b,a << 3); //6 24 //右移运算符 >>。 //1. 算术右移:右边丢弃,左边补原符号位 。 2.逻辑右移:右边丢弃,左边补0。 int m = 11; int n = m >> 1; printf("%d %d\n",n,m >>3);//5 1 /* * 计算机中,整数在内存中存储的是补码。一个整数的二进制表示有三种:原码、反码、补码。正整数的源码、反码、补码相同。 * 负整数的源码、反码、补码计算关系如下: * -1的原码:10000000 00000000 00000000 00000001 最高位1表示符号位。 * -1的反码:11111111 11111111 11111111 11111110 最高位符号位不变,其余二进制位按位取反 * -1的补码:11111111 11111111 11111111 11111111 反码+1就是补码 * 因为内存中存储的是补码,而我们打印时,我们选择用%d的方式打印,就是打印原码。所以补码-1得到反码 * 得到反码后,最高位符号位不变,其余取反。就得到了源码,最高位是符号位,所以打印出了-1 */ /* * 那我们的编译器是采用算术右移还是逻辑右移呢?我们使用-1来测试一下。因为内存中存储的是补码, * 我们将-1右移一位,。如果是算数右移,则左边补符号位,也就是说左边补1。打印出来还是-1 * 如果是逻辑右移,左边补0,就变成了01111111 11111111 11111111 11111111,打印出来应该是一个很大的数。 * * 打印出-1,说明是算术右移。 */ int p = -1; int q = p >> 1; printf("%d",q);//-1 //右移一位,可以看作是将这个数/2,如果有余数,则省略余数。如25右移一位就是12 return 0; }
位操作符
& | | | ^ |
---|---|---|
按位与 | 按位或 | 按位异或 |
-
位运算符:按二进制位进行运算。 两端的操作数必须是整数
// #include <stdio.h> int main() { int a = 3; int b = 5; //按位与 & 。都为1,才为1 //00000000 00000000 00000000 00000011 3 //00000000 00000000 00000000 00000101 5 //00000000 00000000 00000000 00000001 1 int c = a & b; printf("%d\n",c); //1 //按位或 | 。 有1就为1 //00000000 00000000 00000000 00000011 3 //00000000 00000000 00000000 00000101 5 //00000000 00000000 00000000 00000111 7 c = a | b; printf("%d\n",c); //7 //按位异或 ^ 。 如果对应位相同(如都为1,或都为0),则为0;如果对应位数不相同(一个是1一个是0 或 1个是0一个是1),则为1。 //00000000 00000000 00000000 00000011 3 //00000000 00000000 00000000 00000101 5 //00000000 00000000 00000000 00000110 6 c = a ^ b; printf("%d\n",c); //6 }
-
按位异或的使用:不使用中间变量,交换两个变量的值。
//不使用第三个变量,交换两个变量。 #include <stdio.h> int main() { int a =5; int b =3; printf("交换前:a=%d b=%d\n",a,b);//交换前:a=5 b=3 //异或的结果,与原来的其中一个值异或,可以得到另外一个值。 //111 与011进行异或,得到:110 a = a ^ b; //110与011进行异或,得到101,也就是5,因为要交换,所以赋给b b = a ^ b; //110与101进行异或,得到011,也就是3,赋给a a = a ^ b; printf("交换后:a=%d b=%d\n",a,b);//交换后:a=3 b=5 int c = 4; printf("%d %d\n",c^c,c^0);//c^c=0 c^0=4 /* * 解析: * - 一个数与自己进行异或,则会返回0;如果与0进行异或,则会返回这个数。 * 也就是说a与b异或返回给a,此时a=a^b,而(a^b)^b就是a的值,因为a的值求出来了,此时b的值就是(a^b)^a * - 结论:两个数异或得到第三个数,其中两个异或可以得到零一个。 * 比如:a^b=c ,那么a^c可以得到b的值 ,b^c 就可以得到a的值。 */ //将以上a^b的值定义为一个变量会更好理解: int d,e,f; d = 10; e = 20; //根据异或原理可以得到: f = d^e; d = f^e; e = f^d; printf("交换前:d=%d e=%d\n",d,e); //但是我们要交换值,并且不能借助第三个变量,就是: d = d^e; //此时d中存储的是d与e异或的结果,现在d已经不是原来的值了,但是e还是原来的值。 //也就是说,现在的d与e异或,得到的是d的值,因为是要完成变量的交换,我们将这个值赋给e e = d^e; //此时,e中存储的是原来d的值,d中存储的d^e, //现在的d与e异或,得到的就是原来e的值,因为是要完成交换,所以将这个值赋给d d = d^e; printf("交换后:d=%d e=%d\n",d,e); return 0; } //缺陷:当a和b都很大时,a+b可能就会溢出int存储范围。 //int main() //{ // int a =5; // int b =3; // printf("交换前:a=%d b=%d\n",a,b);//交换前:a=5 b=3 // a = a+b; // b = a-b; // a = a-b; // printf("交换后:a=%d b=%d\n",a,b);//交换后:a=3 b=5 // return 0; //}
-
编写代码实现:求一个整数存储在内存中的二进制的1的个数
-
自己做的
//弊端:不能判断负数的1的个数。比如-1,其补码应该有32个1。但是运行出来是1个 #include <stdio.h> int main() { int num =333; int count = 0; //二进制位数 int count1 = 0;//1的位数 do { //只要还进入循环,说明这个数转换为二进制还有位。只要进入循环位数就+1。 count++; if(num%2 != 0) { //我们发现,当一个数可以整除2的时候,他的二进制的最后一位肯定是0。 //最后一位是1,也就是这个数%2不为0的时候,他的二进制位为1的数字位加1。 count1++; } //每次判断完完这个数/2。 num/= 2; //虽然num/2会省略小数点,但是循环条件要是num/2.0,这样最后一次1进来之后虽然1/2是0,但判断条件是1/2.0=0.5 //0.5不为0,还会再次循环,此时0.5/2=0了,num=0,0/2.0=0.000000。所以循环停止 //否则1/2的时候=0就少计算了一位。 }while(num/2.0); printf("%d转换成二进制有:%d位,其中是1的有:%d位",num,count,count1); return 0; }
-
使用运算符做
//需要判断32次,太麻烦了 #include <stdio.h> int main() { int num = 333; int i ; int count = 0; //因为一个int类型的数占四个字节,也就是32位二进制数,所以这里是<32 for(i=0 ; i<32 ; i++) { //i是几,就把1左移几位,右边补0,然后跟num做&运算 //i=0,num与1做&,返回1,条件成立,count+1 //00000000 00000000 00000001 01001101 333 //00000000 00000000 00000000 00000001 1 //00000000 00000000 00000000 00000001 1 //如i=1,左移1位,右边补0,与num做&,返回0条件不成立,自动进入下一次循环 //00000000 00000000 00000001 01001101 333 //00000000 00000000 00000000 00000010 2 //00000000 00000000 00000000 00000000 0 //i=2 ,左移2位,右边补0,与num做&,返回1,条件成立,count+1 //00000000 00000000 00000001 01001101 333 //00000000 00000000 00000000 00000100 4 //00000000 00000000 00000000 00000100 1 //i=3 ,左移3位,右边补0,与num做&,返回1,条件成立,count+1 //00000000 00000000 00000001 01001101 333 //00000000 00000000 00000000 00001000 8 //00000000 00000000 00000000 00001000 1 //i=4,左移4位,右边补0,与num做&,返回0,条件不成立,自动进入下一次循环 //00000000 00000000 00000001 01001101 333 //00000000 00000000 00000000 00010000 16 //00000000 00000000 00000000 00000000 0 //............... //i=31,左移31位,右边补0,与num做&,返回0,自动下次循环 //00000000 00000000 00000001 01001101 333 //10000000 00000000 00000000 00000000 2147483648 //10000000 00000000 00000000 00000000 0 if(num & (1 << i)) { count++; } } printf("该数对应的二进制的个数:%d",count); return 0; }
-
简化
/* * n&(n-1) n =15 * 15的二进制数1111 与 15-1的二进制数1110,按位与运算得到:1110,最右边的1被取了出来 * 继续循环: * - 1110与1101取余,得到:1100,最右边的1被取了出来。 * - 1100与1010取余,得到:1000,最右边的1被取了出来。 * - 1000与0111取余,得到:0000,最右边的1被取了出来 */ #include <stdio.h> int main() { int num = -1; int i = 0; int count = 0; while(num) { //每次取最右侧的1,直到全部取出,num变为0,循环停止。 num = num&(num-1); //每取出一次1,次数+1。 count++; } printf("其中1的个数:%d",count); return 0; }
-
赋值操作符
基本的赋值操作符 | 复合赋值符 | 复合赋值符 |
---|---|---|
= | += | >>= |
-= | <<= | |
*= | &= | |
%= | |= | |
/= | ^= |
-
赋值操作符的优先级:先执行=右边的表达式,将其执行结果赋给左边的变量
#include <stdio.h> int main() { //赋值操作符 = int a =10; int b = 20; a =20; //重新赋值 //赋值操作符可以连续使用:从最右边往左计算。 //int c = a = b+30; //printf("a=%d c=%d",a,c);//a=50 c=50 //但是不推荐连续使用。更推荐: a = b+30; int c =a; //复合赋值 int x,y,z; x += 10; //与x = x + 10;等效 y /= 88; //与y = y / 88; 相同 z = c; z >>= 3 ; //与z = z >> 3; 相同 int m,n; m ^= n; //与m = m^n 相同 return 0; }
单目操作符
什么是单目操作符?
- a+b,+是操作符,+旁边有两个操作数,在这里的+就被叫做双目操作符。
- 单目操作符:操作数只有一个。
! | - | + | sizeof | ~ |
---|---|---|---|---|
逻辑反操作 | 负值 | 正值 | 计算操作数长度,单位:字节 | 对一个数的二进制取反 |
++ | – | & | * | (类型) |
---|---|---|---|---|
自加1 | 自减1 | 取地址 | 间接访问(解引用)操作符 | 强制类型转换 |
-
! 用于取反
//在C语言中:0表示假,非0表示真。!2就返回0,因为非0就表示真,那!0应该返回哪个数?返回1 #include <stdio.h> int main() { int c = 0; if(!c) { printf("%d\n",!0);// !0打印出1 printf("%d\n",!30);// !30打印出0 printf("%d\n",!220);// !220打印出0 } return 0; }
-
+与 - 作为单目操作符的使用。
#include <stdio.h> int main() { int a = 10; int b = -a; //-a表示取-的a。 int c = +b; //+写了跟不写一样,一般省略,此语句等同于:int c = b; printf("a=%d b=%d c=%d",a,b,c);//a=10 b=-10 c=-10 return 0; }
-
sizeof操作符符
/* * sizeof 用于操作数的类型长度。单位:字节 * - sizeof是一个操作符,其在对类型进行操作取长度时,需要把类型放在()里。 * - 如果是取一个变量的长度,则()可以省略。而函数的()是不可以省略的,说明sizeof不是函数,而是一个操作符。 * 如,对int类型取长度:sizeof(int) ; 对int类型的变量a取长度,sizeof a 或 sizeof(a) * - sizeof在编译阶段处理,所以其中的表达式不参与运算。 */ #include <stdio.h> int main() { int a =10; int arr[9] = {0}; printf("%d\n",sizeof(a)); //计算int类型变量a所占空间大小 //4 printf("%d\n", sizeof(int)); //计算int类型所占空间大小 //4 //sizeof是一个操作符,而不是函数。 //如果是计算变量所占空间大小,则()可以省略 printf("%d\n",sizeof a); //4 //printf("%d\n", sizeof int)); //如果是一个类型的大小,则不可以省略 //求数组长度 printf("%d\n", sizeof(arr)); //36字节 //arr数组的类型是:int [9] printf("%d\n", sizeof(int [9])); //36 // short s = 5; a = 10; //这里不管后面的值是几,只要最后是要给s的,就是计算s的大小,而s是short类型的,占2个字节。 printf("%d\n", sizeof(s = a+2)); //2 /* * 这里为什么s还是5呢?因为sizeof()中存放的表达式不参与运算,为什么不参与运算呢? * - 程序运行的时候,先编译.c源文件,再经过链接,最后才是运行.exe程序 * - 而sizeof(s=a+2)是在编译器上执行的,s=a+2并没有运算。在执行sizeof的时候,因为最后计算的还是s的大小 * 所以sizeof直接返回s的大小,因为已经计算好了大小,所以s=a+2这个表达式就算是已经处理完了。 * - 在运行的时候,sizeof(s=a+2)表达式表示的已经是2了,而不是一个待执行的表达式 * - 所以s的值并没有发生变化。 * * - 编译器只是根据sizeof中的表达式s=a+2,其结果最后是放入s中,所以只会计算s的大小,并不执行其中的表达式运算 * 而并不是真的把a+2的结果赋给s * - 而正因为sizeof是编译器处理的,所以其中的表达式并不会被运算。 */ printf("%d\n",s); //5 return 0; }
当数组作为函数参数传递进去的时候,实际上是一个指针变量,如果是要计算传递到其他函数中的数组大小,则计算的是一个指针变量。
#include <stdio.h> void test(int arr[],char ch[]) { //这里是8,因为数组名中存储的是第一个元素的地址,地址应该是一个指针。 // 在32位系统中,是4;我们这里是64位的,所以是8个字节。 printf("test()函数中,传递进的arr大小:%d\n", sizeof(arr));//8 printf("test()函数中,传递进的ch大小:%d\n", sizeof(ch));//8 } int main() { int arr[10] = {0}; char ch[10] ={0}; printf("数组arr的大小:%d\n",sizeof(arr));//40 printf("数组ch的大小:%d\n", sizeof(ch));//10 test(arr,ch); return 0; }
-
~操作符:对一个数的二进制位按取反
// ~ 对一个数的二进制位按取反:把原来的二进制位中的数据:1变成0、0变成1。这里的按位取反,包括符号位取反。 #include <stdio.h> int main() { /* * 计算机中,整数在内存中存储的是补码。一个整数的二进制表示有三种:原码、反码、补码。 * 正整数的源码、反码、补码相同。负整数的源码、反码、补码计算关系如下: * -1的原码:10000000 00000000 00000000 00000001 最高位1表示符号位。 * -1的反码:11111111 11111111 11111111 11111110 原码的最高位符号位不变,其余二进制位按位取反,得到反码 * -1的补码:11111111 11111111 11111111 11111111 反码+1就是补码 */ //如对int类型的a进行~操作,因为int类型占4个字节,也就是32位二进制数。就是32个0,~操作后就变成了32个1 int a = 0; // 00000000 00000000 00000000 00000000 按位取反(所有位的取反,包括符号位) //得到:11111111 11111111 11111111 11111111 这个是补码 //因为内存中存储的是补码,而我们打印时,我们选择用%d的方式打印,就是打印原码。 //所以补码-1得到反码,然后最高位符号位不变,其余取反。就得到了原码,最高位是符号位,所以打印出了-1 printf("%d\n",~a);//-1 return 0; }
运用
#include <stdio.h> int main() { int a =13; //把a的二进制中的第五位置成1 a = a | (1<<4); // 00000000 00000000 00000000 00001101 13 // 00000000 00000000 00000000 00010000 16 //按位或(有1为1)后的结果: // 00000000 00000000 00000000 00011101 29 printf("a=%d \n",a);//29 //把a的二进制中的第五位置成0。 // 00000000 00000000 00000000 00011101 29 //与第五位是0、其他位都是1的数进行&与运算(都为1,才为1),此时除了第五位剩下的数我们都不会改变 // 11111111 11111111 11111111 11101111 //如何得到第五位是0、其他位都是1的数呢? //把第五位是1,其他位都是0的数(1左移4位),做~,也就是~(1<<4),再按位与:& ~(1<<4) a = a & ~(1 << 4); printf("a=%d \n",a);//此时就又得到了:13 return 0; }
-
++与--操作符
#include <stdio.h> /* * ++操作符【自加1】、-–操作符【自减1】 * ++操作符可以出现在变量前,也可以出现在变量后,无论是变量前还是变量后。只要++运算结束,该变量中的值一定会加1 * ++出现在变量后,如a++,规则:先做赋值运算。再对变量中保存的值进行自加1 * ++出现在变量前,如++a,规则:先进行自加1运算。再进行赋值操作 * * --操作符可以出现在变量前,也可以出现在变量后,无论是变量前还是变量后。只要--运算结束,该变量中的值一定会减1 * --出现在变量后,如a--,规则:先做赋值运算。再对变量中保存的值进行自减1 * --出现在变量前,如--a,规则:先进行自减1运算。再进行赋值操作 */ int main() { int a= 100; //++出现在变量后,先做赋值再加1 int b= a++; printf("%d\n",a);//101 printf("%d\n",b);//100 //++出现在变量前,先加1再进行赋值 b = ++a; printf("%d\n",a);//102 printf("%d\n",b);//102 //--出现在变量后,先赋值再加1 b =a--; printf("%d\n",a);//101 printf("%d\n",b);//102 //--出现在变量前,先加1再进行赋值 b =--a; printf("%d\n",a);//100 printf("%d\n",b);//100 return 0; }
-
&取地址操作符与*解引用操作符
#include <stdio.h> int main() { int a = 10; //& 用于取地址 printf("%p\n",&a); //0000001e9cbff754 //编译报错。&的操作数必须是一个变量,不能是一个表达式 // printf("%p",&(a+b)); // printf("%p",&(a++)); // printf("%p",&(++a)); //pa是存放地址的,pa是一个指针变量。因为是存储a的地址,而a是int类型,所以是int* int * pa =&a; //* 解引用操作符、间接访问操作符 ,可以通过*pa找到其指向的变量a,更改其值。 *pa = 20; printf("%d\n",a); //20 return 0; }
-
强制类型转换()
#include <stdio.h> int main() { //3.14是double类型的,而我们要把他存储到int类型的变量pai中。为了使编译通过,可以强制转换类型为int: int pai = (int)3.14; printf("%d",pai); return 0; }
关系操作符
> | >= | < | <= | != | == |
---|---|---|---|---|---|
大于 | 大于等于 | 小于 | 小于等于 | 不相等 | 相等 |
- =是赋值运算符 ==是关系运算符
逻辑操作符
&& | || |
---|---|
逻辑与 | 逻辑或 |
#include <stdio.h>
int main() {
int a = 3;
int b = 2;
//&&两端都为真,才为真。有一个为假就为假
if (a && b) {
printf("a和b都不是0");
}
int c = 0;
if (a && c) {
printf(" ");
} else {
printf("a和c中有一个是0");
}
// ||两端只要有一个为真就为真,两个为假才为假。
if (b || c)
{
printf("hh");
}
int d = 0;
if(d ||c)
{
printf(" ");
}
else{
printf("c和d都为0");
}
return 0;
}
&&与||的短路
#include <stdio.h>
int main()
{
int i = 0;
int a = 0;
int b = 2;
int c = 3;
int d = 4;
/*
* &&在使用的时候,会发生短路与:操作符左边表达式如果执行结果是假(0),就不再执行后面的表达式
* ||在使用的时候,会发生短路或:操作符右边表达式如果执行结果是真(1),就不再执行后面的表达式
*/
//这里a++,因为++出现在变量后,所以是先赋值,a++是使用的a的值,a=0,因为是&&,一旦假的出现后面的表达式都不执行。
//i = a++ && ++b && d++ ;
//a经过++,a=1,剩下的b、c、d不变
//printf("a=%d b=%d c=%d d=%d",a,b,c,d);//a=1 b=2 c=3 d=4
//a++为a的值,也就是0,继续判断++b,++b是3,为真,因为是||,后面的不执行。
i = a++ || ++b || d++;
//a经过++,a=1 ; b经过++,b=3。
printf("a=%d b=%d c=%d d=%d",a,b,c,d);//a=1 b=3 c=3 d=4
return 0;
}
条件操作符/三目操作符
表达式1?表达式2:表达式3 |
---|
表达式1为真,表达式2执行;反之,表达式3执行 |
/*
* exp1 ? exp2 : exp3
* 条件 ? 表达式1:表达式2
*
* 执行原理:
* - 条件为真,执行表达式1
* - 条件为假,执行表达式2。
*/
#include <stdio.h>
//找出两个数种较大的那个数
int main()
{
int a =10;
int b =20;
printf("%d",a>b?a:b); //20
return 0;
}
逗号表达式
//exp1,exp2,exp3,...,expn
//逗号表达式:每个表达式中间用,隔开。从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//即使最后一个表达式与前面的表达式没有联系,也是从左到右依次执行。
#include <stdio.h>
int main()
{
int a = 0;
int b = 3;
int c = 5;
int d = (a=b+2,b=c-4,10);
//逗号表达式,从左向右依次计算,但是只返回最后一个表达式的结果。
printf("%d\n",d); //10
return 0 ;
}
int main()
{
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
//如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
//业务处理
}
}
下标引用、函数调用和结构成员
下标引用:数组名[数组下标]
#include <stdio.h>
int main()
{
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
//[]就是下标引用操作符。[]的操作数有两个:数组名(arr)、数组下标(4)
printf("%d\n",arr[4]);
}
函数调用:函数名(参数);
//如果调用的函数不用传参:函数();
int Add(int x,int y)
{
return x+y;
}
void test()
{
}
int main()
{
int a = 10;
int b = 20;
//函数调用 函数名()
int ret = Add(a,b);
test();
return 0;
}
访问一个结构体的成员
#include <stdio.h>
//创建一个自定义的类型来描述:书
struct Book
{
//结构的成员 或者叫成员变量
char name[20];
char id[10];
int price;
};
int main()
{
struct Book b = {"C语言","1101",30};
//结构体变量名.成员名
printf("书名:%s\n",b.name);
printf("书号:%s\n",b.id);
printf("定价:%d\n",b.price);
// * 表示后面的pb是一个指针变量、*前面的struct Book是pb指向的b的类型是struct Book
struct Book * pb = &b;
//(*pb),*是解引用操作符,通过地址找到对应的b变量并访问。
//这种方式,写起来麻烦。
printf("书名:%s\n",(*pb).name);
printf("书号:%s\n",(*pb).id);
printf("定价:%d\n",(*pb).price);
//结构体指针->成员名
//pb->就等同于b。pb->name表示pb指向的对象的name,pb->id表示pb指向的对象的id.....
printf("书名:%s\n",pb->name);
printf("书号:%s\n",pb->id);
printf("定价:%d\n",pb->price);
return 0;
}
表达式求值
表达式求值的顺序,一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型
隐式类型转换
-
整型提升
- C语言中的整型算数运算总是至少以缺省整型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前需要被转换为普通整形。这种转换称为整型提升。 整型提升的意义: - 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整形操作数的标准时长。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节(也就是两个1字节的数)直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。 什么时候发生整型提升? - 当表达式中的操作数运算时,其数据类型所占大小小于int大小的时候,就进行整型提升 因为int是用来表达寄寄存器的。 int和long,还与编译器(CPU)有关。int就是用来表达寄存器的。 - 平时所说的字长,就是说计算机的寄存器是多宽/多少比特。如果是32bit,也就是说寄存器一次可以表达32bit的数据;同时,也表示:CPU在内存中读取数据的时候,一次传递的是32bit。把32bit叫做寄存器的字长,就是因为寄存器一次可以处理的数据就是32个bit。寄存器字长也可能是64bit。 - 寄存器的字长,在C语言中就反应为int。也就是说int的大小,就是寄存器的大小。这就是为什么在不同的平台/CPU上,int的大小会不同。
-
两个char类型相加
//b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结果被截断,然后再存储于a中。 //如何截断?int有四个字节,char只能存储一个字节。截取int的最后八位,存起来。 char a,b,c; a = b + c;
如何进行整型提升?
整型提升是按照变量的数据类型的符号位来提升的
//负数的整型提升:高位补1 //正数的整型提升:高位补0 //无符号的整型提升:高位补0。 #include <stdio.h> int main() { //负数的整型提升 char a = -1; //整数在内存中存储的是补码。一个整数的二进制表示有三种:原码、反码、补码。 //-1是int类型数据,其在内存中存储的是:11111111 11111111 11111111 11111111 //变量a是char类型,占一个字节(8个二进制位),截断取后八位:11111111 //因为这里的char并不是无符号的,就是说a是有符号的。最高位1为符号位,整型提升的时候,高位补充符号位1 //整型提升后的结果就是:11111111 11111111 11111111 11111111 //以%d打印,是打印有符号数,所以是打印原码,所以这里a发生整型提升。 //以上a整型提升的结果:11111111 11111111 11111111 11111111,这是补码 //补码-1得到反码:11111111 11111111 11111111 11111110 //反码最高位符号位不变,其余取反:10000000 0000000 0000000 00000001 //最高位1表示是负数,所以打印出来是-1 printf("%d\n",a); //-1 //正数的整型提升 char b = 1; //正整数的源码、反码、补码相同。 //1是int类型数据,其在内存中存储的是:00000000 00000000 00000000 00000001 //变量b是char类型,占一个字节(8个二进制位),截断取后八位:00000001 //因为这里的char并不是无符号的,就是说b是有符号的。最高位0为符号位,整型提升的时候,高位补充符号位0 //整型提升后的结果就是:00000000 00000000 00000000 00000001 //以%d打印,是打印有符号数,所以是打印原码,这里b发生整型提升。 //以上b整型提升的结果:00000000 00000000 00000000 00000001,这是补码 //最高位0表示是正数,所以打印出来是1 printf("%d\n",b); //1 return 0; }
原理
#include <stdio.h> int main() { //3是一个int类型数据:00000000 00000000 00000000 00000011 //char中只能存储一个字节的,取最后八位,也就是a中存储的是:00000011 char a = 3; //127是一个int类型数据:00000000 00000000 00000000 01111111 //char中只能存储一个字节的,取最后八位,也就是a中存储的是:01111111 char b = 127; //发现a和b都是char类型的,都没有达到一个int类型大小,所以会发生整型提升。 //整型提升是按照变量的数据类型的符号位来提升:只有unsigned修饰的才是无符号位的 // 这里的char是一个字节,有八位。如果是一个正数,则补0;如果是负数,则补1。 //这里a中存储的是:00000011,需要转换为4个字节大小,高位全部补0:00000000 00000000 00000000 00000011 //这里b中存储的是:01111111,需要转换为4个字节大小,高位全部补0:00000000 00000000 00000000 01111111 //进行相加,得到:00000000 00000000 00000000 10000010。因为c是char类型的,只能存储一个字节,也就是八位 //所以存储的时候发生截断,取低八位,也就是最后八位:10000010。在内存中存储的是补码 char c = a + b; //打印的时候,因为是以%d进行打印,%d打印有符号数,所以是打印原码,所以这里c又发生整型提升。 //10000010,最高位符号位是1,所以补1,变成11111111 11111111 11111111 10000010 //补码-1得到反码:11111111 11111111 11111111 10000001 //反码的最高位符号位1不变,其余取反,得到原码:10000000 00000000 00000000 01111110 //最高位1是符号位,表示是负数,1111110是126。所以c是-126 printf("c=%d",c); //c=-126 return 0; }
-
整型提升例1
#include <stdio.h> int main() { //0xb6转换为十进制是00000000 00000000 00000000 10110110。a中存储了:10110110 //最高位1表示是负数,整型提升补1:11111111 11111111 11111111 10110110,也就是说是个负数 char a = 0xb6; //0xb600,转换为十进制是00000000 00000000 10110110 00000000 //short中可以存储两个字节,也就是b中存储了:10110110 00000000 // 最高位1,整型提升补1:11111111 11111111 10110110 00000000。结果是一个负数 short b =0xb600; int c = 0xb6000000; //a和b都发生了整型提升,变成了负数。c并不发生整型提升,所以输出c if(a == 0xb6) { printf("a"); } if(b == 0xb600) { printf("b"); } if(c == 0xb6000000) { printf("c");// c输出 } return 0; }
-
整型提升案例2
#include <stdio.h> int main() { char c =1; //%u是输入输出格式说明符,表示按unsigned int格式输入或输出数据。 //这里变量c的类型是char,占一个字节 printf("%u\n", sizeof(c));//1 //+、-、!都是运算符,只要参与运算,就需要进行整型提升,提升之后就占4个字节。 //虽然sizeof中的表达式并不参与运算,但这里只是计算一个大小。并不是真的运算。只是计算了+c的大小,而不是要+c的结果 printf("%u\n", sizeof(+c));//4 printf("%u\n", sizeof(-c));//4 printf("%u\n", sizeof(!c));//4 //表达式有两个属性 int a = 2; int b = 3; //这个表达式的属性之一是,值属性。a+b的值是5,这个就是值属性 //第二个属性:类型属性。a与b都是int类型,a+b这个表达式的值一定也是int属性。 a + b; //所以以上+c这个表达式,发生了整型提升之后,占4个字节。整型提升为什么是占四个字节?因为CPU内整型运算器的操作数的字节长度一般就是int字节的字节长度。 //sizeof只是计算了+c这个表达式的大小,而不是去运算这个表达式。sizeof在编译阶段执行,执行之后有了值,在运行阶段就不会再去运行sizeof()内的表达式。 return 0; }
算数转换(自动类型转换)
如果两个不同类型的数就行运算,那么其中一个操作数就要转换为另一个操作数的类型,否则无法进行运算。这种转换就称为寻常算数转换。一般会自动转换为表达数字范围较大的类型。
//类型精度从高到低:
long double > double > float > unsigned long int > long int > unsigned int > int > short > char
//如果某个操作数的类型在上面中排名较低,那么就要首先转换为另一个操作数的类型后,才能进行运算。
//注意:算数转换也需要合理,否则会出现问题。
//如:
float f = 3.14;
int num = f; //隐式转换,会有精度丢失
//对于printf来说,任何小于int的类型会被转换为int,float会被转换为double
//但是scanf不会,要输入short,格式说明符应该为%hd
强制类型转换
//要把一个量强制类型转换为另一个值,语法:(类型)值
int main()
{
//例如:
int a = (int)10.32;
short s = (short)32;
//注意安全性:小范围类型不能表达超过其范围的量。
printf("%d\n",(short)327889);//209
//强制类型转换,只是将某个值/某个变量的值,强制转换为另一个类型。计算出的值=原来的值在要转换类型中的值。
//原来的值的类型和值并没有发生改变
int m = 2147484;
short n = (short)m;
printf("%d\n",n);//-15204
printf("%d\n",m);//2147484
return 0;
}
操作符的属性
复杂表达式的求值有三个因素的影响:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序?
两个相邻的操作符先执行哪个?取决于他们的优先级。如果他们的优先级相同,则取决于他们的结合性。
操作符的优先级(从高到低排列)
结果类型:表达式的结果是什么类型的
- lexp表示左值表达式,rexp表达右值表达式。
- 左值意味着一个位置,而右值意味着一个值。所以,在使用右值的地方也可以使用左值,但是使用左值的地方不能使用右值
结合性:
- N/A,表示不考虑结合性
- L—R,表示从左向右运行
- R—L,表示从右向左运行
是否控制求值顺序:
- 一般为否
- &&操作符,如果左边表达式为假,则不执行右边的表达式
- ||操作符,如果左边表达式为真,则不执行左边的表达式
- ?:三目操作符,如果?前的表达式为真,则执行:前的表达式;如果为假,则执行:后的表达式
- 逗号表达式,必须从左到右依次执行。
操作符 | 描述 | 用法示例 | 结果类型 | 结合性 | 是否控制求值顺序 |
---|---|---|---|---|---|
() | ()里优先算 | (表达式) | 与()中表达式相同 | N/A | 否 |
() | 函数() | rexp(rexp,…rexp) | rexp | L-R | 否 |
[] | 下标引用 | rexp[rexp] | lexp | L-R | 否 |
. | 访问结构成员 | lexp.name | lexp | L-R | 否 |
-> | 访问结构指针成员 | rexp->name | lexp | L-R | 否 |
++ | 变量后++ | lexp ++ | rexp | L-R | 否 |
– | 变量后– | lexp – | rexp | L-R | 否 |
! | 逻辑反 | ! rexp | rexp | R-L | 否 |
~ | 按位取反 | ~ rexp | rexp | R-L | 否 |
+ | 单目,表示正值 | + rexp | rexp | R-L | 否 |
- | 单目,表示负值 | - rexp | rexp | R-L | 否 |
++ | 前置++ | ++ lexp | rexp | R-L | 否 |
– | 前置– | – lexp | rexp | R-L | 否 |
* | 间接访问 | * rexp | lexp | R-L | 否 |
& | 取地址 | & lexp | rexp | R-L | 否 |
sizeof | 取其长度 | sizeof rexp | rexp | R-L | 否 |
(类型) | 类型转换 | (类型) rexp | rexp | R-L | 否 |
* | 乘法 | rexp * rexp | rexp | L-R | 否 |
/ | 除法 | rexp / rexp | rexp | L-R | 否 |
% | 整数取余 | rexp % rexp | rexp | L-R | 否 |
+ | 加法 | rexp + rexp | rexp | L-R | 否 |
- | 减法 | rexp - rexp | rexp | L-R | 否 |
<< | 左移位 | rexp << rexp | rexp | L-R | 否 |
>> | 右移位 | rexp >> rexp | rexp | L-R | 否 |
> | 大于 | rexp > rexp | rexp | L-R | 否 |
>= | 大于等于 | rexp >= rexp | rexp | L-R | 否 |
< | 小于 | rexp < rexp | rexp | L-R | 否 |
<= | 小于等于 | rexp <= rexp | rexp | L-R | 否 |
== | 相等 | rexp == rexp | rexp | L-R | 否 |
!= | 不相等 | rexp != rexp | rexp | L-R | 否 |
& | 按位与 | rexp & rexp | rexp | L-R | 否 |
^ | 按位异或 | rexp ^ rexp | rexp | L-R | 否 |
| | 按位或 | rexp | rexp | rexp | L-R | 否 |
&& | 逻辑与 | rexp && rexp | rexp | L-R | 是 |
|| | 逻辑或 | rexp || rexp | rexp | L-R | 是 |
?: | 条件操作符 | rexp ? rexp : rexp | rexp | N/A | 是 |
= | 赋值 | rexp = rexp | rexp | R-L | 否 |
+= | 加等 | rexp += rexp | rexp | R-L | 否 |
-= | 减等 | rexp -= rexp | rexp | R-L | 否 |
*= | 乘等 | rexp *= rexp | rexp | R-L | 否 |
/= | 除等 | rexp /= rexp | rexp | R-L | 否 |
%= | 模等 | rexp %= rexp | rexp | R-L | 否 |
<<= | 左移等 | rexp <<= rexp | rexp | R-L | 否 |
>>= | 右移等 | rexp >>= rexp | rexp | R-L | 否 |
&= | 按位与等 | rexp &= rexp | rexp | R-L | 否 |
^= | 按位异或等 | rexp ^= rexp | rexp | R-L | 否 |
|= | 按位或等 | rexp |= rexp | rexp | R-L | 否 |
, | 逗号 | rexp,rexp | rexp | L-R | 是 |
-
问题表达式
-
表达式1
a*b + c*d + e*f; //*比+的优先级高,所以只能保证*计算的比a+早,并不能决定第三个*比第一个+早执行 //可能的计算顺序: //顺序一:计算a*b、c*d、e*f,然后计算他们的和 //顺序二:计算a*b、c*d,然后计算a*b + c*d,然后再计算e*f,然后将前两个的和与e*f的结果加起来
-
表达式2
c + --c; //操作符优先级只能决定--的运算在+运算的后面,但是我们并不知道c是在--c之后才获取值,还是获取了c的值之后,再--c,所以结果是不可测的。 //如c=3。 //如果是--c之前获取值:3+2=5 //如果时计算了--c才获取c的值:2+2=4
-
表达式3
//这段代码在不同的编译器下,运行结果都不同 #include <stdio.h> int main() { int i = 10; i = i-- - --i * ( i = -3) * i++ + ++i; printf("i=%d \n",i); //36 return 0; }
-
表达式4
//虽然这个表达式在大多数编译器上求得结果都相同,但是函数调用的先后顺序并不能通过操作符优先级决定 #include <stdio.h> int fun() { static int count = 1; return ++count; } int main() { int answer; //根据操作符优先级,我们可以得知先算乘法,再算加法。但函数调用的先后顺序,我们无法通过操作符优先级确定。 //顺序一:先调用fun()得到count=2, 再调用乘法里的fun(),得到count=3和count=4。然后2-3*4=-10。 //顺序二:先调用乘法里的fun(),得到count=2和count=3,再调用-前面的fun(),得到count=4。然后4-2*3=-2 answer = fun() - fun() * fun(); printf("%d\n",answer);//-10 return 0; }
-
表达式5
//有的编译器运行结果就是12,有的是10 #include <stdio.h> int main() { int i = 1; //为什么会出现12? //计算机计算数的时候,是通过寄存器来计算的。i刚开始是1,计算机将1放到寄存器中,然后++i,就是为寄存器中存储的数+1,数值变成2 //然后再次++i,此时寄存器中存储的是3,再次++i,此时存储的是4。此时++已经计算完了,将寄存器中的值给i。 //此时执行加法:将i的值放到寄存器中,然后+i,再+i,就是3个4相加,就算出12。然后放到ret中。 //如果是这个编译器下进行(++i) + (++i) + (++i) + (++i) + (++i),则结果是30。 //因为是先把所有的++i计算完之后,才计算加法。i=1,5次++之后,i=6。5个6相加=30。 //也即是说,如果是a+b+c+d+e,这里的a、b、c、d、e都是表达式。 //先计算出所有表达式的值(计算出a、b、c、d、e),然后再做加法。 //如果有更多的表达式,则依次类推。最后同一加 //为什么会是10呢? //先计算前两个++i,计算了之后i=3,然后3+3就是6,再用6+(++i),就是6+4=10 //可以理解为:a+b+c,a与b与c都是表达式。先计算a与b的值,然后再计算a+b,然后计算c的值,再用a+b的值+c的值 //int ret = (++i) + (++i) + (++i); //printf("%d\n",ret); //10 //printf("%d\n",i); //4 //所以如果是5个表达式相加,就是a+b+c+d+e,先算a与b的值,然后加起来,算c表达式的值,然后用a+b的值加c的值 //然后算d表达式的值,然后用a+b+c的值+d,然后算e表达式的值,然后用a+b+c+d的值+e。 //这里就是先算++i与++i,i变成3,然后3+3=6,此时计算6+(++i)+(++i)+(++i)中的6+(++i),6的结果是6,++i变成4 //6+4=10,再计算10+(++i)+(++i)中的10+(++i),10的结果是10,++i之后i变成5。10+5=15。 //此时就是计算15+(++i),15的结果就是15,++i之后i变成了6,然后15+6=21。所以这里输出21。 int ret = (++i) + (++i) + (++i) + (++i) + (++i); printf("%d\n",ret); //21 //printf("%d\n",i); //6 //如果是更多的表达式相加,则按照这个编译器相加原理,就是:先计算前两个表达式的值,然后相加,用这个结果去加第三个表达式的结果 //然后用前三个相加的结果+第四个表达式的结果,然后用前四个相加的结果+第五个....以此类推。 return 0; }
结论
虽然操作符有优先级,但有的表达式,其具体的计算顺序还是无法确定。 也就是说,我们写出的表达式,有可能计算1结果并不是唯一的。即使我们知道了操作符的优先级,也无法确定这个表达式的运算结果是唯一的,那么这个就是问题表达式。我们在编程中一定要避免这种情况。
-
练习
-
编写函数,求一个数其二进制中1的个数
/* * n&(n-1) n =15 * 15的二进制数1111 与 15-1的二进制数1110,按位与运算得到:1110,最右边的1被取了出来 * 继续循环: * - 1110与1101取余,得到:1100,最右边的1被取了出来。 * - 1100与1010取余,得到:1000,最右边的1被取了出来。 * - 1000与0111取余,得到:0000,最右边的1被取了出来 * * 扩展:求一个二进制数是不是2的n次方(仅考虑正数,负数不考虑)。 * 思路:n&(n-1) == 0 * 解释:一个数如果是2的倍数,则换算成二进制数,所有数种就只有一个1,用n&(n-1)取出来之后,就只剩下0了 * 所以当n&(n-1) == 0 的时候,这个数是2的倍数。1是2的0次方,不排除在外。 */ #include <stdio.h> int NumberOf1(int num) { int count = 0; while(num) { //每次取最右侧的1,直到全部取出,num变为0,循环停止。 num = num&(num-1); //每取出一次1,次数+1。 count++; } return count; } int main() { int num = -1; printf("其中1的个数:%d",NumberOf1(num)); return 0; }
-
求两个数的二进制数中不同的位数
#include <stdio.h> int main() { int m = 0; int n = 0; scanf("%d %d",&m,&n); int i; int count = 0; for(i=0 ; i<32 ; i++) { //右移1位,然后与1取余,如果得到的是1和0,或0和1,则表示这两个数的二进制位不同,此时统计+1。 if(((m>>i)&1) != ((n>>i)&1)) { count++; } } printf("%d\n",count); return 0; }
异或的方式
#include <stdio.h> int NumberOf1(int num) { int count = 0; while(num) { num = num&(num-1); count++; } return count; } int main() { int m = 0; int n = 0; scanf("%d %d",&m,&n); //按位异或,相同位0,相异为1。 int compare = m^n; //其中有几个1,就有几位不同,放入函数中取1的位数。 int count = NumberOf1(compare); printf("%d\n",count); return 0; }
-
统计二进制中1的个数
#include <stdio.h> //11111111 11111111 11111111 11111111 int main() { int n = 0; scanf("%d",&n); int i = 0; //倒着打印偶数位 printf("偶数位:"); for(i=31 ; i>=1 ; i-=2) { printf("%d ",(n>>i)&1); } //倒着打印奇数位 printf("\n奇数位:"); for(i=30 ; i>=0 ; i-=2) { printf(" %d ",(n>>i)&1); } return 0; }
-
不使用中间变量,交换两个数。
//不使用第三个变量,交换两个变量。 #include <stdio.h> int main() { int a =5; int b =3; printf("交换前:a=%d b=%d\n",a,b);//交换前:a=5 b=3 //异或的结果,与原来的其中一个值异或,可以得到另外一个值。 //111 与011进行异或,得到:110 a = a ^ b; //110与011进行异或,得到101,也就是5,因为要交换,所以赋给b b = a ^ b; //110与101进行异或,得到011,也就是3,赋给a a = a ^ b; printf("交换后:a=%d b=%d\n",a,b);//交换后:a=3 b=5 int c = 4; printf("%d %d\n",c^c,c^0);//c^c=0 c^0=4 /* * 解析: * - 一个数与自己进行异或,则会返回0;如果与0进行异或,则会返回这个数。 * 也就是说a与b异或返回给a,此时a=a^b,而(a^b)^b就是a的值,因为a的值求出来了,此时b的值就是(a^b)^a * - 结论:两个数异或得到第三个数,其中两个异或可以得到零一个。 * 比如:a^b=c ,那么a^c可以得到b的值 ,b^c 就可以得到a的值。 */ //将以上a^b的值定义为一个变量会更好理解: int d,e,f; d = 10; e = 20; //根据异或原理可以得到: f = d^e; d = f^e; e = f^d; printf("交换前:d=%d e=%d\n",d,e); //但是我们要交换值,并且不能借助第三个变量,就是: d = d^e; //此时d中存储的是d与e异或的结果,现在d已经不是原来的值了,但是e还是原来的值。 //也就是说,现在的d与e异或,得到的是d的值,因为是要完成变量的交换,我们将这个值赋给e e = d^e; //此时,e中存储的是原来d的值,d中存储的d^e, //现在的d与e异或,得到的就是原来e的值,因为是要完成交换,所以将这个值赋给d d = d^e; printf("交换后:d=%d e=%d\n",d,e); return 0; } //缺陷:当a和b都很大时,a+b可能就会溢出int存储范围。 //int main() //{ // int a =5; // int b =3; // printf("交换前:a=%d b=%d\n",a,b);//交换前:a=5 b=3 // a = a+b; // b = a-b; // a = a-b; // printf("交换后:a=%d b=%d\n",a,b);//交换后:a=3 b=5 // return 0; //}
-
以下程序输出:0 0 3 4 5
#include <stdio.h> int main() { int arr[] = {1,2,3,4,5}; //内存中是以16进制存储的,并且是倒着存。 //arr数组:01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00 //每个十六进制数就是4位二进制,每两个16进制数就占1个字节。 short* p = (short*)arr; int i = 0; for(i=0 ; i<4 ; i++) { //short类型指针每次跳过一个short类型大小,所以就是每次跳两个字节。将其改为0。 //第一次将01 00变为00 00 第二次将 00 00变为00 00 第三次将02 00变为00 00 第四次将00 00 变为00 00 *(p+i) = 0; //arr数组变为:00 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00 } for(i=0 ; i<5 ; i++) { //也就是将1、2变为了0,所以输出0 0 3 4 5 printf("%d ",arr[i]); //0 0 3 4 5 } return 0; }
-
指针变量与++
#include <stdio.h> int main() { int year = 1009,*p = &year; //*p += 1; //1010 //因为*的优先级更高,所以*p先解引用,再+=1,就是1010 //(*p)++; //1010 //前置++和后置++的优先级都比*更高,加个()先解引用再++。 //++(*p); //1010 //加个()先解引用再++。 printf("%p\n",p);//000000daa27ff6d4 *p++; //++优先级高,所以先p++,再*解引用。因为p是一个指针变量,所以++是+了一个int类型大小,就是4个字节。//year并没有变化 printf("%p\n",p);//000000daa27ff6d8 printf("%d\n",year); return 0; }
-
表达式a*b+d-c的类型为
char a; int b; float c; double d; //类型精度从高到低: long double > double > float > unsigned long int > long int > unsigned int > int //如果某个操作数的类型在上面中排名较低,那么就要首先转换为另一个操作数的类型后,才能进行运算。 所以a*b+d-c的类型为:double类型
-
sizeof结果类型的考察
#include <stdio.h> int i;//i是全局变量,不初始化,默认是0 int main() { i--; if(i > sizeof(i)) { printf(">\n"); } else { printf("<\n"); } //为什么输出 > 呢? printf("%d",i);//-1 printf("%d",sizeof(i));//4 //-1怎么比4大的? //sizeof返回的结果是unsigned int 无符号整型的。有符号整型与无符号整型比较大小的收,会把有符号整型转换为无符号整型,然后进行比较 //-1转换为无符号数后,32个1是一个很大的数。肯定是大于4的。 return 0; }
-
由一个数组成的前n项和
//求 Sn = a + aa + aaa + aaaa + aaaaa的前五项之和。 //例如:2+22+222+2222+22222 #include <stdio.h> int main() { //求出a组成的前n项和,不考虑溢出。 int a = 0; int n = 0; scanf("%d %d",&a,&n); int i; int sum =0; //每一项都等于前一项*10+这个数。第一项就是0*10+数字本身。 int ret = 0; for(i=0 ; i<n ; i++) { ret = ret*10 + a; sum += ret; } printf("sum=%d\n",sum); return 0; }
-
表达式求值的正确说法
/* * - 表达式求值先看是否存在整型提升或算术转换,再进行计算。 * - 表达式真正计算的时候,看相邻操作符的优先级,来决定先计算谁。 * - 相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序。 * * 错误的:只要有了优先级和结合性,表达式就能求出唯一值。 */