目录

前文列表

程序编译流程与 GCC 编译器

C 语言编程 — 基本语法

C 语言编程 — 基本数据类型

C 语言编程 — 变量与常量

C 语言编程 — 运算符

C 语言编程 — 逻辑控制语句

函数

函数的本质就是针对变量的操作过程,同时可能也会改变当前程序的状态。它接受多个输入值,计算并返回一个输出值。

函数大体上分为 3 类:

  1. 主函数:每个 C 程序都至少有一个 main()
  2. 内置函数:由 C 标准库提供。例如,strcat() 用来连接两个字符串,memcpy() 用来复制内存到另一个位置。
  3. 自定义函数

函数的声明

函数声明就是告诉 C 编译器函数的名称、返回类型和参数以及如何调用函数。函数的实际主体可以单独定义,但当你在一个源文件中定义函数且在另一个源文件中调用函数时,函数声明是必需的。

声明函数时,首先将返回值的类型写在前面,后面紧跟函数的名字,而后的一对圆括号里面包裹函数的输入参数,参数之间用 , 进行分割。

函数声明包括以下几个部分:

return_type function_name(parameter list);

在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面两种都是有效的声明:

int max(int num1, int num2);
int max(int, int);

函数的定义

函数体部分紧跟其后,

函数定义除了定义函数的名称、返回类型、形参列表之外,最重要的是编写函数的主题,即函数体。包裹在 {} 里,里面包含了函数执行的所有语句,语句之间使用 ; 分隔。return 语句用来结束函数的执行,并返回一个值。

C 程序中函数的结构如下:

return_type function_name(parameter list){
body of the function
}
  • 返回类型:一个函数可以返回一个值,返回类型定义了函数返回的值的数据类型。有些函数不需要返回值,在这种情况下,return_type 是关键字 void
  • 函数名(函数标识符):函数名和参数列表一起构成了函数签名。
  • 形式参数:形参就像是占位符。当函数被调用时,向形参传递一个值,而这个值被称为实际参数。形参列表包括函数参数的类型、顺序、数量。形参是可选的,即形参列表可以为。
  • 函数主体:包含了一组定义函数执行任务的语句。
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2)
{
/* 局部变量声明 */
int result; if (num1 > num2)
result = num1;
else
result = num2; return result;
}

函数的形参与实参

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。当调用函数时,有两种向函数传递参数的方式:

  • 值传递:该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
  • 引用传递:通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

值传递

默认情况下,C 语言使用传值调用方法来传递参数。一般来说,这意味着函数内的代码不会改变用于调用函数的实际参数。

  • swap.c
/* 函数定义 */
void swap(int x, int y)
{
int temp; temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */ return;
}
  • main.c
#include <stdio.h>

/* 函数声明 */
void swap(int x, int y); 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;
}

引用传递

通过引用传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问。

  • swap.c
/* 函数定义 */
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */ return;
}
  • main.c
#include <stdio.h>

/* 函数声明 */
void swap(int *x, int *y); 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 语言提供了 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。

int func(int, ... ) {}

int main() {
func(1);
func(1, 2);
func(1, 2, 3);
return 0;
}
  • 定义一个函数,int 形参代表了要传递的可变参数的总数,... 可变长形参运算符标识函数的形参数量是可变的。
  • 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
  • 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
  • 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  • 使用宏 va_end 来清理赋予 va_list 变量的内存。
#include <stdio.h>
#include <stdarg.h> double Avg(int num, ...) {
va_list valist;
double sum = 0.0;
int i; /* 初始化 valist,数量为 num 个 */
va_start(valist, num);
for (i = 0; i < num; i++) {
/* 访问所有赋给 valist 的参数 */
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist); return sum / num; } int main() {
printf("Average of 2, 3, 4, 5 = %f\n", Avg(4, 2, 3, 4, 5));
printf("Average of 5, 10, 15 = %f\n", Avg(3, 5, 10, 15));
return 0;
}

运行:

$ ./main
Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000

函数的调用

定义 C 函数时,会定义函数做什么,然后通过调用函数来完成已定义的任务。当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。

调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值。

#include <stdio.h>

/* 函数声明 */
int max(int num1, int num2); int main ()
{
/* 局部变量定义 */
int a = 100;
int b = 200;
int ret; /* 调用函数来获取最大值 */
ret = max(a, b); printf( "Max value is : %d\n", ret ); return 0;
} /* 函数返回两个数中较大的那个数 */
int max(int num1, int num2)
{
/* 局部变量声明 */
int result; if (num1 > num2)
result = num1;
else
result = num2; return result;
}

