从零到一学习C++(基础篇) 作者:羡鱼肘子
温馨提示1:本篇是记录我的学习经历,会有不少片面的认知,万分期待您的指正。
温馨提示2:本篇会尽量避免一些术语,尽量用更加通俗的语言介绍c++的基础,但术语也是很重要的。
温馨提示3:看本篇前可以先了解前篇的内容,知识体系会更加完整哦。
从零到一学习c++(基础篇--筑基期四-auto、decltype)-CSDN博客
数组
数组(Array)是编程中最基础的数据结构之一,用于存储一组相同类型的元素。它在内存中是连续存储的,通过索引快速访问。
定义:数组是连续内存空间中存储的相同数据类型元素的集合。
特点:
元素类型相同,内存连续。
大小在声明时确定,不可动态改变(静态数组)。
通过下标(索引)访问元素,下标从 0
开始。
数据类型 数组名[常量表达式]; // 常量表达式指定数组长度
示例:
int arr[5]; // 声明一个包含5个int元素的数组
float scores[10]; // 声明10个float元素的数组
完全初始化:显式指定所有元素。
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
大小:数组能存储的字符数量(包括末尾的 \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'
虽然数组不能直接拷贝或赋值,但可以通过以下方式实现数组内容的复制:
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
for (int i = 0; i < 5; i++) {
arr2[i] = arr1[i]; // 逐个复制
}
memcpy
函数memcpy
是C标准库中的函数,用于复制内存块。
需要包含头文件
。
#include
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
memcpy(arr2, arr1, sizeof(arr1)); // 复制arr1的内容到arr2
std::copy
函数C++标准库提供了 std::copy
,可以更安全地复制数组。
需要包含头文件
。
#include
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
std::copy(arr1, arr1 + 5, arr2); // 复制arr1的内容到arr2
std::array
可以拷贝和赋值?C++11 引入了 std::array
,它是一个封装了固定大小数组的类模板。与普通数组不同,std::array
支持拷贝和赋值,因为它是一个对象,而不是一个指针。
示例:
#include
std::array arr1 = {1, 2, 3, 4, 5};
std::array arr2;
arr2 = arr1; // 正确!std::array 支持赋值
在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个整数的数组的指针。
typedef
简化复杂声明为了简化复杂的声明,可以使用 typedef
定义类型别名。
示例:
typedef int (*FuncPtr)(int, int); // 定义函数指针类型
FuncPtr funcArr[10]; // 声明函数指针数组
含义:funcArr
是一个包含10个函数指针的数组,每个函数指针指向一个接受两个 int
参数并返回 int
的函数。
在C++中,访问数组元素是通过下标(索引)来实现的。数组的下标从
0
开始,到
数组长度 - 1
结束。
数组名[下标];
下标:必须是整数类型(如 int
),表示要访问的元素位置。
范围:下标从 0
开始,最大为 数组长度 - 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
使用循环遍历数组中的所有元素:
for (int i = 0; i < 5; i++) {
cout << arr[i] << " "; // 依次输出 10 20 30 100 50
}
多维数组(如二维数组)的访问需要多个下标。
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
使用嵌套循环遍历二维数组:
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
字符数组(C风格字符串)的访问方式与普通数组类似,但需要注意字符串以 \0
结尾。
char name[] = "Hello";
// 访问字符数组元素
cout << name[0] << endl; // 输出 'H'
name[1] = 'a'; // 修改第二个字符为 'a'
cout << name << endl; // 输出 "Hallo"
使用循环遍历字符数组:
for (int i = 0; name[i] != '\0'; i++) {
cout << name[i] << " "; // 依次输出 H a l l o
}
动态数组(通过 new
分配内存)的访问方式与普通数组相同。
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; // 释放内存
for (int i = 0; i < 5; i++) {
cout << arr[i] << " "; // 依次输出 10 20 30 100 50
}
错误示例:
int arr[3] = {1, 2, 3};
cout << arr[3]; // 错误!下标越界
\0
:\0'
,否则可能导致访问错误。delete[]
),否则会导致内存泄漏。访问数组元素的基本语法是 数组名[下标]
。
一维数组、多维数组、字符数组和动态数组的访问方式类似。
注意下标越界问题,确保访问的下标在合法范围内。
在C++中,数组名实际上是一个指向数组第一个元素的指针。也就是说,数组名存储的是数组首元素的地址。
int arr[5] = {10, 20, 30, 40, 50};
arr
是一个数组名,它的类型是 int*
(指向 int
的指针)。
arr
的值是 &arr[0]
,即数组第一个元素的地址。
既然数组名是指针,我们可以通过指针的方式来访问数组元素。
cout << arr[2]; // 输出30
arr[2]
等价于 *(arr + 2)
。
cout << *(arr + 2); // 输出30
arr + 2
:指针向后移动2个位置,指向 arr[2]
。
*(arr + 2)
:访问指针指向的值。
在某些情况下,指针和数组可以互换使用。
int* p = arr; // p 指向数组的第一个元素
cout << p[1]; // 输出20
cout << *(p + 1); // 输出20
int* p = new int[5]{1, 2, 3, 4, 5}; // 动态数组
cout << p[2]; // 输出3
delete[] p; // 释放内存
虽然数组名和指针有很多相似之处,但它们也有一些关键区别。
数组名是一个常量指针,不能修改它的值。
int arr[5] = {1, 2, 3, 4, 5};
arr = arr + 1; // 错误!数组名不能修改
指针是一个变量,可以修改它的值。
int* p = arr;
p = p + 1; // 正确!指针可以修改
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位系统)
数组名是指针,传递数组给函数时,实际传递的是指针。
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); // 传递数组名(指针)
}
函数可以返回指针,但要注意指针指向的内存是否有效。
int* getPointer() {
static int num = 42; // 静态变量,内存不会释放
return #
}
int main() {
int* p = getPointer();
cout << *p; // 输出42
}
下一篇:我会学习标准库类型:string,让我们一起加油!!!