C语言教程——指针进阶(1)

目录

前言

1、字符指针

2、指针数组

3、数组指针

3.1数组指针

3.2&数组名VS数组名

3.3数组指针的使用

4、数组参数、指针参数

4.1一维数组传参

4.2二维数组传参

4.3一级指针传参

4.4二级指针传参

4.5总结

5、函数指针

5.1思考

总结


前言

我们在之前知道指针就是一个变量,用来存放地址,地址唯一标识一块内存空间,指针的大小是固定的,32位平台是4个字节,64位是8个字节,指针还是有类型的,指针的类型决定了指针的+ - 整数的步长,指针解引用操作的时候的权限。指针还是有运算的,指针加减整数,指针减去指针,指针的关系运算,忘了的可以去之前的文章复习一下。

C语言教程------指针初阶(1)-CSDN博客

接着我们进行指针的深入学习。


1、字符指针

在指针的类型中我们知道有一种指针类型为字符指针char*

一般都是这样的:

cpp 复制代码
nt main()
{
	char a = 'w';
	char* b = &a;
	return 0;
} 

这里b就是一个字符指针变量。还有一种是这样的:

cpp 复制代码
int main()
{
	const char* pc = "hello";
	printf("%s\n", pc);
	return 0;
}

给定一个常量字符串hello,这里创建了一个pc的指针变量,相当于指向字符串首字母,可以理解成字符数组,这里打印字符串知道起始位置就可以直到识别到\0,之后才会结束打印。

2、指针数组

指针数组是一个存放指针(地址)的数组。

cpp 复制代码
int main()
{
	const char* arr[4] = { "abc","edf","ghi","mno" };//存放字符指针的数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

这里给定了四个元素的char类型的字符指针,通过字符数组来存上这些指针(地址),当我们用循环来访问每一个元素地址的时候,就访问了这些常量字符串。

我们也可以仿照二维数组,来用这个指针数组来实现一个二维数组:

cpp 复制代码
int main()
{
	int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 2,3,4,5 };
	int arr3[4] = { 6,7,8,9 };
	int arr4[4] = { 0,0,0,0 };

	int* arr[4] = { arr1,arr2,arr3,arr4 };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			printf("%d ", arr[i][j]);//或者 *(arr[i] + j );
		}
		printf("\n");
	}
	return 0;
}

使用指针数组的好处是可以灵活地管理内存,只需分配和释放指针所指向的对象的内存空间,而不需要对整个数组进行操作。此外,指针数组还可以方便地传递和操作指针,提高程序的效率。

注意,使用指针数组时需要注意指针的有效性,即确保指针指向的对象在使用时是有效的,否则可能导致程序出错。

3、数组指针

我们知道指针有字符指针,指向字符的指针(char*),整形指针,指向整形的指针(int*),浮点型指针,指向浮点型的指针(float*)(double*)。

3.1数组指针

顾名思义,数组指针就是存放数组地址的指针,指向数组的指针。

但数组指针怎么来写呢:

cpp 复制代码
int arr[10];
int (*pa)[10] = &arr;

这里说明pa是一个指针,因为[ ] 的优先级要高于 * 号的优先级,所以要加上一个( )来保证p先和* 结合,这个指针指向一个有10个整形元素的数组,用来接收arr数组的地址。

3.2&数组名VS数组名

对于这两种数组的区分是啥?

cpp 复制代码
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);
	return 0;
}

这里通过一个十个元素的数组来进行区分,我们发现,这里打印出来的地址都一样,因为都是数组的首地址。这里并不能看出来什么,于是我们将每一个后都加上一个1,如果有不一样的效果那么加上1后的效果也不一样。

cpp 复制代码
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", arr);
	printf("%p\n", arr+1);

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);
	return 0;
}

这里就可以看出来了,十六进制中,第一个和第二个出现的情况都一样,都是多了四个字节,也就是一个整形的大小,而第三个,相差了40个字节,也就是差了一组的大小。他们的类型也不一样,第一个和第二个是整形指针(int *),而第三个的类型是数组指针,也就是(int( * ) [10] )。

所以,数组名是数组首元素的地址,拿到的是第一个元素的地址,而取数组名,拿到的是数组的地址。数组首元素的地址和数组的地址从值的角度来看是一样的,但是意义是不一样的。我们从上述代码就可以看出来。

3.3数组指针的使用

针对数组指针和数组名的了解,我们可以以简单的用数组指针来访问一个二维数组:

cpp 复制代码
#include<stdio.h>

