C语言指针进阶(1)

在这里插入图片描述
大家好,今天我们来分享C语言的进阶指针。

目录

1. 字符指针
2. 数组指针
3. 指针数组
4. 数组传参和指针传参
5. 函数指针

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

【看到下面代码】

int main()
{
    
    
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}

我们定义的char型指针pc里存放的是ch的地址,而ch中我们存放的是字符‘w’,所以我们对pc解引用就能够得到*pc=‘w’。

接下来我们看到下面这个例子:

int main()
{
    
    
const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}

在这里插入图片描述
我们这里是将字符串首字母的地址放入到了指针里,我们打印字符串只需要用%s打印就可以打印出来了。

#include <stdio.h>
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;
}

那么这个题我们来猜猜结果是否会是一样呢?
在这里插入图片描述

在这里插入图片描述
我们看到str1和str2两个数组的结果是不一样的,是因为这两个数组申请了两个相对独立的内存区域,所以我们这两个数组是不一样的,而str3和str4里指针,里面存放的是字符串首字母的地址,而由于存放的地址是一样的,所以我们的系统默认为这两个指针是一样的。

2.指针数组
指针数组是一个存放指针的数组

接下来我们用指针数组模拟一个二维数组。

int main()
{
    
    
	int arr1[] = {
    
     1,2,3,4,5 };
	int arr2[] = {
    
     2,3,4,5,6 };
	int arr3[] = {
    
     3,4,5,6,7 };
	        //int*  int*  int*
	//指针数组
	int* arr[] = {
    
     arr1, arr2, arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < 5; j++)
		{
    
    
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}

	return 0;
}

因为我们指针指向数组名的时候指向的是数组首元素的地址,我们这里取到了arr1,arr2和arr3数组的首元素地址就能够打印整个数组的元素,所以我们接下来只要用循环嵌套就可以将三个数组模拟成一个二维数组。
在这里插入图片描述
我们在这里复习下数组名,以便于我们进行接下来的学习:
数组名是数组首元素的地址
但是存在2个例外:

  1. sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
  2. &数组名,这里的数组名表示整个数组,取出的是数组的地址
int main()
{
    
    
	int arr[10];
	printf("%p\n", arr);//int*
	printf("%p\n", arr+1);

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

	printf("%p\n", &arr);//
	printf("%p\n", &arr+1);

	//指针类型决定了指针+1,到底+几个字节

	return 0;
}

在这里插入图片描述

我们发现这里发现第一个第三个第五个的地址都是一样的,所以我们知道对数组名取地址取的是数组首字母的地址,而第二个和第四个的地址是一样的,所以就可以得到我们打印数组元素的方法有多种。最后一个我们是整个数组加1,最后两个的地址之间相差40个字节,所以我们也可以知道它跳过了一个数组。
看到这里我相信大家对数组名有了更深刻的理解。

int main()
{
    
    
	//指针数组
	char* arr[5] = {
    
    "hello bit", "hehe", "penggeC", "bitejiuyeke", "C++"};

	int i = 0;
	for (i = 0; i < 5; i++)
	{
    
    
		printf("%s\n", arr[i]);
	}

	return 0;
}

因为我们知道指针指向的是字符串首字母的地址,所以我们用这个首地址和%s就能够打印出整个字符串,所以我们接下来利用循环就能够打印整个数组。

在这里插入图片描述

3.数组指针
数组指针就是一个指针。

我们分辨下下面哪个是数组指针?

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
int main()
{
    
    
	int arr[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", p[i]);
	}
	return 0}

在这里插入图片描述
看到这个代码块,我们用指针取数组名的地址,实际上就是取数组的地址,那么指针*p指向的就是数组的地址,接下来我们用循环对数组依次打印就可以打印出来数组了。然而这并不是唯一的打印方法,我们学了数组指针就可以用指针来代替数组名。看到如下代码:

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

	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		printf("%d ", (*p)[i]);
	}

在这里插入图片描述
我们以后就可以用这种方法对数组进行打印,但是我们要保证*p指针得用()给它括起来,因为[ ]的优先级要高于(),所以我们用()来保证 *p结合。

void print_arr2(int (*arr)[5], int row, int col)
{
    
    
int i = 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;
}

这个二维数组我们arr我们用数组指针来接受,我们传递的地址是二维数组的首地址即二维数组第一行的地址,也就是一个一维数组的地址。

4.数组参数、指针参数

接下来让我们一维数组的传参:

#include <stdio.h>
1 void test(int arr[])//ok?
{
    
    }
2 void test(int arr[10])//ok?
{
    
    }
3 void test(int *arr)//ok?
{
    
    }
4 void test2(int *arr[20])//ok?
{
    
    }
5 void test2(int **arr)//ok?
{
    
    }
int main()
{
    
    
int arr[10] = {
    
    0};
int *arr2[20] = {
    
    0};
test(arr);
test2(arr2);
}

数组传参,形参是可以写成数组形式的,数组传参的本质是传递了数组首元素的地址,而且数组传参也可以是指针的形式
以上五种传参均正确,看到最后一个我们发现用的是二级指针,那是因为我们二级指针中存放的是一级指针的地址,所以第五个传参也是正确的。

二维数组的传参:

1 void test(int arr[3][5])//ok?
{
    
    }
2 void test(int arr[][])//ok?
{
    
    }
3 void test(int arr[][5])//ok?
{
    
    }
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
4 void test(int *arr)//ok?
{
    
    }
5 void test(int* arr[5])//ok?
{
    
    }
6 void test(int (*arr)[5])//ok?
{
    
    }
7 void test(int **arr)//ok?
{
    
    }
int main()
{
    
    
int arr[3][5] = {
    
    0};
test(arr);
}

我们在进行二维数组传参的时候行可以省略但是列不可以省略,因为我们是二维数组传参所以我们要么就形参使用二维数组的形式,要么就利用数组指针的形式来进行传参,看到第六个我们用的就是数组指针,它存放的是二维数组第一行的地址,所以我们利用数组指针是可以完成传参的,所以只有1,3,6可以传参成功。

一级指针传参:

#include <stdio.h>
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;
}

这里我们只要简单的将形参写成一维数组就行了。那当一个函数的参数部分为一级指针的时候,函数能接收什么参数?下面几种都可以。
在这里插入图片描述
二级指针传参:

#include <stdio.h>
void test(int** ptr)
{
    
    
printf("num = %d\n", **ptr);
}
int main()
{
    
    
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);
return 0;
}

二级指针传参我们用二级指针接受就可以了,那么当函数的参数为二级指针的时候,可以接收什么参数?
在这里插入图片描述
这些都是可以接受的参数。

5. 函数指针

先让我们看到下面这一段代码:

#include <stdio.h>
void test()
{
    
    
printf("hehe\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();
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

保存地址我们要用指针,那么哪个才是指针呢?pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

经过这样的分享,想必大家都应该掌握了所学,今天我们就简单的学习到这里,感谢大家!

猜你喜欢

转载自blog.csdn.net/Lehjy/article/details/132817446