比较简单 的 指针进阶(基础一)

目录

一.地址与内存

二. 指针变量和地址

2.1 取地址操作符(&)

2.2 指针变量

2.3 指针变量的大小

三. 指针变量类型的意义

3.1指针的意义

3.2 指针+-整数

3.3 void* 指针

四. const修饰指针

五. 指针运算

5.1 指针+-整数

5.2指针-指针

5.3指针的关系运算

六.野指针

6.1 野指针成因

1. 指针未初始化

2. 指针越界访问

3. 指针指向的空间释放

6.2 如何规避野指针

6.2.1 指针初始化

6.2.2 ⼩⼼指针越界

6.2.3 指针变量不再使⽤时,及时置NULL,指针使用之前检查有效性

6.2.4 避免返回局部变量的地址

七. assert断言

八. 指针的使⽤和传址调⽤


点赞后看养成习惯


一.地址与内存

每个内存单元,相当于⼀个学⽣宿舍,⼀个人字节空间⾥⾯能放8个⽐特位,就好⽐同学们住的⼋⼈间,每个⼈是⼀个⽐特位。

每个内存单元也都有⼀个编号(这个编号就相当于宿舍房间的⻔牌号),有了这个内存单元的号,CPU就可以快速找到⼀个内存空间。

⽣活中我们把⻔牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起
了新的名字叫:指针。

总结: 内存单元的编号==地址==指针(重点)
如图:每个内存(字节)都有个  地址(指针)


二. 指针变量和地址

2.1 取地址操作符(&)

在C语言中创建变量其实是向内存申请空间来存放数据

int a = 10;表面上看,是创建了一个整型变量a,并且对a赋值10;

实际上,是向内存申请了4Byte的空间命名为a,用来存放10

比如:0a就是10(16进制)


2.2 指针变量


2.2.1 指针变量
那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:0x006FFD70,这个数值有时候也是
存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在指针变量中。

#include <stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;//取出a的地址并存储到指针变量pa中
    return 0
}

2.2.2 如何理解指针

我们看到pa的类型是 int* ,我们该如何理解指针呢?

一副图就行


2.3 指针变量的大小

       1.指针变量的⼤⼩取决于地址的⼤⼩
       2.32位平台下地址是32个bit位(即4个字节)
       3.64位平台下地址是64个bit位(即8个字节)

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

一 . 32位平台下地址是32个bit位(即4个字节)


二.64位平台下地址是64个bit位(即8个字节)
 

注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的

指针大小  和  指针(⼀次能操作几个字节)不一样!


三. 指针变量类型的意义(如何理解指针类型)

3.1指针的意义

        指针变量的大小和类型无关,只要是指针变量,在同⼀个平台下,大小都是⼀样的,为什么还要有各种各样的指针类型呢?
其实指针类型是有特殊意义的! ! ! ! ! ! !
结论(重要):指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作几个字节)

比如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。


3.2 指针+-整数
        

char* 类型的指针变量+1跳过1个字节 int* 类型的指针变量+1跳过了4个字节

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


3.3 void* 指针


        在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址
        但是也有局限性, void* 类型的指针不能直接进行指针的+ - 整数和解引⽤的运算。


四. const修饰指针

void test2()
{
//const在*的左边
    int n = 10;
    int m = 20;
    const int* p = &n;//或者int const*p = &n
    *p = 20;//指针指向的内容不可以被修改
    p = &m; //指针变量本⾝可以修改
}

void test3()
{
//const在*的右边
    int n = 10;
    int m = 20;
    int *const p = &n;
    *p = 20; //指针指向的内容可以被修改
    p = &m;//指针本身不可以修改
}


结论:const修饰指针变量的时候
• const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。
但是指针变量本⾝的内容可变。
• const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指
向的内容,可以通过指针改
 


五. 指针运算


指针的基本运算有三种,分别是:


5.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;
}


5.2指针-指针

总结:指针-指针是元素之间的个数(记住)

比如:模拟实现strlen函数,就可以使用

#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;
}


5.3指针的关系运算


因为有 低地址高地址 的存在,指针大小也可以比较。

#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]);
    while(p<arr+sz) //指针的⼤⼩⽐较
    {
        printf("%d ", *p);
        p++;
    }
    return 0;
}

六.野指针

6.1 野指针成因


1. 指针未初始化

#include <stdio.h>
int main()
{
    int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
    return 0;
}


2. 指针越界访问

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


3. 指针指向的空间释放

n和 函数 已经被释放,返回的n的地址,已经不是 n 了,继续访问就回越界。
 

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

6.2 如何规避野指针

6.2.1 指针初始化

可以给指针赋值NULL.    NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错
 

include <stdio.h>
int main()
{
    int num = 10;
    int*p1 = &num;
    int*p2 = NULL;
    return 0;
}


6.2.2 ⼩⼼指针越界

不能超出范围访问,超出了就是越界访问。


6.2.3 指针变量不再使⽤时,及时置NULL,指针使用之前检查有效性


6.2.4 避免返回局部变量的地址

如 


 


七. assert断言


assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。

assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。
但是,在 VS 这样的集成开发环境中,在 Release 版本中,assert()直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在 Release 版本不影响用户使⽤时程序的效率。


八. 指针的使⽤和传址调⽤

我们看这样一段代码:

我们发现其实没产⽣交换的效果,这是为什么呢?

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

那怎么样才能修改呢?其实用传址调用就可以解决。


记得点赞噢

  • 33
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值