【c语言】指针就该这么学(1)

简介: 本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。

前言

       指针是我们学习c语言的重要环节之一,可以说学好指针,你才能学好c语言。对于很多初学者来说,指针之前的内容就是“洒洒水”,从指针开始就什么也搞不懂了。希望通过我的讲解,能够加深你对指针的理解。


一、指针是什么

       首先,让我们举一个生活中的例子:假设有一个酒店,这个酒店当中有一百个房间,每一个房间都有一个唯一的编号(001,002,003......100),现在你在这个酒店订房,成交之后前台会告诉你房间的具体编号,这就便于你找到该房间然后入住。


       我们将上述例子运用到计算机当中:计算机也是用类似的方式高效管理内存。内存被计算机划分为一个个的内存单元,一个单元就是一个字节。而这些内存单元就相当于酒店的房间,房间的编号就是内存的地址,也叫做指针。地址本质是一个十六进制数字,每一个字节的内存都有其唯一的地址。


二、指针变量

1.取地址操作符:&

       我们都知道,在c语言中,要创建一个变量,就会申请对应字节的内存空间。比如:

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

我们通过内存窗口来观察一下它们的地址:



可以看到,在我们定义变量a时,向系统申请了四个字节的内存空间,然后将10这个值存入其中。这四个字节的的地址分别是:


0x012FFE0C

0x012FFE0D

0x012FFE0E

0x012FFE0F


在这四个地址当中,a的地址就是其中第一个字节的地址,也就是最小的0x012FFE0C。


那么,我们该如何得到它的地址呢?这就需要我们学习一个新的操作符——&(取地址操作符)


我们在使用它时,在变量名之前加上&符号,就表示这个变量的地址。我们尝试打印一下变量a的地址:

#include <stdio.h>
 
int main()
{
    int a = 10;
    printf("%p\n", &a);//打印地址用%p
    return 0;
}

运行结果:



屏幕上出现了一个16进制数字,这就是它的地址。不过这个值在程序每次运行时都是不同的,因为为变量开辟的内存空间是不一定的


2.指针变量的定义

       我们刚才使用&操作符来取得变量的地址,但是为了方便后期使用,我们可以将这个地址存入到一个变量中,而这个变量就叫做指针变量。也就是说,指针变量是专门存放地址的变量


指针变量的定义方式:

#include <stdio.h>
int main()
{
    int a = 0;
    int* p = &a;//指针变量p当中存放a的地址
    return 0;
}

在定义的变量名前加一个 * 号,这个 * 号说明这是一个指针变量。接着用&取出a的地址,将其赋值给p变量即可。


注意:这里的变量怕,它的类型是 int*,说明是个整形指针变量。如果定义一个浮点型变量,就用float* 类型的指针去指向(存放该变量的地址)它。


3.解引用操作符:*

既然我们已经定义了一个指针变量,那么该如何使用它呢?这就像我们订了一间房需要入住,那就需要用钥匙。解引用操作符就相当于钥匙,通过钥匙就能通过地址找到变量,然后间接性地对变量进行操作。我们尝试使用以下解引用操作符:

#include <stdio.h>
int main()
{
    int a = 0;
    int* p = &a;
    *p = 10;//解引用操作
    printf("%d\n", a);
    return 0;
}

运行结果:



不难看出,p存了a的地址,解引用操作符通过地址找到了相应的值并且改为10,此时*p就等价于a。这里要注意:解引用操作符的 * 和定义指针变量时的 * 是不一样的,定义变量时 * 只是表示它是一个指针变量。


想必你会有疑问了:想要改变a的值,直接改不就可以了嘛,为什么还要这么麻烦地定义一个指针去改它呢?我们这里写一个程序来说明指针的作用:

#include <stdio.h>
 
void swap(int a,int b)
{
    int t = 0;
    t = a;
    a = b; 
    b = t;
}
 
int main()
{
    int a = 3;
    int b = 5;
    swap(a, b);
    printf("a=%d,b=%d", a, b);
}

swap函数用于交换a和b的值,可以想一想,这个函数能否实现功能呢?让我们看看运行结果:

a和b的值并没有交换过来。为什么呢?还记得函数传参的本质吗?函数传参时,形参只是实参的一份临时拷贝,你在函数内改变形参是无法影响到实参的。要做到改变实参,我们就需要传入实参的地址,然后在函数中通过地址来找到值去改变,这叫做传址调用。让我们用代码实现一下:

#include <stdio.h>
 
