C语言基础之9:函数
Tips1:
关键字:return
运算符:*(一元)、&(一元)
函数及其定义方式
如何使用参数和返回值
如何把指针变量用作函数参数
函数类型
ANSI C原型
递归
如何组织程序?C的设计思想是,把函数用作构件块。我们已经用过C标准库的函数,如printf()、scanf()、getchar()、putchar()和 strlen()。现在要进 一步学习如何创建自己的函数。前面章节中已大致介绍了相关过程,本章将 巩固以前学过的知识并做进一步的拓展。
9.1 复习函数
首先,什么是函数?函数(function)是完成特定任务的独立程序代码单元。语法规则定义了函数的结构和使用方式。虽然C中的函数和其他语言 中的函数、子程序、过程作用相同,但是细节上略有不同。一些函数执行某 些动作,如printf()把数据打印到屏幕上;一些函数找出一个值供程序使用, 如strlen()把指定字符串的长度返回给程序。一般而言,函数可以同时具备以 上两种功能。
为什么要使用函数?首先,使用函数可以省去编写重复代码的苦差。如 果程序要多次完成某项任务,那么只需编写一个合适的函数,就可以在需要 时使用这个函数,或者在不同的程序中使用该函数,就像许多程序中使用 putchar()一样。其次,即使程序只完成某项任务一次,也值得使用函数。因为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。例如,假设要编写一个程序完成以下任务:
读入一系列数字;
分类这些数字;
找出这些数字的平均值;
打印一份柱状图。
可以使用下面的程序:
#include <stdio.h>
#define SIZE 50
int main(void)
{float list[SIZE];readlist(list, SIZE);sort(list, SIZE);average(list, SIZE);bargraph(list, SIZE);return 0;
}
//根据拟定的任务可以把程序框架顶出来,上一章的读取输入字符是一个典型例子(比较细化)
当然,还要编写4个函数readlist()、sort()、average()和bargraph()的实现细节。描述性的函数名能清楚地表达函数的用途和组织结构。然后,单独设 计和测试每个函数,直到函数都能正常完成任务。如果这些函数够通用,还可以用于其他程序。
许多程序员喜欢把函数看作是根据传入信息(输入)及其生成的值或响应的动作(输出)来定义的“黑盒”。如果不是自己编写函数,根本不用关心黑盒的内部行为。例如,使用printf()时,只需知道给该函数传入格式字符串 或一些参数以及printf()生成的输出,无需了解printf()的内部代码。以这种方式看待函数有助于把注意力集中在程序的整体设计,而不是函数的实现细节上。因此,在动手编写代码之前,仔细考虑一下函数应该完成什么任务, 以及函数和程序整体的关系。
如何了解函数?首先要知道如何正确地定义函数、如何调用函数和如何 建立函数间的通信。我们从一个简单的程序示例开始,帮助读者理清这些内 容,然后再详细讲解。
9.1.1 创建并使用简单函数
我们的第1个目标是创建一个在一行打印40个星号的函数,并在一个打印表头的程序中使用该函数。如程序清单9.1所示,该程序由main()和 starbar()组成。
程序清单9.1 lethead1.c程序
/* lethead1.c */
#include <stdio.h>
#define NAME "GIGATHINK, INC."
#define ADDRESS "101 Megabuck Plaza"
#define PLACE "Megapolis, CA 94904"
#define WIDTH 40
void starbar(void); /* 函数原型 */int main(void)
{
starbar();
printf("%s\n", NAME);
printf("%s\n", ADDRESS);
printf("%s\n", PLACE);
starbar(); /* 使用函数 */
return 0;
}void starbar(void) /* 定义函数 */
{
int count;
for (count = 1; count <= WIDTH; count++)
putchar('*');
putchar('\n');
}
该程序的输出如下:
****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
9.1.2 分析程序
该程序要注意以下几点。
程序在3处使用了starbar标识符:函数原型(function prototype)告诉编译器函数starbar()的类型;函数调用(function call)表明在此处执行函数; 函数定义(function definition)明确地指定了函数要做什么。
函数和变量一样,有多种类型。任何程序在使用函数之前都要声明该函数的类型。因此,在main()函数定义的前面出现了下面的ANSI C风格的函数原型:
void starbar(void)
//坚持使用ANSI C风格的函数原型很不错,必须指明返回值和参数类型,如果没有就用void代替
注意,一些老版本的编译器甚至连void都识别不了。如果使用这种编译 器,就要把没有返回值的函数声明为int类型。当然,最好还是换一个新的编 译器。
一般而言,函数原型指明了函数的返回值类型和函数接受的参数类型。 这些信息称为该函数的签名(signature)。对于starbar()函数而言,其签名是该函数没有返回值,没有参数。
程序把 starbar()原型置于 main()的前面。当然,也可以放在 main()里面 的声明变量处。放在哪个位置都可以。
在main()中,执行到下面的语句时调用了starbar()函数:
starbar();
这是调用void类型函数的一种形式。
当计算机执行到starbar();语句时, 会找到该函数的定义并执行其中的内容。执行完starbar()中的代码后,计算机返回主调函数(calling function)继续执行下一行(本例中,主调函数是 main()),见图9.1(更确切地说,编译器把C程序翻译成执行以上操作的机器语言代码)。
程序中strarbar()和main()的定义形式相同。首先函数头包括函数类型、 函数名和圆括号,接着是左花括号、变量声明、函数表达式语句,最后以右花括号结束(见图9.2)。注意,函数头中的starbar()后面没有分号,告诉编译器这是定义starbar(),而不是调用函数或声明函数原型。
程序把 starbar()和 main()放在一个文件中。当然,也可以把它们分别放在两个文件中。把函数都放在一个文件中的单文件形式比较容易编译,而使用多个文件方便在不同的程序中使用同一个函数。如果把函数放在一个单独的文件中,要把#define 和#include 指令也放入该文件。我们稍后会讨论使用多个文件的情况。现在,先把所有的函数都放在一个文件中。main()的右花括号告诉编译器该函数结束的位置,后面的starbar()函数头告诉编译器 starbar()是一个函数。
starbar()函数中的变量count是局部变量(local variable),意思是该变量只属于starbar()函数。可以在程序中的其他地方包括main()中使用 count,这不会引起名称冲突,它们是同名的不同变量。
//局部变量重名并不引起冲突,局部变量只在调用函数才起作用
如果把starbar()看作是一个黑盒,那么它的行为是打印一行星号。不用 给该函数提供任何输入,因为调用它不需要其他信息。而且,它没有返回值,所以也不给 main()提供(或返回)任何信息。简而言之,starbar()不需要与主调函数通信。
接下来介绍一个函数间需要通信的例子。
9.1.3 函数参数
在程序清单9.1的输出中,如果文字能居中,信头会更加美观。可以通 过在打印文字之前打印一定数量的空格来实现,这和打印一定数量的星号 (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是修改后的版本。为强调参数的工作原理,程序使用了不同的参数形式。
程序清单9.2 lethead2.c程序
/* lethead2.c */
#include <stdio.h>
#include <string.h> /* 为strlen()提供原型 */
#define NAME "GIGATHINK, INC."
#define ADDRESS "101 Megabuck Plaza"
#define PLACE "Megapolis, CA 94904"
#define WIDTH 40
#define SPACE ' '
void show_n_char(char ch, int num);int main(void)
{
int spaces;
show_n_char('*', WIDTH); /* 用符号常量作为参数 */
putchar('\n');
show_n_char(SPACE, 12); /* 用符号常量作为参数 */
printf("%s\n", NAME);
spaces = (WIDTH - strlen(ADDRESS)) / 2; /* 计算要跳过多少个空格*/
show_n_char(SPACE, spaces); /* 用一个变量作为参数*/
printf("%s\n", ADDRESS);
show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
printf("%s\n", PLACE); /* 用一个表达式作为参数*/
show_n_char('*', WIDTH);
putchar('\n');
return 0;
}/* show_n_char()函数的定义 */
void show_n_char(char ch, int num)
{
int count;
for (count = 1; count <= num; count++)
putchar(ch);
}
该函数的运行结果如下:
****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
下面我们回顾一下如何编写一个带参数的函数,然后介绍这种函数的用法。
9.1.4 定义带形式参数的函数
函数定义从下面的ANSI C风格的函数头开始:
void show_n_char(char ch, int num)
该行告知编译器show_n_char()使用两个参数ch和num,ch是char类型, num是int类型。这两个变量被称为形式参数(formal argument,但是最近的标 准推荐使用formal parameter),简称形参。和定义在函数中变量一样,形式 参数也是局部变量,属该函数私有。这意味着在其他函数中使用同名变量不 会引起名称冲突。每次调用函数,就会给这些变量赋值。
注意,ANSI C要求在每个变量前都声明其类型。也就是说,不能像普通变量声明那样使用同一类型的变量列表:
void dibs(int x, y, z) /* 无效的函数头 */
void dubs(int x, int y, int z) /* 有效的函数头 */
ANSI C也接受ANSI C之前的形式,但是将其视为废弃不用的形式:
void show_n_char(ch, num)
char ch;
int num;
这里,圆括号中只有参数名列表,而参数的类型在后面声明。注意,普通的局部变量在左花括号之后声明,而上面的变量在函数左花括号之前声 明。如果变量是同一类型,这种形式可以用逗号分隔变量名列表,如下所示:
void dibs(x, y, z)
int x, y, z; /* 有效 */
当前的标准正逐渐淘汰ANSI之前的形式。读者应对此有所了解,以便能看懂以前编写的程序,但是自己编写程序时应使用现在的标准形式(C99 和C11标准继续警告这些过时的用法即将被淘汰)。
虽然show_n_char()接受来自main()的值,但是它没有返回值。因此, show_n_char()的类型是void。 下面,我们来学习如何使用函数。
9.1.5 声明带形式参数函数的原型
在使用函数之前,要用ANSI C形式声明函数原型:
void show_n_char(char ch, int num);
当函数接受参数时,函数原型用逗号分隔的列表指明参数的数量和类型。根据个人喜好,你也可以省略变量名:
void show_n_char(char, int);
在原型中使用变量名并没有实际创建变量,char仅代表了一个char类型的变量,以此类推。再次提醒读者注意,ANSI C也接受过去的声明函数形式,即圆括号内没有参数列表:
void show_n_char();
这种形式最终会从标准中剔除。即使没有被剔除,现在函数原型的设计也更有优势(稍后会介绍)。了解这种形式的写法是为了以后读得懂以前写的代码。
说明:函数原型中,参数类型必须有,参数名称没有什么实际含义,但从规范性和易读的角度都建议给一个
9.1.6 调用带实际参数的函数
在函数调用中,实际参数(actual argument,简称实参)提供了ch和num 的值。考虑程序清单9.2中第1次调用show_n_char():
show_n_char(SPACE, 12);
实际参数是空格字符和12。这两个值被赋给show_n_char()中相应的形式 参数:变量ch和num。简而言之,形式参数是被调函数(called function)中 的变量,实际参数是主调函数(calling function)赋给被调函数的具体值。 如上例所示,实际参数可以是常量、变量,或甚至是更复杂的表达式。无论 实际参数是何种形式都要被求值,然后该值被拷贝给被调函数相应的形式参 数。以程序清单 9.2 中最后一次调用show_n_char()为例:
show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
构成该函数第2个实际参数的是一个很长的表达式,对该表达式求值为 10。然后,10被赋给变量num。被调函数不知道也不关心传入的数值是来自 常量、变量还是一般表达式。再次强调,实际参数是具体的值,该值要被赋 给作为形式参数的变量(见图 9.3)。因为被调函数使用的值是从主调函数中拷贝而来,所以无论被调函数对拷贝数据进行什么操作,都不会影响主调函数中的原始数据。
注意:实际参数和形式参数---实际参数是出现在函数调用圆括号中的表达式。形式参数是函数定义的函数头中声明的变量。调用函数时,创建了声明为形式参数的变量并初始化 为实际参数的求值结果。程序清单 9.2 中,'*'和WIDTH都是第1次调用 show_n_char()时的实际参数,而SPACE和11是第2次调用show_n_char()时的实际参数。在函数定义中,ch和num都是该函数的形式参数。
说明:有个很重要的概念,只能由实参传入形参,形参是无法传回实参的
9.1.7 黑盒视角
从黑盒的视角看 show_n_char(),待显示的字符和显示的次数是输入。 执行后的结果是打印指定数量的字符。输入以参数的形式被传递给函数。这 些信息清楚地表明了如何在 main()中使用该函数。而且,这也可以作为编写该函数的设计说明。
黑盒方法的核心部分是:ch、num和count都是show_n_char()私有的局部变量。如果在main()中使用同名变量,那么它们相互独立,互不影响。也就是说,如果main()有一个count变量,那么改变它的值不会改变show_n_char()中的count,反之亦然。黑盒里发生了什么对主调函数是不可见的。
9.1.8 使用return从函数中返回值
前面介绍了如何把信息从主调函数传递给被调函数。反过来,函数的返回值可以把信息从被调函数传回主调函数。为进一步说明,我们将创建一个返回两个参数中较小值的函数。由于函数被设计用来处理int类型的值,所以被命名为imin()。另外,还要创建一个简单的main(),用于检查imin()是否正常工作。这种被设计用于测试函数的程序有时被称为驱动程序(driver), 该驱动程序调用一个函数。如果函数成功通过了测试,就可以安装在一个更重要的程序中使用。程序清单9.3演示了这个驱动程序和返回最小值的函数。
程序清单9.3 lesser.c程序
/* lesser.c -- 找出两个整数中较小的一个 */
#include <stdio.h>
int imin(int, int);int main(void)
{
int evil1, evil2;
printf("Enter a pair of integers (q to quit):\n");
while (scanf("%d %d", &evil1, &evil2) == 2)
{
printf("The lesser of %d and %d is %d.\n",
evil1, evil2, imin(evil1, evil2));
printf("Enter a pair of integers (q to quit):\n");
}
printf("Bye.\n");
return 0;
}int imin(int n, int m)
{
int min;
if (n < m)
min = n;
else
min = m;
return min;
}
回忆一下,scanf()返回成功读数据的个数,所以如果输入不是两个整数会导致循环终止。下面是一个运行示例:
Enter a pair of integers (q to quit):
509 333
The lesser of 509 and 333 is 333.
Enter a pair of integers (q to quit):
-9393 6
The lesser of -9393 and 6 is -9393.
Enter a pair of integers (q to quit):
q
Bye.
关键字return后面的表达式的值就是函数的返回值。在该例中,该函数返回的值就是变量min的值。因为min是int类型的变量,所以imin()函数的类 型也是int。
变量min属于imin()函数私有,但是return语句把min的值传回了主调函数。下面这条语句的作用是把min的值赋给lesser:
lesser = imin(n,m);
是否能像写成下面这样:
imin(n,m);
lesser = min; 不能。因为主调函数甚至不知道min的存在。记住,imin()中的变量是 imin()的局部变量。函数调用imin(evil1, evil2)只是把两个变量的值拷贝了一份。
返回值不仅可以赋给变量,也可以被用作表达式的一部分。例如,可以这样:
answer = 2 * imin(z, zstar) + 25;
printf("%d\n", imin(-32 + answer, LIMIT));
//比较常用的用法,把返回值的表达式当成另一个表达式的子表达式。
返回值不一定是变量的值,也可以是任意表达式的值。例如,可以用以下的代码简化程序示例:
/* 返回最小值的函数,第2个版本 */
imin(int n,int m)
{
return (n < m) ? n : m;
}
条件表达式的值是n和m中的较小者,该值要被返回给主调函数。虽然这里不要求用圆括号把返回值括起来,但是如果想让程序条理更清楚或统一 风格,可以把返回值放在圆括号内。
如果函数返回值的类型与函数声明的类型不匹配会怎样?
int what_if(int n)
{
double z = 100.0 / (double) n;
return z; // 会发生什么?
}
实际得到的返回值相当于把函数中指定的返回值赋给与函数类型相同的变量所得到的值。因此在本例中,相当于把z的值赋给int类型的变量,然后返回int类型变量的值。例如,假设有下面的函数调用:
result = what_if(64);
虽然在what_if()函数中赋给z的值是1.5625,但是return语句返回确实int 类型的值1。
//相当于int a=10.00 当声明类型与实际值不一样时,以类型为准。
使用 return 语句的另一个作用是,终止函数并把控制返回给主调函数的下一条语句。因此,可以这样编写imin():
/*返回最小值的函数,第3个版本*/
imin(int n,int m)
{if (n < m)return n;elsereturn m;
}
许多C程序员都认为只在函数末尾使用一次return语句比较好,因为这样 做更方便浏览程序的人理解函数的控制流。但是,在函数中使用多个return 语句也没有错。无论如何,对用户而言,这3个版本的函数用起来都一样, 因为所有的输入和输出都完全相同,不同的是函数内部的实现细节。下面的版本也没问题:
/*返回最小值的函数,第4个版本*/
imin(int n, int m)
{if (n < m)return n;elsereturn m;printf("Professor Fleppard is like totally a fopdoodle.\n");
}
return语句导致printf()语句永远不会被执行。如果Fleppard教授在自己的 程序中使用这个版本的函数,可能永远不知道编写这个函数的学生对他的看法。
另外,还可以这样使用
return:
return; 这条语句会导致终止函数,并把控制返回给主调函数。因为 return 后面 没有任何表达式,所以没有返回值,只有在void函数中才会用到这种形式。
说明:return的另一个作用终止函数并交还控制给主调函数。
9.1.9 函数类型
声明函数时必须声明函数的类型。带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为void类型。如果没有声明函数的类 型,旧版本的C编译器会假定函数的类型是int。这一惯例源于C的早期,那 时的函数绝大多数都是int类型。然而,C99标准不再支持int类型函数的这种 假定设置。
类型声明是函数定义的一部分。要记住,函数类型指的是返回值的类型,不是函数参数的类型。例如,下面的函数头定义了一个带两个int类型参 数的函数,但是其返回值是double类型。
double klink(int a, int b) //标准写法
要正确地使用函数,程序在第 1 次使用函数之前必须知道函数的类型。 方法之一是,把完整的函数定义放在第1次调用函数的前面。然而,这种方法增加了程序的阅读难度。而且,要使用的函数可能在C库或其他文件中。 因此,通常的做法是提前声明函数,把函数的信息告知编译器。例如,程序 清单 9.3 中的main()函数包含以下几行代码:
#include <stdio.h>
int imin(int, int);
int main(void)
{
int evil1, evil2, lesser;
第2行代码说明imin是一个函数名,有两个int类型的形参,且返回int类 型的值。现在,编译器在程序中调用imin()函数时就知道应该如何处理。
在程序清单9.3中,我们把函数的前置声明放在主调函数外面。当然, 也可以放在主调函数里面。例如,重写lesser.c(程序清单9.3)的开头部分:
#include <stdio.h>
int main(void)
{
int imin(int, int); /* 声明imin()函数的原型*/
int evil1, evil2, lesser;
注意在这两种情况中,函数原型都声明在使用函数之前。
ANSI C标准库中,函数被分成多个系列,每一系列都有各自的头文 件。这些头文件中除了其他内容,还包含了本系列所有函数的声明。例如, stdio.h 头文件包含了标准 I/O 库函数(如,printf()和scanf())的声明。math.h 头文件包含了各种数学函数的声明。例如,下面的声明:
double sqrt(double);
告知编译器sqrt()函数有一个double类型的形参,而且返回double类型的值。不要混淆函数的声明和定义。函数声明告知编译器函数的类型,而函数 定义则提供实际的代码。在程序中包含 math.h 头文件告知编译器:sqrt()返 回double类型,但是sqrt()函数的代码在另一个库函数的文件中。
说明:函数出现的顺序一般为:函数声明(函数原型),函数调用,函数定义
9.2 ANSI C函数原型
在ANSI C标准之前,声明函数的方案有缺陷,因为只需要声明函数的类型,不用声明任何参数。下面我们看一下使用旧式的函数声明会导致什么问题。
下面是ANSI之前的函数声明,告知编译器imin()返回int类型的值:
int imin();
然而,以上函数声明并未给出imin()函数的参数个数和类型。因此,如 果调用imin()时使用的参数个数不对或类型不匹配,编译器根本不会察觉出来。
9.2.1 问题所在
我们看看与imax()函数相关的一些示例,该函数与imin()函数关系密切。程序清单9.4演示了一个程序,用过去声明函数的方式声明了imax()函数,然后错误地使用该函数。
程序清单9.4 misuse.c程序
/* misuse.c -- 错误地使用函数 */
#include <stdio.h>
int imax(); /* 旧式函数声明 */
int main(void)
{
printf("The maximum of %d and %d is %d.\n",3, 5, imax(3));
printf("The maximum of %d and %d is %d.\n",3, 5,
imax(3.0, 5.0));
return 0;
}int imax(n, m)
int n, m;
{
return (n > m ? n : m);
}
第1次调用printf()时省略了imax()的一个参数,第2次调用printf()时用两 个浮点参数而不是整数参数。尽管有些问题,但程序可以编译和运行。
下面是使用Xcode 4.6运行的输出示例:
The maximum of 3 and 5 is 1606416656.
The maximum of 3 and 5 is 3886.
使用gcc运行该程序,输出的值是1359379472和1359377160。这两个编 译器都运行正常,之所以输出错误的结果,是因为它们运行的程序没有使用函数原型。
到底是哪里出了问题?由于不同系统的内部机制不同,所以出现问题的 具体情况也不同。下面介绍的是使用PC和VAX的情况。主调函数把它的参数储存在被称为栈(stack)的临时存储区,被调函数从栈中读取这些参数。 对于该例,这两个过程并未相互协调。主调函数根据函数调用中的实际参数决定传递的类型,而被调函数根据它的形式参数读取值。因此,函数调用 imax(3)把一个整数放在栈中。当imax()函数开始执行时,它从栈中读取两个整数。而实际上栈中只存放了一个待读取的整数,所以读取的第 2 个值是当 时恰好在栈中的其他值。
第2次使用imax()函数时,它传递的是float类型的值。这次把两个double 类型的值放在栈中(回忆一下,当float类型被作为参数传递时会被升级为 double类型)。在我们的系统中,两个double类型的值就是两个64位的值, 所以128位的数据被放在栈中。当imax()从栈中读取两个int类型的值时,它 从栈中读取前64位。在我们的系统中,每个int类型的变量占用32位。这些数 据对应两个整数,其中较大的是3886。
说明:入栈参数和出栈参数不匹配可能会导致程序运行失败,所以需要坚持函数原型
9.2.2 ANSI的解决方案
针对参数不匹配的问题,ANSI C标准要求在函数声明时还要声明变量的类型,即使用函数原型(function prototype)来声明函数的返回类型、参数的数量和每个参数的类型。未标明 imax()函数有两个 int 类型的参数,可以使用下面两种函数原型来声明:
int imax(int, int);
int imax(int a, int b); //函数原型标准写法
第1种形式使用以逗号分隔的类型列表,第2种形式在类型后面添加了变 量名。注意,这里的变量名是假名,不必与函数定义的形式参数名一致。 //尽管如此,保持一致更好
有了这些信息,编译器可以检查函数调用是否与函数原型匹配。参数的 数量是否正确?参数的类型是否匹配?以 imax()为例,如果两个参数都是数 字,但是类型不匹配,编译器会把实际参数的类型转换成形式参数的类型。 例如,imax(3.0, 5.0)会被转换成imax(3, 5)。我们用函数原型替换程序清单9.4 中的函数声明,如程序清单9.5所示。
程序清单9.5 proto.c程序
/* proto.c -- 使用函数原型 */
#include <stdio.h>
int imax(int, int); /* 函数原型 */int main(void)
{
printf("The maximum of %d and %d is %d.\n",
3, 5, imax(3));
printf("The maximum of %d and %d is %d.\n",
3, 5, imax(3.0, 5.0));
return 0;
}int imax(int n, int m)
{
return (n > m ? n : m);
}
编译程序清单9.5时,我们的编译器给出调用的imax()函数参数太少的错 误消息。
如果是类型不匹配会怎样?为探索这个问题,我们用imax(3, 5)替换 imax(3),然后再次编译该程序。这次编译器没有给出任何错误信息,程序的输出如下:
The maximum of 3 and 5 is 5.
The maximum of 3 and 5 is 5.
如上文所述,第2次调用中的3.0和5.0被转换成3和5,以便函数能正确地 处理输入。
虽然没有错误消息,但是我们的编译器还是给出了警告:double转换成 int可能会导致丢失数据。例如,下面的函数调用:
imax(3.9, 5.4)
相当于:
imax(3, 5)
错误和警告的区别是:错误导致无法编译,而警告仍然允许编译。一些 编译器在进行类似的类型转换时不会通知用户,因为C标准中对此未作要求。不过,许多编译器都允许用户选择警告级别来控制编译器在描述警告时的详细程度。
9.2.3 无参数和未指定参数
假设有下面的函数原型:
void print_name();
一个支持ANSI C的编译器会假定用户没有用函数原型来声明函数,它将不会检查参数。为了表明函数确实没有参数,应该在圆括号中使用void关 键字:
void print_name(void);
支持ANSI C的编译器解释为print_name()不接受任何参数。然后在调用该函数时,编译器会检查以确保没有使用参数。
一些函数接受(如,printf()和scanf())许多参数。例如对于printf(),第1 589 个参数是字符串,但是其余参数的类型和数量都不固定。对于这种情况, ANSI C允许使用部分原型。例如,对于printf()可以使用下面的原型:
int printf(const char *, ...);
这种原型表明,第1个参数是一个字符串(第11章中将详细介绍),可能还有其他未指定的参数。 C库通过stdarg.h头文件提供了一个定义这类(形参数量不固定的)函数的标准方法。第16章中详细介绍相关内容。
9.2.4 函数原型的优点
函数原型是C语言的一个强有力的工具,它让编译器捕获在使用函数时 可能出现的许多错误或疏漏。如果编译器没有发现这些问题,就很难觉察出 来。是否必须使用函数原型?不一定。你也可以使用旧式的函数声明(即不 用声明任何形参),但是这样做的弊大于利。
有一种方法可以省略函数原型却保留函数原型的优点。首先要明白,之 所以使用函数原型,是为了让编译器在第1次执行到该函数之前就知道如何 使用它。因此,把整个函数定义放在第1次调用该函数之前,也有相同的效 果。此时,函数定义也相当于函数原型。对于较小的函数,这种用法很普遍:
// 下面这行代码既是函数定义,也是函数原型
int imax(int a, int b) { return a > b ? a : b; }
int main()
{
int x, z;
...
z = imax(x, 50);
...
}
//后面用宏定义一些简单函数更普遍(如果调用次数不是很多,不会占有大量空间的话)
9.3 递归(略)
C允许函数调用它自己,这种调用过程称为递归(recursion)。递归有 时难以捉摸,有时却很方便实用。结束递归是使用递归的难点,因为如果递 归代码中没有终止递归的条件测试部分,一个调用自己的函数会无限递归。 可以使用循环的地方通常都可以使用递归。有时用循环解决问题比较 好,但有时用递归更好。递归方案更简洁,但效率却没有循环高。
9.4 编译多源代码文件的程序
使用多个函数最简单的方法是把它们都放在同一个文件中,然后像编译 只有一个函数的文件那样编译该文件即可。其他方法因操作系统而异,下面 将举例说明。
9.4.1 UNIX
假定在UNIX系统中安装了UNIX C编译器cc(最初的cc已经停用,但是 许多UNIX系统都给cc命令起了一个别名用作其他编译器命令,典型的是gcc 或clang)。假设file1.c和file2.c是两个内含C函数的文件,下面的命令将编译 两个文件并生成一个名为a.out的可执行文件:
cc file1.c file2.c
另外,还生成两个名为file1.o和file2.o的目标文件。如果后来改动了 file1.c,而file2.c不变,可以使用以下命令编译第1个文件,并与第2个文件的目标代码合并:
cc file1.c file2.o
UNIX系统的make命令可自动管理多文件程序,但是这超出了本书的讨 论范围。 注意,OS X的Terminal工具可以打开UNIX命令行环境,但是必须先下载命令行编译器(GCC和Clang)。
9.4.2 Linux
假定Linux系统安装了GNU C编译器GCC。假设file1.c和file2.c是两个内 含C函数的文件,下面的命令将编译两个文件并生成名为a.out的可执行文 件: gcc file1.c file2.c
另外,还生成两个名为file1.o和file2.o的目标文件。如果后来改动了 file1.c,而file2.c不变,可以使用以下命令编译第1个文件,并与第2个文件 的目标代码合并:
gcc file1.c file2.o
9.4.3 DOS命令行编译器
绝大多数DOS命令行编译器的工作原理和UNIX的cc命令类似,只不过 使用不同的名称而已。其中一个区别是,对象文件的扩展名是.obj,而不 是.o。一些编译器生成的不是目标代码文件,而是汇编语言或其他特殊代码 的中间文件。
9.4.4 Windows和苹果的IDE编译器
Windows和Macintosh系统使用的集成开发环境中的编译器是面向项目 的。项目(project)描述的是特定程序使用的资源。资源包括源代码文件。 这种IDE中的编译器要创建项目来运行单文件程序。对于多文件程序,要使 用相应的菜单命令,把源代码文件加入一个项目中。要确保所有的源代码文 件都在项目列表中列出。许多IDE都不用在项目列表中列出头文件(即扩展 名为.h的文件),因为项目只管理使用的源代码文件,源代码文件中的 #include指令管理该文件中使用的头文件。但是,Xcode要在项目中添加头文件。
说明:有关编译器的介绍,现在基本上使用现成的有界面的编译器,不用太关注那些dos命令。
9.4.5 使用头文件
如果把main()放在第1个文件中,把函数定义放在第2个文件中,那么第 1个文件仍然要使用函数原型。把函数原型放在头文件中,就不用在每次使用函数文件时都写出函数的原型。C 标准库就是这样做的,例如,把I/O函 数原型放在stdio.h中,把数学函数原型放在math.h中。你也可以这样用自定 义的函数文件。
另外,程序中经常用C预处理器定义符号常量。这种定义只储存了那些 包含#define指令的文件。如果把程序的一个函数放进一个独立的文件中,你也可以使用#define指令访问每个文件。最直接的方法是在每个文件中再次输入指令,但是这个方法既耗时又容易出错。另外,还会有维护的问题:如果修改了#define 定义的值,就必须在每个文件中修改。更好的做法是,把 #define 指令放进头文件,然后在每个源文件中使用#include指令包含该文件 即可。
总之,把函数原型和已定义的字符常量放在头文件中是一个良好的编程习惯。我们考虑一个例子:假设要管理 4 家酒店的客房服务,每家酒店的房价不同,但是每家酒店所有房间的房价相同。对于预订住宿多天的客户,第 2天的房费是第1天的95%,第3天是第2天的95%,以此类推(暂不考虑这种 策略的经济效益)。设计一个程序让用户指定酒店和入住天数,然后计算并显示总费用。同时,程序要实现一份菜单,允许用户反复输入数据,除非用户选择退出。
程序清单9.9、程序清单9.10和程序清单9.11演示了如何编写这样的程序。第1个程序清单包含main()函数,提供整个程序的组织结构。第 2 个程序清单包含支持的函数,我们假设这些函数在独立的文件中。最后,程序清 单9.11列出了一个头文件,包含了该程序所有源文件中使用的自定义符号常量和函数原型。前面介绍过,在UNIX和DOS环境中,#include "hotels.h"指令中的双引号表明被包含的文件位于当前目录中(通常是包含源代码的目 录)。如果使用IDE,需要知道如何把头文件合并成一个项目。
程序清单9.9 usehotel.c控制模块
/* usehotel.c -- 房间费率程序 */
/* 与程序清单9.10一起编译 */
#include <stdio.h>
#include "hotel.h" /* 定义符号常量,声明函数 */int main(void)
{
int nights;
double hotel_rate;
int code;
while ((code = menu()) != QUIT)
{
switch (code)
{case 1: hotel_rate = HOTEL1;break;case 2: hotel_rate = HOTEL2;break;case 3: hotel_rate = HOTEL3;break;case 4: hotel_rate = HOTEL4;break;default: hotel_rate = 0.0;printf("Oops!\n");break;
}nights = getnights();showprice(hotel_rate, nights);
}
printf("Thank you and goodbye.\n");
return 0;
}
程序清单9.10 hotel.c函数支持模块
/* hotel.c -- 酒店管理函数 */
#include <stdio.h>
#include "hotel.h"int menu(void) //获取一个范围内的数字(1~5)--菜单选择
{
int code, status;
printf("\n%s%s\n", STARS, STARS);
printf("Enter the number of the desired hotel:\n");
printf("1) Fairfield Arms 2) Hotel Olympic\n");
printf("3) Chertworthy Plaza 4) The Stockton\n");
printf("5) quit\n");
printf("%s%s\n", STARS, STARS);
while ((status = scanf("%d", &code)) != 1 ||
(code < 1 || code > 5))
{
if (status != 1)
scanf("%*s"); // 处理非整数输入
printf("Enter an integer from 1 to 5, please.\n");
}
return code;
}int getnights(void) //获取到一个整数--入住天数
{
int nights;
printf("How many nights are needed? ");
while (scanf("%d", &nights) != 1)
{
scanf("%*s"); // 处理非整数输入
printf("Please enter an integer, such as 2.\n");
}
return nights;
}void showprice(double rate, int nights) //获取到价格
{
int n;
double total = 0.0;
double factor = 1.0;
for (n = 1; n <= nights; n++, factor *= DISCOUNT)
total += rate * factor;
printf("The total cost will be $%0.2f.\n", total);
}
程序清单9.11 hotel.h头文件
/* hotel.h -- 符号常量和 hotel.c 中所有函数的原型 */
#define QUIT 5
#define HOTEL1 180.00
#define HOTEL2 225.00
#define HOTEL3 255.00
#define HOTEL4 355.00
#define DISCOUNT 0.95
#define STARS "**********************************"
// 显示选择列表
int menu(void);
// 返回预订天数
int getnights(void);
// 根据费率、入住天数计算费用
// 并显示结果
void showprice(double rate, int nights);
运行示例(略)
顺带一提,该程序中有几处编写得很巧妙。
尤其是,menu()和getnights() 函数通过测试scanf()的返回值来跳过非数值数据,而且调用 scanf("%*s")跳至下一个空白字符。
//含义和while(getchar()!='\n')相同
注意,menu()函数中是如何检查非数值输入和超出范围的数据:
while ((status = scanf("%d", &code)) != 1 ||(code < 1 || code > 5))
以上代码段利用了C语言的两个规则:从左往右对逻辑表达式求值;一 旦求值结果为假,立即停止求值。在该例中,只有在scanf()成功读入一个整 数值后,才会检查code的值。
//输入一个范围值(一个数值且有在一个范围之内)的典型方法。
前面程序构建了两个函数,一个获取到整数,一个函数限定其范围,用这种方法可以将两个函数合成一个函数
用不同的函数处理不同的任务时应检查数据的有效性。当然,首次编写 menu()或getnights()函数时可以暂不添加这一功能,只写一个简单的scanf()即 可。待基本版本运行正常后,再逐步改善各模块。
9.5 查找地址:&运算符
指针(pointer)是C语言最重要的(有时也是最复杂的)概念之一,用于储存变量的地址。前面使用的scanf()函数中就使用地址作为参数。概括地说,如果主调函数不使用return返回的值,则必须通过地址才能修改主调函数中的值。接下来,我们将介绍带地址参数的函数。首先介绍一元&运算符 的用法。
一元&运算符给出变量的存储地址。如果pooh是变量名,那么&pooh是 变量的地址。可以把地址看作是变量在内存中的位置。假设有下面的语句:
pooh = 24;
假设pooh的存储地址是0B76(PC地址通常用十六进制形式表示)。那么,下面的语句: printf("%d %p\n", pooh, &pooh);
将输出如下内容(%p是输出地址的转换说明):
24 0B76
程序清单9.12中使用了这个运算符查看不同函数中的同名变量分别储存在什么位置。
程序清单9.12 loccheck.c程序
/* loccheck.c -- 查看变量被储存在何处 */
#include <stdio.h>
void mikado(int); /* 函数原型 */int main(void)
{
int pooh = 2, bah = 5; /* main()的局部变量 */
printf("In main(), pooh = %d and &pooh = %p\n", pooh,&pooh);
printf("In main(), bah = %d and &bah = %p\n", bah, &bah);
mikado(pooh);
return 0;
}void mikado(int bah) /* 定义函数 */
{
int pooh = 10; /* mikado()的局部变量 */
printf("In mikado(), pooh = %d and &pooh = %p\n", pooh,&pooh);
printf("In mikado(), bah = %d and &bah = %p\n", bah,&bah);
}
程序清单9.12中使用ANSI C的%p格式打印地址。我们的系统输出如 下:
In main(), pooh = 2 and &pooh = 0x7fff5fbff8e8
In main(), bah = 5 and &bah = 0x7fff5fbff8e4
In mikado(), pooh = 10 and &pooh = 0x7fff5fbff8b8
In mikado(), bah = 2 and &bah = 0x7fff5fbff8bc
实现不同,%p表示地址的方式也不同。然而,许多实现都如本例所 示,以十六进制显示地址。顺带一提,每个十六进制数对应4位,该例显示 12个十六进制数,对应48位地址。
该例的输出说明了什么?首先,两个pooh的地址不同,两个bah的地址 也不同。因此,和前面介绍的一样,计算机把它们看成4个独立的变量。其 次,函数调用mikado(pooh)把实际参数(main()中的pooh)的值(2)传递给 形式参数(mikado()中的bah)。注意,这种传递只传递了值。涉及的两个变 量(main()中的pooh和mikado()中的bah)并未改变。
//这个概念非常重要,函数实参传递给形参是值的传递,参数地址不会传递,程序为不同变量分配了不同的地址
我们强调第2 点,是因为这并不是在所有语言中都成立。例如,在 FORTRAN中,子例程会影响主调例程的原始变量。子例程的变量名可能与原始变量不同,但是它们的地址相同。但是,在 C语言中不是这样。每个C 函数都有自己的变量。这样做更可取,因为这样做可以防止原始变量被被调 函数中的副作用意外修改。然而,正如下节所述,这也带来了一些麻烦。
9.6 更改主调函数中的变量
有时需要在一个函数中更改其他函数的变量。例如,普通的排序任务中交换两个变量的值。假设要交换两个变量x和y的值。简单的思路是:
x = y;
y = x;
这完全不起作用,因为执行到第2行时,x的原始值已经被y的原始值替换了。因此,要多写一行代码,储存x的原始值:
temp = x; //交换值的时候临时存储值放在第一位,注意顺序
x = y;
y = temp;
上面这 3 行代码便可实现交换值的功能,可以编写成一个函数并构造一 个驱动程序来测试。
在程序清单9.13中,为清楚地表明变量属于哪个函数, 在main()中使用变量x和y,在intercharge()中使用u和v。
程序清单9.13 swap1.c程序
/* swap1.c -- 第1个版本的交换函数 */
#include <stdio.h>
void interchange(int u, int v); /* 声明函数 */int main(void)
{
int x = 5, y = 10;
printf("Originally x = %d and y = %d.\n", x, y);
interchange(x, y);
printf("Now x = %d and y = %d.\n", x, y);
return 0;
}void interchange(int u, int v) /* 定义函数 */
{
int temp;
temp = u;
u = v;
v = temp;
}
运行该程序后,输出如下:
Originally x = 5 and y = 10.
Now x = 5 and y = 10.
两个变量的值并未交换!我们在interchange()中添加一些打印语句来检查错误
(见程序清单9.14)。
程序清单9.14 swap2.c程序
/* swap2.c -- 查找swap1.c的问题 */
#include <stdio.h>
void interchange(int u, int v);int main(void)
{
int x = 5, y = 10;
printf("Originally x = %d and y = %d.\n", x, y);
interchange(x, y);
printf("Now x = %d and y = %d.\n", x, y);
return 0;
}void interchange(int u, int v)
{
int temp;
printf("Originally u = %d and v = %d.\n", u, v);
temp = u;
u = v;
v = temp;
printf("Now u = %d and v = %d.\n", u, v);
}
下面是该程序的输出:
Originally x = 5 and y = 10.
Originally u = 5 and v = 10.
Now u = 10 and v = 5.
Now x = 5 and y = 10.
看来,interchange()没有问题,它交换了u 和 v 的值。问题出在把结果 传回 main()时。interchange()使用的变量并不是main()中的变量。因此,交换 u和v的值对x和y的值没有影响!是否能用return语句把值传回main()?当然可以,在interchange()的末尾加上下面一行语句:
return(u);
然后修改main()中的调用:
x = interchange(x,y);
这只能改变x的值,而y的值依旧没变。用return语句只能把被调函数中 的一个值传回主调函数,但是现在要传回两个值。这没问题!不过,要使用指针。
9.7 指针简介
指针?什么是指针?从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址。在C语言中,指针有许多用法。本章将介绍如 何把指针作为函数参数使用,以及为何要这样用。
假设一个指针变量名是ptr,可以编写如下语句:
ptr = &pooh; // 把pooh的地址赋给ptr
对于这条语句,我们说ptr“指向”pooh。ptr和&pooh的区别是ptr是变量, 而&pooh是常量。或者,ptr是可修改的左值,而&pooh是右值。还可以把ptr 指向别处:
ptr = &bah; // 把ptr指向bah,而不是pooh
现在ptr的值是bah的地址。 要创建指针变量,先要声明指针变量的类型。假设想把ptr声明为储存 int类型变量地址的指针,就要使用下面介绍的新运算符。
说明:指针是C语言中最强大的功能,最精髓的部分,也是最难的部分。将会慢慢展开
9.7.1 间接运算符:*
假设已知ptr指向bah,如下所示:
ptr = &bah;
然后使用间接运算符*(indirection operator)找出储存在bah中的值,该 运算符有时也称为解引用运算符(dereferencing operator)。不要把间接运算 符和二元乘法运算符(*)混淆,虽然它们使用的符号相同,但语法功能不同。
val = *ptr; // 找出ptr指向的值
语句ptr = &bah;和val = *ptr;放在一起相当于下面的语句:
val = bah;
由此可见,使用地址和间接运算符可以间接完成上面这条语句的功能, 这也是“间接运算符”名称的由来。
小结:与指针相关的运算符
地址运算符:&
一般注解:
后跟一个变量名时,&给出该变量的地址。
示例: &nurse表示变量nurse的地址。
地址运算符:* 一般注解: 后跟一个指针名或地址时,*给出储存在指针指向地址上的值。
示例: nurse = 22;
ptr = &nurse; // 指向nurse的指针
val = *ptr; // 把ptr指向的地址上的值赋给val
执行以上3条语句的最终结果是把22赋给val。 //相当于val=22
9.7.2 声明指针
相信读者已经很熟悉如何声明int类型和其他基本类型的变量,那么如何声明指针变量?你也许认为是这样声明:
pointer ptr; // 不能这样声明指针
为什么不能这样声明?因为声明指针变量时必须指定指针所指向变量的类型,因为不同的变量类型占用不同的存储空间,一些指针操作要求知道操 作对象的大小。另外,程序必须知道储存在指定地址上的数据类型。long和 float可能占用相同的存储空间,但是它们储存数字却大相径庭。下面是一些指针的声明示例:
int * pi; // pi是指向int类型变量的指针
char * pc; // pc是指向char类型变量的指针
float * pf, * pg; // pf、pg都是指向float类型变量的指针
类型说明符表明了指针所指向对象的类型,星号(*)表明声明的变量 是一个指针。int * pi;声明的意思是pi是一个指针,*pi是int类型(见图 9.5)
*和指针名之间的空格可有可无。通常,程序员在声明时使用空格,在解引用变量时省略空格。
pc指向的值(*pc)是char类型。pc本身是什么类型?我们描述它的类型 是“指向char类型的指针”。pc 的值是一个地址,在大部分系统内部,该地址由一个无符号整数表示。但是,不要把指针认为是整数类型。一些处理整数 的操作不能用来处理指针,反之亦然。例如,可以把两个整数相乘,但是不能把两个指针相乘。所以,指针实际上是一个新类型,不是整数类型。因 此,如前所述,ANSI C专门为指针提供了%p格式的转换说明。
9.7.3 使用指针在函数间通信
我们才刚刚接触指针,指针的世界丰富多彩。本节着重介绍如何使用指针解决函数间的通信问题。请看程序清单9.15,该程序在interchange()函数中使用了指针参数。稍后我们将对该程序做详细分析。
程序清单9.15 swap3.c程序
/* swap3.c -- 使用指针解决交换函数的问题 */
#include <stdio.h>
void interchange(int * u, int * v);int main(void)
{
int x = 5, y = 10;
printf("Originally x = %d and y = %d.\n", x, y);
interchange(&x, &y); // 把地址发送给函数
printf("Now x = %d and y = %d.\n", x, y);
return 0;
}void interchange(int * u, int * v)
{
int temp;
temp = *u; // temp获得 u 所指向对象的值
*u = *v;
*v = temp;
}
该程序是否能正常运行?下面是程序的输出:
Originally x = 5 and y = 10.
Now x = 10 and y = 5.
没问题,一切正常。接下来,我们分析程序清单9.15的运行情况。首先 看函数调用: interchange(&x, &y);
该函数传递的不是x和y的值,而是它们的地址。这意味着出现在 interchange()原型和定义中的形式参数u和v将把地址作为它们的值。因此, 应把它们声明为指针。由于x和y是整数,所以u和v是指向整数的指针,其声明如下:
void interchange (int * u, int * v)
接下来,在函数体中声明了一个交换值时必需的临时变量:
int temp;
通过下面的语句把x的值储存在temp中:
temp = *u;
记住,u的值是&x,所以u指向x。这意味着用*u即可表示x的值,这正是我们需要的。不要写成这样:
temp = u; /* 不要这样做 */
因为这条语句赋给temp的是x的地址(u的值就是x的地址),而不是x的值。函数要交换的是x和y的值,而不是它们的地址。
与此类似,把y的值赋给x,要使用下面的语句:
*u = *v;
这条语句相当于:
x = y;
我们总结一下该程序示例做了什么。我们需要一个函数交换x和y的值。 把x和y的地址传递给函数,我们让interchange()访问这两个函数。使用指针和*运算符,该函数可以访问储存在这些位置的值并改变它们。
可以省略ANSI C风格的函数原型中的形参名,如下所示:
void interchange(int *, int *);
//这样做并不好,前面说过虽然函数形参名没意义,也可以不写,但还是不要空着
一般而言,可以把变量相关的两类信息传递给函数。如果这种形式的函数调用,那么传递的是x的值:
function1(x); //第1种形式的函数调用;
如果下面形式的函数调用,那么传递的是x的地址:
function2(&x); //第2种形式的函数调用;
第1种形式要求函数定义中的形式参数必须是一个与x的类型相同的变量:
int function1(int num) //第1种形式的函数原型;
第2种形式要求函数定义中的形式参数必须是一个指向正确类型的指针:
int function2(int * ptr) //第2种形式的函数原型
如果要计算或处理值,那么使用第 1 种形式的函数调用;
如果要在被调函数中改变主调函数的变量,则使用第2种形式的函数调用。
我们用过的 scanf()函数就是这样。当程序要把一个值读入变量时(如本例中的num), 调用的是scanf("%d", &num)。scanf()读取一个值,然后把该值储存到指定的地址上。
//scanf()函数输入了一个值并放在了一个地址上,相当于改变了变量
对本例而言,指针让interchange()函数通过自己的局部变量改变main()中 变量的值
熟悉Pascal和Modula-2的读者应该看出第1种形式和Pascal的值参数相同,第2种形式和Pascal的变量参数类似。C++程序员可能认为,既然C和 C++都使用指针变量,那么C应该也有引用变量。让他们失望了,C没有引用变量。对BASIC程序员而言,可能很难理解整个程序。如果觉得本节的内容晦涩难懂,请多做一些相关的编程练习,你会发现指针非常简单实用(见 图9.6)
变量:名称、地址和值
通过前面的讨论发现,变量的名称、地址和变量的值之间关系密切。我们来进一步分析。
编写程序时,可以认为变量有两个属性:名称和值(还有其他性质,如类型,暂不讨论)。计算机编译和加载程序后,认为变量也有两个属性:地址和值。地址就是变量在计算机内部的名称。
在许多语言中,地址都归计算机管,对程序员隐藏。然而在 C 中,可以通过&运算符访问地址,通过*运算符获得地址上的值。例如,&barn表示 变量barn的地址,使用函数名即可获得变量的数值。例如,printf("%d\n", barn)打印barn的值,使用*运算符即可获得储存在地址上的值。如果pbarn= &barn;,那么*pbarn表示的是储存在&barn地址上的值。
简而言之,普通变量把值作为基本量,把地址作为通过&运算符获得的 派生量,而指针变量把地址作为基本量,把值作为通过*运算符获得的派生量。
虽然打印地址可以满足读者好奇心,但是这并不是&运算符的主要用途。更重要的是使用&、*和指针可以操纵地址和地址上的内容,如swap3.c 程序(程序清单9.15)所示。
说明:使用指针的核心目的使用&和*运算符操纵地址,以及地址上的内容
小结:函数
形式:
典型的ANSI C函数的定义形式为:
返回类型 名称(形参声明列表)
函数体
形参声明列表是用逗号分隔的一系列变量声明。除形参变量外,函数的其他变量均在函数体的花括号之内声明。
示例:
int diff(int x, int y) // ANSI C
{ // 函数体开始
int z; // 声明局部变量
z = x - y;
return z; // 返回一个值
} // 函数体结束
传递值:
实参用于把值从主调函数传递给被调函数。如果变量a和b的值分别是5和2,那么调用:
c = diff(a,b);
把5和2分别传递给变量x和y。5和2称为实际参数(简称实参),diff()函数定义中的变量x和y称为形式参数(简称形参)。使用关键字return把被调 函数中的一个值传回主调函数。本例中, c接受z的值3。被调函数一般不会 改变主调函数中的变量,如果要改变,应使用指针作为参数。如果希望把更 多的值传回主调函数,必须这么做。
函数的返回类型:
函数的返回类型指的是函数返回值的类型。如果返回值的类型与声明的 返回类型不匹配,返回值将被转换成函数声明的返回类型。
函数签名:
函数的返回类型和形参列表构成了函数签名。因此,函数签名指定了传入函数的值的类型和函数返回值的类型。
示例:
double duff(double, int); // 函数原型int main(void)
{
double q, x;
int n;
...
q = duff(x,n); //函数调用
...
}double duff(double u, int k) //函数定义
{
double tor;
...
return tor; //返回double类型的值
}
典型的函数使用形式--包括函数原型,函数调用和函数定义
9.8 关键概念
如果想用C编出高效灵活的程序,必须理解函数。把大型程序组织成若干函数非常有用,甚至很关键。如果让一个函数处理一个任务,程序会更好理解,更方便调试。要理解函数是如何把信息从一个函数传递到另一函数, 也就是说,要理解函数参数和返回值的工作原理。另外,要明白函数形参和 其他局部变量都属于函数私有,因此,声明在不同函数中的同名变量是完全 不同的变量。而且,函数无法直接访问其他函数中的变量。这种限制访问保 护了数据的完整性。但是,当确实需要在函数中访问另一个函数的数据时, 可以把指针作为函数的参数。
9.9 本章小结
函数可以作为组成大型程序的构件块。每个函数都应该有一个单独且定义好的功能。使用参数把值传给函数,使用关键字return把值返回函数。如果函数返回的值不是int类型,则必须在函数定义和函数原型中指定函数的类型。如果需要在被调函数中修改主调函数的变量,使用地址或指针作为参 数。
ANSI C提供了一个强大的工具——函数原型,允许编译器验证函数调用中使用的参数个数和类型是否正确。
C 函数可以调用本身,这种调用方式被称为递归。一些编程问题要用递归来解决,但是递归不仅消耗内存多,效率不高,而且费时。
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- SQL30 使用子查询的方式找出属于Action分类的所有电影对应的title,description
文章目录1. 题目2. 题解3. 反思:子查询1. 题目 题目来源:SQL30 使用子查询的方式找出属于Action分类的所有电影对应的title,description 题目描述 使用子查询的方式找出属于Action分类的所有电影对应的title,description 三张表 2. 题解 注意&#…...
2024/4/18 13:17:42 - SQL29 使用join查询方式找出没有分类的电影id以及名称
文章目录1. 题目2. 题解1. 题目 题目来源:SQL29 使用join查询方式找出没有分类的电影id以及名称 题目描述 使用join查询方式找出没有分类的电影id以及其电影名称。 2. 题解 这题主要考察LEFT JOIN,以及LEFTJOIN搭配IS NULL SELECT f.film_id, f.t…...
2024/4/26 17:52:18 - 软硬链接的区别
硬链接:新建的文件是已经存在的文件的一个别名,当原文件删除时,新建的文件仍然可以使用. 软链接:也称为符号链接,新建的文件以“路径”的形式来表示另一个文件,和Windows的快捷方式十分相似,…...
2024/4/20 12:34:54 - SQL32 将employees表的所有员工的last_name和first_name拼接起来作为Name
文章目录1. 题目2. 题解1. 题目 题目来源:SQL32 将employees表的所有员工的last_name和first_name拼接起来作为Name 题目描述 将employees表的所有员工的last_name和first_name拼接起来作为Name,中间以一个空格区分。 (注:sqllite,字符串拼接…...
2024/5/9 14:01:31 - js 输入与输出
<!DOCTYPE html><html lang"en" xmlns"http://www.w3.org/1999/xhtml"> <head><meta charset"utf-8" /><title></title><!-- 2.内嵌式的js --><script>//prompt 这是一个输入框prompt(请输入你…...
2024/4/13 5:02:45 - 微信小程序登录失败(请使用真实的appid,并检查服务器端配置)
项目:美食屋 服务端的appId、和Appsecret要和微信开发者平台上的一致否者就会报错 参考微信小程序的APPID问题_铁柱的博客-CSDN博客_微信小程序appid必须注册吗...
2024/4/13 5:02:45 - 北太平洋西部热带气旋快速增强的上层海洋热含量和海面温度的探索性分析
第一篇: 北太平洋西部热带气旋快速增强的上层海洋热含量和海面温度的探索性分析 摘要 研究了1998年至2016年北太平洋西部具有快速强化(RI)的热带气旋(TCs)与上层海洋热含量(UOHC)和海面温度&…...
2024/5/9 14:00:58 - 2022/02/12
构造函数:由于类的数据成员不能在类的声明时初始化,所以引入了构造函数来进行初始化。语法特点:没有返回值。 public:Test(){}析构函数:与构造函数相反,当跑出作用域时,自动执行析构函数&#…...
2024/4/25 16:09:47 - uniapp状态栏设置
非H5端,手机顶部状态栏区域会被页面内容覆盖。这是因为窗体是沉浸式的原因,即全屏可写内容。uni-app提供了状态栏高度的css变量--status-bar-height,如果需要把状态栏的位置从前景部分让出来,可写一个占位div,高度设为…...
2024/4/19 14:05:57 - golang力扣leetcode 1020.飞地的数量
1020.飞地的数量1020.飞地的数量题解代码1020.飞地的数量 1020.飞地的数量 题解 BFS或者DFS,这题的思路就是把边框当作搜索的点,那么能搜索到的就是能走通的,即代码中的vis,进行遍历即可算出搜索不到的,就是答案了 …...
2024/5/5 12:40:13 - C语言的关键字
说明 以下关键字顺序已按学习先后顺序编排。 关键字用途void定义空类型变量或空类型指针或指定函数无返回值int定义整型变量或指针short定义短整型变量或指针long定义长整型变量或指针long long定义长长整型变量或指针float定义浮点型变量或指针double定义双精度浮点型变量ch…...
2024/4/13 5:03:40 - CSS学习09之超链接伪类
回顾 文字图片水平对齐 img,span{border: red solid 2px;/*水平对齐*/vertical-align: middle; }超链接伪类 超链接伪类(我们常用的就这两个): hover 鼠标悬停 active 鼠标选择(按压) 简单应用 相信大家淘宝都在用…...
2024/5/5 4:04:15 - 大话 Git 版本控制系统 (一)
前言 为了向零基础的 Git 用户讲述全球最流行的版本控制系统 Git 的原理和操作,故撰写此文,欢迎读者批评斧正。 适用读者 本文面向的是无任何版本控制工具使用经验的读者,尤其是非计算机专业人士。 使用文件夹进行备份 下面我们先来尝试…...
2024/5/5 11:04:42 - 前端工程化与webpack简单使用(2) 最终版
前端工程化与webpack简单使用1.前端工程化1.1实际的前端开发1.2什么是前端工程化1.3前端工程化的解决方案早期目前主流2.webpack的基本使用2.1什么是 webpack2.2隔行变色案例2.3安装webpack2.4配置webpack2.4.1基本配置2.4.2压缩2.4.3webpack中的默认约定2.4.4自定义打包的入口…...
2024/5/5 14:53:14 - 2022.2.12 StudyLog
文章目录 查看显卡属性神经网络 专业术语解释(Step, Batch Size, Iteration,Epoch)提高 GPU 和 CPU 混合训练效率的方法py文件调用其他py文件中的类和函数关于pyc与pyo文件解释器工作:sys.modules可以打印出导入的模块名与模块对象的映射阅读 python 基础书可能有用的论文…...
2024/4/13 5:03:45 - 前端axios下载文件(word、pdf等)
前端使用axios调后端接口来处理文件(word、pdf等) 考虑到项目后期维护,自己封装了axios方法,方便项目后期更换服务器 api.js import axios from axios axios.defaults.withCredentials true // axios.defaults.headers.common…...
2024/5/5 12:01:39 - filebeat收集tomcat,nginx日志,elasticsearch,logstash,kibana实现地图显示用户所在城市
filebeat收集tomcat,nginx日志,elasticsearch,logstash,kibana1 filebeat收集tomcat,nginx日志发送给redis-->logstash --> elasticsearch实现日志分类缓存及写入到Redis不同的index1.1 安装elasticsearch1.2 安装redis,log…...
2024/5/5 5:30:43 - Socket编程 调试总结(笔记)
实现功能:采用Socket,通过ESP32CAM向云端服务器传输图片 客户端:C (软件:esp-idf)(硬件:ESP32CAM) 服务端:Python 问题总结 1、客户端向服务端发送图片时&…...
2024/4/13 5:03:30 - 异步与Promise
异步与Promise(面试必考) AJAX(Async JavaScript And XML) 内容:Ajax异步编程在js里的统一解决方案(js异步编程模型) Promise 什么是异步?什么是同步? 同步:能直接拿到结果 比如你在医院挂号,拿到号才会离开窗口。 同步任务可能消…...
2024/4/15 8:09:43 - VSCode下载安装及修改插件下载位置和配置右键菜单及Java环境
1. 下载 1、 进入官网 2、点击 Download 进入下载页面 下载需要的版本 2. 安装 双击打开,同意协议,下一步 选择安装路径,下一步 下一步 根据情况勾选,下一步 安装 完成 3. 修改插件位置 默认安装在 C盘用户目录下的…...
2024/4/20 11:15:11
最新文章
- 基于Django图像识别系统毕业设计(付源码)
前言:Django是一个由Python编写的具有完整架站能力的开源Web框架,Django本身基于MVC模型,即Model(模型)View(视图) Controller(控制器)设计模式,因此天然具有…...
2024/5/10 12:26:54 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/5/9 21:23:04 - MySQL 底层数据结构 聚簇索引以及二级索引 Explain的使用
数据结构 我们知道MySQL的存储引擎Innodb默认底层是使用B树的变种来存储数据的 下面我们来复习一下B树存储 B树存储 哈希存储的区别 哈希存储,只能使用等值查询 B树与B树存储 我们知道B树实际上就是B树的变种 那么为啥使用B树而不是使用B树呢? 我们知道效率的高低主要取决于…...
2024/5/10 0:17:33 - 不重复数字
map就感觉很舒服 题目描述 给定 n 个数,要求把其中重复的去掉,只保留第一次出现的数。 输入格式 本题有多组数据。 第一行一个整数 T,表示数据组数。 对于每组数据: 第一行一个整数 n。 第二行 n 个数,表示给定的数。…...
2024/5/10 0:18:23 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/8 6:01:22 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/5/9 15:10:32 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/5/4 23:54:56 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/5/9 4:20:59 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/5/4 23:54:56 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/5/4 23:55:05 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/5/4 23:54:56 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/5/7 11:36:39 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/5/4 23:54:56 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/5/6 1:40:42 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/5/4 23:54:56 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/5/8 20:48:49 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/5/7 9:26:26 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/5/4 23:54:56 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/8 19:33:07 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/5/5 8:13:33 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/5/8 20:38:49 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/5/4 23:54:58 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/5/10 10:22:18 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/5/9 17:11:10 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57