文章目录
- 前言
- 1. 声明和初始化数组:
- a. 数组声明:
- b. 数组初始化:
- i. 静态初始化:
- ii. 动态初始化:
- c. 示例:
- 2. 元素访问和修改:
- a. 数组元素的访问:
- b. 数组元素的修改:
- c. 数组越界:
- d. 示例:
- 3. 数组与指针关系:
- a. 数组名是指针:
- b. 指针与数组元素的关系:
- c. 数组名和指针的相似性:
- d. 示例:
- 4. 数组的遍历:
- a. 循环结构:
- b. 遍历数组元素:
- c. 使用范围循环:
- d. 示例:
- 5. 多维数组:
- a. 二维数组的声明和初始化:
- b. 二维数组的访问:
- c. 三维数组:
- d. 多维数组的遍历:
- e. 指针和多维数组:
- f. 示例:
- 6. 数组与函数:
- a. 数组作为函数参数:
- b. 数组的大小推导:
- c. 返回数组或数组元素:
- d. 示例:
- 7. 标准模板库(STL):
- a. std::vector:
- b. std::array:
- c. STL算法:
- 8. 异常处理:
- a. 数组越界:
- b. 使用std::vector和迭代器:
- c. 自定义异常:
- d. 示例:
- 9. 动态数组:
- a. 动态分配内存:
- b. 动态数组的释放:
- c. 动态数组和指针:
- d. 注意事项:
- e. 示例:
- 10. 字符串(String)与字符数组(Char Array):
- a. 字符数组的声明和初始化:
- b. 字符串类 std::string:
- c. 字符数组与字符串类的转换:
- d. 常用字符串操作:
- e. 示例:
前言
深入理解C++的数组和字符串是成为熟练C++程序员的重要一步。本文将探索C++中数组和字符串的基本概念,从基础到进阶,包括数组的声明、初始化、访问和多维数组的操作,以及字符串类的使用和与字符数组的转换。还将涉及异常处理、动态内存分配、STL中的其他容器、常用字符串操作。
1. 声明和初始化数组:
a. 数组声明:
在C++中,数组是一种用于存储相同数据类型的元素序列的数据结构。数组的声明包括数据类型和数组名,形式如下:
dataType arrayName[arraySize];
其中:
dataType 表示数组中元素的数据类型,可以是整数、浮点数、字符等。
arrayName 是数组的标识符,你可以自定义数组的名字。
arraySize 表示数组的大小,即数组中元素的个数。数组的大小必须是一个常量表达式,即在编译时就能确定的值。
b. 数组初始化:
数组可以在声明时进行初始化,有两种主要的初始化方式:
i. 静态初始化:
在声明数组的同时为其赋初值,用花括号 {} 括起来,并按顺序提供初始值给每个元素。如果提供的初始值不足以填满整个数组,未提供初始值的元素将被自动初始化为零(对于数字类型)或空字符(对于字符类型)。
int staticArray[5] = {1, 2, 3, 4, 5};
ii. 动态初始化:
在声明数组后,使用循环为每个元素分别赋值。
int dynamicArray[5];
for (int i = 0; i < 5; ++i) {
dynamicArray[i] = i + 1;
}
c. 示例:
下面是一个完整的示例,演示了如何声明和初始化数组:
#include <iostream>
int main() {
// 静态初始化
int staticArray[5] = {1, 2, 3, 4, 5};
// 动态初始化
int dynamicArray[5];
for (int i = 0; i < 5; ++i) {
dynamicArray[i] = i + 1;
}
// 打印数组元素
std::cout << "Static Array: ";
for (int i = 0; i < 5; ++i) {
std::cout << staticArray[i] << " ";
}
std::cout << "\nDynamic Array: ";
for (int i = 0; i < 5; ++i) {
std::cout << dynamicArray[i] << " ";
}
return 0;
}
2. 元素访问和修改:
a. 数组元素的访问:
数组的元素通过索引访问,索引从0开始,依次递增。通过使用数组名和方括号 [] 操作符,可以访问特定索引位置的元素。
int myArray[5] = {10, 20, 30, 40, 50};
// 访问数组元素
int elementAtIndex2 = myArray[2]; // 获取索引为2的元素,值为30
b. 数组元素的修改:
通过数组名、方括号 [] 操作符和赋值操作,可以修改数组特定索引位置的元素的值。
int myArray[5] = {10, 20, 30, 40, 50};
// 修改数组元素
myArray[2] = 35; // 将索引为2的元素的值修改为35
c. 数组越界:
数组越界是一种常见的错误,它指的是尝试访问或修改数组中不存在的索引位置。这可能导致程序崩溃或产生不可预测的行为。在访问数组元素之前,应确保索引在合法范围内。
int myArray[5] = {10, 20, 30, 40, 50};
// 错误的数组越界访问
int value = myArray[10]; // 这是不安全的,会导致未定义的行为
d. 示例:
以下是一个示例,演示了如何访问和修改数组的元素:
#include <iostream>
int main() {
int myArray[5] = {10, 20, 30, 40, 50};
// 访问数组元素
std::cout << "Element at index 2: " << myArray[2] << std::endl;
// 修改数组元素
myArray[3] = 45;
std::cout << "Modified element at index 3: " << myArray[3] << std::endl;
// 错误的数组越界访问
// int value = myArray[10]; // 这会导致未定义的行为
return 0;
}
3. 数组与指针关系:
a. 数组名是指针:
在C++中,数组名实际上是指向数组首元素的指针。这意味着可以使用指针的概念来操作数组。例如,考虑以下声明:
int myArray[5] = {10, 20, 30, 40, 50};
在这里,myArray 实际上是指向第一个元素 myArray[0] 的指针。可以通过使用 * 操作符解引用这个指针,获取其指向的值。
int firstElement = *myArray; // 获取数组的第一个元素的值
b. 指针与数组元素的关系:
可以使用指针变量来访问数组的元素,通过指针的递增来移动到数组中的下一个元素。这是因为数组元素在内存中是依次存储的。
int myArray[5] = {10, 20, 30, 40, 50};
int *ptr = myArray; // 数组名是指向数组首元素的指针
// 使用指针访问数组元素
int firstElement = *ptr; // 获取数组的第一个元素的值
int secondElement = *(ptr + 1); // 获取数组的第二个元素的值
c. 数组名和指针的相似性:
数组名和指针之间的相似性表现在以下几个方面:
数组名可以像指针一样进行算术运算,例如 array + 1 表示下一个元素的地址。
数组名和指针都可以用于函数参数传递,使得函数能够接收数组作为参数。
数组名可以通过 & 运算符取地址,得到指向整个数组的指针。
d. 示例:
以下是一个示例,演示了数组名和指针之间的关系:
#include <iostream>
int main() {
int myArray[5] = {10, 20, 30, 40, 50};
int *ptr = myArray; // 数组名是指向数组首元素的指针
// 使用指针访问数组元素
std::cout << "First element: " << *ptr << std::endl;
std::cout << "Second element: " << *(ptr + 1) << std::endl;
// 数组名进行算术运算
int *nextElement = myArray + 2;
std::cout << "Third element: " << *nextElement << std::endl;
return 0;
}
4. 数组的遍历:
a. 循环结构:
使用循环结构,通常是 for 循环,是一种有效的方式来遍历数组的所有元素。通过循环,可以访问数组中的每个元素,进行相应的操作。
b. 遍历数组元素:
int myArray[5] = {10, 20, 30, 40, 50};
// 使用循环遍历数组元素
for (int i = 0; i < 5; ++i) {
std::cout << myArray[i] << " ";
}
在这个例子中,for 循环从数组的第一个元素(索引0)迭代到最后一个元素(索引4),输出每个元素的值。
c. 使用范围循环:
C++11 引入了范围循环(range-based for loop),使得遍历数组更加简洁。它的语法如下:
for (int element : myArray) {
std::cout << element << " ";
}
这种写法更加直观,遍历数组中的每个元素并执行相应操作。
d. 示例:
以下是一个完整的示例,演示了如何使用循环遍历数组:
#include <iostream>
int main() {
int myArray[5] = {10, 20, 30, 40, 50};
// 使用for循环遍历数组元素
std::cout << "Using for loop: ";
for (int i = 0; i < 5; ++i) {
std::cout << myArray[i] << " ";
}
std::cout << std::endl;
// 使用范围循环遍历数组元素
std::cout << "Using range-based for loop: ";
for (int element : myArray) {
std::cout << element << " ";
}
std::cout << std::endl;
return 0;
}
5. 多维数组:
a. 二维数组的声明和初始化:
二维数组是数组的数组,可以通过以下方式声明和初始化:
int twoDArray[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
在这个例子中,twoDArray 是一个3行4列的二维数组,可以通过两个索引(行和列)来访问其中的元素。
b. 二维数组的访问:
通过两个索引可以访问二维数组中的元素:
int element = twoDArray[1][2]; // 获取第2行第3列的元素,值为7
c. 三维数组:
可以声明和初始化三维数组:
int threeDArray[2][3][4] = {
{{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}},
{{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}}
};
d. 多维数组的遍历:
遍历多维数组需要使用嵌套循环,每个循环负责一个维度:
// 遍历二维数组
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
std::cout << twoDArray[i][j] << " ";
}
std::cout << std::endl;
}
e. 指针和多维数组:
多维数组的每一行在内存中是连续存储的,因此可以使用指针来遍历:
int (*ptr)[4] = twoDArray; // 指向包含4个整数的数组的指针
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
std::cout << ptr[i][j] << " ";
}
std::cout << std::endl;
}
这里的 ptr 是一个指向包含4个整数的数组的指针。
f. 示例:
以下是一个完整的示例,演示了二维数组的声明、初始化、访问和遍历:
#include <iostream>
int main() {
int twoDArray[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 遍历二维数组
std::cout << "Two-Dimensional Array:" << std::endl;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
std::cout << twoDArray[i][j] << " ";
}
std::cout << std::endl;
}
return 0;
}
6. 数组与函数:
a. 数组作为函数参数:
可以将数组作为函数的参数传递,以便在函数内部对数组进行操作。传递数组时通常需要传递数组的大小,或者使用特殊的标记表示数组的末尾。
// 数组作为函数参数
void printArray(int arr[], int size) {
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
// 调用函数,将数组作为参数传递
printArray(myArray, 5);
return 0;
}
b. 数组的大小推导:
可以使用模板来实现函数,使其可以接受不同大小的数组,从而避免显式传递数组大小。
// 模板函数,推导数组大小
template <size_t SIZE>
void printArrayTemplate(int (&arr)[SIZE]) {
for (size_t i = 0; i < SIZE; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
// 调用模板函数,推导数组大小
printArrayTemplate(myArray);
return 0;
}
c. 返回数组或数组元素:
可以在函数中返回数组或数组元素。需要注意的是,C++中直接返回数组的语法比较复杂,通常建议返回指向数组的指针或使用STL容器。
// 返回数组元素
int getElement(int arr[], int index) {
return arr[index];
}
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
// 调用函数,返回数组元素
int element = getElement(myArray, 2);
std::cout << "Element at index 2: " << element << std::endl;
return 0;
}
d. 示例:
以下是一个完整的示例,演示了数组作为函数参数的传递和在函数中返回数组元素:
#include <iostream>
// 数组作为函数参数
void printArray(int arr[], int size) {
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
// 返回数组元素
int getElement(int arr[], int index) {
return arr[index];
}
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
// 调用函数,将数组作为参数传递
std::cout << "Array as function parameter: ";
printArray(myArray, 5);
// 调用函数,返回数组元素
int element = getElement(myArray, 2);
std::cout << "Element at index 2: " << element << std::endl;
return 0;
}
7. 标准模板库(STL):
a. std::vector:
std::vector 是一个动态数组,可以在运行时动态改变其大小。它提供了许多方便的方法来操作元素,如添加、删除、访问等。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {5, 2, 8, 1, 6};
// 遍历vector
for (int element : vec) {
std::cout << element << " ";
}
std::cout << std::endl;
// 添加元素
vec.push_back(10);
// 使用迭代器遍历vector
std::cout << "After adding an element: ";
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
b. std::array:
std::array 是一个固定大小的数组,与传统数组相似但提供了更多的功能和安全性。
#include <array>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// 遍历array
for (int element : arr) {
std::cout << element << " ";
}
std::cout << std::endl;
// 访问元素
int value = arr[2];
std::cout << "Element at index 2: " << value << std::endl;
return 0;
}
c. STL算法:
STL提供了许多算法,可以直接应用于数组、向量等容器。以下是一些示例:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {5, 2, 8, 1, 6};
// 使用STL算法对vector进行排序
std::sort(vec.begin(), vec.end());
// 使用STL算法查找元素
int target = 6;
auto result = std::find(vec.begin(), vec.end(), target);
if (result != vec.end()) {
std::cout << "Element found at position: " << std::distance(vec.begin(), result) << std::endl;
} else {
std::cout << "Element not found" << std::endl;
}
return 0;
}
8. 异常处理:
a. 数组越界:
数组越界是指尝试访问数组中不存在的索引位置。这可能导致未定义的行为,例如访问不属于数组的内存区域,可能导致程序崩溃。
#include <stdexcept>
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
try {
// 错误的数组越界访问
int value = myArray[10]; // 这会导致未定义的行为
std::cout << "Value at index 10: " << value << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
b. 使用std::vector和迭代器:
使用std::vector和迭代器可以提高代码的安全性,因为它们提供了动态调整大小的能力,且迭代器会自动确保不越界。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
try {
// 安全的访问vector元素
int value = vec.at(10); // 这会抛出std::out_of_range异常
std::cout << "Value at index 10: " << value << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
c. 自定义异常:
有时,可以根据具体的应用场景自定义异常类,以便更好地捕获和处理特定类型的错误。
#include <iostream>
#include <stdexcept>
class ArrayIndexException : public std::runtime_error {
public:
ArrayIndexException(const std::string& message)
: std::runtime_error(message) {}
};
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
try {
// 错误的数组越界访问
int index = 10;
if (index < 0 || index >= 5) {
throw ArrayIndexException("Array index out of bounds");
}
int value = myArray[index];
std::cout << "Value at index " << index << ": " << value << std::endl;
} catch (const ArrayIndexException& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
d. 示例:
以下是一个完整的示例,演示了如何处理数组越界异常:
#include <iostream>
#include <stdexcept>
class ArrayIndexException : public std::runtime_error {
public:
ArrayIndexException(const std::string& message)
: std::runtime_error(message) {}
};
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
try {
// 错误的数组越界访问
int index = 10;
if (index < 0 || index >= 5) {
throw ArrayIndexException("Array index out of bounds");
}
int value = myArray[index];
std::cout << "Value at index " << index << ": " << value << std::endl;
} catch (const ArrayIndexException& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
9. 动态数组:
a. 动态分配内存:
在C++中,可以使用new关键字动态分配内存,用于创建动态数组。动态分配的数组的大小可以在运行时确定。
#include <iostream>
int main() {
// 动态分配一个整数数组
int* dynamicArray = new int[5];
// 初始化动态数组
for (int i = 0; i < 5; ++i) {
dynamicArray[i] = i + 1;
}
// 使用动态数组
for (int i = 0; i < 5; ++i) {
std::cout << dynamicArray[i] << " ";
}
std::cout << std::endl;
// 释放动态分配的内存
delete[] dynamicArray;
return 0;
}
b. 动态数组的释放:
动态分配的数组在使用完毕后,应使用delete[]关键字释放相应的内存,以防止内存泄漏。
int* dynamicArray = new int[5];
// 使用动态数组
// 释放动态分配的内存
delete[] dynamicArray;
c. 动态数组和指针:
动态数组是通过指针来管理的,因此使用指针的概念来访问和操作动态数组。
int* dynamicArray = new int[5];
// 使用指针访问动态数组元素
int value = dynamicArray[2];
// 释放动态分配的内存
delete[] dynamicArray;
d. 注意事项:
必须使用 delete[] 来释放通过 new[] 分配的数组内存。
确保在使用完动态数组后释放内存,以防止内存泄漏。
避免采用不安全的指针操作,以免导致悬挂指针等问题。
e. 示例:
以下是一个完整的示例,演示了如何动态分配和释放内存以创建动态数组:
#include <iostream>
int main() {
// 动态分配一个整数数组
int* dynamicArray = new int[5];
// 初始化动态数组
for (int i = 0; i < 5; ++i) {
dynamicArray[i] = i + 1;
}
// 使用动态数组
std::cout << "Dynamic Array: ";
for (int i = 0; i < 5; ++i) {
std::cout << dynamicArray[i] << " ";
}
std::cout << std::endl;
// 释放动态分配的内存
delete[] dynamicArray;
return 0;
}
10. 字符串(String)与字符数组(Char Array):
a. 字符数组的声明和初始化:
字符数组是一种存储字符序列的数据结构,以null字符 \0 结尾。
char charArray[] = "Hello";
b. 字符串类 std::string:
std::string 是C++标准库中提供的字符串类,它提供了许多方便的方法来处理字符串。
#include <iostream>
#include <string>
int main() {
// 使用字符串类
std::string str = "Hello, C++";
// 输出字符串
std::cout << "String: " << str << std::endl;
// 获取字符串长度
std::cout << "Length: " << str.length() << std::endl;
// 字符串连接
std::string newStr = str + " Programming";
std::cout << "Concatenated String: " << newStr << std::endl;
return 0;
}
c. 字符数组与字符串类的转换:
可以使用字符串类的成员函数 c_str() 将字符串类转换为字符数组。
#include <iostream>
#include <string>
int main() {
// 字符数组转字符串类
char charArray[] = "Hello, C++";
std::string strFromCharArray(charArray);
// 输出字符串类
std::cout << "String from Char Array: " << strFromCharArray << std::endl;
// 字符串类转字符数组
const char* charPtr = strFromCharArray.c_str();
// 输出字符数组
std::cout << "Char Array from String: " << charPtr << std::endl;
return 0;
}
d. 常用字符串操作:
#include <iostream>
#include <string>
int main() {
std::string str = "Hello, C++";
// 获取字符串长度
std::cout << "Length: " << str.length() << std::endl;
// 字符串比较
std::string otherStr = "Hello, C++";
if (str == otherStr) {
std::cout << "Strings are equal" << std::endl;
} else {
std::cout << "Strings are not equal" << std::endl;
}
// 查找子串
size_t pos = str.find("C++");
if (pos != std::string::npos) {
std::cout << "Found at position: " << pos << std::endl;
} else {
std::cout << "Substring not found" << std::endl;
}
return 0;
}
e. 示例:
以下是一个完整的示例,演示了字符数组和字符串类的基本操作:
#include <iostream>
#include <string>
int main() {
// 字符数组转字符串类
char charArray[] = "Hello, C++";
std::string strFromCharArray(charArray);
// 输出字符串类
std::cout << "String from Char Array: " << strFromCharArray << std::endl;
// 字符串类转字符数组
const char* charPtr = strFromCharArray.c_str();
// 输出字符数组
std::cout << "Char Array from String: " << charPtr << std::endl;
// 使用字符串类进行操作
std::string str = "Hello, C++";
std::cout << "Length: " << str.length() << std::endl;
std::string otherStr = "Hello, C++";
if (str == otherStr) {
std::cout << "Strings are equal" << std::endl;
} else {
std::cout << "Strings are not equal" << std::endl;
}
size_t pos = str.find("C++");
if (pos != std::string::npos) {
std::cout << "Found at position: " << pos << std::endl;
} else {
std::cout << "Substring not found" << std::endl;
}
return 0;
}