系列文章目录
操作符的分类:

算术操作符 、强制类型转换 、关系操作符 、条件操作符 、逻辑操作符 文章链接:http://t.csdnimg.cn/7nuOa
移位操作符 、位操作符 文章链接:http://t.csdnimg.cn/wJCub
本文:赋值操作符 、复合操作符 、单目操作符 、关系操作符 、逻辑操作符 、条件操作符 、逗号表达式 、下标引用操作符 、函数调用操作符 、结构体成员
文章目录
前言
一、赋值操作符
=
赋值可以是单个赋值,也可以是连续赋值
注:连续赋值时,在调试的过程中是看不到值得变化过程,故而不太推荐只用连续赋值
例1:
int a = 1; //初始化:在创建变量的时候就给它一个初始值
int b = 2;
int c = 3;
a = b = c+3; //连续赋值
即 b = c + 3;
a = b;
二、复合赋值符
+= 、-= 、*=、/=、%=、>>=、<<=、&=、|=、^=
a = a + 2 ; --> a += 2;
a = a << 1; --> a<<=1;
三、单目操作符
! // 逻辑反操作
- // 负值
+ //正值
& //取地址
sizeof //操作数的类型长度(以字节为长度)
~ //对一个数的二进制按位取反
-- //前置、后置--
++ //前置、后置++
* //间接访问操作符(解引用操作符)
(类型) //强制类型转换
1、! //逻辑取反操作
顾名思义,就是让真的变成假的,让假的变成真的;
2、- //负值
表示一个数为负

没啥价值,但是存在这个操作符
3、+ //正值
表示一个数为正

没啥价值,但是存在这个操作符
4、& //取地址
int a = 1;
int* pa = &a; --> 取出变量a存放在内存中的地址,并将此地址放到指针变量 pa 之中
变量a 的类型为int ,int类型所占空间为4byte, 然a的地址是第一个字节的地址;每个内存单元的大小为1byte;内存单元编号 = 地址 ;
pa是用来存放地址的,故而pa为指针变量,其类型为 int*
5、sizeof //操作数的类型长度(以字节为单位)
sizeof 的操作数可以是类型、变量、表达式
注:当sizeof 的操作数为类型时,()不可省略;当sizeof 的操作数为变量、表达式时,()可要可不要。
注:sizeof的操作数若是表达式,此表达式是不会参与运算的,是根据表达式的类型得到大小。
例1:
代码如下:
#include<stdio.h>
int main()
{
int a = 0;
char b = 1;
printf("%zd\n", sizeof a); //sizeof 的操作数为变量
printf("%zd\n", sizeof(int)); //sizeof 的操作数为类型
printf("%zd\n", sizeof (b = a + 1)); //sizeof 的操作数为表达式
return 0;
}
代码运行结果如下:

