深入理解指针(5)

一 ,sizeof 和 strlen 的对比

1.1  sizeof 

sizeof 是计算变量所占内存空间大小的,单位是字节只关注占用内存空间的大小,不在乎内存中存放什么数据。

int main()
{
	int a = 0;
	printf("%zd\n", sizeof(a));
	printf("%zd\n", sizeof a);
	printf("%zd\n", sizeof(int));
	return 0;
}

1.2 strlen 

 strlen 是C语言库函数 ,功能是求字符串长度的。统计的是strlen 函数的参数 在首字符的地址开始向后,一直统计到  \0  之前字符串中的个数,strlen 函数会一直向后找 \0 字符,直到找到为止,所以可能存在越界查找

针对字符串,统计的是\0之间的字符个数
#include <string.h>
int main()
{
	//法一
	//int len = strlen("abcdef");
	//printf("%zd\n", len);

	//法二
	char arr[] = "abcdef";
	//a b c d e f /0
	size_t len = strlen(arr);
	printf("%zd\n",len);
	return 0;
}

注意: 使用strlen 库函数时,需要使用头文件 <string,h> ! 仅求字符串!

1.3 sizeof 与 strlen 的对比

二 ,数组和指针笔试题解析

2.1  一维数组

例题1: 

int a[] = {1,2,3,4};
printf("%zd\n",sizeof(a));
printf("%zd\n",sizeof(a+0));
printf("%zd\n",sizeof(*a));
printf("%zd\n",sizeof(a+1));
printf("%zd\n",sizeof(a[1]));
printf("%zd\n",sizeof(&a));
printf("%zd\n",sizeof(*&a));
printf("%zd\n",sizeof(&a+1));
printf("%zd\n",sizeof(&a[0]));
printf("%zd\n",sizeof(&a[0]+1));

 首先,数组名是什么?

数组名是数组首元素的地址,但是有两个类型例外:

1. sizeof (数组名):数组名指的是整个数组,sizeof计算的是整个数组的大小,单位是字节;

2.sizeof(&数组名):表示的是整个数组,取出的是整个数组的地址

  •  printf("%zd\n", sizeof(a));                                         sizeof(数组名) 整个数组的大小 16
  • printf("%zd\n", sizeof(a + 0));                    sizeof(a+0)计算的是首元素地址的大小  4/8 
  • printf("%zd\n", sizeof(*a));           a就是首元素的地址,对首元素解引用,就是变量a  4
  • printf("%zd\n", sizeof(a + 1));                           计算的是第二个元素地址的大小   4/8
  • printf("%zd\n", sizeof(a[1]));                                           第二个元素的大小      4
  • printf("%zd\n", sizeof(&a));                 &a 就是数组的地址,而数组的地址也是地址  4/8
  • printf("%zd\n", sizeof(*&a));                                       计算的就是整个数组的大小     16
  • printf("%zd\n", sizeof(&a + 1));                      &a+1跳过的是整个数组后+1,求地址   4/8
  • printf("%zd\n", sizeof(&a[0]));                                                        首元素的地址    4/8
  • printf("%zd\n", sizeof(&a[0] + 1));                                     第二个元素的地址             4/8

2.2  字符数组 

例题1:

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));

 同上面分析步骤一样!

例题2:

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));

 strlen  求的是字符串的长度,这里的字符只有 a  b  c  d  e  f  ,  没有 \0 ,  就意味着strlen 函数会一直向后找,统计字符串的个数,直到遇到了 \0 为止,我们把 strlen 返回的值称为随机值,这里的随机值指的是我们无法预测的一个值!到最后可能会存在非法访问空间的问题。

char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
//字符数组中,不包含\0,所以就会越界往后找\0 --- 随机值(我们无法预测的一个值)

printf("%d\n", strlen(arr + 0));
//首元素的地址,+0还是首元素的地址,从首元素开始向后统计,随机值

printf("%d\n", strlen(*arr));
//arr 是首元素的地址,*arr是首元素,'a'-97,这里是把97传给strlen ,strlen会将97作为地址,向后统计字符串长度,97作为地址的空间,不一定属于当前的程序

printf("%d\n", strlen(arr[1]));
//arr[1]是第二个元素,'b'--98,98传递strlen,也会非法访问

printf("%d\n", strlen(&arr));
//&arr -- char(*)[6]  ,&arr是数组的地址,也是数组开始位置的编号,传递给strlen后,strlen 依旧是从首元素开始向后数字符数,得到的还是随机值,因为没有\0

printf("%d\n", strlen(&arr + 1));
//随机值

printf("%d\n", strlen(&arr[0] + 1));
//第二个元素的地址,从这里向后数字数,还是随机值,因为没有\0

例题3:

int main()
{
	char arr[] = "abcdef";
	//a b c d e f \0
	printf("%zd\n", sizeof(arr));//7

	printf("%zd\n", sizeof(arr + 0));//  4/8

	printf("%zd\n", sizeof(*arr));// 1

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

	printf("%zd\n", sizeof(&arr));// 4/8

	printf("%zd\n", sizeof(&arr + 1));// 4/8

	printf("%zd\n", sizeof(&arr[0] + 1));// 4/8
	return 0;
}

 例题4:

int main()
{
	char arr[] = "abcdef";
	printf("%zd\n", strlen(arr));
	printf("%zd\n", strlen(arr + 0));
	printf("%zd\n", strlen(*arr));
	printf("%zd\n", strlen(arr[1]));
	printf("%zd\n", strlen(&arr));
	printf("%zd\n", strlen(&arr + 1));
	printf("%zd\n", strlen(&arr[0] + 1));
	return 0;
}

 

int main()
{
	char arr[] = "abcdef";
	//a b c d e f \0
	printf("%zd\n", strlen(arr));//6

	printf("%zd\n", strlen(arr + 0));//6

	printf("%zd\n", strlen(*arr));
	//报错,arr首元素的地址,解引用,字符'a',ASCll 码的值是97,从97为起点向后开始找,可能会导致非法访问

	printf("%zd\n", strlen(arr[1]));
	//报错

	printf("%zd\n", strlen(&arr));
	//6  取的是整个数组的地址,整个数组的地址是首地址

	printf("%zd\n", strlen(&arr + 1));
	//随机值 , &arr+1 跳过整个数组

	printf("%zd\n", strlen(&arr[0] + 1));
	//5  第二个元素的地址

	return 0;
}

例题5: 

 

char* p = "abcdef";
printf("%zd\n", sizeof(p));
//4/8  p是指针变量,计算的是指针变量的大小

printf("%zd\n", sizeof(p + 1));
//4/8   p+1 求的是b 的地址

printf("%zd\n", sizeof(*p));
//1  字符'a'的大小

printf("%zd\n", sizeof(p[0]));
//1 把常量字符串看成数组,p[0]就是第一个元素
							
//p[0] --- *(p+0) -- 'a' -- 1
printf("%zd\n", sizeof(&p));
//4/8  取的是地址  

printf("%zd\n", sizeof(&p + 1));
//4/8

printf("%zd\n", sizeof(&p[0] + 1));
//4/8

例题6:

int main()
{
	char* p = "abcdef";
	printf("%zd\n", strlen(p));//6

	printf("%zd\n", strlen(p + 1));//5

	printf("%zd\n", strlen(*p));//'a'--97,非法访问,报错

	printf("%zd\n", strlen(p[0]));//'a'--97,非法访问,报错

	printf("%zd\n", strlen(&p));//随机值

	printf("%d\n", strlen(&p + 1));//随机值

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

	return 0;
}

 2.3 二维数组

int a[3][4] = {0};
printf("%zd\n",sizeof(a));
printf("%zd\n",sizeof(a[0][0]));
printf("%zd\n",sizeof(a[0]));
printf("%zd\n",sizeof(a[0]+1));
printf("%zd\n",sizeof(*(a[0]+1)));
printf("%zd\n",sizeof(a+1));
printf("%zd\n",sizeof(*(a+1)));
printf("%zd\n",sizeof(&a[0]+1));
printf("%zd\n",sizeof(*(&a[0]+1)));
printf("%zd\n",sizeof(*a));
printf("%zd\n",sizeof(a[3]));

 

首先我们还是得明确,数组名是数组首元素的地址,但是有两个例外,sizeof(数组名)与sizeof(&数组名) ,这里的二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址。我们说,二维数组其实是一维数组的数组,通过这一点我们可以尝试解决以上的问题。对于最后一道题,是不存在越界访问的这一个bug,因为sizeof表达式是不会参与计算的,当表达式写出来的那一刻,它的类型就已经确定了,不会存在真是的访问内存,仅仅通过推导,就可以知道长度了.

