你比昨天更懂指针了吗?

Posted Suk_god

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你比昨天更懂指针了吗?相关的知识,希望对你有一定的参考价值。

指针是什么

标准定义

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

实质

指针是个变量,一个用来存放地址数据的变量
即指针就是地址!

大小

在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,这样才能存放一个地址数据。
总结:指针用来存放地址,在32位平台上指针占4个字节,在64位平台上,指针占8个字节。

为什么要有指针

我们想这样一个问题,计算机内存可以看做是一整块空间,系统针对于不同的功能对内存块进行划分。假设划分出来的常量区有4GB,我们存储一个整型常量只占用了4个字节,如果现在有5*1020个数据,我要你找出其中的某个数据,你应该如何查找?
是不是要进行遍历呢?显然时间成本很高!
如果这时候我们有一个变量能够保存数据的地址,我们不就可以根据地址快速找到对应数据。那这不就是指针的作用嘛~
所以说:指针可以提高效率!!

指针基本操作和指针类型

类型

指针的定义方式:type+*+变量名
表示的意义就是:定义一个type*类型的指针变量,指向type类型的数据,type可以是char,short,int,float,double等,也可以是自定义类型。

指针+ -整数

演示实例

# include<stdio.h>
#include<windows.h>

int main()
{
	int n = 10;
	char *pc = (char*)&n;
	int *pi = &n;

	printf("%p\\n", &n);
	printf("%p\\n", pc);
	printf("%p\\n",pc+1);
	printf("%p\\n",pi);
	printf("%p\\n",pi+1);

	system("pause");
	return 0;
}


从此例可以看出,不同类型的指针变量+1结果不尽相同,但都有一个规律,那就是加的大小在数值上等于type的大小~
总结:对指针变量+1,实际上是加上它所指向的数据类型的大小。
当然,-操作也适用。

指针-指针

int MyStrlen(const char *str)
{
	char *p=s;
	while(p!='\\0')
	{
		p++;
	}
	return p-str;
}

此函数的功能是求一个字符串的长度,使用的方法就是双指针指向,其中一个指针指向头,另一个指针指向尾,两者相减就是字符串的长度!
所以:两个指针相减,代表指针之间所经历的元素个数元素由参与计算的指针决定,与指针本身的类型强相关!

指针的关系运算

//代码1
for(vp = &values[N_VALUES];vp>=&values[0];)
{
	*--vp = 0;
}
//代码2
for(vp = &values[N_VALUES];vp>=&values[0];vp--)
{
	*vp = 0;
}

在绝大多数编译器上以上两份代码都可以编译成功,然而我们应该还是要避免这样写,因为标准并保证它可行。
标准规定
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

指针的解引用

demo

# include<stdio.h>
#include<windows.h>

int main()
{
	int n = 0x11223344;
	char *pc = (char *)&n;
	int *pi = &n;
	*pc = 0;
	*pi = 0;
	system("pause");
	return 0;
}


可见对于char类型的指针,在解引用时一次只能访问一个字节。所以说指针的类型决定了,对指针解引用的时候能访问多少个字节。
也就是说对指针解引用,可以看到sizeof(type)个字节

野指针

概念

野指针就是指针所指向的位置是未知的(随机的,不正确的,没有明确限制的)

成因

1、指针未初始化,e.g:

#include<stdio.h>
int main()
{
	int *p;//局部指针未初始化,默认为随机值
	p =20;
	return 0;
}

2、指针越界访问

#include<stdio.h>
int main()
{
	int arr[10] = {0};
	int *p=arr;
	for(int i=0;i<11;i++)
	{
		//当指针指的范围超出arr的临界值,该指针就变成了野指针!!
		*(p++) = i;
	}
	rerurn 0;
}

3、指针指向的空间释放

如何规避野指针

1、指针初始化
2、小心指针越界
3、指针指向的空间释放后即置NULL
4、指针使用之前检查有效性

指针和数组

数组

#include<stdio.h>
int main()
{
	int arr[10] = {1,2,3,4,5};
	printf("%p\\n",arr);
	printf("%p\\n",&arr[0]);
	
}


我们发现,数组名和数组第一个元素的地址相同。
结论:数组名代表首元素的地址。
那么这么写代码是可行的:

int arr[10] = {0};
int *p = arr;
//其中,p代表首元素的地址

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
例如:

#include <stdio.h>
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,0};
    int *p = arr; //指针存放数组首元素的地址
    int sz = sizeof(arr)/sizeof(arr[0]);
    for(i=0; i<sz; i++)
   {
        printf("&arr[%d] = %p   <====> p+%d = %p\\n", i, &arr[i], i, p+i);
   }
    return 0;
 }


所以p+i其实计算的是数组arr下标为i的地址值
那我们就可以直接通过指针访问数组
如下:

int main()
{
	inr arr[10] = {1,1,2,3,4,5};
	int *p = arr;
	int size = sizeof(arr)/sizeof(arr[0]);
	for(int i=0;i<size;i++)
	{
		printf("%d ",*(p+i));
	}
	return 0;
}

