<C/C++基础>设计一个String类(重载运算符等)

本文详细介绍了C++中字符串类的实现,包括构造函数、运算符重载、字符操作等功能,并通过实验对比了不同方法在大量拼接字符串场景下的效率。

参考资源:

谷歌大神,《C++程序设计语言》


一,设计要求

基本要求:C++的基本数据类型中没有字符串变量。C++提高了两种字符串的表示:C风格的字符串和标准C++引入的string类。String类应由字符串数组以及其长度组成。该类至少应包含两个“私有”成员变量,
还应具有如下成员函数:
1)多种功能的构造函数;
2)析构函数;
3)重载运算符operator>>用于输入,同样重载运算符operator<<用于输出操作;
4)字符操作;
5)字符串的特性描述 ;
6)字符串赋值;
7)字符串的链接等
8)字符串的比较

9)返回指定位置的字串

10)字串交换

11)齐全的查找功能

12)替换功能

13)字串的插入

14)字串的删除

15)字串的流处理

16) 异常处理


二,代码实现(要求未完全实现)


1,"String.h"代码如下

include "iostream"
#include "windows.h"
using namespace std;

class MyString
{
public:
	//默认构造函数
	MyString(int size)
	{
		m_pChars=new char[size];
		memset(m_pChars,0,size);
		m_nLength=0;
		maxSize=size;
	}
	MyString(const char *cstr)
	{
		int len=strlen(cstr);
		m_pChars=new char[len+1];
		memset(m_pChars,0,len);
		strcpy_s(m_pChars,len+1,cstr);
	}
	MyString(int size,const char &cstr)
	{
		m_pChars=new char[size+1];
		memset(m_pChars,'\0',size+1);
		memset(m_pChars,cstr,size);
	}
	//析构函数
	~MyString()
	{
		if (m_pChars!=NULL)
		{
			delete[] m_pChars;
			m_pChars=NULL;
		}
		
	}
	friend ostream& operator<< (ostream&,MyString&);//重载<<运算符  
	friend istream& operator>> (istream&,MyString&);//重载>>运算符 
	MyString& operator=(const char *cstr);//只能被重载为成员函数
	char operator[](const int nPos);//只能被重载为成员函数
	char at(const unsigned int nPos);
private:
	int m_nLength; //获取当前 String 对象中的字符数
	char* m_pChars;
	int maxSize;
};

//输入运算符重载
istream& operator>>( istream &input, MyString &str )  
{  
	input>>str.m_pChars;
	int len=strlen(str.m_pChars);
	str.m_nLength=len+1;
	if (len > str.maxSize)
	{
		cerr<<"错误:输入过多的字符(输入运算符重载)。"<<endl;
		cout<<"三秒后退出程序!"<<endl;
		Sleep(3000);
		exit(1);
	}else
	{
		return input; //使用return可以支持连续使用>>运算符  
	}
} 

//输出运算符重载
ostream& operator<<(ostream& os,MyString& str)  
{  
	os << str.m_pChars << endl;  
	return os;  
}

char MyString::operator[](const int nPos)
{
	if ( nPos>strlen(m_pChars))
	{
		cerr<<"错误:输入过多的字符([]运算符重载)。"<<endl;
		cout<<"三秒后退出程序!"<<endl;
		Sleep(3000);
		exit(1);
	}
	return m_pChars[nPos];
}

char MyString::at(const unsigned int nPos)
{
	if ( nPos> strlen(m_pChars))
	{
		cerr<<"错误:输入过多的字符([]运算符重载)。"<<endl;
		cout<<"三秒后退出程序!"<<endl;
		Sleep(3000);
		exit(1);
	}
	return m_pChars[nPos];
}
//赋值运算符重载
MyString& MyString::operator=(const char *cstr)  
{  
	int len=strlen(cstr);
	this->m_nLength=len+1;
	if (len > maxSize)
	{
		cerr<<"错误:输入过多的字符(赋值运算符重载)。"<<endl;
		cout<<"三秒后退出程序!"<<endl;
		Sleep(3000);
		exit(1);
	}else
	{
		strcpy_s(m_pChars,len+1,cstr);
		return *this;
	}
	
}



(2)主测试程序

