C++ 数组、字符串详解与 C 的差异
一、数组(Array)
在 C++ 中,数组是一种非常基础的数据结构,用于存储连续的同类型元素。尽管 C 和 C++ 在语法上非常接近,但 C++ 对数组有一些增强和特性。
1. 基本数组操作
int example[5]; // 栈上创建一个长度为5的整型数组
example[0] = 2; // 给第0个元素赋值
std::cout << example[0] << std::endl; // 输出 2
std::cout << example << std::endl; // 输出数组首地址
example
本质上是数组首元素的地址(指针)。example[0]
是整型值。注意:访问越界(0-4 之外)是未定义行为,Debug 下可能提示内存访问违规,Release 下可能不会报错,但会覆盖无关内存。
2. 数组与循环
数组常常与 for
循环结合:
for(int i=0; i<5; i++)example[i] = i;
小端存储示例:数组连续分配,每个
int
占 4 字节,内存顺序如00 00 00 00 01 00 00 00 ...
。example[i]
实际上是*(example + i)
,偏移量 =i * sizeof(int)
。指针加法按数据类型移动,不是字节。例如:
int* ptr = example;
*(ptr + 2) = 6; // 相当于 example[2] = 6
如果要按字节操作,需要类型转换:
*(int*)((char*)ptr + 2*sizeof(int)) = 6;
3. 栈数组与堆数组
栈数组
int example[5]; // 栈创建
生命周期受作用域控制,离开作用域自动销毁。
内存连续,性能高。
堆数组
int* another = new int[5]; // 堆创建delete[] another; // 需要手动释放
生命周期由程序员控制。
访问可能涉及间接寻址,性能略低。
返回函数中的局部栈数组会失效,堆数组可安全返回:
int* badExample() {int x = 10;return &x; // 不可行,栈变量离开作用域失效
}
静态变量可解决局部变量悬空:
static int x = 10;
4. 类成员数组
栈分配
class Entity {
public:int example[5];Entity() {for(int i=0; i<5; i++)example[i] = i;}
};
堆分配
class Entity {
public:int* example = new int[5];Entity() {for(int i=0; i<5; i++)example[i] = i;}
};
堆数组访问需要间接寻址。
栈数组内存连续,堆数组可能跳跃。
5. 数组大小计算
在 C 和 C++ 中,数组的大小必须是编译期常量。
int size = 5;
int arr[size]; // ❌ 错误:C++ 不允许,必须是编译期已知常量
因为编译器在编译时就需要知道栈上分配多少空间。
但是 int size = 5;
是运行时变量,编译器没法在编译阶段确定它的值。
int a[5]; int count_a = sizeof(a) / sizeof(int); // 栈数组可计算
对堆数组:
int* b = new int[5]; sizeof(b); // 仅返回指针大小,无法得元素个数
C++ 中可使用
const
或static const
原因是:const int exampleSize = 5;
本身就有可能成为编译期常量,但在某些旧的 C++ 标准里,编译器对const int
的处理不够严格,有时会把它当成运行时常量。static
修饰后,保证它有内部链接性,也就是在编译阶段就能确定值,所以可以用来做数组大小。
static const int exampleSize = 5;//是为了让 exampleSize 在编译期就能确定,从而合法地作为数组长度使用。
int example[exampleSize];
6. C++11 的 std::array
#include <array>
std::array<int, 5> another;
another[0] = 1;
std::cout << another.size() << std::endl; // 获取元素数量
std::array
是类模板,提供数组大小和方法。更安全,比原始数组更易管理。
内部存储仍连续,开销略高。
7. 与 C 的差异总结
特性 | C | C++ |
---|---|---|
基本语法 | 一样 | 一样 |
栈数组大小计算 | 可以 | 可以,std::array 提供 .size() |
堆数组创建与释放 | malloc/free | new[]/delete[] |
构造函数初始化 | 无 | 可以在类构造函数中初始化 |
类型安全与封装 | 低 | 高,std::array 支持类型检查 |
返回数组 | 需要静态或堆数组 | 一样 |
总结:
C++ 相比 C,数组管理上更灵活:
提供类成员数组构造与初始化。
引入
std::array
提供安全接口。可以在堆上与栈上自由选择生命周期。
二、字符串(String)
1. C 风格字符串
const char* name = "123"; // 字符串字面量
const char name2[4] = "123"; // 自动添加 '\0'
字符串字面量存储在只读内存,不能修改。
字符串本质是
char
数组,以空终止符\0
结尾。未终止的数组打印可能输出随机字符:
const char name2[3] = {'1','2','3'}; // 没有 \0
2. C++ 字符串 std::string
#include <string>
std::string name = "123"; // 构造 std::string 对象
内部封装
char*
,提供.size()
、+=
等方法。字符串拼接:
std::string name = "123";
name += "hello"; // 正确
std::string name2 = std::string("123") + "hello"; // 显式转换
C++ 字符串拼接运算对比
表达式 左操作数类型 右操作数类型 编译器行为 结果 "123" + "hello"
const char*
const char*
指针相加(非法,报错) ❌ 错误 std::string("123") + "hello"
std::string
const char*
匹配 operator+(const std::string&, const char*)
✅ 结果是 "123hello"
"123" + std::string("hello")
const char*
std::string
匹配 operator+(const char*, const std::string&)
✅ 结果是 "123hello"
std::string("123") + std::string("hello")
std::string
std::string
匹配 operator+(const std::string&, const std::string&)
✅ 结果是 "123hello"
std::string name = "123"; name += "hello";
std::string
const char*
匹配 operator+=(const char*)
✅ 结果是 "123hello"
+
运算要求至少有一个操作数是std::string
,否则两个const char*
会被当成指针相加。+=
更灵活,可以直接接受const char*
。
C++14 用户自定义字面量:
using namespace std::string_literals;
std::string name = "hello"s + " world";
""s
是一个 后缀字面量,作用是把字符串字面量转换为 std::string
。
没有 s
的字面量默认是 const char*
,不能直接相加。
4. 多行字符串
原始字符串字面量
const char* example = R"(Line1
Line2
Line3)";
std::string example2 = R"(Line1
Line2
Line3)"s;
R"( ... )"
表示原始字符串字面量,它会 原样保留换行符和空格,不用写\n
。类型是
const char*
(C风格字符串)在结尾加
s
(需要using namespace std::string_literals;
)会把字符串转换成std::string
。类型变成了
std::string
,可以方便地使用.size()
、.find()
等方法。
拼接方式
std::string example = "Line1\n"s"Line2\n"s"Line3\n"s;
相邻字符串字面量会 自动拼接,等效于
"Line1\nLine2\nLine3\n"
。每一行的最后我们显式写
\n
来表示换行。s
后缀把每个部分转换成std::string
,这样+
拼接和字符串方法都能正常使用。
5. 查询与函数传递
bool contains = name.find("lo") != std::string::npos; // 检查是否包含子串
name.find("lo")
会返回"lo"
子串第一次出现的位置(返回类型是size_t
)。如果没找到,就会返回
std::string::npos
(这是一个特殊值,表示“没找到”,其实是-1
转换为无符号整数)。
void PrintString(const std::string& string) {std::cout << string << std::endl;
}
这里参数写成
const std::string&
(常量引用)。为什么不写
std::string
如果写成
std::string
,调用时会复制一份字符串,浪费性能(特别是字符串很长时)。
如果写成const std::string&
,就不会复制,只是创建一个引用,函数内部直接使用原始字符串。const
表示函数不会修改传入的字符串。
void PrintString(std::string s); // 传值,函数内部有一份拷贝
void PrintString(std::string& s); // 引用,可以修改原始字符串
void PrintString(const std::string& s); // 常量引用,只读访问(推荐)
6. 总结
C++ 中字符串是
std::string
和 C 风格char*
的组合。使用
std::string
更安全、更易管理。支持 原始字符串、多行字符串。
C++ 提供用户自定义字面量,简化字符串拼接。