目录
一、跳转关键字
break:
continue:
goto:
二、函数
概述:
函数的使用:
无参无返回值:
有参无返回值:
有参有返回值:
返回值注意点:
函数的声明:
函数的案例:
局部和全局变量:
局部变量:
全局变量:
总结:
多文件编程:
编辑 防止头文件重复包含:
命令行编译程序:
头文件包含的区别:
extern关键字:
三、指针
指针基本语法:
指针变量的定义和使用:
通过指针简介修改变量的值:
指针与常量:
指针大小:
指针步长:
野指针和空指针:
多级指针:
指针和函数:
函数参数传值:
函数参数传址:
函数指针:
函数名:
函数指针:
回调函数:
一、跳转关键字
- 循环和switch专属的跳转:break
- 循环专属的跳转:continue
- 无条件跳转:goto
break:
- 循环的break说明
-
- 某一条件满足时,不再执行循环体中后续重复的代码,并退出循环
- 需求:一共吃5碗饭, 吃到第3碗吃饱了, 结束吃饭动作
- 示例代码:
#include <stdio.h>
int main() {
// 需求: 一共吃5碗饭, 吃到第3碗吃饱了, 结束吃饭动作
// 1. 定义条件变量,为了查看方便, 计数器从 1 开始
int i = 1;
while (i <= 5) { // 2. 控制条件
if (i == 3) {
printf("吃饱了,不吃了\n");
break; // 结束循环,退出循环
}
printf("吃第 %d 碗饭\n", i);
// 3. 条件变量改变
i++;
}
return 0;
}
continue:
- 某一条件满足时,不再执行本次循环体中后续重复的代码,但进入下一次循环判断
-
- while循环,continue 之前一定要修改计数器(条件变量),否则,导致死循环
- 需求:一共吃5个苹果,吃到第3个遇到虫子,这个跳过不吃,下一个继续
- 示例代码:
#include <stdio.h>
int main() {
// 需求: 一共吃5个苹果,吃到第3个遇到虫子,这个跳过不吃,下一个继续
// 1. 定义条件变量,为了查看方便, 计数器从 1 开始
int i = 1;
while (i <= 5) { // 2. 控制条件
if (i == 3) {
printf("这个有虫子,不吃了\n");
i++; // continue 之前一定要修改计数器(条件变量),否则,导致死循环
continue;; // 跳过本次循环,下次继续
}
printf("吃第 %d 个苹果\n", i);
// 3. 条件变量改变
i++;
}
return 0;
}
goto:
- goto用于无条件跳转
-
- 在一种情况下可以使用goto语句:从一组嵌套的循环中跳出
- goto语句可以导致代码不易理解和维护,并且容易引入不必要的错误。因此,除非必要,最好不要使用goto语句
- 示例代码:
#include <stdio.h>
int main() {
int i = 0;
while (i < 3) {
if(i == 1) {
goto End; // 跳转到End标签
}
printf("i = %d\n", i);
i++;
}
End:
printf("this is ending!\n");
return 0;
}
二、函数
概述:
- 函数是一种可重用的代码块,用于执行特定任务或完成特定功能
- 函数作用:对具备相同逻辑的代码进行封装,提高代码的编写效率,实现对代码的重用
- 函数使用步骤
-
- 定义函数
-
-
- 理解为制作工具,工具只需要制作1次即可
-
-
- 调用函数
-
-
- 理解为使用工具
-
- 函数分类
-
- 系统函数,即库函数:这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们,如我们常用的打印函数printf()。
- 自定义函数:用以解决用户的专门需要。
- 【注意】: main主函数要放到要调用的方法之后。
函数的使用:
无参无返回值:
语法格式:
// 函数定义
void 函数名() {
函数体
}
// 函数调用
函数名();
-
- 函数名是标识符的一种,需要遵循规则
- 函数只需要定义一次,反复调用
- 只定义函数, 不调用函数, 函数永远不会被执行
- 案例需求:
// 函数定义 void 函数名(形参列表) { 函数体 } // 函数调用 函数名(实参列表);
-
- 编写一个函数,里面实现2个数字相加,并打印相加后的结果
- 示例代码:
#include <stdio.h>
// 函数定义
void my_add() {
// 实现2个数字相加,并打印相加后的结果
int res = 1 + 2;
printf("res = %d\n", res);
}
int main() {
// 函数调用
my_add();
return 0;
}
有参无返回值:
语法格式:
// 函数定义
void 函数名(形参列表) {
函数体
}
// 函数调用
函数名(实参列表);
- 函数参数的作用:增加函数的灵活性
-
- 可以根据需求在调用函数时, 通过参数传入不同的数据
-
- 实参和形参的关系:从左往右,一一对应
- 案例需求:
-
- 编写一个函数,实现2个数相加,2个数通过参数传递
- 示例代码:
#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 和返回类型一致的数据(或变量);
}
// 函数调用
和返回类型一致的变量 = 函数名(实参列表);
- 函数返回值的作用:函数外部想使用函数内部的数据
-
- eturn是函数的专属关键字,只能用在函数内容
- 案例需求:
-
- 编写一个函数,实现2个数相加,2个数通过参数传递,返回累加结果给外部使用
- 示例代码:
#include <stdio.h>
// 函数定义
int my_add(int a, int b) {
// 实现2个形参相加,并返回累加的结果
int res = a + b;
return res;
}
int main() {
// 函数调用
int temp = my_add(10, 20);
printf("temp = %d\n", temp);
return 0;
}
返回值注意点:
- return的作用是结束函数
-
- 函数内,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;
}
函数的案例:
- 需求:自定义一个函数,返回2个整数的最大值
- 示例代码:
#include <stdio.h>
// 函数定义
int my_max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
int main() {
// 函数调用
int res = my_max(100, 200);
printf("res = %d\n", res);
return 0;
}
局部和全局变量:
局部变量:
- 定义在代码块{}里面的变量称为局部变量(Local Variable)
- 局部变量的作用域(作用范围)仅限于代码块{}, 离开该代码块{}是无效的
-
- 离开代码块{}后,局部变量自动释放
- 示例代码:
#include <stdio.h>
// 函数定义
void my_add(int a, int b) {
// a, b, res是局部变量,只能在my_add内部使用
int res = a + b;
}
int main() {
// 函数调用
my_add(10, 20);
// 函数外部无法使用函数内的局部变量,下面代码是错误的
printf("%d, %d, %d\n", a, b, res);
return 0;
}
全局变量:
- 在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件
- 示例代码:
#include <stdio.h>
// 函数外定义的变量为全局变量,定义完,所有地方都能使用
int a = 10;
int main() {
// 使用全局变量
printf("a = %d\n", a);
return 0;
}
总结:
多文件编程:
- 把函数声明放在头文件xxx.h中,在主函数中包含相应头文件
- 在头文件对应的xxx.c中实现xxx.h声明的函数
防止头文件重复包含:
- 当一个项目比较大时,往往都是分文件,这时候有可能不小心把同一个头文件 include 多次,或者头文件嵌套包含。
- 为了避免同一个文件被include多次,C/C++中有两种方式。
- 方法一:
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
// 声明语句
#endif
- 方法二:
#pragma once
// 声明语句
命令行编译程序:
gcc -g main.c func.c -o main.exe
- -g 指定编译的文件,多个文件用空格隔开。注意:只需要编译c文件,h文件不需要加入进去;
- -o 指定生成可执行文件的名字;
ps:命令行显示中文乱码,修改命令:chcp 65001
头文件包含的区别:
- <> 表示系统直接按系统指定的目录检索
- "" 表示系统先在 "" 指定的路径(没写路径代表当前路径)查找头文件,如果找不到,再按系统指定的目录检索
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);
}
三、指针
指针基本语法:
-
内存地址
-
- 在计算机内存中,每个存储单元都有一个唯一的地址(内存编号)。
-
-
- 通俗理解,内存就是房间,地址就是门牌号
-
-
指针和指针变量
-
- 指针(Pointer)是一种特殊的变量类型,它用于存储内存地址。
-
-
- 指针的实质就是内存“地址”
-
-
- 指针变量就是存储这个地址的变量。
-
指针作用
-
- 可间接修改变量的值
指针变量的定义和使用:
- 指针也是一种数据类型,指针变量也是一种变量
- 指针变量指向谁,就把谁的地址赋值给指针变量
- 语法格式:
类型 变量;
类型 * 指针变量 = &变量;
-
- & 叫取地址,返回操作数的内存地址
- * 叫解引用,指操作指针所指向的变量的值
- 在定义变量时,* 号表示所声明的变量为指针类型
-
-
- 指针变量要保存某个变量的地址,指针变量的类型比这个变量的类型多一个*
-
-
- 在指针使用时,* 号表示操作指针所指向的内存空间
#include <stdio.h>
int main() {
// 定义一个int类型的变量,同时赋值为10
int a = 10;
// 打印变量的地址
printf("&a = %p\n", &a);
// 定义一个指针变量,int *保存int的地址
// int *代表是一种数据类型,int *指针类型,p才是变量名
int* p;
// 指针指向谁,就把谁的地址赋值给这个指针变量
p = &a;
// 打印p, *p, p指向了a的地址,*p就是a的值
printf("p = %p, *p = %d\n", p, *p);
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;
}
指针与常量:
语法格式:
int a = 1;
const int *p1 = &a; // 等价于 int const *p1 = &a;
int * const p2 = &a;
const int * const p3 = &a;
-
- 从左往右看,跳过类型,看修饰哪个字符
-
-
- 如果是*, 说明指针指向的内存不能改变
- 如果是指针变量,说明指针的指向不能改变,指针的值不能修改
-
- 示例代码:
#include <stdio.h>
int main() {
int a = 1;
int b = 2;
// p1 可以改,*p1不能改
const int *p1 = &a; // 等价于 int const *p1 = &a;
// p1 = &b; // ok
// *p1 = 555; // err
// p2 不能修改,*p2可以修改
int *const p2 = &a;
// p2 = &b; //err
// *p2 = 555; // ok
// p3 和 *p 都不能改
const int *const p3 = &a;
// p3 = &b; // err
// *p3 = 555; // err
return 0;
}
指针大小:
- 使用sizeof()测量指针的大小,得到的总是:4或8
- sizeof()测的是指针变量指向存储地址的大小
-
- 在32位平台,所有的指针(地址)都是32位(4字节)
- 在64位平台,所有的指针(地址)都是64位(8字节)
#include <stdio.h>
int main() {
int *p1;
int **p2;
char *p3;
char **p4;
printf("sizeof(p1) = %llu\n", sizeof(p1));
printf("sizeof(p2) = %llu\n", sizeof(p2));
printf("sizeof(p3) = %llu\n", sizeof(p3));
printf("sizeof(p4) = %llu\n", sizeof(p4));
printf("sizeof(double *) = %llu\n", sizeof(double *));
return 0;
}
指针步长:
- 指针步长指的是通过指针进行递增或递减操作时,指针所指向的内存地址相对于当前地址的偏移量。
- 指针的步长取决于所指向的数据类型。
-
- 指针加n等于指针地址加上 n 个 sizeof(type) 的长度
- 指针减n等于指针地址减去 n 个 sizeof(type) 的长度
#include <stdio.h>
int main() {
char ch;
char *p1 = &ch;
printf("p1:%p, p1+1: %p\n", p1, p1 + 1); // 步长为1字节
int a;
int *p2 = &a;
printf("p2:%p, p2+1: %p\n", p2, p2 + 1); // 步长为4字节
double d;
double *p3 = &d;
printf("p3:%p, p3+1: %p\n", p3, p3 + 1); // 步长为8字节
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 a = 100;
// 一级指针
int* p1 = &a;
printf("&a=%p\n", &a);
printf("p1=%p\n", p1);
printf("&p1=%p\n", &p1);
// 二级指针,可以存储一级指针变量的地址
int** p2 = &p1;
printf("p2=%p\n", p2);
printf("&p2=%p\n", &p2);
// 三级指针,可以存储二级指针变量的地址
int*** p3 = &p2;
printf("p3=%p\n", p3);
printf("&p3=%p\n", &p3);
printf("---------------------\n");
// 通过一级指针访问100,打印出来
printf("*p1=%d\n", *p1);
// 通过二级指针访问100,打印出来
printf("**p2=%d\n", **p2);
// 通过三级指针访问100,打印出来
printf("***p3=%d\n", ***p3);
return 0;
}
指针和函数:
函数参数传值:
- 传值是指将参数的值拷贝一份传递给函数,函数内部对该参数的修改不会影响到原来的变量
- 示例代码:
// 函数参数传值,函数内部交换2个变量的值,验证函数外部的变量有没有改变
#include <stdio.h>
// 函数定义
void func(int m, int n) {
// 函数内部交换2个变量的值
int temp = m;
m = n;
n = temp;
printf("函数内部 m = %d, n = %d\n", m, n);
}
int main() {
int a = 10;
int b = 20;
// 调用函数,值传递
func(a, b);
printf("函数外部 a = %d, b = %d\n", a, b);
return 0;
}
运行结果:
函数内部 m = 20, n = 10
函数外部 a = 10, b = 20
函数参数传址:
- 传址是指将参数的地址传递给函数,函数内部可以通过该地址来访问原变量,并对其进行修改。
- 示例代码:
// 函数参数传地址,函数内部交换2个指针指向内存的值,验证函数外部的变量有没有改变
#include <stdio.h>
// 函数定义
void func(int *m, int *n) {
// 函数内部交换2个指针指向内存的值
int temp = *m;
*m = *n;
*n = temp;
printf("函数内部 *m = %d, *n = %d\n", *m, *n);
}
int main() {
int a = 10;
int b = 20;
// 调用函数,地址传递
func(&a, &b);
printf("函数外部 a = %d, b = %d\n", a, b);
return 0;
}
运行结果:
函数内部 *m = 20, *n = 10
函数外部 a = 20, b = 10
函数指针:
函数名:
- 一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址
#include <stdio.h>
void func(int a) {
printf("func a = %d\n", a);
}
int main() {
// 函数名字,就是代表函数的入口地址,函数地址
printf("%p, %p, %p\n", func, &func, *func);
// 3种调用方法都可以
func(1); // 最简便,最常用
(&func)(2);
(*func)(3);
return 0;
}
函数指针:
- 函数指针:它是指针,指向函数的指针
- 语法格式:
返回值 (*函数指针变量)(形参列表);
-
- 函数指针变量的定义,其中返回值、形参列表需要和指向的函数匹配
#include <stdio.h>
void func(int a) {
printf("a = %d\n", a);
}
int main() {
// 函数指针变量的定义,同时初始化
void (*p1)(int a) = func;
// 通过函数指针变量调用函数
p1(10);
// 先定义函数指针变量,后面再赋值
void (*p2)(int);
p2 = func;
// 通过函数指针变量调用函数
p2(20);
return 0;
}
回调函数:
- 函数指针变量做函数参数,这个函数指针变量指向的函数就是回调函数
- 回调函数可以增加函数的通用性
-
- 在不改变原函数的前提下,增加新功能
#include <stdio.h>
// 定义函数,函数指针做形参
int calc(int a, int b, int (*p)(int, int)){
// 通过函数指针变量调用函数,获取返回值
int res = p(a, b);
return res;
}
// 定义加法函数
int add(int x, int y) {
return x + y;
}
// 定义减法函数
int sub(int x, int y) {
return x - y;
}
int main() {
int result;
// 回调加法函数
result = calc(1, 2, add);
printf("result = %d\n", result);
// 回调减法函数
result = calc(10, 5, sub);
printf("result = %d\n", result);
return 0;
}