个人笔记2(C语言)

目录

字符指针

指针数组

数组指针        

数组参数、指针参数

函数指针

函数指针数组

指向函数指针数组的指针

回调函数

指针和数组题目解析

指针题


指针基础:

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

字符指针

 p存放的是字符串首字母地址

 常量字符串不可修改,两个字符串又相等,所以p1和p2同时指向“abcdef”

“abcdef”分别初始化了两个数组,arr1和arr2是两块不同的空间,所以arr1和arr2首元素地址不等


指针数组

整形数组 int arr[10]; 存放整形的数组

字符数组 char arr2[5]; 存放字符的数组

指针数组 存放指针的数组

int* arr[10]; 存放整形指针的数组

char* ch[5]; 存放字符指针的数组

指针数组模拟二维数组:

parr[i][j] 还可以写成 *(parr[i]+j)


数组指针

数组指针是 指针

整形指针 - 指向整形的指针,存放整形变量地址的

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

int(*p)[10]; p和*先结合,说明p是一个指针变量,然后指向的是一个大小为10个整形的数组,所以p是一个指针,指向一个数组,叫数组指针。(注:[]的优先级大于*)

数组名该怎么理解呢?通常情况下,我们说的数组名都表示数组首元素的地址,但是有两个例外:

  1. sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址

数组指针如何使用?

 但这样用起来很别扭,所以数组指针不用在一维数组上

用在二维数组上:

void print2(int(*p)[5], int c,int r)
{
	for (int i = 0; i < c; i++)
	{
		for (int j = 0; j < r; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
	printf("\n");
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	//写一个函数打印arr数组的内容
	print2(arr,3,5);
}

*(*(p + i) + j)) 中,p+i是指向第i行的,*(p+i)相当于拿到了第i行,也相当于第i行的数组名,数组名表示首元素的地址,*(p+i)就是第i行第一个元素的地址,也可以写作p[i][j],本质上编译器还是得转换成*(*(p + i) + j))

int(* parr3[10])[5];的意思:parr3是一个数组,数组有10个元素,每个元素的类型是:int(*)[5],parr3是存放指针数组的数组


数组参数、指针参数

一维数组传参

int main()
{
	int arr[10] = { 0 };
	test(arr);
}

形参写成数组的形式:(本质上传的是首元素地址,和行列具体是几无关)

  1. void test(int arr[10]){}
  2. void test(int arr[]){} 形参部分的数组大小可以省略
  3. void test(int arr[100]){} 不建议,但是没错

形参写成指针的形式:

  • void test(int* p){}
int main()
{
    int* arr2[20] = { 0 };
	test2(arr2);
}

形参写成数组的形式:

  1. void test2(int* arr[20]){}
  2. void test2(int* arr[]){}

形参写成指针的形式:(首元素是int*)

  • void test2(int* *p){}

二维数组传参

int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

形参写成数组的形式:

  1. void test(int arr[3][5]){}
  2. void test(int arr[][5]){} 行可以省略,列不能省略

形参写成指针的形式:(二维数组首元素的地址是第一行的地址)

  • ​​​​​void test(int(*p)[5]){}

函数指针

 函数指针 - 指向函数的指针

 两个虽然写法不同,但意义完全相同

int(*pf)(int,int) = Add;

pf就是函数指针变量

例:

 函数指针的用法:

(*pf)里的*其实是没有太大意义的

分析代码1:

( *( void (*)() )0 )()

void(*)() 是函数指针类型

( void(*)() )0 对0进行强制类型转换

首先是把0强制类型转换为一个函数指针类型,这就意味着0地址处放一个返回类型是void,无参的一个函数,然后调用0地址处的这个函数

分析代码2:

void (*signal (int, void(*) (int) ) ) (int)

signal是一个函数的声明,signal函数的参数第一个是int类型的,第二个是 void(*)(int) 的函数指针类型,signal函数的返回值类型也是void(*)(int)

可以这样理解:void(*)(int) signal(int, void(*) (int)),但这样表示是错误的

简化:typedef void(* pf_t)(int); 给函数指针类型void(*)(int)重新起名叫pf_t,pf_t signal(int, pf_t); 


函数指针数组

 

 函数指针数组的用途:转移表

使用转移表可以替代冗长的switch-case语句

例:计算器 不用转移表的代码:

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 div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

使用转移表简化后:

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 div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
	while (input)
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
		}
		else
			printf("输入有误\n");
		printf("ret = %d\n", ret);
	}
	return 0;
}

指向函数指针数组的指针

 解引用找到数组元素:int ret = (*p3)[i](x, y);


回调函数

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

写了一个A函数,没有直接调用它,而是把A函数的地址传给了B函数,在B函数里用一个函数指针接收A函数的地址,在B函数里通过函数指针调用A函数时,A函数被称为回调函数。

使用回调函数简化计算器代码:

qsort是一个库函数,基于快排算法实现的一个排序的函数

 其中的比较函数要求qsort函数的使用者,自定义一个比较函数

  1. 排序的整形数据:用>、<
  2. 排序的结构体数据:可能不方便直接使用>、<比较了,使用者根据实际情况,提供一个函数,实现2个数据的比较

例:使用qsort排序数组

 使用qsort排序结构体:

 按照姓名排序(是按首字母顺序排):

 使用冒泡的方式模拟qsort函数:


指针和数组题目解析

sizeof计算的是对象所占内存大小,单位是字节,返回值类型是size_t;不在乎内存中存放的是什么,只在乎大小;sizeof是一个操作符;sizeof在64位平台上应用%llu打印。

strlen是一个库函数;只能求字符串长度,从给定的地址向后访问字符,统计\0之前出现的字符个数。

例1:一维数组int a[] = {1,2,3,4};

printf("%d\n",sizeof(a));

结果是:4byte*4 = 16

printf("%d\n",sizeof(a+0));

4/8,a+0是数组第一个元素的地址,是地址,大小就是4/8个字节

printf("%d\n",sizeof(*a));

4,a表示数组首元素的地址,*a表示数组的第一个元素,sizeof(*a)就是第一个元素的大小4

printf("%d\n",sizeof(a+1));

4/8,a表示数组首元素的地址,a+1表示数组第二个元素的地址,sizeof(a+1)就是第二个元素的地址的大小

printf("%d\n",sizeof(a[1]));

4,计算的是第二个元素的大小

printf("%d\n",sizeof(&a));

4/8,&a取出的是数组的地址,数组的地址也是地址,是地址,大小就是4/8字节

printf("%d\n",sizeof(*&a));

16,计算的是整个数组大小,相当于sizeof(a)

printf("%d\n",sizeof(&a+1));

4/8,&a是数组的地址,+1跳过整个数组,产生的是4后边位置的地址
 

printf("%d\n",sizeof(&a[0]));

4/8,取出的是数组第一个元素的地址

printf("%d\n",sizeof(&a[0]+1));

4/8,数组第二个元素的地址

例2:字符数组char arr[] = {'a','b','c','d','e','f'};

printf("%d\n", sizeof(arr));

结果是:6

printf("%d\n", sizeof(arr+0));

4/8,arr+0是数组首元素的地址

printf("%d\n", sizeof(*arr));

1,*arr是首元素,首元素是一个字符

printf("%d\n", sizeof(arr[1]));

1,arr[1]是数组的第二个元素,大小是1个字节

printf("%d\n", sizeof(&arr));

4/8,&arr是数组的地址,是地址

printf("%d\n", sizeof(&arr+1));

4/8,是从数组的地址开始向后跳过了整个数组产生的一个地址

printf("%d\n", sizeof(&arr[0]+1));

4/8,&arr[0]+1是数组第二个元素的地址

printf("%d\n", strlen(arr));

>=6的随机值,arr数组中没有\0,所以strlen函数会继续往后找\0,统计\0之前出现的字符个数

printf("%d\n", strlen(arr+0));

随机值,arr+0还是数组首元素地址

printf("%d\n", strlen(*arr));

error,arr是数组首元素的地址,*arr是数组的首元素,‘a’->97,strlen会把97当做地址,从97开始向后数字符

printf("%d\n", strlen(arr[1]));

error,相当于传进‘b’

printf("%d\n", strlen(&arr));

随机值,也是从首元素开始数

printf("%d\n", strlen(&arr+1));

随机值,跳过一个数组开始数

printf("%d\n", strlen(&arr[0]+1));

随机值,从‘b’开始数

        

例3:字符数组char arr[] = "abcdef";

printf("%d\n", sizeof(arr));

7,f后有\0

printf("%d\n", sizeof(arr+0));

4/8,arr+0是数组首元素地址

printf("%d\n", sizeof(*arr));

1,*arr是数组的首元素

printf("%d\n", sizeof(arr[1]));

1,arr[1]是数组的第二个元素

printf("%d\n", sizeof(&arr));

4/8,&arr是数组的地址,是地址

printf("%d\n", sizeof(&arr+1));

4/8,&arr+1是\0后边的地址

printf("%d\n", sizeof(&arr[0]+1));

4/8,&arr[0]+1是数组第二个元素的地址

printf("%d\n", strlen(arr));

6,数的是\0之前的个数

printf("%d\n", strlen(arr+0));

6,也是首元素地址

printf("%d\n", strlen(*arr));

error,‘a’非法访问

printf("%d\n", strlen(arr[1]));

error

printf("%d\n", strlen(&arr));

6,也是从首元素开始数

printf("%d\n", strlen(&arr+1));

随机值,跳过了\0向后数

printf("%d\n", strlen(&arr[0]+1));

5,从‘b’开始数

        
例4:char *p = "abcdef"; p存放的是首元素地址

printf("%d\n", sizeof(p));

4/8,p是指针变量,计算的是指针变量的大小

printf("%d\n", sizeof(p+1));

4/8,p+1是‘b’的地址

