C 程序结构 由 数据 和 函数 组成;
函数是由汇编跳转发展而来的 :
模块化程序设计 :
一个复杂的 C 语言程序有几十上百万行代码, 这些代码可以分解成若干模块来实现, 即分解成一个个的函数来实现.
面向过程程序设计思想 :
声明和定义的区别 :
在 test_1.c 中定义变量 int i = 10; 这是定义了 int 类型的变量, 需要为该变量分配内存空间; 在 test_2.c 中声明变量 extern int i; 这是声明了 int 类型的变量, 变量定义在了别的文件中, 不必为该变量分配内存空间;
代码示例 :
#include <stdio.h>
//声明 : 声明外部变量, 该值是在其它文件中定义的
extern int global_int;
//声明 : 声明函数 plus, 该函数定义在下面
int plus(int i, int j);
int main()
{
//声明 : 声明函数 square, 如果不声明编译时会报错, 该声明只在 main 函数中有效果, 在main函数之外使用该方法就会报错
int square(int i);
//使用函数 square, 如果没有声明, 编译会报错
global_int = 3;
printf("%d\n", square(global_int));
//使用函数 plus, 如果没有声明编译会报错
printf("%d\n", plus(1, 2));
return 0;
}
//定义 : 定义函数 plus
int plus(int i, int j)
{
return i + j;
}
//定义 : 定义函数 square
int square(int i)
{
return i * i;
}
//定义 : 定义变量, 在这里需要为变量分配内存空间
int global_int;
函数参数分析 :
函数参数的求值顺序 (盲点) :
编程时尽量不要编写的代码依赖于操作数的实现顺序;
代码示例 :
#include <stdio.h>
int fun(int i, int j)
{
printf("%d, %d\n", i, j);
}
int main()
{
int m = 1;
fun(m, m ++);
printf("%d\n", i);
/*
打印出来的结果是 2, 1 \n 2
分析 : 函数的参数的求值顺序 不是 从左到右的, 是不固定的
这个顺序是编译器制定的, 不同编译器该顺序不同
*/
return 0;
}
分析 : 函数参数计算说明 : fun(m, m ++); 进入函数体之前先计算 m 和 m++ 的值, m 和 m++ 是实参, 在计算完成之后才赋值给 i 和 j 形参; 顺序点 : 在进入函数体前是一个顺序点, 需要将计算完毕的实参 赋值给形参; 实参 m 赋值 : 赋值给 形参 i, 此处已经到达顺序点, m 自增操作已经反映到内存中, 因此 从 内存中获取的 i 的值是 2; 实参 m++ 赋值 : 赋值给 形参 j, m++ 表达式的计算结果是 1, 因此 j 的值是1;
顺序点介绍 :
顺序点列举 :
顺序点代码示例 :
#include <stdio.h>
int fun(int i, int j)
{
printf("%d, %d\n", i, j);
}
//注意 : 这个知识点可能过时, k = k++ + k++; 在 Ubuntu 中执行结果是 5
int main()
{
//顺序点 : 在 k = 2; 表达式以分号结束, 这是一个顺序点
int k = 2;
int a = 1;
/*
顺序点 : 分号结尾处是顺序点, 该顺序点
第 1 个 k++, 计算时 k 先是 2, 自增操作到顺序点时执行;
第 2 个 k++, 计算时 k 还是 2, 自增操作到顺序点时执行;
加法计算完毕后 k 变成 4, 两次自增后变为 6
*/
k = k++ + k++;
printf("k = %d\n", k);
/*
a-- && a 进行逻辑运算,
其中 && 是顺序点, a-- 在 && 时执行 自减操作,
然后 a-- 结果变成了 0, a 的值也变成了 0, 进行逻辑与操作结果为 0
*/
printf("a--&&a = %d\n",a--&&a);
return 0;
}
函数缺省认定简介 :
fun(i)
{
return i
}
等价于
int fun(int i)
{
return i;
}
#include <stdio.h>
//函数缺省认定 : 没有类型的 参数 和 返回值 为 int 类型
fun(i, j)
{
return i + j;
}
int main()
{
printf("fun(i, j) = %d\n",fun(3, 3));
return 0;
}
可变参数简介 :
可变参数示例 :
可变参数的注意点 :
依次读取可变参数时, 注意 可变参数 的 数量 和 类型, 每个位置的参数 是 什么类型, 一定不要读取错误, 否则会产生不可预测的后果;
代码示例 :
#include <stdio.h>
#include <stdarg.h>
/*
定义可变参数 :
① 列出第一个参数 int a
② 使用 ... 表明后面有 个数不定 并且 类型不定 的 参数
*/
double avg(int arg_count, ...)
{
va_list args;
int i = 0; //循环控制变量
double sum = 0; //统计参数之和
//初始化列表, 让列表准备取值
va_start(args, arg_count);
for(i = 0; i < arg_count; i ++)
{
//从可变参数列表中获取数据, 数据类型是 int 类型
sum = sum + va_arg(args, int);
}
//结束使用可变参数列表
va_end(args);
return sum / arg_count;
}
int main()
{
//使用定义了可变参数的函数, 传入 11 个参数
printf("%f\n", avg(10, 1, 2, 3, 4, 5 , 6, 7, 8, 9, 10));
//使用定义了可变参数的函数, 传入 4 个参数
printf("%f\n", avg(3, 444, 555, 666));
return 0;
}
代码示例 : 分别使用 函数 和 宏 将数组数据清零;
#include <stdio.h>
/*
定义宏 : 这个宏的作用是将 p 目前是 void* 类型, 转为 char* 类型,
将后将每个字节的内容都设置为 0
*/
#define RESET(p, len) while(len > 0) ((char*)p)[--len] = 0;
/*
定义函数 : 也是将 p 指向的 len 字节的内存置空
*/
void reset(void* p, int len)
{
while(len > 0)
{
((char*)p)[--len] = 0;
}
}
int main()
{
//1. 定义两个数组, 用函数 和 宏 不同的方式重置数据
int array1[] = {1, 2, 3};
int array2[] = {4, 5, 6, 7};
//2. 获取两个数组大小
int len1 = sizeof(array1);
int len2 = sizeof(array2);
//3. 定义循环控制变量
int i = 0;
//4. 打印两个数组处理前的数据
printf("打印array1 : \n");
for( i = 0; i < 3; i ++)
{
printf("array1[%d] = %d \n", i, array1[i]);
}
printf("打印array2 : \n");
for( i = 0; i < 4; i ++)
{
printf("array2[%d] = %d \n", i, array2[i]);
}
//5. 使用宏的方式处理数组1
RESET(array1, len1);
//6. 使用函数的方式处理数组2
reset(array2, len2);
//7. 打印处理后的数组
printf("打印处理后的array1 : \n");
for( i = 0; i < 3; i ++)
{
printf("array1[%d] = %d \n", i, array1[i]);
}
printf("打印处理后的array2 : \n");
for( i = 0; i < 4; i ++)
{
printf("array2[%d] = %d \n", i, array2[i]);
}
return 0;
}
虽然看起来 函数 和 宏实现了相同的功能, 但是它们有很大的区别;
函数 和 宏 分析 :
宏的优势和弊端 : 宏的执行效率要高于函数, 但是使用宏会有很大的副作用, 非常容易出错, 下面的例子说明这种弊端;
代码示例 :
#include <stdio.h>
#define ADD(a, b) a + b
#define MUL(a, b) a * b
#define _MIN_(a, b) ((a) < (b) ? (a) : b)
int main()
{
int a = 1, b = 10;
//宏替换的结果是 : 2 + 3 * 4 + 5, 最终打印结果是 19
printf("%d\n", MUL(ADD(2, 3), ADD(4, 5)));
//宏替换的结果是 ((a++) < (b) ? (a++) : b), 打印结果是 2
printf("%d\n", _MIN_(a++, b));
return 0;
}
int main()
{
int a = 1, b = 10;
printf("%d\n", 2 + 3 * 4 + 5);
printf("%d\n", ((a++) < (b) ? (a++) : b));
return 0;
}
函数的优缺点 :
宏 定义 优势 :
//宏定义 : 实现分配 n 个 type 类型空间, 并返回 type 类型指针
#define MALLOC(type, n) (type*)malloc(n * sizeof(type))
//分配 5 个 float 大小的动态空间, 并将首地址存放在 指针 p 中;
float *p = MALLOC(int, 5);
宏定义 和 函数 小结 :
活动记录概述 : 函数调用时 将 下面一系列的信息 记录在 活动记录中 ;
参数入栈问题 : 函数参数的计算次序是不固定的, 严重依赖于编译器的实现, 编译器中函数参数入栈次序;
参数入栈 栈维护 问题示例 :
函数参数计算次序依赖于编辑器实现, 函数参数入栈的顺序可以自己设置;
函数参数调用约定 :
函数设计技巧 :
//这里第二个参数仅用于输入, 不需要修改该指针, 那么就将该参数设置成常量参数
void fun(char *dst, const char* src);