注:sizeof中表达式不计算的原因:sizeof 在代码进行编译的时候,就根据表达式的类型确定了;而表达式的执行要在程序运行期间才能执行,在编译期间就已经将sizeof 给处理掉了,所以在运行期间就不会计算sizeof中的表达式。
分析:sizeof (b = a + 1),假设我们不知道上面的注释;假设这个表达式会计算,a+1 的结果最终时存放在 变量b 中的,而变量b的类型为 char ,char 类型在内存中所占的空间大小为 1 Byte。故而,sizeof (b = a + 1) = 1;单位为字节。实际上,sizeof 类型的操作数为表达式时,此表达式不会参与真实的计算,sizeof(表达式) 求得的结果是此表达式的类型。
注:sizeof 计算结果的返回类型为 size_t类型的。sizeof专用占位符:%zd
扩展 sizeof:(为什么sizeof计算的返回结果为 size_t)
sizeof 运算符的返回值,C语言只规定是无符号整数,并没有规定其具体的类型,而是留给系统自己去决定的。在不同的系统中,sizeof 返回的类型也不同,返回类型的值可能是 unsigned int ,也有可能是 unsigned long,甚至是 unsigned long long ,对应printf() 的占位符分别是 %u、%lu 、%llu。为了让sizeof在各个系统的可以统一使用,C语言便创造了一个变量 size_t,用来统一表示sizeof 的返回值类型。
特别:sizeof 与数组
例:
#include<stdio.h>
void test1(int* pa)
{
printf("pa=%zd\n", sizeof(pa));
}
void test2(int* pc)
{
printf("pc=%zd\n", sizeof(pc));
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("arr=%zd\n", sizeof(arr));
printf("ch=%zd\n", sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
在x86 环境下的运行结果:

在x64 环境下的运行结果:

注:数组传参,传的是数组首元素的地址,即pa、pc 为指针变量,而指针变量在内存中所占的空间是由平台决定的,在x86 环境下为 4byte,在x64 环境下为 8byte;而数组在内存中是连续存放的,即 sizeof(arr), arr 代表的是整个数组,那么求的就是整个数组在内存中所占的空间,由于 arr 数组元素的类型为 int 类型,int 类型在内存空间中占 4byte,而数组 arr 中有10个元素,故而 sizeof(arr) = 40 ; 单位是字节;
6、~ //对一个数的二进制按位取反
对一个数存放在内存中的二进制序列——补码(面向所有位,包括符号位),进行按位取反;
按位取反:将0变为1,将1变为0
详情戳戳链接(也是博主写的):http://t.csdnimg.cn/7KXYn
7、-- //前置、后置--
++ //前置、后置++
前置:先自增(减),再使用
后置:先使用,再自增(减)
例2:(以++为例,前置++)
代码如下:
#include<stdio.h>
int main()
{
int a = 3;
printf("%d\n", ++a);
printf("%d\n", a);
return 0;
}
代码运行结果:

分析:前置++是先++再使用,故而在执行 printf("%d\n", ++a); ,体现为先让a 再自增,然后再使用:printf() 打印变量a 的值。
例3:(以++为例,后置++)
代码如下:
#include<stdio.h>
int main()
{
int a = 3;
printf("%d\n", a++);
printf("%d\n", a);
return 0;
}
代码运行结果如下:

分析:后置++是先使用再++,即在执行 printf("%d\n", a++); 时体现为,先使用:printf() 打印变量a 的值,然后 a再自增。
看了例2、例3,你可能还会想到 for 循环中的 前置或则后置++、--,以及传参中的前置或则后置++、--
1、 for(i = 0 ; i < 10 ; i++) --> for ( i = 0; i < 10; ++i ) 在for 循环中,前置和后置本质上是没有区别的,它的功能都是为了实现自增或者自减。
对于内置类型来说前置与后置没太大区别,但是对于自定义类型来说,前置的效率更高。
2、传参:
例如: int a = 10;
test( a-- );
后置++,规则为先使用再自增,即此处传参传过去的数据为 10,之后 a再自增。
又例如: int a = 10;
test( --a );
前置--的规则为先自减,再使用,即 a 先自减再将数据传过去,即传参的值为 9 .
8、* //间接访问操作符(解引用操作符)
注:解引用操作符是配合指针一起使用的
eg. int a = 2;
取出 a 的地址放到变量 p 中,即 p = & a , 变量 p中存放的是地址,所以变量 p为指针变量,其类型为 int*,故而 int* p = &a ;
然而指针变量p 存放变量a 的地址的目的是什么?为了有一天可以通过p中存放的地址而找到 p所指向的对象——a. 那么如何可以通过p中存放的地址而找到 p所指向的对象呢?此时便用到了解引用操作符*,即 *p = a ;
既然 *p = a; 那么想要更改变量 a 便有两种方式,一是直接对 变量 a 进行修改,即 a = 3;
而是利用变量 a的地址绕过变量 a ,去修改(也可以这样理解:不管变量a 是否同意我能不能修改它的值,我都能通过对a 的地址解引用的方式去修改 a 存放在内存中的数据)
例如:
const int a = 2;
a = 3; //此处编译器会报错,因为 const 修饰的常变量具有常属性,即不可被更改
int* p = &a;
*p = 3; //此方式可行
看一下面例子的运行
例4:

而若通过对存放变量a 的地址的指针变量解引用再赋值,则是行得通的
代码如下:
#include<stdio.h>
int main()
{
const int a = 2;
//a = 3; const 修饰的常变量具有常属性,即不可被更改
int* p = &a;
*p = 3;
printf("%d\n", a);
return 0;
}
代码运行结果:

显然 const ”锁定“ 了变量a ,让它不得通过变量a 的途径直接修改其值,但是任然可以通过对存放其地址的指针变量解引用,以”绕过“变量a 的方式,从根本修改了a 的值。
9、(类型) //强制类型转换
( ) 中放的是想要强制转换的结果类型
eg1. int a = ( int ) 3.14;
注:此处的3.14 为double 类型,(遇到浮点数,编译器会默认为double类型,float 类型写成:3.14f )
变量 a是整型,然而3.14却是 double类型,int 类型所占内存空间大小为 4byte,而double 类型所占内存空间大小为 8 byte。显然,如果要将长度长的数据放到长度小的”盒子“里面,势必会让它丢失一些数据。由于类型不和,故而须得让double 类型的数据强制类型转换为 Int 类型,从而可以将数据存放在 int 类型的”盒子“中。
注:强制类型转换将浮点型转换为整型,小数点后的数据是直接丢弃的
eg2. srand((unsigned int) time(NULL));
此处(unsigned int)变为强制类型转换;
srand --> sets a random starting point. 设置一个随机起点--> void srand( unsigned int seed);
time --> geta the system time. 获取系统时间 --> time_t time ( time_t* timer);
srand() 所需参数的类型为 unsigned int ,而 time() 的返回值为time_t, 转到定义,time_t 就是long long 类型,而若想要让time() 的返回值作 srand() 的参数,就得用到强制类型转换。即写为 srand((unsigned int) time(NULL));
三、关系操作符
> //大于
>= //大于等于
< //小于
<= //小于等于
!= //不等,用于测试”不相等“
== //相等,用于测试”相等“
注:1、当浮点数用 ”==“ 来进行比较时,由于浮点数本身存储就不精确,就也会导致一些浮点数的判断会不准确,即会出现问题
2、字符串之间相不相等的比较不能单纯地用关系操作符 "==",而是应该用库函数 strcmp
单纯地用关系操作符来比较字符串,eg. "==""abc"=="abcdef";
并不是在比较字符串中的内容是否相同,而是在比较两字符串的地址是否相同。
3、关系表达式通常返回0或者1,以表示真假。
4、在条件判断是注意区别赋值操作符”=“ 与相等操作符 ”==“;
if ( x=3 )
{
printf(" haha\n ");
}
分析:此处并不是将x 与3 进行比较,而是将值3赋给了x ;非0 即为真,便会执行;
程序员有时候为了避免错将”==“ 写作 ”=“ ,于是就常常这样写:
if( 3 == x )
{
printf( "haha\n ");
}
因为这样倒着写,一旦将”==“写作了”=“,编译器便会报错,这样便将运行时会存在的错误直接让编译器给拦截下来了;
5、关系运算符从左向右计算,多个关系运算不宜连用;
i< k < j 这是合法的式子,编译器不会报错,但是在计算机中并不是我们平时理解意思,即表达的意思并不是指 k在 i和 j 的范围之间;因为关系运算符是从左向右进行计算,如果想表达出 i< k 并且 k < j,应该这样写:
i < k && k < j
表达式为真,返回0;表达式为假,返回1;
eg. i = 1 ; k = 3; j = 2;
i< k 表达式成立,返回值为1;而 1 < j 也成立,所以为真,其返回值为1;
四、逻辑操作符
&& //逻辑与
|| //逻辑或
注:有硬性规定,当逻辑操作符所在的表达式为真时,返回值为1;为假时,返回值为0
1、&& //逻辑与
注:可以理解为并且的意思
规则:只要有一个为假便为假,均为真才为真


2、|| //逻辑或
注:可以理解为或者的意思
规则:只要有一个为真便为真,全为假才为假


思考:那么我们现在就得知:
&&逻辑与--> 只要发现有一个为假,整个表达式便是假的;遇到一个为假,就不用去判断后面表达式的真假;
|| 逻辑或 --> 只要有一个为真,整个表达式便是真的;遇到一个为真,就不用再去判断后面表达式的真假;
例6-1:
代码如下:
#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\ni=%d\n", a, b, c, d, i);
return 0;
}
代码运行结果如下:

分析:&& 逻辑与--> 只要有一个为假那整个表达式都为假,即后面的表达式没有判断的必要;此处 a++,为后置++,先使用再++,即先&&,再让a自增;而a = 0; 已然为假,所以后面的 ++b、d++ 也就不参与计算了;故而 i = 0 ; a = 1; b = 2; c = 3; d = 4;
例6-2:
代码如下:
#include<stdio.h>
int main()
{
int i = 0, a = 1, b = 0, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a=%d\nb=%d\nc=%d\nd=%d\ni=%d\n", a, b, c, d, i);
return 0;
}
代码运行结果如下:

分析:&& 逻辑与--> 只要有一个为假那整个表达式都为假,即后面的表达式没有判断的必要;而在未遇到假之前就要一直计算下去,直到遇到假或者表达式结束;b = 0; ++b, 前置++,先自增再使用,即再&& 时,b = 1; 均为真,故而整个表达式(a++ && ++b && d++)的返回值为1,即 i= 1;所以 a = 2; b = 1; c = 3; d = 5;
例6-3:
代码如下:
#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\ni=%d\n", a, b, c, d, i);
return 0;
}
代码运行结果如下:

