首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >深入理解指针(下)

深入理解指针(下)

作者头像
承渊政道
发布2025-12-18 16:58:22
发布2025-12-18 16:58:22
2510
举报

1. 字符指针变量

在指针的类型中我们知道有⼀种指针类型为字符指针 char* ;

使用方式:

int main()

{

const char* pstr = "hello csdn.";

printf("%s\n", pstr);

return 0;

}

代码 const char* pstr = "hello csdn."; 特别容易让同学以为是把字符串 hello csdn 放

到字符指针 pstr ⾥了,但是本质是把字符串 hello csdn. ⾸字符的地址放到了pstr中。

上⾯代码的意思是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量 pstr 中。

《剑指offer》中收录了⼀道和字符串相关的笔试题,我们⼀起学习⼀下:

代码语言:javascript
复制
#include <stdio.h>
 int main()
 {
 char str1[] = "hello csdn.";
 char str2[] = "hello csdn.";
 const char *str3 = "hello csdn.";
 const char *str4 = "hello csdn.";
 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相同。

2. 数组指针变量

2.1 数组指针变量是什么?

之前我介绍了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。

数组指针变量是指针变量?还是数组?

答案是:指针变量。

我们已经熟悉:

整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。

浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

数组指针变量:

 int (*p)[10];

解释:p先和*结合,说明p是⼀个指针变量,然后指针指向的是⼀个⼤⼩为10个整型的数组。所以p是⼀个指针,指向⼀个数组,叫数组指针。

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

2.2 数组指针变量怎么初始化

数组指针变量是⽤来存放数组地址的,那怎么获得数组的地址呢?就是我之前介绍的&数组名

 int arr[10] = {0};   &arr;//得到的就是数组的地址

如果要存放个数组的地址,就得存放在数组指针变量中,如下:

int(*p)[10] = &arr;

数组指针类型解析:

 int (*p) [10] = &arr;

 | | |

 | | |

 | | p指向数组的元素个数

 | p是数组指针变量名

 p指向的数组的元素类型

3. ⼆维数组传参的本质

有了数组指针的理解,我就能够介绍⼀下⼆维数组传参的本质了。

二维数组传参:实参写的也是数组名,形参也可以写成二维数组的形式。

过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我是这样写的:

代码语言:javascript
复制
#include <stdio.h>
 void test(int a[3][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 ", a[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;
 }

这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?

⾸先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。 如图:

所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类

型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀

⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

代码语言:javascript
复制
 #include <stdio.h>
 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;
 }

总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

4. 函数指针变量

4.1 函数指针变量的创建

什么是函数指针变量呢?

根据前⾯介绍整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论:

函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。

那么函数是否有地址呢?

我们做个测试:

代码语言:javascript
复制
#include <stdio.h>
 void test()
 {
 printf("hehe\n");
 }
 int main()
 {
 printf("test: %p\n", test);
 printf("&test: %p\n", &test);
 return 0;
 }

确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅式获得函数的地址。

如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。如下:

代码语言:javascript
复制
void test()
 {
 printf("hehe\n");
 }
 void (*pf1)() = &test;
 void (*pf2)()= test;
 int Add(int x, int y)
 {
 return x+y;
 }
 int(*pf3)(int, int) = Add;
 int(*pf3)(int x, int y) = &Add;

函数指针类型解析:

 int (*pf3) (int x, int y)

 | | ------------

 | | |

 | | pf3指向函数的参数类型和个数的交代

 | 函数指针变量名

 pf3指向函数的返回类型

 int (*) (int x, int y)

4.2 函数指针变量的使⽤

通过函数指针调⽤指针指向的函数。

代码语言:javascript
复制
#include <stdio.h>
 int Add(int x, int y)
 {
 return x+y;
 }
 int main()
 {
 int(*pf3)(int, int) = Add;
 printf("%d\n", (*pf3)(2, 3));
 printf("%d\n", pf3(3, 5));
 return 0;
 }
4.3 typedef 关键字

typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。

⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:

 typedef unsigned int uint;  //将unsigned int 重命名为uint

如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写:

 typedef int* ptr_t;

但是对于数组指针和函数指针稍微有点区别:

⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

 typedef int(*parr_t)[5]; //新的类型名必须在*的右边

函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

 typedef void(*pfun_t)(int);//新的类型名必须在*的右边

那么要简化代码,可以这样写:

 typedef void(*pfun_t)(int);

 pfun_t signal(int, pfun_t);

5. 函数指针数组

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

⽐如:

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

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

 int (*parr1[3])();

 int *parr2[3]();

 int (*)() parr3[3];

答案是:parr1

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?

是 int (*)() 类型的函数指针。

6. 转移表

函数指针数组的⽤途:转移表

举例:计算器(加减乘除)的⼀般实现:

代码语言:javascript
复制
//方法一:通过分支和循环语句实现计算器的加减乘除
#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(" 0:exit \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;
 }
代码语言:javascript
复制
//方法二:使⽤函数指针数组的实现
#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 }; //转移表
 do
 {
 printf("*************************\n");
 printf(" 1:add 2:sub \n");
 printf(" 3:mul 4:div \n");
 printf(" 0:exit \n");
 printf("*************************\n");
 printf( "请选择:" );
 scanf("%d", &input);
 if ((input <= 4 && input >= 1))
 {
 printf( "输⼊操作数:" );
 scanf( "%d %d", &x, &y);
 ret = (*p[input])(x, y);
 printf( "ret = %d\n", ret);
 }
 else if(input == 0)
 {
 printf("退出计算器\n");
 }
 else
 {
 printf( "输⼊有误\n" );
 }
 }while (input);
 return 0;
 }

7. 回调函数是什么?

回调函数就是⼀个通过函数指针调⽤的函数。

如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数

时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。

代码语言:javascript
复制
#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;
}
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 = 1;
do
{
printf("*************************\n");
printf(" 1:add2:sub \n");
printf(" 3:mul4:div \n");
printf("*************************\n");
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;
}

8. qsort使⽤举例

8.1 使⽤qsort函数排序整型数据
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
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;
 }
8.2 使⽤qsort排序结构数据
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu 
 {
 char name[20];
 int age;
 };
 int cmp_stu_by_age(const void* e1, const void* e2)
 {
 return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
 }
 int cmp_stu_by_name(const void* e1, const void* e2)
 {
 return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
 }
 void test2()
 {
 struct Stu s[] = { {"zhangsan", 30}, {"lisi", 40}, {"wangwu", 50} };
 int sz = sizeof(s) / sizeof(s[0]);
 qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
 }
 void test3()
 {
 struct Stu s[] = { {"zhangsan", 30}, {"lisi", 40}, {"wangwu", 50} };
 int sz = sizeof(s) / sizeof(s[0]);
 qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
 }
 int main()
 {
 test2();
 test3();
 return 0;
 }

9. qsort函数的模拟实现

使⽤回调函数,模拟实现qsort(采⽤冒泡的⽅式)。

注意:这⾥第⼀次使⽤ void* 的指针,讲解 void* 的作⽤。

代码语言:javascript
复制
#include <stdio.h>
 int int_cmp(const void * p1, const void * p2)
 {
 return (*( int *)p1 - *(int *) p2);
 }
 void _swap(void *p1, void * p2, int size)
 {
 int i = 0;
 for (i = 0; i< size; i++)
 {
 char tmp = *((char *)p1 + i);
 *(( char *)p1 + i) = *((char *) p2 + i);
 *(( char *)p2 + i) = tmp;
 }
 }
 void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
 {
 int i = 0;
 int j = 0;
 for (i = 0; i< count - 1; i++)
 {
 for (j = 0; j<count-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 main()
 {
 int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
 int i = 0;
 bubble(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;
 }

10. sizeof和strlen的对⽐

10.1 sizeof

在介绍操作符的时候,我介绍了 sizeof , sizeof 计算变量所占内存空间⼤⼩的,单位是字

节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。

sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。

⽐如:

代码语言:javascript
复制
#inculde <stdio.h>
 int main()
 {
 int a = 10;
 printf("%d\n", sizeof(a));
 printf("%d\n", sizeof a);
 printf("%d\n", sizeof(int));
 return 0;
 }
10.2 strlen

strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:

 size_t strlen ( const char * str );

统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。

strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。

代码语言:javascript
复制
#include <stdio.h>
int main()
{
char arr1[3] = {'a', 'b', 'c'};
char arr2[] = "abc";
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
printf("%d\n", sizeof(arr1));
printf("%d\n", sizeof(arr2));
return 0;
}
10.3 sizeof 和 strlen的对⽐

sizeof

strlen

1.sizeof是操作符

1.strlen是库函数,需要包含头文件<string.h>

2.sizeof计算操作数所占内存的 ⼤⼩,单位是字节

2.srtlen是求字符串⻓度的,统计的是 \0 之前字符的个数

3.不关注内存中存放什么数据

3.关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能 会越界

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

11.1 ⼀维数组
代码语言:javascript
复制
int a[] = {1,2,3,4};
printf("%zd\n",sizeof(a));//sizeof(数组名),4*4=16
printf("%zd\n",sizeof(a+0));//a就是首元素的地址,a+0还是首元素地址,计算的是地址大小,4/8
printf("%zd\n",sizeof(*a));//a就是首元素的地址,*a就是首元素,计算的是首元素的大小,4/8
printf("%zd\n",sizeof(a+1));//a就是首元素的地址,a+1是第二个元素的地址,计算地址大小,4/8
printf("%zd\n",sizeof(a[1]));//计算的是第二个元素的大小:4
printf("%zd\n",sizeof(&a));//&a->取出的是数组的地址,是地址都是4/8个字节
printf("%zd\n",sizeof(*&a));//*&a这里解引用访问到16个字节
printf("%zd\n",sizeof(&a+1));//&a+1是跳过整个数组,指向4的后面,其实也是地址,就是4/8
printf("%zd\n",sizeof(&a[0]));//&a[0]就是首元素,4/8个字节
printf("%zd\n",sizeof(&a[0]+1));//&a[0]+1是第二个元素的地址4/8
11.2 字符数组

代码1:

代码语言:javascript
复制
char arr[] = {'a','b','c','d','e','f'};
 printf("%zd\n", sizeof(arr));//arr是数组名,单独放在sizeof内部,计算的是数组的大小->6
 printf("%zd\n", sizeof(arr+0));//arr是数组名,是数组首元素的地址,arr+0也是地址->4/8
 printf("%zd\n", sizeof(*arr));//arr是数组首元素的地址,*arr是首元素,大小是1个字节
 printf("%zd\n", sizeof(arr[1]));//arr[1]是第二个元素,大小是1个字节
 printf("%zd\n", sizeof(&arr));//&arr是数组的地址,数组的地址也是地址,大小是4/8个字节
 printf("%zd\n", sizeof(&arr+1));//&arr是数组的地址,&arr+1就是跳过整个数组,指向f后面->4/8
 printf("%zd\n", sizeof(&arr[0]+1));//&arr[0]+1是第二个元素的地址,大小是4/8

代码2:

代码语言:javascript
复制
char arr[] = {'a','b','c','d','e','f'};
 printf("%zd\n", strlen(arr));//随机值,arr是首元素的地址,数组中没有\0,一直往后找,什么时候遇到\0不清楚
 printf("%zd\n", strlen(arr+0));//随机值,arr是首元素的地址,arr+1依然是首元素的地址
 printf("%zd\n", strlen(*arr));//*arr是首元素,是'a'-97,传给strlen函数,97会被当做地址,以97作为地址会形成非法访问,运行程序会崩溃
 printf("%zd\n", strlen(arr[1]));//arr[1]是第二个元素,'b'-98,传给strlen函数,98会被当做地址,以98作为地址会形成非法访问,运行程序会崩溃
 printf("%zd\n", strlen(&arr));//&arr是数组的地址,虽然是数组地址,值和首元素地址一样,strlen依然是从第一个字符的位置开始向后找\0,会得到随机值
 printf("%zd\n", strlen(&arr+1));//&arr+1是f后面的地址,什么时候遇到\0不知道,随机值
 printf("%zd\n", strlen(&arr[0]+1));//&arr[0]+1就是第二个元素的地址,得到的也是随机值

代码3:

代码语言:javascript
复制
char arr[] = "abcdef";
 printf("%zd\n", sizeof(arr));//arr表示整个数组,计算的是整个数组的大小单位字节,7*1=7
 printf("%zd\n", sizeof(arr+0));//arr就是数组元素的地址,arr+0还是数组元素的地址4/8
 printf("%zd\n", sizeof(*arr));//arr就是数组首元素的地址,*arr就是首元素,大小就是1个字节
 printf("%zd\n", sizeof(arr[1]));//arr[1]数组的第一个元素,计算的就是第二个元素大小,-1
 printf("%zd\n", sizeof(&arr));//arr表示整个数组,&arr取出的是整个数组的地址,地址大小是4/8
 printf("%zd\n", sizeof(&arr+1));//&arr+1是跳过这个数组后的地址,地址大小是4/8个字节
 printf("%zd\n", sizeof(&arr[0]+1));//&arr[0]是数组首元素的地址,&arr[0]+1是数组第二个元素的地址4/8

代码4:

代码语言:javascript
复制
char arr[] = "abcdef";
 printf("%zd\n", strlen(arr));//arr是数组首元素的地址,从第一个元素开始,统计\0之前字符个数->6
 printf("%zd\n", strlen(arr+0));//arr是数组首元素的地址,arr+0还是数组首元素的地址->6
 printf("%zd\n", strlen(*arr));//arr是数组首元素地址,*arr就是首元素了,*arr==‘a’==97,非法访问内存了,程序会崩溃
 printf("%zd\n", strlen(arr[1]));//arr[1]是第二个元素==‘b’==98,非法访问内存,程序崩溃
 printf("%zd\n", strlen(&arr));////&arr取出的是数组的地址,数组地址和首元素地址是同一个值,strlen也是从第一个字符开始向后统计\0之前的字个数->6
 printf("%zd\n", strlen(&arr+1));//随机值
 printf("%zd\n", strlen(&arr[0]+1));//&arr[0]+1是第二个元素的地址,\0之前有五个元素,所以是5

代码5:

代码语言:javascript
复制
char *p = "abcdef";
 printf("%zd\n", sizeof(p));//p是一个指针,大小就是4/8字节
 printf("%zd\n", sizeof(p+1));//p中存放的是‘a’的地址,p+1是‘b’地址,大小就是4/8个字节
 printf("%zd\n", sizeof(*p));//*p==‘a’->1
 printf("%zd\n", sizeof(p[0]));//p[0]==*(p+0)==*p->1
 printf("%zd\n", sizeof(&p));//&p是指针变量p的地址,是地址大小就是4/8个字节
 printf("%zd\n", sizeof(&p+1));//&p+1还是地址大小是4/8个字节,&p+1是指向p变量的后边
 printf("%zd\n", sizeof(&p[0]+1));//&p[0]+1是‘b’的地址,大小是4/8个字节

代码6:

代码语言:javascript
复制
char *p = "abcdef";
 printf("%zd\n", strlen(p));//p里边存放是‘a’的地址—>6
 printf("%zd\n", strlen(p+1));//p+1是‘b’的地址
 printf("%zd\n", strlen(*p));//非法访问,*p是‘a’
 printf("%zd\n", strlen(p[0]));//非法访问
 printf("%zd\n", strlen(&p));//随机值,&p是p这个变量地址,strlen就是从p这块空间起始地址开始向后找\0
 printf("%zd\n", strlen(&p+1));//随机值,&p+1是p变量后边的地址,从这个位置后的内存数据不知道,什么时候会遇到\0都不确定
 printf("%zd\n", strlen(&p[0]+1));//5
11.3 ⼆维数组
代码语言:javascript
复制
int a[3][4] = {0};
 printf("%d\n",sizeof(a));//48:a是数组名,单独放在sizeof内部,表示整个数组,计算的是整个数组的大小,单位是字节3*4*4=48
 printf("%d\n",sizeof(a[0][0]));//4:a[0][0]是第一行第一个元素
 printf("%d\n",sizeof(a[0]));//16:a[0]是第一行这个一维数组的数组名,数组名单单独放在sizeof内部,计算第一行这个一维数组的大小
 printf("%d\n",sizeof(a[0]+1));//4/8 a[0]是数组名,这里表示数组首元素地址,第一行第一个元素地址,+1就是第一行第二个元素地址
 printf("%d\n",sizeof(*(a[0]+1)));//*(a[0]+1)是第一行第二个元素->大小是4个字节
 printf("%d\n",sizeof(a+1));//a+1就是第二行的地址,是地址就是4/8个字节
 printf("%d\n",sizeof(*(a+1)));//16:a+1是第二行地址,*(a+1)得到的就是第二行
 printf("%d\n",sizeof(&a[0]+1));//4/8 a[0]第一行数组名,&a[0]取出的是第一行这个一维数组的地址,&a[0]+1就是第二行地址
 printf("%d\n",sizeof(*(&a[0]+1)));//*(&a[0]+1)第二行,计算第二行大小,16个字节
 printf("%d\n",sizeof(*a));//16:*a==*(a+0) 计算第一行的大小,16个字节
 printf("%d\n",sizeof(a[3]));//16:没有越界访问,sizeof内部的表达式是不计算的

数组名的意义:

sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。

&数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。

除此之外所有的数组名都表⽰⾸元素的地址。

12. 指针运算笔试题

12.1 题⽬1:
代码语言:javascript
复制
#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;
 }
12.2 题⽬2:
代码语言:javascript
复制
//在X86环境下
 //假设结构体的⼤⼩是20个字节
 //程序输出的结果是啥?
#include <stdio.h>
 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;
 }
12.3 题⽬3:
代码语言:javascript
复制
#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;
 }
12.4 题⽬4:
代码语言:javascript
复制
//假设环境是x86环境,程序输出的结果是啥?
 #include <stdio.h>
 int main()
 {
 int a[5][5];
 int(*p)[5];
 p = a;
 printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
 return 0;
 }
12.5 题⽬5:
代码语言:javascript
复制
#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;
 }
