C语言——[指针] 拒绝对指针的迷茫



一、基本知识

1、什么是内存?

  • 计算机的存储器
  • 大小:通常为4 - 16G

2、什么是内存单元?

  • 为了有效率的使用内存,以 1 字节为单位,对内存进行划分
  • 大小:1byte

3、什么是地址?

  • 为了有效率的使用内存单元
  • 内存单元的编号
  • 一个地址中存1 byte的数据

4、32位 和 64位 的含义

  • 32位 – 32根地址线(物理线) – 通、断电 – 表示1 / 0
  • 电信号 --> 数字信息(1 / 0组成的二进制序列)

5、什么是指针?

  • 内存单元的编号 - 地址 - 指针
  • 指针变量 - 指针(口头表述)

6、什么是指针变量?

  • 存放地址
  • 存储在指针中的值被当做地址处理

7、指针的大小

  • 32位 - 地址由32个 0 / 1 组成二进制序列 - 32 bit - 4byte
  • 64位 - 地址由64个 0 / 1 组成二进制序列 - 64 bit - 8byte

8、一个地址中存储1 byte大小的数据

9、为什么把地址称为指针?

  • 通过地址可以找到所需的变量
  • 地址指向该变量
  • 地址形象化的称呼

二、指针类型

1、指针

  • char*
  • int*
  • float*
  • double*
    ···
  • * :变量为指针变量
  • int : 指针指向的变量的类型为int

2、野指针

  • 指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3、数组指针

  • 指向数组的指针 - 存储数组的地址
  • int (*变量名)[ 所指向的数组的元素个数 ]
  • (*p)说明 p 是指针
  • int [ ] 说明指针指向的对象是整型数组

4、函数指针

  • 指向函数的指针 - 存储函数的地址
  • 函数返回类型 (*变量名)(函数形参类型)
  • (*p)说明 p 是指针
  • 返回类型 (形参类型)说明指针指向函数

5、问题分析

A、指针类型的意义是什么?

  • 指针访问地址中内容权限的大小

  • 指针的步长 (走一步走多远)

B、利用数组分析指针类型的意义在这里插入图片描述


三、野指针

  • 指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

1、为什么会形成野指针?

  • 指针未初始化

  • 指针越界访问

  • 指针指向的空间释放

    指针内存储已释放空间的变量地址

2、如何避免野指针的出现?

  • 指针初始化

  • 小心指针越界 - 用户无NULL访问权限

  • 指针指向的空间(例如局部变量)被释放之后,指针要及时置为 NULL

  • 避免返回局部变量的地址

  • 指针使用之前检查有效性
    在这里插入图片描述

3、代码分析

#include<stdio.h>

int main()
{
	//1、i的定义在 arr[10] 之前
	int i = 0;
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9,10 };
	//2、局部变量(i和arr)在内存中的栈区存储
	//3、栈区内存先使用高地址空间
	//4、数组随着下标增长,地址由低向高变化
	//5、恰好:arr[12] == i;
	//6、arr[12] == i赋值为0,i++循环;
	int* p = arr;
	for (i = 0;i <= 12; i++)
	{
		*(p + i) = 0;
		printf("Love\n");
	}
	//7、程序死循环
	return 0;
}

i 和 arr[9] 的地址间距大小

  • VS2019 - 2个整型
  • gcc -   1个整型
  • VC6.0 -  0

四、指针的运算

  • 指针 ± 整数 指针\pm 整数 指针±整数

  • 指针 − 指针 指针 - 指针 指针指针(指针相加无意义)
    在这里插入图片描述

  • 指针比较大小(关系运算)

C标准规定:

  • 允许指针P 与数组元素arr[Vlue]比较
  • 不允许指针P 与数组元素arr[-1]比较
    在这里插入图片描述

五、二级指针

存储指针的地址

  • * 间接访问操作符 - 访问地址中存储的数据内容
  • 以此类推:n级指针

六、字符指针

1、两种使用方法

#include<stdio.h>
int main()
{
	char* ch = "N";
	//1、存储字符类型的地址
	char* p_1 = &ch;
	printf("%c\n", *p);
	
	//2、存储常量字符串首字符地址
	const char* p_2 = "love";
	//常量字符串 - 不可以修改
	//const修饰 *p_2
	//无const修饰 - 一般编译器会报错
	printf("%s\n", p);       //love
	printf("%c\n", *p);      //l
	printf("%c\n", *(p + 1));//o
	
	return 0;
}

2、代码理解

《剑指offer》中的一段代码