总结:指针与数组没有任何关系~!!!
它们只是在形式上比较相似。至于为什么相似,是因为数组在作为函数的参数传入函数中使用时,要发生降维,降维成指向其内部元素类型的指针,所以说在函数中使用数组时绝大多数情况下是通过指针的形式进行访问的。故而,就有了两套访问数组内容的表达方式。即指针解引用和数组下标索引访问。

二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里呢?这就是二级指针
对于二级指针的运算有:
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .

int b = 20; 
*ppa = &b;//等价于 pa = &b;

**ppa 先通过 *ppa 找到pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .

**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:

int main()
{
	char ch = '	q';
	char *pc = &ch;
	*pc = 'w';
	return 0;
}

还有另一种使用方法:

int main()
{
	char *str = "hello bit";//?
	printf("%s\\n",str);
	return 0;	
}

这个代码中第一条语句是什么含义呢?
×(1)将字符串放到指针变量str中
(2)将字符串的首地址放到指针变量str中
所以本质是把字符串首个字符地址放在指针变量中~~
那么来道面试题!

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char *str3 = "hello bit.";
    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; }


这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

指针数组

实质:是一个数组,数组里面存放的元素是指针类型
e.g:

int* arr1[10];//整型指针数组
char *arr2[4];//一级字符指针数组
char**arr3[4];//二级字符指针数组

数组指针

数组指针的定义

实质:是一个指针,指针指向的是一个数组

int *p1[10];
int (*p2)[10];

这两条代码有什么区别呢?
p1:首先和中括号结合,所以它是一个数组,数组内存放的数据类型是int*,所以p1是一个指针数组
p2:首先执行圆括号里的内容,所以p2先和结合,是一个指针,这个指针指向一个int [10]的数据,也就是数组。所以说,p2是一个数组指针
总结:[ ]的优先级高于
号的,所以必须加上( )来保证p先和 * 结合

&数组名 VS 数组名

#include<stdio.h>
int main()
{
	int arr[10] = {0};
	printf("%p\\n",&arr);
	printf("%p\\n",&arr+1);
	
	printf("%p\\n",arr);
	printf("%p\\n",arr+1);
	return 0;
}

运行结果如下

结论:
&arr和arr在数值上一样,但所表示的意义不一样!!
&arr表示的是数组arr的地址,而不是数组首元素的地址
对数组的地址+1,相当于加上整个数组的大小。
arr数组名则表示数组首元素的地址,对他+1,相当于加上数组元素类型的大小。

数组指针的使用

数组指针,顾名思义,指向数组的指针

#include<stdio.h>
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	int (*p)[10] = &arr;//把数组arr的地址赋给指针变量p
	return 0;
}

一个数组指针的使用

#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; }

对于函数print_arr2()来说,它的第一个参数为数组指针类型,接收的应该是一个数组的地址
而对于二维数组来说,它实际上是一维数组,一维数组中的元素类型也是一维数组,所以arr代表首元素的地址,也就是一维数组的地址,这与上面的叙述自洽。
下面来看看这些代码的意思

int arr[5]//整型数组,5个元素
int *parr1[10]//整型指针数组,10个int*类型的元素
int (*parr2)[10];//指针,指向int类型的数组,所以是数组指针
//首先括号里面是一个指针数组,[5]表示一个拥有5个元素的空间,
//所以整体含义就是有一个5个元素的数组,数组中每个元素的类型为整型指针数组
int (*parr3[10])[5];

数组传参和指针传参

数组传参,发生降维,降维成指向其内部元素类型的指针

一维数组传参

#include<stdio.h>
void test1(int arr[])//right
{}
void test1(int *arr)//right
{}
void test1(int arr[10])//right
{}
void test2(int *arr[20])//right
{}
void test2(int **arr)//right
{}

int main()
{
	int arr[10] = {0};
	int *arr2[20] ={0};
	test1(arr);
	test2(arr2);
	return 0;
}

二维数组传参

#include<stdio.h>
void test(int arr[3][5])//right
{}
void test(int arr[][5])//right
{}
void test(int (*arr)[5])//right
{}
int main()
{
	int arr[3][5] = {0};
	test1(arr);
	return 0;
}

一级指针传参

#include<stdio.h>
void print(int *p,int sz)
{
	for(int i=0;i<sz;i++)
	{
		printf("%d ",*(p+i));
	}
}
int main()
{
	int arr[10] ={1,2,3,4,5,6,7,8,9,0};
	int *p=arr;
	int sz = sizeof(arr)/sizeof(arr[0]);
	print(p,sz);
	return 0;
}

运行结果如图

当一个函数的参数是一级指针(int* ,char*)时可以接收的数据类型有:
一级指针,一维数组,单个字符的地址,字符串,字符数组

二级指针传参

#include<stdio.h>

void test(in

以上是关于你比昨天更懂指针了吗?的主要内容,如果未能解决你的问题,请参考以下文章

更新:C++ 指针片段

片段中的 EditText 上的空指针异常 [重复]

为什么前后端分离了,你比从前更痛苦?

为什么前后端分离了,你比从前更痛苦?

为什么前后端分离了,你比从前更痛苦?

挽回女人时的说话话术技巧