0
点赞
收藏
分享

微信扫一扫

C Primer Plus 第9章(函数)

五殳师兄 2022-02-08 阅读 103
c语言

目录

1. 函数简介

  • 函数(function) 是完成特定任务的独立程序代码单元
  • 使用函数的原因
    • 使用函数可以省去编写重复代码的时间
    • 函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善

1.1 创建并使用简单函数

#include <stdio.h>
// 函数原型
void ABC();
int main() {
    ABC();	// 函数调用
}
// 函数定义
void ABC() {
    printf("ABC\n");
}

// 等价于

#include <stdio.h>
// 函数原型
void ABC(void);
int main(void) {
    ABC();	// 函数调用
}
// 函数定义
void ABC(void) {
    printf("ABC\n");
}

1.2 分析程序

  • 函数原型(function prototype):告诉编译器函数的类型
  • 函数调用(function call):在此处使用函数
  • 函数定义(function definition):明确指出函数要执行的操作
  • 一般而言,函数原型指明了函数的返回值类型和函数接收的参数类型;这些信息被称为该函数的签名(signature)
  • 函数中定义的变量是局部变量(local variable),该变量只属于该函数;可以在程序中的其他地方使用同名变量,不会引起名称冲突,是同名的不同变量

1.3 函数参数

#include <stdio.h>

void ABC(int);

int main() {
    ABC(1);
}

void ABC(int i) {
    printf("%d\n", i);
}

1.4 定义带形式参数的函数

  • 在函数定义时声明的参数被称为形式参数(formal argument)
  • 形式参数也时局部变量,该函数私有
  • 用圆括号括起来的参数体被称为参数列表
void a(int i, char c)

// 等效于

void a(i, c)
int i;
char c;

1.5 调用带实际参数的函数

  • 在函数表用中,实际参数(actual argument) 提供了形式参数的值
  • 形式参数是被调函数(called function) 中的变量
  • 实际参数是主调函数(calling function) 赋给被调函数的具体值
  • 实际参数是具体的值,该值要被赋给作为形式参数的变量
  • 因为被调函数使用的值是从主调函数中拷贝而来,所以无论被调函数对拷贝数据进行什么操作,都不会影响主调函数中的原始数据

1.6 黑盒视角

  • 黑盒方法的核心:函数私有的局部变量
  • 黑盒中发生了什么对主调函数是不可见的

1.7 使用 return 从函数中返回值

  • 被这几用于测试函数的程序有时被称为驱动程序(driver),该驱动程序调用一个函数
#include <stdio.h>

float ABC(int, int);

int main() {

    printf("%f\n", ABC('A', 'a'));
    // 65.000000
    printf("%f\n", ABC(1.11, 1.23));
    // 1.000000

}

float ABC(int i, int j) {
    return (i < j) ? i : j;
    printf("ABC");
}

1.8 函数类型

  • 声明函数时必须声明函数的类型
  • 带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应声明为 void 类型

1.9 所有的C函数皆平等

  • 每个函数都可以调用其他函数,或被其他函数调用
  • main() 函数特殊:
    • 当 main() 函数与程序中的其他函数放在一起时,最开始执行的是 main() 函数中的第1条语句
    • main() 函数也可以被自己或其他函数递归调用

2. ANSI C 函数原型

与原书不同,自写

  • 函数调用的参数列表中的参数数量,只与函数原型有关,参数可以多,但是不可以少
  • 函数定义与函数原型没有一点关系,参数类型可以不同,参数数量可以不同,只有返回值类型必须相同
  • 函数调用时,只会根据函数原型的参数类型和数量,向函数定义传参
    • 先根据函数原型的参数类型和数量,获取函数调用时的参数
      • 调用时按照从左往右的顺序获取参数
      • 并将参数的数据类型按照函数原型中的数据类型一一转换
    • 之后将转换成的参数传入函数定义中
    • 函数定义处理传入的参数
      • 传入参数数量 > 所需参数数量:不影响函数使用
      • 传入参数数量 = 所需参数数量:不影响函数使用
      • 传入参数数量 < 所需参数数量:函数会使用栈(stack) 中的其他值将缺失数据补全

3. 递归

  • C允许函数调用它自己,这种调用过程称为递归(recursion)

3.1 尾递归

  • 最简单的递归形式时把递归调用置于函数的末尾,这种形式的递归被称为尾递归(tail recursion)
#include <stdio.h>

int value = 1;
int ABC(int);

