C语言笔记(指针进阶)

目录

前言

1.字符指针

2.指针数组

3.数组的指针

3.1.创建数组的指针

3.2.&数组名和数组名        

3.3.指针数组和数组指针的不同

4.数组传参和指针传参

4.1.一维数组传参

4.2.二维数组传参

4.3.一级指针传参

4.4.二级指针传参

5.函数指针

5.1.引入

5.2.&函数名和函数名

6.函数指针数组

7.指向函数指针数组的指针

8.回调函数


前言

        这篇文章是指针的进阶,入门的指针是C语言笔记(指针篇)_c语言指针笔记-优快云博客这篇,如果只是想学习一下,可以看看这个,本文详细的内容请看目录。

1.字符指针

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

字符指针有两种使用方式

1.创建字符指针变量,存放一个字符的地址

2.存放字符串的第一个字符的地址,这种使用方式类似于数组。

int main()
{   
    //使用方式一
    char ch ='w';
    char* pc =&ch;
    //使用方式二
    const char *p = "abcdef";//常量字符串 产生的值就是首元素的地址
    //常量字符串不能被修改 因此需要加上一个const
    printf("%s", p);
    //打印字符串,只要有字符串的起始位置就可以了,不用解引用如果解引用打印的就是a.
    return 0;
}
//*p = 'a';//如果加上这一行,程序不会报错但是会崩溃

        字符串"abcdef"内存中的放置是方式是 在内存中存储的方式是连续的,和数组一样,地址由低到高。

但是整型数组是无法依靠一个%s将数组完全输出,因为数组中没有'\0'和0停止字符。

        指针指向是字符串首个元素的地址,指针指向的只是a,并没有其余的元素哦,可以通过指针+1 来寻找其余的字符。

     例题

#include <stdio.h>
int main()
{
   char str1[] = "hello bit.";
   char str2[] = "hello bit.";
   
   char *str3 = "hello bit.";
   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;
}

2.指针数组

        指针数组是用来存放指针的数组。

int* arr1[10]; //整形指针的数组-存放整形指针变量
char* arr2[4]; //一级字符指针的数组-存放字符指针变量
char* *arr3[5];//二级字符指针的数组-存放字符指针的指针变量
//都是存放指针变量的数组,存放的都是指针
//当去掉数组名和[ ]得出
int* ; 元素是int*  元素是整型指针, 
char* ; 元素是char* 元素是字符指针
char* *;元素是char** 元素是字符的二级指针

指针数组的使用如下

举例1

int main()
{
	//存放字符指针的数组,指针数组
	const char* arr[5] = { "abcdef","未经遗憾寒彻骨","怎得梅花扑鼻香","123456" };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

举例2

#include <stdio.h>
int main()
{
	//使用一维数组和指针数组来模拟二维数组
	//1、创建一维数组
	int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 2,3,4,5 };
	int arr3[4] = { 3,4,5,6 };
	int arr4[4] = { 4,5,6,7 };
	//2、创建指针数组
	int *arr[4] = { arr1,arr2,arr3,arr4 };
    //3、采用循环的方式打印数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			//printf("%d ",(*arr[i]+j) );上述的写法和下述的写法在规则上含义是相同的
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

3.数组的指针

         引入

        字符指针—存放字符地址的指针—指向字符的指针 char*

        整型指针—存放整型地址的指针—指向整型的指针 int*

        浮点型指针—存放浮点型地址的指针—指向浮点型的指针 float*

        数组指针——存放数组地址的指针—指向数组的指针,指向的整个数组,因此数组指针是一个指针。

3.1.创建数组的指针

        温馨提示

        (1)由于操作符[  ]的优先级比 * 的优先级更高,因此要想创建的指针是指向数组的指针,而不是成为数组,就要使用( )将*和指针变量pa先结合。pa与[10]就会成为数组。  

        (2)数组去掉数组名和[ ],剩余代码就是元素的类型,指针去掉指针名字就是指针的类型。

int main()
{
	int a = 10;
	int *pa = &a;

	int arr[10] = { 0 };
	int * pa1[10] = &arr;//会报错的哦
	//等号前面前面的是指针数组,其中存储的类型是int * ,和&arr数组指针不同类型
	
	int (*pa2)[10] = &arr;//数组指针,在去掉pa之后
	//int(*)[10] = &arr;表示类型是数组指针 arr是数组的名字 int[10]是数组的类型 

	return 0;
}

        上述程序会报错的哦。

char arr[5];
char(*pc)[5] = &arr;//数组的指针必须有数据类型和数组元素个数,这就是数组的类型

        数组指针的用法,(所举的例子较为鸡肋)主要是加深印象。

int main()
{
	//创建数组
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//创建数组指针
	int(*pa)[10] = &arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", (*pa)[i]);
	}
	return 0;
}

        数组指针解引用之后就可以得到整个数组了。

        打印二维数组的话,会比较方便一点,这里要先知道二维数组的名字是第一行一位数组的数组指针。

#include <stdio.h>
void Print(int str[3][4])
{
	int i = 0;//行数
	for (i = 0; i < 3; i++)
	{
		int j = 0;//列数
		for (j = 0; j < 4; j++)
		{
			printf("%d ",(*(str+i))[j] );
			//[]的优先级比*优先级高,因此外部再加一个括号提高优先级
			//(*(str + i))[j] 和str[i][j]两者是一样的
		}
		printf("\n");
	}
}

int main()
{
	//创建一个完整的二维数组
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
	//使用函数传址来打印
	Print(arr);
	return 0;
}
3.2.&数组名和数组名        

        结论:&数组名是整个数组的地址,数组名就是首元素的地址。

int main()
{
	int arr[10] = { 0 };
	//数组名字
	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;
}

        上述&arr+1跳过的是整个字符,因此&arr取出的指针,指向的是整个数组。

int arr[5];			//整型数组,存储5个元素
int *parr1[10];		//指针数组,存储指针数据10个
int(*parr2)[10];    //数组指针,parr2是指针变量,指向的是数组int[10] ,有10元素,
int(*parr3[10])[5]; //数组指针的数组,指针变量是parr3[10],指向的是int[5],
//相当于有一个int [10],每一个元素中装一个int[5]的地址,

int(*parr3[10])[5]; 

理解 

(1)先理解是数组指针

        将parr[10]去掉,代码是int(*)[5],元素是数组指针的类型,

(2)指针指向

         int(*)[5],(*)表示变量是时针,括号外面的指针指向 ,指向是int [5]类型的数组,

图解 

3.3.指针数组和数组指针的不同

        指针数组是数组,数组的指针是指针。

        先前条件:这样理解 数组名字是arr 数组的类型是 int[10];

//指针数组
int* arr1[10]; //整形指针的数组-存放整形指针变量
int*[10];//去掉数组名字arr1,那么数组的元素类型是int*的  [10],表示元素个数  

//数组指针
int (*pa2)[10] = &arr;//数组指针,在去掉指针变量的名字pa
//int(*)[10] = &arr;类型是数组指针 arr是数组的名字 int[10]是数组的类型 
// int(*)[10]表示就是指向int[10]数组类型的指针 

//整型指针
int a = 0;
int* pb = &a;//去掉pa
//&a的类型就是int*
//指针指向就是 int类型的数据。

4.数组传参和指针传参

