C++基础知识十

1.string尾插

在一个string类型的字符后面插入其它的字符可以用push_back和append,这俩个是常用的。

#include<iostream>
using namespace std;
int main()
{
	string s1("hello");
	s1.push_back(' ');
	s1.push_back('w');
	s1.push_back('o');
	s1.push_back('r');
	s1.push_back('l');
	s1.push_back('d');
	cout << s1;
	return 0;
}

使用push_back需要注意的是参数是char型的,所以要用‘’,且里面是单个字符,不能是字符串。

#include<iostream>
using namespace std;
int main()
{
	string s1("hello");
	s1.append(" world1");
	cout << s1;
	return 0;
}

使用append函数时参数就是字符串用”“符号。 

2.在string中插入数据

往string s1中插入数据可以用insert,可以头插尾插或者是原数据的任意位置。

实现头插

#include<iostream>
using namespace std;
int main()
{
	string s1("hello");
	s1.append(" world1");
	s1.insert(0, "ni hao");
	cout << s1;
	return 0;
}

 指定一个位置插入数据,在索引为0和6的位置插入,索引6就是在w的前面。

#include<iostream>
using namespace std;
int main()
{
	string s1("hello");
	s1.append(" world1");
	s1.insert(0, "ni hao");
	s1.insert(6, " hh ");
	cout << s1;
	return 0;
}

 需要注意在索引5插入&&,insert这个函数是不会覆盖原内容的,会把它挤到后面,这里插入俩个&,所以s1的大小是变大的。

#include<iostream>
using namespace std;
int main()
{
	string s1("hello");
	s1.append("world1");
	s1.insert(5, "&&");
	cout << s1;
	return 0;
}

 用迭代器来,begin()是起始的位置,插入一个‘a’。

#include<iostream>
#include<string>
#include<map>
using namespace std;

int main()
{
	string s1("hello");
	//char ch = 'a';
	s1.insert(s1.begin(), 'a');//写成ch也可以
	cout << s1;
	return 0;
}

 3.string的删除操作

int main()
{
	string s1("hello world");
	s1.erase(6, 2);
	cout << s1;
	
	return 0;
}

使用erase函数去删除指定元素,这里是删除索引为6并且从此位置开始先后俩个元素都被删除,就是第一个参数是指定位置,第二个参数是删除多少个数据。

实现头删

#include<iostream>
#include<string>
#include<map>
using namespace std;

int main()
{
	string s1("hello world");
	s1.erase(0,1);
	cout << s1;
	
	return 0;
}

把索引为0位置的元素删除。

也可以使用迭代器实现头删

int main()
{
	string s1("hello world");
	s1.erase(s1.begin());
	cout << s1;
	
	return 0;
}

注意:不可以在写参数1,因为erase函数有多个重载函数,如果要写了begin()还要在后面再写1的话会报错的,因为其中一个的函数重载是要俩个参数类型都是迭代器的。

iterator erase(iterator position);
iterator erase(iterator first, iterator last);
 

 删除最后一个数据

int main()
{
	string s1("hello world");
	s1.erase(s1.size()-1,1);
	cout << s1;
	
	return 0;
}

这里size-1是因为左闭右开,size是最后一个元素的下一个位置。 

 实现pos位置的查找

int main()
{
	string s1("hello world hh hh");
	size_t pos = s1.find(' ');
	cout << pos;
	
	return 0;
}

这里会去找第一次出现空格的位置,后面再出现则不会返回索引。

2.替代指定位置数据replace

替代指定位置元素常用有replace

int main()
{
	string s1("hello w orld hh hh");
	size_t pos = s1.find(' ');
	while (pos != string::npos)
	{
		s1.replace(pos, 1, "&&");
		cout << s1 << endl;
		pos = s1.find(' ', pos + 2);
	}
	return 0;
}

这里先去找空格的索引,然后替换为&&,后面再更新pos,pos+2是因为插入的&&是俩个字符,所以要在插入的后面重新再去找空格,1是pos位置的一个字符,插入俩个字符&&是会扩大字符长度。

另外一种实现方式

