20260227 151910 Cpp 入门第九课

20260227_151910_CPP_入门第九课.md

第九章:深入理解内存——指针、引用和动态内存

你好!欢迎来到第九章!在前面的章节,我们学习了变量、数组、函数和结构体。你有没有想过,变量在内存中到底存放在哪里?我们如何直接操作内存地址?这一章我们将学习C++中最强大也最需要小心的特性——指针。同时还会学习它的好兄弟引用,以及如何动态地分配内存。学完本章,你就能更深入地理解计算机内存的运作方式!


9.1 指针程序范例

先来看一个最简单的指针程序:定义一个整数变量,然后用指针指向它,通过指针修改它的值。

#include <iostream>
using namespace std;

int main() {
    int a = 10;          // 定义一个普通变量
    int *p;              // 定义一个指针变量,它可以存放整数的地址
    p = &a;              // 把a的地址赋值给p,现在p指向a

    cout << "a的值 = " << a << endl;
    cout << "a的地址 = " << &a << endl;   // &是取地址运算符
    cout << "p的值 = " << p << endl;       // p存的就是a的地址
    cout << "p指向的值 = " << *p << endl;  // *是间接访问运算符,得到p指向的内容

    *p = 20;             // 通过指针修改a的值
    cout << "修改后a的值 = " << a << endl;

    return 0;
}

运行结果(地址会因运行环境不同而变化):

a的值 = 10
a的地址 = 0x61ff08
p的值 = 0x61ff08
p指向的值 = 10
修改后a的值 = 20

这个程序展示了指针的基本操作:&取地址,*解引用。就像你可以通过门牌号找到房子,指针就是内存地址,通过它可以访问变量。


9.2 指针的用法

9.2.1 指针的概念

指针是一个变量,它存储的是另一个变量的内存地址。简单说,指针就是地址的“容器”。每个变量在内存中都有一个地址,就像每间教室都有一个门牌号。

  • &:取地址运算符,获得变量的地址。
  • *:间接访问运算符(解引用),通过地址访问该地址存储的值。

9.2.2 指针的定义和赋值

定义指针的格式:类型 *指针名;
- 例如:int *p; 表示p是一个指向整数的指针。
- double *dp; 表示指向double的指针。
- char *cp; 表示指向字符的指针。

指针必须初始化,否则会变成野指针(指向随机地址),非常危险。

int a = 5;
int *p = &a;   // 定义时初始化
int *q;        // 未初始化,危险!
q = &a;        // 之后赋值也可以

9.2.3 指针的运算

指针可以像整数一样进行加减运算,但它的加减是以指向的类型大小为单位的。

int a[5] = {10, 20, 30, 40, 50};
int *p = &a[0];   // 指向第一个元素

cout << "*p = " << *p << endl;        // 输出10
cout << "*(p+1) = " << *(p+1) << endl; // 输出20,p+1指向下一个整数

指针还可以做减法,得到两个指针之间的元素个数。

9.2.4 指针与数组

数组名在大多数情况下可以看作指向第一个元素的指针。

int a[5] = {1, 2, 3, 4, 5};
int *p = a;          // 等价于 p = &a[0]

for (int i = 0; i < 5; i++) {
    cout << *(p + i) << " ";   // 用指针访问数组
}

但数组名不是真正的指针,它是常量,不能修改。

9.2.5 指针与函数

指针作为函数参数

通过传递指针,函数可以修改调用者的变量(因为知道了地址)。

#include <iostream>
using namespace std;

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int a = 3, b = 5;
    swap(&a, &b);   // 传递地址
    cout << "a = " << a << ", b = " << b << endl;   // a=5, b=3
    return 0;
}
指针作为函数返回值

函数可以返回指针,但要小心不能返回局部变量的地址(因为函数结束局部变量就销毁了)。

int* findMax(int *arr, int size) {
    int *max = arr;
    for (int i = 1; i < size; i++) {
        if (arr[i] > *max) {
            max = &arr[i];
        }
    }
    return max;   // 返回指向最大元素的指针(安全,因为数组生命周期还在)
}

9.2.6 空指针和野指针

  • 空指针:指向空地址的指针,用 nullptr(C++11)或 NULL 表示。解引用空指针会导致程序崩溃。
    cpp int *p = nullptr; // 推荐使用nullptr if (p != nullptr) { *p = 10; // 安全 }

  • 野指针:未初始化或指向已释放内存的指针,使用它非常危险,可能导致程序崩溃或数据损坏。一定要避免!初始化指针是最好的预防。


9.3 引用

9.3.1 引用的概念

引用是给变量起的一个别名,它和原变量共享同一块内存。定义引用时,必须初始化,而且不能改变指向。

int a = 10;
int &b = a;   // b是a的引用,b就是a的别名
b = 20;       // 修改b相当于修改a
cout << a;    // 输出20

9.3.2 引用的用法

引用常用于函数参数,避免拷贝,并且可以直接修改实参。

#include <iostream>
using namespace std;

void swap(int &x, int &y) {   // 引用参数
    int temp = x;
    x = y;
    y = temp;
}

int main() {
    int a = 3, b = 5;
    swap(a, b);   // 直接传变量,不需要取地址
    cout << "a = " << a << ", b = " << b << endl;   // a=5, b=3
    return 0;
}

