4.0. 函数

程序的最小独立单元 - 语句
程序:为解决某一问题而设计的一系列有序指令的集合。
数据说明:数据的描述(数据的名称、类型、和初值等)
语句:如何处理数据的描述

函数的定义:

函数就是对某一特定功能的抽象

函数的作用:

代码重用,模块化(便于定位错误)

例:编写一个加法函数,参数为两个值,无返回值。

函数的分类

  • 无参函数

通过用来执行一些功能比较单一的语句
例如:getchar()

  • 有参函数
  • 通过处理传递过来的参数将函数值返回给主调函数

Sin(),cos()

  • 库函数

库函数分为标准库函数和第三方库函数。
库函数的特点:右C语言提供;用户无需定义,也不必再程序中做类型说明,只想要在程序前包含有该函数的头文件。
典型的如:system()

  • 函数定义的语法格式:
    函数定义有4个要素:参数列表,返回类型,函数名和函数体,参数列表和返回值类型,函数名用于和程序中其他实体区分,而函数体是一段可执行的代码块,实现特定的算法或功能。

    函数调用的两种方式

  1. 函数调用有两种类型,一是”先定义,后调用”,这要求函数定义和调用语句在同一个文件内,编译器能从函数定义中提取函数的参数列表、输出类型等接口信息。

如果没有函数的定义,就想要将函数实例写在主调用函数之前。
举例:函数声明和调用在一个文件内:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

void add(int a,int b);

int main() {

int num1,num2;
printf("请输入数值a:");
scanf("%d", &num1);
printf("\n请输入数值b:");
scanf("%d", &num2);
add(num1,num2);
system("pause");
return 30;
}

void add(int a, int b) {
int c = a + b;
printf("a+b = %d\n", c);
}
  1. 二是”函数声明+函数调用” 大多数情况下,函数的定义与函数的调用,并吧在一个文件内,即使在一个文件中也有可能调用在前而定义在后,这时想要,在调用之前先对函数声明,告诉编译器有这么一个函数的存在。

举例:函数的跨文件调用

/* 头文件 函数.h */
#pragma once // 防止头文件重复包含
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

void Hello() {
printf("Hello World\n");
}
// 源文件
#include "4.0 函数.h"

/* 函数的定义 */
void add(int a,int b);

int main() {

int num1,num2;
printf("请输入数值a:");
scanf("%d", &num1);
printf("\n请输入数值b:");
scanf("%d", &num2);
add(num1,num2);
Hello();
system("pause");
return 30;
}

void add(int a, int b) {
int c = a + b;
printf("a+b = %d\n", c);
}

函数注意事项:

  • 函数的定义在程序中都是平行的,即不允许在一个函数的内部调用(再写)另一个函数。
  • 函数名是用户自定义标识符,当函数值为整型时类型名可省略。当函数不需要向调用出返回值时,使用void类型名。
  • 形参列表中的形参是用户自定义标识符,没有参数时,圆括号不能省略,此时函数为无参函数。
  • 函数在没有声明的时候必须写在主调函数Main之前否则会出错。因为程序是自上而下执行的

    4.1. 数组

    数组的概念

  1. 数组的概念:

数组是可以在内存中连续存储多个元素的结构
数组中的所有元素必须属于相同的数据类型
举例:int a[5] = {0,1,2,3,4}; printf(“&a”);
在内存中查看连续的5个元素

  1. 数组语法

类型数组名[数组元素个数] = {数组元素};

例如:int a[10] = {0};

int a[] = {1,2,3}; // 当数组元素确定的时候数组个数可以省略

int a[5] = {1,2,3}; // 不足填充0

printf(“%d\n”, sizeof(a) / sizeof(int));// 查看数组元素的个数

一组数组就好比对货物的编号。
注意:数组的下标是从0开始的。数组在内存中是连续排列的
告诉编译器4条信息:数组名是a,存放的元素是int型,数组存放的元素个数为10,元素值为0,这样便可以对数组及数组元素进行读写访问。

#include <stdio.h>
#include <stdlib.h>
#define N 5 // 只需要修改常量就可以修改数组和循环函数的值

void main() {

int a[N] = {1,2,3,4,5};

for (int i = 0; i < N; i++) {
printf("%d\n", a[i]);
}

printf("%p\n", &a); // %p用来输出指针类型自身的值

printf("%d\n", sizeof(a)); // 查看a数组大小 1int = 4 4*5 = 20字节
printf("%d\n", sizeof(a) / sizeof(int)); // 查看数组元素的个数

/* 循环打印数组地址 */
for (int i = 0; i < N; i++) {
printf("a[%d] = %d &a[%d] = %x\n", i, a[i], i, &a[i]); //X 打印地址 P也打印地址
}
/* 数组 = 值 数组 = 地址
a[0] = 1 &a[0] = bc36f638
a[1] = 2 &a[1] = bc36f63c
a[2] = 3 &a[2] = bc36f640
a[3] = 4 &a[3] = bc36f644
a[4] = 5 &a[4] = bc36f648
*/

system("pause");
}