int main()
{
	int a[3][4] = { 0 };
	printf("%zd\n", sizeof(a));
	//sizeof(数组名),计算的是整个数组的大小,3*4*sizeof(int) = 48

	printf("%zd\n", sizeof(a[0][0]));
	//计算的是第一个数组元素的大小 -- 4

	printf("%zd\n", sizeof(a[0]));
	//a[0] 表示的是第一行的数组名,计算的是第一行的数组的大小 --- 16

	printf("%zd\n", sizeof(a[0] + 1));
	//数组名表示首元素的地址 -- 第一行第一个元素的地址 -- +1 跳到第二个元素 -- 求的还是地址 4/8

	printf("%zd\n", sizeof(*(a[0] + 1)));
	//第一行第二个元素的大小  -- 4

	printf("%zd\n", sizeof(a + 1));
	//数组名没有单独放在sizeof中,所以a表示首元素的地址 -- 第一行的地址 -- a+1 跳过第一行是第二行的地址 -- 4/8

	printf("%zd\n", sizeof(*(a + 1)));
	//1.a+1是第二行的地址 -- int(*)[4] -- *(a+1) -- 拿到的就是第二行 -- 16
	//2.*(a+1) -- a[1]  sizeof(a[1]) -- 数组单独放在sizeof中,计算的是第二行数组的大小

	printf("%zd\n", sizeof(&a[0] + 1));
	//a[0]是第一行的数组名,&a[0] 取出的是第一行的地址,&a[0]+1就是第二行的地址,求的还是地址 -- 4/8

	printf("%zd\n", sizeof(*(&a[0] + 1)));
	//第二行的地址解引用就是第二行   -- 16

	printf("%zd\n", sizeof(*a));
	//a表示首元素的地址 , 就是第一行的地址 ,*a就是第一行 , 计算的是第一行的大小  --  16
	//*(a) = *(a+0) -- a[0]

	printf("%zd\n", sizeof(a[3]));
	//sizeof里的表达式是不会参与计算的 -- 表达式一旦写出来,它的类型就已经确定了 -- 不存在越界 -- 因为不会真实的访问内存,仅通过推导,就可以知道长度
	
	printf("%zd\n", sizeof(int));
	return 0;
}

三 ,指针运算笔试题解析

 例题1:

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

//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0;
}

 %p打印时,不会把前面的0去掉 

例题3:

#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;
}

 

例题4:

//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
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;
}

 

考察的是指针- 指针到底是什么。 

 例题5:

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

 

 例题6:

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

 

 例题7:

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

 

 

 

 

 

四 ,qsort 函数的模拟实现详解

 在我的《深入理解指针(4)》中,提到了使用冒泡排序的方法来模拟我们的  qsort 函数,来实现可以对任意类型变量进行排序的操作,从qsort 函数的原本形式上来推理出 ,所使用的函数应该怎样设置形参,并且怎样获取变量。

4.1  冒泡排序的限制

限制传参类型,限制比较方法,限制中间变量,如果想要实现对任何类型的数据进行排序,这便是切入点

4.1 实现

int int_cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void Swap( char* buf1, char* buf2, size_t width)
{
	for (int i = 0; i < width; i++)
	{
		int temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base, size_t num, size_t width, int(*cmp)(const void*e1,const void*e2))
{
	//一趟
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			//比较两个数的大小
			//用两个数的地址比较
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

void test1()
{
	int arr[10] = { 3,5,4,8,9,7,6,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//判断大小,然后重新排序
	bubble_sort(arr, sz, sizeof(arr[0]), int_cmp);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	test1();
	return 0;
}

五 ,结构成员访问操作符详解

结构体成员的访问:

结构体变量.成员名

结构体指针->成员名 

 我们可以先创建一个结构体,然后对结构体成员进行访问数据,并且修改数据,但是首先先要注意,之间的传参要采用传址传递

struct S 
{
	int arr[1000];
	int n;
};

void set_stu(struct S t)
{
	t.n = 200;
	t.arr[0] = -1;
	t.arr[1] = -2;
}

void printf_stu(struct S t)
{
	printf("n = %d\n", t.n);
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", t.arr[i]);
	}
}
int main()
{
	struct S s = { {1,2,3,4,5},100 };
	set_stu(s);
	printf_stu(s);
	return 0;
}

我们看以上的代码,并没有达到我们所要的答案.

传址传递:

结构体变量.成员名

struct S 
{
	int arr[1000];
	int n;
};

void set_stu(struct S *t)
{
	(*t).n = 200;
	(*t).arr[0] = -1;
	(*t).arr[1] = -2;
}

void printf_stu(struct S t)
{
	printf("n = %d\n", t.n);
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", t.arr[i]);
	}
}
int main()
{
	struct S s = { {1,2,3,4,5},100 };
	set_stu(&s);
	printf_stu(s);
	return 0;
}

 结构体指针->成员名 

struct S 
{
	int arr[1000];
	int n;
};

void set_stu(struct S *t)
{
	t->n = 200;
	t->arr[0] = -1;
	t->arr[1] = -2;
}

void printf_stu(struct S t)
{
	printf("n = %d\n", t.n);
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", t.arr[i]);
	}
}
int main()
{
	struct S s = { {1,2,3,4,5},100 };
	set_stu(&s);
	printf_stu(s);
	return 0;
}

建议使用传址的形式来实现打印: 

在大型的程序中,会考虑到时间以及空间的运用成本,而指针会是一个好助手! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值