【Linux C/C++开发】第4章:数组、字符串与指针
第四章:数组、字符串与指针
学习目标
本周将深入学习C/C++中的数组、字符串、指针和内存管理,掌握数组的声明、初始化和操作,理解字符串的本质和处理方法,学会指针的使用和内存管理技巧。
核心概念
- 数组的基本概念和声明
- 一维数组和多维数组
- 数组作为函数参数
- 字符数组和字符串
- 字符串处理函数
- 动态数组(C++ vector)
- 数组排序和搜索算法
- 指针基础:定义、运算、与数组和函数的关系
- 内存管理:栈内存与堆内存、malloc/free、new/delete、内存泄漏预防
4.1 数组基础
4.1.1 数组的概念
数组是相同类型元素的集合,这些元素在内存中连续存储。数组提供了一种高效的方式来存储和处理大量相同类型的数据。
4.1.2 一维数组
基本语法
type array_name[size];
声明和初始化
// 声明数组
int numbers[10]; // 声明包含10个整数的数组
double prices[100]; // 声明包含100个双精度数的数组
char letters[26]; // 声明包含26个字符的数组
// 声明并初始化
int scores[5] = {90, 85, 78, 92, 88}; // 完全初始化
int values[10] = {1, 2, 3}; // 部分初始化,其余为0
int data[] = {10, 20, 30, 40, 50}; // 自动推断大小
// C++11统一初始化
int modern_array[5] {1, 2, 3, 4, 5}; // C++11语法
数组访问
int arr[5] = {10, 20, 30, 40, 50};
// 访问数组元素
cout << arr[0] << endl; // 输出第一个元素:10
cout << arr[4] << endl; // 输出最后一个元素:50
// 修改数组元素
arr[2] = 35; // 将第三个元素改为35
// 数组遍历
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
4.1.3 数组边界检查
C/C++不会自动检查数组边界,访问越界会导致未定义行为:
int arr[5] = {1, 2, 3, 4, 5};
// 危险:越界访问
cout << arr[5] << endl; // 越界!可能导致程序崩溃
arr[10] = 100; // 严重错误!可能覆盖其他内存
4.1.4 数组大小计算
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int size = sizeof(arr) / sizeof(arr[0]); // 计算数组元素个数
cout << "数组大小: " << size << endl; // 输出:10
4.2 多维数组
4.2.1 二维数组
二维数组可以看作是数组的数组,常用于表示矩阵、表格等数据结构。
声明和初始化
// 基本声明
int matrix[3][4]; // 3行4列的整数矩阵
// 声明并初始化
int matrix1[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 部分初始化
int matrix2[3][3] = {
{1, 2, 3},
{4, 5} // 第三行自动初始化为0
};
// 自动推断第一维大小
int matrix3[][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
二维数组访问
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 访问元素
cout << matrix[1][2] << endl; // 输出第2行第3列的元素:7
// 遍历二维数组
for (int i = 0; i < 3; i++) { // 遍历行
for (int j = 0; j < 4; j++) { // 遍历列
cout << matrix[i][j] << "\t";
}
cout << endl;
}
4.2.2 三维数组
// 三维数组声明
int cube[2][3][4]; // 2层,每层3行4列
// 初始化
int cube_data[2][2][2] = {
{
{1, 2},
{3, 4}
},
{
{5, 6},
{7, 8}
}
};
4.2.3 多维数组应用示例
// 学生成绩管理系统
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
const int STUDENTS = 3;
const int SUBJECTS = 4;
// 学生成绩表:3个学生,4门课程
double scores[STUDENTS][SUBJECTS] = {
{85.5, 92.0, 78.5, 88.0}, // 学生1
{76.0, 88.5, 91.0, 82.5}, // 学生2
{92.5, 85.0, 87.5, 90.0} // 学生3
};
string subjects[SUBJECTS] = {"数学", "英语", "物理", "化学"};
// 计算每个学生的平均分
cout << "学生成绩统计表:" << endl;
cout << setw(10) << "学生";
for (int j = 0; j < SUBJECTS; j++) {
cout << setw(8) << subjects[j];
}
cout << setw(10) << "平均分" << endl;
for (int i = 0; i < STUDENTS; i++) {
double sum = 0;
cout << setw(10) << "学生" << (i + 1);
for (int j = 0; j < SUBJECTS; j++) {
cout << setw(8) << scores[i][j];
sum += scores[i][j];
}
cout << setw(10) << (sum / SUBJECTS) << endl;
}
return 0;
}
4.3 数组作为函数参数
4.3.1 一维数组作为参数
// 函数声明:数组作为参数
void printArray(int arr[], int size);
void modifyArray(int arr[], int size);
// 函数定义
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void modifyArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // 数组元素乘以2
}
}
int main() {
int numbers[5] = {1, 2, 3, 4, 5};
cout << "原始数组: ";
printArray(numbers, 5);
modifyArray(numbers, 5);
cout << "修改后数组: ";
printArray(numbers, 5); // 数组元素已被修改
return 0;
}
4.3.2 二维数组作为参数
// 二维数组作为函数参数
void printMatrix(int matrix[][4], int rows);
double calculateAverage(int matrix[][4], int rows);
void printMatrix(int matrix[][4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
cout << matrix[i][j] << "\t";
}
cout << endl;
}
}
double calculateAverage(int matrix[][4], int rows) {
int sum = 0;
int count = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
sum += matrix[i][j];
count++;
}
}
return static_cast<double>(sum) / count;
}
4.3.3 数组排序算法
// 冒泡排序
void bubbleSort(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 selectionSort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < size; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
// 插入排序
void insertionSort(int arr[], int size) {
for (int i = 1; i < size; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
4.4 字符串基础
4.4.1 字符数组和字符串
在C语言中,字符串是以空字符’\0’结尾的字符数组。
// 字符数组声明
char str1[20]; // 声明字符数组
char str2[20] = "Hello"; // 初始化字符串
char str3[] = "World"; // 自动推断大小
char str4[20] = {'H', 'i', '\0'}; // 手动添加结束符
// 字符串长度
int length = strlen(str2); // 获取字符串长度
4.4.2 字符串输入输出
#include <iostream>
#include <cstring>
using namespace std;
int main() {
char name[50];
char message[100];
// 使用cin输入字符串(遇到空格停止)
cout << "请输入姓名: ";
cin >> name;
cout << "你好, " << name << endl;
// 使用cin.getline输入整行
cin.ignore(); // 清除输入缓冲区
cout << "请输入留言: ";
cin.getline(message, 100);
cout << "你的留言: " << message << endl;
return 0;
}
4.4.3 字符串处理函数
#include <cstring>
#include <cctype>
// 字符串复制
char source[] = "Hello World";
char destination[50];
strcpy(destination, source); // 复制字符串
// 字符串连接
char str1[50] = "Hello";
char str2[] = " World";
strcat(str1, str2); // 连接字符串
// 字符串比较
int result = strcmp("apple", "banana"); // 返回负数(apple < banana)
// 获取子字符串
char text[] = "Programming is fun";
char substring[20];
strncpy(substring, text, 11); // 复制前11个字符
substring[11] = '\0'; // 手动添加结束符
// 字符串长度
int len = strlen(text); // 获取字符串长度
4.5 C++字符串类
4.5.1 string类的基本使用
C++提供了更安全和方便的string类来处理字符串。
#include <string>
using namespace std;
// string对象声明
string str1; // 空字符串
string str2 = "Hello"; // 初始化
string str3(str2); // 拷贝构造
string str4 = str2 + " World"; // 连接字符串
// string基本操作
str1 = "C++ Programming"; // 赋值
str1 += " is fun"; // 追加
int length = str1.length(); // 获取长度
char ch = str1[0]; // 访问字符
4.5.2 string类的成员函数
#include <string>
#include <iostream>
using namespace std;
int main() {
string text = "C++ Programming";
// 获取长度
cout << "长度: " << text.length() << endl;
cout << "大小: " << text.size() << endl;
// 访问字符
cout << "第一个字符: " << text[0] << endl;
cout << "最后一个字符: " << text.back() << endl;
// 子字符串
string sub = text.substr(4, 11); // 从位置4开始,长度11
cout << "子字符串: " << sub << endl;
// 查找
size_t pos = text.find("Pro");
if (pos != string::npos) {
cout << "找到 'Pro' 在位置: " << pos << endl;
}
// 替换
text.replace(4, 11, "Language");
cout << "替换后: " << text << endl;
// 插入和删除
text.insert(0, "Welcome to ");
text.erase(8, 3); // 从位置8开始删除3个字符
cout << "最终结果: " << text << endl;
return 0;
}
4.5.3 string和C风格字符串转换
#include <string>
#include <cstring>
using namespace std;
int main() {
// C风格字符串转string
char cstr[] = "Hello World";
string cppstr = cstr; // 自动转换
// string转C风格字符串
string str = "C++ Programming";
const char* c_string = str.c_str(); // 获取C风格字符串
// 复制到字符数组
char buffer[50];
strcpy(buffer, str.c_str());
return 0;
}
4.6 数组和字符串应用
4.6.1 文本分析程序
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cctype>
using namespace std;
class TextAnalyzer {
private:
string text;
public:
TextAnalyzer(const string& input) : text(input) {}
// 计算字符数
int countCharacters() {
return text.length();
}
// 计算单词数
int countWords() {
int count = 0;
bool inWord = false;
for (char ch : text) {
if (isalpha(ch)) {
if (!inWord) {
count++;
inWord = true;
}
} else {
inWord = false;
}
}
return count;
}
// 计算句子数
int countSentences() {
int count = 0;
for (char ch : text) {
if (ch == '.' || ch == '!' || ch == '?') {
count++;
}
}
return count;
}
// 查找单词
int findWord(const string& word) {
string lowerText = text;
string lowerWord = word;
// 转换为小写
transform(lowerText.begin(), lowerText.end(), lowerText.begin(), ::tolower);
transform(lowerWord.begin(), lowerWord.end(), lowerWord.begin(), ::tolower);
int count = 0;
size_t pos = 0;
while ((pos = lowerText.find(lowerWord, pos)) != string::npos) {
// 检查是否是完整的单词
bool isWord = true;
if (pos > 0 && isalpha(lowerText[pos - 1])) {
isWord = false;
}
size_t endPos = pos + lowerWord.length();
if (endPos < lowerText.length() && isalpha(lowerText[endPos])) {
isWord = false;
}
if (isWord) {
count++;
}
pos++;
}
return count;
}
// 获取最常用的单词
string getMostFrequentWord() {
vector<string> words;
string word;
for (char ch : text) {
if (isalpha(ch)) {
word += tolower(ch);
} else if (!word.empty()) {
words.push_back(word);
word.clear();
}
}
if (!word.empty()) {
words.push_back(word);
}
if (words.empty()) return "";
// 统计词频
sort(words.begin(), words.end());
string mostFrequent = words[0];
int maxCount = 1;
int currentCount = 1;
for (size_t i = 1; i < words.size(); i++) {
if (words[i] == words[i - 1]) {
currentCount++;
} else {
if (currentCount > maxCount) {
maxCount = currentCount;
mostFrequent = words[i - 1];
}
currentCount = 1;
}
}
if (currentCount > maxCount) {
mostFrequent = words.back();
}
return mostFrequent;
}
// 显示统计信息
void displayStatistics() {
cout << "=== 文本分析统计 ===" << endl;
cout << "原文: \"" << text << "\"" << endl;
cout << "字符数: " << countCharacters() << endl;
cout << "单词数: " << countWords() << endl;
cout << "句子数: " << countSentences() << endl;
cout << "最常用的单词: " << getMostFrequentWord() << endl;
}
};
int main() {
string input;
cout << "请输入要分析的文本: ";
getline(cin, input);
TextAnalyzer analyzer(input);
analyzer.displayStatistics();
// 查找特定单词
string word;
cout << "\n请输入要查找的单词: ";
cin >> word;
int count = analyzer.findWord(word);
cout << "单词 \"" << word << "\" 出现了 " << count << " 次" << endl;
return 0;
}
4.6.2 学生成绩管理系统
#include <iostream>
#include <iomanip>
#include <string>
#include <algorithm>
using namespace std;
const int MAX_STUDENTS = 100;
const int MAX_SUBJECTS = 10;
class StudentGradeSystem {
private:
string studentNames[MAX_STUDENTS];
string subjects[MAX_SUBJECTS];
double grades[MAX_STUDENTS][MAX_SUBJECTS];
int studentCount;
int subjectCount;
public:
StudentGradeSystem() : studentCount(0), subjectCount(0) {}
// 添加科目
void addSubject(const string& subject) {
if (subjectCount < MAX_SUBJECTS) {
subjects[subjectCount++] = subject;
cout << "科目 \"" << subject << "\" 添加成功!" << endl;
} else {
cout << "科目数量已达上限!" << endl;
}
}
// 添加学生
void addStudent(const string& name) {
if (studentCount < MAX_STUDENTS) {
studentNames[studentCount] = name;
// 初始化成绩为0
for (int i = 0; i < subjectCount; i++) {
grades[studentCount][i] = 0.0;
}
studentCount++;
cout << "学生 \"" << name << "\" 添加成功!" << endl;
} else {
cout << "学生数量已达上限!" << endl;
}
}
// 录入成绩
void inputGrades() {
if (studentCount == 0 || subjectCount == 0) {
cout << "请先添加学生和科目!" << endl;
return;
}
for (int i = 0; i < studentCount; i++) {
cout << "\n为学生 \"" << studentNames[i] << "\" 录入成绩:" << endl;
for (int j = 0; j < subjectCount; j++) {
cout << subjects[j] << "成绩: ";
cin >> grades[i][j];
// 验证成绩范围
while (grades[i][j] < 0 || grades[i][j] > 100) {
cout << "成绩必须在0-100之间,请重新输入: ";
cin >> grades[i][j];
}
}
}
cout << "成绩录入完成!" << endl;
}
// 显示所有学生成绩
void displayAllGrades() {
if (studentCount == 0 || subjectCount == 0) {
cout << "没有学生或科目数据!" << endl;
return;
}
cout << "\n=== 学生成绩表 ===" << endl;
cout << setw(15) << "学生姓名";
for (int i = 0; i < subjectCount; i++) {
cout << setw(12) << subjects[i];
}
cout << setw(10) << "平均分" << endl;
cout << string(15 + subjectCount * 12 + 10, '-') << endl;
for (int i = 0; i < studentCount; i++) {
cout << setw(15) << studentNames[i];
double sum = 0;
for (int j = 0; j < subjectCount; j++) {
cout << setw(12) << fixed << setprecision(1) << grades[i][j];
sum += grades[i][j];
}
double average = sum / subjectCount;
cout << setw(10) << fixed << setprecision(1) << average << endl;
}
}
// 计算科目统计信息
void displaySubjectStatistics() {
if (subjectCount == 0) {
cout << "没有科目数据!" << endl;
return;
}
cout << "\n=== 科目统计信息 ===" << endl;
cout << setw(15) << "科目" << setw(10) << "最高分" << setw(10) << "最低分"
<< setw(10) << "平均分" << setw(10) << "及格率" << endl;
cout << string(15 + 4 * 10, '-') << endl;
for (int j = 0; j < subjectCount; j++) {
double maxGrade = grades[0][j];
double minGrade = grades[0][j];
double sum = 0;
int passCount = 0;
for (int i = 0; i < studentCount; i++) {
double grade = grades[i][j];
maxGrade = max(maxGrade, grade);
minGrade = min(minGrade, grade);
sum += grade;
if (grade >= 60) {
passCount++;
}
}
double average = sum / studentCount;
double passRate = (static_cast<double>(passCount) / studentCount) * 100;
cout << setw(15) << subjects[j]
<< setw(10) << fixed << setprecision(1) << maxGrade
<< setw(10) << fixed << setprecision(1) << minGrade
<< setw(10) << fixed << setprecision(1) << average
<< setw(10) << fixed << setprecision(1) << passRate << "%" << endl;
}
}
// 查找学生
void findStudent(const string& name) {
int index = -1;
for (int i = 0; i < studentCount; i++) {
if (studentNames[i] == name) {
index = i;
break;
}
}
if (index == -1) {
cout << "未找到学生 \"" << name << "\"" << endl;
return;
}
cout << "\n=== 学生 \"" << name << "\" 的成绩 ===" << endl;
double sum = 0;
for (int j = 0; j < subjectCount; j++) {
cout << subjects[j] << ": " << fixed << setprecision(1)
<< grades[index][j] << endl;
sum += grades[index][j];
}
double average = sum / subjectCount;
cout << "平均分: " << fixed << setprecision(1) << average << endl;
if (average >= 90) {
cout << "等级: 优秀" << endl;
} else if (average >= 80) {
cout << "等级: 良好" << endl;
} else if (average >= 70) {
cout << "等级: 中等" << endl;
} else if (average >= 60) {
cout << "等级: 及格" << endl;
} else {
cout << "等级: 不及格" << endl;
}
}
// 排序功能
void sortByAverage() {
if (studentCount == 0) {
cout << "没有学生数据!" << endl;
return;
}
// 创建临时数组存储学生索引和平均分
pair<int, double> studentAverages[MAX_STUDENTS];
for (int i = 0; i < studentCount; i++) {
double sum = 0;
for (int j = 0; j < subjectCount; j++) {
sum += grades[i][j];
}
studentAverages[i] = make_pair(i, sum / subjectCount);
}
// 按平均分排序(降序)
sort(studentAverages, studentAverages + studentCount,
[](const pair<int, double>& a, const pair<int, double>& b) {
return a.second > b.second;
});
cout << "\n=== 按平均分排序 ===" << endl;
cout << setw(5) << "排名" << setw(15) << "学生姓名" << setw(10) << "平均分" << endl;
cout << string(5 + 15 + 10, '-') << endl;
for (int i = 0; i < studentCount; i++) {
int studentIndex = studentAverages[i].first;
cout << setw(5) << (i + 1)
<< setw(15) << studentNames[studentIndex]
<< setw(10) << fixed << setprecision(1)
<< studentAverages[i].second << endl;
}
}
};
// 主函数 - 菜单系统
int main() {
StudentGradeSystem system;
int choice;
// 初始化一些数据
system.addSubject("数学");
system.addSubject("英语");
system.addSubject("物理");
system.addSubject("化学");
system.addStudent("张三");
system.addStudent("李四");
system.addStudent("王五");
do {
cout << "\n=== 学生成绩管理系统 ===" << endl;
cout << "1. 添加科目" << endl;
cout << "2. 添加学生" << endl;
cout << "3. 录入成绩" << endl;
cout << "4. 显示所有成绩" << endl;
cout << "5. 科目统计信息" << endl;
cout << "6. 查找学生" << endl;
cout << "7. 按平均分排序" << endl;
cout << "0. 退出系统" << endl;
cout << "请选择操作: ";
cin >> choice;
switch (choice) {
case 1: {
string subject;
cout << "请输入科目名称: ";
cin >> subject;
system.addSubject(subject);
break;
}
case 2: {
string name;
cout << "请输入学生姓名: ";
cin >> name;
system.addStudent(name);
break;
}
case 3:
system.inputGrades();
break;
case 4:
system.displayAllGrades();
break;
case 5:
system.displaySubjectStatistics();
break;
case 6: {
string name;
cout << "请输入学生姓名: ";
cin >> name;
system.findStudent(name);
break;
}
case 7:
system.sortByAverage();
break;
case 0:
cout << "感谢使用学生成绩管理系统!" << endl;
break;
default:
cout << "无效选择,请重新输入!" << endl;
}
} while (choice != 0);
return 0;
}
4.7 动态数组(C++ Vector)
4.7.1 vector基础
#include <vector>
#include <iostream>
using namespace std;
int main() {
// vector声明和初始化
vector<int> vec1; // 空vector
vector<int> vec2(5); // 包含5个元素,值为0
vector<int> vec3(5, 10); // 包含5个元素,值为10
vector<int> vec4{1, 2, 3, 4, 5}; // 初始化列表
// 添加元素
vec1.push_back(10);
vec1.push_back(20);
vec1.push_back(30);
// 访问元素
cout << vec1[0] << endl; // 10
cout << vec1.at(1) << endl; // 20(带边界检查)
cout << vec1.front() << endl; // 10
cout << vec1.back() << endl; // 30
// 获取大小
cout << "大小: " << vec1.size() << endl;
cout << "容量: " << vec1.capacity() << endl;
cout << "是否为空: " << vec1.empty() << endl;
// 遍历
for (int val : vec1) {
cout << val << " ";
}
cout << endl;
return 0;
}
4.7.2 vector高级操作
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
vector<int> numbers = {5, 2, 8, 1, 9, 3};
// 插入元素
numbers.insert(numbers.begin() + 2, 7); // 在位置2插入7
// 删除元素
numbers.erase(numbers.begin() + 1); // 删除位置1的元素
numbers.pop_back(); // 删除最后一个元素
// 排序
sort(numbers.begin(), numbers.end());
// 查找
auto it = find(numbers.begin(), numbers.end(), 8);
if (it != numbers.end()) {
cout << "找到元素: " << *it << endl;
}
// 反转
reverse(numbers.begin(), numbers.end());
// 遍历
for (int num : numbers) {
cout << num << " ";
}
cout << endl;
return 0;
}
4.8 常见错误和调试技巧
4.8.1 数组常见错误
// 错误1:数组越界
int arr[5] = {1, 2, 3, 4, 5};
cout << arr[5] << endl; // 越界访问!
// 错误2:未初始化数组
int data[10];
cout << data[0] << endl; // 未定义的值
// 错误3:错误的数组大小计算
void printArray(int arr[]) {
int size = sizeof(arr) / sizeof(arr[0]); // 错误!arr退化为指针
// ...
}
// 正确做法:显式传递数组大小
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
}
4.8.2 字符串常见错误
// 错误1:字符串未以\0结尾
char str[5] = {'H', 'e', 'l', 'l', 'o'}; // 缺少结束符
cout << str << endl; // 可能导致崩溃
// 错误2:字符串拷贝越界
char dest[5];
strcpy(dest, "Hello World"); // 缓冲区溢出!
// 错误3:修改字符串常量
char* ptr = "Hello";
ptr[0] = 'h'; // 未定义行为!
// 正确做法:使用字符数组
char str[] = "Hello";
str[0] = 'h'; // 正确
4.9 最佳实践
4.9.1 数组使用建议
- 始终检查数组边界:避免越界访问
- 初始化数组:确保数组元素有确定的值
- 使用常量定义数组大小:提高代码可维护性
- 传递数组时同时传递大小:避免数组退化问题
- 考虑使用vector替代原生数组:更安全、更灵活
4.9.2 字符串使用建议
- 优先使用C++ string类:比C风格字符串更安全
- 注意字符串结束符:确保C风格字符串以\0结尾
- 使用安全的字符串函数:如strncpy代替strcpy
- 避免修改字符串常量:使用字符数组存储可修改的字符串
- 注意字符编码:处理多字节字符时要特别小心
4.10 练习题目
基础练习
- 数组反转:编写函数反转整数数组
- 查找最大值:在数组中查找最大值和最小值
- 数组去重:删除数组中的重复元素
- 字符串反转:反转字符串,不使用额外数组
- 统计单词:统计字符串中的单词数量
进阶练习
- 矩阵运算:实现矩阵加法、乘法和转置
- 字符串匹配:实现简单的字符串匹配算法
- 排序算法:实现快速排序或归并排序
- 文本编辑器:实现简单的文本编辑功能
- 动态数组:实现自己的动态数组类
综合项目
- 图书管理系统:使用数组管理图书信息
- 通讯录程序:管理联系人信息
- 单词频率统计:分析文本文件中的单词频率
- 简单数据库:实现基于数组的数据存储系统
- 游戏开发:使用数组实现贪吃蛇或俄罗斯方块
4.11 总结
本周我们深入学习了数组和字符串的相关知识,包括:
- 数组的基本概念和声明方法
- 一维数组和多维数组的使用
- 数组作为函数参数的传递方式
- 字符串的本质和处理方法
- C++ string类的使用
- 动态数组vector的应用
- 实际项目中的综合应用
数组和字符串是编程中最基础也是最重要的数据结构,掌握它们的使用方法对于后续学习更复杂的数据结构和算法至关重要。接下来,我们将学习指针基础和内存管理,这将进一步加深我们对数组和内存的理解。
4.12 指针基础
4.12.1 指针的概念
指针是存储内存地址的变量。通过指针,我们可以直接访问和操作内存中的数据,这是C/C++强大功能的核心之一。
指针的定义与初始化
// 指针变量的定义
type *pointer_name;
int *ptr; // 指向int类型的指针
double *dptr; // 指向double类型的指针
char *cptr; // 指向char类型的指针
// 指针初始化
int num = 42;
int *ptr1 = # // ptr1存储num的地址
int *ptr2 = nullptr; // C++11空指针初始化
int *ptr3 = NULL; // C风格空指针初始化
指针的基本操作
int value = 100;
int *ptr = &value; // ptr指向value的地址
// 获取地址
ptr = &value; // ptr现在存储value的地址
// 解引用(访问指针指向的值)
cout << *ptr << endl; // 输出:100
*ptr = 200; // 修改value的值为200
cout << value << endl; // 输出:200
// 获取指针自身的地址
cout << &ptr << endl; // 输出ptr变量自身的地址
4.12.2 指针运算
指针可以进行算术运算,但运算单位是指向类型的大小。
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // 指向数组第一个元素
// 指针算术运算
cout << *ptr << endl; // 输出:10
cout << *(ptr + 1) << endl; // 输出:20
cout << *(ptr + 2) << endl; // 输出:30
// 指针自增运算
ptr++; // 指向下一个int元素
cout << *ptr << endl; // 输出:20
// 指针相减(计算元素个数)
int *ptr1 = &arr[1];
int *ptr2 = &arr[4];
int diff = ptr2 - ptr1; // diff = 3
cout << "元素个数: " << diff << endl;
4.12.3 指针与数组
数组名本质上是一个指向数组首元素的常量指针。
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // 等价于 int *ptr = &arr[0];
// 数组访问方式
cout << arr[2] << endl; // 数组下标访问:3
cout << *(arr + 2) << endl; // 指针运算访问:3
cout << *(ptr + 2) << endl; // 指针运算访问:3
// 指针遍历数组
for (int i = 0; i < 5; i++) {
cout << *(ptr + i) << " "; // 或者 cout << ptr[i] << " ";
}
cout << endl;
// 指针版本遍历
int *end_ptr = arr + 5;
for (int *p = arr; p < end_ptr; p++) {
cout << *p << " ";
}
cout << endl;
4.12.4 指针与函数
指针可以作为函数参数,实现按引用传递,也可以指向函数。
指针作为函数参数
// 值传递 - 不会改变原始值
void swap_by_value(int a, int b) {
int temp = a;
a = b;
b = temp;
}
// 指针传递 - 会改变原始值
void swap_by_pointer(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 使用示例
int x = 10, y = 20;
swap_by_value(x, y); // x=10, y=20(未改变)
swap_by_pointer(&x, &y); // x=20, y=10(已交换)
函数指针
// 函数指针声明
return_type (*pointer_name)(parameter_list);
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
// 函数指针
typedef int (*Operation)(int, int); // 定义函数指针类型
// 使用函数指针
Operation op = add;
cout << op(5, 3) << endl; // 输出:8
op = subtract;
cout << op(5, 3) << endl; // 输出:2
// 函数指针数组
Operation operations[] = {add, subtract, multiply};
for (int i = 0; i < 3; i++) {
cout << "结果: " << operations[i](10, 5) << endl;
}
4.12.5 空指针与野指针
空指针(Null Pointer)
空指针是不指向任何有效内存地址的指针。
// 空指针的定义
int *null_ptr1 = nullptr; // C++11推荐方式
int *null_ptr2 = NULL; // C风格方式
int *null_ptr3 = 0; // 传统方式
// 空指针检查
if (ptr == nullptr) {
cout << "指针为空,不能解引用" << endl;
} else {
cout << "指针有效,值为: " << *ptr << endl;
}
野指针(Wild Pointer)
野指针是指向未知或无效内存区域的指针,非常危险。
// ❌ 野指针示例
int *wild_ptr; // 未初始化,指向随机地址
int *deleted_ptr = new int(42);
delete deleted_ptr;
// deleted_ptr 现在是野指针,指向已释放的内存
// ✅ 避免野指针的最佳实践
int *safe_ptr = nullptr; // 初始化为空指针
delete safe_ptr; // 释放内存
safe_ptr = nullptr; // 立即设为空指针,避免野指针
4.13 内存管理
4.13.1 栈内存与堆内存
栈内存(Stack Memory)
栈内存由编译器自动管理,用于存储局部变量和函数调用信息。
void stack_example() {
int local_var = 42; // 存储在栈上
int arr[100]; // 数组存储在栈上
// 栈内存特点:
// - 自动分配和释放
// - 大小有限(通常几MB)
// - 访问速度快
// - 生命周期与作用域相关
}
// local_var 和 arr 在函数结束时自动释放
堆内存(Heap Memory)
堆内存由程序员手动管理,用于动态分配内存。
void heap_example() {
// C风格动态内存分配
int *c_style = (int*)malloc(sizeof(int) * 100);
// C++风格动态内存分配
int *cpp_style = new int[100];
// 使用内存...
// 释放内存
free(c_style); // C风格释放
delete[] cpp_style; // C++风格释放
// 堆内存特点:
// - 手动分配和释放
// - 大小几乎无限制(受系统内存限制)
// - 访问速度相对较慢
// - 生命周期由程序员控制
}
4.13.2 C风格内存管理(malloc/free)
#include <cstdlib> // 或者 #include <stdlib.h>
void c_memory_management() {
// 分配单个元素
int *single_int = (int*)malloc(sizeof(int));
if (single_int == nullptr) {
cerr << "内存分配失败" << endl;
return;
}
*single_int = 42;
// 分配数组
int *int_array = (int*)malloc(sizeof(int) * 10);
if (int_array == nullptr) {
cerr << "内存分配失败" << endl;
free(single_int); // 释放之前分配的内存
return;
}
// 初始化数组
for (int i = 0; i < 10; i++) {
int_array[i] = i * i;
}
// 重新分配内存(扩展数组)
int *new_array = (int*)realloc(int_array, sizeof(int) * 20);
if (new_array == nullptr) {
cerr << "内存重新分配失败" << endl;
free(single_int);
free(int_array);
return;
}
int_array = new_array; // 更新指针
// 使用扩展后的数组...
// 释放内存
free(single_int);
free(int_array);
}
4.13.3 C++风格内存管理(new/delete)
void cpp_memory_management() {
// 分配单个对象
int *single_int = new int(42); // 分配并初始化
string *str = new string("Hello");
// 分配对象数组
int *int_array = new int[10]; // 分配数组
string *str_array = new string[5]; // 调用默认构造函数
// 分配并初始化数组
int *initialized_array = new int[5]{1, 2, 3, 4, 5}; // C++11
// 使用内存...
for (int i = 0; i < 10; i++) {
int_array[i] = i * 2;
}
// 释放内存
delete single_int; // 删除单个对象
delete str; // 删除单个对象
delete[] int_array; // 删除数组
delete[] str_array; // 删除数组
delete[] initialized_array; // 删除数组
}
4.13.4 内存泄漏预防
内存泄漏是指分配了内存但没有释放,导致内存浪费。
常见内存泄漏场景
void memory_leak_examples() {
// 1. 忘记释放内存
int *leak1 = new int[100];
// 没有 delete[] leak1;
// 2. 指针覆盖导致内存丢失
int *leak2 = new int[50];
leak2 = new int[100]; // 之前的50个int内存丢失
delete[] leak2; // 只释放了100个int
// 3. 异常导致内存未释放
int *leak3 = new int[200];
// 如果这里抛出异常,内存将不会释放
throw runtime_error("异常");
delete[] leak3; // 永远不会执行
}
内存泄漏预防策略
// ✅ 策略1:使用智能指针(推荐)
#include <memory>
void smart_pointer_example() {
// unique_ptr:独占所有权
unique_ptr<int> smart_int(new int(42));
unique_ptr<int[]> smart_array(new int[100]);
// 自动释放,无需手动delete
// 当unique_ptr离开作用域时自动释放内存
}
// ✅ 策略2:RAII(资源获取即初始化)
class MemoryManager {
private:
int *data;
size_t size;
public:
MemoryManager(size_t s) : size(s) {
data = new int[size];
cout << "分配了 " << size << " 个int的内存" << endl;
}
~MemoryManager() {
delete[] data;
cout << "释放了 " << size << " 个int的内存" << endl;
}
// 禁用拷贝,避免重复释放
MemoryManager(const MemoryManager&) = delete;
MemoryManager& operator=(const MemoryManager&) = delete;
};
// ✅ 策略3:良好的编程习惯
void safe_memory_practices() {
int *safe_ptr = nullptr;
try {
safe_ptr = new int[1000];
// 使用内存...
delete[] safe_ptr; // 及时释放
safe_ptr = nullptr; // 避免野指针
}
catch (...) {
delete[] safe_ptr; // 异常时也要释放
safe_ptr = nullptr;
throw; // 重新抛出异常
}
}
4.13.5 内存管理最佳实践
// 综合示例:安全的内存管理
class SafeArray {
private:
int *data;
size_t size;
public:
// 构造函数
SafeArray(size_t s) : size(s) {
data = new int[size];
for (size_t i = 0; i < size; i++) {
data[i] = 0; // 初始化为0
}
cout << "SafeArray: 分配了 " << size << " 个元素" << endl;
}
// 析构函数
~SafeArray() {
delete[] data;
cout << "SafeArray: 释放了内存" << endl;
}
// 拷贝构造函数(深拷贝)
SafeArray(const SafeArray& other) : size(other.size) {
data = new int[size];
for (size_t i = 0; i < size; i++) {
data[i] = other.data[i];
}
cout << "SafeArray: 拷贝构造" << endl;
}
// 赋值运算符(深拷贝)
SafeArray& operator=(const SafeArray& other) {
if (this != &other) { // 避免自赋值
delete[] data; // 释放原有内存
size = other.size;
data = new int[size];
for (size_t i = 0; i < size; i++) {
data[i] = other.data[i];
}
cout << "SafeArray: 赋值操作" << endl;
}
return *this;
}
// 访问元素
int& operator[](size_t index) {
if (index >= size) {
throw out_of_range("数组下标越界");
}
return data[index];
}
// 获取大小
size_t getSize() const { return size; }
// 填充数组
void fill(int value) {
for (size_t i = 0; i < size; i++) {
data[i] = value;
}
}
// 打印数组
void print() const {
cout << "数组内容: ";
for (size_t i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
// 使用示例
void safe_array_demo() {
cout << "\n=== 安全数组演示 ===" << endl;
// 创建数组
SafeArray arr1(5);
arr1.fill(42);
arr1.print();
// 拷贝构造
SafeArray arr2 = arr1;
arr2[0] = 100;
arr2.print();
arr1.print(); // 原数组不受影响
// 赋值操作
SafeArray arr3(3);
arr3 = arr1;
arr3.print();
// 越界检查
try {
arr1[10] = 999; // 这将抛出异常
}
catch (const out_of_range& e) {
cout << "捕获异常: " << e.what() << endl;
}
// 自动释放内存(RAII)
cout << "离开作用域,自动释放内存..." << endl;
}
4.14 综合练习
练习1:动态数组实现
// 实现一个动态数组类,支持以下功能:
// 1. 动态调整大小
// 2. 在指定位置插入元素
// 3. 删除指定位置的元素
// 4. 查找元素
// 5. 安全的内存管理
class DynamicArray {
private:
int *data;
size_t size;
size_t capacity;
public:
DynamicArray() : data(nullptr), size(0), capacity(0) {}
~DynamicArray() { delete[] data; }
// 在末尾添加元素
void push_back(int value) {
if (size >= capacity) {
resize(capacity == 0 ? 1 : capacity * 2);
}
data[size++] = value;
}
// 获取大小
size_t getSize() const { return size; }
// 访问元素
int& operator[](size_t index) {
if (index >= size) throw out_of_range("下标越界");
return data[index];
}
// 打印数组
void print() const {
cout << "数组: [";
for (size_t i = 0; i < size; i++) {
cout << data[i];
if (i != size - 1) cout << ", ";
}
cout << "]" << endl;
}
private:
void resize(size_t new_capacity) {
int *new_data = new int[new_capacity];
for (size_t i = 0; i < size; i++) {
new_data[i] = data[i];
}
delete[] data;
data = new_data;
capacity = new_capacity;
}
};
练习2:字符串与指针综合应用
// 使用指针实现字符串处理函数
// 计算字符串长度(不使用strlen)
size_t my_strlen(const char *str) {
const char *p = str;
while (*p != '\0') {
p++;
}
return p - str;
}
// 字符串拷贝(不使用strcpy)
char* my_strcpy(char *dest, const char *src) {
char *original_dest = dest;
while ((*dest++ = *src++) != '\0');
return original_dest;
}
// 字符串连接(不使用strcat)
char* my_strcat(char *dest, const char *src) {
char *p = dest;
while (*p != '\0') p++; // 找到dest的末尾
while ((*p++ = *src++) != '\0'); // 拷贝src到dest末尾
return dest;
}
// 字符串比较(不使用strcmp)
int my_strcmp(const char *s1, const char *s2) {
while (*s1 && *s2 && *s1 == *s2) {
s1++;
s2++;
}
return *s1 - *s2;
}
// 使用示例
void string_pointer_demo() {
char str1[100] = "Hello";
char str2[100] = "World";
char result[200];
cout << "字符串长度: " << my_strlen(str1) << endl;
my_strcpy(result, str1);
cout << "拷贝结果: " << result << endl;
my_strcat(result, " ");
my_strcat(result, str2);
cout << "连接结果: " << result << endl;
cout << "比较结果: " << my_strcmp(str1, str2) << endl;
}
4.15 总结
本周我们深入学习了数组、字符串、指针和内存管理的相关知识,包括:
- 数组的基本概念、声明和使用方法
- 多维数组的应用和内存布局
- 字符串的本质和处理技巧
- C++ string类的使用
- 动态数组vector的应用
- 指针基础:定义、运算、与数组和函数的关系
- 内存管理:栈内存与堆内存、malloc/free、new/delete、内存泄漏预防
- 实际项目中的综合应用
数组、字符串和指针是C/C++编程的核心概念,掌握它们的使用方法对于理解计算机内存模型和编写高效程序至关重要。特别是指针和内存管理,它们是C/C++区别于其他高级语言的重要特征,也是理解更高级概念(如数据结构、算法优化)的基础。
在下一周中,我们将学习结构体和文件操作,这将进一步扩展我们对数据组织和持久化存储的理解。
4.16 参考资料
- 《C++ Primer》第3章:字符串、向量和数组
- 《C++程序设计语言》第7章:指针、数组和结构
- 《C++ Primer》第12章:动态内存
- C++官方文档:数组、字符串和指针相关章节
- 在线资源:cppreference.com数组、字符串和指针部分
- 《Effective C++》第3章:资源管理
更多推荐



所有评论(0)