前言
本章节主要介绍一下c中的函数以及存储布局的一些操作。本章节在c基础中属于比较重要的知识内容,无论后续我们后续使用c来干什么项目,这些知识毫无疑问都是不可或缺的。
1.函数
1.1 函数的定义
定义:返回值类型 函数名(形参列表){......}
注意:
如果你不写返回值类型,默认是int类型,在使用的时候,如果没有加函数的声明,那么我们必须将函数定义在main函数之前。如果定义的函数的返回值类型不为void,那么我们必须返回一个与返回值类型一致的值。
案例:
#include <stdio.h>
int add(int a, int b)
{
printf("--");
return a + b;
}
int main()
{
int a = 10;
int b = 20;
int c = add(a, b);
printf("c = %d\n", c);
return 0;
}
1.2 函数的声明
声明方式:返回值类型 函数名(形参列表);
声明位置:main函数之前
案例:
#include <stdio.h>
int add(int a, int b);
int main()
{
int a = 10;
int b = 20;
int c = add(a, b);
printf("c = %d\n", c);
return 0;
}
int add(int a, int b)
{
return a + b;
}
1.3 函数的调用
参考以上两个案例中关于add()函数的调用
1.4分程序编程
简单来说,就是将一个模块的所有函数都放在一个.c文件中,并且在.h文件中存放函数的声明和类型的定义。
案例:
创建三个文件math_utils.h,math_utils.c, main.c
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
// 宏定义
#define PI 3.1415926
// 函数声明
int add(int a, int b);
double circle_area(double radius);
// 类型定义
typedef struct {
double x;
double y;
} Vector;
#endif
// math_utils.c
#include "math_utils.h"
int add(int a, int b) {
return a + b;
}
double circle_area(double radius) {
return PI * radius * radius;
}
// main.c
#include <stdio.h>
#include "math_utils.h"
int main() {
int a = 10;
int b = 20;
int c = add(a, b);
printf("c = %d\n", c);
double radius = 5.0;
double area = circle_area(radius);
printf("area = %f\n", area);
return 0;
}
编译命令:
gcc -o main main.c math_utils.c
1.5 编译多文件
vim 文件1 文件2 ... -p
1.6 多文件程序编译
gcc -o 别名 所有要编译的.c文件
1.7 变参函数
return_value_type func(第一个参数不可变, ...);
例如:常用的scanf()和printf()函数
1.8 预处理指令(宏函数)
定义:
#define 宏定义 定义值
注意:
- 宏没有分号
- gcc预处理阶段做替换
- 宏语句需要在一行,如果过长可以使用续航符(\)进行换行
- 优势
- 函数的调用会降低程序的执行效率,函数调用期间会保存程序执行现场,切换函数的地址到调用函数,当函数调用结束后再恢复保护的现场。
- 宏函数既可以模块化,在执行的时候也不会降低效率,因为宏函数仅仅是纯替换,没有所有的调用过程。
- 宏函数不适合功能很复杂的定义
2. c的存储布局
2.1 物理地址和虚拟地址
2.1.1物理地址
存储在内存上的地址
2.1.2 虚拟地址
虚拟地址由程序生成,在进程中,内核会独立分配4G虚拟地址,虚拟地址并不是数据真正存储的地址,而是我们程序访问处理的地址。
虚拟地址空间布局
虚拟地址空间 | 作用 | 声明周期 |
堆区 | 存储局部变量 | 函数内 |
栈区 | 动态开辟 | 函数内 |
bss段 | 未初始化的静态变量以及全局变量(其中变量的初始值一定为0) | 整个程序运行期间 |
data数据段 | 已经初始化的全局变量以及静态变量 | 整个程序运行期间 |
text文本段 | 存储代码 | 整个程序运行期间 |
2.2 修饰变量的关键字
2.2.1 static
作用:修饰静态变量
语法:static 数据类型 数据名称
案例:
static int a; //未初始化的
static int a = 0; //初始化的
注意:
以上两个定义的结果虽然a的值相同,但是存储的位置并不相同
2.2.2 const
作用:只读变量,用于修饰指针。
语法:const 数据类型 数据名称(= 值)
案例:const int max_value = 100;
注意:const修饰的值不可改变,所以使用的时候我们会进行初始化,否则会得到一个不可改变的脏数据
2.2.3 auto
作用:自动变量,定义局部变量默认修饰关键字
语法:auto 变量名 = 值;
案例:
auto a = 10; // a 被推导为 int
auto b = 3.14; // b 被推导为
double auto c = 'a'; // c 被推导为
char auto d = true; // d 被推导为 bool
2.2.4 register
作用:寄存器变量,主要用于提示编译器这个变量可能会被频繁使用,尽可能将其存储在 CPU 寄存器中,以提高访问速度
语法:register 变量类型 变量名 = 值;
案例:
register int a = 1;
2.2.5 volatile
作用:易失变量,主要用于硬件控制开发,方便编译器对定义的变量优化
语法:volatile 变量类型 变量名 = 值;
案例:
volatile bool flag = false;
2.2.5 extern
作用:外部声明,用于实现跨文件的变量 / 函数共享。
语法:extern 变量类型 变量名 = 值;
案例:
fun.c文件中定义,,main.c中使用
// func.c int add(int a, int b) {return a + b; } // 定义函数 // main.c #include <stdio.h> extern int add(int a, int b); // 声明:add 函数在其他地方定义 int main() { printf("%d\n", add(2, 3)); // 调用外部函数,输出 5 return 0; }
2.3 变量类型
案例
#include <stdio.h>
static int c;
int b = 10;
void demo(int a) {
printf("a = %d, b = %d, c = %d\n", a, b, c);
}
int main() {
int a = 10;
demo(a);
return 0;
}
2.3.1 局部变量
定义在函数体内的变量,函数的形参也是。如案例中的变量a
2.3.2 全局变量
定义在函数体外边的变量,如b
2.3.3 静态变量
使用static修饰的变量,根据位置可以分为全局静态变量和局部静态变量,如果定义的是全局静态变量,则只能在本文件中使用。
3.结束语
本文到此结束,针对这些存储变量以及函数的声明定义,本文并没有详细展开来介绍,只把常用的场景进行了一下说明。