函数的指针

函数名本质就是一个地址,也就是一个指针。函数指针是指向函数的指针类型变量,函数指针可以像一般函数一样,用于调用函数、传递参数。也就是说,函数指针其实可以看做就是一个函数的 “别名”。

函数指针很方便,可以作为实参传入函数,在函数体中,仅仅通过指针就可以调用函数,这也是为了提高代码的效率。函数指针和普通指针差不多,主要的区别是函数指针指向了特定的函数。

声明一个函数指针:

typedef int (*fun_ptr)(int, int);

e.g.

#include <stdio.h>

int max(int x, int y){
return x > y ? x : y;
} int main(void){
/* 定义并初始化一个函数指针 */
int (*p)(int, int) = &max; // 可以省略地址运算符 &
int a, b, c, d; printf("Input three numbers:");
scanf("%d %d %d", &a, &b, &c); d = p(p(a, b), c);
printf("MAX: %d\n", d); return 0;
}

回调函数

回调函数就是将函数指针作为实参传入到某个函数,即:回调函数就是一个通过函数指针调用的函数。

#include <stdlib.h>
#include <stdio.h> void populate_array(int *array, size_t arraySize, int (*getNextValue)(void)) { size_t i; for(i = 0; i < arraySize; i++) {
array[i] = getNextValue();
}
} int getNextRandomValue(void) {
return rand();
} int main() {
int i;
int myarray[10]; populate_array(myarray, 10, &getNextRandomValue); // & 可以省略,因为函数名的本质就是一个指针 for(i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
} printf("\n");
return 0;
}

递归函数

递归,指的是在函数的定义中调用函数自身

void recursion()
{
statements;
... ... ...
recursion(); /* 函数调用自身 */
... ... ...
} int main()
{
recursion();
}

但在使用递归时,一定要注意编写退出条件,否则会进入死循环。递归函数在解决许多数学问题上起了至关重要的作用,比如计算一个数的阶乘、生成斐波那契数列,等等。

数的阶乘

#include <stdio.h>

double factorial(unsigned int i) {
if(i <= 1)
{
return 1;
}
return i * factorial(i - 1);
} int main() {
int i = 15;
printf("%d 的阶乘为 %f\n", i, factorial(i));
return 0;
}

斐波那契数列

#include <stdio.h>

int fibonaci(int i) {
if (i == 0) {
return 0;
}
if (i == 1) {
return 1;
}
return fibonaci(i-1) + fibonaci(i-2);
} int main() {
int i;
for (i = 0; i < 10; i++) {
printf("%d\t\n", fibonaci(i));
}
return 0;
}

构造函数(Constructor)

构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值。例如:

  • Java 中的 new 运算符
  • Python 中的 __init__ 函数

通常的,一个类中只有一个构造函数;特别的,一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。

析构函数(Destructor)

析构函数与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做 “清理善后” 的工作。

以 C++ 为例:析构函数名也应与类名相同,只是在函数名前面加一个位取反符~,例如:~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括 void 类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。

