环境:win10 + VS2019
目录
1 scanf函数理解
可以简单的认为系统I/O调用是unbuffered I/O,而C语言标准库I/O函数(即stdio函数)是buffered I/O
标准I/O提供了三种类型的stdio缓冲:
全缓冲(fully buffered):在这种缓冲模式下,只有在填满stdio缓冲区后才会进行实际的I/O操作(即调用read()或者write()系统调用),也就是说单次读、写数据的大小与stdio缓冲区大小相同。通常打开的文件流是全缓冲的(文件位于磁盘上,而磁盘是块设备)。
行缓冲(line buffered):在这种缓冲模式下,当在输入和输出流遇到换行符时,标准I/O库执行I/O操作(即调用read()或者write()系统调用)。通常情况下,stdin和stdout都是涉及的键盘显示器这些字符设备,所以是行缓冲的。
无缓冲(unbuffered):这种缓冲模式很好理解了,就是不存在stdio缓冲区,每次I/O操作就直接调用read()或者write()系统调用。stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个换行符。
1.1 VS中scanf函数报错
使用scanf函数会报错
error C4996: ‘scanf’: This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details
报错原因:
这种微软的警告,主要因为那些C库的函数,很多函数内部是不进行参数检测的(包括越界类的),微软担心使用这些会造成内存异常,所以就改写了同样功能的函数,改写了的函数进行了参数的检测,使用这些新的函数会更安全和便捷。关于这些改写的函数你不用专门去记忆,因为编译器对于每个函数在给出警告时,都会告诉你相应的安全函数,查看警告信息就可以获知,在使用时也再查看一下MSDN详细了解
解决方法之一:在引用头文件前使用以下宏定义
#define _CRT_SECURE_NO_WARNINGS
1.2 scanf函数介绍
1.2.1 简介
与printf函数一样,都被声明在头文件stdio.h里
- 函数声明:int scanf( format string , arg1 , arg2 , …);
- 函数返回:成功格式化解析的个数
- 调用格式:scanf("<格式化字符串>", <参量表>);
由指示读取动作的格式化字符串( format string )和相应的地址参数 arg1…argn 组成,scanf 函数从标准输入缓冲区 stdin 中读入,并将它们以格式化字符串中指定的格式存储到额外的参数 arg1…arg2 等指定的内存空间中
1.2.2 转换说明
转换说明符 | 意义 |
---|---|
%c | 把输入解释称一个字符 |
%d | 把输入解释称一个有符号十进制整数 |
%e,%f,%g,%a | 把输入解释称一个浮点数(%a是c99标准) |
%E,%F,%G,%A | 把输入解释称一个浮点数(%A是c99标准) |
%i | 把输入解释称一个有符号十进制整数 |
%o | 把输入解释称一个有符号八进制整数 |
%p | 把输入解释称一个指针(一个地址) |
%s | 把输入解释称一个字符串,输入内容以第一个非空白字符作为开始,并且包含直到下一个空白字符的全部字符 |
%u | 把输入解释称一个无符号十进制整数 |
%x,%X | 把输入解释称一个无符号十六进制整数 |
[] | 字符集合 |
1.2.3 读取缓冲区数据
以 % 开头的用于指定输入数据格式的字符为例。如 %d 指定需要读取一个整形,%s 需要读取一个字符串
scanf 等函数首先根据格式说明符尝试去解析 stdin 中的数据,如对于 %d ,scanf 会尝试对 stdin 中已有数据以整型的格式进行解析。若解析成功,则将上述解析结果存放到指定的内存中,若解析失败,如 stdin 中仅存在一个字符 ‘a’,scanf 会退出并返回
但是上述不匹配的数据并不会从缓冲区中清除,后续的 scanf 调用仍从上述输入开始读取
scanf("%s,%d",&a,&b);
//scanf需先读取一个字符串,再读取一个 ',' ,最后读取一个整数
scanf("%d\t%d",&a,&b);
//scanf需先读取一个整数,再将格式化字符串中的 '\t' (空白字符)与缓冲区中0个或多个空白字符匹配并清除,最后读取一个整数
scanf("%d%d",&a,&b); //scanf需要先读取一个整数,之后再读取一个整数,两个整数之间的空白字符会被忽略
2 动态数组实现
C语言是不能直接定义动态数组的,数组必须在初始化时确定长度
如果要在程序运行时才确定数组的长度,就需要在运行的时候,自己去向系统申请一块内存用动态内存分配实现动态数组(申请内存在堆区)
2.1 堆和栈
① 栈一般是存放什么数据的呢?
一般来讲,栈主要是为局部变量(一般是定义在函数里面)、函数参数分配内存大小,但是当他们离开这个"本职岗位"范围之后,就会被操作系统强行给咔嚓掉,最终被释放了出来,归还了给操作系统
这就好比,你去饭店吃饭,你吃饭的时候非常舒服(使用内存),但是当你发现你没钱支付饭钱的时候,搞不好你会被别人强行毒打一顿,然后又给"归还了"出去
② 栈的特点:
- 运行时自动分配和自动回收性:栈是自动管理的,程序员不需要手工干预。方便简单
- 反复使用性:栈内存在程序中其实就是那一块空间,程序反复使用这一块空间
- 遗留性:栈内存由于反复使用,每次使用后程序不会去清理,因此在使用栈时还是上次栈中遗留下的数值
- 临时性:函数不能返回栈变量的指针,因为这个空间是临时的
- 溢出性:因为操作系统事先给定了栈的大小,如果在函数中无穷尽的分配栈内存总能用完
③ 堆的作用:
对于堆来讲,它是由我们程序员来自由分配内存大小的,不过你在给一个指针变量分配内存大小的时候,在主程序return 0 语句之前记得要给它释放,否则会出现不好的影响 —— 内存泄漏
在c语言中,我们经常使用malloc来分配内存大小,而使用free函数释放之前分配的内存大小
④ 关于堆的申请:
malloc返回的是一个void *类型的指针,实质上malloc返回的是堆管理器分配给我本次申请的那段内存空间的首地址(malloc返回的值其实是一个数字,这个数字表示一个内存地址)
void类型不表示没有类型,而表示万能类型。void的意思就是说这个数据的类型当前是不确定的,在需要的时候可以再去指定它的具体类型
void *类型是一个指针类型,这个指针本身占4个字节,但是指针指向的类型是不确定的,换句话说这个指针在需要的时候可以被强制转化成其他任何一种确定类型的指针,也就是说这个指针可以指向任何类型的元素
- malloc的返回值:成功申请空间后返回这个内存空间的指针,申请失败时返回NULL。所以malloc获取的内存指针使用前一定要先检验是否为NULL
- free( p);会告诉堆管理器这段内存我用完了你可以回收了。堆管理器回收了这段内存后这段内存当前进程就不应该再使用了
2.2 动态内存分配函数
2.2.1 malloc()函数
void *malloc(unsigned int size)
分配size个字节的内存空间,返回地址的指针,如果内存不够分,就返回空指针NULL
注意:返回的指针是没有类型的,所以要使用得强制类型转换
如果在子函数中申请堆区内存,在子函数调用结束后,编译器不会理会堆区中的内容,也就是说,子函数调用结束,堆区申请的内存不会自动释放,其生命周期为整个程序运行期间,如不手动释放,则会产生垃圾,内存泄漏等问题