// ConsoleAppKeCheng.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include "String.h"
//#include <string>  
#include <sstream> 
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
	system("color 0A");
	cout<<"一,MyString类的重载运算符>>以及<<"<<endl;
	MyString str1(10);
	cout<<"1,重载输入:请输入字符串  ";
	cin>>str1;
	cout<<"2,重载输出:您的字符串为  "<<str1<<endl;

	cout<<"二,MyString类的构造函数 "<<endl;
	MyString str2(10);
	str2="Ebow";
	cout<<"1,直接赋值(重载赋值运算符):"<<str2;

	char *cs="Tang";
	MyString str3(cs);
	cout<<"2,复制构造函数:"<<str3;
	char ch='E';
	MyString str4(5,ch);
	cout<<"3,赋值指定数目的字符:"<<str4<<endl;

	cout<<"三,MyString类的字符操作"<<endl;
	MyString str5 = "EbowTang";   
	ch = str5[1];//operator[]返回当前字符串中第n个字符的位置   
	cout<<"指定的字符串:"<<str5;
	cout <<"用[]重载返回指定位置1的元素 "<< ch << endl; 

	MyString str6 = "EbowTang";  
	ch = str6.at(4);//at()返回当前字符串中第n个字符的位置,并且提供范围检查,当越界时会抛出异常!    
	cout<<"用at函数返回指定位置5的元素 " << ch << endl;  
	system("pause");
	return 0;
}



(3)测试结果







附带一篇关于《字符串拼接效率比较文章》

最近写的程序用到大量拼接字符串,为了提高拼接效率,比较了一下+=、append、stringstream、sprintf四种拼接字符串的方法。

测试方法

      比较方法是写了4个函数,分别用+=、append、stringstream、sprintf的方式来拼接字符串,拼接方法是将s1="abcedfg",s2="hijklmn",s3="opqrst"三个字符串拼接到一起,总共循环60次。然后在main函数中依次调用这4个函数,并打时间戳来计时。为了使时间差异更明显,可以取循环N(N可以为100或是1000000等)次调用的时间。代码如下:

#include <iostream>
#include <string>
#include <sys/time.h>
#include <sstream>
#include <stdio.h>
using namespace std;
#define OUT_IN_REPEATE_NUM 10000
#define IN_REPEATE_NUM 60

string s1="abcedfg";
string s2="hijklmn";
string s3="opqrst";
void  plusTest(string& ret)
{
    for(int i=0; i<IN_REPEATE_NUM; i++)
    {
        ret += s1;
        ret += s2;
        ret += s3;
    }
}
void  appendTest(string& ret)
{
    for(int i=0; i<IN_REPEATE_NUM; i++)
    {
        ret.append(s1);
        ret.append(s2);
        ret.append(s3);
    }
}
void sprintfTest(string& ret)
{
    const size_t length=26*IN_REPEATE_NUM;
    char tmp[length];
    char* cp = tmp;
    size_t strLength=s1.length()+s2.length()+s3.length();
    for(int i=0; i<IN_REPEATE_NUM; i++)
    {
        sprintf(cp,"%s%s%s", s1.c_str(), s2.c_str(),s3.c_str());
        cp+=strLength;
    }
    ret = tmp;
}

void  ssTest(string& ret)
{
    stringstream ss;
    for(int i=0; i<IN_REPEATE_NUM; i++)
    {
        ss<<s1;
        ss<<s2;
        ss<<s3;
    }
    ret = ss.str();
}
int main() {
    string ss, plus, append, sprintf;
    struct timeval sTime, eTime;

    gettimeofday(&sTime, NULL);
    for(int i=0; i<OUT_IN_REPEATE_NUM; i++)
    {
        sprintf="";
        sprintfTest(sprintf);
    }
    gettimeofday(&eTime, NULL);
    long SprintfTime = (eTime.tv_sec-sTime.tv_sec)*1000000+(eTime.tv_usec-sTime.tv_usec); //exeTime 单位是微秒

    gettimeofday(&sTime, NULL);
    for(int i=0; i<OUT_IN_REPEATE_NUM; i++)
    {
        append="";
        appendTest(append);
    }
    gettimeofday(&eTime, NULL);
    long AppendTime = (eTime.tv_sec-sTime.tv_sec)*1000000+(eTime.tv_usec-sTime.tv_usec); //exeTime 单位是微秒

    gettimeofday(&sTime, NULL);
    for(int i=0; i<OUT_IN_REPEATE_NUM; i++)
    {
        ss="";
        ssTest(ss);
    }
    gettimeofday(&eTime, NULL);
    long SsTime = (eTime.tv_sec-sTime.tv_sec)*1000000+(eTime.tv_usec-sTime.tv_usec); //exeTime 单位是微秒

    gettimeofday(&sTime, NULL);
    for(int i=0; i<OUT_IN_REPEATE_NUM; i++)
    {
        plus="";
        plusTest(plus);
    }
    gettimeofday(&eTime, NULL);
    long PlusTime = (eTime.tv_sec-sTime.tv_sec)*1000000+(eTime.tv_usec-sTime.tv_usec); //exeTime 单位是微秒

    cout<<"PlusTime is :   "<<PlusTime<<endl;
    cout<<"AppendTime is : "<<AppendTime<<endl;
    cout<<"SsTime is :     "<<SsTime<<endl;
    cout<<"SprintfTime is :"<<SprintfTime<<endl;
    if(ss==sprintf && append==plus && ss==plus)
    {
        cout<<"They are same"<<endl;
    }
    else
    {
        cout<<"Different!"<<endl;
        cout<<"Sprintf: "<<sprintf<<endl;
        cout<<"ss:        "<<ss<<endl;
        cout<<"Plus:     "<<plus<<endl;
        cout<<"Append:"<<append<<endl;
    }

}

测试结果:     

在Linux环境下用g++编译以上代码,运行结果如下(时间单位为μm):

外层循环1000000次

外层循环100000次

外层循环10000次

外层循环1000次

外层循环100次

PlusTime is :     3405450

AppendTime is : 4020078

SsTime is :        7835499

SprintfTime is :  14875433

They are same

PlusTime is :      337229

AppendTime is : 401719

SsTime is :        788242

SprintfTime is :  1517999

They are same

PlusTime is :     32177

AppendTime is : 40265

SsTime is :        78928

SprintfTime is :  150839

They are same

PlusTime is :      3402

AppendTime is : 4074

SsTime is :        7984

SprintfTime is :   15425

They are same

PlusTime is :      369

AppendTime is : 429

SsTime is :         921

SprintfTime is :   1591

They are same


 结论:

根据以上结果,如果是使用Linux系统并且是g++(gcc)编译器,大量拼接字符串的效率从高到低依次为:+=、append()、stringstream、sprintf()。

——菜鸟吉姆斯原创,如有错误,敬请指正!


参考资源:

【1】http://www.cnblogs.com/james6176/p/3222671.html



<think>我们正在讨论C/C++中的一些常用头文件。根据用户提供的引用和问题,用户希望了解这些头文件的作用、使用场景及相关知识。以下是对每个头文件的详细解释: 1. **<sys/types.h>**: - **作用**:定义基本系统数据型,如`pid_t`(进程ID)、`size_t`(表示大小)、`off_t`(文件偏移)等。这些型在系统调用和库函数中经常使用。 - **使用场景**:在需要进程控制(如`fork`)、文件操作等系统调用时使用。例如,在引用[3]中,`pid_t`用于存储`fork()`的返回值。 2. **<sys/stat.h>**: - **作用**:提供文件状态信息(metadata)相关的函数和结构体,如`stat`、`fstat`、`lstat`,以及文件模式宏(例如`S_IRUSR`表示用户读权限)。 - **使用场景**:获取文件属性(如大小、权限、修改时间等)。例如,检查文件是否存在或权限设置。 3. **<fcntl.h>**: - **作用**:提供文件控制选项,如文件打开方式(`O_RDONLY`、`O_WRONLY`、`O_CREAT`等)和文件描述符操作(如`fcntl`函数)。 - **使用场景**:打开、创建文件或设置文件描述符属性。例如,引用[3]中可能使用`open`函数(需要此头文件)。 4. **<unistd.h>**: - **作用**:提供POSIX操作系统API,包括文件操作(`read`, `write`, `close`)、进程控制(`fork`, `exec`, `getpid`)、系统调用(`sleep`, `pipe`)等。 - **使用场景**:在Unix-like系统中进行底层I/O、进程管理。例如,引用[3]中的`fork()`、`getpid()`都需要此头文件。 5. **<string.h>**: - **作用**:C语言字符串操作函数,如`strcpy`, `strlen`, `strcmp`, `memset`, `memcpy`等。 - **使用场景**:处理C风格字符串(以`\0`结尾的字符数组)和内存块操作。例如,引用[3]中的`perror`可能间接使用字符串操作。 6. **<string>**: - **作用**:C++标准库中的字符串`std::string`,提供更安全的字符串操作(自动管理内存、支持运算符重载等)。 - **使用场景**:在C++中代替C风格字符串,进行字符串拼接、查找、替换等操作。与`<string.h>`不同,它是C++特有的。 7. **<bits/stdc++.h>**: - **作用**:GCC特有的头文件,包含几乎整个C++标准库。它不是标准头文件,但可用于竞赛编程简化包含。 - **使用场景**:快速编写代码(如编程竞赛),避免逐个包含头文件。但在生产环境中不推荐使用,因为会增加编译时间和潜在的名字冲突。 **相关知识点总结**: - 这些头文件在Unix/Linux系统编程中尤其重要,因为它们提供了与操作系统交互的接口。 - 在C++中,优先使用C++风格头文件(如`<string>`)而非C风格(如`<string.h>`),但C风格头文件在C++中通常被重命名为`<cstring>`(推荐使用`<cstring>`而非`<string.h>`)。 - 注意系统头文件(如`<sys/types.h>`)与标准库头文件(如`<string>`)的区别:前者与操作系统紧密相关,后者是语言标准库。 引用[2]提到`linux/nls.h`报错缺少`wchar_t`定义,通过包含`<asm/types.h>`解决。这表明在包含系统头文件时,有时需要额外的型定义,需要注意包含顺序和依赖关系。 §§相关问题§§ 1. 在C++中,`<cstring>`和`<string.h>`有什么区别? 2. 为什么在工程中不推荐使用`<bits/stdc++.h>`? 3. 如何正确使用`<sys/stat.h>`中的`stat`函数获取文件大小? 4. 在多进程编程中,`<unistd.h>`提供了哪些关键函数? </think>以下是C/C++中常见头文件的作用、使用场景及相关知识解析,结合系统编程实践进行说明: --- ### **1. `<sys/types.h>`** - **作用**:定义基本系统数据型(如 `pid_t`、`size_t`、`off_t`),用于系统调用和文件操作中跨平台的数据兼容性。 - **使用场景**: - 进程控制(如 `fork()` 返回的进程ID型 `pid_t`) - 文件大小/偏移量声明(如 `off_t` 用于 `lseek()`) - **示例**:引用[3]中 `pid_t pid = fork()` 依赖此头文件定义 `pid_t` 型。 - **注意**:通常与其他系统头文件(如 `<sys/stat.h>`)配合使用。 --- ### **2. `<sys/stat.h>`** - **作用**:提供文件状态操作函数和结构体(如 `stat`、`fstat`),以及文件权限宏(如 `S_IRUSR`)。 - **使用场景**: - 检查文件属性(是否存在、型、权限) - 创建文件时设置权限(如 `open()` 的第三个参数) - **关键函数**: ```c struct stat file_info; stat("file.txt", &file_info); // 获取文件元数据 ``` - **权限宏示例**:`S_IRUSR | S_IWUSR` 表示用户可读可写。 --- ### **3. `<fcntl.h>`** - **作用**:控制文件描述符的行为,提供文件打开模式宏(如 `O_RDONLY`)、文件创建标志(如 `O_CREAT`)。 - **使用场景**: - 打开/创建文件(`open()` 函数) - 设置非阻塞I/O(`O_NONBLOCK`) - **示例**: ```c int fd = open("file.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); ``` --- ### **4. `<unistd.h>`** - **作用**:Unix标准API,提供进程控制、文件操作、系统调用封装。 - **核心功能**: - 进程管理:`fork()`、`getpid()`、`exec()`(引用[3]示例) - 文件I/O:`read()`、`write()`、`close()` - 系统调用:`sleep()`、`pipe()` - **使用场景**:几乎所有Linux系统级程序(如多进程、管道通信)。 --- ### **5. `<string.h>`(C风格)** - **作用**:C语言字符串和内存操作函数。 - **关键函数**: - 字符串处理:`strcpy()`、`strlen()`、`strcmp()` - 内存操作:`memset()`、`memcpy()`(常用于网络数据包处理,如引用[4][5]) - **使用场景**:底层数据操作(如网络编程中的缓冲区处理)。 --- ### **6. `<string>`(C++专属)** - **作用**:C++标准库字符串 `std::string`,提供安全的字符串封装和操作。 - **优势**: - 自动内存管理 - 支持运算符重载(如 `+` 拼接) - **对比 `<string.h>`**: 使用 `std::string s = "hello";` 替代 `char s[] = "hello";`,避免缓冲区溢出风险。 --- ### **7. `<bits/stdc++.h>`(慎用!)** - **作用**:GCC专用头文件,包含**整个C++标准库**(如IO、容器、算法)。 - **使用场景**:竞赛编程中简化代码(单行包含所有库)。 - **缺点**: - 非标准头文件,跨平台兼容性差(如Windows不支持) - 显著增加编译时间 - 生产环境禁用(引用[1]中提到的GNU/Linux头文件差异与此相关) - **替代方案**:明确包含所需头文件(如 `<iostream>`、`<vector>`)。 --- ### **关键知识点总结** 1. **系统编程核心头文件**: `<sys/*.h>` + `<unistd.h>` + `<fcntl.h>` 是Linux系统编程的基础组合(如引用[3][4]中的多进程和Socket示例)。 2. **C vs C++头文件**: - C用 `<string.h>`,C++用 `<string>` + `<cstring>`(C兼容版本)。 - C++优先使用 `std::string` 而非C风格字符串。 3. **头文件冲突解决**: 如引用[2]的 `wchar_t` 错误,需包含 `<asm/types.h>` 确保型定义完整(Linux内核头文件优先级问题)。 4. **网络编程相关**: 引用[4][5]的Socket实现依赖 `<sys/socket.h>`,但缓冲区操作需 `<string.h>` 或 `<cstring>`。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值