函数
概述
- 函数是一种可重用的代码块,用于执行特定任务或完成特定功能
- 函数作用:对具备相同逻辑的代码进行封装,提高代码的编写效率,实现对代码的重用
- 函数使用步骤
-
- 定义函数
-
-
- 理解为制作工具,工具只需要制作1次即可
-
-
- 调用函数
-
-
- 理解为使用工具
-
- 函数分类
-
- 系统函数,即库函数:这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们,如我们常用的打印函数printf()。
- 自定义函数:用以解决用户的专门需要。
函数的使用
无参无返值
// 函数定义
void 函数名() {
函数体
}
// 函数调用
函数名();
- 函数名是标识符的一种,需要遵循规则
- 函数只需要定义一次,反复调用
- 只定义函数, 不调用函数, 函数永远不会被执行
案例
//无参无返回值
void hello(){
printf("hello....\n");
}
//1. 定义函数
void sayHi(){
printf("你好...\n");
}
int main(){
//2. 调用函数
sayHi();
hello();
return 0;
}
有参无返值
- 函数参数的作用:增加函数的灵活性
- 可以根据需求在调用函数时, 通过参数传入不同的数据
// 函数定义
void 函数名(形参列表) {
函数体
}
// 函数调用
函数名(实参列表);
案例
#include <stdio.h>
// 函数定义
void my_add(int a, int b) {
// 实现2个形参相加,并打印相加后的结果
int res = a + b;
printf("%d + %d = %d\n", a, b, res);
}
int main() {
// 函数调用
my_add(10, 20);
return 0;
}
无参有返值
//无参有返回值:: 如果一个函数有返回值,那么在函数内部必须有一个return 关键字存在
int hello2(){
return 3;
}
有参有返值
//4. 有参有返回值: 定义一个函数,接收两个整数、返回两数之和。
int add(float a , int b){
printf("aaaaaaaaaaaaaaa\n");
return a + b;
printf("bbbbbbbbbbbbbbb\n"); // 这句话不会执行了,因为return了。
}
函数的声明
- 如果函数定义代码没有放在函数调用的前面,这时候需要先做函数的声明
- 所谓函数声明,相当于告诉编译器,函数是有定义的,再别的地方定义,以便使编译能正常进行
- 注意:一个函数只能被定义一次,但可以声明多次
#include <stdio.h>
// 函数的声明,分号不能省略
// 函数声明的前面可以加extern关键字,也可以不加
// extern int my_add(int a, int b);
int my_add(int a, int b);
// 另一种方式,形参名可以不写
// int my_add(int, int );
int main() {
// 函数调用
int temp = my_add(10, 20);
printf("temp = %d\n", temp);
return 0;
}
// 函数定义
int my_add(int a, int b) {
// 实现2个形参相加,并返回累加的结果
int res = a + b;
return res;
}
局部和全局变量
局部变量
- 定义在代码块{}里面的变量称为局部变量(Local Variable)
- 局部变量的作用域(作用范围)仅限于代码块{}, 离开该代码块{}是无效的
-
- 离开代码块{}后,局部变量自动释放
全局变量
- 在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件
代码示例
#include <stdio.h>
/*
局部变量:
1. 定义在 {} 里面,从它定义的位置开始直到遇见 } 结束,这个区域都是它的使用范围
全局变量:
1. 定义在函数之外,对全局有影响,可以为所有的函数使用
1.不管是全局变量还是局部变量,使用范围的起始位置都是从定义的位置开始往后。仅仅是在范围大小上有所区分而已。
2. 函数的参数也是局部变量,只能允许在函数内部使用。
3. 局部变量所占用的内存空间,在函数执行结束之后就释放掉了。但是全局变量是等程序执行结束之后才会释放。
*/
// 全局变量
int b = 30;
// 函数的参数也是局部变量
void hello(int aaa , int bbb){
//局部变量
int aa = 20;
printf("bbbb===%d" , b);
}
int main(){
// 全局变量基础上 + 10
b = b + 10;
printf("b=%d\n" , b);
//局部变量
int a = 10;
printf("a=%d\n",a);
return 0 ;
}
多文件编程
多文件编程
- 把函数声明放在头文件xxx.h中,在主函数中包含相应头文件
- 在头文件对应的xxx.c中实现xxx.h声明的函数
防止文件重复包含
以下系统能够自动生成的三句话就保证了文件重复包含
extern关键字
extern主要用于声明外部变量或函数,当我们将一个变量或函数声明为extern时,那么就表示该变量或函数是在其他地方定义的,我们只是在当前文件中引用它。
在main.c中:
#include <stdio.h>
extern int global_val;
extern void printf_val();
int main() {
global_val = 100;
printf_val();
return 0;
}
在other.c中:
#include <stdio.h>
int global_val;
void printf_val() {
printf("other->global_val: %d\n", global_val);
}
指针
指针基本语法
指针变量的定义和使用
- 指针也是一种数据类型,指针变量也是一种变量
- 指针变量指向谁,就把谁的地址赋值给指针变量
语法格式:
1.类型 变量;
2.类型 * 指针变量 = & 变量;
案例
#include <stdio.h>
int main(){
/*
指针的定义
1. 指针也是一种特殊的变量,它接收的值是地址
2. 使用取地址符 对某个变量取地址
3. 指针本身也是有地址的,这需要和它的值区分开来。
4. 指针的类型不能乱写!
4.1 这个指针指向什么位置,那个位置存放的数据类型是什么,指针就得是什么类型
*/
//定义变量
int a = 1;
printf("a的值: %d \n" , a);
printf("a的地址: %#x \n" , &a);
printf("\n\n");
//定义指针
int * pa = &a;
printf("pa的值:%#x \n" , pa);
printf("pa的地址:%#x \n" , &pa);
return 0 ;
}
-
- & 叫取地址,返回操作数的内存地址
- * 叫解引用,指操作指针所指向的变量的值
- 在定义变量时,* 号表示所声明的变量为指针类型
-
- 指针变量要保存某个变量的地址,指针变量的类型比这个变量的类型多一个*
- 在指针使用时,* 号表示操作指针所指向的内存空间
解引用
解引用即通过指针间接修改变量的值
- 指针变量指向谁,就把谁的地址赋值给指针变量
- 通过 *指针变量 间接修改变量的值
案例
#include <stdio.h>
int main() {
// 定义一个int类型变量a,同时赋值为0
int a = 0;
// 定义int *指针变量,同时赋值a的地址
int *p = &a;
// 通过指针间接修改a的值
*p = 123;
printf("a = %d\n", a);
// 定义一个int类型变量b,同时赋值为5
int b = 5;
// p 保存 b的地址
p = &b;
// 通过指针间接修改b的值
*p = 250;
printf("b = %d\n", b);
return 0;
}
指针大小
使用sizerof()测试指针的大小,得到的总是4或8
在32位平台,所有的指针(地址)都是32位(4字节)
在64位平台,所有的指针(地址)都是64位(8字节)
指针步长(指针运算)
- 指针步长指的是通过指针进行递增或递减操作时,指针所指向的内存地址相对于当前地址的偏移量。
- 指针的步长取决于所指向的数据类型。
-
- 指针加n等于指针地址加上 n 个 sizeof(type) 的长度
- 指针减n等于指针地址减去 n 个 sizeof(type) 的长度
案例
#include <stdio.h>
int main(){
/*
指针的运算:
1. 指针是可以进行加法和减法运算的。
2. 一旦进行运算,实际上就是让指针指向的位置发生偏移。
3. 具体偏移多少取决于指针的类型【指针指向的那个位置的数据的类型长度】
*/
/* char c = 'a';
char * pc = &c;
printf("pc的值【c的地址】 : %#x \n" , pc); //0x62fff827
pc = pc - 1;
printf("pc - 1 之后的值 : %#x \n" , pc); //0x62fff826
pc = pc - 1;
printf("pc - 1 之后的值 : %#x \n" , pc); //0x62fff825*/
int age = 10;
int * page = &age;
printf("page指向的位置是:%x \n" , &age); //b33ffa84
printf("page指向的位置是:%x \n" , page); //b33ffa84
page = page - 1;
printf("page - 1 之后, page是:%x \n" , page); //b33ffa80
page = page - 1;
printf("page - 1 之后, page是:%x \n" , page); //b33ffa7c
page = page - 1;
printf("page - 1 之后, page是:%x \n" , page); //b33ffa78
page = page + 1;
printf("page + 1 之后, page是:%x \n" , page); //b33ffa7c
page = page + 1;
printf("page + 1 之后, page是:%x \n" , page); //b33ffa80
page = page + 1;
printf("page + 1 之后, page是:%x \n" , page); //b33ffa84
return 0 ;
}
野指针和空指针
- 指针变量也是变量,是变量就可以任意赋值
- 任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针
-
- 此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)
- 野指针不会直接引发错误,操作野指针指向的内存区域才会出问题
- 为了标志某个指针变量没有任何指向,可赋值为NULL
-
- NULL是一个值为0的宏常量
#include <stdio.h>
int main() {
int *p;
p = 0x12345678; // 给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义
// *p = 1000; // 操作野指针指向未知区域,内存出问题,err
printf("111111111111111111\n");
int *q = NULL; // 空指针
return 0;
}
多级指针
- C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
- 二级指针就是指向一个一级指针变量地址的指针。
#include <stdio.h>
int main(){
//多级指针: 一级指针、二级指针、....
int age = 10;
printf("age的值是: %d \n" , age); //10
printf("age的地址是: %#x \n" , &age); //0x147ffb5c
printf("\n");
//一级指针:: 指向目标值
int * page = &age;
printf("page的值是: %#x \n" , page); //0x147ffb5c
printf("page的地址是: %#x \n" , &page); //0x147ffb50
printf("page指向的位置的值是【解引用】: %d \n" , *page);//10
printf("\n");
//二级指针:: 指向一级指针
int ** ppage = &page;
printf("ppage的值是: %#x \n" , ppage);//0x147ffb50
printf("ppage的地址是: %#x \n" , &ppage);//0x147ffb48
printf("ppage指向一级指针,一级指针指向的位置的值: %d \n" , **ppage);//10
return 0 ;
}
const修饰的指针变量
这里提到的const无非就是两种情况,第一种const放在*前,第二种const放在*后.
#include <stdio.h>
int main(){
int age = 10;
int a = 20;
// 指向常量的指针: 以 * 为分界点,表示这是一个指针page 指向一个int类型的常量
const int * page = &age;
//是否可以使用指针来修改指向位置的值 【解引用】 :: 不允许修改值
//*page = 20;
//是否可以修改指向? 可以修改指向!
page = &a;
//-------------------------------------------
int number = 30;
int b = 50;
int * const pnumber = &number;
//是否可以使用指针来修改指向位置的值 【解引用】
*pnumber = 40;
//是否可以修改指向? 不允许修改指向!
// pnumber = &b;
//----------------------------
int c = 8;
// 这是一个常量指针,指向一个int类型的常量
const int * const pc = &c;
//能解引用修改值吗? 不能!
//能否修改指向? 不能!
return 0 ;
}
指针和函数
函数参数传值
- 传值是指将参数的值拷贝一份传递给函数,函数内部对该参数的修改不会影响到原来的变量
#include <stdio.h>
void exchange(int a , int b){ // int a = 3 , int b = 4;
int temp = a;
a = b;
b = temp;
printf("函数内部:a=%d ,b=%d \n" , a , b );//函数内部:a = 4, b = 3
}
int main(){
// 给定两个数,定义一个函数,在函数内部完成这两个数的交换。‘
int a = 3;
int b = 4;
exchange(a , b);
printf("a=%d \n" ,a );//a = 3
printf("b=%d \n" ,b );//b = 4
return 0 ;
}
此处可以看见在main函数下定义两个整数ab,调用函数后,在上面函数中已经确定了输入的a=3,b=4.在函里面执行完输出的是两个变量交换的值.但是在下面调用函数输出的值交换后的值并没有输出.所以接下来我们要实现这个功能就要引用指针.
函数参数传址
- 传址是指将参数的地址传递给函数,函数内部可以通过该地址来访问原变量,并对其进行修改。
#include <stdio.h>
// 如果要修改外部变量的值,参数就不能是普通的变量,就必须是地址!
void exchange(int * a , int * b){ // int * a = &a , int * b = &b;
//1. 先把a 的值拿出来赋给 temp
int temp = *a;
//2. 把b的值,赋给 a
*a = *b;
//3. 把temp的值,赋给b
*b = temp;
printf("函数内部:a=%d ,b=%d \n" , *a , *b );//函数内部:a=4,b=3
}
int main(){
// 给定两个数,定义一个函数,在函数内部完成这两个数的交换。‘
int a = 3;
int b = 4;
exchange(&a , &b);
printf("a=%d \n" ,a );//a=4
printf("b=%d \n" ,b );//b=3
return 0 ;
}
函数指针
函数名,函数指针
- 一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址
- 函数指针:它是指针,指向函数的指针
#include <stdio.h>
void sayHi(){
printf("你好~!\n");
}
int add(int a , int b){
return a + b;
}
int main(){
//调用函数
sayHi();
// 函数名称也是可以取地址,直接使用函数名,等价它的地址
printf("sayHi的地址:%#x \n" , &sayHi); // 0x190214a0
printf("sayHi的地址:%#x \n" , sayHi); // 0x190214a0
// 指针本身就是用来接收地址的。
//int * page = &age;
//把函数的声明拿过来,去掉函数名字,使用(*指针名字) 来替代它。
void (*pp)() = sayHi;
pp(); //用pp()调用
//函数指针来接收add
int (*padd)(int, int ) = add;
int result = padd(3,4);
printf("result=%d \n" , result);
return 0 ;
}
这里函数指针语法就是将上述声明的函数除了{}部分拿过来,然后将函数名换成(*指针名),后面在加有一个(),有形参将形参放入小括号中.等于函数名即可.这里在提供一个案例以便学习.
#include <stdio.h>
void hello(){
printf("hello...\n");
}
void hello2(int number){
printf("hello...%d\n" , number);
}
int main(){
//使用函数指针来接收这两个函数,然后调用这两个函数。
void (*phello)() = hello;
phello();
void (*phello2)(int number) = hello2;
phello2(6);
return 0 ;
}
回调函数
- 函数指针变量做函数参数,这个函数指针变量指向的函数就是回调函数
- 回调函数可以增加函数的通用性
-
- 在不改变原函数的前提下,增加新功能
为了大家更好的理解,请看下面的图片.
总结
函数和指针基本知识这里就介绍完了,小伙伴们要多加练习,下期我们学习的是数组,我们下期见!