6、单目操作符
!、++、--、&、*、+、-、~、sizeof、(类型)
单目操作符只有一个操作数,除了&、*,剩下的我们之前讲过了,这两个我们再之后的指针我们再讲。
7、逗号表达式
表达式,表达式,表达式,表达式
用逗号隔开的多个表达式就是逗号表达式
逗号表达式依次从左往右执行,最后的表达式就是整个表达式的值;
比如下面代码,看c的逗号表达式,从左往右执行,a>b为假是0,a,这两个表达式没有任何影响,接着a+10,赋予b,b=11,最后b>a,为真,返回1就是整个表达式的结果;
int main()
{int a = 1;int b = 3;int c = (a > b, a, b = a + 10, b > a);return 0;
}
有时候,a<b,是这个判断的条件,那从左往右计算,是会影响到最后的表达式的结果的,所以逗号表达式前面计算也是重要的;
if(a = b + 10,b = c / 5,a < b)
举个例子,假设有个APP函数的值赋予a,a又是CPP函数的参数,接着while循环,如果a>0,执行代码,执行完后,再继续之前的代码,那这样写就比较冗余,我们可以用逗号表达式写出来;
int main()
{a = APP();CPP(a);while (a > 0){//执行代码a = APP();CPP(a);}return 0;
}
用逗号表达式写的效果是一样的,先执行APP函数,CPP函数,再判断a是否大于0,再执行代码,循环完一次后再进行判断,再执行APP,CPP函数;
int main()
{while (a = APP(),CPP(a), a > 0){//执行代码}return 0;
}
8、下标访问[ ]、函数调用( )
8.1 [ ]下标引用操作符
比如我们有一个数组,我们想打印数组的第7个元素,那就是arr[6],访问数组的元素下标6,
那[ ]就是下标引用操作符,[ ]下标引用操作符的操作数有两个,一个是数组名,一个是元素下标,这两个操作数,少一个都不行,是找不到对应的元素的;
int main()
{int arr[10] = { 1,2,3,4,5,6,7 };printf("%d\n", arr[6]);return 0;
}
8.2函数调用操作符
比如我们最简单的printf函数后面的括号( )就是函数调用操作符,add( )这个也是函数调用操作符,那函数调用操作符的操作数有几个?
这是不固定的,比如下面第一行代码操作数是函数名printf和字符串“hahaha”,操作数有两个;第二行代码操作数就是函数名add,参数2,参数3,操作数有3个;第三行代码,操作数只有一个函数名,因为它没有参数,只有函数名,那操作数就是1个;所以是不固定的;
int main()
{printf("hahaha\n");//( )操作数就是printf和“hahaha”int r = add(2, 3);//( )操作数就是add,2,3text();//( )操作数就是textreturn 0;
}
9、结构体成员访问操作符
9.1结构体
C语言已经提供了内置类型,比如char、short、int、long、float、double,但是只有这些类型是不够的,比如要描述一个学生,学生的名字用char,但是学生的成绩、学生的体重、年龄呢?不能只用一种数据类型来描述学生,学生是一个复杂对象,那我们自定义一个合适的数据类型,结构体就是自定义的数据类型,
结构是一些值得结合,那这些值就是成员变量;
首先先声明结构体,那里面的变量就是成员变量,比如学生的名字,年龄,成绩;
成员变量可以有一个或是多个;
struct Stu
{char name[20]; //名字int age; //年龄float score; //成绩
};
9.1.1结构体的声明
先是结构体的关键字struct,tag就是结构体的名字,这个可以随便起,括号里面的就是成员变量列表,下面就是变量列表,可写可不写,但是最后的分号;一定要加上;
struct tag{member-list;}variable-list;
9.1.2结构体变量的定义和初始化
我们定义了结构体类型,那类型就相当于一张图纸,有了图纸,我们就可以按照图纸去盖房子,我们有了学生的数据类型,我们就可以去创建一个“学生”;
那我们创建了数据类型,那就是数据类型+变量名;
int main()
{struct Stu s1;struct Stu s2;return 0;
}
那我们定义变量,那我们就可以初始化;
那我们说数组给它多个元素,我们用括号,那结构体里面有这么多变量,我们也用括号括起来;
比如s1学生,名字是张三,年龄是18,成绩是95.5;
我们这是按着顺序给它赋值的,那能不能不按顺序给它数值呢?
struct Stu
{char name[20]; //名字int age; //年龄float score; //成绩
};int main()
{struct Stu s1 = { "张三",18,95.5f };struct Stu s2 = { "李四",19,90.5f };return 0;
}
那我们可以通过结果成员访问操作符,来实现这个操作;
. 是结构成员访问操作符,.name访问结构里的name...........
int main()
{struct Stu s1 = { .age = 18,.score = 95.5f,.name = "张三" };struct Stu s2 = { .score = 90.5f,.name = "李四",.age = 19 };return 0;
}
那我们再复杂一点,说结构体里面还有一个结构体呢?
结构体Stu里面还有一个结构体BB,结构体BB里面有两个成员变量char a、int b;
那我们再初始化的时候,既然它还是一个结构体,那我们就还是用{ }括号给它赋值,就像s1那样;
也可以向s2那样,.bb.a、.bb.b、bb是个结构体也可以用结构成员访问操作符;
struct AA
{char a;int b;
};struct Stu
{char name[20]; //名字int age; //年龄struct AA BB;float score; //成绩
};int main()
{struct Stu s1 = { .age = 18,.score = 95.5f,.name = "张三" ,.BB = {"A",1}};struct Stu s2 = { .score = 90.5f,.name = "李四",.age = 19,.BB.a = "2",.BB.b = 1 };return 0;
}
9.2结构成员访问操作符
9.2.1结构体成员的直接访问
那创建好变量,赋值了,我们可以打印出来,比如我们打印s1学生的名字,用结构成员访问操作符,就是结构体变量.成员名;
int main()
{struct Stu s1 = { .age = 18,.score = 95.5f,.name = "张三" ,.BB = {"A",1}};struct Stu s2 = { .score = 90.5f,.name = "李四",.age = 19,.BB.a = "2",.BB.b = 1 };printf("%s\n",s1.name);printf("%d\n",s1.age);return 0;
}
10、操作符的优先级和结合性
操作符有两个重要的属性就是优先级和结合性,决定了表达式求值的计算顺序。
10.1优先级
比如在一个表达式里面有多个操作符,哪一个操作符先算呢?那不同的操作符的优先级是不一样的。
*的优先级比+的优先级高,所以先算5*6,乘法,再算3+30,加法;
3+5*6
10.2结合性
如果两个操作符的优先级是一样的,那就是看结合性了,看它是左结合,从左往右计算,还是右结合,从右往左计算;大部分是操作符是左结合,左往右计算,但是有少部分是右结合,从右往左计算,比如赋值运算符 =
* 和 / 的优先级是相同的,它们的结合性都是左结合运算符,那就是先算5*6,再 / 2;
5 * 6 / 2
运算符的优先级顺序很多,我们只要大概记记了解一些常用的运算符优先级就行
下列按照优先级从高到低排列;
1、()圆括号;比如5 * 6 / 2,我就先算6/2,那就那就可以5 * (6 / 2);
2、自增运算符(++)、自减运算符(--)
3、单目运算符+、-
4、乘法 *、除法 /
5、加法+、减法-
6、关系运算符(<、>等)
7、赋值运算符 =
我们也有一个表格可以查看
https://zh.cppreference.com/w/c/language/operator_precedence
11、表达式求值
11.1整型提升
C语言中整型算术运算都是以整型类型的精度来计算的;
如果表达式中有char和short,那再使用之前先将它们转换为普通整型,那这种转换就叫做整型提升,char的底层是 ASCII码,它和short也被归为int类型里面;
那a+b就是整型提升;
int main()
{char a = 5;char b = 126;char c = a + b;return 0;
}
整型提升的意义
表达式的整型运算是在CPU相应的运算器上运行的,CPU的整型运算器的操作数的字节长度一般是int的字节长度,因此,两个char类型相加,在CPU上实际也要先转换为CPU整型运算器操作数的字节长度,CPU是难以实现两个8比特位字节直接相加的,所以,表达式中各种长度小于int长度的整型值,都必须先转换位int后者unsigned int,再给CPU计算。
如何整型提升?
1、有符号整数的整数提升是按照数据的符号位来提升的
2、无符号整数的整数提升,高位补 0
5的原码放在char里面,5的二进制位是32个比特位,char是8个比特位,放在char里面我们就要截断成8个比特位,126也是同样的道理;
a+b整型运算,对a,b整型提升,就要提升到32个比特位,在当前编译器char是有符号整型, 所以剩下的24个比特位,用这个数据的符号位来提升;
a和b整型提升后的结果是32个比特位,放在char里面也是要截断的;
当我们打印c,用%d打印,%d打印的是有符号的整型,%u打印的是无符号的整型,c还要整型提升,提升完后,这是c在内存的补码,但是打印的是原码,还要取反+1,得到原码;
这就i是char和short在整型运算的过程;
int main()
{char a = 5;//00000000 00000000 00000000 00000101//00000101 --截断char b = 126;//00000000 00000000 00000000 01111110//01111110 --截断char c = a + b;//00000000 00000000 00000000 000000101 --a整型提升//00000000 00000000 00000000 011111110 --b整型提升//00000000 00000000 00000000 100000011//截断//10000011 --c//用%d打印是有符号整型//对c整型提升//11111111 11111111 11111111 100000011 --补码//打印的是原码//10000000 00000000 00000000 011111101 c原码printf("%d\n",c);return 0;
}
11.2算术转换
如果操作符的两个操作数的类型是不一样的,那就需要算术转化,另一个操作数的类型转换位另一个操作数的类型,是类型相同,可以运算;
算术转换是按照下面表格从下往上转换的;
假设一个操作数类型是float,另一个操作数是int类型,那就i是int操作数向float操作数转换变成float类型,其他的同理;
long double
double
float
unsigned long int
long int
unsigned int
int
11.3问题表达式解析
11.3.1 表达式1
这个表达式是存在问题的,它的计算顺序不是唯一的;
a*b + c*d + e*f
它的顺序可能是两种,都是符合表达式运算的优先级,结合性,但这不是唯一的计算路径,是会有两个结果的,比如我们写一个计算银行利息的代码,一个计算是50,另一个计算是50000,这些很可怕的;
11.3.2表达式2
这个表达式是有问题的,在优先级来说,先计算--,再计算+,但是这个表达式右边的--是知道了,那左边的c是--c之后的c,还是--c之前的c,是有争议的,这也不是唯一的计算路径。
c + --c;
11.3.3表达式3
这个代码是有问题的,这段代码放在不同的编译器结果是不一样的,里面的i的运算,编译器也会凌乱的,这种代码是不能这么写的,你要么拆分,存在变量里,再计算。
int main(){int i = 10;i = i-- - --i * ( i = -3 ) * i++ + ++i;printf("i = %d\n", i);return 0;}
11.3.4表达式4
这段代码的fun函数里的变量用了static,变量的值是会累计的,fun函数第一次返回的是2,第二次返回的是3,第三次返回的是4,那再main函数里面谁是第一次返回,谁是第二次返回,谁是第三次返回,这是不确定的。
#include <sdtio.h>int fun(){static int count = 1;return ++count;}int main(){int answer;answer = fun() - fun() * fun();}printf( "%d\n", answer);return 0
11.3.5表达式5
这段代码也是有问题的,这段代码放在不同编译器的结果也是不一样的。
#include <stdio.h>int main(){int i = 1;int ret = (++i) + (++i) + (++i);printf("%d\n", ret);printf("%d\n", i);return 0;}
11.4总结
我们学了运算表达式的优先级,结合性,我们写出来的表达式不是万无一失的,通常这种表达式是有问题的,它计算的路径不是唯一的,对于以上的表达式,我们不要写成这样,我们可以根据需要,把这些表达拆分,或者用括号确定它的优先级,拆分用变量存起来,再去计算。
感谢观看,感谢指正!