C 语言
这套笔记是参考bilibili上,小甲鱼的视频,讲得非常不错。搜小甲鱼就能找到他的视频,这是他的网站:www.fishc.com 谨以此笔记献给无私奉献的小甲鱼先生。
(一)基本认识
1、对C语言的基本理解
编程语言排行榜TIOBE的排名,C语言是千年老二。老一是Java。
但是,C语言作为底层开发的基础语言,语法上基本很难有太大的改变,因为牵一发而动全身,所以还是非常有必要学一学的。
入门:语法-程序的调试技巧
进阶:指针的应用-多线程、多进程程序的开发-数据库的调用和管理
探索:驱动开发、内核、嵌入式开发
入门:C语言语法
变量、数组、函数、指针结构、标准库,这些都是纯粹的C语言的知识点。
使用C语言写不出外观炫酷的程序,但一个好看的程序其实是需要一个优秀的美工。
C语言语法知识可以在windows平台也可以在linux平台上学习,但是推荐在linux平台上学习,因为在linux平台上C语言有更广阔的空间。
进阶:C语言在linux下的应用
对于linux终端的读写、基于Arduino库的开发、vim快速入门里面的测试小程序、多进程多线程程序的开发、访问和管理数据库、sok网络编程、jik+qt来开发窗口界面。
相比别的操作系统linux是开源的。进阶就是窥视linux的内部,理解操作系统到底是怎么回事,而不是简单调用外部api来实现程序而已。这一阶段对操作系统的概念理解的就比较深刻,同时应用编程能力也会极大提高。
C语言编写windows的应用程序,内容参考:windows程序设计(SDK)视频教程。 就是用C语言来调用windows api函数实现窗口编程。就是C语言在window上编程,开发出窗口程序。
探索:利用Arduino进行开发
智能家居、物联网、电子积木等概念非常火,所以第三阶段转入硬件层面,利用arduino进行开发,不仅要写代码还要动手改装电路、安装传感器、开发很酷的项目。
2、什么是编程语言?为什么要学?
编程语言是人类跟机器打交道的中介,它充当人类的翻译官。
莫斯密码:破译敌方的电台, 就是一个人带一个耳机听着du du du--的声音,然后用笔在纸上写出--. -.. -- . -.的密文,然后通过 莫斯编码表 进行破译,得到敌方的通信信息。
莫斯密码的原理就是查编码表。比如A表示.- , B表示-...., C表示-.-., 1表示成.---, 等等,就是将明文ABC1等人类文字字符编译为点横组合,这个过程我们成为编码。反之将点横的组合解密为人类文字的过程叫解码。
机器码: cpu是计算机的大脑,cpu很快但没有智慧,cpu只懂得二进制的0和1,如果我们用0和1对cpu下指令,我们人类会崩溃,所以编程语言就诞生了。编程语言就是将人类的语言翻译成cpu能读懂的0和1。
但是编程语言给cpu提供的0和1是以指令的形式给它的。和莫斯密码一样,指令和0-1组合之间的对照也是有一个指令表,通过查询这个指令表,把指令和01组合对照起来。这些01组合我们成为'机器码',也叫机器语言,也是cpu直接可以读懂的语言。比如要在屏幕上打印一个hello wordl, 机器码就是屏幕都占满的数字,而且这个数字还是16进制的形式,如果把这些数字转换为二进制01组合的形式那就更多了。这种直接把人类语言转换成二进制01组合的语言是第一代编程语言。
第二代编程语言是汇编语言,汇编语言引入了一些'助记符',将助记符转化为机器码,这个过程称为编译。还是我们想在屏幕上打印一个hello world, 汇编语言就要写 pushl, movl, andl, subl,call, leave等等英文单词的助记符(说明:这些英文单词表示一行行代码,是keyword, 在main:下面)。汇编语言还是麻烦,于是第三代语言C语言诞生了,C语言就被称为高级语言了。汇编语言是低级语言,c,c++, java, delphi, python, r,等都是第三代编程语言,都是高级语言。如果用C语言给屏幕上打印一个hello world, 只需用下面5行代码即可:
int main() { printf('hello world!\n'); return 0; }
所以C语言要用5行代码,汇编语言要用20行代码,机器语言要用上百行代码。所以从代码的可读性和效率上说,C语言都有极大的优势。
所以我们用C语言进行编程,其过程就是:C语言编译器将C语言编译成汇编语言,再由汇编语言编译器将汇编语言编译成机器语言,然后再由cpu读取机器语言,cpu再开始执行指令,显示hello world.
随便说一句:哈哈,如果我们用python语言,我们只需print('hello world'),所以为什么说人生苦短我们就学python吧。但是,本人最开始学的就是python,但是学着学着就发现,不懂计组、不懂信号、不懂原理、不懂OS、不懂数据结构、不懂网络、不懂linux..,只是因为要学算法而开始学python,那到后面是越来越不会。所以还是得先打好地基,而C也是地基的一部分。
3、C语言的特点
效率高、灵活度高、可移植性高。
(1)效率高:
效率高是针对所有的第三代编程语言,就是所有的高级编程语言来说的。C语言是编译性语言,而其他高级编程语言是解释型语言。
编译性语言的源代码最终是被编译成机器代码,机器代码就是我们所说的可执行文件,比如windows.bins的可执行文件,这种文件cpu就可以对它直接的随意执行了。
而C语言被cpu执行的过程是:C语言-汇编语言-机器语言-CPU执行。机器语言就是可执行文件。
解释性语言,比如java, pyhon, r等解释性语言,解释性语言不是直接编译成机器码,而是将源代码转换为中间代码,比如java代码就是先将java代码转化为字节码(这个字节码cpu是读不懂,不是机器码,不是机器语言)然后将字节码发送给解释器,再由解释器逐句翻译给cpu执行,即java-字节码-解释器-cpu执行。这样做的好处就是可以实现跨平台的特性。缺点就是效率低,因为每执行一次字节码就要解释器翻译一次。
(2)灵活度高:
没有哪种语言可以和C语言在灵活性上媲美了,C语言提供多种运算符,还可以完成类似计算机底层的位操作,语法简单、约束少,丰富多变的结构和数据,还可以自定义一些数据类型,还拥有可以直接操作计算机硬件的能力(这点以后学指针的时候会深有体会,指针是C语言的灵魂,对指针掌握的多深你的C语言就多灵活)。
(3)可移植性高:
可移植性高是指源代码可以在不做改动或者稍加修改就可以在其他机器上编译后就可以正确执行。比如对单片机来说,不同机器上C语言编译程序80%的代码都是相通的,就是通用的。无论是windows系统还是linux还是苹果的mac os系统,它们的内核或多或少都是用C语言来写的。
4、搭建C语言环境
第一步:有一台电脑,一个尽量正版一点的windows操作系统。
第二步:安装虚拟软件VM
第三步:打开VM,创建一台linux操作系统的虚拟机
第四步:安装vim
这里写得非常简单,几乎没有参考意义,如果有同学想一步一步安装,可以参考我的linux学习笔记系列的第一篇文件,里面有非常详细的操作步骤。
(二)C语言基础语法
一门语言就是一套自己的规则和约定,所以我们学语言就是学它的规则和约定。
1、第一个程序:在屏幕上打印hello world
printf中的f是format的意思,是格式化输出函数,功能就是实现格式化输出,将引号里面的内容输出到屏幕上。如果不用这个函数,你自己实现一段字符串打印到屏幕上,你还得考虑怎么去读取内存、怎么操作显卡等等环节!
2、打印
\n是换行的意思
printf函数里面双引号里面的内容,是一个整体,有时这个整体太长,需要换行显示,就得加\n,表示换行显示。
\n后面的\表示:把换行后的下一行当作是上一行的延续,就是表示下一行和上一行是一个整体,还是printf函数引号里面的内容。当printf函数要大于的内容很长很长,我们想多行显示的时候就用\n\
比如下面的写法,也是可以正常编译并正确执行的:
3、案例:统计当前目录及所有子目录下,C 语言源文件的代码总行数
对于目录扫描来说,各个操作系统还是自成一家的。所以下面的程序仅适用于linux系统下统计代码行,如果你是win下写的C,那下面的程序就没法统计了,就得重新开发win系统下统计的脚本。
这里的代码有点长,不方便上传,感兴趣的同学自己去找原视频哈。
4、变量
变量和常量是程序处理的两种基本数据对象。这里先说变量。
(1)什么是变量
变量的意义就是确定目标并提供存放空间。
确定目标就是给变量取个名字,变量名,名字就代表这个变量的内容,变量的内容也就是变量的值,变量值。
提供存放空间是因为,我们要把cpu处理的数据都要先放入内存中,所以,变量的值要放入内存,那就要给这个变量提供存放空间。那要提供存放空间就要明确变量的数据类型。
变量名只能用英文字母、数字、下划线组成。
变量名的第一个字符不能是数字,可以是字母也可以是下划线。
变量名区分大小写。
不能用关键字来命名变量。传统的C语言有32个关键字,后来又加到44个,这些关键字都有自己特殊的含义,所以我们定义的变量名不能和这些关键字重名。
(2)数据类型
数据类型指的就是给变量指定其存放空间的大小。不同的数据类型,它的存放空间大小是不一样的。定义数据类型就是为了更好的利用存储空间。不同数据分配不同大小的存储空间。下面是变量的基本数据类型:
char 字符型,占用一个字节
int 整型,通常反映了所有机器中整数的最自然长度。意思就是不同的机器,对整型规定的大小是不一样的。
float 单精度浮点型
double 双精度浮点型
后面会单独再讲数据类型。
(3)声明变量的语法:数据类型 变量名
int a
char b
float c
double d
(4)代码展示:test2.c
解释:
- 字符和字符串是两个概念。字符是char,字符串是str。
变量 b 是char类型,那 b 的值就只能放一个字符!
并且,字符用单引号(上面的b='y'),字符串用双引号(上面的printf函数里面双引号引住的内容就是一个字符串) - printf 是格式化输出函数,格式化就是将双引号里面的内容,不管它是什么数据类型,都转化为字符串的形式打印出来。
- %.2f 表示精确到小数点后2位。
- %11.9f 表示这个变量总共占宽是11,并且这个变量精确到小数点后9位。
5、常量和宏定义
在程序运行的过程中,其值不能被改变的变量叫常量。
变量的值可以改变,常量的值不能被改变。
常量有以下几种类型:
(1)整型常量:123,11,22,33
(2)实型常量:3.14, 3.21, 5.67
(3)字符常量:
(3.1)普通字符常量:'l', 'i', 'y'
如果一个常量是一个普通字符常量,那编译器在编译的时候,就为这个常量准备一个字节的空间即可
(3.2)字符串常量:"liyuanyuan"
用双引号括住的就是一个字符串常量,那编译器在编译的时候就为这个常量准备11个字节,原因下面有解释。
(3.3)转义字符:'\n', '\t', '\b',因为有些操作不能用字符表示,比如换行就没法表示,那就用\n来表示换行的意思。 \表示一个tab键,\b表示一个退格键backspace
(3.4)符号常量:符号常量在使用之前必须先定义。
定义符号常量的语法是:#define 标识符 常量 ,又称为宏定义,比如:
#define URL "www.baidu.com" 就表示所有的URL都会被后面的字符串替代
#define NAME "liyy"
#define YEAR 2023
注意:
(1)为了将符号常量和普通的变量名区分开,我们习惯使用全部大写来命名符号常量,使用小写字母来命名普通变量
(2)当我们用单引号括起来的字符,单引号里面就只能有1个字符,编译器在编译的时候直接给一个字节空间存储即可。但是,当我们用双引号括起来的字符串,那编译器在编译的时候,就要给这个字符串常量一个和字符串大小相当的空间。那操作系统如何来识别一个一个字符串的长度?程序在运行的时候,它读到这个字符串的开头的时候,它怎么知道结束在哪里?我们的内存是连续的,操作系统如果不知道,它就一直往后读,不就出错了嘛,所以编译器会再给字符串常量最后位置加一个特殊的转义字符\0这个标志,来表示这个字符串的结尾位置。在c语言中\0表示一个空字符,表示一个字符串的结束位置。所以当一个变量是字符串变量时,编译器在编译的时候就给这个字符串变量留字符串长度+1个字节来存储这个字符串。也就是上面的liyuanyuan字符串常量为啥要用11个字节来存储。
代码展示宏定义:test3.c
6、数据类型
在C语言中,数据类型指的就是这个数据存放空间的大小。数据类型有:
这里没有规定shor int具体是多少个字节,int具体是多少字节,只限定short int <= int,这种宽松的规定好处就是让C语言可以适应未来硬件的发展。
7、sizeof运算符
上面讲C没有规定每种数据类型的大小,那怎么给空间啊?sizeof运算符就是用来获得数据类型或者表达式的长度的:
-sizeof(数据类型)
-sizeof (对象) ,这里的对象可以加小括号也可以不加小括号。
代码展示:test4.c
8、符号限定符:signed和unsigned
这一对儿是一个符号限定符,用于限定char类型或者任何整型变量的取值。
signed表示该变量是带符号位的,就是该变量可以存放负数;unsigned表示该变量不带符号位,就是该变量只能是正数。
在signed类型的存储单元中,左边第一位表示符号位。如果该位为0,表示该整数是一个正数;如果该位为1,表示该整数是一个负数。
所以unsigned数据类型可以存放数值更大的数,因为它把符号位释放出来了嘛,所以可以存更多位,所以可以存放更大的数值。
[signed] short [int]
unsigned short [int]
[signed] int
unsigned int
[signed] long [int]
unsigned long [int]
[signed] long long [int]
unsigned long long [int]
方括号就是可以省略,就是默认就是的,所以默认的都是带符号的,当你要定义一个不带符号的数据时,unsigned就不能省略。
当你要存放一个负数,如果这个数的数据类型你定义的是unsigned,那这个数就出错了。就是啥数就定义啥数据类型。
代码展示:test5.c
printf()函数里面传入变量的时候,%d表示带符号打印。比如你变量的值是-1,你就得%d打印,打印出来就是-1。
%u表示无符号打印。
说明:变量存和读是两个环节。
当存变量的时候,你定义的是int类型,你就可以存正整数也可以存负整数,你用%d都可以正确打印出你存的数。
当你存变量的时候,你定义的是unsigned int的时候,你就不能存负数,否则不管你%d打印还是%u打印结果都是错的。因为你在存数据的时候,你输入了一个负数,系统会把这个负号转化的,总之存的时候就和正确输入的数不一样了。那printf去读的时候,就不管怎么读都不对。
当你读变量的时候,你%d就是带符号的读取;%u就是无符合读取。
9、取值范围
我们知道计算机内存中都是存储单元,不管是啥存储在内存中都是0101的状态被存储的。
所以,当我们定义一个变量(当然不仅是变量,同其其他任何数据哈,只不过规则不一样而已),首先我们要定义这个变量的数据类型,就是定义这个变量是一个字符型还是字符串型还是整型还是长整型还是长长整型还是浮点型还是。。。,因为只有提前定义了这个变量的数据类型,才好存储和读取这个变量,不然变量存了,到时候读这个变量的时候,去哪儿读这个变量就不知道了,而且这个变量有多长,要读取几个字节才能完整读取这个变量我就也不知道了。所以我们要定义这个变量的数据类型,就是知道这个变量占了内存中的哪个到哪个的存储单元。
当变量的数据类型确定了,就是我正确的找到了那几个字节就是这个变量,那现在这几个字节里面的0101是表示这个变量是多少呢?有人说二进制一一对应十进制,一换算不就知道了嘛。但是数据还分正数和负数呢,负号电脑也是用0101表示的呀,所以这里还要继续设定这个变量是siged还是unsigned。默认是siged,所以当你不用默认的时候,你要加上unsigned。
继续往下想:现在你不仅确定了这个变量是哪几个字节,也确定了这几个字节里面有没有表示符号位的,那就又出现一个问题就是,这几个字节里面除了符号位,其他位都是变量的值嘛?所以就进入下面的问题:数是怎么表示的,用数的二进制?还是补码?反码?
上面我求过sizeof(int)是4个字节,一个字节8位,就是一共32位,就是一共32个0101。
32个1就是十进制中的2的32次方-1这么大的一个数。
用代码计算一下32个1是多少:test6.c
这是正确的代码,执行出的正确结果。
如果上面代码中,printf打印中写成%d,那返回的值就是-1。
因为计算机是用补码来存储整数的值的。 因为用补码表示整数可以解决正数加绝对值相同的负数=0的问题。
正数的补码就是该数的二进制形式。
负数的补码是该数绝对值的二进制数,按位取反后加1。 所以当我们printf %d 的时候,左边第一位上的1表示负号,后面31位上的1都取反,最后再加1,所以等于-1。
前面我们sizeof(int)查看了一下int类型的数据,我本人的机器就是给出4个字节,也就是32位来存储这个数据的。所以我现在要计算32个1的十进制是多少,我就得定义变量a是一个unsigned int 类型,这样32位就全部表示的是数值了。如果我定义变量a是int类型,那32个1就装不下了,就会溢出内存,就会出错。
打印的时候不管是%d打印还是%u打印,结果都是2147483647,这个结果是31个1的十进制大小。
正数和负数在计算机中存储形式:
基本数据类型的取值范围:
这里不太明白的同学自己百度反码补码等概念,不是一句话两句话能说清的。
下面是小甲鱼给大家提供的参考资料:
进制转化:进制转换,《带你学C带你飞》(语法篇),C\C++交流,鱼C论坛 - Powered by Discuz!
使用补码的好处: 使用补码的好处,《带你学C带你飞》(语法篇),C\C++交流,鱼C论坛 - Powered by Discuz!
定点数:用二进制表示小数 定点数:用二进制表示小数(#),《带你学C带你飞》(语法篇),C\C++交流,鱼C论坛 - Powered by Discuz!
浮点数:表示更大范围的小数 浮点数:表示更大范围的小数(#),《带你学C带你飞》(语法篇),C\C++交流,鱼C论坛 - Powered by Discuz!
小结:
我们人类看到的形形色色符号,比如数字、字母、汉字、图片、音频、视频、甚至一些看不到的符号比如回车、空格等等这些人类理解的符号,在计算机的世界中统统都是0101存储并且被计算机认识的。
计算机作为人类的工具,人类就得把自己认识的符号转化为0101才能和计算机沟通。人类把自己想做的事情转化为0101,计算机才能帮人类做。
所以,第一步就是人类如何把自己认识的符号转化为0101让计算机认识。
所以,人类就开始思考,人类的符号首先要0101的形式存储在内存中吧,然后cpu才执行。那怎么存储?内存的存储单元实质就是开关开关的电路,让不同开关组合的电路来帮助人类存储人类的符号。
所以,第一个问题就是,先确定下来人类的符号是"变还是不变"。当这个符号不变就是常量,比如我就存一个3.14这么一个数、或者我就存个liyy这么4个字符、或者我就存个空也行啊、或者我就是想进行个回车、就是想进行个换行。那这些符号和操作动作,我们就和计算机约定,这就是一个常量,就是这些东西你别给我变来变去。我一旦存储了这些东西,计算机你任何时候都不能给我变。而当人类想存储一个模糊东西的时候,比如人类就想存储一个数,也没说啥数,可能是1也可能是-1也可能是任何其他数字,此时人类就得和计算机约定存储的这个东西是一个变量,你先帮我把名字存好,名字不要变来变去就可以,至于名字对应的内容,看我人类的心情,我今天喜欢6我就让这个名字对应的值等于6,我后天喜欢8,或者喜欢hello这个单词,那你就把名字对应的值改成我喜欢的内容。这就是变量。所以人类要存储自己的东西到计算机,第一步要和计算机约定我要存的是变量还是常量。这是人类和计算机约定的第一个存储的规则。
第二个约定的存储规则是,人类你存储在我计算机里的东西到底得多大?就是你存储的东西,我计算机要给你留多大的空间去存?不能出现计算机给人类留了1G的空间,人类就存了个数字2,那岂不是大大的浪费嘛,所以第二步就是,你要和计算机约定你要存的东西,让计算机给你留多大的空间。就是规定你的数据的数据类型。比如,人类说我要存一个整型的常量,就是1这个数字,计算机就给你留4个字节的空间;比如你说要存一个圆周率,要long long int型的,那计算机留8个字节;比如你要存一个数组,那计算机就给你留够足够的空间,或者不留空间,但是给你加个指针,这样就可以灵活使用零碎空间了。等等,这就是人类要和计算机约定的第二个规则,提前说好你的数据类型,计算机好提前给你留坑。
至此,计算机已经可以帮人类记忆了,比如人类忘了圆周率是多少,人类可以问计算机我之前把一个数字3.14存在你那里,存的是一个常量、数据类型是.2f的浮点数,现在我把这个数忘记了,你现在给我调出来我看看。计算机一听,对对对,这个数存在我的第几号单元里面,往后取8个字节,就是这部分电路的表示的0101状态。
此时人类就懵了,4x8=32,这32个0101我怎么看得懂,此时,人类就又得和计算机预定规矩了,人类说我再和你约定,我存储在你这里的东西还得规定一下,这个东西的提前约定前面带不带符号,因为符号计算机也是0101表示的,所以规定这32位里面的第一位是符号位,0表示正数1表示负数。再规定数字用补码存、小数balabala怎么存(人类太聪明了,总能找到和硅基生物沟通的方式),于是第三套约定就建立了,计算机就按照人类的约定把取出的32个0101以人类友好的形式呈现出来了。
至此,硅基生物可以帮助碳基生物记忆了!!!
所以,当我们定义数据的时候,一是要声明这个数据是变量还是常量(不过也不用这么麻烦,因为有默认的,你不声明就用默认),二是要声明这个数据的数据类型(你要存个数字还是字符还是指针类还是构造类的数据),三是声明这个数据是signed还是unsigned,如果你没声明,我都按默认,小心我给你返回你看不懂的东西,嘻嘻。四是,人类你赋值的时候要严格按照约定赋值,你不按约定就会出错,比如约定的整型,你非赋值个小数,比如约定的是siged,你非赋值个负数,就会不是报错就是warning,比如溢出了啥的。
至此,人类可能要疯掉,我就是想存个1,你就和我balabala这么多约定,大约定里有子约定,子约定里面密密麻麻的条款和默认,还涉及到各种进制、各种码、各种数学,死的心都有了。好吧,你要是知道这些边边角角的条款更好,但如果你实在看不懂,你就把它看成一个黑盒,大概记住下面的常识就可以:
(1)如果你确定,你要存的数据里面有负数:你定义的时候就一定不要定义成unsigned
(2)如果你确定,你要存的数据里面有负数:你打印的时候就一定不要写成%u
(3)如果你定义了unsigned变量,你赋值的时候却赋了一个负数,那你不管是%d打印还是%u打印,结果都是错的,结果都不是你赋的值。
(4)如果你定义的是signed变量,或者没写singed,因为默认就是signed,那你打印的时候不能用%u打印,只能用%d打印,否则一旦有负值,%u打印就出错了。
10、字符
在C中,我们使用char来声明字符变量,printf函数使用%c来输出字符。
当我们用char声明变量,但却用%d打印变量的时候,输出的就是字符的ASCII码的十进制表示。
当我们用char声明变量,用%c打印,输出的就是我们输入的字符。
小写c的ASCII码的十进制就是99。ASCII码是123对应的符号就是{ 。
理解:
(1)ASCII字符集使用7位二进制数来表示所有的大小写字母、数字0-9、标点符号、特殊符号等。
(2)char类型也可以是signed char或者unsigned char,C语言语法中没有默认规定char类型是signed char还是unsigned char,这是由编译系统来决定的。所以,常常会出现有人用C写的程序中,用char定义的变量,但是变量的值是数字,打印的时候用%d打印。那这个代码在不同的系统上可能运行出来的结果不一样!
11、字符串
c语言并没有专门为存储字符串设计一个单独的类型,因为没必要。因为字符串就是一串字符,只要在内存中找一块连续的空间,放一串字符类型的变量进去就可以了。
在C中,我们使用char来声明字符串变量,语法是: char 变量名[数量];用printf函数使用%s来输出字符。
对字符串进行赋值,就是在这一块连续的空间内,对每一个字符变量进行赋值。
我们可以通过索引号来获得字符串中每个字符变量的空间。
注意点:
(1)字符串声明的时候要多一个字节存放空字符\0,以表示这个字符串的结尾在什么地方。否则,打印时会出现乱码。如左图1。
(2)左图1是复杂的写法;
右图2是声明和赋值写在一起的简写写法;
右图3是声明和赋值写一起,并且赋值是用字符串常量来赋值的,所以要用""双引号引住,并且文本最后不用加\0,并且你还可以把{}也省略了。这种方式适用于长文本。
(3)[]不能省,但里面的数字可以省,因为当赋值很长的时候,系统会自动帮我们算的,所以[]里面的数字可以省略。
12、算数运算符
前面我们用的=等号,就是赋值运算符;我们声明字符串的时候用的[]方括号,就是方括号运算符也叫下标运算符。
双目就是有2个操作数,单目是有一个操作数。
熟悉计算机原理的同学都知道计算机算1+2时,计算机接受到的信息是12+,然后再计算的。再比如i=1+2,计算机接受到的是i12=+几个东西,人类要计算机帮我们计算,前面解决了存储问题,现在开始解决计算问题,这个例子中就涉及到什么是表达式、运算符的优先级等知识点,解决这些问题,老套路,还是和计算机约定一堆堆的规则,比如越好等号的优先级是14级、加号的优先级是4级,再规定级别越好先处理谁,所以这里就先算1+2,再把结果3赋值给i。
13、类型转换
当一个运算符的几个操作数类型不一样的时候,编译器需要在计算之前,先将这些操作数转化为某种相同的数据类型。
通常情况,编译器是将占用空间较小的数据类型转化为占用空间较大的那个操作数的数据类型,然后再去计算。
比如计算1+2.0时,就会先将整型1转化为浮点型,变成1.0+2.0,然后再计算,得出结果3.0。
从上图中可以看出,计算机在计算1+2.0的时候它的结果是3.0,并且这个3.0的数据类型是浮点型类型,所以printf的时候我们就得用%f打印,如果强用%d打印,结果是不是谬以千里!
如果我们不想编译器自动给我们转换类型,导致我们看不懂,那我们可以自己强制规定操作数的数据类型:
就是我们在数据前面加个(int)就强制将浮点型数据转化为整型类型,然后整型+整型=整型,%d打印整型,结果就没毛病。
计算机将浮点型数据转化为整型类型,是直接把小数点右面的数字全部去掉,不是四舍五入!
14、关系运算符
关系运算符是双目运算符,因为是比较嘛,要两两才能比较。
关系运算符得到的结果是一个逻辑值:true(1) or false(0)。所以,我们打印返回值的时候要%d打印。
15、逻辑运算符
当我们要对多个关系之间进行判断的时候,就得用到逻辑运算符:
优先级总结:
赋值运算符(=)<逻辑运算符的优先级<关系运算符<加减乘除
就是你要先计算加减乘除,算完后再进行大于小于等于不等于的运算关系判断,判断完才能进行与或非的逻辑判断,逻辑判断完毕的结果才能进行赋值操作。
16、短路求值
短路求值又称为最小化求值,是一种逻辑运算符的求值策略。只有当第一个操作数的值无法确定逻辑运算的结果时,才对第二个操作数进行求值。
C语言对应逻辑与和逻辑或采用短路求值的方式。
(1)对于逻辑与,只要第一个条件判断是'非',那就不用判断第二个条件了,直接返回非(0)即可。所以上图A处:只给变量a重新赋值为0,就没有执行&&后面的代码,所以变量b还是3。
(2)对于逻辑非,只要第一个条件判断是'是',那就不用判断第二个条件了,直接返回是(1)即可。所以上图B处,只给变量a重新赋值为1,||符号前面就是'是',就不用判断||后面的代码了,所以变量b还是没有重新赋值,还是原来的3。