个人技术分享

1. 字符指针变量

  1. p2 和 per 里面都是存储的首地址,arr是存储的数组首地址,per是第一个字符的地址。
  2. per和p2 通过首字符的地址可以访问到 \0 前的数据
  3. 字符串数组内容是可以更改的,常量字符串内容是不可更改的
int main()
{
    //字符的三种存储方式
    char a1 = 'b';
    char* p1 = &a1;
    printf("%c\n", *p1);
    
    char arr[10] = "abcdef";//字符串数组内容可变
    char* p2 = &arr;
    printf("%s\n", p2);

    char* per = "abcf";//常量字符串,内容不可以变
     printf("%s\n", per);
    return 0;
}

  1. 再来对比一下,int 和char 、char数组
  2. 字符串的打印需要的是地址,类型是char*。而打印int类型是需要int类型的值,char 存储的类型打印是需要 char类型的值
  3. 所以可以发现,存储方式是什么就用什么来打印。存储的地址就用地址打印。存储的是值就用值打印
int main()
{
	int x = 10;
	printf("%d\n", x);// x 类型是int 值是10
    
	int* px = &x;
	printf("%d\n", *px);//整形的打印需要的是值,不是地址
	return 0;
}

1.1. 笔试题

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");//1
    else
        printf("str1 and str2 are not same\n");//2
    if(str3 ==str4)
        printf("str3 and str4 are same\n");//1
    else
        printf("str3 and str4 are not same\n");//2
    return 0;
}
  1. 此题的结果是什么?给个数字来代替,1为真 ;2为假
  2. 这题结果是 2 、1,为什么呢?
    1. 因为str1和str2是新开辟了两个全新的空间来存放的,所以为假
    2. 而第二个为真,因为str3和str4指向的是同一块空间,因为他们两是一个常量字符串,而在此题中if 比较的是地址。指向同一块空间那么地址也就相等。
  3. 再来画个图更加深刻了解一下存储

  1. 补充:静态区的值是可以改的,代码段是不能改的
  2. 调试图:通过调试可以观察到,确实如我所说,存储位置是一样的

2. 数组指针变量

  1. 数组指针变量是什么?
    1. 数组指针,指向数组的指针,和指针数组不一样
    2. 指针数组是存放指针的数组。
  2. 那数组指针存放数组的地址,能够指向数组的指针变量
int main()
{
	int a = 130;
	int* pa = &a;//指向整形的指针

	char sr = 'b';
	char* psr = &sr;//指向字符的指针

	int arr[] = { 1,2,3,4,5 };
	int* parr[] = { arr,pa };//存放指针的数组
    int(*parr)[5] = &arr;//存放数组的指针
    
	//printf("%d\n", *(parr[1]));//第一个数组访问这个地址,这个地址是int* ,解引用访问到该值
	return 0;
}

2.1. 两者区别

  1. arrpint 是指针数组,指向的每个元素类型是 int*,[5] 可以存放五个 ,arr表示首元素地址刚好类型也是int*
  2. parr 是数组指针,指向的每个元素类型是 int ,[5] 指向数组的元素个数,& 取的是整个数组的地址
  3. 此例子中 数组指针的类型是 int (*)[5],去掉名字就是类型。
int main()
{
	int arr[5] = { 1,2,3,4,5 };

	int* arrpint[5] = {arr};//arr 表示数组首元素的地址

	int(*parr)[5] = &arr;// & 的是整个数组的地址
	//上的类型是 int (*)[5] 是数组指针类型
	return 0;
}
  1. 再拿这个例子来说
    1. 对比和 p 这个char* 指针, + 1跳过几个字节
int main()
{
    char str[10] = "baibai";
	char* p = str;
	char(*pstr)[10] = &str;

	printf("p: %p\n", p);
	printf("p + 1: %p\n", p + 1);
	printf("pstr: %p\n", pstr);
	printf("pstr + 1: %p\n", pstr + 1);
    return 0;
}
  1. 从下面调试图就可以发现,p + 1是 跳过一个字节,pstr + 1是跳过整个数组(10个字节),确确实实是取到了整个数组的地址
  2. 注意:指针+- 运算取决于当前的指针指向的类型

