在学习这篇指针详细讲解之前,我们需要先理解内存,地址和指针的关系。可以看之前发过的一篇初识指针篇,链接如下:https://blog.csdn.net/2301_79755897/article/details/139602535?spm=1001.2014.3001.5501
一、指针的类型
在我们学习变量时变量的类型有int、char、short、long等类型。那指针都有什么类型呢?他们代表什么呢?
int * p | 表示指针p指向的数据类型是int类型 |
char * p | 表示指针p指向的数据类型是char类型 |
short * p | 表示指针p指向的数据类型是short类型 |
long * p | 表示指针p指向的数据类型是long类型 |
float * p | 表示指针p指向的数据类型是float类型 |
double * p | 表示指针p指向的数据类型是double类型 |
通俗易懂的讲就是int *类型的指针,是为了存放int类型数据的地址。
这里注意:在x86环境下,不论什么类型的指针大小都是4个字节。在x64环境下,不论什么类型指针都是8个字节!!
二、指针+-整型
既然我们了解了指针类型,那么不同类型的指针有着什么样的意义呢?
看上面代码打印的结果我放在了右边的方框里了,下面我们来分析一下为什么会产生这样的结果。
#include<stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n);//00B9FB18
//这里打印的就是整型变量n的地址没什么可讲的
printf("%p\n", pc);//00B9FB18
//我们将n取地址强制转换成char*类型赋值给了pc
//就是将n的地址给了字符指针pc
//所以这里打印出来的pc与n的地址一样
printf("%p\n", pc + 1); //00B9FB19
//为什么这里是00B9FB19
//首先指针pc指向的是char数据类型,因为char类型的字节大小是一个字节
// 在这里代表的是跳过一个char类型数据
//所以在给pc+1时加的是一个字节,即地址+1
//从00B9FB18->00B9FB19
printf("%p\n", pi);//00B9FB18
//这里和是将n的地址赋值给了整型指针pi中,所以结果是n的地址
printf("%p\n", pi + 1);//00B9FB1C
//首先指针pi指向的是整型数据类型,整形数据类型在内存占据4个字节
//所以在这里加1代表的是跳过一个整型数据
//00B9FB1C-00B9FB18=4个字节
return 0;
}
三、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
在局部未初始化时,运行过程中系统会报错,所以在我们写代码的时候应该注意这些问题。
这个就是数组越界访问形成了野指针,破环了栈堆。
如何避免野指针的形成?
1. 指针初始化2. 小心指针越界3. 指针指向空间释放即使置 NULL4. 避免返回局部变量的地址5. 指针使用之前检查有效性
四、字符指针
int main()
{
//有单个字符和字符串指针
//那么指针指向的是什么呢?
char a = 'w';
char a1[] = "hello word";
char* ap = &a;//这里ap指向的是a的地址
char* a1p = &a1;//这里a1是一个字符串(字符数组),指向的是字符串首元素地址
return 0;
}
下面有一道大家试试看
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 are not same\n");
//因为str1和str2是两个不同的字符数组,所以他们首元素地址肯定不一样
if (str3 == str4)//str3和str4代表的是他们指向的地址
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
//str3和str4指向的是同一个字符串,所以首元素是地址是一样的
return 0;
}
五、指针数组
我们学习过整型数组,字符数组,浮点型数组,那指针数组是什么呢?(简单理解,存放指针的数组)当然是存放指针的数组,那怎么定义呢?
int main()
{
int* p[10] = { 0 };
//这是一个指针数组
//int* 表示的是类型,那就是整型指针
//p 表示的是数组名
//[10] 表示的是数组元素个数
//整体的表示意思是,一个名为p的数组里放了10个类型为整型指针的数组
//下面的依次类推
short* p1[10] = { 0 };
float* p1[10] = { 0 };
double* p1[10] = { 0 };
return 0;
}
六、数组指针
上面我们刚讲了指针数组这里就来了数组指针,下面我们先从定义了解数组指针。
首先我们需要思考数组指针到底是数组还是指针呢?
int main()
{
//对于指针来说
//整型指针指向的是整型数据
//字符指针指向的是字符数据
//short指针指向的是short数据
//以此类推
//那数组指针指向的就是一个数组
//那该如何定义一个数组指针?
//数据类型(*指针变量名)[数组长度];
int(*p)[10] = { 0 };//指针p就是一个指向数组的数组指针
//int [10]={0}我们先把(*p)去掉
//这里我们能看到的是一个没有数组名的数组
int* p1[10] = { 0 };//这是一个指针数组
//为什么要加(),因为()优先级大于*
//所以在指针数组(int*)p1[10]={0}
//(int*)表示的是数组内元素类型
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
return 0;
}
数组指针的使用
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;
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);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
这个就是关于数组指针在二维数组的理解
七、&数组名VS数组名
下面我将用代码来讲解
我将地址打印出来,大家可以发现arr=&arr,但arr+1和&arr+1不同,(&arr+1)-(&arr)=40(十进制)。&arr是取出整个数组地址,地址是首元素地址,但在运算时+-,是前进或者后退arr整个数组这么大。
八、数组传参,指针传参
一维数组传参
void test(int arr[])//ok? yes
{}
void test(int arr[10])//ok? yes
{}
void test(int* arr)//ok? yes
{}//这里定义的是一个arr指针,因为传过来的是一个地址,所以用指针接受
void test2(int* arr[20])//ok? yes
{}
void test2(int** arr)//ok? yes
{}// 传过来的是arr2首元素地址
// 这个元素是一级指针,所以用二级指针来接收
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
二维数组传参
void test(int arr[3][5])//ok? YES
{}
void test(int arr[][])//ok? NO
{}
void test(int arr[][5])//ok? YES
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//ok? NO
{}//在二维数组传参时,传来的时首行元素的整体地址。
//一级指针不能接收这个整体的地址
void test(int* arr[5])//ok? NO
{}//形参的类型为一个指针数组,我们传过来的是二维数组
void test(int(*arr)[5])//ok? YES
{}
void test(int** arr)//ok? NO
{}// 二级指针存放的是一级指针的地址
// 传过来的是arr二维数组的首行地址,并不是一个一级指针的地址
// 所以不行
int main()
{
int arr[3][5] = { 0 };
test(arr);
//注意二维数组名表示的是首行数组地址,而不是第一行第一列元素地址
}
一级指针传参
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;//这里是将数组首元素地址赋值给了指针p
int sz = sizeof(arr) / sizeof(arr[0]);
print(p, sz);//这里p存放的是数组首元素的地址
return 0;
}
二级指针传参
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);//这里二级指针pp存放的是一级指针的地址
test(&p);//这是一级指针的地址,接收这个参数需要二级指针
//pp=&p
return 0;
}