C语言问答进阶--4、基本运算符

赋值运算符

A:下面将介绍赋值操作符。它的符号就是 = .

A:赋值操作符,就是把一个值赋值给左边的变量。正如以前说的形如a=1之类的表达就是赋值运算符的具体应用。

也许有的人在编写代码的时候写过这种代码:

#include "iostream.h"

int main()

{

    int x;

    1=x;                  

cout<<x<<endl;

return 0;

}

当然会编译错误了:说”=”的左操作数必须是左值;

当然大家想想也都能明白: 赋值操作符的作用是把右边的数赋值给左边,而此时左边是个常量,那么赋值当然不会成功;

A:下面将举个例子说明对较多变量的赋值:

#include "iostream.h"

int main()

{

  int a,b,c,d=1;

  a=d;

  b=d;

  c=d;

  cout<<a<<endl<<b<<endl<<c<<endl<<d<<endl;

  return 0;  

}

Q:那么不能像下面这样赋值吗?

#include "iostream.h"

int main()

{

  int a,b,c,d=1;

  a=b=c=d;

  cout<<a<<endl<<b<<endl<<c<<endl<<d<<endl;  

  return 0;

}

A:当然可以。下面我们将具体分析a=b=c=d;这条语句:

它是一个语句,语句是由表达式组成的,表达式是a=b=c=d  ;

这个表达式含有赋值运算符:它的结合性是右结合:即从右向左计算。即是:

a=b=(c=d)

第一步是执行c=d   ;

然后呢,它的值就是c的值1了;

又执行:a=(b=1)   ;

同理得到a、b、c、d的值都是1.

Q:结合性是如何表现的?

A:如下:

#include <iostream>

using namespace std;

int main()

{

   int a=2;

   a+=a-=6;

   cout<<a<<endl;

   return 0;

}

难点在于 a+=a-=6; 

把握一个原则:在表达式中出现多个运算符,第一就要考虑优先级;优先级如果考虑好了,但是还可能有歧义,那么就要考虑结合性。

a+=a-=6 中两个运算符 +=  -= ; 他们的优先级是一样的,包括赋值操作符在内也是一样的。

那么就考虑结合性!这类复合赋值操作符是右结合的,所以首先计算a-=6 ;

a+=(a-=6);

此得到-4 ;且a的值就是-4.

接着呢?计算a+=-4 ;

得到-8.

关系运算符

关系运算符,就是数据关系的运算符。它包括了<  >  <=  >=  ==  !=这六个。

#include <iostream>

using namespace std;

int main()

{

    int a=1,b=2,c=2;

    cout<<(a<b<c)<<endl;  

    return 0;

}

这个和以往程序不一样的是:a<b<c .

a<b这个似乎很好计算:因为它只是个表达式。

那么多了一个又如何算?

把握一个中心:C语言中对表达式的处理只是按照优先级和结合性来一一计算。

也就是先分析优先级,因为都一样,所以要考虑结合性。

< 是左结合的,所以先计算a<b ;

即是计算如下表达式:

(a<b)<c ;

显然得到0.

条件运算符

? :

这是个关于条件运算符的程序。

#include "iostream.h"

int main()

{

    int a=2,b;

    (a==2)?b=4:b=5;

    cout<<b<<endl;

    return 0;

}

条件运算符先是看?前面的表达式是否为真,为真时则:前面的表达式的值就为此条件表达式的值,否则:后面的表达式的值为此条件表达式的值。   

位运算符

位操作符,应该说,这个操作符把C语言和硬件拉近了距离。

它是对位进行操作的运算符,位从哪里来?当然是此数的二进制补码格式了。

<<  >>  <<=  >>=

下面是个关于移位的程序:

#include "iostream.h"

int main()

{

   int a=4,b;

   b=a<<2;

   cout<<b<<endl;       

   return 0;

}

"<<"被称作移位操作符中的左移操作符,它的作用使得操作数的各二进制位像左移动数位。

b=a<<2;  的作用就是a值得二进制形式向左移动2位,当然是整体移动了。

在计算机中,C语言的整数类型int以补码形式表示,而a的表达就是:

0000 0000 0000 0000 0000 0000 0000 0100

向左移动2位,得到:

