精华内容
下载资源
问答
  • C语言输入输出流和缓冲区的深入理解

    千次阅读 多人点赞 2018-08-28 07:47:00
    C语言输入输出流和缓冲区的深入理解 2015年10月06日 10:08:17 阅读数:15202更多 个人分类: C语言之路 导读:对C语言输入输出流和缓冲区的深入理解,C语言缓冲区(缓存)详解,缓冲区又称为缓存,这些存储...

    对C语言输入输出流和缓冲区的深入理解

    2015年10月06日 10:08:17 阅读数:15202更多

    个人分类: C语言之路

    导读:对C语言输入输出流和缓冲区的深入理解,C语言缓冲区(缓存)详解,缓冲区又称为缓存,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区,为什么要引入缓冲区,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区

    对C语言输入输出流和缓冲区的深入理解

    对C语言输入输出流和缓冲区的深入理解

    C语言缓冲区(缓存)详解

    缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。

    缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

    为什么要引入缓冲区

    比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

    又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。

    现在您基本明白了吧,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

    缓冲区的类型

    缓冲区 分为三种类型:全缓冲、行缓冲和不带缓冲。

    1) 全缓冲

    在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。

    2) 行缓冲

    在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入(stdin)和标准输出(stdout)。

    3) 不带缓冲

    也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。

    ANSI C( C89 )要求缓存具有下列特征:

    ?当且仅当标准输入和标准输出并不涉及交互设备时,它们才是全缓存的。

    ?标准出错决不会是全缓存的。

    但是,这并没有告诉我们如果标准输入和输出涉及交互作用设备时,它们是不带缓存的还是行缓存的,以及标准输出是不带缓存的,还是行缓存的。

    大部分系统默认使用下列类型的缓存:

    ?标准出错是不带缓存的。

    ?如果是涉及终端设备的流,则它们是行缓存的;否则是全缓存的。

    我们经常要用到标准输入输出流,而ANSI C对stdin、stdout和stderr的缓存特征没有强行的规定,以至于不同的系统可能有不同的stdin、stdout和stderr的缓存特征。目前主要的缓存特征是:stdin和stdout是行缓存;而stderr是无缓存的。?

    缓冲区的大小

    如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲

     

    区的大小通常是512个字节的大小。

    缓冲区大小由 stdio.h 头文件中的宏 BUFSIZ 定义,如果希望查看它的大小,包含头文件,直接输出它的值即可:

     

    1.printf("%d", BUFSIZ);

     

    缓冲区的大小是可以改变的,也可以将文件关联到自定义的缓冲区,详情可以查看 setvbuf()?和 setbuf() 函数。

    缓冲区的刷新(清空)

    下列情况会引发缓冲区的刷新:

    ?缓冲区满时;

    ?行缓冲区遇到回车时;

    ?关闭文件;

    ?使用特定函数刷新缓冲区。

    深入理解缓冲区请查看:结合缓冲区谈谈C语言getchar()、getche()、getch()的区别

    结合缓冲区谈谈C语言getchar()、getche()、getch()的区别

    本文将用到C语言缓冲区的概念,如果您不了解缓冲区,请查看:C语言缓冲(缓存)

    三个函数的对比

    -- 缓冲区 头文件 回显

    getchar() 有缓冲区 stdio.h 有回显

    getch() 无缓冲区 conio.h 无回显

    getche() 无缓冲区 conio.h 有回显

    getchar()函数

    先来看一下getchar(),其原型为:

    ? ? int getchar(void);

    当程序调用getchar()函数时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中,直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar()函数才开始从键盘缓冲区中每次读入一个字符。也就是说,后续的getchar()函数调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完后,才重新等待用户按键。

    通俗一点说,当程序调用getchar()函数时,程序就等着用户按键,并等用户按下回车键返回。期间按下的字符存放在缓冲区,第一个字符作为函数返回值。继续调用getchar()函数,将不再等用户按键,而是返回您刚才输入的第2个字符;继续调用,返回第3个字符,直到缓冲区中的字符读完后,才等待用户按键。

    下边的一个实例,会让你有深刻的体会:

     

    1.#include <stdio.h>

    2.int main()

    3.{

    4.char c;

    5.//第一次调用getchar()函数

    6.//程序执行时,您可以输入一串字符并按下回车键,按下回车键后该函数才返回

    7.c=getchar();

    8.//显示getchar()函数的返回值

    9.printf("%c\n",c);

    10.//暂停

    11.system("PAUSE");

    12.while((c=getchar())!='\n')

    13.{

    14.printf("%c",c);

    15.}

    16.//暂停

    17.system("PAUSE");

    18.return 0;

    19.}

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    这段小代码很简单,并且在代码内部都有注释。

    getchar()函数的执行就是采用了行缓冲。第一次调用getchar()函数,会让程序使用者(用户)输入一行字符并直至按下回车键 函数才返回。此时用户输入的字符和回车符都存放在行缓冲区。

    再次调用getchar()函数,会逐步输出行缓冲区的内容。

    请再看下面一个例子:

     

    1.#include <stdio.h>

    2.int main()

    3.{

    4.char ch1;

    5.char ch2;

    6.

    7.ch1 = getchar

     

    ();

    8.ch2 = getchar();

    9.printf("%d %d", ch1, ch2);

    10.return 0;

    11.}

     

     

     

     

     

     

     

     

     

     

    程序的本意很简单,就是从键盘读入两个字符,然后打印出这两个字符的ASCII码值。可是执行程序后会发现出了问题:当从键盘输入一个字符后,就打印出了结果,根本就没有输入第二个字符程序就结束了。例如用户输入字符’a', 打印结果是97,10。这是为什么呢?

    getchar()函数是从输入流缓冲区中读取数据的,而不是从键盘(终端)缓冲区读取。当读取遇到回车(\n)结束时,这个'\n'会一起读入到输入流缓冲区的,所以第一次接收输入时取走字符后会留下字符\n,这样第二次getchar()直接从缓冲区中把\n取走了,显然读取成功了,所以不会再从终端读取!其实这里的10恰好是回车符!这就是为什么这个程序只执行了一次输入操作就结束的原因!

    getch()和getche()函数

    在TC2.0时代,C程序员总是喜欢在程序末尾加上getch(),来实现程序运行完了暂停不退出的效果。如果不这样做,在TC2.0的环境中Ctrl+F9编译并运行后会立即退出程序,根本来不及看到结果。这时如果要看结果,就要按Alt+F5回到DOS环境中去,很麻烦。而如果在程序的结尾加上一行getch();语句,就可以省掉回DOS看结果这个步骤,因为程序运行完了并不退出,而是在程序最后把屏幕停住了,按任意键才退出程序。

     

    实际上,getch()的作用是从键盘接收一个字符,且不带回显。就是说,你按了一个键后它并不在屏幕上显示你按的什么,而继续运行后面的代码,所以在C语言中可以用它来实现“按任意键继续”的效果,即程序中遇到getch();语句,就会停下来,等你按任意键,它接收了这个字符键后再继续执行后面的代码。

    getche()和getch()很相似,它也需要引入头文件conio.h,它们之间的区别就在于:getch()无回显,getche()有回显。请看下面的例子:

     

    1.#include<stdio.h>

    2.#include<conio.h>

    3.void main()

    4.{

    5.char ch;

    6.int i;

    7.for(i=0;i<5;i++)

    8.{

    9.ch=getch();

    10.printf("%c",ch);

    11.}

    12.}

     

     

     

     

     

     

     

     

     

     

     

     

    首先这是个连续5次的循环来实现5次停顿,等待你输入。编译并运行这个程序,假设输入的是abcde,那么屏幕上显示的结果也是abcde,这个abcde并不是在ch=getch();中输出的。把printf("%c",ch);这行语句去掉,就会发现按5次任意键程序就结束了,但屏幕上什么都没有显示。

    你可以把代码中的getch()换成getche()看看有什么不同。如果还是输入abcde,那么屏幕上显示的结果是aabbccddee,我们把printf("%c",ch);这行语句再去掉,显示的结果就是abcde了,说明程序在执行ch=getche();这条语句的时候就把我们输入的键返回显示在屏幕上,有无回显就是它们的唯一区别。

    请大家

     

    再看下面一个例子:

     

    1.#include<stdio.h>

    2.#include<conio.h>

    3.void main()

    4.{

    5.char ch='*';

    6.while(ch=='*')

    7.{

    8.printf("\n按 * 继续循环,按其他键退出!");

    9.ch=getch();

    10.}

    11.printf("\n退出程序!");

    12.}

     

     

     

     

     

     

     

     

     

     

     

     

    你可以在这个循环体中添加你想要的功能,程序中按*继续循环,其他任意键退出,而且利用getch()无回显的特性,不管你按什么键,都不会在屏幕上留下痕迹,使你的界面达到美观效果。

    还有getchar是很值得研究的:getchar()是stdio.h中的库函数,它的作用是从stdin流(标准输入流)中读入一个字符,也就是说,如果stdin有数据的话不用输入它就可以直接读取了。而getch()和getche()是conio.h中的库函数,它的作用是从键盘接收字符。

    与前面两个函数的区别在于: getchar()函数等待输入直到按回车才结束(前提是缓冲区没有数据),回车前的所有输入字符都会逐个显示在屏幕上。但只有第一个字符作为函数的返回值。

     

    1.#include<stdio.h>

    2.#include<conio.h>

    3.void main()

    4.{

    5.char c;

    6.// 从键盘读入字符直到回车结束

    7.//getchar()在这里它只返回你输入字符串的第一个字符,并把返回值赋值给c

    8.c=getchar();

    9.// 显示输入的第一个字符

    10.putchar(c);

    11.}

     

     

     

     

     

     

     

     

     

     

     

    看到这个程序,相信你肯定会有疑问。这个就是从缓冲区中读取字符的例子。第一次getchar()时,确实需要人工的输入,但是如果你输了多个字符,以后的getchar()再执行时就会直接从缓冲区中读取了。

     

    1.#include<stdio.h>

    2.#include<conio.h>

    3.void main()

    4.{

    5.char c;

    6.// 每个getchar()依次读入一个字符

    7.while ((c=getchar())!='\n')

    8.printf("%c",c); // 按照原样输出

    9.}

     

     

     

     

     

     

     

     

     

    程序运行后,首先停下来,等待输入一个字符串,输入完毕后,它会把你输入的整个字符串都输出来了。

    这是为什么?getchar()不是只返回第一个字符么,这里为什么全部输出了?

    因为我们输入的字符串并不是取了第一个字符就把剩下的字符串丢掉了,它还在我们的缓冲区中,就好像开闸放水,你把水放到闸里去以后,开一次闸就放掉一点,开一次就放掉一点,直到放光了为止,这里开闸动作就相当于调用一次getchar()。我们输入的字符串也是这么一回事,首先我们输入的字符串是放在内存的缓冲区中的,我们调用一次getchar()就把缓冲区中里出口最近的一个字符输出,也就是最前面的一个字符输出,输出后,就把它释放掉了,但后面还有字符串,所以我们就用循环把最前面的一个字符一个个的在内存中释放掉,直到不满足循环条件退出为止。

    例子中循环条件里的'\n'实际上就是你输入字符串后的回车符,所以意思就是说,直到遇到回

     

    车符才结束循环,而getchar()函数就是等待输入(或缓冲区中的数据)直到按回车才结束,所以实现了整个字符串的输出。当然,我们也可以把循环条件改一下,比如while ((c=getchar())!='a'),就是遇到字符'a'就停止循环,当然意思是如果你输入“12345a213123/n”那么只会输出到a,结果是12345a。

    请注意:用getchar()是到标准输入流中读取数据,所以第一个getchar()接受的是刚刚中断的流队列中即将出列的第一个字符(不限于回车符,上面举过例子了),如果流队列不为空,执行getchar()就继续放水,直到把回车符也放空为止,空了之后再在执行getchar()就停下等待你的输入了。

    那么为什么getch()每次都是等待用户的输入呢?因为getch()是从键盘接收,即时的接收,并不是从stdin流(标准输入流)中去读取数据。

    C语言FILE结构体以及缓冲区深入探讨

    在C语言中,用一个指针变量指向一个文件,这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。

    定义文件指针的一般形式为:

    ??? FILE? *fp;

    这里的FILE,实际上是在stdio.h中定义的一个结构体,该结构体中含有文件名、文件状态和文件当前位置等信息。我们通过fopen返回一个文件指针(指向FILE结构体的指针)来进行文件操作。

    注意:FILE是文件缓冲区的结构,fp也是指向文件缓冲区的指针。

    不同编译器 stdio.h 头文件中对 FILE 的定义略有差异,这里以标准C举例说明:

     

    1.#define NULL 0

    2.#define EOF (-1)

    3.#define BUFSIZ 1024

    4.#define OPEN_MAX 20 // 一次打开的最大文件数

    5.

    6.// 定义FILE结构体

    7.typedef struct _iobuf {

    8.int cnt; // 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取

    9.char *ptr; // 下一个要被读取的字符的地址

    10.char *base; // 缓冲区基地址

    11.int flag; // 读写状态标志位

    12.int fd; // 文件描述符

    13.} FILE;

    14.

    15.extern FILE _iob[OPEN_MAX];

    16.

    17.#define stdin (&_iob[0]) // stdin 的文件描述符为0

    18.#define stdout (&_iob[1]) // stdout 的文件描述符为1

    19.#define stderr (&_iob[2]) // stdout 的文件描述符为2

    20.

    21.enum _flags {

    22._READ =01, // 读文件

    23._WRITE =02, // 写文件

    24._UNBUF =04, // 无缓冲

    25._EOF = 010, // 文件结尾EOF

    26._ERR = 020 // 出错

    27.};

    28.int _fillbuf(FILE *); // 函数声明,填充缓冲区

    29.int _flushbuf(int, FILE *); // 函数声明,刷新缓冲区

    30.

    #define feof(p) ((p)->flag & _EOF) != 0)

    31.#define ferror(p) ((p)->flag & _ERR) != 0)

    32.#define fileno(p) ((p)->fd)

    33.#define getc(p) (--(p)->cnt >= 0 \

    34.? (unsigned char) *(p)->ptr++ : _fillbuf(p))

    35.#define putc(x,p) (--(p)->cnt >= 0 \

    36.? *(p)->ptr++ = (x) : _flushbuf((x),p))

    37.#define getchar() getc(stdin)

    38.#define putcher(x

     

    ) putc ((x), stdout)

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    看吧,我们经常使用的 NULL、EOF、feof()、getc() 等都是 stdio.h 中定义的宏。

    注意:一个长的 #define 语句可以用反斜杠(\)分成多行。

    下面说一下如果控制缓冲区。

    我们知道,当我们从键盘输入数据的时候,数据并不是直接被我们得到,而是放在了缓冲区中,然后我们从缓冲区中得到我们想要的数据 。如果我们通过setbuf()或setvbuf()函数将缓冲区设置10个字节的大小,而我们从键盘输入了20个字节大小的数据,这样我们输入的前10个数据会放在缓冲区中,因为我们设置的缓冲区的大小只能够装下10个字节大小的数据,装不下20个字节大小的数据。那么剩下的那10个字节大小的数据怎么办呢?暂时放在了输入流中。请看下图:

     

    上面的箭头表示的区域就相当是一个输入流,红色的地方相当于一个开关,这个开关可以控制往深绿色区域(标注的是缓冲区)里放进去的数据,输入20个字节的数据只往缓冲区中放进去了10个字节,剩下的10个字节的数据就被停留在了输入流里!等待下去往缓冲区中放入!接下来系统是如何来控制这个缓冲区呢?

    再说一下 FILE 结构体中几个相关成员的含义:

    ? ? cnt ?// 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取

    ? ? ptr ?// 下一个要被读取的字符的地址

    ? ? base ?// 缓冲区基地址

    在上面我们向缓冲区中放入了10个字节大小的数据,FILE结构体中的 cnt 变为了10 ,说明此时缓冲区中有10个字节大小的数据可以读,同时我们假设缓冲区的基地址也就是 base 是0x00428e60 ,它是不变的 ,而此时 ptr 的值也为0x00428e60 ,表示从0x00428e60这个位置开始读取数据,当我们从缓冲区中读取5个数据的时候,cnt 变为了5 ,表示缓冲区还有5个数据可以读,ptr 则变为了0x0042e865表示下次应该从这个位置开始读取缓冲区中的数据 ,如果接下来我们再读取5个数据的时候,cnt 则变为了0 ,表示缓冲区中已经没有任何数据了,ptr 变为了0x0042869表示下次应该从这个位置开始从缓冲区中读取数据,但是此时缓冲区中已经没有任何数据了,所以要将输入流中的剩下的那10个数据放进来,这样缓冲区中又有了10个数据,此时 cnt 变为了10 ,注意了刚才我们讲到 ptr 的值是0x00428e69 ,而当缓冲区中重新放进来数据的时候这个 ptr 的值变为了0x00428e60 ,这是因为当缓冲区中没有任何数据的时候要将 ptr 这个值进行一下刷新,使其指向缓冲区的基地址也就是0x0042e860这个值!因为下次要从这个位置开始读取数据!

    在这里有点需要说明:当我们从键盘输入字符串的时候需要敲一下回车键才能够将这个字符串送入到缓冲区中,这个换行符\n也会被存储在缓冲区中并且被当成一个字符来计算!比如我们在键盘上敲下,然后敲一下回车键(\r)将这个字符串送入了缓冲区中,那么此时缓冲区中的字节个数是7,缓冲区的刷新就是将指针ptr变为缓冲区的基地址,因为缓冲区刷新后里面是没有数据的!,C语言为指针动态分配内存,C语言程序员要严防内存泄漏,指针是C语言和其它语言的最大区别,也是很多人不能跨入

    对C语言输入输出流和缓冲区的深入理解

    回车键才能够将这个字符串送入到缓冲区中,那么敲入的这个回车键(\r)会被转换为一个换行符\n,这个换行符\n也会被存储在缓冲区中并且被当成一个字符来计算!比如我们在键盘上敲下了123456这个字符串,然后敲一下回车键(\r)将这个字符串送入了缓冲区中,那么此时缓冲区中的字节个数是7 ,而不是6。

    缓冲区的刷新就是将指针 ptr 变为缓冲区的基地址 ,同时 cnt 的值变为0 ,因为缓冲区刷新后里面是没有数据的!

    C语言为指针动态分配内存

    C语言程序员要严防内存泄漏,这个“内存泄漏”就是由动态内存分配引起的。指针是C语言和其它语言的最大区别,也是很多人不能跨入C语言的一道门槛。既然指针是这么一个“危险”的坏东西,干吗不取消它呢?

    其实指针本身并没有好坏,它只是一种操作地址的方法,学会了便可以发挥其它语言难以匹敌的功能,没学会的话,只能做其它语言的程序员,也同样发挥你的光和热。站长本人也在C语言门外徘徊多年,至今仍不属于高手。

    变量和数组可以通过指针来转换

    “int *x”中的x究竟是不是数组?光看这一句无法确定,因为它既可表示单个变量内容,也可表示数组。请理解下面的例子:

     

    1.#include <stdio.h>

    2.int main(void){

    3.int *num = NULL;

    4.int *x, y[] = {12, 22,32}, z = 100;

    5.

    6.//下面演示,指针既可充当变量、也可充当数组

    7.x=&z; //整型变量的地址赋给x

    8.printf("*x=%d, x[0]=%d\n", *x, x[0]);

    9.

    10.x = y; //数组的地址赋给x

    11.printf("*x=%d, x[ 0]=%d, x[ 1]=%d, x[2]=%d\n", *x, x[0], x[1], x[2]);

    12.

    13.x = y + 1; //数组的第二位地址赋给x

    14.printf("*x=%d, x[-1]=%d, x[ 0]=%d, x[1]=%d\n", *x, x[-1], x[0], x[1]);

    15.

    16.x = y + 2; //数组的第三位地址赋给x

    17.printf("*x=%d, x[-2]=%d, x[-1]=%d, x[0]=%d\n", *x, x[-2], x[-1], x[0]);

    18.

    19.return 0;

    20.}

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    运行结果:

    *x=100, x[0]=100

    *x=12, x[ 0]=12, x[ 1]=22, x[2]=32

    *x=22, x[-1]=12, x[ 0]=22, x[1]=32

    *x=32, x[-2]=12, x[-1]=22, x[0]=32

    动态分配内存

    前面讲到的指针,基本上将已经定义好的变量的地址赋给指针变量,现在要学的是向操作系统申请一块新的内存。申请到的内存,必须在某个地方手动释放,因此下面2个函数必须配对使用。malloc()和free(),都是标准函数,在stdlib.h中定义。

    根据不同的电脑使用状况,申请内存有可能失败,失败时返回NULL,因此,动态申请内存时一定要判断结果是否为空。malloc()的返回值类型是“void *”,因此,不要忘记类型转换。(许多人都省略了。)

     

    1.#include <stdio.h>

    2.#include <stdlib.h>

    3.#include <string.h>

    4.int main(void){

    5.char *p ;

    6.

    7.p = (char *)malloc(60 * sizeof(char)) ;

    8.if (p == NULL) { //这个

     

    个判断是必须的

    9.printf("内存分配出错!");

    10.exit(1);

    11.}

    12.strcpy(p, "http://see.xidian.edu.cn/cpp/u/jiaocheng/\n"); //不要忘记给新内存赋值

    13.printf("%s", p);

    14.

    15.free(p); //过河一定要拆桥

    16.p = NULL ; //释放后的指针置空,这是非常好的习惯,防止野指针。

    17.

    18.return 0;

    19.}

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    运行结果:

    http://see.xidian.edu.cn/cpp/u/jiaocheng/

    隐蔽的内存泄漏

    ?内存泄漏主要有以下几种情况:

    ?内存分配未成功,却使用了它。

    ?内存分配虽然成功,但是尚未初始化就引用它。

    ?内存分配成功并且已经初始化,但操作越过了内存的边界。

    ?忘记了释放内存,造成内存泄露。

    ?释放了内存却继续使用它。

    下面的程序造成内存泄漏,想想错在何处?如何修改?

     

    1.#include <stdio.h>

    2.#include <stdlib.h>

    3.int main(void){

    4.int *p, i;

    5.

    6.p = (int *)malloc(6 * sizeof(int)) ;

    7.if (p == NULL) { //判断是否为空

    8.printf("内存分配出错!");

    9.exit(1);

    10.}

    11.

    12.for (i=0; i<6; i++) {

    13.p++;

    14.*p = i;

    15.printf("%2d", *p);

    16.}

    17.printf("\n");

    18.

    19.free(p); //这句运行时出错

    20.

    21.return 0;

    22.}

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    对动态内存的错误观念

    有人对某一只在函数内使用的指针动态分配了内存,用完后不释放。其理由是:函数运行结束后,函数内的所有变量全部消亡。这是错误的。动态分配的内存是在“堆”里定义,并不随函数结束而消亡。

    有人对某动态分配了内存的指针,用完后直接设置为NULL。其理由是:已经为NULL了,这就释放了。这也是错误的。指针可以任意赋值,而内存并没有释放;相反,内存释放后,指针也并不为NULL。

    C语言模块化编程,C语言多文件编译

    C语言头文件深入理解

    C语言程序中,源文件通常分为两种:一种用于保存程序的声明(declaration),称为头文件;另一种用于保存程序的实现(implementation),称为定义(definition)文件。 C程序的头文件以“.h”为后缀,C 程序的定义文件以“.c”为后缀。

    可以将 .h 文件的内容写在 .c 文件中,也可以将 .c 文件的内容写在 .h 中,但这是很不好的习惯。许多初学者用了头文件,却不明其理。在此略作说明。

    在以下场景中会使用头文件:

    ?通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功 能,而不必关心接口怎么实现的。

    ?多文件编译。将稍大的项目分成几个文件实现,通过头文件将其他文件的函数声明引入到当前文件。

    ?头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不

     

    一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。

    编译时只有函数声明没有函数定义是完全正确的。函数声明告诉编译器该函数已经存在,但是入口地址还未确定,暂时在此做个标记,链接时编译器会找到函数入口地址,并将标记替换掉。

     

    编译产生的 .obj 文件(Linux下为 .o 文件)已经是二进制文件,与 .exe 的组织形式类似,只是有些函数的入口地址还未找到,程序不能执行。链接的作用就是找到函数入口地址,将所有的源文件组织成一个可以执行的二进制文件。

    关于头文件的内容,初学者还必须注意:

    ?头文件中可以和C程序一样引用其它头文件,可以写预处理块,但不要写具体的语句。

    ?可以申明函数,但不可以定义函数。

    ?可以申明常量,但不可以定义变量。

    ?可以“定义”一个宏函数。注意:宏函数很象函数,但却不是函数。其实还是一个申明。

    ?结构的定义、自定义数据类型一般也放在头文件中。

    ?#include <filename.h>,编译系统会到环境指定的目录去引用。#include "filename.h",系统一般首先在当前目录查找,然后再去环境指定目录查找。

    好的风格是成功的关键,版本申明、函数功能说明、注释等是C语言程序的一部分。不养成很好的习惯则不能成为C语言高手(专业人员)。

    C标准库中,每一个库函数都在一个头文件中声明,可以通过 #include 预处理命令导入。

    头文件只是声明,不占内存空间,编译时会被合并到源文件;要想知道它的具体实现,要看头文件所声明的函数是在哪个 .c 文件里定义的,然后查看源代码。

    C标准库共包含 15 个头文件,可以分为 3 组,如何正确并熟练的使用它们,可以相应的可区分出 3 个层次的程序员:

    ?合格程序员:<stdio.h>、<ctype.h>、<stdlib.h>、<string.h>

    ?熟练程序员:<assert.h>、<limits.h>、<stddef.h>、<time.h>

    ?优秀程序员:<float.h>、<math.h>、<error.h>、<locale.h>、<setjmp.h>、<signal.h>、<stdarg.h>

    各个头文件的具体内容请查看:C语言标准库

    C语言头文件具有以下几个特性:

    ?幂等性。可以多次包含相同的标准头文件,但效果与只包含一次相同。

    ?相互独立。任何标准头文件的正常工作都不需要以包含其他标准头文件为前提。也没有任何标准头文件包含了其他标准头文件。

    ?和文件级别的声明等同。必须先把某标准头文件包含到你的程序中, 然后才能使用该头文件已定义或声明的东西。不能在声明中包含标准头文件。并且,也不能在包含标准头文件之前用宏定义去代替关键字。

    等幂性是很容易实现的,对于大多数的头文件可以使用宏保护。例如,在 stdio.h 中可以有如下的

     

    宏定义:

     

    1.#ifndef _STDIO_H

    2.#define _STDIO_H

    3./* 主要实现部分 */

    4.#endif

     

     

     

     

    在C程序员中所达成的一个约定是:C源文件的开头部分要包含所有要用到的头文件。在 #include 指令之前只能有一句注释语句。引入的头文件可以按任意顺序排列。

    如果我们自己编写的头文件可能会用到标准头文件中的定义或者声明,最好把标准头文件包含在自定义头文件的开头。这样,就不会在程序中忘记引入该标准头文件,也不会有顺序问题。这正是利用了头文件的等幂性。

     

    注意一个约定,引入标准头文件用尖括号,引入自定义头文件用双引号,例如:

     

    1.#include <stdio.h>

    2.#include "myFile.h"

     

     

    C语言库函数是头文件的最佳实践,仔细阅读各个头文件的内容,尤其是 stdio.h,能够学到很多东西。

    在 VC6.0 中找到头文件

    C标准头文件,例如 stdio.h、string.h 等在 VC6.0 的安装目录中是可以找到的。我的 VC6.0 安装在?C:\Program Files\Microsoft Visual Studio\ 目录,那么 VC6.0 附带的所有头文件(包括但不限于标准头文件)都在?C:\Program Files\Microsoft Visual Studio\VC98\Include\ 目录下。

    如果忘记 VC6.0 的安装目录或者头文件不在安装目录下,可以通过以下方式找到:

    1) 在工具栏中点击“工具”按钮

    2) 在二级菜单中选择“选项”

    3) 在弹出的对话框中选择“目录”标签

    4) 然后选择名字为“目录”的下拉菜单中的“Include files”一项,如下图所示:

     

    第一个C语言多文件编译的例子:C语言多文件编程,10分钟快速上手

    这一节通过一个简单的例子,向大家展示如何有效地将各个文件联系在一起。

    在 VC6.0 中新建一个工程,添加 fun.c、main.c 两个源文件和 fun.h 一个头文件,内容如下:

    fun.c

     

    1.#include <stdio.h>

    2.int fun1(){

    3.printf("The first function!\n");

    4.return 0;

    5.}

    6.int fun2(){

    7.printf("The second function!\n");

    8.return 0;

    9.}

    10.int fun3(){

    11.printf("The third function!\n");

    12.return 0;

    13.}

     

     

     

     

     

     

     

     

     

     

     

     

     

    fun.h

     

    1.#ifndef _FUN_H

    2.#define _FUN_H

    3.

    4.extern int fun1(void);

    5.extern int fun2(void);

    6.extern int fun3(void);

    7.

    8.#endif

     

     

     

     

     

     

    main.c

     

    1.#include <stdio.h>

    2.#include <stdlib.h>

    3.#include "fun.h"

    4.

    5.int main(){

    6.fun1();

    7.fun2();

    8.fun3();

    9.

    10.system("pause");

    11.return 0;

    12.}

     

     

     

     

     

     

     

     

     

     

    对上面的每个 .c 文件都进行编译,然后链接并运行:

    The first function!

    The second function!

    The third function!

    上面的例子,函数定义放在 fun.c 文件中,在 fun.h 头文件中对函数进行声明,暴露接口,然后在主文件 main.c 中引入 fun.h。

    注意:编译是针对单个 .c 文件的,如果项目中有多个 .c 文件,需要逐一编译,然后链接,或者使用“组建 -> 全部重建”选项

     

    ,一次性编译并链接所有文件。

    多文件编程时,只能有一个文件包含 main() 函数,因为一个工程只能有一个入口函数。我们把包含 main() 函数的文件称为主文件。

    可以在其他 .c 文件中对函数进行定义,在 .h 中对函数进行声明,只要主文件包含进相应的头文件,就能使用这些函数。实际开发中,很少有简单到只有几十行代码的C语言项目,合理的组织代码和文件,是开发大中型项目的必备技能。

    为了更好的组织各个文件,一般情况下一个 .c 文件对应一个 .h 文件,并且文件名要相同,例如 fun.c 和 fun.h。如果 fun.c 使用到了 fun.h 的宏定义、类型定义等,还需要在 fun.c 中 #include "fun.c"。

    .c 文件主要包含各个函数的定义,.h 文件声明函数原型,向外暴露接口,供主文件调用。另也可以在 .h 中包含宏定义、类型定义。

    注意:.h 文件头文件中不能有可执行代码,也不能有变量定义,只能有宏、类型( typedef,struct,union,menu )定义和变量、函数的声明。

    这倒不是说在 .h 中定义变量或函数会有语法错误,实际上#icnlude机制很简单,就是把#include所包含的文件中的内容直接复制到#include所在的位置并替换#include语句。但是这样做不符合模块化编程的惯例,也不利于文件的组织,不利于二次开发,不利于团队协作。

    头文件要遵守幂等性原则,即可以多次包含相同的头文件,但效果与只包含一次相同。

    可以使用下面的宏防止一个头文件被重复包含。???????

    复制纯文本新窗口

    1.#ifndef MY_INCLUDE_H

    2.#define MY_INCLUDE_H

    3.//头文件内容

    4.#endif

     

     

     

     

    如果该头文件已被包含,那么会定义宏 MY_INCLUDE_H,再次包含时,就不会对头文件内容进行编译了。

    动态链接库(dll)简介

    DLL 是 Dynamic Link Library 的缩写,译为“动态链接库”。DLL也是一个被编译过的二进制程序,可以被其他程序调用,但与 exe 不同,DLL不能独立运行,必须由其他程序调用载入内存。

    DLL 中封装了很多函数,只要知道函数的入口地址,就可以被其他程序调用。

    Windows API中所有的函数都包含在DLL中,其中有3个最重要的DLL:

    ?Kemel32.dll:它包含那些用于管理内存、进程和线程的函数,例如CreateThread函数;

    ?User32.dll:它包含那些用于执行用户界面任务(如窗口的创建和消息的传送)的函数,例如 CreateWindow 函数;

    ?GDI32.dll:它包含那些用于画图和显示文本的函数。

    静态链接库和动态链接库

    1) 静态库

    函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下, 在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文

     

    件(.EXE文件)。当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。

    2) 动态库

    在使用动态库的时候,往往提供两个文件:一个引入库(.lib)文件和一个DLL (.dll) 文件。虽然引入库的后缀名也是“lib”,但是,动态库的引入库文件和静态库文件有着本质上的区别,对一个DLL来说,其引入库文件(.lib)包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。在使用动态库的情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不复制到可执行文件中,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。这时,在发布产品时,除了发布可执行文件以外,同时还要发布该程序将要调用的动态链接库。

    使用动态链接库的好处

    1) 可以采用多种编程语言来编写

    我们可以采用自己熟悉的开发语言编写DLL,然后由其他语言编写的可执行程序来调用这些DLL。例如,可以利用VB来编写程序的界面,然后调用利用VC++或Delphi编写的完成程序业务逻辑的DLL。

    2) 增强产品的功能

    在发布产品时,可以发布产品功能实现的动态链接库规范,让其他公司或个人遵照这个规范开发自己的DLL,以取代产品原有的DLL,让产品调用新的DLL,从而实现功能 的增强。在实际工作中,我们看到许多产品都提供了界面插件功能,允许用户动态地更换程序的界面,这就可以通过更换界面DLL来实现。

    3) 提供二次开发的平台

    在销售产品的同时,可以采用DLL的形式提供一个二次开发的平台,让用户可以利用该DLL调用其中实现的功能,编写符合自己业务需要的产品,从而实现二次开发。

    4) 简化项目管理

    在一个大型项目开发中,通常都是由多个项目小组同时开发,如果采用串行开发,则效率是非常低的。我们可以将项目细分,将不同功能交由各项目小组以多个DLL的方式实现,这样,各个项目小组就可以同时进行开发了。

    5) 可以节省磁盘空间和内存

    如果多个应用程序需要访问同样的功能,那么可以将该功能以DLL的形式提供,这样在机器上只需要存在一份该DLL文件就可以了,从而节省了磁盘空间。另外,如果多个应用程序使用同一个DLL,该DLL只需要放入内存一次,所有的应用程序就都可以共亨它了。这样,内存的使用将更加有效。

     

    我们知道,当进程被加载时,系统会为它分配内存,接着分析该可执行模块,找到该程序将要调用哪些DLL,然后系统搜索这些DLL,找到后就加载它们,并为它们分配内存空间。DLL的内存空间只有一份,如果有第二个程

    如果产品需要提供多语言版本,那么就可以使用DLL来支持多语言,可以为每种语言创建一个只支持这种语言的动态链接库,输入dumpbin-exportsdllDemo.dll命令,输出如下图所示:,在”对象/库模块(Object/librarymodules)“编辑框中输入dl,输出结果如下:,输出结果与上面相同,也需要加载该DLL,那么它们共享内存空间,相同的DLL不会再次加载。6)有助于资源的共

    对C语言输入输出流和缓冲区的深入理解

    也需要加载该DLL,那么它们共享内存空间,相同的DLL不会再次加载。

    6) 有助于资源的共享

    DLL可以包含对话框模板、字符串、图标和位图等多种资源,多个应用程序可以使用DLL来共享这些资源。在实际工作中,可以编写一个纯资源的动态链接库,供其他应用程序访问。

    7) 有助于实现应用程序的本地化

    如果产品需要提供多语言版本,那么就可以使用DLL来支持多语言。可以为每种语言创建一个只支持这种语言的动态链接库。

    第一个DLL程序:动态链接库DLL教程,30分钟快速上手

    DLL 程序的入口函数是 DllMain(),就像 DOS 程序的入口函数是 main()、Win32 程序的入口函数是 WinMain() 一样。前面我们一直在讲的就是DOS程序。

    DllMain() 函数的原型为:

     

    1.BOOL APIENTRY DllMain(

    2.HANDLE hModule,

    3.DWORD ul_reason_for_call,

    4.LPVOID lpReserved

    5.);

     

     

     

     

     

    其中:

    ?hModule 表示本DLL程序的句柄。

    ?ul_reason_for_call 表示DLL当前所处的状态,例如DLL_PROCESS_ATTACH表示DLL刚刚被加载到一个进程中,DLL_PROCESS_DETACH表示DLL刚刚从一个进程中卸载。

    ?lpReserved 表示一个保留参数,目前已经很少使用。

    一个简单的DLL程序并不比 "Hello World" 程序难,下面就开始介绍如何利用VC6.0创建DLL及其调用方式。

    首先利用VC6.0新建一个 Win32 Dynamic-Link Library 类型的工程,工程取名为 dllDemo,并选择“An empty Dll project"选项,即创建一个空的动态链接库工程。然后,为该工程添加 一个C源文件 main.c,并在其中编写完成加法运算和减法运算的函数,代码如下所示:

     

    1.#include <objbase.h>? // 也可以 #include <windows.h>

    2.#include <stdio.h>

    3.

    4._declspec(dllexport) int add(int a, int b){

    5.??? return a+b;

    6.}

    7._declspec(dllexport)int sub(int a, int b){

    8.??? return a-b;

    9.}

    10.

    11.BOOL APIENTRY DllMain(

    12.??? HANDLE hModule,

    13.??? DWORD? ul_reason_for_call,

    14.??? LPVOID lpReserved

    15.){

    16.??? if(ul_reason_for_call == DLL_PROCESS_ATTACH){

    17.??????? printf("Congratulations! DLL is loaded!");

    18.??? }

    19.}

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    然后利用Build命令生成dllDemo这一动态链接库程序。之后,在该工程的Debug目录下, 可以看到有一个dllDemo.dll文件,这就是生成的动态链接库文件。

    读者要记住,应用程序如果想要访问某个DLL中的函数,那么该函数必须是已经被导出的函数。为了导出一些函数,需要在函数前面添加标识符?_declspec(dllexport)。

    为了查看一个DLL中有哪些导出函数,可 以利用VC6.0提供的命令行工具Dumpbin来实现。

    Dumpbin.exe文件位于VC6.0安装目录下的VC98\bin目录下。在该目录下还有 一个批处理文件VCVARS32.bat,该文件的作用是用来建立VC6.0使用的环境信息。如果读者在其他目录下无法执行Dumpbin命令

     

    令,原因可能就是你的VC6.0安装的环境信息被破坏了,那么可以运行VCVARS32.bat这个批处理文件,之后在其他目录下,就可以 执行Dumpbin命令了。

    注意:当在命令行界面下执行VCVARS32.bat文件后,该文件所设置的环境信息只是在当前命令行窗口生效。如果关闭该窗口,并再次启动一个新的命令行窗口后,仍需要运行VCVARS32.bat文件。

    在命令行界面下,cd 到工程目录下的debug目录,输入dumpbin -exports dllDemo.dll 命令,然后回车,即可查看DLL中的导出函数,如下

     

    注意红色方框标出的信息:

    ordinal ? ?hint ? ? RVA ? ? ? ? ? ? ? ?name

    ? ? ? ? ? 1 ? ? ? ? 0 ? ? 00001005 ? ? ? ?add

    ? ? ? ? ? 2 ? ? ? ? 1 ? ? 0000100A ? ? ? ?sub

    在这段信息中,"ordinal" 列列出的信息 '1' 和 '2' 是导出函数的序号;"hint" 列列出的数字是提示码,该信息不重要;"RVA" 列列出的地址值是导出函数在DLL模块中的位置,也就是说,通过该地址值,可以在DLL中找到它们;最后一列 "name" 列出的是导出函数的名称。

    将 add 函数前面的 _declspec(dllexport) 标识符去掉,再次编译 dllDemo 工程,然后执行?dumpbin -exports dllDemo.dll 命令,输出如下图所示:

     

    可以看到,add 函数已经不是导出函数了。

    打开项目目录下的Debug目录,发现有 dllDemo.dll 和 dllDemo.lib 两个文件。上节已经说过,.lib 文件包含DLL导出的函数和变量的符号名,.dll 文件才包含实际的函数和数据。主程序调用 DLL 需要这两个文件,下节会讲解如何使用。

    注意:DllMain() 函数在DLL程序载入和卸载时执行,可以用来做一些初始化和清理的工作,如果仅仅是向外暴露函数,就可以省略 DllMain() 函数。但是如果有 DllMain() 函数,就一定要?#include <objbase.h>? 或 #include <windows.h>。

    例如,上面DLL如果只想暴露 add() 和 sub() 函数,而不想进行其他操作,那么可以这样写:

     

    1._declspec(dllexport) int add(int a, int b){

    2.return a+b;

    3.}

    4._declspec(dllexport)int sub(int a, int b){

    5.return a-b;

    6.}

    动态链接库DLL的加载:隐式加载(载入时加载)和显式加载(运行时加载)

    静态链接库在链接时,编译器会将 .obj 文件和 .LIB 文件组织成一个 .exe 文件,程序运行时,将全部数据加载到内存。

    如果程序体积较大,功能较为复杂,那么加载到内存中的时间就会比较长,最直接的一个例子就是双击打开一个软件,要很久才能看到界面。这是静态链接库的一个弊端。

    动态链接库有两种加载方式:隐式加载和显示加载。

    ?隐式加载又叫载入时加载,指在主程序载入内存时搜索DLL,并将DLL载入内存。隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受。

    ?显式加载又叫运行时加载,指主程序

     

    在运行过程中需要DLL中的函数时再加载。显式加载是将较大的程序分开加载的,程序运行时只需要将主程序载入内存,软件打开速度快,用户体验好。

    隐式加载

    首先创建一个工程,命名为 cDemo,添加源文件 main.c,内容如下:

     

    1.#include<stdio.h>

    2.

    3.extern int add(int, int); // 也可以是 _declspec(dllimport) int?add(int, int);

    4.extern int sub(int, int); // 也可以是 _declspec(dllimport) int?sub(int, int);

    5.

    6.int main(){

    7.int a=10, b=5;

    8.printf("a+b=%d\n", add(a, b));

    9.printf("a-b=%d\n", sub(a, b));

    10.return 0;

    11.}

     

     

     

     

     

     

     

     

     

    找到上节创建的 dllDemo 工程,将 debug 目录下的?dllDemo.lib 和?dllDemo.dll 复制到当前工程目录下。

     

    前面已经说过:.lib 文件包含DLL导出的函数和变量的符号名,只是用来为链接程序提供必要的信息,以便在链接时找到函数或变量的入口地址;.dll 文件才包含实际的函数和数据。所以首先需要将 dllDemo.lib 引入到当前项目。

    选择”工程(Project) -> 设置(Settings)“菜单,打开工程设置对话框,选择”链接(link)“选项卡,在”对象/库模块(Object/library modules)“编辑框中输入 dllDemo.lib,如下图所示:

     

     

    但是这样引入 .lib 文件有一个缺点,就是将源码提供给其他用户编译时,也必须手动引入 .lib 文件,麻烦而且容易出错,所以最好是在源码中引入 .lib 文件,如下所示:

    #pragma comment(lib, "dllDemo.lib")

    更改上面的代码:

     

    1.#include<stdio.h>

    2.#pragma comment(lib, "dllDemo.lib")

    3.

    4._declspec(dllimport) int add(int, int);

    5._declspec(dllimport) int sub(int, int);

    6.

    7.int main(){

    8.int a=10, b=5;

    9.printf("a+b=%d\n", add(a, b));

    10.printf("a-b=%d\n", sub(a, b));

    11.return 0;

    12.}

     

     

     

     

     

     

     

     

     

     

    点击确定回到项目,编译、链接并运行,输出结果如下:

    Congratulations! DLL is loaded!

    a+b=15

    a-b=5

    在 main.c 中除了用 extern 关键字声明 add() 和 sub() 函数来自外部文件,还可以用?_declspec(dllimport) 标识符声明函数来自动态链接库。

    为了更好的进行模块化设计,最好将 add() 和 sub() 函数的声明放在头文件中,整理后的代码如下:

    dllDemo.h

     

    1.#ifndef _DLLDEMO_H

    2.#define _DLLDEMO_H

    3.

    4.#pragma comment(lib, "dllDemo.lib")

    5._declspec(dllexport) int add(int, int);

    6._declspec(dllexport) int sub(int, int);

    7.

    8.#endif

     

     

     

     

     

     

    main.c

     

    1.#include<stdio.h>

    2.#include "dllDemo.h"

    3.

    4.int main(){

    5.int a=10, b=5;

    6.printf("a+b=%d\n", add(a, b));

    7.printf("a-b=%d\n", sub(a, b));

    8.return 0;

    9.}

     

     

     

     

     

     

     

     

    显式加载

    显式加载动态链接库时,需要用到 LoadLibrary() 函数,该函数的作用是将指定的可执行模块映射到调用进程的地址空间。LoadLibrary() 函数的原型声明如下所示:

    HMODULE ?LoadLibrary(LPCTSTR 1pFileName);

     

    LoadLibrary() 函数

     

    不仅能够加载DLL(.dll),还可以加载可执行模块(.exe)。一般来说,当加载可执行模块时,主要是为了访问该模块内的一些资源,例如位图资源或图标资源等。LoadLibrary() 函数有一个字符串类型(LPCTSTR)的参数,该参数指定了可执行模块的名称,既可以是一个.dll文件,也可以是一个.exe文件。如果调用成功, LoadLibrary() 函数将返回所加载的那个模块的句柄。该函数的返回类型是HMODULE。?HMODULE类型和HINSTANCE类型可以通用。

    当获取到动态链接库模块的句柄后,接下来就要想办法获取该动态链接库中导出函数的地址,这可以通过调用 GetProcAddress() 函数来实现。该函数用来获取DLL导出函数的 地址,其原型声明如下所示:

    FARPROC ?GetProcAddress(HMODULE hModule, LPCSTR 1pProcName);

    可以看到,GetProcAddress函数有两个参数,其含义分别如下所述:

    ?hModule:指定动态链接库模块的句柄,即 LoadLibrary() 函数的返回值。

    ?1pProcName:字符串指针,表示DLL中函数的名字。

    首先创建一个工程,命名为 cDemo,添加源文件 main.c,内容如下:

     

    1.#include<stdio.h>

    2.#include<stdlib.h>

    3.#include<windows.h> // 必须包含 windows.h

    4.

    5.typedef int (*FUNADDR)(); // 指向函数的指针

    6.

    7.int main(){

    8.int a=10, b=5;

    9.HINSTANCE dllDemo = LoadLibrary("dllDemo.dll");

    10.FUNADDR add, sub;

    11.if(dllDemo){

    12.add = (FUNADDR)GetProcAddress(dllDemo, "add");

    13.sub = (FUNADDR)GetProcAddress(dllDemo, "sub");

    14.}else{

    15.printf("Fail to load DLL!\n");

    16.system("pause");

    17.exit(1);

    18.}

    19.

    20.printf("a+b=%d\n", add(a, b));

    21.printf("a-b=%d\n", sub(a, b));

    22.

    23.system("pause");

    24.return 0;

    25.}

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    找到上节创建的 dllDemo 工程,将 debug 目录下的 dllDemo.dll 复制到当前工程目录下。注意,只需要 dllDemo.dll,不需要 dllDemo.lib。

    运行程序,输出结果与上面相同。

    HMODULE 类型、HINSTANCE 类型在 windows.h 中定义;LoadLibrary() 函数、GetProcAddress() 函数是Win32 API,也在 windows.h 中定义。

    通过以上的例子,我们可以看到,隐式加载和显式加载这两种加载DLL的方式各有 优点,如果采用动态加载方式,那么可以在需要时才加载DLL,而隐式链接方式实现起来比较简单,在编写程序代码时就可以把链接工作做好,在程序中可以随时调用DLL导出的函数。但是,如果程序需要访问十多个DLL,如果都采用隐式链接方式加载它们的话, 那么在该程序启动时,这些DLL都需要被加载到内存中,并映射到调用进程的地址空间, 这样将加大程序的启动时间。而且,一般来说,在程序运行过程中只是在某个条件满足时才需要访问某个DLL中的某个函数,其他情况下都不需要访问这些DLL中的函数。但是这时所有的DL

     

    L都已经被加载到内存中,资源浪费是比较严重的。在这种情况下,就可以采用显式加载的方式访问DLL,在需要时才加载所需的DLL,也就是说,在需要时DLL才会被加载到内存中,并被映射到调用进程的地址空间中。有一点需要说明的是,实际上, 采用隐式链接方式访问DLL时,在程序启动时也是通过调用LoadLibrary() 函数加载该进程需要的动态链接库的。

    展开全文
  • https://blog.csdn.net/weixin_41632560/article/details/82141591
    展开全文
  • 一般情况下,由键盘输入的字符并没有直接送入程序,而是被存储在一个缓冲区当中。下面这篇文章主要给大家介绍了关于C语言输入输出流与缓冲区的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
  • c语言输入输出

    千次阅读 2014-11-17 22:01:18
    http://blog.csdn.net/pipisorry/article/details/41219551C语言基本输入输出C语言字符串读取数据sscanfsscanf() - 从一个字符串中读进与指定格式相符的数据。swscanf()- 用于处理宽字符字符串,和sscanf功能相同...

    http://blog.csdn.net/pipisorry/article/details/41219551

    C语言基本输入输出

    标准输入scanf

     scanf("%s", a);

    C语言字符串读取数据sscanf

    sscanf() - 从一个字符串中读进与指定格式相符的数据。
    swscanf()- 用于处理宽字符字符串,和sscanf功能相同。

    c++用代码如何实现向输入缓冲区写入数据

    未解决

    C语言基本输出printf()

    printf函数的返回值

    http://www.360doc.com/content/11/1105/23/1317564_162120554.shtml

    关于printf()函数的返回值问题

    C语言字符串输出sprintf()

    将各种类型的数据构造成字符串
    sprintf 跟printf 在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出。
    sprintf 是个变参函数,定义如下:
      int sprintf( char *buffer, const char *format [, argument] ... );
      除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数格式化字符串上。

    [sprintf()的用法]

    皮皮blog



    非格式化输入输出puts()\gets()函数

    可以由上面讲述的标准格式化输入输出函数代替, 但这些函数编译后代码少, 相对占用内存也小, 从而提高了速度, 同时使用也比较方便。

    1. puts()函数

        puts()函数用来向标准输出设备(屏幕)写字符串并换行,

    调用格式为:    puts(s);

        其中s为字符串变量(字符串数组名或字符串指针)。

        puts()函数的作用与语printf("%s\n", s)相同。

    1. main()  {  
    2.   
    3.      char s[20], *f;   
    4.   
    5.      strcpy(s, "Hello world!"); /*字符串数组变量赋值*/  
    6.   
    7.      f="Thank you";                     /*字符串指针变量赋值*/  
    8.   
    9.      puts(s);  
    10.   
    11.      puts(f);  
    12.   
    13. }  

    说明:

        (1). puts()函数只能输出字符串, 不能输出数值或进行格式变换。

        (2). 可以将字符串直接写入puts()函数中。如:puts("Hello, Turbo C2.0");

    2. gets()函数

        gets()函数用来从标准输入设备(键盘)读取字符串直到回车结束, 但回车符不属于这个字符串。

    调用格式为:    gets(s);

        其中s为字符串变量(字符串数组名或字符串指针)。

        gets(s)函数与scanf("%s", &s)相似, 但不完全相同, 使用scanf("%s", &s)函数输入字符串时存在一个问题, 就是如果输入了空格会认为输入字符串结束,

    空格后的字符将作为下一个输入项处理, 但gets() 函数将接收输入的整个字符串直到回车为止。

    1.     main(){  
    2.   
    3.          char s[20], *f;  
    4.   
    5.          printf("input sth\n");  
    6.   
    7.          gets(s);                      /*等待输入字符串直到回车结束*/  
    8.   
    9.          puts(s);                      /*将输入的字符串输出*/  
    10.   
    11.          puts("input sth\n");  
    12.   
    13.          gets(f);  
    14.   
    15.          puts(f);  
    16.   

    [puts()和gets()函数 用法 ()]

    c语言中读入带空格的字符串

    问题:
     scanf("%s", a);
    运行输入hello world  
    回车
    则输入到a的只是空格之前的部分,怎样把空格之后的部分也输出?
    1. scanf( "%[^\n]", str );
    #include <stdio.h> int main(){ char str[50]; scanf( "%[^\n]", str ); printf( "%s\n", str ); return 0; }

    scanf中的正则表达式

    1、定制自己的扫描集 %[abc]、%[a-z]、%[^abc]、%[^a-z],比isdigit()、isalpha()更加灵活。[]内是匹配的字符,^表示求反集。
    int i;
    char str[80], str2[80];
    // scanf("%d%[abc]%s", &i, str, str2);  
    // printf("%d %s   %s\n",i,str,str2);
    // scanf("%[a-zA-Z0-9]", str);
    // scanf("%[^abce]", str);
    scanf("%[^a-z]", str);
    printf("%s\n",str);
    2、读入一个地址并显示内存地址的内容
    int main(void){
    char ch='c';
    printf("%p\n", &ch); // print the address of ch.
    char *p;
    cout<<"Enter an address: ";
    scanf("%p", &p);     //input the address displayed above
    printf("Value at location %p is %c\n",p,*p);
    return 0;
    }
    3、丢弃不想要的空白符:scanf("%c %c");
    4、控制字符串中的非空白符:导致scanf()读入并丢弃输入流中的一个匹配字符。"%d,%d";
    5、压缩输入:在格式码前加上*,则用户就可以告诉scanf()读这个域,但不把它赋予任何变量。
        scanf("%c%*c, &ch); 使用此方法可以在字符处理时吃掉多余的回车。


    2.scanf()
    int t[999];
    int sum=0;
    while(scanf("%c",&t[sum++])!=EOF);

    3.gets()\fgets()

    #include <stdio.h>  
    //char *fgets( char *str, int num, FILE *stream );
    int main(){
        char buffer[10];
        //fgets(buffer,10,stdin); //带有回车符
        gets(buffer);    //没有回车符
     
        printf("%s",buffer);
        return 0;
    }

    more:
    gets() 不检查字符串容量,有可能越界写数据,用户可不一定给你按套路出牌,可能输入长达300字节甚至2k字节的字符串,这样的话很危险
    虽然strlen,strcyp等等这些函数因为追求高效率也不会检查指针是否为空,指针是否可写,但是这些函数是C程序员来操作的,C程序员有正确使用函数的素质。
    但是gets() 是用户来输入,用户并不知道字符串上限,而且就算知道,也不一定有素质去按规定使用,要多多注意
    用fgets(str,80,stdin);就不危险了,fgets比较安全

    皮皮blog



    c语言文件读取和输入输出

    C语言文件读取fscanf()

    函数名: fscanf

    简述:C语言中基本的文件操作

    功 能: 从一个流中执行格式化输入,fscanf遇到空格和换行时结束,注意空格时也结束。这与fgets有区别,fgets遇到空格不结束。

    用法:

    1
    int fscanf(FILE*stream,constchar*format,[argument...]);
    FILE *stream:文件指针;
    char *format:格式字符串;
    [argument...]:输入列表。
    返回值为输入数据个数
    例如:
    1
    2
    3
    4
    5
    FILE*fp;
    chara[10];
    intb;
    doublec;
    fscanf(fp,"%s%d%lf",a,&b,&c)
    返回值:整型,成功读入的参数的个数

    格式字符说明
    常用基本参数对照:
           %d:读入一个十进制整数.
    %i :读入十进制,八进制,十六进制整数,与%d类似,但是在编译时通过数据前置或后置来区分进制,如加入“0x”则是十六进制,加入“0”则为八进制。例如串“031”使用%d时会被算作31,但是使用%i时会算作25.
    %u:读入一个无符号十进制整数.
    %f %F %g %G : 用来输入实数,可以用小数形式或指数形式输入.
    %x %X: 读入十六进制整数.
    %o': 读入八进制整数.
    %s : 读入一个字符串,遇空字符‘\0'结束。
    %c : 读入一个字符。无法读入空值。空格可以被读入。
    附加格式说明字符表修饰符说明
    L/l 长度修饰符 输入"长"数据
    h 长度修饰符 输入"短"数据
    示例说明
    如果要求从标准输入中输入一串字符串和一个整型数,那么参数“%s%d”表示什么呢?默认情况下,在终端上(这里假设程序为控制台应用程序)输入第一个参数的值的时候敲下回车,则在第二行输入的为第二个参数值,采用这种输入方法那么格式字符的形式就无关紧要了。
    这里要特殊说明的是如果参数在同一行给出,那么格式字符的参数与终端的输入会有什么关系。举个例子:如果格式字符为“%s+%d”,那么参数的输入就应该为 string + integer。

    http://baike.baidu.com/view/656694.htm

    http://blog.csdn.net/jhg1204/article/details/7932187
    c语言中所有文件操作函数详解:http://www.2cto.com/kf/201207/143344.html
    皮皮blog


    C语言控制台字体颜色设置

    一、设置字体颜色:
    1. 0 = 黑色       8 = 灰色
        1 = 蓝色       9 = 淡蓝色
        2 = 绿色       A = 淡绿色
        3 = 浅绿色     B = 淡浅绿色
        4 = 红色       C = 淡红色
        5 = 紫色       D = 淡紫色
        6 = 黄色       E = 淡黄色
        7 = 白色       F = 亮白色 
    #include <stdlib.h>
    system("color 2");system("color F");
    2.
    VC中要调用windows API来改变字体颜色。
    #include  <windows.h> 
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_GREEN);
    printf("Hello\n");
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED);
    printf("Hello\n");

    其中FOREGROUND颜色只有四种:GREEN,BLUE,RED,INTENSITY(加强)

    WinCon.h:
    #define FOREGROUND_BLUE      0x0001 // text color contains blue.
    #define FOREGROUND_GREEN     0x0002 // text color contains green.
    #define FOREGROUND_RED       0x0004 // text color contains red.
    #define FOREGROUND_INTENSITY 0x0008 // text color is intensified.
    #define BACKGROUND_BLUE      0x0010 // background color contains blue.
    #define BACKGROUND_GREEN     0x0020 // background color contains green.
    #define BACKGROUND_RED       0x0040 // background color contains red.
    #define BACKGROUND_INTENSITY 0x0080

    其他颜色都是基于红绿蓝三原色来调和而成:
    红色+绿色=黄色 
    绿色+蓝色=青色 
    红色+蓝色=品红 

    红色+绿色+蓝色=白色

    from:http://blog.csdn.net/pipisorry/article/details/41219551

    ref:cin、cin.get()、cin.getline()、getline()、gets()函数的用法


    展开全文
  • c语言输入输出函数

    千次阅读 多人点赞 2013-08-26 17:58:16
    上学年学习c语言的时候比较匆忙,没好好吸收。 现在有时间好好复习下。 本文就c语言常见输入...C语言输入输出函数  C/C++学习笔记1 - 深入了解scanf()/getchar()和gets()等函数 -。常见输入输出

    上学年学习c语言的时候比较匆忙,没好好吸收。

    现在有时间好好复习下。

    本文就c语言常见输入函数进行简单介绍,对比。

    ps:由于自己能力有限,时间有限,多数介绍,总结都是摘录网上相关学习资料,下面给出本文参考资料的原文链接。

    C语言的输入输出函数  

    -。常见输入输出函数简介。

    getchar()  //从键盘上输入一个字符常量,此常量就是该函数返回的值; 
    putchar() //把变量中的一个字符常量输出; 
    scanf()      //从键盘上输入各类数据,并存放到程序变量中; 
    printf()  //把键盘中的各类数据,加以格式控制输出; 
    gets()      //读入一行字符串常量并放到程序的数组中; 
    puts()      //把数组变量中的一个字符串常量输出,并且带有回车'\n'; 
    sscanf()    //从一个字符串中提取各类数据;  
    sprintf()  //将各类数据写入字符串中;

     

    一、printf()函数 
    printf()函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出 
    信息。在编写程序时经常会用到此函数。printf()函数的调用格式为: 
    printf(" <格式化字符串>", <参量表>); 
    其中格式化字符串包括两部分内容: 一部分是正常字符, 这些字符将按原 
    样输出; 另一部分是格式化规定字符, 以"%"开始, 后跟一个或几个规定字符, 
    用来确定输出内容格式。 
    参量表是需要输出的一系列参数, 其个数必须与格式化字符串所说明的输出 
    参数个数一样多, 各参数之间用","分开, 且顺序一一对应, 否则将会出现意想 
    不到的错误。 


    二、scanf()函数 
    scanf()函数是格式化输入函数, 它从标准输入设备(键盘) 读取输入的信息。 
    其调用格式为: 
    scanf(" <格式化字符串>", <地址表>); 
    格式化字符串包括以下三类不同的字符; 
    1. 格式化说明符: 格式化说明符与printf()函数中的格式说明符基本相同。 
    2. 空白字符: 空白字符会使scanf()函数在读操作中略去输入中的一个或多 
    个空白字符。 
    3. 非空白字符: 一个非空白字符会使scanf()函数在读入时剔除掉与这个非 
    空白字符相同的字符。 
    地址表是需要读入的所有变量的地址, 而不是变量本身。这与printf()函数 
    完全不同, 要特别注意。各个变量的地址之间同","分开。 


    三。puts()和gets()函数 
    1. puts()函数 
    puts()函数用来向标准输出设备(屏幕)写字符串并换行, 其调用格式为: 
    puts(s); 
    其中s为字符串变量(字符串数组名或字符串指针)。 
    puts()函数的作用与语printf("%s\n", s)相同。 
    2. gets()函数 
    gets()函数用来从标准输入设备(键盘)读取字符串直到回车结束, 但回车符 
    不属于这个字符串。其调用格式为: 
    gets(s); 
    其中s为字符串变量(字符串数组名或字符串指针)。 
    gets(s)函数与scanf("%s", &s)相似, 但不完全相同, 使用scanf("%s", &s) 
    函数输入字符串时存在一个问题, 就是如果输入了空格会认为输入字符串结束, 
    空格后的字符将作为下一个输入项处理, 但gets() 函数将接收输入的整个字符 
    串直到回车为止。



    二。 深入了解scanf()/getchar()和gets()等函数


    ---------------------------------------------------
    | 问题描述一:(分析scanf()和getchar()读取字符)   |
    ----------------------------------------------------

        scanf(), getchar()等都是标准输入函数,一般人都会觉得这几个函数非常简单,没什么特殊的。但是有时候却就是因为使用这些函数除了问题,却找不出其中的原因。下面先看一个很简单的程序:
    程序1:
        #include <stdio.h>
        int main()
        {
     char ch1, ch2;
     scanf("%c", &ch1); 
     scanf("%c", &ch2);
     printf("%d  %d/n", ch1, ch2);
     return 0;
        }
        或者是:
        #include <stdio.h>
        int main()
        {
     char ch1, ch2;
     ch1 = getchar();
     ch2 = getchar();
     printf("%d  %d/n", ch1, ch2);
     return 0;
        }
        程序的本意很简单,就是从键盘读入两个字符,然后打印出这两个字符的ASCII码值。可是执行程序后会发现除了问题:当从键盘输入一个字符后,就打印出了结果,根本就没有输入第二个字符程序就结束了。例如用户输入字符'a', 打印结果是97,10。这是为什么呢?
    【分析】:
        首先我们呢看一下输入操作的原理, 程序的输入都建有一个缓冲区,即输入缓冲区。一次输入过程是这样的,当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin函数直接从输入缓冲区中取数据。正因为cin函数是直接从缓冲区取数据的,所以有时候当缓冲区中有残留数据时,cin函数会直接取得这些残留数据而不会请求键盘输入,这就是例子中为什么会出现输入语句失效的原因!
        其实这里的10恰好是回车符!这是因为scanf()和getchar()函数是从输入流缓冲区中读取值的,而并非从键盘(也就是终端)缓冲区读取。而读取时遇到回车(/n)而结束的,这个/n会一起读入输入流缓冲区的,所以第一次接受输入时取走字符后会留下字符/n,这样第二次的读入函数直接从缓冲区中把/n取走了,显然读取成功了,所以不会再从终端读取!这就是为什么这个程序只执行了一次输入操作就结束的原因!

    ----------------------------------------------------
    |  问题描述二:(分析scanf()和gets()读取字符串)   |
    ----------------------------------------------------

    首先我们看一下scanf()读取字符串的问题:
    程序2:
        #include <stdio.h>
        int main()
        {
     char str1[20], str2[20];
     scanf("%s",str1); 
     printf("%s/n",str1);    
     scanf("%s",str2);  
     printf("%s/n",str2);  
     return 0;
        }
        程序的功能是读入一个字符串输出,在读入一个字符串输出。可我们会发现输入的字符串中不能出现空格,例如:
    测试一输入:
    Hello world!
    输出:
    Hello
    world!
    【分析】到此程序执行完毕,不会执行第二次的读取操作!这个问题的原因跟问题一类似,第一次输入Hello world!后,字符串Hello world!都会被读到输入缓冲区中,而scanf()函数取数据是遇到回车、空格、TAB就会停止,也就是第一个scanf()会取出"Hello",而"world!"还在缓冲区中,这样第二个scanf会直接取出这些数据,而不会等待从终端输入。

    测试二:
    Hello[Enter] 
    Hello[输出]
    world[Enter]
    world[输出]
    【分析】程序执行了两次从键盘读入字符串,说明第一次输入结束时的回车符被丢弃!即:scanf()读取字符串会舍弃最后的回车符!


    我们再看一下gets()读取字符串的情况:
    用scanf来读取一个字符串时,字符串中是不可以出现空格的,一旦出现空格,后面的数据就会舍弃残留在缓冲区中。其实有另外一个函数是可以接受空格的,那就是gets(),下面我们看一下这个函数的应用,我们把程序2改动一下:
    程序3:
    #include <stdio.h>
    int main()
    {
     char str1[20], str2[20];
     gets(str1); 
     printf("%s/n",str1);    
     gets(str2);  
     printf("%s/n",str2);  
     return 0;
    }
    测试:
    Hello world! [输入]
    Hello world! [输出]
    12345 [输入]
    12345 [输出]
    【分析】显然与上一个程序的执行情况不同,这次程序执行了两次从键盘的读入,而且第一个字符串取了Hello world! 接受了空格符,而没有像上一个程序那样分成了两个字符串!所以如果要读入一个带空格符的字符串时因该用gets(), 而不宜用scanf()!


    --------------------------------------------------------
    | 问题描述三:(getchar()暂停程序,查看程序执行结果)|
    --------------------------------------------------------
        不知道大家有没有遇到过这样的问题,有的编译器程序执行完后的结果界面不会停下而是一闪就没了,以至于看不到执行结果。所以很多人在程序最后加上getchar()语句,目的是想让程序执行完后停下来,等待从终端接收一个字符再结束程序。可是发现有时候这样根本没用,程序照样跳出去了。这是为什么呢?
    【分析】原因跟上面例子讲的一样,是因为输入缓冲区中还有数据,所以getchar()会成果读到数据,所以就跳出了!

     

    ------------------
    |     【总结】    |
    ------------------

    第一:要注意不同的函数是否接受空格符、是否舍弃最后的回车符的问题!
    读取字符时:
    scanf()以Space、Enter、Tab结束一次输入,不会舍弃最后的回车符(即回车符会残留在缓冲区中);
    getchar()以Enter结束输入,也不会舍弃最后的回车符;
    读取字符串时:
    scanf()以Space、Enter、Tab结束一次输入
    gets()以Enter结束输入(空格不结束),接受空格,会舍弃最后的回车符!

    第二:为了避免出现上述问题,必须要清空缓冲区的残留数据,可以用以下的方法解决:
    方法1:C语言里提供了函数清空缓冲区,只要在读数据之前先清空缓冲区就没问题了!
           这个函数是fflush(stdin)。
    方法2:自己取出缓冲区里的残留数据。
    (说实话这个语句我也没看懂,呵呵!为什么格式控制是这样的!希望高手指点一下!)
           scanf("%[^/n]",string);



    展开全文
  • C语言输入输出函数

    千次阅读 2016-10-06 11:30:32
    提供基本的文字的输入输出流操作(包括屏幕和文件等)。由于C语言并没有提供专用于文字输入输出的关键字,所以该库是最普遍的C语言程序加载库 (2)cstdio是将stdio.h的内容用C++头文件的形式表示出来,cstdio 和...
  • 一、C语言的main函数 常见形式: int main() { … return 0; } 实际上main函数应该为: int main(int argv, char* argv[]) { … return 0; } 二、标准I/O和error 前者由后者封装而成 printf(): fprintf(stdin,"...
  • C语言输入输出

    2017-03-02 16:34:36
      1983年,美国国家标准协会(ANSI)成立了一个委员会,目标是制定“一个无歧义性的且与具体机器无关的C语言定义”,二同时又要保持C语言原有的“精神”。结果产生了C语言的ANSI标准。   C语言最初是由Dennis ...
  • C语言中的输入输出流和缓冲区(重点)详解

    万次阅读 多人点赞 2019-01-01 13:21:56
    C语言中我们用到的最频繁的输入输出方式就是scanf()与printf()。 scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中。 printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。注意宽度输出和精度 ...
  • C语言常用读取输入输出的方法 一、控制台命令行输入输出 函数scanf()和printf() 函数 参数说明 返回值 int scanf(const char * restrict format,…) 字符串,参数(一个或多个) 读取的数据项(个)数,...
  • C语言输入输出总结

    千次阅读 2008-01-31 14:49:00
    C语言输入输出总结 C语言中基本的输入输出函数有:putchar ():把变量中的一个字符常量输出到显示器屏幕上;getchar ();从键盘上输入一个字符常量,此常量就是该函数的值;printf ();把键盘中的各类数据,加以格式控制...
  • 首先谈谈c语言和c++的输入输出的差别,C语言使用的scanf函数和printf函数,c++使用的是cin和cout。虽然cin和cout可以不指定输入输出格式,但是cin和cout消耗时间比scanf和printf多很多。因此在使用时,应该尽量使用...
  • 1、,1,第3章 标准输入与输出,3.1 概述,3.2 字符输入输出函数,3.3 格式化输入和输出函数,3.4 输入和输出函数的讨论,.,2,通过终端输入(如键盘、鼠标等),也称为标准输入(standard input),直接向终端输出(如显示器、...
  • C语言输入输出方法的区别和总结

    千次阅读 2019-04-30 22:01:12
    C语言有很多的输入输出方法,它们在C语言标准库stdio.h中总结下常用方法的区别 常用方法如下: 输入方法 输出方法 int scanf(const char *format, ...)从标准输入 stdin 读取格式化输入。 int printf(const ...
  • gets()函数:输入字符串,并且能输入空格,现在被弃用 scanf(“%s”,buff):无法输入空格,遇到空格就终止 fgets()函数:可以输入空格,遇到回车就终止 ...  size:从中读入n-1个字符   stre
  • C语言输入输出

    万次阅读 2016-11-23 22:53:51
    声明:本篇博客主要讲怎样使用C语言标准库里面的输入输出函数输入输出简单机制我们在使用输入输出函数的时候,不管是从文件还是控制台,它都是会先存放在缓冲区里面,但需要使用的时候才会在缓冲区里面提取。...
  • c语言和C++输入输出流的基本操作

    千次阅读 2014-08-03 17:40:51
    #include<iostream> #include<cstdio> using namespace std; int add(int a,int b) { return a+b; } int main() { int a,b; ... //C语言输入流控制,声明输入类型 ...这是输入输出流的简单控制
  • 第一节 简单的输入输出流 printf:输出函数 scanf:输入函数 %.m输出格式符:保留m位小数点(如%.2d,保留两位小数点) 代码如下: #include <stdio.h> int main() { int a;[添加链接描述]...
  • 输入的时候(因为scanf()输入的值最先也要传到缓冲区) , 将 缓冲区内容 传送给 屏幕 或 文件 称为 刷新缓冲区。 转载于:https://my.oschina.net/u/2423028/blog/483883
  • #include int main() { double a=0.123456789; int a0,a1,a2,a3,a4,a5; int _int=1; scanf("%1d%1d%1d%1d%1d%1d",&a0,&a1,&a2,&a3,&a4,&a5);... 输入流拆分,输入6位数,1每次读取一位,2两位 */ pri
  • 测试C语言文件输出输入重定向时return上面的system(“pause)失效 解决方法 将标准输入输出流定向回控制台即可 int main(void) { int ch; FILE *file = NULL; file = freopen("C:\\Users\\a\\Desktop\\111...
  • C语言输入输出函数之 fputs(...)

    万次阅读 2013-01-25 18:33:14
    C语言中fgets(...)从中读入输入,相反fputs(...)向文件写入数据。  对于ANSI C 程序,运行时系统会打开至少三个,这3个包括:  1. 标准输入 standard input . 标准定义为stdin.  2 标准输出 ...
  • c语言输入输出多个字符串

    万次阅读 2015-01-15 14:01:30
    #include #include int main() { char str[50][50]; char *ps[50];... scanf()函数接受输入以后,回车被保存在输入流中了, 你在scanf()后面加一个getchar()就好了。 这样会吃 掉多余的回车符,后面的g
  • C语言流输入输出函数

    千次阅读 2015-08-27 07:39:05
    printf(scanf)、puts(gets)和putchar(getchar)是分别向标准流输出(由标准读入)字符串、一行字符和单个字符的函数。除了这些函数,C语言也提供了面向任何输入输出函数。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 58,328
精华内容 23,331
关键字:

c语言输入输出流

c语言 订阅