关于C语言的一些零碎思考
前言
在使用C
的时候难免会碰到一些奇怪的用法或者令人困惑的语法等等,考虑到问题过于琐碎,就写于这个合集中,名为关于C
语言的零碎思考
typedef 和 define 的区别
这种关键字的使用常见于对于某种类型的替换,例如下面的场景:
1 |
|
两者的区别在于:define
是一种宏定义,本质上来说就是字符串替换,而typedef
是一种类型封装。
例如参考下面的代码,思考各个变量的类型:
1 |
|
其中变量a
的类型为int*
,而变量b
的类型为int
类型,不难理解宏定义只是字符串替换。
1 | typedef int* ElemType |
其中**变量a
和变量b
的类型均为int*
**,可以理解为typedef
将int*
封装成了一个新的类型。
typedef 和 struct 的使用
关于结构体的相关基础内容我已经在这篇文章中做过说明【9.0】C-结构体与共用体.
这里主要是梳理当typedef
和struct
关键词连用时的逻辑关系,代码示例:
1 | struct{ |
如果我们使用上述代码来定义结构体,那么**Test1
表示的被定义的变量**,而不是数据类型。
上述代码意味着:你将不可能在其他位置声明和Test1
相同类型的变量,除非你继续在结构体声明的时候再添加其他变量。同样的,如果使用如下代码:
1 | struct Test |
此处的Test1
是表示类型为Test
的结构体变量,此代码与上述代码不同之处在于你可以通过使用struct Test
关键词来在其他地方继续声明和Test1
变量相同类型的其他变量。
但是如果对struct
关键词配合typedef
关键词的话,其逻辑结构就不同了,代码示例:
1 | typedef struct{ |
在使用typedef
关键词后,如上代码中的Test1
就不再是变量了,而是结构体类型名称,如果你使用Test1.a
则编译器会报错。你可以通过Test1 变量名称
来定义相同结构体类型的变量。
在了解如上代码的逻辑后,现在来判断如下代码中的Test1
,Test2
,Test
是代表的什么?
1 | typedef struct Test{ |
答案是三者都表示同一种结构体类型,只是这种结构体类型的不同名称变体,其并非变量。不过不同的是,对于Test1
和Test2
两者是相同的,与Test
不同,如下是三者使用时声明的代码示例:
1 | int main(){ |
到这里你就会明白了,其实上面的代码,是下面代码的缩写:
1 | //这是上面的代码 |
Bool函数的使用问题
如果你使用GCC
编译器,将其连接到Visual Code
编辑器中使用,编译C
语言文件。如果文件中使用了bool
关键字,则会报错,需要引用#include <stdbool.h>
来使用bool
关键字。
如果你使用Visual Studio
安装的C/C++
环境,使用bool
关键词,通用需要引用#include <stdbool.h>
,不过在引用后,IDE 依旧会报错,需要再引用#define _CRT_SECURE_NO_WARNINGS
即可使用。
取余运算/取模运算的算法规则
取模运算也就是取余运算,它的作用之一是可以将无线的集合,通过取余来映射到有限的集合里。
C
语言中取余运算符为:%
,其作用于两个整型数(正负皆可),运算结果是返回两数的余数(即返回值为整数),它遵循如下规定:(示例a%b
)
- 运算结果的正负和被除数(
a
)符号一致 - 被除数(
a
)小于除数(b
)时,运算结果等于被除数(a
)
单引号和双引号引发的问题
如果你学习过C#
,JAVA
或者Python
等现代高级语言的时候,你或许对于字符串和字符来说,都是通常来使用双引号(""
)来表示,例如:"这是一个字符串"
,这是一个"c"
字符)。
这种写法在高级语言中是合法的,但是在C语言中,这种写法是不规范的,也是不完全合法的,例如下面的代码:
1 | //声明一个char类型的数组str,声明其包含字符a,b,c |
请思考如上写法是否合法?
很不幸,这种写法并不合法,我们的编译器会给我们报错,如下所示:
那么这是为什么?一共就三个字符a,b,c
。
这是因为C
语言在处理字符的时候,对于双引号("这是双引号的内容"
)的内容来说,它会被优先认为是字符串,对于单引号('这是单引号内容'
)会被优先认为是字符。
那么问题来了,请思考如下写法编译器是否会报错?
1 | char c = "b"; |
答案:会但是不完全会,我们的编辑器或者 IDE 并不会直接报错,它不会和上面的数组示例一样,在未编译的时候就报错,这种写法只会在编译的时候报错,但是仅仅是警告,而不是错误,也就是说,这种写法并不会中断程序的运行。
在程序中,有句话叫做:“错误需要解决,警告可以不管,程序能跑就行”,但是这样真的可以让程序万无一失吗?
答案是并不能,我们在给字符c
赋值字符串b
的时候,编译器会将字符c
的值变为ASCII
码的第 40 号((
)字符,这也就意味着程序并不能如我们所说的那样,警告可以不用管。
C中各种类型的问题
所占内存
类型 | 字节 |
---|---|
int |
4字节(Byte) |
char |
1字节(Byte) |
float |
4字节(Byte) |
double |
8字节(Byte) |
long |
4字节(Byte) |
char类型
因为**char
类型占一个字节,也就是说换算成数值类型,其可以表示 $2^8$ 个数值,也就是 $0 \sim 255$** 。但是实际上考虑到正负号的问题,所以它可以表示的实际数值为 $-128 \sim 127$ 。
综上结果,我们是可以将int
类型存储在char
类型的变量中的,但是其大小只能被限制到如上的实际数值范围中。现在问题来了,思考如下代码:
1 | char c = 128; |
请问它是否合法?
答:赋值合法。但是为什么?
这需要从char
类型和int
类型所占用的内存空间说起,如上表格所示,char
类型在内存占用1 个字节,也就是8 个位,而int
类型占用4 个字节,也就是32 个位。计算机本质是二进制的数值,也就是说对于计算机来说本来就没有字符这一个说法,这些字符的说法都是源于ASCII
码的映射。
标准的ASCII
码是采用1 个字节(8 位)来表示字符,但是实际上最高位用来做数据的奇偶校验位,用来验证数据完整。也就是说,**ASCII
码实际能用的之后的后 7 位**,也就是会产生 $2^7=128$ 种情况。这 128 种情况对应标准ASCII
码中的字符数字控制符等。
奇偶检验的相关内容详情查看《计算机组成原理》
通过ASCII
码,我们建立了字符和数值的映射,这也就意味着字符本质还是数值。所以上述代码“合法”,但是它真的能用吗?请思考如下代码:
1 | char a = 128; |
它们的运行结果是什么?
答案:
- 字符A的十进制表示为:-128
- 字符A的字符表示为:€
为什么?明明给它赋值的 128 ,为什么他的数值结果确是 -128 ?关键是 -128 数值竟然还有对应的字符映射?
先解释第二个,之所以字符表示为€,是因为前面说到标准的ASCII
码表最高位用来做校验位,导致其容量只有 128 个映射。后来人将其最高位也算在数值映射内,也就是使用 8 位来映射字符,而形成了新的拓展ASCII
码表,其拓展内容如下:
十进制 | 八进制 | 十六进制 | 二进制 | 符号 | HTML 编号 | HTML 名称 | 描述 |
---|---|---|---|---|---|---|---|
128 | 200 | 80 | 10000000 | € | | € | 欧元符号 |
129 | 201 | 81 | 10000001 | ||||
130 | 202 | 82 | 10000010 | ‚ | | ‚ | 单个低9引号 |
131 | 203 | 83 | 10000011 | ƒ | | ƒ | 拉丁小写字母f |
132 | 204 | 84 | 10000100 | „ | | „ | 双低9引号 |
133 | 205 | 85 | 10000101 | … | … | 水平省略号 | |
134 | 206 | 86 | 10000110 | † | | † | 匕首 |
135 | 207 | 87 | 10000111 | ‡ | | ‡ | 双匕首 |
136 | 210 | 88 | 10001000 | ˆ | | ˆ | 修饰语字母抑扬音 |
137 | 211 | 89 | 10001001 | ‰ | | ‰ | 千分号 |
138 | 212 | 8A | 10001010 | Š | | Š | 拉丁大写字母S |
139 | 213 | 8B | 10001011 | ‹ | | ‹ | 单左角引号 |
140 | 214 | 8C | 10001100 | Œ | | Œ | 拉丁字母连字OE |
141 | 215 | 8D | 10001101 | ||||
142 | 216 | 8E | 10001110 | Ž | | 拉丁大写字母Z | |
143 | 217 | 8F | 10001111 | ||||
144 | 220 | 90 | 10010000 | ||||
145 | 221 | 91 | 10010001 | ‘ | | ‘ | 左单引号 |
146 | 222 | 92 | 10010010 | ’ | | ’ | 右单引号 |
147 | 223 | 93 | 10010011 | “ | | “ | 左双引号 |
148 | 224 | 94 | 10010100 | ” | | ” | 右双引号 |
149 | 225 | 95 | 10010101 | • | | • | 子弹 |
150 | 226 | 96 | 10010110 | – | | – | 破折号 |
151 | 227 | 97 | 10010111 | — | | — | 破折号 |
152 | 230 | 98 | 10011000 | ˜ | | ˜ | 小波浪号 |
153 | 231 | 99 | 10011001 | ™ | | ™ | 商标标志 |
154 | 232 | 9A | 10011010 | š | | š | 拉丁小写字母S |
155 | 233 | 9B | 10011011 | › | | › | 单个右指向角引号 |
156 | 234 | 9C | 10011100 | œ | | œ | 拉丁文小连字oe |
157 | 235 | 9D | 10011101 | ||||
158 | 236 | 9E | 10011110 | ž | | 拉丁小写字母z | |
159 | 237 | 9F | 10011111 | Ÿ | | Ÿ | 拉丁大写字母Y |
160 | 240 | A0 | 10100000 | 不间断空间 | |||
161 | 241 | A1 | 10100001 | ¡ | ¡ | ¡ | 倒感叹号 |
162 | 242 | A2 | 10100010 | ¢ | ¢ | ¢ | 分号 |
163 | 243 | A3 | 10100011 | £ | £ | £ | 英镑符号 |
164 | 244 | A4 | 10100100 | ¤ | ¤ | ¤ | 货币符号 |
165 | 245 | A5 | 10100101 | ¥ | ¥ | ¥ | 日元符号 |
166 | 246 | A6 | 10100110 | ¦ | ¦ | ¦ | 管道,竖线损坏 |
167 | 247 | A7 | 10100111 | § | § | § | 分区标志 |
168 | 250 | A8 | 10101000 | ¨ | ¨ | ¨ | 间隔透析-umlaut |
169 | 251 | A9 | 10101001 | © | © | © | 版权标志 |
170 | 252 | AA | 10101010 | ª | ª | ª | 女性顺序指示器 |
171 | 253 | AB | 10101011 | « | « | « | 左双角引号 |
172 | 254 | AC | 10101100 | ¬ | ¬ | ¬ | 不签名 |
173 | 255 | AD | 10101101 | | | | 软连字符 |
174 | 256 | AE | 10101110 | ® | ® | ® | 注册商标标志 |
175 | 257 | AF | 10101111 | ¯ | ¯ | ¯ | 间隔宏-上划线 |
176 | 260 | B0 | 10110000 | ° | ° | ° | 学位标志 |
177 | 261 | B1 | 10110001 | ± | ± | ± | 正负号 |
178 | 262 | B2 | 10110010 | ² | ² | ² | 上标二平方 |
179 | 263 | B3 | 10110011 | ³ | ³ | ³ | 上标三方 |
180 | 264 | B4 | 10110100 | ´ | ´ | ´ | 急性口音-间隔锐 |
181 | 265 | B5 | 10110101 | µ | µ | µ | 微标志 |
182 | 266 | B6 | 10110110 | ¶ | ¶ | ¶ | 稻草人标志-段落标志 |
183 | 267 | B7 | 10110111 | · | · | · | 中间点-格鲁吉亚逗号 |
184 | 270 | B8 | 10111000 | ¸ | ¸ | ¸ | 间距塞迪利亚 |
185 | 271 | B9 | 10111001 | ¹ | ¹ | ¹ | 上标一 |
186 | 272 | BA | 10111010 | º | º | º | 男性顺序指示器 |
187 | 273 | BB | 10111011 | » | » | » | 右双角引号 |
188 | 274 | BC | 10111100 | ¼ | ¼ | ¼ | 分数的四分之一 |
189 | 275 | BD | 10111101 | ½ | ½ | ½ | 分数的一半 |
190 | 276 | BE | 10111110 | ¾ | ¾ | ¾ | 分数四分之三 |
191 | 277 | BF | 10111111 | ¿ | ¿ | ¿ | 倒问号 |
192 | 300 | C0 | 11000000 | À | À | À | 拉丁大写字母A |
193 | 301 | C1 | 11000001 | Á | Á | Á | 拉丁大写字母A |
194 | 302 | C2 | 11000010 | Â | Â | Â | 拉丁大写字母A |
195 | 303 | C3 | 11000011 | Ã | Ã | Ã | 拉丁大写字母A |
196 | 304 | C4 | 11000100 | Ä | Ä | Ä | 拉丁大写字母A |
197 | 305 | C5 | 11000101 | Å | Å | Å | 拉丁大写字母A |
198 | 306 | C6 | 11000110 | Æ | Æ | Æ | 拉丁大写字母AE |
199 | 307 | C7 | 11000111 | Ç | Ç | Ç | 拉丁大写字母C |
200 | 310 | C8 | 11001000 | È | È | È | 拉丁大写字母E |
201 | 311 | C9 | 11001001 | É | É | É | 拉丁大写字母E |
202 | 312 | CA | 11001010 | Ê | Ê | Ê | 拉丁大写字母E |
203 | 313 | CB | 11001011 | Ë | Ë | Ë | 拉丁大写字母E |
204 | 314 | CC | 11001100 | Ì | Ì | Ì | 拉丁大写字母I |
205 | 315 | CD | 11001101 | Í | Í | Í | 拉丁大写字母I |
206 | 316 | CE | 11001110 | Î | Î | Î | 拉丁大写字母I |
207 | 317 | CF | 11001111 | Ï | Ï | Ï | 拉丁大写字母I |
208 | 320 | D0 | 11010000 | Ð | Ð | Ð | 拉丁大写字母ETH |
209 | 321 | D1 | 11010001 | Ñ | Ñ | Ñ | 拉丁大写字母N |
210 | 322 | D2 | 11010010 | Ò | Ò | Ò | 拉丁大写字母O |
211 | 323 | D3 | 11010011 | Ó | Ó | Ó | 拉丁大写字母O |
212 | 324 | D4 | 11010100 | Ô | Ô | Ô | 拉丁大写字母O |
213 | 325 | D5 | 11010101 | Õ | Õ | Õ | 拉丁大写字母O |
214 | 326 | D6 | 11010110 | Ö | Ö | Ö | 拉丁大写字母O |
215 | 327 | D7 | 11010111 | × | × | × | 乘法 |
216 | 330 | D8 | 11011000 | Ø | Ø | Ø | 拉丁大写字母O |
217 | 331 | D9 | 11011001 | Ù | Ù | Ù | 拉丁大写字母U |
218 | 332 | DA | 11011010 | Ú | Ú | Ú | 拉丁大写字母U |
219 | 333 | DB | 11011011 | Û | Û | Û | 拉丁大写字母U |
220 | 334 | DC | 11011100 | Ü | Ü | Ü | 拉丁大写字母U |
221 | 335 | DD | 11011101 | Ý | Ý | Ý | 拉丁大写字母Y |
222 | 336 | DE | 11011110 | Þ | Þ | Þ | 拉丁大写字母THORN |
223 | 337 | DF | 11011111 | ß | ß | ß | 拉丁小写字母sharp s - ess-zed |
224 | 340 | E0 | 11100000 | à | à | à | 拉丁小写字母a |
225 | 341 | E1 | 11100001 | á | á | á | 拉丁小写字母a |
226 | 342 | E2 | 11100010 | â | â | â | 拉丁小写字母a |
227 | 343 | E3 | 11100011 | ã | ã | ã | 拉丁小写字母a |
228 | 344 | E4 | 11100100 | ä | ä | ä | 拉丁小写字母a |
229 | 345 | E5 | 11100101 | å | å | å | 拉丁小写字母a |
230 | 346 | E6 | 11100110 | æ | æ | æ | 拉丁小写字母a |
231 | 347 | E7 | 11100111 | ç | ç | ç | 拉丁小写字母c |
232 | 350 | E8 | 11101000 | è | è | è | 拉丁小写字母e |
233 | 351 | E9 | 11101001 | é | é | é | 拉丁小写字母e |
234 | 352 | EA | 11101010 | ê | ê | ê | 拉丁小写字母e |
235 | 353 | EB | 11101011 | ë | ë | ë | 拉丁小写字母e |
236 | 354 | EC | 11101100 | ì | ì | ì | 拉丁小写字母i |
237 | 355 | ED | 11101101 | í | í | í | 拉丁小写字母i |
238 | 356 | EE | 11101110 | î | î | î | 拉丁小写字母i |
239 | 357 | EF | 11101111 | ï | ï | ï | 拉丁小写字母i |
240 | 360 | F0 | 11110000 | ð | ð | ð | 拉丁小写字母eth |
241 | 361 | F1 | 11110001 | ñ | ñ | ñ | 拉丁小写字母n |
242 | 362 | F2 | 11110010 | ò | ò | ò | 拉丁小写字母o |
243 | 363 | F3 | 11110011 | ó | ó | ó | 拉丁小写字母o |
244 | 364 | F4 | 11110100 | ô | ô | ô | 拉丁小写字母o |
245 | 365 | F5 | 11110101 | õ | õ | õ | 拉丁小写字母o |
246 | 366 | F6 | 11110110 | ö | ö | ö | 拉丁小写字母o |
247 | 367 | F7 | 11110111 | ÷ | ÷ | ÷ | 除号 |
248 | 370 | F8 | 11111000 | ø | ø | ø | 拉丁小写字母o |
249 | 371 | F9 | 11111001 | ù | ù | ù | 拉丁小写字母u |
250 | 372 | FA | 11111010 | ú | ú | ú | 拉丁小写字母u |
251 | 373 | FB | 11111011 | û | û | û | 拉丁小写字母u |
252 | 374 | FC | 11111100 | ü | ü | ü | 拉丁小写字母u |
253 | 375 | FD | 11111101 | ý | ý | ý | 拉丁小写字母y |
254 | 376 | FE | 11111110 | þ | þ | þ | 拉丁小写字母thorn |
255 | 377 | FF | 11111111 | ÿ | ÿ | ÿ | 拉丁小写字母y |
你可以很清楚的发现,表中的对应关系,**十进制的 128 对应的字符为 €
**,这也就是为什么第二个输出为字符€
。
那为什么第一个采用十进制输出就变成了 -128 了呢?
这就不得不提到数值是如何在计算机中存储的了,我在博客没搬家之前写过一篇《计算机的原码,反码,补码》的内容,不过搬到Hexo
后感觉质量不是很好,遂没有腾过来。
计算机原码,反码,补码的详细内容可以参考(知乎)计算机补码运算背后的数学原理是什么?,知识出处《计算机组成原理》
我先说结论,数值在计算机中采用补码的形式存储,前面提到字符其实是数值十进制的映射,而十进制和二进制又有一层映射,所以本质来说,所有字符数值都是和二进制(当前情况是 8 位)的映射。
我们在代码中(char a = 128
)给变量a
赋值 128 (十进制),其二进制原码为 1000 0000
,很不幸的是,计算机采用 8 位,存储整型数值的时候,最高位用来表示符号位,也就是说计算机用 8 位表示数值的时候只能表示 $-128 \sim 127$ 这个范围。对于 128 的二进制源码其实它溢出到最高位了(最高位是符号位,只有 $2^7 =128$ 个数值了),127 的二进制原码为0111 1111
,将它 $+1$ 也就是 128 ,它最后数值溢出到了最高位符号位上了,计算机会将它认为是负数,会按照 原码 -> 反码 -> 补码 的过程将其转换成补码存储。
当计算机需要读取它的十进制数值的时候,会再将 补码 -> 反码 -> 原码 -> 十进制呈现在屏幕上。这样就是为什么最后再输出十进制的时候会变成 -128 了。根据上述原理,下述代码同样的就是 -127 (十进制数值)。
1 | char a = 129; |
现在查看如下代码,请判断其是否合法?如果合法其输出结果是什么?
1 | char a = '33'; |
答:“合法但是有问题”,上述代码会根据编译器的不同而不同,但是最多报个警报,并不会终端程序的运行,代码运行结果如下:
- 字符A的十进制表示为:51
- 字符A的字符表示为:3
看着这个输出结果,更加迷惑了,明明赋值的 33 输出一个 51 一个给我 3 .
原因:在上面的单引号和双引号的部分我说过,单引号(''
)表示字符,也就是说,你赋值的'33'
意味着你告诉计算机我要赋值给这个变量a
一个字符33
,计算机说:好的,字符'33'
赋值给…..嗯?我这ASCII
码表里可没有你说的字符'33'
,怎么办?它在这里做了一个模运算,伪代码应该是这样数值%10
,这里的模运算的结果是将无限的整型数值映射到了ASCII
码表里有的 $0 \sim 9$ 字符。
我们示例中的'33'
经过模运算就是字符'3'
,查询ASCII
码表可知,字符'3'
对应的十进制整型数值为 51 。
类型转换带来的未定义行为
关于未定义行为(Undefined behavior)的解释如下:
In computer programming, undefined behavior (UB) is the result of executing a program whose behavior is prescribed to be unpredictable, in the language specification to which the computer code adheres. This is different from unspecified behavior, for which the language specification does not prescribe a result, and implementation-defined behavior that defers to the documentation of another component of the platform (such as the ABI or the translator documentation).
In the C community, undefined behavior may be humorously referred to as “nasal demons“, after a comp.std.c post that explained undefined behavior as allowing the compiler to do anything it chooses, even “to make demons fly out of your nose”.
简述来说,未定义行为并不是错误,更多的来说是因为C
的标准定义并没有对此进行详细的标准定义,以至于其对于不同的编译器可能会存在不同的处理方式,最终出现对于不同的编译器来说,运行结果不同的结果。
现在来尝试观察如下代码,分析其输出结果:
1 | int main() { |
上述代码运行结果:我不好说,因为它已经进入了未定义行为了。
如果你了解隐式类型转换的话,对于上述运行的来说变量a
是float
类型, 和int
类型的变量b
进行加和的时候编译器会将b
转换成float
类型再参与运算,你发现了吗?其运算结果是float
类型,也就意味着在printf()
函数中使用%d
输出float
值就会出现未定义行为了。
现在对上述代码进行微小改动,来参考如下代码,思考其运行结果:
1 | int main() { |
运行结果:test:1
你会发现**对于c=a+b
来说,会将a+b
其值的float
类型转换为int
**,虽然会丢失一定的精度,但是不至于因为%d
而出现未定义行为。
现在来陈胜追击,查看一段奇怪的代码,思考其运行结果:
1 |
|
代码出自:【Stack Overfl0w】 Why are the int and float passed in printf going to the wrong positions in the format string?]
根据一开始的代码可以得知,这段代码也会因为%f
,%d
而出现未定义行为,其提问者的运行结果如下:
1 | i=43.289200 x=10 |
其中一个高赞回答:
What you’re doing invokes undefined behavior1, but looking at the resulting assembly using GCC on a platform with the System V AMD64 ABI we might formulate a hypothesis. The floating-point value is passed in the xmm0
register (an SSE register), while the integer is passed in the esi
register (a general register). Presumably, your printf
implementation expects floating-point numbers to be passed in SSE registers and integers to be passed in general registers, and simply picks the xmm0
register to read from when it encounters the first %f
(and vice versa).
简述来说,就是如上代码的操作会引发未定义行为,在具有System V AMD64 ABI
的平台上使用 GCC 查看其汇编,推出的假设是:浮点值在寄存器(SSE 寄存器)中传递,而整数在寄存器(通用寄存器)中传递,因为调用关系问题,导致寄存器读取取反了。
回答作者:You
C函数问题
关于 Main 函数的有趣问题
如果你使用Visual Studio
集成的 C/C++ 编译器,在编写main()
函数的时候将其写为mian()
,编译器并不会保mian()
的错误,反而是报错如下所示:
Scanf函数读取问题
Scanf
作为C语言入门级别函数,功能上来说学习过C语言的人都理解,现在来思考一下下面一段代码:
1 | int main() { |
现在如果读取字符的时候,输入一个 A (或者任意一个单字符),猜一下输出结果是什么?会是输入的字符为:A
吗?又或者是其他结果?
很不幸,它的运行结果如下所示:
这就奇了个怪了,scanf
函数不应该不读取回车字符吗?
有了上面的运行结果,现在来看看下面的代码,猜一下运行结果是什么?
1 | int main() { |
现在输入 2 (或者任意一个单数值),然后回车确认,猜猜运行结果是什么?运行结果如下:
发现了,scanf
对于%c
即字符的处理和其他的处理是不一样的,我猜测是因为字符采用的是ASCII
码表存储的,因为ASCII
存在各种控制字符,包括“回车”和“空格”等控制字符,事实上对于一般除字符格式的字符都会在scanf
前将空格等控制字符删除,而字符缺不会做这个处理,也就出现了上面的字符读取了回车字符的情况。
关于上述scanf
读取回车字符的解决方案如下:
1 | scanf(" %c",&value); //在%c前面加个空格就可以读取输入字符的时候避免回车和空格 |
当然你也可以选择使用其他的读取输入的函数,例如:sscanf
等等来解决上述问题。
题外话:对于读取输入函数scanf
来说,其实它会将键盘的输入读取到缓存区,你可以把它理解为队列,读取的字符压入队列,先入先出,对于每次输入结束都会在其末尾加上回车符(编译器自动),但是对于字符来说,编译器并没有给我的缓存区加上回车符,而是我们输入的时候把回车符输入了,由于字符的特殊性,导致读取字符的时候编译器并没有给我们删除前面的控制字符,使得我们读取到了回车字符。
因为缓存区的存在,所以对于上述输入来说,例如:我们希望输入ABCD
四个字符,其实可以不需要A
,回车,B
,回车,这样输入,其实我们可以直接在第一次输入就输入ABCD
,程序会依次将第一次输入存储到缓存区,然后挨个读取。
返回值数组
C
不允许函数返回一个数组,但是可以通过返回一个类型指针来代替(顺序表)。
关于数组指针,需要注意的是它是线性的。
数组名本身其实是一块地址的首地址,创建一个数组例如int a[10]
类型的数组,编译器会根据我们传入的数组包含的数组个数(10),来开辟一块大小为 $10 \times 4byte$ 大小空间,并将空间的首地址返回给所其的数组名称a
,所以本质上来说,数组名其实是一块首地址的指针;至于我们根据数组下标的访问数组,其实是根据首地址的加和计算出来的。所以参考如下代码:
1 | int main() { |
其运行结果:
对于二维及以上的数组来说,如果采用数组指针的方式要获取其值,不可以采用常规的a[n][n]...
来获取值,其本质原因是因为数组本身就是线性的,二维数组本质是上还是线性的存储,参考如下代码:
1 | int main() { |
如果你运行上述代码,会发现它是无法运行的,原因就是其本质是线性存储的,如果你希望正常读取二维数组的内容,可以修改成如下:
1 | int main() { |
上述代码也印证了数组名是个地址,而且二维数组是线性存储的,其运行结果如下: