《C语言》--指针->从初阶道进阶 (初阶)

本文详细介绍了C语言中的指针,包括指针的定义、内存和地址的关系、指针变量与解引用操作,以及指针运算、野指针的产生与避免。指针是C语言的重要特性,对于高效利用内存和数据结构至关重要。
摘要由CSDN通过智能技术生成

        C语言中的指针是一种强大的特性,它允许程序直接访问和操作内存中的数据。指针的概念对于理解和高效使用C语言至关重要,它为数据结构的构建、内存管理、函数参数传递等提供了灵活性和效率。那么接下来,我疆带着大家来详细了解指正及其用法。

一、 什么是指针

     指针  是作为c语言中的重难点,也是一位程序员必须学会的知识点之一。其本质上是一种变量,它存储了另一个变量的""内存地址""。

    总的来说:指针就是地址,口语中说的指针通常指的是指针变量。

    那我们如何理解 指针 这个抽象概念呢?

1.内存

    在了解指针之前,我先为大家讲解 内存 这一概念。

    内存 是  \ 计算机 \ 中的一块区域,用于  临时 存储数据 和 程序。当我们运行一个程序,比如用C语言写的小程序,程序的数据和代码会被加载到内存中。

    内存  分为 \不同的部分\ ,比如有的部分用来存放  正在运行的函数和它的局部变量(这叫做 ),有的部分则用来 动态地 申请和释放 空间(这叫做 <对于栈与堆,会在数据结构内作详细介绍,此处只是为大家简单了解一下>

    了解内存如何工作对编程很重要,因为合理地使用内存能让程序运行得更快,也能避免很多问题,比如内存泄漏。

变量与内存

        在C语言中,当我们声明一个变量时,计算机会在内存中为这个变量分配一定的空间。这个空间的大小取决于变量的类型。

    在计算机中,内存 是以  字节<byte>  为单位存储数据的,一个字节8 个比特<bit>  组成。比特 是 最小的信息单位,可以存储0或1。而 字节 是用来组织和管理数据的单位。

    每个字节 都有 一个 唯一的地址,可以通过 这个地址 来 访问和操作数据。

    因此,字节<byte>比特<bit>  是计算机中数据处理的基本单位,字节是用来存储数据的,而比特则是数据的最小表示单位。

    以下是各个类型所占的字节以及范围:

2.指针变量和地址

    理解了内存和地址的关系,我们再回到C语⾔,在C语⾔中创建变量其实就是向内存申请空间。

2.1 取地址操作符(&)

    举个例子:

#include <stdio.h>
int main()
{
 int a = 10;
 return 0;
}

    代码调试如下:

    ⽐如,上述的代码就是创建了整型变量a,内存中申请4个字节,⽤于存放整数10,其中每个字节都 有地址,上图中4个字节的地址分别是:

0x00000058A192F5D4

0x00000058A192F5D5

0x00000058A192F5D6

0x00000058A192F5D7

 在此处,我们就可以使用 &<取地址操作符> 来获取 a 的 首地址

#include <stdio.h>
int main()
{
 int a = 10;
//在输出地址,我们使用%p来打印出地址。
 printf("%d 的首地址是 %p",a,&a);
 return 0;
}

我们不难发现,在每次运行代码的时候,对a这个变量所分配的空间也是随机的。虽然只会输出其首地址,但通过上述所说,int 类型只占4个字节,其余的地址也是可以顺藤摸瓜找上来的。

2.2 指针变量与解引用操作符(*)

    2.2.1指针变量

    了解完‘ & ’ 之后,紧接着我们我们就要了解一个新的概念: 指针变量 。   

    当我们使用 & 之后,得到的地址,例如:0x006FFD72 ,这个数值有的时候也要存储起来,方便以后再使用,那么我们就要用到 指针变量 去储存这个地址。

#include <stdio.h>

int main()
{
    int a = 10;
    int* pa = &a;//取出a的地址并存放到指针变量pa中。
    return 0;
}

  在这里,(int*)为整形指针变量。同样的,对于每一种类型的数据,他们都有一一对应的指针。

例如:int ---- int*        char ---- char *  等等。

不难发现,在每一种类型后,加一个  ‘ * ’ 之后,它就会变成对应的指针类型。

那么,* 是什么呢,接下来我讲详细为大家讲讲---> 解引用操作符:  * 

    2.2.2 解应用操作符( ‘ * ’ )

    在c语言中,我们只要拿了地址(指针),就可以通过地址(指正)来找到该地址(指针)所存储(指向)的对象,就要用到解应用操作符( ‘ * ’ )。

    当我们看到 int *ptr; 这样的代码时,* 符号表示 ptr 是一个指针,而且是一个指向 int 类型数据的指针。这意味着 ptr 里面存的不是一个整数,而是一个整数的地址。

    为什么要在 int 后面加上 * 呢?这是为了告诉编译器,这个变量是一个指针,而不是一个普通的整数变量。这样,编译器就知道如何处理这个变量了。

    简单来说,int *ptr; 就是声明了一个可以存储整数地址的变量 ptr。这样,我们就可以通过这个地址来找到和操作那个整数了。

#include <stdio.h>
int main()
{
 int a = 100;
 int* pa = &a;
 *pa = 0;
 return 0;
}

     在上述代码中,*pa 的意思就是通过pa中存放的地址 ,其指向的空间(*pa)其实就是 a变量了,所以*pa = 0,这个操作符 是把 a 改成了 0 。

2.2.3 指针的解引用

    一般来说 :指针的类型 决定了对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。

 例子:

#include <stdio.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);
 return 0;
}

 代码运行结果:

