第二夜:函数、作用域、预处理和头文件

今晚还是在研究相对轻松的内容。主要涉及C语言的函数、作用域、预处理和头文件。

函数的声明

C语言中函数的定义:

1
2
3
4
return_type function_name( parameter list )
{
body of the function
}

C语言中函数的传值调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

void swap(int x, int y)
{
int temp;

temp = x;
x = y;
y = temp;

return;
}
int main ()
{
/* 局部变量定义 */
int a = 100;
int b = 200;

printf("交换前,a 的值: %d\n", a );
printf("交换前,b 的值: %d\n", b );

swap(a, b);

printf("交换后,a 的值: %d\n", a );
printf("交换后,b 的值: %d\n", b );

return 0;
}

C语言函数的引用调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>

void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */

return;
}

int main ()
{
/* 局部变量定义 */
int a = 100;
int b = 200;

printf("交换前,a 的值: %d\n", a );
printf("交换前,b 的值: %d\n", b );

/* 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
swap(&a, &b);

printf("交换后,a 的值: %d\n", a );
printf("交换后,b 的值: %d\n", b );

return 0;
}

C语言可变参数的函数:

在C语言标准库中,stdarg.h头文件用于支持可变参数的函数,该文件提供了实现可变参数功能的函数和宏。具体步骤如下:
定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
使用宏 va_end 来清理赋予 va_list 变量的内存。

具体操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdarg.h>

//一个求平均数的函数
double average(int num,...){
va_list valist;
double sum = 0.0;
int i;

/* 为 num 个参数初始化 valist */
va_start(valist, num);

/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++){
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);

return sum/num;
}

int main(){
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

函数的作用域

  1. 在函数或块内部声明的是的局部变量
    局部变量只能在函数内部使用。

  2. 在所有函数外部声明的是全局变量
    全局变量即可在函数内部使用,也可在函数外部使用。全局变量通常声明在程序的顶部,其在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。

局部变量和全局变量的名称可以相同,但是在函数内,如果两个名字相同,会使用局部变量值,全局变量不会被使用。

全局变量与局部变量在内存中的区别:

全局变量保存在内存的全局存储区中,占用静态的存储单元;
局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。

具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>  
#include <stdlib.h>
int k1 = 1;
int k2;
static int k3 = 2;
static int k4;
int main(){
static int m1 = 2, m2;
int i = 1;
char*p;
char str[10] = "hello";
char*q = "hello";
p = (char *)malloc(100);
free(p);
printf("栈区-变量地址 i:%p\n", &i);
printf("栈区-变量地址 p:%p\n", &p);
printf("栈区-变量地址 str:%p\n", str);
printf("栈区-变量地址 q:%p\n", &q);
printf("堆区地址-动态申请:%p\n", p);
printf("全局外部有初值 k1:%p\n", &k1);
printf(" 外部无初值 k2:%p\n", &k2);
printf("静态外部有初值 k3:%p\n", &k3);
printf(" 外静无初值 k4:%p\n", &k4);
printf(" 内静态有初值 m1:%p\n", &m1);
printf(" 内静态无初值 m2:%p\n", &m2);
printf(" 文字常量地址:%p, %s\n", q, q);
printf(" 程序区地址:%p\n", &main);
return 0;
}

/*
执行结果:
栈区-变量地址 i:0xff9f9e8c
栈区-变量地址 p:0xff9f9e88
栈区-变量地址 str:0xff9f9e7e
栈区-变量地址 q:0xff9f9e78
堆区地址-动态申请:0x574ef160
全局外部有初值 k1:0x56644024
外部无初值 k2:0x5664403c
静态外部有初值 k3:0x56644028
外静无初值 k4:0x56644034
内静态有初值 m1:0x5664402c
内静态无初值 m2:0x56644038
文字常量地址:0x56642008, hello
程序区地址:0x566411b9
*/

  1. C语言的形参与实参

形参(形式参数)
在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
实参(实际参数)
函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。

形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。

形参和实参的区别和联系

  1. 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。

  2. 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。

  3. 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。

  4. 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。

预处理和头文件

C语言的预处理器

C 预处理器相当于一个文本替换工具,会指示编译器在实际编译之前完成所需的预处理。C 预处理器(C Preprocessor)简写为 CPP。
所有的C预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
下面列出了所有重要的预处理器指令:

指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中

一些样例:

1
2
3
//这个指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42。
#undef FILE_SIZE
#define FILE_SIZE 35
1
2
3
4
//这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。
#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif
1
2
3
4
5
6
7
#ifdef DEBUG
/* Your debugging statements here */
#endif
/*
这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,如果向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用。
它定义了 DEBUG,可在编译期间随时开启或关闭调试。
*/

预定义宏

ANSI C 定义了许多宏。在编程中可以使用这些宏,但是不能直接修改这些预定义的宏。

描述
__DATE__ 当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量。
__TIME__ 当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。
__FILE__ 这会包含当前文件名,一个字符串常量。
__LINE__ 这会包含当前行号,一个十进制常量。
__STDC__ 当编译器以 ANSI 标准编译时,则定义为 1。

C语言头文件
头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。

只引用一次头文件

1
2
3
4
5
6
#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

有条件引用

有时需要从多个不同的头文件中选择一个引用到程序中。

例如,需要指定在不同的操作系统上使用的配置参数:

1
2
3
4
5
6
7
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
第三夜:指针和数组 第一夜:编译器、语法关键字、运算符、变量及数据类型
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×