目录
一.指针是什么(*)
1.内存与地址
要想学习好指针,必须先搞好内存。
因为指针就是用来访问内存的,那么内存到底是什么?
内存:内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的。
为了有效地使用内存,把内存划分为一个一个的内存单元,每个内存单元的大小是1个字节,并且为了更加有效的访问到内存的每个单元,于是就给内存单元进行了一系列编号,这些编号就被称为该内存单元的地址,因此可以这样理解:地址叫做指针,指针叫做编号。
地址与编号怎么产生的呢?
在计算机中,有地址线,也就是物理电线。例如,32位机器上就有32根地址线,每根地址线上都有着高(1)/低电平(0)的信号,即可将电信号转化成数字信号。
所以32根地址线产生的地址就会是:
共有2的32次方个地址。
原因:共有32根地址线,所以将32根地址线排列开来的二进制的所有可能性共有 2^32种可能,故此也共有 2^32个编号来对内存进行编号,因为1个编号可管理1个字节的空间,所以32根地址线一共可管理 2^32个字节的空间。
2.指针
指针到底是什么?
指针:在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
所以这样形象化理解:
总结1:指针是个变量,用来存放内存单元的地址(编号)。
则相应的代码:
#include <stdio.h>
int main()
{
int a = 10; // 在内存中开辟一块空间用来存放 a
int* p = &a; // 对变量a,取出它的地址(使用&操作符)。
// 将变量a的地址存放到变量p中,此时p就是一个指针变量
// 变量p的类型是 int*
return 0;
}
分析:因为一个int类型的变量占4个字节,所以该变量在内存中的存储为:
总结2:指针就是变量,用来存放地址的变量。存放在指针中的值都被当成地址处理。
总结3:在32位的机器上,地址是32个0或者1组成的二进制序列,那么地址就得用4的字节(4*8bit)的空间来存储,所以一个指针变量的大小就是4个字节;那么在64位机器上,若有64根地址线,那一个指针变量的大小就应该是8个字节来存放一个地址。
总结:
指针是用来存放地址的,地址是唯一标识一块地址空间的。
指针的大小在32位平台上是4个字节,在64位平台上是8个字节。(只与平台有关)
二.指针和指针类型
对于指针的类型。在此之前我们已经了解变量有着不同的类型,整形,浮点型,字符型等。那相应的,指针也有其特定的类型。
看代码:
int num = 10;
p = #
要将&num(num的地址)保存到p中,因为p就是一个指针变量,那么它的类型是什么呢?我们给出指针变量相应的类型。
char* pc = NULL;
short* pc = NULL;
int* pc = NULL;
long* pc = NULL;
float* pc = NULL;
double* pc = NULL;
所以,指针的定义的类型方式:type+*。 例如:char* 类型的指针就是为了存放 char 类型变量得到地址,int* 类型的指针就是为了存放 int 类型变量的地址。故,上述存放num的地址p的类型就是int*。
三.野指针
野指针: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量 在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一 个地址是不确定的变量,此时去解引用就是去访问了一个不确定的低质,即结果是不可知的。
通常情况下导致野指针出现大致有两种:1.指针未被初始化;2.指针越界访问;3.指针所指向的空间被释放。
1.指针未被初始化
#include <stdio.h>
int main()
{
int* p;
*p = 20;
return 0;
}
在代码中,指针变量未初始化,所以其默认为随机值,那么它指向的地址也是随机的。 此时这个指针 p 就是野指针。
2.指针越界访问
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
*p = i;
p++;
}
return 0;
}
我们知道,除了&数组名和sizeof(数组名),其他的一切情况数组名都代表着数组首元素的地址,也就是&arrr[0] 这一地址 。
所以当指针指向的范围超过数组 arr 的范围是,p就是野指针。
3.指针所指向的空间释放
#include<stdio.h>
int* test()
{
int a = 20; //a是一个局部变量,出了作用域(出了test函数)就会被销毁
//即内存空间内没有了a
return &a;
}
int main()
{
int* p = test();
printtf("%d", *p);
return 0;
}
因为 a 出了作用域就被销毁了,所以要通过指针p来访问到 a 也就不可能了,此时 p 就是个野指针。
如何避免出现野指针:
1.指针初始化
必须要明确知道指针应该初始化位谁的位置,就直接初始化;若不知道指针初始化为什么值,就暂时初始化为NULL( NULL-->(void*)0 )。
2.小心指针越界
3.指针所指向的空间释放应立即将指针置NULL
int* ptr = NULL; // ptr是一个空指针,没有指向任何有效的空间
// 这个指针不能直接使用
int* ptr; //一个野指针。
4.指针使用之前应检查有效性(指针是否有效)
判断指针的有效性:
if(ptr != NULL)
{
// 可以使用
}
5.避免返回局部变量的地址
正常代码:
#include<stdio.h>
int main()
{
int* p = NULL;
int a = 20;
p = &a;
if (p != NULL)
{
*p = 100;
}
printf("a = %d", a);
return 0;
}
四.指针的运算
1.指针 +- 整数
可以不使用下标就可访问数组。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]); //用来计算数组元素的个数
for (i = 0; i < sz; i++)
{
*p = i;
p++; // p = p+1;
}
p = arr;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
分析:
由图:指针+-整数就可以不使用下标来访问整个数组。
此代码还可这样表示:
for (i = 0; i < sz; i++)
{
*(p + i) = i;
}
2.指针 - 指针
指针-指针所得到的数值的绝对值:指针与指针之间的元素个数。
前提条件:两个指针必须得指向同一块空间。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%d\n", &arr[0] - &arr[9]);
printf("%d\n", &arr[9] - &arr[0]);
return 0;
}
运行结果:
分析:
随着数组下标的增长,地址也随之的增加。所以返回的数值的绝对值是指针与指针之间的元素个数。
3.指针的运算关系
所谓运算关系,实际上就是来比大小。因为地址是有大小的。
#include<stdio.h>
int main()
{
int arr[5] = { 0 };
int* p = NULL;
for (p = &arr[5]; p > &arr[0];) //指针来比大小
{
p--;
*p = 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
将上列代码修改一下:
int arr[5] = { 0 };
int* p = NULL;
for (p = &arr[5]; p >= &arr[0];) //指针来比大小
{
p--;
*p = 1;
}
我们发现该代码是运行不起来的,why?
在指针运算关系中,有这样一标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
五.指针和数组
指针与数组之间的不同:
从本质上来看:
指针(指针变量)就是指针(指针变量),不是数组,指针变量的大小是固定的4/8个字节,是专门用来存放地址的。
数组就是数组,不是指针,数组是一块连续的空间,可以存放一个或多个类型相同的数据。
数组与指针之间的联系:
数组中,通常情况下数组名其实就是数组首元素的地址,即(数组名 <===> 地址 <===> 指针)
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int* p = arr; // p存放的是数组首元素的地址
所以 既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问每一个元素就成为可能。
例如:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr; // 指针p存放的是数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 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 的地址。所以我们就可以通过指针访问数组。
例如:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
运行结果:
六.二级指针
了解二级指针:
我们了解:指针也就是指针变量,是变量就有地址,那么,指针变量的地址存放的地方就是二级指针。
int a = 10;
int* p = &a;
int** pp = &p;
画图:
发现:一级指针p 存放的是变量a的地址,二级指针pp存放的是一级指针变量p的地址。
二级指针的运算:
1.通过对二级指针pp的解引用操作,先找到p,即 *pp 访问的就是 p。
int a = 10;
*pp = &a; //等价于 p = &a
2.通过对二级指针pp的解引用操作,先找到p,然后再次进行解引用操作找到a,即**pp访问的就是 a。
**pp = 100;
// 等价于 *p = 100;
// 等价于 a = 100;
二级指针应用的实例:
#include<stdio.h>
int main()
{
char arr1[] = "china";
char arr2[] = "be";
char arr3[] = "victory";
char* arr[] = { arr1,arr2,arr3 };
char** parr = &arr;
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%s ", *(parr+i));
}
return 0;
}
运行结果:
分析:
通过对二级指针,对一级指针数组遍历,实现对一级指针数组里面的字符串的打印。对二级指针+/- 1,找出对应一级指针数组内元素的地址,再进行相应的打印。(字符串的打印只需要知道起始位置(首元素地址)就行了)
七.指针数组
指针数组:重点在后两个字数组,所以指针数组就是一个数组,是存放指针的数组。
我们已经了解了整形数组,字符数组等。
int arr1[5]
char arr2[5];
那么指针数组是什么样的呢?
int* arr3[5];
arr3是一个数组,有5个元素,每个元素的类型是一个整形指针。
实例:用指针数组模拟一个二维数组
#include<stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* arr[] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for(j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
运行结果:
分析:
可以这样理解,指针数组内的每个元素都是地址。
通过arr[i]来对整形指针数组 arr 进行遍历,arr[0] 这一地址找到 arr1数组 ,通过arr[1] 这一地址找到 arr2数组 ,通过arr[2] 这一地址找到 arr3数组,再通过 j 这一变量实现对各个数组内元素的打印。
** 进阶指针(指针精髓)**
今天的指针(初阶)先给大家分享到这里。
此篇为学习指针的地基/基础,指针的精髓(**重点**)可看查看文章:
https://blog.csdn.net/2302_78684687/article/details/137246229?spm=1001.2014.3001.5501