函数基本概念
Linux 中,函数在内存的代码段(code 区),地址比较靠前。
函数定义
C 语言中,函数有三个要素:入参、返回值、函数名,缺一不可。函数使用前必须先声明,或者在使用之前定义。
函数声明格式如下:
int test(int a, char *p);
函数定义格式如下:
int test(int a, char *p)
{
// 干点啥
return 666;
}
函数调用
char c = 'a';
int result;
result = fun(666, &c);
函数的形参和实参,值传递和引用传递
函数定义时,为了用参数进行操作,为参数预留的占位符就是形参。
函数调用时,调用方传到函数中的真实参数就是实参。
函数调用时,传递的是参数的值(实际上就是复制一份内存),而非参数的地址。值传递时,形参的所有改动,都不会影响实参。值传递和引用传递的区别:
- 值传递会在内存中开辟新空间,复制实参的数据,作为函数的形参。而引用传递则直接把实参的地址传到函数中
- 值传递时,形参的修改不影响实参。引用传递因为实参和形参都是指针,且指向同一块内存空间,任何改动都会相互影响。
值传递示例:
#include <stdio.h>
int swap(int a, int b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 1, b = 666;
printf("before swap, a is: %d, b is: %d
", a, b);
swap(a, b);
printf("after swap, a is: %d, b is: %d
", a, b);
return 0;
}
输出:
before swap, a is: 1, b is: 666
after swap, a is: 1, b is: 666
如果要想在调用的函数中修改参数,就必须传参数的地址过去,类似上面的函数可以改为引用传递:
#include <stdio.h>
int swap(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int a = 1, b = 666;
printf("before swap, a is: %d, b is: %d
", a, b);
swap(&a, &b); // 这里需要传地址
printf("after swap, a is: %d, b is: %d
", a, b);
return 0;
}
引用传递可以改变原参数,输出:
before swap, a is: 1, b is: 666
after swap, a is: 666, b is: 1
函数的入参
因为值传递时,需要为实参多开辟一份内存,所以在函数参数占用空间较大时(例如数组、结构体),通常使用引用传递。
连续空间
结构体
对于下面的结构体,通常用引用传递,而不是值传递:
#include <stdio.h>
struct People {
int age;
char * name;
};
void fun2(struct People p) {
printf("people's name is:%s, age is: %d
", p.name, p.age);
}
void fun(struct People *p) {
printf("people's name is:%s, age is: %d
", p->name, p->age);
}
int main()
{
struct People p1 = {22, "jack"};
fun(&p1); // 推荐
fun2(p1);
}
数组
C 语言中,用数组做函数的参数时要注意,因为数组名本身就是个表示地址的标签,所以实参是数组时,实际上就是引用传递:
int arr[10];
int fun(int *p) {}
连续空间只读性
引用传递时,如果只是想节省内存空间,而不想让调用的函数修改该空间;或者会传递常量指针给函数。这两种情况下,都需要明确把函数声明中的指针用 const 描述。
编译通过,运行时段错误示例:
#include <stdio.h>
void fun(char * p)
{
p[0] = 'x'; // 因为传过来的是字符串常量,这里的修改会报 段错误 segmentation fault
}
int main()
{
fun("hello");
return 0;
}
只读参数限定示例:
#include <stdio.h>
void fun(const char * p)
{
p[0] = 'x'; // 因为参数限定为 const,函数内不可修改,否则编译会报错
}
int main()
{
fun("hello");
return 0;
}
sprintf 示例
printf 将格式化字符串打印到标准输出流,而 sprintf 则将格式化字符串输出到变量中,这几个函数及定义可以通过 man 3 sprintf
查看:
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
#include <stdio.h>
int main(void) {
int a = 666;
char * str;
printf("a is: %d
", a);
sprintf(str, "a is: %d
", a);
printf("str is: %s", str);
}
输出:
a is: 666
str is: a is: 666
字符空间
任何内存空间,在操作之前都需要知道两个要素:首地址、结束标志(或字节个数)。
字符空间是以 (0x0000 0000)结束的连续内存空间。 这个字符不会出现在字符空间,但是可能出现在非字符空间。字符空间有两种限定方式:
const char *p
:常量,不可修改,例如字符串常量。通常用双引号初始化"..."
。char *p
:变量,允许修改,例如字符数组。通常用字符数组初始化char buf[5]
。
void fun(char *p)
{
int i = 0;
while(p[i] != ' ') // 这里也可以直接用 while(p[i])
{
//干点啥
i++;
}
}
strlen 示例
strlen 函数用于统计字符空间中字符的个数,函数语义如下:
int strlen(const char * str);
可以自己实现一个 strlen:
int mystrlen (const char *p) {
// 错误处理
if (p == NULL) return 0;
// 内存处理
int i = 0;
while(p[i])
{
i++;
}
return i;
}
strcpy 示例
strcpy 用于拷贝字符,函数语义如下:
void strcpy(char * dest, const char *src);
可见 strcpy 函数的源字符串限定为 const char *
类型,不可修改。
非字符空间
字符空间固定以