3. 二维数组传参的本质

  1. 我们知道,数组名表示首元素地址,一维数组的第一个元素就是首元素
  2. 那么二维数组的首元素地址呢?我们可以把二维数组的首元素地址看成一维数组,什么意思呢?
  3. 二维数组的首元素地址是第一行的地址,这里可以看做是一维数组的首元素
  4. 而第一行的类型是:数组指针类型
  5. 所以这里二维数组的数组名也可以表示数组首元素地址

3.1. 代码演示

  1. int(*p)[5],表示指向的这个数组有五个元素
  2. 而在这个例子中,二维数组的首元素(第一行的地址),就是一个一维数组,然后这个一维数组里面又有5个元素
  3. 代码解释
    1. *(*(p + i) + j),分解一下这个代码;p + i ,p是数组的地址,+i访问到对应行(二维数组的每行)
    2. 然后对其解引用 *(p + i) 就是拿到了一维数组的首元素地址
    3. *(p + i) + j , + j访问到对应元素的地址,再对其解引用,就能拿到对应数组的值了
#include <stdio.h>
void Print(int(*p)[5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	//一维数组
	int arrt[5] = { 1,2,3,4,5 };

	//二维数组
	int arrt2[3][5] = { {1,1,1,1,1},{2,2,2,2,2},{3,3,3,3,3} };
	Print(arrt2, 3, 5);
	return 0;
}
int main()
{
	//一维数组
	int arrt[5] = { 1,2,3,4,5 };
	//二维数组
	int arrt2[3][5] = { {1,1,1,1,1},{2,2,2,2,2},{3,3,3,3,3} };

	int(*arrp)[5] = &arrt;//数组指针

	int x = 10;
	int* px = &x;
	int** ppx = *px;
	return 0;
}

  1. 注意:数组是连续存放的,传了首元素地址就可以访问到后面的元素

3.2. 对比二级指针

  1. 那么可以用二级指针来传参吗?答案是当然不可以
  2. 二级指针使用来接收一级指针的地址的,和接收数组的地址完全是两回事
int main()
{
	//一维数组
	int arrt[5] = { 1,2,3,4,5 };
	//二维数组
	int arrt2[3][5] = { {1,1,1,1,1},{2,2,2,2,2},{3,3,3,3,3} };

	int(*arrp)[5] = &arrt;//数组指针

	int x = 10;
	int* px = &x;
	int** ppx = *px;//二级指针
	return 0;
}
  1. 这里还有一个小对比,如果是传一维数组,可以传首元素的地址,通过首元素访问到后面的值。

4. 函数指针变量

4.1. 函数指针变量的创建

  1. 什么是函数指针?
    1. 可以从前面对比发现,函数指针是函数的地址,但是和数组指针有区别
    2. 数组名表示首元素地址,&数组名表示整个数组的值;他们两的值一样但是属性不一样,体现在指针+-运算的时候
int Add(int x, int y)
{
	return x + y;
}
int main()
{
    //数组指针
	int arr[5] = { 0,1,2,3,4 };
	int(*parr)[5] = &arr;

    //函数的地址
	printf("Add:%p\n", Add);
	printf("&Add:%p\n", &Add);
	return 0;
}

  1. 从调试的图中发现函数是有地址的,而且函数的地址;取地址和不取地址都是:函数的地址

4.1.1. 函数地址的存储

  1. 当然函数地址的存储和数组指针非常相似
  2. 下面函数指针的类型是什么?,去掉名字就是类型;int (*)(int, int)
int Add(int x, int y)
{
	return x + y;
}
char* test(int x, char* d)
{
	;
}
int main()
{
	//数组指针
	int arr[5] = { 0,1,2,3,4 };
	int(*parr)[5] = &arr;

	//函数指针,存放函数的地址
	int (*PAdd)(int, int) = Add;//PAdd用来专门存放Add函数地址的

	char* (*Ptest)(int, char*) = &test;
	return 0;
}

4.2. 函数指针变量的使用

  1. 下面的三种调用函数的方式;通过函数指针来调用函数
    1. 首先是直接调用函数传参
    2. 第二种调用的方式:(*PAdd)(3, 3),可以不用写(*),就算写了(*****PAdd)写很多个 * ,也不影响;这个 * 就是摆设
    3. 可以直接写成第三种的调用函数的方式 PAdd( )
    4. 因为PAdd里面本来就存储着函数的地址,直接调用就行了
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//函数指针,存放函数的地址
	int (*PAdd)(int, int) = Add;//PAdd用来专门存放Add函数地址的
    //第1种
	printf("%d\n", Add(2, 2));
    //2
	printf("%d\n", (*PAdd)(3, 3));
    //3
	printf("%d\n", PAdd(5, 5));
	return 0;
}

