Skip to main content

moregeek program

指针进阶_玄鸟轩墨的博客-多极客编程

序言

重点说一下指针数组和数组指针以及数组与指针的关系,内容较多,要是有缺漏,后面后补加上来

指针数组

前面我们分析过.本质是一个数组,和字符数组、整型数组等一样,里面存储的不过是指针罢了

#include<stdio.h>
#include<windows.h>

int main()
{
int a = 10;
int b = 10;
int c = 10;
int*parr[3] = {&a,&b,&c};//parr就是一个指针数组,3可以省略
for(int i = 0;i<3;i++)
{
printf("*arr[%d] == %d\n",i,*parr[i]);
}
system("pause");
return 0;
}

指针进阶_数组

总结一下,挖掘重点

  • parr去掉,int*[ ],' [ ] '的优先级高所以先结合,是一个数组
  • int*表示存储的是指针,指针的类型是整型

数组指针

首先我们先给出结论 数组指针是一个指针,就像好孩子一样,本质是孩子,修饰语是“ 好 ”

  • 数组指针是一个指针
  • 数组指针指向一个数组,就像整型指针指向一个整型一样
#include<stdio.h>
#include<windows.h>

int main()
{
int arr[10] = {0};
int(*p) [10] = &arr; //p就是一个数组指针

system("pause");
return 0;
}

我们可以看出,p先和*结合,表明是一个指针.p和[]结合,表明该指针指向向一个数组,10表示里面存储10个元素,每一个元素都是int类型.

  • (* )这里表明p是一个指针
  • int[10]表明这个指针指向的是一个数组,数组是一个整形数组,里面有10个元素

注意:这里的10一定不可以省略,不然会报错

指针与数组

我们都对数组很是了解,对一些指针的的基础知识也不陌生,今天我们就仔细的看看他们之间有什么关系.这里也回答一下指针的另一个左右简化代码.

一维数组

先从简单的开始吧,我们从现象中寻找结论.我们先看一下数组名和首元素地址的关系

#include<stdio.h>
#include<windows.h>

int main()
{
int arr[10] = {0};
printf("%p\n",arr);
printf("%p\n",&arr);
printf("%p\n",&arr[0]);
system("pause");
return 0;
}

指针进阶_#include_02

所以说,一维数组名的地址就是整个数组的地址,我们也可p和arr是一样的

#include<stdio.h>
#include<windows.h>

int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
system("pause");
return 0;
}

指针进阶_数组名_03

&数组名和数组名

这里我们需要重点谈了,上面我们发现&数组名和数组名再数值上是一样的,那么它们的含义一样吗?

int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);
system("pause");
return 0;
}

指针进阶_#include_04

我们发现再数值上是一样的.我们对数组取地址,那么我们接受的指针应该是一个数组指针,它+1跳过是整个数组的长度.

int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);
printf("==============\n");
printf("%p\n", arr + 1);
printf("%p\n", &arr + 1);
system("pause");
return 0;
}

指针进阶_数组_05

这里给大家总结一下,&数组名表示是数组的地址,数组名表示是首元素的地址,它们只是恰巧在数值上相等罢了.

那么这里我们就有点疑惑,我们如何接受&数组名,这里就用到我们前面的数组指针.

int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
return 0;
}

那么这里有一个问题,我们如何通过数组的地址拿到数组的元素呢,下面的就可以.*p表示我们得到数组首元素的地址,+i表示得到数组底i的地址,后面再解引用一下.

int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
for (int i = 0; i < 10; i++)
{
printf("%d ", *((*p) + i));
}
return 0;
}

指针进阶_#include_06

这里我们需要观察sizeof(数组名)的值,后面的专题部分会专门谈到.

#include<stdio.h>
#include<windows.h>

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

printf("%d\n", sizeof(p));
printf("%d\n", sizeof(arr));
system("pause");
return 0;
}

指针进阶_#include_07

  • 首元素地址和数组名、数组的地址相同
  • sizeof(数组名)求得是整个数组所开辟的空间,sizeof(p)求的是一个元素的大小,这里后面的面试题重点谈

数组传参

