1、资料清单

资料 内容 入口
教材 《C++语言程序设计(第4版)》Chap6,原书页码 204-266 抽取 Markdown 下载
课件 Chap6 课件,位于本地郑莉 C++ 资料目录 本地资料夹
例题源代码 C++V5 源代码 下载
VS2019 工程 C++V5 VS2019 solution 下载

本章是 C++ 基础里最容易出错的一章。主线是数组下标 → 指针和地址 → 动态内存 → vector/string → 深拷贝与浅拷贝

2、颜色标注

颜色 含义 使用位置
蓝色 核心术语、定义 数组、指针、引用、字符串
绿色 推荐写法、主线 std::vectorstd::string、RAII
黄色 易错点、边界条件 下标越界、空指针、浅拷贝
红色 不建议做法 未初始化指针、忘记 delete[]gets

3、学习目标

目标 要会到什么程度
数组 会定义一维、二维、多维数组,知道下标从 0 开始
指针 分清地址、指针变量、解引用、取地址
引用 能说清引用和指针的区别
动态内存 理解 new / deletenew[] / delete[] 配对
vector 会用 std::vector 替代大多数手写动态数组
字符串 分清 C 风格字符串和 std::string
深拷贝 会解释为什么有指针成员时要写复制构造和赋值
函数指针 能看懂普通函数指针和成员函数指针的声明

4、章节目录

小节 内容 本节关键词
6.1 数组 一维数组、二维数组、多维数组、下标
6.2 指针 地址、解引用、空指针、指针运算
6.3 动态内存分配 newdelete、资源释放
6.4 vector 动态数组、边界管理、标准库容器
6.5 深复制与浅复制 指针成员、复制构造、赋值运算
6.6 字符串 字符数组、\0std::string
6.7 个人银行账户管理程序改进 数组保存对象、批量管理

5、数组

数组是一组类型相同、内存连续、通过下标访问的数据。

1
int numbers[5] = {1, 2, 3, 4, 5};

访问:

1
2
std::cout << numbers[0] << '\n'; // 第一个元素
std::cout << numbers[4] << '\n'; // 最后一个元素

数组下标从 0 开始。长度为 n 的数组,合法下标是 0n - 1。访问 array[n] 是越界。

5.1 一维数组

1
2
3
4
5
int scores[3] = {90, 85, 100};

for (int i = 0; i < 3; ++i) {
std::cout << scores[i] << '\n';
}

5.2 二维数组

二维数组可以理解成表格。

1
2
3
4
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};

访问第 i 行第 j 列:

1
matrix[i][j]

5.3 数组长度

对同一作用域内的原生数组,可以用:

1
int length = sizeof(numbers) / sizeof(numbers[0]);

但数组传入函数后会退化为指针,sizeof 不再得到原数组总大小。

1
2
3
void print(int array[]) {
// 这里的 array 已经是指针,不再是完整数组
}

所以现代 C++ 更推荐:

1
2
3
4
5
#include <array>
#include <vector>

std::array<int, 5> fixedNumbers = {1, 2, 3, 4, 5};
std::vector<int> dynamicNumbers = {1, 2, 3, 4, 5};

6、指针

指针变量保存的是另一个对象的地址。

1
2
int value = 10;
int* p = &value;
写法 含义
&value value 的地址
int* p 定义一个指向 int 的指针
*p 解引用,访问指针所指向的对象
1
2
*p = 20;
std::cout << value << '\n'; // 20

指针必须先指向有效对象,才能解引用。未初始化指针不能直接 *p = ...

6.1 空指针

如果暂时没有指向对象,使用 nullptr

1
int* p = nullptr;

使用前先判断:

1
2
3
if (p != nullptr) {
std::cout << *p << '\n';
}

6.2 指针和数组

数组名在很多表达式中会退化为指向首元素的指针。

1
2
3
4
5
int a[3] = {10, 20, 30};
int* p = a;

std::cout << *p << '\n'; // 10
std::cout << *(p + 1) << '\n'; // 20

p + 1 移动到下一个 int 元素,不是地址数字简单加 1。

7、引用和指针

引用是对象的别名,定义时必须初始化,之后不能改绑。

1
2
3
int a = 10;
int& r = a;
r = 20;

指针是保存地址的变量,可以改指向,也可以为空。

1
2
3
4
int a = 10;
int b = 20;
int* p = &a;
p = &b;
对比 引用 指针
是否必须初始化 必须 不必须,但必须使用前有效
是否可以为空 不可以 可以是 nullptr
是否可以改绑 不可以 可以
使用形式 r *p

8、动态内存

动态内存在运行时申请。

1
2
int* p = new int(5);
delete p;

数组动态分配:

1
2
int* array = new int[10];
delete[] array;

必须配对:

申请 释放
new T delete
new T[n] delete[]

手写 new 后忘记 delete 会内存泄漏;用错 delete / delete[] 也会出问题。现代 C++ 优先用标准库容器和智能指针

1
2
3
4
5
#include <memory>
#include <vector>

auto value = std::make_unique<int>(5);
std::vector<int> array(10);

9、vectorstring

9.1 std::vector

std::vector 是动态数组,大小可以变化,能自动管理内存。

1
2
3
4
#include <vector>

std::vector<int> numbers = {1, 2, 3};
numbers.push_back(4);

遍历:

1
2
3
for (int value : numbers) {
std::cout << value << '\n';
}

9.2 C 风格字符串

C 风格字符串本质是以空字符 '\0' 结尾的字符数组。

1
char text[] = "Hello";

内存中大致是:

1
H e l l o \0

9.3 std::string

现代 C++ 推荐优先使用 std::string

1
2
3
4
#include <string>

std::string text = "Hello";
text += ", C++";

它会自动管理内存,支持长度查询、拼接、查找等操作。

10、深拷贝与浅拷贝

如果一个类中有指针成员,默认复制往往只是复制地址,这叫浅拷贝。

1
2
3
4
class BadString {
private:
char* data;
};

浅拷贝的问题:

问题 说明
两个对象指向同一块内存 修改一个会影响另一个
析构时重复释放 两个对象都 delete[] data
生命周期混乱 一个对象销毁后另一个变成悬空指针

深拷贝是复制指针所指向的内容,给新对象分配自己的内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyBuffer {
public:
MyBuffer(const MyBuffer& other)
: size(other.size), data(new int[other.size]) {
for (int i = 0; i < size; ++i) {
data[i] = other.data[i];
}
}

~MyBuffer() {
delete[] data;
}

private:
int size;
int* data;
};

复盘重点:类里一旦出现“自己管理资源”的裸指针,就要想到复制构造函数、赋值运算符和析构函数,也就是传统的 Rule of Three。

11、本章代码复盘

下面这个例子把 vectorstring、指针和引用串起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <string>
#include <vector>

void addScore(std::vector<int>& scores, int score) {
scores.push_back(score);
}

double average(const std::vector<int>& scores) {
int total = 0;
for (int score : scores) {
total += score;
}
return scores.empty() ? 0.0 : static_cast<double>(total) / scores.size();
}

int main() {
std::vector<int> scores;
addScore(scores, 90);
addScore(scores, 85);
addScore(scores, 100);

std::string name = "Rui";
const std::string* pName = &name;

std::cout << *pName << "'s average: "
<< average(scores) << '\n';

return 0;
}

观察点:

观察点 说明
std::vector<int>& 引用传递,函数能修改原 vector
const std::vector<int>& 避免复制,又保证只读
const std::string* 指针指向的字符串不能通过该指针修改
scores.empty() 防止除以 0

12、课后习题答案

以下答案按现代 C++ 重新整理。原答案中的 iostream.hvoid main()gets() 等旧写法已经替换。

6-1 数组 A[10][5][15] 一共有多少个元素

元素个数:

1
10 × 5 × 15 = 750

答案:750 个元素。

6-2 在数组 A[20] 中第一个元素和最后一个元素是哪一个

第一个元素是 A[0],最后一个元素是 A[19]

6-3 用一条语句定义一个有五个元素的整型数组,并依次赋值 15

1
int integerArray[5] = {1, 2, 3, 4, 5};

也可以让编译器推断长度:

1
int integerArray[] = {1, 2, 3, 4, 5};

6-4 已知数组 oneArray,用一条语句求出元素个数

1
int length = sizeof(oneArray) / sizeof(oneArray[0]);

注意:这个写法只适用于 oneArray 仍然是原生数组的作用域内。如果传入函数后退化为指针,就不能这样求长度。

6-5 定义一个 5 × 3 的二维整型数组,并依次赋值 115

1
2
3
4
5
6
7
int theArray[5][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{10, 11, 12},
{13, 14, 15}
};

6-6 运算符 *& 的作用是什么

运算符 名称 作用
& 取地址运算符 取得对象的内存地址
* 解引用运算符 访问指针所指向的对象

例子:

1
2
3
int value = 10;
int* p = &value;
std::cout << *p << '\n';

6-7 什么叫指针?指针中储存的地址和这个地址中的值有什么区别

指针是一种变量,它保存另一个对象的地址。

1
2
int value = 10;
int* p = &value;
表达式 含义
p 保存的地址
*p 地址里存放的值
&value value 的地址

6-8 定义整型指针,用 new 分配 10 个整型元素的地址空间

教材写法:

1
2
int* pInteger = new int[10];
delete[] pInteger;

现代写法更推荐:

1
std::vector<int> values(10);

如果必须动态分配,也可以:

1
auto values = std::make_unique<int[]>(10);

6-9 字符串 "Hello, world!" 的结束符是什么

结束符是空字符 '\0'

C 风格字符串会在最后自动保存一个 '\0',用来表示字符串结束。

6-10 定义五个元素的整型数组,输入并显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

int main() {
int myArray[5];

for (int i = 0; i < 5; ++i) {
std::cout << "Value for myArray[" << i << "]: ";
std::cin >> myArray[i];
}

for (int i = 0; i < 5; ++i) {
std::cout << i << ": " << myArray[i] << '\n';
}

return 0;
}

示例:

1
2
3
4
5
6
7
8
9
10
Value for myArray[0]: 2
Value for myArray[1]: 5
Value for myArray[2]: 7
Value for myArray[3]: 8
Value for myArray[4]: 3
0: 2
1: 5
2: 7
3: 8
4: 3

6-11 引用和指针有何区别?何时只能使用指针而不能使用引用

对比 引用 指针
本质 对象别名 保存地址的变量
是否必须初始化 必须 可以先为空
是否可为空 不可以 可以是 nullptr
是否可改指向 不可以 可以
使用方式 r *p

当需要“没有指向任何对象”的状态,或者需要在运行过程中改指向时,只能使用指针。

6-12 声明下列指针

1
2
3
float* pFloat;
char* pString;
customer* prec;

如果 customer 是结构体类型,也可以写成:

1
struct customer* prec;

6-13 给定 float 类型指针 fp,显示它所指向的值

1
std::cout << "Value == " << *fp << '\n';

使用前要保证 fp 已经指向有效的 float 对象。

6-14 定义一个 double 指针,显示指针大小和所指变量大小

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int main() {
double value = 0.0;
double* counter = &value;

std::cout << "Size of pointer == " << sizeof(counter) << '\n';
std::cout << "Size of addressed value == " << sizeof(*counter) << '\n';

return 0;
}

说明:

表达式 含义
sizeof(counter) 指针变量自身占多少字节
sizeof(*counter) 指针指向的 double 对象占多少字节

6-15 const int* p1int* const p2 的区别

1
2
const int* p1; // 指向常量的指针
int* const p2 = &value; // 指针常量
写法 能否改指向 能否通过指针改值
const int* p1 可以 不可以
int* const p2 不可以 可以

6-16 定义变量、指针和引用,通过指针改为 10,通过引用改为 5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

int main() {
int a = 0;
int* p = &a;
int& r = a;

*p = 10;
std::cout << a << '\n';

r = 5;
std::cout << a << '\n';

return 0;
}

输出:

1
2
10
5

6-17 程序有什么问题

问题代码的核心错误是指针没有初始化就被解引用。

1
2
int* p;
*p = 9; // 危险:p 没有指向有效对象

正确写法:

1
2
3
int value = 0;
int* p = &value;
*p = 9;

或者:

1
2
int* p = new int(9);
delete p;

6-18 程序有什么问题,请改正

原程序中 Fn1() 申请了动态内存,但返回值是 *p,导致指针丢失,无法释放,产生内存泄漏。

更直接的修正:不需要动态分配。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int fn1() {
return 5;
}