4.3. 两段加深理解的代码

4.3.1. 代码1 : (* (void (*)())0 )( ); 这个代码是什么意思呢?

  1. 首先 void (*)() 是一个函数指针类型
  2. (void(*)() ) 0 ,对 0 这个整型强制转换成函数指针类型
  3. 调用 0 这个地址处放着 返回值为void;无参数的函数
  4. 注意:这个 0 地址处 是不能调用的,调用程序会崩掉的。重点是理解这段代码;当然实际写代码里面也是不允许这么写的

4.3.2. 代码2:void (* signal(int , void(*)(int)))(int);

  1. 代码解释:
    1. 上面的代码是一个函数声明,参数部分参数1: int;参数2:void(*)(int),但是还缺一个返回值
    2. 首先为了方便理解先写个错误的给大家看看,主要是理解意思
      1. void (*)(int) signal(int, void(*)(int))//err
    3. 这段代码是错的,但是它的返回值是:void (*)(int),根据语法它不得不写成最开始那样(代码2)
    4. 那么怎么解决呢?用typedef;先讲下typedef,然后再回到代码2的问题

4.4. typedef关键字

  1. typedef是用来类型重命名的,可以将复杂的类型,简单化。
  2. unsigned int == u_int ;两种声明方式一样的,名字会更加简洁易懂
int main()
{
	typedef unsigned int u_int;
	unsigned int a = 10;
	u_int num = 20;

	typedef char* prt_c;
	char* str1 = NULL;
	prt_c str2 = NULL;
	return 0;
}
  1. 数组指针和函数指针略有不同,这之前是数组指针的名字,在typedef里重命名下变成了类型的名字
int main()
{
	//数组指针和指针数组
	typedef int(*parr)[5];//重命名的名字就是parr了
	int(*arr)[5] = NULL;
	parr numarr = NULL;

	typedef int(*pfun)(int, int); //重命名的名字就是pfun了
	int (*fun)(int, int) = NULL;
	pfun fun2 = NULL;
	return 0;
}
  1. 现在可以回到上面那段代码的问题了
    1. void (*)(int) signal(int, void(*)(int)),写成下面这种正确的形式
    2. 直接对这个类型重命名
int main()
{
    typedef void (*void_int)(int);
    
    void_int signal(int, void_int);//最后简化的形式
    return 0;
}
  1. 还有一个问题,拓展一下;
    1. #defind 定义的和typdef定义的有什么区别
    2. #defind是将类型替换,p3 的替换有int*,但是p4被替换成了int;通过调试图来看,也确实如此
#define PTR_T int*
int main()
{
    typedef int* ptr_t;
    ptr_t p1, p2;// p1 p2 都是指针变量
    PTR_T p3, p4;// p3 是指针变量,p4 是int类型的变量
    return 0;
}

