1. C语言函数基础概念
函数是C语言程序的基本构建块,它是一组执行特定任务的语句集合。每个C程序至少包含一个main函数,这是程序的入口点。函数的主要优势在于代码复用和模块化编程。
1.1 函数的基本结构
一个典型的C函数由以下几部分组成:
c复制返回类型 函数名(参数列表) {
// 函数体
局部变量声明;
执行语句;
return 返回值;
}
例如,一个简单的加法函数:
c复制int add(int a, int b) {
int sum = a + b;
return sum;
}
这个函数包含:
- 返回类型:int
- 函数名:add
- 参数列表:int a, int b
- 函数体:包含变量声明和return语句
1.2 函数声明与定义
函数声明告诉编译器函数的存在,而函数定义则提供具体实现。声明通常放在头文件或程序开头:
c复制// 函数声明
int max(int num1, int num2);
// 函数定义
int max(int num1, int num2) {
return (num1 > num2) ? num1 : num2;
}
声明时可以省略参数名,只保留类型:
c复制int max(int, int);
2. 函数参数传递机制
2.1 值传递(Pass by Value)
默认情况下,C语言使用值传递方式。函数内对参数的修改不会影响原始变量:
c复制void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
// 这里交换的只是x,y的副本
}
int main() {
int a = 5, b = 10;
swap(a, b); // a,b的值不会被交换
printf("a=%d, b=%d", a, b); // 输出a=5, b=10
return 0;
}
2.2 指针传递(Pass by Pointer)
通过传递变量的地址,可以在函数内修改原始变量:
c复制void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp; // 通过指针操作实际变量
}
int main() {
int a = 5, b = 10;
swap(&a, &b); // 传递地址
printf("a=%d, b=%d", a, b); // 输出a=10, b=5
return 0;
}
2.3 数组作为函数参数
数组名本身就是指针,传递数组时实际传递的是数组首地址:
c复制void printArray(int arr[], int size) {
for(int i=0; i<size; i++) {
printf("%d ", arr[i]);
}
}
int main() {
int nums[5] = {1,2,3,4,5};
printArray(nums, 5); // 输出1 2 3 4 5
return 0;
}
3. 函数的返回值与作用域
3.1 返回值处理
函数可以返回基本类型、指针,甚至结构体:
c复制// 返回基本类型
int getMax(int a, int b) {
return (a > b) ? a : b;
}
// 返回指针
int* createArray(int size) {
int *arr = (int*)malloc(size * sizeof(int));
return arr;
}
// 返回结构体
typedef struct {
int x;
int y;
} Point;
Point createPoint(int x, int y) {
Point p = {x, y};
return p;
}
3.2 变量的作用域
C语言中有以下几种作用域:
- 局部变量:函数内部定义,只在函数内有效
- 全局变量:函数外部定义,整个程序可见
- 静态局部变量:函数内用static声明,生命周期延长到程序结束
- 寄存器变量:用register声明,建议编译器放入寄存器
c复制int globalVar = 10; // 全局变量
void func() {
static int count = 0; // 静态局部变量
count++;
printf("Count: %d\n", count);
}
int main() {
func(); // 输出Count: 1
func(); // 输出Count: 2
return 0;
}
4. 高级函数特性
4.1 递归函数
递归函数直接或间接调用自身,必须包含终止条件:
c复制// 计算阶乘
int factorial(int n) {
if(n <= 1) return 1; // 终止条件
return n * factorial(n-1); // 递归调用
}
// 斐波那契数列
int fibonacci(int n) {
if(n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
递归虽然简洁,但效率较低,可能造成栈溢出。对于复杂问题,可以考虑使用迭代或动态规划。
4.2 函数指针
函数指针可以指向函数,实现回调等高级功能:
c复制#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main() {
int (*operation)(int, int); // 声明函数指针
operation = add;
printf("5 + 3 = %d\n", operation(5, 3));
operation = sub;
printf("5 - 3 = %d\n", operation(5, 3));
return 0;
}
4.3 可变参数函数
使用stdarg.h可以实现接受可变数量参数的函数:
c复制#include <stdarg.h>
#include <stdio.h>
double average(int count, ...) {
va_list ap;
double sum = 0;
va_start(ap, count);
for(int i=0; i<count; i++) {
sum += va_arg(ap, int);
}
va_end(ap);
return sum / count;
}
int main() {
printf("Avg: %.2f\n", average(3, 10, 20, 30)); // 输出20.00
return 0;
}
5. 函数的最佳实践与常见问题
5.1 函数设计原则
- 单一职责原则:一个函数只做一件事
- 合理的函数长度:通常不超过一屏(约50行)
- 有意义的命名:函数名应清晰表达其功能
- 适当的参数数量:一般不超过5个
- 避免全局变量:尽量通过参数传递数据
5.2 常见错误与调试
- 未声明函数:导致编译器警告
- 参数类型不匹配:可能造成隐式类型转换
- 忘记return语句:非void函数必须有返回值
- 栈溢出:递归深度过大
- 指针错误:野指针或空指针解引用
调试技巧:
- 使用printf打印中间值
- 分步测试小函数
- 使用调试器设置断点
5.3 性能优化建议
- 小函数使用inline关键字(但编译器可能忽略)
- 频繁调用的简单函数考虑用宏实现
- 避免在循环中调用复杂函数
- 对于递归函数,考虑尾递归优化或改为迭代
c复制// 使用宏代替简单函数
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
// 尾递归优化的阶乘函数
int factorial_tail(int n, int result) {
if(n <= 1) return result;
return factorial_tail(n-1, n * result);
}
int factorial(int n) {
return factorial_tail(n, 1);
}
6. 实际应用案例
6.1 字符串处理函数
实现标准库中的常用字符串函数:
c复制// 字符串长度
int my_strlen(const char *str) {
int len = 0;
while(*str++) len++;
return len;
}
// 字符串复制
char* my_strcpy(char *dest, const char *src) {
char *ret = dest;
while((*dest++ = *src++));
return ret;
}
// 字符串比较
int my_strcmp(const char *s1, const char *s2) {
while(*s1 && (*s1 == *s2)) {
s1++;
s2++;
}
return *(unsigned char*)s1 - *(unsigned char*)s2;
}
6.2 数学计算函数
实现一些常用数学计算:
c复制// 计算平方根(牛顿迭代法)
double sqrt_newton(double x) {
if(x < 0) return -1; // 错误值
if(x == 0) return 0;
double guess = x;
double epsilon = 1e-10; // 精度
while(fabs(guess * guess - x) > epsilon) {
guess = (guess + x / guess) / 2.0;
}
return guess;
}
// 判断素数
int is_prime(int n) {
if(n <= 1) return 0;
if(n == 2) return 1;
if(n % 2 == 0) return 0;
for(int i=3; i*i<=n; i+=2) {
if(n % i == 0) return 0;
}
return 1;
}
6.3 文件操作函数
封装常用文件操作:
c复制// 复制文件
int copy_file(const char *src, const char *dest) {
FILE *fsrc = fopen(src, "rb");
if(!fsrc) return -1;
FILE *fdest = fopen(dest, "wb");
if(!fdest) {
fclose(fsrc);
return -1;
}
char buffer[4096];
size_t bytes;
while((bytes = fread(buffer, 1, sizeof(buffer), fsrc)) > 0) {
fwrite(buffer, 1, bytes, fdest);
}
fclose(fsrc);
fclose(fdest);
return 0;
}
// 统计文件行数
int count_lines(const char *filename) {
FILE *fp = fopen(filename, "r");
if(!fp) return -1;
int count = 0;
char ch;
while((ch = fgetc(fp)) != EOF) {
if(ch == '\n') count++;
}
// 最后一行可能没有换行符
if(ftell(fp) > 0) {
fseek(fp, -1, SEEK_END);
if(fgetc(fp) != '\n') count++;
}
fclose(fp);
return count;
}
7. 函数库与模块化开发
7.1 创建自己的函数库
- 头文件(.h):包含函数声明和宏定义
- 源文件(.c):包含函数实现
示例mathlib.h:
c复制#ifndef MATHLIB_H
#define MATHLIB_H
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
double divide(int a, int b);
#endif
mathlib.c:
c复制#include "mathlib.h"
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
double divide(int a, int b) {
if(b == 0) return 0; // 简单处理除零错误
return (double)a / b;
}
7.2 静态库与动态库
-
静态库(.a/.lib):
- 编译时链接到可执行文件
- 命令:
gcc -c mathlib.c && ar rcs libmath.a mathlib.o - 使用:
gcc main.c -L. -lmath -o main
-
动态库(.so/.dll):
- 运行时加载,节省内存
- 命令:
gcc -shared -fPIC -o libmath.so mathlib.c - 使用:
gcc main.c -L. -lmath -o main
7.3 多文件项目管理
合理的项目结构示例:
code复制project/
├── include/
│ └── mathlib.h
├── src/
│ ├── mathlib.c
│ └── main.c
├── lib/
└── Makefile
简单Makefile示例:
makefile复制CC = gcc
CFLAGS = -Iinclude -Wall
LDFLAGS = -Llib -lmath
SRC = src/main.c src/mathlib.c
OBJ = $(SRC:.c=.o)
EXEC = main
all: $(EXEC)
$(EXEC): $(OBJ)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJ) $(EXEC)
8. 现代C语言函数特性
8.1 C99/C11新增特性
- 内联函数:
c复制inline int max(int a, int b) {
return a > b ? a : b;
}
- 变长数组参数:
c复制void process_array(int rows, int cols, int arr[rows][cols]) {
// 可以处理任意大小的二维数组
}
- 复合字面量:
c复制// 无需先定义变量
print_point((struct Point){.x=10, .y=20});
8.2 安全函数版本
C11增加了许多安全函数版本,如:
c复制// 传统不安全的字符串复制
strcpy(dest, src);
// 安全版本
strcpy_s(dest, dest_size, src);
8.3 泛型选择(C11)
c复制#define print_type(x) _Generic((x), \
int: printf("%d\n", x), \
float: printf("%f\n", x), \
char*: printf("%s\n", x), \
default: printf("Unknown type\n"))
int main() {
print_type(10); // 输出: 10
print_type(3.14f); // 输出: 3.140000
print_type("Hello"); // 输出: Hello
return 0;
}
9. 函数调试与测试
9.1 单元测试框架
简单测试框架示例:
c复制#include <stdio.h>
#define TEST(expr) \
do { \
if(!(expr)) { \
printf("[FAIL] %s:%d: %s\n", __FILE__, __LINE__, #expr); \
return 1; \
} else { \
printf("[PASS] %s\n", #expr); \
} \
} while(0)
int add(int a, int b) { return a + b; }
int test_add() {
TEST(add(1, 1) == 2);
TEST(add(-1, 1) == 0);
TEST(add(100, 200) == 300);
return 0;
}
int main() {
if(test_add()) {
printf("Some tests failed!\n");
return 1;
}
printf("All tests passed!\n");
return 0;
}
9.2 调试技巧
- 使用assert进行断言检查:
c复制#include <assert.h>
double divide(int a, int b) {
assert(b != 0); // 如果b为0,程序会终止并输出错误信息
return (double)a / b;
}
- 打印调试信息:
c复制#ifdef DEBUG
#define DBG_PRINT(fmt, ...) fprintf(stderr, "DEBUG: " fmt, ##__VA_ARGS__)
#else
#define DBG_PRINT(fmt, ...)
#endif
void complex_function(int x) {
DBG_PRINT("Entering function with x=%d\n", x);
// 函数逻辑
DBG_PRINT("Leaving function\n");
}
- 使用gdb调试:
code复制$ gcc -g program.c -o program
$ gdb ./program
(gdb) break main
(gdb) run
(gdb) next
(gdb) print variable
(gdb) backtrace
10. 函数优化与性能考量
10.1 编译器优化选项
常用GCC优化选项:
- -O1:基本优化
- -O2:推荐优化级别
- -O3:激进优化
- -Os:优化代码大小
- -Ofast:不考虑标准合规性的激进优化
bash复制gcc -O2 -march=native -pipe program.c -o program
10.2 内联函数优化
小函数使用inline可以避免函数调用开销:
c复制static inline int min(int a, int b) {
return a < b ? a : b;
}
注意事项:
- 函数体必须简单
- 通常在头文件中定义
- static避免多重定义
- 编译器可能忽略inline建议
10.3 循环优化
将函数调用移出循环:
c复制// 不佳的实现
for(int i=0; i<n; i++) {
result += expensive_function(x);
}
// 优化后的实现
int temp = expensive_function(x);
for(int i=0; i<n; i++) {
result += temp;
}
10.4 内存访问优化
减少函数内的内存访问:
c复制// 不佳的实现
void sum_array(int *arr, int size, int *result) {
for(int i=0; i<size; i++) {
*result += arr[i]; // 每次迭代都访问内存
}
}
// 优化后的实现
void sum_array(int *arr, int size, int *result) {
int sum = 0; // 使用寄存器变量
for(int i=0; i<size; i++) {
sum += arr[i];
}
*result = sum; // 只访问一次内存
}
11. 函数指针高级应用
11.1 回调函数
函数指针常用于实现回调机制:
c复制typedef void (*Callback)(int status, const char *message);
void long_operation(Callback cb) {
// 模拟耗时操作
for(int i=0; i<3; i++) {
sleep(1);
cb(i, "Processing...");
}
cb(100, "Complete");
}
void my_callback(int status, const char *msg) {
printf("[%d%%] %s\n", status, msg);
}
int main() {
long_operation(my_callback);
return 0;
}
11.2 函数指针数组
实现类似"跳转表"的功能:
c复制#include <stdio.h>
void add(int a, int b) { printf("%d + %d = %d\n", a, b, a+b); }
void sub(int a, int b) { printf("%d - %d = %d\n", a, b, a-b); }
void mul(int a, int b) { printf("%d * %d = %d\n", a, b, a*b); }
typedef void (*Operation)(int, int);
int main() {
Operation ops[] = {add, sub, mul};
int choice;
printf("1. Add\n2. Subtract\n3. Multiply\nChoice: ");
scanf("%d", &choice);
if(choice >=1 && choice <=3) {
ops[choice-1](10, 5); // 调用相应函数
}
return 0;
}
11.3 面向对象风格编程
用函数指针模拟类方法:
c复制typedef struct {
int x, y;
void (*print)(const struct Point*);
void (*move)(struct Point*, int, int);
} Point;
void point_print(const Point *p) {
printf("Point(%d, %d)\n", p->x, p->y);
}
void point_move(Point *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
Point create_point(int x, int y) {
Point p = {x, y, point_print, point_move};
return p;
}
int main() {
Point p = create_point(10, 20);
p.print(&p);
p.move(&p, 5, -5);
p.print(&p);
return 0;
}
12. 多线程与函数
12.1 线程安全函数
确保函数在多线程环境下安全运行:
c复制#include <pthread.h>
// 线程不安全版本
int counter = 0;
void unsafe_increment() {
counter++;
}
// 线程安全版本
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void safe_increment() {
pthread_mutex_lock(&lock);
counter++;
pthread_mutex_unlock(&lock);
}
12.2 线程局部存储
使用__thread关键字(GCC扩展):
c复制#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
__thread int tls_var = 0;
void* thread_func(void *arg) {
tls_var = (int)(long)arg;
printf("Thread %ld: tls_var = %d\n", (long)pthread_self(), tls_var);
sleep(1);
printf("Thread %ld: tls_var = %d\n", (long)pthread_self(), tls_var);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, (void*)1);
pthread_create(&t2, NULL, thread_func, (void*)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
12.3 原子操作
C11引入了原子类型和操作:
c复制#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>
atomic_int counter = ATOMIC_VAR_INIT(0);
int thread_func(void *arg) {
for(int i=0; i<100000; i++) {
atomic_fetch_add(&counter, 1);
}
return 0;
}
int main() {
thrd_t t1, t2;
thrd_create(&t1, thread_func, NULL);
thrd_create(&t2, thread_func, NULL);
thrd_join(t1, NULL);
thrd_join(t2, NULL);
printf("Counter: %d\n", counter); // 正确输出200000
return 0;
}