4.1.1 二维数组

  1. 概念

一维数组常称为向量,二维数组,最简单的理解是”有两个下标”,如果把一维数组理解为一行数据,那么,二维数组可形象地表示为行列结构。X Y就是二维,X Y Z 就是三维
例子:int a[2][3] = {1,2,3,4,5,6}
其中第一个方括号中的元素代表行,第二个方括号中的元素代表列

身高|体态 丰满的 苗条的
修长的
小巧玲珑的

查看数组在内存当中的分布。

#include <stdio.h>
#include <stdlib.h>

void main() {

// 存储了3*5 = 15个元素 3行5列 一行5个元素
int a[3][5] = { { 1,2,3,4,5 },{6,7,8,9,10},{11,12,13,14,15} };

printf("%p\n", &a);
printf("%d\n", sizeof(a)); // 大小是 60 4字节*15元素 = 60字节
printf("%d\n", sizeof(a) / sizeof(int)); // 60字节 / 4字节 = 15元素
system("pause");
}
  1. 初始化二维数组

注意:二维数组中的元素同样是给分配在一段连续的内存
初始化表达式中内层花括号代表一行,这样和一维数组中只能对前几个元素初始化不同,二维数组的初始化可以跳过某些中间元素,给后面的元素赋值。

int a[2][3] = {{1,2,3},{4,5,6}};

也可以这样初始化:

int a[][4] = {{1,2,3,4}};

大括号初始化了以后,行号可以省略

两种赋值方法:

#include <stdio.h>
#include <stdlib.h>

void main() {

// 存储了3*5 = 15个元素 3行5列 一行5个元素
int a[3][5] = { { 1,2,3,4,5 },{6,7,8,9,10},{11,12,13,14,15} };

printf("%p\n", &a);

printf("%d\n", sizeof(a)); // 大小是 60 4字节*15元素 = 60字节
printf("%d\n", sizeof(a) / sizeof(int)); // 60字节 / 4字节 = 15元素

/* 二维数组赋值方法 */
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("a = %d\n", a[i][j]);
}
}
printf("------------------------------------------------\n");

int b[3][5] = { 0 };
int c[3][5] = { 0 };
for (int i = 0; i < 15; i++) {
b[i / 5][i % 5] = i;
}

for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
c[i][j] = i * 5 + j + 1;
}
}

for (int i = 0; i < 3; i++){
for (int j = 0; j < 5; j++) {
printf("b = %d\n", b[i][j]);
}
}

printf("------------------------------------------------\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("c = %d\n", c[i][j]);
}
}

system("pause");
}

二维数组地址的引用

元素a[i][j]的地址是&a[i][j];二维数组的数组名代表该数组的首地址;
比如:a实际上就是&a[0][0]。二维数组元素在内存中按行存放,第一行的首地址为a[0]第二行的首地址为a[1],……,第n行的首地址为a[n-1];
&a[i][j]等价于 a[i]+j。

4.2 基础算法

选择排序法

  1. 如何看懂带算法的程序
    1. 看流程判断和循环条件
    2. 搞清楚每个语句的功能
    3. 试数
    4. 调试
    5. 模仿改
  2. 选择排序法求极值
#include <stdio.h>
#include <stdlib.h>

void main() {

int a[10] = { 1,2,3,4,5,6,7,8,9,0 };

int Max = a[0]; // 初始化 Max = 1 然后去比较如果a[i]大于Max 那将a[i]的值赋予Max

for (int i = 0; i < 10; i++) {
// 这行打断点,打开局部变量调试查看
if (Max < a[i]) {
Max = a[i];
}
}
printf("Max(最大值)的值为:%d\n", Max);

for (int i = 0; i < 10; i++) {
// 这行打断点,打开局部变量调试查看
if (Max > a[i]) {
Max = a[i];
}
}
printf("最小值为:%d\n", Max);

int i = 0;
while (i < 10) {
i++;
if (Max < a[i]) {
Max = a[i];
}
}
printf("while循环下Max的值为:%d\n", Max);

system("pause");
}

选择排序方法一:

#include <stdio.h>
#include <stdlib.h>

/**
* 假设初始化 Max 的值是最大的
* Max = i 在每次循环中加1 然后用 Max 去循环和 j 比较
* 如果 j 大于 Max 就交换位置
*
* Max = a[0] = 1 | j = i+1 = 2 | = 2 1
* 内循环第一次最终结果:2,1,3,4,5,6,7,8,9,0
* 内循环第二次最终结果:3,1,2,4,5,6,7,8,9,0
* 外循环第一次最终结果:9,1,2,3,4,5,6,7,8,0
* 外循环第二次最终结果:9,8,1,2,3,4,5,6,7,0
* 外循环第三次最终结果:9,8,7,1,2,3,4,5,6,0
*
* 总结:
* 选择排序法 按住一个位置不动 循环出一个最大值
* 就好比打擂台 1挑全部 挑完一个再进行下一个
*
*/