void print(int(*p)[4], int a, int b)
{
	int i = 0;
	for (i = 0; i < a; i++)
	{
		int k = 0;
		for (k = 0; k < b; k++)
		{
			printf("%d ", (*(p + i))[k]);//等同于p[i][j]
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	print(arr, 3, 4);
	return 0;
}

取到地址后,就通过每行+第几列遍历来进行访问数据打印数据,最后打印出这个二维数组。

学过后可以看看下面的代码代表什么意思?

cpp 复制代码
int(*arr[10])[5];

这里是arr这个数组的里面存着的是每个元素的类型是:int( * ) [5]的数组指针类型。arr3里每一个元素存着的就是一个五个int类型元素的数组的地址(也就是指向)。

4、数组参数、指针参数

4.1一维数组传参

cpp 复制代码
void test(int arr[]){}
void test(int arr[10]){}
void test(int *arr){}
void test2(int *arr[20]){}
void test2(int **arr[20]) {};


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

可以通过上述代码中的方式来传入一维数组的参数,其中二级指针是因为在主函数中要传入的是一级指针,所以传入的参数要用二级指针来获取这个一级指针的地址。

4.2二维数组传参

二维数组在传参数的时候也是一样的道理,大差不差:

cpp 复制代码
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

我们要把一个二维数组传入test这个函数中,可以这么做:

cpp 复制代码
void test(int arr[3][5]) {}
void test(int arr[][5]){}

通过传入二维数组,但这里要注意,二维数组行可以省略,但是列不可以省略。

二维数组传参形参的部分可以写成指针吗?以下有几种形式,我们来进行分析:

cpp 复制代码
void test(int *arr) {}

这样不可以,因为数组名表示首元素地址,而这里是一个二维数组,所以这里写成一个整形一维数组的地址肯定不行。

cpp 复制代码
void test(int* arr[5]){}

这里传入的是一个数组但不是二维数组,可以存指针但又不是一个指针,这里传参要么是数组,要么是一个指针,所以这个完全不搭边。

cpp 复制代码
void test(int(*arr)[5]){}

这个是传入的是一个数组指针,也就是传入的是第一行五个整形元素的数组的地址,而刚刚好二维数组一行是五个元素,所以这个可以。

cpp 复制代码
void test(int **arr){}

二级指针是用来接收一级指针的地址的,这里需要传入的是第一行的地址,是一个数组的地址,不能用二级指针来作为参数传入。

4.3一级指针传参

cpp 复制代码
void test(int *p)
{}

这里传入可以直接传入一个一级指针或者整形变量的地址

cpp 复制代码
int a=10;
int *p=&a;
test(&a);
test(p);

这里也可以传入数组,因为传入的是首元素的地址

cpp 复制代码
int arr[10];
test(arr);

4.4二级指针传参

二级指针传参形参的部分就用二级指针来接收就可以。

cpp 复制代码
void test(int **a)
{ }

int main()
{
    int n=10;
    int *p=&n;
    int **pp=&p;
    test(pp);
    test(&p);
    return 0;
}

如果我们发现函数的参数是二级指针,完全就可以传入一个二级指针的变量,也可以传入一个一级指针的地址,也可以传入一个指针数组的数组名。

4.5总结

传过去的是一级指针,就用一级指针来接收就行,传过去的是二级指针,就用二级指针就行。

5、函数指针

函数指针,顾名思义,就是指向函数的指针。

cpp 复制代码
#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pc)(int, int) = &Add;
	printf("%p", pc);
	return 0;
}

这里写出一个加法的简单函数,通过函数指针来接收这个加法函数的地址,之后打印这个函数指针。&函数名和函数名都是函数的地址,所以取不取地址符号都可以。

函数指针的写法和数组指针非常非常的类型:

函数指针:

int ( *pc )( int , int )

函数返回类型+指针变量+(参数类型)

数组指针:

int (*pc)[ 10 ]

数组元素类型+指针变量+数组元素个数(可以不写但要写[ ] )

注意优先级括号问题

我们可不可以通过函数指针来调用函数呢?

cpp 复制代码
( *pc )( 2 , 3);
pc( 2 , 3);

对pc解引用,也就是找到了函数,之后进行传参,这样就可以实现函数的调用。不加解引用也可以,因为pc就相当于Add。用第一行来写是为了方便理解,就是为了摆设,告诉这是一个指针解引用。如果要是写这个*,一定要加上括号。

5.1思考

接下来看两段代码:

cpp 复制代码
( *(void(*)()) 0 ) ();

这段代码出自《C陷阱和缺陷》。将0强制类型转换成一个函数指针,这个函数的参数没有,返回类型为void,这里就是把0地址处的函数调用了一下,调用的时候后面传参,因为函数没有参数,所以直接加个括号无参。

该代码是一次函数的调用,调用0地址处的一个函数。

接下来再看一个:

cpp 复制代码
void (*signal(int,void(*)(int)))(int)

该代码是一次函数的声明,声明的函数名字叫signal,signal函数的参数有两个,一个参数是int,第二个参数是一个函数指针类型,该函数指针指向的参数是int,返回类型是void,signal函数的返回类型是一个函数指针,该函数指针能够指向的那个函数的参数是int,返回类型是void。

可以把这个代码简化一下:

cpp 复制代码
typedef void(*pc)(int)
pc signal(int,pc)

这里说一下,typedef重命名,只有对指针的时候才会把类型名放在 * 旁边,像之前的

cpp 复制代码
typedef unsigned int unit;

就正常写就可以。


总结

本篇文章讲了一些不同类型指针的使用和写法,指针在编程中是非常实用的工具,可以帮助我们更好地管理内存、构建和操作数据结构,提高程序的性能和效率。熟练地使用指针可以让我们编写出更加灵活、高效和可靠的程序。下一篇继续学习。

相关推荐
灵哎惹,凌沃敏4 分钟前
华为C语言编程规范总结
c语言·开发语言
计算机毕设指导618 分钟前
基于Springboot的景区民宿预约系统【附源码】
java·开发语言·spring boot·后端·mysql·spring·intellij idea
pumpkin8451426 分钟前
什么是 LuaJIT?
开发语言
云端 架构师41 分钟前
Lua语言的语法
开发语言·后端·golang
AI向前看1 小时前
Objective-C语言的网络编程
开发语言·后端·golang
梦想的初衷~1 小时前
AI赋能R-Meta分析核心技术:从热点挖掘到高级模型、助力高效科研与论文发表
开发语言·人工智能·r语言
霜雪殇璃1 小时前
c++对结构体的扩充以及类的介绍
开发语言·c++·笔记·学习
编程小筑1 小时前
Clojure语言的并发编程
开发语言·后端·golang
心向阳光的天域1 小时前
黑马跟学.苍穹外卖.Day03
java·开发语言·spring boot
雪芽蓝域zzs1 小时前
JavaWeb开发(九)JSP技术
java·开发语言