指针(初级)
- 指针是什么
- 指针和指针类型
- 野指针
- 指针运算
- 指针和数组
- 二级指针
- 指针数组
1 指针是什么?
指针是什么?
指针理解的2个要点:
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
那我们就可以这样理解:内存
指针变量
我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个
变量就是指针变量#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int* p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
//中,p就是一个之指针变量。
return 0;
}
总结:
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那这里的问题是:
一个小的单元到底是多大?(1个字节)
如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。
这里我们就明白:
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以
一个指针变量的大小就应该是4个字节。 - 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以
一个指针变量的大小就应该是4个字节。
总结:
- 指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节。
2 指针和指针类型
我们都知道,变量有不同的类型,整形,浮点型等。同理:指针也有类型。如:
#include <stdio.h>
int main()
{
char* pc = NULL;//char型指针变量,存放char类型变量的地址
int* pi = NULL;//int 型指针变量,存放int类型变量的地址
short* ps = NULL;//short 型指针变量,存放short类型变量的地址
long* pl = NULL;//long 型指针变量,存放long类型变量的地址
float* pf = NULL;//float 型指针变量,存放float类型变量的地址
double* pd = NULL;// double 型指针变量,存放double类型变量的地址
return 0;
}
这里可以看到,指针的定义方式是: 类型 + *
那指针类型的意思是什么?从下面的代码来探索:
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("n=%p\n", &n);
printf("pc=%p\n", pc);
printf("pc+1=%p\n", pc+1);
printf("pi=%p\n", pi);
printf("pi+1=%p\n", pi+1);
return 0;
}
从运行的结果来看,第一个是N的地址,第二个是char类型指针变量的地址,第三个是char+1的地址,在原来的地址上面+1,为什么会是这样的结果。因为char类型大小只有一个字节,+1是跳过一个char类型的大小。第四个是int 类型的指针变量。第五个是int 类型指针变量+1,因为int 是四个字节,+1是跳过一个int类型大小,在原来地址上+4.
总结:
指针的类型决定了指针向前或者向后走一步有多大(距离)。
2.2指针的解引用
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节
3、野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1、野指针的成因
- 概念: 指针未初始化。
int main()
{
int* p;//局部变量指针未初始化,默认为随机值
p = 20;
return 0;
}
2.指针的越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.指针指向的空间被释放。
3.2如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放,及时置NULL
- 避免返回局部变量的地址
- 指针使用之前检查指针的有效性
4、指针的运算
- 指针±整数
- 指针-指针
- 指针的关系运算
4.1指针 + - 整数
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int len = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (int i = 0; i < len; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
通过指针p加整数解引用,从而找到对应的值。
4.2指针-指针
int my_strlen(char *p)
{
char* s = p;
while (*p != '\0')
{
p++;
}
return p-s;
}
4.3指针的关系运算
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int* p = &arr[9];
for (*p; p >= &arr[0]; p--)
{
*p = 0;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
}
5.指针和数组
通过下面的代码来观察它们的关系
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
运行的结果:
可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
就如前面的指针加减整数一样。可以通过指针来找到对应的地址。
5.1二级指针
什么是二级指针呢?指针变量也是变量用来存放指针变量地址的指针变量叫做二级指针。
一级指针二级指针的联系是什么啊?
二级指针就是用来存储一级指针的地址。
指针(进阶)
- 字符指针
- 数组指针
- 指针数组
- 数组传参和指针传参
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 回调函数
- 指针和数组面试题的解析
1.字符指针
正片开始,通过下面的代码探究:
int main()
{
const char* arr="dfsgsgsd";
printf("%s\n",arr);
return 0;
}
这里是把字符串dfsgsgsd存在arr指针变量里面了吗?答案显然不是。他的本质是把字符串dfsgsgsd的首地址放到了arr中。
再通过下面的一道面试题,来体验下:
#include <stdio.h>
int main()
{
char str1[] = "hello world";
char str2[] = "hello world";
const char* str3 = "hello world";
const char* str4 = "hello world";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 not same\n");
if (str3==str4)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 not same\n");
return 0;
}
运行的结果是什么?答案如下:
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
2.指针数组
什么是指针数组?指针数组是存放指针的数组。
看看下面的是什么数组:
int * arr1[10];//整型指针的数组
char *arr2[4];//一级字符指针数组
char **arr3[5];//二级字符指针数组
3.数组指针
3.1数组指针的定义
数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
下面代码哪个是数组指针?
int *p1[10];// 指针数组
int (*p2)[10];// 数组指针
为什么p2是数组指针呢?
答:这里要注意[ ]的优先级要高于号的,所以必须要加上()来保证p先和结合。p2先和 * 结合,说p2是一个指针变量,然后指向的是一个大小为10个整型数组。所以p2 是一个指针,指向一个数组,叫做数组指针。
3.2 & 数组名 vs 数组名
int arr[ 10 ] ;
arr和&arr分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
可见数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
我们再看一段代码:
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>
void print_arr1(int arr[3][5], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*p)[5], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", p[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,其实相当于第一行的地址,是一维数组的地址,所以可以用数组指针来接收。
4. 函数指针
首先看一段代码:
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
输出的是两个地址,这两个地址是 test 函数的地址。 那我们的函数的地址要想保存起来,怎么保存? 下面我们看代码:
void (*pf1)( );
void *pf2( );
首先,能存储地址,就要求pf1或者pf2是指针,但是那个是函数指针?答案是:
pf1可以存放。pf1 先和* 结合,说明pf1是指针,指向一个函数,指向的函数无参数,返回值类型为void。
阅读两端有趣的代码:
//代码1
(*(void (*)())0)();
//现在将该语句分段解读
//1. void(*)()----函数指针类型
//2. (void(*)())0----对0进行强制类型转换,解释为一个函数的地址
//3.(*(void (*)())0)----对0地址进行了解引用操作
//4.(*(void(*)())0)()----调用0地址处的函数
//代码2
void (*signal(int, void (*)(int)))(int);
//1.void(*)(int)----函数指针类型,传参类型为int
//2.signal(int ,void (*)(int ))--signal函数的定义和传参
//3.(*signal(int, void (*)(int)))----对signal进行解引用
//4.(*signal(int, void (*)(int)))(int)----调用signal地址的函数,传参为int类型
5.函数指针数组
数组是存放相同类型数据的存储空间。我们在前面就介绍了指针数组。那函数指针数组要怎么定义呢?
把函数的地址存放到数组中,这个数组就叫函数指针数组。那么要如何定义?
格式如下:int (arr[10])();
解释:**arr先和[ ]结合,说明arr是数组。数组的内容是什么啊?是int ()()类型的函数指针**。
我们现在知道了函数指针数组的形式,那么该怎么样具体的使用它呢????
我们通过写一段代码来模拟实现计算器的加、减、乘、除,来加深印象。
#include<stdio.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("******* 1.Add 2.Sub ******\n");
printf("******* 3.Mul 4.Div ******\n");
printf("******* 0.exit ******\n");
printf("********************************\n");
}
int main()
{
int a;
int b;
int input;
int ret;
do {
Menu();
printf("请选择->:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个运算数:");
scanf("%d %d", &a, &b);
ret = Add(a, b);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个运算数:");
scanf("%d %d", &a, &b);
ret = Sub(a, b);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个运算数:");
scanf("%d %d", &a, &b);
ret = Mul(a, b);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个运算数:");
scanf("%d %d", &a, &b);
ret = Div(a, b);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("输入错误,请重新输入:\n");
break;
}
} while (input);
return 0;
}
这里我们将加、减、乘、除写成了四个函数来实现他们的功能,并通过do while();和switch语句来配合实现。他们看起来是不是感觉很多,感觉很杂乱,很多语句都重复使用。这样我们就可以使用函数指针数组来实现了计算器的功能,使代码更加的简洁。
函数指针数组的实现:
#include<stdio.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("******* 1.Add 2.Sub ******\n");
printf("******* 3.Mul 4.Div ******\n");
printf("******* 0.exit ******\n");
printf("********************************\n");
}
int main()
{
int a;
int b;
int input;
int ret;
int(*Calarr[])(int, int) = { NULL,&Add,&Sub,&Mul,&Div };
do {
Menu();
printf("请选择->:");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:");
scanf("%d %d", &a, &b);
ret = Calarr[input](a, b);//调用
printf("%d\n", ret);
}
else
{
printf("输入错误,请重新输入:\n");
}
} while (input);
return 0;
}
我们都知道,数组的下标是从0开始的,但是我们希望输入的数组能够和数组下标对应,所以我们把数组下标为0的地方放一个NULL空指针,在进行判断如果等于零的话,就执行退出的语句。
函数指针数组的实际引用就是:管理多个函数。
6.回调函数
回调函数就是通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指的函数时,我们就说这就是回调函数。回调函数不是由函数的实现方直接调用,而是在特定的事件或者条件发生时由另外的一方调用,用于对该事件或条件进行响应。
其中qsort函数就是回调函数的典型例子,下面我们一起来看看qsort函数怎么使用。
我们可以看到qsort函数的头文件是<stdlib.h>或者<search.h>,文件中qsort的具体定义为:
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
其中的 void *base对应的是传入的地址,size_t num是传入元素的个数,size_t是指传入元素的大小,是多大的字节,int (__cdecl *compare )(const void *elem1, const void *elem2 ) 是一个函数指针,这个函数是需要我们自己建立,返回值如下图:
这个函数的参数类型是void *,这是因为qsort可以排序任意的数据类型,void 可以强制转换为任意的数据类型,到时候强制转换就可以使用了。
下面开始使用qsort函数来排序整形的数组。
#include<stdio.h>
#include <stdlib.h>
int comper(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 5,8,42,59,46,3,7,40,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), comper);
int i;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
运行的结果为:
有人会问,排序数组可以用冒泡排序就可以了,为什么要这个麻烦。那是因为qsort函数不单单可以排序数组,它还可以排序其它类型的数据,比如:结构题。
我们先排序结构体的age(年龄)
#include<stdio.h>
#include <stdlib.h>
typedef struct student
{
int age;
char name[20];
}stu;
int comper(const void* e1, const void* e2)
{
return ((stu*)e1)->age - ((stu*)e2)->age;
}
int main()
{
stu arr[3] = { {20,"zhangsan"},{19,"libai"},{22,"aiqiyi" } };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), comper);
int i;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i].age);
}
return 0;
在来排序结构体的name(姓名),排序字符串需要用到函数strcmp()函数。具体的代码实现如下:
int comper(const void* e1, const void* e2)
{
return strcmp(((stu*)e1)->name , ((stu*)e2)->name);
}
只需要把comper重新选择,就可以实现字符串的比较。
qsort是如何实现的这样的功能的呢?
下面我们模拟sqrot(采用冒泡排序的方式)
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
void swap(void * e1,void * e2,int arr_size)
{
int i = 0;
for (int i = 0; i < arr_size; i++)
{
char temp = *((char*)e1 + i);
*((char*)e1 + i)= *((char*)e2 + i);
*((char*)e2 + i) = temp;
}
}
int cmp(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
void blublu(void* base, int arr_num, int arr_size, int (*cmp)(const void* e1, const void* e2))
{
int i = 0, j = 0;
for ( i = 0; i < arr_num - 1; i++)
{
for (j = 0; j < arr_num - 1 - i; j++)
{
if (cmp((char *)base + arr_size*j,(char *)base +(j+1)*arr_size)>0)
{
swap((char *)base+j*arr_size,(char*)base+ (j+1)*arr_size,arr_size);
}
}
}
}
int main()
{
int arr[] = { 1,3,5,7,9,2,4,6,8,10 };
int arr_num = sizeof(arr) / sizeof(arr[0]);
blublu(arr, arr_num, sizeof(arr[0]),cmp);
for (int i = 0; i < arr_num; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
运行结果如下:
qsort到此结束!!!
7.指针和数组笔试题解析
7.1 一维数组
int main()
{
int arr[] = { 1,2,3,4 };
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[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(*&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]));
printf("%d\n", sizeof(&arr[0]+1));
return 0;
}
解释:
int arr[] = { 1,2,3,4 };
printf("%d\n", sizeof(arr));
//arr数组名单独放在sizeof()里面,计算的是整个数组的大小,单位是字节。16
printf("%d\n", sizeof(arr + 0));
//arr没有单独放在sizeof里面,也没有&,所以这里是arr+0是数组首元素。 4
printf("%d\n", sizeof(*arr));
//*arr是指针,指向数组首元素的地址,所以是数组首元素的大小 4
printf("%d\n", sizeof(arr + 1));
//arr是数组首元素的地址,+1表示第二个元素,大小为 4
printf("%d\n", sizeof(arr[1]));
//是数组第二个元素的大小 4
printf("%d\n", sizeof(&arr));
//&arr表示取arr的地址,地址的大小是4/8 4
printf("%d\n", sizeof(*&arr));
//解释为取出arr的地址,然后对他进行解引用,相当于整个数组 16
printf("%d\n", sizeof(&arr + 1));
//&arr+1表示跳过数组,指向数组后面元素的地址,打印地址 4
printf("%d\n", sizeof(&arr[0]));
//表示数组第一个元素的地址, 4
printf("%d\n", sizeof(&arr[0] + 1));
//表示数组第二个元素的地址 4
return 0;
这里介绍 ”数组名“ 的两个例外:
1、sizeof(数组名)表示计算的是整个数组的大小,单位是字节。
2、&数组名:表示整个数组,取出的是整个数组的地址。
除此之外,数组名都是数组首元素的地址。
7.2字符数组
int main()
{
char arr[] = { 'a','b','c','b' };
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;
}
解释:
char arr[] = { 'a','b','c','b' };
printf("%d\n", sizeof(arr));
//arr表示数组名,单独放在sizeof()里面,计算的是整个数组的大小 4
printf("%d\n", sizeof(arr + 0));
//没有单独放,也没有&,表示数组的首元素,;计算首元素的地址
printf("%d\n", sizeof(*arr));
//表示数组首元素,计算数组首元素的大小 1
printf("%d\n", sizeof(arr[1]));
//表示计算数组第二个元素的大小 1
printf("%d\n", sizeof(&arr));
//表示计算数组的地址,地址大小4/8 4
printf("%d\n", sizeof(&arr + 1));
//表示跳过整个数组的大小,然后指向后面一个地址 4
printf("%d\n", sizeof(&arr[0] + 1));
//表示数组首元素地址+1,计算地址。 4
char arr[] = { 'a','b','c','b' };
printf("%d\n", strlen(arr));
//计算arr字符长度,但是没有\0,无法知道结果 随机值
printf("%d\n", strlen(arr + 0));
//计算arr[0]到\0的长度,但是没有\0;无法知道结果 随机值
printf("%d\n", strlen(*arr));
//strlen需要的是一个地址,从这个地址向后统计到\0的长度
//但是*arr是数组首元素,也就是’a‘,传给strlen就是’a' error
//的ASCII 97,从97后面开始统计字符,会造成访问冲突。
printf("%d\n", strlen(arr[1]));
//同里 表示‘b' ,造成访问冲突。 error
printf("%d\n", strlen(&arr));
//取出arr的地址,但是指向起始位置从起始位置向后找字符串不知道\0, 随机值
printf("%d\n", strlen(&arr + 1));
//取出数组+1的地址,计算到\0的长度 随机值
printf("%d\n", strlen(&arr[0] + 1));
//取出首元素+1的地址,计算到\0长度;无法知道长度 随机值
return 0;
题目:
int main()
{
char arr[] = "abcd";
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;
}
解释:
char arr[] = "abcd";//abcd\0
printf("%d\n", sizeof(arr));
//数组名arr单独放在sizeof里面表示计算数组的大小,但是末尾有\0 5
printf("%d\n", sizeof(arr + 0));
//arr表示数组首元素+0还是表示首元素,表示地址 4
printf("%d\n", sizeof(*arr));
//arr表示数组首元素,解引用是'a'的大小 1
printf("%d\n", sizeof(arr[1]));
//表示'b',计算字符b的大小 1
printf("%d\n", sizeof(&arr));
//&arr表示取出数组的地址,计算的是地址 4
printf("%d\n", sizeof(&arr + 1));
//表示跳过数组的地址,指向数组后面的地址,计算的是地址 4
printf("%d\n", sizeof(&arr[0] + 1));
//表示首元素地址+1,指向'b'的地址,计算的是地址 4
printf("%d\n", strlen(arr));
//arr表示首元素的地址,向后面查找\0, 4
printf("%d\n", strlen(arr + 0));
//表示数组首元素+0,向后面计算到\0的长度 4
printf("%d\n", strlen(*arr));
//表示数组首元素地址,解引用,字符‘a'的ASCII码是97,造成访问冲突 error
printf("%d\n", strlen(arr[1]));
//表示数组第二个元素'b',ASCII码是98,造成访问冲突 error
printf("%d\n", strlen(&arr));
//表示数组的地址,但是依然指向首元素的地址, 4
printf("%d\n", strlen(&arr + 1));
//表示数组元素的地址向后+1,开始查找\0,但是\0不可知 随机值
printf("%d\n", strlen(&arr[0] + 1));
//表示数组首元素+1的地址,向后面查找\0, 3
题目:
int main()
{
char *arr = "abcd";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+1));
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[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 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[0] + 1));
return 0;
}
解释:
char* arr = "abcd";
printf("%d\n", sizeof(arr));
//*arr是指针变量,sizoef计算的是*arr的地址 4
printf("%d\n", sizeof(arr + 1));
//arr指向’b‘的地址,计算的是地址, 4
printf("%d\n", sizeof(*arr));
//表示解引用,表示第一个字符a的大小类型是 char 1
printf("%d\n", sizeof(arr[0]));
//表示*(arr+0)==*arr[0]的大小 1
printf("%d\n", sizeof(&arr));
//表示取出数组名的地址计算 地址就是4/8 4
printf("%d\n", sizeof(&arr + 1));
//表示数组地址向后+1的地址,地址大小4/8 4
printf("%d\n", sizeof(&arr[0] + 1));
//表示计算数组首元素+1的地址 4
printf("%d\n", strlen(arr));
//表示计算字符串的长度,*arr指向第一个元素,向后面查找 4
//\0的位置
printf("%d\n", strlen(arr + 1));
//表示从arr+1开始的位置向后找\0的位置 3
printf("%d\n", strlen(*arr));
//表示解引用arr,把a的ASCII码传给strlen ,会造成访问冲突 error
printf("%d\n", strlen(arr[0]));
//arr[0]也就是'a',strlen会把97作为起始地址向后统计\0,
//造成访问冲突 error
printf("%d\n", strlen(&arr));
//取出变量arr的地址,但是依然指向原来的地址,\0不可知 随机值
printf("%d\n", strlen(&arr + 1));
//取出地址,向后面+1,然后统计\0的,但是\0不可知 随机值
printf("%d\n", strlen(&arr[0] + 1));
//取出第一个的地址向后面+1的地址,开始统计\0 3
return 0;
7.3 二维数组
int main()
{
int arr[3][4] = { 0};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr[0][0]));
printf("%d\n", sizeof(arr[0]));
printf("%d\n", sizeof(arr[0] + 1));
printf("%d\n", sizeof(*(arr[0] + 1)));
printf("%d\n", sizeof(arr + 1));
printf("%d\n", sizeof(*(arr + 1)));
printf("%d\n", sizeof(&arr[0] + 1));
printf("%d\n", sizeof(*(&arr[0] + 1)));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[3]));
return 0;
}
这里我们要清楚,数组他是如何从存放,二维数组的首元素是第一行,以此类推;
int arr[3][4] = {0};
printf("%d\n", sizeof(arr));
//数组名单独放在sizeof里面,表示计算数组的大小 48
printf("%d\n", sizeof(arr[0][0]));
//表示计算数组第一行第一列的元素大小 int类型 4
printf("%d\n", sizeof(arr[0]));
//表示计算数组第一行元素的大小 16
printf("%d\n", sizeof(arr[0] + 1));
//arr[0]表示首元素的地址+1表示arr[0][1]的地址 4
printf("%d\n", sizeof(*(arr[0] + 1)));
//表示arr[0][1]的地址解引用,计算他的大小 4
printf("%d\n", sizeof(arr + 1));
//arr表示数组首元素的地址,+1表示第二行元素的地址 地址是4/8 4
printf("%d\n", sizeof(*(arr + 1)));
//arr+1表示数组第二行的地址,解引用就是计算数组第二行的大小 16
printf("%d\n", sizeof(&arr[0] + 1));
//&arr[0]+1表示第二行的地址,计算第二行的地址 4
printf("%d\n", sizeof(*(&arr[0] + 1)));
//表示第二行地址解引用 计算第二行的大小 16
printf("%d\n", sizeof(*arr));
//arr没有单独放在sizeof里面表示首元素地址,对他进行解引用
//计算的是第一行的大小 16
printf("%d\n", sizeof(arr[3]));
//arr[3]越界了,但是编译器会认为 int arr[3][0] 计算他的大小 16
return 0;
总结:数组名的意义:
1.sizeof(数组名),这里的数组名表示的是整个数组,计算的是整个数组的大小。
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3.除此之外所有的数组名都表示数组首元素的地址。
7.4 指针笔试
好了,数组的笔试题结束,现在我们继续进行指针的笔试题:
笔试题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;
}
//程序的结果是什么?
解析:
题目二:
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//已知结构体的大小为20个字节
int main()
{
p=(struct Test*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
解析:
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1);
//首先要知道上面的表示16进制,+1 跳过一个结构体类型的大小 结果为0x100010
printf("%p\n", (unsigned long)p + 0x1);
//我们将p强制的转化为 unsigned long类型,p被看作为一个十进制的数字
//+1就变为了 0x100001 结果为0x100001
printf("%p\n", (unsigned int*)p + 0x1);
//将p强制转化为unsigned int* 类型,+1跳过四个字节。 结果为0x100004
题目三:
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;
}
笔试题四:
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
大家初看到这个题目是不是感觉到简单,但是却藏着一个坑,那就是逗号表达式仔细的看看,数组里面的元素我们要么不用{ },要么就用{ }这两种,但是( ,)构成了逗号表达式,从左往右,所以真正储存的是1,3 ,5 .a[0]表示的是首元素的地址。p[0]=*(p+0),就是接应用,所以结果 是第一行的第一个元素 ,也就是 1
笔试题五:
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;
}
解释:
题目六:
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;
}
解析:
题目七:
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
解析:
题目八:
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;
}
解析:
第一问(绿色线条):**++cpp。++优先级高于*,cpp++指向cp的第二个元素,解引用找到c+2,cp在解引用找到的是c的POINT的地址,然后%s打印 POINT
第二问(橙色线条):cpp在原来指向地址上++,指向cp的第以个元素然后进行解引用找到c+1,然后减减(c+1-1)解引用,找到的是c的0号地址,进行+3的操作找到E , 从E开始打印 结果是ER。
第三问:(紫色线条):*cpp[-2]+3==**(cpp-2)+3。cpp-2指向的是cp的0号位置解引用,然后在解引用找到c的第三个元素的位置,+3找到S的位置,开始打印结果是ST。
第四问:(酸橙色线条):cpp[-1][-1]+1==*( *(cpp-1)-1)+1.cpp-1我们可以拿到cp第二个元素的地址,解引用访问了第二个元素的,再减1,就会变成c+1,解引用就会拿到c数组中NEW的地址,+1指向EW,然后打印EW。
总结
到这里指针的讲解就结束了,希望大家能够理解并且掌握,如果大家认为我有哪些不足的地方或者知识上面有错误都可以告诉我,我会在之后的文章中不断改正,望大家包含。