第一章 导言
本栏中所有的程序都是基于64bit Windows10 OS, VS2013进行的代码编写和编译。
斜体: 代表书中摘录。
//
1.1 入门
1.1.0 一个最简单的main函数实例,打印“hello, world”。
#include <stdio.h>
int main()
{
printf("hello, world.\n");
return 0;
}
1.1.1 字符串分多行,添加续行符\
P3: “在printf函数的参数中,只能用’\n’表示换行符,如果用程序的换行来代替’\n’,编译器将会报错。”
下面来试验一下上面这句话是否会有报错:
测试程序(下面程序是错误的):
#include <stdio.h>
int main()
{
printf("hello, world.
");
return 0;
}
编译上面的程序,报错信息如下:
解决方案:
先清理第一个Error C2001,其实只要将printf语句写在一行里面就没有这个报错了。
至于第二个Error C2143,在第一个Error清理掉后,它也就消失了。
继续查找Microsoft 在线文档中对于Error C2001的详细释义。
A string constant cannot be continued on a second line unless you do the following:
1.End the first line with a backslash.
2.Close the string on the first line with a double quotation mark and open the string on the next line with another double quotation mark.
Ending the first line with \n is not sufficient.
大致意思就是说:一个字符串常量是不能被分割到多行的。但是,如果确实要分到多行里面的话,可以有两种方法:法一,在行尾添加一个续行符(\);法二,每一行都保证是一个完整的字符串常量。
而且,他们也给出了两个错误示例以及正确的coding方法:
// C2001.cpp 错误示例:
// C2001b.cpp 正确写法: 加续行符或者保证每行都是一个完整字符串
释义链接:Compiler Error C2001
1.1.2 调用printf函数换行,需添加转义字符\n
P3: “printf函数永远不会自动换行。”
1.1.3 转义字符的使用
P3:“转义字符只代表一个字符,它为表示无法输入的字符或不可见的字符提供了一种通用的可扩充机制。”
常用的转义字符:制表符\t,回退符\b, 双引号\", 反斜杠本身\\。
测试程序(练习1-2,测试\c在printf中会输出什么?)
#include<stdio.h>
int main()
{
printf("hello, \cworld.\n");
return 0;
}
编译上面程序,可以编译成功,但是会有warning。
输出结果:
解决方案:
虽然对程序运行不产生影响,但还是推荐使用正确的转义字符,以免输出错误字符。
查找Microsoft在线文档中对于warning C4129的释义:
The character following a backslash () in a character or string constant is not recognized as a valid escape sequence. The backslash is ignored and not printed. The character following the backslash is printed.
上面这段话的意思就是说,在单个字符或者字符串里出现的反斜线和该字符的组合被认为是一个无效的转义字符。反斜线会被忽略掉,不去打印输出,紧跟反斜线后边的该字符会被打印出来。
To print a single backslash, specify a double backslash (\).
The C++ standard, in section 2.13.2 discusses escape sequences.
同时文档中也给出了引发该warning的测试用例,如下
The following sample generates C4129:释义链接:Warning C4129
1.1.4 知识拓展
fprintf函数打印信息到屏幕(这样的用法在《C++沉思录》中有介绍)
一般来说,fprintf函数主要是用于将信息打印输出到文件中的,但是下面的代码就可以将信息打印到屏幕。
#include<stdio.h>
int main()
{
fprintf(stdout, "hello, world.\n");
return 0;
}
printf, printf_s, fprintf,fprintf_s函数之间的区别与联系
这四个函数的声明都在文件:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\stdio.h
printf, printf_s函数:
函数声明(文件路径:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\stdio.h):
_Check_return_opt_ _CRTIMP int __cdecl printf(_In_z_ _Printf_format_string_ const char * _Format, ...);
_Check_return_opt_ _CRTIMP int __cdecl printf_s(_In_z_ _Printf_format_string_ const char * _Format, ...);
函数实现(文件路径:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\printf.c)
int __cdecl printf ( const char *format, ... ){//具体实现去文件中查看}
int __cdecl printf_s ( const char *format, ... ){//具体实现去文件中查看}
fprintf,fprintf_s函数:
函数声明(文件路径:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\stdio.h):
_Check_return_opt_ _CRTIMP int
__cdeclfprintf(_Inout_ FILE * _File, _In_z_ _Printf_format_string_ const char * _Format, ...);
_Check_return_opt_ _CRTIMP int __cdecl fprintf_s(_Inout_ FILE * _File, _In_z_ _Printf_format_string_ const char * _Format, ...);
函数定义(文件路径:C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\fprintf.c)
int __cdecl fprintf ( FILE *str, const char *format, ... ) {//具体实现去文件中查看}
int __cdecl fprintf_s ( FILE *str, const char *format, ... ) {//具体实现去文件中查看}
FAQ:
Q1:_Check_return_opt_
是个什么东西?
Q2: _CRTIMP
是个什么东西?
Q3: __cdecl
如何理解?
Q4: _Inout_
是个什么东西?
Q5: FILE *
到底是个什么东西?为何stdout
可以直接赋值给它?
Ans: 在头文件stdio.h中定义了FILE 和 stdout
, FILE
就是结构体_iobuf
的一个别名,而stdout
是
_iobuf
的一个具体实例的地址。
#ifndef _FILE_DEFINED
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif /* _FILE_DEFINED */
先说一下函数__iob_func()
的作用:
函数声明以及_IOB_ENTRIES
的宏定义(都在stdio.h中)
#define _IOB_ENTRIES 20
_CRTIMP FILE * __cdecl __iob_func(void);
函数实现以及_iob
的定义(都在文件C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src_file.c中)
FILE _iob[_IOB_ENTRIES] = {
/* _ptr, _cnt, _base, _flag, _file, _charbuf, _bufsiz */
/* stdin (_iob[0]) */
{ _bufin, 0, _bufin, _IOREAD | _IOYOURBUF, 0, 0, _INTERNAL_BUFSIZ },
/* stdout (_iob[1]) */
{ NULL, 0, NULL, _IOWRT, 1, 0, 0 },
/* stderr (_iob[3]) */
{ NULL, 0, NULL, _IOWRT, 2, 0, 0 },
};
_CRTIMP FILE * __cdecl __iob_func(void)
{
return _iob;
}
#ifndef _STDSTREAM_DEFINED
#define stdin (&__iob_func()[0])
#define stdout (&__iob_func()[1])
#define stderr (&__iob_func()[2])
#define _STDSTREAM_DEFINED
#endif /* _STDSTREAM_DEFINED */
通过上述代码,我们可以知道,_iob
是一个可以容纳20个FILE对象的结构体数组,而函数__iob_func
返回了该结构体数组的首地址,也就是一个FILE*
指针类型。__iob_func()[1]
就是数组_iob
里index==1时候的FILE
对象。取地址&__iob_func()[1]
就是一个FILE*
类型了。
Q6: 为什么 FILE *
既可以控制输出到文件,又可以控制输出到屏幕,它是如何做到的?
Q7: printf,printf_s
是怎么将信息输出到屏幕的?
//