int main() {
int a = fn1();
std::cout << "the value of a is: " << a << '\n';
return 0;
}

如果题目要求保留动态分配,可以返回指针并释放:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int* fn1() {
return new int(5);
}

int main() {
int* a = fn1();
std::cout << "the value of a is: " << *a << '\n';
delete a;
return 0;
}

现代 C++ 写法:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <memory>

std::unique_ptr<int> fn1() {
return std::make_unique<int>(5);
}

int main() {
auto a = fn1();
std::cout << "the value of a is: " << *a << '\n';
return 0;
}

6-19 声明函数指针和成员函数指针

声明一个参数为 int,返回值为 long 的普通函数指针:

1
long (*p_fn1)(int);

声明类 A 的一个成员函数指针,参数为 int,返回值为 long

1
long (A::*p_fn2)(int);

例子:

1
2
3
4
5
6
7
8
class A {
public:
long f(int value) {
return value;
}
};

long (A::*p_fn2)(int) = &A::f;

6-20 实现 SimpleCircle,半径用 int* 保存

这题的重点是指针成员和深拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>

class SimpleCircle {
public:
SimpleCircle() : radius(new int(5)) {}

explicit SimpleCircle(int value) : radius(new int(value)) {}

SimpleCircle(const SimpleCircle& other)
: radius(new int(other.getRadius())) {}

SimpleCircle& operator=(const SimpleCircle& other) {
if (this == &other) {
return *this;
}

*radius = other.getRadius();
return *this;
}

~SimpleCircle() {
delete radius;
}

void setRadius(int value) {
if (value >= 0) {
*radius = value;
}
}

int getRadius() const {
return *radius;
}

double getArea() const {
constexpr double pi = 3.14159265358979323846;
return pi * (*radius) * (*radius);
}

private:
int* radius;
};

int main() {
SimpleCircle circleOne;
SimpleCircle circleTwo(9);
SimpleCircle circleThree = circleTwo;

circleTwo.setRadius(12);

std::cout << "CircleOne radius: " << circleOne.getRadius() << '\n';
std::cout << "CircleTwo radius: " << circleTwo.getRadius() << '\n';
std::cout << "CircleThree radius: " << circleThree.getRadius() << '\n';

return 0;
}

输出:

1
2
3
CircleOne radius: 5
CircleTwo radius: 12
CircleThree radius: 9

circleThree 仍然是 9,说明复制构造函数做的是深拷贝,不是两个对象共享同一块半径内存。

6-21 编写函数,统计英文句子中字母个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <cctype>
#include <iostream>
#include <string>

int countLetters(const std::string& text) {
int count = 0;

for (unsigned char ch : text) {
if (std::isalpha(ch)) {
++count;
}
}

return count;
}

int main() {
std::string text;

std::cout << "输入一个英语句子:" << std::endl;
std::getline(std::cin, text);

std::cout << "这个句子里有 "
<< countLetters(text)
<< " 个字母。" << std::endl;

return 0;
}

示例:

1
2
3
输入一个英语句子:
It is very interesting!
这个句子里有 19 个字母。

6-22 编写函数 index(s, t),返回子串最左位置

使用 std::string 的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <string>

int indexOf(const std::string& s, const std::string& t) {
std::size_t position = s.find(t);
if (position == std::string::npos) {
return -1;
}
return static_cast<int>(position);
}

int main() {
std::string str1;
std::string str2;

std::cout << "输入一个英语单词:";
std::cin >> str1;
std::cout << "输入另一个英语单词:";
std::cin >> str2;

int position = indexOf(str1, str2);

if (position >= 0) {
std::cout << str2 << " 在 " << str1
<< " 中左起第 " << position + 1
<< " 个位置。" << std::endl;
} else {
std::cout << str2 << " 不在 " << str1 << " 中。" << std::endl;
}

return 0;
}

如果按题目保留 C 风格字符串,可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int indexOf(const char* s, const char* t) {
for (int i = 0; s[i] != '\0'; ++i) {
int j = i;
int k = 0;

while (t[k] != '\0' && s[j] == t[k]) {
++j;
++k;
}

if (t[k] == '\0') {
return i;
}
}

return -1;
}

判断是否找到时应该用 position >= 0。如果子串从第 0 个位置开始,position 等于 0,也是成功匹配。

