C语言学习(三)函数、指针

函数

概述

  • 函数是一种可重用的代码块,用于执行特定任务或完成特定功能
  • 函数作用:对具备相同逻辑的代码进行封装,提高代码的编写效率,实现对代码的重用
  • 函数使用步骤
    • 定义函数
      • 理解为制作工具,工具只需要制作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 ;
}

回调函数

  • 函数指针变量做函数参数,这个函数指针变量指向的函数就是回调函数
  • 回调函数可以增加函数的通用性
    • 在不改变原函数的前提下,增加新功能

为了大家更好的理解,请看下面的图片.

总结

函数和指针基本知识这里就介绍完了,小伙伴们要多加练习,下期我们学习的是数组,我们下期见!

  • 18
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

'Magic'

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值