问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C语言指针详解:概念、变量、运算及应用

创作时间:
作者:
@小白创作中心

C语言指针详解:概念、变量、运算及应用

引用
CSDN
1.
https://blog.csdn.net/2303_79266237/article/details/141783806

指针是C语言中一个非常重要但又相对抽象的概念。本文将通过生活化的比喻和详细的代码示例,帮助读者深入理解指针的概念、用法及其相关知识。

指针的概念

指针的定义

首先,我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是从内存中读取的,处理后的数据也会放回到内存中。 这个内存指的是买电脑的运行内存: 8GB/16GB/32GB。

在C语言中,内存其实被划分成了一个个的内存单元,每个内存单元的大小占一个字节。

其实,每个内存单元,相当于一个学生宿舍。而一个宿舍能放八个学生(因为一个字节等于8个比特位,每个人是一个比特位)

然后,每个内存单元都一个编号(这个编号就相当于门牌号),有了这个编号,CPU就能快速找到内存空间。

生活中我们把门牌号叫做地址,因为当你有了门牌号你能快速的定位别人的位置。在计算中我们把内存单元的编号叫做地址。C语言中给地址取了新的名字叫:指针

所以我们可以理解为:

内存单元的编号=地址=指针

了解编址(硬件层面)--内存单元的编号

CPU访问内存中的某个字节空间,必须知道这个字节空间在内存中的位置。因为内存中的字节很多,所以要给它进行编址 。(就像宿舍很多,需要给宿舍编一个门牌号)

计算机中的编址,并不是把每个字节的地址记录下来(内存单元的编号不需要存起来),而是通过硬件设计(地址总线)完成的。

地址总线:

我们可以简单理解,32位机器有32根地址总线, 每根线只有两态,表⽰0,1【电脉冲有⽆】,那么 ⼀根线,就能表⽰2种含义,2根线就能表⽰4种含 义,依次类推。32根地址线,就能表⽰2^32种含 义,每⼀种含义都代表⼀个地址。

简单讲一下原理:当CPU想要读取信息时,会发送一个地址信号 给 地址总线;地址总线就在内存中直接找到地址。

指针的变量和地址

取地址操作符

在C语言中,创建变量的本质上向内存申请空间。

#include <stdio.h>
int main()
{
    int a = 0x11223344; //申请了四个字节
    
    return 0;
}  

比如,上述的代码就是创建了整型变量a,内存中 申请4个字节,⽤于存放十六进制的数(一个十六进制位用4个二进制位表示),其中每个字节都 有地址

那我们怎么得到a的地址呢?

这里就要知道一个操作符&--取地址操作符

注意: a & b 按位与操作符,因为这是有俩个操作数,这是双目操作符

& -- 取地址操作符,而这边是单目操作符,只有一个操作数

#include <stdio.h>
int main()
{
    int a = 0x11223344; //申请了四个字节
    printf("%p\n", &a);
    return 0;
}  

当我们打印出地址时:0x009DFBFC

因为&a取出的是a所占字节中地址较小的字节地址

虽然整型变量占⽤4个字节,我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据也是可 ⾏的

指针的变量

int n = 10;

int * pn = &n;

首先取出n的地址,放在pn的变量中,而这种变量叫做指针变量,那么int*是什么呢?其实这指的是pn的类型(就像int a的类型是int一样)。那如何理解指针的类型呢?其实 *指的是pn是指针变量,前面的int说明pn指向的是整型类型的对象(int a)。

注意:

&n -- n的地址 --地址就是指针

pn就是用来存放地址的,也可以说用来存放指针的

pn就可以被称为指针变量(存放地址的变量)

总的来说

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

解引用操作符(间接访问操作符)

C语⾔中,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。

#include <stdio.h>
int main()
{
    int n = 20;
    int* pn = &n;
    //解引用操作符,间接访问操作符
    *pn = 30; //把地址里的值改为30
    printf("%d\n", *pn); //访问pn地址里的值
    return 0;
}  

因为pn是指针变量存放了n的地址,当想要访问地址里的值时,就要*pn,这个意思是间接访问pn地址的值 或者 修改。

指针的大小

问题与解答

问题:指针变量是多大空间呢?

解答:我们可以这样想,指针变量存放的是地址,地址的存放需要多大空间呢?那么指针变量的大小就是多大。

前⾯的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后 是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4 个字节才能存储。

同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要 8个字节的空间,指针变量的⼤⼩就是8个字节。

结论:

• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节

• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节

• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

指针类型有什么意义?

若指针类型为int * 的指针+1,那么它将跳过4个字节的大小指向4个字节以后的内容:

**若指针类型为char * 的指针+1,那么它只会跳过1个字节的大小指向下一个字节的内容,以此类推 **

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

2.指针解引用

指针的类型决定了指针解引用的时候能够访问几个字节的内容。

若指针类型为int *,那么将它进行解引用操作,它将可以访问从指向位置开始向后4个字节的内容:

结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。 ⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

const修饰指针

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。

const修饰指针主要有俩种情况

1.const放在的左边 : int const p 或者 const int* p

意思:表示指针指向的内容,不能通过指针来改变了,但是指针变量本身的值是可以改的

2.const放在*的右边: int *const p

意思:指针变量p本身不能被修改了,但是指针指向的内容可以通过指针变量来改变

3.const int * const p

意思:指针变量p本身 和指针指向的内容,都不能被改变。

总的来说:

const的位置不同,所产生的效果不同,根据所需要的功能,选择把const放在不同的位置。

指针的运算

1.指针+-整数

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = &arr[0];
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", *(p + i)); //p + i 就是指针加整数
    }
    return 0;
}  

2.指针-指针

指针-指针后的绝对值,得到的是元素的个数。

前提条件:俩个指针指向的是同一个空间。

#include <stdio.h>
size_t my_strlen(char* str)
{
    char* p = str;
    while (*str != '\0')
    {
        str++;
    }
    return str - p;
}
int main()
{
    char arr[] = "abcdef";
    //数组名就是首个元素的地址
    size_t len = my_strlen(arr);
    printf("%zd\n", len);
    return 0;
}  

3.指针的关系运算

#include <stdio.h>
int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    //使用指针的关系运算来打印数组的内容
    int* p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    while (p < &arr[sz]) 
    {
        printf("%d ", *p);
        p++;
    }
    return 0;
}  

通过while (p < &arr[sz]) 这一行的指针关系运算去得到数组中的每一个的值。

野指针

概念:野指针就是指向位置是不可知的(随机的、不正确的、没有明确限制的)指针。

野指针的成因

  1. 指针未初始化
#include<stdio.h>
int main()
{
    int* p;
    *p = 10;
    return 0;
}
  

局部指针变量p未初始化,默认为随机值,所以这个时候的p就是野指针。

2.指针越界访问

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p = &arr[0];
    int i = 0;
    for (i = 0; i < 11; i++)
    {
        *p++ = i;
    }
    return 0;
}
  

当指针指向的范围超出arr数组时,p就是野指针。

3.指针指向的空间被释放

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

当a返回的时候,内存空间返回给了操作系统,就没有了使用权限。(在自定义的test函数中,int a 的生命都在test中,当运行好这个函数之后,这个局部变量就在栈区销毁了)

指针变量p得到地址后,地址指向的空间已经释放了,所以这个时候的p就是野指针。(局部变量出了自己的作用域就被释放了)

如何避免野指针

1.指针初始化

当指针明确知道要存放某一变量地址时,在创建指针变量时就存放该变量地址。

当不知道指针将要用于存放哪一变量地址时,在创建指针变量时应置为空指针(NULL)。

#include<stdio.h>
int main()
{
    int a = 10;
    int* p1 = &a;//明确知道存放某一地址
    int* p2 = NULL;//不知道存放哪一地址时置为空指针
    return 0;
}
  

2.小心指针越界

3.指针指向的空间被释放后及时置为NULL

4.使用指针之前检查有效性

在使用指针之前需确保其不是空指针,因为空指针指向的空间是无法访问的。

传值和传址的认识

传值

#include<stdio.h>
void swap(int x, int y)
{
    int bottle = x;
    x = y;
    y = bottle;
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d%d", &a, &b);
    printf("交换之前:a = %d b = %d\n", a, b);
    swap(a, b);
    printf("交换之后:a = %d b = %d\n ", a, b);
    return 0;
}  

这里我们看到,实参传给形参的值是没有交换的,这是为什么呢?其实这是因为形参又重新创建了一块内存空间,它是在自己的内存空间中活动的,不影响实参。

结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。

传址

这是指通过传地址,以此来达到交换的目的

#include<stdio.h>
void swap(int* pa, int* pb)
{
    int bottle = *pa;
    *pa = *pb;
    *pb = bottle;
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d%d", &a, &b);
    printf("交换之前:a = %d b = %d\n", a, b);
    swap(&a, &b);
    printf("交换之后:a = %d b = %d\n ", a, b);
    return 0;
}  

传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量

如果函数内部要修改 主调函数中的变量的值,就需要传址调⽤。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号