C语言详解:指针

article/2025/8/29 7:08:02

文章目录

    • 指针
      • 1. 指针的定义
        • 1.1 内存划分
        • 1.2 指针与指针变量
        • 1.3 指针的大小
      • 2. 指针的类型
        • 2.1 指针解引用方面
        • 2.2 指针 ± 整数方面
        • 2.3 总结
      • 3 野指针
        • 3.1 野指针的定义
        • 3.2 野指针的成因
        • 3.3 如何规避野指针
      • 4 指针运算
        • 4.1 指针 ± 整数
        • 4.2 指针`-`指针
        • 4.3 指针关系运算
      • 5 指针和数组
      • 6. 二级指针
        • 6.1 多级指针的定义
        • 6.2 类型中“*”的含义
        • 6.3 多级指针的使用
      • 7 指针数组
        • 7.1 指针数组的定义
        • 7.2 指针数组的使用

指针

这次的指针(Pointer)不是全部内容,因为后面的进阶部分还会更加深入。

1. 指针的定义

了解指针必须先理解内存的概念,因为指针指向的就是一个个的内存单元。

1.1 内存划分

内存是一块很大的空间,由一个个小的的内存单元组成,每一个内存单元占一字节。每个内存单元对应绑定着一个地址也就是内存单元的编号,像是身份证号一样,通过地址我们就可以唯一确定地找到一块内存单元。如:

1.2 指针与指针变量

地址直接指向了存储在内存的数据的位置。由于能通过地址找到所标识的变量,也就是地址指向了唯一确定的内存单元,故将地址形象化称为指针。

定义一个整型变量a,就是在内存中给它分配了4个字节的空间。由此我们也能看出定义变量的本质就是在内存中分配空间。变量a的第一个字节的地址为0x0012ff40,它就代表变量a的地址。

定义一个“指针”指向一个变量a。我们用&a把变量a的地址取出来,再放到变量pa中,由于变量pa中存的是地址,所以用类型int*去定义变量pa

int * pa = &a; 

这样的变量pa也是真实存在于内存中的一个变量,其中存储的是地址编号。这样的变量叫指针变量

  1. 指针即地址,地址即指针。
  2. 指针变量是存放地址的变量,其中的内容都被当作地址处理。

一般指针变量经常被人们简称为指针,我们要去从语境中区分他人说的是指针还是指针变量。

1.3 指针的大小

一个内存单元有多大?地址是如何进行编号的?

首先我们分析一下,内存单元的大小为什么是一个字节。

对于32位机器,即32根地址线,每一个地址线在寻址时产生的电信号(正电/负电)转化为数字信号 ,正点就是1,负电就是0。更通俗来说,通电即为1,没通电就是0。

那么32根地址线有多少种01组合呢,高中的排列知识就可以说明共有 2 32 2^{32} 232 种01序列。即从32个全0到32个全1。

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

… …

11111111 11111111 11111111 11111110

11111111 11111111 11111111 11111111

当然64位机器,就有 2 64 2^{64} 264 种排列组合。既然我们32位机器上,有 2 32 2^{32} 232 种排列组合。

每一个二进制序列就是一个内存单元的编号,那么就有 2 32 2^{32} 232 个内存单元可供使用,转化为十进制就是 4 , 294 , 967 , 296 4,294,967,296 4,294,967,296。如果每个内存单元是1bit 大小的话,那么除以8就有 536 , 870 , 912 536,870,912 536,870,912 个byte,就有 524 , 288 524,288 524,288 个kb,再除以1024就是我们熟悉的 512 512 512 个MB,约含半个GB。这样的话一个char类型的变量就需要8个地址,是不是太浪费了?假设每个内存单元是1个byte的话,转化到最后正好是4个GB。这就正好了,最早期的时候只有1个或者2个GB。

指针变量用来存储地址,一个地址就是32个比特位,那么正好需要4个字节。所以无论是什么类型,指针变量的大小都是4个字节。当然,32位机器指针的大小为4个字节,64位机器下指针大小为8个字节。