我们把数组作为参数在实际中也是很常见的,我们都知道形参是实参的一份临时拷贝,试想一下,如果我们传入的是数组,编译器把这个数组的元素都给拷贝的一份,这样的话对空间的要求不是很大吗,尤其那些我们只是传过去看看数组里面的元素,不做其他的动作,这样全部拷贝的做法实在是太傻了.所以在实际中,我们数组传参,会退化.数组会退化成一个指针,也就是我们拷贝的是一份指针,也就是4/8个字节,这样大大提高了效率.

#include<stdio.h>
#include<windows.h>

void Print1(int arr[],int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}

void Print2(int* parr, int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", parr[i]);
}
}

int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
Print1(arr,sz);
printf("\n");
Print2(arr,sz);
system("pause");
return 0;
}

指针进阶_#include_08

二维数组

二维数组相比较于一位有点困难.首先我们都明白,二维数组在逻辑上可以说是一个一位数组,只不过一维数组的元素也是一个一维数组而已.

指针进阶_数组_09

当然,这个是我们逻辑上这么认为的,在在实际物理内存中是这样的,这就是我们二维数组列的大小不能省略的原因,它标识这二维数组的一行我们可以4几个元素,后面有大用.

指针进阶_数组_10

&数组名和数组名

首先我们确定一点就是,数组的地址在数值上就是就是首元素的地址,这句话是对的,不过有一点的考究,二维数组的首元素是什么

#include<stdio.h>
#include<windows.h>

int main()
{
int arr[4][4] = { 0 };
printf("%p\n", arr[0]);
printf("%p\n", arr);
printf("%p\n", &arr[0][0]);
system("pause");
return 0;
}

指针进阶_#include_11

这里和大家说一下,和二维数组数组一样,数组名就是首元素的地址,&数组名是真正数组的地址,只不过在数值上相等罢了.

int main()
{
int arr[4][4] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);
printf("%p\n", &arr[0][0]);

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


system("pause");
return 0;
}

指针进阶_数组_12

我先来看一下&数组名应该如何做.

int main()
{
int arr[4][4] = { 0 };
int(*p)[4][4] = &arr;
system("pause");
return 0;
}

在来谈一下首元素的地址,我们知道,数组名是首元素的的地址,按照逻辑上上而言,二维数组的首元素是一个指针,该指针指向一个数组,也就是首元素是一个数组指针.

int main()
{
int arr[4][4] = { 0 };
int(*p)[4] = arr;
system("pause");
return 0;
}

指针进阶_数组_13

既然是一个函数指针,那么数组名+1应该跳过一个一维数组的大小

#include<stdio.h>
#include<windows.h>

int main()
{
int arr[4][4] = { 0 };
printf("%p\n", arr);
printf("%p\n", arr[1]);
printf("%p\n", arr+1);
system("pause");
return 0;
}

指针进阶_数组名_14

二维数组传参

这个就比较简单的了,我们知道了二维数组的数组名是一个数组指针,这里我们可以用方法二接受二维数组

#include<stdio.h>
#include<windows.h>
// 方法一
void Print1(int arr[4][4])
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
}
}
// 方法二
void Print2(int(*p)[4])
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", *((*(p + i)) + j));
}
}
}

int main()
{
int arr[4][4] = { { 1, 2, 3, 4 } };
Print1(arr);
printf("%\n");
Print2(arr);
system("pause");
return 0;
}

指针进阶_数组_15

至于*(*(p + i)) + j),我们也分析一下,p+i表明我们在二维数组的第i行,*(p+i)表明我们得到第i行的第一个元素的地址,加上j表示得到第i行第j列的元素的地址,我们最后解引用一下.这里可以令i = j = 0.这就是((*(p + 0)) + 0) 与 arr[0][0]关系

  1. p+0 就是p 指向arr的 首元素的
  2. *(p+0) 就是 arr[0]
  3. *(p + 0)) + 0 指向arr[0]+0
  4. 那么((*(p + 0)) + 0)就是arr[0][0]

函数指针

在写这个之前,我们先做一下铺垫

函数有地址吗

我们这里有点疑惑,函数也存在地址吗,如果存在,它们的引用是什么呢?

void test()
{
printf("hehe");
}

int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}

指针进阶_数组名_16

看来函数是有地址的,而且和数组类似,&函数名 和函数名的地址一样,这里我们把这两个方法看作是一样的

如何定义函数地址

这个很难用文字只管描述出来,我们是例子来辅助