printf("%d\n", sizeof(*p));

1,*p就是‘a’

printf("%d\n", sizeof(p[0]));

1,p[0] -> *(p+0) -> *p

printf("%d\n", sizeof(&p));

4/8,&p是指针变量p在内存中的地址

printf("%d\n", sizeof(&p+1));

 4/8,&p+1是跳过p,p最后的地址

printf("%d\n", sizeof(&p[0]+1));

4/8,&p[0]是a的地址,&p[0]+1就是b的地址

printf("%d\n", strlen(p));

6,从a开始数字符

printf("%d\n", strlen(p+1));

5,从b的位置开始向后数

printf("%d\n", strlen(*p));

error,‘a’非法访问

printf("%d\n", strlen(p[0]));

error,p[0]=*p

printf("%d\n", strlen(&p));

随机值,和字符串没关系,数的是p的地址

printf("%d\n", strlen(&p+1));

随机值

printf("%d\n", strlen(&p[0]+1));

5,从b的位置开始向后数字符,p[0]=*p,&p[0] = ‘a’

例5:二维数组int a[3][4] = {0};

可以把二维数组想象成一维数组,二维数组的每一行是一个一维数组的一个元素

printf("%d\n",sizeof(a));

48,计算的是整个数组的大小,单位是字节,3*4*4byte

printf("%d\n",sizeof(a[0][0]));

4,第一行第一个元素的大小

printf("%d\n",sizeof(a[0]));

16,a[0]是第一行的数组名,sizeof(a[0])就是第一行的数组名单独放在sizeof内部,计算的是第一行的大小

printf("%d\n",sizeof(a[0]+1));

4/8,a[0]没有单独放在sizeof内部,也没有被取地址,表示的是第一行第一个元素的地址,+1表示的是第二个元素的地址

printf("%d\n",sizeof(*(a[0]+1)));

4,表示的是第一行第二个元素

printf("%d\n",sizeof(a+1));

4/8,a表示首元素的地址,就是第一行的地址,所以a+1就是第二行的地址

printf("%d\n",sizeof(*(a+1)));

16,对第二行的地址解引用访问到的就是第二行,也可以理解为*(a+1) -> a[1]

printf("%d\n",sizeof(&a[0]+1));

4/8,a[0]是第一行的数组名,&a[0]取出的就是第一行的地址,&a[0]+1就是第二行的地址

printf("%d\n",sizeof(*(&a[0]+1)));

 16,对第二行的地址解引用访问到的就是第二行

printf("%d\n",sizeof(*a));

16,a就是首元素地址,就是第一行的地址,*a就是第一行

printf("%d\n",sizeof(a[3]));

16,类型是int[4],并没有访问该地址

 总结:arr[i]只是形式上这么写(为了方便初学者理解),本质上是*(arr+i) 


指针题

例1:程序的结果是什么?

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0;
}

结果为:2,5

例2:假设p的值为0x100000。 如下表表达式的值分别为多少(x86环境)?(已知,结构体Test类型的变量大小是20个字节)

struct Test
{
    int Num;
    char *pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p;
int main()
{
    p=(struct Test*)0x100000;
    printf("%p\n", p + 0x1);
    printf("%p\n", (unsigned long)p + 0x1);
    printf("%p\n", (unsigned int*)p + 0x1);
    return 0;
}

结果为:00100014;00100001;00100004

p是结构体指针,+1加的是1*sizeof(结构体)大小;p被强制类型转换成unsigned long,变成一个整形,整形+1加的就是一个字节;p被强制类型转换成unsigned int*,整形指针+1就是+4

例3:假设是小端存储

int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);
    int *ptr2 = (int *)((int)a + 1);
    printf( "%x,%x", ptr1[-1], *ptr2);
    return 0;
}

结果为:4,2000000

 例4:

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
    return 0;
}

结果为:1

注意逗号表达式,所以该二维数组只初始化为 int a[3][2] = { 1,3,5 }; 实际情况是 int a[3][2] = {{1,3},{5,0},{0,0}};

例5:

int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}

结果为:FF FF FF FC(打印的是-4的补码),-4

指针-指针得到的是指针之间元素的个数

 例6:

int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int *ptr1 = (int *)(&aa + 1);
    int *ptr2 = (int *)(*(aa + 1));
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

结果为:10,5

 例7:

#include <stdio.h>
int main()
{
    char *a[] = {"work","at","alibaba"};
    char**pa = a;
    pa++;
    printf("%s\n", *pa);
    return 0;
}

结果为:at

 例8:

int main()
{
    char *c[] = {"ENTER","NEW","POINT","FIRST"};
    char**cp[] = {c+3,c+2,c+1,c};
    char***cpp = cp;
    printf("%s\n", **++cpp);
    printf("%s\n", *-- *++cpp+3);
    printf("%s\n", *cpp[-2]+3);
    printf("%s\n", cpp[-1][-1]+1);
    return 0;
}

结果为:POINT;ER;ST;EW

 

 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安慕蜥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值