- 一、注释符号
- (一)、注释的本质
- (二)、c语言注释风格嵌套问题
- (三)、/和*的连用问题
- (四)、注释的基本要求
- 二、条件编译
- 二、斜杠符号(续航符)
- (一)、介绍
- (二)、续航符和换行符是有区别的
- (三)、转义
- (四)、有趣的换行:n和回车:r
- 1.旋转符号例子
- 2.倒计时例子
- 三、单引号和双引号
- (一)、介绍
- (二)、补充
- 四、为什么计算机需要ASCII
- (一)、初步认识
- (二)、ASCII表
- 五、逻辑运算符
- (一)、 逻辑与符号 (&&)
- (二)、逻辑或符号 (||)
- (三)、短路问题
- 六、位运算符号
- (一)、基本了解
- (二)、逻辑运算符和按位运算符区别
- (三)、按位异或(^)
- (四)、实例:交换两个数
- (五)、建议
- 实例
- 1.实现一个宏,对任意一个比特位设置为1
- 2.实现一个宏,对任意一个比特位设置为0
- (六)、整型提升问题
- (七)、左移和右移
- 1.基本了解
- 丢弃问题
- 2.深入理解
- 七、++和--操作符
- (一)、初步了解
- (二)、深入理解后置++
- (三)、复杂表达式
- 贪心算法(表达式匹配问题)
- 八、取模/取余运算问题
- (一)、取整问题
- 1.向0取整
- 2.向负无穷取整
- 3.向正无穷取整
- 4.四舍五入
- 5.综合
- (二)、取模问题
- 1.取模概念
- 2.负数取模
- 3.取余和取模一样吗?
- 4.计算数据同符号
- 5.计算数据不同符号
- 九、运算符优先级问题
初步了解一下;这个是c语言风格,//是c++风格。
首先run代码引入主题:
int main()
{
int i;//ok
char* s = "abcdefgh //hijklmn";//ok
//Is it a
valid comment? //ok
int t j;//err
return 0;
}
我们发现int t j;是错误的。用Linux看预处理:
那么就是编译出错了。
结论:注释被替换,本质是替换成空格。
有了这个总结,下面我们再来看一个例子:
这样就足以看出我们的注释是替换成了空格。
#includeint main() { //不报错 printf("hello world!"); printf("hello world!"); */ //报错 }
说明 匹配。
(三)、/和*的连用问题#includeint main() { int x = 10; int y = 0; int z = 5; int* p = &z; y = x/*p; //err 具有二义性 return 0; }
这里的x/*p,本以为是对x和 *p除法运算,实际上编译器把/ *当作了注释。最好对 *p加上(),这样就可以解决问题了。
(四)、注释的基本要求
- 注释应该准确、易懂,防止有二义性。
- 注释是对代码的“提示”,并不是文档。
- 对于全局数据(全局变量、常量定义)都必须加上注释。
- 注释的位置应与被描述代码相邻,必须在上方。
- 注释的缩进要与代码的缩进一致。
- 代码注释段时应注重why而不是how.
- 数值的单位一定要注释。
- 对于函数的入口/出口数据、条件语句、分支语句给注释。
- 在复杂的函数中,在分支、循环语句结束之后需要给注释。
例如:
#includeint main() { while(1) { if(){ } else if{ } else{ }//end of else(对其进行注释) //........ //非常多的判断代码 }//end of while(对其进行注释) return 0; }
- 对变量的范围进行注释,尤其是数。
具体就是这么多的推荐,主要是了解,后期可以做项目了,自然就会了。
二、条件编译
通过两个图可以直观了解条件编译是什么?
两个图进行对比,很容易知道条件编译的用途。
其实,在我们手机中,我们手机对应的设置中有个语言选项,有很多语言选项,在设置的时候,其原理就是条件编译实现的,代码并没有变化,而是类似于条件编译这种思维完成的。
二、斜杠符号(续航符) (一)、介绍
#includeint main() { int a = 1; int b = 2; int c = 3; if(1 == a && 2 == b && 3 == c) { printf("hello human"); } return 0; }
(二)、续航符和换行符是有区别的那么我们可不可以在之前带空格?
这个是可以的。
可不可以在之后带空格?
这个是不行的,在续航符之后最好任何符号都不要带上。
续航符号顾名思义就是对这一行后面补充,而换行符则是换行。
(三)、转义转义有两种转法:
- 字面转特殊
- 特殊转字面
#include(四)、有趣的换行:n和回车:rint main() { printf(""");// "转义就是特殊转字面 printf("hello n human");// n就是字面转特殊 return 0; }
首先理解回车和换行两个概念:
回车和换行并不是一回事,回车就是光标回到当前行的开始,换行就是光标换到下一行。
了解了这,我们就知道我们按下键盘enter键的时候其实是回车换行或者说换行回车。
#include2.倒计时例子int main() { int i = 0; const char *c = "|/-\"; while (1) { i %= 4; printf("[%c]r", c[i]);//r是回车 i++; Sleep(200); } return 0; }
#includeint main() { int i = 10; for (; i >= 0; i--) { printf("[%2d]r", i); Sleep(500); } return 0; }
三、单引号和双引号 (一)、介绍
单引号是字符,双引号是字符串。
"abcd";//ok "a";//ok "";//ok 'abcd';//ok 'a';//ok '';//err 报错原因是单引号中必须要有至少一个字符
上面我们对’abcd’这个语法看上去肯定很陌生,不应该单引号中只能有一个字符吗,为什么有四个字符并没有报错呢,还有一个有意思的,看代码:
#includeint main() { char c = 'a';//ok char a = 'ab';//ok char v = 'abc';//ok char x = 'abcd';//ok char y = 'abcde';//err return 0; }
超出了四个字符就会报错,提示常量中字符太多。
下面我们用sizeof关键字来理解单引号和双引号。
(二)、补充第一个sizeof中是1这个整型;第二个是字符串(含有 );第三个按常理来说应该是字符1,打印出四个空间的大小,不可思议;第四个是字符打印出一个空间,没问题。
原因在于,c99的标准规定,'a’叫做整型字符常量,别看成是int类型。
这样们再来看第三个就很简单了,来看第四个,sizeof©为什么是1个空间,原因就是本来是int类型的,但是c是char类型,当int类型转化为char类型时,存储会发生截断。所以是1个空间的大小。
#includeint main() { char c = 'abcd'; printf("%dn",c); return 0; }
输出结果是d,这里’a’是int类型,为什么输出d?就和大小端有关系,暂且不做研究。
四、为什么计算机需要ASCII (一)、初步认识
计算机中只认识二进制,为了解决人的问题,人不擅长二进制,只认识语言文字,所以在数据传在显示器上也不可能是二进制,对应的是我们认识的文字,所以每个二进制对应的就有大众认识的文字,这就是映射关系,ASCII码表对应的就是映射关系。(映射是由显卡完成的)
(二)、ASCII表
注意:A-a=32,空格十进制数对应的是32。
五、逻辑运算符
前提非零为真,零为假。
(一)、 逻辑与符号 (&&)&逻辑与就是两个或者多个表达式必须同时为真的时候,结果才为真。
用一个实例说明:
#include(二)、逻辑或符号 (||)int main() { int i = 0; int j = 0; if ((++i > 0) && (++j > 0)) { printf("yes!n"); } printf("%d,%d", i, j); return 0; }//输出结果是yes! 1,1
逻辑或就是两个或多个表达式,必须至少一个为真,结果才为真。
int main()
{
int i = 0;
int j = 0;
if ((++i > 0) || (++j > 0))
{
printf("yes!n");
}
printf("%d,%d", i, j);
return 0;
}//输出结果是yes! 1,0
这段代码,我们发现表达式中只执行了前面的(++i>0)并没有执行后面是(++j>0),这种情况我们叫短路。
(三)、短路问题#includeint show() { printf("yes!n"); return 1; } int main() { int flag = 0; scanf("%d", &flag); flag || show(); }输入0后打印出yes!
短路问题依据就是表达式从左依次执行。
六、位运算符号 (一)、基本了解
按位与(&),按位或(|)、按位异或(^)、按位取反(~)。都是二进制进行运算的。
int main()
{
printf("%dn", 2 | 3);
printf("%dn", 2 & 3);
printf("%dn", 2 ^ 3);
printf("%dn", ~0);
return 0;
}//输出结果是3 2 1 -1.
说明:
(二)、逻辑运算符和按位运算符区别2|3就是2和3的按位或,2(10)|3(11) = 3(11),二进制中两个对应的值都为0,则为0,否则为1。
2&3就是2和3的按位与,2(10)|3(11) = 2(10),二进制中两个对应的值都为1,则为1,否则为0。
2^3就是2和3的按位异或,2(10) ^3(11) = 2(01),二进制中两个对应的值不相同时,则为1,否则为0。
~0就是按位取反,0(0000 0000 0000 0000 0000 0000 0000 0000),~0(1111 1111 1111 1111 1111 1111 1111 1111(补码)=1000 0000 0000 0000 0000 0000 0000 0001(原码) =-1 )
逻辑运算符对应的逻辑表达式,需要的是真假。而按位运算符是逐个比特位进行计算
-
任何数和0的按位异或都是这个数本身。
-
两个相同的数的异或是0。
-
按位异或支持交换律和结合律。
- 第一种方法;创建临时变量
#includeint main() { int temp = 0; int a = 10; int b = 20; temp = a; a = b; b = temp; printf("%d,%dn", a, b); return 0; }
- 第二种方法:加法运算
#includeint main() { int a = 10; int b = 20; a = a + b; b = a - b; a = a - b; printf("%d,%d", a, b); return 0; }
注意:加饭运算中二进制有进位现象,如果两个足够大的数相加,那么返回时就会出现溢出问题。
3.第三种方法:按位异或方法
#includeint main() { int a = 10; int b = 20; a = a ^ b; b = a ^ b; a = a ^ b; printf("%d,%dn", a, b); return 0; }
注意:异或运算只是对比特位进行比较判断,并不会出现二进制进位溢出问题。
位操作需要用宏定义好后再使用。
实例 1.实现一个宏,对任意一个比特位设置为1#include#define SetBit(x,n) (x |= (1<<(n-1)))//1< int num = sizeof(x) * 8-1;//控制循环次数 while (num>=0) { if (x & 1< printf("1"); } else { printf("0"); } num--; } printf("n"); } //主体 int main() { int x = 0; //设置指定比特位为1,这里使用按位或 //x表示那个数据设置比特位,n表示设置第几个比特位(从右往左) SetBit(x, 5); //SetBit(x,6); //SetBit(x,1); //等等.... //显示32位比特位 ShowBit(x); return 0; }//输出结果是00000000000000000000000000010000,移动五位,第五位变为1
也可以对多个比特位设置,这里有宏定义,可以直接多次设置宏。
2.实现一个宏,对任意一个比特位设置为0#include(六)、整型提升问题#define ClrBit(x,n) (x &= ~(1<<(n-1))) extern void ShowBit(int x); //ShowBit函数输出比特位 void ShowBit(int x) { int num = sizeof(x) * 8-1;//控制循环次数 while (num>=0) { if (x & 1< printf("1"); } else { printf("0"); } num--; } printf("n"); } //主体 int main() { int x = 0xFFFFFFFF; //设置比特位为0 ClrBit(x, 2); ClrBit(x, 3); ClrBit(x, 4); ClrBit(x, 5); //显示比特位 ShowBit(x); return 0; }//输出结果是11111111111111111111111111100001
先run代码:
#includeint main() { char c = 0; printf("%dn", sizeof(c)); printf("%dn", sizeof(~c)); printf("%dn", sizeof(c<<1)); printf("%dn", sizeof(c>>1)); return 0; }//输出结果是1 4 4 4
这里发生了整型提升,这里整形提升就不细讲了,后期我们再深入讲解整型提升。
这里我们只需要知道计算机计算是在CPU中的寄存器进行的,一般情况下,在32位下,寄存器的位数也是32位,char类型只有八个比特位,只能填补低8位,那么高24位呢?就需要进行整型提升。
左移:最高位丢弃,最低为补零
右移:1. 无符号数:最低位丢弃,最高位补零(逻辑右移) 2.有符号数:最低为丢弃,最高位补符号位(算术右移)
//左移 #includevoid ShowBit(int x) { int num = sizeof(x) * 8 - 1;//控制循环次数 while (num >= 0) { if (x & 1 << num)//先从最高位检验 { printf("1"); } else { printf("0"); } num--; } printf("n"); } int main() { unsigned int x = 0xFFFFFFFF; x <<= 1; ShowBit(x); return 0; }//输出结果:11111111111111111111111111111110,有符号数也是此结果,左移没有符号位差别
//右移 #include丢弃问题void ShowBit(int x) { int num = sizeof(x) * 8 - 1;//控制循环次数 while (num >= 0) { if (x & 1 << num)//先从最高位检验 { printf("1"); } else { printf("0"); } num--; } printf("n"); } int main() { signed int x = 0xFFFFFFFE; //1111 1111 1111 1111 1111 1111 1111 1110(原本的x) x >>= 1; ShowBit(x); return 0; }//输出结果:11111111111111111111111111111111
左移和右移都是在CPU中进行,参与移动的变量是在内存中的,所以需要把数据移动到CPU寄存器中,在进行移动。在大小固定的单元内,一定会有位置到“外边”的情况。(外边的理解就是超出了寄存器储存单元大小)
2.深入理解//左移 #includeint main() { unsigned int a = 1; printf("%un", a << 1); printf("%un", a << 2); printf("%un", a << 3); return 0; }//输出结果:2 4 8
//右移 #includeint main() { unsigned int b = 100; printf("%un", b >> 1); printf("%un", b >> 2); printf("%un", b >> 3); return 0; }//输出结果:50 25 12
//算术右移 #includeint main() { int c = -1; //1111 1111 1111 1111 1111 1111 1111 1111(补码) printf("%dn", c >> 1); printf("%dn", c >> 2); printf("%dn", c >> 3); return 0; }//输出结果:-1 -1 -1
//算术右移 #include七、++和–操作符 (一)、初步了解int main() { unsigned int d = -1; printf("%dn", d >> 1); printf("%dn", d >> 2); printf("%dn", d >> 3); return 0; }//不会报错,最高位补0
让代码run起来!
//前置++(先对a自增再使用) #includeint main() { int a = 10; int b = ++a; printf("%d,%dn", a, b); return 0; }//输出结果:11 11
#include(二)、深入理解后置++//后置++(先使用再对a自增) int main() { int a = 10; int b = a++; printf("%d,%dn", a, b); return 0; }//输出结果:11 10
还是用代码看现象
#includeint main() { int a = 8; int b = a++; return 0; }
来看这段代码的反汇编:
这样就能很好的理解为什么是先使用再自增了。
#includeint main(0 { int a = 8; a++;//并没有使用 return 0; }
再来看这段代码的反汇编:
#includeint main() { int i =1; int j = (++i)+(++i)+(++i); printf("%dn",j); return 0; }
这段代码在visual studio 2022中输出结果是12,在gcc中输出结果是10。不同的编译器有不同的计算规则。
编译器依据符号自动匹配表达式。
在visual studio 2022中,我们每输完一条语句时,当我们打上分号,按下回车换行(enter)键时,编译器会自动匹配表达式。
通过代码看现象。run起来!
1.向0取整#includeint main() { int i = -2.9; int j = 2.9; printf("%dn", i);//输出结果是-2 printf("%dn", j);//输出结果是2 return 0; }
看图:
c语言中默认是向0取整。
c语言中有一个trunc取整函数:
通过代码了解:
可见trunc函数是一个向0取整函数。
floor函数就是一个向负无穷取整的函数。
初步了解:
通过代码进一步了解:
也可以这么理解:
ceil函数就是一个向正无穷取整函数。
先对ceil函数进行初步认识:
通过代码来看:
可以这么理解ceil函数:
round函数就是一个四舍五入函数。
初步了解:
通过代码来理解:
源代码:
#include(二)、取模问题 1.取模概念#include int main() { const char* format = "%.1f t%.1f t%.1f t%.1f t%.1fn"; printf("valuetroundtfloortceilttruncn"); printf("-----t-----t-----t----t-----n"); printf(format, 2.3, round(2.3), floor(2.3), ceil(2.3), trunc(2.3)); printf(format, 3.8, round(3.8), floor(3.8), ceil(3.8), trunc(3.8)); printf(format, 5.5, round(5.5), floor(5.5), ceil(5.5), trunc(5.5)); printf(format, -2.3, round(-2.3), floor(-2.3), ceil(-2.3), trunc(-2.3)); printf(format, -3.8, round(-3.8), floor(-3.8), ceil(-3.8), trunc(-3.8)); printf(format, -5.5, round(-5.5), floor(-5.5), ceil(-5.5), trunc(-5.5)); return 0; }
如果a和d两个自然数,d非零,可以证明存在两个唯一的整数q和r,满足a=q*d+r且0<=r< d,其中,q被称为商,r被称为余数。
#include//visual stuio2022中 int main() { int a = -10; int d = 3; printf("%dn", a / d);//输出-3 printf("%dn", a % d);//输出-1 return 0; }//满足-10=(-3)*3+(-1)
用python环境下,-10%3=-4,-10/3=2,。不同的编译器有不同的计算规则。
3.取余和取模一样吗?4.计算数据同符号取余:尽可能让商,进行向0取整。
取模:尽可能让商,向负无穷大取整。
利用上述a=q*d+r很好理解。不需要讲解了。
这也一样,利用上述a=q*d+r很好理解。
结论:如果参与取余的数据同符号,取模等价于取余;如果参与取余的两个数据符号不同,在c语言中(或者其他采用向0取整)余数符号和被除数相同。
这个图表就没什么好讲的了,一目了然!