不难发现,char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

即:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

2.2 野指针

    野指针是指那些指向无效内存地址的指针。它们通常是由于程序员的不当操作而产生的,并且可能导致程序崩溃或数据损坏。以下是野指针产生的一些常见原因以及如何避免它们:

野指针产生原因:
  1. 未初始化的指针:声明指针变量后,没有为其分配有效的内存地址,就直接使用它。

  2. 指针越界访问:访问数组或动态分配内存时超出了其有效范围。

  3. 释放内存后继续使用指针:使用 freedelete 释放了内存,但指针本身没有被置为 NULL,之后仍尝试通过该指针访问内存。

  4. 非法内存访问:比如通过野指针或悬挂指针(悬挂指针是曾指向已释放内存的指针)来访问内存。

如何避免产生野指针:
  1. 初始化指针:在声明指针变量时,立即将其初始化为 NULL 或指向有效的内存地址。
  2. 检查指针是否有效:在解引用指针之前,检查它是否指向有效的内存地址。
  1. 释放内存后置空指针:在释放指针所指向的内存后,立即将指针置为 NULL。或者使用智能指针(C++特有)来自动管理内存。
  2. 避免指针运算超出边界:当对数组或动态分配的内存进行指针运算时,确保指针不会超出其有效范围。

  3. 使用安全的内存操作函数:使用像 strcpy_s(C11)这样的安全函数来替代可能产生越界问题的函数,如 strcpy

  4. 使用工具检测内存错误:使用如 Valgrind 这样的内存检测工具来发现潜在的内存错误,包括野指针问题。

       通过遵循这些最佳实践,可以大大减少野指针的产生,从而提高程序的稳定性和可靠性。

3.        指针运算

    指针的基本运算有三种,分别是:
1    指针+- 整数
2    指针-指针
3    指针的关系运算
1    指针+- 整数 
#include <stdio.h>
//指针+- 整数
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 for(i=0; i<sz; i++)
 {
 printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
 }
 return 0;
}

    因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有素,而对于指针来说,自增(p++)相当于本身加上(int)字节数(例如 0x00000058A192F5D4 运算玩就会变成0x00000058A192F5D8),同样的,p+i也相当与自身 加上 i 倍(int)字节数,实现跨越试访问。

 2    指针-指针

    在减法运算中,值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位),因为减法运算的结果将除以数组元素类型的长度。

//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
 char *p = s;
 while(*p != '\0' )
 p++;
 return p-s;
}
int main()
{
 printf("%d\n", my_strlen("abc"));
 return 0;
}

在上述代码中,应为char 所占字节为1,所以当p-s执行完之后,会精确返回字符串字数(

p= 0x...F24,s=0x...F21,两者相减后结果就为3)

3 指针的关系运算

    对于指针的关系运算,其实与整形(int)的关系运算大差不差。

设两个指针 p , q

p > q 表示 p 指向的存储地址是否大于 q 指向的地址
2. p < q 表示 px 指向的存储地址是否小于 py 指向的地址
3. p == q 表示 px 和 py 是否指向同一个存储地址
4. p == 0 和 p != 0 表示 p 是否为空指针(当p为空指针时,在if(判别式)中的判别式会返回为假的值,即if(0),跳过此if语句)

4 二级指针

    二级指针,也被称为指向指针的指针,是一个存储指针地址的变量。它主要用于获取指针的存放地址,或者用于在内存中对指针进行间接操作。在C语言中,二级指针常用于多级数据间的传递。

    在定义二级指针时,通常的形式为“数据类型 **变量名”,其中数据类型为指针类型,变量名为二级指针变量的名称。例如,如果有一个指向整数的指针int *p,那么一个指向这个指针的二级指针就可以定义为int **pp = &p。

二级指针的主要使用场景包括:

  1. 在栈区建立数组,指向常量区数据的字符串,并通过二级指针进行传参。
  2. 在堆区手动分配空间,创建指针数组,并使用二级指针进行操作。
  3. 当需要改变数组所指向的数据时,可以使用二级指针进行输出操作。

    在使用二级指针时,需要注意避免野指针的产生,即确保所有指针在使用前都已经正确初始化,指向有效的内存地址,并在不再需要时及时释放内存,避免内存泄漏。

此处仅仅为指针的冰山一角,更多深层内容我以后面会慢慢为大家说明。

--------------------------------------------------------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值