C语言学习记录(十一)——指针进阶

指针的进阶

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

float*  加1,加4个字节
int*    加1,加4个字节
double* 加1,加8个字节
char*   加1,加1个字节

4.指针的运算
  对于void *p类型指针,它是无类型指针,其主要在传参时使用,对于这种类型的指针不能对它进行+-法的操作,此处p+1*p就属于不合法的。

#include<stdio.h>
int main(){
	int a = 10;
	void *p = &a;
	printf("%d\n", p+1);
	printf("%d\n",*p);
	return 0;
}
对于void * 类型的指针,加减操作和解引用操作都是不可以的

一、字符指针

  字符指针char*,一般使用:

int main(){
	char ch = 'w';
	char *pc = &ch;   此时把p就叫做字符指针
	return 0;
}

此处对于另一种指针字符串的形式

int main(){
char *ps = "hellp"; 此时ps还是一种字符指针,
                    指针是用来存放地址的,此处存放的是字符串"hello"在内存当中的地址,存放的是h的地址
                    而对于这个字符串而言,它是用""双引号来定义的,即它将被存放在常量区(只读区)
}

例1:关于数组存放字符串与指针指向字符串的一道题

int main(){
	char str1[] = "hello bit.";//数组中存放的是 h e l l o  b i t . \0
	char str2[] = "hello bit.";
	char *str3 = "hello bit."; //指针存放的是h的地址
	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){//双引号引起来的将存放在只读区
		              //则"hello"在只读区就只有一份
		              //两个指针都会指向这一个区域
		              //即->str3和str4都保存的是h的地址
		printf("str3 and str4 are same\n");//会输出这一句
	}
	else{
		printf("str3 and str4 are not same\n");
	}
}

例2.关于字符指针传参时的一些问题:

//在传参的过程中
void  func1(char *p){ //如果这里是char *p

}
int main(){
	
	//那么在调用func1时,有以下几种传法
	//第一种
	char c = 'a';
	func1(&c); //传过去的是一个字符地址

	//第二种
	char *str3 = "hello bit.";//当我们这样定义了str3
	func1(str3); //这里str3存放的就是h的地址
	func1(&str3);//是不能这样传的,这里取地址,相当于是取了指针的地址,此时在函数的形参部分就要拿二级指针接受(char **p )

	//第三种  传一个数组
	char str1[] = "hello bit";
	func1(str1); //调用func1时,可用数组名作为实参,因为数组名就代表了数组首元素的地址
}

二、指针数组

指针数组是一个存放指针的数组
int main(){
int* arr[5]; //一个长度为5,里面存放的是int*的数组
}

对于以下三种形式
int* arr1[10];  //整形指针的数组
char *arr2[4];  //一级字符指针的数组
char **arr3[5]; //每一个元素是一个二级指针(即一级指针地址)

三、数组指针

数组指针的定义

  数组指针是能够指向数组的指针

int main(){
    int (*p)[5];  一个指针,指向一个数组,数组中每个元素为int类型
}

  对于下面两种形式分别是什么?

int *p1[10];  ->指针数组
int (*p2)[10]; ->数组指针
[]的优先级要高于*号,所以必须加上()来保证p先和*结合
&数组名 与 数组名
int main(){
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	printf("%d\n", arr);     //代表数组首元素的地址
	printf("%d\n", &arr[0]); //代表数组首元素的地址
	printf("%d\n", &arr);    //整个数组的地址
	//以上这三个虽然输出一样,但表示的含义并不完全相同
	所以此处可以通过指针来接受这个数组地址
	int *p = arr; //等号右边是一个数组名,用int *p来保存arr
	int *p2 = &arr[0]; //等号右边是对arr第0个元素取地址,也可以用int *p来保存
	int (*p3)[10] = &arr;//等号的右边是&arr,表示对整个数组取地址,此时这个指针应该指向这个数组,即指向数组的指针应该是一个数组指针
	                     //这里的p3是一个指针,指向一个长度为10的数组,数组内每一个元素为int 型
}

  那么针对上面数组指针,我们提出一个问题,如何通过p3来访问数组当中的元素?