void swap(int* pa, int* pb)//传入地址,用指针去接收
{
    int t = 0;
    t = *pa;
    *pa = *pb;
    *pb = t;
}
 
int main()
{
    int a = 3;
    int b = 5;
    swap(&a, &b);//地址作为函数参数
    printf("a=%d,b=%d", a, b);
}

运行结果:



可以看到,a和b的值交换过来了。


4.指针变量的大小

       接下来,我们来探讨一下指针变量的大小。指针变量既然是一个变量,那么它也肯定是占用内存空间的,它占用的内存大小是多少呢?我们使用sizeof操作符查看一下:

#include <stdio.h>
 
int main()
{
    printf("%zd\n", sizeof(char*));
    printf("%zd\n", sizeof(short*));
    printf("%zd\n", sizeof(int*));
    printf("%zd\n", sizeof(float*));
    return 0;
}

首先在X86环境下运行:



然后再X64环境下运行:



可以看出,在X86环境下,指针无论是什么类型,它的大小都是4个字节,而X64环境下大小是8个字节。所以指针的大小和它的类型是无关的


       既然指针大小与类型无关,那为什么还有这么多种类型的指针变量?其实,指针变量类型是有它独特的意义的。


三、指针类型的意义

1.指针解引用访问的字节数

       我们写一段代码,观察一下不同类型指针指向相同类型变量的结果:

#include <stdio.h>
int main()
{
    int a = 0x11223344;//这里设置成十六进制数字方便调试观察
    int b = 0x11223344;
    int* pa = &a;
    char* pb = (char*)&b;//用char指针接收int型变量,为保证严谨性将地址强制转换为char*类型
    *pa = 0x0;
    *pb = 0x0;
    printf("a=%#x b=%#x", a, b);//%#x可以在输出的十六进制数字前带0x
    return 0;
}

运行结果:



可以发现,a的值直接被改为0,而b的最后两位被改为了0。这里要注意:这是一个十六进制数字,十六进制数字的一位就代表四位二进制数字,两位被改成0,就说明八位2进制数字被改成0,而八位刚好是一个字节。这说明我们使用char*类型的指针去修改变量的值时,修改的是一个字节的内容。而int*类型的指针修改了四个字节。


结论:指针变量的类型决定了它访问变量时的权限(能够操作的字节个数)。


为了便于让我们理解,我们画图表示一下:



2.指针+-整数

       我们写一段代码观察对指针加或减一个整数的效果:

#include <stdio.h>
 
int main()
{
    int a = 0;
    char* p1 = (char*)&a;
    int* p2 = &a;
    printf("%p\n", p1);
    printf("%p\n", p2);
    printf("%p\n", p1 + 1);
    printf("%p\n", p2 + 1);
    return 0;
}

运行结果:



不难看出,char型的指针+1,它存放的地址就+1,相当于就向前走了一个字节,而int型指针+1就走了4个字节。


结论:指针变量的类型决定了它向前或者向后走的步长有多大。


四、const修饰

1.const修饰变量

       在我们编写程序的时候,如果我们想要一个变量不可被修改,那么就可以在变量定义时加一个const,例如:



可以看到,当我想要重新给a赋值时,会报错。


但是如果我们使用指针去修改呢?

#include <stdio.h>
 
int main()
{
    const int a = 0;
    int* p = &a;
    *p = 10;
    printf("%d\n", a);
    return 0;
}

运行结果:



如果使用指针去间接修改a的值,const就形同虚设了。很显然,这样做就相当于是锁了门却从窗户爬出去的行为。那么,我们是否可以对指针变量使用const修饰,关上“这扇窗”呢?


2.const修饰指针

const修饰指针变量的形式有三种,分别是:

const int * p;//const放在 * 前边
int * const p;//const放在 * 后边
const int * const p;// * 前后都放const

2.1 const放在 * 前面

const放在 * 前面时,指针所指向的内容不能改变,但是指针变量可以改变。例如:



想要通过指针去修改变量a的值就会报错。


2.2 const放在 * 后面

const放在 * 后面时,指针所指向的内容可以改变,但是指针变量不能改变。例如:



当使用指针去指向变量b时,就会报错。


3.3  * 左右两边都放const

当 * 左右两边都放const时,指针变量本身和其所指向的内容都不能改变。例如:



可以看到,无论想要通过指针改变a的值,还是让指针指向b,都是无法进行的。