6-23 编写递归函数 reverse(char* s),使字符串倒序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <cstring>
#include <iostream>

void reverseRange(char* left, char* right) {
if (left >= right) {
return;
}

char temp = *left;
*left = *right;
*right = temp;

reverseRange(left + 1, right - 1);
}

void reverse(char* s) {
if (s == nullptr || *s == '\0') {
return;
}

reverseRange(s, s + std::strlen(s) - 1);
}

int main() {
char text[100];

std::cout << "输入一个字符串:";
std::cin >> text;

std::cout << "原字符串为:" << text << '\n';
reverse(text);
std::cout << "倒序反转后为:" << text << '\n';

return 0;
}

示例:

1
2
3
输入一个字符串:abcdefghijk
原字符串为:abcdefghijk
倒序反转后为:kjihgfedcba

6-24 输入 8 个成绩,计算平均成绩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <vector>

int main() {
constexpr int studentCount = 8;
std::vector<double> grades(studentCount);

for (int i = 0; i < studentCount; ++i) {
std::cout << "Enter grade #" << i + 1 << ": ";
std::cin >> grades[i];
}

double total = 0.0;
for (double grade : grades) {
total += grade;
}

double average = total / studentCount;
std::cout << "\nAverage grade: " << average << std::endl;

return 0;
}

示例:

1
2
3
4
5
6
7
8
9
10
Enter grade #1: 86
Enter grade #2: 98
Enter grade #3: 67
Enter grade #4: 80
Enter grade #5: 78
Enter grade #6: 95
Enter grade #7: 78
Enter grade #8: 56

Average grade: 79.75

6-25 设计字符串类 MyString

这题是本章最重要的综合题:构造、析构、复制构造、赋值、拼接、下标访问,本质都围绕“自己管理一段字符数组”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <cstring>
#include <iostream>
#include <stdexcept>

class MyString {
public:
MyString() : data(new char[1]), length(0) {
data[0] = '\0';
}

MyString(const char* text) {
if (text == nullptr) {
length = 0;
data = new char[1];
data[0] = '\0';
return;
}

length = std::strlen(text);
data = new char[length + 1];
std::strcpy(data, text);
}

MyString(const MyString& other)
: data(new char[other.length + 1]), length(other.length) {
std::strcpy(data, other.data);
}

MyString& operator=(const MyString& other) {
if (this == &other) {
return *this;
}

char* newData = new char[other.length + 1];
std::strcpy(newData, other.data);

delete[] data;
data = newData;
length = other.length;

return *this;
}

~MyString() {
delete[] data;
}

char& operator[](std::size_t index) {
if (index >= length) {
throw std::out_of_range("MyString index out of range");
}
return data[index];
}

char operator[](std::size_t index) const {
if (index >= length) {
throw std::out_of_range("MyString index out of range");
}
return data[index];
}

MyString operator+(const MyString& other) const {
MyString result;

delete[] result.data;
result.length = length + other.length;
result.data = new char[result.length + 1];

std::strcpy(result.data, data);
std::strcat(result.data, other.data);

return result;
}

MyString& operator+=(const MyString& other) {
*this = *this + other;
return *this;
}

std::size_t getLength() const {
return length;
}

const char* c_str() const {
return data;
}

private:
char* data;
std::size_t length;
};

int main() {
MyString s1("initial test");
std::cout << "S1:\t" << s1.c_str() << '\n';

s1 = "Hello World";
std::cout << "S1:\t" << s1.c_str() << '\n';

MyString tempTwo("; nice to be here!");
s1 += tempTwo;
std::cout << "tempTwo:\t" << tempTwo.c_str() << '\n';
std::cout << "S1:\t" << s1.c_str() << '\n';

std::cout << "S1[4]:\t" << s1[4] << '\n';
s1[4] = 'x';
std::cout << "S1:\t" << s1.c_str() << '\n';

MyString s2(" Another MyString");
MyString s3;
s3 = s1 + s2;
std::cout << "S3:\t" << s3.c_str() << '\n';

MyString s4;
s4 = "Why does this work?";
std::cout << "S4:\t" << s4.c_str() << '\n';

return 0;
}

输出:

1
2
3
4
5
6
7
8
S1:     initial test
S1: Hello World
tempTwo: ; nice to be here!
S1: Hello World; nice to be here!
S1[4]: o
S1: Hellx World; nice to be here!
S3: Hellx World; nice to be here! Another MyString
S4: Why does this work?

复盘重点:

成员 作用
构造函数 申请字符数组并写入字符串
复制构造函数 深拷贝,给新对象自己的内存
赋值运算符 处理自赋值,释放旧内存,复制新内容
析构函数 释放 data
operator+ 返回拼接后的新字符串
operator+= 在当前对象上追加
operator[] 访问指定字符

真实项目中不要自己写字符串类,直接用 std::string。这道题的价值是理解深拷贝、析构和资源管理。

6-26 编写 3 × 3 矩阵转置函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>

void transpose(int matrix[3][3]) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < i; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}

void printMatrix(int matrix[3][3]) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
std::cout << matrix[i][j] << ' ';
}
std::cout << '\n';
}
}

int main() {
int data[3][3];

std::cout << "输入矩阵的元素" << std::endl;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
std::cout << "第 " << i + 1 << " 行第 "
<< j + 1 << " 个元素为:";
std::cin >> data[i][j];
}
}

std::cout << "输入的矩阵为:" << std::endl;
printMatrix(data);

transpose(data);

std::cout << "转置后的矩阵为:" << std::endl;
printMatrix(data);

return 0;
}

输入 19 时,输出:

1
2
3
4
5
6
7
8
输入的矩阵为:
1 2 3
4 5 6
7 8 9
转置后的矩阵为:
1 4 7
2 5 8
3 6 9

6-27 编写矩阵转置函数,矩阵维数由用户输入

std::vector 存储一维展开的矩阵:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <vector>

int& at(std::vector<int>& matrix, int n, int row, int col) {
return matrix[row * n + col];
}

void transpose(std::vector<int>& matrix, int n) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
int temp = at(matrix, n, i, j);
at(matrix, n, i, j) = at(matrix, n, j, i);
at(matrix, n, j, i) = temp;
}
}
}

void printMatrix(const std::vector<int>& matrix, int n) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
std::cout << matrix[i * n + j] << ' ';
}
std::cout << '\n';
}
}

int main() {
int n;

std::cout << "请输入矩阵的维数:";
std::cin >> n;

std::vector<int> matrix(n * n);

std::cout << "输入矩阵的元素" << std::endl;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
std::cout << "第 " << i + 1 << " 行第 "
<< j + 1 << " 个元素为:";
std::cin >> matrix[i * n + j];
}
}

std::cout << "输入的矩阵为:" << std::endl;
printMatrix(matrix, n);

transpose(matrix, n);

std::cout << "转置后的矩阵为:" << std::endl;
printMatrix(matrix, n);

return 0;
}

这里用 vector 避免了手动 new[] 后忘记 delete[] 的问题。

6-28 定义 Employee 类,包含姓名、街道地址、城市和邮编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <string>

class Employee {
public:
Employee(std::string nameValue,
std::string streetValue,
std::string cityValue,
std::string zipValue)
: name(nameValue),
street(streetValue),
city(cityValue),
zip(zipValue) {}

void changeName(const std::string& newName) {
name = newName;
}

void display() const {
std::cout << name << ' '
<< street << ' '
<< city << ' '
<< zip << '\n';
}

private:
std::string name;
std::string street;
std::string city;
std::string zip;
};

int main() {
Employee employee("张三", "平安大街 3 号", "北京", "100000");

employee.display();
employee.changeName("李四");
employee.display();

return 0;
}

输出:

1
2
张三 平安大街 3 号 北京 100000
李四 平安大街 3 号 北京 100000

13、本章复盘

复盘问题 自查答案
数组下标从几开始 从 0 开始
&* 分别是什么 取地址与解引用
未初始化指针能否解引用 不能
new[] 应该配什么释放 delete[]
引用和指针最大区别是什么 引用必须绑定对象,指针可以为空且可改指向
C 风格字符串结束符是什么 '\0'
为什么 MyString 要写复制构造和赋值 防止浅拷贝导致共享内存和重复释放
真实项目优先用什么 std::vectorstd::string

下一章 Chap7 会进入继承与派生,开始把多个类组织成层次结构。