C语言学习笔记之 函数和指针

函数和指针

函数

函数分类

  • 系统函数,即库函数
    由编译系统提供
  • 用户定义函数
    用以解决用户需要

函数的作用

省去重复代码
一段代码指令为相同功能服务,可以将这段代码定义成函数
函数可以让程序模块化,便于阅读和修改

函数的定义

返回值类型 函数名(参数列表)
{
    函数体;
}

返回类型:用户需要函数返回的数据类型(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 = &num;
	printf("*p = %#x
", *p);

	short *p1 = &num;
	printf("*p1 = %#x
", *p1);

	char *p2 = &num;
	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 = &num; // *p只读,p可读可写
*p = 10; // 报错
p = NULL; // 没问题

int * const p2 = &num; // *p2可读可写,p2只读
*p2 = 10; // 没问题
p2 = NULL; // 报错

const int * const p3 = &num; // *p3只读,p3只读

指向同一数组的两个指针间的关系

  1. 指向同一数组的两个指针变量相减,是两个指针变量间元素的个数
  2. 指向同一数组的两个指针变量相加无意义
  3. 指向同一数组的两个指针变量 p1 > p2,p1 == p2等,表示两个指针间的位置关系
  4. 指向同一数组的两个指针变量 p1 = p2,指向同一位置

注意事项

  1. 不要操作未初始化的指针变量,建议将指针变量初始化为NULL
  2. 不要操作NULL指针,在使用指针前先判断是否为NULL
  3. 小心野指针和悬空指针
  4. 不要操作自定义地址
原文地址:https://www.cnblogs.com/zhujiangyu/p/13620017.html