C++-string学习笔记

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码逐个字符比较

相关推荐
编程大师哥2 小时前
JAVA 动态代理
java·开发语言
ACGkaka_2 小时前
ES 学习(六)设置账号密码(安全认证)
学习·安全·elasticsearch
ytttr8732 小时前
C# 读取数据库表结构工具设计与实现
开发语言·数据库·c#
kyle~2 小时前
导航---Small-GICP重定位算法
c++·机器人·ros2·导航
Jinuss2 小时前
源码分析之React中的useImperativeHandle
开发语言·前端·javascript
WBluuue2 小时前
AtCoder Beginner Contest 451(ABCDEFG)
c++·算法
csdn2015_2 小时前
HashSet 和 LinkedHashSet 区别
java·开发语言
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,MongoDB副本集的核心机制(11)
数据库·学习·mongodb