C语言printf函数详解和示例
C语言格式化输出的函数有printf、sprintf和snprintf等,功能略有不同,使用方法大同小异,本文以printf函数为例来介绍它们的用法。
对于 printf 函数,相信大家并不陌生。之所以称它为格式化输出函数,该函数的声名如下:
大家看到printf函数的声明就会有点懵,它参数的写法与我们之前学到的函数知识不一样,printf函数是一个“可变参数函数”(即函数参数的个数是可变的),可变参数函数的知识以后再介绍,现在只要知道怎么使用就行了。
printf函数的参数的个数和类型都是可变的,每一个参数的输出格式都有对应的格式说明符与之对应,从格式串的左端第 1 个格式说明符对应第 1 个输出参数,第 2 个格式说明符对应第 2 个输出参数,第 3 个格式说明符对应第 3 个输出参数,以此类推。
其中,格式说明符的形式如下(方括号 [] 中的项为可选项):
它用以表示输出数据的类型,以下是常用类型的汇总,不常用的就不介绍了。
%hd、%d、%ld 以十进制、有符号的形式输出 short、int、long 类型的整数。
%hu、%u、%lu 以十进制、无符号的形式输出 short、int、long 类型的整数
%c 输出字符。
%lf 以普通方式输出double(float弃用,long doube无用)。
%e 以科学计数法输出double。
%s 输出字符串。
它用于控制输出内容的宽度。
flags它用于控制输出内容的对齐方式。
不填或+:输出的内容右对齐,这是缺省的方式,上一小节就是右对齐的示例。
–:输出的内容左对齐。
如果输出的内容是整数或浮点数,并且对齐的方式是右对齐,可以加0填充,例如:
从上面第一行代码的结果看出,输出的内容不是整数或浮点数,是字符串,不能在前面填0。
左对齐的时候,能在整数或浮点数的后面补0吗?浮点数最多可以补到6位,整数不行,为什么?您的存款能在后面补0吗?
如果输出的内容是浮点数,它用于控制输出内容的精度,也就是说小数点后面保留多少位,后面的数四舍五入。
printf是把结果输出到屏幕,sprintf把格式化输出的内容保存到字符串str中,snprintf的n类似于strncpy中的n,意思是只获取输出结果的前n-1个字符,不是n个字符。
C语言提供了把字符串转换为整数和浮点数据的库函数,但是没有把整数和浮点数转换为字符串的库函数,而是采用sprintf和snprintf函数格式化输出到字符串。
示例(book98.c)
运行结果
程序运行第二行只输出了6个字符,注意,snprintf函数在unix和windows平台下的表现略有不同,在windows平台下,第二行会输出7个字符。
C语言技术网原创文章,转载请说明文章的来源、作者和原文的链接。
来源:C语言技术网(www.freecplus.net)
作者:码农有道
C语言中的函数,从9个方面透彻分析
首先,什么是函数?函数(function)是完成特定任务的独立程序代码单元。语法规则定义了函数的结构和使用方式。虽然C中的函数和其他语言中的函数、子程序、过程作用相同,但是细节上略有不同。一些函数执行某些动作,如printf()把数据打印到屏幕上;一些函数找出一个值供程序使用,如strlen()把指定字符串的长度返回给程序。一般而言,函数可以同时具备以上两种功能。
为什么要使用函数?首先,使用函数可以省去编写重复代码的苦差。如果程序要多次完成某项任务,那么只需编写一个合适的函数,就可以在需要时使用这个函数,或者在不同的程序中使用该函数,就像许多程序中使用putchar()一样。其次,即使程序只完成某项任务一次,也值得使用函数。因为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。例如,假设要编写一个程序完成以下任务:
- 读入一系列数字;
- 分类这些数字;
- 找出这些数字的平均值;
- 打印一份柱状图。
可以使用下面的程序:
当然,还要编写4个函数readlist()、sort()、average()和bargraph()的实现细节。描述性的函数名能清楚地表达函数的用途和组织结构。然后,单独设计和测试每个函数,直到函数都能正常完成任务。
如果这些函数够通用,还可以用于其他程序。许多程序员喜欢把函数看作是根据传入信息(输入)及其生成的值或响应的动作(输出)来定义的“黑盒”。如果不是自己编写函数,根本不用关心黑盒的内部行为。例如,使用printf()时,只需知道给该函数传入格式字符串或一些参数以及printf()生成的输出,无需了解printf()的内部代码。以这种方式看待函数有助于把注意力集中在程序的整体设计,而不是函数的实现细节上。因此,在动手编写代码之前,仔细考虑一下函数应该完成什么任务,以及函数和程序整体的关系。
如何了解函数?首先要知道如何正确地定义函数、如何调用函数和如何建立函数间的通信。我们从一个简单的程序示例开始,帮助读者理清这些内容,然后再详细讲解。
我们的第1个目标是创建一个在一行打印40个星号的函数,并在一个打印表头的程序中使用该函数。如程序清单9.1所示,该程序由main()和starbar()组成。
该程序的输出如下:
该程序要注意以下几点。
- 程序在3处使用了starbar标识符:函数原型(functionprototype)告诉编译器函数starbar()的类型;函数调用(function call)表明在此处执行函数;函数定义(function definition)明确地指定了函数要做什么。
- 函数和变量一样,有多种类型。任何程序在使用函数之前都要声明该函数的类型。因此,在main()函数定义的前面出现了下面的ANSI C风格的函数原型:
圆括号表明starbar是一个函数名。第1个void是函数类型,void类型表明函数没有返回值。第2个void(在圆括号中)表明该函数不带参数。分号表明这是在声明函数,不是定义函数。也就是说,这行声明了程序将使用一个名为starbar()、没有返回值、没有参数的函数,并告诉编译器在别处查找该函数的定义。对于不识别ANSI C风格原型的编译器,只需声明函数的类型,如下所示:
注意,一些老版本的编译器甚至连void都识别不了。如果使用这种编译器,就要把没有返回值的函数声明为int类型。当然,最好还是换一个新的编译器。
– 一般而言,函数原型指明了函数的返回值类型和函数接受的参数类型。这些信息称为该函数的签名(signature)。对于starbar()函数而言,其签名是该函数没有返回值,没有参数。
– 程序把starbar()原型置于main()的前面。当然,也可以放在main()里面的声明变量处。放在哪个位置都可以。
– 在main()中,执行到下面的语句时调用了starbar()函数:
这是调用void类型函数的一种形式。当计算机执行到starbar();语句时,会找到该函数的定义并执行其中的内容。执行完starbar()中的代码后,计算机返回主调函数(calling function)继续执行下一行(本例中,主调函数是main()),见图9.1(更确切地说,编译器把C程序翻译成执行以上操作的机器语言代码)。
– 程序中starbar()和main()的定义形式相同。首先函数头包括函数类型、函数名和圆括号,接着是左花括号、变量声明、函数表达式语句,最后以右花括号结束。注意,函数头中的starbar()后面没有分号,告诉编译器这是定义starbar(),而不是调用函数或声明函数原型。
Structure of a simple function.
- 程序把starbar()和main()放在一个文件中。当然,也可以把它们分别放在两个文件中。把函数都放在一个文件中的单文件形式比较容易编译,而使用多个文件方便在不同的程序中使用同一个函数。如果把函数放在一个单独的文件中,要把#define和#include指令也放入该文件。我们稍后会讨论使用多个文件的情况。现在,先把所有的函数都放在一个文件中。main()的右花括号告诉编译器该函数结束的位置,后面的starbar()函数头告诉编译器starbar()是一个函数。
- starbar()函数中的变量count是局部变量(localvariable),意思是该变量只属于starbar()函数。可以在程序中的其他地方(包括main()中)使用count,这不会引起名称冲突,它们是同名的不同变量。
如果把starbar()看作是一个黑盒,那么它的行为是打印一行星号。不用给该函数提供任何输入,因为调用它不需要其他信息。而且,它没有返回值,所以也不给main()提供(或返回)任何信息。简而言之,starbar()不需要与主调函数通信。接下来介绍一个函数间需要通信的例子。
上述程序的输出中,如果文字能居中,信头会更加美观。可以通过在打印文字之前打印一定数量的空格来实现,这和打印一定数量的星号(starbar()函数)类似,只不过现在要打印的是一定数量的空格。虽然这是两个任务,但是任务非常相似,与其分别为它们编写一个函数,不如写一个更通用的函数,可以在两种情况下使用。我们设计一个新的函数show_n_char()(显示一个字符n次)。唯一要改变的是使用内置的值来显示字符和重复的次数,show_n_char()将使用函数参数来传递这些值。
我们来具体分析。假设可用的空间是40个字符宽。调用show_n_char(\’*\’, 40)应该正好打印一行40个星号,就像starbar()之前做的那样。第2行GIGATHINK, INT.的空格怎么处理?GIGATHINK, INT.是15个字符宽,所以第1个版本中,文字后面有25个空格。为了让文字居中,文字的左侧应该有12个空格,右侧有13个空格。因此,可以调用show_n_char(\’ \’,12)。
show_n_char()与starbar()很相似,但是show_n_char()带有参数。从功能上看,前者不会添加换行符,而后者会,因为show_n_char()要把空格和文本打印成一行。程序清单9.2是修改后的版本。为强调参数的工作原理,程序使用了不同的参数形式。
该函数的运行结果如下:
****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
下面我们回顾一下如何编写一个带参数的函数,然后介绍这种函数的用法。
函数定义从下面的ANSI C风格的函数头开始:
该行告知编译器show_n_char()使用两个参数ch和num,ch是char类型,num是int类型。这两个变量被称为形式参数(formal-argument,但是最近的标准推荐使用formalparameter),简称形参。和定义在函数中变量一样,形式参数也是局部变量,属该函数私有。这意味着在其他函数中使用同名变量不会引起名称冲突。每次调用函数,就会给这些变量赋值。
注意,ANSI C要求在每个变量前都声明其类型。也就是说,不能像普通变量声明那样使用同一类型的变量列表:
ANSI C也接受ANSI C之前的形式,但是将其视为废弃不用的形式:
这里,圆括号中只有参数名列表,而参数的类型在后面声明。注意,普通的局部变量在左花括号之后声明,而上面的变量在函数左花括号之前声明。如果变量是同一类型,这种形式可以用逗号分隔变量名列表,如下所示:
当前的标准正逐渐淘汰ANSI之前的形式。读者应对此有所了解,以便能看懂以前编写的程序,但是自己编写程序时应使用现在的标准形式(C99和C11标准继续警告这些过时的用法即将被淘汰)。虽然show_n_char()接受来自main()的值,但是它没有返回值。因此,show_n_char()的类型是void。下面,我们来学习如何使用函数。
在使用函数之前,要用ANSI C形式声明函数原型:
当函数接受参数时,函数原型用逗号分隔的列表指明参数的数量和类型。根据个人喜好,你也可以省略变量名:
在原型中使用变量名并没有实际创建变量,char仅代表了一个char类型的变量,以此类推。
再次提醒,ANSI C也接受过去的声明函数形式,即圆括号内没有参数列表:
这种形式最终会从标准中剔除。即使没有被剔除,现在函数原型的设计也更有优势(稍后会介绍)。了解这种形式的写法是为了以后读得懂以前写的代码。
在函数调用中,实际参数(actual argument,简称实参)提供了ch和num的值。考虑程序清单9.2中第1次调用show_n_char():
实际参数是空格字符和12。这两个值被赋给show_n_char()中相应的形式参数:变量ch和num。简而言之,形式参数是被调函数(called-function)中的变量,实际参数是主调函数(calling-function)赋给被调函数的具体值。如上例所示,实际参数可以是常量、变量,或甚至是更复杂的表达式。无论实际参数是何种形式都要被求值,然后该值被拷贝给被调函数相应的形式参数。以程序清单9.2中最后一次调用show_n_char()为例:
构成该函数第2个实际参数的是一个很长的表达式,对该表达式求值为10。然后,10被赋给变量num。被调函数不知道也不关心传入的数值是来自常量、变量还是一般表达式。再次强调,实际参数是具体的值,该值要被赋给作为形式参数的变量(见图9.3)。因为被调函数使用的值是从主调函数中拷贝而来,所以无论被调函数对拷贝数据进行什么操作,都不会影响主调函数中的原始数据。
Formal parameters and actual arguments
注意:
实际参数和形式参数实际参数是出现在函数调用圆括号中的表达式。形式参数是函数定义的函数头中声明的变量。调用函数时,创建了声明为形式参数的变量并初始化为实际参数的求值结果。程序清单9.2中,\’*\’和WIDTH都是第1次调用show_n_char()时的实际参数,而SPACE和11是第2次调用show_n_char()时的实际参数。在函数定义中,ch和num都是该函数的形式参数。
从黑盒的视角看show_n_char(),待显示的字符和显示的次数是输入。执行后的结果是打印指定数量的字符。输入以参数的形式被传递给函数。这些信息清楚地表明了如何在main()中使用该函数。而且,这也可以作为编写该函数的设计说明。
黑盒方法的核心部分是:ch、num和count都是show_n_char()私有的局部变量。如果在main()中使用同名变量,那么它们相互独立,互不影响。
也就是说,如果main()有一个count变量,那么改变它的值不会改变show_n_char()中的count,反之亦然。黑盒里发生了什么对主调函数是不可见的。
前面介绍了如何把信息从主调函数传递给被调函数。反过来,函数的返回值可以把信息从被调函数传回主调函数。为进一步说明,我们将创建一个返回两个参数中较小值的函数。由于函数被设计用来处理int类型的值,所以被命名为imin()。另外,还要创建一个简单的main(),用于检查imin()是否正常工作。这种被设计用于测试函数的程序有时被称为驱动程序(driver),该驱动程序调用一个函数。如果函数成功通过了测试,就可以安装在一个更重要的程序中使用。程序清单9.3演示了这个驱动程序和返回最小值的函数。
回忆一下,scanf()返回成功读取数据的个数,所以如果输入不是两个整数会导致循环终止。下面是一个运行示例:
关键字return后面的表达式的值就是函数的返回值。在该例中,该函数返回的值就是变量min的值。因为min是int类型的变量,所以imin()函数的类型也是int。
变量min属于imin()函数私有,但是return语句把min的值传回了主调函数。下面这条语句的作用是把min的值赋给lesser:
是否写成下面这样:
不能。因为主调函数甚至不知道min的存在。记住,imin()中的变量是imin()的局部变量。函数调用imin(evil1, evil2)只是把两个变量的值拷贝了一份。
返回值不仅可以赋给变量,也可以被用作表达式的一部分。例如,可以这样:
返回值不一定是变量的值,也可以是任意表达式的值。例如,可以用以下的代码简化程序示例:
条件表达式的值是n和m中的较小者,该值要被返回给主调函数。虽然这里不要求用圆括号把返回值括起来,但是如果想让程序条理更清楚或统一风格,可以把返回值放在圆括号内。
如果函数返回值的类型与函数声明的类型不匹配会怎样?
实际得到的返回值相当于把函数中指定的返回值赋给与函数类型相同的变量所得到的值。因此在本例中,相当于把z的值赋给int类型的变量,然后返回int类型变量的值。例如,假设有下面的函数调用:
虽然在what_if()函数中赋给z的值是1.5625,但是return语句返回int类型的值1。
使用return语句的另一个作用是,终止函数并把控制返回给主调函数的下一条语句。因此,可以这样编写imin():
许多C程序员都认为只在函数末尾使用一次return语句比较好,因为这样做更方便浏览程序的人理解函数的控制流。但是,在函数中使用多个return语句也没有错。无论如何,对用户而言,这3个版本的函数用起来都一样,因为所有的输入和输出都完全相同,不同的是函数内部的实现细节。下面的版本也没问题:
return语句导致printf()语句永远不会被执行。如果Fleppard教授在自己的程序中使用这个版本的函数,可能永远不知道编写这个函数的学生对他的看法。另外,还可以这样使用return:
这条语句会导致终止函数,并把控制返回给主调函数。因为return后面没有任何表达式,所以没有返回值,只有在void函数中才会用到这种形式。
声明函数时必须声明函数的类型。带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为void类型。如果没有声明函数的类型,旧版本的C编译器会假定函数的类型是int。这一惯例源于C的早期,那时的函数绝大多数都是int类型。然而,C99标准不再支持int类型函数的这种假定设置。
类型声明是函数定义的一部分。要记住,函数类型指的是返回值的类型,不是函数参数的类型。例如,下面的函数头定义了一个带两个int类型参数的函数,但是其返回值是double类型。
要正确地使用函数,程序在第1次使用函数之前必须知道函数的类型。方法之一是,把完整的函数定义放在第1次调用函数的前面。然而,这种方法增加了程序的阅读难度。而且,要使用的函数可能在C库或其他文件中。因此,通常的做法是提前声明函数,把函数的信息告知编译器。
第2行代码说明imin是一个函数名,有两个int类型的形参,且返回int类型的值。现在,编译器在程序中调用imin()函数时就知道应该如何处理。
我们把函数的前置声明放在主调函数外面。当然,也可以放在主调函数里面。例如,重写lesser.c 的开头部分:
注意在这两种情况中,函数原型都声明在使用函数之前。
ANSI-C标准库中,函数被分成多个系列,每一系列都有各自的头文件。这些头文件中除了其他内容,还包含了本系列所有函数的声明。例如,stdio.h头文件包含了标准I/O库函数(如,printf()和scanf())的声明。math.h头文件包含了各种数学函数的声明。例如,下面的声明:
告知编译器sqrt()函数有一个double类型的形参,而且返回double类型的值。不要混淆函数的声明和定义。函数声明告知编译器函数的类型,而函数定义则提供实际的代码。在程序中包含math.h头文件告知编译器:sqrt()返回double类型,但是sqrt()函数的代码在另一个库函数的文件中。
C语言函数的定义、声明、调用 – 整理,归纳,总结
通过前面的举例分析,我们知道函数就像是一个工具,可以用来完成某项任务,例如printf函数可以把数据输出到终端,system函数可以执行linux系统中工具。那么,我们可以自己定义函数,来实现某项任务。函数的定义格式如下:
返回数据类型 函数名(形参列表)
{
函数体
}
函数定义格式说明:
(1) 函数定义的第一个单词是“返回数据类型”,它说明该函数的返回类型;如果是int类型,说明该函数返回一个整数类型数据;如果是char类型,说明该函数返回一个字符型数据;如果不想返回任何数据,可以使用void数据类型,表示没有返回数据。
(2) 在“返回数据类型”后面接着空格,然后,接着“函数名”;这个函数名必须是一个合法的标识符,标记这个函数的名字。
(3) 在“函数名”后面,接着小括号(),在小括号中定义“形参列表”,这个形参列表表示要传递给函数的数据。例如(int a)表示函数参数接收一个int类型的变量。如果不想给函数传递数据,那么,形参列表可以为空或者为void数据类型;
(4) 在小括号后面,接着大括号{},在大括号中存放的是函数体;
(5) 函数体就是函数需要执行的具体任务;例如main函数的函数体,就是我们自己编写的代码,用来实现具体的运算操作。
此时,我们了解了函数的定义,那么,怎么样使用函数?很简单,就是两种形式:
(1) 如果没有定义形参列表,函数的使用形式是:函数名();
(2) 如果定义形参列表,函数使用的形式是:函数名(参数列表);
下面我们会循序渐进地讲解不同函数类型的使用方式。函数的使用,一般也称为:函数的调用;使用函数,称为:调用函数。
在学习变量的时候,我们知道:变量在使用之前,必须先定义,例如:
int a = 168;
printf(\”a = %d\\n\”, a);
可以看到,在使用printf函数输出变量a的时候,就是使用了变量a。那么,在使用变量a之前,必须先对变量a进行定义。修改上面的代码如下:
printf(\”a = %d\\n\”, a);
int a = 168;
此时,编译代码异常,提示printf函数在使用变量a的时候,变量a没有定义。因为变量a的定义,放在了printf函数之后。
同理,函数在使用之前,也必须先定义或者声明。此时,我们就提到两个概念:函数的定义和函数的声明。总结如下:
(1) 函数的定义是实现整个函数的功能,包括函数体的实现,要定义函数体;
(2) 函数的声明,不要实现函数体,只是说明函数定义的格式;
例如,有如下的函数:
void func(int a, int b, int c)
{
printf(\”in func2, a = %d, b = %d, c = %d\\n\”, a, b, c);
}
可以看到,这一段代码说明了func函数的格式和函数体的定义,那么,这段代码就是func函数的定义。
那么,函数的声明,不需要函数体的定义,只需要函数的格式说明,所以func函数的声明如下:
void func(int a, int b, int c);
注意,就是函数定义时,是由函数的返回值类型、函数名和函数的形参列表组成。而且,最后是以引号“;”作为结束符。C语言的函数声明,还可以有另一种格式,就是函数的形参列表中,可以不用给出形参变量名,只给出形参变量类型即可。例如:
void func(int, int, int);
可以看到,此时声明func函数,在形参列表中,只给出形参列表的变量类型即可,不用给出变量名。所以,我们可以总结函数的声明有如下两种形式:
(1) 函数返回值类型 函数名(形参类型1变量名1, 形参类型2 变量名2, …);
(2) 函数返回值类型 函数名(形参类型1, 形参类型2, …);
程序测试例子如下:
深入学习,可以交个朋友,工人人人号:韦凯峰linux编程学堂
程序运行结果如下:
可以看到,在main函数中调用了func1、func2函数,那么,func1、func2函数是在main函数后面定义。就是说,在main函数中使用func1、func2函数之前,并没有看到对func1、func2函数的定义。
所以,就必须在main函数之前,对func1、func2函数进行“函数声明”。可以看到,对func1、func2函数的声明,我们使用了两种不同的格式。对func1函数的声明,在形参列表中,说明了形参变量的名称。对func2函数的声明,在形参列表中,没有给出形参变量的名称。
注意:函数定义的时候,函数的返回值类型,函数名和形参列表都定义完整了。那么,在函数声明的时候,函数的返回值类型和函数名必须相同,而且不能够省略。对于形参列表,可以只给出形参变量的类型,不用给出形参变量的名称。
那么,在对函数进行声明的时候,可以不用给出形参变量的名称;如果要写形参变量的名称,也可以与函数定义格式中,与形参变量名不同。程序测试代码如下:
深入学习,可以交个朋友,工人人人号:韦凯峰linux编程学堂
程序运行结果如下:
深入学习,可以交个朋友,工人人人号:韦凯峰linux编程学堂
可以看到,func1函数在声明的时候,形参变量的名称可以与函数定义时的形参变量名不同,只要形参变量的类型相同就可以了。
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。