五、指针的运算

       接下来,我们学习一下指针的三种运算方式,便于我们加深对指针的理解,易于上手指针操作。


1.指针+-整数

       刚才已经提到,指针+-整数可以跳过特定单位的字节。那么它的实际作用是什么?我们都知道,数组中的元素在内存中是连续存放的,那么我们就可以利用指针跳过特定字节的特性,来访问数组当中的每一个元素。示例如下:

#include <stdio.h>
 
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];//p中存放第一个元素的地址
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *p);//通过指针访问元素并打印
        p++;//指针自增1,跳过一个整形元素(4个字节)
    }
    return 0;
}

我们通过循环的方式,运用指针访问元素并且打印,之后让指针自增1,就跳过了四个字节,也就是一个整形元素,就可以访问下一个元素了。


以上代码也可以这样写:

#include <stdio.h>
 
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];
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *(p + i));
    }
    return 0;
}

指针变量p先加i之后再解引用,就能跳过特定数量来访问元素。


      注意:第一段代码中指针变量p由于自增,所以它在循环结束之后已经不再指向第一个元素了,如果要重新访问这个数组,就需要先将第一个元素的地址赋值给p,防止越界访问


2.指针-指针


指针-指针,也就是两个地址相减,得到的是两个指针之间的元素个数


注意:在进行指针相减运算时,一定要保证这两个指针指向的是一块连续的空间,比如数组。否则就是无意义的。


举个例子:

#include <stdio.h>
 
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p1 = &arr[0];
    int* p2 = &arr[9];
    printf("%d\n", p2 - p1);
    return 0;
}

运行结果:



在数组arr当中,从第一个元素的地址开始,到第十个元素,中间确实有九个元素。


       既然有指针-指针的操作,那么有没有指针+指针呢?这个问题很简单,比如我们有一本日历,我们要算算两个日期之间有多少天,那么就用大的日期减去小的日期。但是两个日期相加,就是没有意义的。同样的,指针+指针也是无意义的。


3.指针的关系运算(大小比较)

       由于地址有大小,所以指针也可以进行大小比较。我们改造一下刚才打印数组元素的代码:

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

可以看出,指针的相关操作还是十分灵活的。


六、野指针

       接下来我们学习一个概念:野指针


1.野指针的概念

如果一个指针指向的位置不可知或者并非自己申请的,那么它就是一个野指针


2.野指针出现的情况

2.1 指针变量未初始化

#include <stdio.h>
 
int main()
{
    int a = 0;
    int* p;//未初始化的指针
    *p = 20;//使用了野指针
    return 0;
}

2.2 使用指针越界访问

#include <stdio.h>
 
int main()
{
    int arr[5] = { 0 };
    int* p = &arr[10];
    *p = 20;//越界访问,此时p为野指针
    return 0;
}

2.3 指针指向的空间已经释放

#include <stdio.h>
 
int* fun()//int*表示函数返回值是一个int*类型的地址
{
    int a = 0;
    int* p = &a;
    return p;
}
 
int main()
{
    int* p = fun();//函数退出,返回的地址已经被释放
    printf("%d\n", *p);
    return 0;
}

野指针总是会在不经意的地方出现,并可能产生意想不到的后果。我们在实际编程的时候,要学会规避野指针


3.如何规避野指针

3.1 指针初始化赋值NULL

       在指针变量初始化时,如果不知道存入谁的地址,那么就先制成空指针(NULL)。


NULL是C语言中定义的一个常量,它的值是0,同时也是一个地址,表示内存地址为0的地方。0地址处的空间是不可使用的。如果对NULL进行解引用操作,就会发生报错。


例如:

int *p = NULL;

3.2 防止指针越界

       在使用指针访问一片连续的内存时,一定要检查指针是否会越界,防止出现野指针。


3.3 避免函数返回局部变量的地址

       我们在定义一个函数的时候,要避免返回局部变量的地址,这样就不会在外部函数中出现使用野指针的情况。


总结

       以上就是对指针的定义和基本操作,真正运用指针的方式还有很多很多。后续博主还会继续和大家探索指针的更多知识。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

相关文章
|
1月前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
36 1
|
24天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
25天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
25天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
1月前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
1月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
1月前
|
C语言
C语言指针(3)
C语言指针(3)
11 1
|
1月前
|
C语言
C语言指针(2)
C语言指针(2)
13 1
|
1月前
|
存储 搜索推荐 C语言
深入C语言指针,使代码更加灵活(二)
深入C语言指针,使代码更加灵活(二)