废话不多说我们之间开始
如果有错误请大佬指正,谢谢
在讲内存和地址之前,我们先举个例子
假设你有一把酒店某一房间的钥匙,并且房间没有编号,所以你需要一个一个试一下,在几百个,这样效率太低了。如果给房间编上号,便可根据钥匙找到。
我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,内存的相关知识我们在数组中讲过,就不在赘述了,那这些内存空间如何高效的管理呢?
其实也是把内存划分为一个个的内存单元,每个内存单元的大小取一个字节。
一个bit位可以存储一个2进制的位1或者0
如何理解编址
1.内存被划分为一个个的单元,一个内存单元的大小是1个字节
2.每个内存单元的都给一个编号,这个编号就是地址,C语言中把地址又称为:指针
编号==地址==指针
···
1.创建一个变量a,并赋值为10
2.在内存上申请4个字节的空间,存放10
#include
int main()
{
int a = 10;
printf("%p\n", &a);
return 0;
}
&为取地址操作符,按位与需要两个操作数
上述地址以16进制展示
#include
int main()
{
int a = 0x11223344;
printf("%p\n", &a);
return 0;
}
#include
int main()
{
int a = 0x11223344;
/*printf("%p\n", &a);*/
int* pa = &a;//pa是指针变量-存放地址-地址又被称为指针,指针变量是用来存放指针的。
return 0;
}
int *是pa的类型
这3种写法没有区别
指针变量的大小
那么指针类型的意义是什么呢?
//代码1
#include
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
}
//代码2
#include
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
return 0;
}
指针+-整数
#include
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc + 1);
printf("%p\n", pi);
printf("%p\n", pi + 1);
return 0;
}
我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。
void* 指针在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进 ⾏指针的+-整数和解引⽤的运算。
const修饰变量
#include
int main()
{
int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的
return 0;
}
此时n不能被修改,但是我们倘若取到n的地址便就能够修改n的值了。
指针运算,assert断言和传址调用 不做讲解
看到这里了必须加油
我们之间概括一下这个:
数组名是首元素的地址,但是有两个例外
sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地址。
在这里就不写代码解释了,就是使用void*类型的指针兼容任何数据,使用其他类型的指针得选择相应的数据,否则会报警告。
这个应该是写过好多次了,所以直接上手写他
void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;//假设这⼀趟已经有序了
int j = 0;
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
flag = 0;//发⽣交换就说明,⽆序
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
if (flag == 1)//这⼀趟没交换就说明已经有序,后续⽆序排序了
break;
}
}
int main()
{
int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
⾸先从⼀个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把函数传给⼀个函数后,函数内部求数组的元素个数吗?
我们发现在函数内部是没有正确获得数组的元素个数。 数组名是数组⾸元素的地址;那么在数组传参 的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组⾸元素的地址。所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
*ppa 通过对ppa中的地址进⾏解引⽤,这样找到的是 pa , *ppa 其实访问的就是 pa .int b = 20 ;*ppa = &b; // 等价于 pa = &b;
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a .**ppa = 30 ;// 等价于 *pa = 30;// 等价于 a = 30;
指针数组是存放指针的数组。
指针数组的每个元素是地址,⼜可以指向⼀块区域。
这里类似于我们之后在讲解二维数组传参本质时的前一种用法,我们在这里写一下等到了那里就不写了哈。
#include
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
int* parr[3] = { arr1, arr2, arr3 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数组中的元素。 上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。
在这里可能我们的解释没有太详细,因为后面会讲到。
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
上述代码是一般使用支付指针变量时的用法
我们继续看下面的代码:
#include
int main()
{
const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
printf("%s\n", pstr);
return 0;
}
#include
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");
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;
}
这里附上详细解释:
这⾥str3和str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域, 当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始 化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
顾名思义数组指针变量是一种指针变量
整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
数组指针变量怎么初始化:
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,实参是⼆维数组,形参也写成⼆维数组的形式。⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀ 维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。
#include
void test(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
test(arr, 3, 5);
return 0;
}
//总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式
函数指针创建的举例:
函数指针类型解析:
//函数指针变量怎样使用呢?
//通过函数指针调用指针指向的函数
#include
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf)(int, int) = Add;
printf("%d\n", (*pf)(5, 8));
printf("%d\n", pf(9, 10));
return 0;
}
上图涉及的知识点有:
1.认识函数指针类型
2.知道强制类型转换
3。通过函数指针调用函数的方式
5分之3狠狠拿下
#include
//实现一个计算器:能够计算整数的加法减法乘法除法
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");
}
int main()
{
int input=0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请输入你的选择:");
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");
break;
}
} while (input);
return 0;
}
此时代码虽然运行并且结果正确,但却有自己的局限性
在这里如果我们想要实现& | ^ << >> && || 我们可以看到写到菜单中和switch语句是很繁琐的,还有switch中的每一个选择中每次函数调用都会有重复的代码
如何解决呢?没错,此时就要用到我们的函数指针数组的方式
此时注意,函数指针数组的元素与菜单并不对应,因此改为这样。
#include
//实现一个计算器:能够计算整数的加法减法乘法除法
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");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
//函数指针数组的方式解决
int(*pf[])(int, int) = { NULL,Add,Sub,Mul,Div };
printf("请输入你的选择:");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret=pf[input](x, y);
printf("%d\n", ret);
break;
}
else
{
printf("选择错误,请重新选择\n");
}
} while (input);
return 0;
}
运行一下我们看结果
这样写的好处是以后可以随意增加别的函数
上述代码的函数指针数组我们称为转移表
回调函数是什么?回调函数就是⼀个通过函数指针调⽤的函数。如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数 时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条 件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。
对于上面那个繁琐的计算机实现,就是那个不用函数指针数组的,我们怎样更改呢?我们直接上正确代码:
#include
//实现一个计算器:能够计算整数的加法减法乘法除法
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");
}
void calc(int(*pf)(int, int))
{
int ret = 0;
int x, y;
printf("请输入操作数:");
scanf("%d%d", &x, &y);
ret = pf(x, y);
printf("ret=%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
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");
break;
}
} while (input);
return 0;
}
我们在这里
#include
int cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
int arr[] = { 1,9,0,2,3,6,4,7,5,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
qsort(arr, sz, sizeof(int), cmp);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
2个结构体的数据进行比较时,不能直接使用>=<来比较
可以按照结构体的某种类型的数据比较
比较姓名时需要用到strcmp函数,具体详情如下:
#include
#include
#include
struct Student
{
char name[50];//名字
int age;//年龄
};
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((struct Student*)e1)->name, ((struct Student*)e2)->name);
//return strcmp((*(struct Student*)e1).name, (*(struct Student*)e2).name); 这样写也可以
}
void test1()
{
struct Student s1[] = { {"yangzihao",18},{"mayun",40},{"liuqiangdong",45} };
int sz = sizeof(s1) / sizeof(s1[0]);
qsort(s1, sz,sizeof(s1[0]),cmp_name);
}
int main()
{
//因为结构体有很多元素,所以我们写成封装函数形式
test1();
//test2();
}
接下来我们实现排序结构体年龄,这个比上述排序姓名要简单一些。
#include
#include
#include
struct Student
{
char name[50];//名字
int age;//年龄
};
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((struct Student*)e1)->name, ((struct Student*)e2)->name);
//return strcmp((*(struct Student*)e1).name, (*(struct Student*)e2).name); 这样写也可以
}
int cmp_age(const void* e1, const void* e2)
{
return ((struct Student*)e1)->age - ((struct Student*)e2)->age;
}
void test1()
{
struct Student s1[] = { {"yangzihao",18},{"mayun",40},{"liuqiangdong",45} };
int sz = sizeof(s1) / sizeof(s1[0]);
qsort(s1, sz,sizeof(s1[0]),cmp_name);
}
void test2()
{
struct Student s1[] = { {"yangzihao",18},{"mayun",40},{"liuqiangdong",45} };
int sz = sizeof(s1) / sizeof(s1[0]);
qsort(s1, sz, sizeof(s1[0]), cmp_age);
}
int main()
{
//因为结构体有很多元素,所以我们写成封装函数形式
//test1();
test2();
}
我们在模拟实现之前先来复习一下冒泡排序:
#include
void bubble_sort(int arr[], int sz)
{
int i = 0;
//趟
for (i = -0; i < sz - 1; i++)
{
//每一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz-1-i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i
但是此时程序中的bubble_sort函数中的参数已经设计死了
为了方便我们接下来写代码,我们把此时主函数中的代码封装到test(1)中。
qosort是一个库函数,可以直接使用
qsort的实现是使用快速排序算法来排序的
此时要想把cmp_int传给qsort函数时,要关注上述图4中的函数的参数是什么样的,否则不能传给qsort
#include
#include
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i
接下来我们就开始模拟实现qsort函数
改造函数的前提还是使用冒泡排序
首先要知道躺数和每一趟对数比较是不会改变的,就是和之前冒泡排序一样
我们先看看之前的冒泡排序实现中哪些需要改变:
为了能够接收任意可能得指针类型,设计为void*
void类型的指针就像一个垃圾桶一样,谁的地址都能接收。
base是一个无具体类型的指针,他只知道他指向的一个数据从这里开始,但是他不知道他要排序的有几个元素。冒泡排序必须要知道元素个数,因为我们模拟qsort函数是从冒泡思想出发的,因此我们要传上要排序的元素个数,又因为元素个数不能是负数,因此元素个数的类型我们定义为size_t
size_t---------无符号整型
知道了元素的个数,还不知道元素是什么类型的,因此需要传参元素类型的大小
不同类型排序的比较不同,因此在参数中,我们需要用一个函数指针来指向另一个函数,让这个函数来实现不同类型排序的比较 在这里不知道是什么类型的指针,所以函数指针的写法为
int (*cmp)(void*p1,void*p2)
这个函数指针指向的函数只是比较p1,p2指向的元素,不会修改p1 p2指向的内容
因此加上const进行限制 int (*cmp)(const void*p1,const void*p2)
到这里我们讲解了函数的所用的4个参数。
void shuai(void* base,size_t sz,size_t width,int (*cmp)(const void*p1,const void*p2))
{
int i = 0;
//趟
for (i = -0; i < sz - 1; i++)
{
//每一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz-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);
}
}
}
}
那如何把这两个地址传给cmp呢?
用一个函数Swap 元素比较过程中,只告诉两个元素的起始地址也不够,元素的类型不知道,但是知道元素的宽度
因此此时元素交换有3个参数
我们不知道一个元素大小,因为我们知道宽度,所以我们一个字节一个字节交换。
因此此时Swap函数的具体实现为:
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
OK,接下来我们发一个整体代码
#include
#include
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void shuai(void* base,size_t sz,size_t width,int (*cmp)(const void*p1,const void*p2))
{
int i = 0;
//趟
for (i = -0; i < sz - 1; i++)
{
//每一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz-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 print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i
此时还是进行整数的排序,接下来我们将进行实现结构体的排序
至于为什么将地址转化为char*,其实很简单,因为不知道元素的大小,所以一个字节一个字节进行比较,进而进行交换。
结构体和数组的初始化都用{ }
此时结构体就相当于一个数据类型,这个在之前的博客中我们提到过
结构体之间的比较不能直接用大于或小于号比较
废话少说,上代码。
#include
#include
#include
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void shuai(void* base,size_t sz,size_t width,int (*cmp)(const void*p1,const void*p2))
{
int i = 0;
//趟
for (i = -0; i < sz - 1; i++)
{
//每一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz-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);
}
}
}
}
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
//p1 p2指向的是两个结构体对象的
strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test4()
{
struct Stu arr[] = { {"zhangsan",18},{"lisi",35},{"wangwu",15} };
int sz = sizeof(arr) / sizeof(arr[0]);
shuai(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
//打印arr数组的内容
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
}
int main()
{
//test1();
//test2();
/*test3();*/
test4();
return 0;
}
那次是是按名字来排序的,那能不能按照年龄来排序呢?
#include
#include
#include
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void shuai(void* base,size_t sz,size_t width,int (*cmp)(const void*p1,const void*p2))
{
int i = 0;
//趟
for (i = -0; i < sz - 1; i++)
{
//每一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz-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);
}
}
}
}
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
//p1 p2指向的是两个结构体对象的
return ((struct Stu*)p1)->age-((struct Stu*)p2)->age;
}
void test4()
{
struct Stu arr[] = { {"zhangsan",18},{"lisi",35},{"wangwu",15} };
int sz = sizeof(arr) / sizeof(arr[0]);
//shuai(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
shuai(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
//打印arr数组的内容
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
}
int main()
{
//test1();
//test2();
/*test3();*/
test4();
return 0;
}
此时调试我们得到width的值为24
整型指针+1跳过4个字节
要跳过结构体的大小,最好将其转换为char*
假设一个元素是7个字节,那么该如何做呢,此时只能是一个字节一个字节的来
拥有这个函数指针,我们把不同数据比较的函数抽离了出来,当我们排序的时候,两个元素比较的方法一定会有差异。 因此我们得出结论,如果脱离了函数指针,那么这个函数指针就无法实现了。
快看完了老铁
sizeof
sizeof,sizeof计算变量所占内存空间大小的,单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。
sizeof只关注占用内存空间的大小,不在乎内存中放什么数据。
#include
int main()
{
int a = 10;
printf("%zd\n", sizeof(a));
printf("%zd\n", sizeof(int));
//printf("%zd\n", sizeof int);error错误
printf("%zd\n", sizeof a);
a所占内存空间的大小是4个字节,用int所创建的变量的大小就是4个字节,int类型的长度是4个字节
int是类型,是个模具,用这个模具在内存空间中套出一块空间大小就是4个字节。
#include
int main()
{
//int a = 10;
//printf("%zd\n", sizeof(a));
//printf("%zd\n", sizeof(int));
printf("%zd\n", sizeof int);error错误
//printf("%zd\n", sizeof a);
int arr1[4] = { 0 };
char arr2[4] = { 0 };
printf("%zd\n", sizeof(arr1));
printf("%zd\n", sizeof(int[4]));
printf("%zd\n", sizeof(arr2));
printf("%zd\n", sizeof(char [4]);
return 0;
}
strlen
strlen 是C语言库函数,功能是求字符串长度,函数原型为:
int main()
{
char arr[] = "abcdef";
size_t len = strlen(arr);
printf("%zd\n", len);
return 0;
}
strlen找的就是\0之前
#include
int main()
{
char arr[20] = "abcdef";
size_t len = strlen(arr);
printf("len=%zd\n", len);//6
size_t sz = sizeof(arr);
printf("sz=%zd\n", sz);//20
return 0;
}
我们再看下面这个代码,这个代码我们只知道开辟了一分空间arr(a b c),他的前面和后面我们都不知道,所以用strlen来求字符串长度时,会一直往后找\0,所以结果是随机值。
#include
int main()
{
//char arr[20] = "abcdef";
//size_t len = strlen(arr);
//printf("len=%zd\n", len);//6
//size_t sz = sizeof(arr);
//printf("sz=%zd\n", sz);//20
char arr[] = { 'a','b','c' };
printf("%zd\n", strlen(arr));
return 0;
}
#include
int main()
{
char arr[6] = "abcdef";
printf("%zd\n", sizeof(arr));//6
printf("%zd\n", strlen(arr));//error 错误
return 0;
}
sizeof不挑类型,strlen只针对字符串
数组名的理解
数组名一般表示首元素的地址,但是有两个例外
1.sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
2.&数组名,数组名表示整个数组,取出的是数组的地址
#include
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16 计算的是整个数组的大小
printf("%d\n", sizeof(a + 0));//4/8 数组首元素地址,具体大小看环境
printf("%d\n", sizeof(*a));//4 首个元素
printf("%d\n", sizeof(a + 1));//4/8 第二个元素的地址
printf("%d\n", sizeof(a[1]));//4 第二个元素
printf("%d\n", sizeof(&a));//4/8 取出的是整个数组的地址,但也仅仅是个地址
printf("%d\n", sizeof(*&a));//16 int(*)[4]=&a;
//&a的类型是数组指针,int(*)[4],*&a就是对数组指针解引用访问一个数组的大小,是16个字节
printf("%d\n", sizeof(&a + 1));//4/8 跳过整个数组后的一个地址
printf("%d\n", sizeof(&a[0]));//4/8 第一个元素的地址
printf("%d\n", sizeof(&a[0] + 1));//4/8 第二个元素的地址
return 0;
}
#include
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6
printf("%d\n", sizeof(arr + 0));//4/8 第一个元素地址
printf("%d\n", sizeof(*arr));//1 首元素的大小
printf("%d\n", sizeof(arr[1]));//1 第一个元素
printf("%d\n", sizeof(&arr));//4/8 整个数组的地址
printf("%d\n", sizeof(&arr + 1));//4/8 跳过整个数组的地址
printf("%d\n", sizeof(&arr[0] + 1));//4/8 第二个元素的地址
return 0;
}
#include
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//随机值
printf("%d\n", strlen(arr + 0));//随机值
//printf("%d\n", strlen(*arr));//error
//printf("%d\n", strlen(arr[1]));//error
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//随机值
return 0;
}
#include
int main()
{
char arr[] = "abcdef"; //7个元素
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));
return 0;
}
此时注意,应该使用%zd打印,否则vs编译器就会给出警告
因为前面的讲解已经很详细了,所以在此就一带而过了
#include
int main()
{
char arr[] = "abcdef"; //7个元素
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;
}
#include
#include
int main()
{
char arr[] = "abcdef";
printf("%zd\n", strlen(arr));//6
printf("%zd\n", strlen(arr + 0));//arr+0数组首元素的地址,6
//printf("%zd\n", strlen(*arr));// 传递的是字符a也就是97,error
//printf("%zd\n", strlen(arr[1]));//error
printf("%zd\n", strlen(&arr));//6
printf("%zd\n", strlen(&arr + 1));//随机值
printf("%zd\n", strlen(&arr[0] + 1));//5
return 0;
}
printf("%zd\n", strlen(&arr));//6
此时这条语句会报警告
因为strlen的参数是char*的,而&arr取出的是整个数组的地址
#include
int main()
{
char* p = "abcdef";//一级指针变量
printf("%zd\n", sizeof(p));//4/8
printf("%zd\n", sizeof(p + 1));//4/8
printf("%zd\n", sizeof(*p));//1
printf("%zd\n", sizeof(p[0]));//1 p[0]--->*(p+0)-*p
printf("%zd\n", sizeof(&p));//4/8 &p也是地址,是指针变量的地址,二级指针
printf("%zd\n", sizeof(&p + 1));//4/8 &p+1是指向p指针变量后面的空间,也是地址
printf("%zd\n", sizeof(&p[0] + 1));//4/8 p[0]是数组的第一个元素,&地址后加1,变为b的地址
return 0;
}
//int a=10;
//int* p = &a;
//p + 1;//p跳过一个整型
//char* p
//char* *q=&p;
//q+1//跳过char* 的变量
#include
int main()
{
char* p = "abcdef";
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5
//printf("%d\n", strlen(*p));//error
//printf("%d\n", strlen(p[0]));//error p[0]----*(p+0)-->*p
printf("%d\n", strlen(&p));//随机值
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//5
return 0;
}
#include
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a[0][0]));
printf("%d\n", sizeof(a[0]));
printf("%d\n", sizeof(a[0] + 1));
printf("%d\n", sizeof(*(a[0] + 1)));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(*(a + 1)));
printf("%d\n", sizeof(&a[0] + 1));
printf("%d\n", sizeof(*(&a[0] + 1)));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a[3]));
return 0;
}
这是我们假设想象的二维数组,真正的二维数组存储是连续的,就是第一行之后紧接着存储第二行,第二行后面紧接着第三行。
#include
int main()
{
//注意:二维数组的首元素就是第一行
//二维数组也是数组,之前对数组名的理解也是适合的
//
int a[3][4] = { 0 };
printf("%zd\n", sizeof(a)); //48
printf("%zd\n", sizeof(a[0][0]));//4
printf("%zd\n", sizeof(a[0]));//16 4*4 是第一行这个一维数组的数组名,数组名单独放在
//sizeof内部了 计算的是第一行的大小,单位是16个字节
printf("%zd\n", sizeof(a[0] + 1));//4/8 a[0]第一行这个一维数组的数组名,
//这里表示数组首元素,也就是a[0][0]的地址,a[0]+1是a[0][1]的地址
printf("%zd\n", sizeof(*(a[0] + 1)));//4 a[0][1]
printf("%zd\n", sizeof(a + 1));//4/8 a是二维数组的数组名,但是没有&,也没有单独放在
//sizeof内部 所以这里的a是数组首元素的地址,应该是第一行的地址,a+1是第二行的地址
printf("%zd\n", sizeof(*(a + 1)));//16 第二行的地址解引用,相当于拿到了第二行
//第二行是一个一维数组的地址进行解引用,拿到了整个一维数组
//另一种理解方式*(a + 1)相当于==>a[1],第二行的数组名单独放在sizeof中
//计算的是第二行的大小
printf("%zd\n", sizeof(&a[0] + 1));//4/8 &a[0]是第一行的地址,&a[0]+1就是第二行的地址
printf("%zd\n", sizeof(*(&a[0] + 1))); //16
// int(*p)[4]=&a[0] + 1
printf("%zd\n", sizeof(*a));//16
//这里的a是第一行的地址,*a就是第一行,sizeof(*a)计算的就是第一行的大小
//*a----->*(a+0)------>a[0]
printf("%zd\n", sizeof(a[3]));//16
// 我们在这里的第一反应都是越界了,但事实是这里不存才越界
//sizeof内部的表达式不会真实计算的
//计算的是第四行的大小
return 0;
}
在这里,我们再次强调数组名的理解:
题目一:
#include
int main()
{
int a[5] = { 1,2,3,4,5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
&a取出的是整个元素的地址。
题目二:
//题⽬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;
}
如果改为%x之后打印出来的结果的0就会省略掉
题目三:
#include
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
此时a数组中存的值是不是和我们预想的不太一样
那是因为数组初始化赋值是用了小括号,若按照小括号这种思路其实就是按照逗号表达式存放的·
题目四:
//假设环境是x86环境,程序输出的结果是啥?
#include
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;
}
p是数组指针,指向的元素是4个int类型的元素
内存中的地址不存在原码反码补码,所以打印内存中的地址,就是打印补码,这样不太准确,以16进制打印补码
数组随着下标的增长,地址是由低到高变化的。
题目五:
#include
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;
}
&aa代表整个数组,+1跳过整个数组
aa代表首元素调用,+1代表第二行的首元素的地址
题目六:
#include
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
题目七:
#include
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;
}
对**++cpp的理解: cpp+1,cpp指向的值变化,然后两次解引用,第一次解引用得到c+2的地址,第二次解引用得到point的地址
所以第一次打印出的字符串为point
对*--*cpp+3详细的解释:此时我们用画图来直观地看一下:
对*cpp【-2】+3详细的解释,我们同样用图直观的表现出来:
对cpp[-1][-1]+1,我们同样用上述图的方法来表示:
恭喜你,也谢谢你支持我的文章,如果觉得文章不错,就点赞收藏加评论。