#include<stdio.h>
int main()
{
	char str_1[] = "love";
	char str_2[] = "love";

	const char* str_3 = "love";
	const char* str_4 = "love";

	//不同的数组 - 开辟不同的内存空间
	if (str_1 == str_2)
		printf("Same\n");
	else
		printf("Not same\n");
	
	//C/C++会把常量字符串存储到单独的一个内存区域
	//str_3 和 str_4 指向地址相同
	if (str_3 == str_4)
		printf("Same\n");
	else
		printf("Not same\n");
	
	return 0;
}


七、指针与数组

数组与指针的关系

  • p+i 计算数组 arr[i] 的地址。

  • 通过指针访问数组

  • 编译器翻译arr[i] --> *(arr + i)

  • 指针数组 - 存储地址的数组

  • 数组指针 - 指向数组的指针


八、指针数组

存储地址的数组

指针数组的使用

#include<stdio.h>
int main()
{	
	//指针数组
	//不常见的使用 
	int a = 0;
	int b = 0;
	int c = 0;
	int* arr[3] = { &a, &b, &c };//存放整型指针的数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		//* 访问地址中存储的数据
		printf("%d\n", *arr[i]);
	}
	return 0;
	return 0;
}

指针数组模拟二维数组

#include<stdio.h>
int main()
{

	int a[5] = { 1,2,3,4,5 };
	int b[] = { 2,3,4,5,6 };
	int c[] = { 3,4,5,6,7 };
	int* arr[3] = { a, b, c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(arr[i] + j));
			printf("%d ", arr[i][j]);//写出这样也可以打印
		}
		printf("\n");
	}
	return 0;
}

九、数组指针

指向数组的指针 - 存储数组的地址

1、arr 和 & arr

arr : 数组首元素的首字符地址 - 元素的地址

  • 类型:指针

&arr:整个数组的首字符地址 - 数组的地址

  • 类型:数组指针

在这里插入图片描述

2、数组指针

  • 指向数组的指针
  • int (*变量名)[所指向的数组的元素个数]
  • 自定义了指针的访问权限大小 (访问一个数组大小)
  • 自定义了指针的步长 (一步走过一个数组)
  • 一般用于二维数组

数组指针 和 一维数组

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	
	//存储首元素的地址
	int *p = arr;
	//存储整个数组的地址
	//1、优先级:[] > *
	//2、(*p) - 保证*先和p结合 - 说明p是一个指针
	//3、[] - 说明指针p指向数组
	int(*p)[10] = &arr;

	//数组指针访问一维数组元素
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		// *p -> arr
		// arr -> 首元素地址
		//* - 访问地址里存储的数据
		// *(*p + i) -> arr[i] <- *(arr + i)
		printf("%d ", *(*p + i));
	}
	
	return 0;
}

数组指针 和 二维数组

