浙江省兰溪建设局网站,游戏推广招聘,营口组织部网站 两学一做,平台网站建设协议书目录
前言
标准库#xff1a;C语言的百宝箱
头文件#xff1a;库函数的藏宝图
实例分析#xff1a;计算平方根的sqrt函数
功能描述
头文件包含的重要性
库函数文档的一般格式
自定义函数#xff1a;释放你的编程创造力
函数的语法形式 函数的比喻
函数的举例 简化…目录
前言
标准库C语言的百宝箱
头文件库函数的藏宝图
实例分析计算平方根的sqrt函数
功能描述
头文件包含的重要性
库函数文档的一般格式
自定义函数释放你的编程创造力
函数的语法形式 函数的比喻
函数的举例 简化函数定义 形参和实参
形参
实参
实参和形参的关系
return 语句
数组做函数参数
嵌套调⽤和链式访问 嵌套调⽤
链式访问
函数的声明和定义
单个⽂件
多个文件
static 和 extern static 修饰局部变量 结论
static 修饰全局变量 static 修饰函数 前言 在数学的世界里我们早已邂逅了函数这一概念例如那条简洁的直线方程 ykxb其中 k 和 b 是常数。只要你给出一个 x 值它就能准确无误地返回对应的 y 值。这一概念在 C 语言的编程领域同样适用函数有时也被称作子程序在这里扮演着至关重要的角色。在 C 语言中函数是一段执行特定任务的代码它遵循特定的编写规则和调用方式。 想象一下一个复杂的软件程序就像一座由无数块乐高积木搭建的城堡。每一块积木就好比一个函数负责完成一个具体的小任务。通过这种方式庞大的计算任务被巧妙地分解成一系列较小、更易于管理的函数每个函数都专注于自己的职责。这种模块化的方法不仅让程序更加清晰也极大地提高了开发效率因为一旦编写完成这些函数就可以在不同的程序中重复使用。 在 C 语言的浩瀚星河中我们主要会遇到两类函数 库函数这些是编程世界中已经预先构建好的“工具箱”由经验丰富的开发者们精心打造供我们直接使用。它们就像是我们编程工具箱中的瑞士军刀包含了各种常用的功能如输入输出、数学计算等。 自定义函数这些则是我们根据特定需求亲自编写的函数。它们就像是我们为解决特定问题而定制的工具提供了更高的灵活性和针对性。 标准库C语言的百宝箱 C语言本身定义了一套严谨的语法规则但它并不直接提供具体的功能实现——这便是标准库大显身手的地方。ANSI C国际标准为常用的功能设定了标准而各编译器厂商则根据这些标准提供了丰富的函数实现这些预定义的函数就是我们所说的库函数。 想象一下printf和scanf这些我们耳熟能详的函数就像是程序员的工具箱中现成的锤子和锯子我们无需从零开始打造这些工具只需了解如何使用它们就能直接投入到编程的创作中。这不仅极大地提升了开发效率也因为这些库函数经过了严格的测试和优化其质量和执行效率都得到了保证。 头文件库函数的藏宝图 在C语言的宝库中库函数被精心分类并存储在不同的头文件中每个头文件都像是一张藏宝图指引我们找到所需的函数和类型定义。无论是处理数学运算的math.h还是操作字符串的string.h或是处理日期和时间的time.h这些头文件中都包含了丰富的函数和类型声明。 点击这里你将找到一份完整的C标准库头文件列表它涵盖了从基础数学运算到复杂的多线程处理无所不包。 学习库函数不必急于一时我们可以一步步来逐个掌握。随着对这些头文件的深入了解你会发现它们不仅是代码的组成部分更是提升编程技艺的宝贵资源。
C/C官方文档提供了全面的库函数参考是学习和查阅库函数的权威资源。 C标准库头文件cplusplus.com提供了C语言库的详细描述和示例适合快速查找和学习。 C library - C Reference
实例分析计算平方根的sqrt函数
让我们以sqrt函数为例探讨如何使用库函数。sqrt函数用于计算一个数的平方根它定义在math.h头文件中。
#include stdio.h
#include math.hint main() {double d 16.0; // 待计算的数double r sqrt(d); // 调用sqrt函数计算平方根printf(The square root of %lf is %lf\n, d, r);return 0;
} 在这个例子中
sqrt是函数名表示“平方根”。d是函数的参数表示调用sqrt函数时需要传递的值这里是一个double类型的值。
功能描述
sqrt函数的主要功能是计算一个数的平方根。它接受一个double类型的参数并返回该参数的平方根。
头文件包含的重要性 使用库函数时必须包含对应的头文件。这是因为库函数的声明和相关类型定义都在这些头文件中。例如要使用sqrt函数你需要在代码中包含math.h头文件。如果不包含编译器将无法识别该函数可能会导致编译错误。 库函数文档的一般格式 1. 函数原型 2. 函数功能介绍 3. 参数和返回类型说明 4. 代码举例 5. 代码输出 6. 相关知识链接 自定义函数释放你的编程创造力 在掌握了库函数的使用之后我们将注意力转向自定义函数——这是编程中更具创造性的部分。自定义函数不仅重要而且它们让程序员能够根据自己的需求设计和实现特定的功能。 函数的语法形式
ret_type fun_name(parameter_list) {// 函数体
} ret_type指定函数返回值的数据类型。如果函数不返回任何值可以使用void。fun_name函数的名称它应该反映函数的功能以便于理解和使用。parameter_list函数的参数列表用于传递输入值给函数。参数可以有零个或多个每个参数都需要指明数据类型和名称。花括号{}内的部分称为函数体这里包含了完成特定任务的代码。 函数的比喻
我们可以将函数比作一个小型的加工厂工厂需要输入原材料经过加工后产出产品。类似地函数通常接收一些输入值参数在函数体内进行处理最终产生输出返回值。
函数的举例
让我们通过一个简单的例子来演示如何编写一个自定义函数。假设我们需要编写一个加法函数用于计算两个整型变量的和。
#include stdio.h// 自定义加法函数
int Add(int x, int y) {int z 0;z xy;return z; // 直接返回计算结果
}int main() {int a, b;// 输入printf(Enter two integers: );scanf(%d %d, a, b);// 调用加法函数并输出结果int sum Add(a, b);printf(The sum is: %d\n, sum);return 0;
} 在这个例子中我们定义了一个名为Add的函数它接收两个整型参数x和y并返回它们的和。在main函数中我们从用户那里获取两个整数调用Add函数计算它们的和并将结果打印出来。 简化函数定义
实际上Add函数可以进一步简化直接在函数体内返回计算结果
int Add(int x, int y) {return x y;
} 函数的参数部分需要交代清楚参数个数每个参数的类型是啥形参的名字叫啥。上⾯只是⼀个例⼦未来我们是根据实际需要来设计函数函数名、参数、返回类型都是可以灵活变化的。
形参和实参 在函数使⽤的过程中把函数的参数分为实参和形参。 形参
在上⾯代码中第2⾏定义函数的时候在函数名 Add 后的括号中写的 x 和 y 称为形式参数简 称形参。 为什么叫形式参数呢实际上如果只是定义了 Add 函数⽽不去调⽤的话 Add 函数的参数 x 和 y 只是形式上存在的不会向内存申请空间不会真实存在的所以叫形式参数。形式参数只有在函数被调⽤的过程中为了存放实参传递过来的值才向内存申请空间这个过程就是形式的实例化。 实参和形参的关系 虽然我们提到了实参是传递给形参的他们之间是有联系的但是形参和实参各⾃是独⽴的内存空 间。 这个现象是可以通过调试来观察的。请看下⾯的代码和调试演⽰ #include stdio.h
int Add(int x, int y)
{int z 0;z xy;return z;
}int main()
{int a 0;int b 0;//输⼊scanf(%d %d, a, b);//调⽤加法函数完成a和b的相加//求和的结果放在r中int r Add(a, b);//输出printf(%d\n, r);return 0;
}
实参 在上⾯代码中第2~7⾏是 Add 函数的定义有了函数后再第17⾏调⽤Add函数的。 我们把第17⾏调⽤Add函数时传递给函数的参数a和b称为实际参数简称实参。 实际参数就是真实传递给函数的参数。 形参 在上⾯代码中第2⾏定义函数的时候在函数名 Add 后的括号中写的 x 和 y 称为形式参数简 称形参。 为什么叫形式参数呢实际上如果只是定义了 Add 函数⽽不去调⽤的话 Add 函数的参数 x 和 y 只是形式上存在的不会向内存申请空间不会真实存在的所以叫形式参数。形式参数只有在 函数被调⽤的过程中为了存放实参传递过来的值才向内存申请空间这个过程就是形式的实例化。 实参和形参的关系 虽然我们提到了实参是传递给形参的他们之间是有联系的但是形参和实参各⾃是独⽴的内存空 间。 这个现象是可以通过调试来观察的。请看下⾯的代码和调试演⽰: #include stdio.h
int Add(int x, int y)
{int z 0;z x y;return z;
}int main()
{int a 0;int b 0;//输⼊scanf(%d %d, a, b);//调⽤加法函数完成a和b的相加//求和的结果放在r中int r Add(a, b);//输出printf(%d\n, r);return 0;
} 我们在调试的可以观察到x和y确实得到了a和b的值但是x和y的地址和a和b的地址是不⼀样的所以我们可以理解为形参是实参的⼀份临时拷⻉。 return 语句 在函数的设计中函数中经常会出现return语句这⾥讲⼀下return语句使⽤的注意事项。 return后边可以是⼀个数值也可以是⼀个表达式如果是表达式则先执⾏表达式再返回表达式的结果。 return后边也可以什么都没有直接写 return; 这种写法适合函数返回类型是void的情况。 return返回的值和函数返回类型不⼀致系统会⾃动将返回的值隐式转换为函数的返回类型。 return语句执⾏后函数就彻底返回后边的代码不再执⾏。 如果函数中存在if等分⽀的语句则要保证每种情况下都有return返回否则会出现编译错误。 数组做函数参数 在使⽤函数解决问题的时候难免会将数组作为参数传递给函数在函数内部对数组进⾏操作。 ⽐如写⼀个函数对将⼀个整型数组的内容全部置为-1再写⼀个函数打印数组的内容。 简单思考⼀下基本的形式应该是这样的 #include stdio.h
void set_arr(int arr2[10], int sz2)
{int i 0;for (i 0; i sz2; i){arr2[i] -1;}
}void print_arr(int arr2[], int sz2)
{int i 0;for (i 0; i sz2; i){printf(%d , arr2[i]);}printf(\n);int main()
{int arr[] {1,2,3,4,5,6,7,8,9,10};set_arr();//设置数组内容为-1print_arr();//打印数组内容return 0;
} 这⾥的set_arr函数要能够对数组内容进⾏设置就得把数组作为参数传递给函数同时函数内部在设置数组每个元素的时候也得遍历数组需要知道数组的元素个数。所以我们需要给set_arr传递2个参数⼀个是数组另外⼀个是数组的元素个数。仔细分析print_arr也是⼀样的只有拿到了数组和元素个数才能遍历打印数组的每个元素。 #include stdio.h
int main()
{int arr[] {1,2,3,4,5,6,7,8,9,10};int sz sizeof(arr)/sizeof(arr[0]);set_arr(arr, sz);//设置数组内容为-1print_arr(arr, sz);//打印数组内容return 0;
} 数组作为参数传递给了set_arr 和 print_arr 函数了那这两个函数应该如何设计呢 这⾥我们需要知道数组传参的⼏个重点知识 函数的形式参数要和函数的实参个数匹配函数的实参是数组形参也是可以写成数组形式的形参如果是⼀维数组数组⼤⼩可以省略不写形参如果是⼆维数组⾏可以省略但是列不能省略数组传参形参是不会创建新的数组的形参操作的数组和实参的数组是同⼀个数组 嵌套调⽤和链式访问 嵌套调⽤ 嵌套调⽤就是函数之间的互相调⽤每个函数就⾏⼀个乐⾼零件正是因为多个乐⾼的零件互相⽆缝的配合才能搭建出精美的乐⾼玩具也正是因为函数之间有效的互相调⽤最后写出来了相对⼤型的程序。 假设我们计算某年某⽉有多少天如果要函数实现可以设计2个函数: is_leap_year()根据年份确定是否是闰年 get_days_of_month()调⽤is_leap_year确定是否是闰年后再根据⽉计算这个⽉的天数 int is_leap_year(int y)
{if(((y%40)(y%100!0))||(y%4000))return 1;elsereturn 0;
}
int get_days_of_month(int y, int m)
{int days[] {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};int day days[m];if (is_leap_year(y) m 2)day 1;return day;
}int main()
{int y 0;int m 0;scanf(%d %d, y, m);int d get_days_of_month(y, m);printf(%d\n, d);return 0;
}这⼀段代码完成了⼀个独⽴的功能。代码中反应了不少的函数调⽤ main 函数调⽤ scanf 、 printf 、 get_days_of_month get_days_of_month 函数调⽤ is_leap_yea 未来的稍微⼤⼀些代码都是函数之间的嵌套调⽤但是函数是不能嵌套定义的。 链式访问 所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数像链条⼀样将函数串起来就是函数的链式访问。 ⽐如 #include stdio.h
int main()
{int len strlen(abcdef);//1.strlen求⼀个字符串的⻓度printf(%d\n, len);//2.打印⻓度 return 0;
}前⾯的代码完成动作写了2条语句把如果把strlen的返回值直接作为printf函数的参数呢这样就是⼀个链式访问的例⼦了。 #include stdio.h
int main()
{printf(%d\n, strlen(abcdef));//链式访问return 0;
} 在看⼀个有趣的代码下⾯代码执⾏的结果是什么呢 #include stdio.h
2 int main()
3 {
4 printf(%d, printf(%d, printf(%d, 43)));
5 return 0;
6 } 这个代码的关键是明⽩printf 函数的返回是啥 int printf ( const char * format, ... ); printf函数返回的是打印在屏幕上的字符的个数。 上⾯的例⼦中我们就第⼀个printf打印的是第⼆个printf的返回值第⼆个printf打印的是第三个 printf的返回值。 第三个printf打印43在屏幕上打印2个字符再返回2 第⼆个printf打印2在屏幕上打印1个字符再放回1 第⼀个printf打印1 所以屏幕上最终打印4321 函数的声明和定义 单个⽂件 ⼀般我们在使⽤函数的时候直接将函数写出来就使⽤了。 ⽐如我们要写⼀个函数判断⼀年是否是闰年。 #include stido.h
//判断⼀年是不是闰年
int is_leap_year(int y)
{if(((y%40)(y%100!0)) || (y%4000))return 1;elsereturn 0;
}int main()
{int y 0;scanf(%d, y);int r is_leap_year(y);if(r 1)printf(闰年\n);elseprintf(⾮闰年\n);return 0;
}上⾯代码中橙⾊的部分是函数的定义绿⾊的部分是函数的调⽤。 这种场景下是函数的定义在函数调⽤之前没啥问题。 那如果我们将函数的定义放在函数的调⽤后边如下 #include stido.h
int main()
{int y 0;scanf(%d, y);int r is_leap_year(y);if(r 1)printf(闰年\n);elseprintf(⾮闰年\n);return 0;
}
//判断⼀年是不是闰年
int is_leap_year(int y){if(((y%40)(y%100!0)) || (y%4000))return 1;elsereturn 0;
}这个代码在VS2022上编译会出现下⾯的警告信息 这是因为C语⾔编译器对源代码进⾏编译的时候从第⼀⾏往下扫描的当遇到第7⾏is_leap_year 函数调⽤的时候并没有发现前⾯有is_leap_year的定义就报出了上述的警告。 把怎么解决这个问题呢就是函数调⽤之前先声明⼀下is_leap_year这个函数声明函数只要交代清楚函数名函数的返回类型和函数的参数。 如int is_leap_year(int y)这就是函数声明函数声明中参数只保留类型省略掉名字也是可以 的。代码变成这样就能正常编译了。 #include stido.h
int is_leap_year(int y)//函数声明
int main()
{int y 0;scanf(%d, y);int r is_leap_year(y);if(r 1)printf(闰年\n);elseprintf(⾮闰年\n);return 0;
}
//判断⼀年是不是闰年
int is_leap_year(int y){if(((y%40)(y%100!0)) || (y%4000))return 1;elsereturn 0;
} 函数的调⽤⼀定要满意先声明后使⽤ 函数的定义也是⼀种特殊的声明所以如果函数定义放在调⽤之前也是可以的。 多个文件 ⼀般在企业中我们写代码时候代码可能⽐较多不会将所有的代码都放在⼀个⽂件中我们往往会根据程序的功能将代码拆分放在多个⽂件中。 ⼀般情况下函数的声明、类型的声明放在头⽂件.h中函数的实现是放在源⽂件.c⽂件中。 如下 add.c //函数的定义
int Add1(int x, int y)
{return x y;
} add1.h #pragma once
//函数的声明
int Add(int x, int y); test.c #include stdio.h
#include add1.h
int main()
{int a 10;int b 20;//函数调⽤int c Add1(a, b);printf(%d\n, c);return 0;
} 有了函数声明和函数定义的理解我们写代码就更加⽅便了。 static 和 extern static 和 extern 都是C语⾔中的关键字。 static 是 静态的 的意思可以⽤来 修饰局部变量 修饰全局变量 修饰函数 extern 是⽤来声明外部符号的。 在讲解 static 和 extern 之前再讲⼀下作⽤域和⽣命周期。 作⽤域scope是程序设计概念通常来说⼀段程序代码中所⽤到的名字并不总是有效可⽤的⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。 1. 局部变量的作⽤域是变量所在的局部范围。 2. 全局变量的作⽤域是整个⼯程项⽬。 ⽣命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。 1. 局部变量的⽣命周期是进⼊作⽤域变量创建⽣命周期开始出作⽤域⽣命周期结束。 2. 全局变量的⽣命周期是整个程序的⽣命周期。 static 修饰局部变量 //代码1
#include stdio.h
void test()
{int i 0;i;printf(%d , i);
}
int main()
{int i 0;for(i0; i5; i){test();}return 0} //代码2
#include stdio.h
void test()
{//static修饰局部变量static int i 0;i;printf(%d , i);
}
int main()
{int i 0;for(i0; i5; i){test();}return 0;
} 对⽐代码1和代码2的效果理解 static 修饰局部变量的意义。 代码1的test函数中的局部变量i是每次进⼊test函数先创建变量⽣命周期开始并赋值为0然后 再打印出函数的时候变量⽣命周期将要结束释放内存。 代码2中我们从输出结果来看i的值有累加的效果其实 test函数中的i创建好后出函数的时候是不会销毁的重新进⼊函数也就不会重新创建变量直接上次累积的数值继续计算。 结论 static修饰局部变量改变了变量的⽣命周期⽣命周期改变的本质是改变了变量的存储类型本来⼀个局部变量是存储在内存的栈区的但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的⽣命周期就和程序的⽣命周期⼀样了只有程序结束变量才销毁内存才回收。但是作⽤域不变的。 使⽤建议未来⼀个变量出了函数后我们还想保留值等下次进⼊函数继续使⽤就可以使⽤static 修饰。 static 修饰全局变量 代码1 add.c
int g_val 2018; test.c #include stdio.h
extern int g_val;
int main()
{printf(%d\n, g_val);return 0;
} 代码2 add.c static int g_val 2018; test.c #include stdio.h
extern int g_val;
int main()
{printf(%d\n, g_val);return 0;
} extern 是⽤来声明外部符号的如果⼀个全局的符号在A⽂件中定义的在B⽂件中想使⽤就可以使⽤ extern 进⾏声明然后使⽤。 代码1正常代码2在编译的时候会出现链接性错误。 结论 ⼀个全局变量被static修饰使得这个全局变量只能在本源⽂件内使⽤不能在其他源⽂件内使⽤。 本质原因是全局变量默认是具有外部链接属性的在外部的⽂件中想使⽤只要适当的声明就可以使⽤但是全局变量被 static 修饰之后外部链接属性就变成了内部链接属性只能在⾃⼰所在的源⽂件内部使⽤了其他源⽂件即使声明了也是⽆法正常使⽤的。 使⽤建议如果⼀个全局变量只想在所在的源⽂件内部使⽤不想被其他⽂件发现就可以使⽤static修饰。 static 修饰函数
代码1
add.c
int Add(int x, int y)
{return xy;
}
test.c
#include stdio.h
extern int Add(int x, int y);
int main()
{printf(%d\n, Add(2, 3));return 0;
}
代码2
add.c
static int Add(int x, int y)
{return xy;
}
test.c
#include stdio.h
extern int Add(int x, int y);
int main()
{printf(%d\n, Add(2, 3));return 0;
}代码1是能够正常运⾏的但是代码2就出现了链接错误。 修饰全局变量是⼀模⼀样的⼀个函数在整个⼯程都可以使⽤被static修饰后只能在本⽂件内部使⽤其他⽂件⽆法正常的链接使⽤了。 本质是因为函数默认是具有外部链接属性具有外部链接属性使得函数在整个⼯程中只要适当的声明就可以被使⽤。但是被 static 修饰后变成了内部链接属性使得函数只能在⾃⼰所在源⽂件内部使⽤。 使⽤建议⼀个函数只想在所在的源⽂件内部使⽤不想被其他源⽂件使⽤就可以使⽤ static 修饰。