0000 0000 0000 0000 0000 0000 0001 0000

即是数据16.

所以得到16.

两个是关于移位的例子:

#include "iostream.h"

int main()

{

  int i=1;

  i<<=32;

  cout<<i<<endl;

  return 0;  

}

#include "iostream.h"

int main()

{

  int i=1;

  i<<=31;

  cout<<i<<endl;  

  return 0;

}

下面是关于取反操作符的程序:

#include "iostream.h"

int main()

{

    int i=0;

    i=~i;

    cout<<i<<endl;

    return 0;

}

i的形式为:

0000 0000 0000 0000 0000 0000 0000 0000

~i即是将i的各位的二进制形式取反:

1111 1111 1111 1111 1111 1111 1111 1111

又整数int类型在内存中以补码形式存放,所以它的值被看成-1.

这个运算符要与"非"运算符 ! 区分开来:

#include "iostream.h"

int main()

{

    int i=0;

    i=!i;

    cout<<i<<endl;

    return 0;

}

非运算符是逻辑运算符,逻辑在计算机中就是0或1,即对的或错的。

0的非就是1,非0的非就是0.它的计算不细致到每一个二进制位;

而取反运算符是细致到每一个二进制位的。

是个关于移位操作的程序:

#include <iostream>

using namespace std;

int main()

{

    int i=1;

    i<<=33;

    cout<<i<<endl;      

    return 0;

}

引用

&表示引用的含义,这是C++中的一个运算符,C中并没有此含运算符

#include "iostream.h"

int main()

{

   int a=2;

   int & b=a;

   cout<<a<<endl;

   cout<<b<<endl; 

   return 0;

}

引用是什么?它不过是变量的别名。什么是别名?就是它和所引用的变量代表同一个变量。

对它们任意一个的操作都将是对每个引用的操作。

那么编译器如何实现这个机制?

其实说到底,这还是在指针层次进行操作的。或者说,引用本身不占用内存空间,它是指向被它引用变量的。

正如本例:

int & b=a; 声明了变量b为变量a的引用;计算机只为变量a分配了内存空间,而没有为变量b分配空间,它是指向a的内存空间的。

可以这么认为: int & 就是引用类型,当然只是一种理解方式。

后面分别打印a和b变量的值,当然都只是在打印变量a的值。

当然,引用得是同一种变量类型了;如下,就会编译错误:

#include "iostream.h"

int main()

{

   int a=1;

   float &b=a;                  

   cout<<b<<endl; 

   return 0;

}

出现如下的编译错误:

【把引用和指针联系起来】

 就像一种指针类型变量只能指向此指针类型对应的变量类型一样,引用也是这样。

【深入引用类型】

其实,可以把引用看成是指针的缩减版。它的产生与人们对指针的恐惧有关,指针可能让程序员自己都不知道到底指到哪了,但是引用让程序员很清楚到底对什么变量进行处理的。

Q:那么可以把int &看成是引用类型名了吗?

A:是的。以后会遇到的的指针类型一样有类似的道理,指针的类型名看以看做是int *.

Q:刚刚您说这里的变量b是对变量a的引用,那么也就是说对b的操作也就是等于对a的操作了?

A:是的。

#include "iostream.h"

int main()

{

   int a=2;

   int & b=a;

   cout<<a<<endl;

   cout<<b<<endl;

   b=3;

   cout<<a<<endl;

   cout<<b<<endl; 

   return 0;

}

在代码b=3;处对变量b进行了改变,那么最后打印的变量a的值也跟着变了。

Q:既然可以把int &看成是是一种类型,那么这样为什么会出现编译的错误呢?

#include <iostream>

using namespace std;

int main()

{

    int a=1;

    int &b;

    b=a;

    cout<<b<<endl;

    return 0;

}

编译错误信息为:

A:呵呵。这个错误是在int &b; 这个位置,你得在后面加上初始化的值!

Q:为什么一定要这样呢?

A:如果要知道原因,这个要从引用这个类型角度出发了,引用最初被定义为只能对一个变量进行引用,即声明的引用变量不能再对第二个变量进行引用。正是因为如此,引用类型的变量都被要求在声明的时候对它进行初始化,正如前面的:

int & b=a;

