文章目录
- 一.注释
- 二.续行符与转义符
- 1.续行符
- 2.转义符
- 三.回车与换行
- 四.逻辑操作符
- 五.位操作符和移位操作符
- 六.前置++与后置++
- 七.字符与字符串
- 八./和%
- 1.四种取整方式
- 2.取模与取余的区别和联系
- 3./两边异号的情况
- 1.左正右负
- 2.左负右正
- 九.运算符的优先级
一.注释
注释的两种符号:
- // +内容
- /*+内容 */
- 说明:/*只会与最近的 */进行匹配。
int main()
{
//这是一段注释
/*这是一段注释*/
return 0;
}
说明:注释在预处理阶段(准确的来说是预编译)就被删除了,而与之代替的是空格,所以注释的本质上其实是空格。注释的意义在于提高代码的可读性。
4. 补充:
1.空格:C语言中空格是用来当做符号识别的分割符
2:预处理器识别符号的基本方式是贪心法,所谓贪心指的是,编译器会尽可能的识别多的字符,(也就是说编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么就再读一个字符,判断已经读入的两个字符组成的字符串是否可能是一个字符的组成部分;如果可能,继续读入下一个字符,判断已经读入的两个字符是否可能是一个符号的组成部分;如果可能继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已经不再可能组成一个有意义的字符)——<<C陷阱与缺陷>>,这样做是为了满足大多数的情况,而少数的情况需要我们自己对符号进行分割,那具体如何分割,当然用空格了。
例1:
#include<stdio.h>
int main()
{
int a = 1;
int* p = &a;
int b = a/ *p;//注意:这里的/*之间需加一个空格,否则会被编译器判断为注释符号
return 0;
}
例2:
int main()
{
int a = 1;
int b = 2;
int c = a-- - b;//是a后置减减再减去b
printf("%d\n", c);//答案是-1
a = 1;
b = 2;
c = a - --b;//是b前置--,然后a再减去b
printf("%d\n", c);//答案:0
return 0;
}
说明:像这类准二义性的问题,有时候会给我们带来麻烦。
不好的注释方式:
例1:
#if 0
int main()
{
printf("hello world\n");
return 0;
}
#endif
例2:
int main()
{
if (0)
{
printf("hello world\n");
}
return 0;
}
说明:这样注释的方式并不明显,所以阅读代码时,并不确定这段代码是否是一段注释,所以不建议这样进行注释。
二.续行符与转义符
1.续行符
逻辑表达式:
#include<stdio.h>
int main()
{
int a = 1;
int b = 1;
int c = 0;
int d = 1;
if (a == 1 &&\
b == 1 &&\
c == 0 &&\
d == 1)
{
printf("hello world\n");
}
return 0;
}
说明:不带续行符也是可以的,但这就好像不写函数参数默认为int一样,所以严格一点,还是建议带上续行符号。
注意:续行符后不能带空格
字符串:
int main()
{
char* p = "abcd\
efg";//这是为了处理比较长的字符串而准备的,我们这里就暂且用一下。
printf("%s\n", p);//字符串的续行符是不会被打印出来的
return 0;
}
2.转义符
说明:\在这里是改变原字符的意思,具体有以上14种。
注意:单个\出现是不能被打印,要想打印需把\转义也就是\\。
例1:
#include <stdio.h>
int main()
{
printf("c:\code\test.c\n");
int len = strlen("c:\code\test.c\n");//这里的\是打不出来的
printf("%d",len);//len是13
return 0;
}
解释:
1——c,
2——:
3——\c(注意在vs 2019中 \加上字符被编译器认为是一个转义字符),
4——0,
5——d;
6——e,
7——\t,
8——e,
9——s,
10——t,
11—— . ,
12——c,
13——\n,
例2:
#include<stdio.h>
#include<string.h>
int main()
{
printf("%d\n",strlen("c:\\code\\test.c\\n"));
//这样能打印出\了,但是字符的个数比前面多了3个——总共16个字符
printf("c:\\code\\test.c\\n");
return 0;
}
效果:
例3:
#include<stdio.h>
int main()
{
printf("\"");//这里\是改变"的意思,而能被打印出来
return 0;
}
说明:"会自动与最近的”进行匹配
三.回车与换行
1.回车
符号:\r
说明:指的是回到当前行的首元素
2.换行
符号:\n
说明:准确的是回到当前行的下一行的相同位置
注意:一般编译器实现的换行是:回车+换行(上面提及的换行)
一个有意思的旋转光标的实现:
#include<stdio.h>
#include<Windows.h>
int main()
{
while (1)
{
printf("\\\r");
Sleep(100);
printf("|\r");
Sleep(100);
printf("-\r");
Sleep(100);
printf("/\r");
Sleep(100);
}
return 0;
}
四.逻辑操作符
逻辑与:&&
说明:一假即为假,全真才为真(串联)
逻辑或: ||
说明: 一真即为真,全假才为假(并联)
运算顺序:从左向右
#include<stdio.h>
int my_print()
{
printf("hello\n");
return 1;
}
int main()
{
int judge = 0;
scanf("%d", &judge);
judge && my_print();
//输入0时,不执行my_print
//输入1时,执行my_print
//judge || my_print();
//输入1时,不执行
//输入0时,执行
return 0;
}
五.位操作符和移位操作符
位运算符是对内存的数直接进行运算的(补码)
按位与:&
说明:同1才为1,其余都为0
按位或:|
说明:同0才为0,其余为1
按位异或:^
说明:相同为0,相异为1
按位取反: ~
说明:1变0,0变1,包括符号位。
简单运用:
int main()
{
printf("%d\n", 1 & 2);//0
// 01(二进制)
//& 10
// 00
//答案是:0
printf("%d\n", 1 | 2);//3
// 01
//& 10
// 11
//答案:3
printf("%d\n", 1 ^ 2);//3
// 01
//^ 10
// 11
//答案:3
printf("%d\n", ~-1);//0
// 11111111 11111111 11111111 11111111
//~
// 00000000 00000000 00000000 00000000
//答案:0
return 0;
}
移位操作符
1.移位操作符运算后不会影响操作数本身
2.移位操作符运算的范围为整数
- 左移操作符
符号:<<(双箭头向左)
功能:将补码整体左移n位舍去,右边补0。
int a =1;
int b = a<<1;//这是将a的补码向左移动一位,
a的补码:00000000000000000000000000000001
b的补码:00000000000000000000000000000010
2. 右移操作符
符号:>>(双箭头向右)
1.算数右移
功能:将补码整体向右移n位,最左边补符号位
例子:
int a =-1;
int b = a>>1:
a的补码:11111111111111111111111111111111
b的补码:11111111111111111111111111111111
1.逻辑右移
功能:将补码整体向右移n位,最左边补0
一般右移都是算数右移(补符号位)
关于sizeof的整形提升问题:
#include<stdio.h>
int main()
{
char a = 0;
printf("%d\n", sizeof(~a));//4
printf("%d\n", sizeof(!a));//1
printf("%d\n", sizeof(a&a));//4
printf("%d\n", sizeof(a|a));//4
printf("%d\n", sizeof(a^a));//4
printf("%d\n", sizeof(a >>1));
printf("%d\n", sizeof(a <<1));
return 0;
}
说明:位运算符合移位操作符,都是在整形的大小进行计算的。
注意:!a在VS下是1,在Linux下是4,这里我们推荐当做4进行理解。
经典运用:
1.用异或和按位与实现加法
说明:CPU也是通过异或和按位与进行加法运算的。
#include<stdio.h>
int main()
{
int begin = 0;
int process = 0;
int end = 0;
scanf("%d%d", &begin, &end);
process = end;
while (process)//当没有进位信息,就停止循环
{
//先进行异或不保留进位信息
end = begin ^ process;
//获取进位信息,左移之后是进位
process = (begin & process) << 1;
//更新下一轮的加数信息
begin = end;
}
printf("%d\n", end);
return 0;
}
2.用异或实现两个数交换
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
printf("a==%d,b==%d\n", a, b);
a = a ^ b;
b = a ^ b;
// b =a^(b^b)=a^0=a
a = a ^ b;
//a = b^(a^a)=b^0=b
printf("a==%d,b==%d\n", a, b);
return 0;
}
说明:
1.异或支持交换律和结合律
2.0与任何数异或等于本身
3.两个相同的数异或为0
3.用按位与将二进制序列输出
void ShowBites(int x)
{
for (int i = 31; i >= 0; i--)
{
if (((x>>i) & 1)== 1)
{
printf("1");
}
else
{
printf("0");
}
}
}
4.用异或将指定的数变为1
#include<stdio.h>
#define SPECIALBIT(x,y) ((x)|=(1<<(y-1)))
int main()
{
int a = 0;
SPECIALBIT(a, 5);
ShowBites(a);
return 0;
}
5.用按位与将指定位置变为0
#include<stdio.h>
#define SPECIALBITS(x,y) ((x)^=(1<<(y-1)))
int main()
{
int a = 0xFFFFFFFF;
SPECIALBITS(a, 5);
ShowBites(a);
return 0;
}
六.前置++与后置++
我们一般说前置++是先自增一,再使用,后置++是先使用,然后再加1,真的是这样吗?
答案:应该说大多数情况下是这样的,这样只是为了方便我们记忆,这种情况并不是绝对的。
满足情况的例子:
int main()
{
int a = 0;
int b = 1;
b = a++;
return 0;
}
b=a++的反汇编代码
int main()
{
int a = 0;
int b = 1;
b = ++a;
return 0;
}
不满足情况的例子:
int main()
{
int a = 0;
++a;
a++;
return 0;
}
一个典型的问题表达式:
所谓的问题表达式,并不是说表达式的语法有问题,而是说表达式的计算路径具有二义性,也就是有多种计算路径。
#include<stdio.h>
int main()
{
int a = 1;
a = (++a) + (++a) + (++a);
printf("%d\n", a);
return 0;
}
说明:这里是++与+的计算顺序与优先级的问题
有两种可能:
1.先计算全部的++,也就是把a的值先自增三次,再把自增过后的a相加,答案为12,这是VS下运行的结果
2.先计算前两个++,也就是先把a自增两次,再把自增过后的前两个a相加,最后再对a自增一,再把前两次相加的结果再相加此时a自增的结果,也就是3+3+4=10
这是Linux下的运行结果。
七.字符与字符串
1.字符串
说明:C语言并没有字符串类型,但是有字符类型。
字符串在C语言中主要以两种形式存在:
1.数组
2.字符指针
但是字符串应用的场景很多。
int main()
{
char* p ="abcdef";//p是字符串首字符的地址
//说明:这里的字符串是不能进行修改的。
char arr[]="abcdef";//字符串被放在数组中,字符串里面的字符是能修改的
char c = "0123456"[1];//这是字符指针的形式。
printf("%d\n",sizeof("abcdef");//这是以数组形式存在的,其大小是
//字符串的字符个数
printf("%d\n", sizeof(""));//里面有一个字符\0,所以是1
return 0;
}
2.字符
说明:字符常量是以整形存在的,字符常量被放在字符变量中是要发生截断的。
注意:
1.一个字符里面最多包含4个字符(‘abcd’),对应4个字节
2.一个字符里面不能为空!
int main()
{
printf("%d\n", sizeof('c'));//这是4个字节
char c = 'c';
printf("%d\n", sizeof(c));//这是1个字节
printf("%d\n",sizeof(+c));//整形提升,4个字节
//CPU运算是以整形大小的
return 0;
}
为什么存在ASCII码表?
因为代码是外国人发明的,外国的字符由26个英文字符组成,这也就照应了ASCII码表里面,为啥有26个大小写字符,那与计算机怎么交互信息呢?自然是由ASCII码表翻译出的字符啦!
八./和%
1.四种取整方式
1.向0取整
函数:trunc()
参数:double
返回值:double
#include<stdio.h>
#include<math.h>
int main()
{
printf("%f\n", trunc(5.4));//5.0
return 0;
}
2.向负无穷取整
#include<stdio.h>
#include<math.h>
int main()
{
printf("%f\n", floor(5.4));//5.0
return 0;
}
3.向正无穷取整
函数:ceil()
参数:double
返回值:double
#include<math.h>
int main()
{
printf("%f\n", ceil(5.4));//6.0
return 0;
}
4.四舍五入取整
函数:round()
参数:double
返回值:double
#include<math.h>
int main()
{
printf("%f\n", round(5.4));//5.0
return 0;
}
2.取模与取余的区别和联系
在数学中余数的定义为:
如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r 且0 ≤ r < d。其中,q被称为商,r 被称为余数。
注意:这里的余数r是大于等于0的。
在计算机中余数的定义
如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = qd + r其中,q被称为商,r 被称为余数。
注意:这里的余数是可以小于0的,商的结果是向0取整的
比如:5/-2=-2.5,向0取整为-2,这里的 /准确的来说是取余
余数的求法:r=a-qd=5-(-2)*(-2)=5-4=1,也就是用公式求。
C语言的 / 就是取余
在计算机中取模的定义
如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = qd + r其中,q被称为商,r 被称为余数。
说明:a/d=q余r,这里的/准确的来说是取模
注意:这里的余数是可以小于0的,商的结果是向负无穷取整的
比如:5/-2=-2.5,向负无穷取整为-3
余数的求法:用公式求,r=a-qd=5-(-3)*(-2)=5-6=-1.
Python的 / 就是取模
3./两边异号的情况
1.左正右负
这里的r=a-qd,a是被除数,q是商,d是除数,
也就是a>0,d<0,q<0,q * d > 0,所以可以这样写r=|a|-|q*d|,
1.当为取模时,商实际上会比较小(负数),绝对值比较大,所以|q*d|>|a|,因此r是小于0的
2.当为取余时,商实际上会比较大(负数),绝对值比较小,所以|qd|<|a|,r是大于0的。
2.左负右正
这里的r=a-qd,a是被除数,q是商,d是除数,
也就是a<0,d>0,q<0,q*d<0,所以可以这样写r=|q*d|-|a|
1.当为取模时,商实际上会比较小(负数),绝对值比较大,所以|q*d|>|a|,因此r是大于0的
2.当为取余时,商实际上会比较大(负数),绝对值比较小,所以|qd|<|a|,r是小于0的。