C语言中的操作符

前言

C语言中各种各样的操作符使得C语言程序灵活多变,使用好各类操作符更能使程序灵巧轻便,这里记录下自己学习的操作符,也与各位共享

1.算数操作符

算数操作符中有

1
+ -  *  /  %

此处对 / 和 % 做些说明:

/:对于除法来说两个数中有一个是浮点数,则执行的是浮点数除法,两个都是整数,则执行的是整数除法

%:对于取余,符号后一定是整数,不能是浮点数,如:%3.0(错)

1
2
3
4
5
6
7
8
#include<stdio.h>
int main()
{
int a = 10;
int b = 0;
printf("a=%d b=%d",a/3,a%3);
return 0;
}

运行结果
a=3 b=1
说明:取余看的是余数,除法看的是商

2.移位操作符

左移操作 <<
右移操作 >>
移位移的是数字在内存中存的二进制的补码,移位移的是二进制位
移位操作如果不给自己赋值是不会改变自己的,表达式是移位后的结果

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
int main()
{
int a = 10;
int b = a<<1;
int c = a>>1;
//整数的原码,反码,补码都相同
//00000000 00000000 00000000 00001010
printf("a = %d\n",a);
printf("b = %d\n",b);
return 0;
}

运行结果:a = 20 b = 5
左移:左边直接丢弃,右边补零,有x2的效果
右移:①算术右移:右边丢弃,左边补符号位
②逻辑右移:右边丢弃,左边补0
而这两种右移方式执行哪一个,完全取决于你的编译器,但经测试,大部分编译器都采用算术右移,更合理一点
waring: 对于移位运算符,不要移动负数位,这个是标准未定义的

3.位操作符

按位与 & :与:一个是零 则为零,两个为一,才为一

按位或 |:一个为1则为1,两个为0,才为0

按位异或 ^:相同为0,相异为 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
int main()
{
int a = -1;
int b = 10;
int c = a&b;
int d = a|b;
int k = a^b;
//a:11111111 11111111 11111111 11111111
//b:00000000 00000000 00000000 00001010
//a&b:00000000 00000000 00000000 00001010
//a|b:11111111 11111111 11111111 11111111
//a^b:11111111 11111111 11111111 11110101 k的补码
//11111111 111111111 11111111 11110100 补码-1
//10000000 000000000 00000000 00001011 补码-1取反得k的原码 即-11
//异或:相同为0,相异为 1
//与:一个是零 则为零,两个为一,才为一
//或: 一个为1则为1,两个为0,才为0
printf("c = %d d = %d k = %d",c,d,k);
return 0;
}

运行结果: c = 10 d = -1 k = -11

4.赋值操作符(=)

1
int weight = 60;  //使用赋值操作符赋值

赋值操作符可以连续使用 a = x = y+1

复合赋值符

1
2
3
a = a+10;
a += 10;
//两种表达的效果相同,第二种便是复合赋值符

5.单目操作符

正值:+

负值:-

逻辑反操作 !

1
2
3
4
5
6
7
#include<stdio.h>
int main()
{
int flag = 10;
printf("%d",!flag); //真变假
return 0;
}

运行结果: 0

对一个数的二进制位进行取反 ~

取地址: &

取变量地址

1
printf("%p",&a); //可以打印出a的地址

数组取地址:

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
int main()
{
int arr[10] = {0}; //数组名相当于首元素的地址
printf("%p\n",arr);
printf("%p\n",arr+1);

printf("%p\n",&arr); //取出的是数组的地址
printf("%p\n",&arr+1); ////&arr+1 跳过了整个数组指向末尾
return 0;
}

运行结果:0028FE98
0028FE9C
0028FE98
0028FEC0
数组名加1与数组首元素差4个字节而取地址数组名加1则与取地址数组名差了40个字节,即一整个数组

函数取地址

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>

void test()
{
printf("heeh\n");
}


int main()
{
printf("%p",&test);
return 0;
}

操作数的类型长度(以字节为单位): sizeof

sizeof的作用:就是返回一个对象或者类型所占的内存字节数。
三个程序说明sizeof

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
int main()
{
int a = 10;
int arr[10] = {0};
printf("%d\n",sizeof(a)); //4字节
printf("%d\n",sizeof(int)); //4
printf("%d\n",sizeof(arr)); //40
printf("%d\n",sizeof(int [10])); //40
return 0;
}

由此可见,sizeof可以求一个变量的内存大小。也可以求数组的内存大小

1
2
3
4
5
6
7
8
9
#include<stdio.h>
int main()
{
int a = 10;
int arr[10] = {0};
printf("%d\n",sizeof a);
// printf("%d\n",sizeof int); 类型一定要带上()否则是无法运行的
return 0;
}

运行结果为4
此程序说明sizeof是个操作符,他并不是个函数

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
int main()
{
short s = 10;
int a = 6;
printf("%d\n",sizeof(s=a+8)); //2
printf("%d\n",a); //6
printf("%d",s); //10
return 0;
}

此处运行结果为 2 6 10
此处说明,sizeof内的表达式不参与计算,sizeof所计算的变量类型遵从被赋值的变量

对一个数的二进制数取反:~

将一个数的二进制序列取反。0变1,1变0

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
int main()
{
printf("%d",~10);
//00000000 00000000 00000000 00001010
//11111111 11111111 11111111 11110101 补码
//10000000 00000000 00000000 00001010
//10000000 00000000 00000000 00001011
return 0;
}

运行结果 -11

前置– 后置– : –

