IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言指针详解 -> 正文阅读

[C++知识库]C语言指针详解

前言:指针作为 C 语言中的精髓,也是最难的一部分内容,只要理解指针与内存之间的关系,也许你会有一个新的认识。

指针的基本概念

首先先看下指针的概念(摘自百度):

指针是一个占据存储空间的实体在这一段空间起始位置的相对距离值。在C/C++语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量),数组,函数等占据存储空间的实体。

通俗的来说就是:

  • 内存区的每一个字节都有一个编号,这就是“地址”。
  • 如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)
  • 指针的实质就是内存“地址”。指针就是地址,地址就是指针。
  • 指针是内存单元的编号,指针变量是存放地址的变量。
  • 通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。

普通变量对应内存存储情况

不同的类型的数据占用的字节数不同,例如 int 占用 4 个字节,char 占用 1 个字节。
在这里插入图片描述
指针变量对应内存存储情况

指针存储的值是内存地址,且占用的字节数都是不会变的,在 32 位操作系统下是 4 个字节,64 位操作系统下是 8 个字节。
在这里插入图片描述

指针变量的定义和使用

  • 指针也是一种数据类型,指针变量也是一种变量
  • 指针变量指向谁,就把谁的地址赋值给指针变量
  • “*”操作符操作的是指针变量指向的内存空间

先看一段代码

#include <stdio.h>

int main() {

    // 定义整型变量a
    int a = 10;

    // 定义指针变量p
    int *p;

    // 指针变量赋值,指针变量p指向变量a的地址
    p = &a;

    printf("%p\n", &a); // 000000000061FE14(a变量的内存地址)
    printf("%p\n", p);  // 000000000061FE14(p变量存储的指针值)
    printf("%p\n", &p); // 000000000061FE10(p变量的内存地址)

    printf("%d\n", a); // 10(a变量存储的值)
    printf("%d\n", *p); // 10(通过*操作指针变量指向的内存)

    printf("%d\n", sizeof(a)); // 4(int类型占用内存的大小)
    printf("%d\n", sizeof(p)); // 8(指针类型占用内存的大小,32位操作系统下是4个字节,64位操作系统下是8个字节)

    return 0;
}

以上代码对应的内存存储情况
在这里插入图片描述
总结

  1. p 存放的值 = a 的内存地址,也就是说 p 存储的是 a 的内存地址;
  2. 通过 * 操作指针变量指向的内存的值,也就是说 *p 取出来的是 a 存放的值。

& 是取地址符号,是升维度的。
* 是取值符号,是降维度的。

野指针和空指针

野指针:指针变量指向一个未知的空间

虽然指针变量也是变量,是变量就可以任意赋值,不要超过变量定义的数据类型长度即可,但是,指针存储的值一定是内存的地址,且占用内存的字节数是固定的(32位为4字节,64位为8字节),所以,如果指针变量存储的不是内存地址的话,也就没有任何意义了,因此就成了野指针

野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。

#include <stdio.h>

int main() {

    char ch = 'a';

    int *p = &ch;

    printf("%d\n", sizeof(ch)); // 1
    printf("%d\n", sizeof(p)); // 8

    printf("%p\n", &ch); // 000000000061FE17
    printf("%p\n", p); // 000000000061FE17

    *p = ch; // 把 ch 的值赋值给指针变量 p,也就是说 *p = 'a',此时 p 存储的值是 'a',没有意义,所以 p 为野指针

    printf("%p\n", &ch); // 000000000061FE17
    printf("%p\n", p); // 0000000000000000

    *p = 100; // 直接给指针变量 p 赋值,没有意义,此时 p 为野指针

    printf("%p\n", &ch); // 000000000061FE17
    printf("%p\n", p); // 0000000000000000

    return 0;
}

空指针:指内存地址编号为0的空间

野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针

#include <stdio.h>

int main() {

    int *p = NULL; // 定义一个空指针

    // 操作空指针对应的空间一定报错
    printf("%d\n", *p); // 报错

    // 赋值之后也报错
    *p = 100;
    printf("%d\n", *p); // 报错

    return 0;
}

万能指针 void *