int main()
{
	string s1("hello w orld hh hh");
	size_t pos = s1.find(' ');
	string s2;
	for (auto ch : s1)
	{
		if (ch == ' ')
		{
			s2 += "&&";
		}
		else
		{
			s2 += ch;
		}
	}
	s1.swap(s2);
	cout << s1;
	return 0;
}

建立另一个string s2,用范围for去逐个判断,为空格就要重载+=”&&“,这里+=实际就是尾插,不为‘ ’就尾插原元素,这样就把空格处都变为&&,然后再swap去交换俩个内容。 

swap 的作用

  • swap 是一个成员函数,用于交换两个字符串的内容而不需要进行实际的数据拷贝。这是一个高效的操作,因为它通常只涉及交换指针和大小等元数据,而不是逐个字符的复制。
  • 在这段代码中,s1.swap(s2) 使得 s1 最终保存了新的替换后的字符串,s2 则变成了一个空字符串。这种方法在处理字符串时,既简单又高效。

3.读取当前文件内容

#define  _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<map>

using namespace std;

int main()
{
	string s1("hello w orld hh hh");
	size_t pos = s1.find(' ');
	string s2;
	for (auto ch : s1)
	{
		if (ch == ' ')
		{
			s2 += "&&";
		}
		else
		{
			s2 += ch;
		}
	}
	s1.swap(s2);
	cout << s1;
	string file;
	cin >> file;
	FILE* fout = fopen(file.c_str(), "r");
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	fclose(fout);
	return 0;
}

使用C语言的fopen去打开文件,.c_str()是把类型为string的file变为指针,”r“ 书读操作,

fgetc是读取一个字符的函数,循环的判断是当读取为EOF(文件或者数据流的末尾)停下,注意每次调用fgetc时都会自动下一个而不是一直读取一开始的哪一个,打开文件还需要关闭文件。

4.rfind和substr函数

int main()
{
	string s("abc.d.wwwwhhhh");
	size_t pos = s.rfind('.');
	string suffin = s.substr(pos);
	
	cout << suffin;
	return 0;
}

这rfind就是从后面开始寻找‘.’,而substr是子串,参数pos是从pos的位置开始(包括pos) 往后的数据拷贝到suffin。

std::string substr(size_t pos = 0, size_t len = npos) const;
 

 5.find_first_of函数

int main()
{
	string s("abc.d.wwwwhhhh");
	size_t found = s.find_first_of("wd");
	while (found != std::string::npos)
	{
		s[found] = '*';
		found = s.find_first_of("wd", found + 1);
	}
	
	cout << s;
	return 0;
}

find_first_of会把()里面的内容的索引返回,这里while就是替换完才停下,循环结束时w和d都被替换为*,found+1就是更新寻找位置,避免重复找同一个地方。

std::string 类提供了一个非常有用的成员函数 find_first_of,用于查找字符串中首次出现某个字符或字符集合的位置

6.string的getline

当我们想要输入时如果有空格,查看时是没有空格的,因为空格是停止符号,想要保留空格就需要使用getline,getline默认是遇到换行才停止,也可以改成其它的作为停止符号。

在C++中,当你使用标准输入流(std::cin)来读取数据时,默认的行为是以空格、换行符和制表符为分隔符。这意味着,当你尝试读取一个字符串或者其他类型的数据时,输入流会在遇到第一个空格、换行符或制表符时停止读取。

int main()
{
	string a;
	getline(cin, a);
	return 0;
}

 

这样就可以把空格给输入进去了,下面是把终止符改为*,遇到*就会停下。

(没有内容” “,当常量字符串规定会有/0在后面) 

易错处

int main()
{
	string s1("hello");

	string s2 = s1 + "world";
	cout << s2 << endl;

	string s3 = "world" + s1;
	cout << s3 << endl;
	return 0;
}

这s2的操作是合理的,而s3按理会报错,但是没有是因为隐式转换为string,因为不是成员函数是不可以实现字符+类的,因为成员函数都有隐式参数*this,所以如果字符在第一个就类型不符合了。所以全局的函数可以。

7.模拟实现string的一些操作

关于c_str

	string()
		:_str(0)
		,_size(0)
		,_capacity(0)
	{}