分析:|| 逻辑或 --> 只要有一个为真,整个表达式便是真的;遇到一个为真,就不用再去判断后面表达式的真假;a++;为后置++,先使用再++,即先&& 而后 a再自增;此处为假,便继续向下计算,++b,前置++,b先自增而后 b再&&,此处为真;因为|| 逻辑或,只要有过一个为真,整个表达式便为真,所以后面的 d++就没有计算的必要了;故而 i = 1 ; a = 1 ; b = 3 ; c = 3; d = 4;
同理:当 || 遇到假或者整个表达式计算结束,就不再计算了;即 当|| 未遇到真 就到一直计算下去直到整个表达式计算完。
五、条件操作符
exp1 ? exp2 : exp3
注:条件操作符又称三目操作符
规则:从左到右依次计算
注:理解 exp1 ? exp2 : exp3,当表达式1(exp1)成立,即为真时,便会执行表达式2(exp2),即表达式2 计算的结果为整个 表达式(exp1 ? exp2 : exp3 )的结果;反之,当表达式1(exp1)不成立,即为假时,便会执行表达式3(exp3),即表达式3 计算的结果为整个 表达式(exp1 ? exp2 : exp3 )的结果.
条件操作符的存在可以使得表达式更加整洁
例如:
在求最大值时:
if ( a > b )
printf ( "%d\n " , a);
else
printf( "%d\n",b);
如若是利用条件操作符,便可以写成:
printf( "%d\n", (a>b ? a : b ) );
六、逗号表达式
exp1 , exp2 , exp3,...expN
注:逗号表达式就是用逗号隔开多个表达式
规则:逗号表达式,从左向右依次计算,整个表达式的结果为最后一个表达式的结果。
例7:
代码如下:
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a++, b=b + a, a=b + 2,2 * b + a);
printf("%d\n", c);
return 0;
}
代码运行结果如下:

