一、什么是指针?
- 指针就是地址,内存单元的编号。
二、指针变量和地址
1.(&)操作符为取地址操作符。
#include <stdio.h>
int main()
{
int a = 10;
&a;
printf("%p\n", &a);
return 0;
}
在本段代码中, &a则表示为取出a的地址。
2.指针变量和(*)解引用操作符
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a; //取出a的地址并存储到指针变量pa中
return 0;
}
在本段代码中,* 是在说明 pa 是指针变量,int 是在说明 pa 指向的是整型(int)类型的对象。
#include <stdio.h>
int main()
{
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));
return 0;
}
指针变量的大小取决于地址的大小。32位平台下地址是32个bit位(即4个字节);64位平台下地址是64个bit位(即8个字节)。
【注】指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
三、指针变量类型的意义
1.指针的解引用
指针的类型决定了对指针解引⽤的时候有多大的权限,即⼀次能操作几个字节。
下面请看这两段代码:
//代码1
#include <stdio.h>
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
}
//代码2
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
return 0;
}
在这两段代码中,char* 的指针解引⽤就只能访问⼀个字节,而 int* 的指针的解引⽤就能访问四个字节。
四、const
1.const修饰变量
- const修饰变量使得这个变量不能被修改。
请看下面这段代码:
#include <stdio.h>
int main()
{
int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的
return 0;
}
在这段代码中,n被const修饰过后是不能被修改的。否则就会报错。
2.const修饰指针
- const放在*的左边,意思是不能通过指针来修饰指针指向的内容,但是指针变量本身是可以修改的。
- const放在*的右边,意思是不能修改指针变量的指向,但是可以修改指针方向的内容。
请看下面这段代码:
#include <stdio.h>
int main()
{
int m = 100;
n = 10;
const int * p = &n; // const放在*的左边
*p = 0; // 此行会报错
p = &m; // 此行可修改
return 0;
}
在本段代码中, const放在*的左边,n 不可以通过 *p 被修改,但 p 可以修改。
#include <stdio.h>
int main()
{
int m = 100;
n = 10;
int * const p = &n; // const放在*的右边
*p = 0; // 此行可修改
p = &m; // 此行会报错
return 0;
}
在本段代码中, const放在*的右边,p 不可以修改,但 p 指向的对象可以修改。
五、野指针
1.什么是野指针?
- 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
2.造成野指针的原因
- 指针未初始化
- 指针越界访问
- 指针指向的空间释放
3.如何规避野指针?
- 指针初始化
- 小心指针越界
- 指针变量不再使⽤时,及时置NULL,指针使用之前检查有效性
- 避免返回局部变量的地址
六、指针的传址调用
- 传址调用是一种函数调用方式,它通过传递变量的内存地址作为参数,使得在函数内部可以直接修改原始变量的值。
- 当使用传址调用时,被调用函数的形参将成为原始变量的指针,通过该指针可以访问和修改原始变量的内容。这种方式可以在函数内部修改变量的值,并且这些修改对于函数外部是可见的。
- 要进行传址调用,需要在函数声明和定义时使用指针类型的形参,并使用取地址操作符"&"将原始变量的地址传递给函数。
请看下面这段代码:
#include <stdio.h>
void Swap2(int*px, int*py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
在本段代码中,就是通过传递变量a、b 的地址给 Swap2 函数进行传址调用,从而实现两个整型变量值的交换。
七、指针与数组
1.什么是数组名?
- 数组名:就是数组首元素(第⼀个元素)的地址。
- sizeof(数组名):sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
-
&数组名:这里的数组名表示整个数组,取出的是整个数组的地址。
【注】除此之外,任何地方使用数组名,数组名都表示首元素的地址。
2.使用指针访问数组
请看下面这段代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};
//输⼊
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
//输⼊
int* p = arr;
for(i=0; i<sz; i++)
{
scanf("%d", p+i);
//scanf("%d", arr+i);//也可以这样写
}
//输出
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));
// printf("%d ", p[i]); // 也可以这样写
}
return 0;
}
- 数组名arr是数组首元素的地址,可以赋值给p,其实数组名arr和p在这⾥是等价的。那我们可以使用arr[i]可以访问数组的元素。
- 在本段代码中,在倒数第5行的位置,将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i] 是等价于 *(p+i)。同理arr[i] 应该等价于 *(arr+i),数组元素的访问在编译器处理的时候,也是转换成⾸元素的地址+偏移量求出元素的地址,然后解引⽤来访问的。
3.一维数组传参的本质
- 通过上面的知识我们知道数组名是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组首元素的地址。
- 所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。那么在函数内部我们写 sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。正是因为函数的参数部分的本质是指针,所以在函数内部是没办法求的数组元素个数的。
【总结】⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
代码示例:
void test(int arr[])//参数写成数组形式,本质上还是指针
{
printf("%d\n", sizeof(arr));
}
void test(int* arr)//参数写成指针形式
{
printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
test(arr);
return 0;
}
4.二级指针
请看下面这段代码:
int main()
{
int a = 10;
int * p = &a; // p 是一级指针变量
int ** pp = &p; // pp 是二级指针变量
int *** ppp = &pp; // ppp 是三级指针变量
//....
return 0;
}
在本段代码中,
- a是整型变量,占用4个字节的空间,a是有自己的地址,&a拿到的就是a所占4个字节的第一个字节的地址;
- p是指针变量,占用4/8个字节的空间,p也是有自己的地址,&p就拿到了p的地址。
- pp也是指针变量,pp前有两个 * 则为二级指针变量,存放的是一级指针变量的地址;同理,ppp为三级指针变量,存放的是二级指针变量的地址。
5.指针数组
- 整型数组是存放整型的数组、字符数组是存放字符的数组,那么,指针数组是什么?
- 顾名思义,指针数组:是数组,是存放指针(地址)的数组。
int* parr[5]; // 存放整型指针的数组
char* pc[10]; // 存放字符型指针的数组
如上所示,int 存放整型指针数组、char 存放字符型指针数组。元素类型只要是指针类型就是指针数组。
6.数组指针
- 字符指针是指向字符的指针,存放的是字符的地址; char ch = 'w'; char * pc = &ch;
- 整型指针是指向整型的指针,存放的是整型的地址; int n = 100; int * p = &n;
那么,数组指针又是什么呢? - 顾名思义,数组指针:是指针,是指向数组的指针,存放的是数组的地址。
请看下面这行代码:
int (* p)[10] ;
在这行代码中,p先和 * 结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个大小为10个整型的数组。所以 p 是⼀个指针,指向⼀个数组,叫 数组指针。
这里要注意:[ ] 的优先级要高于 * 号的,所以必须加上()来保证p先和 * 结合 。
- 数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 &数组名。如下:
int arr[10] = {0};
&arr; // 得到的就是数组的地址
int (* p)[10] = &arr; // 如果要存放个数组的地址,就得存放在数组指针变量中
数组指针类型解析:
int (*p) [10] = &arr;
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的元素类型
7.函数指针
- 函数指针:是指针,是指向函数的指针,存放的是函数的地址。
-
函数名就是函数的地址,当然也可以通过 &函数名 的方式获得函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量,那么函数指针变量的写法是什么呢?
函数指针变量的写法其实和数组指针非常类似。请看下面代码:
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;//x和y写上或者省略都是可以的
函数指针类型解析:
int (* pf3) (int x, int y)
| | ------------
| | |
| | pf3指向函数的参数类型和个数的交代
| 函数指针变量名
pf3指向函数的返回类型
8.函数指针数组
数组是一个存放相同类型数据的存储空间。例如:
int *arr[10]; //数组的每个元素是int*
- 整型指针数组是数组,是存放整型指针的数组。
- 当然,函数指针数组:也是数组,是存放函数指针的数组。
示例如下:
int (*parr1[3])();
在这行代码中,parr1 先和 [ ] 结合,说明 parr1是数组;接着数组的内容是 int ( * )() 类型的函数指针。
完整示例代码:
int main()
{
int* arr[10]; // 整型指针的数组
// 1、函数指针数组
int (* p1)(int, int) = Add;
int (* p1)(int, int) = Sub;
// 2、函数指针数组 —— 存放函数指针的数组
int (* pArr[4])(int, int) = {Add; Sub};
return 0;
}
9.typedef关键字
-
typedef 是用来类型重命名的,可以将复杂的类型简单化。
请看下面这行代码:
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); //新的类型名必须在*的右边
10.函数指针类型的用途(转移表)
- 代码案例:使用函数指针数组实现简单的计算器
#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;
}
八、回调函数
- 回调函数是⼀个通过函数指针调用的函数。
- 如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。
- 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的⼀方调用的,用于对该事件或条件进行响应。
代码示例:
#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("******************
printf(" 1:add
printf(" 3:mul
printf("******************
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;
}
在这段代码中,请看void calc部分和case部分,我们把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能。
九、sizeof 和 strlen
1.sizeof
-
sizeof是计算变量所占内存空间大小的,单位是字节。如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。
代码示例:
#inculde <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a); // sizeof 后面是变量的时候()可以省略
printf("%d\n", sizeof(int));
return 0;
}
本段代码中,输出结果为4、4、4;显然, sizeof 只关注占用内存空间的大小,不关注内存中存放什么数据。
- sizeof 在计算大小的时候其实是根据类型推算的。
- sizeof 的操作数如果是一个表达式,表达式是不参与计算的。
请看下面这段代码:
#inculde <stdio.h>
int main()
{
short s = 10; // 占2个字节
int i = 2 ; // 占4个字节
int n = sizeof(s = i + 4); // 截断
printf("%d\n", n);
printf("%d\n", s);
return 0;
}
在本段代码中,其结果为:n = 2;s = 10;分析如下:
- 首先,在第6行代码中,i + 4 为整型 + 整型,结果还是整型,占4个字节;
s 为short 类型,占2个字节,s = i + 4 则表示为 将4个字节放入到2个字节中,此时发生截断,最终为2个字节。- 其次,sizeof 在计算大小的时候是根据类型推算的,当sizeof 看到s为short类型时,其返回值就是short所占的字节大小(2)。所以,n = 2
- 最后,第6行代码中,操作数 s = i + 4是 sizeof 的一个表达式,所以其表达式是不参与计算的。最终 s 的结果仍为 10
2.strlen
- strlen是C语言库函数,功能是求字符串长度。函数原型如下:
size_t strlen (const char * str);
统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。
strlen 函数会⼀直向后找 \0 字符,直到找到为止,所以可能存在越界查找。
示例如下:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abc\0def";
printf("%d\n", strlen(arr));
return 0;
}
strlen 统计的是 \0 之前字符串中字符的个数。所以在本段代码中, \0 之前字符为abc,个数为3,最终其返回结果为 3.
3.sizeof 与 strlen 的对比
sizeof
- sizeof 是操作符;
- sizeof 计算操作数所占内存的大小,单位是字节;
- 不关注内存中存放什么数据,sizeof 的操作数如果是一个表达式,表达式是不参与计算的。
strlen
- strlen 是库函数,使用需要包含头文件string.h;
- strlen 是求字符串长度的,统计的是\0之前字符的个数;
- 关注内存是否有\0,如果没有 \0 ,就会持续往后找,可能会越界。
十、数组指针笔试练习题
1.一维数组
下面这段代码的输出结果是什么呢?
#include <stdio.h>
int main()
{
int a[] = {1,2,3,4};
printf("%zd\n",sizeof(a)); // 16
printf("%zd\n",sizeof(a+0)); // 4/8
printf("%zd\n",sizeof(*a)); // 4
printf("%zd\n",sizeof(a+1)); // 4
printf("%zd\n",sizeof(a[1])); // 4
printf("%zd\n",sizeof(&a)); // 4/8
printf("%zd\n",sizeof(*&a)); // 16
printf("%zd\n",sizeof(&a+1)); // 4/8
printf("%zd\n",sizeof(&a[0])); // 4/8
printf("%zd\n",sizeof(&a[0]+1)); // 4/8
return 0;
}
【解析如下:】
- sizeof(数组名):数组名表示整个数组,计算的是整个数组的大小,单位是字节。a 是一个数组,存放了4个int类型,一个int类型为4个字节,所以 sizeof(a)结果为16.
- 数组名是数组首元素的地址。在 sizeof(a+0) 中,数组名a并没有单独放在sizeof内部,也没有&,所以a就是数组首元素的地址,是地址 大小就是 4/8 个字节。所以sizeof(a+0)结果为 4/8.
- a是数组首元素的地址,也就是第一个元素的地址,在sizeof( * a)中,a==&a[0];* a 就是第一个元素,也就是 a[0];a的类型为整型,一个整型为4个字节,所以sizeof( * a)结果为4.
- 在 sizeof(a+1) 中,数组名 a 没有单独放在sizeof内部,所以 a 就是数组首元素的地址,也就是 &a[0],则 a+1 就是第二个元素的地址,也就是 &a[1];是地址 大小就是 4/8 个字节。所以sizeof(a+1)结果为4/8.
- a[1] 就是数组的第二个元素,在 sizeof(a[1]) 中,sizeof(a[1]) 就是计算第二个元素的大小,单位是字节,所以sizeof(a[1])结果为4.
- &数组名:数组名表示整个数组,取出的是数组的地址。数组的地址也是地址,是地址 大小就是 4/8 个字节。所以sizeof(&a)结果为4.
- ( * p)是访问一个数组的大小;&a 取出数组的地址,类型是数组指针,即:int ( * p)[4] = &a,此时p是指向一个大小为4的整型数组;在 sizeof( * &a) 中,对数组指针进行解引用( * &a) ,也就是访问的是整个数组,sizeof 的整个大小也就是16,所以sizeof( * &a) 结果为16.
也可以这样看:* 和 & 相抵消,所以 sizeof( * &a) 就像于是 sizeof(a) ,大小为16字节,所以sizeof( * &a) 结果为16.- &a 取出的是数组的地址,&a+1 是跳过整个数组后的地址,是地址 大小就是 4/8 个字节。所以sizeof(&a+1)结果为4/8.
- a[0] 为数组首元素,&a[0] 取的是数组首元素的地址,是地址 大小就是 4/8 个字节。所以sizeof(&a[0])结果为4/8.
- &a[0] 是数组首元素的地址,&a[0]+1 就是数组第二个元素的地址,也就是 &a[1],是地址 大小就是 4/8 个字节;所以sizeof(&a[0]+1)结果为4/8.
2.字符数组
- 下面这段代码的输出结果是什么呢?
#include <stdio.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%zd\n", sizeof(arr)); // 6
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;
}
【解析如下:】
- char 类型的数据占1个字节。arr 数组中共有6个字符,所以sizeof(arr)的大小为6.
- arr 是数组首元素的地址,arr+0 还是数组首元素的地址,是地址 大小就是 4/8 个字节。所以sizeof(arr+0)结果为4/8
- arr 是数组首元素的地址,* arr 就是首元素,占一个字符大小,就是1字节,所以sizeof( * arr)结果为1.
- arr[1] 为数组的第二个元素,大小为1字节,所以 sizeof(arr[1])结果为1.
- &arr 是数组的地址,是地址 大小就是 4/8 个字节。所以sizeof(&arr)结果为4/8.
- &arr+1 是跳过整个数组后的地址,指向的是 f 后面,是地址 大小就是 4/8 个字节。所以sizeof(&arr+1)结果为4/8.
- &arr[0] 表示的是数组首元素的地址,&arr[0]+1 则为数组第二个元素的地址,是地址 大小就是 4/8 个字节。所以sizeof(&arr[0]+1)结果为4/8.
- 下面这段代码的输出结果是什么呢?
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = {'a','b','c','d','e', 'f'};
printf("%zd\n", strlen(arr)); // 随机值
printf("%zd\n", strlen(arr+0)); // 随机值
printf("%zd\n", strlen(*arr)); // 非法访问(错误)
printf("%zd\n", strlen(arr[1])); // 非法访问(错误)
printf("%zd\n", strlen(&arr)); // 随机值
printf("%zd\n", strlen(&arr+1)); // 随机值
printf("%zd\n", strlen(&arr[0]+1)); // 随机值
return 0;
}
【解析如下:】
- strlen 是求字符串长度的,统计的是 \0 之前字符的个数。在 strlen(arr) 中,因为数组中没有明确给出 \0 ,所以 strlen(arr)结果为随机值。
- arr 为数组首元素地址,arr+0 仍为数组首元素地址,在 strlen(arr+0) 中,因为数组中没有明确给出 \0 ,所以strlen(arr+0)结果为随机值。
- (* arr) 得到的是字符a,a 的ASCII码是97,strlen的参数是指针类型,传任何参数 strlen都会把它当作地址,此时会将传入的97当作地址,就会形成非法访问。所以strlen( * arr)结果会报错,非法访问。
- arr[1] 为数组中的第二个元素,即:字符 b ,b 的ASCII码是98,同样,strlen会将传入的98当作地址,此时就会形成非法访问。所以 strlen(arr[1])结果会报错,非法访问。
- &arr 是数组指针类型,strlen 的参数是 const char * ,此时将该类型传入strlen 中,strlen 得到的是数组起始位置的地址(首元素的地址)。因为数组中没有明确给出 \0 ,所以strlen(&arr)结果为随机值。
- &arr+1 是跳过整个数组后的地址,指向的是 f 后面。因为数组中没有明确给出 \0 ,所以strlen(&arr+1)的结果仍为随机值。
- &arr[0]+1 表示的是数值第二个元素的地址。因为数组中没有明确给出 \0 ,所以strlen(&arr[0]+1)的结果仍为随机值。
- 下面这段代码的输出结果是什么呢?
#include <stdio.h>
int main()
{
char arr[] = "abcdef";
printf("%llu\n", sizeof(arr)); // 7
printf("%llu\n", sizeof(arr+0)); // 4/8
printf("%llu\n", sizeof(*arr)); // 1
printf("%llu\n", sizeof(arr[1])); // 1
printf("%llu\n", sizeof(&arr)); // 4/8
printf("%llu\n", sizeof(&arr+1)); // 4/8
printf("%llu\n", sizeof(&arr[0]+1)); // 4/8
return 0;
}
【解析如下:】
- "abcdef" 的内容为{ a, b, c, d, e, f, \0 }共7个内容,所以sizeof(arr)的大小为7.
- arr 是数组首元素的地址,arr+0 还是数组首元素的地址,是地址 大小就是 4/8 个字节。所以sizeof(arr+0)结果为4/8.
- arr 是数组首元素的地址,* arr 就是首元素,占一个字符大小,就是1字节,所以sizeof( * arr)结果为1.
- arr[1] 为数组的第二个元素,大小为1字节,所以 sizeof(arr[1])结果为1.
- &arr 是数组的地址,是地址 大小就是 4/8 个字节。所以sizeof(&arr)结果为4/8.
- &arr+1 是跳过整个数组后的地址,指向的是 f 后面,是地址 大小就是 4/8 个字节。所以sizeof(&arr+1)结果为4/8.
- &arr[0] 表示的是数组首元素的地址,&arr[0]+1 则为数组第二个元素的地址,是地址 大小就是 4/8 个字节。所以sizeof(&arr[0]+1)结果为4/8.
- 下面这段代码的输出结果是什么呢?
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%lld\n", strlen(arr)); // 6
printf("%lld\n", strlen(arr+0)); // 6
printf("%lld\n", strlen(*arr)); // 非法访问(错误)
printf("%lld\n", strlen(arr[1])); // 非法访问(错误)
printf("%lld\n", strlen(&arr)); // 6
printf("%lld\n", strlen(&arr+1)); // 随机值
printf("%lld\n", strlen(&arr[0]+1)); // 5
return 0;
}
【解析如下:】
- strlen 是求字符串长度的,统计的是 \0 之前字符的个数,"abcdef" 的内容为{ a, b, c, d, e, f, \0 }。所以 strlen(arr)结果为6。
- arr 为数组首元素地址,arr+0 仍为数组首元素地址,"abcdef" 的内容为{ a, b, c, d, e, f, \0 }。所以strlen(arr+0)结果为6。
- (* arr) 得到的是字符a,a 的ASCII码是97,strlen的参数是指针类型,传任何参数 strlen都会把它当作地址,此时会将传入的97当作地址,就会形成非法访问。所以strlen( * arr)结果会报错,非法访问。
- arr[1] 为数组中的第二个元素,即:字符 b ,b 的ASCII码是98,同样,strlen会将传入的98当作地址,此时就会形成非法访问。所以 strlen(arr[1])结果会报错,非法访问。
- &arr 是数组指针类型,strlen 的参数是 const char * ,此时将该类型传入strlen 中,strlen 得到的是数组起始位置的地址(首元素的地址)。因为数组内容为{ a, b, c, d, e, f, \0 },其中包含 \0 ,所以strlen(&arr)结果为6。
- &arr+1 是跳过整个数组后的地址,指向的是 f 后面。因为数组中 f 后面是否有 \0 未知,所以strlen(&arr+1)的结果为随机值。
- &arr[0]+1 表示的是数组第二个元素的地址。因为数组内容为{ a, b, c, d, e, f, \0 },其中包含 \0 。即从 b 开始向后查找,查找到 \0 之前====>b,c,d,e,f. 所以strlen(&arr[0]+1)的结果为5。
5.下面这段代码的输出结果是什么呢?
#include <stdio.h>
int main()
{
char *p = "abcdef";
printf("%lld\n", sizeof(p)); // 4/8
printf("%lld\n", sizeof(p+1)); // 4/8
printf("%lld\n", sizeof(*p)); // 1
printf("%lld\n", sizeof(p[0])); // 1
printf("%lld\n", sizeof(&p)); // 4/8
printf("%lld\n", sizeof(&p+1)); // 4/8
printf("%lld\n", sizeof(&p[0]+1)); // 4/8
return 0;
}
【解析如下:】
- p 是一个指针变量,sizeof(p) 就是指针变量的大小,为4/8字节。
- p+1 是字符b的地址,是地址 大小就是 4/8 个字节。
- *p 是首元素(首字符),大小是1字节。
- p[0] == *(p+0),就是字符串中的首字符,大小为1字节。
- &p 是 p 的地址,是地址 大小就是 4/8 个字节。
- &p+1 是跳过p变量后的地址,是地址 大小就是 4/8 个字节。
- &p[0]+1 是字符串中第二元素的地址,也就是 b 的地址,是地址 大小就是 4/8 个字节。
6.下面这段代码的输出结果是什么呢?
#include <stdio.h>
#include <string.h>
int main()
{
char *p = "abcdef";
printf("%lld\n", strlen(p)); // 6
printf("%lld\n", strlen(p+1)); // 5
printf("%lld\n", strlen(*p)); // 非法访问(错误)
printf("%lld\n", strlen(p[0])); // 非法访问(错误)
printf("%lld\n", strlen(&p)); // 随机值
printf("%lld\n", strlen(&p+1)); // 随机值
printf("%lld\n", strlen(&p[0]+1)); // 5
return 0;
}
【解析如下:】
- strlen 是求字符串长度的,统计的是 \0 之前字符的个数,"abcdef" 的内容为{ a, b, c, d, e, f, \0 }。p 中存放的是 a 的地址,从 a 的地址开始向后访问,所以 strlen(p)结果为6。
- p 为字符串中首元素地址,p+1 为字符串中第二个元素的地址,也就是 b 的地址,从 b 的地址开始向后访问,所以strlen(p+1)结果为5。
- (* p) 得到的是字符a,a 的ASCII码是97,strlen的参数是指针类型,传任何参数 strlen都会把它当作地址,此时会将传入的97当作地址,就会形成非法访问。所以strlen( * p)结果会报错,非法访问。
- p[0] == * (p+0) == * p,得到的是字符a,所以strlen(p[0])结果会报错,非法访问。
- &p 是 p 的地址,从 p 所占空间的起始位置开始查找的,其返回结果是随机值。
- &p+1 是跳过p变量后的地址,其返回结果为随机值。
- &p[0]+1 表示的是字符串中第二个元素的地址。即从 b 开始向后查找,所以strlen(&p[0]+1)的结果为5。
3.二维数组
下面这段代码的输出结果是什么呢?
#include <stdio.h>
int main()
{
int a[3][4] = {0};
printf("%d\n",sizeof(a)); // 48
printf("%d\n",sizeof(a[0][0])); // 4
printf("%d\n",sizeof(a[0])); // 16
printf("%d\n",sizeof(a[0]+1)); // 4/8
printf("%d\n",sizeof(*(a[0]+1))); // 4
printf("%d\n",sizeof(a+1)); // 16
printf("%d\n",sizeof(*(a+1))); // 16
printf("%d\n",sizeof(&a[0]+1)); // 4/8
printf("%d\n",sizeof(*(&a[0]+1))); // 16
printf("%d\n",sizeof(*a)); // 16
printf("%d\n",sizeof(a[3])); // 16
return 0;
}
【解析如下:】
- sizeof(a) 计算的是整个二维数组的大小,单位是字节。即:3×4×4=48字节。
- a[0][0]) 是第一行第一个元素,大小为4个字节。
- a[0] 是第一行的数组名,单独放到了 sizeof 中,计算的是第一行的大小,其中包括4个元素,大小为16(4×4)字节。
- 在 sizeof(a[0]+1) 中,数组名 a[0] 没有单独放在sizeof内部,所以 a[0] 就是数组首元素的地址,也就是 &a[0][0],则 a[0]+1 就是第一行第二个元素的地址,也就是 &a[0][1],大小为4/8个字节。
- a[0]+1 是第一行第二个元素的地址,则 a[0]+1 解引用就是第一行第二个元素,大小是4个字节。
- 在 sizeof(a+1)中,数组名 a 没有单独放在sizeof内部,所以 a 就是数组首元素的地址,也就是第一行的地址,**则 a[0]+1 就是第二行的地址,也就是 &a[1],其中包括4个元素,大小为16个字节。
- (* (a+1) )是第二行的地址 ,大小为16字节。
- a[0] 是第一行的数组名,&a[0] 取出的是第一行的地址,&a[0]+1 得到的就是第二行的地址,是地址,大小就是4/8个字节。
- &a[0]+1 为第二行的地址,对第二行的地址解引用,大小为16字节。
- a 就是数组首元素的地址,也就是第一行的地址,对第一行的地址解引用,大小为16字节。
- 在 sizeof(a[3]) 中,数组名 a[3] 单独放到 sizeof 中,计算的是第一行的大小,其中包括4个元素,大小为16(4×4)字节。
数组指针小结
以上就是数组指针的练习题,从以上练习中,我们得出数组名有以下几点意义:
- 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
- 3. 除此之外所有的数组名都表示首元素的地址。
结尾
好了!本文到这里就结束了!以上内容就是对指针知识的学习,希望能够帮助你更好地学习指针。能够跟着本篇文章学习下来的小伙伴们,为你们点赞!请继续加油!