一 ,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;
}
建议使用传址的形式来实现打印:
在大型的程序中,会考虑到时间以及空间的运用成本,而指针会是一个好助手!