2. 指针的类型

int a = 10;
int * pa = &a;
  • * 代表pa是指针
  • int 代表pa所指向的变量类型为int

变量有不同的类型,很明显指针变量也应该有不同的类型。可是依据前面的推导,不管什么类型的指针变量,32位平台下大小都是4个字节,那指针的类型有什么作用呢?体现在两个方面,一是指针解引用,二是指针加减整数。

2.1 指针解引用方面

int a = 0x11223344;
int* pa = &a;
*pa = 0; 

我们先创建一个变量a,并用指针变量pa指向它,然后再对pa解引用把a置为0,我们可以从内存中看到:

这个结果大家都能猜到,那么接下来我们对指针变量的类型稍作修改,把int* pa改成char* pa

这样的话,区别就有了,int* 的指针访问并修改了4个字节的内容,而char* 的指针只修改了1个字节的内容。所以指针解引用操作时能够访问的字节(内存大小)。

2.2 指针 ± 整数方面

现在我们再用不同类型的指针分别指向同一个变量,对其+1。如:

可以看到int*型的指针+1向后跳过了4个字节,char*型的指针+1向后跳过了1个字节。所以指针±整数时能跳过几个字节(步长)。

2.3 总结

指针类型决定了:

  1. 指针解引用操作时能够访问的字节(内存大小)。
  2. 指针±整数时能跳过几个字节(步长)。

这样的话,我们用不同类型的指针,就可以实现跳过不同的字节,继而更细致的访问变量内容。如:

int arr[10] = { 0 };
//1.
int* pa = arr;
//2.
char * pa = arr;
for (int i = 0; i < 10; i++)
{*(pa + i) = 1;
}

两种不同的指针,带来不同的效果,如图所示:

第一种是一个整型一个整型访问数组元素,第二个是一个字符一个字符地访问数组。如:

3 野指针

3.1 野指针的定义

指向不明确的位置(随机的,不正确的,无明确限制的)的指针是野指针。不正确的位置就是指向了没有分配的内存空间,造成越界访问。

3.2 野指针的成因

  1. 指针未初始化
int* p; //未初始化 
*p = 20;
  1. 指针越界访问
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i <= 10; i++) //越界访问
{*(p + i) = i;
}

越界勉强不报错,但不能越界访问^_^。

Example

int* test()
{int a = 10;return &a;
}
int main()
{int* p = test();printf("%d\n", *p); //野指针越界访问return 0;
}

这里的atest函数中定义的,出了作用域就会被销毁,所以我们这里打印*p就属于越界访问。

但我们这执行程序仍能发现结果是10,这是为什么呢?

原因是a变量所占的空间回收后操作系统还未将其销毁,编译器对其作一次保留。而且传参先行于调用,所以再调用printf函数之前就*p就已经替换为10

我们稍作修改,在打印*p的前面再调用一次printf函数,如:

int* test()
{int a = 10;return &a;
}
int main()
{int* p = test();printf("hehe\n");printf("%d\n", *p);return 0;
}

这次调用printf函数,使得原来分配给a的空间被覆盖,又分配给了printf函数。栈区的使用习惯就是压栈弹栈(如果不了解的话可以去看看栈区空间的开辟和销毁)。

那如果我们把打印printf("%d\n", *p);改为赋值语句*p = 20;的话,如:

int* test()
{int a = 10;return &a;
}
int main()
{int* p = test();*p = 20;//访问非法内存return 0;
}

编译器就直接检测出这块空间是非法内存,就会直接报错。

  1. 指针指向空间已释放

从上面的例子也可以看出,指针p指向test函数原先占有的已被释放的内存空间,这也是一件非常危险的事情,必然会成为野指针。动态内存开辟的地方也会将指向动态开辟的内存的指针free掉,这也是防止其成为野指针。

3.3 如何规避野指针

  1. 明确指针初始化,确定指向
int* p = &a;
int* p =NULL;//不知道该指向何处时,置为空NULL
  1. 谨防指针越界

  2. 指针指向空间释后,立即置为NULL

  3. 避免函数返回局部变量地址