void * 指针可以指向任意变量的内存空间

指向 int 类型

#include <stdio.h>

int main() {

    int a = 10;

    /*int *p = &a;
    printf("%p\n", p); // 000000000061FE14
    printf("%d\n", *p); // 10*/

    // 万能指针可以接收任意类型变量的内存地址
    void *p = &a;

    // 再通过万能指针修改变量的值时,需要找到变量对应的指针类型
    // *p = 100; // 报错
    *(int *) p = 100; // 正确

    printf("%p\n", p); // 000000000061FE14
    printf("%d\n", *(int *) p); // 100
    printf("%d\n", sizeof(void *)); // 8

    return 0;
}

指向 char 类型

#include <stdio.h>

int main() {

    char ch = 'a';

    // 万能指针可以接收任意类型变量的内存地址
    void *p = &ch;

    // 再通过万能指针修改变量的值时,需要找到变量对应的指针类型
    // *p = 'b'; // 报错
    *(char *) p = 'b'; // 正确

    printf("%p\n", p); // 000000000061FE17
    printf("%d\n", *(char *) p); // 98
    printf("%d\n", sizeof(void *)); // 8

    return 0;
}

const 修饰指针

const 修饰指针有三种情况:

  • const 修饰指针 — 常量指针

  • const 修饰常量 — 指针常量

  • const 即修饰指针,又修饰常量 — 只读指针

const 修饰普通变量(常量)的值,可通过指针间接修改

#include <stdio.h>

int main() {

    const int a = 10;
    // a = 100; // 错误,常量不允许修改

    // 指针间接修改常量值
    int *p = &a;
    *p = 100;
    printf("%d\n", a); // 100

    return 0;
}

常量指针

可以修改指针变量的值,不可以修改指针指向内存空间的值

#include <stdio.h>

int main() {

    int a = 10;
    int b = 20;

    // 定义常量指针
    const int *p = &a;

    p = &b; // ok,可以修改指针变量的值
    // *p = 100; // 错误,不可以修改指针指向内存空间的值

    printf("%d\n", *p); // 20

    return 0;
}

指针常量

不可以修改指针变量的值,可以修改指针指向内存空间的值

#include <stdio.h>

int main() {

    int a = 10;
    int b = 20;

    // 定义指针常量
    int *const p = &a;

    // p = &b; // 错误,不可以修改指针变量的值
    *p = 100; // ok,可以修改指针指向内存空间的值

    printf("%d\n", *p); // 100

    return 0;
}

只读指针

不可以修改指针变量的值,不可以修改指针指向内存空间的值

#include <stdio.h>

int main() {

    int a = 10;
    int b = 20;

    // const 即修饰指针,又修饰常量,只读指针
    const int *const p = &a;

    // p = &b; // 错误,不可以修改指针变量的值
    // *p = 100; // 错误,不可以修改指针指向内存空间的值

    return 0;
}

只读指针实际上可通过二级指针修改指针变量的值和内存空间的值

#include <stdio.h>

int main() {

    int a = 10;
    int b = 20;

    // const 即修饰指针,又修饰常量,只读指针
    const int *const p = &a;

    // p = &b; // 错误,不可以修改指针变量的值
    // *p = 100; // 错误,不可以修改指针指向内存空间的值

    // 通过二级指针操作
    int **pp = &p;

    *pp = &b;
    printf("%d\n", *p); // 20

    **pp = 100;
    printf("%d\n", *p); // 100
    
    printf("%d\n", a); // 10
    printf("%d\n", b); // 100

    return 0;
}

修改前内存存储情况
在这里插入图片描述
分析

  1. *pp 是一级指针 p 的值 0x10001,通过 *pp = &b,修改 p 的值,所以此时 p 的值为 0x10002
  2. **pp 是一级指针 p 变量的值 20,通过 **pp = 100,修改 p 变量的值,所以此时 p 变量的值为 100,即 b = 100

修改后内存存储情况
在这里插入图片描述
总结

一级只读指针可以通过二级指针修改值,以此类推…

指针和数组

数组名

数组名字是数组的首元素地址,但它是一个常量,不允许被修改。

#include <stdio.h>

int main() {

    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', 'c'};

    // 数组名是一个常量,不允许赋值
    // arr = 100; // err

    // 数组名是数组首元素的地址,所有直接赋值给指针变量即可,不需要&符号
    int *p = arr;

    printf("%p\n", arr); // 000000000061FDE0
    printf("%p\n", p); // 000000000061FDE0

    return 0;
}

指针操作数组元素

#include <stdio.h>

int main() {

    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', 'c'};

    int *p = arr;

    for (int i = 0; i < 10; i++) {
        printf("%d\n", *p);
        p++; // 指针类型变量+1,等同于内存地址+sizeof(int)
    }

    return 0;
}

以上代码对应的内存存储情况
在这里插入图片描述
总结

  1. 数组名是数组首元素的地址,所以数组名 arr 对应的内存地址是 0x10001
  2. 指针类型变量+1,等同于内存地址 +sizeof(int) 两个指针的偏移量,所以 p+1 对应的内存地址是 0x10002,以此类推…

指针数组

指针数组,它是数组,数组的每个元素都是指针类型。

#include <stdio.h>

int main() {

    int a = 10;
    int b = 20;
    int c = 30;
    int *arr[3] = {&a, &b, &c};

    int len = sizeof(arr) / sizeof(arr[0]);

    for (int i = 0; i < len; ++i) {
        printf("%d\n", *arr[i]);
    }

    return 0;
}

以上代码对应的内存存储情况
在这里插入图片描述
指针和二维数组的应用

#include <stdio.h>

int main() {

    int a[] = {1, 2, 3};
    int b[] = {4, 5, 6};
    int c[] = {7, 8, 9};

    // printf("%p\n", a); // 000000000061FE0C 数组名是指数组首元素的指针地址

    // 指针数组里面元素存储的是指针
    // 指针数组是一个特殊的二维数组模型
    int *arr[] = {a, b, c};

    // printf("%p\n", arr[0]); // 000000000061FE0C
    // printf("%p\n", a); // 000000000061FE0C
    // printf("%p\n", &a[0]); // 000000000061FE0C

    // 遍历出二维数组的所有数据
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        for (int j = 0; j < 3; j++) {
            // 二维数组
            // printf("%d", arr[i][j]); // 下标方式获取
            // printf("%d", *(arr[i] + j)); // 指针+下标方式获取
            printf("%d", *(*(arr + i) + j)); // 纯指针方式获取
        }
    }

    // printf("%p\n", arr); // 000000000061FDD0
    // printf("%p\n", &arr); // 000000000061FDD0
    // printf("%p\n", *arr); // 000000000061FE0C 此处*代表指针的地址
    // printf("%d\n", *(*arr)); // 1 此处*代表指针的地址的值

    printf("%p\n", arr + 1); // 000000000061FDD8
    printf("%p\n", &arr[1]); // 000000000061FDD8
    printf("%p\n", *(arr + 1)); // 000000000061FE00
    printf("%p\n", &b); // 000000000061FE00
    printf("%d\n", *(*(arr + 1) + 1)); // 5

    return 0;
}

指针和函数

函数形参改变实参的值

利用指针作函数参数,可以修改实参的值。

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {

    int a = 10;
    int b = 20;

    swap(&a, &b); // 地址传递
    printf("%d\n", a); // 20
    printf("%d\n", b); // 10

    return 0;
}

数组名做函数参数

数组名做函数参数,函数的形参会退化为指针。

#include <stdio.h>

void printArray(int *a, int n) {
    printf("%p\n", a); // 000000000061FDF0,指针

    int i = 0;
    for (i = 0; i < n; i++) {
        printf("%d\n", a[i]);
    }
}

int main() {

    int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int n = sizeof(a) / sizeof(a[0]);

    // 数组名做函数参数
    printArray(a, n);

    return 0;
}

指针做为函数的返回值

#include <stdio.h>

int a = 10;

int *getA() {
    return &a;
}

int main() {

    *(getA()) = 111;
    printf("%d\n", a); // 111

    return 0;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-16 11:05:17  更:2021-07-16 11:06:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 1:29:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码