目录
前言
啊哈兄弟们大家好啊!我们又见面了看了数组这方面的内容感觉还行吧,那么今天我们这篇文章重点就来讲讲我们c语言的操作符相关的内容,通过这篇文章你将知道我们平时遇到的操作符的功能具体是啥,这样我们以后再遇到这些操作符的时候就不会再一脸懵逼了,那么我们废话不多说开始吧,
算术操作符
我们首先看看有哪些算数操作符
前三个大家应该都十分的熟悉,他们的功能就分别的对应我们数学中的加,减,乘,那么这里大家可能会感到困惑的就是这里的后两个/和%的区别,首先我们这里先举一个例子:5÷3=1…2那么我们这里的1我们称为就称为商,而这里的2我们就称为余数,但是在我们的c语言当中是没有(÷)这个操作符的,所以我们这里就用/和%来代替我们数学当中除号的功能,其中除法操作符(/)得到的结果就是我们数学当中的商,我们的取模操作符(%)得到的就是我们数学当中的余数,就拿上面的例子来说我们如果将这个算式放到我们的c语言当中那么就会得到这样的结果:5 / 3 = 1 , 5 % 3 = 2,那么这时就有小伙伴们就要问了,为什么c语言当中的除法不能得到小数啊?那么这里我就来给大家介绍一下另一个情况,我们上面的5 / 3 = 1 这里都是两个整数的相除得到的是商,但是我们只要将操作符( / )对应的两个操作数的其中一个改成浮点型,我们得到的结果就是浮点型也可以说成小数,我们来看看下面代码的运行的结果:
#include<stdio.h>
int main()
{
float a = 5.0 / 2;
float b = 5 / 2.0;
float c = 5.0 / 2.0;
printf("%f\n", a);
printf("%f\n", b);
printf("%f\n", c);
return 0;
}
我们来看看这个代码的运行结果:
但是这时有小伙伴们就要问了,那么取模操作符能不能也把两边的数据改成浮点型来得到同样的效果呢?那么这里答案是不行的哈,因为我们c语言规定我们的取模操作符的两边必须得是整型不能是浮点型哈,那么这里我们的两个操作符就介绍到这里。
移位操作符
首先我们来看看移位操作符有哪些:
我们在介绍这两个操作符之前,我们首先介绍一下数据在内存中存储的情况,首先我们要说的是数组在内存中的存储有三种表达形式:分别时原码,反码,补码,那么我们平时说所的二进制的形式都是原码,比如说3的二进制就是11,但是我们一般都是按照整型的类型来存储这个3,所以我们这里有32个字节,也就是说有32个二进制的数字,那么我们3的原码就是这样:
但是这是我们平时人看到的认为的二进制表达情况,但是我们计算机在存储3的时候却不是按照原码的形式来存储,它是按照补码的形式来进行存储,所以我们这里是有一个转换的过程,我们的原码得先转换成反码,再把反码转换成补码,那么这里就会有对应的转换法则,第一点就是正数的原码,反码,补码的形式是一样的,如果是负数的话则原码除符号位以外按位取反得到反码,反码再加1得到补码,那么我们这里就拿-3来举一个例子,我们把二进制的最高位看作是符号位,1就代表着负数,0就代表着正数,那么我们-3的二进制原码的形式就是:
然后我们再将除最高位的其他位按位取反就可以得到反码其形式为:
最后再加上1就可以得到其补码的形式为:
看到这里我们知道了数据存储中的原码反码补码之间的联系,那么这里我们就可以来看看这两个操作符的具体作用是什么,首先来介绍一下左移操作符,这个操作符的规则就是:左边抛弃,右边补0,那么这个左移它移动的是我们这个数据存储中的补码,不是原码,那么这个移到底是怎样移动的呢?那么我们来看一个具体的例子就知道了:
#include<stdio.h>
int main()
{
int a = 10;
a = a << 1;
printf("%d", a);
return 0;
}
我们首先写出10的补码,因为这里的10为正数所以我们这里补码的形式与原码的形式一模一样,那么其补码的形式如下:
我们将这32个二进制数字放到一个大的格子里面,那么我们这里的左移就是将这一整个二进制的数字向左移动一个位子,那么这样的话就会出现一种情况就是有最左边的数字会出这个各种,最右边的就会空出一个格子,那么此时呈现的情况就是这样:
我们可以看到我们向左边移动一个位子就会使得最左边的0出这个方格,而这个方格的最右边就会空出一个位置出来,那么我们这里再看看我们这个左移的规则:左边抛弃,右边补0,按照这么看的话我们最左边的0就会被抛弃,最右边的空格就会添加一个0进来,那么移动完之后的情况就是这样的:
这是二进制的补码,但是这个补码代表的是正数,所以这里的补码反码原码都相同,所以我们将上面的二进制数就表示的是:20,所以啊我们就法先我们的左移操作符就有一个功能就是向左移动一位就会让我们的数据的大小变大两倍,那么这里我们还不能做出这么绝对的判断,因为我们这里就只举了一个例子,如果对于其他的数据左移是不是也一样的变大两倍呢?如果真的按照上面说的,那么我向左移动两位是不是应该会变大四倍呢?向左移动三位是不是应该变大8倍呢?我们可以再来写几段代码来验证一下我们的结果:
#include<stdio.h>
int main()
{
int a = 7;
int b = 8;
int c = 9;
printf("一开始a的值为:%d\n",a);
a = a << 1;
printf("移动1位之后的结果为:%d\n", a);
printf("一开始b的值为:%d\n",b);
b = b << 2;
printf("移动2位之后的结果为:%d\n", b);
printf("一开始c的值为:%d\n",c);
c = c << 3;
printf("移动3位之后的结果为:%d\n", c);
return 0;
}
我们来看看这段代码的运行结果,确实是跟我们想的一模一样:
那么看到这里想必大家可能就会有点疑问说:啊你上面举的例子都是正数的,那我们的负数向左移动一位也是扩大两倍吗,那么这里我们再来举一个负数的例子,我们来看看下面的一段代码:
#include<stdio.h>
int main()
{
int a = -3;
a = a << 1;
printf("负数a向左移动一位之后的值为:%d", a);
return 0;
}
我们还是来画图来一步一步的解决:首先我们把-3的原码写出来将他放到一个大括号里面,因为这里是负号,所以我们这里的最高位也就是符号位为1,所以我们的原码如下:
然后我们再将这一串数字除符号位以外的其他位全部按位取反就可以得反码:
然后我们在把反码的值加上1就可以得到补码:
那么按照上面的思路,我们向左一位就可以得到这样的情况
那么我们这里的1就会被删除,我们就会在后面的空格就会补上0,那么我们的得到的结果就是
但是我们这里得到的结果是补码,并不是原码所以我们这里就得再将他们转换成原码才能知道左移后的结果是什么,既然反码转换位补码得加上1,那么我们这里的补码转换成反码就得减去1,那么减去1 的结果就为:
再对这个反码除了最高位进行按位取反就可以得到原码:
那么这里的原码的表示结果就是-6,我们可以看看我们这段代码的运行结果:
那么根据我们上面的疑问我们这里就会有同样的猜想,我们对负数向左移动两位是不是也是扩大四倍呢?我们对负数向左移动3位是不是也是扩大八倍呢?我们可以再来写一段代码来看看结果:
#include<stdio.h>
int main()
{
int a = -7;
int b = -8;
int c = -9;
printf("一开始a的值为:%d\n",a);
a = a << 1;
printf("移动1位之后的结果为:%d\n", a);
printf("一开始b的值为:%d\n",b);
b = b << 2;
printf("移动2位之后的结果为:%d\n", b);
printf("一开始c的值为:%d\n",c);
c = c << 3;
printf("移动3位之后的结果为:%d\n", c);
return 0;
}
我们来看看这段代码的运行结果:
看到这里想必大家应该能得到一个结论就是对于整型不管是正数还是负数,只要我们向左移动一位我们就会对这个数的值乘以2,向左移动两位就会对他的值乘以4,后面就以此类推,向左移动n位就会对他的值乘以2的n次方,那么看到这里我们的左移操作符就讲到这里,我们来看看右移操作符会有什么不同,首先我们知道的一点就是我们将右移得分为两种情况,一个是逻辑右移,一个是算术右移,其中逻辑右移他的规则是:左边用0填充,右边丢弃,算数右移的规则就是:左边用原该值的符号位填充,右边丢弃。那么看到这里小伙伴就会有些许疑惑了,就是我们这里到底采用哪种右移呢?啊这里就要跟大家说一下就是我们计算机采用哪种右移得看编译器自己本身与我们本人无关,那么这里我们就得写一段代码来测试一下我们编译器是采用哪种右移了,在测试之前我们得先明白一件事情就是我我们这里采用正数来测试还是采用负数来进行测试呢?答案是必须得采用负数来进行测试,因为我们这两种右移方法主要是我们右边的补位不同,我们的逻辑右移不管原来的最高位是什么都补0,而我们的算数右移在补位的时候还得看我们原来的最高位是什么,如果是1他就补1,如果是0他就补0,那么这里我们就想一下我们正数的最高位是0,那么不管我们这里采用的是逻辑右移还是算术右移我们的最高位补的不都是0吗?那么如果采用正数来进行测试的话就测不出来了嘛,那么我们这里就得采用负数进行测试,我们来看看下面的一段代码:
#include<stdio.h>
int main()
{
int a = -6;
a = a >> 1;
printf("%d", a);
return 0;
}
首先我们将-6的补码写出来:
然后我们再向右移动一个位置就可以得:
那么我们这里就得进行两个假设了,如果是逻辑右移那么我们这里得到的补码的结果就为:
如果位算术右移,那么我们这里得到的补码就为
因为我们这里存储的是补码最高位代表着符号位,所以我们这里的逻辑右移这里的补码就代表着数,所以他的原码的结果就跟补码的结果一样为4294967293,如果是算数位移,那么我们这里就是负数,如果是负数的我们就得再把他转换成原码,那么转换之后的原码就为:
那么这里我们的就可看到我们算数右移得到的结果就为-3,所以啊我们这里就可以看到我们的逻辑右移和算术右移得到的结果是不同的,那么我们将上面的代码运行一下,看看我们的vs2022运行的结果为:
这就说明我们的vs2022采用的是算术位移,当然我们这里还可以向右移动多位,比如我们的-8向右移动两位得到的结果就是将其除以4等于-2,向右移动三位得到的结果就是将其除以8得到的结果就是-1,那么这里规律就和我们的上面的左移规律是一样的,说到这里我们的移位操作符就差不多讲完了,但是这里要提醒大家一点就是我们不论是向左移还是向右移都不能移动负数位,可能有小伙伴们要说了,向左移动-1位是不是就可以等价为向右移动一位啊,这里大家记住是不不行的哈,我们c语言从来没有定义过移动负数位这种概念,所以大家在写的时候注意一点哈。
位操作符
首先我们来看看位操作符有哪些:
首先来介绍第一个:按位与(&)这个操作符的作用就是对应的补码只要有0,那么我们的结果就会为0,这里我们来举一个例子:
#include<stdio.h>
int main()
{
int a = 14;
a = a & 20;
printf("%d", a);
return 0;
}
我们一开始将a初始化为14,那么这个14对应的二进制就为001110,当然我们这里的二进制有32位,我们这里就不一一写出来太占位子了我们就将前面的一些0全部都省略掉,那么我们的20的二进制就为010100,那么我们这里的a&b是什么意思呢?那么这里我们就将他们两的二进制数并列写,就是这样的情况:
那么这里的按位与就是将这里的六列进行运算,如果这一列里面只要有一个是0,那么这一列的结果就位0,比如说最右边的一列,两个都是0,那么这里的运算结果就为0,从右往左数的第二列,虽然这一列中有一个是1,但是还是有0,那么这一列的运算结果还是0,只有从右往左数的第三列两个都为1的,结果才能为1,那么我们这里的计算结果就为:
000100这个是二进制的补码,但是又是因为是正数,所以我们这里的原码也是000100,再将其转换为10进制,那么这个值就是4,所以我们的14(a)&20得到的结果就为4,我们来看一下这个代码运行的结果是否跟我们算的一样:
确实是一样的,那么有了上面的思路我们接着看下一个操作符:( | )按位或,那么上面的那个操作符是只要有0结果就为0,那么我们的按位或就是对应的补码只要有1那么对应的结果就为1,我们再来举一个例子:
#include<stdio.h>
int main()
{
int a = 15;
a = a | 21;
printf("%d", a);
return 0;
}
我们首先将15和21的二进制写出来,那么这里为了大家观看我们还是省略一部分的0,15的二进制为:001111,21的二进制为010101,我们将他们并列起来就可以得:
同样的这里有六列数据,这每一列当中只要出现一个1 ,那么这一列的计算结果就为1,所以我们这里就发现除了最左边的一列是两个0以外其他的列中都有1,那么我们这里的计算结果就为:
011111,这是二进制的补码,但是这个补码的最高位为0所以就为正数,所以这个二进制对应的原码反码补码都一样,所以我们就可以得到15(a) | 21的结果为31,我们运行一下这段代码可以发现结果确实是一样的:
那么最后我们就来介绍一下按位异或操作符( ^ )这个操作符的作用就是对应的补码只要相同就为0,相异就为1 ,比如说两个0或者两个1计算的结果就为0,如果是一个0和一个1那么计算的结果就为1,那么这里就不举例子了,大家可以自己下去尝试尝试因为大致相同不必多赘述,那么看到这里有小伙伴们就要问了,我们学这个有什么用啊,那么接下来我们来看几个综合一点的例子就知道了。
有关位操作符的例子
第一个例子:
不创建第三个变量,来实现两个变量的交换。
看到这个题,大家应该是十分有感触的,因为我们之前交换两个变量所使用的方法就是创建第三个变量,把第一个变量的值赋值给第三个变量,再把第二个变量的值赋值给第一个变量,再把第三个变量的值赋值给第二个变量,这样我们就实现了交换两个变量的目的,代码如下:
#include<stdio.h>
int main()
{
int a = 20;
int b = 30;
int c = 0;
c = a;
a = b;
b = c;
return 0;
}
但是我们这里是通过第三个变量来达到目的的,如果我们不创建第三个变量又是如何来实现的呢?那么这里就得靠我们的位操作符来实现了,首先我们先想一下按位异或的特性是什么?是不是相同为0相异为1 啊!那么也就是说两个相同的数字进行按位异或的话是等于0的,那么我们接下来再想一件事就是:0与其他非0的数字进行异或的话得到的结果还是那个非0的数字,因为相同为0相异为1而我们0的二进制都是0,所以这也就导致一种情况就是只要那个非零数字的二进制数据中为0的列,那么那些列的异或结果就是0,如果那些为1的列异或结果就为1,我们看个图来理解理解:
我们可以发现这个计算的结果和第一行的数据一模一样,那么根据上面的两个结论我们可以知道的一件事就是:这里3^3^5的结果还是5,那么看到这里我们还需要知道最后一个结论就是我们连续的异或事支持交换律的,比如说3^3^5也可以写成3^5^3这两个的结果都是一样的,都是等于5那么我们这里就可以回过头来想想我们这个问题:如何不创建第三个变量来实现第两个变量的数据的内容的转换,我们先创建两个a和b,将a初始化为20,将b初始化为30,那么我们先将a的值赋值成a^b,我们先不着急将他们的值算出来,然后再将b的值赋值成a^b,但是这里大家要注意的一点就是此时a的值是a^b也就是20^30,所以此时b的值就30^20^30,那么根据我们上面的三个上个结论我们就可以知道此时的值就为20,那么算到这里我们就完成了将a的值赋值到b的操作,那么接下来要做的事就是如何将a的值变成原来的b,这里大家首先要知道的一件事就是我们此时的a还是20^30,b的值变成了20,那么这里我们就只用将a的值异或上此时b的值就可以了:20^30^20,这样我们就将a的值赋值成了30,那么操作到了这里我们就实现了不创建第三个变量来实现两个变量的内容的转换,那么我们的代码如下:
#include<stdio.h>
int main()
{
int a = 20;
int b = 30;
printf("交换之前a的值为:%d\n", a);
printf("交换之前b的值为:%d\n", b);
a = a ^ b;
b = a ^ b;
a = b ^ a;
printf("交换之后a的值为:%d\n", a);
printf("交换之后b的值为:%d\n", b);
return 0;
}
我们可以运行一看看结果如何:
确实是如我们所愿,那么接下来我们就来看看下一题。
第二个例子
编写代码实现:求一个正数储存在内存中的二进制中1的个数。
那么这一题我们得用到上面学的位操作符和移位操作符来进行求解,首先我们知道的是我们的按位与的特点就是只要对应的补码中有一个为0那么这一列的运算结果就是0,那么这里我们就得反则来思考这个特点就是两个都为1,他的运算结果就是1,那么我们创建一个整型的变量将其赋值为1 ,那么将另一个非1的数据与这个1进行按位与运算的话,我们就会发现一件事情就是我们的的1除了最低位为1其他位都是0,那么这按位或的话得出最低位的结果可能是1可能是0的话,其他位的结果必定是0,那么把这个整个数据放到一起的话就是这样的结果:我们异或得到的结果要么是1要么就是0,而到底是1还是0则取决于我们要求的那个数据中的最低为是1还是0,那么看到这里大家是否想清楚一个事情没,我们这样的作法只能够求得一位也就是最低为到底是1还是0,那么我们如何将其推广到所有位呢?也就是这个数据在内存中的补码中的每一位呢,那么这里就得用到我们上面所说的移位操作符来是实现这个功能了,我们可以将要求的数据进行向右移位,每移动一位我们就计算一次,如果计算的结果为1,我们就可以将一个变量的值加上1,然后我们再将这个操作放到一个循环里面,这样我们循环32次就可以知道这个补码里面有多少个1了,那么我们的代码实现如下:
#include<stdio.h>
int main()
{
int a = 0;
int i =0 ;
int count = 0;
printf("请输入你想要算的值:");
scanf("%d", &a);
for (i = 0; i < 32; i++)
{
if (a & 1)
{
count++;
}
a = a >> 1;
}
printf("该数据含有的二进制的1有 %d个", count);
return 0;
}
那么看到这里大家现在的脑子里肯定会涌现出许许多多的想法,比如说我能不能不移动我们要计算的这个数据啊,我能不能移动1来进行计算啊,当然可以那么这里就要想一个问题就是我们将1进行左移的时候,如果遇到了那位是1的话我们的那位1是会保留下来的 ,如果那位不是1的话我们这里计算的结果就是0,所以我们只用将计算的表达式放到if后面的括号里面就可以用来判断了,无需知道如果为1 之后的结果等于多少,因为0为假,非0为真,那么这里我们的代码实现如下:
#include<stdio.h>
int main()
{
int a = 0;
int i =0 ;
int count = 0;
printf("请输入你想要算的值:\n");
scanf("%d", &a);
for (i = 0; i < 32; i++)
{
if (a & (1 << i))
{
count++;
}
}
printf("二进制中1的个数为:%d", count);
return 0;
}
那么看到这里我再来跟大家讲解一个方法,首先我们想一个问题就是我们对一个数减去1,会对其二进制照成什么影响呢?我们来看看
我们将上面的数减一之后得到的结果就是下面的一行数,那么这里有小伙伴要说了这有什么规律性的改变吗?当然这里确实是没有规律性的改变的,但是我要是这么说的话你认同吗?我们对一个二进制的数减去一个1 就一定会导致这个二进制中有一个1会改变变成0,并且这个1的位置在所有的1中一定是在最右边的,而这个1右边的数一定是0,这些0也会跟着发生改变变成1你认同吗?如果你认同的话我们就可以接着往下看,我们再将这个减去1的结果与没减1的结果再进行按位与的话,又会出现怎么样的结果呢?我们想一下,之前靠右边的1被我们减一干掉了,就会出现了许多的1,但是这出现的1又会被我们的按位与干掉,因为之前的数据那个1的右边全是0,那么这个操作的结果是什么呢?是不是就把这个二进制的数据中的一个1给变成0了吗,既然我们循环1次就可以使得其中的一个数变成0,那么我们每循环一次就使得count的值加一,这样的话我们每次循环都把异或之后的值赋值给原数据,那么这样的话我们直接将这个值放到while循环里面,因为我们每次循环都会减去1,当我们的1都减完了这个值就会变成0,变成0了我们的while循环就会结束这样的话我们再将这个计数的变量打印出来就可以了,那么我们的代码实现如下:
#include<stdio.h>
int main()
{
int a = 0;
int count = 0;
printf("请输入你想要算的值:");
scanf("%d", &a);
while (a)
{
count++;
a = a & (a - 1);
}
printf("二进制中1的个数为:%d", count);
return 0;
}
看了这题是不是感觉受益匪浅啊,那么我们继续往下出发吧。
赋值操作符
啊这里就非常的好理解了,就是重新给一个变量赋值嘛,比如说
#include<stdio.h>
int main()
{
int weight = 180;//初始化
weight = 90;//赋值
double salary = 1000000.0;//初始化
salary = 2000000.0;//赋值
return 0;
}
大家记住一点哈,不要把初始化和赋值搞混了就可以了哈,但是这里有一种写法还是用提醒大家一下这里的写法问题有小伙伴喜欢这样来赋值,我们看一下这个例子:
#include<stdio.h>
int main()
{
int a = 10;
int x = 20;
int y = 30;
a = x = y + 1;
return 0;
}
这里的a = x = y + 1是什么意思呢?就是先将y+1的值赋值给x,再把x的值赋值给a,但是这里可能有小伙伴觉得这里写的也没有什么问题啊,但是大家要注意的一点的就是虽然我们这里是将他分成两步走的,但是我们计算机在调试的时候这里就只会经历一步,不方便大家以后调试,所以我们在赋值的时候还是最好分成两步走,避免调试的时候麻烦。那么接下来我们就来看看一个知识点就是符合赋值符,首先我们来看有哪些复合赋值符:
这些复合赋值符的用法就很简单,比如说我们a+=1这个意思就是等价于a=a+1,a*=2就等价于a=a*2
a|=2就等价于a=a|2,那么这里的计算规则想必都清除就是先计算右边的值再来赋值给左边,因为大家都应该都见到很多次了,我们这里就不必多讲了。那么接下来就到了我们的重点:单目操作符
单目操作符
首先我们讲讲什么是单目操作符,单目操作符就是说这个操作符的只有对应的一个操作数,如果说有两个操作数,那么这个就是一个双目操作符,比如说a+b中的+就是一个典型的双目操作符,这里的a和b就是对应的操作数,那么我们来看看单目操作符有哪些:
!
这个操作符的作用就是逻辑反操作,那么逻辑反是什么意思呢?我们可以看一个例子来理解理解:
#include<stdio.h>
int main()
{
int a = 1;
while (!1)
{
printf("haha\n");
}
return 0;
}
我们这里写了一个非常简单的代码就是一个while循环,但是这个while循环后面的判断的表达式的内容为!1那这是什么意思呢?我们都知道到0表示假非0表示的意思就是真,而我们这里的(!)操作符他的作用是使得逻辑反,也就是说将真的变成假的,将假的变成真的,那么我们这里1本来是真的,但是前面加了一个!所以这里的判断就为假了,我们可以运行一下发现不会进入这个循环:
我们可以将这个代码改一下,将里面的1改成0这样就变成原本0表示是假,但是前面有个!就将这个假的变成了真的,所以就会进入这个死循环,我们来看一下代码:
#include<stdio.h>
int main()
{
int a = 1;
while (!0)
{
printf("haha\n");
}
return 0;
}
我们来看看运行的结果为:
确实如我们所想的一样。
-
这个操作符的作用跟我在数学当中的作用其实是差不多的,但是这个不是数学中所谓的加法减法的那个减,因为我们这里是单目操作符对应的只有一个操作数所以我们这个操作符的作用就是负值,简单的说就是能够使一个正数变成一个负数,能使一个负数变成一个正数,比如说我们下面的这个例子:
#include<stdio.h>
int main()
{
int a = 10;
int b = -20;
a = -a;
printf("a的值为%d\n", a);
b = -b;
printf("b的值为%d\n", b);
return 0;
}
这个a经过我们的负值操作符就会从原来的10变成-10,我们的-20经过我们的负值操作符就会变成20我们来看一下运行结果:
确实跟我们想的一样哈。
+
同样的道理这个不是所说的加减乘除的+,因为这是一个单目操作符表示的意思是正值,那么这时候有小伙伴们就想啊,这个操作的功能是不是让我们的负数变成正数啊,那么这里可能让大家失望了,因为这个操作符不能使得我们的负数变成正数,我们来看一下下面的代码:
#include<stdio.h>
int main()
{
int a = -1;
a = +a;
printf("a的值为:%d", a);
return 0;
}
我们可以看一下这个代码的运行结果还是-1,
那么这时候就有小伙伴们要问了,那这个操作符有啥用呢?大家记住这个操作符真没啥用哈。不用太在意哈。
&
我们之前在初识c语言当中说过,我们在创建一个变量的时候是会向内存中申请一段空间的,而我们计算机会对这每一个内存单元进行编号,我们将这些编号称为地址,那么我们将这个变量存入电脑的时候,这个变量也就应该是有个对应的内存地址,那么我们是如何得到这个地址的呢?这里就得要用到我们的取地址操作符(&),当然这个地址我们一般都喜欢叫他指针,那么这里我们就来通过一个例子来解释一下取地址操作符是如何进行使用的
#include<stdio.h>
int main()
{
int a = 0;
int* p = &a;
printf("%p", p);
return 0;
}
我们首先创建了一个变量a,再用我们的取地址操作符将这个a的地址提取出来,既然我们将这个地址提取出来了,那么我们是不是得创建一个变量来进行接收并且储存啊,那么我们这里就创建了一个指针变量来接收这个地址,而这个变量的类型就是int *类型这个int表示的是这个地址里面装的内容是整型的数据,而这个*表示的是我们这个指针变量里面装的是地址,那么同理可得我们这里如果我们要存储浮点型,字符型的指针的时候就得将前面的int改成对应的类型,那么这里我们的代码的运行就为:
那么看到这里想必有些小伙伴们可能会有些许疑问,我们都知道在c语言当中一个整型是4个字节而我们的一个内存单元是一个字节,那么我们这里就应该对应的就是4个地址啊,那为什么这里就只打印出一个地址呢?那么这里我们就得跟大家说一下我们这里的取地址操作符取出来的是这4个地址的中的起始地址,并不会将这4个地址全部都提取出来,那么看到这里想必大家对这个操作符应该能够理解他的用法,那么接下来就来和大家聊一聊解引用操作符。
*
我们上面讲了取地址操作符(&),这个操作符的作用就是将我们创建的变量对应的地址去取出来,那么我们知道了一个变量的地址,按道理来说我们是不是可以通过这个地址来找到对应的这个变量里面装的值呢?答案是确实可以的,那么这里就得用到我们的解引用操作符,我们来看一个例子:
#include<stdio.h>
int main()
{
int a = 0;
int* p = &a;
*p = 3;
printf("%d", a);
return 0;
}
我们先创建一个变量a,然后我们用取地址操作符将变脸a的地址取出来,然后用指针变量p来存储a的地址,我们可以看到指针变量a的类型为int *型,然后这里我们知道变量a的地址那么我们这里就可以通过解引用操作符得到这个地址所对应的内容,那么此时我们修改这个地址所对应的内容就可以也就
相当与修改我们这个变量的里面的内容,那么我们看一下这段代码运行出来的结果
我们可以看到变量a的值确实被修改了,那么看到这个例子我们就可以知道我们这个解引用操作符的作用就是得到地址里面的内容,并且可以通过该地址修改这个地址里面的内容。
sizeof
小伙伴们注意了哈这是一个操作符哈,可千万不要把他当作是函数了,那么这个操作符的作用就是求一个变量或者一种类型所创建出来的变量的大小单位是字节,那么我们看一下具体的例子:
#include<stdio.h>
int main()
{
int a = 0;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long ));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
return 0;
}
我们可以看到这个输出对应的结果确实跟我们之前想的一样,如果对应的是变量那么这个sizeof求出的结果就是这个变量的大小,如果是类型的话就是对应的类型所创建出来的变量的大小,那么这里我们还可以看一个例子,我们创建一个含有10个元素的整型数组,然后将数组名放到我们的sizeof里面,再将我们的数组的首元素放到外面的sizeof里面,我们再来看看这个打印出来的结果是什么:
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%d\n", sizeof(arr[0]));
printf("%d\n", sizeof(arr));
return 0;
}
我们可以看到第一个打印出来的结果是整个数组的大小,而第二个打印出来的结果为首元素的大小,那么这里大家通过这个例子就可以知道,我们的sizeof里面要是放的是一个数组的名字的话,那么这个求的就是整个数组的大小,如果不是数组名而是数组内的元素的话,那么就是这个元素的大小。看到这里想必大家对sizeof的作用应该有所了解,那么我们接着往下看。
~
这个操作符的作用就是按位取反,那么这个是什么意思呢?我们来看个个例子:我们首先创建一个变量a,然后将他的值初始化为30,然后我们在创建一个变量b,再将b的值赋值为~a,那么这个b的值会是多少呢?
#include<stdio.h>
int main()
{
int a = 30;
int b = ~a;
printf("%d", b);
return 0;
}
那么这里我们首先就得将这个a的30转换为二进制,而且我们这里创建变量的类型为int类型,那么这里就是32个字节,那么我们的a的原码为:
又因为我们这里a的值是一个正数,所以在内存里面的原码反码补码都相同,所以上面的图片也是我们补码储存的形式,那么我们将这个值按位取反(~)就是将上面的每一位都取反,如果你原来是1的话,那么按位取反后就是0,如果你原来是0的话,那么按位取反之后就是1 ,那么我们的这里按位取反之后的值就因该如下图所示:
看到这里想必会有小伙伴们要说:这个b打印出来就是这个值了,这个值好大啊,那么如果你是这么想的话,那么你就成功的忽略了一点就是,我们这里的~a的补码的符号位是1啊,那么这个就表示的是负数啊,我们将其打印的时候得进行一系列的转化啊,所以我们这里在打印之前得再进一步进行转化:
那么这时我们打印出来的值就是-31,我们可以看一下这个代码的运行结果:
确实跟我们想的是一样的,那么这时可能会有小伙伴们要问了,啊这个按位取反有什么用啊?那么这里我就用一个例子来带着大家理解一下这个操作符的作用,比如说我们这里有个变量a,它的值为:5,我们要将这个值的二进制位的第第二位变成1,该怎么去做呢?我们首先得将这个a以二进制的形式写出来就是:
那么这时有小伙伴们就想啊这还不简单直接加2不就可以了吗!确实我们加上2的话确实是可以将这里的第二位的0变成1 ,但是大家有没有想过这里我们是数据给的简单,如果我说将这里的19位,这里的第30位变成1,你难道还要一个一个的算吗,那么面对这样的情况的话,我们直接加上某一个数的做法是不是多多少少都有点显得不妥,那么这里我们就可以采用另一种方法,我们前面说过一个按位或操作符( | ),这个操作符的作用就是只要一对二进制位里面有1那么这一对的按位或的结果就是1,那么这里我们是不是就可以这么想如果想让一个二进制中的第18位的0变成1的话,那么我们是不是就可以让他按位或上一个数值,这个数值的二进制位上的第19位是1,而其他位全部是0,那么这样的结果是不是就可以保证除了第19位的数值,其他位的值都不变啊,因为你原来有1的话你得到的结果就是1,你要是原来是0的话你按位或的结果是不是还是0啊,那么我们如何来得到这样的数值呢?我们可以之前还说过这么一个操作符就是左移和右移,既然我们要想第19位为1的话,那么我们这里就可以让1向左移18位,这样我们就可以既保证第19位为1,其他位为0了,那么我们这里就假设我们变量a的值为409763,然后让这个数的二进制的第九位变成1,如果我们上面的思路是对的话我们这里打印的结果就是410019,我们可以将上面的思路写成代码:
#include<stdio.h>
int main()
{
int a = 409763;
int b = 1;
b <<= 8;
a |= b;
printf("b的值为:%d", a);
return 0;
}
我们看到这个打印出来的结果确实跟我们预期的一样,那么说明这个情况我们的思路是对的,那么这里我们是将第9位的0变成了1,那么我们这里肯定会联想出另一个问题就是既然我们现在能够将0变成1,那么我们这里能不能将里面的1变成0呢?那么这里我们的思路就跟上面的例子差不多,我们上面用的按位或的方法将0变成1,那么按位或有个好兄弟按位与(&)他的作用就是在二进制里面只要一对有0,那么这一对的按位或的结果就是0,那么这里想必大家应该会有所启发,那么我们这里假设将第六位的1变成0的话,我们这里是不是就可以将这个值按位与上一个特殊的值,这个值的特点就是除了第六位以外其他为全部都是1,这样就可以保证我们除第六位的其他的位的值都能够保持不变,那么我们这里如何来得到这个值呢?我们上面的特殊值是通过将数字1,左移8位得到的,但是得到的这个值的特点确实除第8位以外的其他位都是0,跟我们的预期好像是相反的,嗯?相反的我们刚刚好像讲的就是按位取反操作符,那么我们这里是不是就可以这个值按位取反一下来得到我们想要的值了,那么我们这里就可以来假设一下,如果确实转换成功的话我们的值会变成409731,我们来看看代码:
#include<stdio.h>
int main()
{
int a = 409763;
int b = 1;
b <<= 5;
b= ~b;
a &= b;
printf("a的值为:%d", a);
return 0;
}
我们来看看这个代码的运行结果为:
确实跟我们想的一模一样,那么看到这里想必大家应该能够明白我们这个取反操作符的作用,极其运算的规律。
++ –
这个操作符想必大家都非常的熟悉了,我们这里将其分为前置++(–)和后置++(–),那么这里大家就只需要明白一点的就是这里的前置和后置的区别,我们的后置++(–)是先使用这个值,再对这个值进行加一或者减一,而我们的前置++(–)是相对这个值进行加一或者减一再来使用这个值,我们来看看下面这段代码来理解理解
#include<stdio.h>
int main()
{
int a = 1;
int b = 0;
b = a++;
printf("a的值为:%d\n", a);
printf("b的值为:%d\n", b);
return 0;
}
我们将a的值初始化为1 将b的值初始化为0,然后我们再把a++赋值给我们的b,那么我们这里这里是后置++,也就是说我们是先将a的值赋值给我们的b,再将a的值进行加加,那么这里我们将其打印的话a的值就是2,而我们b的值就是1
如果我们将其改成前置++的话,那么我们这里就是先将a的值++,再把a的值赋值给我们的b,那么这样打印出来的结果就是a和b都是2:
#include<stdio.h>
int main()
{
int a = 1;
int b = 0;
b = ++a;
printf("a的值为:%d\n", a);
printf("b的值为:%d\n", b);
return 0;
}
那么看到这里想必大家应该能够理解我们这两个操作符的作用,但是这里需要提醒大家的是我们这里的使用并不单单指的是在计算表达式的时候,我们在函数传参调用函数的时候也是这样,比如说我们这里的打印a++的值就是先把我们a的值传过去,再来打印我们的a:
#include<stdio.h>
int main()
{
int a = 1;
printf("%d\n", a++);
printf("%d\n", a);
return 0;
}
所以大家需要多多理解这个使用的含义。
强制转换操作符
这个操作符我们在学习指针的时候会遇到特别的多哈,就比如说我们这里是一个char类型的变量,我们想将这个变量赋值给一个int类型的变量,那么这里编译器肯定是会报出错误的
#include<stdio.h>
int main()
{
#include<stdio.h>
int main()
{
int n = 9;
float* pfloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pfloat的值为:%f\n", *pfloat);
return 0;
}
我们这里是创建了一个整型的变量n,这时我们又创建了一个指针变量这个变量的类型为float*,那么我们这里是不能n的地址放到pfloat里面去的,因为这两个的指针变量的类型不同,所以我们这里就如果强行进行赋值的话就会报错
所以我们这里就得将其强制类型转化一下,将这个a的地址变成float*类型这样我们就可以顺利的编译下去,那么这里要跟大家提一个概念就是我们的一个变量有两个属性,一个是值属性,另一个就是类型属性,我们这里的强制类型转化改变的就是我们这里的类型属性,而不是我们的值属性,所以我们大家不要以为我们这里的强制类型转化把我们在内存中存储的值也改变了。.0
关系操作符
外面首先看看有哪些关系操作符:
这里就非常简单了,因为这些操作符的意思跟我们数学上面的差不多,这里还是有两点需要大家注意一下的就是我们c语言中判断相等的时候用的是两个等于号==,不是一个等于号=,然后我们在比较两个字符串是否相等的时候是不能用==这个操作符的,因为这样比较的话就是比较两个字符串的首字符的地址,并不是这两个字符串的内容,如果要比较两个字符串的内容的话这里得用到我们c语言提供的一个函数strcmp,不能直接用==来进行比较,还有一点就是我们的在比较浮点数的时候可能会出现错误,因为我们浮点型的数据在存储的时候会出现进度上的误差,所以就会产生精度上的一些问题,大家这里要注意一下。
逻辑操作符
我们首先看看有哪些逻辑操作符:
第一个逻辑与就是只要有一个为假,那么整个表达式的结果就为假,比如说表达式3=(表达式1 )&&(表达式2),我们这里只要表达式1和表达式2的结果只要有一个为假,那么我们表达式3 的结果就为假,那么与之对应的就是我们这里逻辑或,这个就是只要有一个表达式的结果为真,那么整个表达式的结果就为真,比如果说我们上面的那个例子表达式3=(表达式1 )||(表达式2),只要其中的一个表达式的结果为真,那么我们整个个表达式也就是表达式3 的值就为真,如果这么看的话我们的逻辑与就相当于我们语文中的并且,我们的逻辑或就相当于我们语文当中的或者,那么这里想必大家可能都会遇到这么个问题,我们来看看下面的代码
#include<stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
if (3 <= n <= 4)
{
printf("hello world");
}
return 0;
}
这段代码的意思就是我们这里输入一个数字,如果我们的数字在3到4之间就会打印出hello world,但是我们小伙伴们在运行的时候就会发现不管输入多大的数字都会打印出hello world,这是为什么呢?因为我们这里的c语言跟我们学的数学在这里是有那么一点点的差异的,我们数学当中会把这个 3 <= n <= 4看成一个整体,但是在我们的c语言当中却不会把这个看成一个整体,他会将其看成两个部分因为我们<=的结合性是从左向右,所以我们的系统就会先将我们的3<=n看成一个整体,然后将这个整体返回的值再拿来跟我们的4进行比较,而这个整体返回的值要么是1 要么是0,所以我们这里的第二次比较就会始终为真,所以就会出现不管我们输入多大的值,都会打印hello world的情况,那么我们这个如何进行修改了,那么这里就可以用到我们上面讲的逻辑操作符,我们将3 <= n <= 4改成
3<=n&&n<=4这样就跟我们数学上面的意思就一模一样了,那么这里我们再来看看一道360的笔试题:
#include<stdio.h>
int main()
{
int i = 0;
int a = 0;
int b = 2;
int c = 3;
int d = 4;
i = ++a || ++b || d++ ;
printf("a=%d\nb=%d\nc=%d\nd=%d\n",a,b,c,d);
printf("i=%d", i);
return 0;
}
我们来看看这段代码的运行结果是什么,可能会有小伙伴们说啊,这还不简单嘛,因为我们这里的||的结合性是从左到右,所以我们这里是先计算我们的a,而又因为我们这里的a是前置加加所以我们这里的a的值就为1,然后因为这个a等于1答案为真所以我们这个整个表达式的结果就为真,所以我们下面的i就会打印出为1,然后我们后面的b和d都一样嘛,一个个计算就好啦,但是这里我们将其运行一下就会发现运行的结果跟我们想的有点不一样:
我们发现这里的b和d并没有进行计算,但是我们的1确实是进行计算了的,那这是为什么呢?因为我们这里的第一步在判断a的时候a的结果就已经是为真的了,而且我们这里是按位或他的特点就是只要表达式当中有一个为真那么整个表达式就为真,我们这里第一个算出来的结果就为真,那么这整个的表达式的结果就为真,那你觉得我们还有必要算接下来的一些的表达式吗?那就没必要了对吧,就好比有些非常厉害的人他们高考前报送了一些非常厉害的学校,你觉得他们还有必要去参加高考了吗?没必要了对吧,那么我们这里也是一样,一旦这里面的某个子表达式能够确定整个表达式的结果,那么接下来的表达式就没必要再去计算和运行了,那么看到这里我们就可以将这个表达式修改一下,我们把第一个前置加加改成后置加加那么算出的结果就会有所改变
#include<stdio.h>
int main()
{
int i = 0;
int a = 0;
int b = 2;
int c = 3;
int d = 4;
i = a++ || ++b || d++ ;
printf("a=%d\nb=%d\nc=%d\nd=%d\n",a,b,c,d);
printf("i=%d", i);
return 0;
}
我们知道后置加加的性质是先使用再加加,所以我们这里的a一开始初始化的值是0;0表示的是假,所以我们这里第一个表达式的结果就是假,但是我们这里是按位或操作符,所以第一个表达式的结果不能决定整个表达式的结果,所以我们这里就会接着计算下一个表达式,而我们第二个表达式是++b,这里是先加加再使用我们b的值为2,所以我们这里相加之后的值就为3,3为非0所以我们这这里就判断为真,所以这个表达式2就可以决定我们整个表达式的结果,所以我们就不用再计算后面的d++,那么这里计算的结果结果为1 3 3 4我们运行一下看看:
看到这里想必大家应该对逻辑操作符有所了解了,那么我们接着往下看。
条件操作符
我们先来看看条件操作符长啥样哈:
这个操作符是什么意思呢?就是说我们会先判断表达式1的结果是否为真,如果表达式1的结果为真的话那么我们整个表达式的结果就会为我们的表达式2,如果表达式1的结果为假的话,那么我们整个表达式的结果就等于我们的表达式3,我们来看一个简单的例子就可以懂得这个操作符的用法:
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d", &a);
scanf("%d", &b);
int max = a > b ? a : b;
int min = a < b ? a : b;
printf("两数中较小的为:%d\n",min);
printf("两数中较大的为:%d\n",max);
return 0;
}
我们来看看这段代码首先我们创建两个变量a,b然后我们随便输入两个值,输入完之后接下来我们就要比较这两个数的大小了,我们将这两个数中的较大值赋值给我们的max,将两个数的较小值赋值给我们的min,那么我们如何来得到这两个数的较大值和较小值呢?那么这里我们就有一个很好的方法就是条件操作符,我们来看上面的代码,第一个条件操作符是用来求得最大值的,我们这里的表达式1就是a>b,如果a确实大于b的话我们就会把表达式2的结果作为这整个表达式的结果赋值给我们的max,而我们这里的表达式2就是a,那就把a的值赋值给我们的max,如果a不大于b那么我们就会把表达式3的结果最为整个表达式的结果赋值给我们的max,那也就是把b的值赋值给我们的min,那么我们这一步条件操作符的目的就是得到a和b中的最大值,那么下面的条件操作符跟我们上面的类似,只不过求的是两个数的最小值并且把这个最小值赋值给我们的min,那么看到这里想必大家应该能够明白我们这个条件操作符的用法极其意义,那么看到这里大家有没有发现一件事情就是我们的条件操作符跟我们的if…else语句有那么点相似,我们是不是可以将其转化一下呢?答案是确实是可以的。我们来看一下这段代码:
#include<stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
int b = a > 5 ? 3 : -3;
return 0;
}
我们将这个转化为if…else语句那是什么样的呢?我们可以看一下下面的代码:
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d", &a);
if (a > 5)
{
b = 3;
}
else
{
b = -3;
}
return 0;
}
逗号表达式
我们来看看逗号表达式的基本形式:
啥是逗号表达式?所谓逗号表达式就事用逗号隔开的多个表达式,逗号表达式是从左往右依次执行,整个表达式的结果是最后一个表达式的结果,也就是我们这里的exp N的结果,那么这时候有小伙伴们就要说了,啊这还不简单吗?我每次看到逗号表达式我就直接看最后一个表达式,前面的我都不看反正最后的值也跟他们没有啥关系,那么你如果这么想的话就会出问题了,虽然我们这个逗号表达式的值是我们最后一个表达式的值,但是逗号表达式前面的值可能会影响我们最后一个表达式的结果啊,所以我们遇到逗号表达式的时候还是得一个一个的从左往右算,那么我们可以看一个例子来理解理解:
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a>b,a=b+10,a,b=a+1);
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);
return 0;
}
我们来看看这段代码,首先我们创建两个变量a和b,分别将他们初始化为1 和2,于是我们再创建一个变量c,然后将一个逗号表达式赋值给这个c,然后我们再将这三个变量的值全部打印出来,那么我们这三个值的大小会是多少呢?在创建变量c之前我们a和b的值为1和2,然后我们就会经过一个逗号表达式这个表达式的是从左边往右进行计算,首先我们就得先计算a>b,这个运算的结果是假,但是这个表达式时不会影响任何变量的,所以我们就会来到第二个表达式a=b+10,因为我们的b是2,所以这时我们的a的值就会发生改变变成了12,那么经过这个表达式我们的a的值就从原来的1变成了12,好接下来就是下一个表达式一个单独a,因为他不是最后一个表达式,也没有改变任何的值,所以我们就可以直接跳过这个表达式,那么接下来我们就来到了最后一个表达式b=a+1,这个表达式就会改变我们b的值,因为之前我们的a发生了改变变成了12,所以我们这里的b就变成了13,又因为这是最后一个表达式,这个表达式的结果就会赋值给我们的c,那么我们这个表达式的结果是什么呢?啊这里结果就是我们赋值给b的值就是我们这个表达式的结果,那么我们的c的值就是13,我们可以运行一下来看看:
确实跟我们想的一样哈,那么我们的逗号表达式就说的这里
下标引用,函数调用和结构体成员
下标引用
下标应用操作符这个就见的很多了,我们在使用数组的时候就经常看到这个操作符,这个操作符的作用就是可以得到数组中对应下标的值,比如说我将这个数组初始化为10个元素,这10个元素分别为:
1,2,3,4…9,10.然后我们要将这10个元素分别打印出来,那么这里就会用到我们的下标引用操作符,我们可以看一下下面的代码:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d", arr[i]);
}
return 0;
}
我们这里打印的时候就可以通过这个[ ]下标引用操作符来得到这个数组里面对应的值,那么既然是操作符,那么就应该有对应的操作数,那么这里的操作数是什么呢?那么这个操作数就是我们这里的数组名和对应的下标,那这时有小伙伴们就要想了了,我们计算机中3+4和4+3是同一个值,那么我们这里的操作符能将两个操作符的顺序进行替换吗?答案是当然可以我们可以将这里的arr[ i ]改写成
i[ arr ]这是不会报错的
但是不建议大家这么去写哈因为跟我们平时的习惯有所不同,好说到这里我就再来提一点就是我们的在定义数组的时候这个[ ]方括号这时并不是下标引用的意思,而是表示这个变量名是一个数组。
函数调用
函数调用操作符见到的地方可以就太多了,长啥样了就是我们英文的一个圆括号这就是我们的函数调用操作符,这个操作符的操作数可以是一个或者多个,第一个操作数就是函数名,剩余的操作数就是传递给函数的参数,也就是我们只要调用函数就必定得用到这个圆括号,那么这里大家应该会想到我们的sizeof我们说过这是一个操作数,因为只要他求的不是类型所创建出来的变量的大小的时候,他是可以不加圆括号的这里就可以证明我们的sizeof是个操作数而不是一个函数。
结构成员
等后面我们就会学到结构体,那么我们创建了一个结构体变量,我们该如何调用这个结构体中的成员呢?那么这里就得用到我们这里说的操作符
我们首先来看看第一个,这个操作符就是一个点,我们首先声明一个结构体类型,那么这里我们就用这个结构体来描述一个学生,既然是学生的话,那么是不是就有该学生的性别,身高,年龄,名字该四个最基本的特征,那么我们就用这四个特征来创建出一个结构体:
#include<stdio.h>
struct student
{
char name[10];
char sex[10];
int age;
int high;
};
int main()
{
return 0;
}
我们把结构体的类型声明好了,那么接下来我们就要用这个类型来创建变量,并且将其初始化:
#include<stdio.h>
struct student
{
char name[10];
char sex[10];
int age;
int high;
};
int main()
{
struct student s = { "zhangsan","nan",18,180 };
return 0;
}
这里我们创建了一个变量s,并且将其名字初始化为张三,性别为男,年龄为18岁,身高180cm,那么这里我们将这个变量初始化完了,那么我们要想将他的每个变量都打印出来那该怎么做呢?首先我们可以想到的是肯定是可以用到我们printf函数的,但是printf函数得需要一个具体的变量,比如说我创建了一个变量a,我要打印这个变量a的值的话我就得在printf函数中的最后把a加上,那么我们这里的变量s的类型是个结构体那该怎么办呢,我如果单单的把这个s写上去那肯定是不可以的,因为这个给s包含的变量有多个,所以我们这里就得用到我们这里说的操作符,我们将这个变量s后面加上一个点然后再加上你想要打印的变量就可以了:
#include<stdio.h>
struct student
{
char name[10];
char sex[10];
int age;
int high;
};
int main()
{
struct student s = { "zhangsan","nan",18,180 };
printf("学生的姓名为:%s\n",s.name );
printf("学生的性别为:%s\n",s.sex);
printf("学生的年龄为:%d\n",s.age );
printf("学生的身高为:%d\n",s.high);
return 0;
}
但是我们这个操作符他只能针对我们的结构体,如果是结构体的指针的话就得用到这个操作符: ->
比如说我们这里要创建这个函数这个函数的功能是修改我们这个结构体变量的内容的,那么我们结构体和我们的变量有个同样的特征就是如果函数传值调用的话,系统是会自己创建一个空间,然后把传过来的内容进行复制一份,然后你要是在函数里面进行修改的话,改的是复制过来的内容,并不是实际的内容,所以我们这里要传址调用,那么我们函数接收也就得用struct student *进行接收:
#include<string.h>
#include<stdio.h>
struct student
{
char name[10];
char sex[10];
int age;
int high;
};
void change(struct student* ss)
{
strcpy(ss->name, "lisi");
strcpy(ss->sex, "nv");
ss->age = 19;
ss->high = 165;
}
int main()
{
struct student s = { "zhangsan","nan",18,180 };
printf("学生的姓名为:%s\n",s.name );
printf("学生的性别为:%s\n",s.sex);
printf("学生的年龄为:%d\n",s.age );
printf("学生的身高为:%d\n",s.high);
change(&s);
printf("修改之后:\n");
printf("学生的姓名为:%s\n", s.name);
printf("学生的性别为:%s\n", s.sex);
printf("学生的年龄为:%d\n", s.age);
printf("学生的身高为:%d\n", s.high);
return 0;
}
我们来看一下运行之后的结果为:
那么这里总结一下如果遇到的是结构体的变量名那么就用这个点( . )如果是结构体的指针那么就用
-> 。
隐式类型转换
c的整型算术运算总是至少以默认整型类型的进度来进行的,为了获得这个精度,表达式中的字符和短整型操作数在使用之前会被转化为普通整型,这种转换称为整型提升,那么整型提升的意义是什么呢?我们表达式的整型运算要在cpu的相应运算器件内执行,cpu内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是cpu的通用寄存器的长度。因此,即使两个char类型的变量进行相加,在cpu执行的时实际上也要先转换为cpu内整型操作数的标准长度。通用cup是难以直接实现两个8比特字节直接相加运算,所以表达式中各种长度可能小于int长度的整型值,都必须先转换为int或者unsigned int,然后才能送入cpu去执行计算,比如说下面的代码:
int main()
{
char a = 1;
char b = 1;
char c = 1;
a = b + c;
return 0;
}
这里我们将将两个char类型的变量进行相加,那么这里的相加过程就是向将b和c的值提升为普通的整型,然后再进行加法运算,加完之后再将结果进行阶段赋值给我们的a,那么我们这里我们是如何进行提升的呢?我们加完之后又是如何进行截断的呢?首先我们的整型提升的规则就是高位补充符号位,无符号整型提升高位就补0,比如说我们的char c1= -1
,那么这里我们是一个字节的大小那么对应的二进制的补码就是:
因为这里的符号位为1,所以我们在提升的时候高位就补1,那补多少个1呢?那么这里就补到整型对应的32个1就可以了,那么如果这里是char c1= 1
那么我们的二进制的补码就是
因为这里的最高位的符号位是0,所以我们在整型提升的时候就会补符号位0上去,一直补到32位。那么这里大家要注意的一点就是我们这里的整型提升针对的是我们补码,不是原码和反码哈,说完了提升接下来就是截断这个就很容易了,你是多少个大小就从低位往高位进行截断就可以了,比如说我们最后要将其赋值给我们的char类型的一个变量,那么你就直接从低位往高位数8个比特位就可以了,这就是那个char类型的变量里面放的内容,那么接下来我们看一道关于整型提升的题:
#include<stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a== 0xb6)
{
printf("a");
}
if (b== 0xb600)
{
printf("b");
}
if (c== 0xb6000000)
{
printf("c");
}
return 0;
}
首先我们可以看到外面创建三个类型的变量分别为char类型,short类型,int类型,将a的值初始化为
0xb6,那么这里对应的是16进制外面将其转化为2进制就是:
因为我们这里的b6是正数所以我们在赋值的时候就会直接将这个b6放到我们的内存里面去,换句话说就是我们的补码就是我们上面的10110110这个值,我们可以通过内存来看一下:
那么我们内存当中存的确实是b6,但是我们下面会对其进行比较判断这个a==0xb6,这里就会有一个问题我们会吧这个a来出来进行比较,这个比较也是一个算术运算它也会进行算术提升,所以他就会最高位补充符号位,那么因为我们这里的符号位是1,所以提升的时候就会全部补充1,所以提升之后我们的a就变成了一个负数,所以我们这里就会判断为假也就不会打印a,同理下面的变量b也是这个道理,他也会进行整型提升所以也会变成一个负数,所以b也不会进行打印,那么我们的c会打印这是为什么呢?因为我们的变量c本来就是一个整型,它不用进行整型提升所以就不会变成负数所以结果为真。我们再来看一个题:
#include<stdio.h>
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
这里我们创建一个char类型的变量c,那么我们下面就要计算c,+c,-c的大小,首先毫无疑问我们的sizeof( c )的大小肯定是1,那么我们的+c和-c呢?这里虽然就在前面加个+和-,但是这里依然算是参与了表达式运算所以我们这里就会进行整型提升,变成一个int类型所以我们这里打印出来的结果就会是1 4 4
那么看到这里我们的小伙伴就会有疑问了,你这里说的是short和char转换成int类型,那要是比int大的类型那又该如何进行转换呢?如果某个操作符的各个操作数属于不同的类型,那么除非其中的液体个操作数的转换为另一个操作数的类型,否则操作就无法进行,下面的层次体系就称为寻常算术转换
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另一个操作数的类型后执行运算,越往上排名越高。
操作符的属性
复杂表达式的求值有三个影响的因素:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性,那么我们操作符有一张表
通过这个表格我们就可以知道这个优先级的顺序,那么这个优先级是怎么用的呢?我们来看一个例子:3+4*5+6
这个表达式我们可以看到左边的加号与中间的乘号共用了一个操作对象4,那么是哪个操作符来先使用这个4呢?根据我们的优先级可以看到乘法的优先级是大于加法的,所以是乘法先操作这个4,同样的道理操作数5也是乘法先使用,那么外面将乘法乘完表达式就成这样:3+20+6,然后外面发现这个表达式里面的20又被两个加法操作符共用,那么是哪个加法先执行呢?因为我们这里是两个加法表达式所以优先级是相同的,所以这时哪个加法先执行就得取决于我们的加法的结合性,我们通过上面表格发现我们的加法的结合性是从左向右,所以就是我们左边的加法先执行再来执行我们的右边的加法。我们再来看一个表达式:6*12+5*20
;我们来看看这个表达式的计算顺序首先我们看到12这个操作数是被两个操作符公用的分别是*和+,然后我们根据优先级知道我们这里是先执行*,然后5又被两个操作符公用分也是+和所以我们这里也是先执行,那么问题来了我们这里的两个*又是谁先来执行呢?啊这时候有小伙伴要说了这里不就是看结合性了嘛,啊根据结合性可得我们是先执行左边的再来执行右边的,可是事实是这样的吗?我们说结合律只使用于共享同一运算对象的操作符,例如
12/ 3*2中的/和*的优先级相同共享同一个对象3,那么从左向右的结合性这时候就起到了作用,我们这里就是先执行除再来执行乘,那么我们再回到这个例子我们的结合性与这个不是同一个东西,那么这里是先执行哪一个呢?啊这里c语言就把主动权留给语言的实现者,根据不同的硬件来决定先计算前者还是先计算后者,但是不管采用哪种计算方法我们最后得到的结果都是一样的哈。看到这里有小伙伴们就想啊既然我们的有了结合性和优先级那么我们每个表达式的值是不是都是唯一的呢?答案是不是的,我们依然能写出很奇奇怪怪的表达式比如说很基本的:a*b+c*d+e*f
这个表达式我们只能知道我们的前两个乘法肯定是比第一个加法先执行,但是我们不能决定第三个乘法比第一个加法先执行,那么这里有小伙伴们就要说了啊这有啥好怕的啊,反正结果都是一样的怕啥啊,大家思考一下真的是一样的吗?如果我们这里的abcdef不是简简单单的数字而是一个一个的表达式呢?并且这些表达式还相互影响呢?那么这样看的话不同的表达式执行顺序是不是就显得非常重要了,那么我们这里大家在写代码的时候就要注意一下,以免出现问题,这样的例子还有:c+--c
我们这里根据操作符的优先级可以知道的一件事就是自减操作符的优先级肯定是高于+的但是这里我们不知道左边c的值的获取是右操作数之前还是之后就比如说一开始c的值是5那么这个表达式的计算就有两种可能一个是5+6另一个就是6+6,所以这样的问题大家写代码的时候一定要注意避免出现歧义,我们还有一个代码:
#include<stdio.h>
int main()
{
int i = 10;
i = i-- - --i * (i = -3) * i++ + ++i;
printf("%d", i);
return 0;
}
就是这个表达式每个编译器算出来的结果都不一样,如果你工作写出这样的代码,那么你离开除不远了,所以我们在写表达式的时候一定要注意避免出现起义,那么我们该如何来规避呢?
如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减操作符。
如果一个变量多次出现在一个表达式中,不要对该变量使用递增或者递减操作符。
看到这里我们的操作符的内容就结束了谢谢大家观看谢谢!
点击此处获取代码