1.函数的介绍
- 为完成某一功能的程序指令(语句)的集合,称为函数
- 在C语言中,函数分为: 自定义函数、系统函数
2.函数的定义
2.1函数的基本语法
返回类型 函数名(形参列表){
执行语句...; // 函数体
return 返回值; // 可选
}
- 形参列表:表示函数的输入
- 函数中的语句:表示为了实现某一功能代码块
- 函数可以有返回值,也可以没有,如果没有返回值,返回类型声明为 void
2.2函数的代码示例
#include<stdio.h>
double cal(int num1, int num2, char oper) {//申明一个四则运算函数
double result = 0.0;//申明一个变量用于存放结果
int error = 0;//用于判断输入的操作符是否错误
switch (oper) {//判断oper的种类 + - * /
case '+':
result = (double)num1 + num2;//需要进行double的强制类型转换,否则会提示warning
break;
case '-':
result = (double)num1 - num2;
break;
case '*':
result = (double)num1 * num2;
break;
case '/':
result = (double)num1 / num2;
break;
default://如果输入的四则运算符号,则提示错误
error = 1;
result = 0.0;
}
if (error == 0) {
printf("%d %c %d = %f", num1, oper, num2, result);
}
else {
printf("你的输入有误,请检查!");
}
return result;//返回result的值
}
void main() {
int num1, num2;
double result;
char oper;
printf("请输入数字1、数字2和操作符!");
scanf_s("%d %d %c", &num1, &num2, &oper, sizeof(oper));
result = cal(num1, num2, oper);
}
3.头文件
3.1头文件的基本概念
- 头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件: 程序员编写的头文件和 C 标准库自带的头文件
- 在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它
- #include 叫做文件包含命令,用来引入对应的头文件(.h 文件)。#include 也是 C 语言预处理命令的一种。#include 的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源 文件,这与复制粘贴的效果相同。但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错, 特别在程序是由多个源文件组成的时候。
3.2头文件代码示例
- 头文件myFun.h
double cal(int num1, int num2, char oper);
-
源文件myFun.c
#include<stdio.h> double cal(int num1, int num2, char oper) {//申明一个四则运算函数 double result = 0.0;//申明一个变量用于存放结果 int error = 0;//用于判断输入的操作符是否错误 switch (oper) {//判断oper的种类 + - * / case '+': result = (double)num1 + num2;//需要进行double的强制类型转换,否则会提示warning break; case '-': result = (double)num1 - num2; break; case '*': result = (double)num1 * num2; break; case '/': result = (double)num1 / num2; break; default://如果输入的四则运算符号,则提示错误 error = 1; result = 0.0; } if (error == 0) { printf("%d %c %d = %f", num1, oper, num2, result); } else { printf("你的输入有误,请检查!"); } return result;//返回result的值 }
-
使用myFun.h头文件计算
#include<stdio.h> #include"myFun.h" void main() { int num1, num2; double result; char oper; printf("请输入数字1、数字2和操作符!"); scanf_s("%d %d %c", &num1, &num2, &oper, sizeof(oper)); result = cal(num1, num2, oper); }
3.3.头文件的注意事项和细节
- 引用头文件相当于复制头文件的内容
- 源文件的名字可以不和头文件一样,但是为了好管理,一般头文件名和源文件名一样
- include <>:引用的是编译器的类库路径里面的头文件,用于引用系统头文件
- include "":引用的是你程序目录的相对路径中的头文件,如果在程序目录没有找到引用的头文件则到编译器的类库路径的目录下找该头文件,用于引用用户头文件
- 引用 系统头文件,两种形式都会可以,include <> 效率高;引用 用户头文件,只能使用 include ""
- 一个 #include 命令只能包含一个头文件,多个头文件需要多个 #include 命令
- 同一个头文件如果被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引 入的机制
4.函数调用过程
4.1.函数调用过程示例
#include<stdio.h>
void cal(int num1) {
int count = num1 + 2;
printf("count = %d\n", count);
}
void main() {
int num2 = 7;
cal(num2);
printf("ok!\n");
}
4.2.函数调用规则
- 当调用(执行)一个函数时,就会开辟一个独立的空间(栈)
- 每个栈空间是相互独立
- 当函数执行完毕后,会返回到调用函数位置,继续执行
- 如果函数有返回值,则将返回值赋给接收的变量
- 当一个函数返回后,该函数对应的栈空间也就销毁
5.函数的递归调用
5.1.递归调用的基本介绍
一个函数在函数体内又调用了本身,我们称为递归调用
5.2.递归调用代码示例
#include<stdio.h>
void test(int num) {
if (num >= 7) {
test(num - 1);
printf("num = %d\n", num);
}
}
void main() {
test(9);
}
5.3.递归调用需遵守的原则
- 执行一个函数时,就创建一个新的受保护的独立空间
- 函数的局部变量是独立的,不会相互影响
- 递归必须向退出递归的条件逼近,否则就是无限递归
- 当一个函数执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁
5.4.递归练习
- 有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。 当到第十天时,想再吃时(还没吃),发现只有 1 个桃子了。问题:最初共多少个桃子?
//有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个! //以后每天猴子都吃其中的一半,然后再多吃一个。 //当到第十天时,想再吃时(还没吃),发现只有 1 个桃子了。问题:最初共多少个桃子? #include<stdio.h> int peachCount(int day, int peach) { if (day == 1) {//当day==1的时候跳出返回总数 return peach; } else { return peachCount(day - 1, 2 * (peach + 1));//如果day!=1则继续递归调用peachCount函数 } } void main() { int peach = 1, day = 0; printf("请输入天数:"); scanf_s("%d", &day); peach = peachCount(day, peach); printf("最初有%d个桃子", peach); }
-
已知 f(1)=3; f(n) = 2*f(n-1)+1; 请使用递归的思想编程,求出 f(n)的值?
//已知 f(1) = 3; f(n) = 2 * f(n - 1) + 1; 请使用递归的思想编程,求出 f(n)的值 ? #include<stdio.h> int cal(int n,int result) { if (n == 1) { return result; } else { cal(n - 1, 2 * result + 1); } } void main() { int num, result = 3; printf("请输入n=\n"); scanf_s("%d", &num); result = cal(num, result); printf("f(n)的结果为:%d\n", result); }
-
//给你一个整数 n,求出它的斐波那契数是多少? #include<stdio.h> int f(int count) { if (count == 1 || count == 2) { return 1; } else { return f(count - 1) + f(count - 2); } } void main() { int count, result; printf("请输入要计算的第几个斐波那契数:"); scanf_s("%d", &count); result = f(count); printf("%d", result); }
6.函数的注意事项
- 函数的形参列表可以是多个
- C语言传递参数可以是值传递,也可以传递指针
- 函数的命名遵循标识符命名规范,首字母不能是数字,可以采用驼峰法或者下划线法
- 函数中的变量是局部的,函数外不生效
- 基本数据类型默认是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值
#include<stdio.h> void test(int num) { num++;//num进行的是值传递,仅在test函数体内进行num++ printf("test内的num = %d\n", num); } void main() { int num = 7; test(num); printf("main内的num = %d", num); }
-
如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用(即传递指针)
#include<stdio.h> void test(int *num) {//test里的num指针指向main函数体的num (*num)++;//对test的num指针指向的地址的内容进行++ printf("test内的num = %d\n", *num); } void main() { int num = 7; test(&num);//将main函数体的num的地址赋值给test函数的num指针 printf("main内的num = %d", num); }
-
C语言不支持函数重载(不能重复定义)
-
C语言支持可变参数函数
7.练习
请编写一个函数 swap(int *n1, int *n2) 可以交换 n1 和 n2 的值
//请编写一个函数 swap(int *n1, int *n2) 可以交换 n1 和 n2 的值
#include<stdio.h>
void swap(int* num1, int* num2) {
int temp;//申明一个变量temp,用于暂时存放*num1的内容
temp = *num1;
*num1 = *num2;
*num2 = temp;
}
void main() {
int num1, num2;
printf("请输入num1和num2分别为:\n");
scanf_s("%d%d", &num1, &num2);
swap(&num1, &num2);
printf("交换过后的结果为:num1 = %d\t num2 = %d", num1, num2);
}
8.函数参数的传递方式
8.1.函数参数的传递方式基本介绍
C 语言传递参数可以是值传递,也可以传递指针。其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。
8.2.值传递和引用传递使用特点
- 值传递:变量直接存储值,内存通常在栈中分配
- 默认是值传递的数据类型有 1. 基本数据类型 2. 结构体 3. 共用体 4. 枚举类型
- 引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值)
- 默认是引用传递的数据类型有:指针和数组
- 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量(*指针)。从效果上看类似引用
9.变量作用域
9.1变量作用域的基本说明
所谓变量作用域(Scope),就是指变量的有效范围
- 函数内部声明/定义的局部变量,作用域仅限于函数内部
- 函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用局部变量(编译器 使用就近原则)
#include<stdio.h> int num = 9; //如果与全局变量同名它们会优先使用局部变量 void test(int num){ printf("test的num = %d\n", num);//函数内部声明/定义的局部变量,作用域仅限于函数内部 } void main() { test(7); printf("main的num = %d", num); }
-
在一个代码块,比如 for / if中 的局部变量,那么这个变量的的作用域就在该代码块
-
在所有函数外部定义的变量叫全局变量,作用域在整个程序有效
9.2.初始化局部变量和全局变量
- 局部变量,系统不会对其默认初始化,必须对局部变量初始化后才能使用,否则,程序运行后可能会异常退出
- 全局变量,系统会自动对其初始化
数据类型 初始化默认值 int 0 char '\0' float 0.0 double 0.0 pointer指针 NULL #include<stdio.h> int a; char b; float c; double d; void main() { printf("a =%d\tb = %c\tc = %f\t d =%f", a, b, c, d); }
9.3.作用域的注意事项和细节
- 全局变量(Global Variable)保存在内存的全局存储区中,占用静态的存储单元,它的作用域默认是整个程序,也就是所有的代码文件,包括源文件(.c 文件)和头文件(.h 文件)
- 局部变量(Local Variable)保存在栈中,函数被调用时才动态地为变量分配存储单元,它的作用域仅限于函数内部
- C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量
- 在同一个作用域,变量名不能重复,在不同的作用域,变量名可以重复,使用时编译器采用就近原则
- 由{ }包围的代码块也拥有独立的作用域
#include<stdio.h> void main(){ int a = 97; if (1) { int a = 97; } }
9.4.练习
-
#include<stdio.h> double price = 200.0; void test01() { printf("%.2f\n", price); } void test02() { price = 250.0;//把全局变量price改成250.0 printf("%.2f\n", price); }void test03() { double price = 200.0;//在test03内申明一个double类型的price变量 printf("%.2f\n", price); } void main() { printf("main price = %.2f\n", price);//main price = 200.00 test01();//200.00 test02();//250.00 test01();//250.00 test03();//200.00 }
-
#include<stdio.h> int n = 20; void func1() { int n = 20; printf("func1 n: %d\n", n);//输出20 } void func2(int n) { printf("func2 n: %d\n", n);//输出30 } void func3() { printf("func3 n: %d\n", n);//输出20 } int main() { int n = 30; func1(); func2(n); func3(); { int n = 40; printf("block n: %d\n", n);//输出40 } printf("main n: %d\n", n);//输出30 return 0; }
10.static关键字
10.1.static基本介绍
static关键字在c语言中比较常用,使用恰当能够大 大提高程序的模块化特性,有利于扩展和维护
10.2.局部变量使用static修饰
- 局部变量被 static 修饰后,我们称为静态局部变量。对应静态局部变量在声明时未赋初值,编译器 也会把它初始化为0
#include<stdio.h> void main() { static int a; printf("a = %d", a); }
-
静态局部变量存储于进程的静态存储区(全局性质),只会被初始一次,即使函数返回,它的值也会保持不变(存放在红色笔画线区域)
#include<stdio.h> void testStatic() { //第一次执行testStatic函数的时候会运行static in num = 7 //并把num存入静态存储区中,故第一次运行完num++之后 //num = 8 并把num = 8存入静态存储区 //第二次运行则不执行static in num = 7,而在执行num++时 //变量num的值从静态存储区中取出,即num = 8 ,后进行num++ static int num = 7; num++; printf("num = %d\n", num); } void main() { testStatic(); printf("***********************\n"); testStatic(); }
10.3.全局变量使用static修饰
- 普通全局变量对整个工程可见,其他文件可以使用extern 外部声明后直接使用。也就是说其他文件不能再定义一个 与其相同名字的变量了(否则编译器会认为它们是同一个 变量),静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响
#include<stdio.h> extern int num; void main() { printf("num = %d", num); }
-
定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用
10.4.函数使用 static 修饰
- 函数的使用方式与全局变量类似,在函数的返回类型前加上 static,就是静态函数。非静态函数可以在另一个文件中通过 extern 引用;静态函数只能在声明它的文件中可见,其他文件不能引用该函数
- 不同的文件可以使用相同名字的静态函数,互不影响
11.字符串中常用的系统函数
字符串的头文件<string.h>
- 得到字符串的长度
size_t strlen(const char *str)//计算字符串 str 的长度,直到空结束字符,但不包括空结束字符
-
拷贝字符串
char *strcpy(char *dest, const char *src)//把 src 所指向的字符串复制到 dest
-
连接字符串
char *strcat(char *dest, const char *src)//把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
-
代码示例
#include<stdio.h>
#include<string.h>
void main() {
char* test01 = "江南";//使用size_t strlen(const char *str)得到字符串的长度
char test02[50] ="", test03[50] = "";
printf("test01的长度为%d\n", strlen(test01));
//使用char *strcpy(char *dest, const char *src)拷贝字符串
//使用strcpy时,数组原来的内容将会被覆盖
strcpy_s(test02, strlen(test01) + 1, test01);
printf("test02 = %s\n", test02);
//连接字符串char *strcat(char *dest, const char *src)
strcpy_s(test03, strlen(test01) + 1, test01);//test03里的内容为“江南”
strcat_s(test03, strlen(test01) + strlen(test03) +1, test01);
printf("test03 = %s\n", test03);
//printf("test03 = %s\n", test03);
}
12.时间和日期相关函数
时间的头文件为<time.h>
- 获取当前时间
char *ctime(const time_t *timer)//返回一个表示当地时间的字符串,当地时间是基于参数 timer
#include<stdio.h> #include<time.h> void main() { time_t curtime;//time_t是一个结构体类型,定义一个名称叫curtime的time_t结构体 time(&curtime);//time()初始化 char time[50]; ctime_s(time, sizeof(time), &curtime); printf("当前时间 = %s", time); }
-
编写一段代码来统计 函数 test 执行的时间
double difftime(time_t time1, time_t time2)//返回 time1 和 time2 之间相差的秒数 (time1-time2)
#include<stdio.h> #include<time.h> void test() { int a, b, sum=0; for (a = 0; a < 7777777; a++) { for (b = 0; b < 300; b++) { sum += b; } } } void main() { time_t startTime, endTime; double differentTime = 0.0; printf("程序启动!"); time(&startTime); test(); time(&endTime); differentTime = difftime(endTime, startTime); printf("程序运行时间为%.2f", differentTime); }
13.数学相关函数
-
double exp(double x)//返回 e 的 x 次幂的值 double log(double x)//返回 x 的自然对数(基数为 e 的对数) double pow(double x, double y)//返回 x 的 y 次幂 double sqrt(double x)//返回 x 的平方根 double fabs(double x)//返回 x 的绝对值
-
代码示例
#include<stdio.h> #include<math.h> void main() { double result1 = exp(7.0); printf("e的7次方 = %f\n", result1); double result2 = log(7.0); printf("ln7 = %f\n", result2); double result3 = pow(9.0, 7.0); printf("9的7次方 = %f\n", result3); double result4 = sqrt(7.0); printf("根号7 = %f\n", result4); double result5 = fabs(-7.0); printf("-7的绝对值 = %f", result5); }
14.基本数据类型和字符串类型的转换
14.1.基本介绍
在程序开发中,我们经常需要将基本数据类型转成字符串类型(即 char 数组 ),或者将字符串类型转成基本数据类型
14.2.sprintf函数的用法
sprintf和平时我们常用的 printf函数的功能很相似。sprintf函数打印到字符串中,而 printf函数打印输出到屏幕 上。sprintf函数在我们完成其他数据类型转换成字符串类型的操作中应用广泛。该函数包含在 stdio.h 的头文件中
14.3.基本类型转字符串类型
#include<stdio.h>
void main() {
char str1[20], str2[20], str3[20];
int num1 = 97, num3 = 977;
double num2 = 97.777777;
//将num1和num3的内容存入str1中,其中按照num1→num3的顺序
sprintf_s(str1, sizeof(str1), "%d%d", num1, num3);
//将num2的内容保留两个小数点存入str2
sprintf_s(str2, sizeof(str2), "%.2f", num2);
//将num2的内容一共保留七位数字,其中两位小数,保存到str3中,不足的位数用空格补齐
sprintf_s(str3, sizeof(str3),"%7.2f", num2);
printf("str1 = %s\nstr2 = %s\nstr3 = %s", str1, str2, str3);
}
14.4符串类型转基本数据类型
通过<stdlib.h>的函数调用 atoi atof 即可
#include<stdio.h>
#include<stdlib.h>
void main() {
char str1[77] = "12.34567";
char str2[77] = "123.4567";
char str3[77] = "abcdefg";
char str4[77] = "1234567";
int num1 = atoi(str1);//atoi(str1)将str1转换为整数
int num2 = str4;
double num3 = atof(str2);//atof(str2)将str2转换为浮点数
char num4 = str3[6];//表示取出str3这个字符串的第七个字符'g'
//因为str3[6]是从str[0]开始计算 0 1 2 3 4 5 6 第七个
printf("num1 = %d\nnum 2 = %d\nnum 3 = %f\nnum 4 = % c", num1, num2, num3, num4);
}
14.5.注意事项
- 在将 char 数组 类型转成 基本数据类型时,要确保能够转成有效的数据。例如,可以把"777"转为数字,但是不能将"jiangnan"转为一个整数
- 如果格式不正确,会默认转成 0 或者 0.0