【C语言】基础篇五、指针

文章详细介绍了指针的概念,包括指针作为内存地址,指针变量的使用,指针的解引用以及不同类型指针解引用的字节数。同时,讨论了指针加减整数的操作,指针的关系运算,以及指针在数组和多级指针中的应用。此外,重点提到了野指针的问题,包括其成因和规避方法,强调了初始化和内存管理的重要性。
摘要由CSDN通过智能技术生成

1、指针是什么

  1. 指针是是内存中一个最小单元的编号,也就是地址
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

总结:指针=地址=内存编号,口语中说的指针通常指的是指针变量

int a = 10;
int* pa = &a;
//a为整型,大小为4字节,假设它的地址为0x0012ff40 0x0012ff41 0x0012ff42 0x0012ff43
//&a时,只取出了0x0012ff40
//指针变量的大小为4(32位)/8(64位)字节
//0x开头是16进制数字,一个16进制数字是4比特位(0.5个字节),所以0x0012ff40需要4字节存储,此时pa为4字节

指针变量,用来存放地址的变量(存放在指针中的值都被当成地址处理)

32位虚拟地址空间

CPU - 32(bit)位地址 - 地址线传输 -> 内存

64位虚拟地址空间

CPU - 64(bit)位地址 - 地址线传输 -> 内存

指针和指针类型

指针的解引用

指针类型决定了,指针在被解引用的时候,访问的权限有多大(能访问几个字节):整型指针解引用访问4个字节;字符指针解引用访问1个字节

指针 ± 整数

指针类型决定了,指针向前或者向后走一步,走多大距离

int * + n --> n * sizeof(int) == +4n

char * + n --> n * sizeof(char) == +n

sizeof(long) ≥ sizeof(int)

32位环境下:sizeof(long) - 4字节

64位环境下:sizeof(long) - 8字节

int arr[10] = { 0 };
//假如你希望逐字节访问这40个字节
char* pc = (char*)arr;//int*转换为char*,不强制转换会警告,但不报错,所以可以不强制转换
int i = 0;
for (i = 0; i < 40; i++)
{
    *pc = 'x';
    pc++;
}
//假如你希望逐4个字节访问这40个字节
int* pa = arr;
for (i = 0; i < 40; i++)
{
    *pa = 0x11223344;
    pa++;
}

指针指向任何类型都是最低地址

float n = 3.14f;
int* p = &n;
*p//整型和浮点型数据在内存中存储方式不同,解引用操作不可知

野指针

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

野指针成因

  1. 指针未初始化
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int main()
{
    int* p;//局部变量指针未初始化,默认为随机值。随机值找到一块内存空间,但不属于自己,不让访问
    *p = 20;
    return 0;
}

//正确做法1:
int* p = NULL;
//正确做法2:
int a = 10;
int* p = &a;
  1. 指针越界访问
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    int i = 0;
    for (i = 0; i <= 11; i++)
    {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
    }
    return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int* test()
{
    int a = 10;//出作用域销毁,无法访问
    printf("%d\n", a);
    return &a;
}

int main()
{
    int* p = test();
    printf("%d", *p);
    return 0;
}
  1. 指针指向的空间释放

放在动态内存开辟的时候讲解

如何规避野指针

  1. 指针初始化
//正确做法1:不知道初始化为什么值
int* p = NULL;//NULL本质就是0
//正确做法2:知道初始化为什么值
int a = 10;
int* p = &a;
  1. 小心数组越界
  2. 指针指向的空间释放,及时置为NULL(指针不用就指向NULL
  3. 避免返回局部变量的地址
  4. 指针使用之前检查有效性
if (p != NULL)
{
    *p = 20;
}

指针运算

  • 指针±整数
  • 指针-指针
  • 指针的关系运算

指针 ± 整数

#define N_VALUES 5
float values[N_VALUES];
float* vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)//指针比大小,为关系运算
{
    *vp++ = 0;//++的操作对象是vp
}
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int i = 0;
    int* p = arr;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", i[arrr]);
    }
}
//*(p + i) == arr[i] == i[arr]