int Add(int x, int y)
{
return x + y;
}

int main()
{
int(*pf1)(int, int) = Add;
int(*pf2)(int, int) = &Add;
printf("%d\n", pf1(1, 2));
printf("%d\n", (*pf1)(1, 2));
printf("%d\n", (*pf2)(1, 2));
return 0;
}

指针进阶_#include_17

这里我们得出一些个结论

  • *pf1 和 pf1是一样的
  • &函数名和函数名类型是一样的

我来解释一下int(*pf1)(int, int) = Add;

  1. pf1先和*结合,表明pf1是一个指针
  2. 其次和后面的(int,int)中的()结合,表明pf1指向的是一个函数
  3. (int,int)表名函数的参数有两个,都是int型
  4. 最前面的int表示返回值是int型

这里我们说一下啊函数指针的应用,要是前面的用法就有点挫了.我们实现一个简单的计算器.

#include<stdio.h>
#include<windows.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("*** 0. exit ***\n");
printf("*** 1.Add 2.Sub ***\n");
printf("*** 3.Mul 4 Div ***\n");
printf("************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
do
{
menu();
printf("请选择: > ");
scanf("%d", &input);
switch (input)
{
case 0:
printf("已退出\n");
break;
case 1:
printf("请输入两个数: ");
scanf("%d %d",&x, &y);
printf("%d\n", Add(x, y));
break;
case 2:
printf("请输入两个数: ");
scanf("%d %d", &x, &y);
printf("%d\n", Sub(x, y));
break;
case 3:
printf("请输入两个数: ");
scanf("%d %d", &x, &y);
printf("%d\n", Mul(x, y));

break;
case 4:
printf("请输入两个数: ");
scanf("%d %d", &x, &y);
printf("%d\n", Div(x, y));

break;
default:
printf("请重新选择\n");
break;
}
} while (input);
return 0;
}

指针进阶_数组名_18

上面的代码你不会觉得很冗余吗,尤其是在case里面的那部分,我们是不是可以把它封成一个函数,传入函数的地址就可以了.这样的话可以大大简化代码.

#include<stdio.h>
#include<windows.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("*** 0. exit ***\n");
printf("*** 1.Add 2.Sub ***\n");
printf("*** 3.Mul 4 Div ***\n");
printf("************************\n");
}
void calculator(int(*pf)(int,int))
{
int x = 0;
int y = 0;
printf("请输入两个数: ");
scanf("%d %d", &x, &y);
printf("%d\n", (*pf)(x, y));
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择: > ");
scanf("%d", &input);
switch (input)
{
case 0:
printf("已退出\n");
break;
case 1:
calculator(Add);
break;
case 2:
calculator(Sub);
break;
case 3:
calculator(Mul);
break;
case 4:
calculator(Div);
break;
default:
printf("请重新选择\n");
break;
}
} while (input);
return 0;
}

指针进阶_数组名_19

我们来阅读下面的两个代码,你将会看到函数指针的"魅力".

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

​(*(void (*)())0)();()0表示强制类型转换,转换的类型是void (*)().这是一个函数指针,没有参数没有返回值.也就是说这个代码是这样理解的,我们把地址为0 的地方转换成函数,这个函数没有参数和返回值

void (*signal(int , void(*)(int)))(int);这个就比较难理解了,signal是一个函数,它存在两个参数,一个是int,另一个是一个函数指针void(*)(int).signal函数的返回值还是一个函数指针void(*)(int)).

你会发现函数指针实在是太难了,这里我们不要求精通,只需要了解,可以分辨出是什么样的函数指针就可以了.

函数指针数组

我们都知道数组是相同类型元素的集合,既然函数指针也是一种指针类型,那么它也可以构成数组,不过要求他们参数和返回类型一摸一样

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(*pf[4])(int, int) = { Add,Sub,Mul,Div };
return 0;
}

这里我也解释一下,int(*pf[4])(int, int) = { Add,Sub,Mul,Div };

  1. pf先和[4]结合,表明是一个数组,数组里面有4 的元素
  2. 去掉pf[4],得到int(* )(int, int) ,这是一个函数指针,所以说4个元素每个都是是一个函数指针

这里我还是以计算器为例子.这个就比较简单了.