#include<stdio.h>
void print_1(arr[][5], r, c)
{
	int i = 0;
	for (i = 0;i < r; i++)
	{
		int j = 0;
		for (j = 0;j < c; j++)
		{
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
}
void print_2(int(*p)[5], r, c)
{
	int i = 0;
	for (i = 0;i < r; i++)
	{
		int j = 0;
		for (j = 0;j < c; j++)
		{
		  //(*p + i) -> arr[i] -> 每行看作一个一维数组
		  // arr[i] -> 指针 -> 首元素地址
		  //访问一维数组arr[i]的元素
		  // *((*p + i) + j) -->  arr[i][j]  <-- *(arr[i] + j)
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{1,3,5,7,9} 
}
	//二维数组的首元素 - 第一行
	//二维数组数组名 - 第一行的地址 - 一维数组的地址- 类型 - 数组指针
	print_1(arr, 3, 5);
	print_2(arr, 3, 5);
	return 0;
}

3、数组 和 指针

在这里插入图片描述


十、数组传参

一维数组传参的几种方式

  • 形参是数组
  • 形参是指针
void test_1(int arr[]){;}
void test_1(int arr[10]){;}
void test_1(int* p){;}

void test_2(int* arr[20]){;}//指针数组 - 传参 -> 指针数组
void test_2(int** p){;}//指针数组 - 传参 - > 二级指针 - 指针的地址

int main()
{
	int arr_1[10] = { 0 };
	int* arr_2[20];
	
	test_1(arr_1);
	
	test_2(arr_2);
	
	return 0;
}

二维数组传参的几种方式

  • 形参是数组
  • 形参是指针
void(int arr[][5]){;}//列不可省
void(int arr[3][5]){;}
void(int(*P)[5]){;}//二维数组首元素 - 第一行 -> 数组指针类型

int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

十一、指针传参

1、指针类型的实参,形参的类型是什么?

#include<stdio.h>
//int* 类型的实参用 int* 接收
void print_1(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}

//int** 类型的实参用 int** 接收
void print_2(int** pp, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(*pp + i));
		//*pp -> p    访问pp中存储的地址 -> p
		//**pp -> arr   访问*pp中存储的地址 -> arr
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
	
	int* p = arr;
	print_1(p, sz);
	printf("\n");
	
	int** pp = &p;
	print_2(pp, sz);
	
	return 0;
}

2、指针类型的形参,可以接收什么?

void test_1(int* p){;}

void test_2(int** p){;}
int main()
{
	int arr[10] = { 0 };
	int a = 10;
	int* p = arr;
	test_1(arr);//1、一维数组
	test_1(&a); //2、变量的地址
	test_1(p);  //3、一级指针

	int* parr[10];
	int** pp = parr;
	test_2(parr); //1、指针数组
	test_2(&p);   //2、一级指针的地址
	test_2(pp);   //3、二级指针
	return 0;
}

十二、函数指针

指向函数的指针 - 存储函数的地址

1、什么是函数指针?

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int x = 0;
	int y = 0;
	scanf("%d%d", &x, &y);
	int ret = Add(x, y);

	//1、函数指针 - 存储函数的地址
	//2/优先级:() > *
	//3、(*p) - *先和p结合 - 说明p是指针
	//4、(int, int) - 表明指针p指向函数
	//5、int, int - 表明指向函数的两个参数类型分别是int, int
	//6、Add <==> &Add
	int(*p)(int, int) = &Add;//只写Add也一样

	//7、函数指针调用函数
	ret = (*p)(x, y);
	//8、(*****p)也是一样的,*是摆设
	//因为Add <==> &Add <==> p
	ret = p(x, y);
}

2、代码理解

代码1

int main()
{
	//《C陷阱与缺陷》中,调用0地址处的函数
	(*(void(*)())0)();
	//1、void(*)() - 函数指针类型
	//2、(void(*)())0 - 强制类型转换
	//3、*(void(*)())0 - 访问0地址处函数
	//4、(*(void(*)())0)() - 调用0地址处函数
		
	return 0;
}

代码2

int main()
{
	//声明函数 - 返回类型是函数指针
	void (*signal(int, void(*)(int)))(int);
	//1、signal先和()结合 - 说明signal是函数
	//2、函数参数类型是int 和 void(*)(int)
	//3、void(*)(int) -指向一个参数类型int,返回类型void 的函数
	//4、signal是返回类型是void(*)(int)
	
	//5、让人理解的写法 - 语法不支持 - 是错误的
	void(*)(int) signal(int, void(*)(int));//c错误的
	//   拆分成void(*)(int) 和 signal(int, void(*)(int))
	//   语法要求signal(int, void(*)(int))放在*后

	//6、类型重定义typedef
	//重新定义int(*)(int)类型
	//重新定义的类型 - pfun_t - 也要放在*后
	typedef int(*pfun_t)(int)
	//7、声明函数
	pfun_t signal(int, pfun_t);
	
	return 0;
}

十三、函数指针数组

1、什么是函数指针数组

2、函数指针数组的用法

简单的整数计算器

#include<stdio.h>
int Add(int x, int y)//加法函数
{
	return x + y;
}
int Sub(int x, int y)//减法函数
{
	return x - y;
}
int Mul(int x, int y)//乘法函数
{
	return x * y;
}
int Div(int x, int y)//除法函数
{
	return x / y;
}
int main()
{
	int(*parr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//使下标与数组中存储的函数地址对应
	int input = 0;//选择加减乘除
	//要计算的两个数
	int x = 0;
	int y = 0;
	
	do
	{
		printf("请选择(0 - 退出):\n");
		printf("1 - 加法  2 - 减法\n");
		printf("3 - 乘法  4 - 除法\n");
		scanf("%d", &input);
	
		if (input >=1 && input <= 4)
		{
			printf("请输出两个整数:");
			scanf("%d%d", &x, &y);
			int ret = (*parr[input])(x, y);//访问函数指针 - 调用函数
			printf("%d\n", ret);
		}
		
		else if (input == 0)
			break;
		
		else
			printf("输入错误,请重新输入\n");
	}while(1);//循环程序
	
	return 0;
}


十四、指向函数指针数组的指针

存储函数指针数组的地址

int main()
{
	//函数指针数组
	int(*parr[5])(int, int) = { NULL };
	
	//指向函数数组的指针
	//1、(*p) - *先和p结合 - 说明p是指针变量
	//2、(*p)[5] - 说明p指向数组
	//3、p指向数组的类型 - int(*)(int, int) - 函数指针类型
	int(*(*p)[5])(int, int) = parr;
	
	return 0;
}

十五、回调函数

  • 在函数B中,通过函数指针,调用函数A
void Fun_A(int n){;}

void Fun_B(void(*p)(int))
{
	int n = 0;
	p(n);//回调函数
}
int main()
{
	Fun_B(Fun_A);
	return 0;
}

共勉!


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值