day 10 函数、数组学习
一、函数
1. 函数的思想
2. 函数语法
函数定义
//位置
main函数之前
main函数之后
函数调用
main函数之前定义 直接用
main函数之后定义 函数声明 //函数头 复制 后 加个分号
//必须放到 使用之前
3. 函数定义的细节
//函数返回值类型
//函数名
//形参
//形参和实参 ---- 个数 类型 顺序
//函数体
4. 设计函数原则
功能单一原则
是否要反复被用
数据梳理流程 (数据流)
实参给到形参 ---实际就是把实参的值 给到形参变量中 ---- 值传递
函数与函数之间的数据传递 ---- //全局变量
为了遵循高内聚、低耦合的设计原则,应尽量避免使用全局变量。全局状态会破坏模块独立性,增加代码维护成本。

5. 代码出问题 或 没达到效果
怎么办?
调试代码
//最基本的方法 ---就是加打印
// 把程序运行过程中的关键数据打印出来
函数递归
递归 --- 自己调用自己
递归 --- 1. 是一种特殊的循环
2. 如果不加结束条件,最终也会结束 ----函数调用-- 栈空间耗尽
3. for/while/do-while
for (;x;) //死循环
while(1)
{
}
do-while(1)
例子:1+2+3+ ...+100 //累加求和
sum = sum + i;
//递归的思想
sum(100)
|---sum(99) + 100
|---sum(98)+99
|---sum(97)+98
|---sum(96)+97
....
sum(3)+4
|---sum(2)+3
|---sum(1)+ 2
//1
递归解决问题的思路:
1.要解决问题n,
要看问题n-1的解决
2.当一个函数需要被自身重复调用时---可以考虑递归
//递归实现编程: 两个要素
1.递推关系
问题n 和 问题n-1之间关系
sum(100) = sum(99) + 100
|--- sum(98) + 99
sum(n) = sum(n-1) + n
2.递归结束条件
n = 1
递归结束
//递归代码
递归程序的代码逻辑
int sum(int n) //sum(n)
{
//判断是否到达递归结束条件
//是 --- 则返回
//否 --- 继续往下递归 (自己调用自己)
if(n == 1)
{
return 1;
}else //递归结束条件不满足
{
return sum(n-1) + n;
}
}
//递归的限制
1.层次不能太深
2.有些问题的解决 --- 确实需要通过递归实现



使用递归方式实现n!,把上面程序中的加法改为乘法即可。
汉诺塔实现



二、数组
1、数组 在函数中作为参数
数组基础回顾
int a[10] = {1, 2, 3, 4}; // 初始化数组,剩余元素自动赋值为0
a[0] = 1; // 数组元素是int型变量
a[1] = 2;
(1)数组元素作为函数参数
数组元素可以像普通变量一样作为函数参数,传递的是值(值传递):
// 1. 数组元素作为函数参数
void printArrayEle(int ele) {
printf("%d ", ele);
}
int main() {
int a[10] = {1, 2, 3, 4, 5};
printArrayEle(a[0]); // 传递数组元素的值
printArrayEle(a[1]);
return 0;
}
(2)整个数组作为函数参数
1. 传递机制
// 2. 数组做函数参数
int main() {
int a[10] = {1, 2, 3, 4, 5};
printArray(a, 10); // 传递数组名和长度
return 0;
}
void printArray(int arr[], int size) {
for(int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
2. 关键理解要点:
1. 传递的不是整个数组数据
-
传递的是首元素地址(指针)
-
实际上是地址传递,不是值传递
2. 为什么通过首地址可以访问所有元素
-
连续性:数组元素在内存中连续存储
-
单一性:所有元素类型相同,大小固定
-
有序性:通过下标偏移量计算元素位置
-
公式:
元素地址 = 首地址 + 下标 * sizeof(元素类型)
3. 函数形参的两种等价写法
// 形式1:数组写法(推荐,可读性好) void printArray(int a[], int size); // 形式2:指针写法(本质) void printArray(int *a, int size);
4. 重要注意事项
void printArray(int a[], int size) {
// 错误!这里sizeof(a)得到的是指针大小(4或8字节)
// 而不是数组总大小
printf("sizeof(a) = %lu\n", sizeof(a));
// 正确:需要额外传递数组长度参数
for(int i = 0; i < size; i++) {
// 正确访问
}
}
2.总结
整个数组作为函数参数:
形式上
形参 数组形式 数组长度形参
实参 数组名(首元素地址) 传实际长度
二维数组:
整型二维数组 做函数参数
形参 数组形式 行数 //形式上
printArray(int a[3][4],int row); //形式上
printArray(int (*a)[4],int row); //本质上 a起始还是指针变量
实参 数组名 实际行数
二维的本质 --- 一维数组的一维数组
int a[3][4];
int[4] a[3];
printArray(int[4] a[],int len);//从二维数组的本质考虑
二维字符型数组:
存放多个字符串
char s[3][10] = {"hello","world","china"};
形参 数组形式 行数 //形式上
printArray(char a[3][10],int row); //形式上
printArray(char (*a)[10],int row); //本质上 a起始还是指针变量
实参 数组名 实际行数
总结:
1.函数思想
2.函数定义
3.函数调用
4.递归调用
5.数组作为函数
一维字符数组做函数参数
6.标识符的作用域 和 可见性问题
标识符
名字
变量名
函数名
作用域
指的是标识符起效的范围
//局部作用域
{ }
//全局作用域
不在任何一个{ }范围内
可见性
站在程序角度看,执行到某句代码时,哪些标识符可见
int a=10;
main()
{
int a=20;
printf("a = %d\n",a);
}
标识符的可见性的规则:
1.先定义,后使用
2.同一作用域中,不能有同名标识符
3.在不同的作用域,同名标识符,相互之间没有影响
4.如果是不同的作用域,但是作用域之间存在嵌套关系,
则内层的作用域的同名标识符,会屏蔽外层的作用域的同名标识符。
(就近原则)
变量:
局部变量
放在 {} 范围内的 就是局部变量
全局变量
不在任何一个{} 范围内的,就是全局变量
时间维度:
变量的声明周期
(无到有再到无)
局部变量
生命周期
当程序运行到对应的定义数据时,才创建
当程序运行到标识符,所在作用域结束时,销毁
全局变量
生命周期
从程序运行开始,就存在
到程序运行结束,销毁
存储类别的关键字
auto --- 数据存储在 栈上 ---自动申请 自动释放
static --- 静态 表示数据存储在 静态区
静态区变量初始化, 不能用变量初始化,只能用常量初始化
static 修饰的局部变量,值具有继承性,只需要初始化一次
register
extern
//五个区
内存分区:
-
栈区:自动变量,函数调用时分配,函数结束释放
-
堆区:动态分配内存(malloc/free)
-
静态区/全局区:全局变量和静态变量
-
字符串常量区:常量字符串
-
代码区:程序代码
int main()
{
auto int a; //局部变量 --- 一般空间开在栈上
}
更多推荐



所有评论(0)