【c语言】指针进阶(1)

目录

一、字符指针

二、数组指针

三、数组参数、指针参数

1.一维数组

2.二维数组

3.一级指针传参

4.二级指针传参

四、函数指针


指针的概念:

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

4.指针的运算,(指针-指针)的绝对值得到的是指针和指针之间的元素个数。

一、字符指针

字符指针除了赋值变量外,还可以赋值常量。

int main()
{
	char* p = "hello";
	printf("%s\n", p);
	printf("%c", *p);
	return 0;
}

这里的char* p与const char* p意思一样,"hello"这个常量也是在内存中开辟了一块空间,是不可改的。p里面放的也是"hello"的首字符的地址。

做道题来看一下字符指针、数组和常量字符之间的关系

int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	return 0;
} 

可以观察到这里str3和str4指向的是一个同一个常量字符串。C会把常量字符串存储到单独的一个内存区域。当几个指针,指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候,数组就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

二、数组指针

先来看看指针数组和数组指针的区别

int* p1[10];//指针数组
int (*p2)[10];//数组指针

因为[]的优先级比*高,p1是先和[10]结合说明它是一个数组有10个元素,元素类型是int*;p2是先和*结合说明p2是一个指针,然后指向10个整型的数组(int[10])。所以p1是指针数组,p2是数组指针。

存放一维数组:

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;

p存放的是数组的地址&arr,不能赋值arr,arr是首元素地址类型是int,&arr是数组地址类型是int[10]40个字节。

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

一般不会这样写,数组指针通常用来传二维数组:

void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
			printf("\n");
	}
}
void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr1(arr, 3, 5);
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收
	print_arr2(arr, 3, 5);
	return 0;
}

三、数组参数、指针参数

接下来我们来看几段代码,看看是否可以传参,什么原因

1.一维数组

void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int* arr)//ok?
{}
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

都是可也传参的。

test:arr传递的是数组首元素的地址,地址需要一个指针来接收。int arr[]是个数组,但它做参数时就是个指针,只是方便给初学者阅读可以写成数组;int arr[10]也同样它是个指针[]里面的值可以随便写;int* arr是个指针自然可以。

test2:arr2它的元素类型是int*,传递的地址是int*类型。int* arr[20]和arr2的类型相同是可以传参的;int** arr他的类型是int*也可以接受arr2。

2.二维数组

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
void test(int* arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok?
{}
void test(int** arr)//ok?
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

二维数组的传参和创建是一样的,只能省略第一个[]的值,因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。前三个参数都为二维数组只有第二个参数它没有给第二个[]值,所以它不能传参。

二维数组的数组名传递的参数是第一行的地址,arr的类型是int[5]。int* arr的类型是int,不可传参;int* arr[5]是个指针数组,类型是int*,不可传参;int(*arr)[5]是数组指针,类型是int[5],可以传参;int** arr是二级指针,类型是int*,不可传参。

3.一级指针传参

void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

根据上面我们可以总结,形参只需要和实参类型相同就可以传参。所以一级指针传参用一级指针传参就可以了。

这时我们就可以反推一下,形参是一级指针时可以传什么实参

void test(int* p)
{
}

实参类型也要和形参形同

int main()
{
	int a = 10;
	int* px = &a;
	int arr[10] = { 0 };
	test(arr);//整形一维数组的数组名
	test(&a);//整形变量的地址
	test(px);//整形指针
	return 0;
}

4.二级指针传参

void test(char** p)
{ }
int main()
{
	char c = 'b';
	char* pc = &c;
	char** ppc = &pc;
	char* arr[10];
	test(&pc);
	test(ppc);
	test(arr);//Ok?
	return 0;
}

&pc传的是一级指针的地址,类型是int*,可以传参;ppc本身就是二级指针,可以传参;char* arr[10]是指针数组,元素类型是char*,可以传参。

四、函数指针

函数指针就是指向函数的指针,自然会使用到函数的地址,我们可以先打印出来看一下

void test()
{
	printf("hello\n");
}
int main()
{
	printf("%p\n", &test);
	printf("%p\n", test);
	return 0;
}

这里函数名也是函数的地址

那函数的地址又如何保存呢

首先创建个这个指针要和函数的类型是一样的:

void test()
{
    printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

和数组指针一样要看优先级,()函数调用是比*的优先级高的。pfun1先和*结合表示它是个指针void()是它的类型,pfun2先和()结合说明它是函数,类型就是void*()。所以函数指针的写法是pfun1。

再来看一段代码:

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int x = 10;
	int y = 5;
	int (*p)(int, int)=Add;
	int ret = p(x, y);
	printf("%d", ret);
	return 0;
}

可以发现函数指针它的参数只需要写类型就可以,而在使用p时可以不写*,在上面可以看到Add本身就是地址,所以p写不写*都可以。在p前也可以写多个*和p的意思是一样的。

阅读两端有意思的代码:

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

代码1:先从内向外看,void (*)()可以在里面个p,void (*p)(),这就可以看出它是个函数指针,而void (*)()是p的类型。这个类型加()放在0前面,是强制类型转换,这时0是函数指针,*0和0在前面说过意思是一样的。最后的()和void (*)()类型对应没有参数。

代码2:可以看出里还有一个函数指针signal,它的类型是int和void(*)(int)两个参数,函数指针的类型也可以作为参数出现,现在就剩下它的返回类型。将signal和它的类型拿出来剩下的是void(*)(int)函数指针类型,它的返回值就是void(*)(int)。

代码2很绕,我们来总结一下:这个代码是一次函数声明,声明的是signal,它的参数有两个,第一个是int,第二个是void(*)(int)该类型的参数是int,返回值是void。signal的返回类型也是void(*)(int)。

这里有人会想能不能这样写void(*)(int) signal(int,void(*)(int),这样c规则是不允许的。我们可以这样来:

typedef void(*pfun_t)(int);//这里pfun_t是必须放在*后的
pfun_t signal(int, pfun_t);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值