void test(int x) 
{
return &x //err调用结束后形参已经销毁,返回地址无意义
}
  1. 检查指针有效性

空指针不可解引用。

if(p != NULL) 
{*p=20;//检验不为空指针,再使用
}

或者直接用assert断言函数,assert(p)判断指针p是否为空指针,如有误返回错误信息。

4 指针运算

当然指针的解引用操作也算是指针的运算,但我们这里仅考虑一下三类,毕竟指针解引用是基本运算。

指针加减整数所得还是指针,就像日期加天数后还是日期。而指针减指针所得为元素个数,就像日期减日期为天数,从这个例子也可以看出来指针加指针是没有意义的。

4.1 指针 ± 整数

float values[N_VALUE];
float* vp = values;
for (vp = &values[0]; vp < &values[N_VALUE];)
{*vp++=0;
}

上述代码,依靠指向数组的指针循环遍历置零。

  • 循环体内,vp++*,尽管++的优先级比*要高,但是后置++是先使用再++。

所以仿佛是对指针先解引用再++的。

  • float类型指针的加一,跳过一个float类型的长度,故跳到下一个元素。

不论指针是什么类型,指针++,都是跳过一个类型的长度。

  • vp指向数组最后一个元素其后的地址,不满足条件,结束循环。

该地址虽不属于数组,但仅是用所判断大小的条件(地址有高低),没有访问该地址的内容,所以不算越界访问。

本例子涉及到了两个指针的运算:指针加减整数,也就是指针++。指针的关系运算,指针相互比大小作判断条件。指针加整数即指针向后跳整数个类型大小的字节,再来看看指针减整数。

如图所示,指针p-1就是指针p向前跳一个类型的大小。指针加减整数即指针向后或向前跳过整数个类型大小的字节

4.2 指针-指针

指针可以减去指针,代表两个地址之间的”差距“。那可以用指针加上指针吗?相当于两个地址相加是没有意义的。

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);

这题答案是什么?是36还是9?答案是9,语法规定指针-指针,得到的是两地址之间的元素个数(下标相减)。当然两地址间的元素个数,也可以理解为所占字节大小除以类型大小。

那要是在不同的数组中运算呢?如:

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
char ch[] = { '1','2','3' };
printf("%d\n", &arr[9] - &ch[0]);

编译器不会报错,因为没有语法错误。但是所得到的数字即元素的个数,该元素是int类型还是char类型的呢?所以这数字根本就是没有意义的。

所以我们得到指针相减运算的前提:是两指针指向同一块空间,如同一个数组。

  • 指针-指针的前提:是两指针指向同一块空间。

  • 指针-指针,得到的数字的绝对值是两地址之间的元素个数。

应用:实现strlen函数

int my_strlen(const char* s) 
{char* begin = s;      //标记开头while(*s++);          //s先++再判断是否为\0return s - begin - 1; //指针相减
}

4.3 指针关系运算

将指针加减整数代码例子拿过来稍作修改。

//1.
for(vp = &values[N_VALUE];vp > &values[0];) {*--vp = 0;
}

把数组后面的空间,也想象成数组内容根据数组下标拿取是可以的,毕竟数组在内存中是连续存放的。从后往前遍历,先--再解引用,就不会造成数组越界访问。我们再稍作修改:

//2.
for(vp = &values[N_VALUE-1];vp >= &values[0];vp--) {*vp = 0;
}

最后一次遍历时,指针指向values[0]前面的一块地址,当然再回来判断时不满足条件,就退出循环。

尽量选择第一种方法,因为C语言标准规定:允许指向数组的指针,与指向数组最后元素之后的内存位置进行比较,但不允许与首元素之前的位置进行比较。如图:

原因是编译器可能会在数组前的位置存储和数组有关的信息,如数组元素个数等。这样可能会影响到程序的运行。

指针也是地址,地址是编号是数字,就可以进行比较大小指针的关系运算就是比较大小