前置++  后置++ : ++

这里只用了解前置的情况下先自增(自减)在使用,后置的情况下先使用在自增(自减)。
值得注意的是指针的++ –

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
int main()
{
int a[10] = {0};
int *p = a;
printf("%d\n",p);
printf("%d\n",++p);
//char c = 'a';
//char *p = &c;
//printf("%p\n",p);
//printf("%p\n",++p);
return 0;
}

运行结果:2686612 2686616
相差了4个字节,由此可见自增自减也不一定任何情况下都是加一或减一,这和他所指向的类型的关系有关,当换成字符类型,又变为加减1字节

间接访问操作符(解引用操作符): *

1
2
3
4
5
6
7
8
#include<stdio.h>
int main()
{
int a = 10;
int *p = &a; //第一个*表示指针变量
*p = 20; //第二个*为解引用操作,间接访问
return 0;
}

解引用的作用是拿到某个地址,通过地址找到其内容。

强制类型转换:(类型)

1
2
3
4
5
6
#include<stdio.h>
int main()
{
int a = (int)3.14;
return 0;
}

强制类型转换容易丢失精度,一般不建议使用

6.关系操作符

C语言中的关系操作符有 > >= < <= != == 这些关系运算符较为简单,不o做多余赘述,须记住在比较两个字符的时候,是不能用 == 的,而是要用strcmp

7.逻辑操作符

逻辑与:

&&:逻辑与,读作并且
表达式左右两边都为真,那么结果才为真
口诀:一假则假

逻辑或:

||:逻辑或,读作或者
表达式左右两边,有一个为真,那么结果就为真
口诀:一真则真

逻辑运算符的短路问题

tips:非0为真,0为假
短路的情况:
&&:左边如果为假,则右边短路(右边不会被执行)
||:左边如果为真,则右边短路(右边不会执行)

1
2
3
4
5
6
7
8
#include<stdio.h>
int main()
{
int i = 0,a = 0,b = 2,c = 3,d = 4;
i = a++ && ++b && d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n",a,b,c,d);
return 0;
}

运行结果 1 2 3 4
原因: a = 0为假导致 a++ && ++b 为假,所以++b短路,不执行, b=2,然后a++ && ++b 整体为假,导致a++ && ++b && d++为假,所以d++不执行,d = 4,整个过程只有a自增了一下,a =1

8.条件操作符(三目操作符)

如果希望获得两个数中最大的一个,可以使用 if 语句,例如:

1
2
3
4
5
if(a>b){
max = a;
}else{
max = b;
}

不过,C语言提供了一种更加简单的方法,叫做条件运算符,语法格式为:

1
2
3
4
5
表达式1 ? 表达式2 : 表达式3
条件运算符是C语言中唯一的一个三目运算符,其求值规则为:如果表达式1的值为真,则以表达式2 的值作为整个条件表达式的值,否则以表达式3的值作为整个条件表达式的值。条件表达式通常用于赋值语句之中。
上面的 if else 语句等价于:
```c
max = (a>b) ? a : b;

该语句的语义是:如a>b为真,则把a赋予max,否则把b 赋予max。
三目操作符完全可以用if-else替代

9.逗号表达式

逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式

1
2
3
int a = 1;
int b = 2;
int c = (a>b,a=b+10,a,b=a+1);

运行结果 c = 13

10.下标引用、函数调用和结构成员

下表引用

arr[10]中[]就是下标引用操作符,他有两个操作数 arr和10
①除了优先级之外,下表引用和间接访问完全相同。如下面两个表达式是等同的:

1
2
arr[4];
*(arr+4);

②4[arr]表达式是合法的
表达的意思与(4+arr)也就是(arr+4)是完全一样的。但是绝对不应该这么写,影响程序的可读性。
附:

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%d\n",arr[4]);
printf("%d\n",*(arr+4));
printf("%d\n",*(4+arr));
printf("%d\n",4[arr]);

return 0;
}

函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
void test() //最少一个操作数
{
printf("heh\n");
}
int Add(int x, int y)
{
return x+y;
}

int main()
{
test(); //()为函数调用操作符 ,test函数名为他的操作数
Add(1,2); //三个函数操作数
return 0;
}

访问一个结构的成员

结构体代码的基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
#include<string.h>

struct Stu
{
int age;
char name[20] = {0};
float score;
};

int main()
{
struct Stu s;
struct Stu* ps = &s; //结构体指针
strcpy(s.name,"zhang san "); //把一个字符串拷贝到另一个空间
s.age = 20;
s.score = 60.0f;
// printf("%s %d %f",s.name,s.age,s.score);
// printf("%s %d %f",(*ps).name, (*ps).age, (*ps).score); //通过结构体指针输出,但这个过于繁琐,可以改为 ->
printf("%s %d %f",ps->name,ps->score,ps->age);
return 0;
}

注意:
①在定义结构体时大括号后一定有一个分号;
②结构体是一个类型,没有占用空间,只是个模板(地图),等同于int,float等,他是通过s才创建空间的;
③从结构体到主函数的过程叫做实例化;
④获取结构体成员:通过结构体指针可以获取结构体成员,一般形式为:

1
(*ps).name

或者:

1
ps->name

第一种写法中,.的优先级高于,(name)两边的括号不能少。
第二种写法中,->是一个新的运算符,习惯称它为“箭头”,有了它,可以通过结构体指针直接取得结构体成员;这也是->在C语言中的唯一用途。
上面的两种写法是等效的,我们通常采用后面的写法,这样更加直观。