首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《C++ 程序设计》第 6 章 - 数组、指针与字符串

《C++ 程序设计》第 6 章 - 数组、指针与字符串

作者头像
啊阿狸不会拉杆
发布2026-01-21 12:34:53
发布2026-01-21 12:34:53
60
举报
        大家好!今天我们来深入学习《C++ 程序设计》的第 6 章内容 ——数组、指针与字符串。这一章是 C++ 编程的核心基础,也是后续学习数据结构、算法的重要铺垫。数组让我们能批量处理数据,指针让我们能灵活操作内存,字符串则是编程中高频使用的数据类型。掌握这些知识,能让你写出更高效、更灵活的 C++ 代码。

本章知识思维导图

6.1 数组

        数组是相同类型数据元素的有序集合,在内存中连续存储,通过下标快速访问。数组是 C++ 中批量处理数据的基础工具。

6.1.1 数组的声明与使用

数组的声明需要指定数据类型数组名元素个数(数组大小)。

基本语法:
代码语言:javascript
复制
// 一维数组声明
数据类型 数组名[数组大小];

// 二维数组声明(可理解为"数组的数组")
数据类型 数组名[行数][列数];
使用示例:
代码语言:javascript
复制
#include <iostream>
using namespace std;

int main() {
    // 声明一维数组(存储5个int类型元素)
    int scores[5]; 
    
    // 赋值(下标从0开始,范围0~4)
    scores[0] = 90;
    scores[1] = 85;
    scores[2] = 92;
    scores[3] = 88;
    scores[4] = 95;
    
    // 访问元素并输出
    cout << "第3个成绩:" << scores[2] << endl;  // 输出92
    
    // 二维数组(3行2列)
    int matrix[3][2] = {{1,2}, {3,4}, {5,6}};
    cout << "第二行第一列元素:" << matrix[1][0] << endl;  // 输出3
    
    return 0;
}

⚠️ 注意:数组下标从 0 开始,最大下标为 “数组大小 - 1”。访问scores[5]会导致下标越界,可能破坏内存数据或程序崩溃。

6.1.2 数组的存储与初始化

        数组在内存中是连续存储的,每个元素占用相同大小的内存空间(由数据类型决定)。

数组存储示意图(以 int scores [5] 为例):
代码语言:javascript
复制
内存地址:0x1000  0x1004  0x1008  0x100C  0x1010
元素值:   90      85      92      88      95
下标:      0       1       2       3       4

(int 类型占 4 字节,所以地址间隔 4)

数组初始化方式:
代码语言:javascript
复制
#include <iostream>
using namespace std;

int main() {
    // 1. 完全初始化:指定所有元素值
    int arr1[3] = {10, 20, 30};
    
    // 2. 部分初始化:未指定的元素自动为0(全局数组默认初始化为0,局部数组不初始化则值随机)
    int arr2[5] = {1, 2};  // 元素为[1,2,0,0,0]
    
    // 3. 省略大小:编译器根据初始化元素个数自动确定大小
    int arr3[] = {5, 6, 7, 8};  // 大小为4
    
    // 4. 二维数组初始化:按行初始化
    int matrix[2][3] = {{1,2,3}, {4,5,6}};  // 2行3列
    
    // 打印arr3验证
    for(int i=0; i<4; i++){
        cout << arr3[i] << " ";  // 输出:5 6 7 8
    }
    return 0;
}
6.1.3 数组作为函数参数

        数组作为函数参数时,本质是传递数组首地址(不是整个数组拷贝),因此函数内部修改数组会影响原数组。由于数组传参不携带大小信息,通常需要额外传递数组长度。

传参语法:
代码语言:javascript
复制
// 形参写法1:数组形式([]内的大小可省略)
void func(int arr[], int len);

// 形参写法2:指针形式(与数组形式等价)
void func(int* arr, int len);
示例:求数组元素之和
代码语言:javascript
复制
#include <iostream>
using namespace std;

// 计算数组元素之和(数组传参+长度)
int sumArray(int arr[], int len) {  // 等价于int* arr
    int sum = 0;
    for(int i=0; i<len; i++){
        sum += arr[i];
    }
    return sum;
}

// 修改数组元素(演示传址修改)
void doubleArray(int* arr, int len) {
    for(int i=0; i<len; i++){
        arr[i] *= 2;  // 等价于*(arr+i) *= 2
    }
}

int main() {
    int nums[] = {1,2,3,4,5};
    int len = sizeof(nums)/sizeof(nums[0]);  // 计算数组长度
    
    cout << "原数组和:" << sumArray(nums, len) << endl;  // 输出15
    
    doubleArray(nums, len);
    cout << "翻倍后数组和:" << sumArray(nums, len) << endl;  // 输出30
    
    return 0;
}
6.1.4 对象数组

        对象数组是元素为对象的数组,每个元素都是类的实例。声明时需确保类有合适的构造函数,初始化时会自动调用构造函数。

示例:学生对象数组
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

// 学生类
class Student {
private:
    string name;
    int age;
public:
    // 构造函数
    Student() : name("未知"), age(0) {}  // 默认构造
    Student(string n, int a) : name(n), age(a) {}  // 带参构造
    
    // 打印信息
    void showInfo() {
        cout << "姓名:" << name << ",年龄:" << age << endl;
    }
};

int main() {
    // 声明对象数组(3个Student对象)
    Student stuArray[3] = {
        Student("张三", 18),  // 调用带参构造
        Student("李四", 19),
        Student()  // 调用默认构造
    };
    
    // 访问对象数组元素
    for(int i=0; i<3; i++){
        cout << "学生" << i+1 << ":";
        stuArray[i].showInfo();  // 调用成员函数
    }
    return 0;
}

输出:

6.1.5 程序实例:数组综合应用(成绩统计)

        需求:输入 5 名学生的成绩,统计平均分、最高分、最低分,并输出成绩等级(A:90+, B:80-89, C:70-79, D:60-69, E:60-)。

代码语言:javascript
复制
#include <iostream>
#include <iomanip>  // 用于setprecision
using namespace std;

// 统计函数:返回平均分,通过指针输出最高分和最低分
double statScores(int scores[], int len, int* maxScore, int* minScore) {
    int sum = 0;
    *maxScore = scores[0];
    *minScore = scores[0];
    
    for(int i=0; i<len; i++){
        sum += scores[i];
        if(scores[i] > *maxScore) *maxScore = scores[i];
        if(scores[i] < *minScore) *minScore = scores[i];
    }
    return (double)sum / len;
}

// 获取成绩等级
char getGrade(int score) {
    if(score >= 90) return 'A';
    else if(score >= 80) return 'B';
    else if(score >= 70) return 'C';
    else if(score >= 60) return 'D';
    else return 'E';
}

int main() {
    const int STUDENT_NUM = 5;  // 学生数量
    int scores[STUDENT_NUM];
    int maxScore, minScore;
    
    // 输入成绩
    cout << "请输入" << STUDENT_NUM << "名学生的成绩:" << endl;
    for(int i=0; i<STUDENT_NUM; i++){
        cin >> scores[i];
    }
    
    // 统计
    double avg = statScores(scores, STUDENT_NUM, &maxScore, &minScore);
    
    // 输出结果
    cout << "\n成绩统计结果:" << endl;
    cout << "平均分:" << fixed << setprecision(1) << avg << endl;
    cout << "最高分:" << maxScore << endl;
    cout << "最低分:" << minScore << endl;
    
    // 输出每个学生的等级
    cout << "\n各学生成绩等级:" << endl;
    for(int i=0; i<STUDENT_NUM; i++){
        cout << "第" << i+1 << "名:" << scores[i] << "分 → " << getGrade(scores[i]) << endl;
    }
    
    return 0;
}

运行示例:

6.2 指针

        指针是存储内存地址的变量,它让程序能间接访问内存中的数据,是 C++ 灵活性和高效性的核心特性之一。

6.2.1 内存空间的访问方式

计算机中每个内存单元都有唯一地址,数据存储在内存中。C++ 有两种访问方式:

  • 直接访问:通过变量名直接访问(如int a=10; cout<<a;
  • 间接访问:通过指针存储的地址访问(如int* p=&a; cout<<*p;
内存访问方式流程图
内存访问方式详解:

1. 栈对象访问 (自动存储期)

代码语言:javascript
复制
void function() {
    int x = 10;         // 直接值访问
    MyClass obj;        // 自动构造
    obj.method();       // 成员访问
    x = obj.value;      // 对象值复制
} // 自动析构obj并释放栈内存
  • 特点
    • 自动分配/释放
    • 快速访问(寄存器/栈指针)
    • 作用域结束时自动销毁
    • 大小受限(通常1-8MB)

2. 堆对象访问 (动态存储期)

代码语言:javascript
复制
MyClass* create() {
    MyClass* p = new MyClass(); // 堆分配
    p->method();                // 指针成员访问
    (*p).value = 20;            // 解引用访问
    return p;
}

void use() {
    MyClass* ptr = create();
    delete ptr; // 必须显式释放
}
  • 访问方式
    • 指针操作:ptr->member
    • 解引用:*ptr
    • 智能指针:unique_ptr<MyClass> up(new MyClass());
  • 风险
    • 内存泄漏(忘记delete)
    • 悬垂指针(delete后访问)
    • 野指针(未初始化访问)

3. 全局/静态对象访问

代码语言:javascript
复制
int global = 5;             // 全局变量

void func() {
    static int count = 0;   // 静态局部变量
    count++;
    global = count;         // 直接访问
}

class Singleton {
    static Singleton& get() {
        static Singleton instance; // 静态成员
        return instance;
    }
};
  • 特点
    • 程序启动时初始化
    • 整个生命周期存在
    • 多线程访问需同步
    • 初始化顺序不确定(跨编译单元)

4. 类成员访问

代码语言:javascript
复制
class Container {
public:
    int value;          // 栈成员
    int* data;          // 堆成员指针
    
    Container(int size) 
        : value(0), data(new int[size]) {} // 堆分配
    
    ~Container() { delete[] data; } // 必须释放
    
    void process() {
        value++;        // 直接成员访问
        data[0] = value;// 堆成员访问
    }
};
  • 访问规则
    • 值成员:直接访问 obj.member
    • 指针成员:需解引用 obj.ptr->x
    • 引用成员:别名访问 obj.ref = y
内存访问安全指南:

访问方式

安全实践

风险提示

栈访问

保持小对象(<1KB)

栈溢出(stack overflow)

堆访问

使用智能指针(unique_ptr/shared_ptr)

内存泄漏/野指针

全局访问

避免跨编译单元依赖初始化顺序

未初始化访问

类成员访问

遵循RAII原则,在构造函数中初始化

空指针访问/浅拷贝问题

指针访问

访问前检查 nullptr

段错误(segmentation fault)

关键内存操作对比:
代码语言:javascript
复制
// 值访问(栈复制)
int a = obj.value;  

// 指针访问(堆间接)
int* p = &obj.value;
*p = 100;           

// 引用访问(别名)
int& ref = obj.value;
ref = 200;          

// 数组成员访问
int arr[5];
arr[2] = 300;       

// 智能指针访问
auto uptr = make_unique<MyClass>();
uptr->method(); 

最佳实践:优先使用栈对象和智能指针,避免裸指针操作。使用Valgrind/AddressSanitizer检测内存错误,遵循RAII原则管理资源。

6.2.2 指针变量的声明

        指针变量的声明需要指定基类型(指针指向的数据类型),语法:基类型* 指针名;

说明:
  • 基类型决定了指针解引用后能操作的数据大小(如 int每次访问 4 字节,double访问 8 字节)
  • *表示该变量是指针,与变量名绑定(int* p1,p2;中 p1 是指针,p2 是 int 变量)
示例:
代码语言:javascript
复制
#include <iostream>
using namespace std;

int main() {
    int a = 100;
    double b = 3.14;
    char c = 'A';
    
    // 声明指针并指向对应变量
    int* pInt = &a;      // int类型指针,指向a
    double* pDouble = &b;// double类型指针,指向b
    char* pChar = &c;    // char类型指针,指向c
    
    cout << "pInt指向的值:" << *pInt << endl;    // 输出100
    cout << "pDouble指向的值:" << *pDouble << endl;// 输出3.14
    cout << "pChar指向的值:" << *pChar << endl;    // 输出A
    
    return 0;
}
6.2.3 与地址相关的运算 ——“*” 和 “&”
  • &取地址运算符,返回变量的内存地址(如&a返回 a 的地址)
  • *解引用运算符,返回指针指向地址中的值(如*p返回 p 指向的值)
示例:
代码语言:javascript
复制
#include <iostream>
using namespace std;

int main() {
    int x = 20;
    int* p;  // 声明指针p
    
    p = &x;  // p存储x的地址(&x)
    
    cout << "x的值:" << x << endl;       // 输出20
    cout << "x的地址:" << &x << endl;    // 输出x的内存地址(如0x7ffd9a8b8a4c)
    cout << "p存储的地址:" << p << endl; // 输出与&x相同
    cout << "p指向的值:" << *p << endl;  // 输出20(解引用p)
    
    *p = 30;  // 通过指针修改x的值
    cout << "修改后x的值:" << x << endl;  // 输出30
    
    return 0;
}
6.2.4 指针的赋值

        指针可以通过赋值指向不同变量,但需确保类型兼容(或强制转换)。常见赋值场景:

  • 指向变量:int* p=&a;
  • 指向数组元素:int arr[5]; int* p=&arr[0];(等价于p=arr;,数组名是首地址)
  • 空指针:int* p=NULL; 或 int* p=nullptr;(C++11 推荐 nullptr,指向空地址)
  • 不指向任何有效地址:野指针(未初始化的指针,危险!)
示例:
代码语言:javascript
复制
#include <iostream>
#include <cstdio>  // 引入NULL的定义
using namespace std;

int main() {
    int a = 10, b = 20;
    int arr[] = {1,2,3};
    
    int* p = &a;  // 指向a
    cout << "p指向a:" << *p << endl;  // 10
    
    p = &b;       // 重新指向b
    cout << "p指向b:" << *p << endl;  // 20
    
    p = arr;      // 指向数组首元素(等价于&arr[0])
    cout << "p指向arr[0]:" << *p << endl;  // 1
    p++;          // 指针后移(指向arr[1])
    cout << "p指向arr[1]:" << *p << endl;  // 2
    
    p = NULL;  // 使用NULL代替nullptr,兼容C++98标准
    // cout << *p;  // 错误:解引用空指针会崩溃
    
    return 0;
}
6.2.5 指针运算

指针支持有限的运算:加减运算比较运算,运算规则与基类型相关。

运算规则:
  • 指针 ±n:指针向前 / 向后移动 n 个基类型大小的地址(如 int* p; p+1 表示地址 + 4 字节)
  • 指针 - 指针:两个同类型指针相减,结果为元素个数差(需指向同一数组才有意义)
  • 比较运算:指针可比较地址大小(如p1 < p2判断地址前后)
示例:
代码语言:javascript
复制
#include <iostream>
using namespace std;

int main() {
    int arr[] = {10,20,30,40,50};
    int* p = arr;  // 指向arr[0](地址假设为0x1000)
    
    cout << "p指向的值:" << *p << endl;  // 10(0x1000)
    p += 2;  // 移动2个int(8字节),指向arr[2](0x1008)
    cout << "p+2指向的值:" << *p << endl;  // 30
    
    int* pEnd = &arr[4];  // 指向arr[4](0x1010)
    cout << "pEnd - p = " << pEnd - p << endl;  // 结果2(arr[4]-arr[2]有2个元素)
    
    // 比较指针地址
    if(p < pEnd) {
        cout << "p在pEnd前面" << endl;  // 输出此句
    }
    
    return 0;
}
6.2.6 用指针处理数组元素

        数组名本质是首元素地址常量,指针与数组下标可互换使用:arr[i] 等价于 *(arr+i) 等价于 *(p+i)(p 是指向 arr 的指针)。

示例:指针遍历数组
代码语言:javascript
复制
#include <iostream>
using namespace std;

int main() {
    int nums[] = {1,2,3,4,5};
    int len = sizeof(nums)/sizeof(nums[0]);
    
    // 方法1:下标法(直观)
    cout << "下标法遍历:";
    for(int i=0; i<len; i++){
        cout << nums[i] << " ";
    }
    cout << endl;
    
    // 方法2:指针法(高效)
    cout << "指针法遍历:";
    int* p = nums;  // p指向首元素
    for(int i=0; i<len; i++){
        cout << *(p+i) << " ";  // 等价于p[i]
    }
    cout << endl;
    
    // 方法3:指针自增遍历
    cout << "指针自增遍历:";
    for(p = nums; p < nums + len; p++){  // 指针范围:首地址 ~ 尾地址
        cout << *p << " ";
    }
    cout << endl;
    
    return 0;
}
6.2.7 指针数组

        指针数组是元素为指针的数组,语法:基类型* 数组名[大小];。常用于存储多个字符串(避免存储冗余)。

示例:字符串指针数组
代码语言:javascript
复制
#include <iostream>
using namespace std;

int main() {
    // 指针数组:每个元素是char*(字符串指针)
    const char* languages[] = {
        "C++",
        "Python",
        "Java",
        "JavaScript"
    };
    int len = sizeof(languages)/sizeof(languages[0]);
    
    // 遍历指针数组
    cout << "编程语言列表:" << endl;
    for(int i=0; i<len; i++){
        cout << i+1 << ". " << languages[i] << endl;  // 输出字符串
    }
    
    return 0;
}

输出:

6.2.8 用指针作为函数参数

        指针作为函数参数时,函数可通过指针间接修改实参的值,实现 “返回多个结果” 的效果。

示例:交换两个数 + 返回和
代码语言:javascript
复制
#include <iostream>
using namespace std;

// 交换a和b的值(通过指针修改实参)
void swap(int* p1, int* p2) {
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

// 计算和,并通过指针返回差
int sumAndDiff(int a, int b, int* pDiff) {
    *pDiff = a - b;  // 差通过指针返回
    return a + b;    // 和通过返回值返回
}

int main() {
    int x = 10, y = 20;
    cout << "交换前:x=" << x << ", y=" << y << endl;  // x=10, y=20
    
    swap(&x, &y);
    cout << "交换后:x=" << x << ", y=" << y << endl;  // x=20, y=10
    
    int diff;
    int sum = sumAndDiff(x, y, &diff);
    cout << "x+y=" << sum << ", x-y=" << diff << endl;  // 30, 10
    
    return 0;
}
6.2.9 指针型函数

        指针型函数是返回值为指针的函数,语法:基类型* 函数名(参数列表);。常用于返回动态分配的内存或数组元素地址。

示例:查找数组中最大值的地址
代码语言:javascript
复制
#include <iostream>
#include <cstdio>  // 引入NULL的定义
using namespace std;

// 指针型函数:返回数组中最大值的地址
int* findMax(int arr[], int len) {
    if(len <= 0) return NULL;  // 使用NULL替代nullptr
    
    int* pMax = &arr[0];  // 假设首元素最大
    for(int i=1; i<len; i++){
        if(arr[i] > *pMax){
            pMax = &arr[i];  // 更新最大值地址
        }
    }
    return pMax;
}

int main() {
    int nums[] = {5, 2, 9, 1, 7};
    int len = sizeof(nums)/sizeof(nums[0]);
    
    int* pMax = findMax(nums, len);
    if(pMax != NULL){  // 使用NULL替代nullptr
        cout << "最大值:" << *pMax << endl;  // 输出9
        cout << "最大值位置(下标):" << pMax - nums << endl;  // 输出2(第3个元素)
    }
    
    return 0;
}
6.2.10 指向函数的指针

        指向函数的指针是存储函数地址的指针,可通过指针调用函数,常用于回调函数场景。

声明语法:返回类型 (*指针名)(参数类型列表);
示例:计算器(通过函数指针调用不同运算)
代码语言:javascript
复制
#include <iostream>
#include <cstdio>  // 引入NULL的定义
using namespace std;

// 加法
int add(int a, int b) {
    return a + b;
}

// 减法
int sub(int a, int b) {
    return a - b;
}

// 乘法
int mul(int a, int b) {
    return a * b;
}

// 通过函数指针调用运算
void calculate(int a, int b, int (*op)(int, int)) {
    if(op != NULL){  // 使用NULL替代nullptr
        cout << "结果:" << op(a, b) << endl;  // 调用指针指向的函数
    }
}

int main() {
    int x = 10, y = 5;
    
    // 声明函数指针并指向不同函数
    int (*pOp)(int, int);
    
    pOp = add;  // 指向加法
    calculate(x, y, pOp);  // 输出15
    
    pOp = sub;  // 指向减法
    calculate(x, y, pOp);  // 输出5
    
    pOp = mul;  // 指向乘法
    calculate(x, y, pOp);  // 输出50
    
    return 0;
}
6.2.11 对象指针

        对象指针是指向类对象的指针,通过->运算符访问对象成员(等价于(*指针).成员)。

示例:对象指针使用
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

class Person {
private:
    string name;
public:
    Person(string n) : name(n) {}
    
    void setName(string n) { name = n; }
    string getName() { return name; }
};

int main() {
    Person p1("Alice");  // 创建对象
    Person* pPtr = &p1;  // 对象指针指向p1
    
    // 访问成员:两种方式等价
    cout << "姓名:" << pPtr->getName() << endl;  // ->运算符
    cout << "姓名:" << (*pPtr).getName() << endl;  // 解引用+ .运算符
    
    pPtr->setName("Bob");  // 修改成员
    cout << "修改后姓名:" << p1.getName() << endl;  // 输出Bob
    
    // 动态创建对象(后续讲动态内存分配)
    Person* pNew = new Person("Charlie");
    cout << "动态对象姓名:" << pNew->getName() << endl;  // 输出Charlie
    delete pNew;  // 释放内存
    
    return 0;
}

6.3 动态内存分配

        静态内存(如局部变量、全局变量)由编译器自动分配和释放,动态内存由程序员通过newdelete手动管理,用于灵活控制内存大小(如运行时确定数组大小)。

动态内存操作:
  • new 类型:分配单个元素内存,返回指向该元素的指针
  • new 类型[大小]:分配数组内存,返回首元素指针
  • delete 指针:释放单个元素内存
  • delete[] 指针:释放数组内存(必须与 new [] 配对)
示例:动态数组
代码语言:javascript
复制
#include <iostream>
#include <cstdio>  // 引入NULL的定义
using namespace std;

int main() {
    // 动态分配单个int
    int* pInt = new int;  // 分配4字节内存
    *pInt = 100;
    cout << "动态int值:" << *pInt << endl;
    delete pInt;  // 释放内存,避免泄漏
    pInt = NULL;  // 用NULL替代nullptr,置空指针
    
    // 动态分配数组(大小运行时确定)
    int n;
    cout << "请输入数组大小:";
    cin >> n;
    
    int* pArr = new int[n];  // 分配n个int的内存
    if(pArr == NULL){  // 用NULL替代nullptr,检查内存分配失败
        cout << "内存分配失败!" << endl;
        return 1;
    }
    
    // 给动态数组赋值
    for(int i=0; i<n; i++){
        pArr[i] = i * 10;
    }
    
    // 打印数组
    cout << "动态数组元素:";
    for(int i=0; i<n; i++){
        cout << pArr[i] << " ";
    }
    cout << endl;
    
    delete[] pArr;  // 释放数组内存(必须用delete[])
    pArr = NULL;  // 用NULL替代nullptr,置空指针
    
    return 0;
}

⚠️ 注意:

  • 动态内存忘记释放会导致内存泄漏(程序运行时间越长,占用内存越多)
  • 释放后继续使用指针会导致悬垂指针(访问无效内存)
  • 同一内存释放多次会导致程序崩溃

6.4 用 vector 创建数组对象

   vector是 C++ 标准库提供的动态数组容器,自动管理内存(无需手动 new/delete),支持动态扩容,比原生数组更安全、便捷。

vector 常用操作:

操作

说明

vector<类型> v;

声明空 vector

vector<类型> v(n, val);

声明 n 个元素,初始值为 val

v.push_back(val);

尾部添加元素

v.size();

获取元素个数

v[i];

访问第 i 个元素(下标越界会崩溃)

v.at(i);

访问第 i 个元素(越界抛异常)

v.begin();

返回首元素迭代器(类似指针)

v.end();

返回尾后迭代器

示例:vector 使用
代码语言:javascript
复制
#include <iostream>
#include <vector>  // 需包含头文件
using namespace std;

int main() {
    // 创建vector并初始化
    vector<int> nums;  // 空vector
    vector<string> fruits = {"苹果", "香蕉", "橙子"};  // C++11初始化列表
    
    // 尾部添加元素
    nums.push_back(10);
    nums.push_back(20);
    nums.push_back(30);
    
    // 访问元素
    cout << "fruits第2个元素:" << fruits[1] << endl;  // 输出香蕉
    cout << "nums元素个数:" << nums.size() << endl;  // 输出3
    
    // 遍历vector(三种方式)
    cout << "nums元素(下标):";
    for(int i=0; i<nums.size(); i++){
        cout << nums[i] << " ";  // 10 20 30
    }
    cout << endl;
    
    cout << "nums元素(迭代器):";
    for(auto it = nums.begin(); it != nums.end(); it++){  // it是迭代器(类似指针)
        cout << *it << " ";  // 10 20 30
    }
    cout << endl;
    
    cout << "nums元素(范围for):";
    for(int num : nums){  // C++11范围for
        cout << num << " ";  // 10 20 30
    }
    cout << endl;
    
    // 动态数组优势:自动扩容
    nums.push_back(40);
    cout << "添加后size:" << nums.size() << endl;  // 输出4
    
    return 0;  // vector自动释放内存,无需手动操作
}

6.5 深层复制与浅层复制

        当类中包含指针成员时,默认的拷贝操作(拷贝构造函数、赋值运算符)是浅层复制(仅复制指针地址,不复制指针指向的内容),可能导致 “同一块内存被释放两次” 的问题。深层复制会复制指针指向的内容,避免此问题。

示例:浅层复制问题与深层复制解决方案
代码语言:javascript
复制
#include <iostream>
#include <cstring>  // 字符串函数
using namespace std;

class MyString {
private:
    char* str;  // 指针成员
public:
    // 构造函数
    MyString(const char* s = "") {
        int len = strlen(s) + 1;  // 包含'\0'
        str = new char[len];  // 分配内存
        strcpy(str, s);  // 复制字符串
    }
    
    // 浅层复制:默认拷贝构造(编译器生成)会导致问题
    // MyString(const MyString& other) : str(other.str) {}  // 危险!
    
    // 深层复制:自定义拷贝构造
    MyString(const MyString& other) {
        int len = strlen(other.str) + 1;
        str = new char[len];  // 新分配内存
        strcpy(str, other.str);  // 复制内容
    }
    
    // 浅层复制:默认赋值运算符(编译器生成)
    // MyString& operator=(const MyString& other) {
    //     if(this != &other) str = other.str;  // 危险!
    //     return *this;
    // }
    
    // 深层复制:自定义赋值运算符
    MyString& operator=(const MyString& other) {
        if(this != &other) {  // 避免自赋值
            delete[] str;  // 释放原有内存
            
            int len = strlen(other.str) + 1;
            str = new char[len];  // 新分配内存
            strcpy(str, other.str);  // 复制内容
        }
        return *this;
    }
    
    // 析构函数:释放内存
    ~MyString() {
        delete[] str;  // 释放str指向的数组
    }
    
    // 打印字符串
    void print() const {
        cout << str << endl;
    }
};

int main() {
    MyString s1("Hello");
    MyString s2 = s1;  // 调用拷贝构造(深层复制安全)
    MyString s3;
    s3 = s1;  // 调用赋值运算符(深层复制安全)
    
    s1.print();  // Hello
    s2.print();  // Hello
    s3.print();  // Hello
    
    return 0;  // 三个对象析构时释放各自内存,无冲突
}
浅层复制 vs 深层复制 核心对比表:

特性

浅层复制 (Shallow Copy)

深层复制 (Deep Copy)

复制内容

仅复制指针值(内存地址)

复制指针指向的实际数据

资源开销

低(不分配新内存)

高(需要分配新内存)

对象独立性

共享资源,修改相互影响

完全独立,修改互不影响

析构安全性

高风险(double free)

安全(各自管理资源)

实现方式

编译器默认生成

需要自定义复制构造/赋值运算符

典型应用场景

无资源管理的简单对象

含指针/文件句柄/网

6.6 字符串

        C++ 中有两种字符串处理方式:C 风格字符串(字符数组)和 C++ 风格string类(更安全便捷)。

6.6.1 用字符数组存储和处理字符串

C 风格字符串是以 '\0'(空字符)结尾的字符数组,需使用<cstring>库函数操作。

常用函数:
  • strlen(s):计算字符串长度(不含 '\0')
  • strcpy(dst, src):复制字符串(需保证 dst 足够大)
  • strcat(dst, src):拼接字符串
  • strcmp(s1, s2):比较字符串(相等返回 0,s1>s2 返回正)
示例:
代码语言:javascript
复制
#include <iostream>
#include <cstring>
using namespace std;

int main() {
    // 字符数组初始化字符串(自动添加'\0')
    char str1[] = "Hello";  // 大小6:'H','e','l','l','o','\0'
    char str2[20];  // 预留足够空间
    
    // 字符串长度
    cout << "str1长度:" << strlen(str1) << endl;  // 输出5
    cout << "str1数组大小:" << sizeof(str1) << endl;  // 输出6
    
    // 复制字符串
    strcpy(str2, str1);  // str2 = "Hello"
    cout << "复制后str2:" << str2 << endl;
    
    // 拼接字符串
    strcat(str2, " World");  // str2 = "Hello World"
    cout << "拼接后str2:" << str2 << endl;
    
    // 比较字符串
    char str3[] = "Hello";
    if(strcmp(str1, str3) == 0){  // 不能用==比较
        cout << "str1与str3相等" << endl;
    }
    
    return 0;
}
6.6.2 string 类

   string类是 C++ 标准库提供的字符串类,封装了字符数组,支持直接用运算符操作(如+拼接、==比较),自动管理内存,比 C 风格字符串更安全易用。

示例:string 类使用
代码语言:javascript
复制
#include <iostream>
#include <string>  // 需包含头文件
using namespace std;

int main() {
    // string初始化
    string s1 = "Hello";
    string s2("World");
    string s3;  // 空字符串
    
    // 字符串拼接(+运算符)
    s3 = s1 + " " + s2;  // s3 = "Hello World"
    cout << "s3:" << s3 << endl;
    
    // 字符串长度
    cout << "s3长度:" << s3.size() << endl;  // 输出11
    cout << "s3长度:" << s3.length() << endl;  // 输出11(与size等价)
    
    // 字符串比较(==, !=, <, >等)
    if(s1 == "Hello"){
        cout << "s1等于\"Hello\"" << endl;
    }
    if(s1 < s2){  // 按字典序比较
        cout << "s1在s2前面" << endl;  // "Hello" < "World"
    }
    
    // 访问字符
    cout << "s3第1个字符:" << s3[0] << endl;  // 'H'
    cout << "s3第6个字符:" << s3.at(5) << endl;  // ' '(at会检查越界)
    
    // 字符串截取(substr(pos, len))
    string sub = s3.substr(6, 5);  // 从位置6开始,取5个字符
    cout << "截取子串:" << sub << endl;  // 输出"World"
    
    // C风格字符串转换
    const char* cstr = s3.c_str();  // 返回const char*
    cout << "C风格字符串:" << cstr << endl;
    
    return 0;
}

6.7 综合实例 —— 个人银行账户管理程序

        结合数组、指针、字符串和类,实现一个简单的银行账户管理系统,支持添加账户、查询余额、存款、取款功能。

代码语言:javascript
复制
#include <iostream>
#include <string>
#include <vector>  // 用vector存储账户
using namespace std;

// 银行账户类
class BankAccount {
private:
    string accountId;  // 账号
    string name;       // 姓名
    double balance;    // 余额
public:
    // 构造函数
    BankAccount(string id, string n, double bal = 0.0) 
        : accountId(id), name(n), balance(bal) {}
    
    // 存款
    void deposit(double amount) {
        if(amount > 0) {
            balance += amount;
            cout << "存款成功!当前余额:" << balance << endl;
        } else {
            cout << "存款金额必须为正数!" << endl;
        }
    }
    
    // 取款
    void withdraw(double amount) {
        if(amount <= 0) {
            cout << "取款金额必须为正数!" << endl;
            return;
        }
        if(amount > balance) {
            cout << "余额不足!当前余额:" << balance << endl;
            return;
        }
        balance -= amount;
        cout << "取款成功!当前余额:" << balance << endl;
    }
    
    // 查询余额
    double getBalance() const {
        return balance;
    }
    
    // 获取账号
    string getAccountId() const {
        return accountId;
    }
    
    // 显示账户信息
    void showInfo() const {
        cout << "账号:" << accountId << endl;
        cout << "姓名:" << name << endl;
        cout << "余额:" << balance << endl;
    }
};

// 账户管理类
class AccountManager {
private:
    vector<BankAccount> accounts;  // 用vector存储账户对象
    
public:
    // 添加账户
    void addAccount(const BankAccount& acc) {
        accounts.push_back(acc);
        cout << "账户添加成功!" << endl;
    }
    
    // 根据账号查找账户(返回指针,找不到返回nullptr)
    BankAccount* findAccount(const string& id) {
        for(auto& acc : accounts) {  // 范围for遍历
            if(acc.getAccountId() == id) {
                return &acc;  // 返回找到的账户指针
            }
        }
        return nullptr;
    }
    
    // 显示所有账户
    void showAllAccounts() const {
        if(accounts.empty()) {
            cout << "暂无账户!" << endl;
            return;
        }
        cout << "\n===== 所有账户信息 =====" << endl;
        for(size_t i=0; i<accounts.size(); i++) {  // size_t是无符号整数
            cout << "\n账户" << i+1 << ":" << endl;
            accounts[i].showInfo();
        }
    }
};

// 主函数:菜单交互
int main() {
    AccountManager manager;
    int choice;
    
    do {
        // 菜单
        cout << "\n===== 银行账户管理系统 =====" << endl;
        cout << "1. 添加账户" << endl;
        cout << "2. 查询余额" << endl;
        cout << "3. 存款" << endl;
        cout << "4. 取款" << endl;
        cout << "5. 显示所有账户" << endl;
        cout << "0. 退出" << endl;
        cout << "请选择操作:";
        cin >> choice;
        
        switch(choice) {
            case 1: {  // 添加账户
                string id, name;
                double bal;
                cout << "请输入账号:";
                cin >> id;
                cout << "请输入姓名:";
                cin >> name;
                cout << "请输入初始余额:";
                cin >> bal;
                manager.addAccount(BankAccount(id, name, bal));
                break;
            }
            case 2: {  // 查询余额
                string id;
                cout << "请输入账号:";
                cin >> id;
                BankAccount* acc = manager.findAccount(id);
                if(acc) {
                    cout << "账户余额:" << acc->getBalance() << endl;
                } else {
                    cout << "账号不存在!" << endl;
                }
                break;
            }
            case 3: {  // 存款
                string id;
                double amount;
                cout << "请输入账号:";
                cin >> id;
                BankAccount* acc = manager.findAccount(id);
                if(acc) {
                    cout << "请输入存款金额:";
                    cin >> amount;
                    acc->deposit(amount);
                } else {
                    cout << "账号不存在!" << endl;
                }
                break;
            }
            case 4: {  // 取款
                string id;
                double amount;
                cout << "请输入账号:";
                cin >> id;
                BankAccount* acc = manager.findAccount(id);
                if(acc) {
                    cout << "请输入取款金额:";
                    cin >> amount;
                    acc->withdraw(amount);
                } else {
                    cout << "账号不存在!" << endl;
                }
                break;
            }
            case 5:  // 显示所有账户
                manager.showAllAccounts();
                break;
            case 0:  // 退出
                cout << "谢谢使用,再见!" << endl;
                break;
            default:
                cout << "无效选择,请重试!" << endl;
        }
    } while(choice != 0);
    
    return 0;
}

运行示例:

代码语言:javascript
复制
===== 银行账户管理系统 =====
1. 添加账户
2. 查询余额
3. 存款
4. 取款
5. 显示所有账户
0. 退出
请选择操作:1
请输入账号:1001
请输入姓名:张三
请输入初始余额:1000
账户添加成功!

...(省略其他操作步骤)...

===== 所有账户信息 =====

账户1:
账号:1001
姓名:张三
余额:1500

6.8 深度探索

6.8.1 指针与引用

引用(int& ref = a;)是变量的别名,与指针有相似之处,但本质不同:

特性

指针

引用

定义

int* p = &a;

int& ref = a;

初始化

可先声明后赋值(如int* p; p=&a;)

必须初始化(int& ref;错误)

指向性

可指向不同变量(p=&b;)

一旦绑定,不可更改引用对象

空值

可指向 nullptr

必须指向有效变量,无空引用

解引用

需要*运算符(*p = 10;)

直接使用(ref = 10;)

sizeof

存储地址大小(如 4 或 8 字节)

引用对象的大小(sizeof(ref)=sizeof(a))

作为参数

传地址,可修改实参

传引用,效果同指针(更简洁)

示例:指针与引用对比
代码语言:javascript
复制
#include <iostream>
using namespace std;

// 指针作为参数
void swapPtr(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 引用作为参数
void swapRef(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;  // 直接操作引用,等价于操作原变量
}

int main() {
    int x = 10, y = 20;
    
    // 指针使用
    int* p = &x;
    *p = 15;  // x变为15
    p = &y;
    *p = 25;  // y变为25
    cout << "x=" << x << ", y=" << y << endl;  // 15,25
    
    // 引用使用
    int& ref = x;
    ref = 30;  // x变为30
    // ref = y;  // 不是更改引用对象,而是x=y(x变为25)
    cout << "x=" << x << ", y=" << y << endl;  // 25,25
    
    // 交换函数对比
    swapPtr(&x, &y);
    cout << "swapPtr后:x=" << x << ", y=" << y << endl;  // 25,25(值相同,交换无变化)
    
    x=10, y=20;  // 重置
    swapRef(x, y);
    cout << "swapRef后:x=" << x << ", y=" << y << endl;  // 20,10
    
    return 0;
}
6.8.2 指针的安全性隐患及其应对方案

指针使用不当会导致严重问题,常见隐患及解决方案:

  1. 野指针:未初始化的指针(指向随机地址) 解决方案:指针声明时初始化(如int* p = nullptr;
  2. 空指针解引用:对 nullptr 解引用(*p = 10;当 p=nullptr 时) 解决方案:解引用前检查(if(p != nullptr) *p = 10;
  3. 悬垂指针:指针指向的内存已被释放(如delete p; *p=10;) 解决方案:释放内存后将指针置空(delete p; p=nullptr;
  4. 内存泄漏:动态内存未释放(new后无delete) 解决方案:确保newdelete配对,使用智能指针(unique_ptrshared_ptr
  5. 指针越界:访问数组外的内存(如int arr[3]; arr[5] = 10;) 解决方案:访问前检查下标范围,使用 vector 的at()方法
示例:智能指针解决内存泄漏
代码语言:javascript
复制
#include <iostream>
#include <memory>  // 智能指针头文件
using namespace std;

int main() {
    // unique_ptr:独占所有权,自动释放内存
    unique_ptr<int> up(new int(100));  // 无需手动delete
    cout << *up << endl;  // 100
    // unique_ptr<int> up2 = up;  // 错误:unique_ptr不可复制
    
    // shared_ptr:共享所有权,引用计数为0时释放
    shared_ptr<string> sp1(new string("Hello"));
    cout << "sp1引用计数:" << sp1.use_count() << endl;  // 1
    
    shared_ptr<string> sp2 = sp1;  // 共享所有权
    cout << "sp1引用计数:" << sp1.use_count() << endl;  // 2
    cout << "sp2指向内容:" << *sp2 << endl;  // Hello
    
    sp1.reset();  // 释放sp1的所有权
    cout << "sp2引用计数:" << sp2.use_count() << endl;  // 1
    
    // 离开作用域时,sp2自动释放内存,无泄漏
    
    return 0;
}
6.8.3 const_cast 的应用

  const_cast是 C++ 的类型转换运算符,用于移除指针或引用的 const 属性(仅能转换 const 限定符)。

使用场景:
  • 调用非 const 函数但实参是 const 对象(需确保不修改对象,否则行为未定义)
  • 处理旧代码中接口不匹配的问题
示例:
代码语言:javascript
复制
#include <iostream>
using namespace std;

// 非const函数,修改参数
void modify(int* p) {
    *p = 100;
}

int main() {
    const int a = 5;  // const变量
    // int* p = &a;  // 错误:不能将const int*转为int*
    
    // 使用const_cast移除const属性
    int* p = const_cast<int*>(&a);  // 允许转换,但修改const变量是未定义行为!
    
    // 危险:修改原const变量(可能编译器优化导致结果不符合预期)
    *p = 10;
    cout << "a的值:" << a << endl;  // 可能输出5(编译器优化)或10(未优化)
    cout << "*p的值:" << *p << endl;  // 可能输出10
    
    // 正确用法:临时移除const,不修改原对象
    const int b = 20;
    const int* cp = &b;
    int* np = const_cast<int*>(cp);
    // *np = 30;  // 禁止:不要修改原const对象!
    
    // 合法场景:非const引用指向const对象(不修改)
    const string s = "Hello";
    string& ref = const_cast<string&>(s);
    // ref = "World";  // 禁止修改!
    cout << ref << endl;  // 合法:只读访问
    
    return 0;
}

⚠️ 注意:const_cast不能修改原 const 对象的值,否则行为未定义(可能崩溃或结果异常),仅用于临时移除 const 属性以适配接口。

6.9 小结

本章我们学习了 C++ 中三个核心概念:

  • 数组:相同类型元素的连续集合,支持批量数据处理,需注意下标越界和传参方式。
  • 指针:存储地址的变量,支持间接访问内存,是 C++ 灵活性的核心,但需注意安全问题(野指针、内存泄漏等)。
  • 字符串:C 风格字符串(字符数组)和 C++ string类,string类更安全便捷,推荐优先使用。

        此外,我们还学习了动态内存分配(new/delete)、vector 动态数组、深层 / 浅层复制、指针与引用的区别等重要知识。这些内容是后续学习数据结构(如链表、树)和高级 C++ 特性的基础,建议多通过实例练习巩固。

关键原则

  • 数组下标从 0 开始,避免越界
  • 指针使用前初始化,释放后置空
  • 动态内存newdelete配对使用
  • 类中包含指针成员时,实现深层复制
  • 优先使用stringvector,减少原生指针和数组的使用风险

        希望本章内容对你有所帮助!如果有任何问题或建议,欢迎在评论区留言交流~ 👍</think># 《C++ 程序设计》第 6 章 - 数组、指针与字符串:从基础到实战

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本章知识思维导图
  • 6.1 数组
    • 6.1.1 数组的声明与使用
      • 基本语法:
      • 使用示例:
    • 6.1.2 数组的存储与初始化
      • 数组存储示意图(以 int scores [5] 为例):
      • 数组初始化方式:
    • 6.1.3 数组作为函数参数
      • 传参语法:
      • 示例:求数组元素之和
    • 6.1.4 对象数组
      • 示例:学生对象数组
    • 6.1.5 程序实例:数组综合应用(成绩统计)
  • 6.2 指针
    • 6.2.1 内存空间的访问方式
      • 内存访问方式流程图
    • 内存访问方式详解:
    • 内存访问安全指南:
    • 关键内存操作对比:
    • 6.2.2 指针变量的声明
      • 说明:
      • 示例:
    • 6.2.3 与地址相关的运算 ——“*” 和 “&”
      • 示例:
    • 6.2.4 指针的赋值
      • 示例:
    • 6.2.5 指针运算
      • 运算规则:
      • 示例:
    • 6.2.6 用指针处理数组元素
      • 示例:指针遍历数组
    • 6.2.7 指针数组
      • 示例:字符串指针数组
    • 6.2.8 用指针作为函数参数
      • 示例:交换两个数 + 返回和
    • 6.2.9 指针型函数
      • 示例:查找数组中最大值的地址
    • 6.2.10 指向函数的指针
      • 声明语法:返回类型 (*指针名)(参数类型列表);
      • 示例:计算器(通过函数指针调用不同运算)
    • 6.2.11 对象指针
      • 示例:对象指针使用
  • 6.3 动态内存分配
    • 动态内存操作:
    • 示例:动态数组
  • 6.4 用 vector 创建数组对象
    • vector 常用操作:
    • 示例:vector 使用
  • 6.5 深层复制与浅层复制
    • 示例:浅层复制问题与深层复制解决方案
    • 浅层复制 vs 深层复制 核心对比表:
  • 6.6 字符串
    • 6.6.1 用字符数组存储和处理字符串
      • 常用函数:
      • 示例:
    • 6.6.2 string 类
      • 示例:string 类使用
  • 6.7 综合实例 —— 个人银行账户管理程序
  • 6.8 深度探索
    • 6.8.1 指针与引用
      • 示例:指针与引用对比
    • 6.8.2 指针的安全性隐患及其应对方案
      • 示例:智能指针解决内存泄漏
    • 6.8.3 const_cast 的应用
      • 使用场景:
      • 示例:
  • 6.9 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档