12.6 题⽬6:
代码语言:javascript
复制
 #include <stdio.h>
 int main()
 {
 const char *a[] = {"work","at","alibaba"};
 const char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
 }
12.7 题⽬7:
代码语言:javascript
复制
#include <stdio.h>
 int main()
 {
 const char *c[] = {"ENTER","NEW","POINT","FIRST"};
 const char**cp[] = {c+3,c+2,c+1,c};
 const 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;
 }

以上的7个题目大家可以自己去体会体会,看看结果为什么是这些。

以上就是今天的博客内容了,希望能够帮助到读者朋友们!

我们一起继续加油努力💪!

本篇完结,点赞收藏加关注,找到小编不迷路,感谢大家🙏🤝!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 字符指针变量
  • 2. 数组指针变量
    • 2.1 数组指针变量是什么?
    • 2.2 数组指针变量怎么初始化
  • 3. ⼆维数组传参的本质
  • 4. 函数指针变量
    • 4.1 函数指针变量的创建
    • 4.2 函数指针变量的使⽤
    • 4.3 typedef 关键字
  • 5. 函数指针数组
  • 6. 转移表
  • 7. 回调函数是什么?
  • 8. qsort使⽤举例
    • 8.1 使⽤qsort函数排序整型数据
    • 8.2 使⽤qsort排序结构数据
  • 9. qsort函数的模拟实现
  • 10. sizeof和strlen的对⽐
    • 10.1 sizeof
    • 10.2 strlen
    • 10.3 sizeof 和 strlen的对⽐
  • 11. 数组和指针笔试题解析
    • 11.1 ⼀维数组
    • 11.2 字符数组
    • 11.3 ⼆维数组
  • 12. 指针运算笔试题
    • 12.1 题⽬1:
    • 12.2 题⽬2:
    • 12.3 题⽬3:
    • 12.4 题⽬4:
    • 12.5 题⽬5:
    • 12.6 题⽬6:
    • 12.7 题⽬7:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档