namespace zym
{
	void test()
	{
		string s1;
		cout << s1.c_str();
	}
	
}
int main()
{
	zym::test();
	return 0;
}

 在构造函数时给——str初始化为0导致程序崩溃,所以要给_str初始化为\0,给一个字符。

	string()
		:_str(new char[1]{'\0'})
		,_size(0)
		,_capacity(0)
	{}

在 C++ 中,c_str() 是 std::string 类的一个成员函数,用于返回一个 C 风格的字符串(const char*)。这个函数会返回一个指向字符串内部字符数组的指针,该指针以空字符('\0')终止。
为什么 c_str() 在对象为空指针时会崩溃
std::string 对象的 c_str() 方法是设计为在该对象有效且存在时返回一个有效的指针。如果你试图对一个已经被销毁或未初始化的 std::string 对象调用 c_str(),这会导致程序崩溃。以下是一些可能导致崩溃的场景:

1.使用空指针(未初始化的 std::string 对象):
如果你有一个 std::string 对象的指针,但这个指针未被初始化或已被设置为 nullptr,调用 c_str() 会试图解引用空指针,这会导致未定义行为(通常是崩溃)。

   std::string* strPtr = nullptr;
   const char* cStr = strPtr-&gt;c_str();  // 崩溃,因为 strPtr 是一个空指针


2.访问已经被销毁的 std::string 对象:
如果一个 std::string 对象已经被销毁(比如超出作用域),然后你试图访问它的 c_str(),这也会导致未定义行为。

   const char* cStr;
   {
       std::string str = "Hello, world!";
       cStr = str.c_str();  // 在 str 的作用域内有效
   }
   // str 超出作用域并被销毁,此时 cStr 指向一个已销毁的内存


3.对象处于非法状态:
如果 std::string 对象的内部状态因某种原因(如编程错误或内存破坏)变得非法,尝试访问 c_str() 也可能导致崩溃。

如何避免这些问题

4.确保对象有效:
在调用 c_str() 前,确保 std::string 对象有效且处于合法状态。避免使用空指针或悬挂指针。

   std::string str = "Hello";
   const char* cStr = str.c_str();  // 确保 str 是有效的 std::string 对象


5.避免使用空指针:
不要对空指针调用 c_str() 或其他成员函数。如果你有一个指针,先检查它是否为 nullptr。

   std::string* strPtr = new std::string("Hello");
   if (strPtr) {
       const char* cStr = strPtr-&gt;c_str();  // 确保 strPtr 不为空
   }
   delete strPtr;

总结
c_str() 方法是设计为在有效的 std::string 对象上调用的。如果 std::string 对象本身是空指针,或者已被销毁,或者对象的状态不合法,调用 c_str() 会导致崩溃或未定义行为。因此,在使用 c_str() 时,确保对象的有效性和生命周期是避免崩溃的关键。

打印c_str时不是指针而是内容,是因为C++会对其做处理,要想看地址需要强置转换void*。 

在 C++ 中,std::cout 对 C 风格字符串(即 const char* 类型)有特殊的处理。当你使用 std::cout 输出一个 const char* 指针时,它不会直接打印指针的地址,而是会输出指针所指向的字符串内容。下面是这个过程的详细解释:
1. 重载运算符:
std::cout 是 C++ 标准库中的输出流对象,它重载了 &lt;&lt; 运算符,以便可以处理多种数据类型。当你用 cout &lt;&lt; 输出一个 const char* 类型的指针时,实际上调用了以下重载版本的 &lt;&lt; 运算符:
std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, const char* s);

这个重载版本的实现是专门用于处理 C 风格字符串的,它会逐字符输出字符串,直到遇到 null 字符('\0'),这使得输出的是字符串的内容,而不是指针的值。
2. 输出指针 vs 输出内容:

1.打印指针的地址:
如果你直接尝试输出一个指针的地址,例如 std::cout &lt;&lt; (void*)pointer;,那么输出的将是该指针所指向的内存地址。
2.打印指针所指向的内容:
使用 std::cout &lt;&lt; pointer;,则会调用处理 C 风格字符串的重载版本,输出的是字符串内容,而不是地址。

3. 示例代码:
以下是一个示例代码,展示了打印指针地址和打印字符串内容的区别:
#include &lt;iostream&gt;
#include &lt;string&gt;