int main() {

	int i = 0;

	printf("i = %d, value = %d\n", i, ABC(i));

}

int ABC(int i) {
	if (i >= 1)
	{
		value *= i;
		return ABC(i - 1);
	}
	else
	{
		return value;
	} 
}

// 优化

#include <stdio.h>

int ABC(int);

int main() {

	int i = 3;
	printf("i = %d, value = %d\n", i, ABC(i));

}

int ABC(int i) {
	return (i >= 1) ? i * ABC(i - 1) : 1;
}

3.2 递归的优缺点

  • 优点:递归为某些编程问题提供了最简单的解决方案
  • 缺点:
    • 一些递归算法会快速消耗计算机的内存资源
    • 递归不方便阅读和维护

4. 编译多源代码文件的程序

  • 使用多个函数最简单的方法是把它们都放在同一个文件中,然后像编译只有一个函数的文件那样编译该文件即可

4.1 UNIX

cc file1.c file2.c
  • 此命令将编译两个文件并生成一个名为 a.out 的可执行文件,两个分别名为 file1.o 和 file2.o 的目标文件
cc file1.c file2.o
  • 如果后来改动了 file1.c,而 file2.c 不变,可以使用此命令编译第1个文件,并与第2个文件的目标代码合并

4.2 Linux

gcc file1.c file2.c
  • 此命令将编译两个文件并生成一个名为 a.out 的可执行文件,两个分别名为 file1.o 和 file2.o 的目标文件
gcc file1.c file2.o
  • 如果后来改动了 file1.c,而 file2.c 不变,可以使用此命令编译第1个文件,并与第2个文件的目标代码合并

4.3 DOS 命令行编译器

  • 绝大多数 DOS 命令行编译器的工作原理和 UNIX 的 cc 命令类似
  • 其中的一个区别为:对象文件的扩展名是 .obj,而不是 .o
  • 一些编译器生成的不是目标代码文件,而是汇编语言或其他特殊代码的中间文件

4.4 Windows 和苹果的 IDE 编译器

  • Windows 和 Macintosh 系统使用的集成开发环境中的编译器是面向项目

  • 项目(project) 描述的是特定程序使用的资源

  • 资源包括源代码文件

4.5 使用头文件

  • Test.c
#include <stdio.h>
#include "Head.h"

int main() {
	int i = 3;
	printf("i = %d, value = %d\n", i, ABC(i));
}
  • Achieve.c
#include <stdio.h>
#include "Head.h"

int ABC(int i) {
	return (i >= 1) ? i * ABC(i - 1) : 1;
}
  • Head.h
int ABC(int i);

5. 查找地址:&运算符

  • 指针(pointer) 是C语言最重要的概念之一,用于存储变量的地址
  • 一元 & 运算符给出变量的存储地址
int i = 1;
printf("i = %d, &i = %#x = %p\n", i, &i);

6. 更改主调函数中的变量

  • 交换两个变量的数值
int a = 1;
int b = 2;
int i = 0;

printf("a = %d, b = %d\n", a, b);
i = b;
b = a;
a = i;
printf("a = %d, b = %d\n", a, b);
  • 如像通过函数交换主调函数中两个变量的值,需要使用指针

7. 指针简介

  • 指针(pointer) 是一个值为内存地址的变量(或数据对象)

7.1 间接运算符:*

  • 间接运算符(indirection operator) 可以用来获取存储在地址中的值,该运算符有时也称为 解引运算符(dereferencing operator)
i = &a;
j = *i;
// 等效于
j = a;

7.2 声明指针

  • 声明指针变量时必须指定指针所指向变量的类型

    • 因为不用的变量类型占用不同的内存空间,一些指针操作要求知道操作对象的大小
    • 另外,程序必须知道存储在指定地址上的数据类型
    int * a;			// a 是指向 int 类型变量的指针,*a 是 int 类型
    char * b;
    float * c, * d;
    
  • 指针名和 * 之间的空格可有可无

    • 程序员在声明时使用空格
    • 在解引用变量时省略空格

7.3 使用指针在函数间通信

#include <stdio.h>

void change(int *, int *);

int main() {
	int a = 1;
	int b = 2;
	
	printf("a = %d, b = %d\n", a, b);
	change(&a, &b);
	printf("a = %d, b = %d\n", a, b);

}

void change(int * a, int * b) {
	int i;
	i = *b;
	*b = *a;
	*a = i;
}
举报

相关推荐

0 条评论