5 指针和数组

指针和数组之间有什么区别,有什么联系吗?

  • 数组是一个相同类型元素的集合,其中元素存放在连续的空间中。数组的大小取决于元素类型和元素个数。

  • 指针存储地址,是一个变量。指针的大小固定为4 (32bit) / 8 (64bit)。

int arr[10] = { 0 };
printf("%p\n", arr);      //0x0012ff40
printf("%p\n", &arr[0]);  //0x0012ff40

由此可得:数组名就是数组首元素的地址

ps:以下两种情况数组名代表整个数组,除该两种情况外,数组名都代表首元素地址。

  1. sizeof(arr)
  2. &arr

数组名可以作为地址,存放在指针变量中,我们就可以通过指针访问数组。

事实上,数组作函数形参时,都是降级优化为指针的,一整个数组是传不过去的。不过这也是后话了。

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
for (int i = 0; i < sz; i++)
{printf("&arr[%d] = %p <===> p+%d = %p\n", i, &arr[i], i, p + i);
}

也就是说,p+i其实就是数组arr下标为i的地址,本质上二者就是一回事。

6. 二级指针

6.1 多级指针的定义

顾名思义,二级指针就是用来存放一级指针的地址的,通过二级指针也可以访问到一级指针。

  1. 首先创建了一个变量a,存了10,所以它的类型为int,变量的地址为0x0012ff40
  2. 然后取出a的地址,再创建了一个新的变量pa,并把&a存了进去,所以它的类型为int*(一级指针),变量的地址为0x004ffabc
  3. 最后又创建了一个新的变量ppa,把&p存了进去,所以它的类型为int**(二级指针)。

通过ppap的地址,可以找到p,通过pa的地址,也可以找到a

6.2 类型中“*”的含义

灰框中的*代表变量是一个指针变量。

  • 一级指针p前面的int表示p指向的对象aint型的。
  • 二级指针pp前面的int*表示pp指向的对象p的类型是int*型的。
  • 三级指针ppp前面的int**表示ppp指向的对象pp的类型是int**型的。

6.3 多级指针的使用

*p = 1;
* *pp = 2;
* * *ppp = 3;

如上述代码所示,我们一级一级分析。

  • 对一级指针p解引用*p,找到a
  • 对二级指针pp解引用*pp,找到p,再解引用**pp,找到a
  • 对三级指针ppp解引用*ppp找到pp,再解引用**ppp,找到p,再解一次引用***ppp,找到a

所以可以看出,有多少级指针,就要解多少次引用。

7 指针数组

7.1 指针数组的定义

在回答何为指针数组前,我们先来看何为整型数组,何为字符数组。

int arr[10] = {0};
//整型数组 - 存放整型变量的数组
char ch[10] = {'0'};
//字符数组 - 存放字符变量的数组

通过类比整型数组和字符数组,可以得到指针数组就是存放指针变量的数组

数组名前的类型intchar表示,数组元素的类型是int或者char。所以指针数组名前的类型名就是int*或者是char*。如:

//整型指针数组
int* parr[10];
//字符型指针数组
char* pch[5];

对于整型指针数组,每个元素都是整型变量的地址,对于字符型指针数组,每个元素都是字符型变量的地址。由此也可以看出,指针数组的大小,仅取决于数组元素个数。

7.2 指针数组的使用

int arr[] = { 10,20,30 };
int* parr[5] = {NULL};
//输入
for (int i = 0; i < 3; i++)
{parr[i] = &arr[i];
}
//输出1.
for (int i = 0; i < 3 ; i++)
{printf("%d ", *parr[i]);
}
//输出2.
for (int i  = 0; i < 3; i++)
{printf("%d ", **(parr+i));
}
  1. 切记要么初始化要么指定大小。指针数组记得内容初始化为空指针。
  2. 指针数组遍历数组元素打印时,记得要解引用。用数组名+i遍历数组元素时,就要解两层引用。

目前对应指针数组就理解到这个层次,后续还会学习指针的进阶。


