string学习笔记
string 头文件:
plain
#pragma once
#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;
//命名空间
//自定义命名空间,将类封装在 zzj 命名空间内,避免命名冲突。使用时需要 zzj::string
namespace zzj
{
//类定义
class string
{
//公有访问权限
public:
//构造函数,与类同名,无返回值
//= " ":默认参数,如果不传参,默认构造一个包含空格的字符串
string(const char* str = " ");
//析构函数,用于释放_str指向的堆内存
~string();
//获取C字符串(内联函数)
//第一个 const:返回指向常量的指针(不能通过返回值修改内容)
//第二个 const:常成员函数,承诺不修改类的成员变量
const char* c_str() const
{
return _str;
}
//容量和修改操作
void reserve(size_t n);//预留空间,至少能容纳n个字符
void push_back(char ch);//尾部添加单个字符
void append(const char* str);//尾部添加c字符串
string& operator+=(char ch);//重载+=,添加字符
string& operator+=(const char* str);//重载+=,添加字符串
//插入和删除操作
void insert(size_t pos, size_t n, char ch);//在pos位置插入n个字符ch
void insert(size_t pos, const char* str);//在pos位置插入字符串
void erase(size_t pos = 0, size_t len = npos);//从pos位置开始删除Len个字符
//私有成员变量
private:
char* _str;
size_t _size;
size_t _capacity;
//静态成员常量
const static size_t npos;
};
//这个算全局函数嘛?
//不算全局函数,属于zzj命名空间
void test_string1();
void test_string2();
}
1、关键语法:
1.1内联函数
内联函数:定义在类体内部的函数,编译器会隐式视为内联函数
显示VS隐式内联:
plain
class string {
public:
// 【隐式内联】定义在类内部
const char* c_str() const {
return _str;
}
// 【声明】只在类内声明
void push_back(char ch);
};
// 【显式内联】在类外定义时加 inline 关键字
inline void string::push_back(char ch) {
// 实现代码...
}
内联的本质:
| 普通函数 | 内联函数 |
|---|---|
| 有独立的函数地址 | 无独立地址(展开到调用处) |
| 调用时有压栈/跳转开销 | 直接展开代码,消除调用开销 |
| 代码只存一份 | 每个调用点都插入代码 |
在类内声明+定义是隐式内联
1.2静态成员常量
plain
class string {
private:
const static size_t npos; // 声明
};
静态成员变量=全类共享的"固定不变"的值
static = 大家共用一份
const = 这份不能改
npos = -1 = 用最大数表示"无穷/无效"
.cpp文件
plain
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
//定义
namespace zzj
{
//为什么这里写的是string::,不是zzj::?
const size_t string::npos = -1;
//初始化列表
string::string(const char* str)
:_size(strlen(str))//初始化列表先计算长度
{
_capacity = _size;//长度
_str = new char[_size + 1];//申请空间
strcpy(_str, str);//拷贝内容
}
//析构函数
string::~string()
{
delete[]_str;//释放数组
_str = nullptr;//置空,防止成为野指针
_size = 0;
_capacity = 0;
}
//预留空间,至少能容纳n个字符
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n;
}
}
//尾插ch
void string::push_back(char ch)
{
if (_size + 1 > _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
//尾部添加c字符串
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
if (_size + len > 2 * _capacity)
{
newCapacity = _size + len;
}
reserve(newCapacity);
}
//?????如何实现的
strcpy(_str + _size, str);
//strcpy 特性:会把 str 的 \0 也拷贝过去!
_size += len;
}
//迭代器的实现方式,详细讲解!!!
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//在pos位置插入n个字符ch
void string::insert(size_t pos, size_t n, char ch)
{
assert(pos <= _size);//检查pos是否合法
if (_size+n>_capacity)
{
size_t newCapacity = 2 * _capacity;
if (_size + n > 2 * _capacity)
{
newCapacity = _size + n;
}
reserve(newCapacity);
}
//后移元素
size_t end = _size;
while (end >= pos)//>=是因为\0也要往后搬,不然字符串就断了
{
_str[end + n] = _str[end];
--end;
}
for (size_t i = 0; i < n; i++)
{
_str[pos + i] = ch;
}
_size += n;
}
void insert(size_t pos, const char* str);
void erase(size_t pos = 0, size_t len );
}
1.3初始化列表
string::string(const char* str)的含义:创建对象时,初始化列表先执行,再进入{ }内部执行
为什么用初始化列表?
- 成员变量是先初始化,后进入构造函数体
- const或引用成员必须在初始化列表初始化
为什么要用初始化列表?
先初始化再赋值!!!;传统写法直接是赋值,不是初始化
正确写法:
plain
class Person {
string _name;
int _age;
const int _id; // const 成员
int& _scoreRef; // 引用成员
public:
// ✅ 初始化列表:在 { } 之前就初始化好
Person(string n, int a, int id, int& score)
: _name(n) // 直接构造
, _age(a) // 直接初始化
, _id(id) // const 必须在这里初始化
, _scoreRef(score) // 引用必须在这里初始化
{
// 构造函数体可以为空!
}
};
三种初始化的方式:
plain
class Demo {
int a;
int b;
int c;
public:
Demo()
: a(10) // ✅ 初始化列表:直接给值
, b{20} // ✅ C++11 花括号也可以
, c = 30 // ❌ 错误!不能用 =
{
// 函数体里的是"赋值",不是"初始化"
}
};
必须使用初始化列表的方式:
plain
class MustUseInitList {
const int MAX_SIZE; // const 常量
int& ref; // 引用
string& nameRef; // 类类型引用
// 没有默认构造的成员对象
public:
MustUseInitList(int max, int& r, string& s)
: MAX_SIZE(max) // ✅ const 必须初始化
, ref(r) // ✅ 引用必须初始化
, nameRef(s) // ✅ 引用必须初始化
{
// 如果在这里写 MAX_SIZE = max; 会编译错误!
// 因为 const 不能赋值,只能初始化
}
};
1.4析构方式
为什么 delete[] 不是 delete?

