函数的概念
函数是一段执行特定任务的代码块,具有名称和可选的参数和返回值。
函数可以将代码模块化,提高代码的可读性、可维护性和可重用性。
函数的组成
返回类型 函数名(形参列表)
{
}
返回值 -> 函数返回值类型可在函数体内部通过return语句返回对应类型
调用约定 -> __cdecl __stdcall __fastcall
函数名 -> 用户定义的函数名类似于变量标识符,遵守标识符的命名规则
参数 -> 各类型的参数集合,通过逗号分隔,在定义函数时指定的形参(未进行函数调用时不占用内存空间因此也称为形式参数或虚拟参数且不能赋值),格式为类型 名称, 类型 名称 ... 参数是可有可无的并不是必须存在
函数体 -> 函数内代码放在{}内
return -> 函数体内部执行到return语句则会返回调用位置执行剩余代码,return语句后尽量与函数定义返回值类型保持一直且存在返回值类型时必须在return语句后写对应的值
函数的声明和定义
函数的声明是指在使用函数之前提供函数的原型,包括函数的名称、参数类型和返回类型。
函数的定义是指实现函数的代码块,包括函数的名称、参数列表、函数体和返回语句。
// 函数的声明
int add(int a, int b);
// 函数的定义
int add(int a, int b)
{
return a + b;
}
函数的参数传递
函数可以接受参数,参数是函数执行时传递给函数的值。
参数可以是基本数据类型、指针、数组或结构体等。
// 基本数据类型参数
int square(int num);
// 指针参数
void swap(int *a, int *b);
// 数组参数
int sum(int arr[], int size);
// 结构体参数
void printStudent(struct Student s);
函数的返回值
函数可以返回一个值,表示函数执行的结果。
返回值可以是基本数据类型、指针、结构体或联合体等。
// 返回基本数据类型
int add(int a, int b);
// 返回指针
int* createArray(int size);
// 返回结构体
struct Point getMidPoint(struct Point p1, struct Point p2);
控制台程序入口函数
#include
#include
int main()
{
printf("Hello 0xCC \r\n");
return 0;
}
函数参数
反汇编分析入口函数参数
参数含义
argc -> 命令行参数个数
argv -> 命令行参数
envp -> 环境变量
#include
#include
int main(int argc, char* argv[], char* envp[])
{
printf("argc -> %d \r\n", argc);
do
{
printf("argv -> %s\r\n", *argv);
} while (*(++argv));
do
{
printf("envp -> %s\r\n", *envp);
} while (*(++envp));
return 0;
}
入口函数
函数声明
函数声明是在使用函数之前提供函数的原型,以便编译器知道函数的名称、参数类型和返回类型。
函数声明格式 -> 返回类型 函数名(参数列表);
函数声明作用
函数定义
课堂代码
#include
#include
//函数声明
int Add(int a, int b);
int main()
{
//函数调用
int ret = Add(12, 6);
printf("%d \r\n", ret);
return 0;
}
//函数定义
int Add(int a, int b)
{
int c = a + b;
return c;
}
函数声明提供函数的原型,包括函数名、参数列表和返回类型。
函数定义实现函数的具体代码,包括函数名、参数列表、返回类型和函数体。
函数声明和定义的目的是提供函数的接口和实现,以便在程序中使用和调用函数。
函数声明和定义需要保持一致,包括函数名、参数列表和返回类型。
函数声明允许在使用函数之前提供函数的原型,以便编译器检查函数调用的正确性。
函数分文件编写是将程序中的函数分散到多个独立的源文件中,每个文件包含一个或多个相关的函数。
函数分文件编写的步骤
课程示例
头文件
#pragma once
//函数声明
int Add(int a, int b);
源文件
#include "Cacl.h"
int Add(int a, int b)
{
return a + b;
}
调用
#include
#include
#include "Cacl.h"
int main()
{
//函数调用
int ret = Add(12, 6);
printf("%d \r\n", ret);
return 0;
}
值传递
函数值传递是指将参数的值复制一份,然后将复制的值传递给函数。在函数内部,对参数的修改不会影响原始的参数值。
代码示例
#include
void modifyValue(int x)
{
x = x * 2;
printf("Inside function: %d\n", x);
}
int main()
{
int num = 5;
printf("Before function: %d\n", num);
modifyValue(num);
printf("After function: %d\n", num);
return 0;
}
函数的参数是在栈上分配内存的,函数调用结束后,栈上的内存会被释放,不会影响原始参数的值。
地址传递
#include
void modifyval(int* p)
{
//p -> main.num.addr
*p = 100;
printf("Fun -> %d\r\n", *p);
}
int main()
{
int num = 10;
printf("Main -> %d\r\n", num);
modifyval(&num);
printf("Main -> %d\r\n", num);
return 0;
}
#include
void Fun(int* p)
{
for (size_t i = 0; i < 5; i++)
{
p[i] = 10 + i;
}
}
int main()
{
int Arr[5] = { 0 };
for (size_t i = 0; i < 5; i++)
{
printf("Arr[%d] -> %d\t", i, Arr[i]);
}
printf("\n");
Fun(Arr);
for (size_t i = 0; i < 5; i++)
{
printf("Arr[%d] -> %d\t", i, Arr[i]);
}
return 0;
}
标准调用约定(CDECL) -> 在标准调用约定下,函数的参数从右向左依次入栈,由调用者负责清理栈空间。返回值通常通过寄存器传递。
/*
_Check_return_
int __cdecl strcmp(
_In_z_ char const* _Str1,
_In_z_ char const* _Str2
);
*/
微软调用约定(STDCALL) -> 微软调用约定是用于Windows平台的一种调用约定,函数的参数从右向左依次入栈,由被调用函数负责清理栈空间。
/*
#define WINAPI __stdcall
int WINAPI MessageBoxA(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);
*/
快速调用约定(FASTCALL) -> 快速调用约定在标准调用约定的基础上做了优化,将部分参数通过寄存器传递,减少了栈操作的开销。
void __fastcall Fun(int a, int b, int c, int d)
{
}
调用约定 | 参数压栈顺序 | 平衡堆栈 |
---|---|---|
__cdecl | 从右至左入栈 | 调用者清理栈 |
__stdcall | 从右至左入栈 | 自身清理堆栈 |
__fastcall | ECX(PARAM1)/EDX(PARAM2) 剩下->从右至左入栈 | 自身清理堆栈 |
__cedcl
#include
#include
void __cdecl Fun(int a, int b, int c, int d)
{
}
int main()
{
Fun(1, 2, 3, 4);
return 0;
}
push 4
push 3
push 2
push 1
call Fun (044F922h)
add esp,10h
__stdcall
#include
#include
void __stdcall Fun(int a, int b, int c, int d)
{
}
int main()
{
Fun(1, 2, 3, 4);
return 0;
}
main
push 4
push 3
push 2
push 1
call Fun (045037Ch)
Fun
ret 10h
__fastcall
#include
#include
void __fastcall Fun(int a, int b, int c, int d)
{
}
int main()
{
Fun(1, 2, 3, 4);
return 0;
}
main
push 4
push 3
mov edx,2
mov ecx,1
call Fun (0450381h)
Fun
ret 8
宏函数是一种在编译期间进行文本替换的机制,它可以用来定义常量、简化代码、提高代码的可读性和可维护性等。
宏定义的基本语法 -> #define 宏名称 替换文本
宏定义的注意事项
宏定义的课堂示例
#include
#include
//宏函数
#define ADD(A,B) ((A) + (B))
int __cdecl Add(int a, int b)
{
return a + b;
}
int main()
{
int num1 = Add(123, 123);
int num2 = Add(112323, 123);
printf("%d %d \r\n", num1, num2);
int num3 = ADD(123, 123);
int num4 = ADD(112323, 123);
printf("%d %d \r\n", num3, num4);
return 0;
}
数组
数组参数
#include
void printArray(int arr[], int size)
{
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
printArray(arr, size);
return 0;
}
数组返回
#include
int* createArray(int size)
{
int* arr = malloc(size * sizeof(int));
for (int i = 0; i < size; i++)
{
arr[i] = i + 1;
}
return arr;
}
int main()
{
int size = 5;
int* arr = createArray(size);
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
结构体
结构体普通参数
#include
#include
struct Student
{
int m_Id;
int m_Score;
};
void Fun1(struct Student student)
{
student.m_Id = 1234;
student.m_Score = 60;
printf("Id -> %d Score -> %d \r\n", student.m_Id, student.m_Score);
}
int main()
{
struct Student s1 = {9527, 99};
printf("Id -> %d Score -> %d \r\n", s1.m_Id, s1.m_Score);
Fun1(s1);
printf("Id -> %d Score -> %d \r\n", s1.m_Id, s1.m_Score);
return 0;
}
结构体指针参数
#include
#include
struct Student
{
int m_Id;
int m_Score;
};
void Fun1(struct Student* student)
{
student->m_Id = 1234;
student->m_Score = 60;
printf("Id -> %d Score -> %d \r\n", student->m_Id, student->m_Score);
}
int main()
{
struct Student s1 = {9527, 99};
printf("Id -> %d Score -> %d \r\n", s1.m_Id, s1.m_Score);
Fun1(&s1);
printf("Id -> %d Score -> %d \r\n", s1.m_Id, s1.m_Score);
return 0;
}
结构体数组参数
#include
#include
struct Student
{
int m_Id;
int m_Score;
};
void Fun1(struct Student Arr[], int nSize)
{
for (size_t i = 0; i < nSize; i++)
{
Arr[i].m_Id = (i + 1) * 10;
Arr[i].m_Score = (i + 1) * 20;
}
}
int main()
{
struct Student Arr[5] = { 0 };
Fun1(Arr, 5);
for (size_t i = 0; i < 5; i++)
{
printf("Index -> [%d] Id -> [%d] Score -> [%d] \r\n",
i,
Arr[i].m_Id,
Arr[i].m_Score);
}
return 0;
}
不定量参数
函数框架
#include
return_type function_name(type fixed_param, ...)
{
va_list args;
// 初始化可变参数列表
va_start(args, fixed_param);
// 访问可变参数
// 在函数内部,使用va_arg宏来读取参数值
// 结束可变参数列表的访问
va_end(args);
// 函数体
}
课堂示例
#include
#include
void PrintIntegers(int count, ...)
{
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++)
{
int value = va_arg(args, int);
printf("%d ", value);
}
va_end(args);
}
int main()
{
PrintIntegers(3, 1, 2, 3); // 输出:1 2 3
return 0;
}
课堂练习
#include
#include
int MyPrintf(const char* format, ...)
{
va_list args;
va_start(args, format);
int count = 0; // 记录输出字符的数量
for (int i = 0; format[i] != '\0'; i++)
{
if (format[i] == '%')
{
i++; // 跳过 '%' 字符
// 根据格式字符读取对应类型的参数值
switch (format[i])
{
case 'd':
count += printf("%d", va_arg(args, int));
break;
case 'f':
count += printf("%.2f", va_arg(args, double));
break;
case 's':
count += printf("%s", va_arg(args, char*));
break;
default:
putchar(format[i]);
count++;
break;
}
}
else
{
putchar(format[i]);
count++;
}
}
va_end(args);
return count;
}