整理今天的学习内容
1.操作符的属性
(1)优先级
优先级决定不同操作符执行的先后顺序
(2)结合性
如果两个运算符优先级相同,则根据运算符是左结合还是右结合决定执行顺序,大部分运算符是左结合(从左到右执行),少数运算符是右结合( 从右到左执行)
(3)常用运算符的优先级顺序(从高到低)
圆括号()
自增运算符++,自减运算符--
单目运算符+和-
乘号*,除号/
加法+,减法-
关系运算符<,>等
赋值运算符=
可以使用圆括号改变其他运算符的优先级
参考网址:https://zh.cppreference.com/w/c/language/operator_precedence
2.表达式求值
(1)整形提升(只针对字符型和短整型)
概念:
C语⾔中整型算术运算总是⾄少以缺省(默认)整型类型的精度来进⾏的。为了获得这个精度,表达式中的字符和短整型操作数在使⽤之前被转换为普通整型,这种转换称为整型提升
整型提升的意义:表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓度。通⽤CPU难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种⻓度可能⼩于int⻓度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执⾏运算
方式:
有符号数据类型整形提升高位补符号位,无符号数据类型整形提升高位补0
(2)算数转换(除字符型和短整型)
除字符型和短整型外,若某个操作符的各个操作数属于不同的类型,那么按照下面的层次体系(寻常算数转换),低层次的数据类型会向高层次数据类型转换
1long double
2double
3float
4unsigned long int
5long int
6unsigned int
7int (向上转换)
3.问题表达式解析
(1)a*b+c*d+e*f
由于 * 比 + 的优先级高,只能保证 * 的计算比 + 早,但是优先级并不能决定第三个 * 比第⼀个 + 早执行
所以表达式的执行顺序可能是:
1 a*b a*b
2 c*d c*d
3 a*b + c*d e*f
4 e*f a*b + c*d
5 a*b + c*d + e*f 或 a*b + c*d + e*f
(2)c+ --c
同上,操作符的优先级只能决定⾃减 -- 的运算在 + 的运算的前⾯,但是没有办法得知 + 操作符的左操作数的获取在右操作数求值操作之前还是之后,所以结果是不可预测的,有歧义
所以结果可能是2c-2或2c-1
(3)
这个表达式在不同编译器中测试结果是不同的:
(4)
上述表达式中只能通过操作符的优先级得知先算乘法再算减法,但函数调用的先后顺序不确定
(5)
上面表达式中,由于 ++ 比 + 的优先级高,只能保证 ++的计算比 + 早,但是第一个+执行时,第三个++是否执行是不确定的
所以表达式的执行顺序可能是:
1++i ++i
2 ++i ++i
3(++i )+ (++i) ++i
4 ++i (++i )+ (++i)
5(++i )+ (++i)+ (++i) 或 (++i )+ (++i)+ (++i)
即使有了操作符的优先级和结合性,写出的表达式依然有可能不能通过操作符的属性确定唯⼀的计算路径,那这个表达式就是存在潜在⻛险的,所以建议不要写出特别复杂的表达式
深入理解指针
1.内存和地址
内存被划分为一个个的内存单元,每个内存单元的大小取一个字节
一比特可以储存一个二进制位
1Byte=8bit
1Kb=1024Byte
1Mb=1024Kb
1Gb=1024Mb
1Tb=1024Gb
1Pb=1024Tb
每个内存单元都有一个编号,CPU可以通过这个编号快速找到一个内存空间,计算机把内存单元的编号称为地址,C语言中地址也叫指针
CPU和内存之间通过线进行数据交互,简单理解我32位机器有32根地址总线,每根线只有两态,表示0,1(电脉冲有无),那么32根地址线就能表示2^32种含义,每⼀种含义都代表⼀个地址
地址信息被下达给内存,内存上就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器
2.指针变量和地址
(1)在C语言中创建变量其实就是向内存申请空间,通过取地址操作符得到地址
&i取出的是i所占四个字节中地址较小的字节的地址
(2)指针变量和解引用操作符
地址可以存放在指针变量中
指针变量的类型是int*
*说明pi是指针变量,int说明pi指向的是整形类型的对象
解引用操作符(*)可以通过地址找到地址指向的对象
*pi其实就是i变量,把对i的修改交给*pi来操作,写代码会更加灵活
(3)指针变量的大小
32位机器有32根地址总线,一个地址占32个bit位,指针变量的大小占4个字节,64位机器有64根地址总线,一个地址占64个bit位,指针变量的大小占8个字节
指针变量的大小和类型⽆关,只要在相同的平台下,大小都是相同的
3.指针变量类型的意义
(1)
指针的类型决定了对指针解引用时有多大权限(一次能操作几个字节)
(2)
char*类型的指针变量+1跳过1个字节,int*类型的指针变量+1跳过4个字节,指针+1指向跳过一个相应单位字节对应的元素
指针的类型决定了指针移动的单位大小
(3)void*指针
void * 类型的指针可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算
编译器给出警告:
使用void*类型可以避免这种问题
⼀般 void* 类型的指针是使⽤在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果,使一个函数能处理多种类型的数据