#include<stdio.h>
#include<windows.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("*** 0. exit ***\n");
printf("*** 1.Add 2.Sub ***\n");
printf("*** 3.Mul 4 Div ***\n");
printf("************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;

int (*pfArr[5])(int, int) = { 0, Add, Sub, Mul, Div };//pfArr是一个函数指针的数组,也叫转移表

do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
break;
}
else if (input >= 1 && input <= 4)
{
printf("输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("ret = %d\n", ret);
}
else
{
printf("选择错误\n");
}
} while (input);

return 0;
}

指针进阶_数组名_20

指向函数指针数组的指针

首先说一下,我也不想套娃,不过知识点在这,我也没办法,这是最后一个了.

int main()
{
int(*pf[4])(int, int) = { Add,Sub,Mul,Div };

int(*(*ppf)[4])(int, int) = &pf;
return 0;
}

解释吧 int(*(*ppf)[4])(int, int) = &pf;

  1. ppf和*结合,标明是指针
  2. 去掉ppf,得到的 int(*[4])(int, int)表示是一个数组

回调函数

回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数.回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应.这里我们先不谈了,等到后面我有时间了补上.

指针例题

我们先来看一下指针常见的.

sizeof(数组名) & sizeof(p)

我们知道sizeof(数组名)是计算该数组占据的所有字节,这里我们就有下面的问题了.我们在32为平台下测试

这里我们简单的下一个结论,只要数组名显式的和数字结合(显示主要是为了分辨0),这个里就要考虑指针的大小.要是只有数组名就是整个数组的大小,&数组名就是指针的大小

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

指针进阶_数组名_21

这里还有测试一下字符数组

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

指针进阶_数组名_22

这里还有的,涉及到字符数组的sizeof,我们就好把它strlen进行比较.注意,strlen函数不管你是&数组名还是数组名,他就把参数当作一个指针,按照指针去找'\0'.有人可能会疑惑,如果我们传入&数组名,这个不就是数组指针吗?到时候+1可是跳整个数组,这里大家想一想strlen是记录什么的,它是计算字符串的大小,也就是函数的参数char*,这里会发生类型转换,所有前面的顾虑没必要.

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));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
printf("%d\n", strlen(*arr)); // 这是一个 bug 野指针
printf("%d\n", strlen(arr[1]));
return 0;
}

指针进阶_#include_23

二维数组相关的计算

注意,整个部分是有点苦困难的,我们需要把上面的结论给跟新一下,我们先来看现象,后面总结结论.

#include <stdio.h>
#include <string.h>
int main()
{
//二维数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); // 3*4 *4
printf("%d\n", sizeof(a[0][0])); // 4
printf("%d\n", sizeof(a[0])); // 4*4
printf("%d\n", sizeof(a[0] + 1)); // 4
printf("%d\n", sizeof(*(a[0] + 1))); // 4
printf("%d\n", sizeof(a + 1)); // 4 * 4
printf("%d\n", sizeof(*(a + 1))); // 4*4
printf("%d\n", sizeof(&a[0] + 1)); //
printf("%d\n", sizeof(*(&a[0] + 1))); // 4*4
printf("%d\n", sizeof(*a)); // 4
printf("%d\n", sizeof(a[3])); // 4*4
return 0;
}

指针进阶_数组_24

我们需要解释一下,我们把a看作一个一位数组,sizeof(a)就是所有的空间只要a显示的加上一个数,就是指针的大小.

int main()
{
//二维数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(a+0));

return 0;
}

指针进阶_数组名_25

但是一旦我们*(a+n)或者a[n]就可以看作一个一维数组的数组名,得到该行的大小.这两个规则和一位数组是一样的.

int main()
{
//二维数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(*(a + 0)));
return 0;
}

指针进阶_#include_26

类型转换

由于不同类型的指针+1跳过的字节数是不同的,这里就会出现一些强制类型转换的例题.

例题一: 这道题倒是挺简单的,跳过几个字节,我们看指针解引用占据几个字节

#include <stdio.h>
#include <string.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1); // &a 是一个数组指针,解引用是一个数组 +1跳过整个数组 后面又强制类型转换了
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}

指针进阶_数组名_27

指针进阶_#include_28

例题二:下面这道题,我们可以知道p解引用是20的字节的变量,所以+1跳过20个字节,

