指针是什么
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。 从根本上看,指针(Pointer)是一个值为内存地址的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是整数,指针变量的值是地址。
注:与指针相关的运算符 取地址操作符:& 间接访问(或解引用)操作符:*
例:
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
return 0;
}
总结∶指针就是一个变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
经过上方的初步了解,我们可能会产生一下问题: 1.一个内存单元有多大?(1byte) 2.如何编址?
我们内存单位可以分为:bit(比特)、byte(字节)、kb、mb、gb、tb、pb… 1byte = 8bit 1kb = 1024byte 1mb = 1024kb 1gb = 1024mb以此类推
假设我们一个内存单元是1bit,那么一个char类型的变量所占空间是1byte,所以内存就需要为char类型的变量分配8个内存空间,若是int类型的变量,那就需要分配32个内存空间,这样就会造成内存空间不够用的情况。
若一个内存单元是1byte,那一个char类型的变量所占空间是1byte,所以内存就只需要为char类型的变量分配1个内存空间,若是int类型的变量,那只需需要分配4个内存空间,这样则刚刚好。
若一个内存单元是1kb,那一个char类型的变量所占空间是1byte,所以内存就只需要为char类型的变量分配1个内存空间,若是int类型的变量,那也只需要分配1个内存空间,但这样是非常不合适的,1kb有1024个byte,会造成空间的浪费
综合考虑,一个内存单元为1byte最为合适。
我们计算机可以分为32位机器和64位机器
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电 / 负电(1或者0) 那么32根地址线产生的地址就会是∶ 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 … 10000000 00000000 00000000 00000000 10000000 00000000 00000000 00000001 ……. 11111111 11111111 11111111 11111111 这里就有2的32次方个地址。 每个地址标识一个字节,那我们就可以给(2 ^ 32Byte == 2 ^ 32 / 1024KB == 2 ^ 32 / 1024 / 1024MB == 2 ^ 32 / 1024 / 1024 / 1024GB == 4GB)4G的空闲进行编址。 同样的方法,64位机器,如果给64根地址线,我们也可以计算出能编址的空间大小
这里我们就明白 :
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
- 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结: 指针是用来存放地址的,地址是唯一标示一块地址空间的。 指针的大小在32位平台是4个字节,在64位平台是8个字节。
声明指针: 指针声明示例:
int * pi;
char * pc;
float * pf;
类型说明符表明了指针所指向对象的类型,星号( * )表明声明的变量是一个指针。int* pi;声明的意思是pi是一个指针变量。
指针和指针类型
我们都知道,数据的类型有char、short、int、float、double、long等类型,那指针是不是也有类型呢?答案是有的。
我们平时使用指针变量的时候,一般都会这样声明指针变量:
int main()
{
int* pa;
char* pb;
short* pc;
float* pf;
double* pd;
return 0;
}
从上方我们也知道,指针的大小是固定的,在32位平台是4个字节,在64位平台是8个字节。那为什么还要将指针进行分类的?这样做有什么意义呢?请看下方代码:
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
我们将a的地址放入指针变量pa中,再加上 pa = 0; 即调用指针来更改指针指向地址中的内容。通过F10调试,打开窗口-- - 内存监视器(类似变量监视窗口),可以看到a的内容确实改变了: 当我们将上方代码中a的地址放入 char 的指针变量中,接下来我们看看a的内容会发生怎样的变化:
#include <stdio.h>
int main()
{
int a = 0x11223344;
char* pc = &a;
*pc = 0;
return 0;
}
我们可以发现,这回与上方放入int* 类型的指针变量有所不同,它只将一个字节的内容改为0,而上方int* 类型指针变量是将四个字节的内容全部改为0。 通过这个我们可以发现指针的类型还是有意义的,其意义在于指针解引用操作时,一次访问几个字节(访问内存的大小),比如int * 类型的指针,可以改动4个字节的内容,而char * 类型的指针只能更改一个字节的内容。
指针类型的意义1:指针的解引用
总结:指针类型决定了指针进行解引用操作的时候,能够访问内存的大小
举例: char * p; *p能够访问1个字节 int * p; *p能够访问4个字节 double * p; *p能够访问8个字节
指针类型的意义2:指针 + - 整数 请看下面例子:
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
char* pc = &a;
printf("%p\n", pa);
printf("%p\n", pa + 1);
printf("\n");
printf("%p\n", pc);
printf("%p\n", pc+1);
return 0;
}
从运行结果可以看出:pa与pa+1之间相差为4,而pc与pc+1之间相差为1。
总结:指针类型决定了,指针 + - 整数的时候的步长(指针向前或者向后跳过几个字节)
举例: char* 指针 + 1 跳过1个字节 int* 指针 + 1 跳过4个字节 double* 指针 + 1 跳过8个字节
指针类型意义的运用(价值): 如果我们将int * p = arr; 改成char* pc = arr;会发生什么呢? 是不是只会将10个字节改成1呢? 而我们数组int arr[10]有40个字节 ,10个字节的大小相当于2个半int类型的大小,我们可以打开内存窗口调试,看一下是不是这样的情况。
从这我们可以直观的看出指针类型的价值所在了。
野指针
定义: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因:
1. 指针未初始化:
#include <stdio.h>
int main()
{
int *p;
*p = 20;
return 0;
}
2. 指针越界访问:
#include <stdio.h>
int main()
{
int arr[10] = 0;
int i = 0;
int* p = arr;
for (i = 0; i <= 10; i++)
{
*p = i;
p++;
}
return 0;
}
3. 指针指向的空间释放:
#include <stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
如何避免野指针: 1.指针初始化 2.小心指针越界 3.指针指向空间释放即使置NULL 4.指针使用之前检查有效性 5.避免返回局部变量的地址
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
int* p2 = NULL;
if (p2 != NULL)
{
*p2 = 100;
}
return 0;
}
指针运算
指针运算包括一下三种: 1、指针+ - 整数 2、指针 - 指针 3、指针的关系运算
下面我们对这三种运算进行详细讲解
指针 + - 整数
#include<stdio.h>
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\t%p\n", *p, p);
p++;
}
return 0;
}
我们可以这样将数组里的内容一一打印出来。 这里我们使用的是指针 + 1,那么指针 + 2或指针 + 3是什么样子的呢?请看下面的举例: 指针除了可以 + 整数,也可以 - 整数,例如:
#include<stdio.h>
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* p = &arr[9];
printf("%p\n", p);
printf("%p\n", p-2);
return 0;
}
可以看出p与p-2之间相差8个字节,说明向前跳过了2个整型。
指针 - 指针 我们现在已经可以知道,指针变量是用来存放地址的,那么 指针-指针 就是 地址-地址,那么两指针相减所得结果是什么呢?请看下方举例:
#include<stdio.h>
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
printf("%d\n", &arr[9] - &arr[0]);
printf("%d\n", &arr[0] - &arr[9]);
return 0;
}
从运行结果可以得出,打印&arr[9] - &arr[0]和&arr[0] - &arr[9]的结果分别为9和-9,其实这代表从arr[0]到arr[9], 其中间有9个元素。
建议:指针 - 指针,用大地址 - 小地址。否则就会像本例所示,出现负数。
总结:指针 - 指针 得到的数字的绝对值是指针和指针之间元素的个数。
警告:指针 - 指针的前提是两个指针指向同一块区域(比如同一个数组) 错误示例: 指针 - 指针实际应用举例: 求字符串长度:
#include<stdio.h>
int my_strlen(char* s)
{
int count = 0;
char* str = s;
while (*s != '\0')
{
s++;
}
return s-str;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
指针的关系运算 指针的关系运算就是比较指针的大小 请看下方举例: #include<stdio.h> float values[5]; float* vp; //指针+ -整数;指针的关系运算 for (vp = &values[5]; vp > &values[0]; ) { *- -vp = 0; } 我们将代码简化, 代码修改如下: 实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。 标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
图形理解:
指针和数组
数组是什么? 数组 – 是一块连续的空间,放的是相同类型的元素。 数组大小和元素类型,元素个数有关系 指针是什么? 指针(变量)-- 是一个变量,存放地址 指针变量的大小 是 4(32bit)/8(64bit)个byte
简单复习一下数组的知识点: 在之前学习数组的时候,我们已经知道了数组名就是首元素的地址,请看下面这个例子: 可以看到arr 和& arr[0]结果是一样,说明数组名就是首元素的地址。 我们学习过数组就会知道,数组名是首元素地址还有两个例外:
数组名在绝大多数情况下是首元素地址,但有两个例外: 1、& arr-- - 取地址+数组名 – 这时数组名不是首元素的地址,表示整个数组 &数组名 取出的是整个数组的地址。 而整个数组的地址和首元素地址有什么区别呢?这个问题我们在数组中已经详细讲解过 2、sizeof(arr)----sizeof(数组名)-- - 数组名表示的整个数组–sizeof(数组名)计算的是整个数组的大小。
既然数组名是首元素地址,那么就可以把数组名当成地址存放到一个指针中,这样我们就可以使用指针来访问一个数组。看下面例子:
#include<stdio.h>
int main()
{
int arr[] = { 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("&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[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
二级指针
指针变量是用来存放地址的,但它也是一个变量,是变量就有存储空间,就有地址,那么指针变量的地址存放在哪里呢?
这就出现了二级指针的概念:
二级指针:存放指针变量的地址
请看下面例子:
#include <stdio.h>
int main()
{
int a = 20;
int* p = &a;
int** pp = &p;
return 0;
}
这里我们进行画图理解: 接下来我们要如何使用二级指针进行访问呢? 我们可以对pp进行解引用操作 * pp就能找到 p的内容也就是a的地址,再次进行解引用操作** pp,就能找到a了! 如图:
指针数组
指针数组是指针还是数组呢? 我们可以进行这样的类推,如:
整型数组:存放整型的数组就是整型数组 字符数组:存放字符的数组就是字符数组
那我们就可以认为: 指针数组:存放指针的数组就是指针数组
总结:指针数组 - - - 是数组,是存放指针的数组。
指针数组的声明: int* parr[5]代表整型指针数组,每个元素都是一个整型的地址 char* pc[6]代表字符指针数组,每个元素都是一个字符的地址
那指针数组是如何使用的呢?请看下面例子:
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = { &a, &b, &c };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d\n", *(arr[i]));
}
return 0;
}
以上就是初阶指针的全部内容。 希望大佬们帮忙指点一下呀 😃
|