int main() {
    std::string str = "Hello, World!";
    const char* cStr = str.c_str();

    // 输出字符串内容
    std::cout &lt;&lt; "内容: " &lt;&lt; cStr &lt;&lt; std::endl;  // 输出: Hello, World!

    // 输出指针的地址
    std::cout &lt;&lt; "指针地址: " &lt;&lt; (void*)cStr &lt;&lt; std::endl;  // 输出: 指针的内存地址

    return 0;
}

4. 输出结果:
运行上述代码时,输出将类似于:
内容: Hello, World!
指针地址: 0x7ffee6b7c3d0  // 这个值会因每次运行而不同


3.第一个输出显示了字符串的内容。
4.第二个输出显示了指针的内存地址。

总结:

5.当使用 std::cout &lt;&lt; 输出 const char* 时,C++ 使用了重载运算符来处理该指针,输出其指向的字符串内容。
6.如果希望输出指针的地址,则需要显式地转换指针类型,例如 (void*)pointer。
 

关于初始化

如果使用原括号则里面不应该有参数,且是把所有元素初始化为0,使用花括号则可以自定义初始化元素 

在 C++ 中,圆括号 () 和花括号 {} 都可以用于初始化动态分配的内存,但它们的使用方式略有不同。具体来说,new char[1]() 和 new char[1]{'\0'} 这两种方式都可以用来创建和初始化一个包含一个字符的数组,但它们在初始化时的行为略有不同。
1. 使用圆括号 ()
当你使用圆括号 () 来初始化动态分配的数组时,编译器会将数组的所有元素初始化为默认值:
char* str = new char[1]();  // 使用圆括号进行初始化

在这个例子中:

1.new char[1]() 分配了一个大小为 1 的字符数组,并将其初始化为零。
2.对于 char 类型,零值就是 '\0'(null 字符),所以这与使用 {'\0'} 的效果相同。

2. 使用花括号 {}
当你使用花括号 {} 来初始化动态分配的数组时,你可以明确指定数组元素的初始化值:
char* str = new char[1]{'\0'};  // 使用花括号进行初始化

在这个例子中:

3.new char[1]{'\0'} 分配了一个大小为 1 的字符数组,并将其初始化为 '\0'(null 字符)。

3. 比较

4.圆括号 ():提供了一个简单的初始化方式,将所有元素初始化为零(或默认构造值)。对于内置类型如 char,零初始化就是 '\0'。
5.花括号 {}:允许更显式的初始化,可以初始化部分或全部元素,并且支持列表初始化。如果提供的初始值少于数组大小,未初始化的元素会被零初始化。

4. 示例代码
下面是一个示例,演示了两者的效果:
#include &lt;iostream&gt;
#include &lt;cstring&gt;

int main() {
    char* str1 = new char[1]();      // 使用圆括号初始化
    char* str2 = new char[1]{'\0'};  // 使用花括号初始化

    std::cout &lt;&lt; "str1 内容: '" &lt;&lt; str1 &lt;&lt; "'" &lt;&lt; std::endl;  // 输出: str1 内容: ''
    std::cout &lt;&lt; "str2 内容: '" &lt;&lt; str2 &lt;&lt; "'" &lt;&lt; std::endl;  // 输出: str2 内容: ''

    std::cout &lt;&lt; "str1 字符串长度: " &lt;&lt; strlen(str1) &lt;&lt; std::endl; // 输出: str1 字符串长度: 0
    std::cout &lt;&lt; "str2 字符串长度: " &lt;&lt; strlen(str2) &lt;&lt; std::endl; // 输出: str2 字符串长度: 0

    delete[] str1;  // 释放内存
    delete[] str2;  // 释放内存

    return 0;
}

总结

6.圆括号 () 和 花括号 {} 都可以用来初始化动态分配的数组。
7.new char[1]() 和 new char[1]{'\0'} 实际上会产生相同的效果,对于 char 类型,数组中的唯一元素会被初始化为 '\0'。
8.使用圆括号 () 是一个简洁的方式来进行零初始化,而使用花括号 {} 则可以提供更详细的初始化列表。
 

关于迭代器 