分析:由于在逗号表达式中,a++也是一个表达式,在这里没有使用的过程,所以a只需要自增;a++--> a = 1; b = b +a ; --> b = 4; a = b+2; --> a = 6; 最后一个式子:2 * b + a的结果便为整个表达式(a++, b=b + a, a=b + 2,2 * b + a)的结果,即 c = 14; 故而输出为14;
思考:逗号表达式中的所有表达式都会执行,只不过此逗号表达式的结果取决于逗号表达式中的最后一个表达式--> 那么,可以利用这个特点来简化表达式;
eg.1 :
b = 3*a + c;
c = 2 * c + a;if (c > b)
printf("hehe\n");可以简写为:
if(b = 3 * a + c,c = 2 * c + a,c > b)
printf("hehe\n");同样也适用于while循环中,看一下例子
eg.2:
count_val();
while (a > 0)
{//一些代码
a = get_val();
count_val();
}可以简写为:
while (a = get_val(), count_val(),a > 0)
{
//一些代码
}
七、下标引用操作符
[ ] //下标引用操作符
注:其操作数为: 一个数组名+ 一个索引值
int arr[10] = { 0 }; //数组的初始化 ; [ ] 的操作数为 arr 和 10
arr [ 9 ] = 10 ; //利用下标来访问数组的元素 ; [ ] 的操作数为 arr 和 9
思考,既然[ ] 有两个操作数; 而 2 + 3 = 3 + 2 --> [ ] 是否也可以像 + 一样交换它的两个操作数呢?