引用也可以作为函数返回值,但同样不能返回局部变量的引用。

9.3.3 引用与指针的区别

特性 指针 引用
初始化 可以不初始化(但危险) 必须初始化
可修改 可以改变指向其他变量 一旦绑定,不能改变
访问方式 * 解引用 直接像普通变量一样用
空值 可以为 nullptr 不能为空
用途 动态内存、数组操作等 函数参数、操作符重载等

简单说:引用是更安全、更方便的指针,但功能稍弱。


9.4 动态内存分配

有时候我们不知道程序运行时会需要多少内存(比如需要根据用户输入决定数组大小),这时就需要动态内存分配。C++中用 newdelete 来申请和释放内存。

9.4.1 new和delete

  • new 在堆上分配内存,返回指向该内存的指针。
  • delete 释放由 new 分配的内存。
int *p = new int;   // 分配一个整数大小的内存
*p = 10;
cout << *p << endl;
delete p;           // 释放内存,避免内存泄漏
p = nullptr;        // 将指针置空,避免野指针

也可以初始化:

int *p = new int(20);   // 分配并初始化为20

9.4.2 动态数组

new[] 分配数组,用 delete[] 释放。

int n;
cout << "请输入数组大小:";
cin >> n;
int *arr = new int[n];   // 动态分配数组

for (int i = 0; i < n; i++) {
    arr[i] = i * 10;
}

for (int i = 0; i < n; i++) {
    cout << arr[i] << " ";
}
cout << endl;

delete[] arr;            // 释放数组内存
arr = nullptr;

9.4.3 内存泄漏

如果 new 分配了内存,但忘记 delete,这块内存在程序结束前都无法再使用,造成内存泄漏。程序运行时间越长,占用的内存越多,最终可能崩溃。所以一定要成对使用 new/deletenew[]/delete[]

重要规则
- new 对应 delete
- new[] 对应 delete[]
- 不要重复 delete
- 释放后将指针置空。


9.5 编程实例讲解

实例1:用指针实现字符串复制(C风格)

#include <iostream>
using namespace std;

void stringCopy(char *dest, const char *src) {
    while (*src != '\0') {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = '\0';   // 添加字符串结束符
}

int main() {
    char s1[100] = {0};
    char s2[] = "Hello, C++!";
    stringCopy(s1, s2);
    cout << "复制后的字符串:" << s1 << endl;
    return 0;
}

实例2:动态创建结构体

#include <iostream>
#include <string>
using namespace std;

struct Student {
    string name;
    int age;
    double score;
};

int main() {
    Student *p = new Student;   // 动态分配一个学生结构体
    p->name = "小明";           // 用->访问成员,等价于 (*p).name
    p->age = 12;
    p->score = 98.5;

    cout << "姓名:" << p->name << ",年龄:" << p->age << ",成绩:" << p->score << endl;

    delete p;   // 释放内存
    p = nullptr;
    return 0;
}

实例3:动态数组求平均值

#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "请输入学生人数:";
    cin >> n;

    double *scores = new double[n];
    double sum = 0;

    for (int i = 0; i < n; i++) {
        cout << "请输入第" << i+1 << "个学生的成绩:";
        cin >> scores[i];
        sum += scores[i];
    }

    double avg = sum / n;
    cout << "平均分:" << avg << endl;

    delete[] scores;
    scores = nullptr;
    return 0;
}

实例4:用引用交换两个数(对比指针)

#include <iostream>
using namespace std;

void swapByPointer(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

void swapByReference(int &x, int &y) {
    int temp = x;
    x = y;
    y = temp;
}

int main() {
    int a = 3, b = 5;
    swapByPointer(&a, &b);
    cout << "a=" << a << ", b=" << b << endl;   // 5,3

    int c = 10, d = 20;
    swapByReference(c, d);
    cout << "c=" << c << ", d=" << d << endl;   // 20,10
    return 0;
}

9.6 第9章编程作业

恭喜你学完了指针、引用和动态内存!现在来挑战几个综合题目。

作业1:动态数组排序

编写程序,让用户输入数组大小n,然后动态分配数组,输入n个整数,用冒泡排序(用指针操作数组)排序后输出,最后释放内存。

作业2:字符串统计(用指针)

编写函数 int countWords(const char *str),统计字符串中单词的个数(单词之间用空格分隔)。在 main 中测试。

作业3:动态学生信息管理

用结构体 Student 和动态内存实现:
- 输入学生人数n
- 动态创建学生数组
- 输入每个学生的姓名、年龄、成绩
- 按成绩从高到低排序(用指针或引用实现交换)
- 输出排序后的结果
- 释放内存

作业4:引用与指针对比

写三个函数,分别用传值、传指针、传引用的方式实现一个函数 increment,将参数增加1。在 main 中测试,并观察原变量的变化。

作业5:动态二维数组(选做)

动态创建一个 m×n 的二维数组(用指针的指针),输入矩阵并计算转置。注意释放内存。


好了,第九章的内容就到这里!指针是C++中最灵活也最复杂的部分,需要多加练习才能熟练掌握。记住:指针一定要初始化,new和delete要成对出现。下一章我们将学习更高级的内容——类和对象,进入面向对象编程的世界。加油!🚀