#include <stdio.h>
#include <string.h>

struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知结构体类型的变量test的大小有20个字节
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1); // 强制类型转换 就变成了 3+1 = 4
printf("%p\n", (unsigned int*)p + 0x1); // 强制类型转换成 unsigned int*,解引用是4个字节,所以+1跳过4个字节
return 0;
}

指针进阶_数组名_29

例题三,这个又有点困难了.这里强制类型转换的更加厉害.

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

指针进阶_#include_30

这道题涉及到三个知识知识点,我们分别讲述.首先就是大小端的问题,数据在内存中是如何存储的,我的电脑是小段机,存储数据按照"小小小"来存储.在说ptr2,我们首先明白指针指向第一个字节,强制类型转换后,跳过一个字节,又因为我们强制类型转换成int*,所以解引用访问4个字节,这就是我们得到下面结果的理由.

指针进阶_数组名_31

例题四 注意这是我们再一次涉及到二维数组,我们需要先分析一下.

p[0]和*(p+0)等价,这里面又等价于*(p).我们知道p是一个数组指针,数组指针解引用得到该数组首元素的地址,这里的地址是int.第二个要注意的是这里我们用的是逗号表达式,这里我们需要细心点.

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

指针进阶_数组名_32

指针进阶_#include_33

例题五:这个题很难,这里我在图片上解释了,这里就不浪费时间了

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

指针进阶_数组名_34

指针进阶_数组_35

例题六 这个就比较简单了

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

指针进阶_数组_36

指针进阶_#include_37

例题七这个我要解释一下,不是太难,主要是一个思路的问题,我们需要*先和谁结合.char* pa = a;首先pa先和左面的临近它的*结合,表明它是一个指针,指向的是char*.我们加1跳过4个字节.

int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}

指针进阶_#include_38

指针进阶_数组名_39

例题八,这个是我们这篇博客最后一个例题,来个看起比较麻烦的.等下我整体做一个总结.

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

指针进阶_数组名_40

指针进阶_#include_41

总结

指针是C语言里面的一个难点,我们需要细心的琢磨.遇到题不要慌,多画画图,一点点分析.这里要注意指针和数组的关系,一般的题就是按照这个思路出的.

©著作权归作者所有:来自51CTO博客作者玄鸟轩墨的原创作品,请联系作者获取转载授权,否则将追究法律责任

指针初阶_玄鸟轩墨的博客-多极客编程

序言指针这个模块是C语言里面比较难理解的的,学习成本倒是不高,就是有点费脑子.我们这里重点关注什么是指针和指针的用法.这篇博客我重新写了写了一遍,原来的那个实在太简陋了,里面新增了一下内容.地址谈到指针我们不得不说一下地址.什么是地址呢?地址就是能够标识一件事物的确切位置.这里有一个例子.张三是你的同学,一天,张三给你打电话,说李四,今天你来我家吧,我家在XXX小区XXX号楼.说完就把电话挂了.这

数组找出单身狗经典问题_mb6318b1fa06863的博客-多极客编程

前言单身狗问题是大厂近几年的一个热门考点,所以我们就一起来探讨一下吧!摘要单身狗的问题解法有很多种,今天我带给大家两种经典解法,一、数组比较法,二、异或法,这两种解法我会分开来讲。思路我放在具体板块讲解一、数组比较法(两只狗)首先呢,我们要用数组比较,就需要把元素两两比较,当一个数出现两次,使之相减,结果为0的话不是单身狗,反之,这就是我们判断出单身狗的条件,但是如果数组乱序的话相邻元素比较可能为

力扣刷题详解(含代码动态展示)_萌新的日常的博客-多极客编程

(文章目录) 一、448. 找到所有数组中消失的数字 给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。 示例 1: 输入:nums = [4,3,2,7,8,2,3,1] 输出:[5,6] 示例 2: 输入:nums = [1,1] 输出:[2] 1.完整

数据结构重点知识汇总_wx619474981d7fe的博客-多极客编程

1 线性表的合并(编程题)1.1 有序表的合并算法步骤:创建表长为m+n的空表LC。指针pc初始化,指向LC的第一个元素。指针pa和pb初始化,分别指向LA和LB的第一个元素。当指针pa和pb均为到达相应表尾时,则依次比较pa和pb所指向的元素值,从LA或LB中摘取元素值较小的结点插入到LC的最后。如果pb已到达LB的表尾,依次将LA的剩余元素查到LC的最后。如果pa已到达LA的表尾,依次将LB的