监视器:

显然 写成 3 [ arr ] = 3; 编译器未报错并且成功将 3 这个数据放到了数组中;
注:定义、初始化数组的时候不能这样写,只有当访问数组的某一个元素时,可以这样写;
八、函数调用操作符
作用:接收一个或者多个操作数
注:函数调用操作符的第一个操作数是函数名,剩余的操作数就是传递给函数的参数;所以函数调用操作符至少有一个操作数;
九、结构体成员
. //点操作符
-> // 箭头操作符
访问一个结构体成员可以用两种方式:
一是利用结构体变量名 . 成员名 ;变形:解引用结构体变量的指针 . 成员名
二是利用结构体变量的指针 -> 成员名
例8:
代码如下:
#include<stdio.h>
struct stu
{
char name[10];
int age;
char tel[12];
};
void print(struct stu* p)
{
printf("name=%s\nage=%d\ntel=%s\n", (*p).name, (*p).age, (*p).tel);
//这种方法本质就是 结构体变量.成员名
printf("name=%s\nage=%d\ntel=%s\n", p->name, p->age, p->tel);
}
int main()
{
struct stu s = { "zhangsan",19,"123456789"};
printf("name=%s\nage=%d\ntel=%s\n", s.name, s.age, s.tel);
print(&s);
return 0;
}
代码运行结果如下:

总结
1、赋值可以是单个赋值,也可以是连续赋值;连续赋值时,在调试的过程中是看不到值得变化过程,故而不太推荐只用连续赋值
2、-(负值)、+(正值) 没啥价值,但是存在此操作符;
3、sizeof 的操作数可以是类型、变量、表达式;当sizeof 的操作数为类型时,()不可省略;当sizeof 的操作数为变量、表达式时,()可要可不要。sizeof的操作数若是表达式,此表达式是不会参与运算的,是根据表达式的类型得到大小。因为在编译期间便将 sizeof 给清除了,而表达式的计算只有在程序运行的时候才能执行。
4、~ 按位取反:对一个数存放在内存中的二进制序列——补码(面向所有位,包括符号位),进行按位取反;
5、前置:先自增(减),再使用
后置:先使用,再自增(减)
6、* 间接访问操作符(解引用操作符)配合指针一起使用
7、强制类型转换:( ) 中放的是想要强制转换的结果类型
8、当浮点数用 ”==“ 来进行比较时,由于浮点数本身存储就不精确,就也会导致一些浮点数的判断会不准确,即会出现问题;字符串之间相不相等的比较不能单纯地用关系操作符 "==",而是应该用库函数 strcmp 。
9、&&逻辑与,可以理解为并且的意思。规则:只要有一个为假便为假,均为真才为真--> 只要发现有一个为假,整个表达式便是假的;遇到一个为假,就不用去判断后面表达式的真假;
|| 逻辑或 ,可以理解为或者的意思。规则:只要有一个为真便为真,全为假才为假--> 只要有一个为真,整个表达式便是真的;遇到一个为真,就不用再去判断后面表达式的真假;
10、 exp1 ? exp2 : exp3 ;条件操作符又称三目操作符,规则:从左到右依次计算;
11、逗号表达式:就是用逗号隔开多个表达式;exp1 , exp2 , exp3,...expN;
规则:逗号表达式,从左向右依次计算,整个表达式的结果为最后一个表达式的结果。
12、[ ] 下标引用操作符,其操作数有两个: 一个数组名+ 一个索引值;
arr [ 9 ] = 10 ; --> 9[ arr ] = 10; --> 定义、初始化数组的时候不能这样写,只有当访问数组的某一个元素时,可以这样写.
13、函数调用操作符的第一个操作数是函数名,剩余的操作数就是传递给函数的参数;所以函数调用操作符至少有一个操作数;
14、访问一个结构体成员可以用两种方式:
一是利用结构体变量名 . 成员名 ;变形:解引用结构体变量的指针 . 成员名
二是利用结构体变量的指针 -> 成员名

被折叠的 条评论
为什么被折叠?