http://chatgpt.dhexx.cn/article/6bAUSuPF.shtml

相关文章

C语言有关指针的详解笔记

指针 文章目录 指针1.指针是什么2.指针类型3.野指针野指针成因 4.指针运算5.指针和数组6.二级指针7.指针数组 1.指针是什么 在计算机科学中&#xff0c;指针是编程语言中的一个对象&#xff0c;利用地址&#xff0c;他的值直接指向存在电脑存储器中另一个地方的值。由于通过地…

初阶C语言——指针【详解】

文章目录 1.指针是什么2.指针和指针类型2.1 指针的解引用2.2 指针 -整数 3.野指针3.1 野指针成因3.2 如何规避野指针 4. 指针运算4.1 指针-整数4.2 指针-指针4.3 指针的关系运算 5. 指针和数组6. 二级指针7. 指针数组 1.指针是什么 指针理解的2个要点&#xff1a; 指针是内存中…

C语言——指针初阶详解

&#x1f412;博客名&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;别人可以拷贝我的模式&#xff0c;但不能拷贝我不断往前的激情 目录 1. 指针是什么 2. 指针和指针类型 2.1指针类型的第一个意义 2.2指针类型的第二个意义 3. 野指针 3.1 野指针成因 3.2 如何…

C语言中指针的详解

文章目录 前提一.指针基础1.1 变量指针1.2 数据指针1.3 指针的本质1.4 指针数组1.5 指针的移动1.5 Scanf函数的解释 二.指针的进阶玩法2.1 二维指针2.2 结构体指针 结语 前提 指针&#xff0c;是C语言中的一个重要概念及其特点&#xff0c;也是掌握C语言比较困难的部分。指针也…

C语言:指针详解

目录 指针就是地址&#xff0c;口语中说的指针通常指的是指针变量 指针的定义以及指针类型 野指针 指针运算 a.指针/-整数 b.指针只能-指针&#xff0c;不能指针 指针与数组 二级指针 指针数组 指针的大小是固定的4/8个字节&#xff08;32位平台/64位平台&#xff09…

C语言,指针详解

1. 指针是什么&#xff1f; 1. 指针是内存中一个最小单元的编号 2. 口头上的“指针”&#xff0c;指的是指针变量 #include<stdio.h>int main() {int a10;int * pa&a;// pa 是一个指针变量,用来存放 a 的地址//int ——> pa 所指向对象的类型//* 代表 pa是个指针…

C语言指针详细解析

C语言指针详细解析 概述指针指针运算符示例 指针类型示例 指针变量的初始化关系运算示例 数组一维数组示例 二维数组示例 字符串指针示例示例 指针函数示例 函数指针示例 指针函数和函数指针定义写法用途 最后 概述 指针也就是内存地址&#xff0c;指针变量是用来存放内存地址…

C语言:详解指针

本人录制技术视频地址&#xff1a;https://edu.csdn.net/lecturer/1899 欢迎观看。 指针应该算得上是c语言的精华&#xff0c;但也是难点。很多教程或者博客都有对其详细的讲解与分析。我这一节的内容&#xff0c;也是讲解指针&#xff0c;但我会尽量使用图解的方式&#xff0…

【C语言】指针(详解)

文章目录 前言1.字符指针2. 指针数组3. 数组指针3.1 数组指针的定义3.2 &数组名VS数组名3.3 数组指针的使用 4. 数组参数、指针参数4.1 一维数组传参4.2 二维数组传参4.3 一级指针传参4.4 二级指针传参 5. 函数指针6. 函数指针数组7. 指向函数指针数组的指针8. 回调函数8.1…

【c语言】 指针详解 【图文+代码】

指针 前言一、指针是什么&#xff1f;1.1 认识指针1.2 变量1.3 变量放在哪&#xff1f;1.4 指针的本质1.5 解引用 二、指针类型2.1指针有类型吗&#xff1f;2.2指针类型的意义是什么&#xff1f; 三. 野指针3.1什么是野指针3.2野指针成因3.2.1指针未初始化3.2.2 指针越界访问3.…