指针 - 指针

相减前提:两个指针指向同一块空间

int arr[10] = { 0 };
printf("%d\n", &arr[9] - &arr[0]);//9
printf("%d\n", &arr[0] - &arr[9]);//-9
//指针-指针的绝对值是指针和指针之间的元素的个数

char ch[5] = { 0 };
int arr[5] = { 0 };
&arr[0] - &ch[0];//错误
//my_strlen
//1.计数器的方法
//2.递归
//3.指针-指针
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int my_strlen(char* str)
{
    char* start = str;
    while (*str)//\0的ASCII码是0
    {
        str++;
    }
    return str - start;
}

int main()
{
    char arr[] = "abcdef";
    int len = my_strlen(arr);
    printf("%d\n", len);
    return 0;
}
int my_strlen(char* s)
{
    char* start = s;
    while (*s != '\0')
    {
        s++;
    }
    return s - start;
}

int my_strlen(char* s)
{
    char* start = s;
    while (*s++);
    return s - start - 1;
}

int main()
{
    char arr[] = "abcdef";
    printf("%d", my_strlen(arr));
}

指针的关系运算

即比较指针的大小

#define N_VALUES 5
float values[N_VALUES];
float* vp;
for (vp = &values[N_VALUES]; vp > &values[0];)
{
    *--vp = 0;
}

代码简化,这将代码修改如下:

#define N_VALUES 5
float values[N_VALUES];
float* vp;
for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
{
    *vp = 0;
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行

标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较

指针和数组

联系:数组名是首元素地址,通过指针可以遍历访问数组

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);//结果相同
    //2个例外
    printf("%d\n", sizeof(arr));//计算的是整个数组的大小
    &arr;//数组名表示整个数组,取出的是整个数组的地址
    return 0;
}
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%p == %p\n", &arr[i], p + i);//连续递增4
    }
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *(p + i));
    }
    return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
    printf("%p\n", arr);
    printf("%p\n", arr + 1);//+4

    printf("%p\n", &arr[0]);
    printf("%p\n", &arr[0] + 1);//+4

    printf("%p\n", &arr);
    printf("%p\n", &arr + 1);//+40,所以说是整个数组的地址
    return 0;
}

多级指针

指针变量也是变量,是变量就有地址

int a = 10;
int* p = &a;//一级指针变量
//*说明p是指针,int说明p指向int类型的数据
int** pp = &p;//二级指针变量
//*说明pp是指针,int*说明pp指向int*类型的数据
//*pp -> p
//**pp -> a
*p = 20;
**pp = 30;

int*,后面*表示是指针变量,前面int表示指向int类型

int**,后面*表示是指针变量,前面int表示指向int

指针数组

int arr[5];//指针数组 - 存放整型的数组
char ch[6];//字符数组 - 存放字符的数组

int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;
int* arr2[5] = { &a, &b, &c, &d, &e };//指针数组

//打印指针数组
int i = 0;
for (i = 0; i < 5; i++)
{
    printf("%d ", *(arr2[i]));
}
int main()
{
    char arr1[] = "hello";
    char arr2[] = "world";
    char arr3[] = "!";
    
    char* parr[3] = { arr1, arr2, arr3 };//指针数组
    char** p = parr;//数组指针
    
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        printf("%s ", parr[i]);
    }
    return 0;
}
int main()
{
    int arr1[] = { 1,2,3,4,5 };
    int arr2[] = { 6,7,8,9,10 };
    int arr3[] = { 11,12,13,14,15 };
    
    int* parr[] = {arr1, arr2, arr3};
    
    int i = 0;
    int j = 0;
    for (i = 0; i <= 2; i++)
    {
        for(j = 0; j <= 4; j++)
        {
            printf("%d ", parr[i][j]);
            //printf("%d ", *(parr[i] + j));
            //printf("%d ", *(*(parr + i) + j));
        }
        printf("\n");
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值