让学指针变得更简单(一)


前言

在这里插入图片描述

学指针没有方法和好的老师教授想要学好确实是难的,希望我写的小小的文章可以让大家对指针的学习有更深的了解或者能够有一些帮助!!!很多知识的学习如果没有兴趣的话说实话是枯燥的和难学的,这是正常的,个人的提升哪有简单的?不要因为难就轻易放弃,你的追求的目标的高度决定你以后人生的高度,不用因为难而一直降低对理想目标的高度;

在这里插入图片描述


一、指针的概念和特点

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)注意这个特点很重要,很多题都会考这个知识点,不要掉坑里了。
  3. 指针是有类型的,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限,类型决定解引用能够访问空间的大小,加减整数跳过空间的大小。指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
    比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节,short* 解引用访问两个字节等等。

4.指针–指针表示两个指针之间的元素的个数,可以正数也可以负数,看后面的指针比前面的指针如何,要注意的是元素的个数!!!

二、野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针成因
1.指针未初始化
#include <stdio.h>
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.动态内存开辟没有释放空间不仅造成了野指针,还有内存的泄露!!!

三,字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;
int main()
{
char ch = ‘w’;
char *pc = &ch;
pc = ‘w’;
return 0;
}
很容易看出就是把字符的地址放到字符指针类型变量里面;再来看看这个类型的字符指针;
int main()
{
const char
pstr = “hello bit.”;//这里是把一个字符串放到pstr指针变量里了吗?
printf(“%s\n”, pstr);
return 0;
}
上面const修饰为了字符指针pstr指向的内容不能被修改,在这里并不是把整个字符串的地址放进pstr中,也放不进去,这里的字符指针存放的是首元素的地址,也就是字符‘h’的地址;我们看下面这道面试题:

#include <stdio.h>
int main()
{
char str1[] = “hello bit.”;
char str2[] = “hello bit.”;
const char *str3 = “hello bit.”;
const char *str4 = “hello bit.”;
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;
}
这里就直接开始解释了,答案是str1!=str2,str3==str4;
为啥呢?这里的 "hello bit."它是个常量字符串,是不能够被改变的,在c/c++中常量字符串存放在一个特定的空间中,当我们用指针指向这个常量字符串所在的空间的时候,其实每个指针指向的都是同一个首元素,存的也是同一个首元素的地址,可以说str3和str4是相同的;再看str1,str2是数组,定义并且初始化数组的时候开辟的空间是各自独立的,虽然存放的内容是一样的,但是str1,str2本身表示的是首元素的地址,所以是不同的;下面是更官方的解释:
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同;

四,指针数组

这个简单,指针数组是一个存放指针的数组;
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

五,数组指针

5.1 数组指针的定义

数组指针首先肯定是指针,而不是数组,这个我们要清楚;然后就是:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针,也就是存放的是数组的地址嘛。
int* p1[10];
int(* p2)[10];
//p1, p2分别是什么?
//解释:p1先和[]结合,所以p1是数组,大小是10个int* 类型元素的数组,
p2先和 * 结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于 * 号的,所以必须加上()来保证p先和*结合。
那么啥又是数组的地址呢?

5.2&数组名VS数组名

先来一个数组吧!int arr[10]={0};
arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf(“%p\n”, arr);
printf(“%p\n”, &arr);
return 0;
}
其实打印出来的地址都是一样的,都是首元素的地址,不过在加减整数的时候就知道有什么不同了;
在这里插入图片描述
再看一段代码:
#include <stdio.h>
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的区别;

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

5.3.数组指针的使用

那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include <stdio.h>
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;
}
上面两个打印数组方法但是一样的,不同的是参数的传递不同;二维数组的首元素的特性也恰好适合数组指针的运用;
再次重复:但是二维数组的首元素是二维数组的第一行,很重要!!!后面会用很多!!!然后就是 *(p+1)和p[1]两个玩意儿是一个东西,CPU在运算的时候都是按照 *(p+1)这个东西来运算的,虽然可能是直接传过来按照指针的定义来表示形参,但是都用那个来算的哈;这个我后面要写的指针面试题题目博客会用很多,大家也一定要牢记!!!

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5];//数组,5个int类型大小的数组
int *parr1[10]; //先于括号结合,是大小是10,元素类型为int * 的数组
int (*parr2)[10]; //先和 * 结合,是指针,指向的是大小是10,元素类型为int的数组
int (*parr3[10])[5]; //先和[]结合,是数组,大小是10,元素类型是数组指针类型,该指针指向的数组是大小为5,元素类型是int类型的数组;

六,数组参数、指针参数

