函数
c语言是一种面向过程的高级语言
打开VS安装路径就可以看到封装好的库函数的头文件.h及其实现的.c文件c,其实有的库函数封装了系统调用函数,调用库函数就可以间接调用系统调用
拿库函数strlen来举个栗子,strlen是计算字符串字符个数的函数,位于string.h头文件下
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "producing!";
printf("%d", strlen(str));
return 0;
}
模拟实现一下strlen
递归实现strlen
//递归实现strlen
#include <stdio.h>
int strlen_self(char* ch) {
if (*ch == '\0')
return 0;
return 1 + strlen_self(ch + 1);
}
int main() {
char st[10] = "ASDFGHJKL";
printf("%d\n", strlen_self(st));
return 0;
}
非递归实现strlen
//非递归实现strlen
#include <stdio.h>
int strlen_self(char* ch) {
int sum = 0;
while (*ch++ != '\0') {
sum++;
}
return sum;
}
int main() {
char st[10] = "ASDFGHJKL";
printf("%d\n", strlen_self(st));
return 0;
}
库函数是人为封装好的供程序开发人员使用的成熟的安全的函数,避免了重复造轮子的时间开销,提高了程序开发的效率和缩短了时间周期
递归
程序调用自身的编程技巧称为递归( recursion)。递归是一种分治的思想,将大规模的一系列相同操作的作业分成若干相同操作的小作业从而简化步骤。
递归的两个必要条件:
-
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
-
每次递归调用之后越来越接近这个限制条件。
从上面递归模拟实现strlen函数的函数出发,我们对递归的过程进行分析
为了方便观察和追踪理解递归的过程,我们将st初始化为字符串"ASD"
//递归实现strlen
#include <stdio.h>
int strlen_self(char* ch) {
if (*ch == '\0')
return 0;
return 1 + strlen_self(ch + 1);
}
int main() {
char st[4] = "ASD";
printf("%d\n", strlen_self(st));
return 0;
}
我们观察函数栈帧就可以发现,strlen_self函数总共调用了4次,栈帧出栈的过程便是递归归的过程
不断出栈…
直到栈中只剩余main函数的帧
便会在main函数中得到strlen_self的返回值
函数传参
按值传参
举一个生动的例子:reduce函数想一次消耗一个苹果
#include <stdio.h>
void reduce(int apple) {
apple--;
}
int main() {
int apple = 10;
reduce(apple);
printf("%d\n", apple);
return 0;
}
实际上这样是做不到对main函数中的apple减减的,reduce函数的形参列表只有一个参数,这个参数是形参,按值传参是不会影响实参的值的,也就是把reduce函数中访问的apple变量是main函数中apple变量的一份临时拷贝
在c++中引入了引用传参,其底层实现和按址传参是一样的原理,形参就是装实参的引用,因此形参的改变可以影响实参的值
#include <iostream>
using namespace std;
void reduce(int& apple) {
apple--;
}
int main() {
int apple = 10;
reduce(apple);
cout<<apple<<endl;
return 0;
}
按址传参
除了C++引用传参外,C语言按址传参就能做到对实参进行值的修改
#include <stdio.h>
void reduce(int* apple) {
(*apple)--;
}
int main() {
int apple = 10;
reduce(&apple);
printf("%d\n", apple);
return 0;
}
注意后置–的优先级是高于取值操作符(解引用操作符)*的
再看一个数组传参与sizeof使用的例子
#include <stdio.h>
void show_arr1(int arr[]) {
printf("%d\n", sizeof(arr));
}
void show_arr2(int* arr) {
printf("%d\n", sizeof(arr));
}
int main() {
int arr[] = { 1, 2, 3, 4, 5, 6 };
show_arr1(arr);
show_arr2(&arr);
return 0;
}
以上代码是有问题的这里要注意:sizeof只能在数组定义的作用域内,使用sizeof统计数组大小
要想让调用的访问数组的函数知道数组长度,函数必须设置一个参数用于接收数组长度,如下:
#include <stdio.h>
void show_arr1(int arr[], int n) {
printf("%d\n", sizeof(arr));
for (int i = 0; i < n; ++i)
printf("%d ", arr[i]);
printf("\n");
}
void show_arr2(int* arr, int n) {
printf("%d\n", sizeof(arr));
for (int i = 0; i < n; ++i)
printf("%d ", *(arr+i));
printf("\n");
}
int main() {
int arr[] = { 1, 2, 3, 4, 5, 6 };
int n = sizeof(arr) / sizeof(int);
show_arr1(arr, n);
show_arr2(&arr, n);
printf("%p\n%p\n", arr, &arr);
return 0;
}
这里需要注意两个细节,arr数组名就是数组的首元素地址,arr本质就是一个*const 1 的指针变量,而&arr数组名取地址与数组名arr的地址是一样的
关于const修饰指针变量的知识点请查看博文:2.变量的作用域和常量 中 扩展:const修饰指针常见的三种情况 ↩︎