Javer 学 c++(八):指针篇

22 阅读6分钟

总览

主要讲了 c++ 中指针的概念、定义和使用、const以及与函数、数组的使用关系

v2.0: 重新解释了常量指针和指针常量的概念,这个中文翻译实在是太迷惑了

指针的基本概念

作用:可以通过指针间接访问内存

  • 内存的编号是从 0 开始记录的,一般用 16 进制数字表示
  • 可以利用指针变量保存地址

指针变量的定义和使用

指针变量定义语法:数据类型 * 变量名

#include <iostream>
using namespace std;

int main() {

    int a = 10;
    // 1. 定义指针
    int * p;
    // 将指针指向 a 的地址
    p = &a;

    // 2. 使用指针:解引用(指针前加*号)
    // 通过解引用方式来找到指针指向的内存,找到指针指向内存中的数据
    *p = 1000;
    // 结果为两个 1000,即已经将0x16f433018地址的数修改了
    cout << a << endl;
    cout << *p << endl;

    return 0;
}

指针所占内存空间

指针也是一种数据类型,那么指针占用的内存空间是多大呢?

#include <iostream>
using namespace std;
#include "swap.h"

int main() {

    int a = 10;
    // 1. 定义指针
    int * p;
    // 将指针指向 a 的地址
    p = &a;

    // 64 位系统 8 字节
    // 若是 32 位则为 4 字节
    cout << sizeof(p) << endl;

    return 0;
}

空指针和野指针

空指针

定义:指针变量指向内存中编号为 0 的空间

用途:初始化指针变量

重点:空指针指向的内存是不可访问的

#include <iostream>
using namespace std;
#include "swap.h"

int main() {

    // 1. 定义指针
    int * p = NULL;

    *p = 100;
    // 内存编号 0-255 为系统占用内存,不允许用户访问
    cout << *p << endl;

    return 0;
}

野指针

指针变量指向非法的内存空间

#include <iostream>
using namespace std;

int main() {

    // 指针变量 p 指向内存地址编号为 0x1100 的空间
    int * p = (int *)0x1100;

    // 访问野指针报错
    cout << *p << endl;

    return 0;
}

重点:空指针和野指针本质上都不是我们自己申请的空间,不要随意的去访问它

const 修饰指针

const 修饰指针:常量指针

特点:指针的指向可以修改,但是指针指向的值不可以修改

  • 常量指针指向的对象不能通过这个指针来修改,可是仍然可以通过原来的声明修改
  • 常量指针可以被赋值为变量的地址,之所以叫常量指针,是限制了通过这个指针修改变量的值
  • 指针还可以指向别处,因为指针本身只是个变量,可以指向任意地址
#include <iostream>
using namespace std;

int main() {

    int a = 10;
    int b = 20;
    // 常量指针
    const int * p = &a;

    a = 20; // ok,仍然可以通过原来的声明修改值
    // *p = 20; Error,*p 是const int的,不可修改,即常量指针不可修改其指向地址
    p = &b; //OK,指针还可以指向别处,因为指针只是个变量,可以随意指向;

    return 0;
}

const 修饰常量:指针常量

本质是一个常量,而用指针修饰它。指针常量的值是指针,这个值因为是常量,所以不能被赋值。

  • 它是个常量!
  • 指针所保存的地址可以改变,然而指针所指向的值却不可以改变
  • 指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化
#include <iostream>
using namespace std;

int main() {

    int a = 10;
    int b = 20;
    // 常量指针
    int * const p = &a;

    a = 20; // OK,仍然可以通过原来的声明修改值
    *p = 20; // OK,指针是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化
    // p = &b; Error,因为p2是const 指针,因此不能改变p2指向的内容

    return 0;
}

const 修饰指针和常量

指针就是一个常量,且它指向的对象也是一个常量。

#include <iostream>
using namespace std;

int main() {

    int a = 10;
    int b = 20;
    // 常量指针
    const int * const p = &a;

    a = 20; // OK
    //*p = 20; ERROR
    //p = &b; Error

    return 0;
}

总结:这个命名方式实在让人蛋疼,我们把 p 理解为指针,把 * p 理解为值

const 如果在 * 之前,说明是修饰值 * p 的,那么值不允许改变,但是指针的引用可以改变 const 如果在 * 和 p 之间,说明是修饰指针 p 的,那么引用不允许改变,值可以改变

指针和数组

作用:利用指针访问数组元素

#include <iostream>
using namespace std;

int main() {

    //利用指针访问数组
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 第一个元素
    int * p = arr; // arr 就是数组首地址
    cout << *p << endl;

    // 第二个元素
    // 因为数组里存放的是 int,所以只需要指针向后移动 4 个字节即可
    // 同时指针也是一个 int 类型,所以 p++ 就是让指针向后移动 4 个字节
    p++;
    cout << *p << endl;

    // 遍历
    int * p2 = arr;
    for (int i = 0; i < 10; i++) {
        cout << *p2++ << endl;
    }

    return 0;
}

指针和函数

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

值传递

前面我们讲到了 c++是值传递,形参的改变不会影响形参的值,这就是值传递

#include <iostream>
using namespace std;

// 返回值类型 函数名(参数列表)
void swap(int a, int b) {
    int tmp = a;
    a = b;
    b = tmp;
}

int main() {

    int a = 10;
    int b = 20;
    swap(a, b);
    // 因为是值传递,所以a=10,b=20没有变化
    cout << a << b << endl;

    return 0;
}

地址传递

#include <iostream>
using namespace std;

// 返回值类型 函数名(参数列表)
void swap(int *p1, int *p2) {
    int tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

int main() {

    int a = 10;
    int b = 20;
    swap(&a, &b);
    // 因为是地址传递,所以a=20,b=10已经交换
    cout << a << b << endl;

    return 0;
}

本质上地址传递也是值传递,因为传递的是地址的值

课后作业

描述:封装一个函数,利用冒泡排序,实现对整数数组的排序

#include <iostream>
using namespace std;

// 这么传 int arr[] 也可以
void bubble_sort(int * arr, int size) {
    for (int i = 0; i < size-1; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

void printArray(int * arr, int size) {
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
}

int main() {

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

    // 预计算数组长度
    int size = sizeof(arr) / sizeof(arr[0]);

    bubble_sort(arr, size);

    printArray(arr, size);

    return 0;
}

如何将数组作为参数传入?

  • 传入数组的引用
  • 传入数组及其大小