C语言进阶之指针的进阶

请添加图片描述

指针的进阶

  • 1. 字符指针
  • 2. 指针数组
  • 3. 数组指针
    • 3.1 数组指针的定义
    • 3.2 &数组名VS数组名
    • 3.3 数组指针的使用
  • 4. 数组参数、指针参数
    • 4.1 一维数组传参
    • 4.2 二维数组传参
    • 4.3 一级指针传参
    • 4.4 二级指针传参
  • 5. 函数指针
  • 6. 函数指针数组
  • 7. 指向函数指针数组的指针
  • 8. 回调函数
  • 9. 指针和数组笔试题解析
    • 9.1 一维数组
    • 9.2 字符数组
    • 9.3 字符指针
    • 9.4 二维数组
  • 10. 指针笔试题
    • 10.1 笔试题1
    • 10.2 笔试题2
    • 10.3 笔试题3
    • 10.4 笔试题4
    • 10.5 笔试题5
    • 10.6 笔试题6
    • 10.7 笔试题7
    • 10.8 笔试题8
  • 结语

指针的主题,我们在C语言初阶博客已经接触过了,我们知道了指针的概念:

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
  4. 指针的运算。
    下面,我们继续探讨指针的高级主题

1. 字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:

int main()
{char ch = 'a';char *pc = &ch;*pc = 'a';return 0;
}

还有一种使用方式如下:

int main()
{const char* pstr = "hello C.";printf("%s\n", pstr);return 0;
}

那么这里是把一个字符串放到pstr指针变量里了吗?
其实并不是哦,这里实际上是把字符串的首字符也就是h的地址存放到指针变量pstr中。

我们看看下面这道关于字符指针的面试题

#include <stdio.h>
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");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

在这里插入图片描述
首先数组名代表数组的首元素地址,所以这里的str1和str2分别存的是对应数组的首字符地址,他们并不相同,所以第一个输出结果是str1 and str2 are not same,这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

2. 指针数组

前面在初阶C语言时我们就讲过指针数组
如下:

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

3. 数组指针

3.1 数组指针的定义

数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * p; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
那么下面代码哪个是数组指针?

int *p1[10];
int (*p2)[10];

答案是int (*p2)[10];

p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。

要注意的是:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合

3.2 &数组名VS数组名

对于数组
arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:

#include <stdio.h>
int main()
{int arr[10] = {0};printf("%p\n", arr);printf("%p\n", &arr);return 0;
}

在这里插入图片描述
我们可以看到数组名和&数组名打印的地址是一样的,难道两个是一样的吗?
再看下一段代码:

#include <stdio.h>
int main()
{int arr[10] = { 0 };printf("arr = %p\n", arr);printf("&arr= %p\n", &arr);printf("arr+1 = %p\n", arr + 1);printf("&arr+1= %p\n", &arr + 1);return 0;
}

在这里插入图片描述
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型,数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40

3.3 数组指针的使用

那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
看代码:

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };int(*p)[10] = &arr;return 0;
}

把数组arr的地址赋值给数组指针变量p,但是我们一般很少这样写代码
如下:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{int i = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
void print_arr2(int(*arr)[5], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };print_arr1(arr, 3, 5);print_arr2(arr, 3, 5);return 0;
}

数组名arr,表示首元素的地址
但是二维数组的首元素是二维数组的第一行
所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
可以数组指针来接收

我们再来看看下面代码分别代表什么意思

int arr[5];//数组
int *parr1[10];//指针数组
int (*parr2)[10];//数组指针
int (*parr3[10])[5];//数组指针数组

4. 数组参数、指针参数

4.1 一维数组传参

void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int* arr)//ok?
{}
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
int main()
{int arr[10] = { 0 };int* arr2[20] = { 0 };test(arr);test2(arr2);
}

这几种传递方式都是可以的

4.2 二维数组传参

void test(int arr[3][5])//ok
{}
void test(int arr[][])//不可以
{}
void test(int arr[][5])//ok
{}
void test(int* arr)//不可以
{}
void test(int* arr[5])//ok
{}
void test(int(*arr)[5])//ok
{}
void test(int** arr)//不可以
{}
int main()
{int arr[3][5] = { 0 };test(arr);
}

总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。

4.3 一级指针传参

#include <stdio.h>
void print(int* p, int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d\n", *(p + i));}
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);print(p, sz);return 0;
}

4.4 二级指针传参

#include <stdio.h>
void test(int** ptr)
{printf("num = %d\n", **ptr);
}
int main()
{int n = 10;int* p = &n;int** pp = &p;test(pp);test(&p);return 0;
}

5. 函数指针

先看代码

#include <stdio.h>
void test()
{printf("hehe\n");
}
int main()
{printf("%p\n", test);printf("%p\n", &test);return 0;
}

在这里插入图片描述
不难看出函数名即为函数地址,这两个地址是 test 函数的地址。 那我们的函数的地址要想保存起来,怎么保存?

#include <stdio.h>
void test()
{printf("hehe\n");
}
void (*pfun1)();

pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

6. 函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

int *arr[10];
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10])();

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。
函数指针数组的用途:转移表
示例:计算器常规写法

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

使用函数指针数组的实现:

#include <stdio.h>
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);}elseprintf("输入有误\n");printf("ret = %d\n", ret);
}
return 0;
}

这样代码就不显得冗余了

7. 指向函数指针数组的指针

指向函数指针数组的指针是一个 指针,指针指向一个数组 ,数组的元素都是函数指针 ;
那么该如何定义呢,看下面代码:

void test(const char* str)
{printf("%s\n", str);
}
int main()
{//函数指针pfunvoid (*pfun)(const char*) = test;//函数指针的数组pfunArrvoid (*pfunArr[5])(const char* str);pfunArr[0] = test;//指向函数指针数组pfunArr的指针ppfunArrvoid (*(*ppfunArr)[5])(const char*) = &pfunArr;return 0;
}

8. 回调函数

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

我们以库函数种的qsort函数为例,我们先看此函数的定义

_ACRTIMP void __cdecl qsort(_Inout_updates_bytes_(_NumOfElements * _SizeOfElements) void*  _Base,_In_                                                    size_t _NumOfElements,_In_                                                    size_t _SizeOfElements,_In_                _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction);

在这里插入图片描述
第一个形参为void* 是因为其能够容纳任意类型的指针,
最后一个 _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction实际上就是一个回调函数,我们在使用qsort函数时,自己还需要再写一个_CompareFunction函数对不同类型排序。
示例:

#include<stdlib.h>
#include <stdio.h>
int int_cmp(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

其中int_cmp函数即为qsort调用的_CoreCrtNonSecureSearchSortCompareFunction _CompareFunction
在这里插入图片描述
需要注意的是,这种默认的写法一般为升序,若想改为降序,可以将p1和p2互换,如下代码:

int int_cmp(const void* p1, const void* p2)
{return (*(int*)p2 - *(int*)p1);
}

我们再看对结构体数组的排序

#include<stdlib.h>
#include <stdio.h>
#include <string.h>
struct Stu
{char name[20];int age;
};int cmp_stu_by_age(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}void test1()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]); printf("初始序列:> ");for (int i = 0; i < sz; i++){printf("%s,%d     ", arr[i].name, arr[i].age);}printf("\n");qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);printf("按年龄排序:> ");for (int i = 0; i < sz; i++){printf("%s,%d     ", arr[i].name, arr[i].age);}printf("\n");
}int cmp_stu_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}void test2()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);printf("按名字排序:> ");for (int i = 0; i < sz; i++){printf("%s,%d     ", arr[i].name, arr[i].age);}
}int main()
{test1();test2();return 0;
}

在这里插入图片描述
根据同样的原理,我们还可以写出适应多类型的冒泡排序回调函数
示例:

#include<stdio.h>
void Swap(void* p1, void* p2, int size)
{for (int i = 0; i < size; i++){char tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}
void Bubble_Sort(void* base, int num, int size, int(*cmp)(void*,void*))
{int i = 0;int j = 0;for (i = 0; i < num - 1; i++){for (j = 0; j < num - i - 1; j++){if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)Swap((char*)base + j * size, (char*)base + (j + 1) * size,size);}}
}int Int_Sort(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}
void Int_Print(int* arr,int sz)
{for (int i = 0; i < sz; i++)printf("%d ", arr[i]);
}void Test1()
{int arr[] = { 7,6,5,4,8,9,3,1,2 };int sz = sizeof(arr) / sizeof(arr[0]);printf("初始序列:> ");Int_Print(arr, sz);printf("\n");Bubble_Sort(arr, sz, sizeof(arr[0]), Int_Sort);printf("排序后  :> ");Int_Print(arr,sz);
}int main()
{Test1();return 0;
}

在这里插入图片描述

9. 指针和数组笔试题解析

以下所有代码均为64平台编译

9.1 一维数组

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//sizeof中数组名代表整个数组
printf("%d\n",sizeof(a+0));//数组名+0为第一个元素的地址,也就是指针,指针大小就是4/8个字节,当前为64位,所以是8
printf("%d\n",sizeof(*a));//对数组名进行解引用操作,获取指向数组第一个元素
printf("%d\n",sizeof(a+1));//a+1代表第二个元素的地址
printf("%d\n",sizeof(a[1]));//代表第二个元素
printf("%d\n",sizeof(&a));//对数组名取地址,代表的是指针
printf("%d\n",sizeof(*&a));//&再*解引用等于没做操作,还是整个数组
printf("%d\n",sizeof(&a+1));//对数组名取地址再+1代表的是该数组之后的地址
printf("%d\n",sizeof(&a[0]));//代表第一个元素的地址
printf("%d\n",sizeof(&a[0]+1));//代表第二个元素的地址

在这里插入图片描述

9.2 字符数组

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//整个数组
printf("%d\n", sizeof(arr+0));//数组名+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));//代表第二个元素的地址

在这里插入图片描述

char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//因为字符数组arr中没有\0,所以在求字符串长度的时候,会一直往后找,产生的结构就是随机值
printf("%d\n", strlen(arr+0));//arr + 0是首元素的地址,和第一个一样,也是随机值
//printf("%d\n", strlen(*arr));//错误, arr是数组首元素的地址,*arr就是数组首元素,就是'a'-97
//strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,'a'的ASCII码值是97,那就是将97作为地址传参
//strlen就会从97这个地址开始统计字符串长度,这就非法访问内存了
//printf("%d\n", strlen(arr[1]));//错误,同上,只不过这里传的是第二个元素b
printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,那么传递给strlen函数后,依然是从数组的第一个元素的位置开始往后统计
printf("%d\n", strlen(&arr+1));//随机值-6,减去了上面6个元素的长度
printf("%d\n", strlen(&arr[0]+1));//&arr[0] + 1是第二个元素的地址。结果也是随机值

在这里插入图片描述

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//整个数组
printf("%d\n", sizeof(arr + 0));//arr + 0是首元素的地址
printf("%d\n", sizeof(*arr));//*arr其实就是首元素,1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr + 1));//&arr + 1是跳过一个数组的地址,4/8
printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 4/8

在这里插入图片描述

char arr[] = "abcdef";
printf("%d\n", strlen(arr));//整个字符串的长度
printf("%d\n", strlen(arr+0));//首元素的地址开始,所以结果同上
//printf("%d\n", strlen(*arr));//错误, arr是数组首元素的地址,*arr就是数组首元素
//printf("%d\n", strlen(arr[1]));//错误,同上,只不过这里传的是第二个元素b
printf("%d\n", strlen(&arr));//整个数组的地址,还是从首元素开始
printf("%d\n", strlen(&arr+1));//整个数组后开始计算,所以是随机值
printf("%d\n", strlen(&arr[0]+1));//从第二个元素地址开始算

在这里插入图片描述

9.3 字符指针

也可以说是字符数组,数组本身也是指针

char *p = "abcdef";
printf("%d\n", sizeof(p));//p为指针变量,大小为4/8
printf("%d\n", sizeof(p+1));//p+1是'b'的地址
printf("%d\n", sizeof(*p));//*p 就是字符a
printf("%d\n", sizeof(p[0]));//同上
printf("%d\n", sizeof(&p));//*p的地址
printf("%d\n", sizeof(&p+1));//*p之后的地址
printf("%d\n", sizeof(&p[0]+1));//&p[0] + 1得到是'b'的地址

在这里插入图片描述

char *p = "abcdef";
printf("%d\n", strlen(p));//正常计算一个字符串的长度
printf("%d\n", strlen(p+1));//从第二个字符开始算
//printf("%d\n", strlen(*p));//错误,传的是首元素
//printf("%d\n", strlen(p[0]));//错误,同上
printf("%d\n", strlen(&p));//从首元素的地址中计算,是随机值,不确定的,和分配的地址有关
printf("%d\n", strlen(&p+1));//同上也是随机值
printf("%d\n", strlen(&p[0]+1));//从第二个字符开始算

在这里插入图片描述

9.4 二维数组

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));
//a[0]作为第一行的数组名,没有单独放在sizeo内部,没有&
//a[0]表示数组首元素的地址,也就是a[0][0]的地址
//所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节

在这里插入图片描述

int a[3][4] = { 0 };
printf("%d\n", sizeof(*(a[0] + 1)));//计算的是就是第一行第2个元素的大小
printf("%d\n", sizeof(a + 1));//a是数组首元素的地址,是第一行的地址 int(*)[4],a+1 就是第二行的地址
printf("%d\n", sizeof(*(a + 1)));//计算的是第二行的大小
printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址 int(*)[4],&a[0]+1 是第二行的地址 int(*)[4]
printf("%d\n", sizeof(*(&a[0] + 1)));//计算的是第二行的大小
printf("%d\n", sizeof(*a));//计算的是第一行的大小
printf("%d\n", sizeof(a[3]));//计算的是一行的大小,并不存在越界,因为实际并没有访问内存

在这里插入图片描述
总结
数组名的意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

10. 指针笔试题

10.1 笔试题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;
}

程序的结果是什么?
在这里插入图片描述
*(a + 1)访问的第二个元素,*ptr是跳过整个数组后的地址

10.2 笔试题2

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

假设p 的值为0x10000000。 如下表达式的值分别为多少?
已知,结构体Test类型的变量大小是20个字节
在这里插入图片描述
第一个结果是跳过整个结构体,所以直接加20,地址是以十六进制打印,所以是10000014
第二个结果是先将结构体地址强转为长整形,而整形计算则是直接加1
第三个结果是先将结构体地址强制转换为指针,加1则跳过一个指针的大小,即4/8个字节

10.3 笔试题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;
}

在这里插入图片描述
在这里插入图片描述

10.4 笔试题4

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

在这里插入图片描述
这里需要注意的是,在初始化时,我们只初始化了前3个值,因为这里面放的是()而不是{},所以编译时只将逗号表达式中的数字,即1,3,5,而后都应是0;所以打印指向第一行的值,就只打印a[0][0],即1。

10.5 笔试题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;
}

在这里插入图片描述
在这里插入图片描述

10.6 笔试题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;
}

在这里插入图片描述
&aa+1跳过的是整个数组,所以再-1,打印的整数值即为aa数组最后一个元素
aa+1是跳过二维数组第一行,即6的起始地址,,所以再-1,打印的整数值即为aa数组第一行最后一个元素

10.7 笔试题7

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

在这里插入图片描述
首先char* a[]存储的就是三个首字母的地址,而char** pa存储的是指针数组中首元素w的地址,所以pa++,pa指向的就是第二个元素a的地址,解引用后打印即为at。

10.8 笔试题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;
}

在这里插入图片描述
在这里插入图片描述

结语

有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/20879.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

汇报方案设计方案规划方案资源下载

标题汇报方案设计方案规划方案资源下载https://wheart.cn/so/home?mdw&tag%E5%AE%89%E5%85%A8文章标签事业单位人事人才信息综合管理系统建设设计报价方案人事系统,人事人才,事业单位,工资系统,职称系统xx纪检委智慧监督平台建设方案汇报.docx建设方案,规划设计,汇报方案营…

HTML语法

文章目录 前言HTML 文件基本结构常见标签标签种类特殊符号图片链接a链接 双标签链接 列表表格 &#xff1a;表单多行文本域: 前言 HTML是有标签组成的 <body>hello</body>大部分标签成对出现. 为开始标签, 为结束标签. 少数标签只有开始标签, 称为 “单标签”. 开…

Acwing:第 111 场周赛(2023.7.12 C++)

目录 5047. 1序列 题目描述&#xff1a; 实现代码&#xff1a; 5048. 无线网络 题目描述&#xff1a; 实现代码&#xff1a; 二分 贪心 5049. 选人 题目描述&#xff1a; 实现代码&#xff1a; 数学 5047. 1序列 题目描述&#xff1a; 实现代码&#xff1a; #incl…

MySQL(九):MySQL语法-高级

MySQL语法-高级 LIMITLIKEASCREATE UNIQUE INDEX、DROP INDEXCREATE VIEW、DROP VIEWGROUP BYHAVINGMYSQL - JOININNER JOIN、JOINLEFT JOIN、LEFT OUTER JOINRIGHT JOIN、RIGHT OUTER JOINLEFT JOIN ... WHERE ...RIIGHT JOIN ... WHERE ... TRUNCATE TABLEINSERT INTO 表1 (列…

反垄断在中国

中国通过反垄断法 中国通过了具有里程碑意义的反托拉斯立法,外国企业表示谨慎性的欢迎,希望该法案能带来更大的开放性,但需要观察它是如何实施的。(华尔街日报 2007年8月32日报道) 反垄断法禁止垄断协议和诸如卡特尔及价格操纵,但允许能促进创新和技术进步的垄断之存在。…

C人脸识别

1、原始图片&#xff1a; 2、灰度化下&#xff1a; 3、均值滤波&#xff1a; 4、 二值图加边缘检测 5、生成积分图 6、把待检测的人脸区域划分为25个&#xff0c;因为是一个数组&#xff0c;这样分别统计每个区域的像素个数&#xff1a; x0: 60, y0: 100, x1: 157, y1: 200 …

动态内存管理

目录 动态内存分配存在的原因 动态内存函数的介绍 malloc和free calloc realloc 常见的动态内存错误 对NULL指针的解引用操作 对动态开辟空间的越界访问 对非动态开辟内存使用free释放 使用free释放一块动态开辟内存的一部分 对同一块动态内存多次释放 动态开辟…

Vue+elementUI实现下拉框多选和反选

Vue代码如下&#xff1a; <el-form-item label"下拉框名称&#xff1a;"><el-select size"mini" v-model"testModelName" focus"getSelectInfo" :disabled"SelectStyle" filterable clearable placeholder"&…

基于 FPGA 的 HDMI/DVI 显示

文章目录 前言一、HDMI 与 DVI 的区别与联系1.1 DVI 接口含义1.2 HDMI 接口含义1.3 HDMI 与 DVI 的区别1.4 HDMI 与 DVI 的兼容性1.5 HDMI 与 DVI 接口对比 二、DVI 数据链路介绍2.1 输入接口层2.2 TMDS 发送器2.3 TMDS 接收器2.4 输出接口层 三、传输原理与实现3.1 TMDS原理3.…

express框架使用express-generator工具

1.全局安装 npm install -g express-generator 2.检测是否安装成功 express -h 3. 快速创建Express应用程序的工具 express -e express-generator 说明&#xff1a;express-e和express-generator都是用于快速创建Express应用程序的工具。express-e是一个命令行工具&#xff0…

基于Javaweb实现ATM机系统开发实战(九)存款功能实现

先看前端界面确定后端需要处理的参数&#xff0c;把一些参数进行修改&#xff1a; <% page language"java" contentType"text/html; charsetUTF-8" pageEncoding"UTF-8"%> <% taglib prefix"c" uri"http://java.sun.com…

ubuntu使用WHEELTE N100并用rviz显示

写在最开头&#xff0c;如果wheeltec n100被自己改动过参数导致无法读取数据&#xff0c;建议在window的上位机中恢复出厂设置并重新上电&#xff0c;在转入ubuntu。因为我就是这个问题&#xff0c;客服远程操控才帮我解决的。 所有官方资料共享&#xff0c;侵删&#xff1a; …

bug:file name too long文件名超出系统最大限制

各操作系统支持最长的文件和目录名称长度&#xff08;Linux、Win、Mac&#xff09; 今天开发需求的时候发现无法新建文件&#xff0c;提示file name too lang&#xff0c;于是翻阅和查询了一些资料&#xff0c;发现不同操作系统下文件名和目录名最长的长度不同。 操作系统文件名…

Live800在线客服系统:工单系统如何提升企业服务效率?

随着企业规模的扩大和客户需求的增加&#xff0c;如何有效地管理客户服务日益成为企业发展过程中重要的一环。作为客户服务的重要支撑系统之一&#xff0c;工单系统被越来越多的企业所采用。那么工单系统究竟是如何帮助企业提升服务效率的呢&#xff1f;本文将从工单的分配、追…

LeetCode[470]用Rand7()实现Rand10()

难度&#xff1a;Medium 题目&#xff1a; 给定方法 rand7 可生成 [1,7] 范围内的均匀随机整数&#xff0c;试写一个方法 rand10 生成 [1,10] 范围内的均匀随机整数。 你只能调用 rand7() 且不能调用其他方法。请不要使用系统的 Math.random() 方法。 每个测试用例将有一个内部…

Android Java代码与JNI交互 JNI访问Java构造方法(九)

🔥 Android Studio 版本 🔥 🔥 创建包含JNI相关函数类 JNIConstructorClass.java 🔥 package com.cmake.ndk1.jni;import com.cmake.ndk1.model.Animal;public class JNIConstructorClass {static {System.loadLibrary("constructor-class-lib");}public …

Django_获取api接口的传参

目录 当参数为form-data 或者x-www-form-urlencoded类型时&#xff0c;使用request.POST获取到参数 当参数为raw类型时&#xff0c;使用request.body获取到参数&#xff0c;获取的参数需要经过处理才能使用 源码等资料获取方法 当参数为form-data 或者x-www-form-urlencoded…

连接区块链节点的 JavaScript 库 web3.js

文章目录 前言web3.js 介绍web3.js安装web3.js库模块介绍连接区块链节点向区块链网络发送数据查询区块链网络数据 前言 通过前面的文章我们可以知道基于区块链开发一个DApp&#xff0c;而DApp结合了智能合约和用户界面&#xff08;客户端&#xff09;&#xff0c;那客户端是如…

ubuntu20离线安装nodejs、GO、go.rice及yarn

虽然是离线安装&#xff0c;但该有的安装包还是需要的… 目录 1、安装nodejs1.1查看本地是否存在nodejs1.2创建nodejs文件夹1.3下载nodejs二进制文件1.4解压并改名1.5添加软连接 2安装GO2.1创建go文件夹2.2下载go二进制文件2.3解压文件2.4添加环境变量2.5设置sudo可执行go命令…

stm32(SPI读写W25Q18)

SPI 是什么&#xff1f; SPI是串行外设接口&#xff08;Serial Peripheral Interface&#xff09;的缩写&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总 线&#xff0c;并且在芯片的管脚上只占用四根线&#xff0c;节约了芯片的管脚&#xff0c;同时为PC…