C语言 #赋值操作符 #复合操作符 #单目操作符 #关系操作符 #逻辑操作符 #条件操作符 #逗号表达式 #下标引用操作符 #函数调用操作符 #结构体成员

系列文章目录

操作符的分类:

 算术操作符 、强制类型转换 、关系操作符 、条件操作符 、逻辑操作符 文章链接:http://t.csdnimg.cn/7nuOa

移位操作符 、位操作符 文章链接:http://t.csdnimg.cn/wJCub

本文:赋值操作符 、复合操作符 、单目操作符 、关系操作符 、逻辑操作符 、条件操作符 、逗号表达式 、下标引用操作符 、函数调用操作符 、结构体成员


文章目录

前言

一、赋值操作符

二、复合赋值符

三、单目操作符

1、!  //逻辑取反操作

2、-  //负值

3、+  //正值

4、&   //取地址

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

6、~        //对一个数的二进制按位取反

7、--       //前置、后置--

      ++     //前置、后置++

8、*       //间接访问操作符(解引用操作符)

9、(类型)    //强制类型转换

三、关系操作符

>  //大于

>=  //大于等于

<  //小于

<=  //小于等于

!=   //不等,用于测试”不相等“

==  //相等,用于测试”相等“

四、逻辑操作符

1、&&  //逻辑与

2、||   //逻辑或

五、条件操作符

 exp1 ? exp2 : exp3 

六、逗号表达式

exp1 , exp2 , exp3,...expN

七、下标引用操作符

[ ]   //下标引用操作符

八、函数调用操作符

九、结构体成员

.      //点操作符

->     // 箭头操作符

总结


前言


一、赋值操作符

=

赋值可以是单个赋值,也可以是连续赋值

注:连续赋值时,在调试的过程中是看不到值得变化过程,故而不太推荐只用连续赋值

例1:

int a = 1; //初始化:在创建变量的时候就给它一个初始值

int b = 2;

int c = 3;

a = b = 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、访问一个结构体成员可以用两种方式:

一是利用结构体变量名 . 成员名 变形:解引用结构体变量的指针 . 成员名

二是利用结构体变量的指针 -> 成员名

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值