【引用类型变量只能对一个变量进行引用】

可能有人会想,一个引用变量对某个变量进行引用了,那么为什么它不能再对别的变量进行引用呢?

可是,如果假设刚刚的想法成立,那么它和指针还有什么区别呢?

引用是安全的“指针” 。

Q:哦。也就是如下的代码一样会编译错误是吧。

#include <iostream>

using namespace std;

int main()

{

    int a=1,b=2;

    int &c=a;

    cout<<c<<endl;

    c=b;

    cout<<c<<endl;

    return 0;

}

但是这段代码是可以正确通过编译的,且执行如下:

A:上面得到的结论是正确的。

注意 c=b; 这条语句,它的作用仅仅是把变量b的值赋值给引用变量c.

这合情合理,它并不是让引用变量c重新引用变量b.

运算符的优先级

这个问题是由于程序代码中可能有不同类型变量或不同运算符在一起参与运算,而导致必须有个明确的先算哪个再算哪个的概念,在C语言中也就是优先级的概念。

#include "iostream.h"

int main()

{

    int a=3,b=4,c=5;

    int d=a*b%c;

cout<<d<<endl;

return 0;

}

执行结果是:

【常用运算符优先级】

现在介绍下常用运算符的优先级:

从我们最熟悉的  +  -  *  / 开始说起,*和/高于+和-.且*和/的优先级是相同的,那么,如果有如下运算式2*3/4,那么是先算2*3(得到6),再接着计算6/4的值,这里又涉及到一个结合性的问题。而,对于 + 和-,它们还可能有一个别的用途,那就是当作数据的符号,如果是这种情况的话:它们的优先级就很高了,超过乘法*和除法/的优先级;

另外,还有高级语言一般都有的运算符%,它是求余运算符,它的优先级和* 以及 /是相同的。

括号是标准的最高优先级,而逗号运算符是标准地最低优先级,而比逗号运算符高一级的就是赋值运算符、复合赋值运算符。

【结合性问题】

这个问题的产生来源于在表达式中会产生有相同优先级的运算符在一起参与运算,这导致了编译器也不明白到底是该从左向右计算呢,还是应该从右向左计算呢,而这正是结合性中左结合性和右结合性要解决的问题。

左结合性就是在这种场合下从左向右计算,而右结合性正好相反,是从右向左计算。

赋值运算符就是右结合性的典型代表(实际上,除了赋值操作符、自增自减运算符、正号负号运算符和条件运算符等之外,别的运算符一般都是左结合性):

a=4;   (a是之前定义的变量)  这里表达的意思是把4这个值传递给变量a,而从编译器把代码编译的汇编来看呢,一般是mov操作符的汇编代码。

从这段描述我们可以看出来,它是从赋值运算符右边的数4开始执行操作的,向左把值传递给了赋值运算符左边的变量a.

它不像1+2这种表达,从左边的1开始,再接着加上一个数2,这对应的汇编代码可能为:

mov ax,1

mov bx,2

add ax,bx

(这些指令只是可能的一种形式)

我们可以看出来,这是从左边的1开始执行操作的,这就是左结合性的体现。

【括号的作用】

这没什么,也许您对于 “*”和”%”的优先级还有些犹豫,那么您最好加上括号,清除您一切的怀疑,同时这也是可读性高程序的表现,推荐在必要的时候用括号。

如下:

#include "iostream.h"

int main()

{

    int a=3,b=4,c=5;

    int d=(a*b)%c;

cout<<d<<endl;

return 0;

}

自增自减运算符

现在我们来研究自增运算符。

++  --

正如自增的含义,它的作用是此变量的值增加1;自减就是把自己的值减少1.

这个运算符存在的必要性很强,因为程序代码中有很多的时候对一个变量进行增加或减少,尤其是在循环的时候,此非常常见。

而,对应于汇编指令,也有相应的专用指令:

inc 指令和dec指令。

如下将举例:

#include "iostream.h"

int main()

{

   int x=1;

   cout<<++x<<endl; 

   return 0;

}

执行结果为:

#include "iostream.h"

int main()

{

   int x=1;

   cout<<x++<<endl; 

   return 0;

}

执行结果为:

这就是自增运算符的两种情形,一个是先自增1,然后使用;

一个是先被使用,然后自增1.

我想这个道理就不用多说了;可是有个问题得去考虑下:

有一天,我不小心把程序多写了几个符号:

#include "iostream.h"

int main()

{

   int x=1,y=2;

   cout<<++(x+y)<<endl; 

   return 0;

}

大家先想下结果会是什么呢?

很可惜,编译错误了:

意思是++运算符的操作数不能是超过1个的;

想想也有道理,如果是这样那么到底是x自增还是y自增呢?

【深入自增和自减运算符】

这样的程序您应该知道结果了:

#include "iostream.h"

int main()

{

    int i=1;

    cout<<i<<endl<<i++<<endl<<i+2<<endl;

    return 0;

}

您认为将依次打印 1  1  4 吗?

但是很可惜,结果是:

似乎有点奇怪了:

换成这样的程序:

#include "iostream.h"

#include"stdio.h"

int main()

{

    int i=1;

    cout<<i<<endl;

    cout<<i++<<endl;

cout<<i+2<<endl;

return 0;

}

结果就是我们刚刚想的了:

为什么?

再看这个代码:

#include"stdio.h"

int main()

{

    int i=1;

printf("%d\n%d\n%d\n",i,i++,i+2);

return 0;

}

您会认为结果是什么呢?

还是刚刚的1  1  4吗?

结果一定又让您大吃一惊了:

似乎真的让人很吃惊。

【验证自增的两种形式】

我们以前曾经分析过一个变量i的i++和++i的区别:现在来用程序来验证下:如下:

#include "iostream.h"

int main()

{

int i=3,temp;

    temp=i;

    if(temp+1==++i)

      cout<<"++i是先++,再用i"<<endl;

    return 0;

}

这个:

#include "iostream.h"

int main()

{

int i=3,temp;

    temp=i;

    if(i==i++)

      cout<<"i++是先用i,再++"<<endl;

    return 0;

}

结果就是:

结论和我们以前的结论是一样的!

当然我们应该多学学用别的方式去证明已有的结论或是用学过的方式来预测一个事件未来的结论!

取地址运算符

A:从此运算符的名字我们就可以猜出来,是得到一个变量地址的运算符。

#include "iostream.h"

int main()

{

    int i=1;

    int j=2;

    cout<<&i<<endl;

    cout<<&j<<endl;

    

    return 0;

}

执行如下:

在这里,我们可以看到打印了变量i和j的地址。

Q:这两个地址好像很相近?

A:是的。你求下它们之间的差是多少?

Q:4.

Q:int类型变量占据的内存空间是多少(32位机器上)?

A:4字节。

A:变量i和j都是存放在栈中的,而且根据此程序,它们是相临存放的。在这里,对照i和j的位置,符合吗?

Q:符合!

【虚拟地址和物理地址】

我们说,程序的执行是在物理内存和CPU的寄存器或外设接口寄存器等物理设备中进行的。

也就是说,真实程序的真正执行不必考虑虚拟地址。

但是,虚拟地址是为了扩展一个程序的大小而设定的。

在32位机器上,任何一个进程都可以占用4G的虚拟地址空间。一般来说,其中低2G空间是用户程序空间,而高2G空间是系统空间。

而我们写的程序代码、静态数据、堆栈、堆全部都是在用户程序空间的。当然,就我们如今一般的机器而言,内存达到2G就算很不错了。就别说4G的空间了。

所以说,4G是虚拟空间。程序一般使用虚拟地址,即刚刚

这里使用的也是虚拟地址。每个虚拟地址都能对应于唯一的物理地址(即在物理内存中的地址),而且不同的虚拟地址对应的物理地址一定不一样(出错的情况下为例外)。这需要一个转换,操作系统帮我们做了这个工作。

【char类型地址的诡异现象】

#include "iostream.h"

int main(){

char a='A',b='B';

cout<<&a<<endl;

cout<<&b<<endl;

return 1;

}

最后打印的结果可能是乱码,因为已经开始输出字符串了;

某一时刻的执行结果:


微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。

我是程序员小迷(致力于C、C++、Java、Kotlin、Android、iOS、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

欢迎关注。助您在编程路上越走越好!

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值