void main() {

int a[10] = { 1,2,3,4,5,6,7,8,9,0 };

int Max = 0;

for (int i = 0; i < 10 - 1; i++) { // 最后剩余一个数肯定最小就不用比较了 所以减一
Max = i;
for (int j = i + 1; j < 10; j++) {
if (a[Max] < a[j]) {
int team = a[j];
a[j] = a[Max];
a[Max] = team;
}
}

/* 逐步分析结果 */
for (int i = 0; i < 10; i++) {
printf(" %4d", a[i]);
}
printf("\n");

}

printf("------------- 最终结果 ---------------\n");

for (int i = 0; i < 10; i++) {
printf("a 的值为:%d\n", a[i]);
}

system("pause");
}

选择排序法方法二(优化):

#include <stdio.h>
#include <stdlib.h>

/**
* 10 个人当老大
* 我只和其中最厉害的打
*/

void main() {

int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
int Max = 0;

for (int i = 0; i < 10 - 1; i++) {
Max = i;
for (int j = i + 1; j < 10; j++) {
if (a[Max] < a[j]) {
Max = j; // 存储 j 的下标 不直接交换 只比较不交换
}
}
if (Max != i) {
int team = a[i];
a[i] = a[Max];
a[Max] = team;

for (int i = 0; i < 10; i++) {
printf("%4d", a[i]);
}
}
printf("------------- 最终结果 ---------------\n");
for (int i = 0; i < 10; i++) {
printf("%4d\n", a[i]);
}

system("pause");
}

冒泡排序法

冒泡排序法求极值

#include <stdio.h>
#include <stdlib.h>
/**
* 冒泡算法是两两的进行比较 9和8比较 8和7比较 如果大就相互交换
*/

void main() {

int a[10] = { 9,8,7,6,5,4,3,2,1,0 };

for (int i = 0; i < 10 - 1; i++) {

if (a[i] > a[i + 1]) {
int team = a[i + 1];
a[i + 1] = a[i];
a[i] = team;
}
}

printf("a 的最大值为:%d\n",a[9]);

system("pause");
}

冒泡排序法

#include <stdio.h>
#include <stdlib.h>

/**
* 冒泡排序法
* 和选择排序法不同,选择排序法是固定一个值去比较大小
* 冒泡排序法是两两进行比较
*/

void main37() {

int a[10] = { 9,8,7,6,5,4,3,2,1,0 };

for (int i = 0; i < 10 - 1; i++) {
// i每循环一次都会筛选出最大值
for (int j = 0; j < 10 - 1 - i; j++) { //-i 是因为一个数已经在底了,所以就不用循环它了
if (a[j] > a[j + 1]) {
int team = a[j + 1];
a[j + 1] = a[j];
a[j] = team;
}
}
for (int i = 0; i < 10; i++) {
printf("%4d", a[i]);
}
printf("\n");
}

printf("\n------------- 最终结果 ---------------\n");

for (int i = 0; i < 10; i++) {
printf("%d\n", a[i]);
}

system("pause");
}

二分查找法

二分查找法用于快速查找信息或数据,当然还有更快的方法插值算法
二分查找法,是去一段数据的中间值,我们用中间值和我们的数据进行比较,如果我们的数大于中间值,那么小于中间值的我们就不需要进行比较。
然后我们找到需要进行比较的头和尾,再去中间值进行判断去排除。反复的循环就可以找到我们需要的数
例如:
我们有一个数组从1-12,我们需要的数值是9。
那么我们可以算出1-12的中间值为6。
6和9进行比较,9大于6那么1-6都排除,
我们重新定义7为数据的头12为数据的尾。
假设我们定义11为中间值,9是小于11的那么11-12就可以排除
我们定义7-10为头和尾进行比较。以此类推我们就可以获取到我们的值。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

void FindNum(int a[], int data);

// 当然还有更快的算法:插值算法

void main() {

int a[1000] = { 0 };
for (int i = 0; i < 1000; i++) {
a[i] = i;
printf("i = %d\n",i); // 打印出数值赋值的数
}

int data;
printf("请输入您要查询的值:");
scanf("%d",&data);

FindNum(a,data);

system("pause");
}

void FindNum(int a[], int data) {
int head = 0;
int foot = 1000 - 1;
int flag = -2; // 找到与没找到的判断值
int ci = 0; // 排除次数
while (head <= foot) {
int median = (head + foot) / 2;
printf("head 的值:%d Foot的值:%d Median的值:%d 次数:%d\n",head,foot,median,++ci);
if (data == a[median]) {
printf("找到了 data的值为:%d\n", data);
flag = 1;
break;
}else if (data > a[median]) { // 值大于中间值,小于中间值的全部值被抛弃
head = median + 1;

}else { // 值小于中间值,大于中间值的全部值被抛弃
foot = median - 1;
}
}

if (flag == -2) { // 如果找不到这个值
printf("找不到这个值\n");
}


}