一. 函数
1. 函数的概述
函数是一种可复用的代码块, 用于执行特定的任务,来完成特定的功能
2. 函数的作用
函数对代码进行封装, 提高代码的编写效率, 提高代码的复用率
3. 函数参数的作用
增加函数的灵活性,可以根据需求在调用函数时, 通过参数传入不同的数据
4. 函数返回值的作用
函数外部想使用函数内部的数据, 在使用函数返回值时,需要注意,return是结束函数执行, return后面的代码将不再执行
5. 函数的形式
5.1 无参无返回值
其中void作为返回值类型,表示无返回值
5.2 有参无返回值
还是使用void
5.3 有参有返回值
返回值的类型就是函数的类型
代码示例
#include <stdio.h>
// 1. 无参,无返回值
// 定义函数
void a()
{
printf("hello world\n");
}
// void 代表无返回值
// 2.有参,无返回值
void add(int a, int b)
{
printf("%d\n", a + b);
}
// 3. 有参,有返回值
int add2(int a, int b)
{
printf("%d\n", a + b);
return a + b;
printf("不执行\n");
}
int main()
{
// 使用函数
a();
add(1, 2);
int result = add2(4, 3);
printf("result = %d\n", result);
return 0;
}
6. 函数的声明
如果函数定义代码没有放在函数调用的前面, 这个时候需要先做函数的声明
所谓函数声明, 相当于告诉编译器, 函数是有定义的, 在别的地方定义,以便使编译能够正常进行
注意: 一个函数只能被定义一次, 但是可以声明多次
#include <stdio.h>
// 先声明 有sum 这个函数
int sum(int a, int b);
sum(int a, int b);
int main() {
sum(10, 20);
return 0;
}
// 再去(实现)定义
int sum(int a, int b) {
return a + b;
}
// int sum(int a, int b) {
// return a + b;
// }
7. 函数案例
需求:自定义一个函数,返回2个整数的最大值
#include <stdio.h>
// 求两个数最大值
int maxNum(int a, int b)
{
return a > b ? a : b;
}
int main()
{
int max = maxNum(10, 20);
printf("%d\n", max);
// 求三个数最大值
int maxManyNum = maxNum(max, 99);
printf("%d\n", maxManyNum);
return 0;
}
8. 局部全局变量
8.1 局部变量
局部变量一般在函数中定义,也就是花括号中{ }
生命周期(生效的范围) : 函数中 ({ }内)
失效时间: 函数执行完成后失效(释放)
#include <stdio.h>
int add(int a, int b)
{
// sum只在函数内生效
int sum = a + b;
}
int main()
{
printf("%d\n", add(10, 20));
return 0;
}
8.2 全局变量
全局变量一般定义在函数外
声明周期(生效的范围) : 整个文件
失效时间: 整个文件代码运行完成才释放
#include <stdio.h>
int a = 10;
void prt()
{
printf("%d\n", a);
}
int main()
{
prt();
printf("%d\n", a);
return 0;
}
9. 多文件编程(重点)
1. 首先在add.h文件中声明方法add,
为了防止文件重复包含需要添加 #ifndef __文件名_H__和#define __文件名_H__和#endif
2. 在同名的add.c文件中实现add.h的方法
3. 在main.c主文件中使用add.h中的方法,这里需要引入#include "./add.h"
如果想要直接使用b.c文件中的变量和函数时
在main.c主文件中,先引入b.c文件(#include "./b.c")
使用extern + b.c中的变量和方法 (extern int d; extern void print_d();)
防止头文件重复包含
当一个项目比较大时, 往往都是分文件,这个时候有可能不小心把同一个头文件include多次, 或者头文件嵌套包含
为了避免同一个文件被include多次, C/C++中有两种方式
方法一:
#ifndef __文件名_H__
#define __文件名_H__
// 声明语句
#endif
方法二: (这种方式在嵌入式中不可用哦)
#pragma once // 声明语句
二. 指针(重点)
1. 指针的定义
指针就是一种数据类型,用来存储地址,操作地址,指针的实质就是内存地址
指针变量指向谁, 就把谁的地址赋值给它
#include <stdio.h>
int main()
{
// 定义一个变量
int a = 20;
// 打印a变量的值
printf("%d\n", a);
// 打印a变量的地址(%p)
// &a中的&这里表示取地址符
printf("%p\n", &a);
// 将a的地址赋值给指针p
int *p = &a;
// 打印p变量的地址(%p)
printf("%p\n", p);
// 打印指针p指向的值(*指针变量)
printf("%d\n", *p);
return 0;
}
2. 通过指针间接修改变量的值
指针变量指向谁, 就把谁的地址赋值给指针变量
通过 * 指针变量,间接修改指针变量的值
#include <stdio.h>
int main()
{
// 定义一个变量
int a = 20;
// 打印a变量的值
printf("%d\n", a); // 20
int *p = &a;
// 修改指针变量p对应的值
*p = 80;
printf("%d\n", a); // 80
return 0;
}
3. const修饰的指针变量
一句话总结: 自动忽略变量类型的前提下, const修饰谁,谁就不可以改变(const是常量,不可修改哦)
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
// 1.此时const修饰的是*p1,所以p1可以修改,*p1不可以
const int *p1 = &a;
p1 = &b;
//*p1 = 5;
// 2.此时const修饰的是p2,所以p2不可以修改,*p2可以
int *const p2 = &a;
// p2 = &b;
*p2 = 5;
printf("%d\n", *p1);
printf("%d\n", *p2);
return 0;
}
4. 指针的大小(sizeof)
使用sizeof( )测量指针的大小, 得到的总是 : 4 或者8
sizeof( ) 测的是指针变量指向存储地址的大小
- 在32位平台中, 所有的指针(地址) 都是32位(4字节)
- 在64位平台中, 所有的指针(地址) 都是64位(8字节)
但是这里需要注意:
无论是定义几级的指针,对指针大小都没有影响(也就是***多少都关系)
并且,指针的大小和指针的类型也没有关系(也就是int, char都没关系)
#include <stdio.h>
int main() {
char* p;
int* p1;
double* p2;
int** p3;
double**** p4;
printf("sizeof p = %d\n", sizeof(p));
printf("sizeof p1 = %d\n", sizeof(p1));
printf("sizeof p2 = %d\n", sizeof(p2));
printf("sizeof p3 = %d\n", sizeof(p3));
printf("sizeof p4 = %d\n", sizeof(p4));
return 0;
}
5. 指针的步长
指针步长指的是通过指针进行递增或者递减操作时, 指针所指向的内存地址相对于当前地址的偏移量
指针的步长取决于所指向的数据类型
- 指针加n等于指针地址加上n个sizeof(type)的长度
- 指针减n等于指针地址减去n个sizeof(type)的长度
#include <stdio.h>
int main() {
char a = 10;
char* p = &a;
// 0x7ff7bbac63bb p
// 0x7ff7bbac63bc p+1
// char 类型 偏移 1字节
printf("p = %p\n p+1 = %p\n", p, p + 1);
printf("-------------\n");
// pp = 000000000061FE00
// pp+1 = 000000000061FE04 // 4个字节
// int 类型的指针偏移量是4个字节
// 指针的步长取决于所指向的数据类型。
int b = 10;
int* pp = &b;
printf("pp = %p\n pp+1 = %p\n", pp, pp + 1);
// ppp = 0x7ff7bfcff398
// ppp+1 = 0x7ff7bfcff3a0 // 8个字节
double c = 20;
double* ppp = &c;
printf("ppp = %p\n ppp+1 = %p\n", ppp, ppp + 1);
return 0;
}
6. 野指针和空指针
-
指针变量也是变量,是变量就可以任意赋值
-
任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针
-
-
此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)
-
-
野指针不会直接引发错误,操作野指针指向的内存区域才会出问题
-
为了标志某个指针变量没有任何指向,可赋值为NULL
-
-
NULL是一个值为0的宏常量
-
下面的代码中,出现了野指针的情况,请指出并修复错误
#include <stdio.h>
int main() {
int *ptr;
int num = 10;
*ptr = num;
printf("Value: %d\n", *ptr);
return 0;
}
修改后
#include <stdio.h>
int main()
{
// 设置为NULL,NULL是一个值为0的宏常量
int *ptr = NULL;
int num = 10;
// 输出地址
printf("%p\n", &num); // 000000000061FE14
printf("%p\n",ptr); // 0000000000000000
//*ptr = num;
ptr = #
printf("Value: %d\n", *ptr);
return 0;
}
7. 多级指针
-
简单来说, 一级就是 * ,二级就是 ** ,以此类推
-
C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针
-
二级指针可以存储一级指针的地址,三级指针可以存储二级指针的地址,以此类推...
三. 指针和函数
1. 函数参数传值
-
传值是指将参数的值拷贝一份传递给函数,函数内部对该参数的修改不会影响到原来的变量
2. 函数参数传址(地址) (重点)
-
传址是指将参数的地址传递给函数,函数内部可以通过该地址来访问原变量,并对其进行修改
实例
编写一个程序,定义一个整型变量,初始值为100,通过某个函数修改改变量的内容为123
#include <stdio.h>
// 通过地址修改
int change2(int *p)
{
printf("%d\n", *p); // 100
*p = 123;
printf("%d\n", *p); // 123
return *p;
}
int main()
{
int a = 100;
printf("%p\n", &a); // 地址
// 通过地址修改
printf("修改为: %d\n", change2(&a));
return 0;
}