【初阶】C语言指针详解——指针必备的7大知识点

文章目录 前言一、指针是什么&#x1f351;1、浅谈指针&#x1f351;2、内存&#x1f351;3、指针变量 二、指针和指针类型&#x1f351;1、指针类型&#x1f351;2、指针-整数&#x1f351;3、指针的解引用 三、野指针&#x1f351;1、野指针成因&#x1f333;(1) 指针未初始化…

详解C语言指针

文章目录 1.字符指针2.指针数组3.数组指针3.1 数组指针的定义3.2 &数组名和数组名的区别3.3 数组指针的使用 4.数组参数&#xff0c;指针参数4.1 一维数组传参4.2 二维数组传参4.3 一级指针传参4.4 二级指针传参 5.函数指针6.函数指针数组7.指向函数指针数组的指针8.回调函…

C语言——指针(入门详解)

文章目录 1.什么是指针&#xff1f;1.1.理解指针的两个要点&#xff1a;1.2.指针变量&#xff1a;1.3.内存是如何编址&#xff1f; 2.指针和指针类型2.1指针的创建与初始化2.2.指针类型 3.野指针3.1.什么视野指针&#xff1f;3.2.野指针成因3.3.规避野指针 4.指针运算4.1.指针-…

c语言指针用法详解,通俗易懂超详细!

文章转自&#xff1a;无际单片机 大家好&#xff0c;我是无际。 今天给大家来讲解一下指针。 我会由浅到深&#xff0c;最后结合实际应用讲解&#xff0c;让大家学会指针的同时&#xff0c;知道大佬们都用指针来干嘛&#xff01; 长文预警&#xff01;全文大约5200多字&#xf…

C语言指针详解(超级详细)

C语言指针精解 前言 这不是我第一次写关于C指针的文章了&#xff0c;只是因为指针对于C来说太重要&#xff0c;而且随着自己编程经历越多&#xff0c;对指针的理解越多&#xff0c;因此有了本文。然而&#xff0c;想要全面理解指针&#xff0c;除了要对C语言有熟练的掌握外&…

C语言中的指针详解

1. 什么是指针 C语言中指针是一种数据类型,指针是存放数据的内存单元地址。 计算机系统的内存拥有大量的存储单元,每个存储单元的大小为1字节,为了便于管理,必须为每个存储单元编号,该编号就是存储单元的“地址”,每个存储单元拥有一个唯一的地址。 指针变量除了可以存…

C语言——指针详解(必收藏)

目录 1.什么是指针&#xff1f; 1.1概念 1.2指针的大小 ​ 1.3指针类型的作用 2.野指针 2.1野指针产生的原因 2.2 如何规避野指针 3.指针运算 3.1指针-整数 3.2指针-指针 3.3 指针的关系运算 4. 二级指针 5. 数组名 *6.指针数组和数组指针 6.1指针数组 *6.…

C语言指针详解

一、什么是指针 C语言里&#xff0c;变量存放在内存中&#xff0c;而内存其实就是一组有序字节组成的数组&#xff0c;每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里&#xff0c;数据对象是指存储在内存中的一个指定数据类…

C语言之指针详解

文章目录 1 指针1.1 简介1.2 什么是指针1.2.1 定义1.2.2 指针表示1.2.3 为什么*p&a不正确 1.3 使用指针1.3.1 简单使用1.3.2 NULL 指针1.3.3 指针算术运算1.3.3.1 定义1.3.3.2 遍历数组&#xff1a;递增一个指针1.3.3.3 遍历数组&#xff1a;递减一个指针1.3.3.4 指针的比较…

c语言—指针详解

文章目录 一、指针是什么二、指针和指针变量1.左值与右值2.两者的区别 三、指针和指针类型1.定义指针2.大小端3.指针的解引用 四、野指针野指针成因如何规避野指针 五、指针运算1.指针- 整数2.指针-指针3.指针的关系运算 六、指针和数组七、二级指针八、指针数组和数组指针1.指…