函数和指针
函数
函数分类
- 系统函数,即库函数
由编译系统提供 - 用户定义函数
用以解决用户需要
函数的作用
省去重复代码
一段代码指令为相同功能服务,可以将这段代码定义成函数
函数可以让程序模块化,便于阅读和修改
函数的定义
返回值类型 函数名(参数列表)
{
函数体;
}
返回类型:用户需要函数返回的数据类型(void表示没有返回)
函数名代表的是函数的入口地址#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> void test() { ; } int main() { printf("%p ", test); return 0; }
运行结果:
00441271
参数列表:用户将函数外部数据传入函数内部的 局部变量
函数体:函数功能的实现代码
参数列表(形参)
形参在函数定义时没有空间,只有在调用函数的时候形参才有空间
注意!在定义函数时不要给函数赋值!因为形参在函数没有被调用的时候没有空间
形参格式:
类型名 + 变量名
例:
void test(int a, char s[]) {
puts(s);
printf("%d",a);
}
形参的本质是局部变量,当函数执行完毕,形参会被释放
函数的声明
函数名(形参列表);
函数名是一个地址
自动识别
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(char s[])
{
puts(s);
}
int main() {
test("Hello world
"); // 自动识别
return 0;
}
显示声明
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
void test(); // 显示声明
test("Hello world
"); // 调用
return 0;
}
void test(char s[])
{
puts(s);
}
如果省略函数的返回类型,会默认返回int
分文件编程
在开发中可以将自定义函数与主文件(含主函数)分开,写在不同的文件中,通过头文件(.h)来连接
例:
项目结构:
func.h(函数声明):
#pragma once // 防止头文件重复包含
#ifndef _FUNC_H_
#define _FUNC_H_ // _头文件名大写_H_
double average(int x, int y); // 函数声明
#endif
func.c(函数实现):
double average(int x, int y) // 函数的定义
{
return ((double)(x + y)) / 2.0;
}
main.c(主文件):
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "func.h" // 自定义头文件
int main() {
int x = 10, y = 8;
printf("%g
", average(x, y));
return 0;
}
编译:
gcc func.c main.c -o main.exe
运行结果:
9
return与exit
return
return 返回值;
也可以这样写:
return;
当执行到return语句,会结束函数,继续从函数调用下一处执行
exit
exit(退出状态);
exit会结束进程
退出状态:
- 0:正常退出
- 1:发生错误,强制退出
递归
函数自己调用自己,递归必须有出口,否则会导致内存泄漏
// 例:汉诺塔
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void hannuota(int n, char a, char b, char c)
{
if (n == 1) // 出口
{
printf("%c --> %c
", a, c);
}
else
{
hannuota(n - 1, a, c, b);
printf("%c --> %c
", a, c);
hannuota(n - 1, b, a, c);
}
}
int main()
{
printf("请输入汉诺塔层数:");
int n;
scanf("%d", &n);
hannuota(n, 'A', 'B', 'C');
return 0;
}
运行结果:
请输入汉诺塔层数:3
A --> C
A --> B
C --> B
A --> C
B --> A
B --> C
A --> C
指针
指针是C/C++最强大的武器,但也是最容易出错的东西,指针用好了很强大,没用好可能会导致内存泄漏,最终损坏计算机
概述
内存
- 存储器:计算机组成中,用来存储数据和程序,辅助CPU进行运算处理的主要部分
- 内存:内部存储器,暂存数据,掉电丢失SRAM、DRAM、DDR、DDR2、DDR3
- 外存:外部存储器,如硬盘
内存是沟通CPU与硬盘的桥梁
- 暂时存放CPU中的运算数据
- 暂存与硬盘等外存交换的数据
物理存储器和存储地址空间
物理存储器:实际存在的具体存储器芯片
- 主板上装插内存条
- 显示卡上的显示RAM芯片
- 各种适配卡上的RAM芯片和ROM芯片
存储地址空间:对存储器编码的范围。在软件上常说的内存是指这一层含义
- 编码:对每个物理存储单元(一个字节)分配一个号码
- 寻址:可以根据分配的号码找到对应的存储单元,完成数据的读写
内存地址
- 将内存抽象成一个很大的一堆字符数组
- 编码就是对内存的每一个字节分配一个32位或64位的编号
- 这个内存编号称之为内存地址
内存中的每一个数据都会分配相应的地址,地址是唯一的
- char:占一个字节分配一个地址
- int:占四字节,分配四个地址
指针和指针变量
通过指针找到存储空间,完成数据读写
在32位平台,地址编号占4字节
在64位平台,地址编号占8字节
也就是说,指针的大小是固定不变的
指针就是地址,地址就是指针
指针变量是存放地址的变量
指针的定义
语法:
指向类型 *变量名;
例:
int *p; // 指向int类型的指针
// *修饰的变量为指针变量,指针变量名为p,而不是*p
int *(a[]); // 指向数组的指针
int *a[]; // 指针数组,数组的每一个元素都是指针
void *a; // 可以指向任何类型
指针的初始化与赋值
// 初始化
int a = 0; // int 数据
int *p = NULL; // 初始化
// 赋值
p = &a; // 将a的地址赋值给指针p,相当于让p指向a
建议指针初始化为NULL
NULL的定义:
#define NULL ((void *)0)
NULL即空指针,指向空,当指针指向的内存空间被释放或是不知道指针该指哪里时,尽量都赋值为NULL,在使用指针前先判断是否为NULL,避免内存泄漏
指针变量存放的是地址,可以通过改变地址让指针指向另外的数据
#include <stdio.h>
int main()
{
int a = 0, b = 1;
int *p = &a; // 指向a
printf("p = %p
&a = %p
",p,&a);
p = &b; // 指向b
printf("p = %p
&b = %p
",p,&b);
return 0;
}
运行结果:
p = 0060FE98
&a = 0060FE98
p = 0060FE94
&b = 0060FE94
指针的基础使用
通过指针变量间接操作数据:
通过 *指针名 可以访问对应的空间
#include <stdio.h>
int main()
{
int a = 0;
int *p;
p = &a;
// 在使用中,*p等价于a
printf("*p = %d
a = %d
",*p,a);
*p = 100; // 间接修改数据
printf("*p = %d
a = %d
",*p,a);
printf(">");
scanf("%d",p);
printf("*p = %d
a = %d
",*p,a);
++(*p);
printf("*p = %d
a = %d
",*p,a);
return 0;
}
运行结果:
*p = 0
a = 0
*p = 100
a = 100
>237
*p = 237
a = 237
*p = 238
a = 238
指针的宽度
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int num = 0x01020304;
int *p = #
printf("*p = %#x
", *p);
short *p1 = #
printf("*p1 = %#x
", *p1);
char *p2 = #
printf("*p2 = %#x
", *p2);
return 0;
}
运行结果:
*p = 0x1020304
*p1 = 0x304
*p2 = 0x4
解析:
指针的跨度
跨度由指向的类型决定
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char *pc = NULL;
printf("pc = %#x
pc + 1 = %#x
",pc, (pc + 1));
short *ps = NULL;
printf("ps = %#x
ps + 1 = %#x
", ps, (ps + 1));
int *pi = NULL;
printf("pi = %#x
pi + 1 = %#x
", pi, (pi + 1));
return 0;
}
运行结果:
pc = 0
pc + 1 = 0x1
ps = 0
ps + 1 = 0x2
pi = 0
pi + 1 = 0x4
使用实例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a[4] = { 0,1,0,0 };
int *p = NULL;
p = a;
printf("%d", *(p + 1));
return 0;
}
运行结果:
1
const指针
int num = 0;
const int *p = # // *p只读,p可读可写
*p = 10; // 报错
p = NULL; // 没问题
int * const p2 = # // *p2可读可写,p2只读
*p2 = 10; // 没问题
p2 = NULL; // 报错
const int * const p3 = # // *p3只读,p3只读
指向同一数组的两个指针间的关系
- 指向同一数组的两个指针变量相减,是两个指针变量间元素的个数
- 指向同一数组的两个指针变量相加无意义
- 指向同一数组的两个指针变量 p1 > p2,p1 == p2等,表示两个指针间的位置关系
- 指向同一数组的两个指针变量 p1 = p2,指向同一位置
注意事项
- 不要操作未初始化的指针变量,建议将指针变量初始化为NULL
- 不要操作NULL指针,在使用指针前先判断是否为NULL
- 小心野指针和悬空指针
- 不要操作自定义地址