C语言指针(进阶内容)

目录

1. 指针的基本概念:

2. 不同类型的指针的意义是什么?

3. 野指针是什么?

4.指针加减整数 

5.指针运算 (指针 - 指针)

6.一级指针和二级指针以及多级指针是什么意思?

7.指针数组

8.数组指针

9.函数指针


1. 指针的基本概念:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的 4/8 个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的 + - 整数的步长,指针解引用操作的时候的权限。
4. 指针的运算


2. 不同类型的指针的意义是什么?

指针本身在内存中都是4/8(32位平台/64位平台)个字节大小,区别就在于不同指针的步长是不相同的。在于被解引用时改变内存空间的多少个字节,不同的指针改变不同的内存空间字节大小。

int main()    
{	
	int a = 1111111;
	char* ps = (char*)&a; // 强制类型转换成char*
	printf("%d",*ps ); // 71
	return 0;
}

int和char指针控制指针偏移量不同 所以修过地址时会发生数据截断,整形空间存储的是4个字节大小,而字符串指针的步长是一个字节大小,这里强制类型转换会截断数据,ps存放的是一个字节的数据,而删除了高位的三个地址(字节)。


3. 野指针是什么?

指向未知、无效或已释放的内存位置的指针被称为野指针。

    int* p;
    *p = 10; // 非法访问内存 p就是野指针

p没有初始化就意味着没有明确指向,一个局部变量没有初始化 放的就是随机值 0xcccccc。

int *test()
{
	int a = 10;
	return &a;
}

int main()
{
  // *p可以获取a的值 前提是没有其他变量占领a的空间否则就是乱码
	int*p = test();
	return 0;
}

即使这里p获取到了a的地址,但是也无法通过解引用去改变a的值,可以获取a的值为10,不过依然无法改变,a变量的地址已经被销毁了。

 这里涉及到函数栈帧的概念,可以去查看这篇文章,很好的解释了什么是栈帧。【详解】函数栈帧——多图(c语言)_指针与函数值传递-CSDN博客


4.指针加减整数 

指针的类型决定了指针的 + - 整数的步长,指针加n,即跳过n * 指针步长的字节,就是指向同一指针类型的第n + 1个元素。

int main()
{
	// 用指针的方式初始化数组
	int arr[5];
	int* ps;
	for (ps = arr; ps < &arr[5];)
	{
		*ps++ = 1;
		// *ps = 1;
		// ps++
	}
	return 0;
}

这里通过指针++的方式,初始化数组。&arr[5]不存在数组越界问题,因为没有访问他的地址,只是用于判断。


5.指针运算 (指针 - 指针)

指针 - 指针 得到的是指针之间的元素个数
不是所以指针都可以相减 指向同一内存空间的两个指针才能相减

int my_strlen3(char* str)
{
	char *start = str; // 存储首字符地址
	while (*str != '\0')
	{
		str++; // 向后寻找 直到寻找到\0
	}
	return str - start; // 指针 - 指针
}

int main()
{
	int len = my_strlen3("abcdef"); // 第六个指针 - 第0个指针
	printf("%d", len);// 6
	return 0;
}

这是一个使用指针运算的方式重写Strlen内置函数的案例,my_strlen3函数最后return返回的是两个指针相减的结果,在同一内存空间中第6个指针减去第0个指针,得出的是元素的个数也就是6。


6.一级指针和二级指针以及多级指针是什么意思?

一级指针指向一块内存空间,其变量值即为该地址,通过“ * ”运算可以获取该地址存储的值。

二级指针是指向指针的指针,其变量值为一级指针的地址。

以此类推三级指针就是指向指针的指针,其变量值为二级指针的地址....

    int a = 10;
    int* pa = &a;	// 一级指针
	int** paa = &pa; // 二级指针
	**paa = 20;

二级指针通过解引用 *paa的方式可以访问到a的内存空间,从而修改a的值。


7.指针数组

指针数组是一个数组,其中的每个元素都是指向某种数据类型的指针

指针数组存储了一组指针,每个指针可以指向不同的数据对象。

    int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 2,2,3,4 };
	int arr3[4] = { 3,2,3,4 };
    // 将arr1 arr2 arr3的数组首元素地址存储到数组中
	int* parr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}

这里模拟了一个二维数组的结构,将三个数组以地址的形式存放到parr指针数组中,再通过双重for循环遍历parr来获取每个数组中元素的值。


8.数组指针

数组指针本质是一个指针,指向了一个数组。

数组指针常用于接收二维或多维数组的函数传参。

int main()
{
	int* p1 = arr;	// 存放的是数组的首元素地址
	int(*p2)[10] = &arr; // 存放的是整个数组的地址

	return 0;
}

简单演示数组指针的使用,数组指针存放的是整个数组的地址

  •  那么如何理解数组指针的写法和意思呢? 
int main()
{
	char* arr[10] = { 0 };
	//  (*ps)代表这是一个指针变量名叫ps  
	//  char* 代表存放的数组中的元素是char* 
	//  [] 中必须和存放数组的元素个数一样
	char* (*ps)[10] = &arr;  

	return 0;
}

初始化一个字符串指针类型的数组arr,再用指针数组来存放arr,而这个数组指针存放的arr数组中元素是char *类型,千万不要误以为是二级指针!

  • 如何使用指针数组呢? 
 //数组指针常用于 二维数组甚至三维数组!
void print2(int(*ps)[5], int r, int c)  // 实参用 int (*)[5] 这个类型来接受 是第一行数组的大小
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
            // 这两种写法的结果都相同
            // 因为 *(ps + i) == ps[i]
			//printf("%d ", *(*(ps + i) + j));
			printf("%d ", ps[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
	// 这里arr 传的是第一行的地址
	print2(arr, 3, 5);
	return 0;
}

我们初始化一个二维数组arr,再传入print2函数的第一个参数中,此时arr实际传入的是第一行的数组,而我们函数中第一个形参类型就是数组指针 int(*ps)[5] ,于是我们接收到了二维数组传入进来的第一行数组的地址。

1. 那我们解释一下第一中写法,ps+i 等同于第一行数组向下移动 i 行,*(ps + i )就获取了第 i 行数组的地址,数组的地址也就是首元素地址又等同于数组名。第 i 行的数组名 + j就是数组首元素地址加上 j ,此时二维数组的全部元素都被遍历了一遍。

2. 第二种写法就很好理解了,*(ps + i )等同于ps[ i ],因为传进函数的是arr,所以arr[ i ]就是数组的第 i 行。最后ps[ i ]加上[ j ]就是完整的二维数组写法,也就能将二维数组全部遍历一次了。

  •  一维数组和二维数组的传参时的规范和易错点
// 一维数组传参
void test(int arr[]); //ture
void test(int* arr); //ture
void test(int arr[10]); //ture

void test2(int* arr[20]); //ture
void test2(int** arr); //ture

int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = {0};
	test(arr);
	test2(arr2);
	return 0;
}

 一维数组

 

// 二维数组传参
void test(int arr[3][5]); // ture
void test(int arr[][]); // false
void test(int arr[][5]); // ture
void test(int*arr); // false  因为是第一行数组地址 所以不能用整形指针接收
void test(int*arr[5]); // false 
void test(int(*arr)[5]); // ture 要用数组指针这个类型来接收第一行数组的地址
void test(int**arr); // ture

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

 二维数组


9.函数指针

函数指针是指向函数的指针变量。

通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。

  • 函数指针的基本写法和调用方法
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	// &函数名等于函数名 都可以获取函数的地址
	//printf("%p", &Add);  ==  printf("%p", Add);
	
	// 函数指针写法
	int (*ps)(int, int) = &Add;
	int ret = (*ps)(3, 2); // 通过指针的方式调用函数 也可以这样写 ps(3, 2)
	printf("%d", ret);
	return 0;
}

1.&函数名等于函数名 都可以获取函数的地址,只是写法上的不同。

2.函数指针就是获取函数的地址,int (*ps)(int, int) 括号中写的是函数形参的数据类型

3.可以通过函数指针调用函数,可以省略(*)不写,效果也是一样

  • 函数指针数组也被称为转移表
int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

void munu()
{
	printf("*********************\n");
	printf("********1. 相加*************\n");
	printf("********2. 相减*************\n");
	printf("********3. 退出*************\n");
	printf("*********************\n");
	printf("*********************\n");
}

int main()
{
	// 函数指针数组 - 存放函数指针的数组
	// 转移表
	int (*arr[2])(int, int) = { Add,Sub };
	int x = 0;
	int y = 0;
	int input = 0;
	
	do
	{
		munu();
		scanf("%d", &input);
		if (input == 3)
		{
			printf("退出游戏\n");
		}
		else if (input >= 1 && input <= 2)
		{
			printf("请输入坐标:>");
			scanf("%d %d", &x,&y);
			int ret = arr[input-1](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("输入错误的选项\n");
		}

	} while (input);

	return 0;

}	

 这个案例简单实现了加减数的功能。我们将加法函数和减法函数的地址存储到函数指针数组中,函数指针数组是可以存放多个函数地址的数组,但函数指针数组中的形参类型都要相同。再通过用户输入和简单的判断调用指定的函数。

  • 指向【函数指针数组】的指针 
int main()
{	
	// 函数指针数组
	int (*arr[2])(int, int) = { Add,Sub };
	// 指向【函数指针数组】的指针
	int (*(arr)[2])(int, int) = &arr[2];
	return 0;
}

 可以无限套娃,但是不建议

  • 无具体类型指针 
int main()
{
	int a = 10;
	void* b = &a; // void*是无具体类型的指针,可以接收
	// void*是无具体类型指针 不可以进行指针+-和引用操作
	return 0;
}

 无具体类型指针void * 通常用于函数形参,不确定传入的是什么数据类型时使用。


好了这就是C语言指针进阶部分的内容,如有哪里错误或者混淆的地方请指出或者留言。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值