6.1一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
看看那个ok?
我一通乱看后,额,其实都可以哈;test(arr)这个函数传参的话,传数组用数组接收没毛病,而且一维数组可以不写大小的哦,但是二维数组是有规定的,所以为了习惯,免得二维数组也不设置大小,所以我们还是习惯写上去的,自己懂一维数组接收可以不设置大小就是了;所以第一第二都可以哈,然后实参是数组名,实际上也就是首元素地址嘛,所以用指针来接收地址没毛病吧?没毛病哈,第三个也可以;
test2函数的话传过来的数组是大小是20,元素类型是int * 类型的,就是数组里面放的是地址嘛;那么第四个一模一样的肯定没毛病吧,第五个嘛,首先形参是二级指针,接收数组,数组元素类型是int *类型,那也没毛病吧,看上面的类型是int类型的就用int呗,再用 *表示指针,就是int * arr,我们int * 类型的数组用int **表示也可以理解了吧;

6.2 二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int arr)//ok?
{}
void test(int
arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
二维数组为实参,那就用二维数组接收没毛病,但是二维数组后面括号的大小必须要指定哈,所以第一个,第三个可以,第二个不行;但是用指针接收的话就要仔细点哈,传过去的是二维数组首元素地址,又二维数组首元素地址是可以看成第一行的地址(二维数组可以看成多个一维数组,这里的话就是3个元素个数为5的一维数组,第一行就是第一个一维数组嘛);一维数组的地址自然是用数组指针来接收了嘛,所以的话那个是数组指针咧,倒数第二个吧,其它的什么都不能够接收二维数组的哈,二级指针不行的哈,那个是接收二级地址的,或者是一维数组,元素是指针类型的才行哈;

6.3一级指针传参

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test1(int * p)
{}
//test1函数能接收什么参数?
1.地址
2.指针
3.数组(其实也就是地址嘛)
void test2(char* p)
{}
//test2函数能接收什么参数?
1.char 类型的变量地址
2.char * 类型的指针
3.数组,元素是char类型

6.4二级指针传参

当函数的参数为二级指针的时候,可以接收什么参数?

void test(char ** p)
{
}
int main()
{
char c = ‘b’;
char * pc = &c;
char ** ppc = &pc;
char * arr[10];
test(&pc);
test(ppc);
test(arr);//Ok?
return 0;
}
第一个是一级指针的地址,没毛病吧,可以,第二个是二级指针,用二级指针接收更没毛病了,第三个的话其实也是可以滴,数组名本身就是首元素地址,然后数组元素又是char *类型的,所以用这个没毛病哈,和我上面那个一级指针传参差不多的方法分析哈;

七,函数指针

首先说明啊,函数test,和&test这两个地址是一样的哈,都可以表示函数的地址,要是将函数传参的话,其实是可以不用&符号的,后面函数指针调用的时候也可以不用&符号;

void test()
{
printf(“hehe\n”);
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (* pfun1)();
void * pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void;我们要先和 * 结合参可以表示指针,上面也说过很多次了,用括号括起来嘛,因为不这样容易先和小括号结合就不能表示为指针了;
//代码1
( * ( void ( * ) ( ) ) 0) ( );
看得懂这个代码的逻辑莫?解释一下哈
靠近0左边的括号其实是表示强制类型的括号,把0强制类型成函数指针类型,就是把0看成一个无参,返回值为void的函数的地址嘛,然后的话再解引用地址调用函数嘛,这个函数指针指向的函数无参,返回值为void,无返回值嘛,调用的时候所以也没有参数传过去嘛;
//代码2
void ( * signal ( int , void ( * ) ( int )) ) ( int );
这个玩意儿仔细看看还是不会哈哈,这里给你们解释一下哈
signal首先和()结合,不是指针,那么猜一下,看到了括号里的两个东西,signal应该是函数名,里面的是两个参数类型了哈,然后去掉函数名和参数的话就是剩下返回值了,返回值是我们刚刚学过的 void( * ) (int),这个不就是函数指针类型嘛,说明返回值不就是函数指针嘛,该函数是返回值void,参数是只有一个int类型的函数;然后看看参数吧,第一个是int类型的,第二个也是函数指针类型的哈,一样的,该函数是返回值void,参数是只有一个int类型的函数;这玩意是一个函数声明哈!!!
代码2太复杂,如何简化:
typedef void(*pfun_t)(int); 注意是将pfun_t放进里面的哈,要注意!!!
pfun_t signal(int, pfun_t);


总结

先写到这里,后面指针博客应该还要写两篇左右才能写完!!!
在这里插入图片描述
个人觉得这个图片挺形象的哈哈

猜你喜欢

转载自blog.csdn.net/qq_68844357/article/details/125404380