C 语言编程 — 函数的更多相关文章

  1. Linux下C语言编程实现spwd函数

    Linux下C语言编程实现spwd函数 介绍 spwd函数 功能:显示当前目录路径 实现:通过编译执行该代码,可在终端中输出当前路径 代码实现 代码链接 代码托管链接:spwd.c 所需结构体.函数. ...

  2. LINUX下C语言编程调用函数、链接头文件以及库文件

    LINUX下C语言编程经常需要链接其他函数,而其他函数一般都放在另外.c文件中,或者打包放在一个库文件里面,我需要在main函数中调用这些函数,主要有如下几种方法: 1.当需要调用函数的个数比较少时, ...

  3. C语言编程学习:使用函数必须知道的3点注意事项

    C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现 ...

  4. C语言编程中函数指针的定义及使用

    C语言中函数指针的定义: typedef int (*funcPtr)(int, int)表示定义了一个函数指针funcPtr,这个函数指针只能指向如下: int add(int, int).int ...

  5. C语言编程实现Linux命令——who

    C语言编程实现Linux命令--who 实践分析过程 who命令是查询当前登录的每个用户,它的输出包括用户名.终端类型.登录日期及远程主机,在Linux系统中输入who命令输出如下: 我们先man一下 ...

  6. 个人c语言编程风格总结

    总结一下我个人的编程风格及这样做的原因吧,其实是为了给实验室写一个统一的C语言编程规范才写的.首先声明,我下面提到的编程规范,是自己给自己定的,不是c语言里面规定的. 一件事情,做成和做好中间可能隔了 ...

  7. C语言的函数

    "函数"在英文的翻译是"function",无论在自然科学还是计算机科学都是这个词,而"function"的本意是"功能" ...

  8. 混合语言编程:启用CLR(公共语言运行时编译)让C#调用C++

    前言 关于混合C#和C++的编程方式,本人之前写过一篇博客(参见混合语言编程:C#使用原生的Directx和OpenGL),在之前的博客中,介绍了在C#的Winform和WPF下使用原生的Direct ...

  9. Linux基础与Linux下C语言编程基础

    Linux基础 1 Linux命令 如果使用GUI,Linux和Windows没有什么区别.Linux学习应用的一个特点是通过命令行进行使用. 登录Linux后,我们就可以在#或$符后面去输入命令,有 ...

  10. LINUX下C语言编程基础

    实验二 Linux下C语言编程基础 一.实验目的 1. 熟悉Linux系统下的开发环境 2. 熟悉vi的基本操作 3. 熟悉gcc编译器的基本原理 4. 熟练使用gcc编译器的常用选项 5 .熟练使用 ...

随机推荐

  1. #博弈论,贪心#AT2376 [AGC014D] Black and White Tree

    题目传送门 分析 考虑到先手放一个白点后手必将在相邻位置放一个黑点, 如果没有合适的位置放黑点先手必胜,也就是问是否存在完美匹配, 直接从叶子节点到根贪心匹配即可 代码 #include <cs ...

  2. #KMP,矩阵乘法#洛谷 3193 [HNOI2008]GT考试

    题目 给定\(n,m,K\)和一个长度为\(m\)的数\(x\), 问有多少个\(n\)位数满足任意一段不与\(x\)完全相同,可含前导0 \(n\leq 10^9,m\leq 20\) 分析 设\( ...

  3. #计数#A 古老谜题

    From NOIP2020 模拟赛 B 组 Day4 题目 给定一个长度为\(n\)的01序列\(a\), 问有多少个三元组\((l,p,r),1\leq l<p<r\leq n\) 满足 ...

  4. 小师妹学JavaIO之:File文件系统

    目录 简介 文件权限和文件系统 文件的创建 代码中文件的权限 总结 简介 小师妹又遇到难题了,这次的问题是有关文件的创建,文件权限和文件系统相关的问题,还好这些问题的答案都在我的脑子里面,一起来看看吧 ...

  5. OpenHarmony Meetup 2023 广州站圆满举办,城市巡回全面启航

      "OpenHarmony正当时--技术开源"OpenHarmony Meetup 2023城市巡回活动,旨在通过meetup线下交流形式,解读OpenHarmony作为下一代智 ...

  6. 本周四晚19:00知识赋能第八期第1课丨ArkUI框架整体设计

    OpenAtom OpenHarmony(以下简称"OpenHarmony")开源开发者成长计划项目自 2021 年 10 月 24 日上线以来已经走过了7期,为开发者提供了一个良 ...

  7. 深度剖析 Spring 框架在 Java 应用开发中的优势与应用

    Spring 是用于企业 Java 应用程序开发的最流行的应用程序开发框架.全球数百万开发人员使用 Spring Framework 创建高性能.易于测试和可重用的代码.Spring Framewor ...

  8. MyBatis resultMap中collection过滤空字段

    在使用MyBatis查询数据时,返回值可以定义为resultMap. 如果返回的对象中有列表,还可以使用collection标签进行定义. 此时,如果不想某些字段为空的数据加入列表,可以使用notNu ...

  9. 当使用git出现提示untracked files时怎么办?

    当使用 git 出现提示 untracked files 时怎么办? 背景介绍: 在使用 git 工具时,遇到如下错误. 报错内容: $ git status On branch master No ...

  10. K8S 性能优化 - K8S APIServer 调优

    前言 K8S 性能优化系列文章,本文为第二篇:Kubernetes API Server 性能优化参数最佳实践. 系列文章: <K8S 性能优化 - OS sysctl 调优> 参数一览 ...