九、操作符
操作符很多,这里简单只了解一下:
1.算数操作符
+ - * / %
加减法和乘法就不用说了,浅看一下算数运算符中除法 / 和取余 % 这两个:
除法 / :
10/3结果是3.333...(3循环),打印出来的却只有3,这是因为它的定义的数据类型是整型,结果只取整数部分。
那我们试一下用float单精度浮点数类型能不能打印出小数部分呢?
打印出来有小数部分但结果不对,这是因为在做除法运算的两个数都是整数,把两个操作数的任意一个改为小数就好了(如下图所示)
注:float型打印时用%f double型打印时用%lf
由于10.0是double型,3.0是int型,10.0/3的运算结果应为double型,而我们定义的a是float型所以会发生从“double”到“float”截断,结果为3.333333
取余 % :
10%3=3余1,余数为1。
取余操作数%两端的数只能是整数。
2.移位操作符
>> <<
3.位操作符
& ^ |
以上两种操作符与二进制有关,暂时不学。
4.赋值操作符
= += -= *= /= &= ^= |= >>= <<=
a += 1 等价于 a = a+1
a -= 1 等价于 a = a-1...由此可以类推出后面几个操作符的含义。
5.单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置 --
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
什么叫做单目操作符?
对于a+b这个加法表达式,有a、b两个操作数,+ 就叫双目操作符,那么单目操作符顾名思义就是只有一个操作数的操作符。
单目操作符怎么使用?
!:逻辑反操作
看下面一段代码:
int main() { int a=10; int b=!a; printf("b=%d\n",b); return 0; }
在C语言中,0就是假,非0就是真,!可以把真变成假,把假变成真。图中a = 10,非0为真,那么!a结果就是假,打印出0。规定:如果a为假,当!a把假变成真时,输出为1。
怎么使用?
int main() { int flag=5; //如果flag为真做什么事情 if(flag) printf("hehe\n"); //如果flag为假做什么事情 if(!flag) printf("haha\n"); return 0; }
上图代码解释:如果flag为真,打印hehe。如果flag为假,打印haha。
- :负号
int main() { int a=-10;//负号 int b=-5; return 0; }
+:正号,很少使用不做介绍。
&:取地址,与指针有关系,后面学指针的时候在说。
sizeof:操作符的类型大小(以字节为单位)
看下面一段代码:
int main() { int a=10; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(int)); return 0; }
以上代码的运行结果为4 4。a和int所占的空间大小都是4个字节。我们在前面学习数据类型的时候用sizeof计算过所有内置类型的大小,如果有兴趣的话可以回去看看。
~ :对一个数的二进制位按位取反
在计算机中相当于把一串二进制数中的0变为1,1变为0。例如:000101001按位取反为111010110
++ :前置、后置++
看下面一段代码:
int main() { int a=10; int b=++a;//前置++,先++,后使用 printf("a=%d b=%d\n",a,b); return 0; }
上图中++a是前置++,运行时先自增1,然后赋给b。打印结果为a=11 b=11
int main() { int a=10; int b=a++;//后置++,先使用,后++ printf("a=%d b=%d\n",a,b); return 0; }
上图中a++是后置++,运行时先把值赋给b,然后自增1.打印结果为a=11 b=10
同理我们可以得出操作符“--”的用法。
(类型):强制类型转换
我们在运行上面一段代码时会出现警告,因为给初始化整型变量a时赋值3.14。
为了解决这个问题,这时如果我们不想改变前面的int,可以使用下面这一段代码:
int main() { int a=(int)3.14;//强制类型转换为int printf("%d\n",a); return 0; }
经过强制类型转换,上图代码打印结果为3。当然,我们不建议使用强制类型转换,“强制”听起来也不像是啥好词。
6.关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
关系操作符主要用于比较,很简单,上手就能用,唯一要强调的是:相等是“==”。
7.逻辑操作符
&& 逻辑与
|| 逻辑或
注:逻辑操作符关注的是真假。
看下面一段代码:
int main() { int a = 0; scanf("%d", &a);//输入 if (a > 0 && a < 10) printf("hello"); else printf("byebye"); }
假设此时输入11,不能同时满足a>0和a<10,则打印出byebye
int main() { int a = 0; scanf("%d", &a);//输入 if (a > 0 || a < 10) printf("hello"); else printf("byebye"); }
假设此时输入11,满足a>0但不满足a<10,打印结果是hello
由此可见,逻辑与就是“并且”的意思,逻辑或就是“或者”的意思,逻辑与是&&两侧条件同时满足时执行,逻辑或是||两侧条件满足任意一个时执行。
8.条件操作符
exp1?exp2 :exp3
它的意思是:当exp1结果为真时,执行exp2,不执行exp3,exp2执行的结果作为整体结果
当exp1结果为假时,执行exp3,不执行exp2,exp3执行的结果作为整体结果
具体使用如下面一段代码,实现a,b两数的比较并将最大值存储在m中:
int main() { int a = 1; int b = 4; int m = 0; m = (a > b) ? a : b; printf("m=%d", m); return 0; }
9.逗号操作符
exp1,exp2,exp3....expn
看下面一段代码:
int main() { int a = 1; int b = 3; int c = 4; int d = (a = b - 2, b = a * 2, c = a - b); printf("%d %d %d %d\n",a,b,c,d); return 0; }
运行结果为:
图中被逗号隔开的表达式叫逗号表达式,逗号表达式从左向右依次计算,整个表达式的结果是最后一个表达式的结果。
10.下标引用、函数调用和结构成员
[ ] () . ->
[ ]:下标引用
int main() { int arr[10] = { 1,2,3,4,5 }; // 下标:0 1 2 3 4 printf("%d\n", arr[4]);//打印下标为4的元素 return 0; }
上述代码中打印出下标为4的数组元素是5
注:数组长度大于{ }中的元素数时,其余元素在计算机中默认为0,那么arr[10]中存放的数就是1,2,3,4,5,0,0,0,0,0
():函数调用
下面是用函数写一段比较大小的代码:
int Max(int x, int y) { if (x > y) return x; else return y; } int main() { int m = Max(2, 3);//()即为函数调用操作符,()的操作数有3个:Max 2 3 printf("%d\n", m); return 0; }
. ->:结构成员,暂时不讲。
十、关键字
常用关键字:
c语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,用户自己不能创造关键字
先介绍下面几个关键字,后面的遇到讲解。
1.关键字 auto
auto即自动,修饰的变量叫自动变量
int main() { auto int a=0; return 0; }
上述代码中,进入主函数时,变量a自动创建,出去时变量a自动销毁,它的前面本来应该有auto,但是在后来的使用中逐渐省略了它,所以我们很少见到,
其实它本身就是用来修饰局部变量的。
2.关键字 typedef
typedef顾名思义是类型定义,这里应该理解为类型重命名
说个有意思的,小时候父母给孩子起完名字后,一般还会起个小名,类似于“壮壮”“丫丫”之类的,在之后的日子里,尽管他们叫的不是本名,但你知道他们叫的一定是你,就像一个代号一样,
其实C语言中的关键字typedef的作用就类似于“起小名”。
typedef unsigned int uint;//将无符号整型变量重命名为uint int main() { unsigned int num1=0;//无符号整型变量 uint num2=0; return 0; }
上述代码中num1和num2的数据类型是一样的。重命名后使用unsigned int也行,使用uint也行。
typedef一般用于复杂的类型的重命名,使其变得简单。
3.关键字 register
register 寄存器关键字
在计算机中的一种存储器叫寄存器,是集成到CPU上的。
学过计算机的同学应该都知道金字塔存储结构:
金字塔结构越往上速度越快,造价越高,空间越小。
那为什么会出现这样的结构呢?
因为CPU早期与内存之间进行数据交换,它们的读写速度是相匹配的, 随着计算机的发展,CPU处理数据的速度增快,而从内存中拿数据比较慢,为了解决CPU与内存之间速度不匹配的问题,在内存与CPU之间增加了高速缓存,CPU上集成寄存器,把内存中的数据往高速缓存中替换,把高速缓存中的数据往寄存器替换,CPU直接在寄存器里面拿数据,而寄存器的速度比较快,这样一来整个计算机的处理速度就变快了。
了解了以上内容,我们来看这么一段代码:
int main() { register int num=0; return 0; }
在我们想来,图中代码将num的值放入register中后处理速度就会变快,但是注意register只是起到一个建议的作用,建议将num的值放入寄存器,最终应该是由编译器决定的。
注意:寄存器变量不能取地址。
4.关键字static
在C语言中,
static是用来修饰变量和函数的
1) 修饰局部变量-称为静态局部变量
2) 修饰全局变量-称为静态全局变量
3) 修饰函数-称为静态函数
1)修饰局部变量
看下面一段代码:
void test() { int n = 1; n++; printf("%d ", n); } int main() { int i = 0; while (i < 10) { test(); i++; } return 0; }
由我们已学过的函数和循环的相关知识可以得到上述代码的打印结果为:2 2 2 2 2 2 2 2 2 2
再看看使用关键字static后的代码及结果:
void test() { static int n = 1; n++; printf("%d ", n); } int main() { int i = 0; while (i < 10) { test(); i++; } return 0; }
运行结果:
函数中的局部变量n是进去的时候创建,出去的时候销毁,它每次循环时都会创建一个新的值n=1,并不会使用变量n上次的值,所以打印出的结果都是2
关键字static延长了局部变量n的生命周期,使n上次的值保留了下来,每次循环时都是用上次n的值,第一次循环,n的值为2,第二次循环开始把2作为n的初始值,经过自增后n=3,以此类推,经过10次循环后打印出:2 3 4 5 6 7 8 9 10
那为什么stastic可以延长局部变量的生命周期呢?
这与变量在内存中存放的位置有关,在内存中分为栈区、堆区和静态区,各个区域存放的变量不同,具体如下图:
局部变量存放在栈区是临时的变量,而static修饰的局部变量存放在静态区,所以它的生命周期更长。
总结一下:
1.static 修饰局部变量时,改变了局部变量的存储类型:
本来一个局部变量是存储在栈区的,被static修饰的局部变量是存储在静态区的。
2.存储在静态区的变量出了它的作用域,变量也不销毁,所以生命周期比较长。
3.static修饰的局部变量的生命周期和程序的生命周期一样。
4.static修饰的局部变量作用域并不发生改变。
2)修饰全局变量
上图中g_val是一个全局变量,打印结果是2023。
实际上,上图中的test1.c和test.c文件在编译器里是分开编译的,他们经过编译→链接→生成可执行程序(.exe),而全局变量具有外部链接属性,在第二步链接时两个.c文件才整合在一起。
static修饰全局变量后的代码及结果:
这时就出现了报错。
总结一下:
static修饰全局变量的时候,改变了全局变量的链接属性:
本来一个全局变量具有外部链接属性,但被static修饰后就变成了内部链接属性。
这时static修饰的全局变量只能在本源文件(.c)中使用,其他文件无法再使用。
3)修饰函数
上图是个两数求和的代码,test1.c中的函数在test.c中也能够被调用,说明函数也具有外部链接属性。
static修饰函数后的代码及结果:
这时就报错了。
总结一下:
其实static修饰函数与static修饰全局变量是类似的,
本来一个函数具有外部链接属性,但被static修饰后就变成了内部链接属性。
这时static修饰的函数只能在本源文件(.c)中使用,其他文件无法再使用。
今天就先学到这儿啦,未完待续。。。