知识点的总结+实例加深理解,有不对地方希望各位指正,如果不理解指针,可以看看你第一小节内容。
目录
1. 指针是什么
在计算机科学中,指针是变成语言的一个对象,利用地址,它的值直接指向存在电脑存储器中的另一个地方的值。由于通过地址可以找到所需的变量单元,可以说,地址指向该变量单元。指针的意思就是通过它能找到以它为地址的存储单元。实话说,
理解,有两个要点
1.指针式内存中一个最小单元的编号,也就是地址。
2.平时所说的指针,通常指的就是指针变量,用来存放内存地址的变量。
实话说,就是指针就是地址,通常说的指针就是指针变量。
1.内存,我们电脑上的运行内存就是4G/8G/16G/32G,电脑上的存储设备,程序运行的时候会加载到内存中也会使用内存空间,例如你的任务管理器中的内存使用率
我打开的软件网页运行均会占用内存,这就是电脑内存。那么开始说指针,假设机器是32位的操作机器,目前规定内存中的最小单元是1个字节(8个bit),下方是【一个有32个地址线才有的内存,每一个字节都有一个编号,但是这个编号是不存储的,一个编号就是指针。
32根地址线可以产生2的32次方的不同的编码(电脑中的数字信号),对应的最小的内存单元,那这个内存是多大呢?2的32次方/1024/1024/1024=4GB,内存为4G。而我们的指针变量实际存储的就是地址编号,例如0x00.......,存储的是一个4个字节大小的值(32位地址编号的大小就是4个字节),因此需要4个字节来存储指针变量,这样就可以理解指针就是地址,就是内存中的一个编号。那么64位的机器指针变量的占用多大的内存?8个字节。
那么我们再引入一个取地址得操作符&,
int a = 10;
&a;//对a取地址,取出a的地址
a是一个整型变量,整型变量占用的是4个字节,每一个字节都有对应的地址,
&a取出来的地址是整型a的第一个字节的地址(较小的地址)。
2.
指针变量就是地址,如果我知道你的住址,我就可以找到你,找到你房子,瞅瞅你房子里面的东西,指针变量所存储的地址,就是你家门牌号,地址中存储的东西,就是你家房子里有啥。
总结
1. 指针是用来存放地址的,地址是唯一标示一块地址空间的。
2. 指针的大小在32位平台是4个字节,在64位平台是8个字节。
看个实例如何用
int main()
{
int a = 10;//在内存中开辟空间创建a变量,占用四个字节
int* pa = &a;//创建指针变量,在常规内置变量的后方加上*表示创建同类型的指针变量
//将a的第一个字节的地址存放在pa指针变量中,
return 0;
}
其他数据类型指针的创建。
char* p1;
float* p2;
short* p3;
pa是指针变量,指针变量是专门用来存放地址的。如下图,在&取地址和指针变量的内容是一样的。
2. 指针和指针类型
大家都知道变量有不同的类型,那指针肯定也有不同的类型来配合不同的变量。我们直接引用实例来说明指针类型的意义。
指针的类型
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
//NULL是空值的意思,NULL=0,在没有确切的指针存放的指针变量最好赋值NULL
引入解引用操作符 * , *(pa)将pa值得是pa指向的变量
2.1 指针类型访问权限
创建的整型指针变量
创建的字符指针变量
可以看出的是指针变量类型也是有意义的
1.指针变量决定了指针机型解引用操作的操作的是狗一次性可以访问的几个字节。(访问权限的大小)
char*的指针,解引用访问的是1个int;
int*的指针,解引用访问的是4个int;
float*的指针,解引用访问的是4个int。
如果像访问三个字节的话,可以使用字符类型一个一个访问。
2.2 指针类型的步长
再来一个程序看看区别
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pb = &a;
printf("%p\n", pa);
printf("%p\n", pa+1);
printf("%p\n", pb);
printf("%p\n", pb + 1);
return 0;
}
2.指针类型决定了指针的步长(指针+1能跳过几个字节)
字符指针+1,跳过1个字节;
整型指针+1,跳过4个字节.
注意:
指针的加减,指针指向的是何种变量的类型没有关系,主要和指针的类型有
3. 野指针
3.1 野指针定义
野指针就是未初始化的指针,就是指向恶的位置不可知(随机的,不正确,没有明确限制的)。
3.2 野指针的成因
(1)指针未初始化
int main()
{
int* pa;
*pa = 20;//在未初始化的指针,其中的值也是随机,程序是不会执行的
//解引用也就是访问了一个不确定值
printf("%d", *pa);
return 0;
}
(2)指针的越界访问
指针指向的空间不合理,比如一个10个元素整型的数组,打印出第11的元素的整型,那么第十一个元素就是随机值了
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%d ", *pa);
pa++;
}
return 0;
}
(3)指针指向的释放空间
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* pa = test();
printf("%d\n", *pa);
return 0;
}
解释一下:子函数中局部变量的内存在return之后就已经返还给内存了,再次访问的是野指针,但是你执行之后还是会输出10,说明函数的栈帧还未被覆盖,可以printf()加上一个函数覆盖内存,破坏test()建立的栈帧空间。
3.3 如何规避野指针
1. 指针初始化(代码1)
2. 小心指针越界
3. 指针指向空间释放即使置NULL(代码2)
4. 指针使用之前检查有效性 (代码3)
//代码1
int a = 10;
int* pa = &a;//初始化
//代码2
int* p = NULL;//强制初始化位0
//NULL-空指针,专门用来初始化指针的
//代码3
if (*p != NULL)
{
}
4. 指针运算
指针+- 整数
指针-指针
指针的关系运算
4.1 指针+ - 整数
int main()
{
float arr[5] = { 1.0,2.0,3.0,4.0,5.0 };
float*pa = &arr[0];
for (pa = &arr[0]; pa < &arr[5];)
{
*pa++ = 0; //*pa=0;pa++ 将数组arr中的元素全部置0
}
return 0;
}
不做赘述,指针类型处有。
4.2 指针-指针
前提:两个指针要指向同一块空间
指针-指针,得到的是两个指针之间的元素个数的绝对值。
int main()
{
int arr[10] = { 0 };
printf("%d", &arr[9]-&arr[0] );
return 0;
}
得出的结果是9,反过来是-9
练习求字符串中元素的个数,在不用库函数的情况下
int my_strlen(char* str)
{
char*start = str;
while (*str != '\0')
{
str++;
}
return str-start;
}
int main()
{
char arr[10] = "abcdef";
int len = my_strlen(arr);
printf("%d", len );
return 0;
}
4.3 指针的关系运算
其实就是指针关系的比较,
//代码1
int main()
{
float arr[5] = { 1.0,2.0,3.0,4.0,5.0 };
float*pa = &arr[0];
for (pa = &arr[5]; pa > &arr[0];)
{
*pa++ = 0; //*pa=0;pa++ 将数组arr中的元素全部置0
}
return 0;
}
//代码2
int main()
{
float arr[5] = { 1.0,2.0,3.0,4.0,5.0 };
float*pa = &arr[0];
for (pa = &arr[4]; pa >= &arr[0];pa--)
{
*pa = 0; //*pa=0;pa++ 将数组arr中的元素全部置0
}
return 0;
}
代码1pa的值开始便越界pa = &arr[5],代码2中的pa在执行玩最后一次的时候还会执行pa--程序,会有pa=&arr[-1]的存在,但是前端的越界是问题的。
上述两者的指针均已越界,但是标注规定
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。所以代码1可能会存在问题。
代码2实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
5. 指针和数组
数组名就是指针,数组名就是数组第一个元素的第一个字节,明确地址之后我就可以找到整个数组的元素。(知道你家地址,我就可以找到你家电视,卧室,厨房,厕所)
int main()
{
int arr[10] = { 0 };
int*pa = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//赋值
for (i = 0; i < sz; i++)
{
*pa = i + 1;
pa++;
}
//打印
pa = arr;
for (i = 0; i < sz; i++)
{
printf("%d ", *pa);
pa++;
}
return 0;
}
改善的一下
//赋值
for (i = 0; i < sz; i++)
{
*(pa+i) = i + 1;
}
//打印
pa = arr;
for (i = 0; i < sz; i++)
{
printf("%d ", *(pa+i));
}
arr[i]--->*(arr+i)两者是等价的
printf("%d ", *(arr + i));
printf("%d ", * (i + arr));
printf("%d ", arr[i]));
printf("%d ", i[arr]);
上述的表示方式是一样的,但是写的代码要正常点儿。
6. 二级指针
二级指针就是创建一级指针变量的指针变量,用来指向指针变量的二级指针变量。
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;//* 代表pa是指针,int 代表指针指向的类型是整型
int* * ppa = &pa; // 靠近ppa的*代表ppa是指针,int*代表ppa指向的数据类型
printf("%p\n", *ppa);//*ppa=pa
printf("%d\n", *pa);//*pa=a;
return 0;
}
也存在三级指针,但是三级指针就很少见了。
7. 指针数组
指针数组就是存放指针的数组。如整型数组,字符数组,浮点型数组,同样的也有存储指针的数组。
int arr1[5] = { 0 };
char arr2[5] = { 0 };
int* arr3[5] = { 0 };//数组的类型时指针数组
举个例子就明白
int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int e = 50;
int *arr[5] = { &a, &b, &c, &d, &e };//用来存放指针变量的数组
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
再来一个实例:使用一维数组来模拟二维数组。
int main()
{
int a[] = { 1,2,3,4 };
int b[] = { 2,3,4,5 };
int c[] = { 3,4,5,6 };
int* arr[] = { a,b,c };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
使用三个一维数组组成一个三行四列的二维数组,数组的名字就是数组首元素的地址,我们三个一维数组组成一个数组。
初阶指针目前已经总结完成,如果后续有其他的基础问题也会补充。
初识C语言已经完成了,过段时间会更新一些C语言的题目和进阶C语言的知识。