迭代器就像支付宝一样,以前网上用钱需要操作比较麻烦,每个银行的操作都不一样,密盾,扫脸什么的,现在可以直接扫码,因为那些银行的操作在支付宝那里弄好了,迭代器把不同接口的操作都处理了,我们只需要按照统一的方式就可以访问。 

在 C++ 中,迭代器是一个用于遍历容器(如 vector、list、map 等)元素的对象。它提供了一种统一的方式来访问容器中的数据,而不需要直接处理底层数据结构的细节。迭代器可以被视为指向容器中元素的指针,但提供了更丰富的功能。
迭代器的类型
C++ 中的迭代器根据其功能和特性可以分为几种类型:

1.输入迭代器(Input Iterator):


2.只允许读取数据,不能修改。
3.可以进行递增操作。


4.输出迭代器(Output Iterator):


5.只允许写入数据。
6.可以进行递增操作。


7.前向迭代器(Forward Iterator):


8.允许读取和写入数据。
9.可以多次访问相同的元素。
10.只能向前移动。


11.双向迭代器(Bidirectional Iterator):


12.允许读取和写入数据。
13.可以向前和向后移动。


14.随机访问迭代器(Random Access Iterator):


15.支持直接访问容器的任意元素。
16.允许进行加法和减法运算。

迭代器的基本用法
下面是一些使用 C++ STL(标准模板库)迭代器的示例:
1. 使用 vector 的迭代器
#include &lt;iostream&gt;
#include &lt;vector&gt;

int main() {
    std::vector&lt;int&gt; vec = {1, 2, 3, 4, 5};

    // 使用迭代器遍历 vector
    std::vector&lt;int&gt;::iterator it;
    for (it = vec.begin(); it != vec.end(); ++it) {
        std::cout &lt;&lt; *it &lt;&lt; " ";  // 输出: 1 2 3 4 5
    }
    std::cout &lt;&lt; std::endl;

    return 0;
}

在这个示例中,vec.begin() 返回指向 vector 第一个元素的迭代器,而 vec.end() 返回指向 vector 最后一个元素后面的迭代器。
2. 使用 list 的迭代器
#include &lt;iostream&gt;
#include &lt;list&gt;

int main() {
    std::list&lt;std::string&gt; myList = {"apple", "banana", "cherry"};

    // 使用迭代器遍历 list
    for (auto it = myList.begin(); it != myList.end(); ++it) {
        std::cout &lt;&lt; *it &lt;&lt; " ";  // 输出: apple banana cherry
    }
    std::cout &lt;&lt; std::endl;

    return 0;
}

这里使用 auto 关键字简化迭代器的声明。
3. 使用 map 的迭代器
#include &lt;iostream&gt;
#include &lt;map&gt;

int main() {
    std::map&lt;std::string, int&gt; myMap = {{"apple", 1}, {"banana", 2}, {"cherry", 3}};

    // 使用迭代器遍历 map
    for (auto it = myMap.begin(); it != myMap.end(); ++it) {
        std::cout &lt;&lt; it-&gt;first &lt;&lt; ": " &lt;&lt; it-&gt;second &lt;&lt; std::endl;  // 输出: apple: 1, banana: 2, cherry: 3
    }

    return 0;
}

在 map 中,迭代器的 first 成员表示键,second 成员表示值。
迭代器的优点

17.抽象化:迭代器提供了一种统一的接口来访问不同类型的容器。
18.简化代码:可以使用相同的遍历方式处理不同类型的容器。
19.提高可读性:使用迭代器可以使代码更易读、易理解。

其他注意事项

20.迭代器的有效性:当容器被修改(如插入或删除元素)时,某些迭代器可能会失效,尤其是 vector 和 deque。应谨慎操作。
21.迭代器的常量性:可以使用常量迭代器(如 std::vector&lt;int&gt;::const_iterator)来防止修改容器中的元素。

总结
C++ 的迭代器是一种强大且灵活的工具,允许我们以一种一致的方式访问和操作不同类型的容器。通过理解和使用迭代器,可以有效提高代码的可读性和可维护性。如果你还有其他问题或需要更多示例,欢迎继续提问!

  • 避免重复定义:在头文件中仅提供声明,多个源文件可以包含同一个头文件,而不会导致函数或类的重复定义错误。