4.1.一维数组传参
#include <stdio.h>
void test(int arr[])//参数是数组可以使用
{}
void test(int arr[10])//创建数组可以使用
{}
void test(int *arr)//参数是int *
{}
void test2(int *arr[20])//指针数组
{}
void test2(int **arr)//参数是二级指针
{}
int main()
{
	int arr[10] = { 0 };
	int *arr2[20] = { 0 };
	test(arr);//数组的名字可以代表整个数组,或者是数组的首个元素的地址 
    //参数可以是数组,或者是int * 
	test2(arr2);//传递的是指针数组,arr2表示的是指向第一个元素(一级指针)的指针
    //参数可以是 指针数组  或者 二级指针 
}
4.2.二维数组传参
void test(int arr[3][5])//可以二维数组
{}
void test(int arr[][])//不可以使用,数组创建的列不可以省略
{}
void test(int arr[][5])//可以,列没有省略
{}
void test(int *arr)//数组名是第一行的地址,是数组指针,int*类型不对
{}
void test(int* arr[5])//指针数组,存放的元素是int * 不可以使用
{}
void test(int(*arr)[5])//指针数组,可以接受第一行的地址
{}
void test(int **arr)//传递的来的是数组指针,不是int形式的二级指针
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);//传递的是二维数组,或者是二维数组的第一行的元素的地址(数组的指针)
    //接受方式:二维数组
}

        注意:数组指针和整型指针是不一样的,数组指针是一种类型的指针,整型指针也是一种类型的指针。

4.3.一级指针传参
#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;
}

当一个函数的参数部分是一级指针的时候,函数可以接受的参数。

void test(int *p)
{
}
int main()
{
	int a = 0;
	int *p = &a;
	int arr[10];
//形式参数是一级指针,可以接受实参
	test(p);
	test(&a);
	test(arr);

	return 0;
}
4.4.二级指针传参

        函数的参数是二级指针,可以接受参数类型。

#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.函数指针

5.1.引入

        数组指针--指向的数组。

        函数指针--指向的是函数。

下述例子中,数组的类型是 int [10]  函数的类型是int(int , int);加上(*)表示就是指针。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//数组指针
	int arr[10];
	int(*pa)[10] = &arr;
	//int(*)[10] = &arr;去掉pa
	
    //函数指针
	int(*pf)(int, int) = &Add;
    //去掉pf
    //int(*)(int, int) = &Add;
    //int表示返回类型,(int ,int)表示参数,int(int, int) 指针指向的类型
    //(*)表示指针,指向函数的指针 ,pf存储的Add的地址
 
	  
	return 0;
}
5.2.&函数名和函数名

结论:&函数名和函数名都是函数的地址,二者所表示的方式是一样的。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf1)(int, int) = &Add;
	int(*pf2)(int, int) = Add;
	printf("%p\n", pf1);
	printf("%p", pf2);
	return 0;
}

使用方式:

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf1)(int, int) = &Add;
	int(*pf2)(int, int) = Add;

	//使用,下述四种方式是一样的,函数名和&函数名一样
	int ret1 = Add(2, 3);
	int ret2 = (&Add)(2,3);
	int ret3 = (*pf1)(2,3);
	int ret4 = pf1(2, 3);

	printf("%d\n", ret1);
	printf("%d\n", ret2);
	printf("%d\n", ret3);
	printf("%d\n", ret4);
	return 0;
}

有趣的例子:

int main()
{
	//代码1
	(*    (   void(*)()  )  0 )();
	// 函数调用,
	//1.调用0地址处的一个函数
	//2.代码将0强制类型转换成为类型void(*)()的函数指针
	//3.再去调用0地址处的函数,*在对其继续进行解引用相当于函数名字,最后面()表示参数是空的
	//
	//代码2
	void(   * signal(    int, void(*)(int)  )   )(int);
	//一次函数的声明,声明函数的名字为 signal
	//signal由两个参数 一个为int类型,一个为函数指针类型void(*)(int)
	//void(*)(int)指向的函数参数类型是int,返回类型是void
	//signal的返回类型是函数指针,函数指针指向的函数的参数是int,返回类型是void
}
//换一种方式写
    typedef void(*pf_t)(int);    //将void(*)(int)重新定义为pf_t
    void(*signal(int, void(*)(int)))(int);
    pf_t signal(int, pf_t);    //pf_t的返回类型,一个参数是 int 一个是pf_t

6.函数指针数组

        数组是一个存放相同类型数据的存储空间,函数指针数组就是存放函数指针的数组。这里的Add函数只是演示的作用,没有实际作用。