驱动开发:内核中的自旋锁结构_lyshark的博客-多极客编程

提到自旋锁那就必须要说链表,在上一篇《驱动开发:内核中的链表与结构体》文章中简单实用链表结构来存储进程信息列表,相信读者应该已经理解了内核链表的基本使用,本篇文章将讲解自旋锁的简单应用,自旋锁是为了解决内核链表读写时存在线程同步问题,解决多线程同步问题必须要用锁,通常使用自旋锁,自旋锁是内核中提供的一种高IRQL锁,用同步以及独占的方式访问某个资源。 首先以简单的链表为案例,链表主要分为单向链表与

c++ stl 概述_严丝合缝的合作者们_一枚大果壳的博客-多极客编程

1. 初识 STL 什么是STL? STL(Standard Template Library) 是C++以模板形式提供的一套标准库,提供了很多开发过程需要的通用功能模块。使用 STL ,可以让开发者将主要精力用于解决程序的高级业务逻辑,而无须关心底层的基础逻辑调用。 STL 由 6大部分组成: 容器:存储和组织数据的类模板,是STL的核心。 迭代器:独立于容器,提供访问容器中数据的通用操作组

翻了concurrenthashmap1.7 和1.8的源码,我总结了它们的主要区别。_博学谷狂野架构师的博客-多极客编程

ConcurrentHashMap 思考:HashTable是线程安全的,为什么不推荐使用? HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占,相当于所有线程进行读写时都去竞争一把锁,导致效率非常低下。 1 ConcurrentHashMap 1.7 在JDK1.7中ConcurrentHashMap采用了数组+分段

指针初阶_玄鸟轩墨的博客-多极客编程

序言指针这个模块是C语言里面比较难理解的的,学习成本倒是不高,就是有点费脑子.我们这里重点关注什么是指针和指针的用法.这篇博客我重新写了写了一遍,原来的那个实在太简陋了,里面新增了一下内容.地址谈到指针我们不得不说一下地址.什么是地址呢?地址就是能够标识一件事物的确切位置.这里有一个例子.张三是你的同学,一天,张三给你打电话,说李四,今天你来我家吧,我家在XXX小区XXX号楼.说完就把电话挂了.这

​​vb.net的数据类型​_vb.net课程的博客-多极客编程

​VB.NET的数据类型: VB类型CLR类型名义存储空间存值范围默认值BooleanBoolean由实施平台确定True 或 FalseFalseDateDateTime8个字节公元1年1月1日0:00:00到公元9999年12月31日11:59:59 PM。其最小值CharChar2 个字节0到65535之间的字符码码位为0的字符StringString由实施平台确定0 to大约20亿(231

通过thread pool executor类解析线程池执行任务的核心流程_华为云开发者社区的博客-多极客编程

摘要:ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态。本文分享自华为云社区《​​【高并发】通过Thread Pool Executor类的源码深度解析线程池执行任务的核心流程​​》,作者:冰 河。今天,我们通过Thread Pool Executor类的源码深度解析线程池执行任务的核心流程,小

python 内置数据类型与方法_ni_cue~的博客-多极客编程

序列类型包括列表、元组和范围(range)以及字符串,序列类型的对象有一些共同的操作,如操作符运算、切片操作等。 1. list类型与操作 1.1 玩转索引 列表(list)类型的对象可以通过 list()函数来创建。如果list()函数没有传入参数,则创建一个空列表。 In [1]:L1=1izt([123, 23, [4,5,6], 'abe')) In [2]:L1 0ut[[2]:[123

cgo之#cgo_zzxiaoma的博客-多极客编程

在 import "C" 语句前的注释中可以通过 #cgo 语句设置编译阶段和链接阶段的相关参数。编译阶段的参数主要用于定义相关宏和指定头文件检索路径。链接阶段的参数主要是指定库文件检索路径和要链接的库文件。 // #cgo CFLAGS: -DPNG_DEBUG=1 -I./include// #cgo LDFLAGS: -L/usr/local/lib -lpng// #include &l