原生指针模拟迭代器 

string s("hello");
for (auto ch : s)
{
	cout << ch << endl;
}
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}

因为迭代器跟指针相似,所以用原生指针去模拟迭代器,手写一个begin()和end()函数去满足范围for的需求,能用原生指针模拟迭代器是因为string的_str是数组,别的可能模拟不了,底层是数组才可以用原生指针模拟迭代器,迭代器屏蔽了底层细节,因为不论底层是什么begin()就是头,end()就是尾,迭代器是封装的体现,另外如果把begin()变成Begin()就不行,因为这个是傻瓜式的替换,必须跟正版的名字一样。

8.代码讲解

模拟实现迭代器的功能,通过返回地址的方法来实现,_size+_str是最后一个元素的地址,这了const表示的是另一种类型迭代器,表示所有内容都不可修改。

typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}

这里赋值”“是因为常量字符串 规定后面自带一个/0,所以是初始化为0,capacity+1是因为要给/0留位置,strcpy函数可以把str的内容拷贝到_str上面去。

string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

 返回string对象的地址。

const char* c_str() const
		{
			return _str;
		}

清除内容,这里把第一个字符设置为0,实际上是用来标识字符串的结束,当字符串第一个字符为‘\0’时,表示这个字符串是空的,设置一个字符的原因是效率块,不需要去遍历数组去清空, 

void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

 

 尾插首先要判断其空间与大小想不想等,去做出扩容操作,这里用三目操作符来做判断,为0就开四个大小的空间,不为0就开原来的2倍空间大小,注意尾插完后还需要补会一个终止符号‘\0’,不然就会出现问题,乱码的问题。

void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		_str[_size] = ch;
		++_size;
		_str[_size] = '/0';//因为/0被覆盖了所以需要重新加上/0

	}

 

这里是直接给空间开确定大小,当要给'\0 '留一个位置,然后把原内容移动过去,删除原来空间,指向新开辟更大的空间。


	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];//给/0留一个位置
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

 这也是尾插的作用,先得到要加入的字符长度len,还要判断加入后是否超出空间大小,_str+_size是原数据最后的位置,所以把新来到数据接到最后面去。

void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}

这里的end=_size+1是因为要把'\0'算进去,因为插入要把后面的进行挪动,通过控制end去把袁术进行后移,把pos的位置看出来,插入元素后还需要让_size+1。 

void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		++_size;
	}

 

 9.代码展示

string.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once


#include<iostream>
#include<string>
#include<assert.h>
using namespace std;


namespace zym
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}

		/*string()
			:_str(new char[1]{'\0'})
			,_size(0)
			,_capacity(0)
		{}*/

		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
				_size = _capacity = 0;
			}
		}
		const char* c_str() const
		{
			return _str;
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n);
		void push_back(char ch);
		string& operator+=(char ch);
		string& operator+=(const char* str);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);
		void append(const char* str);
		//void test();
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
		//static const size_t npos=1;//这里是定义的行为,static const才可以,其它不写而且还要是 整型
		//给static const整型开绿灯 为了方便下面行为
		//static const int N =10
		//int buff[N]  
		static const size_t npos;
	};
}

string.cpp 

#include"string.h"

namespace zym
{
	const size_t string::npos = -1;
	
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];//给/0留一个位置
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

	void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		_str[_size] = ch;
		++_size;
		_str[_size] = '/0';//因为/0被覆盖了所以需要重新加上/0

	}
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		++_size;
	}
	void string::insert(size_t pos, const char* s)
	{
		assert(pos <= _size);
		size_t len = strlen(s);
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		size_t end = _size + len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}
		for (size_t i = 0; i < len;)
		{
			_str[pos + i] = s[i];
		}
		_size += len;
	}

}

test.c

#include"string.h"
//using namespace std;



namespace zym
{
	void test()
	{
		string s1("hello");
		//cout << s1.c_str();
		s1 += "x";
		cout << s1.c_str();
		//cout << s1;

		//for (auto ch : s1)
		//{
		//	cout << ch << endl;
		//}
	}
	
}
int main()
{
	zym::test();
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值