int *p = arr;
对于第1中如何访问,数组中的元素?
这里p代表的数组首元素的地址,则有
for(int i = 0; i<size; i++){
    printf("%d\n",*(p+i));    //此处p+i相当于是地址
                              //对p+i进行解引用即*(p+i)则得到当前地址的内容
                              //这里  *(p+1) 就相当于 p[1] 的意思
                              
int *p2 = &arr[0];   //与第一种情况类似

int (*p3)[10] = &arr; 
                     //此处若进行 p3+1 ,则他代表加了一整个数组,在此处相当于加了40个字节,相当于&arr+1
那么此处如何只加一个元素呢?
printf("%d",*(*p3+1));  此处对p3进行解引用 *p3 就拿到了p3的地址,之后再加一 (*p3+1),此时加1就相当于加4个字节,这样
                        就可以向后走一位的地址,之后在进行解引用*(*p3+1),就拿到新地址中的值
数组指针的使用

  对于二维数组,打印出数组中的每一个元素的方法是:

void Show(int arr[][5], int row, int col){//为了打印这个二维数组中的每一个元素
	for (int i = 0; i < row; i++){
		for (int j = 0; j < col; j++){
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
}
int main(){
	int arr[3][5] = { 0 }; //我们有一个三行五列的二维数组
	Show(arr,3,5);
}

  二维数组是一个特殊的一维数组,即它的每一个元素就是一个一维数组。以这个三行五列的二维数组为例,此处这个特殊的一维数组第一个元素就是第一行的这个一维数组。
  那么二维数组的首元素是一个一维数组,即二维数组的数组名就代表一维数组的地址

voud Show1(int(*p)[5], int row, int col){ //通过一个数组指针来接收
	for (int i = 0; i < row; i++){
		for (int j = 0; j < col; j++){
			//printf("%d ",p[i][j]);
		    //printf("%d ",*(p+i)[j]);
		      printf("%d ",*(*(p+i)+j) );
		}
		printf("\n");
	}
}
int main(){
    int arr[3][5] = {0};
    二维数组的数组名代表一维数组的地址,一维数组的地址就可以使用数组指针来接受
    则此处可以使用这种写法
    int (*p)[5] = arr;   //对于这种写法,我们就可以将打印数组元素的函数修改为Show1()函数
    Show(arr,3,5);   //这里arr传递的是数组名
}

  分析int (*parr[10])[5];

int (*parr[10])[5];    //存放数组指针的数组
对它进行拆开分析
首先分析最内部()优先级最高部分
parr3[10]  是一个长度为10的数组
数组中存放的是 int (*)[5]
意味着,这个数组长度为10,里面的每一个元素指向一个长度为5的数组,数组中存放的是int类型。
它的存放形式可以用以下代码理解
int main(){
    int (*p)[5] = {0}; //数组指针
    int (*parr[10])[5] = {p};
    数组长度为10,里面每一个元素都是一个数组指针
}

数组参数、指针参数

一维数组传参
void test(int arr[]){
}
void test(int arr[10]){
}
void test(int *arr){
}
int main(){
	int arr[10] = { 0 };    //整形数组
	test(arr);  //数组名代表数组首元素的地址
void test2(int *arr[20]){//原生类型
}
void test2(int **arr){ //用二级指针接收指针的地址
}
int main(){
	int *arr2[20] = { 20 }; //数组里面放的int*类型
	test2(arr2); //数组名代表数组首元素的地址
	             //此时数组的首元素是 int*p
	             //  int*p的地址相当于是p的地址,即&p。
}
二维数组传参
void test(int arr[3][5]){}
void test(int arr[][]){× 不能够省略列
}
void test(int arr[][5]){}
void test(int *arr){ × 首元素是一个一维数组的地址,不能拿一个简单的指针来接收
                     此处的一级指针接受的是 一维数组的数组名 或者是 普通的整形变量的地址
}
void test(int* arr[5]){ // × 这是一个指针数组
}
void test(int(*arr)[5]){ √ 这是一个数组指针,指向一个存放5个整形的数组
}
void test(int **arr){ × 二级指针是用来存放一级指针的地址,此时是一个数组,数组的地址是一个数组指针,而并非指针的地址。
}
int main(){
	int arr[3][5] = { 0 };
	test(arr); //传了一个二维数组的数组名
	           //此处首元素代表了一维数组的地址
}
一级指针传参
#include<stdio.h>
void print(int *p, int size){  //int *p 这里传过来的一定是一个int类型的地址
	int i = 0;
	for (i = 0; i < size; i++){
		printf("%d\n", *(p + i));
	}
}
int main(){
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int *p = arr;  //数组名,表示数组首元素的地址,首元素为1,是一个整形,所以这里可以用int*来接收
	int size = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, size);
	return 0;
}

  当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

比如:
void test1(int *p){ //此处p接收的是int类型的地址
                    //*p 表示p是一个指针,传过来的地址是一个int类型的地址
}
viod test2(char *p){ //此处p接收的是char类型的地址
}
二级指针传参

  二级指针传参,传的一定是一级指针的地址

#include<stdio.h>
void test(int **ptr){ //此处 *ptr代表ptr是指针,int**ptr表示传过来的类型为int*, int*表示的就是一个指针,所以这里传过来的就是指针的地址
	printf("num = %d\n", **ptr);
}
int main(){
	int n = 10;    //普通变量是10
	int *p = &n;   //p是一个指针,可以接收n的地址
	int **pp = &p; //pp是一个二级指针,接收p的地址,p本身是一个int*类型的
	test(pp);      //这里的实参pp,与函数中的形参ptr都是int**,都是存放p的地址
	test(&p);
	return 0;
}

  当函数的参数为二级指针时,可以接受什么参数?

void test(char **p){ //p接收的一定是char*类型的
}
int main(){
	char c = 'b';
	char *pc = &c;
	char **ppc = &pc;
	char *arr[10];  //指针数组,长度为10,每个元素是char*的
	test(&pc);
	test(ppc);
	test(arr);
	return 0;
}

函数指针

  指向函数中的指针

#include<stdio.h>
void test(int a){
	printf("hello\n");
}
int main(){
	printf("%d\n", test);
	printf("%d\n", &test);
	void(*pfunc)(int) = test;  //*pfunc是一个指针,指向一个函数(),这个函数的返回值是void类型
	                        //指针要保存地址,函数名就代表函数的地址
	//通过函数的指针来调用这个函数
	(*pfunc)(); //先解引用,再利用()来调用函数
}
(*(void(*)())0)();    
拆分
void(*)() 一个指针,指向一个函数,返回值为void型,是一个函数指针,相当于此处存放的是一个地址
(地址)0,  对0强转成这个地址,相当于,把0变成了一个地址,地址的类型取决于指针的类型
(*(地址)0)(), 然后解引用
对于这串代码的解释为:
      这里调用了一个函数(),前面(*(void(*)())0)相当于这个函数的地址

函数指针数组

int main(){
   void(*arr[2])(int,int);    arr[2]是一个数组,数组有两个元素。数组里面的每一个元素就是void(*)(int,int)
                                       VOID(* P )(int,int)  此处P指向一个函数,这个函数有两个参数,这两个参数分别是int ,int,这个函数的返回值是vooid
}

  用函数指针数组实现一个计算器

这是简单的一个计算器
int add(int a, int b){
	return a + b;
}
int sub(int a, int b){
	return a - b;
}
int mul(int a, int b){
	return a * b;
}
int dev(int a, int b){
	return a / b;
}
void menu(){
	printf("********************\n");
	printf("*****1.add****2.sub*\n");
	printf("*****3.mul****4.dev*\n");
	printf("********************\n");
}
int main(){
	int input = 1;
	int x = 0;
	int y = 0;
	int ret = 0;
	do{
		menu();
		printf("请输入你的操作:\n");
		scanf("%d", &input);
		switch (input){
		case 1:
			printf("请输入两个操作数:\n");
			scanf("%d%d", &x, &y);
			ret = add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:\n");
			scanf("%d%d", &x, &y);
			ret = sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:\n");
			scanf("%d%d", &x, &y);
			ret = mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:\n");
			scanf("%d%d", &x, &y);
			ret = dev(x, y);
			printf("%d\n", ret);
			break;
		default:
			break;
		}
	} while (input);

}

  我们对它进行优化,明确一个概念:函数名代表函数的地址,那么将函数名放到数组当中,数组应该是一个函数指针数组

int add(int a, int b){
   return a + b;
}
int sub(int a, int b){
   return a - b;
}
int mul(int a, int b){
   return a * b;
}
int dev(int a, int b){
   return a / b;
}
void menu(){
   printf("********************\n");
   printf("*****1.add****2.sub*\n");
   printf("*****3.mul****4.dev*\n");
   printf("********************\n");
}
/*
函数名代表函数的地址,那么将函数名放到数组当中,数组应该是一个函数指针数组
*/
int main(){
   //定义一个函数指针
   int(*func[5])(int x,int y) = {NULL, add, sub, mul, dev};
   int input = 1;
   int x = 0;
   int y = 0;
   int ret = 0;
   do
   {
       	menu(); 
   	    printf("请输入你的操作:\n");
   	    scanf("%d", &input);
   	    printf("请输入两个操作数:\n");
   	    scanf("%d%d",&x,&y);
   	    if (input <= 1 && input <= 4){
   	        ret = (*func[input])(x,y);
   	        printf("%d\n",ret);
   	    }
   }while(input);
   	    return 0;

回调函数

  回调函数就是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

练习题:

练习1:对于char *p = "hello bit",双引号引起来的这一段是一个常量字符串,本质是一个常量字符串数组类型,赋给一个指针,相当于把这个数组的首地址赋给指针,即第一个元素h的地址。
练习2:对于以下代码的解释:

int** arr[10]      二级指针数组
int (*arr[10])     指针数组
char *(*arr)[10]   指针数组的指针(char*数组的指针)
char(*)arr[10]    char*的数组

练习3:定义一个函数指针,指向的函数与两个int形参并且返回一个函数指针,返回的指针指向一个有一个int形参且返回int的函数。符合这这段描述的是:int (*(*F))(int, int)(int)
练习4:一个参数为int*,返回值为int的函数指针:int (*fun)(int*)
练习5:一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针
练习6:关于回调函数的补充:
  ①回调函数就是一个通过函数指针调用的函数。
  ②回调函数一般通过函数指针实现
  ③回调函数一般不是函数的实现方调用,而是在特定的场景下,由另外一方调用。
练习7:下面test函数设计正确的是?

char* arr[5] = {"hello", "bit"};
test(arr);
A. void test(char* arr);
B. void test(char** arr);
C. void test(char arr[5]);
D. void test(char* arr[5])
思路:指针的数组传递给子函数变为指针的指针,也就二级指针。即可以写成 char **arr ; char *arr[] ; char * arr[5]

练习8:下面程序的结果:

int main(){
    int aa[2][5] = {10,9,8,7,6,5,4,3,2,1};
    int *ptr1 = (int *)(&aa + 1);
    int *ptr2 = (int *)(*(aa + 1));
    printf("%d,%d", *(ptr -1), *(ptr2 - 1));
    return 0;
}
思路:
    &aa的类型是int(*)[2][5](数组指针),加 1 操作会导致跳转一个 int[2][5] 的长度,直接跑到刚好越界的位置。减 1 以后回到最后一个位置 1 处。
    *(aa + 1) 相当于 a[1],也就是第二行的首地址,则是5位置。减 1 以后由于多维数组空间的连续性,会回到上一行末尾的6处。

练习9:以下四种形式所代表的含义

int a[10]           普通的int型数组
int (*a)[10]        int数组指针
int *a[10]          int的指针数组
int (*a[10])(int)   int(*)(int)函数指针的数组
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值