1.5operator+=
核心:返回引用支持链式调用
1.5.1迭代器
迭代器是什么:迭代器就是"智能指针",用来遍历容器中的元素
plain
// 传统数组遍历
int arr[5] = {1,2,3,4,5};
for (int i = 0; i < 5; i++) {
cout << arr[i]; // 用下标访问
}
// 迭代器遍历(类似指针)
vector<int> v = {1,2,3,4,5};
for (auto it = v.begin(); it != v.end(); ++it) {
cout << *it; // 用 * 解引用,像指针一样
}
plain
zzj::string s = "hello";
// ========== 方式1:普通迭代器遍历 ==========
zzj::string::iterator it = s.begin(); // it 指向 'h'
while (it != s.end()) { // end() 指向 '\0' 的位置
cout << *it; // 解引用,输出字符
++it; // 移到下一个
}
// 输出: hello
// ========== 方式2:范围for循环(底层用迭代器)==========
for (char c : s) { // 编译器自动转成迭代器遍历
cout << c;
}
// ========== 方式3:修改内容 ==========
for (auto it = s.begin(); it != s.end(); ++it) {
*it = toupper(*it); // 转大写
}
// s 变成 "HELLO"
// ========== 方式4:const对象用const迭代器 ==========
const zzj::string cs = "world";
for (auto it = cs.begin(); it != cs.end(); ++it) {
// *it = 'X'; // ❌ 错误!const_iterator 不能修改
cout << *it; // ✅ 只能读
}
1.6strstr
功能:在字符串中查找子字符串

plain
char* strstr(const char* haystack, const char* needle);
记忆口诀:对象有 const,函数加 const,返回也 const
重载 [] 的目的:让自定义类像数组一样直观访问元素
两个版本的目的:普通对象能读写,const 对象只能读(安全保护)
1.6strcmp
功能:比较两个成字符串
返回值 含义 说明
0 str1 == str2 两字符串相等
< 0(负数) str1 < str2 str1小于str2
0(正数) str1 > str2 str1大于str2
比较规则:按ASCII码逐个字符比较