5. 函数指针数组

  1. 函数指针数组,就是存放函数指针的,那么书写格式是什么呢
  2. int(*pptr[4])(int, int) = { add,sub,mul,div };其实就和函数差不多,函数指针数组就在名字后面加上[ ],就可以了
  3. 那么当然,去掉名字就是对应函数指针数组的类型;int(*)(int, int)[4]
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 pi[5] = { 0 };
	int(*pint)[5] = &pi;//数组指针

	int(*pptr[4])(int, int) = { add,sub,mul,div };//函数指针数组
	return 0;
}
  1. 使用的方式可以看看调试图,通过循环遍历数组以此访问对应函数。
  2. 那么是不是可以给这些其定义值,通过值来依次访问

6. 转移表

  1. 我们来实现一个简单的加减乘除,来演示一下转换表,体会一下函数指针数组的用途
  2. int(*parr[5])(int, int) = { 0,add,sub,mul,div };//转换表
    1. 上面这行代码就是转换表,当用户输入一个值,第一个加上0 ,后面刚好对应函数指针数组的下标位置
    2. 而这些下标位置都是一个一个的函数指针,前面我们讲过,可以用一个函数名来调用函数还可以通过函数指针来调用
  3. 那么要如何使用这个呢?
    1. int ret = parr[input](x, y);
    2. parr[input],就是对应下标的函数,再传x y参数,调用对应函数,然后把结果返回
  4. 那么可以发现,拿到input的值 --> 找到对应下标 -->函数指针调用 -->返回对应值。这个被传来传去,这个就是转换表
#include <stdio.h>
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;
}
void menu()
{
	printf("*********************\n");
	printf("**** 1.add 2.sub ****\n");
	printf("**** 3.mul 4,div ****\n");
	printf("**** 0.exit      ****\n");
	printf("*********************\n");
}
int main()
{
	int input = 0;
	int x = 0, y = 0;
	int(*parr[5])(int, int) = { 0,add,sub,mul,div };//转换表
	do
	{
		menu();
		printf("请输入你要进行的操作:");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入操作的两值:");
			int err = scanf("%d %d", &x, &y);
			if (err == 0)
			{
				printf("输入格式不正确\n");
				break;
			}
			int ret = parr[input](x, y);//转换表
			printf("%d\n", ret);
		}
		else if (input == 0)
			printf("退出中...\n");
		else
			printf("输入错误,请重新输入\n");
	} while (input);
	return 0;
}

6.1. 方法2:回调函数

待优化的常规版本

#include <stdio.h>
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;
}
void menu()
{
	printf("*********************\n");
	printf("**** 1.add 2.sub ****\n");
	printf("**** 3.mul 4,div ****\n");
	printf("**** 0.exit      ****\n");
	printf("*********************\n");
}
int main()
{
	int input = 0;
	int x = 0, y = 0,ret = 0;
	do
	{
		menu();
		printf("请输入你要进行的操作:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入操作的两值:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入操作的两值:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入操作的两值:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入操作的两值:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出中...\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
		}
	} while (input);
	return 0;
}
  1. 在主调函数内没有调用对应的函数(加减乘除函数),而是通过calc函数传过来的函数指针去调用对应函数这种就被成为回调函数
  2. 函数被当作参数传递到中间函数的时候,让中间函数去调用函数指针。
  3. 当在特定的条件或者发生条件时由另外一方调用的,对于该事件或条件做出响应;也就是这个代码案例
  4. 使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能。
  5. 上面这种容易造成代码冗余,那么就用回调函数来优化一下

优化后的代码

void calc(int(*pr)(int, int))//回调函数
{
	int x = 0, y = 0, ret = 0;
	printf("请输入操作的两值:");
	scanf("%d %d", &x, &y);
	ret = pr(x, y);//通过函数指针调用对应函数
	printf("%d\n", ret);
}
int main()//主调函数
{
	int input = 0;
	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");
		}
	} while (input);
	return 0;
}
  • 当然用图来更加深刻的来理解一下

总结:还要继续持之以恒的坚持和不屑的努力。。。つづく