从零到一学习C++(基础篇) 作者:羡鱼肘子
温馨提示1:本篇是记录我的学习经历,会有不少片面的认知,万分期待您的指正。
温馨提示2:本篇会尽量避免一些术语,尽量用更加通俗的语言介绍c++的基础,但术语也是很重要的。
温馨提示3:看本篇前可以先了解前篇的内容,知识体系会更加完整哦。
数组
数组(Array)是编程中最基础的数据结构之一,用于存储一组相同类型的元素。它在内存中是连续存储的,通过索引快速访问。
一、 数组的基本概念
-
定义:数组是连续内存空间中存储的相同数据类型元素的集合。
-
特点:
-
元素类型相同,内存连续。
-
大小在声明时确定,不可动态改变(静态数组)。
-
通过下标(索引)访问元素,下标从
0
开始。
-
二、数组的声明与初始化
1. 声明语法
数据类型 数组名[常量表达式]; // 常量表达式指定数组长度
示例:
int arr[5]; // 声明一个包含5个int元素的数组
float scores[10]; // 声明10个float元素的数组
2. 初始化方式
-
完全初始化:显式指定所有元素。
int arr[3] = {1, 2, 3}; // arr[0]=1, arr[1]=2, arr[2]=3
-
部分初始化:未显式初始化的元素为默认值(如
int
默认0)。int arr[5] = {1, 2}; // arr[0]=1, arr[1]=2, 其余为0
-
自动推断长度:省略大小,编译器根据初始化列表确定长度。
int arr[] = {4, 5, 6}; // 数组长度自动推断为3
3.字符数组
-
大小:数组能存储的字符数量(包括末尾的
\0
)。 -
示例:
char name[10]; // 声明一个可以存储9个字符+1个'\0'的数组
-
直接赋值:
char name[] = "Tom"; // 编译器会自动加上'\0'
等价于:
char name[] = {'T', 'o', 'm', '\0'};
-
逐个字符赋值:
char name[4]; name[0] = 'T'; name[1] = 'o'; name[2] = 'm'; name[3] = '\0'; // 必须手动添加'\0'
- 温馨提示:数组不能拷贝和赋值
4.如何实现数组的拷贝
虽然数组不能直接拷贝或赋值,但可以通过以下方式实现数组内容的复制:
1. 使用循环逐个复制
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
for (int i = 0; i < 5; i++) {
arr2[i] = arr1[i]; // 逐个复制
}
2. 使用 memcpy
函数
-
memcpy
是C标准库中的函数,用于复制内存块。 -
需要包含头文件
<cstring>
。
#include <cstring>
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
memcpy(arr2, arr1, sizeof(arr1)); // 复制arr1的内容到arr2
3. 使用 std::copy
函数
-
C++标准库提供了
std::copy
,可以更安全地复制数组。 -
需要包含头文件
<algorithm>
。
#include <algorithm>
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
std::copy(arr1, arr1 + 5, arr2); // 复制arr1的内容到arr2
5.为什么 std::array
可以拷贝和赋值?
C++11 引入了 std::array
,它是一个封装了固定大小数组的类模板。与普通数组不同,std::array
支持拷贝和赋值,因为它是一个对象,而不是一个指针。
示例:
#include <array>
std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
std::array<int, 5> arr2;
arr2 = arr1; // 正确!std::array 支持赋值
6.如何去理解复杂的数组声明
在C++中,复杂的数组声明可能会让人感到困惑,尤其是当数组与指针、函数、多维数组等结合时。我们可以使用一种系统化的方法来解析它们。以下是一些技巧和步骤,帮助理解复杂的数组声明。
基本原则
-
从内到外:从变量名开始,逐步向外解析。
-
优先级规则:
-
括号
()
优先级最高。 -
数组
[]
和函数()
的优先级高于指针*
。
-
-
结合性:数组和函数是左结合的(从左到右解析),指针是右结合的(从右到左解析)。
解析复杂声明的步骤
-
找到变量名:从变量名开始,这是声明的核心。
-
解析右侧:先看变量名右侧的符号(如
[]
或()
)。 -
解析左侧:再看变量名左侧的符号(如
*
或类型)。 -
递归解析:如果遇到括号,递归解析括号内的内容。
看几个栗子
1. 简单数组
int arr[10];
-
解析:
-
arr
是一个数组,大小为10,元素类型是int
。
-
-
含义:
arr
是一个包含10个整数的数组。
2. 指针数组
int* arr[10];
-
解析:
-
arr
是一个数组,大小为10。 -
数组的元素类型是
int*
(指向整数的指针)。
-
-
含义:
arr
是一个包含10个指向整数的指针的数组。
3. 数组指针
int (*arr)[10];
-
解析:
-
arr
是一个指针。 -
指针指向的类型是
int[10]
(包含10个整数的数组)。
-
-
含义:
arr
是一个指向包含10个整数的数组的指针。
4. 函数指针数组
int (*funcArr[10])(int, int);
-
解析:
-
funcArr
是一个数组,大小为10。 -
数组的元素类型是
int (*)(int, int)
(指向函数的指针)。 -
函数指针指向的函数接受两个
int
参数,返回int
。
-
-
含义:
funcArr
是一个包含10个函数指针的数组,每个函数指针指向一个接受两个int
参数并返回int
的函数。
5. 多维数组
int arr[3][4];
-
解析:
-
arr
是一个数组,大小为3。 -
数组的元素类型是
int[4]
(包含4个整数的数组)。
-
-
含义:
arr
是一个3行4列的二维数组。
6. 指向多维数组的指针
int (*arr)[3][4];
-
解析:
-
arr
是一个指针。 -
指针指向的类型是
int[3][4]
(3行4列的二维数组)。
-
-
含义:
arr
是一个指向3行4列的二维数组的指针。
7. 返回数组指针的函数
int (*func(int))[10];
-
解析:
-
func
是一个函数,接受一个int
参数。 -
函数的返回类型是
int (*)[10]
(指向包含10个整数的数组的指针)。
-
-
含义:
func
是一个函数,接受一个int
参数,返回一个指向包含10个整数的数组的指针。
7.使用 typedef
简化复杂声明
为了简化复杂的声明,可以使用 typedef
定义类型别名。
示例:
typedef int (*FuncPtr)(int, int); // 定义函数指针类型
FuncPtr funcArr[10]; // 声明函数指针数组
-
含义:
funcArr
是一个包含10个函数指针的数组,每个函数指针指向一个接受两个int
参数并返回int
的函数。
三、访问数组元素
在C++中,访问数组元素是通过下标(索引)来实现的。数组的下标从
0
开始,到
数组长度 - 1
结束。
1.访问数组元素的基本语法
数组名[下标];
-
下标:必须是整数类型(如
int
),表示要访问的元素位置。 -
范围:下标从
0
开始,最大为数组长度 - 1
。
2.一维数组的访问
1. 示例代码
int arr[5] = {10, 20, 30, 40, 50};
// 访问数组元素
cout << arr[0] << endl; // 输出 10(第一个元素)
cout << arr[2] << endl; // 输出 30(第三个元素)
arr[3] = 100; // 修改第四个元素为 100
cout << arr[3] << endl; // 输出 100
2. 遍历数组
使用循环遍历数组中的所有元素:
for (int i = 0; i < 5; i++) {
cout << arr[i] << " "; // 依次输出 10 20 30 100 50
}
3.多维数组的访问
多维数组(如二维数组)的访问需要多个下标。
1. 二维数组示例
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 访问二维数组元素
cout << matrix[0][1] << endl; // 输出 2(第一行第二列)
matrix[1][2] = 10; // 修改第二行第三列的元素为 10
cout << matrix[1][2] << endl; // 输出 10
2. 遍历二维数组
使用嵌套循环遍历二维数组:
for (int i = 0; i < 2; i++) { // 遍历行
for (int j = 0; j < 3; j++) { // 遍历列
cout << matrix[i][j] << " ";
}
cout << endl; // 换行
}
输出:
1 2 3
4 5 10
4.字符数组的访问
字符数组(C风格字符串)的访问方式与普通数组类似,但需要注意字符串以 \0
结尾。
1. 示例代码
char name[] = "Hello";
// 访问字符数组元素
cout << name[0] << endl; // 输出 'H'
name[1] = 'a'; // 修改第二个字符为 'a'
cout << name << endl; // 输出 "Hallo"
2. 遍历字符数组
使用循环遍历字符数组:
for (int i = 0; name[i] != '\0'; i++) {
cout << name[i] << " "; // 依次输出 H a l l o
}
5.动态数组的访问
动态数组(通过 new
分配内存)的访问方式与普通数组相同。
1. 示例代码
int* arr = new int[5]{10, 20, 30, 40, 50};
// 访问动态数组元素
cout << arr[2] << endl; // 输出 30
arr[3] = 100; // 修改第四个元素为 100
cout << arr[3] << endl; // 输出 100
delete[] arr; // 释放内存
2. 遍历动态数组
for (int i = 0; i < 5; i++) {
cout << arr[i] << " "; // 依次输出 10 20 30 100 50
}
6.注意事项
-
下标越界:
访问超出数组范围的下标会导致未定义行为(如程序崩溃或数据损坏)。-
错误示例:
int arr[3] = {1, 2, 3}; cout << arr[3]; // 错误!下标越界
-
-
字符数组的
字符数组的末尾必须有 ‘\0
:\0'
,否则可能导致访问错误。 -
动态数组的内存管理:
动态数组使用后必须手动释放内存(delete[]
),否则会导致内存泄漏。
7.小结
-
访问数组元素的基本语法是
数组名[下标]
。 -
一维数组、多维数组、字符数组和动态数组的访问方式类似。
-
注意下标越界问题,确保访问的下标在合法范围内。
四、数组和指针的关系
1.数组名是指针
在C++中,数组名实际上是一个指向数组第一个元素的指针。也就是说,数组名存储的是数组首元素的地址。
示例
int arr[5] = {10, 20, 30, 40, 50};
-
arr
是一个数组名,它的类型是int*
(指向int
的指针)。 -
arr
的值是&arr[0]
,即数组第一个元素的地址。
2.通过指针访问数组元素
既然数组名是指针,我们可以通过指针的方式来访问数组元素。
1. 使用数组下标访问
cout << arr[2]; // 输出30
-
arr[2]
等价于*(arr + 2)
。
2. 使用指针算术访问
cout << *(arr + 2); // 输出30
-
arr + 2
:指针向后移动2个位置,指向arr[2]
。 -
*(arr + 2)
:访问指针指向的值。
3.指针和数组的等价性
在某些情况下,指针和数组可以互换使用。
1. 数组名作为指针
int* p = arr; // p 指向数组的第一个元素
cout << p[1]; // 输出20
cout << *(p + 1); // 输出20
2. 指针作为数组名
int* p = new int[5]{1, 2, 3, 4, 5}; // 动态数组
cout << p[2]; // 输出3
delete[] p; // 释放内存
4.数组名和指针的区别
虽然数组名和指针有很多相似之处,但它们也有一些关键区别。
1. 数组名是常量指针
-
数组名是一个常量指针,不能修改它的值。
int arr[5] = {1, 2, 3, 4, 5}; arr = arr + 1; // 错误!数组名不能修改
2. 指针是变量
-
指针是一个变量,可以修改它的值。
int* p = arr; p = p + 1; // 正确!指针可以修改
3. sizeof
的行为不同
-
对数组名使用
sizeof
,返回整个数组的大小。 -
对指针使用
sizeof
,返回指针本身的大小。int arr[5] = {1, 2, 3, 4, 5}; int* p = arr; cout << sizeof(arr); // 输出20(5个int,每个4字节) cout << sizeof(p); // 输出8(指针的大小,64位系统)int arr[5] = {1, 2, 3, 4, 5}; int* p = arr; cout << sizeof(arr); // 输出20(5个int,每个4字节) cout << sizeof(p); // 输出8(指针的大小,64位系统)
5.指针和数组在函数中的使用
1. 传递数组给函数
数组名是指针,传递数组给函数时,实际传递的是指针。
void printArray(int* arr, int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printArray(arr, 5); // 传递数组名(指针)
}
2. 返回指针的函数
函数可以返回指针,但要注意指针指向的内存是否有效。
int* getPointer() {
static int num = 42; // 静态变量,内存不会释放
return #
}
int main() {
int* p = getPointer();
cout << *p; // 输出42
}
下一篇:我会学习标准库类型:string,让我们一起加油!!!