编译器的工作流程
大端和小端
使用补码的好处
scanf()函数解析:由scanf说起之1:scanf函数和回车、空格 及其返回值
视频见:小甲鱼,VIP资料\【视频教程】C++快速入门\第二讲和第三讲
要求:编写一个程序,要求用户输入一串整数和任意数目的空格,这些整数必须位于同一行中,但允许出现在该行中的任何位置。当用户按下键盘上的“Enter”键时,数据输入结束。程序自动对所有的整数进行求和并打印出结果。
示例代码:
比较好的习题,用到了scanf函数:https://fishc.com.cn/forum.php?mod=viewthread&tid=68999&extra=page%3D1%26filter%3Dtypeid%26typeid%3D570
https://fishc.com.cn/thread-66471-1-1.html
https://fishc.com.cn/thread-67234-1-1.html
扩展:浮点数和科学记数:
科学记数法
浮点数:表示更大范围的小数(#)
进制转换
示例:二进制(补码)-> 十进制
对于有符号数(补码)来说:
如果符号位为 0,表示该数为正数,转换跟无符号数没什么两样。
如果符号位为 1,表示该数为负数,此时符号位的位权不变,但该位的权值应该乘以 -1 得到。
举个栗子,将有符号数 0011 1100 转换成十进制数,与无符号数的做法是一样的:
然后如果符号位为 1,表示这是一个负数,比如 1011 1100,那么符号位的权值就应该乘以 -1 得到:
在举个极端点的例子,比如 1000 0000:
补码:
正数的补码是该数的二进制形式。负数的补码需要通过以下几步获得:
- 先取得该数的绝对值的二进制形式
- 再将第1步的值按位取反
- 最后将第2步的值加1
基本数据类型的取值范围:
数据类型 |
字节数 |
取值范围 |
char |
1 |
-128 ~ 127 |
unsigned char |
1 |
0 ~ 255 |
short |
2 |
-32768 ~ 32767 |
unsigned short |
2 |
0 ~ 65535 |
int |
4 |
-2147483648 ~ 2147483647 |
unsigned int |
4 |
0 ~ 4294967295 |
long |
4 |
-2147483648 ~ 2147483647 |
unsigned long |
4 |
0 ~ 4294967295 |
long long |
8 |
-9223372036854775808 ~ 9223372036854775807 |
unsigned long long |
8 |
0 ~ 18446744073709551615 |
数据类型 |
字节数 |
取值范围(绝对值) |
float |
4 |
1.17549 * 10-38 ~ 3.40282 * 1038 |
double |
8 |
2.22507 * 10-308 ~ 1.79769 * 10308 |
long double |
12 |
2.22507 * 10-308 ~ 1.79769 * 10308 |
关于 定点数、浮点数 解释:
https://fishc.com.cn/forum.php?mod=viewthread&tid=67265&extra=page%3D1%26filter%3Dtypeid%26typeid%3D584
优先级 |
运算符 |
含义 |
使用形式 |
结合性 |
说明 |
1 |
[ ] |
数组下标 |
数组名[整型表达式] |
左到右 → |
|
( ) |
圆括号 |
(表达式) |
|||
. |
成员选择(对象) |
对象.成员名 |
|||
-> |
成员选择(指针) |
对象指针->成员名 |
|||
++ |
自增运算符 |
变量名++ |
单目运算符 |
||
-- |
自减运算符 |
变量名-- |
单目运算符 |
||
2 |
- |
负号运算符 |
-表达式 |
右到左 ← |
单目运算符 |
(类型) |
强制类型转换 |
(数据类型)表达式 |
单目运算符 |
||
++ |
自增运算符 |
++变量名 |
单目运算符 |
||
-- |
自减运算符 |
--变量名 |
单目运算符 |
||
* |
取值运算符 |
*指针表达式 |
单目运算符 |
||
& |
取地址运算符 |
&左值表达式 |
单目运算符 |
||
! |
逻辑非运算符 |
!表达式 |
单目运算符 |
||
~ |
按位取反运算符 |
~表达式 |
单目运算符 |
||
sizeof |
长度运算符 |
sizeof 表达式 或 sizeof(类型) |
单目运算符 |
||
3 |
/ |
除 |
表达式 / 表达式 |
左到右 → |
双目运算符 |
* |
乘 |
表达式 * 表达式 |
双目运算符 |
||
% |
余数(取模) |
整型表达式 % 整型表达式 |
双目运算符 |
||
4 |
+ |
加 |
表达式 + 表达式 |
左到右 → |
双目运算符 |
- |
减 |
表达式 - 表达式 |
双目运算符 |
||
5 |
<< |
左移 |
表达式 << 表达式 |
左到右 → |
双目运算符 |
>> |
右移 |
表达式 >> 表达式 |
双目运算符 |
||
6 |
> |
大于 |
表达式 > 表达式 |
左到右 → |
双目运算符 |
>= |
大于等于 |
表达式 >= 表达式 |
双目运算符 |
||
< |
小于 |
表达式 < 表达式 |
双目运算符 |
||
<= |
小于等于 |
表达式 <= 表达式 |
双目运算符 |
||
7 |
== |
等于 |
表达式 == 表达式 |
左到右 → |
双目运算符 |
!= |
不等于 |
表达式 != 表达式 |
双目运算符 |
||
8 |
& |
按位与 |
整型表达式 & 整型表达式 |
左到右 → |
双目运算符 |
9 |
^ |
按位异或 |
整型表达式 ^ 整型表达式 |
左到右 → |
双目运算符 |
10 |
| |
按位或 |
整型表达式 | 整型表达式 |
左到右 → |
双目运算符 |
11 |
&& |
逻辑与 |
表达式 && 表达式 |
左到右 → |
双目运算符 |
12 |
|| |
逻辑或 |
表达式 || 表达式 |
左到右 → |
双目运算符 |
13 |
?: |
条件运算符 |
表达式1? 表达式2: 表达式3 |
右到左 ← |
三目运算符 |
14 |
= |
赋值运算符 |
变量 = 表达式 |
右到左 |
双目运算符 |
/= |
除后赋值 |
变量 /= 表达式 |
双目运算符 |
||
*= |
乘后赋值 |
变量 *= 表达式 |
双目运算符 |
||
%= |
取模后赋值 |
变量 %= 表达式 |
双目运算符 |
||
+= |
加后赋值 |
变量 += 表达式 |
双目运算符 |
||
-= |
减后赋值 |
变量 -= 表达式 |
双目运算符 |
||
<<= |
左移后赋值 |
变量 <<= 表达式 |
双目运算符 |
||
>>= |
右移后赋值 |
变量 >>= 表达式 |
双目运算符 |
||
&= |
按位与后赋值 |
变量 &= 表达式 |
双目运算符 |
||
^= |
按位异或后赋值 |
变量 ^= 表达式 |
双目运算符 |
||
|= |
按位或后赋值 |
变量 |= 表达式 |
双目运算符 |
||
15 |
, |
逗号运算符 |
表达式1,表达式2,表达式3,… |
左到右 → |
注1:优先级相同的运算符,运算次序由结合性决定。
注2:* 目运算符是指操作数的个数,比如单目运算符只有一个操作数,双目运算符有两个操作数,而三目运算符则有三个操作数。
注3:通过使用小括号可以提升表达式的优先级至最高。
运算符 |
名称 |
例子 |
结果 |
+ |
加法运算符(双目) |
5 + 3 |
8 |
- |
减法运算符(双目) |
5 - 3 |
2 |
* |
乘法运算符(双目) |
5 * 3 |
15 |
/ |
除法运算符(双目) |
5 / 3 |
1 |
5.0 / 3.0 |
1.666667 |
||
% |
求余运算符(双目) |
5 % 3 |
2 |
5.0 % 3.0 |
出错 |
||
+ |
正号运算符(单目) |
+5 |
5 |
- |
负号运算符(单目) |
-5 |
-5 |
算术运算符知识点:https://fishc.com.cn/forum.php?mod=viewthread&tid=67733&extra=page%3D1%26filter%3Dtypeid%26typeid%3D584
求余运算符两边要求均为整型 ;
关系运算符,得到的为逻辑值:真、假;
逻辑表达式:!、&&、||;逻辑非、逻辑与、逻辑或;
逻辑运算符注意: 短路求值原则;
case 后边应该只能跟整型常量或常量表达式,不能是浮点型常量。
任意两个 case 跟随的整型常量值不能相同(否则编译系统无法判断你要往哪儿跳转)。
[知识点备忘] S1E12:switch语句和分支嵌套
[课后作业] S1E12:switch语句和分支嵌套 | 课后测试题及答案
[知识点备忘] S1E15:break语句和continue语句
赋值运算符的左边必须是一个 lvalue,变量名就是 lvalue,但常数就不是了,所以你把 5 写在赋值号的左边就会出错:
5 = a;
什么是左值(lvalue)和右值(rvalue)?
知识点总结:https://fishc.com.cn/forum.php?mod=viewthread&tid=69845&extra=page%3D1%26filter%3Dtypeid%26typeid%3D584
[阶段考核] 第一阶段考核(考核S1E1~S1E16知识点)
答案:进入答案的密码为:233168
[阶段考核] 第一阶段考核答案及解析(密码:第 0 题的答案)
https://fishc.com.cn/forum.php?mod=viewthread&tid=70264&extra=page%3D1%26filter%3Dtypeid%26typeid%3D570
关于闰年判断;
1. 写一个生命计算器,要求用户输入生日,显示他在这个世界上活了多少天?c
定义一个字符数组的方式:
str、str1、str2:
输入一个字符串的方式1:
输入一个字符串的方式2:
gets(str);
不使用 scanf 函数接收字符串是因为 scanf 遇到空格会终止;
(1)函数strlen、关键字:sizeof
strlen 函数文档
请问下边程序会打印什么内容?
答:sizeof 运算符是取得字符串的尺寸,即该字符串所处存储空间的大小。.Wn8E
代码开头的 char str[] = "I love FishC.com!" 决定了该字符数组的尺寸。而字符串的长度则是由第一个遇到的结束符('\0')所定义的。只要编译器读取到结束符('\0'),它不管你字符数组后边是否有其它内容,都会认为字符串已经结束。
即最后结果为:
sizeof(str)=18; strlen(str)=7
(2)拷贝字符串 strcpy() strcpy 函数文档
拷贝字符串(受限),需要在目标数组中添加'\0' strncpy 函数文档
请问下边代码将打印什么内容?
答:结果应该是只打印 New 这个字符串。K@$,GB
Wb#M)C*wA{DxJkl5d[-$
有些同学可能会觉得 str1 的长度比 str2 长,调用 strcpy 函数后会只覆盖前边的内容,后边保留……但事实上并不是这样,因为 strcpy 函数复制 str2 的时候,会将该字符串最后的 '\0' 也一并复制过去。
如下图所示:版权
(3)连接字符串 strcat();strcat 函数文档
1、函数自动在末尾添加'\0',
2、同时要保证目标数组足够大,容纳下待连接的字符数组;
连接字符串 strncat() 受限; strncat 函数文档
(4)比较字符串strcmp() strcmp 函数文档
对两个字符数组的字符逐个比较ASCII大小
如果str1==str2,返回0;
如果str1
如果str1>str2,返回1;
比较字符串(受限) strncmp 函数文档
定义一个变量,定义一个一维数组、定义一个二维数组;可以认为为:点、线、面
测试程序的小模版:
获取二维数组的长度:
sizeof(a) / sizeof(a[0][0])
指针是一个地址/内存的地址;
指针变量是存放地址的变量;指针变量存在类型,其类型指的是存放地址指向的数据类型;(32位系统,指针变量是4个字节)
普通变量存放的是数据;
取地址运算符/&和取值运算符/*
直接访问通过变量名,间接访问通过指针
指针未初始化,即没有给指针赋值;
[知识点备忘] S1E21:指针
取址操作符(&)的作用对象应该是一个左值。
重要的内容要重复强调:C 语言的术语 lvalue 指用于识别或定位一个存储位置的标识符。(注意:左值同时还必须是可改变的)
什么是左值(lvalue)和右值(rvalue)?
[课后作业] S1E21:指针 | 课后测试题及答案
数组不是指针、指针不是数组;
数组名只是一个地址,而指针是一个左值;什么是左值(lvalue)和右值(rvalue)?
数组名其实是数组第一个元素的地址;(也即可以将数组名当作一个指针,用指针法对数组元素进行操作)
显示结果:
上面的代码:char str[50]; (&str+1)指向了哪里?答:指向了以50个char类型数组的下一个数组的开始;这个思想很重要!
对于一个数组中元素的操作:采用基本的数组法,按照数组规则运行;或者指针法,定义一个指向数组的指针,然后访问;
指针可以指向一个地址,也可以指向一个字符串;
char *p="I Love Beijing"
请问 str[20] 是否可以写成 20[str]?V
答:可以。Powered by bbs.fishc.com
因为在访问数组的元素的时候,数组名被解释为数组第一个元素的地址。JS2?FT
所以 str[20] == *(str + 20) == *(20 + str) == [20]strBJ]_,P
你们可能对 *target++ != '\0' 这一行代码有疑问,这里我给大家解释下。首先在“运算符的优先级和结合性”(http://bbs.fishc.com/thread-67664-1-1.html)可以查到自增运算符(++)的优先级比取值运算符(*)要高,所以 *target++ 相当于 *(target++),先执行自增运算符,再取值。但由于这是一个后缀的自增运算符,所以自增的效果要在下一条语句才会生效,因此这里取出来的依然是 target 地址自增前指向的数组元素的值。
基本要求:使用 fgets 函数接收用户输入的两个字符串到 str1 和 str2 中,将 str2 连接到 str1 后边,并打印出来。u
程序如下:
重点关注下,这节课的课后习题:
[课后作业] S1E22:指针和数组 | 课后测试题及答案
看后面两个字;原理:根据优先级,[ ]优先级高于 *
指针数组:是一个数组,每个数组元素存在一个指针变量;int *p1[5];
[ ]、() 优先级相同,结合顺序为:从左向右;
数组指针:是一个指针,它指向一个数组; int (*p1)[5]
[知识点备忘] S1E23:指针数组和数组指针
比如定义一个数组:
int array[5]={1,2,3,4,5};
请问 array 和 &array 有区别吗?!IA|i8Xt
O!`cU4-,e.&ws[DdNGqlhTCBP7~H
答:有。版权属于:bbs.fishc.com
L=a{SYBZE3>dVMc;
解析:虽然 array 和 &array 的值相同,但含义是不一样的。array 表示数组第一个元素的位置,而 &array 表示的是整个数组的位置(这里将数组看做一个整体)。
运行结果:
如果要使用指针来指向二维数组,只能使用数组指针。重点!
[扩展阅读] 什么是语法糖(Syntactic sugar
在 C 语言里用 a[n] 表示 *(a+n),用 a[n][m] 表示 *(*(a+n)+m),这就是语法糖的应用,因为在内部,编译器会自动将 a[n] 转换为 *(a+n) 的形式实现。
*(array +1) == array[1] //第二行首元素的地址
*(array+1)表示 数组第二行 ,第一个元素的地址
运行结果:
*(array +1)+3 == &array[1][3] //第二行,第三个元素的地址
运行结果:
结论:多纬数组是一纬数组的线性扩展……;
用上面提到的“语法糖”进行理解;数组名为指针变量,然后用偏移法进行表示;
数组指针 与 二维数组 与上面16、指针数组 和 数组指针 联系起来一块学习!
运行结果:
总结:
请问 str[3] 和 *(str + 3) 是否完全等价?Y7ha*'qFON
hGA'Qrb XP0SB`|?I9o2f
答:完全等价。Powered by bbs.fishc.com
解析:在 C 语言中,数组名是被作为指针来处理的。更确切的说,数组名就是指向数组第一个元素的指针,而数组索引就是距离第一个元素的偏移量。这也解释了为什么在 C 语言中数组下标是从 0 开始计数的,因为这样它的索引可以对应到偏移量上。因此,str[3] 和 3[str] 是相同的,因为它们在编译器的内部都会被解释为 *(str + 3)。
如果数组 array 的定义如下:L-B_JPzj
int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
请问 array 和 &array 有区别吗?!IA|i8Xt
O!`cU4-,e.&ws[DdNGqlhTCBP7~H
答:有。版权属于:bbs.fishc.com
L=a{SYBZE3>dVMc;
解析:虽然 array 和 &array 的值相同,但含义是不一样的。array 表示数组第一个元素的位置,而 &array 表示的是整个数组的位置(这里将数组看做一个整体)。
如果有 int a[10];,请问 &a 和 &a[0] 表达式的类型有何不同?y6:`<
;<5vHXRjZY?a%MsJ{Wn4hl
答:a 是一个数组,在 &a 这个表达式中,数组类型做左值,&a 表示取整个数组的首地址,类型是 int (*)[10];&a[0] 则表示数组 a 的第一个元素的首地址,虽然两个地址的数值相同,但后者的类型是 int *。@
[课后作业] S1E23:指针数组和数组指针 | 课后测试题及答案
[课后作业] S1E24:指针和二维数组 | 课后测试题及答案
void指针 为 通用指针,就是可以指向任意类型的数据。也就是说,任何类型的指针都可以赋值给void指针;
NULL指针 visual studio中,定义如下:
当你含不清楚将指针初始化为什么地址时,请将它初始化NULL;
当你对指针进行解引用时,先检查该指针是否为NULL。
注意分清楚 NULL 与 NUL:
NULL 用于指针和对象,表示指向一个不被使用的地址;
而NUL 即 ‘\0’表示字符串的结尾。
你猜下边代码会打印多少?}u
答:会报错!那 void 既然是无类型,我们就不应该用它来定义一个变量,如果你一定要这么做,那么程序就会给你报错(我只是换个提问方式,看看多少童鞋会上当)。5uJt^
1. 那 sizeof(void *) 呢?O6]|JjM'^,
&9?]N1i C*{p$Wq=3KD#LbI>Uv`.u
答:如果你回答是 4 个字节或 8 个字节,那么本题不能算你答对。因为指针的尺寸是与编译器的目标平台相关的。比如目标平台是 32 位的,那么 sizeof(void*) 就是 4,如果是 64 位的,那么 sizeof(void *) 就是 8,如果是 16 位的,那么就是 2 啦。J
2.对 NULL 指针进行解引用,结果是什么?SIA}6?R^
A]kS`'p+ ?Rr9@wNu$H&"aB
答:报错。无论什么操作系统,对空指针进行解引用都是非法的。
3.请问下边定义有没有问题?={(5U
答:报错。
NULL的宏定义为:
如果为.c文件:
这里是将 0 强制转换成 void 指针,所以要这么写才能通过编译:
int *p = (void *)0;
[课后作业] S1E25:void指针和NULL指针 | 课后测试题及答案
[课后作业] S1E26:指向指针的指针 | 课后测试题及答案
参考16、17的知识点;
如果要使用指针来指向二维数组,只能使用数组指针。重点!
指针数组 和 指向指针的指针。
指针数组
字符串指针数组的使用;
https://zhuanlan.zhihu.com/p/36791383
使用指向指针的指针来指向数组指针,至少有两个优势:
- 避免重复分配内存(虽然我们进行了多个分类,但每本书的书名只占据一个存储位置,没有浪费)
- 只需要进行一处修改(如果其中一个书名需要修改,我们只需要修改一个地方即可)
这样,代码的灵活性和安全性都有了显著地提高。
[知识点备忘] S1E26:指向指针的指针
指向常量的指针:
注意当 常值变量和指向常值变量的指针 与 常值变量和指向变量的指针 有区别?
常量指针:
1、指向非常量的常量指针:
2、指向常量的常量指针:
请问下边代码段中,A、B 和 C 中哪一个语句是错误的?你从中总结出了什么道理?4B
A:
B:
C:
D:
答:只有 A 是错误的做法!在赋值、初始化或参数传参的过程中,赋值号左边的类型应该比右边的类型限定更为严格,或至少是同样严格。在 A 中,使用指针 p,就可能间接地绕过 const 设定的防线。sCl
请问下面代码可以成功通过编译并运行吗?2.&'dt<3>
RZ$|i,=a(QpK%m?^Dd.Ec
代码:
答:虽然会“友情提示”,但代码还是可以通过编译并运行的。E{b`
1>d:\cppworkspace\c_again\exercise\exercise\s1e27.c(115): warning C4090: “初始化”: 不同的“const”限定符
很多鱼油不经要问:num 明摆着是一个 const 的变量,为何还可以通过指针进行间接地修改呢?'[GN2%kSwM
Xp5])0K=cwQlV|*!>L@b-B3U+v4uTZ
因为 const 其实只是对变量名(num)起到一个限制作用,也就是说你不可以通过这个变量名(num)修改它所在的内存;
但是可以通过指针进行间接修改。但是,这并不是说这块内存就不可以修改了,如果你可以通过其他形式访问到这块内存,还是可以进行修改的。所以,尽管编译器发现苗头不对,但它也只能义务地提醒你而已。&
现在请编写一个程序,测试一下你当前的计算机是大端还是小端? 大端和小端?[
运行结果: