目录
1指针和数组笔试题解析
一维数组
说出下面代码的输出结果
//一维数组
#include<stdio.h>
int main()
{
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));
return 0;
}
解析:
//一维数组
//数组名的意义:
//1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
//2. & 数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
//3. 除此之外所有的数组名都表示首元素的地址。
#include<stdio.h>
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 整个数组的大小(取地址解引用相当于最上面的printf)
printf("%d\n", sizeof(&a + 1));//4/8 跳过a数组的下个空间的地址
printf("%d\n", sizeof(&a[0]));//4/8 第一个元素的地址
printf("%d\n", sizeof(&a[0] + 1));//4/8 第二个元素的地址
return 0;
}
字符数组
说出下面代码的输出结果
#include<stdio.h>
#include<string.h>
int main()
{
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));
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));
return 0;
}
解析:
#include<stdio.h>
#include<string.h>
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
printf("%d\n", strlen(arr));//随机数(没有\0)
printf("%d\n", strlen(arr + 0));//上面的随机数(没有\0)
printf("%d\n", strlen(*arr));//(把97当做地址)错误
printf("%d\n", strlen(arr[1]));//错误
printf("%d\n", strlen(&arr));//上面的随机数(没有\0)
printf("%d\n", strlen(&arr + 1));//上面的随机数-6(没有\0)
printf("%d\n", strlen(&arr[0] + 1));//上面的随机数-1(没有\0)
return 0;
}
说出下面代码的输出结果
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
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));
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));
return 0;
}
解析:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7 (加上\0)!!!
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
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr + 0));//6
printf("%d\n", strlen(*arr));//错误emm
printf("%d\n", strlen(arr[1]));//错误emm
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr + 1));//随机数
printf("%d\n", strlen(&arr[0] + 1));//5
return 0;
}
说出下面代码的输出结果
#include<stdio.h>
#include<string.h>
int main()
{
char* p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p + 1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p + 1));
printf("%d\n", sizeof(&p[0] + 1));
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));
return 0;
}
解析:
#include<stdio.h>
#include<string.h>
int main()
{
char* p = "abcdef";
printf("%d\n", sizeof(p));//4/8 指针的大小
printf("%d\n", sizeof(p + 1));//4/8 第二个元素的地址!
printf("%d\n", sizeof(*p));//1 第一个元素的大小!
printf("%d\n", sizeof(p[0]));//1 同上emm
printf("%d\n", sizeof(&p));//4/8
printf("%d\n", sizeof(&p + 1));//4/8
printf("%d\n", sizeof(&p[0] + 1));//4/8
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5
printf("%d\n", strlen(*p));//错误
printf("%d\n", strlen(p[0]));//错误
printf("%d\n", strlen(&p));//随机数 从p的地址算,p里面存的地址!!
printf("%d\n", strlen(&p + 1));//和上面不同的随机数
printf("%d\n", strlen(&p[0] + 1));//5
return 0;
}
二维数组
说出下面代码的输出结果
#include<stdio.h>
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<stdio.h>
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//12*4=48 整个数组大小
printf("%d\n", sizeof(a[0][0]));//4 第一个元素大小
printf("%d\n", sizeof(a[0]));//16 下一a[0]不是单独放在内部,不表示第一行
printf("%d\n", sizeof(a[0] + 1));//4/8 a[0]是整形的地址,+1跳过一个整形emm
printf("%d\n", sizeof(*(a[0] + 1)));//4 第一行第二个元素的大小
printf("%d\n", sizeof(a + 1));//4/8 第二行的地址
printf("%d\n", sizeof(*(a + 1)));//16 第二行大小 相当于a[1]
printf("%d\n", sizeof(&a[0] + 1));//4/8 第二行地址
printf("%d\n", sizeof(*(&a[0] + 1)));//16 第二行大小 同上上
printf("%d\n", sizeof(*a));//16 第一行大小等价于*(a+0)等价于a[0]
printf("%d\n", sizeof(a[3]));//16 3+5 1.值属性:8 2.类型属性:int
//a[3] 值属性越界了,但是类型属性是int [4] 编译器推算的,不会真正取地址emm
//且sizeof内部是不进行计算的
return 0;
}
总结:
数组名的意义:
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。
2指针笔试题一
笔试题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;
}
//程序的结果是什么?
解析:
#include<stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1); //整个数组地址+1跳过一个数组 本来的类型是int*[5]
printf("%d,%d", *(a + 1), *(ptr - 1));//2,5 都是整形指针,只能跳过4个字节emm
return 0;
}
笔试题2:
#include<stdio.h>
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
解析:
#include<stdio.h>
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);//+16进制的1,就是1 这个结构体指针+1跳过20字节
//0x100000变成0x100014 (16进制14就是10进制的20)
printf("%p\n", (unsigned long)p + 0x1); //整形+1就是+1 0x100001
printf("%p\n", (unsigned int*)p + 0x1);//跳过4个字节 0x100004
return 0;
}
笔试题3:
#include<stdio.h>
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<stdio.h>
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1); //跳过一个数组
int* ptr2 = (int*)((int)a + 1); //a的地址转化为int +1
//一个地址给一个字节,差一个字节
printf("%x,%x", ptr1[-1], *ptr2); //ptrl[-1]==*(ptr1-1)==4
return 0;
}
//结果是4,2000000 看图
看看佬的讲解:


笔试题4:
#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;
}
解析:
#include<stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//()不是{} 是逗号表达式! 天坑 1 3
// 5 0
// 0 0
int* p;
p = a[0];
printf("%d", p[0]);//p==a[0] p[0]==a[0][0]==1
return 0;
}
笔试题5:
#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;
}
解析:
#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;
}
//a的类型是int(*)[5]+1跳过5*4个字节 p的类型是int(*)[4] +1跳过4*4个字节
//答案是FFFFFFFC,-4 看图
能力有限,再看看佬的讲解:


笔试题6:
#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;
}
解析:
#include<stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1); //&aa整个数组的地址+1跳过整个数组
int* ptr2 = (int*)(*(aa + 1));//首元素(首元素是第一行)地址+1,第二行地址
//第二行首元素地址解引用得到6 int*没有意义,本来就是 *(aa + 1)相当于aa[1]
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//指针减一(前一个元素):10,5
return 0;
}
笔试题7:
#include<stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
解析:
#include<stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };//指针数组
char** pa = a;
pa++;//跳过数组里的一个元素,跳到at的地址
printf("%s\n", *pa);//at(%s从a开始向后打印字符串)
return 0;
}
笔试题8:
#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;
}
解析:哪一步不懂可以看看放在后面的佬的图
#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);//++cpp==c+2 解引用得c+2内容,即POINT地址 再解引用得POINT
printf("%s\n", *-- * ++cpp + 3);//++cpp在上一行基础在+1得到c+1地址
//解引用得c+1内容 再--得到c的内容 再解引用得到ENTER的地址即E的地址
//再+3得到第二个E的地址 打印出ER
printf("%s\n", *cpp[-2] + 3);//*cpp[-2]==*(*(cpp-2))
//cpp在上面内容-2得到c+3的内容 解引用得到c+3地址 再解引用得到FIRST地址
//再+3得到S的地址 打印出ST
printf("%s\n", cpp[-1][-1] + 1);//cpp[-1][-1]==*(*(cpp-1)-1)
//++cpp等价于cpp=cpp+1 cpp变了 但cpp-2 cpp没有变 还是指向c+1
//cpp在上面内容-1得到c+2的内容 解引用得到c+2地址 再-1得到c+1的地址
//再解引用得到NEW的地址 再+1得到E的地址 打印出EW
return 0;
}
可以一边看文字一边画图理解:(也可以配合调试)

佬的讲解:

3笔试题二
3.1指向函数指针数组的指针
声明一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针,该函数的返回值是int,
参数是int*,正确的是( )
A.(int *p[10])(int*)
B.int [10]*p(int *)
C.int (*(*p)[10])(int *)
D.int ((int *)[10])*p
解析:
A选项,第一个括号里是一个完整定义,第二个括号里是个类型,四不像。BD选项,[]只能在标识符右边,双双排除。只有C是能编过的。
3.2函数指针
定义一个函数指针,指向的函数有两个int形参并且返回一个函数指针,返回的指针指向一个有一个
int形参且返回int的函数。下面哪个是正确的?( )
A.int (*(*F)(int, int))(int)
B.int (*F)(int, int)
C.int (*(*F)(int, int))
D.*(*F)(int, int)(int)
解析:
D类型不完整先排除,然后看返回值,B的返回值是int,C的返回值是int *,故选A。判断返回值类型只需要删掉函数名/函数指针和参数列表再看就行了。int (*(*F)(int, int))(int)删掉(*F)(int, int)后剩下int (*)(int),符合题意。
3.3函数指针
设有以下函数void fun(int n,char *s){……},则下面对函数指针的定义和赋值均是正确的是:( )
A.void (*pf)(int,char); pf=&fun;
B.void (*pf)(int n,char *s); pf=fun;
C.void *pf(); *pf=fun;
D.void *pf(); pf=fun;
解析:
CD前半句压根就不是定义而是声明,A选项参数列表的第二个参数错了。应为char *,B选项正确。需要说明的是,对于函数名来说,前面的&和*都会被忽略,所以fun前面加不加取地址都没区别。只有定义出的函数指针变量(如题面中的pf)加上&后才会变成二级函数指针。
3.4指针+-
#include <stdio.h>
int main()
{
int aa[2][5] = { 10,9,8,7,6,5,4,3,2,1 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
A.1, 6
B.10, 5
C.10, 1
D.1, 5
解析:
&aa的类型是int (*)[2][5],加一操作会导致跳转一个int [2][5]的长度,直接跑到刚好越界的位置。减一以后回到最后一个位置1处。*(aa + 1)相当于aa[1],也就是第二行的首地址,自然是5的位置。减一以后由于多维数组空间的连续性,会回到上一行末尾的6处。故选A。
3.5数组指针
#include <stdio.h>
int main()
{
int a[5] = { 5, 4, 3, 2, 1 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
A.5, 1
B.4, 1
C.4, 2
D.5, 2
解析:
*(a + 1)等同于a[1],第一个是4,a的类型是int [5],&a的类型就是int(*)[5],是个数组指针。所以给int(*)[5]类型加一,相当于加了一个int [5]的长度。也就是这个指针直接跳过了a全部的元素,直接指在了刚好越界的位置上,然后转换成了int *后再减一,相当于从那个位置向前走了一个int,从刚好越觉得位置回到了1的地址处,所以第二个是1,故选B。
3.6函数参数设计
下面代码中print_arr函数参数设计哪个是正确的?( )
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr(arr, 3, 5);
A.void print_arr(int arr[ ][ ],int row, int col);
B.void print_arr(int* arr, int row, int col);
C.void print_arr(int (*arr)[5], int row, int col);
D.void print_arr(int (*arr)[3], int row, int col);
解析:
二维数组相当于数组的数组,传到子函数变成数组的指针。int arr[3][5]相当于是3个元素的arr,每个元素是int [5],所以int [5]是类型说明不能省略。丢失的信息只有数组的元素个数,也就是3。A丢了类型中的5,B选项arr是首元素地址,首元素是第一行数组,指针层次错了。D选项5写成了3,
3.7函数设计
下面test函数设计正确的是:( )
char* arr[5] = {"hello", "bit"};
test(arr);
A.void test(char* arr);
B.void test(char** arr);
C.void test(char arr[5]);
D.void test(char* arr[5]);
解析:
指针的数组传递给子函数变为指针的指针,也就是二级指针。但是允许中括号写法,
写成char **arr、char *arr[]、char * arr[5]都可。所以BD正确。
3.8(编程)杨氏矩阵
杨氏矩阵
有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,
请编写程序在这样的矩阵中查找某个数字是否存在。
要求:时间复杂度小于O(N);
解题:
如杨氏矩阵:
1 2 3
4 5 6
7 8 9
再如:
1 2 3
2 3 4
3 4 5
查找某个元素——若遍历二维数组,若数组有n个元素,
则最坏情况下找n次,即时间复杂度为O(N),(时间复杂度是数据结构的内容,现在不懂可以略过)
而题目要求时间复杂度小于n。时间复杂度小于O(N)的算法:
#include<stdio.h>
int find_arr(int arr[3][3], int row, int col, int k)
{
int x = 0, y = col - 1;//从右上角开始找
while (x < row && y >= 0)
{
if (arr[x][y] > k)
{
y--;
}
else if (arr[x][y] < k)
{
x++;
}
else
{
printf("%d %d\n", x, y);
return 1;
}
}
return 0;
}
int main()
{
int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
// 1 2 3
// 4 5 6
// 7 8 9
//若要找的元素是k=2,比右上角的3小,因为3是一列中最小的元素,所以去掉一列;
// 若要找的元素是k=7,比右上角的3大,因为3是一行中最大的元素,
// 所以去掉一行;7比在剩下的元素中右上角的6还要大,则又去掉一行;
// 7与剩下元素中右上角的9小,则在这一行中查找7,
// (因为9已经是剩下元素所在列中最小的元素)去掉一列;
// 8比7大说明还在8的左边,去掉一列;7与7相等则找到;
// 如果7还找不到则结果就是找不到。
int k = 0;
scanf("%d", &k);
//找到返回1 找不到返回0
//传arr和行、列、要找的元素
if (find_arr(arr, 3, 3, k))
{
printf("找到了\n");
}
else
{
printf("找不到\n");
}
return 0;
}
虽然封装了函数,但是找到的下标是自己打印的,这不是好的解决办法。
解决办法:用函数查找,用函数带回或返回找不到。
而且return不能直接带回两个值。
更好的代码实现:
#include<stdio.h>
int find_arr(int arr[3][3], int* row, int* col, int k)
{
int x = 0, y = *col - 1;//从右上角开始找
while (x < *row && y >= 0)
{
if (arr[x][y] > k)
{
y--;
}
else if (arr[x][y] < k)
{
x++;
}
else
{
*row = x;
*col = y;
return 1;
}
}
return 0;
}
int main()
{
int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
int k = 0, x = 3, y = 3;
scanf("%d", &k);
//找到返回1 找不到返回0
//传arr和行、列、要找的元素
//传行和列的地址,则在函数中所求的下标就可以通过这两个地址放到x、y中
if (find_arr(arr, &x, &y, k))
{
printf("找到了,下标是%d,%d\n",x,y);
}
else
{
printf("找不到\n");
}
return 0;
}
//这里传& x和& y的好处:
//既可以带进函数两个值:3和3,在函数中使用;
//又可以在函数结束的时候带回值——这种设计是返回型参数(输出型参数),
//用指针修改,把数值带回去。
3.9(编程)模拟实现qsort
自己敲出一个模拟的qsort并应用
(代码在上一篇)C语言进阶⑪(指针_上)(知识点和对应练习)回调函数模拟实现qsort。_GR C的博客-优快云博客(有时间自己敲,不懂再看一眼)
(还有一些和字符串有关的指针作业放在下一篇了)
本篇完。