引入

int Add(const char* ch)
{
	return 0;
}
int main()
{
	//指针数组
	char* ch[10] = { 0 };
    //char* 表示元素的类型


	//函数指针数组
	int(*pfA[5])(const char*) = { &Add };
	//int arr[10]={0}
	//将元素个数和数组去掉之后,
	//int(*)(const char*)表示元素的类型 

	return 0;
}

         我的理解就是将数组名族和[10]去掉,这样所表露就是数组内存储的元素的类型。

         其实就是C语言定义的书写方式,大家可以根据自己的方式记住的。

举例子加深理解一下:

//这是一个简单的加减乘除的计算器
#include <stdio.h>
void menu()
{
	printf("***************************\n");
	printf("*******1.Add  2.Sub *******\n");
	printf("*******3.Mul  4.Div *******\n");
	printf("*******0.Exit       *******\n");
	printf("***************************\n");
}
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
flag:
	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 = Div(x, y);
				printf("%d\n", ret);
				break;
			}
			case 0:
			{
				printf("退出程序\n");
				break;
			}
			default:
			{
				printf("输入错误,请重新输入\n");
				goto flag;
			}
		}

	} while (input);

	return 0;
}

        switch选择部分的每一个小分支的内容都是相似的;所以使用函数指针数组优化的主函数代码代码:将原来的switch语句换成了if else语句。使用此方式的前提就是函数返回值和参数要是一样的。

//创建函数指针数组
//函数指针数组的用途:转移表
int(*pa[5])(int, int) = { NULL,Add,Sub,Mul,Div };
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
flag:
	do
	{
		menu();
		printf("请输入一个数字\n");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器");
		}
		else if(input>=1&&input<=4)
		{
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			ret = pa[input](x, y);
			printf("%d\n", ret);
		}
		else 
		{
			printf("输入错误,请重新输入\n");
			goto flag;
		}

	} while (input);

	return 0;
}

7.指向函数指针数组的指针

        指向函数函数数组的指针

引入

int Add(const char* ch)
{
	return 0;
}
int main()
{
	//函数指针
	int(*pa[5])(const char*) = { &Add };
	//数组指针
	int arr[10];
	int(*pd)[10] = &arr;
	//int [10]是数据的类型
	//指向函数指针数组的指针
	int(*(*ppf)[5])(const char*) = &pd;
	//int(*[5])(const char*)是数据的类型(*ppf)ppf就是指针了  *表示指针指向的数据类型
	//  ppf如果和[5]相结合就是数组了,所以使用(*)先将ppf转换成指针,向外看看见[5]指向的是数组


    return 0;
}

8.回调函数

        回调函数就是一个通过函数指针调用的函数。

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

      (2)  回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

        调用函数就是使用函数的指针来调用函数的使用方式。

根据上述的计算器的例子来进一步的优化程序。

主函数

int main()
{
	int input = 0;
flag:
	do
	{
		menu();
		printf("请输入一个数字\n");
		scanf("%d", &input);
		switch (input)
		{
					
			case 1:
			{
				calc(Add);
				break;
			}
			case 2:
			{
				calc(Sub);
				break;
			}
			case 3:
			{
				calc(Mul);
				break;
			}
			case 4:
			{
				calc(Div);
				break;
			}
			case 0:
			{
				printf("退出程序\n");
				break;
			}
			default:
			{
				printf("输入错误,请重新输入\n");
				goto flag;
			}
		}

	} while (input);

	return 0;
}

调用函数指针的函数

int calc(int(*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作符\n");
	scanf("%d %d", &x, &y);
	int ret = (*pf)(x, y);
	printf("%d \n", ret);
}

        向上述函数中calc()函数只是一个中介,可以通过函数Add,Mul,Sub,Div的函数指针调用函数,被调用的函数就是回调函数。

        上述看的例子很明显满足(1)中的内容,(2)中的内容:calc函数只有在 case1/2/3/4的条件下才会调用函数,也满足了。

        若有错误,请各位批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值